diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..75286947 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.deb +*.changes +*.buildinfo +debian/.debhelper/* +debian/debhelper-build-stamp +debian/*.substvars +debian/*.debhelper* +debian/files +debian/dkms.conf +debian/intel-mipi-gmsl-dkms.config +debian/intel-mipi-gmsl-dkms.install +debian/intel-mipi-gmsl-dkms.postinst +debian/intel-mipi-gmsl-dkms.templates +debian/intel-mipi-gmsl-dkms/ \ No newline at end of file diff --git a/Makefile b/Makefile index 0236e98f..3efe0ad2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ KERNELRELEASE ?= $(shell uname -r) KERNEL_SRC ?= /lib/modules/$(KERNELRELEASE)/build -KERNEL_VERSION := $(shell echo $(KERNELRELEASE) | cut -d- -f1 | sed -r 's/([0-9]+\.[0-9]+).*/\1.0/g') +KERNEL_REV := $(shell echo $(KERNELRELEASE) | cut -d- -f1 | sed -r 's/([0-9]+\.[0-9]+).*/\1.0/g') BUILD_EXCLUSIVE_KERNEL="^(6\.(1[278])\.)" MODSRC := $(shell pwd) @@ -27,39 +27,52 @@ export CONFIG_VIDEO_AR0830=m export CONFIG_VIDEO_AR0234=m export CONFIG_VIDEO_ISX031=m export CONFIG_VIDEO_MAX9X=m -export CONFIG_VIDEO_LT6911UXE=m -export CONFIG_VIDEO_LT6911UXC=m -export CONFIG_VIDEO_LT6911GXD=m export CONFIG_VIDEO_INTEL_IPU6=m export CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET=y export CONFIG_INTEL_IPU_ACPI=m +export CONFIG_INTEL_IPU_VC_EXT=y +export CONFIG_VIDEO_D4XX_MAX9295_VC_EXT = n +export CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT = y +export CONFIG_VIDEO_D4XX_MAX9295 = m +export CONFIG_VIDEO_D4XX_MAX9296 = m +export CONFIG_VIDEO_D4XX_COMPAT=m +export CONFIG_VIDEO_D4XX_MAX96724 = m # Define config macros for conditional compilation in ipu-acpi.c # IS_ENABLED() checks for CONFIG_XXX or CONFIG_XXX_MODULE subdir-ccflags-$(CONFIG_VIDEO_MAX9X) += -DCONFIG_VIDEO_MAX9X subdir-ccflags-$(CONFIG_VIDEO_ISX031) += -DCONFIG_VIDEO_ISX031 subdir-ccflags-$(CONFIG_VIDEO_AR0233) += -DCONFIG_VIDEO_AR0233 +subdir-ccflags-$(CONFIG_VIDEO_D4XX_COMPAT) += -DCONFIG_VIDEO_D4XX +subdir-ccflags-$(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) += -DCONFIG_VIDEO_D4XX_MAX9295_VC_EXT +subdir-ccflags-$(CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT) += -DCONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT -DCONFIG_VIDEO_D4XX_MAX96712_LEGACY subdir-ccflags-$(CONFIG_VIDEO_AR0820) += -DCONFIG_VIDEO_AR0820 subdir-ccflags-$(CONFIG_VIDEO_AR0234) += -DCONFIG_VIDEO_AR0234 -DCONFIG_V4L2_CCI_I2C subdir-ccflags-$(CONFIG_IPU_BRIDGE) += -DCONFIG_IPU_BRIDGE subdir-ccflags-$(CONFIG_INTEL_IPU_ACPI) += -DCONFIG_INTEL_IPU_ACPI -subdir-ccflags-$(CONFIG_VIDEO_LT6911UXE) += -DCONFIG_VIDEO_LT6911UXE -subdir-ccflags-$(CONFIG_VIDEO_LT6911UXC) += -DCONFIG_VIDEO_LT6911UXC -subdir-ccflags-$(CONFIG_VIDEO_LT6911GXD) += -DCONFIG_VIDEO_LT6911GXD subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU6) += -DCONFIG_VIDEO_INTEL_IPU6 subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET) += -DCONFIG_VIDEO_INTEL_IPU6_ISYS_RESET # Override LINUXINCLUDE to put our include path first LINUXINCLUDE := -I$(src)/include $(LINUXINCLUDE) ccflags-y := -I$(src)/include -ifeq ($(KERNEL_EQ_6_17),1) + # IPU7 driver configs export CONFIG_VIDEO_INTEL_IPU7=m export CONFIG_VIDEO_INTEL_IPU6=m export CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET=y +export CONFIG_VIDEO_INTEL_IPU7_ISYS_RESET=y +export CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS=y +export CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS=y +export CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS=y -subdir-ccflags-y += -DCONFIG_VIDEO_INTEL_IPU7 -subdir-ccflags-y += -DCONFIG_VIDEO_INTEL_IPU6 +subdir-ccflags-y += -DCONFIG_VIDEO_INTEL_IPU7 -DENABLE_FW_OFFLINE_LOGGER -DCONFIG_DEBUG_FS +subdir-ccflags-y += -DCONFIG_VIDEO_INTEL_IPU6 -DCONFIG_DEBUG_FS +subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET) += -DCONFIG_VIDEO_INTEL_IPU6_ISYS_RESET +subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU7_ISYS_RESET) += -DCONFIG_VIDEO_INTEL_IPU7_ISYS_RESET +subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS) += -DCONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS +subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS) += -DCONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS +subdir-ccflags-$(CONFIG_INTEL_IPU_VC_EXT) += -DCONFIG_INTEL_IPU_VC_EXT # Build IPU7 drivers from submodule obj-m += ipu7-drivers/drivers/media/pci/intel/ipu7/ @@ -68,25 +81,10 @@ obj-m += ipu7-drivers/drivers/media/pci/intel/ipu7/ obj-m += ipu6-drivers/drivers/media/pci/intel/ipu6/ # Build V4L2 core module -obj-m += 6.17.0/drivers/media/v4l2-core/ - -# Build ipu-bridge module -obj-m += 6.17.0/drivers/media/pci/intel/ - -else ifeq ($(KERNEL_EQ_6_12),1) -# IPU6 driver configs -export CONFIG_VIDEO_INTEL_IPU6=m -export CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET=y - -subdir-ccflags-y += -DCONFIG_VIDEO_INTEL_IPU6 - -# Build IPU6 drivers from submodule -obj-m += ipu6-drivers/drivers/media/pci/intel/ipu6/ +obj-m += $(KERNEL_REV)/drivers/media/v4l2-core/ # Build ipu-bridge module -obj-m += 6.12.0/drivers/media/pci/intel/ - -endif +obj-m += $(KERNEL_REV)/drivers/media/pci/intel/ obj-y += drivers/media/platform/intel/ obj-m += drivers/media/i2c/ diff --git a/README.md b/README.md index 6e59d849..e281c00a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This repository contains reference drivers and configurations for Intel MIPI CSI | ISX031 | GMSL | Leopard Imaging | IPU6EP, IPU6EPMTL, IPU75XA | | ISX031 | GMSL | Sensing | IPU6EP, IPU6EPMTL, IPU75XA | | ISX031 | MIPI CSI-2 | D3 Embedded | IPU6EP, IPU6EPMTL, IPU75XA | +| D4XX | GMSL | Realsenseai | IPU6EP, IPU6EPMTL, IPU75XA | > **Note:** \ IPU6EP represents ADL, TWL, ASL and RPL platforms; \ @@ -56,60 +57,431 @@ IPU75XA represents PTL platforms. ## Directory Structure -| Directory | Description | -|-----------|-------------| -| [drivers/](drivers) | Host Linux kernel drivers for supported sensors | -| [config/](config) | Host middleware configuration files | -| [doc/](doc) | Documentation guide for kernelspace and userspace configuration | -| [include/](include) | Header files for driver compilation | - -## Getting Started Guide - -1. Download `Getting Started Guide` (table below), and setup according to your target `Platform`. -2. Inside the guide, follow all instructions under section `Getting Started with Ubuntu with Kernel Overlay`. -3. Under section `Auto Script Installation`, download and use `Ubuntu Kernel Overlay Auto Installar Script` from platform-respective Software Packages. - -> **Note:** All collaterals belows can be downloaded in [rdc.intel.com](https://www.intel.com/content/www/us/en/resources-documentation/developer.html) with proper granted access. - -| Platform | Getting Started Guide | Software Package | -|---|---|---| -| ARL | [828853](https://www.intel.com/content/www/us/en/secure/content-details/828853/ubuntu-with-kernel-overlay-on-intel-core-ultra-200u-and-200h-series-processors-code-named-arrow-lake-u-h-for-edge-platforms-get-started-guide.html?DocID=828853) | [831484](https://www.intel.com/content/www/us/en/secure/design/confidential/software-kits/kit-details.html?kitId=831484) | -| MTL | [779460](https://www.intel.com/content/www/us/en/secure/content-details/779460/ubuntu-with-kernel-overlay-on-intel-core-mobile-processors-code-named-meteor-lake-u-h-for-edge-platforms-get-started-guide.html?DocID=779460) | [790840](https://www.intel.com/content/www/us/en/secure/content-details/790840/meteor-lake-ps-ubuntu-with-kernel-overlay-software-packages.html?DocID=790840) | -| TWL | [793827](https://www.intel.com/content/www/us/en/secure/content-details/793827/ubuntu-with-kernel-overlay-intel-atom-x7000re-x7000c-x7000fe-processor-series-intel-processor-n150-n250-intel-core-3-processor-n355-for-edge-applications-get-started-guide-amston-lake-mr5-amston-lake-fusa-pv-twin-lake-mr2.html?DocID=793827) | [803960](https://www.intel.com/content/www/us/en/secure/design/confidential/software-kits/kit-details.html?kitId=803960) | -| PTL | [858119](https://edc.intel.com/content/www/us/en/secure/design/confidential/products-and-solutions/processors-and-chipsets/panther-lake-h/with-linux-os-get-started-guide-for-edge-compute-applications/) | [871556](https://www.intel.com/content/www/us/en/secure/design/confidential/software-kits/kit-details.html?kitId=860689) | - -Reference: [IntelĀ® IPU6 Enabling Partners Technical Collaterals Advisory](https://www.intel.com/content/www/us/en/secure/content-details/817101/intel-ipu6-enabling-partners-technical-collaterals-advisory.html?DocID=817101) - -## Software Dependencies - -Install these software dependencies in your target system: - -- ipu-camera-bins (E.g. [ipu6-camera-bins](https://github.com/intel/ipu6-camera-bins/tree/iotg_ipu6) / [ipu7-camera-bins](https://github.com/intel/ipu7-camera-bins)) -- ipu-camera-hal (E.g. [ipu6-camera-hal](https://github.com/intel/ipu6-camera-hal/tree/iotg_ipu6) / [ipu7-camera-hal](https://github.com/intel/ipu7-camera-hal)) -- [icamerasrc](https://github.com/intel/icamerasrc/tree/icamerasrc_slim_api) - -| IPU Version | ipu-camera-bins | ipu-camera-hal | icamerasrc | -|-------------|------------------------------------------|------------------------------------------|------------------------------------------| -| IPU6 | 0b102acf2d95f86ec85f0299e0dc779af5fdfb81 | a647a0a0c660c1e43b00ae9e06c0a74428120f3a | 4fb31db76b618aae72184c59314b839dedb42689 | -| IPU7 | 2ef0857570b2dde3c2072fdacf22fdfff1a89bf2 | 3b9388ecdb682b6e7e9f57a4192b4612bfb43410 | 4fb31db76b618aae72184c59314b839dedb42689 | - -## Setup Procedure - -### Kernel Driver DKMS Build - -Initialize and update current repository recursively to ensure all dependencies are correctly fetched. - - git checkout main - git submodule update --init --recursive - -Build and install modules using DKMS - - sudo dkms remove ipu-camera-sensor/0.1 - sudo rm -rf /usr/src/ipu-camera-sensor-0.1/ - - sudo dkms add . - sudo dkms build -m ipu-camera-sensor -v 0.1 - sudo dkms install -m ipu-camera-sensor -v 0.1 --force +- `6.xx.0/`: Kernel Dependent upstream/unpatched v4l2-core API codebase +- `drivers/`: Host Linux kernel drivers for supported sensors +- `helpers/`: v4l2 utils helpers script and udev to easily create gmsl camera media pipeline +- `config/`: Host Middleware Configuration files, BIOS setting and sample commands for supported sensors +- `doc/`: Documentation on Kernel Driver dependency on ipu6-drivers repository +- `patches/`: Host Dependent IPU v4l2 patches to enable specific sensors +- `include/`: Header files for driver compilation + +## Getting Developper Started with Reference GMSL Camera + +1. Clone this repository and checkout debian dev branch + +``` +export $HOME=$(pwd) +cd $HOME +git clone --recursive https://github.com/Pirouf/Intel-mipi-gmsl-modules.git -b 20260421/Q1.2-d4xx-max9672x intel-mipi-gmsl-drivers-debian-20260421 +``` + +2. Create origin debian source archive + +``` +tar czvf intel-mipi-gmsl-drivers_20260421.orig.tar.gz --exclude=".git" --exclude=".gitignore" --exclude=".gitmodules" --exclude="debian" --exclude=".pc" intel-mipi-gmsl-drivers-debian-20260421 +``` + +3. Install debian package dependencies (if done once already, you can skip it) + +``` +sudo apt install devscripts +cd $HOME/intel-mipi-gmsl-drivers-debian-20260421 +mk-build-deps -i --host-arch amd64 --build-arch amd64 -t "apt-get -y -q -o Debug::pkgProblemResolver=yes --no-install-recommends --allow-downgrades" debian/control +rm *.buildinfo *.changes *.deb +dpkg-buildpackage +``` + +4. Stitch the `0001-media-ctl-add-pad-support-2-digits-stream-id.patch` fix/workaround for `media-ctl` 1-digit stream-id limitation onto Debian latest `v4l-utils` v1.32 stable + +``` +cd $HOME +git clone https://salsa.debian.org/debian/libv4l.git -b debian/1.32.0-2 +cd $HOME/libv4l +mk-build-deps -i --host-arch amd64 --build-arch amd64 -t "apt-get -y -q -o Debug::pkgProblemResolver=yes --no-install-recommends --allow-downgrades" debian/control +rm *.buildinfo *.changes *.deb +export QUILT_PATCHES=debian/patches +export QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index" +quilt pop -af || true +quilt import -f -d n ${HOME}/intel-mipi-gmsl-drivers-debian-20260211/patches/libv4l/0001-media-ctl-add-pad-support-2-digits-stream-id.patch +quilt push -a +dpkg-buildpackage +``` + +5. Install local dkms and v4l-utils debian packages + +``` +cd $HOME +sudo apt install ./intel-mipi-gmsl-dkms_20260421-1_amd64.deb ./v4l-utils_1.32.0-2_amd64.deb ./libv4l*_1.32.0-2_amd64.deb +``` +6. Select max929x or max967xx deserializer to compile certain linux v4l2 i2c sensors driver with. + +7. Reboot in UEFI/BIOS menu, to enable Intel IPU in and configure Camera ACPI PDATA i2c and CSI2 controls under + +``` +systemctl reboot --firmware-setup + +``` + +Select `Intel Advanced Menu -> System Agent (SA) Configuration -> MIPI Camera Configuration -> < Enable >`. +Configure each Camera ACPI device individualy `Intel Advanced Menu -> System Agent (SA) Configuration -> MIPI Camera Configuration -> Link Options`. +Select `Intel Advanced Menu -> System Agent (SA) Configuration -> MIPI Camera Configuration -> Link Options -> User Custom`. + +For example Realsense D457 on IPU7 (Intel Core Ultra 3 - Panther Lake) - one ACPI entry each D457 Cameras + +| UEFI Custom Sensor (D457) | Camera 1 | Camera 2 | Camera 3 | Camera 4 | +|------------------------------------------|------------|-----------|------------|------------| +| GMSL deserializer suffix | c | c | c | c | +| Custom HID | INTC10CD | INTC10CD | INTC10CD | INTC10CD | +| CSI bus-type | CPHY | CPHY | CPHY | CPHY | +| Rotation (d4xx deserializer input-link) | 0 | 90 | 180 | 270 | +| PPR Value (deserializer # of lanes) | 2 | 2 | 2 | 2 | +| PPR Unit (# of Cameras per-device) | 1 | 1 | 1 | 1 | +| Camera module label | d4xx | d4xx | d4xx | d4xx | +| MIPI Port (Index) | 2 | 2 | 2 | 2 | +| LaneUsed (serializer # of lanes) | x2 | x2 | x2 | x2 | +| Number of I2C | 3 | 3 | 3 | 3 | +| I2C Channel | I2C1 | I2C1 | I2C1 | I2C1 | +| Device0 I2C Address (sensor) | 12 | 13 | 14 | 15 | +| Device1 I2C Address (serializer) | 42 | 43 | 44 | 45 | +| Device2 I2C Address (deserializer) | 27 | 27 | 27 | 27 | + +Note: Using `INTC10CD` HID, the `Rotation` selection defines `d4xx-max96724` deserializer input-link mapping: GMSL A (`Rotation=0`) or GMSL B (`Rotation=90`) or GMSL C (`Rotation=180`) or D (`Rotation=270`) + +For example d3embedded ISX031 GMSL modules on IPU7 (Intel Core Ultra 3 - Panther Lake) - only one ACPI entry for all 4 ISX031 Cameras + +| UEFI Custom Sensor (ISX031) | Camera 1 | +|------------------------------------------|------------| +| GMSL deserializer suffix | c | +| Custom HID | INTC031M | +| CSI bus-type | CPHY | +| Rotation (max9x deserializer out-link) | 180 | +| PPR Value (deserializer # of lanes) | 2 | +| PPR Unit (# of Cameras per-device) | 4 | +| Camera module label | max96724 | +| MIPI Port (Index) | 2 | +| LaneUsed (serializer # of lanes) | x4 | +| Number of I2C | 3 | +| I2C Channel | I2C1 | +| Device0 I2C Address (deserilizer) | 27 | +| Device1 I2C Address (serializer) | 42 | +| Device2 I2C Address (sensor) | 12 | + +Note: Using `INTC031M` HID, the `Rotation` selection defines `max9x` deserializer output-link mapping: CSI PHY A (`Rotation=0`) or CSI PHY C (`Rotation=180`) + +8. User to need to double check and edit `modprobe.d` module config file if necessary to configure it for `max9296` or `max967xx` add-on board + +For example of 4x Realsense D457 on IPU7 (Intel Core Ultra 3 - Panther Lake) + +``` +# modinfo d4xx +filename: /lib/modules/6.17.0-1010-oem/updates/dkms/d4xx.ko.zst +version: 20260421-0eci1 +license: GPL v2 +author: Guennadi Liakhovetski , + Nael Masalha , + Alexander Gantman , + Emil Jahshan , + Xin Zhang , + Qingwu Zhang , + Evgeni Raikhel , + Shikun Ding , + Florent Pirou , + Dmitry Perchanov +description: RealSense D4XX Camera Driver +srcversion: B6A9500EDADC591780A52F9 +alias: i2c:d4xx-awg +alias: i2c:d4xx-asr +alias: i2c:d4xx +alias: of:N*T*Cintel,d4xxC* +alias: of:N*T*Cintel,d4xx +depends: d4xx-max9295,d4xx-max96724,v4l2-async,videodev,mc +name: d4xx +retpoline: Y +vermagic: 6.17.0-1010-oem SMP preempt mod_unload modversions +sig_id: PKCS#7 +signer: eci-rvp-ptl-gmsl-4 Secure Boot Module Signature key +sig_key: 1F:18:1B:36:4E:5E:1C:D9:C7:D9:7D:AB:F2:A6:78:C8:A5:07:8F:A5 +sig_hashalgo: sha512 +signature: 24:9D:08:A3:72:A4:97:CF:F1:B5:47:B3:A3:07:88:7F:0B:1B:66:FD: + 7F:A8:AD:02:A9:A8:BB:EC:B5:AE:AB:6E:A2:F2:5C:8A:E6:E2:68:97: + BA:A2:CA:53:00:54:B6:44:4D:FF:CE:80:ED:4D:BE:16:2D:DF:C0:65: + 1B:DD:D0:24:E6:B6:1E:06:06:C3:A1:87:92:2F:E4:8E:AE:C7:88:12: + C0:1B:6A:B6:A4:6B:5D:A9:A5:B3:57:61:38:1A:E1:05:FB:4A:E3:A3: + 8F:26:C3:EC:2C:3C:2F:04:EA:96:CE:3A:8D:D0:F5:8F:64:0F:5E:49: + A3:03:5F:B6:40:E0:37:9C:E6:12:92:34:AE:53:6B:38:74:6E:8B:D9: + EA:B4:AA:EE:50:21:41:84:4E:51:9E:0A:B7:5E:40:D3:82:A2:8E:92: + 2E:86:35:31:AE:64:AC:8C:4A:27:FF:F5:81:FB:D8:EC:7C:E2:87:11: + D7:E9:23:FB:A2:BE:E2:75:CC:AC:58:2F:67:38:BE:61:C6:64:FF:5E: + 1A:EF:E3:35:F9:3D:96:CF:CC:71:08:F1:5A:BD:50:FD:A4:2F:C0:30: + 99:E2:85:11:BD:5F:D9:07:A9:BA:AE:6B:20:78:16:AE:F3:DE:97:5A: + 7A:4B:E4:EB:28:BF:94:BE:AE:E4:26:6A:18:54:84:AE +parm: sensor_vc:VC set for sensors + sensor_vc=0,1,2,3,2,3,0,1,1,0,3,2,3,2,1,0 (array of ushort) +parm: serdes_bus:d4xx-max96724 deserializer i2c bus + serdes_bus=0,0,1,1 (array of ushort) +parm: des_addr:d4xx-max96724 deserializer i2c address + des_addr=0x27,0x27,0x27,0x27 (array of ushort) +parm: des_csi:d4xx-max96724 deserializer csi output + des_csi=0,2,0,2 (array of ushort) + +``` + +``` +# uncomment d4xx driver boot options corresponding to your Intel platform +# +# MTL GMSL2 AIC max9296 device generally on i2c-2 and i2c-3 buses +#options d4xx des_addr=0x48,0x4a,0x48,0x4a serdes_bus=2,2,3,3 +# ARL GMSL2 AIC max9296 device generally on i2c-0 and i2c-3 buses +#options d4xx des_addr=0x48,0x4a,0x48,0x4a serdes_bus=0,0,3,3 +# ADL/RPL/ASL GMSL2 AIC max9296 device generally on i2c-4 bus +#options d4xx des_addr=0x48,0x4a,0x68,0x6c serdes_bus=4,4,4,4 +# ARL GMSL2 AIC dual-max96712 device generally on i2c-0 bus +#options d4xx des_addr=0x6b,0x6b,0x29,0x29 serdes_bus=0,0,0,0 + +# PTL GMSL2 AIC dual-max96724 device generally on i2c-0 and i2c-1 buses +options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 +# PTL GMSL2 AIC dual-max96724 device generally on i2c-0 and i2c-1 buses (with VC_EXTENSION enabled) +#options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 sensor_vc=0,1,2,3,2,3,0,1,5,4,7,6,7,6,5,4 +# !!WARNING!! Intel Servoss Mountain 1.0 (Fab A) dual-max96724 board cphy CRD connectivity is inverted +# max96724 csi 0 CPHY-2T DA0/DA1 maps to ipu7 isys csi 2 +# max96724 csi 2 CPHY-2T DB0/DB1 maps to ipu7 isys csi 0 +#options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=1,1,2,2 des_csi=2,2,0,0 +# + +#options d4xx dyndbg +#options d4xx-max9295 dyndbg +#options d4xx-max9296 dyndbg +#options d4xx-max96724 dyndbg + +``` + +``` +modprobe --show-depends d4xx +insmod /lib/modules/6.17.0-1010-oem/kernel/drivers/media/mc/mc.ko.zst +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/videodev.ko.zst +insmod /lib/modules/6.17.0-1010-oem/kernel/drivers/media/v4l2-core/v4l2-async.ko.zst +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/d4xx-max96724.ko.zst dyndbg +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/d4xx-max9295.ko.zst +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/d4xx.ko.zst des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 + +``` + +For example of 4x D3 ISX031 + +``` +modinfo isx031 +filename: /lib/modules/6.17.0-1010-oem/updates/dkms/isx031.ko.zst +license: GPL v2 +author: Wei Khang, Goh +author: Jonathan Lui +author: Hao Yao +description: isx031 sensor driver +srcversion: 12A145D4C2F70B04B1E8D25 +alias: i2c:isx031 +alias: acpi*:INTC113C:* +depends: videodev,mc,v4l2-fwnode,v4l2-async +name: isx031 +retpoline: Y +vermagic: 6.17.0-1010-oem SMP preempt mod_unload modversions +sig_id: PKCS#7 +signer: eci-rvp-ptl-gmsl-4 Secure Boot Module Signature key +sig_key: 1F:18:1B:36:4E:5E:1C:D9:C7:D9:7D:AB:F2:A6:78:C8:A5:07:8F:A5 +sig_hashalgo: sha512 +signature: 57:81:3B:A6:5C:99:C4:26:46:7D:32:A0:5C:49:4D:8A:31:1B:EA:E1: + 51:A5:B6:CF:2A:8D:F7:AD:8E:F3:01:F2:D3:1E:8E:3F:32:69:FF:59: + 40:27:13:98:56:2F:72:AD:8E:F5:52:FC:91:CB:68:81:A2:9C:DE:23: + 08:9F:51:DB:4D:16:B6:FD:2F:E4:DC:EF:B3:76:59:3B:DF:4D:64:03: + E1:20:9D:06:83:7F:94:3E:84:AA:47:57:F6:CA:58:24:89:72:42:9A: + 4B:9A:24:FF:BD:B5:17:78:D4:F3:B8:7F:18:0E:F8:33:62:D7:71:04: + 52:75:B8:B9:4D:2F:68:53:31:08:12:3E:D9:97:76:16:FA:1A:62:9C: + 00:ED:12:2E:B3:B7:4D:92:6A:DD:1D:74:20:4A:89:58:29:0A:C8:8A: + 01:89:32:15:55:3C:38:94:71:F7:40:98:80:5D:15:28:E0:2B:8E:0B: + E7:DD:E7:38:E6:A8:47:61:8D:25:66:A3:2A:FB:4A:45:46:3A:5E:EE: + 9B:9E:73:16:69:8A:31:90:D2:71:D0:BA:A5:94:79:C5:E1:59:12:4C: + C0:8E:F6:76:2C:A5:B8:91:5F:B4:DA:21:53:F5:AF:87:CA:8F:67:A4: + 68:79:FC:22:8C:B9:96:F2:19:94:8D:42:30:25:BF:2C +``` + +``` +modprobe --show-depends isx031 +insmod /lib/modules/6.17.0-1010-oem/kernel/drivers/media/mc/mc.ko.zst +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/videodev.ko.zst +insmod /lib/modules/6.17.0-1010-oem/kernel/drivers/media/v4l2-core/v4l2-async.ko.zst +insmod /lib/modules/6.17.0-1010-oem/kernel/drivers/media/v4l2-core/v4l2-fwnode.ko.zst +insmod /lib/modules/6.17.0-1010-oem/updates/dkms/isx031.ko.zst +``` + +9. load IPU ISYS module and bind ALL sensors as v4l2 media pipeline graph. + +``` +modprobe intel-ipu7-isys +``` + +For example of 4x D457 on IPU7 (Intel Core Ultra 3 - Panther Lake) - + +``` +/usr/share/camera/rs_ipu_d457_bind.sh -n -q && /usr/share/camera/rs-enum-ipu.sh -n +(count=1 on IPU7 CSI 2)... +(count=2 on IPU7 CSI 2)... +(count=3 on IPU7 CSI 2)... +(count=4 on IPU7 CSI 2)... +Bus Camera Sensor Node Type Video Node RS Link + ipu7 a-2 ir Inactive /dev/video37 /dev/video-rs-ir-8 + ipu7 a-2 depth Stream(id=0) /dev/video33 /dev/video-rs-depth-8 + ipu7 a-2 imu Inactive /dev/video38 /dev/video-rs-imu-8 + ipu7 a-2 color Stream(id=2) /dev/video35 /dev/video-rs-color-8 + i2c a-2 d4xx Firmware /dev/d4xx-dfu-a-2 /dev/d4xx-dfu-8 + ipu7 b-2 ir Inactive /dev/video43 /dev/video-rs-ir-9 + ipu7 b-2 depth Stream(id=6) /dev/video39 /dev/video-rs-depth-9 + ipu7 b-2 imu Inactive /dev/video44 /dev/video-rs-imu-9 + ipu7 b-2 color Stream(id=8) /dev/video41 /dev/video-rs-color-9 + i2c b-2 d4xx Firmware /dev/d4xx-dfu-b-2 /dev/d4xx-dfu-9 + ipu7 c-2 ir Inactive /dev/video45 /dev/video-rs-ir-10 + ipu7 c-2 depth Stream(id=1) /dev/video34 /dev/video-rs-depth-10 + ipu7 c-2 imu Inactive /dev/video46 /dev/video-rs-imu-10 + ipu7 c-2 color Stream(id=3) /dev/video36 /dev/video-rs-color-10 + i2c c-2 d4xx Firmware /dev/d4xx-dfu-c-2 /dev/d4xx-dfu-10 + ipu7 d-2 ir Inactive /dev/video47 /dev/video-rs-ir-11 + ipu7 d-2 depth Stream(id=7) /dev/video40 /dev/video-rs-depth-11 + ipu7 d-2 imu Inactive /dev/video48 /dev/video-rs-imu-11 + ipu7 d-2 color Stream(id=9) /dev/video42 /dev/video-rs-color-11 + i2c d-2 d4xx Firmware /dev/d4xx-dfu-d-2 /dev/d4xx-dfu-11 +``` +note: v4l2 limites to 8 streams/routes max per CSI ports has priortized depth+rgb + +For example of selecting only 1-out-of-4 D457 on IPU7 (Intel Core Ultra 3 - Panther Lake) + +``` +# /usr/share/camera/rs_ipu_d457_bind.sh -n -m a-2 -q && /usr/share/camera/rs-enum-ipu.sh -n +(count=1 on IPU7 CSI 2)... +Bus Camera Sensor Node Type Video Node RS Link + ipu7 a-2 ir Stream(id=4) /dev/video37 /dev/video-rs-ir-8 + ipu7 a-2 depth Stream(id=0) /dev/video33 /dev/video-rs-depth-8 + ipu7 a-2 imu Stream(id=5) /dev/video38 /dev/video-rs-imu-8 + ipu7 a-2 color Stream(id=2) /dev/video35 /dev/video-rs-color-8 + i2c a-2 d4xx Firmware /dev/d4xx-dfu-a-2 /dev/d4xx-dfu-8 +``` + +For example of 4x D3embedded ISX031 on IPU7 (Intel Core Ultra 3 - Panther Lake) + +``` +./helpers/ipu_max9x_bind.sh -q -s isx031 +Bind IPU7 to isx031 a-2 through max9x c .. +Bind IPU7 to isx031 b-2 through max9x c .. +Bind IPU7 to isx031 c-2 through max9x c .. +Bind IPU7 to isx031 d-2 through max9x c .. +``` + +10. Enjoy your GMSL cameras streaming on Intel IPU MIPI CSI2 ports! + +For example 4x D457 on IPU7 (Intel Core Ultra 3 - Panther Lake) - + +``` +rs-enumerate-devices -S +Device Name Serial Number Firmware Version +Intel RealSense D457 241122303302 5.17.0.10 +Intel RealSense D457 242422303692 5.17.0.10 +Intel RealSense D457 242422304793 5.17.0.10 +Intel RealSense D457 242422303456 5.17.0.10 +Device info: + Name : Intel RealSense D457 + Serial Number : 241122303302 + Firmware Version : 5.17.0.10 + Recommended Firmware Version : 5.17.0.9 + Physical Port : /dev/video-rs-depth-10 + Debug Op Code : 15 + Advanced Mode : YES + Product Id : ABCD + Camera Locked : YES + Product Line : D400 + Asic Serial Number : 321543110732 + Firmware Update Id : 321543110732 + Dfu Device Path : /dev/d4xx-dfu-10 + Connection Type : GMSL + +Device info: + Name : Intel RealSense D457 + Serial Number : 242422303692 + Firmware Version : 5.17.0.10 + Recommended Firmware Version : 5.17.0.9 + Physical Port : /dev/video-rs-depth-11 + Debug Op Code : 15 + Advanced Mode : YES + Product Id : ABCD + Camera Locked : YES + Product Line : D400 + Asic Serial Number : 250343111497 + Firmware Update Id : 250343111497 + Dfu Device Path : /dev/d4xx-dfu-11 + Connection Type : GMSL + +Device info: + Name : Intel RealSense D457 + Serial Number : 242422304793 + Firmware Version : 5.17.0.10 + Recommended Firmware Version : 5.17.0.9 + Physical Port : /dev/video-rs-depth-8 + Debug Op Code : 15 + Advanced Mode : YES + Product Id : ABCD + Camera Locked : YES + Product Line : D400 + Asic Serial Number : 252143110007 + Firmware Update Id : 252143110007 + Dfu Device Path : /dev/d4xx-dfu-8 + Connection Type : GMSL + +Device info: + Name : Intel RealSense D457 + Serial Number : 242422303456 + Firmware Version : 5.17.0.10 + Recommended Firmware Version : 5.17.0.9 + Physical Port : /dev/video-rs-depth-9 + Debug Op Code : 15 + Advanced Mode : YES + Product Id : ABCD + Camera Locked : YES + Product Line : D400 + Asic Serial Number : 245543111541 + Firmware Update Id : 245543111541 + Dfu Device Path : /dev/d4xx-dfu-9 + Connection Type : GMSL +``` + +or + +``` +gst-launch-1.0 -e -v \ +v4l2src device=/dev/video-rs-color-8 ! 'video/x-raw, width=640, height=480, format=YUY2, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_0 \ +v4l2src device=/dev/video-rs-color-9 ! 'video/x-raw, width=640, height=480, format=YUY2, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_1 \ +v4l2src device=/dev/video-rs-color-10 ! 'video/x-raw, width=640, height=480, format=YUY2, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_2 \ +v4l2src device=/dev/video-rs-color-11 ! 'video/x-raw, width=640, height=480, format=YUY2, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_3 \ +compositor name=comp \ +sink_0::xpos=0 sink_0::ypos=0 sink_0::width=640 sink_0::height=480 sink_0::zorder=1 \ +sink_1::xpos=640 sink_1::ypos=0 sink_1::width=640 sink_1::height=480 sink_1::zorder=2 \ +sink_2::xpos=0 sink_2::ypos=480 sink_0::width=640 sink_2::height=480 sink_2::zorder=3 \ +sink_3::xpos=640 sink_3::ypos=480 sink_3::width=640 sink_3::height=480 sink_3::zorder=2 ! videoconvert ! fakesink +``` + +For example 4x D3embedded ISX031 GMSL modules on IPU7 (Intel Core Ultra 3 - Panther Lake) + +``` +gst-launch-1.0 -e -v \ +v4l2src device=/dev/video-isx031-a-2 ! 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_0 \ +v4l2src device=/dev/video-isx031-b-2 ! 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_1 \ +v4l2src device=/dev/video-isx031-c-2 ! 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_2 \ +v4l2src device=/dev/video-isx031-d-2 ! 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_3 \ +compositor name=comp \ +sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ +sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ +sink_2::xpos=0 sink_2::ypos=1536 sink_2::width=1920 sink_2::height=1536 sink_2::zorder=3 \ +sink_3::xpos=1920 sink_3::ypos=1536 sink_3::width=1920 sink_3::height=1536 sink_3::zorder=4 ! videoconvert ! fakesink + +``` + +## Documentation + +Detailed documentation for each sensor can be found in the [doc/](doc) directory, organized by sensor model. ### BIOS Configuration diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..83a77ee4 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,59 @@ +intel-mipi-gmsl-drivers (20260421-1) UNRELEASED; urgency=low + + * initial package based of Intel MIPI-CSI Camera Reference drivers [Release 2026 Q1.2](https://github.com/intel/Intel-MIPI-CSI-Camera-Reference-Driver/commit/9ae0a6ace8aeb7d35b7c77fc79c188e620f964e8) + + -- ECI Maintainer Wed, 29 Apr 2026 20:16:24 +0000 + +intel-mipi-gmsl-drivers (20260211-4) UNRELEASED; urgency=low + + * ipu7-isys: workaround isys-queue noise in HW_ERR_BAD_FRAME_DIM conditions + * helpers: add isx031 mtbf testcases + * helpers: bind 8-only srcpad ipu7-isys and ipu6-isys subdevs media graph + + -- ECI Maintainer Wed, 25 Mar 2026 12:32:36 +0000 + +intel-mipi-gmsl-drivers (20260211-3) UNRELEASED; urgency=low + + * ipu-acpi : fallback for uefi camera parameters without the `rotation` entry + * ipu6-isys: workaround ipu6-isys STREAM_CAPTURE fw cmd race-conditions + + -- ECI Maintainer Wed, 25 Mar 2026 12:32:36 +0000 + +intel-mipi-gmsl-drivers (20260211-2) UNRELEASED; urgency=low + + * d4xx : fix max96724 realsense2 multicam and multistream support + * ipu7-isys: workaround csi2 unsupported YUYV8_1X16 css pixelformat + + -- ECI Maintainer Fri, 20 Mar 2026 13:57:30 +0000 + +intel-mipi-gmsl-drivers (20260211-1) UNRELEASED; urgency=low + + * d4xx : add d4xx realsense2 custom max9295, max9296 and max96724 implementations + * d4xx : add d4xx realsense2 v4l2 graph binding helper scripts + * ipu6-isys, ipu6-isys: add d4xx video VIDIOC_S_EXT_CTRLS by streamid + + -- ECI Maintainer Mon, 15 Feb 2026 13:20:02 +0000 + +intel-mipi-gmsl-drivers (20260211-0) UNRELEASED; urgency=low + + * initial package based of Intel MIPI-CSI Camera Reference drivers [Release 2026 Q1.1.1a](https://github.com/intel/Intel-MIPI-CSI-Camera-Reference-Driver/commit/96ae361c20e778efe53426848b89528205d18ad3) + + -- ECI Maintainer Wed, 11 Feb 2026 09:24:00 +0000 + +intel-mipi-gmsl-drivers (20260115-2) UNRELEASED; urgency=low + + * debian : add postinstall and debconf + * d4xx : add d4xx realsense2 custom max9295, max9296 and max96724 implementations + * d4xx : add d4xx realsense2 v4l2 graph binding helper scripts + * ipu6-isys, ipu6-isys: add d4xx video VIDIOC_S_EXT_CTRLS by streamid + + -- ECI Maintainer Thu, 19 Jan 2026 14:46:17 +0000 + +intel-mipi-gmsl-drivers (20260115-1) UNRELEASED; urgency=low + + * debian : initial package rules and control + * ipu7-drivers : add baseline IPU7 20251226_1140_191_PTL_PV_IoT + * ipu6-drivers : add baseline IPU6 & PTL release for iot on 2025-12-30 + * v4l2-core : add linux 6.12, 6.17 and 6.18 stream and route v4l2 api enabled videodev + + -- ECI Maintainer Thu, 19 Jan 2026 14:46:17 +0000 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..5c16c1c5 --- /dev/null +++ b/debian/control @@ -0,0 +1,29 @@ +Source: intel-mipi-gmsl-drivers +Section: kernel +Priority: optional +Maintainer: ECI Maintainer +Rules-Requires-Root: no +Build-Depends: + debhelper-compat (= 13), + dh-modaliases, + dkms, + dh-sequence-dkms, + quilt, +Standards-Version: 4.7.0 +Homepage: https://github.com/intel/Intel-MIPI-CSI-Camera-Reference-Driver + +Package: intel-mipi-gmsl-dkms +Architecture: amd64 +Depends: + dkms, + pahole, + v4l-utils, + ${misc:Depends}, +Provides: ipu7-modules +Conflicts: ipu7-modules, intel-ipu6-dkms +Replaces: ipu7-modules, intel-ipu6-dkms +XB-Modaliases: ${modaliases} +Description: Intel Integrated Image Processing Unit 6/7 (IPU6/IPU7) drivers + This package provides kernel drivers for GMSL2 cameras through the Intel IPU7 + on Intel Panther Lake and the Intel IPU6 + on Intel Alder Lake, Raptor Lake, Meteor Lake and Arrow lake platforms. diff --git a/debian/dkms.conf.in b/debian/dkms.conf.in new file mode 100644 index 00000000..51a9b5bc --- /dev/null +++ b/debian/dkms.conf.in @@ -0,0 +1,250 @@ +PACKAGE_NAME="intel-mipi-gmsl-dkms" +PACKAGE_VERSION="@DKMS_PACKAGE_VERSION@" +BUILD_EXCLUSIVE_KERNEL="^(7\.([0])\.)|^(6\.(1[278])\.)|^6\.(1[27])rt-intel|^6\.(1[27])-intel" +VERSION_SUFFIX="${PACKAGE_VERSION}" + +KBASE=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build +#Extract kernel version substring that is used to identify source directory for kernel modules +# i.e 6.12.8-intel-ese-experimental-lts-rt ==> 6.12.0 +# i.e 6.17.0-oem-1011 ==> 6.17.0 +# i.e 6.17rt-intel ==> 6.17.0 +# i.e 6.18.6-intel-ese-experimental-lts-rt ==> 6.18.0 +SELECTED_KERNEL=$(echo $kernelver | cut -d- -f1 | sed -r 's/([0-9]+\.[0-9]+).*/\1.0/g') +KERNEL_VER=$(echo $kernelver | cut -d. -f1) +KERNEL_PATCH=$(echo $kernelver | cut -d. -f2 | cut -d- -f1) + +RUNTIME_KERNEL_VER=$(uname -r) + +#This variable is required to address includes from videodev.ko +srctree=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build + +#We will invoke multiple make directives to build all the required kernel modules, then manually copy them to the staged directory for dkms install rule +MAKE="if [ '${RUNTIME_KERNEL_VER}' == '${kernelver}' ] && [ -f $kernel_source_dir/tools/bpf/resolve_btfids/resolve_btfids ] && [ -f /sys/kernel/btf/vmlinux ]; then ln -s /sys/kernel/btf/vmlinux $kernel_source_dir/vmlinux; fi ;make V=1 KERNELRELEASE=$kernelver DRIVER_VERSION_SUFFIX='${VERSION_SUFFIX}' CFLAGS_MODULE=\"$(echo $MAKE_OPTS)\" KERNEL_SRC=$kernel_source_dir" + +MAKE_MATCH[1]="^(7\.([0])\.)|^(6.1[278])" + +CLEAN="if [ -f $kernel_source_dir/vmlinux ]; then rm $kernel_source_dir/vmlinux; fi;make V=1 KERNELRELEASE=$kernelver KERNEL_SRC=$kernel_source_dir clean" + +AUTOINSTALL="yes" +BUILD_EXCLUSIVE_CONFIG="CONFIG_VIDEO_V4L2_I2C" + +MODULE_PATH="drivers/media/i2c" +MODULE_DEST="/kernel/drivers/media/i2c/" + +j=0 + +# V4l2 core patches OoT patches +# kernel patches for Linux 6.12 +PATCH[$j]="0001-dkms-v4l2-core-makefile-adaptation-6.12.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0002-dkms-disable-tuner-build-in-v4l2-core-6.12.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0003-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(6.1[2])" +# kernel patches for Linux 6.17 +PATCH[$((++j))]="0004-dkms-v4l2-core-makefile-adaptation-6.17.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0005-dkms-disable-tuner-build-in-v4l2-core-6.17.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0006-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(6.1[7])" +# kernel patches for Linux 6.18 +PATCH[$((++j))]="0007-dkms-v4l2-core-makefile-adaptation-6.18.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0008-dkms-disable-tuner-build-in-v4l2-core-6.18.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0009-Revert-media-v4l2-subdev-Make-struct-v4l2_subdev_str.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0010-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(6.1[8])" +# kernel patches for Linux 7.0 +PATCH[$((++j))]="0011-dkms-v4l2-core-makefile-adaptation-7.0.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0012-dkms-disable-tuner-build-in-v4l2-core-7.0.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0013-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(7.[0])" + +# IPU6 drivers OoT patches +PATCH[$((++j))]="0050-isys-video-let-v4l2-set-default-colorspace.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0051-ipu6-dkms-add-isys-makefile-adaptation-6.12.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0052-ipu6-isys-disable-src_pad-validation-for-dkms.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0053-Revert-ipu6-isys-lt6911uxc-2-pads-linked-to-ipu-2-po.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0054-module-Add-version-conditional-for-symbol-namespace-.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" + +# IPU7 drivers OoT patches +PATCH[$((++j))]="0100-isys-video-let-v4l2-set-default-colorspace.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0101-ipu7-isys-make-modules-suffix-versioning-same-as-dkm.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" + +i=0 + +BUILT_MODULE_NAME[$i]="ipu-acpi" +BUILT_MODULE_LOCATION[$i]="drivers/media/platform/intel" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ipu-acpi-pdata" +BUILT_MODULE_LOCATION[$i]="drivers/media/platform/intel" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ipu-acpi-common" +BUILT_MODULE_LOCATION[$i]="drivers/media/platform/intel" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ar0233" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ar0234" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ar0830" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="ar0820" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="isx031" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="max9x" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/max9x" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +# IPU6 drivers d4xx OoT patches +PATCH[$((++j))]="0055-ipu6-isys-allow-virtual-channels-on-16-csi2-input-vi.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0056-ipu6-isys-add-d4xx-pixel-format-support.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0057-ipu6-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0058-ipu6-isys-video-force-START-then-CAPTURE-distinct-st.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" + +# IPU7 drivers d4xx OoT patches +PATCH[$((++j))]="0102-ipu7-isys-allow-virtual-channels-on-16-csi2-input-vi.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0103-ipu7-isys-add-d4xx-pixel-format-support.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0104-ipu7-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0105-isys-queue-ignore-HW_ERR_BAD_FRAME_DIM-led-expected-.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0106-ipu7-isys-enable-cphy-dphy-5-4-bits-vc-extension.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" + +BUILT_MODULE_NAME[$((++i))]="d4xx" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/d4xx-compat" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="d4xx-max9295" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/d4xx-compat" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="d4xx-max9296" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/d4xx-compat" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="d4xx-max96724" +BUILT_MODULE_LOCATION[$i]="drivers/media/i2c/d4xx-compat" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +# IPU6 drivers from submodule +BUILT_MODULE_NAME[$((++i))]="intel-ipu6" +BUILT_MODULE_LOCATION[$i]="ipu6-drivers/drivers/media/pci/intel/ipu6" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="intel-ipu6-isys" +BUILT_MODULE_LOCATION[$i]="ipu6-drivers/drivers/media/pci/intel/ipu6" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="intel-ipu6-psys" +BUILT_MODULE_LOCATION[$i]="ipu6-drivers/drivers/media/pci/intel/ipu6/psys" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +# kernel patches for Linux 6.12 +PATCH[$((++j))]="0200-ipu-bridge-port-6.12.0-to-bkc.patch" +PATCH_MATCH[$j]="^(6.1[2]-intel)" +PATCH[$((++j))]="0201-ipu-bridge-6.12-makefile.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0202-ipu-bridge-6.12-bkc-backport.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0203-ipu-bridge-6.12-add-sensors.patch" +PATCH_MATCH[$j]="^(6.1[2])" +# kernel patches for Linux 6.17 +PATCH[$((++j))]="0204-ipu-bridge-6.17-makefile.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0205-ipu-bridge-6.17-bkc-backport.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0206-ipu-bridge-6.17-add-sensors.patch" +PATCH_MATCH[$j]="^(6.1[7])" +# kernel patches for Linux 6.18 +PATCH[$((++j))]="0207-ipu-bridge-6.18-makefile.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0208-ipu-bridge-6.18-bkc-backport.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0209-ipu-bridge-6.18-add-sensors.patch" +PATCH_MATCH[$j]="^(6.1[8])" +# kernel patches for Linux 7.0 +PATCH[$((++j))]="0210-ipu-bridge-7.0-makefile.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0211-ipu-bridge-7.0-bkc-backport.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0212-ipu-bridge-7.0-add-sensors.patch" +PATCH_MATCH[$j]="^(7.[0])" + +BUILT_MODULE_NAME[$((++i))]="ipu-bridge" +BUILT_MODULE_LOCATION[$i]="${SELECTED_KERNEL}/drivers/media/pci/intel" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="videodev" +BUILT_MODULE_LOCATION[$i]="${SELECTED_KERNEL}/drivers/media/v4l2-core/" +DEST_MODULE_NAME[$i]="videodev" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +if [ "$KERNEL_VER" -ge 6 ] && [ "$KERNEL_PATCH" -ge 17 ]; then +# IPU7 drivers from submodule +BUILT_MODULE_NAME[$((++i))]="intel-ipu7" +BUILT_MODULE_LOCATION[$i]="ipu7-drivers/drivers/media/pci/intel/ipu7" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="intel-ipu7-isys" +BUILT_MODULE_LOCATION[$i]="ipu7-drivers/drivers/media/pci/intel/ipu7" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +BUILT_MODULE_NAME[$((++i))]="intel-ipu7-psys" +BUILT_MODULE_LOCATION[$i]="ipu7-drivers/drivers/media/pci/intel/ipu7/psys" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no + +fi diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 00000000..f1e4983c --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,5 @@ +[DEFAULT] +debian-branch = debian/latest +upstream-branch = upstream/latest +pristine-tar = False +git-ignore-new = True diff --git a/debian/intel-mipi-gmsl-dkms.config.in b/debian/intel-mipi-gmsl-dkms.config.in new file mode 100644 index 00000000..0f0df01f --- /dev/null +++ b/debian/intel-mipi-gmsl-dkms.config.in @@ -0,0 +1,28 @@ +#!/bin/bash +# inspired by http://www.fifi.org/doc/debconf-doc/tutorial.html + +# Exit on error +set -e +# Source debconf library. +. /usr/share/debconf/confmodule +db_version 2.0 + +# This conf script is capable of backing up +#db_capb backup + +declare -A FEATURES=@deserlist@ +STATE="max9x" + +while [ "$STATE" != "quit" -a "$STATE" != "done" ]; do + case "$STATE" in + max9x) + # query max9x + db_input critical intel-mipi-gmsl-dkms/max9x || true + if db_go; then + STATE="quit" + else + STATE="done" + fi + ;; + esac +done diff --git a/debian/intel-mipi-gmsl-dkms.install.in b/debian/intel-mipi-gmsl-dkms.install.in new file mode 100644 index 00000000..9951e145 --- /dev/null +++ b/debian/intel-mipi-gmsl-dkms.install.in @@ -0,0 +1,18 @@ +debian/dkms.conf /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +Makefile /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +drivers /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +include /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +ipu6-drivers /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +ipu7-drivers /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +6.12.0 /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +6.17.0 /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +6.18.0 /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +patches /usr/src/@DKMS_PACKAGE_NAME@-@DKMS_PACKAGE_VERSION@ +helpers/blacklist-intel-ipu.conf /etc/modprobe.d +helpers/d4xx.conf /etc/modprobe.d +helpers/ipu_ops.conf /etc/modprobe.d +helpers/rs-enum-ipu.sh /usr/share/camera +helpers/ipu_max9x_bind.sh /usr/share/camera +helpers/rs_ipu_d457_bind.sh /usr/share/camera +helpers/99-ipu-gmsl-mipi.rules /lib/udev/rules.d +helpers/mtbf-test-isx031-gmsl_gst-v4l2src.sh /usr/share/camera diff --git a/debian/intel-mipi-gmsl-dkms.modaliases b/debian/intel-mipi-gmsl-dkms.modaliases new file mode 100644 index 00000000..d8536b3b --- /dev/null +++ b/debian/intel-mipi-gmsl-dkms.modaliases @@ -0,0 +1,9 @@ +alias pci:v00008086d0000645Dsv*sd*bc*sc*i* intel-ipu7 +alias pci:v00008086d0000B05Dsv*sd*bc*sc*i* intel-ipu7 +alias pci:v00008086d0000D719sv*sd*bc*sc*i* intel-ipu7 +alias pci:v00008086d0000462Esv*sd*bc*sc*i* intel-ipu6-psys +alias pci:v00008086d0000465Dsv*sd*bc*sc*i* intel-ipu6-psys +alias pci:v00008086d00004E19sv*sd*bc*sc*i* intel-ipu6-psys +alias pci:v00008086d00007D19sv*sd*bc*sc*i* intel-ipu6-psys +alias pci:v00008086d00009A19sv*sd*bc*sc*i* intel-ipu6-psys +alias pci:v00008086d0000A75Dsv*sd*bc*sc*i* intel-ipu6-psys diff --git a/debian/intel-mipi-gmsl-dkms.postinst.in b/debian/intel-mipi-gmsl-dkms.postinst.in new file mode 100644 index 00000000..49573227 --- /dev/null +++ b/debian/intel-mipi-gmsl-dkms.postinst.in @@ -0,0 +1,50 @@ +# Automatically added by dh_dkms/3.0.10-7 +# The original file can be found in template-dkms-mkdeb/debian/postinst +# in the DKMS tarball, check it for copyright notices. + +DKMS_NAME=@DKMS_PACKAGE_NAME@ +DKMS_PACKAGE_NAME=@DKMS_PACKAGE_NAME@ +DKMS_VERSION=@DKMS_PACKAGE_VERSION@ + +postinst_found=0 + +set -e + +. /usr/share/debconf/confmodule + +db_get ${DKMS_PACKAGE_NAME}/max9x +MAX9X=${RET} + +case "$1" in + configure) + echo "postinst d4xx build for ${MAX9X}" >&2 + if [ "$MAX9X" = "max9296" ]; then + echo "clear MAKE_OPTS" >&2 + export MAKE_OPTS="" + fi + if [ "$MAX9X" = "max96724" ]; then + echo "apply MAKE_OPTS=\"-DCONFIG_VIDEO_D4XX_MAX96724\"" >&2 + export MAKE_OPTS="-DCONFIG_VIDEO_D4XX_MAX96724" + fi + if [ "$MAX9X" = "max96712" ]; then + echo "apply MAKE_OPTS=\"-DCONFIG_VIDEO_D4XX_MAX96712\"" >&2 + export MAKE_OPTS="-DCONFIG_VIDEO_D4XX_MAX96712" + fi + + for DKMS_POSTINST in /usr/lib/dkms/common.postinst /usr/share/$DKMS_PACKAGE_NAME/postinst; do + if [ -f $DKMS_POSTINST ]; then + $DKMS_POSTINST $DKMS_NAME $DKMS_VERSION /usr/share/$DKMS_PACKAGE_NAME "" $2 + postinst_found=1 + break + fi + done + if [ "$postinst_found" -eq 0 ]; then + echo "ERROR: DKMS version is too old and $DKMS_PACKAGE_NAME was not" + echo "built with legacy DKMS support." + echo "You must either rebuild $DKMS_PACKAGE_NAME with legacy postinst" + echo "support or upgrade DKMS to a more current version." + exit 1 + fi + ;; +esac +# End automatically added section diff --git a/debian/intel-mipi-gmsl-dkms.templates.in b/debian/intel-mipi-gmsl-dkms.templates.in new file mode 100644 index 00000000..4171df32 --- /dev/null +++ b/debian/intel-mipi-gmsl-dkms.templates.in @@ -0,0 +1,7 @@ +Template: intel-mipi-gmsl-dkms/max9x +Type: select +Choices: @marchlist@ +Default: @marchdefault@ +Description: Select d4xx GMSL2 Deserializer type [defaut:@marchdefault@] + build d4xx drivers with compile options either for @marchlist@. + \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..1310d45e --- /dev/null +++ b/debian/rules @@ -0,0 +1,69 @@ +#!/usr/bin/make -f + +export DH_VERBOSE = 1 +include /usr/share/dpkg/pkg-info.mk + +# Deserializer arch +deserlist := "max9296 max96712 max96724" +defaultdeser := max9296 + +# special characters +empty := +space := $(empty) $(empty) +comma := , +semicol := ; + +# helper to create comma separated list from space separated list +commasep = $(subst $(space),$(comma)$(space),$1) +semicolsep = $(subst $(space),$(comma),$1) + +# remove double quotes +unquote = $(subst $\",,$1) + +# recursive wildcard +rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) + +DKMS_PACKAGE_NAME = $(shell grep '^PACKAGE_NAME=' debian/dkms.conf.in | cut -d\" -f2) +DKMS_PACKAGE_VERSION = $(DEB_VERSION_UPSTREAM) +DKMS_PACKAGE_REVISION = $(DEB_VERSION_UPSTREAM_REVISION) + +generated_dkms_files = \ + debian/dkms.conf \ + debian/intel-mipi-gmsl-dkms.install \ + debian/intel-mipi-gmsl-dkms.config \ + debian/intel-mipi-gmsl-dkms.templates \ + debian/intel-mipi-gmsl-dkms.postinst \ + $(empty) + +%: + dh $@ --with modaliases + +override_dh_auto_configure: + ./dkms-kernel-source.sh "drivers/media/v4l2-core" "include/media" "drivers/media/pci/intel/Kconfig" "drivers/media/pci/intel/Makefile" "drivers/media/pci/intel/ipu-bridge.c" + +override_dh_auto_build: +override_dh_auto_test: +override_dh_auto_install: +override_dh_auto_clean: + +override_dh_dkms: debian/dkms.conf debian/intel-mipi-gmsl-dkms.postinst + dh_dkms -V $(DEB_VERSION_UPSTREAM_REVISION) -- debian/dkms.conf + cp -a debian/intel-mipi-gmsl-dkms.postinst debian/intel-mipi-gmsl-dkms.postinst.debhelper + +override_dh_installdebconf-arch: debian/intel-mipi-gmsl-dkms.config debian/intel-mipi-gmsl-dkms.templates + dh_installdebconf -a + +override_dh_install: debian/intel-mipi-gmsl-dkms.install debian/dkms.conf + dh_install --exclude=.gitignore --exclude=.gitmodules + +override_dh_clean: + -rm -f $(generated_dkms_files) + dh_clean + +$(generated_dkms_files) : $(generated_dkms_files:=.in) debian/rules + sed -e 's,@DKMS_PACKAGE_NAME@,$(DKMS_PACKAGE_NAME),g' \ + -e 's,@DKMS_PACKAGE_VERSION@,$(DEB_VERSION_UPSTREAM_REVISION),g' \ + -e 's/@deserlist@/$(call unquote,$(deserlist))/g' \ + -e 's/@marchdefault@/$(defaultdeser)/g' \ + -e 's/@marchlist@/$(call commasep,$(call unquote,$(deserlist)))/g' \ + <$(@:=.in) >$@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 00000000..80441c80 --- /dev/null +++ b/debian/source/options @@ -0,0 +1,26 @@ +tar-ignore=.git +tar-ignore=.gitignore +tar-ignore=.gitmodules +tar-ignore=.ci +tar-ignore=ci +tar-ignore=dkms.conf +tar-ignore=patch/* +tar-ignore=ipu6-drivers/.git/* +tar-ignore=ipu6-drivers/patch/* +tar-ignore=ipu6-drivers/kernel_patches/* +tar-ignore=ipu6-drivers/README.md +tar-ignore=ipu6-drivers/dkms.conf +tar-ignore=ipu6-drivers/Makefile +tar-ignore=ipu7-drivers/.git/* +tar-ignore=ipu7-drivers/CODE_OF_CONDUCT.md +tar-ignore=ipu7-drivers/CONTRIBUTING.md +tar-ignore=ipu7-drivers/patch/* +tar-ignore=ipu7-drivers/README.md +tar-ignore=ipu7-drivers/SECURITY.md +tar-ignore=ipu7-drivers/dkms.conf +tar-ignore=ipu7-drivers/Makefile +tar-ignore=debian/*.tmpl +tar-ignore=.pc +tar-ignore=*.deb +tar-ignore=*.changes +tar-ignore=*.buildinfo diff --git a/dkms-kernel-source.sh b/dkms-kernel-source.sh index 9fdcec44..da0a08a0 100755 --- a/dkms-kernel-source.sh +++ b/dkms-kernel-source.sh @@ -1,27 +1,31 @@ #!/bin/bash -if [[ -z "${kernelver:-}" ]]; then - kernelver="$(uname -r)" -fi +KVERS=("6.12.0" "6.17.0" "6.18.0" "7.0.0") -major=$(echo "$kernelver" | cut -d- -f1| cut -d. -f1) -minor=$(echo "$kernelver" | cut -d- -f1| cut -d. -f2) -patch=$(echo "$kernelver" | cut -d- -f1| cut -d. -f3) +for KVER in "${KVERS[@]}"; do -if ! [[ "$major" =~ ^[0-9]+$ ]]; then major=0; fi -if ! [[ "$minor" =~ ^[0-9]+$ ]]; then minor=0; fi -if ! [[ "$patch" =~ ^[0-9]+$ ]]; then patch=0; fi + [[ -d $KVER ]] && continue -echo "Downloading major $major minor $minor patch $patch" -if (( patch != 0 )); then - kernelprefix="linux-$major.$minor.$patch" -else - kernelprefix="linux-$major.$minor" -fi -wget --no-check-certificate https://mirrors.edge.kernel.org/pub/linux/kernel/v$major.x/$kernelprefix.tar.xz -O $kernelprefix.tar.xz + major=$(echo "$KVER" | cut -d- -f1| cut -d. -f1) + minor=$(echo "$KVER" | cut -d- -f1| cut -d. -f2) + patch=$(echo "$KVER" | cut -d- -f1| cut -d. -f3) -for arg in "$@"; do - echo "Extracting: $kernelprefix/$arg" - tar -xvf "$kernelprefix.tar.xz" "$kernelprefix/$arg" \ - --xform="s,^${kernelprefix//./\\.}/,$major.$minor.0/," + if ! [[ "$major" =~ ^[0-9]+$ ]]; then major=0; fi + if ! [[ "$minor" =~ ^[0-9]+$ ]]; then minor=0; fi + if ! [[ "$patch" =~ ^[0-9]+$ ]]; then patch=0; fi + + echo "Downloading major $major minor $minor patch $patch" + if (( patch != 0 )); then + kernelprefix="linux-$major.$minor.$patch" + else + kernelprefix="linux-$major.$minor" + fi + wget --no-check-certificate https://mirrors.edge.kernel.org/pub/linux/kernel/v$major.x/$kernelprefix.tar.xz -O $kernelprefix.tar.xz + echo $@ + for arg in "$@"; do + echo "Extracting: $kernelprefix/$arg" + tar -xvf "$kernelprefix.tar.xz" "$kernelprefix/$arg" \ + --xform="s,^${kernelprefix//./\\.}/,$major.$minor.0/," + done + rm -rf "$kernelprefix.tar.xz" done diff --git a/dkms.conf b/dkms.conf index 2df3cd9b..465e400e 100644 --- a/dkms.conf +++ b/dkms.conf @@ -7,7 +7,14 @@ CLEAN="make KERNELRELEASE=$kernelver KERNEL_SRC=$kernel_source_dir clean" AUTOINSTALL="yes" -BUILD_EXCLUSIVE_KERNEL="^(6\.(1[278])\.)|^6\.(1[27])rt-intel|^6\.(1[27])-intel" +BUILD_EXCLUSIVE_KERNEL="^(7\.([0)\.)|^(6\.(1[278])\.)|^6\.(1[27])rt-intel|^6\.(1[27])-intel" + +#Extract kernel version substring that is used to identify source directory for kernel modules +# i.e 6.12.8-intel-ese-experimental-lts-rt ==> 6.12.0 +# i.e 6.17.0-oem-1011 ==> 6.17.0 +# i.e 6.17rt-intel ==> 6.17.0 +# i.e 6.18.6-intel-ese-experimental-lts-rt ==> 6.18.0 +SELECTED_KERNEL=$(echo $kernelver | cut -d- -f1 | sed -r 's/([0-9]+\.[0-9]+).*/\1.0/g') KERNEL_VER=$(echo $kernelver | cut -d. -f1) KERNEL_PATCH=$(echo $kernelver | cut -d. -f2 | cut -d- -f1) @@ -18,22 +25,54 @@ POST_ADD="dkms-kernel-source.sh" j=0 -# Patches for 6.17 kernel - -# V4l2 core patches +# V4l2 core patches OoT patches POST_ADD+=" drivers/media/v4l2-core" -PATCH[$j]="0004-dkms-v4l2-core-makefile-adaptation-6.17.patch" -PATCH_MATCH[$j]="^(6.1[7])" -PATCH[$((++j))]="0005-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +# kernel patches for Linux 6.12 +PATCH[$j]="0001-dkms-v4l2-core-makefile-adaptation-6.12.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0002-dkms-disable-tuner-build-in-v4l2-core-6.12.patch" +PATCH_MATCH[$j]="^(6.1[2])" +PATCH[$((++j))]="0003-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(6.1[2])" +# kernel patches for Linux 6.17 +PATCH[$((++j))]="0004-dkms-v4l2-core-makefile-adaptation-6.17.patch" PATCH_MATCH[$j]="^(6.1[7])" -PATCH[$((++j))]="0009-dkms-disable-tuner-build-in-v4l2-core-6.17.patch" +PATCH[$((++j))]="0005-dkms-disable-tuner-build-in-v4l2-core-6.17.patch" PATCH_MATCH[$j]="^(6.1[7])" - -# IPU6 drivers -PATCH[$((++j))]="0052-module-version-conditional-symbol-namespace.patch" -PATCH_MATCH[$j]="^(6.1[27])" -PATCH[$((++j))]="0053-ipu6-isys-disable-src_pad-validation-for-dkms.patch" +PATCH[$((++j))]="0006-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" PATCH_MATCH[$j]="^(6.1[7])" +# kernel patches for Linux 6.18 +PATCH[$((++j))]="0007-dkms-v4l2-core-makefile-adaptation-6.18.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0008-dkms-disable-tuner-build-in-v4l2-core-6.18.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0009-Revert-media-v4l2-subdev-Make-struct-v4l2_subdev_str.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0010-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(6.1[8])" +# kernel patches for Linux 7.0 +PATCH[$((++j))]="0011-dkms-v4l2-core-makefile-adaptation-7.0.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0012-dkms-disable-tuner-build-in-v4l2-core-7.0.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0013-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch" +PATCH_MATCH[$j]="^(7.[0])" + +# IPU6 drivers OoT patches +PATCH[$((++j))]="0050-isys-video-let-v4l2-set-default-colorspace.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0051-ipu6-dkms-add-isys-makefile-adaptation-6.12.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0052-ipu6-isys-disable-src_pad-validation-for-dkms.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0053-Revert-ipu6-isys-lt6911uxc-2-pads-linked-to-ipu-2-po.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" +PATCH[$((++j))]="0054-module-Add-version-conditional-for-symbol-namespace-.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" + +# IPU7 drivers OoT patches +PATCH[$((++j))]="0100-isys-video-let-v4l2-set-default-colorspace.patch" +PATCH_MATCH[$j]="^(6.1[278])|^(7.[0])" i=0 @@ -113,27 +152,50 @@ BUILT_MODULE_LOCATION[$i]="ipu6-drivers/drivers/media/pci/intel/ipu6/psys" DEST_MODULE_LOCATION[$i]="/updates" STRIP[$i]=no -if [ "$KERNEL_VER" -eq 6 ] && [ "$KERNEL_PATCH" -eq 12 ]; then - POST_ADD+=" drivers/media/pci/intel/Kconfig drivers/media/pci/intel/Makefile drivers/media/pci/intel/ipu-bridge.c" -PATCH[$((++j))]="0039-ipu-bridge-port-6.12.0-to-bkc.patch" +# kernel patches for Linux 6.12 +PATCH[$((++j))]="0200-ipu-bridge-port-6.12.0-to-bkc.patch" PATCH_MATCH[$j]="^(6.1[2]-intel)" - -PATCH[$((++j))]="0040-ipu-bridge-6.12-makefile.patch" +PATCH[$((++j))]="0201-ipu-bridge-6.12-makefile.patch" PATCH_MATCH[$j]="^(6.1[2])" -PATCH[$((++j))]="0041-ipu-bridge-6.12-bkc-backport.patch" +PATCH[$((++j))]="0202-ipu-bridge-6.12-bkc-backport.patch" PATCH_MATCH[$j]="^(6.1[2])" -PATCH[$((++j))]="0042-ipu-bridge-6.12-add-sensor.patch" +PATCH[$((++j))]="0203-ipu-bridge-6.12-add-sensors.patch" PATCH_MATCH[$j]="^(6.1[2])" +# kernel patches for Linux 6.17 +PATCH[$((++j))]="0204-ipu-bridge-6.17-makefile.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0205-ipu-bridge-6.17-bkc-backport.patch" +PATCH_MATCH[$j]="^(6.1[7])" +PATCH[$((++j))]="0206-ipu-bridge-6.17-add-sensors.patch" +PATCH_MATCH[$j]="^(6.1[7])" +# kernel patches for Linux 6.18 +PATCH[$((++j))]="0207-ipu-bridge-6.18-makefile.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0208-ipu-bridge-6.18-bkc-backport.patch" +PATCH_MATCH[$j]="^(6.1[8])" +PATCH[$((++j))]="0209-ipu-bridge-6.18-add-sensors.patch" +PATCH_MATCH[$j]="^(6.1[8])" +# kernel patches for Linux 7.0 +PATCH[$((++j))]="0210-ipu-bridge-7.0-makefile.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0211-ipu-bridge-7.0-bkc-backport.patch" +PATCH_MATCH[$j]="^(7.[0])" +PATCH[$((++j))]="0212-ipu-bridge-7.0-add-sensors.patch" +PATCH_MATCH[$j]="^(7.[0])" BUILT_MODULE_NAME[$((++i))]="ipu-bridge" -BUILT_MODULE_LOCATION[$i]="6.12.0/drivers/media/pci/intel" +BUILT_MODULE_LOCATION[$i]="${SELECTED_KERNEL}/drivers/media/pci/intel" DEST_MODULE_LOCATION[$i]="/updates" STRIP[$i]=no -fi +BUILT_MODULE_NAME[$((++i))]="videodev" +BUILT_MODULE_LOCATION[$i]="${SELECTED_KERNEL}/drivers/media/v4l2-core/" +DEST_MODULE_NAME[$i]="videodev" +DEST_MODULE_LOCATION[$i]="/updates" +STRIP[$i]=no -if [ "$KERNEL_VER" -eq 6 ] && [ "$KERNEL_PATCH" -eq 17 ]; then +if [ "$KERNEL_VER" -ge 6 ] && [ "$KERNEL_PATCH" -ge 17 ]; then # IPU7 drivers from submodule BUILT_MODULE_NAME[$((++i))]="intel-ipu7" BUILT_MODULE_LOCATION[$i]="ipu7-drivers/drivers/media/pci/intel/ipu7" @@ -150,23 +212,4 @@ BUILT_MODULE_LOCATION[$i]="ipu7-drivers/drivers/media/pci/intel/ipu7/psys" DEST_MODULE_LOCATION[$i]="/updates" STRIP[$i]=no -POST_ADD+=" drivers/media/pci/intel/Kconfig drivers/media/pci/intel/Makefile drivers/media/pci/intel/ipu-bridge.c" -PATCH[$((++j))]="0040-ipu-bridge-6.17-makefile.patch" -PATCH_MATCH[$j]="^(6.1[7])" -PATCH[$((++j))]="0041-ipu-bridge-6.17-bkc-backport.patch" -PATCH_MATCH[$j]="^(6.1[7])" -PATCH[$((++j))]="0042-ipu-bridge-6.17-add-sensor.patch" -PATCH_MATCH[$j]="^(6.1[7])" - -BUILT_MODULE_NAME[$((++i))]="ipu-bridge" -BUILT_MODULE_LOCATION[$i]="6.17.0/drivers/media/pci/intel" -DEST_MODULE_LOCATION[$i]="/updates" -STRIP[$i]=no - -BUILT_MODULE_NAME[$((++i))]="videodev" -BUILT_MODULE_LOCATION[$i]="6.17.0/drivers/media/v4l2-core/" -DEST_MODULE_NAME[$i]="videodev" -DEST_MODULE_LOCATION[$i]="/updates" -STRIP[$i]=no - fi diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 42d33362..e096a2b8 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -12,9 +12,13 @@ obj-$(CONFIG_VIDEO_AR0234) += ar0234.o obj-$(CONFIG_VIDEO_AR0820) += ar0820.o obj-$(CONFIG_VIDEO_AR0830) += ar0830.o obj-$(CONFIG_VIDEO_ISX031) += isx031.o -obj-$(CONFIG_VIDEO_LT6911UXC) += lt6911uxc.o -obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o -obj-$(CONFIG_VIDEO_LT6911GXD) += lt6911gxd.o + +ifeq ($(CONFIG_VIDEO_D4XX), m) + ccflags-y += -DCONFIG_VIDEO_D4XX_SERDES +endif subdir-ccflags-y := -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" + obj-$(CONFIG_VIDEO_MAX9X) += max9x/ + +obj-$(CONFIG_VIDEO_D4XX_COMPAT) += d4xx-compat/ diff --git a/drivers/media/i2c/d4xx-compat/Makefile b/drivers/media/i2c/d4xx-compat/Makefile new file mode 100644 index 00000000..ee5094db --- /dev/null +++ b/drivers/media/i2c/d4xx-compat/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2010 - 2025 Intel Corporation. + +KERNEL_VERSION := $(shell echo $(KERNELRELEASE) | cut -d- -f1 | sed -r 's/([0-9]+\.[0-9]+).*/\1.0/g') + +ccflags-y += -I$(src)/../../../$(KERNEL_VERSION)/include-overrides +ccflags-y += -DCONFIG_VIDEO_D4XX_SERDES -DCONFIG_SYSFS -DPLATFORM_AXIOMTEK + +obj-$(CONFIG_VIDEO_D4XX_MAX9295) += d4xx-max9295.o +obj-$(CONFIG_VIDEO_D4XX_MAX9296) += d4xx-max9296.o +obj-$(CONFIG_VIDEO_D4XX_MAX96724) += d4xx-max96724.o +obj-$(CONFIG_VIDEO_D4XX_COMPAT) += d4xx.o diff --git a/drivers/media/i2c/d4xx-compat/d4xx-max9295.c b/drivers/media/i2c/d4xx-compat/d4xx-max9295.c new file mode 100644 index 00000000..32658e76 --- /dev/null +++ b/drivers/media/i2c/d4xx-compat/d4xx-max9295.c @@ -0,0 +1,1158 @@ +/* + * max9295.c - max9295 GMSL Serializer driver + * + * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// #include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register specifics */ +#define MAX9295_MIPI_RX0_ADDR 0x330 +#define MAX9295_MIPI_RX1_ADDR 0x331 +#define MAX9295_MIPI_RX2_ADDR 0x332 +#define MAX9295_MIPI_RX3_ADDR 0x333 + +#define MAX9295_PIPE_X_DT_ADDR 0x314 +#define MAX9295_PIPE_Y_DT_ADDR 0x316 +#define MAX9295_PIPE_Z_DT_ADDR 0x318 +#define MAX9295_PIPE_U_DT_ADDR 0x31A + +#define MAX9295_CTRL0_ADDR 0x10 +#define MAX9295_SRC_CTRL_ADDR 0x2BF +#define MAX9295_SRC_PWDN_ADDR 0x02BE +#define MAX9295_SRC_OUT_RCLK_ADDR 0x3F1 +#define MAX9295_START_PIPE_ADDR 0x311 +#define MAX9295_PIPE_EN_ADDR 0x2 +#define MAX9295_CSI_PORT_SEL_ADDR 0x308 + +#define MAX9295_I2C4_ADDR 0x44 +#define MAX9295_I2C5_ADDR 0x45 + +#define MAX9295_DEV_ADDR 0x00 + +#define MAX9295_LINK_STATUS_ADDR 0x13 +#define MAX9295_LINK_LOCK_BIT 0x08 + +#define MAX9295_PIPE_X_STATUS_ADDR 0x102 +#define MAX9295_PIPE_Y_STATUS_ADDR 0x10A +#define MAX9295_PIPE_Z_STATUS_ADDR 0x112 +#define MAX9295_PIPE_U_STATUS_ADDR 0x11A + +#define MAX9295_STREAM_PIPE_UNUSED 0x22 +#define MAX9295_CSI_MODE_1X4 0x00 +#define MAX9295_CSI_MODE_2X2 0x03 +#define MAX9295_CSI_MODE_2X4 0x06 + +#define MAX9295_CSI_PORT_B(num_lanes) (((num_lanes) << 4) & 0xF0) +#define MAX9295_CSI_PORT_A(num_lanes) ((num_lanes) & 0x0F) + +#define MAX9295_CSI_1X4_MODE_LANE_MAP1 0xE0 +#define MAX9295_CSI_1X4_MODE_LANE_MAP2 0x04 + +#define MAX9295_CSI_2X4_MODE_LANE_MAP1 0xEE +#define MAX9295_CSI_2X4_MODE_LANE_MAP2 0xE4 + +#define MAX9295_CSI_2X2_MODE_LANE_MAP1 MAX9295_CSI_2X4_MODE_LANE_MAP1 +#define MAX9295_CSI_2X2_MODE_LANE_MAP2 MAX9295_CSI_2X4_MODE_LANE_MAP2 + +#define MAX9295_ST_ID_0 0x0 +#define MAX9295_ST_ID_1 0x1 +#define MAX9295_ST_ID_2 0x2 +#define MAX9295_ST_ID_3 0x3 + +#define MAX9295_PIPE_X_START_B 0x80 +#define MAX9295_PIPE_Y_START_B 0x40 +#define MAX9295_PIPE_Z_START_B 0x20 +#define MAX9295_PIPE_U_START_B 0x10 + +#define MAX9295_PIPE_X_START_A 0x1 +#define MAX9295_PIPE_Y_START_A 0x2 +#define MAX9295_PIPE_Z_START_A 0x4 +#define MAX9295_PIPE_U_START_A 0x8 + +#define MAX9295_START_PORT_A 0x10 +#define MAX9295_START_PORT_B 0x20 + +#define MAX9295_CSI_LN2 0x1 +#define MAX9295_CSI_LN4 0x3 + +#define MAX9295_EN_LINE_INFO 0x40 + +#define MAX9295_VID_TX_EN_X 0x10 +#define MAX9295_VID_TX_EN_Y 0x20 +#define MAX9295_VID_TX_EN_Z 0x40 +#define MAX9295_VID_TX_EN_U 0x80 + +#define MAX9295_VID_INIT 0x3 +#define MAX9295_SRC_RCLK 0x89 + +#define MAX9295_RESET_ALL 0x80 +#define MAX9295_RESET_SRC 0x60 +#define MAX9295_PWDN_GPIO 0x90 + +#define MAX9295_MAX_PIPES 0x4 + +/* Log all register writes */ +#define MAX9295_WRITE_REG(map, reg, val) \ +({ \ + int __ret; \ + struct regmap *__map = (map); \ + unsigned int __reg = (reg); \ + unsigned int __val = (val); \ + dev_dbg(regmap_get_device(__map), "REG_WRITE: [0x%04X] = 0x%02X", __reg, __val); \ + __ret = regmap_write(__map, __reg, __val); \ + if (__ret) \ + dev_err(regmap_get_device(__map), "REG_WRITE FAILED: [0x%04X] = 0x%02X, ret=%d", __reg, __val, __ret); \ + __ret; \ +}) + +/* Log register update_bits (read-modify-write) */ +#define MAX9295_UPDATE_BITS(map, reg, mask, val) \ +({ \ + int __ret; \ + struct regmap *__map = (map); \ + unsigned int __reg = (reg); \ + unsigned int __mask = (mask); \ + unsigned int __val = (val); \ + unsigned int __old_val = 0; \ + unsigned int __new_val = 0; \ + regmap_read(__map, __reg, &__old_val); \ + __new_val = (__old_val & ~__mask) | (__val & __mask); \ + dev_dbg(regmap_get_device(__map), "REG_UPDATE_BITS: [0x%04X] mask=0x%02X, old=0x%02X, new=0x%02X (val=0x%02X)", \ + __reg, __mask, __old_val, __new_val, __val); \ + __ret = regmap_update_bits(__map, __reg, __mask, __val); \ + if (__ret) \ + dev_err(regmap_get_device(__map), "REG_UPDATE_BITS FAILED: [0x%04X] mask=0x%02X val=0x%02X, ret=%d", \ + __reg, __mask, __val, __ret); \ + __ret; \ +}) + +struct max9295_client_ctx { + struct gmsl_link_ctx *g_ctx; + bool st_done; +}; + +struct max9295 { + struct i2c_client *i2c_client; + struct regmap *regmap; + struct max9295_client_ctx g_client; + struct mutex lock; + /* primary serializer properties */ + __u32 def_addr; + __u32 pst2_ref; + __u32 d4xx_hacks; +}; + +static struct max9295 *prim_priv__; + +struct map_ctx { + u8 dt; + u16 addr; + u8 val; + u8 st_id; +}; + +static int max9295_read_reg(struct device *dev, + u16 addr, unsigned int *val) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err; + + err = regmap_read(priv->regmap, addr, val); + if (err) + dev_err(dev, + "%s:i2c read failed, 0x%x\n", + __func__, addr); + + return err; +} + +static int max9295_write_reg(struct device *dev, u16 addr, u8 val) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err; + + err = MAX9295_WRITE_REG(priv->regmap, addr, val); + if (err) + dev_err(dev, "%s:i2c write failed, 0x%x = %x\n", + __func__, addr, val); + + /* delay before next i2c command as required for SERDES link */ + usleep_range(100, 110); + + return err; +} + +int max9295_check_status(struct device *dev) +{ + struct max9295 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + /* Double-Check GMSL link status after stream start configuration */ + { + unsigned int link_status = 0; + max9295_read_reg(dev, MAX9295_LINK_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Link status: 0x%02x (LOCK=%d, bit0-5: %s%s%s%s)\n", + __func__, link_status, !!(link_status & MAX9295_LINK_LOCK_BIT), + (link_status & 0x02) ? "CMU_LOCKED " : "", + (link_status & 0x04) ? "ERROR " : "", + (link_status & MAX9295_LINK_LOCK_BIT) ? "LOCKED " : "", + (link_status & 0x30) == 0x01? "LINKA " : ""); + max9295_read_reg(dev, MAX9295_PIPE_X_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe X Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Y_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Y Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Z_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Z Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_U_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe U Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + } + + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max9295_check_status); + +int max9295_setup_streaming(struct device *dev) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err = 0; + u32 csi_mode; + u32 lane_map1; + u32 lane_map2; + u32 port; + u32 rx1_lanes; + u32 st_pipe; + u32 pipe_en; + u32 port_sel = 0; + struct gmsl_link_ctx *g_ctx; + u32 i; + u32 j; + u32 st_en; + + mutex_lock(&priv->lock); + + if (!priv->g_client.g_ctx) { + dev_err(dev, "%s: no sdev client found\n", __func__); + err = -EINVAL; + goto error; + } + + if (priv->g_client.st_done) { + dev_dbg(dev, "%s: stream setup is already done\n", __func__); + goto error; + } + + g_ctx = priv->g_client.g_ctx; + + switch (g_ctx->csi_mode) { + case GMSL_CSI_1X4_MODE: + csi_mode = MAX9295_CSI_MODE_1X4; + lane_map1 = MAX9295_CSI_1X4_MODE_LANE_MAP1; + lane_map2 = MAX9295_CSI_1X4_MODE_LANE_MAP2; + rx1_lanes = MAX9295_CSI_LN4; + break; + case GMSL_CSI_2X2_MODE: + csi_mode = MAX9295_CSI_MODE_2X2; + lane_map1 = MAX9295_CSI_2X2_MODE_LANE_MAP1; + lane_map2 = MAX9295_CSI_2X2_MODE_LANE_MAP2; + rx1_lanes = MAX9295_CSI_LN2; + break; + case GMSL_CSI_2X4_MODE: + csi_mode = MAX9295_CSI_MODE_2X4; + lane_map1 = MAX9295_CSI_2X4_MODE_LANE_MAP1; + lane_map2 = MAX9295_CSI_2X4_MODE_LANE_MAP2; + rx1_lanes = MAX9295_CSI_LN4; + break; + default: + dev_err(dev, "%s: invalid csi mode\n", __func__); + err = -EINVAL; + goto error; + } + + port = (g_ctx->src_csi_port == GMSL_CSI_PORT_B) ? + MAX9295_CSI_PORT_B(rx1_lanes) : + MAX9295_CSI_PORT_A(rx1_lanes); + + max9295_write_reg(dev, MAX9295_MIPI_RX0_ADDR, csi_mode); + max9295_write_reg(dev, MAX9295_MIPI_RX1_ADDR, port); + max9295_write_reg(dev, MAX9295_MIPI_RX2_ADDR, lane_map1); + max9295_write_reg(dev, MAX9295_MIPI_RX3_ADDR, lane_map2); + + if (priv->d4xx_hacks) { + struct map_ctx map_pipe_dtype[] = { + {GMSL_CSI_DT_RAW_12, MAX9295_PIPE_Z_DT_ADDR, 0x2C, + MAX9295_ST_ID_2}, + {GMSL_CSI_DT_UED_U1, MAX9295_PIPE_X_DT_ADDR, 0x30, + MAX9295_ST_ID_0}, + {GMSL_CSI_DT_EMBED, MAX9295_PIPE_Y_DT_ADDR, 0x12, + MAX9295_ST_ID_1}, + }; + + for (i = 0; i < g_ctx->num_streams; i++) { + struct gmsl_stream *g_stream = &g_ctx->streams[i]; + + g_stream->st_id_sel = GMSL_ST_ID_UNUSED; + for (j = 0; j < ARRAY_SIZE(map_pipe_dtype); j++) { + if (map_pipe_dtype[j].dt == g_stream->st_data_type) { + /* + * TODO: + * 1) Remove link specific overrides, depends + * on #2. + * 2) Add support for vc id based stream sel + * overrides TX_SRC_SEL. would be useful in + * using same mappings in all ser devs. + */ +#if !defined(CONFIG_VIDEO_D4XX_MAX96724) || !defined(CONFIG_VIDEO_D4XX_MAX96712) + if (g_ctx->serdes_csi_link == + GMSL_SERDES_CSI_LINK_B) { + map_pipe_dtype[j].addr += 2; + map_pipe_dtype[j].st_id += 1; + } +#endif + + g_stream->st_id_sel = map_pipe_dtype[j].st_id; + st_en = (map_pipe_dtype[j].addr == + MAX9295_PIPE_X_DT_ADDR) ? + 0xC0 : 0x40; + + max9295_write_reg(dev, map_pipe_dtype[j].addr, + (st_en | map_pipe_dtype[j].val)); + } + } + } + dev_dbg(dev, "%s: d4xx specfic setup done\n", __func__); + } else { + //HACK: JNW 2024-10-10 Until we actually setup num_streams for proper mapping + port_sel |= 0x61; // Just pipe X & port B + } + + for (i = 0; i < g_ctx->num_streams; i++) + if (g_ctx->streams[i].st_id_sel != GMSL_ST_ID_UNUSED) + port_sel |= (1 << g_ctx->streams[i].st_id_sel); + + if (g_ctx->src_csi_port == GMSL_CSI_PORT_B) { + st_pipe = (MAX9295_PIPE_X_START_B | MAX9295_PIPE_Y_START_B | + MAX9295_PIPE_Z_START_B | MAX9295_PIPE_U_START_B); + port_sel |= (MAX9295_EN_LINE_INFO | MAX9295_START_PORT_B); + } else { + st_pipe = MAX9295_PIPE_X_START_A | MAX9295_PIPE_Y_START_A | + MAX9295_PIPE_Z_START_A | MAX9295_PIPE_U_START_A; + port_sel |= (MAX9295_EN_LINE_INFO | MAX9295_START_PORT_A); + } + + pipe_en = (MAX9295_VID_TX_EN_X | MAX9295_VID_TX_EN_Y | + MAX9295_VID_TX_EN_Z | MAX9295_VID_TX_EN_U | MAX9295_VID_INIT); + + max9295_write_reg(dev, MAX9295_START_PIPE_ADDR, st_pipe); + max9295_write_reg(dev, MAX9295_CSI_PORT_SEL_ADDR, port_sel); + max9295_write_reg(dev, MAX9295_PIPE_EN_ADDR, pipe_en); + + priv->g_client.st_done = true; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9295_setup_streaming); + +int max9295_setup_control(struct device *dev) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err = 0; + struct gmsl_link_ctx *g_ctx; + u32 offset1 = 0; + u32 offset2 = 0; + u32 i; + + u8 i2c_ovrd[] = { + 0x6B, 0x10, + 0x73, 0x11, + 0x7B, 0x30, + 0x83, 0x30, + 0x93, 0x30, + 0x9B, 0x30, + 0xA3, 0x30, + 0xAB, 0x30, + 0x8B, 0x30, + }; + + u8 addr_offset[] = { + 0x80, 0x00, 0x00, + 0x84, 0x00, 0x01, + 0x86, 0x00, 0x02, + 0x88, 0x00, 0x03, + 0x8A, 0x00, 0x04, + 0x8C, 0x00, 0x05, + 0xC0, 0x02, 0x06, + 0xC4, 0x02, 0x07, + 0xC6, 0x02, 0x08, + 0xC8, 0x02, 0x09, + 0xCA, 0x02, 0x0a, + 0xCC, 0x02, 0x0b, + //0xD6, 0x00, 0x0c, /* 0x6b << 1 = 0xd6 */ + }; + + mutex_lock(&priv->lock); + + if (!priv->g_client.g_ctx) { + dev_err(dev, "%s: no sensor dev client found\n", __func__); + err = -EINVAL; + goto error; + } + + g_ctx = priv->g_client.g_ctx; + +#ifdef CONFIG_OF + if (prim_priv__) { + /* update address reassignment */ + max9295_write_reg(&prim_priv__->i2c_client->dev, + MAX9295_DEV_ADDR, (g_ctx->ser_reg << 1)); + } +#else + if (priv->def_addr) { + /* update address reassignment */ + struct i2c_client *c = to_i2c_client(dev); + int addr = c->addr; + dev_info(dev, "%s: update address reassignment 0x%x->0x%x\n", __func__,priv->def_addr, g_ctx->ser_reg); + + c->addr = priv->def_addr; + max9295_write_reg(dev, + MAX9295_DEV_ADDR, (g_ctx->ser_reg << 1)); + c->addr = addr; + prim_priv__ = priv; + } else { + prim_priv__ = NULL; + } +#endif +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + if (g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_A || + g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_B || + g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_C || + g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_D) + err = max9295_write_reg(dev, MAX9295_CTRL0_ADDR, 0x21); +#else + if (g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_A) + err = max9295_write_reg(dev, MAX9295_CTRL0_ADDR, 0x21); +#endif + else + err = max9295_write_reg(dev, MAX9295_CTRL0_ADDR, 0x22); + + /* check if serializer device exists */ + if (err) { + dev_err(dev, "%s: ERROR: ser device not found\n", __func__); + goto error; + } + + /* delay to settle link */ + msleep(150); + + for (i = 0; i < ARRAY_SIZE(addr_offset); i += 3) { + if ((g_ctx->ser_reg << 1) == addr_offset[i]) { + offset1 = addr_offset[i+1]; + offset2 = addr_offset[i+2]; + break; + } + } + + if (i == ARRAY_SIZE(addr_offset)) { + dev_err(dev, "%s: invalid ser slave address\n", __func__); + err = -EINVAL; + goto error; + } + + for (i = 0; i < ARRAY_SIZE(i2c_ovrd); i += 2) { + /* update address overrides */ + i2c_ovrd[i+1] += (i < 4) ? offset1 : offset2; + + /* i2c passthrough2 must be configured once for all devices */ + if ((i2c_ovrd[i] == 0x8B) && prim_priv__ && + prim_priv__->pst2_ref) + continue; + + max9295_write_reg(dev, i2c_ovrd[i], i2c_ovrd[i+1]); + } + + /* dev addr pass-through2 ref */ + if (prim_priv__) + prim_priv__->pst2_ref++; + + dev_dbg(dev, "%s: configuring I2C4=0x%02x (sdev_reg=0x%02x), I2C5=0x%02x (sdev_def=0x%02x)\n", + __func__, (g_ctx->sdev_reg << 1), g_ctx->sdev_reg, + (g_ctx->sdev_def << 1), g_ctx->sdev_def); + err = max9295_write_reg(dev, MAX9295_I2C4_ADDR, (g_ctx->sdev_reg << 1)); + if (err) + dev_err(dev, "%s: ERROR: failed to write I2C4_ADDR, err=%d\n", __func__, err); + err = max9295_write_reg(dev, MAX9295_I2C5_ADDR, (g_ctx->sdev_def << 1)); + if (err) + dev_err(dev, "%s: ERROR: failed to write I2C5_ADDR, err=%d\n", __func__, err); + msleep(150); + + dev_dbg(dev, "%s: configuring sensor power and reset\n", __func__); + err = max9295_write_reg(dev, MAX9295_SRC_PWDN_ADDR, MAX9295_PWDN_GPIO); + if (err) + dev_err(dev, "%s: ERROR: failed to write SRC_PWDN, err=%d\n", __func__, err); + err = max9295_write_reg(dev, MAX9295_SRC_CTRL_ADDR, MAX9295_RESET_SRC); + if (err) + dev_err(dev, "%s: ERROR: failed to write SRC_CTRL, err=%d\n", __func__, err); + err = max9295_write_reg(dev, MAX9295_SRC_OUT_RCLK_ADDR, MAX9295_SRC_RCLK); + if (err) + dev_err(dev, "%s: ERROR: failed to write SRC_OUT_RCLK, err=%d\n", __func__, err); + + /* Wait for sensor to power up and stabilize */ + msleep(200); + dev_dbg(dev, "%s: sensor power/reset configuration complete\n", __func__); + + /* Double-Check GMSL link status after stream start configuration */ + { + unsigned int link_status = 0; + max9295_read_reg(dev, MAX9295_LINK_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Link status: 0x%02x (LOCK=%d, bit0-5: %s%s%s%s)\n", + __func__, link_status, !!(link_status & MAX9295_LINK_LOCK_BIT), + (link_status & 0x02) ? "CMU_LOCKED " : "", + (link_status & 0x04) ? "ERROR " : "", + (link_status & MAX9295_LINK_LOCK_BIT) ? "LOCKED " : "", + (link_status & 0x30) == 0x01? "LINKA " : ""); + } + + g_ctx->serdev_found = true; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9295_setup_control); + +int max9295_reset_control(struct device *dev) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err = 0; + + mutex_lock(&priv->lock); + if (!priv->g_client.g_ctx) { + dev_err(dev, "%s: no sdev client found\n", __func__); + err = -EINVAL; + goto error; + } + + priv->g_client.st_done = false; +#ifndef CONFIG_OF + if (priv->def_addr) { + prim_priv__ = priv; + } else { + prim_priv__ = NULL; + } +#endif + if (prim_priv__) { + prim_priv__->pst2_ref--; + + max9295_write_reg(dev, MAX9295_DEV_ADDR, + (prim_priv__->def_addr << 1)); + if (prim_priv__->pst2_ref == 0) +#ifdef CONFIG_OF + max9295_write_reg(&prim_priv__->i2c_client->dev, + MAX9295_CTRL0_ADDR, MAX9295_RESET_ALL); +#else + if (priv->def_addr) { + /* update address reassingment */ + struct i2c_client *c = to_i2c_client(dev); + int addr = c->addr; + c->addr = priv->def_addr; + max9295_write_reg(dev, + MAX9295_CTRL0_ADDR, MAX9295_RESET_ALL); + c->addr = addr; + } +#endif + } + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9295_reset_control); + +int max9295_sdev_pair(struct device *dev, struct gmsl_link_ctx *g_ctx) +{ + struct max9295 *priv; + int err = 0; + + if (!dev || !g_ctx || !g_ctx->s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + mutex_lock(&priv->lock); + if (priv->g_client.g_ctx) { + dev_err(dev, "%s: device already paired\n", __func__); + err = -EINVAL; + goto error; + } + + priv->g_client.st_done = false; + + priv->g_client.g_ctx = g_ctx; + dev_dbg(dev, "paired 0x%x->0x%x SerDes context\n", + g_ctx->sdev_def, + g_ctx->sdev_reg); + +error: + mutex_unlock(&priv->lock); + return 0; +} +EXPORT_SYMBOL(max9295_sdev_pair); + +int max9295_sdev_unpair(struct device *dev, struct device *s_dev) +{ + struct max9295 *priv = NULL; + int err = 0; + + if (!dev || !s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + if (!priv->g_client.g_ctx) { + dev_err(dev, "%s: device is not paired\n", __func__); + err = -ENOMEM; + goto error; + } + + if (priv->g_client.g_ctx->s_dev != s_dev) { + dev_err(dev, "%s: invalid device\n", __func__); + err = -EINVAL; + goto error; + } + + priv->g_client.g_ctx = NULL; + priv->g_client.st_done = false; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9295_sdev_unpair); + +static struct regmap_config max9295_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +struct reg_pair { + u16 addr; + u8 val; +}; + +static int max9295_set_registers(struct device *dev, struct reg_pair *map, + u32 count) +{ + int err = 0; + u32 j = 0; + + for (j = 0; j < count; j++) { + err = max9295_write_reg(dev, + map[j].addr, map[j].val); + if (err != 0) + break; + } + + return err; +} + +static int __max9295_set_pipe(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id) +{ + int err = 0; + u8 bpp = 0x30; + static u8 pipe_x_val = 0x0; + struct reg_pair map_multi_pipe_en[] = { + {0x0315, 0x80}, + }; + struct reg_pair map_bpp8dbl[] = { + {0x0312, 0x0F}, + }; + struct reg_pair map_pipe_control[] = { + /* addr, val */ + {MAX9295_PIPE_X_DT_ADDR, 0x5E}, // Pipe X pulls data_type1 + {0x0315, 0x52}, // Pipe X pulls data_type2 + {0x0309, 0x01}, // # Pipe X pulls vc_id + {0x030A, 0x00}, + }; + + if (data_type1 == GMSL_CSI_DT_RAW_8 || data_type1 == GMSL_CSI_DT_EMBED + || data_type2 == GMSL_CSI_DT_RAW_8 || data_type2 == GMSL_CSI_DT_EMBED) { + map_bpp8dbl[0].val |= (1 << pipe_id); + } else { + map_bpp8dbl[0].val &= ~(1 << pipe_id); + } + err |= max9295_set_registers(dev, map_bpp8dbl, ARRAY_SIZE(map_bpp8dbl)); + + map_pipe_control[0].addr += 0x2 * pipe_id; + map_pipe_control[1].addr += 0x2 * pipe_id; + map_pipe_control[2].addr += 0x2 * pipe_id; + map_pipe_control[3].addr += 0x2 * pipe_id; + + map_pipe_control[0].val = 0x40 | data_type1; + map_pipe_control[1].val = 0x40 | data_type2; +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + map_pipe_control[2].val = vc_id > 7 ? 0x00 : 1 << (vc_id & 0x07); + map_pipe_control[3].val = vc_id > 7 ? 1 << (vc_id & 0x07) : 0x00; +#else + map_pipe_control[2].val = 1 << (vc_id % MAX9295_MAX_PIPES); + map_pipe_control[3].val = 0x00; +#endif + + if (pipe_id == 0) + pipe_x_val = map_pipe_control[1].val; + + err |= max9295_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); + + map_multi_pipe_en[0].val = 0x80 | pipe_x_val; + err |= max9295_set_registers(dev, map_multi_pipe_en, + ARRAY_SIZE(map_multi_pipe_en)); + + return err; +} + +static int __max9295_set_pipe_d4xx(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id) +{ + int err = 0; + u8 bpp = 0x30; + static u8 pipe_x_val = 0x0; + struct reg_pair map_multi_pipe_en[] = { + {0x0315, 0x80}, + }; + struct reg_pair map_bpp8dbl[] = { + {0x0312, 0x0F}, + }; + struct reg_pair map_pipe_control[] = { + /* addr, val */ + {MAX9295_PIPE_X_DT_ADDR, 0x5E}, // Pipe X pulls data_type1 + {0x0315, 0x52}, // Pipe X pulls data_type2 + {0x0309, 0x01}, // # Pipe X pulls vc_id + {0x030A, 0x00}, + {0x031C, 0x30}, // BPP in pipe X + {0x0102, 0x0E}, // LIM_HEART Pipe X: Disabled + }; + + dev_dbg(dev, "%s: d4xx specfic pipe set\n", __func__); + + if (data_type1 == GMSL_CSI_DT_RAW_8 || data_type1 == GMSL_CSI_DT_EMBED + || data_type2 == GMSL_CSI_DT_RAW_8 || data_type2 == GMSL_CSI_DT_EMBED) { + map_bpp8dbl[0].val |= (1 << pipe_id); + } else { + map_bpp8dbl[0].val &= ~(1 << pipe_id); + } + err |= max9295_set_registers(dev, map_bpp8dbl, ARRAY_SIZE(map_bpp8dbl)); + + if (data_type1 == GMSL_CSI_DT_RGB_888) + bpp = 0x18; + + map_pipe_control[0].addr += 0x2 * pipe_id; + map_pipe_control[1].addr += 0x2 * pipe_id; + map_pipe_control[2].addr += 0x2 * pipe_id; + map_pipe_control[3].addr += 0x2 * pipe_id; + map_pipe_control[4].addr += 0x1 * pipe_id; + map_pipe_control[5].addr += 0x8 * pipe_id; + + map_pipe_control[0].val = 0x40 | data_type1; + map_pipe_control[1].val = 0x40 | data_type2; +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + map_pipe_control[2].val = vc_id > 7 ? 0x00 : 1 << (vc_id & 0x07); + map_pipe_control[3].val = vc_id > 7 ? 1 << (vc_id & 0x07) : 0x00; +#else + map_pipe_control[2].val = 1 << (vc_id % MAX9295_MAX_PIPES); + map_pipe_control[3].val = 0x00; +#endif + map_pipe_control[4].val = bpp; + map_pipe_control[5].val = 0x0E; + + if (pipe_id == 0) + pipe_x_val = map_pipe_control[1].val; + + err |= max9295_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); + + map_multi_pipe_en[0].val = 0x80 | pipe_x_val; + err |= max9295_set_registers(dev, map_multi_pipe_en, + ARRAY_SIZE(map_multi_pipe_en)); + + return err; +} + +int max9295_init_settings(struct device *dev) +{ + int err = 0; + int i; + struct max9295 *priv = dev_get_drvdata(dev); + + struct reg_pair map_pipe_opt[] = { + // Enable all pipes + {MAX9295_PIPE_EN_ADDR, 0xF3}, +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + // Write 0xBB for 4 lanes - Extended VC Enabled + {MAX9295_MIPI_RX1_ADDR, 0x99}, +#else + // Write 0x33 for 4 lanes - Extended VC Disabled + {MAX9295_MIPI_RX1_ADDR, 0x11}, +#endif + // All pipes pull clock from port B + {MAX9295_CSI_PORT_SEL_ADDR, 0x6F}, + // All pipes pull data from port B + {MAX9295_START_PIPE_ADDR, 0xF0}, + }; + + mutex_lock(&priv->lock); + + // Init control + err |= max9295_set_registers(dev, map_pipe_opt, + ARRAY_SIZE(map_pipe_opt)); + + if (priv->d4xx_hacks) { + for (i = 0; i < MAX9295_MAX_PIPES; i++) + err |= __max9295_set_pipe_d4xx(dev, i, GMSL_CSI_DT_YUV422_8, + GMSL_CSI_DT_EMBED, i); + } else { + for (i = 0; i < MAX9295_MAX_PIPES; i++) + err |= __max9295_set_pipe(dev, i, GMSL_CSI_DT_YUV422_8, + GMSL_CSI_DT_EMBED, i); + } + + /* Double-Check GMSL link status after stream start configuration */ + { + unsigned int link_status = 0; + max9295_read_reg(dev, MAX9295_LINK_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Link status: 0x%02x (LOCK=%d, bit0-5: %s%s%s%s)\n", + __func__, link_status, !!(link_status & MAX9295_LINK_LOCK_BIT), + (link_status & 0x02) ? "CMU_LOCKED " : "", + (link_status & 0x04) ? "ERROR " : "", + (link_status & MAX9295_LINK_LOCK_BIT) ? "LOCKED " : "", + (link_status & 0x30) == 0x01? "LINKA " : ""); + max9295_read_reg(dev, MAX9295_PIPE_X_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe X Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Y_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Y Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Z_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Z Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_U_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe U Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + } + + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9295_init_settings); + +int max9295_set_mfp(struct device *dev, int pin, int val) +{ + struct max9295 *priv = dev_get_drvdata(dev); + struct regmap *map = priv->regmap; + int err; + + if (pin > 10) + return -EINVAL; + + err = MAX9295_UPDATE_BITS(map, 0x2BE + (pin * 3), + (1<<4 | 1<<2 | 1<<0), + ((val ? 1<<4 : 0) | 0<<2 | 0<<0) ); + + err |= MAX9295_UPDATE_BITS(map, 0x2BF + (pin * 3), + 3<<6|1<<5, + 1<<6|1<<5); + + return err; + +} +EXPORT_SYMBOL(max9295_set_mfp); + +int max9295_set_pipe(struct device *dev, int pipe_id, + u8 data_type1, u8 data_type2, u32 vc_id) +{ + struct max9295 *priv = dev_get_drvdata(dev); + int err = 0; + + if (pipe_id > (MAX9295_MAX_PIPES - 1)) { + dev_info(dev, "%s, input pipe_id: %d exceed max9295 max pipes\n", + __func__, pipe_id); + return -EINVAL; + } +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + dev_info(dev, "%s pipe_id %d, data_type1 %u, data_type2 %u, vc_ext_id %u\n", + __func__, pipe_id, data_type1, data_type2, vc_id); +#else + dev_info(dev, "%s pipe_id %d, data_type1 %u, data_type2 %u, vc_id %u\n", + __func__, pipe_id, data_type1, data_type2, (vc_id % MAX9295_MAX_PIPES)); +#endif + + mutex_lock(&priv->lock); + + + if (priv->d4xx_hacks) { + err = __max9295_set_pipe_d4xx(dev, pipe_id, data_type1, data_type2, vc_id); + } else { + err = __max9295_set_pipe(dev, pipe_id, data_type1, data_type2, vc_id); + } + + + /* Double-Check GMSL link status after stream start configuration */ + { + unsigned int link_status = 0; + max9295_read_reg(dev, MAX9295_LINK_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Link status: 0x%02x (LOCK=%d, bit0-5: %s%s%s%s)\n", + __func__, link_status, !!(link_status & MAX9295_LINK_LOCK_BIT), + (link_status & 0x02) ? "CMU_LOCKED " : "", + (link_status & 0x04) ? "ERROR " : "", + (link_status & MAX9295_LINK_LOCK_BIT) ? "LOCKED " : "", + (link_status & 0x30) == 0x01? "LINKA " : ""); + max9295_read_reg(dev, MAX9295_PIPE_X_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe X Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Y_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Y Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_Z_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe Z Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + max9295_read_reg(dev, MAX9295_PIPE_U_STATUS_ADDR, &link_status); + dev_dbg(dev, "%s: Pipe U Link status: 0x%02x (bit3-7: %s%s%s%s%s)\n", + __func__, link_status, + (link_status & 0x04) ? "LIM_HEART " : "", + (link_status & 0x10) ? "FIFO_WARN " : "", + (link_status & 0x20) ? "OVERFLOW " : "", + (link_status & 0x40) ? "DRIFT_ERR " : "", + (link_status & 0x80) ? "PCLKDET " : ""); + } + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9295_set_pipe); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max9295_probe(struct i2c_client *client, const struct i2c_device_id *id) +#else +static int max9295_probe(struct i2c_client *client) +#endif +{ + struct max9295 *priv; + struct max9295_pdata *pdata = client->dev.platform_data; + int err = 0; +#ifdef CONFIG_OF + struct device_node *node = client->dev.of_node; +#endif + + dev_info(&client->dev, "[MAX9295]: probing GMSL Serializer\n"); + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv->i2c_client = client; + priv->regmap = devm_regmap_init_i2c(priv->i2c_client, + &max9295_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(priv->regmap)); + return -ENODEV; + } + + mutex_init(&priv->lock); +#ifdef CONFIG_OF + if (of_get_property(node, "is-prim-ser", NULL)) { + if (prim_priv__) { + dev_err(&client->dev, + "prim-ser already exists\n"); + return -EEXIST; + } + + err = of_property_read_u32(node, "reg", &priv->def_addr); + if (err < 0) { + dev_err(&client->dev, "reg not found\n"); + return -EINVAL; + } + + prim_priv__ = priv; + } +#else + if (pdata->is_prim_ser) { + prim_priv__ = priv; + priv->def_addr = pdata->def_addr; + } else { + priv->def_addr = 0; + } +#endif + if (pdata->d4xx_hacks) { + dev_info(&client->dev, "%s: set for d4xx\n", __func__); + priv->d4xx_hacks = pdata->d4xx_hacks; + } else { + priv->d4xx_hacks = 0; + } + dev_set_drvdata(&client->dev, priv); + + /* dev communication gets validated when GMSL link setup is done */ + dev_info(&client->dev, "%s: success\n", __func__); + + return err; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max9295_remove(struct i2c_client *client) +#else +static void max9295_remove(struct i2c_client *client) +#endif +{ + struct max9295 *priv; + + if (client != NULL) { + priv = dev_get_drvdata(&client->dev); + dev_info(&client->dev, "[MAX9295]: remove GMSL Serializer\n"); + mutex_destroy(&priv->lock); +#ifdef CONFIG_OF + i2c_unregister_device(client); + client = NULL; +#endif + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +static const struct i2c_device_id max9295_id[] = { + { "d4xx-max9295", 0 }, + { }, +}; + +static const struct of_device_id max9295_of_match[] = { + { .compatible = "maxim,d4xx-max9295", }, + { }, +}; +MODULE_DEVICE_TABLE(of, max9295_of_match); +MODULE_DEVICE_TABLE(i2c, max9295_id); + +static struct i2c_driver max9295_i2c_driver = { + .driver = { + .name = "d4xx-max9295", + .owner = THIS_MODULE, + }, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + .probe = max9295_probe, +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + .probe_new = max9295_probe, +#else + .probe = max9295_probe, +#endif + .remove = max9295_remove, + .id_table = max9295_id, +}; + +static int __init max9295_init(void) +{ + return i2c_add_driver(&max9295_i2c_driver); +} + +static void __exit max9295_exit(void) +{ + i2c_del_driver(&max9295_i2c_driver); +} + +module_init(max9295_init); +module_exit(max9295_exit); + +MODULE_DESCRIPTION("GMSL Serializer driver max9295"); +MODULE_AUTHOR("Sudhir Vyas "); +MODULE_AUTHOR("Dmitry Perchanov "); +MODULE_LICENSE("GPL v2"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +MODULE_VERSION(DRIVER_VERSION_SUFFIX); +#endif diff --git a/drivers/media/i2c/d4xx-compat/d4xx-max9296.c b/drivers/media/i2c/d4xx-compat/d4xx-max9296.c new file mode 100644 index 00000000..ed05e147 --- /dev/null +++ b/drivers/media/i2c/d4xx-compat/d4xx-max9296.c @@ -0,0 +1,1308 @@ +/* + * max9296.c - max9296 GMSL Deserializer driver + * + * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2024-2025, INTEL CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include + +/* register specifics */ +#define MAX9296_DST_CSI_MODE_ADDR 0x330 +#define MAX9296_LANE_MAP1_ADDR 0x333 +#define MAX9296_LANE_MAP2_ADDR 0x334 + +#define MAX9296_LANE_CTRL0_ADDR 0x40A +#define MAX9296_LANE_CTRL1_ADDR 0x44A +#define MAX9296_LANE_CTRL2_ADDR 0x48A +#define MAX9296_LANE_CTRL3_ADDR 0x4CA + +#define MAX9296_TX11_PIPE_X_EN_ADDR 0x40B +#define MAX9296_TX45_PIPE_X_DST_CTRL_ADDR 0x42D + +#define MAX9296_PIPE_X_SRC_0_MAP_ADDR 0x40D +#define MAX9296_PIPE_X_DST_0_MAP_ADDR 0x40E +#define MAX9296_PIPE_X_SRC_1_MAP_ADDR 0x40F +#define MAX9296_PIPE_X_DST_1_MAP_ADDR 0x410 +#define MAX9296_PIPE_X_SRC_2_MAP_ADDR 0x411 +#define MAX9296_PIPE_X_DST_2_MAP_ADDR 0x412 +#define MAX9296_PIPE_X_SRC_3_MAP_ADDR 0x413 +#define MAX9296_PIPE_X_DST_3_MAP_ADDR 0x414 + +#define MAX9296_PIPE_X_ST_SEL_ADDR 0x50 + +#define MAX9296_PWDN_PHYS_ADDR 0x332 +#define MAX9296_PHY1_CLK_ADDR 0x320 +#define MAX9296_CTRL0_ADDR 0x10 + +/* data defines */ +#define MAX9296_CSI_MODE_4X2 0x1 +#define MAX9296_CSI_MODE_2X4 0x4 +#define MAX9296_LANE_MAP1_4X2 0x44 +#define MAX9296_LANE_MAP2_4X2 0x44 +#define MAX9296_LANE_MAP1_2X4 0x4E +#define MAX9296_LANE_MAP2_2X4 0xE4 + +#define MAX9296_LANE_CTRL_MAP(num_lanes) \ + (((num_lanes) << 6) & 0xF0) + +#define MAX9296_ALLPHYS_NOSTDBY 0xF0 +#define MAX9296_ST_ID_SEL_INVALID 0xF + +#define MAX9296_PHY1_CLK 0x2C + +#define MAX9296_RESET_ALL 0x80 + +/* Dual GMSL MAX9296A/B */ +#define MAX9296_MAX_SOURCES 2 + +#define MAX9296_MAX_PIPES 4 + +#define MAX9296_PIPE_X 0 +#define MAX9296_PIPE_Y 1 +#define MAX9296_PIPE_Z 2 +#define MAX9296_PIPE_U 3 +#define MAX9296_PIPE_INVALID 0xF + + +#define MAX9296_CSI_CTRL_0 0 +#define MAX9296_CSI_CTRL_1 1 +#define MAX9296_CSI_CTRL_2 2 +#define MAX9296_CSI_CTRL_3 3 + +#define MAX9296_INVAL_ST_ID 0xFF + +/* Use reset value as per spec, confirm with vendor */ +#define MAX9296_RESET_ST_ID 0x00 + +struct max9296_source_ctx { + struct gmsl_link_ctx *g_ctx; + bool st_enabled; +}; + +struct pipe_ctx { + u32 id; + u32 dt_type; + u32 dst_csi_ctrl; + u32 st_count; + u32 st_id_sel; +}; + +struct max9296 { + struct i2c_client *i2c_client; + struct regmap *regmap; + u32 num_src; + u32 max_src; + u32 num_src_found; + u32 src_link; + bool splitter_enabled; + struct max9296_source_ctx sources[MAX9296_MAX_SOURCES]; + struct mutex lock; + u32 sdev_ref; + bool lane_setup; + bool link_setup; + struct pipe_ctx pipe[MAX9296_MAX_PIPES]; + u8 csi_mode; + u8 lane_mp1; + u8 lane_mp2; + int reset_gpio; + int pw_ref; + struct regulator *vdd_cam_1v2; + __u32 d4xx_hacks; +}; + +static int max9296_write_reg(struct device *dev, + u16 addr, u8 val) +{ + struct max9296 *priv; + int err; + + priv = dev_get_drvdata(dev); + + err = regmap_write(priv->regmap, addr, val); + if (err) + dev_err(dev, + "%s:i2c write failed, 0x%x = %x\n", + __func__, addr, val); + + /* delay before next i2c command as required for SERDES link */ + usleep_range(100, 110); + + return err; +} + +static int max9296_get_sdev_idx(struct device *dev, + struct device *s_dev, unsigned int *idx) +{ + struct max9296 *priv = dev_get_drvdata(dev); + unsigned int i; + int err = 0; + + mutex_lock(&priv->lock); + for (i = 0; i < priv->max_src; i++) { + if (priv->sources[i].g_ctx->s_dev == s_dev) + break; + } + if (i == priv->max_src) { + dev_err(dev, "no sdev found\n"); + err = -EINVAL; + goto ret; + } + + if (idx) + *idx = i; + +ret: + mutex_unlock(&priv->lock); + return err; +} + +static void max9296_pipes_reset(struct max9296 *priv) +{ + /* + * This is default pipes combination. add more mappings + * for other combinations and requirements. + */ + struct pipe_ctx pipe_defaults[] = { + {MAX9296_PIPE_X, GMSL_CSI_DT_RAW_12, + MAX9296_CSI_CTRL_1, 0, MAX9296_INVAL_ST_ID}, + {MAX9296_PIPE_Y, GMSL_CSI_DT_RAW_12, + MAX9296_CSI_CTRL_1, 0, MAX9296_INVAL_ST_ID}, + {MAX9296_PIPE_Z, GMSL_CSI_DT_EMBED, + MAX9296_CSI_CTRL_1, 0, MAX9296_INVAL_ST_ID}, + {MAX9296_PIPE_U, GMSL_CSI_DT_EMBED, + MAX9296_CSI_CTRL_1, 0, MAX9296_INVAL_ST_ID} + }; + + /* + * Add DT props for num-streams and stream sequence, and based on that + * set the appropriate pipes defaults. + * For now default it supports "2 RAW12 and 2 EMBED" 1:1 mappings. + */ + memcpy(priv->pipe, pipe_defaults, sizeof(pipe_defaults)); +} + +static void max9296_reset_ctx(struct max9296 *priv) +{ + unsigned int i; + + priv->link_setup = false; + priv->lane_setup = false; + priv->num_src_found = 0; + priv->src_link = 0; + priv->splitter_enabled = false; + max9296_pipes_reset(priv); + for (i = 0; i < priv->num_src; i++) + priv->sources[i].st_enabled = false; +} + +int max9296_power_on(struct device *dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + + mutex_lock(&priv->lock); + if (priv->pw_ref == 0) { + usleep_range(1, 2); + if (priv->reset_gpio) + gpio_set_value(priv->reset_gpio, 0); + + usleep_range(30, 50); + + if (priv->vdd_cam_1v2) { + err = regulator_enable(priv->vdd_cam_1v2); + if (unlikely(err)) + goto ret; + } + + usleep_range(30, 50); + + /*exit reset mode: XCLR */ + if (priv->reset_gpio) { + gpio_set_value(priv->reset_gpio, 0); + usleep_range(30, 50); + gpio_set_value(priv->reset_gpio, 1); + usleep_range(30, 50); + } + + /* delay to settle reset */ + msleep(20); + } + + priv->pw_ref++; + +ret: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9296_power_on); + +void max9296_power_off(struct device *dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + priv->pw_ref--; + + if (priv->pw_ref < 0) + priv->pw_ref = 0; + + if (priv->pw_ref == 0) { + /* enter reset mode: XCLR */ + usleep_range(1, 2); + if (priv->reset_gpio) + gpio_set_value(priv->reset_gpio, 0); + + if (priv->vdd_cam_1v2) + regulator_disable(priv->vdd_cam_1v2); + } + + mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL(max9296_power_off); + +static int max9296_write_link(struct device *dev, u32 link) +{ + if (link == GMSL_SERDES_CSI_LINK_A) { + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x01); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x21); + } else if (link == GMSL_SERDES_CSI_LINK_B) { + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x02); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x22); + } else { + dev_err(dev, "%s: invalid gmsl link\n", __func__); + return -EINVAL; + } + + /* delay to settle link */ + msleep(100); + + return 0; +} + +int max9296_setup_link(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + unsigned int i = 0; + + err = max9296_get_sdev_idx(dev, s_dev, &i); + if (err) + return err; + + mutex_lock(&priv->lock); + + if (!priv->splitter_enabled) { + err = max9296_write_link(dev, + priv->sources[i].g_ctx->serdes_csi_link); + if (err) + goto ret; + + priv->link_setup = true; + } + +ret: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9296_setup_link); + +int max9296_setup_control(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + unsigned int i = 0; + + err = max9296_get_sdev_idx(dev, s_dev, &i); + if (err) + return err; + + mutex_lock(&priv->lock); + + if (!priv->link_setup) { + dev_err(dev, "%s: invalid state\n", __func__); + err = -EINVAL; + goto error; + } + + if (priv->sources[i].g_ctx->serdev_found) { + priv->num_src_found++; + priv->src_link = priv->sources[i].g_ctx->serdes_csi_link; + } + + /* Enable splitter mode */ + if ((priv->max_src > 1U) && + (priv->num_src_found > 0U) && + (priv->splitter_enabled == false)) { + dev_info(dev, "%s: set SerDes GMSL A & B dual-link)\n", + __func__); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x03); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x23); + + priv->splitter_enabled = true; + + /* delay to settle link */ + msleep(100); + } + + max9296_write_reg(dev, + MAX9296_PWDN_PHYS_ADDR, MAX9296_ALLPHYS_NOSTDBY); + + priv->sdev_ref++; + + dev_info(dev, "%s: checking serializer sources (count/ref=%d/%d -> src[%d]=%s)\n", + __func__, + priv->num_src_found, + priv->sdev_ref, i, + priv->sources[i].g_ctx->serdes_csi_link == GMSL_SERDES_CSI_LINK_A? "GMSL A": "GMSL B"); + + /* Reset splitter mode if all devices are not found */ + if ((priv->sdev_ref != priv->max_src) && + (priv->splitter_enabled == true) && + (priv->num_src_found > 0U) && + (priv->num_src_found < priv->max_src)) { + dev_info(dev, "%s: restore SerDes %s single-link\n", + __func__, + priv->src_link == GMSL_SERDES_CSI_LINK_A? "GMSL A": "GMSL B"); + err = max9296_write_link(dev, priv->src_link); + if (err) { + dev_info(dev, "%s: fail to write source link\n", + __func__); + goto error; + } + + priv->splitter_enabled = false; + + } else if (priv->num_src_found < priv->sdev_ref) { + + dev_info(dev, "%s: restore SerDes %s single-link\n", + __func__, + priv->src_link == GMSL_SERDES_CSI_LINK_A ? "GMSL A": "GMSL B"); + + err = max9296_write_link(dev, priv->src_link); + if (err) { + dev_info(dev, "%s: fail to write source link\n", + __func__); + goto error; + } + priv->splitter_enabled = false; + } + + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9296_setup_control); + +int max9296_reset_control(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + + mutex_lock(&priv->lock); + if (!priv->sdev_ref) { + dev_info(dev, "%s: dev is already in reset state\n", __func__); + goto ret; + } + + priv->sdev_ref--; + if (priv->sdev_ref == 0) { + max9296_reset_ctx(priv); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, MAX9296_RESET_ALL); + + /* delay to settle reset */ + msleep(100); + } + +ret: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9296_reset_control); + +int max9296_sdev_register(struct device *dev, struct gmsl_link_ctx *g_ctx) +{ + struct max9296 *priv = NULL; + unsigned int i; + int err = 0; + + if (!dev || !g_ctx || !g_ctx->s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + if (priv->num_src > priv->max_src) { + dev_err(dev, + "%s: MAX9296 inputs size exhausted\n", __func__); + err = -ENOMEM; + goto error; + } + + if (priv->d4xx_hacks) { + /* Check csi mode compatibility */ + if (!((priv->csi_mode == MAX9296_CSI_MODE_2X4) ? + ((g_ctx->csi_mode == GMSL_CSI_1X4_MODE) || + (g_ctx->csi_mode == GMSL_CSI_2X4_MODE)) : + ((g_ctx->csi_mode == GMSL_CSI_2X2_MODE) || + (g_ctx->csi_mode == GMSL_CSI_4X2_MODE)))) { + dev_err(dev, + "%s: csi mode not supported\n", __func__); + err = -EINVAL; + goto error; + } + dev_dbg(dev, "%s: d4xx specfic csi-mode set\n", __func__); + } + + for (i = 0; i < priv->num_src; i++) { + if (g_ctx->serdes_csi_link == + priv->sources[i].g_ctx->serdes_csi_link) { + dev_err(dev, + "%s: serdes csi link is in use\n", __func__); + err = -EINVAL; + goto error; + } + /* + * All sdevs should have same num-csi-lanes regardless of + * dst csi port selected. + * Later if there is any usecase which requires each port + * to be configured with different num-csi-lanes, then this + * check should be performed per port. + */ + if (g_ctx->num_csi_lanes != + priv->sources[i].g_ctx->num_csi_lanes) { + dev_err(dev, + "%s: csi num lanes mismatch\n", __func__); + err = -EINVAL; + goto error; + } + } + + priv->sources[priv->num_src].g_ctx = g_ctx; + priv->sources[priv->num_src].st_enabled = false; + + priv->num_src++; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9296_sdev_register); + +int max9296_sdev_unregister(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = NULL; + int err = 0; + unsigned int i = 0; + + if (!dev || !s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + mutex_lock(&priv->lock); + + if (priv->num_src == 0) { + dev_err(dev, "%s: no source found\n", __func__); + err = -ENODATA; + goto error; + } + + for (i = 0; i < priv->num_src; i++) { + if (s_dev == priv->sources[i].g_ctx->s_dev) { + priv->sources[i].g_ctx = NULL; + break; + } + } + + if (i == priv->num_src) { + dev_err(dev, + "%s: requested device not found\n", __func__); + err = -EINVAL; + goto error; + } + priv->num_src--; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9296_sdev_unregister); + +static int max9296_get_available_pipe(struct device *dev, + u32 st_data_type, u32 dst_csi_port) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < MAX9296_MAX_PIPES; i++) { + /* + * TODO: Enable a pipe for multi stream configuration having + * similar stream data type. For now use st_count as a flag + * for 1 to 1 mapping in pipe and stream data type, same can + * be extended as count for many to 1 mapping. Would also need + * few more checks such as input stream id select, dst port etc. + */ + if ((priv->pipe[i].dt_type == st_data_type) && + ((dst_csi_port == GMSL_CSI_PORT_A) ? + (priv->pipe[i].dst_csi_ctrl == + MAX9296_CSI_CTRL_0) || + (priv->pipe[i].dst_csi_ctrl == + MAX9296_CSI_CTRL_1) : + (priv->pipe[i].dst_csi_ctrl == + MAX9296_CSI_CTRL_2) || + (priv->pipe[i].dst_csi_ctrl == + MAX9296_CSI_CTRL_3)) && + (!priv->pipe[i].st_count)) + break; + } + + if (i == MAX9296_MAX_PIPES) { + dev_err(dev, "%s: all pipes are busy\n", __func__); + return -ENOMEM; + } + + return i; +} + +struct reg_pair { + u16 addr; + u8 val; +}; + +static int max9296_setup_pipeline(struct device *dev, + struct gmsl_link_ctx *g_ctx) +{ + struct max9296 *priv = dev_get_drvdata(dev); + struct gmsl_stream *g_stream; + struct reg_pair *map_list; + u32 arr_sz = 0; + int pipe_id = 0; + u32 i = 0; + u32 j = 0; + u32 vc_idx = 0; + + for (i = 0; i < g_ctx->num_streams; i++) { + /* Base data type mapping: pipeX/RAW12/CSICNTR1 */ + struct reg_pair map_pipe_raw12[] = { + /* addr, val */ + {MAX9296_TX11_PIPE_X_EN_ADDR, 0x7}, + {MAX9296_TX45_PIPE_X_DST_CTRL_ADDR, 0x15}, + {MAX9296_PIPE_X_SRC_0_MAP_ADDR, 0x2C}, + {MAX9296_PIPE_X_DST_0_MAP_ADDR, 0x2C}, + {MAX9296_PIPE_X_SRC_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_DST_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_SRC_2_MAP_ADDR, 0x01}, + {MAX9296_PIPE_X_DST_2_MAP_ADDR, 0x01}, + }; + + /* Base data type mapping: pipeX/EMBED/CSICNTR1 */ + struct reg_pair map_pipe_embed[] = { + /* addr, val */ + {MAX9296_TX11_PIPE_X_EN_ADDR, 0x7}, + {MAX9296_TX45_PIPE_X_DST_CTRL_ADDR, 0x15}, + {MAX9296_PIPE_X_SRC_0_MAP_ADDR, 0x12}, + {MAX9296_PIPE_X_DST_0_MAP_ADDR, 0x12}, + {MAX9296_PIPE_X_SRC_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_DST_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_SRC_2_MAP_ADDR, 0x01}, + {MAX9296_PIPE_X_DST_2_MAP_ADDR, 0x01}, + }; + + g_stream = &g_ctx->streams[i]; + g_stream->des_pipe = MAX9296_PIPE_INVALID; + + if (g_stream->st_data_type == GMSL_CSI_DT_RAW_12) { + map_list = map_pipe_raw12; + arr_sz = ARRAY_SIZE(map_pipe_raw12); + } else if (g_stream->st_data_type == GMSL_CSI_DT_EMBED) { + map_list = map_pipe_embed; + arr_sz = ARRAY_SIZE(map_pipe_embed); + } else if (g_stream->st_data_type == GMSL_CSI_DT_UED_U1) { + dev_dbg(dev, + "%s: No mapping for GMSL_CSI_DT_UED_U1\n", + __func__); + continue; + } else { + dev_err(dev, "%s: Invalid data type\n", __func__); + return -EINVAL; + } + + pipe_id = max9296_get_available_pipe(dev, + g_stream->st_data_type, g_ctx->dst_csi_port); + if (pipe_id < 0) + return pipe_id; + + for (j = 0, vc_idx = 3; j < arr_sz; j++, vc_idx += 2) { + /* update pipe configuration */ + map_list[j].addr += (0x40 * pipe_id); + /* update vc id configuration */ + if (vc_idx < arr_sz) + map_list[vc_idx].val |= + (g_ctx->dst_vc << 6); + + max9296_write_reg(dev, map_list[j].addr, + map_list[j].val); + } + + /* Set stream id select input */ + if (g_stream->st_id_sel == GMSL_ST_ID_UNUSED) { + dev_err(dev, "%s: Invalid stream st_id_sel\n", + __func__); + return -EINVAL; + } + + g_stream->des_pipe = MAX9296_PIPE_X_ST_SEL_ADDR + pipe_id; + + /* Update pipe internals */ + priv->pipe[pipe_id].st_count++; + priv->pipe[pipe_id].st_id_sel = g_stream->st_id_sel; + } + + return 0; +} + +int max9296_start_streaming(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + struct gmsl_link_ctx *g_ctx; + struct gmsl_stream *g_stream; + int err = 0; + unsigned int i = 0; + + err = max9296_get_sdev_idx(dev, s_dev, &i); + if (err) + return err; + + mutex_lock(&priv->lock); + g_ctx = priv->sources[i].g_ctx; + + for (i = 0; i < g_ctx->num_streams; i++) { + g_stream = &g_ctx->streams[i]; + + if (g_stream->des_pipe != MAX9296_PIPE_INVALID) + max9296_write_reg(dev, g_stream->des_pipe, + g_stream->st_id_sel); + } + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max9296_start_streaming); + +int max9296_stop_streaming(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + struct gmsl_link_ctx *g_ctx; + struct gmsl_stream *g_stream; + int err = 0; + unsigned int i = 0; + + err = max9296_get_sdev_idx(dev, s_dev, &i); + if (err) + return err; + + mutex_lock(&priv->lock); + g_ctx = priv->sources[i].g_ctx; + + for (i = 0; i < g_ctx->num_streams; i++) { + g_stream = &g_ctx->streams[i]; + + if (g_stream->des_pipe != MAX9296_PIPE_INVALID) + max9296_write_reg(dev, g_stream->des_pipe, + MAX9296_RESET_ST_ID); + } + + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max9296_stop_streaming); + +int max9296_setup_streaming(struct device *dev, struct device *s_dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + struct gmsl_link_ctx *g_ctx; + int err = 0; + unsigned int i = 0; + u16 lane_ctrl_addr; + + err = max9296_get_sdev_idx(dev, s_dev, &i); + if (err) + return err; + + mutex_lock(&priv->lock); + if (priv->sources[i].st_enabled) + goto ret; + + g_ctx = priv->sources[i].g_ctx; + + err = max9296_setup_pipeline(dev, g_ctx); + if (err) + goto ret; + + /* Derive CSI lane map register */ + switch(g_ctx->dst_csi_port) { + case GMSL_CSI_PORT_A: + case GMSL_CSI_PORT_D: + lane_ctrl_addr = MAX9296_LANE_CTRL1_ADDR; + break; + case GMSL_CSI_PORT_B: + case GMSL_CSI_PORT_E: + lane_ctrl_addr = MAX9296_LANE_CTRL2_ADDR; + break; + case GMSL_CSI_PORT_C: + lane_ctrl_addr = MAX9296_LANE_CTRL0_ADDR; + break; + case GMSL_CSI_PORT_F: + lane_ctrl_addr = MAX9296_LANE_CTRL3_ADDR; + break; + default: + dev_err(dev, "%s: invalid gmsl csi port!\n", __func__); + err = -EINVAL; + goto ret; + }; + + /* + * rewrite num_lanes to same dst port should not be an issue, + * as the device compatibility is already + * checked during sdev registration against the des properties. + */ + max9296_write_reg(dev, lane_ctrl_addr, + MAX9296_LANE_CTRL_MAP(g_ctx->num_csi_lanes-1)); + + if (!priv->lane_setup) { + max9296_write_reg(dev, + MAX9296_DST_CSI_MODE_ADDR, priv->csi_mode); + max9296_write_reg(dev, + MAX9296_LANE_MAP1_ADDR, priv->lane_mp1); + max9296_write_reg(dev, + MAX9296_LANE_MAP2_ADDR, priv->lane_mp2); + max9296_write_reg(dev, + MAX9296_PHY1_CLK_ADDR, MAX9296_PHY1_CLK); + + priv->lane_setup = true; + } + + priv->sources[i].st_enabled = true; + +ret: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max9296_setup_streaming); + +static int max9296_set_registers(struct device *dev, struct reg_pair *map, + u32 count) +{ + int err = 0; + u32 j = 0; + + for (j = 0; j < count; j++) { + err = max9296_write_reg(dev, + map[j].addr, map[j].val); + if (err != 0) + break; + } + + return err; +} + +int max9296_get_available_pipe_id(struct device *dev, int vc_id) +{ + int i; + int pipe_id = -ENOMEM; + struct max9296 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + for (i = 0; i < MAX9296_MAX_PIPES; i++) { + if (i == vc_id && !priv->pipe[i].st_count) { + priv->pipe[i].st_count++; + pipe_id = i; + break; + } + } + mutex_unlock(&priv->lock); + + return pipe_id; +} +EXPORT_SYMBOL(max9296_get_available_pipe_id); + +int max9296_release_pipe(struct device *dev, int pipe_id) +{ + struct max9296 *priv = dev_get_drvdata(dev); + + if (pipe_id < 0 || pipe_id >= MAX9296_MAX_PIPES) + return -EINVAL; + + mutex_lock(&priv->lock); + priv->pipe[pipe_id].st_count = 0; + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max9296_release_pipe); + +void max9296_reset_oneshot(struct device *dev) +{ + struct max9296 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + if (priv->splitter_enabled) { + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x03); + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x23); + } else { + max9296_write_reg(dev, MAX9296_CTRL0_ADDR, 0x31); + } + /* delay to settle link */ + msleep(100); + mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL(max9296_reset_oneshot); + +static int __max9296_set_pipe_d4xx(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + int i = 0; + u8 en_mapping_num = 0x0F; + u8 all_mapping_phy = 0x55; + + struct reg_pair map_pipe_opt[] = { + {0x1458, 0x28}, // PHY A Optimization + {0x1459, 0x68}, // PHY A Optimization + {0x1558, 0x28}, // PHY B Optimization + {0x1559, 0x68}, // PHY B Optimization + // 4 lanes on port A, write 0x50 for 2 lanes + {MAX9296_LANE_CTRL1_ADDR, 0x50}, + // 1500Mbps/lane on port A + {MAX9296_PHY1_CLK_ADDR, 0x2F}, + // Do not un-double 8bpp (Un-double 8bpp data) + //{0x031C, 0x00}, + // Do not un-double 8bpp + //{0x031F, 0x00}, + // 0x02: ALT_MEM_MAP8, 0x10: ALT2_MEM_MAP8 + {0x0473, 0x10}, + }; + + struct reg_pair map_pipe_control[] = { + // Enable 4 mappings for Pipe X + {MAX9296_TX11_PIPE_X_EN_ADDR, 0x0F}, + // Map data_type1 on vc_id + {MAX9296_PIPE_X_SRC_0_MAP_ADDR, 0x1E}, + {MAX9296_PIPE_X_DST_0_MAP_ADDR, 0x1E}, + // Map frame_start on vc_id + {MAX9296_PIPE_X_SRC_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_DST_1_MAP_ADDR, 0x00}, + // Map frame end on vc_id + {MAX9296_PIPE_X_SRC_2_MAP_ADDR, 0x01}, + {MAX9296_PIPE_X_DST_2_MAP_ADDR, 0x01}, + // Map data_type2 on vc_id + {MAX9296_PIPE_X_SRC_3_MAP_ADDR, 0x12}, + {MAX9296_PIPE_X_DST_3_MAP_ADDR, 0x12}, + // All mappings to PHY1 (master for port A) + {MAX9296_TX45_PIPE_X_DST_CTRL_ADDR, 0x55}, + // SEQ_MISS_EN: Disabled / DIS_PKT_DET: Disabled + {0x0100, 0x23}, // pipe X + }; + + dev_dbg(dev, "%s: d4xx specfic pipe set\n", __func__); + + for (i = 0; i < 10; i++) { + map_pipe_control[i].addr += 0x40 * pipe_id; + } + map_pipe_control[10].addr += 0x12 * pipe_id; + + if (data_type2 == 0x0) { + en_mapping_num = 0x07; + all_mapping_phy = 0x15; + } + map_pipe_control[0].val = en_mapping_num; + map_pipe_control[1].val = (vc_id << 6) | data_type1; + map_pipe_control[2].val = (vc_id << 6) | data_type1; + map_pipe_control[3].val = (vc_id << 6) | 0x00; + map_pipe_control[4].val = (vc_id << 6) | 0x00; + map_pipe_control[5].val = (vc_id << 6) | 0x01; + map_pipe_control[6].val = (vc_id << 6) | 0x01; + map_pipe_control[7].val = (vc_id << 6) | data_type2; + map_pipe_control[8].val = (vc_id << 6) | data_type2; + map_pipe_control[9].val = all_mapping_phy; + map_pipe_control[10].val = 0x23; + + err |= max9296_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); + + err |= max9296_set_registers(dev, map_pipe_opt, + ARRAY_SIZE(map_pipe_opt)); + return err; +} + +static int __max9296_set_pipe(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + int i = 0; + u8 en_mapping_num = 0x0F; + u8 all_mapping_phy = 0x55; + + struct reg_pair map_pipe_control[] = { + // Enable 4 mappings for Pipe X + {MAX9296_TX11_PIPE_X_EN_ADDR, 0x0F}, + // Map data_type1 on vc_id + {MAX9296_PIPE_X_SRC_0_MAP_ADDR, 0x1E}, + {MAX9296_PIPE_X_DST_0_MAP_ADDR, 0x1E}, + // Map frame_start on vc_id + {MAX9296_PIPE_X_SRC_1_MAP_ADDR, 0x00}, + {MAX9296_PIPE_X_DST_1_MAP_ADDR, 0x00}, + // Map frame end on vc_id + {MAX9296_PIPE_X_SRC_2_MAP_ADDR, 0x01}, + {MAX9296_PIPE_X_DST_2_MAP_ADDR, 0x01}, + // Map data_type2 on vc_id + {MAX9296_PIPE_X_SRC_3_MAP_ADDR, 0x12}, + {MAX9296_PIPE_X_DST_3_MAP_ADDR, 0x12}, + // All mappings to PHY1 (master for port A) + {MAX9296_TX45_PIPE_X_DST_CTRL_ADDR, 0x55}, + // SEQ_MISS_EN: Disabled / DIS_PKT_DET: Disabled + }; + + for (i = 0; i < ARRAY_SIZE(map_pipe_control); i++) { + map_pipe_control[i].addr += 0x40 * pipe_id; + } + + if (data_type2 == 0x0) { + en_mapping_num = 0x07; + all_mapping_phy = 0x15; + } + map_pipe_control[0].val = en_mapping_num; + map_pipe_control[1].val = (vc_id << 6) | data_type1; + map_pipe_control[2].val = (vc_id << 6) | data_type1; + map_pipe_control[3].val = (vc_id << 6) | 0x00; + map_pipe_control[4].val = (vc_id << 6) | 0x00; + map_pipe_control[5].val = (vc_id << 6) | 0x01; + map_pipe_control[6].val = (vc_id << 6) | 0x01; + map_pipe_control[7].val = (vc_id << 6) | data_type2; + map_pipe_control[8].val = (vc_id << 6) | data_type2; + map_pipe_control[9].val = all_mapping_phy; + + err |= max9296_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); + return err; +} +int max9296_init_settings(struct device *dev) +{ + int err = 0; + int i; + struct max9296 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + if (priv->d4xx_hacks) { + for (i = 0; i < MAX9296_MAX_PIPES; i++) + err |= __max9296_set_pipe_d4xx(dev, i, GMSL_CSI_DT_YUV422_8, + GMSL_CSI_DT_EMBED, i); + } else { + for (i = 0; i < MAX9296_MAX_PIPES; i++) + err |= __max9296_set_pipe(dev, i, GMSL_CSI_DT_YUV422_8, + GMSL_CSI_DT_EMBED, i); + } + + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9296_init_settings); + +int max9296_set_pipe(struct device *dev, int pipe_id, + u8 data_type1, u8 data_type2, u32 vc_id) +{ + struct max9296 *priv = dev_get_drvdata(dev); + int err = 0; + + if (pipe_id > (MAX9296_MAX_PIPES - 1)) { + dev_info(dev, "%s, input pipe_id: %d exceed max9296 max pipes\n", + __func__, pipe_id); + return -EINVAL; + } + + dev_dbg(dev, "%s pipe_id %d, data_type1 %u, data_type2 %u, vc_id %u\n", + __func__, pipe_id, data_type1, data_type2, vc_id); + + mutex_lock(&priv->lock); + + if (priv->d4xx_hacks) { + err = __max9296_set_pipe_d4xx(dev, pipe_id, data_type1, data_type2, vc_id); + } else { + err = __max9296_set_pipe(dev, pipe_id, data_type1, data_type2, vc_id); + } + + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max9296_set_pipe); + +static const struct of_device_id max9296_of_match[] = { + { .compatible = "maxim,max9296", }, + { }, +}; +MODULE_DEVICE_TABLE(of, max9296_of_match); + +#ifdef CONFIG_OF +static int max9296_parse_dt(struct max9296 *priv, + struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + int err = 0; + const char *str_value; + int value; + const struct of_device_id *match; + + if (!node) + return -EINVAL; + + match = of_match_device(max9296_of_match, &client->dev); + if (!match) { + dev_err(&client->dev, "Failed to find matching dt id\n"); + return -EFAULT; + } + + err = of_property_read_string(node, "csi-mode", &str_value); + if (err < 0) { + dev_err(&client->dev, "csi-mode property not found\n"); + return err; + } + + if (!strcmp(str_value, "2x4")) { + priv->csi_mode = MAX9296_CSI_MODE_2X4; + priv->lane_mp1 = MAX9296_LANE_MAP1_2X4; + priv->lane_mp2 = MAX9296_LANE_MAP2_2X4; + } else if (!strcmp(str_value, "4x2")) { + priv->csi_mode = MAX9296_CSI_MODE_4X2; + priv->lane_mp1 = MAX9296_LANE_MAP1_4X2; + priv->lane_mp2 = MAX9296_LANE_MAP2_4X2; + } else { + dev_err(&client->dev, "invalid csi mode\n"); + return -EINVAL; + } + + err = of_property_read_u32(node, "max-src", &value); + if (err < 0) { + dev_err(&client->dev, "No max-src info\n"); + return err; + } + priv->max_src = value; + + priv->reset_gpio = of_get_named_gpio(node, "reset-gpios", 0); + if (priv->reset_gpio < 0) { + dev_err(&client->dev, "reset-gpios not found %d\n", err); + return err; + } + + /* digital 1.2v */ + if (of_get_property(node, "vdd_cam_1v2-supply", NULL)) { + priv->vdd_cam_1v2 = regulator_get(&client->dev, "vdd_cam_1v2"); + if (IS_ERR(priv->vdd_cam_1v2)) { + dev_err(&client->dev, + "vdd_cam_1v2 regulator get failed\n"); + err = PTR_ERR(priv->vdd_cam_1v2); + priv->vdd_cam_1v2 = NULL; + return err; + } + } else { + priv->vdd_cam_1v2 = NULL; + } + + return 0; +} +#else +static int max9296_parse_pdata(struct max9296 *priv, + struct i2c_client *client) +{ + struct max9296_pdata *pdata = client->dev.platform_data; + if (pdata) { + if (pdata->csi_mode == GMSL_CSI_2X4_MODE) { + priv->csi_mode = MAX9296_CSI_MODE_2X4; + priv->lane_mp1 = MAX9296_LANE_MAP1_2X4; + priv->lane_mp2 = MAX9296_LANE_MAP2_2X4; + } else if (pdata->csi_mode == GMSL_CSI_4X2_MODE) { + priv->csi_mode = MAX9296_CSI_MODE_4X2; + priv->lane_mp1 = MAX9296_LANE_MAP1_4X2; + priv->lane_mp2 = MAX9296_LANE_MAP2_4X2; + } else { + dev_err(&client->dev, "invalid csi mode\n"); + return -EINVAL; + } + priv->max_src = pdata->max_src; + if (pdata->d4xx_hacks) { + dev_info(&client->dev, "%s: set for d4xx\n", __func__); + priv->d4xx_hacks = pdata->d4xx_hacks; + } else { + priv->d4xx_hacks = 0; + } + } else { + priv->csi_mode = MAX9296_CSI_MODE_2X4; + priv->lane_mp1 = MAX9296_LANE_MAP1_2X4; + priv->lane_mp2 = MAX9296_LANE_MAP2_2X4; + priv->max_src = 1; + priv->reset_gpio = 0; + priv->vdd_cam_1v2 = NULL; + priv->d4xx_hacks = 0; + } + return 0; +} + +#endif +static struct regmap_config max9296_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max9296_probe(struct i2c_client *client, + const struct i2c_device_id *id) +#else +static int max9296_probe(struct i2c_client *client) +#endif +{ + struct max9296 *priv; + int err = 0; + + dev_info(&client->dev, "[MAX9296]: probing GMSL Deserializer\n"); + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv->i2c_client = client; + priv->regmap = devm_regmap_init_i2c(priv->i2c_client, + &max9296_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(priv->regmap)); + return -ENODEV; + } +#ifdef CONFIG_OF + err = max9296_parse_dt(priv, client); + if (err) { + dev_err(&client->dev, "unable to parse dt\n"); + return -EFAULT; + } +#else + err = max9296_parse_pdata(priv, client); + if (err) { + dev_err(&client->dev, "unable to parse pdata\n"); + return -EFAULT; + } +#endif + max9296_pipes_reset(priv); + + if (priv->max_src > MAX9296_MAX_SOURCES) { + dev_err(&client->dev, + "max sources more than currently supported\n"); + return -EINVAL; + } + + mutex_init(&priv->lock); + + dev_set_drvdata(&client->dev, priv); + + /* dev communication gets validated when GMSL link setup is done */ + dev_info(&client->dev, "%s: success\n", __func__); + + return err; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max9296_remove(struct i2c_client *client) +#else +static void max9296_remove(struct i2c_client *client) +#endif +{ + struct max9296 *priv; + + if (client != NULL) { + priv = dev_get_drvdata(&client->dev); + dev_info(&client->dev, "[MAX9296]: remove GMSL Deserializer\n"); + mutex_destroy(&priv->lock); +#ifdef CONFIG_OF + i2c_unregister_device(client); + client = NULL; +#endif + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +static const struct i2c_device_id max9296_id[] = { + { "d4xx-max9296", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max9296_id); + +static struct i2c_driver max9296_i2c_driver = { + .driver = { + .name = "d4xx-max9296", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max9296_of_match), + }, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + .probe = max9296_probe, +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + .probe_new = max9296_probe, +#else + .probe = max9296_probe, +#endif + .remove = max9296_remove, + .id_table = max9296_id, +}; + +static int __init max9296_init(void) +{ + return i2c_add_driver(&max9296_i2c_driver); +} + +static void __exit max9296_exit(void) +{ + i2c_del_driver(&max9296_i2c_driver); +} + +module_init(max9296_init); +module_exit(max9296_exit); + +MODULE_DESCRIPTION("Dual GMSL Deserializer driver max9296"); +MODULE_AUTHOR("Sudhir Vyas +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include +#include + + +/* MAX96724 register specifics */ +#define MAX96724_FLD_OFS(n, bits_per_field, count) (((n) % (count)) * (bits_per_field)) +#define MAX96724_OFFSET_GENMASK(offset, h, l) GENMASK(offset + h, offset + l) +#define MAX96724_FIELD_PREP(_mask, _val) \ + ({ \ + ((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \ + }) + + +#define MAX96724_REG3_ADDR 0x0003 /* I2C Remote Control Channel Enable */ +#define MAX96724_REG5_ADDR 0x0005 +#define MAX96724_REG4_ADDR 0x0004 +#define MAX96724_REG6_ADDR 0x0006 +#define MAX96724_REG10_ADDR 0x0010 /* PHY A/B RX/TX rate control */ +#define MAX96724_REG11_ADDR 0x0011 /* PHY C/D RX/TX rate control */ +#define MAX96724_PWR1_ADDR 0x0013 /* RESET_ALL */ +#define MAX96724_REG14_ADDR 0x0014 +#define MAX96724_REG15_ADDR 0x0015 +#define MAX96724_REG16_ADDR 0x0016 +#define MAX96724_REG17_ADDR 0x0017 /* Internal regulator control */ +#define MAX96724_REG18_ADDR 0x0018 /* RESET_ONESHOT control */ +#define MAX96724_REG19_ADDR 0x0019 /* Internal regulator control */ + +#define MAX96724_REG_POC_CTRL1 MAX96724_REG14_ADDR +#define MAX96724_REG_POC_CTRL2 MAX96724_REG15_ADDR +#define MAX96724_REG_POC_CTRL3 MAX96724_REG16_ADDR + +#define MAX96724_REG17_CTRL_EN_FIELD BIT(2) +#define MAX96724_REG19_CTRL_EN_FIELD BIT(4) + +#define MAX96724_REM_CC MAX96724_REG3_ADDR +#define MAX96724_REM_CC_DIS_PORT_FIELD(link, port) BIT(MAX96724_FLD_OFS(link, 2, 4) + (port % 2)) + +#define MAX96724_LINK_CTRL_ADDR MAX96724_REG6_ADDR +#define MAX96724_LINK_CTRL_GMSL_FIELD(link) BIT(link) << 4 +#define MAX96724_LINK_CTRL_EN_FIELD(link) BIT(link) + +#define MAX96724_RESET_CTRL_ADDR MAX96724_REG18_ADDR +#define MAX96724_RESET_CTRL_FIELD(link) BIT(link) +#define MAX96724_RESET_LINK_FIELD(link) BIT(link) << 4 + +#define MAX967XX_DEV_ID_ADDR 0x000D +#define MAX96712_DEV_REV_ID 0xA0 +#define MAX96724_DEV_REV_ID 0xA2 + +#define MAX967XX_DEV_REV_ADDR 0x004C +#define MAX967XX_DEV_REV_FIELD GENMASK(3, 0) + +/* Note that CSIs and pipes overlap: + * 0x901 through 0x90A and 0x933 through 0x934 are CSIs, repeated every 0x40 up to 4 times + * 0x90B through 0x932 are pipes, repeated every 0x40 up to 4 times + */ +#define MAX96724_MIPI_TX0 0x0900 +#define MAX96724_MIPI_TX(pipe) (MAX96724_MIPI_TX0 + ((pipe) * 0x40)) + +#define MAX96724_MIPI_TX_DESKEW_INIT(csi) (MAX96724_MIPI_TX(csi) + 0x03) +#define MAX96724_MIPI_TX_LANE_CNT(csi) (MAX96724_MIPI_TX(csi) + 0x0A) +#define MAX96724_MIPI_TX_ALT_MEM(csi) (MAX96724_MIPI_TX(csi) + 0x33) + +#define MAX96724_MAP_CON_SYNC_PIPE_FIELD GENMASK(5, 4) +#define MAX96724_MAP_CON_SYNC_PIPE_MASTER(pipe) (((pipe) << 4) & MAX96724_MAP_CON_SYNC_PIPE_FIELD) +#define MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(pipe) BIT(pipe) +#define MAX96724_MAP_CON_SYNC_PIPE_0_FIELD MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(0) +#define MAX96724_MAP_CON_SYNC_PIPE_1_FIELD MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(1) +#define MAX96724_MAP_CON_SYNC_PIPE_2_FIELD MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(2) +#define MAX96724_MAP_CON_SYNC_PIPE_3_FIELD MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(3) +#define MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD BIT(7) + +#define MAX96724_MAP_STATUS(pipe) (MAX96724_MIPI_TX(pipe) + 0x02) +#define MAX96724_MAP_EN_L(pipe) (MAX96724_MIPI_TX(pipe) + 0x0B) +#define MAX96724_MAP_EN_H(pipe) (MAX96724_MIPI_TX(pipe) + 0x0C) +#define MAX96724_MAP_SRCDST_EN_FIELD(map) BIT(map) + +#define MAX96724_MAP_SRC_L(pipe, map) (MAX96724_MIPI_TX(pipe) + 0x0D + ((map) * 2)) +#define MAX96724_MAP_DST_L(pipe, map) (MAX96724_MIPI_TX(pipe) + 0x0D + ((map) * 2) + 1) + +#define MAX96724_MAP_PHY_DEST(pipe, map) (MAX96724_MIPI_TX(pipe) + 0x2D + ((map) / 4)) +#define MAX96724_MAP_DPHY_DEST_FIELD(map) GENMASK(1, 0) << MAX96724_FLD_OFS(map, 2, 4) + +#define MAX96724_MIPI_TX_MAP_CON(pipe) (MAX96724_MIPI_TX(pipe) + 0x31) + +#define MAX96724_MIPI_TX_EXT0 0x0800 +#define MAX96724_MIPI_TX_EXT(pipe) (MAX96724_MIPI_TX_EXT0 + ((pipe) * 0x10)) +#define MAX96724_MAP_SRCDST_H(pipe, map) (MAX96724_MIPI_TX_EXT(pipe) + (map)) + +/* Video Pipe MIPI Lanes deskew init */ +#define MAX96724_DESKEW_INIT0_ADDR MAX96724_MIPI_TX_DESKEW_INIT(0) +#define MAX96724_DESKEW_INIT1_ADDR MAX96724_MIPI_TX_DESKEW_INIT(1) +#define MAX96724_DESKEW_INIT2_ADDR MAX96724_MIPI_TX_DESKEW_INIT(2) +#define MAX96724_DESKEW_INIT3_ADDR MAX96724_MIPI_TX_DESKEW_INIT(3) +#define MAX96724_MIPI_TX_DESKEW_INIT_AUTO_FIELD BIT(7) +#define MAX96724_MIPI_TX_DESKEW_WIDTH_FIELD GENMASK(2, 0) + +/* Video Pipe MIPI Lanes Controls */ +#define MAX96724_LANE_CTRL0_ADDR MAX96724_MIPI_TX_LANE_CNT(0) +#define MAX96724_LANE_CTRL1_ADDR MAX96724_MIPI_TX_LANE_CNT(1) +#define MAX96724_LANE_CTRL2_ADDR MAX96724_MIPI_TX_LANE_CNT(2) +#define MAX96724_LANE_CTRL3_ADDR MAX96724_MIPI_TX_LANE_CNT(3) +#define MAX96724_MIPI_TX_LANE_CNT_FIELD GENMASK(7, 6) +#define MAX96724_MIPI_TX_CPHY_EN_FIELD BIT(5) +#define MAX96724_MIPI_TX_VCX_EN_FIELD BIT(4) +#define MAX96724_MIPI_TX_WAKEUP_CYC_FIELD GENMASK(2, 0) + + + +#define MAX96724_LANE_CTRL_MAP(num_lanes) \ + (((num_lanes) << 6) & MAX96724_MIPI_TX_LANE_CNT_FIELD) + +#define MAX96724_CSI_DPHY 0x0 +#define MAX96724_CSI_CPHY 0x1 +#define MAX96724_CSI_MODE_1X4 0x0 +#define MAX96724_CSI_MODE_4X2 0x1 +#define MAX96724_CSI_MODE_2X4 0x4 + +/* Video Pipe MIPI Mapping Enable */ +#define MAX96724_PIPE_X_MIPI_EN MAX96724_MAP_EN_L(0) +#define MAX96724_MAP_EN_FIELD GENMASK(7, 0) +#define MAX96724_PIPE_X_MAP_CON_ADDR()AX96724_MAP_CON(0) + +/* Video Pipe X Cross Mapping baseline */ +#define MAX96724_PIPE_X_EN MAX96724_MAP_EN_L(0) +#define MAX96724_PIPE_X_EN_L_ADDR MAX96724_MAP_EN_L(0) +#define MAX96724_PIPE_X_EN_H_ADDR MAX96724_MAP_EN_H(0) +#define MAX96724_PIPE_X_SRC_0_MAP_ADDR MAX96724_MAP_SRC_L(0,0) +#define MAX96724_PIPE_X_DST_0_MAP_ADDR MAX96724_MAP_DST_L(0,0) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_0_MAP_ADDR MAX96724_MAP_SRCDST_H(0,0) +#define MAX96724_PIPE_X_SRC_1_MAP_ADDR MAX96724_MAP_SRC_L(0,1) +#define MAX96724_PIPE_X_DST_1_MAP_ADDR MAX96724_MAP_DST_L(0,1) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_1_MAP_ADDR MAX96724_MAP_SRCDST_H(0,1) +#define MAX96724_PIPE_X_SRC_2_MAP_ADDR MAX96724_MAP_SRC_L(0,2) +#define MAX96724_PIPE_X_DST_2_MAP_ADDR MAX96724_MAP_DST_L(0,2) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_2_MAP_ADDR MAX96724_MAP_SRCDST_H(0,2) +#define MAX96724_PIPE_X_SRC_3_MAP_ADDR MAX96724_MAP_SRC_L(0,3) +#define MAX96724_PIPE_X_DST_3_MAP_ADDR MAX96724_MAP_DST_L(0,3) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_3_MAP_ADDR MAX96724_MAP_SRCDST_H(0,3) +#define MAX96724_PIPE_X_SRC_4_MAP_ADDR MAX96724_MAP_SRC_L(0,4) +#define MAX96724_PIPE_X_DST_4_MAP_ADDR MAX96724_MAP_DST_L(0,4) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_4_MAP_ADDR MAX96724_MAP_SRCDST_H(0,4) +#define MAX96724_PIPE_X_SRC_5_MAP_ADDR MAX96724_MAP_SRC_L(0,5) +#define MAX96724_PIPE_X_DST_5_MAP_ADDR MAX96724_MAP_DST_L(0,5) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_5_MAP_ADDR MAX96724_MAP_SRCDST_H(0,5) +#define MAX96724_PIPE_X_SRC_6_MAP_ADDR MAX96724_MAP_SRC_L(0,6) +#define MAX96724_PIPE_X_DST_6_MAP_ADDR MAX96724_MAP_DST_L(0,6) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_6_MAP_ADDR MAX96724_MAP_SRCDST_H(0,6) +#define MAX96724_PIPE_X_SRC_7_MAP_ADDR MAX96724_MAP_SRC_L(0,7) +#define MAX96724_PIPE_X_DST_7_MAP_ADDR MAX96724_MAP_DST_L(0,7) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_7_MAP_ADDR MAX96724_MAP_SRCDST_H(0,7) +#define MAX96724_PIPE_X_SRC_8_MAP_ADDR MAX96724_MAP_SRC_L(0,8) +#define MAX96724_PIPE_X_DST_8_MAP_ADDR MAX96724_MAP_DST_L(0,8) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_8_MAP_ADDR MAX96724_MAP_SRCDST_H(0,8) +#define MAX96724_PIPE_X_SRC_9_MAP_ADDR MAX96724_MAP_SRC_L(0,9) +#define MAX96724_PIPE_X_DST_9_MAP_ADDR MAX96724_MAP_DST_L(0,9) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_9_MAP_ADDR MAX96724_MAP_SRCDST_H(0,9) +#define MAX96724_PIPE_X_SRC_10_MAP_ADDR MAX96724_MAP_SRC_L(0,10) +#define MAX96724_PIPE_X_DST_10_MAP_ADDR MAX96724_MAP_DST_L(0,10) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_10_MAP_ADDR MAX96724_MAP_SRCDST_H(0,10) +#define MAX96724_PIPE_X_SRC_11_MAP_ADDR MAX96724_MAP_SRC_L(0,11) +#define MAX96724_PIPE_X_DST_11_MAP_ADDR MAX96724_MAP_DST_L(0,11) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_11_MAP_ADDR MAX96724_MAP_SRCDST_H(0,11) +#define MAX96724_PIPE_X_SRC_12_MAP_ADDR MAX96724_MAP_SRC_L(0,12) +#define MAX96724_PIPE_X_DST_12_MAP_ADDR MAX96724_MAP_DST_L(0,12) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_12_MAP_ADDR MAX96724_MAP_SRCDST_H(0,12) +#define MAX96724_PIPE_X_SRC_13_MAP_ADDR MAX96724_MAP_SRC_L(0,13) +#define MAX96724_PIPE_X_DST_13_MAP_ADDR MAX96724_MAP_DST_L(0,13) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_13_MAP_ADDR MAX96724_MAP_SRCDST_H(0,13) +#define MAX96724_PIPE_X_SRC_14_MAP_ADDR MAX96724_MAP_SRC_L(0,14) +#define MAX96724_PIPE_X_DST_14_MAP_ADDR MAX96724_MAP_DST_L(0,14) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_14_MAP_ADDR MAX96724_MAP_SRCDST_H(0,14) +#define MAX96724_PIPE_X_SRC_15_MAP_ADDR MAX96724_MAP_SRC_L(0,15) +#define MAX96724_PIPE_X_DST_15_MAP_ADDR MAX96724_MAP_DST_L(0,15) +#define MAX96724_PIPE_X_SRCDST_VC_EXT_15_MAP_ADDR MAX96724_MAP_SRCDST_H(0,15) + +#define MAX96724_PIPE_X_PHY_DEST_0_MAP_ADDR MAX96724_MAP_PHY_DEST(0,0) +#define MAX96724_PIPE_X_PHY_DEST_1_MAP_ADDR MAX96724_MAP_PHY_DEST(0,4) +#define MAX96724_PIPE_X_PHY_DEST_2_MAP_ADDR MAX96724_MAP_PHY_DEST(0,8) +#define MAX96724_PIPE_X_PHY_DEST_3_MAP_ADDR MAX96724_MAP_PHY_DEST(0,12) + +#define MAX96724_PIPE_X_STATUS_0_MAP_ADDR MAX96724_MAP_STATUS(0) +#define MAX96724_PIPE_X_STATUS_1_MAP_ADDR MAX96724_MAP_STATUS(1) +#define MAX96724_PIPE_X_STATUS_2_MAP_ADDR MAX96724_MAP_STATUS(2) +#define MAX96724_PIPE_X_STATUS_3_MAP_ADDR MAX96724_MAP_STATUS(3) + +#define MAX96724_MAP_SRC_L_VC_FIELD GENMASK(7, 6) +#define MAX96724_MAP_SRC_L_DT_FIELD GENMASK(5, 0) +#define MAX96724_MAP_DST_L_VC_FIELD GENMASK(7, 6) +#define MAX96724_MAP_DST_L_DT_FIELD GENMASK(5, 0) + +/* Video Pipe X to alternative memory Mapping */ +#define MAX96724_PIPE_X_ALT_MEM_ADDR MAX96724_MIPI_TX_ALT_MEM(0) +#define MAX96724_MIPI_TX_ALT_MEM_FIELD GENMASK(4, 0) +#define MAX96724_MIPI_TX_ALT_MEM_8BPP BIT(1) +#define MAX96724_MIPI_TX_ALT_MEM_10BPP BIT(2) +#define MAX96724_MIPI_TX_ALT_MEM_12BPP BIT(0) +#define MAX96724_MIPI_TX_ALT_MEM_24BPP BIT(3) +#define MAX96724_MIPI_TX_ALT2_MEM_8BPP BIT(4) + +/* Video Pipe X to PHY Mapping */ +#define MAX96724_PIPE_X_SRCDST_0_MAP_ADDR MAX96724_MAP_SRCDST_H(0,0) +#define MAX96724_PIPE_X_SRCDST_1_MAP_ADDR MAX96724_MAP_SRCDST_H(0,1) +#define MAX96724_PIPE_X_SRCDST_2_MAP_ADDR MAX96724_MAP_SRCDST_H(0,2) +#define MAX96724_PIPE_X_SRCDST_3_MAP_ADDR MAX96724_MAP_SRCDST_H(0,3) +#define MAX96724_MAP_SRCDST_H_SRC_VC_FIELD GENMASK(7, 5) +#define MAX96724_MAP_SRCDST_H_DST_VC_FIELD GENMASK(4, 2) + +#define MAX96724_VID_RX0_ADDR 0x0100 +#define MAX96724_VID_RX0(pipe) (MAX96724_VID_RX0_ADDR + (0x12 * (pipe))) +#define MAX96724_VID_RX6_ADDR 0x0106 +#define MAX96724_VID_RX6(pipe) (MAX96724_VID_RX6_ADDR + (0x12 * (pipe))) +#define MAX96724_VID_RX8_ADDR 0x0108 +#define MAX96724_VID_STATUS_ADDR(pipe) (MAX96724_VID_RX8_ADDR + (0x12 * (pipe))) +#define MAX96724_VID_LOCK_BIT BIT(6) + +#define MAX96724_PIPE_DE_STATUS_ADDR 0x11F0 +#define MAX96724_PIPE_HS_STATUS_ADDR 0x11F1 +#define MAX96724_PIPE_VS_STATUS_ADDR 0x11F2 + +#define MAX96724_PIPE_X_ST_SEL_ADDR 0x0050 + +/* Video Pipe Input Source Selection + * This tells MAX96724 which PHY (Link) feeds which Video Pipe. + * Without this, video data cannot flow from GMSL links to CSI-2 output. + * + * REG 0x00F0: Video Pipe 0/1 input selection + * Bit[7:6] = 01: Pipe 1 from GMSL B link + * Bit[5:4] = 10: Pipe 1 uses internal Pipe X + * Bit[3:2] = 00: Pipe 0 from GMSL A link + * Bit[1:0] = 10: Pipe 0 uses internal Pipe X + * Value: 0xF0 + * + * REG 0x00F1: Video Pipe 2/3 input selection + * Bit[7:6] = 11: Pipe 3 from GMSL D link + * Bit[5:4] = 10: Pipe 3 uses internal Pipe X + * Bit[3:2] = 10: Pipe 2 from GMSL C link + * Bit[1:0] = 10: Pipe 2 uses internal Pipe X + * Value: 0xEA + * + * REG 0x00F4: Enable all Video Pipes + * Bit[3:0] = 1111: Enable Pipe 3/2/1/0 + * Value: 0x0F + */ +#define MAX96724_VIDEO_PIPE_SEL_0_ADDR 0x00F0 /* Pipe 0/1 input: GMSL A/B */ +#define MAX96724_VIDEO_PIPE_SEL_1_ADDR 0x00F1 /* Pipe 2/3 input: GMSL C/D */ +#define MAX96724_VIDEO_PIPE_SEL_2_ADDR 0x00F2 /* Pipe 4/5 input: GMSL A/B max96712 only */ +#define MAX96724_VIDEO_PIPE_SEL_3_ADDR 0x00F3 /* Pipe 6/7 input: GMSL C/D max96712 only */ +#define MAX96724_VIDEO_PIPE_EN_ADDR 0x00F4 /* Enable Video Pipes */ +#define MAX96724_VIDEO_PIPE_SEL(pipe) (MAX96724_VIDEO_PIPE_SEL_0_ADDR + ((pipe) / 2)) +#define MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(link) GENMASK(1, 0) << (MAX96724_FLD_OFS(link, 4, 2) + 2) +#define MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(link) GENMASK(1, 0) << (MAX96724_FLD_OFS(link, 4, 2) + 0) +#define MAX96724_VIDEO_PIPE_EN(pipe) (MAX96724_VIDEO_PIPE_EN_ADDR) +#define MAX96724_VIDEO_PIPE_EN_FIELD(pipe) BIT(pipe) +#define MAX96724_VIDEO_PIPE_STREAM_SEL_ALL_FIELD BIT(4) +#define MAX96724_VIDEO_PIPE_LEGACY_MODE 0U +#define MAX96724_VIDEO_PIPE_SRCMAP_MODE 1U + +/* MIPI CSI-2 PHY Configuration */ +#define MAX96724_PHY_RATE_CTRL_ADDR MAX96724_REG10_ADDR +#define MAX96724_PHY_RATE_CTRL(link) (MAX96724_PHY_RATE_CTRL_ADDR + ((link) / 2)) +#define MAX96724_PHY_RATE_CTRL_TX_FIELD(link) GENMASK(1, 0) << (MAX96724_FLD_OFS(link, 4, 2) + 2) +#define MAX96724_PHY_RATE_CTRL_RX_FIELD(link) GENMASK(1, 0) << (MAX96724_FLD_OFS(link, 4, 2) + 0) +#define MAX96724_PHY_RATE_CTRL_TX_6GBPS 0x10 +#define MAX96724_PHY_RATE_CTRL_TX_3GBPS 0x01 + +#define MAX96724_BACKTOP1_ADDR 0x0400 +#define MAX96724_DPLL_STATUS_ADDR MAX96724_BACKTOP1_ADDR +#define MAX96724_DPLL_STATUS_FIELD(phy) BIT(phy) << 4 + +#define MAX96724_BACKTOP22_ADDR 0x0415 +#define MAX96724_DPLL_FREQ(phy) (MAX96724_BACKTOP22_ADDR + ((phy) * 3)) +#define MAX96724_PHY0_CLK_ADDR MAX96724_DPLL_FREQ(0) +#define MAX96724_PHY1_CLK_ADDR MAX96724_DPLL_FREQ(1) +#define MAX96724_PHY2_CLK_ADDR MAX96724_DPLL_FREQ(3) +#define MAX96724_PHY3_CLK_ADDR MAX96724_DPLL_FREQ(3) +#define MAX96724_DPLL_FREQ_FIELD GENMASK(4, 0) + +#define MAX96724_BACKTOP12_ADDR 0x040B +#define MAX96724_CSI_OUT_EN_ADDR MAX96724_BACKTOP12_ADDR +#define MAX96724_CSI_OUT_EN_FIELD BIT(1) + +#define MAX96724_DPLL_RESET(phy) (0x1C00 + ((phy) * 0x100)) +#define MAX96724_DPLL_RESET_SOFT_RST_FIELD BIT(0) + +#define MAX96724_MIPI_PHY0 0x08A0 +#define MAX96724_MIPI_PHY0_MODE_FIELD GENMASK(4, 0) +#define MAX96724_MIPI_PHY0_CLK_SEL_EN_FIELD(csi) BIT((csi) / 2) +#define MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD BIT(7) + +#define MAX96724_MIPI_PHY2 0x08A2 +#define MAX96724_MIPI_PHY_ENABLE MAX96724_MIPI_PHY2 +#define MAX96724_PWDN_PHYS_ADDR MAX96724_MIPI_PHY_ENABLE +#define MAX96724_MIPI_PHY_ENABLE_FIELD(csi) BIT((csi) + 4) + +#define MAX96724_MIPI_PHY3 0x08A3 +#define MAX96724_MIPI_PHY_LANE_MAP(csi) (MAX96724_MIPI_PHY3 + (csi) / 2) +#define MAX96724_MIPI_PHY_LANE_MAP_FIELD(csi, lane) \ + (GENMASK(1, 0) << (MAX96724_FLD_OFS(csi, 4, 2) + MAX96724_FLD_OFS(lane, 2, 2))) + +#define MAX96724_MIPI_PHY4 0x08A4 +#define MAX96724_MIPI_PHY5 0x08A5 +#define MAX96724_MIPI_PHY13 0x08AD +#define MAX96724_CPHY_PREAMBLE_ADDR MAX96724_MIPI_PHY13 +#define MAX96724_CPHY_PREAMBLE_FIELD GENMASK(5, 0) + +#define MAX96724_MIPI_PHY14 0x08AE +#define MAX96724_CPHY_PREP_ADDR MAX96724_MIPI_PHY14 +#define MAX96724_CPHY_PREP_LENGTH_FIELD GENMASK(1, 0) +#define MAX96724_CPHY_POST_TIMING_FIELD GENMASK(6, 2) + +/* GPIO Configuration Registers */ + +#define MAX96724_GPIO_REG(n) (0x0300 + (3 * n)) +#define MAX96724_GPIO_RES_CFG BIT(7) +#define MAX96724_GPIO_TX_ENABLE BIT(1) +#define MAX96724_GPIO_OUTDRV_DISABLE BIT(0) +#define MAX96724_GPIO_PUSH_PULL BIT(5) +#define MAX96724_GPIO_A_REG(n) (MAX96724_GPIO_REG(n) + 1) +#define MAX96724_GPIO_B_REG(n) \ + ((n <= 2) ? (0x0337 + 3 * n) : (n <= 7) ? (0x341 + 3 * (n - 3)) \ + : (0x351 + 3 * (n - 8))) +#define MAX96724_GPIO_C_REG(n) \ + ((n == 0) ? (0x036D) : (n <= 5) ? (0x371 + 3 * (n - 1)) \ + : (0x381 + 3 * (n - 6))) +#define MAX96724_GPIO_D_REG(n) \ + ((n <= 3) ? (0x03A4 + 3 * n) : (n <= 8) ? (0x3B1 + 3 * (n - 4)) \ + : (0x3C1 + 3 * (n - 9))) + +/* According to MAX96724 spec: + * GPIO7_A (RES_CFG, TX_PRIO, TX_COMP_EN, GPIO_OUT, GPIO_IN, GPIO_RX_EN, GPIO_TX_EN, GPIO_OUT_DIS) + * GPIO7_B (PULL_UPDN_SEL[1:0], GPIO_TX_ID[4:0]) + * GPIO7_C (OVR_RES_CFG, GPIO_RX_ID[4:0]) + * These are GPIO configuration registers, NOT CSI mode or lane mapping registers. + */ +#define MAX96724_GPIO5_A MAX96724_GPIO_A_REG(5) +#define MAX96724_GPIO5_B MAX96724_GPIO_B_REG(5) +#define MAX96724_GPIO5_C MAX96724_GPIO_C_REG(5) +#define MAX96724_GPIO6_A MAX96724_GPIO_A_REG(6) + +#define MAX96724_GPIO7_C MAX96724_GPIO_C_REG(7) + +#define MAX96712_GPIO7_C 0x0318 +/* GPIO Configuration Registers */ +#define MAX96712_GPIO15_A 0x0330 /* GPIO15_A: RES_CFG, TX_PRIO, TX_COMP_EN, GPIO_OUT, GPIO_IN, GPIO_RX_EN, GPIO_TX_EN, GPIO_OUT_DIS */ +#define MAX96712_GPIO15_B 0x0331 /* GPIO15_B: PULL_UPDN_SEL[1:0], GPIO_TX_ID[4:0] */ +#define MAX96712_GPIO15_C 0x0332 /* GPIO15_C: OVR_RES_CFG, GPIO_RX_ID[4:0] */ +#define MAX96712_GPIO16_A 0x0333 /* GPIO16_A: RES_CFG, TX_PRIO, TX_COMP_EN, GPIO_OUT, GPIO_IN, GPIO_RX_EN, GPIO_TX_EN, GPIO_OUT_DIS */ + +#define MAX96724_CTRL1_ADDR MAX96724_REG18_ADDR + +/* data defines */ + +#define MAX96724_ALLPHYS_NOSTDBY 0xF0 +#define MAX96724_ST_ID_SEL_INVALID 0xF + +#define MAX96724_CPHY_PREAMBLE_DEFAULT 0x1f +#define MAX96724_CPHY_PREP_DEFAULT 0x5d + +// write 0x20 for 1500Mbps/lane on CSI2 controller & CPHY +#define MAX96724_CPHY_CLK_1500BPS 0x26 +#define MAX96724_CPHY_CLK_2000BPS 0x29 +// write 0x2f for 1500Mbps/lane on CSI2 controller & DPHY +#define MAX96724_DPHY_CLK_1500BPS 0x34 + +#define MAX96724_RESET_ALL 0x80 + +/* MAX96724 Quad GMSL Deserializer: + * - 4 GMSL input links (Link A/B/C/D from serializers) + * - 4 video pipelines (Pipe X/Y/Z/U) + * - 2 CSI-2 output controllers (2x4-lane or 4x2-lane modes) + */ +#define MAX96724_MAX_SOURCES 4 +#define MAX96724_NUM_CSI_LINKS 4 +#define MAX96724_MAX_PIPES 4 +#define MAX96724_MAX_VCX 8 + +#define MAX96724_DEFAULT_SER_ADDR_A 0x28 +#define MAX96724_DEFAULT_SER_ADDR_B 0x2A +#define MAX96724_RESET_DELAY_MS 100 + +#define MAX96724_PIPE_X 0 +#define MAX96724_PIPE_Y 1 +#define MAX96724_PIPE_Z 2 +#define MAX96724_PIPE_U 3 +#define MAX96724_PIPE_INVALID 0xF + +/* CSI Controller IDs (not register addresses) */ +#define MAX96724_CSI_CTRL_ID_0 0 +#define MAX96724_CSI_CTRL_ID_1 1 +#define MAX96724_CSI_CTRL_ID_2 2 +#define MAX96724_CSI_CTRL_ID_3 3 + +#define MAX96724_INVAL_ST_ID 0xFF + +/* Use reset value as per spec, confirm with vendor */ +#define MAX96724_RESET_ST_ID 0x00 + +#define MAX96724_LINK_STATUS(link) ((link) == 0 ? 0x1A : 0x0A + ((link) - 1)) +#define MAX96724_LINK_LOCK_BIT BIT(3) + +#define MAX96724_GMSL_MODE_REG 0x06 +#define MAX96724_GMSL_MODE_PER_LINK_MASK 0x3 +#define MAX96724_GMSL_MODE_AUTO 0x0 +#define MAX96724_GMSL_MODE_GMSL2 0x1 +#define MAX96724_GMSL_MODE_GMSL1 0x2 +#define MAX96724_GMSL_MODE_RESERVED 0x3 + +#define MAX96724_VC_2_BITS 0U +#define MAX96724_VC_4_BITS 1U + +// See: Errata for MAX96724/MAX96724F/MAX96724R (Rev. 1) document +// https://www.analog.com/media/en/technical-documentation/data-sheets/max96724-f-r-rev1-b-0a-errata.pdf +static const struct reg_sequence max96724_errata_rev1[] = { + // Errata #5 - GMSL2 Link requires register writes for robust 6 Gbps operation + { 0x1449, 0x75, }, + { 0x1549, 0x75, }, + { 0x1649, 0x75, }, + { 0x1749, 0x75, }, +}; + +enum max96724_rev { + MAX96724_REV_F = 1, + MAX96724_REV_R, +}; + +static const struct reg_sequence max96712_phy_tuning_revABC[] = { + // PHY A + { 0x1458, 0x28, }, + { 0x1459, 0x68, }, + { 0x143E, 0xB3, }, + { 0x143F, 0x72, }, + // PHY B + { 0x1558, 0x28, }, + { 0x1559, 0x68, }, + { 0x153E, 0xB3, }, + { 0x153F, 0x72, }, + // PHY C + { 0x1658, 0x28, }, + { 0x1659, 0x68, }, + { 0x163E, 0xB3, }, + { 0x163F, 0x72, }, + // PHY D + { 0x1758, 0x28, }, + { 0x1759, 0x68, }, + { 0x173E, 0xB3, }, + { 0x173F, 0x72, }, +}; + +static const struct reg_sequence max96712_phy_tuning_revE[] = { + // Increase CMU regulator output voltage (bit 4) + { 0x06C2, 0x10, }, + // Set VgaHiGain_Init_6G (bit 1) and VgaHiGain_Init_3G (bit 0)* for PHY A/B/C/D + { 0x14D1, 0x03, }, + { 0x15D1, 0x03, }, + { 0x16D1, 0x03, }, + { 0x17D1, 0x03, }, +}; + +enum max96712_rev { + MAX96712_REV_A = 1, + MAX96712_REV_B, + MAX96712_REV_C, + MAX96712_REV_D, + MAX96712_REV_E, +}; + +/* Log all register writes */ +#define MAX96724_WRITE_REG(map, reg, val) \ +({ \ + int __ret; \ + struct regmap *__map = (map); \ + unsigned int __reg = (reg); \ + unsigned int __val = (val); \ + dev_dbg(regmap_get_device(__map), "REG_WRITE: [0x%04X] = 0x%02X", __reg, __val); \ + __ret = regmap_write(__map, __reg, __val); \ + if (__ret) \ + dev_err(regmap_get_device(__map), "REG_WRITE FAILED: [0x%04X] = 0x%02X, ret=%d", __reg, __val, __ret); \ + __ret; \ +}) + +/* Log register update_bits (read-modify-write) */ +#define MAX96724_UPDATE_BITS(map, reg, mask, val) \ +({ \ + int __ret; \ + struct regmap *__map = (map); \ + unsigned int __reg = (reg); \ + unsigned int __mask = (mask); \ + unsigned int __val = (val); \ + unsigned int __old_val = 0; \ + unsigned int __new_val = 0; \ + regmap_read(__map, __reg, &__old_val); \ + __new_val = (__old_val & ~__mask) | (__val & __mask); \ + dev_dbg(regmap_get_device(__map), "REG_UPDATE_BITS: [0x%04X] mask=0x%02X, old=0x%02X, new=0x%02X (val=0x%02X)", \ + __reg, __mask, __old_val, __new_val, __val); \ + __ret = regmap_update_bits(__map, __reg, __mask, __val); \ + if (__ret) \ + dev_err(regmap_get_device(__map), "REG_UPDATE_BITS FAILED: [0x%04X] mask=0x%02X val=0x%02X, ret=%d", \ + __reg, __mask, __val, __ret); \ + __ret; \ +}) + +struct max96724_source_ctx { + struct gmsl_link_ctx *g_ctx; + bool st_enabled; +}; + +struct pipe_ctx { + u32 id; + u32 dt_type; + u32 dst_csi_ctrl; + u32 st_count; + u32 st_id_sel; + u32 src_link; +}; + +struct max96724 { + struct i2c_client *i2c_client; + struct regmap *regmap; + u32 num_src; + u32 max_src; + u32 num_src_found; + u32 src_link; + bool splitter_enabled; + struct max96724_source_ctx sources[MAX96724_MAX_SOURCES]; + struct mutex lock; + u32 sdev_ref; + bool lane_setup; + bool link_setup; + struct pipe_ctx pipe[MAX96724_MAX_PIPES]; + u8 csi_mode; + int reset_gpio; + int pw_ref; + struct regulator *vdd_cam_1v2; + __u32 d4xx_hacks; + u32 dev_id; + u32 dst_link; + u8 csi_phy; + u8 dst_n_lanes; +}; + +static int max96724_read_reg(struct device *dev, + u16 addr, unsigned int *val) +{ + struct max96724 *priv; + int err; + + priv = dev_get_drvdata(dev); + + err = regmap_read(priv->regmap, addr, val); + if (err) + dev_err(dev, + "%s:i2c read failed, 0x%x\n", + __func__, addr); + + return err; +} + +static int max96724_write_reg(struct device *dev, + u16 addr, u8 val) +{ + struct max96724 *priv; + int err; + + priv = dev_get_drvdata(dev); + + err = MAX96724_WRITE_REG(priv->regmap, addr, val); + if (err) + dev_err(dev, + "%s:i2c write failed, 0x%x = %x\n", + __func__, addr, val); + + /* delay before next i2c command as required for SERDES link */ + usleep_range(100, 110); + + return err; +} + +static const char *max96724_get_link_name(u32 link) +{ + switch (link) { + case GMSL_SERDES_CSI_LINK_A: + return "GMSL A"; + case GMSL_SERDES_CSI_LINK_B: + return "GMSL B"; + case GMSL_SERDES_CSI_LINK_C: + return "GMSL C"; + case GMSL_SERDES_CSI_LINK_D: + return "GMSL D"; + default: + return "GMSL UNKNOWN"; + } +} + +static u8 max96724_link_to_port(u32 link) +{ + switch (link) { + case GMSL_SERDES_CSI_LINK_B: + return 1; + case GMSL_SERDES_CSI_LINK_C: + return 2; + case GMSL_SERDES_CSI_LINK_D: + return 3; + case GMSL_SERDES_CSI_LINK_A: + default: + return 0; + } +} + +static int max96724_get_sdev_idx(struct device *dev, + struct device *s_dev, unsigned int *idx) +{ + struct max96724 *priv = dev_get_drvdata(dev); + unsigned int i; + int err = 0; + + mutex_lock(&priv->lock); + + for (i = 0; i < priv->max_src; i++) { + + if (!priv->sources[i].g_ctx || !priv->sources[i].g_ctx->s_dev) + continue; + + if (priv->sources[i].g_ctx->s_dev == s_dev) + break; + } + + if (i == priv->max_src) { + dev_err(dev, "no sdev found\n"); + err = -EINVAL; + goto ret; + } + + dev_dbg(dev, "%s: sdev found on %s (link_id=%u)\n", + __func__, + max96724_get_link_name(priv->sources[i].g_ctx->serdes_csi_link), + i); + + if (idx) + *idx = i; + +ret: + mutex_unlock(&priv->lock); + return err; +} + +static void max96724_pipes_reset(struct max96724 *priv) +{ + /* + * This is default pipes combination. add more mappings + * for other combinations and requirements. + */ + struct pipe_ctx pipe_defaults[] = { + {MAX96724_PIPE_X, GMSL_CSI_DT_RAW_12, + MAX96724_CSI_CTRL_ID_0, 0, MAX96724_INVAL_ST_ID, GMSL_SERDES_CSI_LINK_A}, + {MAX96724_PIPE_Y, GMSL_CSI_DT_RAW_12, + MAX96724_CSI_CTRL_ID_0, 0, MAX96724_INVAL_ST_ID, GMSL_SERDES_CSI_LINK_A}, + {MAX96724_PIPE_Z, GMSL_CSI_DT_EMBED, + MAX96724_CSI_CTRL_ID_0, 0, MAX96724_INVAL_ST_ID, GMSL_SERDES_CSI_LINK_A}, + {MAX96724_PIPE_U, GMSL_CSI_DT_EMBED, + MAX96724_CSI_CTRL_ID_0, 0, MAX96724_INVAL_ST_ID, GMSL_SERDES_CSI_LINK_A} + }; + + /* + * Add DT props for num-streams and stream sequence, and based on that + * set the appropriate pipes defaults. + * For now default it supports "2 RAW12 and 2 EMBED" 1:1 mappings. + */ + memcpy(priv->pipe, pipe_defaults, sizeof(pipe_defaults)); +} + +static void max96724_reset_ctx(struct max96724 *priv) +{ + unsigned int i = (unsigned int) max96724_link_to_port(priv->src_link); + + priv->link_setup = false; + priv->lane_setup = false; + priv->num_src_found = 0; + priv->sources[i].st_enabled = false; + //priv->src_link = 0; + priv->splitter_enabled = false; + max96724_pipes_reset(priv); +} + +int max96724_power_on(struct device *dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int err = 0; + + mutex_lock(&priv->lock); + if (priv->pw_ref == 0) { + usleep_range(1, 2); + if (priv->reset_gpio) + gpio_set_value(priv->reset_gpio, 0); + + usleep_range(30, 50); + + if (priv->vdd_cam_1v2) { + err = regulator_enable(priv->vdd_cam_1v2); + if (unlikely(err)) + goto ret; + } + + usleep_range(30, 50); + + /*exit reset mode: XCLR */ + if (priv->reset_gpio) { + gpio_set_value(priv->reset_gpio, 0); + usleep_range(30, 50); + gpio_set_value(priv->reset_gpio, 1); + usleep_range(30, 50); + } + + /* delay to settle reset */ + msleep(20); + } + + priv->pw_ref++; + +ret: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max96724_power_on); + +void max96724_power_off(struct device *dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + priv->pw_ref--; + + if (priv->pw_ref < 0) + priv->pw_ref = 0; + + if (priv->pw_ref == 0) { + /* enter reset mode: XCLR */ + usleep_range(1, 2); + if (priv->reset_gpio) + gpio_set_value(priv->reset_gpio, 0); + + if (priv->vdd_cam_1v2) + regulator_disable(priv->vdd_cam_1v2); + } + + mutex_unlock(&priv->lock); +} +EXPORT_SYMBOL(max96724_power_off); + +/* CRITICAL: max96724_check_status wait for sensor video to stabilize before enabling CSI output! + * 1. Triggers all cameras via GPIO (MFP7/MFP8) + * 2. sleep 0.3 ← 300ms delay to let sensors stabilize! + * 3. i2ctransfer ... 0x04 0x0b 0x02 ← Enable CSI + * 4. i2ctransfer ... 0x08 0xa0 0x84 ← Enable continuous clock + * + * Without this delay, deserializer tries to lock video before sensor + * outputs stable video data, resulting in VIDEO_LOCK failure (0x0108=0x02) + */ +int max96724_check_status(struct device *dev, u32 src_link) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int err = 0; + u8 src_port = max96724_link_to_port(src_link); + + mutex_lock(&priv->lock); + + /* Enable MIPI TX controller and enable PHY CLK cycle + dev_info(dev, "%s: Wake-up %s CSI %u %s mode %s (num_lanes:x%u)\n", + __func__, + max96724_get_link_name(src_link), + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes); + + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_LANE_CNT(priv->dst_link), + MAX96724_MIPI_TX_WAKEUP_CYC_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_TX_WAKEUP_CYC_FIELD, 1U)); + if (err) { + dev_err(dev, "%s: Failed to configure CSI %u %s wakeup cycles: %d\n", + __func__, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + // enable CSI out link after initialization complet + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_CSI_OUT_EN_ADDR, + MAX96724_CSI_OUT_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_CSI_OUT_EN_FIELD, 1U)); + if (err) { + dev_err(dev, "%s: Failed to enable csi output link: %d\n", + __func__, + err); + } + */ + + /* Turn on MIPI PHY continuous clock mode (matches script line 520) + * Reference script does this AFTER CSI enable (0x040B = 0x02) + * This enables continuous clock on MIPI CSI-2 interface + * 0x84 = 0b10000100 + * Bit[7] = 1: Enable continuous clock mode + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_PHY0, + MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD, 1U)); + if (err) + dev_err(dev, "%s: Failed to enable %s %s continuous clock : %d\n", + __func__, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + err); + else + dev_dbg(dev, "%s: %s CSI %u %s %s (num_lanes:x%u) continuous clock mode enabled\n", + __func__, + max96724_get_link_name(src_link), + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes); + */ + + /* Re-Check GMSL link status after initial configuration */ + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(src_port), &link_status); + dev_dbg(dev, "%s: %s Link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + unsigned int pll_status = 0; + max96724_read_reg(dev, MAX96724_DPLL_STATUS_ADDR, &pll_status); + dev_dbg(dev, "%s: %s DPLL status: 0x%02x (bit0-7: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pll_status, + (pll_status & MAX96724_DPLL_STATUS_FIELD(0)) ? "CSIPLL0_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(1)) ? "CSIPLL1_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(2)) ? "CSIPLL2_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(3)) ? "CSIPLL3_LOCK " : ""); + unsigned int vid_status = 0; + max96724_read_reg(dev, MAX96724_VID_STATUS_ADDR(src_port), &vid_status); + dev_dbg(dev, "%s: %s Video RX status: 0x%02x (LOCK=%d, bit4-6: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), vid_status, !!(link_status & MAX96724_VID_LOCK_BIT), + (vid_status & 0x40) ? "VID_LOCK " : "", + (vid_status & 0x20) ? "VID_PKT_DET " : "", + (vid_status & 0x10) ? "VID_SEQ_ERR " : ""); + unsigned int pipe_0_status = 0, pipe_1_status = 0, pipe_2_status = 0, pipe_3_status = 0; + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_0_MAP_ADDR, &pipe_0_status); + dev_dbg(dev, "%s: %s Video TX CSI 0 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_0_status, + (pipe_0_status & 0x01) ? "SYNC_EN " : "", + (pipe_0_status & 0x02) ? "in-SYNC " : "", + (pipe_0_status & 0x04) ? "LOST_SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_1_MAP_ADDR, &pipe_1_status); + dev_dbg(dev, "%s: %s Video TX CSI 1 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_1_status, + (pipe_1_status & 0x01) ? "EN-SYNC " : "", + (pipe_1_status & 0x02) ? "IN-SYNC " : "", + (pipe_1_status & 0x04) ? "LOST-SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_2_MAP_ADDR, &pipe_2_status); + dev_dbg(dev, "%s: %s Video TX CSI 2 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_2_status, + (pipe_2_status & 0x01) ? "SYNC_EN " : "", + (pipe_2_status & 0x02) ? "in-SYNC " : "", + (pipe_2_status & 0x04) ? "LOST_SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_3_MAP_ADDR, &pipe_3_status); + dev_dbg(dev, "%s: %s Video TX CSI 3 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_3_status, + (pipe_3_status & 0x01) ? "SYNC_EN " : "", + (pipe_3_status & 0x02) ? "in-SYNC " : "", + (pipe_3_status & 0x04) ? "LOST_SYNC " : ""); + unsigned int pipe_de_status = 0, pipe_hs_status = 0, pipe_vs_status = 0; + max96724_read_reg(dev, MAX96724_PIPE_DE_STATUS_ADDR, &pipe_de_status); + dev_dbg(dev, "%s: %s Video Pipeline DE status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_de_status, + (pipe_de_status & 0x01) ? "DE_DET_0 " : "", + (pipe_de_status & 0x02) ? "DE_DET_1 " : "", + (pipe_de_status & 0x04) ? "DE_DET_2 " : "", + (pipe_de_status & 0x08) ? "DE_DET_3 " : ""); + max96724_read_reg(dev, MAX96724_PIPE_HS_STATUS_ADDR, &pipe_hs_status); + dev_dbg(dev, "%s: %s Video Pipeline HS status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_hs_status, + (pipe_hs_status & 0x01) ? "HS_DET_0 " : "", + (pipe_hs_status & 0x02) ? "HS_DET_1 " : "", + (pipe_hs_status & 0x04) ? "HS_DET_2 " : "", + (pipe_hs_status & 0x08) ? "HS_DET_3 " : ""); + max96724_read_reg(dev, MAX96724_PIPE_VS_STATUS_ADDR, &pipe_vs_status); + dev_dbg(dev, "%s: %s Video Pipeline VS status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_vs_status, + (pipe_vs_status & 0x01) ? "VS_DET_0 " : "", + (pipe_vs_status & 0x02) ? "VS_DET_1 " : "", + (pipe_vs_status & 0x04) ? "VS_DET_2 " : "", + (pipe_vs_status & 0x08) ? "VS_DET_3 " : ""); + } + + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max96724_check_status); + +int max96724_switch_link_channel(struct device *dev) +{ + struct max96724 *priv = NULL; + int err = 0; + + if (!dev) { + dev_warn(dev, "%s: invalid sdev\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + /* Turn on ALL link channels + */ + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC, + ~(MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(0, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(1, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(2, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(3, 0), 1U))); + if (err) + dev_err(dev, "%s: Failed to switch ALL link i2c channel: %d\n", + __func__, + err); + + mutex_unlock(&priv->lock); + + msleep(5); // delay to settle link + + return err; +} +EXPORT_SYMBOL(max96724_switch_link_channel); + +int max96724_set_mfp(struct device *dev, int pin, int val) +{ + struct max96724 *priv = dev_get_drvdata(dev); + struct regmap *map = priv->regmap; + int err; + + if (pin > 10) + return -EINVAL; + + err = MAX96724_UPDATE_BITS(map, MAX96724_GPIO_REG(pin), + (1<<4 | 1<<2 | 1<<0), + ((val ? 1<<4 : 0) | 0<<2 | 0<<0) ); + + err |= MAX96724_UPDATE_BITS(map, MAX96724_GPIO_REG(pin), + 3<<6|1<<5, + 1<<6|1<<5); + + return err; + +} +EXPORT_SYMBOL(max96724_set_mfp); + +static int max967xx_phy_tuning(struct device *dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + struct regmap *map = priv->regmap; + int ret; + unsigned rev = 0; + + if (priv->dev_id == MAX96724_DEV_REV_ID) { + + ret = regmap_read(map, MAX967XX_DEV_REV_ADDR, &rev); + ret |= regmap_multi_reg_write(map, + max96724_errata_rev1, + ARRAY_SIZE(max96724_errata_rev1)); + + goto phy_tuning_out; + } + + ret = regmap_read(map, MAX967XX_DEV_REV_ADDR, &rev); + switch (FIELD_GET(MAX967XX_DEV_REV_FIELD, rev)) { + case MAX96712_REV_A: + case MAX96712_REV_B: + case MAX96712_REV_C: + ret |= regmap_multi_reg_write(map, + max96712_phy_tuning_revABC, + ARRAY_SIZE(max96712_phy_tuning_revABC)); + break; + case MAX96712_REV_D: + // No tuning necessary + break; + case MAX96712_REV_E: + ret |= regmap_multi_reg_write(map, + max96712_phy_tuning_revE, + ARRAY_SIZE(max96712_phy_tuning_revE)); + break; + default: + dev_warn(dev, "Unknown chip revision"); + break; + } + +phy_tuning_out: + dev_dbg(dev, "%s: applying %s (rev %u) CSI %u %s tunning\n", + __func__, + priv->dev_id == MAX96724_DEV_REV_ID ? "MAX96724" : "MAX96712", + rev, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY"); + return ret; +} + +static int max96724_write_link(struct device *dev, u32 link) +{ + int ret = -EINVAL; + int err = 0; + struct max96724 *priv = dev_get_drvdata(dev); + u8 src_port = 0; + + src_port = max96724_link_to_port(link); + if (src_port < 0) { + dev_err(dev, "%s: invalid link 0x%x\n", __func__, link); + return ret; + } + + dev_dbg(dev, "%s: write %s link (idx=%u)\n", + __func__, max96724_get_link_name(link), src_port); + + /* Enable GMSL2 input sources links state + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_LINK_CTRL_ADDR, + MAX96724_LINK_CTRL_EN_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(src_port), 1U)); + if (err) { + dev_err(dev, "%s: Failed to enable %s link control: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto write_link_out; + } + + /* Reset GMSL2 links state + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(src_port), 1U)); + if (err) { + dev_err(dev, "%s: Failed to reset link: %d\n", + __func__, + err); + goto write_link_out; + } + + msleep(10); // delay to settle link + + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(src_port), 0U)); + if (err) { + dev_err(dev, "%s: Failed to reset link: %d\n", + __func__, + err); + goto write_link_out; + } + + msleep(300); // delay to settle link + + ret = 0; + +write_link_out: + return ret; +} + +int max96724_setup_link(struct device *dev, struct device *s_dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int err = 0; + u8 src_port = 0; + u32 link = 0; + unsigned int i = 0, j = 0; + + dev_dbg(dev, "%s: ENTER\n", __func__); + + err = max96724_get_sdev_idx(dev, s_dev, &i); + if (err) { + dev_err(dev, "%s: Failed to get sdev_idx: %d\n", __func__, err); + return err; + } + + src_port = (u8) i; + link = priv->sources[i].g_ctx->serdes_csi_link; + + dev_info(dev, "%s: Source index=%u, link=%s\n", __func__, src_port, + max96724_get_link_name(link)); + + mutex_lock(&priv->lock); + + if (priv->splitter_enabled) + goto reset; + + /* Configure 6Gbps GMSL2 mode BEFORE reset to match MAX9295 serializer + * Reference script: enable_6g sets 0x0010=0x22, 0x0011=0x22 + * These are CTRL0 and CTRL1 registers for GMSL2 6Gbps PHY rate + */ + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_PHY_RATE_CTRL(0), 0x22); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_PHY_RATE_CTRL(2), 0x22); + if (err) { + dev_err(dev, "%s: Failed to enable %s 6Gbps link: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + /* Disable CSI output initially (matches script step 2) + * This is done early in initialization, before any video configuration + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_CSI_OUT_EN_ADDR, + MAX96724_CSI_OUT_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_CSI_OUT_EN_FIELD, 0U)); + if (err) { + dev_err(dev, "%s: Failed to csi output link: %d\n", + __func__, + err); + goto ret; + } + +#ifndef CONFIG_VIDEO_D4XX_MAX96712 + /* Enable internal regulators (0x17 and 0x19) - before reset + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_REG17_ADDR, + MAX96724_REG17_CTRL_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_REG17_CTRL_EN_FIELD, 1U)); + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_REG19_ADDR, + MAX96724_REG19_CTRL_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_REG19_CTRL_EN_FIELD, 1U)); + */ + + /* Enable PoC (Power over Coax) for cameras + dev_dbg(dev, "Enabling PoC (Power over Coax)...\n"); + ret = MAX96724_WRITE_REG(priv->regmap, MAX96724_REG_POC_CTRL1, 0x80); + ret = MAX96724_WRITE_REG(priv->regmap, MAX96724_REG_POC_CTRL2, 0x80); + ret = MAX96724_WRITE_REG(priv->regmap, MAX96724_REG_POC_CTRL3, 0x01); + + msleep(100); // Wait for power to stabilize + */ +#endif + /* initialize default link to pipe selection + */ + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, 0xF); + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_SEL(0), + MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(0) + | MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(1) + | MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(0) + | MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(1), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(0), 0U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(1), 1U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(0), 0U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(1), 1U)); + + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_SEL(2), + MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(2) + | MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(3) + | MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(2) + | MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(2), 2U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(3), 3U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(2), 2U) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(3), 3U)); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_MIPI_TX_MAP_CON(0), MAX96724_MAP_CON_SYNC_PIPE_MASTER(0)); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_MIPI_TX_MAP_CON(1), MAX96724_MAP_CON_SYNC_PIPE_MASTER(1)); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_MIPI_TX_MAP_CON(2), MAX96724_MAP_CON_SYNC_PIPE_MASTER(2)); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_MIPI_TX_MAP_CON(3), MAX96724_MAP_CON_SYNC_PIPE_MASTER(3)); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_REG3_ADDR, 0xFF); + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_REG4_ADDR, 0x0F); + if (err) { + dev_err(dev, "%s: Failed to set default %s link stream streams (X,Y,Z and/or U) selection: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + /* initialize GMSL2 input sources links state + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_LINK_CTRL_ADDR, + MAX96724_LINK_CTRL_EN_FIELD(0) + | MAX96724_LINK_CTRL_GMSL_FIELD(0) + | MAX96724_LINK_CTRL_EN_FIELD(1) + | MAX96724_LINK_CTRL_GMSL_FIELD(1) + | MAX96724_LINK_CTRL_EN_FIELD(2) + | MAX96724_LINK_CTRL_GMSL_FIELD(2) + | MAX96724_LINK_CTRL_EN_FIELD(3) + | MAX96724_LINK_CTRL_GMSL_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(0), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_GMSL_FIELD(0), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(1), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_GMSL_FIELD(1), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(2), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_GMSL_FIELD(2), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(3), 1U) + | MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_GMSL_FIELD(3), 1U)); + if (err) { + dev_err(dev, "%s: Failed to enable %s link control: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + /* initialize GMSL2 all link & control state + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(0) + | MAX96724_RESET_LINK_FIELD(0) + | MAX96724_RESET_LINK_FIELD(1) + | MAX96724_RESET_LINK_FIELD(2) + | MAX96724_RESET_LINK_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(0), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(1), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(2), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(3), 1U)); + + // delay to settle link + msleep(10); + + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_LINK_FIELD(0) + | MAX96724_RESET_LINK_FIELD(1) + | MAX96724_RESET_LINK_FIELD(2) + | MAX96724_RESET_LINK_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(0), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(1), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(2), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(3), 0U)); + if (err) { + dev_err(dev, "%s: Failed to reset all ctrls: %d\n", + __func__, err); + goto ret; + } + + // delay to settle link + msleep(300); + +reset: + + // Enable GMSL2 input sources links state + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_LINK_CTRL_ADDR, + MAX96724_LINK_CTRL_EN_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_LINK_CTRL_EN_FIELD(src_port), 1U)); + if (err) { + dev_err(dev, "%s: Failed to enable %s link control: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + /* Reset GMSL2 links state + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_LINK_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(src_port), 1U)); + + // delay to settle link + msleep(10); + + /* Reset GMSL2 links state + */ + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_LINK_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_LINK_FIELD(src_port), 0U)); + if (err) { + dev_err(dev, "%s: Failed to reset %s link: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + // delay to settle link + msleep(300); + + //err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC, + // ~(MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(src_port, 0), 1U))); + switch (link) { + case GMSL_SERDES_CSI_LINK_A: + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC,0xFE); + break; + case GMSL_SERDES_CSI_LINK_B: + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC,0xFB); + break; + case GMSL_SERDES_CSI_LINK_C: + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC,0xEF); + break; + case GMSL_SERDES_CSI_LINK_D: + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC,0xBF); + break; + default: + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC, + ~(MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(0, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(1, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(2, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(3, 0), 1U))); + break; + } + if (err) { + dev_err(dev, "%s: Failed to switch %s link i2c channel: %d\n", + __func__, + max96724_get_link_name(link), + err); + goto ret; + } + + if (!priv->splitter_enabled) { + err = max96724_write_link(dev, link); + if (err) + goto ret; + + priv->link_setup = true; + } + + /* Check GMSL link status - hardware auto-negotiation should have completed */ + for (j = 0; j < priv->max_src; j++) { + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(j), &link_status); + dev_info(dev, "%s: GMSL %c 6Gbps link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, 'A' + j, link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + } + } + +ret: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max96724_setup_link); + +int max96724_setup_control(struct device *dev, struct device *s_dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int err = 0; + u8 src_port = 0; + unsigned int i=0, j=0; + + dev_dbg(dev, "%s: ENTER\n", __func__); + + err = max96724_get_sdev_idx(dev, s_dev, &i); + if (err) { + dev_err(dev, "%s: Failed to get sdev_idx: %d\n", __func__, err); + return err; + } + + src_port = (u8) i; + + mutex_lock(&priv->lock); + + if (!priv->link_setup) { + dev_err(dev, "%s: invalid state\n", __func__); + err = -EINVAL; + goto error; + } + + if (priv->sources[i].g_ctx->serdev_found) { + priv->num_src_found++; + priv->src_link = priv->sources[i].g_ctx->serdes_csi_link; + priv->dst_link = priv->sources[i].g_ctx->dst_csi_port; + priv->dst_n_lanes = priv->sources[i].g_ctx->num_csi_lanes; + } + + /* Enable splitter mode */ + if ((priv->max_src > 1U) && + (priv->num_src_found > 0U) && + (priv->splitter_enabled == false)) { + + //Fetch and output chip name + unsigned int dev_id = 0; + err = max96724_read_reg(dev, MAX967XX_DEV_ID_ADDR, &dev_id); + if (err) { + dev_warn(dev, "%s(): Failed to read Serdes chip ID", __func__); + dev_id = MAX96724_DEV_REV_ID; // fallback max96724 + } + priv->dev_id = dev_id; + + dev_dbg(dev, "%s: Switched %s SerDes GMSL %s link channel)\n", + __func__, + priv->dev_id == MAX96724_DEV_REV_ID ? "MAX96724" : "MAX96712", + max96724_get_link_name(priv->src_link)); + + priv->splitter_enabled = true; + } + + //max96724_write_reg(dev, + // MAX96724_PWDN_PHYS_ADDR, MAX96724_ALLPHYS_NOSTDBY); + + priv->sdev_ref++; + + dev_dbg(dev, "%s: checking serializer sources (count/ref=%d/%d -> src[%d]=%s)\n", + __func__, + priv->num_src_found, + priv->sdev_ref, i, + max96724_get_link_name(priv->sources[i].g_ctx->serdes_csi_link)); + + /* Reset splitter mode if all devices are not found */ + if ((priv->num_src_found < priv->sdev_ref) || + ((priv->sdev_ref == priv->max_src) && + (priv->splitter_enabled == true) && + (priv->num_src_found > 0U) && + (priv->num_src_found < priv->max_src))) { + + for (j = 0; j < priv->max_src; j++) { + + if (!priv->sources[j].g_ctx) + continue; + + if (!priv->sources[j].g_ctx->serdev_found) + continue; + + /* Check GMSL link status - hardware auto-negotiation should have completed */ + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(j), &link_status); + + if (!(link_status & MAX96724_LINK_LOCK_BIT)) + continue; + + dev_info(dev, "%s: GMSL %c 6Gbps link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, 'A' + j , link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + } + + dev_dbg(dev, "%s: restore SerDes %s (link=%u)\n", + __func__, + max96724_get_link_name(priv->sources[j].g_ctx->serdes_csi_link), + j); + + /* Only restore link if it's valid */ + err = max96724_write_link(dev, priv->sources[j].g_ctx->serdes_csi_link); + if (err) { + dev_warn(dev, "%s: fail to restore source link\n", + __func__); + continue; + } + } + + if (priv->num_src_found < 2) + priv->splitter_enabled = false; + } + +error: + mutex_unlock(&priv->lock); + dev_dbg(dev, "%s: EXIT (err=%d, sdev_ref=%d, num_src_found=%d)\n", + __func__, err, priv->sdev_ref, priv->num_src_found); + return err; +} +EXPORT_SYMBOL(max96724_setup_control); + +int max96724_reset_control(struct device *dev, struct device *s_dev) +{ + struct max96724 *priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + if (!priv->sdev_ref) { + dev_info(dev, "%s: dev is already in reset state\n", __func__); + goto ret; + } + + priv->sdev_ref--; + if (priv->sdev_ref == 0) { + max96724_reset_ctx(priv); + max96724_write_reg(dev, MAX96724_PWR1_ADDR, MAX96724_RESET_ALL); + + /* delay to settle reset */ + msleep(100); + } + +ret: + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max96724_reset_control); + +int max96724_sdev_register(struct device *dev, struct gmsl_link_ctx *g_ctx) +{ + struct max96724 *priv = NULL; + unsigned int i=0; + int err = 0; + bool csi_mode_ok = true; + bool csi_lanes_ok = true; + + if (!dev || !g_ctx || !g_ctx->s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + + mutex_lock(&priv->lock); + + if (priv->num_src > priv->max_src) { + dev_err(dev, + "%s: MAX96724 inputs size exhausted\n", __func__); + err = -ENOMEM; + goto error; + } + + dev_dbg(dev, + "%s: Checking deserializer %s link map to CSI %u link (dst-mode:%s,dst-lanes:%u)\n", + __func__, + max96724_get_link_name(g_ctx->serdes_csi_link), + g_ctx->dst_csi_port, + g_ctx->csi_mode == GMSL_CSI_4X2_MODE ? "4X2" : "2X4", + g_ctx->num_csi_lanes); + + switch (priv->csi_mode) { + case MAX96724_CSI_MODE_1X4: + case MAX96724_CSI_MODE_2X4: + csi_mode_ok = (g_ctx->csi_mode == GMSL_CSI_1X4_MODE) || + (g_ctx->csi_mode == GMSL_CSI_2X4_MODE); + csi_lanes_ok = (g_ctx->num_csi_lanes == 4); + break; + case MAX96724_CSI_MODE_4X2: + csi_mode_ok = (g_ctx->csi_mode == GMSL_CSI_4X2_MODE); + csi_lanes_ok = (g_ctx->num_csi_lanes == 2); + break; + default: + csi_mode_ok = false; + csi_lanes_ok = false; + break; + } + + if (!csi_mode_ok) + dev_warn(dev, + "%s: csi %s mode mismatch, fallback on valid csi mode %s\n", + __func__, + g_ctx->csi_mode == GMSL_CSI_4X2_MODE ? "4X2" : "2X4", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4"); + + if (!csi_lanes_ok) + dev_warn(dev, "%s: invalid nlanes for CSI %s mode, fallback on valid nlanes\n", + __func__, + g_ctx->csi_mode == GMSL_CSI_4X2_MODE ? "4X2" : "2X4"); + + i = (unsigned int) max96724_link_to_port(g_ctx->serdes_csi_link); + + priv->sources[i].g_ctx = g_ctx; + priv->sources[i].st_enabled = false; + + if (!csi_lanes_ok) + priv->sources[i].g_ctx->num_csi_lanes = ((priv->csi_mode == MAX96724_CSI_MODE_4X2) + ? 2 + : 4); + if (!csi_mode_ok) + priv->sources[i].g_ctx->csi_mode = ((priv->csi_mode == MAX96724_CSI_MODE_4X2) + ? GMSL_CSI_4X2_MODE + : GMSL_CSI_2X4_MODE); + + dev_dbg(dev, + "%s: Registered [src:%u] deserializer %s link map to CSI %u link (dst-mode:%s,dst-lanes:%u)\n", + __func__, i, + max96724_get_link_name(priv->sources[i].g_ctx->serdes_csi_link), + priv->sources[i].g_ctx->dst_csi_port, + priv->sources[i].g_ctx->csi_mode == GMSL_CSI_4X2_MODE ? "4X2" : "2X4", + priv->sources[i].g_ctx->num_csi_lanes); + + priv->num_src++; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max96724_sdev_register); + +int max96724_sdev_unregister(struct device *dev, struct device *s_dev) +{ + struct max96724 *priv = NULL; + int err = 0; + unsigned int i = 0; + + if (!dev || !s_dev) { + dev_err(dev, "%s: invalid input params\n", __func__); + return -EINVAL; + } + + priv = dev_get_drvdata(dev); + mutex_lock(&priv->lock); + + if (priv->num_src == 0) { + dev_err(dev, "%s: no source found\n", __func__); + err = -ENODATA; + goto error; + } + + for (i = 0; i < priv->max_src; i++) { + if (s_dev == priv->sources[i].g_ctx->s_dev) { + priv->sources[i].g_ctx = NULL; + break; + } + } + + if (i == priv->max_src) { + dev_err(dev, + "%s: requested device not found\n", __func__); + err = -EINVAL; + goto error; + } + priv->num_src--; + +error: + mutex_unlock(&priv->lock); + return err; +} +EXPORT_SYMBOL(max96724_sdev_unregister); + +static int max96724_get_available_pipe(struct device *dev, + u32 st_data_type, u32 dst_csi_port) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < MAX96724_MAX_PIPES; i++) { + /* + * TODO: Enable a pipe for multi stream configuration having + * similar stream data type. For now use st_count as a flag + * for 1 to 1 mapping in pipe and stream data type, same can + * be extended as count for many to 1 mapping. Would also need + * few more checks such as input stream id select, dst port etc. + */ + if ((priv->pipe[i].dt_type == st_data_type) && + ((dst_csi_port == GMSL_CSI_PORT_A || + dst_csi_port == GMSL_CSI_PORT_B || + dst_csi_port == GMSL_CSI_PORT_C || + dst_csi_port == GMSL_CSI_PORT_D) ? + (priv->pipe[i].dst_csi_ctrl == + MAX96724_CSI_CTRL_ID_0) || + (priv->pipe[i].dst_csi_ctrl == + MAX96724_CSI_CTRL_ID_1) : + (priv->pipe[i].dst_csi_ctrl == + MAX96724_CSI_CTRL_ID_2) || + (priv->pipe[i].dst_csi_ctrl == + MAX96724_CSI_CTRL_ID_3)) && + (!priv->pipe[i].st_count)) + break; + } + + if (i == MAX96724_MAX_PIPES) { + dev_err(dev, "%s: all pipes are busy\n", __func__); + return -ENOMEM; + } + + return i; +} + +struct reg_pair { + u16 addr; + u8 val; +}; + +static int max96724_set_registers(struct device *dev, struct reg_pair *map, + u32 count) +{ + int err = 0; + u32 j = 0; + + for (j = 0; j < count; j++) { + err = max96724_write_reg(dev, + map[j].addr, map[j].val); + if (err != 0) + break; + } + + return err; +} + +int max96724_get_available_pipe_id(struct device *dev, int vc_id, u32 src_link) +{ + int i; + int pipe_id = -ENOMEM; + int err = 0; + struct max96724 *priv = dev_get_drvdata(dev); + int _vc_id = vc_id % MAX96724_MAX_PIPES; + + mutex_lock(&priv->lock); + for (i = 0; i < MAX96724_MAX_PIPES; i++) { +#ifdef CONFIG_INTEL_IPU_VC_EXT + if (i == _vc_id && priv->pipe[i].st_count < 2) { +#else + if (i == vc_id && !priv->pipe[i].st_count) { +#endif + priv->pipe[i].st_count++; + priv->pipe[i].src_link = src_link; + pipe_id = i; + break; + } + } + + mutex_unlock(&priv->lock); + + return pipe_id; +} +EXPORT_SYMBOL(max96724_get_available_pipe_id); + +int max96724_release_pipe(struct device *dev, int pipe_id) +{ + struct max96724 *priv = dev_get_drvdata(dev); + + if (pipe_id < 0 || pipe_id >= MAX96724_MAX_PIPES) + return -EINVAL; + + if (!priv->pipe[pipe_id].st_count) + return 0; + + mutex_lock(&priv->lock); +#ifdef CONFIG_INTEL_IPU_VC_EXT + priv->pipe[pipe_id].st_count--; +#else + priv->pipe[pipe_id].st_count = 0; +#endif + mutex_unlock(&priv->lock); + + return 0; +} +EXPORT_SYMBOL(max96724_release_pipe); + +void max96724_reset_oneshot(struct device *dev, u32 src_link) +{ + struct max96724 *priv = dev_get_drvdata(dev); + int err = 0; + unsigned int i, st_count; + + u8 src_port = max96724_link_to_port(src_link); + + for (i = 0, st_count = 0; i < MAX96724_MAX_PIPES; i++) { + + if (!priv->pipe[i].st_count) + continue; + + if (src_link != priv->pipe[i].src_link) + continue; + + st_count++; + } + + if (st_count > 1) { + dev_warn(dev, "%s: ignore %s Link reset ctrl\n", + __func__, max96724_get_link_name(src_link)); + return; + } + + /* disable each video pipe configuration corresponding to the reset link */ + for (i = 0; i < MAX96724_MAX_PIPES; i++) { + + if (src_link != priv->pipe[i].src_link) + continue; + + dev_dbg(dev, "%s: disable %s video pipe %d\n", + __func__, max96724_get_link_name(src_link), i); + + /* disable video pipe if is no longer used */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(i), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(i), 0U)); + } + + /* Re-Check GMSL link status after initial configuration */ + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(src_port), &link_status); + dev_dbg(dev, "%s: %s Link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + unsigned int pll_status = 0; + max96724_read_reg(dev, MAX96724_DPLL_STATUS_ADDR, &pll_status); + dev_dbg(dev, "%s: %s DPLL status: 0x%02x (bit0-7: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pll_status, + (pll_status & MAX96724_DPLL_STATUS_FIELD(0)) ? "CSIPLL0_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(1)) ? "CSIPLL1_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(2)) ? "CSIPLL2_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(3)) ? "CSIPLL3_LOCK " : ""); + unsigned int vid_status = 0; + max96724_read_reg(dev, MAX96724_VID_STATUS_ADDR(src_port), &vid_status); + dev_dbg(dev, "%s: %s Video RX status: 0x%02x (LOCK=%d, bit4-6: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), vid_status, !!(link_status & MAX96724_VID_LOCK_BIT), + (vid_status & 0x40) ? "VID_LOCK " : "", + (vid_status & 0x20) ? "VID_PKT_DET " : "", + (vid_status & 0x10) ? "VID_SEQ_ERR " : ""); + unsigned int pipe_0_status = 0, pipe_1_status = 0, pipe_2_status = 0, pipe_3_status = 0; + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_0_MAP_ADDR, &pipe_0_status); + dev_dbg(dev, "%s: %s Video TX CSI 0 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_0_status, + (pipe_0_status & 0x01) ? "SYNC_EN " : "", + (pipe_0_status & 0x02) ? "in-SYNC " : "", + (pipe_0_status & 0x04) ? "LOST_SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_1_MAP_ADDR, &pipe_1_status); + dev_dbg(dev, "%s: %s Video TX CSI 1 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_1_status, + (pipe_1_status & 0x01) ? "EN-SYNC " : "", + (pipe_1_status & 0x02) ? "IN-SYNC " : "", + (pipe_1_status & 0x04) ? "LOST-SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_2_MAP_ADDR, &pipe_2_status); + dev_dbg(dev, "%s: %s Video TX CSI 2 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_2_status, + (pipe_2_status & 0x01) ? "SYNC_EN " : "", + (pipe_2_status & 0x02) ? "in-SYNC " : "", + (pipe_2_status & 0x04) ? "LOST_SYNC " : ""); + max96724_read_reg(dev, MAX96724_PIPE_X_STATUS_3_MAP_ADDR, &pipe_3_status); + dev_dbg(dev, "%s: %s Video TX CSI 3 status: 0x%02x (bit0-2: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_3_status, + (pipe_3_status & 0x01) ? "SYNC_EN " : "", + (pipe_3_status & 0x02) ? "in-SYNC " : "", + (pipe_3_status & 0x04) ? "LOST_SYNC " : ""); + unsigned int pipe_de_status = 0, pipe_hs_status = 0, pipe_vs_status = 0; + max96724_read_reg(dev, MAX96724_PIPE_DE_STATUS_ADDR, &pipe_de_status); + dev_dbg(dev, "%s: %s Video Pipeline DE status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_de_status, + (pipe_de_status & 0x01) ? "DE_DET_0 " : "", + (pipe_de_status & 0x02) ? "DE_DET_1 " : "", + (pipe_de_status & 0x04) ? "DE_DET_2 " : "", + (pipe_de_status & 0x08) ? "DE_DET_3 " : ""); + max96724_read_reg(dev, MAX96724_PIPE_HS_STATUS_ADDR, &pipe_hs_status); + dev_dbg(dev, "%s: %s Video Pipeline HS status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_hs_status, + (pipe_hs_status & 0x01) ? "HS_DET_0 " : "", + (pipe_hs_status & 0x02) ? "HS_DET_1 " : "", + (pipe_hs_status & 0x04) ? "HS_DET_2 " : "", + (pipe_hs_status & 0x08) ? "HS_DET_3 " : ""); + max96724_read_reg(dev, MAX96724_PIPE_VS_STATUS_ADDR, &pipe_vs_status); + dev_dbg(dev, "%s: %s Video Pipeline VS status: 0x%02x (bit0-3: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pipe_vs_status, + (pipe_vs_status & 0x01) ? "VS_DET_0 " : "", + (pipe_vs_status & 0x02) ? "VS_DET_1 " : "", + (pipe_vs_status & 0x04) ? "VS_DET_2 " : "", + (pipe_vs_status & 0x08) ? "VS_DET_3 " : ""); + } + + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(src_port), 1U)); + if (err) + dev_err(dev, "%s: Failed to trigger %s link reset: %d\n", + __func__, + max96724_get_link_name(src_link), + err); + + /* delay to settle link */ + msleep(100); + + /* clear link and ctrl reset */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(src_port), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(src_port), 0U)); + if (err) + dev_err(dev, "%s: Failed to clear %s link reset: %d\n", + __func__, + max96724_get_link_name(src_link), + err); + + /* Enable all channels + */ + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC, + ~(MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(0, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(1, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(2, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(3, 0), 1U))); + if (err) + dev_err(dev, "%s: Failed to switch ALL link i2c channel: %d\n", + __func__, + err); + + /* delay to settle link */ + msleep(100); +} +EXPORT_SYMBOL(max96724_reset_oneshot); + +static int __max96724_set_pipe_d4xx(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id, u32 csi_id, u32 src_link) +{ + int err = 0; + struct max96724 *priv = dev_get_drvdata(dev); + int i = 0, j = 0; + unsigned int st_count, src_link_st_count; + + u8 all_mapping_phy = 0x55; + u8 phy4_lanes_mapping = 0xE4; + u8 phy3_lanes_mapping = 0xE4; + u8 phy2_lanes_mapping = 0xF4; + u8 dis_lim_heart = 0x0A; + + u8 link_id = max96724_link_to_port(src_link); + + u8 vc_id_2b_lsb = ((u8) vc_id) & 0x3; +#ifdef CONFIG_VIDEO_D4XX_MAX9295_VC_EXT + u8 vc_id_3b_src = MAX96724_FIELD_PREP(MAX96724_MAP_SRCDST_H_SRC_VC_FIELD, (vc_id >> 2)); +#else + u8 vc_id_3b_src = MAX96724_FIELD_PREP(MAX96724_MAP_SRCDST_H_SRC_VC_FIELD, 0U); +#endif +#ifdef CONFIG_INTEL_IPU_VC_EXT + u8 vc_id_3b_dst = MAX96724_FIELD_PREP(MAX96724_MAP_SRCDST_H_DST_VC_FIELD, (vc_id >> 2)); +#else + u8 vc_id_3b_dst = MAX96724_FIELD_PREP(MAX96724_MAP_SRCDST_H_DST_VC_FIELD, 0U); +#endif + +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + int _pipe_id = vc_id_3b_dst > 0 ? (MAX96724_MAX_PIPES - 1) - (vc_id % MAX96724_MAX_PIPES) : pipe_id; + int _fwd_id = link_id; + int _aggregator_id = csi_id; + u8 en_mapping_num = 0xFF; +#else + int _fwd_id = pipe_id; + int _pipe_id = pipe_id; + u8 en_mapping_num = 0x0F; +#endif + + if (data_type2 == 0x0) +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + en_mapping_num = 0x77; +#else + en_mapping_num = 0x07; +#endif + + switch (csi_id) { + case GMSL_CSI_PORT_A: + all_mapping_phy = 0x00; + /* 0 (DA0) -> PHY0.0 + * 1 (DA1) -> PHY0.1 + */ + if (priv->csi_mode != MAX96724_CSI_MODE_4X2) + phy3_lanes_mapping = 0xE4; + else + phy3_lanes_mapping = 0x44; + phy2_lanes_mapping = 0x34; + break; + case GMSL_CSI_PORT_B: + all_mapping_phy = 0x55; + /* 0 (DA0) -> PHY1.0 + * 1 (DA1) -> PHY1.1 + * 2 (DA2) -> PHY0.0 + * 3 (DA3) -> PHY0.1 + */ + if (priv->csi_mode != MAX96724_CSI_MODE_4X2) + phy3_lanes_mapping = 0x4E; + else + phy3_lanes_mapping = 0x44; + phy2_lanes_mapping = 0x34; + break; + case GMSL_CSI_PORT_C: + all_mapping_phy = 0xAA; + /* 0 (DB0) -> PHY2.0 + * 1 (DB1) -> PHY2.1 + * 2 (DB2) -> PHY3.0 + * 3 (DB3) -> PHY3.1 + */ + if (priv->csi_mode != MAX96724_CSI_MODE_4X2) + phy4_lanes_mapping = 0xE4; + else + phy4_lanes_mapping = 0x44; + phy2_lanes_mapping = 0x44; + break; + case GMSL_CSI_PORT_D: + all_mapping_phy = 0xFF; + /* 0 (DB0) -> PHY3.0 + * 1 (DB1) -> PHY3.1 + */ + if (priv->csi_mode != MAX96724_CSI_MODE_4X2) + phy4_lanes_mapping = 0x4E; + else + phy4_lanes_mapping = 0x44; + phy2_lanes_mapping = 0x84; + break; + default: + dev_warn(dev, "%s: invalid deserializer csi port, default CSI 1!\n", __func__); + }; + + for (i = 0, st_count = 0, src_link_st_count = 0; i < MAX96724_MAX_PIPES; i++) { + + if (!priv->pipe[i].st_count) + continue; + + st_count++; + + if (src_link != priv->pipe[i].src_link) + continue; + + src_link_st_count++; + } + + struct reg_pair map_pipe_select[] = { + {MAX96724_REG5_ADDR, 0x80}, // Enable lock + {MAX96724_REG6_ADDR, 0xFF}, // Enable all GMSL input sources + {MAX96724_MIPI_PHY0, + MAX96724_FIELD_PREP(MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD, 0U) | + MAX96724_FIELD_PREP(MAX96724_MIPI_PHY0_MODE_FIELD, priv->csi_mode) + }, + {MAX96724_MIPI_PHY2, phy2_lanes_mapping}, + {MAX96724_MIPI_PHY3, phy3_lanes_mapping}, + {MAX96724_MIPI_PHY4, phy4_lanes_mapping}, + {MAX96724_MIPI_PHY5, 0x00}, + }; + + struct reg_pair map_pipe_control[] = { + // Enable 4 mappings for Pipe X + {MAX96724_PIPE_X_EN_L_ADDR, 0x0F}, + {MAX96724_PIPE_X_EN_H_ADDR, 0x00}, + // Map data_type1 on vc_id + {MAX96724_PIPE_X_SRC_0_MAP_ADDR, GMSL_CSI_DT_YUV422_8}, + {MAX96724_PIPE_X_DST_0_MAP_ADDR, GMSL_CSI_DT_YUV422_8}, + // Map frame_start on vc_id + {MAX96724_PIPE_X_SRC_1_MAP_ADDR, 0x00}, + {MAX96724_PIPE_X_DST_1_MAP_ADDR, 0x00}, + // Map frame end on vc_id + {MAX96724_PIPE_X_SRC_2_MAP_ADDR, 0x01}, + {MAX96724_PIPE_X_DST_2_MAP_ADDR, 0x01}, + // Map data_type2 on vc_id + {MAX96724_PIPE_X_SRC_3_MAP_ADDR, GMSL_CSI_DT_EMBED}, + {MAX96724_PIPE_X_DST_3_MAP_ADDR, GMSL_CSI_DT_EMBED}, + // Mappings to CSI 1 (master for port A) + {MAX96724_PIPE_X_PHY_DEST_0_MAP_ADDR, all_mapping_phy}, + // SEQ_MISS_EN: Disabled / DIS_PKT_DET: Disabled + {MAX96724_VID_RX0_ADDR, 0x23}, // pipe X + // LIM_HEART : Disabled + {MAX96724_VID_RX6_ADDR, dis_lim_heart}, + /* Extend each SRC/DST to CPHY 5-bits / DPHY 4-bits VCs + */ + {MAX96724_PIPE_X_SRCDST_VC_EXT_0_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_1_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_2_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_3_MAP_ADDR, 0x0}, + }; + +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + + struct reg_pair map_pipe_control_dt_ext[] = { + // Enable 16 mappings for Pipe X + {MAX96724_PIPE_X_EN_L_ADDR, 0xFF}, + {MAX96724_PIPE_X_EN_H_ADDR, 0x00}, + // Map UYVY data_type1 on vc_id + {MAX96724_PIPE_X_SRC_4_MAP_ADDR, GMSL_CSI_DT_YUV422_8}, + {MAX96724_PIPE_X_DST_4_MAP_ADDR, GMSL_CSI_DT_YUV422_8}, + // Map UYVY frame_start on vc_id + {MAX96724_PIPE_X_SRC_5_MAP_ADDR, 0x00}, + {MAX96724_PIPE_X_DST_5_MAP_ADDR, 0x00}, + // Map frame end on vc_id + {MAX96724_PIPE_X_SRC_6_MAP_ADDR, 0x01}, + {MAX96724_PIPE_X_DST_6_MAP_ADDR, 0x01}, + // Map data_type2 on vc_id + {MAX96724_PIPE_X_SRC_7_MAP_ADDR, GMSL_CSI_DT_EMBED}, + {MAX96724_PIPE_X_DST_7_MAP_ADDR, GMSL_CSI_DT_EMBED}, + // Mappings to CSI 1 (master for port A) + {MAX96724_PIPE_X_PHY_DEST_1_MAP_ADDR, all_mapping_phy}, + // SEQ_MISS_EN: Disabled / DIS_PKT_DET: Disabled + {MAX96724_VID_RX0_ADDR, 0x23}, // pipe X + // LIM_HEART : Disabled + {MAX96724_VID_RX6_ADDR, dis_lim_heart}, + /* Extend each SRC/DST to CPHY 5-bits / DPHY 4-bits VCs + */ + {MAX96724_PIPE_X_SRCDST_VC_EXT_4_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_5_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_6_MAP_ADDR, 0x0}, + {MAX96724_PIPE_X_SRCDST_VC_EXT_7_MAP_ADDR, 0x0}, + }; + + for (i = 0; i < 11; i++) { + map_pipe_control[i].addr += 0x40 * _pipe_id; + map_pipe_control_dt_ext[i].addr += 0x40 * _pipe_id; + } + + if (vc_id_3b_dst > 0) { + + map_pipe_control_dt_ext[0].val = en_mapping_num; + map_pipe_control_dt_ext[1].val = 0x0; + map_pipe_control_dt_ext[2].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control_dt_ext[3].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control_dt_ext[4].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control_dt_ext[5].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control_dt_ext[6].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control_dt_ext[7].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control_dt_ext[8].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control_dt_ext[9].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control_dt_ext[10].val = all_mapping_phy; + + map_pipe_control_dt_ext[11].addr += 0x12 * _pipe_id; + map_pipe_control_dt_ext[12].addr += 0x12 * _pipe_id; + + map_pipe_control_dt_ext[11].val = 0x23; + map_pipe_control_dt_ext[12].val = dis_lim_heart; + + /* Extend each SRC/DST to CPHY 5-bits / DPHY 4-bits VCs + */ + map_pipe_control_dt_ext[13].addr += 0x10 * _pipe_id; + map_pipe_control_dt_ext[14].addr += 0x10 * _pipe_id; + map_pipe_control_dt_ext[15].addr += 0x10 * _pipe_id; + map_pipe_control_dt_ext[16].addr += 0x10 * _pipe_id; + + map_pipe_control_dt_ext[13].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control_dt_ext[14].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control_dt_ext[15].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control_dt_ext[16].val = (vc_id_3b_src | vc_id_3b_dst); + + } else { + + map_pipe_control[0].val = en_mapping_num; + map_pipe_control[1].val = 0x0; + map_pipe_control[2].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control[3].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control[4].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control[5].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control[6].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control[7].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control[8].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control[9].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control[10].val = all_mapping_phy; + + map_pipe_control[11].addr += 0x12 * _pipe_id; + map_pipe_control[12].addr += 0x12 * _pipe_id; + + map_pipe_control[11].val = 0x23; + map_pipe_control[12].val = dis_lim_heart; + + /* Extend each SRC/DST to CPHY 5-bits / DPHY 4-bits VCs + */ + map_pipe_control[13].addr += 0x10 * _pipe_id; + map_pipe_control[14].addr += 0x10 * _pipe_id; + map_pipe_control[15].addr += 0x10 * _pipe_id; + map_pipe_control[16].addr += 0x10 * _pipe_id; + + map_pipe_control[13].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[14].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[15].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[16].val = (vc_id_3b_src | vc_id_3b_dst); + } +#else + for (i = 0; i < 11; i++) { + map_pipe_control[i].addr += 0x40 * _pipe_id; + } + + map_pipe_control[0].val = en_mapping_num; + map_pipe_control[1].val = 0x0; + map_pipe_control[2].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control[3].val = (vc_id_2b_lsb << 6) | 0x00; + map_pipe_control[4].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control[5].val = (vc_id_2b_lsb << 6) | 0x01; + map_pipe_control[6].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control[7].val = (vc_id_2b_lsb << 6) | data_type1; + map_pipe_control[8].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control[9].val = (vc_id_2b_lsb << 6) | data_type2; + map_pipe_control[10].val = all_mapping_phy; + + map_pipe_control[11].addr += 0x12 * _pipe_id; + map_pipe_control[12].addr += 0x12 * _pipe_id; + + map_pipe_control[11].val = 0x23; + map_pipe_control[12].val = dis_lim_heart; + + /* Extend each SRC/DST to CPHY 5-bits / DPHY 4-bits VCs + */ + map_pipe_control[13].addr += 0x10 * _pipe_id; + map_pipe_control[14].addr += 0x10 * _pipe_id; + map_pipe_control[15].addr += 0x10 * _pipe_id; + map_pipe_control[16].addr += 0x10 * _pipe_id; + + map_pipe_control[13].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[14].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[15].val = (vc_id_3b_src | vc_id_3b_dst); + map_pipe_control[16].val = (vc_id_3b_src | vc_id_3b_dst); +#endif + +#ifdef CONFIG_INTEL_IPU_VC_EXT + dev_info(dev, "%s: %s pipe %u set on vc_ext_id=%u [vc_id_3bits: src:0x%x, dst:0x%x], %u/%u active pipes\n", + __func__, + max96724_get_link_name(src_link), + _pipe_id, + vc_id, + vc_id_3b_src, + vc_id_3b_dst, + src_link_st_count, + st_count); +#else + dev_info(dev, "%s: %s pipe %u set on vc_id=%u, %u/%u active pipes\n", + __func__, + max96724_get_link_name(src_link), + _pipe_id, + vc_id_2b_lsb, + src_link_st_count, + st_count); +#endif + + /* ONLY on initial streaming, skip if CSI already streaming data + */ + if (st_count < 2) { + // disable CSI out link during initial pipe re-configuration + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_CSI_OUT_EN_ADDR, + MAX96724_CSI_OUT_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_CSI_OUT_EN_FIELD, 0)); + if (err) + dev_err(dev, "%s: Failed to enable csi output link: %d\n", + __func__, + err); + + // Disable DPLL link during pipe re-configuration + for (i = 0; i < MAX96724_NUM_CSI_LINKS; i++) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_DPLL_RESET(i), + MAX96724_DPLL_RESET_SOFT_RST_FIELD, + MAX96724_FIELD_PREP(MAX96724_DPLL_RESET_SOFT_RST_FIELD, 0U)); + if (err) + dev_err(dev, "%s: Failed to reset configure CSI %u %s DPLL : %d\n", + __func__, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + /* Video Pipe Input Source Selection + * This tells MAX96724 which PHY (Link) feeds which Video Pipe. + * Without this, video data cannot flow from GMSL links to CSI-2 output. + * + * REG 0x00F0: Video Pipe 0/1 input selection + * Bit[7:6] = : Pipe 1 from GMSL A, B, C or D link + * Bit[5:4] = 01: Pipe 1 uses internal Pipe Y + * Bit[3:2] = : Pipe 0 from GMSL A, B, C or D link + * Bit[1:0] = 00: Pipe 0 uses internal Pipe X + * + * REG 0x00F1: Video Pipe 2/3 input selection + * Bit[7:6] = : Pipe 3 from GMSL A, B, C or D link + * Bit[5:4] = 11: Pipe 3 uses internal Pipe Z + * Bit[3:2] = : Pipe 2 from GMSL A, B, C or D link + * Bit[1:0] = 10: Pipe 2 uses internal Pipe U + */ + + /* Video Pipe Disable Selection + * if same link source, disable all before re-nable one-by-one. + * + * REG 0x00F4: Enable/disable Video Pipes + * Bit[3:0] = : Enable Pipe X + */ + if (src_link_st_count > 1) { + for (i = 0; i < MAX96724_MAX_PIPES; i++) { + if (!priv->pipe[i].st_count) + continue; + + if (src_link != priv->pipe[i].src_link) + continue; + + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(i), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(i), 0U)); + } + } else + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), 0U)); + + /* ONLY on initial streaming, skip if CSI already streaming data + */ + if (st_count < 2) + err |= max96724_set_registers(dev, map_pipe_select, + ARRAY_SIZE(map_pipe_select)); + +#if !defined(CONFIG_VIDEO_D4XX_MAX96712) && !defined(CONFIG_VIDEO_D4XX_MAX96712_LEGACY) + // Enable specific pipe streams (X,Y,Z and/or U) source mapping + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_STREAM_SEL_ALL_FIELD, + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_STREAM_SEL_ALL_FIELD, + MAX96724_VIDEO_PIPE_SRCMAP_MODE)); +#else + // Enable max96712 "legacy" mode + // Non "legacy" mode ignores pipe mapping, and selects all streams for pipe + // 0. The ipu doesn't know what to do with that and throws spurious data + // stream errors. + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_STREAM_SEL_ALL_FIELD, + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_STREAM_SEL_ALL_FIELD, + MAX96724_VIDEO_PIPE_LEGACY_MODE)); + + //WA: override Multiple fwd-pipe source to a single video-pipe sink + _fwd_id = vc_id_2b_lsb; +#endif + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_SEL(_pipe_id), + MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(_pipe_id) + | MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(_pipe_id), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_LINK_FIELD(_pipe_id), link_id) + | MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_SEL_INPUT_FIELD(_pipe_id), _fwd_id)); + if (err) + dev_err(dev, "%s: Failed to select streams: %d\n", + __func__, + err); + else +#if !defined(CONFIG_VIDEO_D4XX_MAX96712) && !defined(CONFIG_VIDEO_D4XX_MAX96712_LEGACY) + dev_dbg(dev, "%s: mapped max967xx link %c to video pipe %u (through all X,U,Z and U pipes)\n", + __func__, + 'A' + link_id, + _pipe_id); +#else + dev_dbg(dev, "%s: mapped max967xx link %c to video pipe %u (through %c input pipe)\n", + __func__, + 'A' + link_id, + _pipe_id, + _fwd_id == 3 ? 'U' : 'X' + _fwd_id); +#endif + +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + if (vc_id_3b_dst > 0) + err = max96724_set_registers(dev, map_pipe_control_dt_ext, + ARRAY_SIZE(map_pipe_control_dt_ext)); + else + err = max96724_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); +#else + err = max96724_set_registers(dev, map_pipe_control, + ARRAY_SIZE(map_pipe_control)); +#endif + + if (st_count < 2) + // 0x02: ALT_MEM_MAP8, 0x10: ALT2_MEM_MAP8 + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_MIPI_TX_ALT_MEM(csi_id), 0x10); + + +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + // Trial #0 - uncomment to test single link-aggregator + + /* Trial #1 - uncomment to test two-alternative link-aggregation + if (vc_id_3b_dst > 0) { + _aggregator_id = 0U; + } else { + _aggregator_id = 1U; + } + */ + + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_MAP_CON(_aggregator_id), + MAX96724_MAP_CON_SYNC_PIPE_0_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_1_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_2_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_3_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_FIELD + | MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, + MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_0_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_1_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_2_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_3_FIELD, 0U) + | MAX96724_MAP_CON_SYNC_PIPE_MASTER(_pipe_id) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, 0U)); + + /* Trial #2 - uncomment to test concatenation + if (src_link_st_count < 2) + else + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_MAP_CON(_aggregator_id), + MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(_pipe_id) + | MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, + MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_SEL_FIELD(_pipe_id), 1U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, 1U)); + */ + +#if !defined(CONFIG_VIDEO_D4XX_MAX96712) && !defined(CONFIG_VIDEO_D4XX_MAX96712_LEGACY) + dev_info(dev, "%s: mapped fwd-link %c through X,Y,Z & U input pipes\t->\tVideo pipe %u through Aggregator %c to CSI %u\n", + __func__, + 'A' + link_id, + _pipe_id, + 'A' + _aggregator_id, + csi_id); +#else + dev_info(dev, "%s: mapped fwd-link %c through %c input pipe\t->\tVideo pipe %u through Aggregator %c to CSI %u\n", + __func__, + 'A' + link_id, + _fwd_id == 3 ? 'U' : 'X' + _fwd_id, + _pipe_id, + 'A' + _aggregator_id, + csi_id); +#endif + +#else + /* Configure per Pipe 0, 1, 2 and 3 MIPI interleaved-SYNC FCFS aggregation + * Aggregator A, B, C and D corresponds to GMSL A, B, C and D input src + */ + if (src_link_st_count < 2) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_MAP_CON(link_id), + MAX96724_MAP_CON_SYNC_PIPE_0_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_1_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_2_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_3_FIELD + | MAX96724_MAP_CON_SYNC_PIPE_FIELD + | MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, + MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_0_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_1_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_2_FIELD, 0U) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_PIPE_3_FIELD, 0U) + | MAX96724_MAP_CON_SYNC_PIPE_MASTER(_pipe_id) + | MAX96724_FIELD_PREP(MAX96724_MAP_CON_SYNC_EN_4WxH_FIELD, 0U)); + else + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_MAP_CON(link_id), + MAX96724_MAP_CON_SYNC_PIPE_FIELD, + MAX96724_MAP_CON_SYNC_PIPE_MASTER(_pipe_id)); +#endif + + /* ONLY on initial streaming, skip if CSI already streaming data + * Configure per Pipe CSI and PHY type + */ + if (st_count < 2) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_LANE_CNT(csi_id), + MAX96724_MIPI_TX_CPHY_EN_FIELD + | MAX96724_MIPI_TX_VCX_EN_FIELD + | MAX96724_MIPI_TX_LANE_CNT_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_TX_CPHY_EN_FIELD, + priv->csi_phy == MAX96724_CSI_CPHY ? 1U : 0U) +#ifdef CONFIG_INTEL_IPU_VC_EXT + | MAX96724_FIELD_PREP(MAX96724_MIPI_TX_VCX_EN_FIELD, MAX96724_VC_4_BITS) +#else + | MAX96724_FIELD_PREP(MAX96724_MIPI_TX_VCX_EN_FIELD, MAX96724_VC_2_BITS) +#endif + | MAX96724_LANE_CTRL_MAP(priv->dst_n_lanes-1)); + + + /* Video Pipe Enable Selection + * if same link source activate at least twice, disable all concurrent streams before re-nable one-by-one. + * + * REG 0x00F4: Enable/disable Video Pipes + * Bit[3:0] = : Enable Pipe X + */ + if (src_link_st_count > 1) { + for (i = 0; i < MAX96724_MAX_PIPES; i++) { + if (!priv->pipe[i].st_count) + continue; + + if (src_link != priv->pipe[i].src_link) + continue; + + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(i), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(i), 1U)); + } + } else + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), 1U)); + + if (err) { + dev_err(dev, "%s: Failed to configure pipe control %u %s %u lanes: %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->dst_n_lanes, + err); + } + + /* ONLY on initial streaming, skip if CSI already streaming data + */ + if (st_count > 1) + return err; + + err |= max967xx_phy_tuning(dev); + if (err) { + dev_err(dev, "%s: Failed to apply %s tunning : %d\n", + __func__, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + /* Configure lane mapping DPLL Frequency + * Script: DPHY_CLK_1500BPS 0x0415/0x0418/0x041B/0x041E = 0x34 + * Script: CPHY_CLK_1500BPS 0x0415/0x0418/0x041B/0x041E = 0x26 + */ + for (i = 0; i < MAX96724_NUM_CSI_LINKS; i++) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_DPLL_FREQ(i), + MAX96724_DPLL_FREQ_FIELD, + MAX96724_FIELD_PREP(MAX96724_DPLL_FREQ_FIELD, + priv->csi_phy == MAX96724_CSI_CPHY ? MAX96724_CPHY_CLK_1500BPS + : MAX96724_DPHY_CLK_1500BPS)); + if (err) { + dev_err(dev, "%s: Failed to set CSI %u %s at 1500bps: %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + /* Configure DPHY or CPHY for all pipes + * Script: 0x0903/0x0943/0x0983/0x09C3 = auto deskew enable + * Script: 0x8AD/0x8AE = cphy preamble + */ + for (i = 0; i < MAX96724_NUM_CSI_LINKS; i++) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_DESKEW_INIT(i), + MAX96724_MIPI_TX_DESKEW_INIT_AUTO_FIELD + || MAX96724_MIPI_TX_DESKEW_WIDTH_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_TX_DESKEW_INIT_AUTO_FIELD, 1U) + || MAX96724_FIELD_PREP(MAX96724_MIPI_TX_DESKEW_WIDTH_FIELD, 7U)); + if (err) { + dev_err(dev, "%s: Failed to set CSI %u %s deskew to 7: %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + /* Configure CPHY preamble + */ + if (priv->csi_phy == MAX96724_CSI_CPHY) { + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_CPHY_PREAMBLE_ADDR, + MAX96724_FIELD_PREP(MAX96724_CPHY_PREAMBLE_FIELD, MAX96724_CPHY_PREAMBLE_DEFAULT)); + if (err) { + dev_err(dev, "%s: Failed to set CSI %u %s preamble : %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + err |= MAX96724_WRITE_REG(priv->regmap, MAX96724_CPHY_PREP_ADDR, MAX96724_CPHY_PREP_DEFAULT); + if (err) { + dev_err(dev, "%s: Failed to set CSI %u %s prep and post : %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + dev_dbg(dev, "%s: preparing %s preamble : 0x8AD/0x8AE applied\n", + __func__, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY"); + } + + + /* Enable DPLL link after pipe re-configuration + */ + for (i = 0; i < MAX96724_NUM_CSI_LINKS; i++) + err |= MAX96724_UPDATE_BITS(priv->regmap, MAX96724_DPLL_RESET(i), + MAX96724_DPLL_RESET_SOFT_RST_FIELD, + MAX96724_FIELD_PREP(MAX96724_DPLL_RESET_SOFT_RST_FIELD, 1U)); + if (err) { + dev_err(dev, "%s: Failed to set/reset CSI %u %s: %d\n", + __func__, + csi_id, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + return err; +} + +int max96724_init_settings(struct device *dev) +{ + int err = 0; + int i; + int _pipe_id; + unsigned rev; + + struct max96724 *priv = dev_get_drvdata(dev); + struct regmap *map = priv->regmap; + u8 src_port = max96724_link_to_port(priv->src_link); + + dev_dbg(dev, "%s: Clearing %s CSI %u %s mode %s (num_lanes:x%u)\n", + __func__, + max96724_get_link_name(priv->src_link), + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes); + + mutex_lock(&priv->lock); + +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + for (i = 0; i < MAX96724_MAX_VCX; i++) { + _pipe_id = (i % MAX96724_MAX_PIPES); +#else + for (i = 0; i < MAX96724_MAX_PIPES; i++) { + _pipe_id = i; +#endif + dev_dbg(dev, "%s: enable %s link to Video pipe %d", + __func__, + max96724_get_link_name(priv->src_link), + _pipe_id); + + err |= __max96724_set_pipe_d4xx(dev, _pipe_id, GMSL_CSI_DT_YUV422_8, + GMSL_CSI_DT_EMBED, i, priv->dst_link, priv->src_link); + + /* + * REG 0x00F4: disable Video Pipes by default + * Bit[3:0] = : Disable Pipe 0/1/2/3. + */ + err |= MAX96724_UPDATE_BITS(map, MAX96724_VIDEO_PIPE_EN_ADDR, + MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), + MAX96724_FIELD_PREP(MAX96724_VIDEO_PIPE_EN_FIELD(_pipe_id), 0U)); + } + if (err) { + dev_err(dev, "%s: Failed to configure %s link to Video pipes : %d\n", + __func__, + max96724_get_link_name(priv->src_link), + err); + goto init_settings_out; + } + + /* Reset all GMSL links after configuration (matches script line 508) + * Reference script does this AFTER all serializer config and BEFORE CSI enable: + * 1. Restore all links (0x0006 = 0xFF) + * 2. Reset all links (0x0018 = 0x0F) ← This step! + * 3. Enable CSI output (0x040B = 0x02) + * 4. Configure MIPI PHY continuous clock (0x08A0 = 0x84) + * + * This reset ensures all link configurations take effect properly + * and synchronizes the GMSL links before CSI streaming starts. + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(0) + | MAX96724_RESET_CTRL_FIELD(1) + | MAX96724_RESET_CTRL_FIELD(2) + | MAX96724_RESET_CTRL_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(0), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(1), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(2), 1U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(3), 1U)); + if (err) + dev_err(dev, "%s: Failed to reset all links: %d\n", + __func__, err); + + // delay to settle link + msleep(100); + + // clear GMSL2 links Reset + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_RESET_CTRL_ADDR, + MAX96724_RESET_CTRL_FIELD(0) + | MAX96724_RESET_CTRL_FIELD(1) + | MAX96724_RESET_CTRL_FIELD(2) + | MAX96724_RESET_CTRL_FIELD(3), + MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(0), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(1), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(2), 0U) + | MAX96724_FIELD_PREP(MAX96724_RESET_CTRL_FIELD(3), 0U)); + if (err) + dev_err(dev, "%s: Failed to reset all links: %d\n", + __func__, err); + */ + + /* Enable all channels + */ + err = MAX96724_WRITE_REG(priv->regmap, MAX96724_REM_CC, + ~(MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(0, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(1, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(2, 0), 1U) + | MAX96724_FIELD_PREP(MAX96724_REM_CC_DIS_PORT_FIELD(3, 0), 1U))); + if (err) + dev_err(dev, "%s: Failed to switch %s link i2c channel: %d\n", + __func__, + max96724_get_link_name(priv->src_link), + err); + else + dev_dbg(dev, "%s: Switched to %s link i2c channel\n", + __func__, + max96724_get_link_name(priv->src_link)); + + /* Configure MIPI D-PHY/C-PHY + * These registers configure the MIPI physical layer for CSI-2 output + */ + dev_info(dev, "%s: Configuring CSI %u %s mode %s: num_lanes x%u (%s)\n", + __func__, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes, + priv->dev_id == MAX96724_DEV_REV_ID ? "MAX96724" : "MAX96712"); + + /* Re-Check GMSL link status after initial configuration */ + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(src_port), &link_status); + dev_dbg(dev, "%s: %s Link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, max96724_get_link_name(priv->src_link), link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + unsigned int pll_status = 0; + max96724_read_reg(dev, MAX96724_DPLL_STATUS_ADDR, &pll_status); + dev_dbg(dev, "%s: %s DPLL status: 0x%02x (bit0-7: %s%s%s%s)\n", + __func__, max96724_get_link_name(priv->src_link), pll_status, + (pll_status & MAX96724_DPLL_STATUS_FIELD(0)) ? "CSIPLL0_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(1)) ? "CSIPLL1_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(2)) ? "CSIPLL2_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(3)) ? "CSIPLL3_LOCK " : ""); + unsigned int vid_status = 0; + max96724_read_reg(dev, MAX96724_VID_STATUS_ADDR(src_port), &vid_status); + dev_dbg(dev, "%s: %s Video RX status: 0x%02x (LOCK=%d, bit4-6: %s%s%s)\n", + __func__, max96724_get_link_name(priv->src_link), vid_status, !!(link_status & MAX96724_VID_LOCK_BIT), + (vid_status & 0x40) ? "VID_LOCK " : "", + (vid_status & 0x20) ? "VID_PKT_DET " : "", + (vid_status & 0x10) ? "VID_SEQ_ERR " : ""); + } + +init_settings_out: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max96724_init_settings); + +int max96724_set_pipe(struct device *dev, int pipe_id, + u8 data_type1, u8 data_type2, u32 vc_id, u32 src_link) +{ + struct max96724 *priv = dev_get_drvdata(dev); + unsigned src_pipe = MAX96724_PIPE_X; + unsigned rev; + int err = 0; + u8 src_port = max96724_link_to_port(src_link); + unsigned int st_count; + int i = 0; + + if (pipe_id > (MAX96724_MAX_PIPES - 1)) { + dev_info(dev, "%s, input pipe_id: %d exceed max96724 max pipes\n", + __func__, pipe_id); + return -EINVAL; + } + + mutex_lock(&priv->lock); + + for (i = 0, st_count = 0; i < MAX96724_MAX_PIPES; i++) { + + if (!priv->pipe[i].st_count) + continue; + + st_count++; + } + +#ifdef CONFIG_INTEL_IPU_VC_EXT + dev_info(dev, "%s() : %s Re-configuring pipe_id %d, data_type1 %x, data_type2 %x, vc_ext_id %u\n", + __func__, + max96724_get_link_name(src_link), + pipe_id, data_type1, data_type2, vc_id); +#else + dev_info(dev, "%s() : %s Re-configuring pipe_id %d, data_type1 %x, data_type2 %x, vc_id %u\n", + __func__, + max96724_get_link_name(src_link), + pipe_id, data_type1, data_type2, (vc_id % MAX96724_MAX_PIPES)); +#endif + + err = __max96724_set_pipe_d4xx(dev, pipe_id, data_type1, data_type2, vc_id, priv->dst_link, src_link); + + if (st_count > 1) + goto status; + + /* CRITICAL: DO NOT enable CSI_OUT_EN (0x040b) here! + * + * Reference script shows CSI should be enabled LAST, after: + * 1. All MAX967xx configuration complete + * 2. GMSL link is locked + * 3. Sensor is streaming valid data + * + * IPU behavior is CORRECT: + * - Firmware waits for initial capture BEFORE calling s_stream + * - This ensures IPU is ready to receive data + * - CSI should only output when sensor has valid data ready + * + * We enable CSI in s_stream AFTER sensor stream-on succeeds. + */ + dev_info(dev, "%s: Wake-up %s CSI %u %s mode %s (num_lanes:x%u)\n", + __func__, + max96724_get_link_name(src_link), + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes); + + /* Enable MIPI TX controller and enable PHY CLK cycle + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_TX_LANE_CNT(priv->dst_link), + MAX96724_MIPI_TX_WAKEUP_CYC_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_TX_WAKEUP_CYC_FIELD, 1U)); + if (err) { + dev_err(dev, "%s: Failed to configure CSI %u %s wakeup cycles: %d\n", + __func__, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + err); + } + + // enable CSI out link after initialization complet + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_CSI_OUT_EN_ADDR, + MAX96724_CSI_OUT_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_CSI_OUT_EN_FIELD, 1U)); + if (err) { + dev_err(dev, "%s: Failed to enable csi output link: %d\n", + __func__, + err); + } + + /* Turn on MIPI PHY continuous clock mode (matches script line 520) + * Reference script does this AFTER CSI enable (0x040B = 0x02) + * This enables continuous clock on MIPI CSI-2 interface + * 0x84 = 0b10000100 + * Bit[7] = 1: Enable continuous clock mode + */ + err = MAX96724_UPDATE_BITS(priv->regmap, MAX96724_MIPI_PHY0, + MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD, + MAX96724_FIELD_PREP(MAX96724_MIPI_PHY0_CLK_FORCE_EN_FIELD, 1U)); + if (err) + dev_err(dev, "%s: Failed to enable %s %s continuous clock : %d\n", + __func__, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + err); + else + dev_dbg(dev, "%s: %s CSI %u %s %s (num_lanes:x%u) continuous clock mode enabled\n", + __func__, + max96724_get_link_name(src_link), + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4", + priv->dst_n_lanes); +status: + /* Double-Check GMSL link status after stream start configuration */ + { + unsigned int link_status = 0; + max96724_read_reg(dev, MAX96724_LINK_STATUS(src_port), &link_status); + dev_dbg(dev, "%s: %s Link status: 0x%02x (LOCK=%d, bit0-7: %s%s%s%s%s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), link_status, !!(link_status & MAX96724_LINK_LOCK_BIT), + (link_status & 0x01) ? "VID_LOCK " : "", + (link_status & 0x02) ? "CONFIG_DETECT " : "", + (link_status & 0x04) ? "VIDEO_DETECT " : "", + (link_status & 0x08) ? "LOCK " : "", + (link_status & 0x10) ? "ERROR " : "", + (link_status & 0x20) ? "bit5 " : "", + (link_status & 0x40) ? "bit6 " : "", + (link_status & 0x80) ? "LOCKED " : ""); + unsigned int pll_status = 0; + max96724_read_reg(dev, MAX96724_DPLL_STATUS_ADDR, &pll_status); + dev_dbg(dev, "%s: %s DPLL status: 0x%02x (bit0-7: %s%s%s%s)\n", + __func__, max96724_get_link_name(src_link), pll_status, + (pll_status & MAX96724_DPLL_STATUS_FIELD(0)) ? "CSIPLL0_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(1)) ? "CSIPLL1_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(2)) ? "CSIPLL2_LOCK " : "", + (pll_status & MAX96724_DPLL_STATUS_FIELD(3)) ? "CSIPLL3_LOCK " : ""); + unsigned int vid_status = 0; + max96724_read_reg(dev, MAX96724_VID_STATUS_ADDR(src_port), &vid_status); + dev_dbg(dev, "%s: %s Video RX status: 0x%02x (LOCK=%d, bit4-6: %s%s%s)\n", + __func__, max96724_get_link_name(src_link), vid_status, !!(link_status & MAX96724_VID_LOCK_BIT), + (vid_status & 0x40) ? "VID_LOCK " : "", + (vid_status & 0x20) ? "VID_PKT_DET " : "", + (vid_status & 0x10) ? "VID_SEQ_ERR " : ""); + } + + dev_info(dev, "%s: Successfully enabled %s pipe %u (vc_id %u) CSI %u %s lanes %s map\n", + __func__, + max96724_get_link_name(src_link), + pipe_id, vc_id, + priv->dst_link, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4"); + + set_pipe_out: + mutex_unlock(&priv->lock); + + return err; +} +EXPORT_SYMBOL(max96724_set_pipe); + +static const struct of_device_id max96724_of_match[] = { + { .compatible = "maxim,d4xx-max96724", }, + { }, +}; +MODULE_DEVICE_TABLE(of, max96724_of_match); + +#ifdef CONFIG_OF +static int max96724_parse_dt(struct max96724 *priv, + struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + int err = 0; + const char *str_value; + int value; + const struct of_device_id *match; + + if (!node) + return -EINVAL; + + match = of_match_device(max96724_of_match, &client->dev); + if (!match) { + dev_err(&client->dev, "Failed to find matching dt id\n"); + return -EFAULT; + } + + err = of_property_read_string(node, "csi-mode", &str_value); + if (err < 0) { + dev_err(&client->dev, "csi-mode property not found\n"); + return err; + } + + if (!strcmp(str_value, "2x4")) { + priv->csi_mode = MAX96724_CSI_MODE_2X4; + } else if (!strcmp(str_value, "4x2")) { + priv->csi_mode = MAX96724_CSI_MODE_4X2; + } else { + dev_err(&client->dev, "invalid csi mode\n"); + return -EINVAL; + } + + err = of_property_read_u32(node, "max-src", &value); + if (err < 0) { + dev_err(&client->dev, "No max-src info\n"); + return err; + } + priv->max_src = value; + + priv->reset_gpio = of_get_named_gpio(node, "reset-gpios", 0); + if (priv->reset_gpio < 0) { + dev_err(&client->dev, "reset-gpios not found %d\n", err); + return err; + } + + /* digital 1.2v */ + if (of_get_property(node, "vdd_cam_1v2-supply", NULL)) { + priv->vdd_cam_1v2 = regulator_get(&client->dev, "vdd_cam_1v2"); + if (IS_ERR(priv->vdd_cam_1v2)) { + dev_err(&client->dev, + "vdd_cam_1v2 regulator get failed\n"); + err = PTR_ERR(priv->vdd_cam_1v2); + priv->vdd_cam_1v2 = NULL; + return err; + } + } else { + priv->vdd_cam_1v2 = NULL; + } + + return 0; +} + +#else +static int max96724_parse_pdata(struct max96724 *priv, + struct i2c_client *client) +{ + struct max96724_pdata *pdata = client->dev.platform_data; + if (pdata) { + dev_dbg(&client->dev, "%s: Parsing Deserializer PDATA (mode=%s) \n", + __func__, + pdata->csi_mode == GMSL_CSI_4X2_MODE ? "4X2" : "2X4"); + if (pdata->csi_mode == GMSL_CSI_2X4_MODE || + pdata->csi_mode == GMSL_CSI_1X4_MODE) + priv->csi_mode = MAX96724_CSI_MODE_2X4; + else if (pdata->csi_mode == GMSL_CSI_4X2_MODE) + priv->csi_mode = MAX96724_CSI_MODE_4X2; + else { + dev_err(&client->dev, "invalid csi mode\n"); + return -EINVAL; + } + if (pdata->csi_phy == GMSL_CSI_CPHY) + priv->csi_phy = MAX96724_CSI_CPHY; + else if (pdata->csi_phy == GMSL_CSI_DPHY) + priv->csi_phy = MAX96724_CSI_DPHY; + else { + dev_err(&client->dev, "invalid csi phy\n"); + return -EINVAL; + } + + priv->max_src = pdata->max_src; + priv->src_link = pdata->src_link; + if (pdata->d4xx_hacks) { + priv->d4xx_hacks = pdata->d4xx_hacks; + } else { + priv->d4xx_hacks = 0; + } + priv->dst_link = GMSL_CSI_PORT_B; + } else { + priv->csi_mode = MAX96724_CSI_MODE_2X4; + priv->csi_phy = MAX96724_CSI_CPHY; + priv->src_link = GMSL_SERDES_CSI_LINK_A; + priv->dst_link = GMSL_CSI_PORT_B; + priv->max_src = 1; + priv->reset_gpio = 0; + priv->vdd_cam_1v2 = NULL; + priv->d4xx_hacks = 1; + } + + dev_info(&client->dev, "%s: %s Deserializer PDATA set %s link (max_src=%u) to CSI %s %s lane config\n", + __func__, + pdata->d4xx_hacks ? "d4xx" : "", + max96724_get_link_name(priv->src_link), + priv->max_src, + priv->csi_phy == MAX96724_CSI_CPHY ? "CPHY" : "DPHY", + priv->csi_mode == MAX96724_CSI_MODE_4X2 ? "4X2" : "2X4"); + + return 0; +} + +#endif + +static struct regmap_config max96724_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max96724_probe(struct i2c_client *client, + const struct i2c_device_id *id) +#else +static int max96724_probe(struct i2c_client *client) +#endif +{ + struct max96724 *priv; + int err = 0; + + dev_info(&client->dev, "[MAX96724]: probing GMSL Deserializer\n"); + + /* Try to wake up MAX96724 by attempting to write to various control registers */ + { + u8 wake_sequence[] = {MAX96724_CTRL1_ADDR, 0x00}; /* CTRL0 register, reset value */ + struct i2c_msg wake_msg = { + .addr = client->addr, + .flags = 0, + .len = 2, + .buf = wake_sequence + }; + + dev_info(&client->dev, "Attempting to wake up MAX96724...\n"); + i2c_transfer(client->adapter, &wake_msg, 1); + msleep(50); /* Give device time to initialize */ + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv->i2c_client = client; + priv->regmap = devm_regmap_init_i2c(priv->i2c_client, + &max96724_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(&client->dev, + "regmap init failed: %ld\n", PTR_ERR(priv->regmap)); + return -ENODEV; + } + +#ifdef CONFIG_OF + err = max96724_parse_dt(priv, client); + if (err) { + dev_err(&client->dev, "unable to parse dt\n"); + return -EFAULT; + } +#else + err = max96724_parse_pdata(priv, client); + if (err) { + dev_err(&client->dev, "unable to parse pdata\n"); + return -EFAULT; + } +#endif + + max96724_pipes_reset(priv); + + dev_dbg(&client->dev, "[DEBUG] MAX96724_MAX_SOURCES defined as: %d, priv->max_src: %d\n", + MAX96724_MAX_SOURCES, priv->max_src); + + if (priv->max_src > MAX96724_MAX_SOURCES) { + dev_err(&client->dev, + "max sources more than currently supported\n"); + return -EINVAL; + } + + mutex_init(&priv->lock); + + dev_set_drvdata(&client->dev, priv); + + /* Verify device communication by reading chip ID registers */ + { + unsigned int reg_val; + int ret; + u8 buf[2]; + struct i2c_msg msgs[2]; + + dev_info(&client->dev, "Testing I2C communication at address 0x%02x\n", client->addr); + + /* First try raw I2C read of device ID register */ + buf[0] = 0x0D; /* Device ID register */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = buf; + + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = &buf[1]; + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret == 2) { + dev_info(&client->dev, "Raw I2C success! Device ID: 0x%02x\n", buf[1]); + } else { + dev_err(&client->dev, "Raw I2C failed: ret=%d\n", ret); + + /* Try alternative addresses */ + dev_info(&client->dev, "Trying alternative I2C addresses...\n"); + for (int alt_addr = 0x29; alt_addr <= 0x2C; alt_addr++) { + msgs[0].addr = alt_addr; + msgs[1].addr = alt_addr; + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret == 2) { + dev_info(&client->dev, "Found device at address 0x%02x! Device ID: 0x%02x\n", + alt_addr, buf[1]); + break; + } + } + } + + /* Now try regmap-based access */ + err = max96724_read_reg(&client->dev, 0x0D, ®_val); + if (err) { + dev_err(&client->dev, "Regmap read device ID failed\n"); + } else { + dev_info(&client->dev, "Regmap success! Device ID: 0x%02x\n", reg_val); + } + + /* Read CTRL0 register to verify basic communication */ + err = max96724_read_reg(&client->dev, MAX96724_CTRL1_ADDR, ®_val); + if (err) { + dev_err(&client->dev, "Failed to read CTRL1 register (0x%x)\n", + MAX96724_CTRL1_ADDR); + } else { + dev_info(&client->dev, "MAX96724 CTRL1: 0x%02x\n", reg_val); + } + + /* Check initial link status on all ports */ + max96724_read_reg(&client->dev, 0x001A, ®_val); + dev_info(&client->dev, "Initial Link A status (0x001A): 0x%02x (LOCK=%d)\n", + reg_val, (reg_val >> 3) & 1); + max96724_read_reg(&client->dev, 0x000A, ®_val); + dev_info(&client->dev, "Initial Link B status (0x000A): 0x%02x (LOCK=%d)\n", + reg_val, (reg_val >> 3) & 1); + max96724_read_reg(&client->dev, 0x000B, ®_val); + dev_info(&client->dev, "Initial Link C status (0x000B): 0x%02x (LOCK=%d)\n", + reg_val, (reg_val >> 3) & 1); + max96724_read_reg(&client->dev, 0x000C, ®_val); + dev_info(&client->dev, "Initial Link D status (0x000C): 0x%02x (LOCK=%d)\n", + reg_val, (reg_val >> 3) & 1); + + /* Read REG 0x06 (GMSL configuration) - this tells us if GMSL1 or GMSL2 */ + max96724_read_reg(&client->dev, 0x06, ®_val); + dev_dbg(&client->dev, "REG 0x06 (GMSL config): 0x%02x (Bit0-3: LinkA-D mode)\n", reg_val); + + } + + /* dev communication gets validated when GMSL link setup is done */ + dev_info(&client->dev, "%s: success\n", __func__); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int max96724_remove(struct i2c_client *client) +#else +static void max96724_remove(struct i2c_client *client) +#endif +{ + struct max96724 *priv; + + if (client != NULL) { + priv = dev_get_drvdata(&client->dev); + dev_info(&client->dev, "[MAX96724]: remove GMSL Deserializer\n"); + mutex_destroy(&priv->lock); +#ifdef CONFIG_OF + i2c_unregister_device(client); + client = NULL; +#endif + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +static const struct i2c_device_id max96724_id[] = { + { "d4xx-max96724", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max96724_id); + +static struct i2c_driver max96724_i2c_driver = { + .driver = { + .name = "d4xx-max96724", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(max96724_of_match), + }, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + .probe = max96724_probe, +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + .probe_new = max96724_probe, +#else + .probe = max96724_probe, +#endif + .remove = max96724_remove, + .id_table = max96724_id, +}; + +module_i2c_driver(max96724_i2c_driver); + +MODULE_DESCRIPTION("Quad GMSL Deserializer driver max96724"); +MODULE_AUTHOR("Florent Pirou "); +MODULE_LICENSE("GPL v2"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +MODULE_VERSION(DRIVER_VERSION_SUFFIX); +#endif diff --git a/drivers/media/i2c/d4xx-compat/d4xx.c b/drivers/media/i2c/d4xx-compat/d4xx.c new file mode 100644 index 00000000..e92abdfa --- /dev/null +++ b/drivers/media/i2c/d4xx-compat/d4xx.c @@ -0,0 +1,7792 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ds5.c - Intel(R) ReaSlense(TM) D4XX camera driver + * + * Copyright (c) 2017-2024, INTEL CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +#include +#else +#include +#endif +#endif +#include +#include +#include +#include +#include + +#ifdef CONFIG_VIDEO_D4XX_SERDES +#include +#include +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) +#include +#else +#include +#endif +#else +#include +#define GMSL_CSI_DT_YUV422_8 0x1E +#define GMSL_CSI_DT_RGB_888 0x24 +#define GMSL_CSI_DT_RAW_8 0x2A +#define GMSL_CSI_DT_EMBED 0x12 +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0) +#include +#endif + +//#define DS5_DRIVER_NAME "DS5 RealSense camera driver" +#define DS5_DRIVER_NAME "d4xx" +#define DS5_DRIVER_NAME_AWG "d4xx-awg" +#define DS5_DRIVER_NAME_ASR "d4xx-asr" +#define DS5_DRIVER_NAME_CLASS "d4xx-class" +#define DS5_DRIVER_NAME_DFU "d4xx-dfu" + +#define DS5_MIPI_SUPPORT_LINES 0x0300 +#define DS5_MIPI_SUPPORT_PHY 0x0304 +#define DS5_MIPI_DATARATE_MIN 0x0308 +#define DS5_MIPI_DATARATE_MAX 0x030A +#define DS5_FW_VERSION 0x030C +#define DS5_FW_BUILD 0x030E +#define DS5_DEVICE_TYPE 0x0310 +#define DS5_DEVICE_TYPE_D41X 7 +#define DS5_DEVICE_TYPE_D45X 6 +#define DS5_DEVICE_TYPE_D43X 5 +#define DS5_DEVICE_TYPE_D46X 4 + +#define DS5_MIPI_LANE_NUMS 0x0400 +#define DS5_MIPI_LANE_DATARATE 0x0402 +#define DS5_MIPI_CONF_STATUS 0x0500 + +#define DS5_START_STOP_STREAM 0x1000 +#define DS5_DEPTH_STREAM_STATUS 0x1004 +#define DS5_RGB_STREAM_STATUS 0x1008 +#define DS5_IMU_STREAM_STATUS 0x100C +#define DS5_IR_STREAM_STATUS 0x1014 + +#define DS5_STREAM_DEPTH 0x0 +#define DS5_STREAM_RGB 0x1 +#define DS5_STREAM_IMU 0x2 +#define DS5_STREAM_IR 0x4 +#define DS5_STREAM_STOP 0x100 +#define DS5_STREAM_START 0x200 +#define DS5_STREAM_IDLE 0x1 +#define DS5_STREAM_STREAMING 0x2 + +#define DS5_DEPTH_STREAM_DT 0x4000 +#define DS5_DEPTH_STREAM_MD 0x4002 +#define DS5_DEPTH_RES_WIDTH 0x4004 +#define DS5_DEPTH_RES_HEIGHT 0x4008 +#define DS5_DEPTH_FPS 0x400C +#define DS5_DEPTH_OVERRIDE 0x401C + +#define DS5_RGB_STREAM_DT 0x4020 +#define DS5_RGB_STREAM_MD 0x4022 +#define DS5_RGB_RES_WIDTH 0x4024 +#define DS5_RGB_RES_HEIGHT 0x4028 +#define DS5_RGB_FPS 0x402C + +#define DS5_IMU_STREAM_DT 0x4040 +#define DS5_IMU_STREAM_MD 0x4042 +#define DS5_IMU_RES_WIDTH 0x4044 +#define DS5_IMU_RES_HEIGHT 0x4048 +#define DS5_IMU_FPS 0x404C + +#define DS5_IR_STREAM_DT 0x4080 +#define DS5_IR_STREAM_MD 0x4082 +#define DS5_IR_RES_WIDTH 0x4084 +#define DS5_IR_RES_HEIGHT 0x4088 +#define DS5_IR_FPS 0x408C +#define DS5_IR_OVERRIDE 0x409C + +#define DS5_DEPTH_CONTROL_BASE 0x4100 +#define DS5_RGB_CONTROL_BASE 0x4200 +#define DS5_MANUAL_EXPOSURE_LSB 0x0000 +#define DS5_MANUAL_EXPOSURE_MSB 0x0002 +#define DS5_MANUAL_GAIN 0x0004 +#define DS5_LASER_POWER 0x0008 +#define DS5_AUTO_EXPOSURE_MODE 0x000C +#define DS5_EXPOSURE_ROI_TOP 0x0010 +#define DS5_EXPOSURE_ROI_LEFT 0x0014 +#define DS5_EXPOSURE_ROI_BOTTOM 0x0018 +#define DS5_EXPOSURE_ROI_RIGHT 0x001C +#define DS5_MANUAL_LASER_POWER 0x0024 +#define DS5_PWM_FREQUENCY 0x0028 + +#define DS5_DEPTH_CONFIG_STATUS 0x4800 +#define DS5_RGB_CONFIG_STATUS 0x4802 +#define DS5_IMU_CONFIG_STATUS 0x4804 +#define DS5_IR_CONFIG_STATUS 0x4808 + +#define DS5_STATUS_STREAMING 0x1 +#define DS5_STATUS_INVALID_DT 0x2 +#define DS5_STATUS_INVALID_RES 0x4 +#define DS5_STATUS_INVALID_FPS 0x8 + +#define MIPI_LANE_RATE 1000 + +#define MAX_DEPTH_EXP 200000 +#define MAX_RGB_EXP 10000 +#define DEF_DEPTH_EXP 33000 +#define DEF_RGB_EXP 1660 +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + +#define MAX9295_REG0 0x0000 +#define MAX9295_I2C_4 0x0044 +#define MAX9295_I2C_5 0x0045 + +#define MAX9296_CTRL0 0x0010 +#define MAX96724_CTRL0 MAX9296_CTRL0 +#define RESET_LINK (0x1 << 6) +#define RESET_ONESHOT (0x1 << 5) +#define AUTO_LINK (0x1 << 4) +#define DUAL_LINK (0x0) +#define LINK_A (0x1) +#define LINK_B (0x2) +#define SPLITTER (0x3) +#define MAX9296_NUM (4) +#define MAX96724_NUM MAX9296_NUM + +#define MAX9295_I2C_ADDR_DEF 0x40 +#define D457_I2C_ADDR 0x10 +#endif +enum ds5_mux_pad { + DS5_MUX_PAD_EXTERNAL, + DS5_MUX_PAD_DEPTH, + DS5_MUX_PAD_RGB, + DS5_MUX_PAD_IR, + DS5_MUX_PAD_IMU, + DS5_MUX_PAD_COUNT, +}; + +#define DS5_N_CONTROLS 8 + +#define CSI2_MAX_VIRTUAL_CHANNELS 4 + +#define DFU_WAIT_RET_LEN 6 + +#define DS5_START_POLL_TIME 10 +#define DS5_START_MAX_TIME 2000 +#define DS5_START_MAX_COUNT (DS5_START_MAX_TIME / DS5_START_POLL_TIME) + +/* DFU definition section */ +#define DFU_MAGIC_NUMBER "/0x01/0x02/0x03/0x04" +#define DFU_BLOCK_SIZE 1024 +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +#define DFU_I2C_STANDARD_MODE 100000 +#define DFU_I2C_FAST_MODE 400000 +#define DFU_I2C_BUS_CLK_RATE DFU_I2C_FAST_MODE +#endif +#define ds5_read_with_check(state, addr, val) {\ + if (ds5_read(state, addr, val)) \ + return -EINVAL; } +#define ds5_raw_read_with_check(state, addr, buf, size) {\ + if (ds5_raw_read(state, addr, buf, size)) \ + return -EINVAL; } +#define ds5_write_with_check(state, addr, val) {\ + if (ds5_write(state, addr, val)) \ + return -EINVAL; } +#define ds5_raw_write_with_check(state, addr, buf, size) {\ + if (ds5_raw_write(state, addr, buf, size)) \ + return -EINVAL; } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#define max96724_write_8_with_check(state, addr, buf) {\ + if (max96724_write_8(state, addr, buf)) \ + return -EINVAL; \ + } +#define max9296_write_8_with_check(state, addr, buf) {\ + if (max9296_write_8(state, addr, buf)) \ + return -EINVAL; \ + } +#define max9295_write_8_with_check(state, addr, buf) {\ + if (max9295_write_8(state, addr, buf)) \ + return -EINVAL; \ + } +#define D4XX_LINK_FREQ_1250MHZ 1250000000ULL +#define D4XX_LINK_FREQ_1125MHZ 1125000000ULL +#define D4XX_LINK_FREQ_1000MHZ 1000000000ULL +#define D4XX_LINK_FREQ_900MHZ 900000000ULL +#define D4XX_LINK_FREQ_840MHZ 840000000ULL +#define D4XX_LINK_FREQ_750MHZ 750000000ULL +#define D4XX_LINK_FREQ_720MHZ 720000000ULL +#define D4XX_LINK_FREQ_600MHZ 600000000ULL +#define D4XX_LINK_FREQ_576MHZ 576000000ULL +#define D4XX_LINK_FREQ_480MHZ 480000000ULL +#define D4XX_LINK_FREQ_450MHZ 450000000ULL +#define D4XX_LINK_FREQ_360MHZ 360000000ULL +#define D4XX_LINK_FREQ_300MHZ 300000000ULL +#define D4XX_LINK_FREQ_288MHZ 288000000ULL +#define D4XX_LINK_FREQ_240MHZ 240000000ULL +#define D4XX_LINK_FREQ_225MHZ 22500000ULL +#endif +enum dfu_fw_state { + appIDLE = 0x0000, + appDETACH = 0x0001, + dfuIDLE = 0x0002, + dfuDNLOAD_SYNC = 0x0003, + dfuDNBUSY = 0x0004, + dfuDNLOAD_IDLE = 0x0005, + dfuMANIFEST_SYNC = 0x0006, + dfuMANIFEST = 0x0007, + dfuMANIFEST_WAIT_RESET = 0x0008, + dfuUPLOAD_IDLE = 0x0009, + dfuERROR = 0x000a +}; + +enum dfu_state { + DS5_DFU_IDLE = 0, + DS5_DFU_RECOVERY, + DS5_DFU_OPEN, + DS5_DFU_IN_PROGRESS, + DS5_DFU_DONE, + DS5_DFU_ERROR +} dfu_state_t; + +struct hwm_cmd { + u16 header; + u16 magic_word; + u32 opcode; + u32 param1; + u32 param2; + u32 param3; + u32 param4; + unsigned char Data[0]; +}; + +static const struct hwm_cmd cmd_switch_to_dfu = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x1e, + .param1 = 0x01, +}; + +enum table_id { + COEF_CALIBRATION_ID = 0x19, + DEPTH_CALIBRATION_ID = 0x1f, + RGB_CALIBRATION_ID = 0x20, + IMU_CALIBRATION_ID = 0x22 +} table_id_t; + +static const struct hwm_cmd get_calib_data = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x15, + .param1 = 0x00, //table_id +}; + +static const struct hwm_cmd set_calib_data = { + .header = 0x0114, + .magic_word = 0xCDAB, + .opcode = 0x62, + .param1 = 0x00, //table_id + .param2 = 0x02, //region +}; + +static const struct hwm_cmd gvd = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x10, +}; + +static const struct hwm_cmd set_ae_roi = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x44, +}; + +static const struct hwm_cmd get_ae_roi = { + .header = 0x014, + .magic_word = 0xCDAB, + .opcode = 0x45, +}; + +static const struct hwm_cmd set_ae_setpoint = { + .header = 0x18, + .magic_word = 0xCDAB, + .opcode = 0x2B, + .param1 = 0xa, // AE control +}; + +static const struct hwm_cmd get_ae_setpoint = { + .header = 0x014, + .magic_word = 0xCDAB, + .opcode = 0x2C, + .param1 = 0xa, // AE control + .param2 = 0, // get current +}; + +static const struct hwm_cmd erb = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x17, +}; + +static const struct hwm_cmd ewb = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x18, +}; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +static const s64 link_freq_menu_items[] = { + D4XX_LINK_FREQ_1250MHZ, + D4XX_LINK_FREQ_1125MHZ, + D4XX_LINK_FREQ_1000MHZ, + D4XX_LINK_FREQ_900MHZ, + D4XX_LINK_FREQ_840MHZ, + D4XX_LINK_FREQ_750MHZ, + D4XX_LINK_FREQ_720MHZ, + D4XX_LINK_FREQ_600MHZ, + D4XX_LINK_FREQ_576MHZ, + D4XX_LINK_FREQ_480MHZ, + D4XX_LINK_FREQ_450MHZ, + D4XX_LINK_FREQ_360MHZ, + D4XX_LINK_FREQ_300MHZ, + D4XX_LINK_FREQ_288MHZ, + D4XX_LINK_FREQ_240MHZ, + D4XX_LINK_FREQ_225MHZ, +}; +#endif +struct __fw_status { + uint32_t spare1; + uint32_t FW_lastVersion; + uint32_t FW_highestVersion; + uint16_t FW_DownloadStatus; + uint16_t DFU_isLocked; + uint16_t DFU_version; + uint8_t ivcamSerialNum[8]; + uint8_t spare2[42]; +}; + +/*************************/ + +struct ds5_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl_handler handler_depth; + struct v4l2_ctrl_handler handler_rgb; + struct v4l2_ctrl_handler handler_y8; + struct v4l2_ctrl_handler handler_imu; + struct { + struct v4l2_ctrl *log; + struct v4l2_ctrl *fw_version; + struct v4l2_ctrl *gvd; + struct v4l2_ctrl *get_depth_calib; + struct v4l2_ctrl *set_depth_calib; + struct v4l2_ctrl *get_coeff_calib; + struct v4l2_ctrl *set_coeff_calib; + struct v4l2_ctrl *ae_roi_get; + struct v4l2_ctrl *ae_roi_set; + struct v4l2_ctrl *ae_setpoint_get; + struct v4l2_ctrl *ae_setpoint_set; + struct v4l2_ctrl *erb; + struct v4l2_ctrl *ewb; + struct v4l2_ctrl *hwmc; + struct v4l2_ctrl *laser_power; + struct v4l2_ctrl *manual_laser_power; + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + /* in DS5 manual gain only works with manual exposure */ + struct v4l2_ctrl *gain; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *query_sub_stream; + struct v4l2_ctrl *set_sub_stream; + }; +}; + +struct ds5_resolution { + u16 width; + u16 height; + u8 n_framerates; + const u16 *framerates; +}; + +struct ds5_format { + unsigned int n_resolutions; + const struct ds5_resolution *resolutions; + u32 mbus_code; + u8 data_type; +}; + +struct ds5_sensor { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt format; + u16 mux_pad; + struct { + const struct ds5_format *format; + const struct ds5_resolution *resolution; + u16 framerate; + } config; + bool streaming; + /*struct ds5_vchan *vchan;*/ + const struct ds5_format *formats; + unsigned int n_formats; + int pipe_id; + int initialized; +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + int routing_initialized; +#endif +#endif +}; + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +#include +#define ds5_mux_subdev camera_common_data +#else +struct ds5_mux_subdev { + struct v4l2_subdev subdev; +}; +#endif + +struct ds5_variant { + const struct ds5_format *formats; + unsigned int n_formats; +}; + +struct ds5_dfu_dev { + struct cdev ds5_cdev; + struct class *ds5_class; + int device_open_count; + enum dfu_state dfu_state_flag; + unsigned char *dfu_msg; + u16 msg_write_once; + // unsigned char init_v4l_f; // need refactoring + u32 bus_clk_rate; +}; + +enum { + DS5_DS5U, + DS5_ASR, + DS5_AWG, +}; + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#define NR_OF_DS5_PADS 7 +#define NR_OF_DS5_STREAMS 4 +struct v4l2_mbus_framefmt ds5_ffmts[NR_OF_DS5_PADS]; +#endif + +#ifdef CONFIG_VIDEO_D4XX_SERDES +struct serdes_state { + bool isolated; + int bus_nr; + int addr; +}; +#endif + +struct ds5 { + struct { struct ds5_sensor sensor; } depth; + struct { struct ds5_sensor sensor; } ir; + struct { struct ds5_sensor sensor; } rgb; + struct { struct ds5_sensor sensor; } imu; + struct { + struct ds5_mux_subdev sd; + struct media_pad pads[DS5_MUX_PAD_COUNT]; + struct ds5_sensor *last_set; + } mux; + struct ds5_ctrls ctrls; + struct ds5_dfu_dev dfu_dev; + bool power; + struct i2c_client *client; + /*struct ds5_vchan virtual_channels[CSI2_MAX_VIRTUAL_CHANNELS];*/ + /* All below pointers are used for writing, cannot be const */ + struct mutex lock; + struct regmap *regmap; +#ifndef CONFIG_VIDEO_D4XX_SERDES + struct regmap *regmap_max9296; + struct regmap *regmap_max9295; +#endif + struct regulator *vcc; + const struct ds5_variant *variant; + int is_depth, is_y8, is_rgb, is_imu; + int aggregated; +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + int routing_initialized; +#endif +#endif + u16 fw_version; + u16 fw_build; +#ifdef CONFIG_VIDEO_D4XX_SERDES + struct gmsl_link_ctx g_ctx; + struct device *ser_dev; + struct device *dser_dev; + struct i2c_client *ser_i2c; + struct i2c_client *dser_i2c; + struct serdes_state dser_st; +#endif +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#define NR_OF_CSI2_BE_SOC_STREAMS 16 +#define NR_OF_DS5_SUB_STREAMS 6 /*d+d.md,c+c.md,ir,imu*/ + int pad_to_vc[DS5_MUX_PAD_COUNT]; + int pad_to_substream[NR_OF_CSI2_BE_SOC_STREAMS]; + struct mutex mutex; +#endif +}; + +struct ds5_counters { + unsigned int n_res; + unsigned int n_fmt; + unsigned int n_ctrl; +}; + +#define ds5_from_depth_sd(sd) container_of(sd, struct ds5, depth.sd) +#define ds5_from_ir_sd(sd) container_of(sd, struct ds5, ir.sd) +#define ds5_from_rgb_sd(sd) container_of(sd, struct ds5, rgb.sd) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +static inline void msleep_range(unsigned int delay_base) +{ + usleep_range(delay_base * 1000, delay_base * 1000 + 500); +} +#ifndef CONFIG_VIDEO_D4XX_SERDES +static int max9296_write_8(struct ds5 *state, u16 reg, u8 val) +{ + int ret; + + ret = regmap_raw_write(state->regmap_max9296, reg, &val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, val); + + return ret; +} + +static int max9296_read_8(struct ds5 *state, u16 reg, u8 *val) +{ + int ret; + + ret = regmap_raw_read(state->regmap_max9296, reg, val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c read 0x%04x = 0x%x\n", + __func__, reg, *val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, *val); + + return ret; +} +static int max9295_write_8(struct ds5 *state, u16 reg, u8 val) +{ + int ret; + + ret = regmap_raw_write(state->regmap_max9295, reg, &val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, val); + + return ret; +} + +static int max9295_read_8(struct ds5 *state, u16 reg, u8 *val) +{ + int ret; + + ret = regmap_raw_read(state->regmap_max9295, reg, val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c read 0x%04x = 0x%x\n", + __func__, reg, *val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, *val); + + return ret; +} + +#else +static int ds5_write_8(struct ds5 *state, u16 reg, u8 val) +{ + int ret; + + ret = regmap_raw_write(state->regmap, reg, &val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + return ret; +} +#endif +#endif + +static int ds5_write(struct ds5 *state, u16 reg, u16 val) +{ + int ret; + u8 value[2]; + + value[1] = val >> 8; + value[0] = val & 0x00FF; + + dev_dbg(&state->client->dev, + "%s(): writing to register: 0x%04x, value1: 0x%x, value2:0x%x\n", + __func__, reg, value[1], value[0]); + + ret = regmap_raw_write(state->regmap, reg, value, sizeof(value)); + if (ret < 0) + dev_err(&state->client->dev, + "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + return ret; +} + +static int ds5_raw_write(struct ds5 *state, u16 reg, + const void *val, size_t val_len) +{ + + int ret = regmap_raw_write(state->regmap, reg, val, val_len); + if (ret < 0) + dev_err(&state->client->dev, + "%s(): i2c raw write failed %d, %04x size(%d) bytes\n", + __func__, ret, reg, (int)val_len); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, + "%s(): i2c raw write 0x%04x: %d bytes\n", + __func__, reg, (int)val_len); + + return ret; +} + +static int ds5_read(struct ds5 *state, u16 reg, u16 *val) +{ + + int ret = regmap_raw_read(state->regmap, reg, val, 2); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else { + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c read 0x%04x: 0x%x\n", + __func__, reg, *val); + } + + return ret; +} + +static int ds5_raw_read(struct ds5 *state, u16 reg, void *val, size_t val_len) +{ + int ret = regmap_raw_read(state->regmap, reg, val, val_len); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + + return ret; +} + +static u8 d4xx_set_sub_stream[NR_OF_CSI2_BE_SOC_STREAMS]; +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static s64 d4xx_query_sub_stream[NR_OF_CSI2_BE_SOC_STREAMS]; +static void set_sub_stream_fmt(int index, u32 code) +{ + d4xx_query_sub_stream[index] &= 0xFFFFFFFFFFFF0000; + d4xx_query_sub_stream[index] |= code; +} + +static void set_sub_stream_h(int index, u32 height) +{ + s64 val = height; + + val &= 0xFFFF; + d4xx_query_sub_stream[index] &= 0xFFFFFFFF0000FFFF; + d4xx_query_sub_stream[index] |= val << 16; +} + +static void set_sub_stream_w(int index, u32 width) +{ + s64 val = width; + + val &= 0xFFFF; + d4xx_query_sub_stream[index] &= 0xFFFF0000FFFFFFFF; + d4xx_query_sub_stream[index] |= val << 32; +} + +static void set_sub_stream_dt(int index, u32 dt) +{ + s64 val = dt; + + val &= 0xFF; + d4xx_query_sub_stream[index] &= 0xFF00FFFFFFFFFFFF; + d4xx_query_sub_stream[index] |= val << 48; +} + +static void set_sub_stream_vc_id(int index, u32 vc_id) +{ + s64 val = vc_id; + + val &= 0xFF; + d4xx_query_sub_stream[index] &= 0x00FFFFFFFFFFFFFF; + d4xx_query_sub_stream[index] |= val << 56; +} + +static int get_sub_stream_vc_id(int index) +{ + s64 val = 0; + + val = d4xx_query_sub_stream[index] >> 56; + val &= 0xFF; + return (int)val; +} +#endif + +/* Pad ops */ + +static const u16 ds5_default_framerate = 30; + +// ********************** +// FIXME: D16 width must be doubled, because an 8-bit format is used. Check how +// the Tegra driver propagates resolutions and formats. +// ********************** + +//TODO: keep 6, till 5 is supported by FW +static const u16 ds5_framerates[] = {5, 30}; + +#define DS5_FRAMERATE_DEFAULT_IDX 1 + +static const u16 ds5_framerate_30 = 30; +static const u16 ds5_framerate_25 = 25; +static const u16 ds5_framerate_15_30[] = {15, 30}; +static const u16 ds5_framerate_15_25[] = {15, 25}; +static const u16 ds5_depth_framerate_to_30[] = {5, 15, 30}; +static const u16 ds5_framerate_to_30[] = {5, 10, 15, 30}; +static const u16 ds5_framerate_to_60[] = {5, 15, 30, 60}; +static const u16 ds5_framerate_to_90[] = {5, 15, 30, 60, 90}; +static const u16 ds5_41x_depth_framerate_to_30[] = {6, 15, 30}; +static const u16 ds5_41x_framerate_to_30[] = {6, 15, 30}; +static const u16 ds5_41x_framerate_to_60_no_15[] = {6, 30, 60}; +static const u16 ds5_41x_framerate_to_60[] = {6, 15, 30, 60}; +static const u16 ds5_41x_framerate_to_90[] = {6, 15, 30, 60, 90}; +static const u16 ds5_framerate_100[] = {100}; +static const u16 ds5_framerate_90[] = {90}; +static const u16 ds5_imu_framerates[] = {50, 100, 200, 400}; + +static const struct ds5_resolution d41x_depth_sizes[] = { + { + .width = 1280, + .height = 720, + .framerates = ds5_41x_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_41x_depth_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 848, + .height = 100, + .framerates = ds5_framerate_100, + .n_framerates = ARRAY_SIZE(ds5_framerate_100), + }, { + .width = 640, + .height = 480, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 256, + .height = 144, + .framerates = ds5_framerate_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_90), + }, +}; + +static const struct ds5_resolution d43x_depth_sizes[] = { + { + .width = 1280, + .height = 720, + .framerates = ds5_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_depth_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 848, + .height = 100, + .framerates = ds5_framerate_100, + .n_framerates = ARRAY_SIZE(ds5_framerate_100), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 256, + .height = 144, + .framerates = ds5_framerate_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_90), + }, +}; + +static const struct ds5_resolution d46x_depth_sizes[] = { + { + .width = 1280, + .height = 960, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, +}; + +static const struct ds5_resolution y8_sizes[] = { + { + .width = 1280, + .height = 720, + .framerates = ds5_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_depth_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + } +}; + +static const struct ds5_resolution y8_41x_sizes[] = { + { + .width = 1920, + .height = 1080, + .framerates = ds5_framerate_15_25, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_25), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_41x_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_41x_depth_framerate_to_30), + }, { + .width = 960, + .height = 540, + .framerates = ds5_framerate_15_25, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_25), + }, { + .width = 848, + .height = 480, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 848, + .height = 100, + .framerates = ds5_framerate_100, + .n_framerates = ARRAY_SIZE(ds5_framerate_100), + }, { + .width = 640, + .height = 480, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_41x_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_90), + } +}; + +static const struct ds5_resolution ds5_41x_rgb_sizes[] = { + { + .width = 1920, + .height = 1080, + .framerates = ds5_41x_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_30), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_41x_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_30), + }, { + .width = 960, + .height = 540, + .framerates = ds5_41x_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60), + }, { + .width = 848, + .height = 480, + .framerates = ds5_41x_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60), + }, { + .width = 640, + .height = 480, + .framerates = ds5_41x_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60), + }, { + .width = 640, + .height = 360, + .framerates = ds5_41x_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60), + }, { + .width = 424, + .height = 240, + .framerates = ds5_41x_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60), + }, { + .width = 320, + .height = 240, + .framerates = ds5_41x_framerate_to_60_no_15, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60_no_15), + }, { + .width = 320, + .height = 180, + .framerates = ds5_41x_framerate_to_60_no_15, + .n_framerates = ARRAY_SIZE(ds5_41x_framerate_to_60_no_15), + }, +}; + +static const struct ds5_resolution ds5_rlt_rgb_sizes[] = { + { + .width = 1280, + .height = 800, + .framerates = ds5_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_30), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, +}; + +static const struct ds5_resolution ds5_onsemi_rgb_sizes[] = { + { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 960, + .height = 720, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 1920, + .height = 1080, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 2048, + .height = 1536, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, +}; + +static const struct ds5_resolution ds5_size_w10 = { + .width = 1920, + .height = 1080, + .framerates = &ds5_framerate_30, + .n_framerates = 1, +}; + +static const struct ds5_resolution d41x_calibration_sizes[] = { + { + .width = 1920, + .height = 1080, + .framerates = ds5_framerate_15_25, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_25), + }, +}; + +static const struct ds5_resolution d43x_calibration_sizes[] = { + { + .width = 1280, + .height = 800, + .framerates = ds5_framerate_15_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_30), + }, +}; + +static const struct ds5_resolution d45x_calibration_sizes[] = { + { + .width = 1280, + .height = 800, + .framerates = ds5_framerate_15_25, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_25), + }, +}; + +static const struct ds5_resolution d46x_calibration_sizes[] = { + { + .width = 1600, + .height = 1300, + .framerates = ds5_framerate_15_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_30), + }, +}; + +static const struct ds5_resolution ds5_size_imu[] = { + { + .width = 32, + .height = 1, + .framerates = ds5_imu_framerates, + .n_framerates = ARRAY_SIZE(ds5_imu_framerates), + }, +}; + +// 32 bit IMU introduced with IMU sensitivity attribute Firmware +static const struct ds5_resolution ds5_size_imu_extended[] = { + { + .width = 38, + .height = 1, + .framerates = ds5_imu_framerates, + .n_framerates = ARRAY_SIZE(ds5_imu_framerates), + }, +}; + +static const struct ds5_format ds5_depth_formats_d41x[] = { + { + // TODO: 0x31 is replaced with 0x1e since it caused low FPS in Jetson. + .data_type = GMSL_CSI_DT_YUV422_8, /* Z16 */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(d41x_depth_sizes), + .resolutions = d41x_depth_sizes, + }, { + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(d41x_depth_sizes), + .resolutions = d41x_depth_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d41x_calibration_sizes), + .resolutions = d41x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_depth_formats_d43x[] = { + { + // TODO: 0x31 is replaced with 0x1e since it caused low FPS in Jetson. + .data_type = GMSL_CSI_DT_YUV422_8, /* Z16 */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(d43x_depth_sizes), + .resolutions = d43x_depth_sizes, + }, { + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(d43x_depth_sizes), + .resolutions = d43x_depth_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d43x_calibration_sizes), + .resolutions = d43x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_depth_formats_d46x[] = { + { + // TODO: 0x31 is replaced with 0x1e since it caused low FPS in Jetson. + .data_type = GMSL_CSI_DT_YUV422_8, /* Z16 */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(d46x_depth_sizes), + .resolutions = d46x_depth_sizes, + }, { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(d46x_depth_sizes), + .resolutions = d46x_depth_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d46x_calibration_sizes), + .resolutions = d46x_calibration_sizes, + }, +}; + +#define DS5_DEPTH_N_FORMATS 1 + +static const struct ds5_format ds5_y_formats_ds5u[] = { + { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = GMSL_CSI_DT_YUV422_8, /* Y8I */ + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d43x_calibration_sizes), + .resolutions = d43x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_y_formats_41x[] = { + { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(y8_41x_sizes), + .resolutions = y8_41x_sizes, + }, { + .data_type = GMSL_CSI_DT_YUV422_8, /* Y8I */ + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .n_resolutions = ARRAY_SIZE(y8_41x_sizes), + .resolutions = y8_41x_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* Y12I, 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .n_resolutions = ARRAY_SIZE(d41x_calibration_sizes), + .resolutions = d41x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_y_formats_45x[] = { + { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = GMSL_CSI_DT_YUV422_8, /* Y8I */ + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = GMSL_CSI_DT_RGB_888, /* Y12I, 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, + .n_resolutions = ARRAY_SIZE(d45x_calibration_sizes), + .resolutions = d45x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_41x_rgb_format = { + .data_type = GMSL_CSI_DT_YUV422_8, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .n_resolutions = ARRAY_SIZE(ds5_41x_rgb_sizes), + .resolutions = ds5_41x_rgb_sizes, +}; + +static const struct ds5_format ds5_rlt_rgb_format = { + .data_type = GMSL_CSI_DT_YUV422_8, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .n_resolutions = ARRAY_SIZE(ds5_rlt_rgb_sizes), + .resolutions = ds5_rlt_rgb_sizes, +}; +#define DS5_RLT_RGB_N_FORMATS 1 + +static const struct ds5_format ds5_onsemi_rgb_format = { + .data_type = GMSL_CSI_DT_YUV422_8, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .n_resolutions = ARRAY_SIZE(ds5_onsemi_rgb_sizes), + .resolutions = ds5_onsemi_rgb_sizes, +}; +#define DS5_ONSEMI_RGB_N_FORMATS 1 + +static const struct ds5_variant ds5_variants[] = { + [DS5_DS5U] = { + .formats = ds5_y_formats_ds5u, + .n_formats = ARRAY_SIZE(ds5_y_formats_ds5u), + }, +}; + +static const struct ds5_format ds5_imu_formats[] = { + { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* IMU DT */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(ds5_size_imu), + .resolutions = ds5_size_imu, + }, +}; + +static const struct ds5_format ds5_imu_formats_extended[] = { + { + /* First format: default */ + .data_type = GMSL_CSI_DT_RAW_8, /* IMU DT */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(ds5_size_imu_extended), + .resolutions = ds5_size_imu_extended, + }, +}; + +static const struct v4l2_mbus_framefmt ds5_mbus_framefmt_template = { + .width = 0, + .height = 0, + .code = MEDIA_BUS_FMT_FIXED, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +/* Get readable sensor name */ +static const char *ds5_get_sensor_name(struct ds5 *state) +{ + static const char *sensor_name[] = {"unknown", "RGB", "DEPTH", "Y8", "IMU"}; + int sensor_id = state->is_rgb * 1 + state->is_depth * 2 + \ + state->is_y8 * 3 + state->is_imu * 4; + if (sensor_id >= (sizeof(sensor_name)/sizeof(*sensor_name))) + sensor_id = 0; + + return sensor_name[sensor_id]; +} + +static void ds5_set_state_last_set(struct ds5 *state) +{ + dev_dbg(&state->client->dev, "%s(): %s\n", + __func__, ds5_get_sensor_name(state)); + + if (state->is_depth) + state->mux.last_set = &state->depth.sensor; + else if (state->is_rgb) + state->mux.last_set = &state->rgb.sensor; + else if (state->is_y8) + state->mux.last_set = &state->ir.sensor; + else + state->mux.last_set = &state->imu.sensor; +} + +/* This is needed for .get_fmt() + * and if streaming is started without .set_fmt() + */ +static void ds5_sensor_format_init(struct ds5_sensor *sensor) +{ + const struct ds5_format *fmt; + struct v4l2_mbus_framefmt *ffmt; + unsigned int i; + + if (sensor->config.format) + return; + + dev_dbg(sensor->sd.dev, "%s(): on pad %u\n", __func__, sensor->mux_pad); + + ffmt = &sensor->format; + *ffmt = ds5_mbus_framefmt_template; + /* Use the first format */ + fmt = sensor->formats; + ffmt->code = fmt->mbus_code; + /* and the first resolution */ + ffmt->width = fmt->resolutions->width; + ffmt->height = fmt->resolutions->height; + + sensor->config.format = fmt; + sensor->config.resolution = fmt->resolutions; + /* Set default framerate to 30, or to 1st one if not supported */ + for (i = 0; i < fmt->resolutions->n_framerates; i++) { + if (fmt->resolutions->framerates[i] == ds5_framerate_30 /* fps */) { + sensor->config.framerate = ds5_framerate_30; + return; + } + } + sensor->config.framerate = fmt->resolutions->framerates[0]; +} + +/* No locking needed for enumeration methods */ +static int ds5_sensor_enum_mbus_code(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_mbus_code_enum *mce) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + dev_dbg(sensor->sd.dev, "%s(): sensor %s pad: %d index: %d\n", + __func__, sensor->sd.name, mce->pad, mce->index); + if (mce->pad) + return -EINVAL; + + if (mce->index >= sensor->n_formats) + return -EINVAL; + + mce->code = sensor->formats[mce->index].mbus_code; + + return 0; +} + +static int ds5_sensor_enum_frame_size(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + const struct ds5_format *fmt; + unsigned int i; + + dev_dbg(sensor->sd.dev, "%s(): sensor %s is %s\n", + __func__, sensor->sd.name, ds5_get_sensor_name(state)); + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) + if (fse->code == fmt->mbus_code) + break; + + if (i == sensor->n_formats) + return -EINVAL; + + if (fse->index >= fmt->n_resolutions) + return -EINVAL; + + fse->min_width = fse->max_width = fmt->resolutions[fse->index].width; + fse->min_height = fse->max_height = fmt->resolutions[fse->index].height; + + return 0; +} + +static int ds5_sensor_enum_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + const struct ds5_format *fmt; + const struct ds5_resolution *res; + unsigned int i; + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) + if (fie->code == fmt->mbus_code) + break; + + if (i == sensor->n_formats) + return -EINVAL; + + for (i = 0, res = fmt->resolutions; i < fmt->n_resolutions; i++, res++) + if (res->width == fie->width && res->height == fie->height) + break; + + if (i == fmt->n_resolutions) + return -EINVAL; + + if (fie->index >= res->n_framerates) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = res->framerates[fie->index]; + + return 0; +} + +static int ds5_sensor_get_fmt(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_format *fmt) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + + if (fmt->pad) + return -EINVAL; + + mutex_lock(&state->lock); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad); +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + fmt->format = *v4l2_subdev_get_try_format(sd, v4l2_state, fmt->pad); +#else + fmt->format = *v4l2_subdev_state_get_format(v4l2_state, fmt->pad); +#endif + else + fmt->format = sensor->format; + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + + mutex_unlock(&state->lock); + + dev_dbg(sd->dev, "%s(): pad %x, code %x, res %ux%u\n", + __func__, fmt->pad, fmt->format.code, + fmt->format.width, fmt->format.height); + +#else + int ret = 0; + if (!v4l2_state) + goto sensor_g_fmt_state_end; + + struct v4l2_mbus_framefmt *mf; + mf = v4l2_subdev_state_get_format(v4l2_state, fmt->pad, fmt->stream); + if (!mf) { + dev_warn(sd->dev, "Failed to get %s format for sensor pad/stream %u/%u \n", + sd->name, fmt->pad, fmt->stream); + ret = -EINVAL; + goto sensor_g_fmt_state_end; + } + sensor->format = *mf; + + sensor_g_fmt_state_end: + mutex_unlock(&state->lock); + dev_dbg(sensor->sd.dev, + "%s(): fmt->pad/stream: %u/%u@0x%x/%ux%u\n", + __func__, + fmt->pad, fmt->stream,fmt->format.code, fmt->format.width, fmt->format.height); + return ret; +#endif + + return 0; +} + +/* Called with lock held */ +static const struct ds5_format *ds5_sensor_find_format( + struct ds5_sensor *sensor, + struct v4l2_mbus_framefmt *ffmt, + const struct ds5_resolution **best) +{ + const struct ds5_resolution *res; + const struct ds5_format *fmt; + unsigned long best_delta = ~0; + unsigned int i; + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) { + if (fmt->mbus_code == ffmt->code) + break; + } + dev_dbg(sensor->sd.dev, "%s(): mbus_code = %x, code = %x \n", + __func__, fmt->mbus_code, ffmt->code); + + if (i == sensor->n_formats) { + /* Not found, use default */ + dev_dbg(sensor->sd.dev, "%s:%d Not found, use default\n", + __func__, __LINE__); + fmt = sensor->formats; + } + for (i = 0, res = fmt->resolutions; i < fmt->n_resolutions; i++, res++) { + unsigned long delta = abs(ffmt->width * ffmt->height - + res->width * res->height); + if (delta < best_delta) { + best_delta = delta; + *best = res; + } + } + + ffmt->code = fmt->mbus_code; + ffmt->width = (*best)->width; + ffmt->height = (*best)->height; + + ffmt->field = V4L2_FIELD_NONE; + /* Should we use V4L2_COLORSPACE_RAW for Y12I? */ + ffmt->colorspace = V4L2_COLORSPACE_SRGB; + + return fmt; +} + +#define MIPI_CSI2_TYPE_NULL 0x10 +#define MIPI_CSI2_TYPE_BLANKING 0x11 +#define MIPI_CSI2_TYPE_EMBEDDED8 0x12 +#define MIPI_CSI2_TYPE_YUV422_8 0x1e +#define MIPI_CSI2_TYPE_YUV422_10 0x1f +#define MIPI_CSI2_TYPE_RGB565 0x22 +#define MIPI_CSI2_TYPE_RGB888 0x24 +#define MIPI_CSI2_TYPE_RAW6 0x28 +#define MIPI_CSI2_TYPE_RAW7 0x29 +#define MIPI_CSI2_TYPE_RAW8 0x2a +#define MIPI_CSI2_TYPE_RAW10 0x2b +#define MIPI_CSI2_TYPE_RAW12 0x2c +#define MIPI_CSI2_TYPE_RAW14 0x2d +/* 1-8 */ +#define MIPI_CSI2_TYPE_USER_DEF(i) (0x30 + (i) - 1) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +static unsigned int mbus_code_to_mipi(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB565_1X16: + return MIPI_CSI2_TYPE_RGB565; + case MEDIA_BUS_FMT_RGB888_1X24: + return MIPI_CSI2_TYPE_RGB888; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + case MEDIA_BUS_FMT_YUYV10_1X20: + return MIPI_CSI2_TYPE_YUV422_10; +#endif + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + return MIPI_CSI2_TYPE_YUV422_8; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return MIPI_CSI2_TYPE_RAW12; + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + return MIPI_CSI2_TYPE_RAW10; + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + return MIPI_CSI2_TYPE_RAW8; + case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: + case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: + case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: + case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: + return MIPI_CSI2_TYPE_USER_DEF(1); + default: + WARN_ON(1); + return -EINVAL; + } +} +#endif + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +static int ds5_s_state_pad(struct ds5 *state, int pad) +{ + int ret = 0; + + dev_dbg(&state->client->dev, "%s(): set state for pad: %d\n", __func__, pad); + + switch (pad) { + case DS5_MUX_PAD_DEPTH: + state->is_depth = 1; + state->is_rgb = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB: + state->is_depth = 0; + state->is_rgb = 1; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IR: + state->is_depth = 0; + state->is_rgb = 0; + state->is_y8 = 1; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IMU: + state->is_depth = 0; + state->is_rgb = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + default: + dev_warn(&state->client->dev, "%s(): unknown pad: %d\n", __func__, pad); + ret = -EINVAL; + break; + } + ds5_set_state_last_set(state); + return ret; +} + +static int ds5_s_state(struct ds5 *state, int vc) +{ + int ret = 0; + int i = 0; + int pad = 0; + for (i = 0; i < ARRAY_SIZE(state->pad_to_vc); i++) { + if (state->pad_to_vc[i] == vc) { + pad = i; + break; + } + } + + dev_info(&state->client->dev, "%s(): set state for vc: %d on pad: %d\n", __func__, vc, pad); + + ret = ds5_s_state_pad(state, pad); + return ret; +} + +#endif + +static int __ds5_sensor_set_fmt(struct ds5 *state, struct ds5_sensor *sensor, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf;// = &fmt->format; +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + int substream = -1; +#endif + //unsigned r; + + dev_dbg(sensor->sd.dev, "%s(): state %p, " + "sensor %p, fmt %p, fmt->format %p\n", + __func__, state, sensor, fmt, &fmt->format); + mf = &fmt->format; + + if (fmt->pad) + return -EINVAL; + + mutex_lock(&state->lock); + + sensor->config.format = ds5_sensor_find_format(sensor, mf, + &sensor->config.resolution); + //r = DS5_FRAMERATE_DEFAULT_IDX < sensor->config.resolution->n_framerates ? + // DS5_FRAMERATE_DEFAULT_IDX : 0; + /* FIXME: check if a framerate has been set */ + //sensor->config.framerate = sensor->config.resolution->framerates[r]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + if (cfg && fmt->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(&sensor->sd, cfg, fmt->pad) = *mf; +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + if (v4l2_state && fmt->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(&sensor->sd, v4l2_state, fmt->pad) = *mf; +#else + if (v4l2_state && fmt->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_state_get_format(v4l2_state, fmt->pad) = *mf; +#endif + + else +// FIXME: use this format in .s_stream() + sensor->format = *mf; + + state->mux.last_set = sensor; + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + mutex_unlock(&state->lock); + substream = state->pad_to_substream[sensor->mux_pad]; + + if (substream != -1) { + set_sub_stream_fmt(substream, mf->code); + set_sub_stream_h(substream, mf->height); + set_sub_stream_w(substream, mf->width); + set_sub_stream_dt(substream, mbus_code_to_mipi(mf->code)); + } + + dev_dbg(sensor->sd.dev, "%s(): fmt->pad: %d, sensor->mux_pad: %d, code: 0x%x, %ux%u substream:%d\n", __func__, + fmt->pad, sensor->mux_pad, fmt->format.code, + fmt->format.width, fmt->format.height, substream); + return 0; +#else + int ret = 0; + if (!v4l2_state) + goto sensor_s_fmt_state_end; + + mf = v4l2_subdev_state_get_format(v4l2_state, fmt->pad, fmt->stream); + if (!mf) { + dev_warn(sensor->sd.dev, "Failed to get %s format for sensor pad %u/%u \n", + sensor->sd.name, fmt->pad, fmt->stream); + ret = -EINVAL; + goto sensor_s_fmt_state_end; + } + *mf = sensor->format; + + sensor_s_fmt_state_end: + mutex_unlock(&state->lock); + dev_dbg(sensor->sd.dev, + "%s(): fmt->pad/stream: %u/%u@0x%x/%ux%u\n", + __func__, + fmt->pad, fmt->stream,fmt->format.code, fmt->format.width, fmt->format.height); + return ret; +#endif +} + +static int ds5_sensor_set_fmt(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_format *fmt) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + /* set state by vc */ + ds5_s_state_pad(state, sensor->mux_pad); + + dev_dbg(sd->dev, "%s(): %s (state %p)\n", + __func__, sd->name, state); + +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + return __ds5_sensor_set_fmt(state, sensor, cfg, fmt); +#else + return __ds5_sensor_set_fmt(state, sensor, v4l2_state, fmt); +#endif +} + +#ifdef CONFIG_VIDEO_D4XX_SERDES +static int ds5_setup_pipeline(struct ds5 *state, u8 data_type1, u8 data_type2, + int pipe_id, u32 vc_id) +{ + int ret = 0; + dev_warn(&state->client->dev, +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && !defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + "set pipe %d, data_type1: 0x%x, data_type2: 0x%x, vc_ext_id: %u (vc_ds5_id: %u)\n", + pipe_id, data_type1, data_type2, vc_id, (vc_id % NR_OF_DS5_STREAMS)); +#else + "set pipe %d, data_type1: 0x%x, data_type2: 0x%x, vc_id: %u\n", + pipe_id, data_type1, data_type2, vc_id); +#endif +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + ret = max96724_switch_link_channel(state->dser_dev); + ret |= max9295_set_pipe(state->ser_dev, pipe_id, + data_type1, data_type2, vc_id); + ret |= max96724_set_pipe(state->dser_dev, pipe_id, + data_type1, data_type2, vc_id, state->g_ctx.serdes_csi_link); +#else + ret = max9295_set_pipe(state->ser_dev, pipe_id, + data_type1, data_type2, vc_id); + ret |= max9296_set_pipe(state->dser_dev, pipe_id, + data_type1, data_type2, vc_id); +#endif + if (ret) + dev_warn(&state->client->dev, + "failed to set pipe %d, data_type1: 0x%x, \ + data_type2: 0x%x, vc_id: %u\n", + pipe_id, data_type1, data_type2, vc_id); + + return ret; +} +#endif + +static int ds5_configure(struct ds5 *state) +{ + struct ds5_sensor *sensor; + u16 fmt, md_fmt, vc_id; +#ifdef CONFIG_VIDEO_D4XX_SERDES + u16 data_type1, data_type2; +#endif + u16 dt_addr, md_addr, override_addr, fps_addr, width_addr, height_addr; + int ret; + + if (state->is_depth) { + sensor = &state->depth.sensor; + dt_addr = DS5_DEPTH_STREAM_DT; + md_addr = DS5_DEPTH_STREAM_MD; + override_addr = DS5_DEPTH_OVERRIDE; + fps_addr = DS5_DEPTH_FPS; + width_addr = DS5_DEPTH_RES_WIDTH; + height_addr = DS5_DEPTH_RES_HEIGHT; + md_fmt = GMSL_CSI_DT_EMBED; + vc_id = 0; + } else if (state->is_rgb) { + sensor = &state->rgb.sensor; + dt_addr = DS5_RGB_STREAM_DT; + md_addr = DS5_RGB_STREAM_MD; + override_addr = 0; + fps_addr = DS5_RGB_FPS; + width_addr = DS5_RGB_RES_WIDTH; + height_addr = DS5_RGB_RES_HEIGHT; + md_fmt = GMSL_CSI_DT_EMBED; + vc_id = 1; + } else if (state->is_y8) { + sensor = &state->ir.sensor; + dt_addr = DS5_IR_STREAM_DT; + md_addr = DS5_IR_STREAM_MD; + override_addr = DS5_IR_OVERRIDE; + fps_addr = DS5_IR_FPS; + width_addr = DS5_IR_RES_WIDTH; + height_addr = DS5_IR_RES_HEIGHT; + md_fmt = GMSL_CSI_DT_EMBED; + vc_id = 2; + } else if (state->is_imu) { + sensor = &state->imu.sensor; + dt_addr = DS5_IMU_STREAM_DT; + md_addr = DS5_IMU_STREAM_MD; + override_addr = 0; + fps_addr = DS5_IMU_FPS; + width_addr = DS5_IMU_RES_WIDTH; + height_addr = DS5_IMU_RES_HEIGHT; + md_fmt = 0x0; + vc_id = 3; + } else { + return -EINVAL; + } + +#ifdef CONFIG_VIDEO_D4XX_SERDES + data_type1 = sensor->config.format->data_type; + data_type2 = state->is_imu ? 0x00 : md_fmt; + + vc_id = state->g_ctx.dst_vc; + + ret = ds5_setup_pipeline(state, data_type1, data_type2, sensor->pipe_id, + vc_id); + // reset data path when switching to Y12I + if (state->is_y8 && data_type1 == GMSL_CSI_DT_RGB_888) +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + max96724_reset_oneshot(state->dser_dev, state->g_ctx.serdes_csi_link); +#else + max9296_reset_oneshot(state->dser_dev); +#endif + if (ret < 0) + return ret; +#endif + + fmt = sensor->streaming ? sensor->config.format->data_type : 0; + + /* + * Set depth stream Z16 data type as 0x31 + * Set IR stream Y8I data type as 0x32 + */ + if (state->is_depth && fmt != 0) + ret = ds5_write(state, dt_addr, 0x31); + else if (state->is_y8 && fmt != 0 && + sensor->config.format->data_type == GMSL_CSI_DT_YUV422_8) + ret = ds5_write(state, dt_addr, 0x32); + else + ret = ds5_write(state, dt_addr, fmt); + if (ret < 0) + return ret; + +#if (defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712)) && !defined(CONFIG_VIDEO_D4XX_MAX9295_VC_EXT) + ret = ds5_write(state, md_addr, ((vc_id % NR_OF_DS5_STREAMS) << 8) | md_fmt); +#else + ret = ds5_write(state, md_addr, (vc_id << 8) | md_fmt); +#endif + if (ret < 0) + return ret; + + if (!sensor->streaming) + return ret; + + if (override_addr != 0) { + ret = ds5_write(state, override_addr, fmt); + if (ret < 0) + return ret; + } + + ret = ds5_write(state, fps_addr, sensor->config.framerate); + if (ret < 0) + return ret; + + ret = ds5_write(state, width_addr, sensor->config.resolution->width); + if (ret < 0) + return ret; + + ret = ds5_write(state, height_addr, sensor->config.resolution->height); + if (ret < 0) + return ret; + + return 0; +} + +static int ds5_sensor_g_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) +/* pad ops */ + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +#else +/* Video ops */ + struct v4l2_subdev_frame_interval *fi) +#endif +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + if (NULL == sd || NULL == fi) + return -EINVAL; + + fi->interval.numerator = 1; + fi->interval.denominator = sensor->config.framerate; + + dev_dbg(sd->dev, "%s(): %s %u\n", __func__, sd->name, + fi->interval.denominator); + + return 0; +} +static u16 __ds5_probe_framerate(const struct ds5_resolution *res, u16 target); + +static int ds5_sensor_s_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) +/* pad ops */ + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +#else +/* Video ops */ + struct v4l2_subdev_frame_interval *fi) +#endif +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + u16 framerate = 1; + + if (NULL == sd || NULL == fi || fi->interval.numerator == 0) + return -EINVAL; + + framerate = fi->interval.denominator / fi->interval.numerator; + framerate = __ds5_probe_framerate(sensor->config.resolution, framerate); + sensor->config.framerate = framerate; + fi->interval.numerator = 1; + fi->interval.denominator = framerate; + + dev_dbg(sd->dev, "%s(): %s %u\n", __func__, sd->name, framerate); + + return 0; +} + +static int ds5_sensor_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + dev_dbg(sensor->sd.dev, "%s(): sensor: name=%s state=%d\n", + __func__, sensor->sd.name, on); + + sensor->streaming = on; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + return 0; +#else + return v4l2_subdev_s_stream_helper(sd, on); +#endif +#endif + +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static int ds5_sensor_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + unsigned int i; + int ret = 0; + struct v4l2_subdev_route *route; + struct v4l2_subdev_stream_configs *new_configs; + struct v4l2_mbus_framefmt *ffmt = &sensor->format; + u32 idx; + + /* set state by vc */ + ds5_s_state_pad(state, sensor->mux_pad); + + if (sensor->routing_initialized) { + /* + * Fill in the 'pad' and stream' value for each item in the array from + * the routing table + */ + new_configs= &v4l2_state->stream_configs; + dev_dbg(sensor->sd.dev, "%s(): update %s route on stream-id=%u vc-id=%u (num_configs=%u)\n", + __func__, + sensor->sd.name, + state->pad_to_substream[sensor->mux_pad], + state->pad_to_vc[sensor->mux_pad], + new_configs->num_configs); + idx = 0; + for_each_active_route(&v4l2_state->routing, route) { + new_configs->configs[idx].pad = route->sink_pad; + new_configs->configs[idx].stream = state->pad_to_substream[sensor->mux_pad]; + new_configs->configs[idx].fmt = *ffmt; + + dev_dbg(sd->dev, "%s(): update %s stream_configs->configs[%u] sink pad/stream=%u/%u fmt: code %x, res %ux%u\n", + __func__, sd->name, i, route->sink_pad, state->pad_to_substream[sensor->mux_pad], + ffmt->code, ffmt->width, ffmt->height); + + idx++; + + new_configs->configs[idx].pad = route->source_pad; + new_configs->configs[idx].stream = state->pad_to_substream[sensor->mux_pad]; + new_configs->configs[idx].fmt = *ffmt; + + dev_dbg(sd->dev, "%s(): update %s stream_configs->configs[%u] src pad/stream=%u/%u fmt: code %x, res %ux%u\n", + __func__, sd->name, i, route->sink_pad, state->pad_to_substream[sensor->mux_pad], + ffmt->code, ffmt->width, ffmt->height); + + idx++; + } + return 0; + } + + struct v4l2_subdev_route ds5_sensor_routes[] = { + { + .source_stream = state->pad_to_substream[sensor->mux_pad], + .sink_stream = state->pad_to_substream[sensor->mux_pad], + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting ds5_sensor_routing = { + .num_routes = ARRAY_SIZE(ds5_sensor_routes), + .routes = ds5_sensor_routes, + }; + + /* Needed by v4l2_subdev_s_stream_helper(), even with 1 stream only */ + ret = v4l2_subdev_set_routing(sd, v4l2_state, &ds5_sensor_routing); + if (ret) { + dev_warn(sensor->sd.dev, "%s(): failed to set default route on %s\n", + __func__, sensor->sd.name); + return ret; + } + + new_configs= &v4l2_state->stream_configs; + dev_dbg(sensor->sd.dev, "%s(): initialized %s route on stream-id=%u vc-id=%u (num_configs=%u)\n", + __func__, + sensor->sd.name, + state->pad_to_substream[sensor->mux_pad], + state->pad_to_vc[sensor->mux_pad], + new_configs->num_configs); + + sensor->routing_initialized = true; + + return 0; +} + +static int ds5_sensor_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + u32 pad, u64 streams_mask) +{ + + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + dev_dbg(sensor->sd.dev, "%s(): enable: name=%s pad=%u streams_mask=%llu\n", + __func__, sensor->sd.name, pad, streams_mask); + + return 0; + +} +static int ds5_sensor_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + u32 pad, u64 streams_mask) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + dev_dbg(sensor->sd.dev, "%s(): disable: name=%s pad=%u streams_mask=%llu\n", + __func__, sensor->sd.name, pad, streams_mask); + + return 0; +} +static int ds5_sensor_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_frame_desc *desc) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + struct ds5_format *mf = sensor->config.format; + + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + desc->num_entries = 0; + desc->entry[desc->num_entries].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + desc->entry[desc->num_entries].stream = state->pad_to_substream[sensor->mux_pad]; + desc->entry[desc->num_entries].pixelcode = mf->mbus_code; + desc->entry[desc->num_entries].length = 0; + desc->entry[desc->num_entries].bus.csi2.vc = state->pad_to_vc[sensor->mux_pad]; + desc->entry[desc->num_entries].bus.csi2.dt = mf->data_type; + desc->num_entries++; + return 0; +} +#endif +#endif + +static const struct v4l2_subdev_pad_ops ds5_depth_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .get_frame_desc = ds5_sensor_get_frame_desc, + .enable_streams = ds5_sensor_enable_streams, + .disable_streams = ds5_sensor_disable_streams, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + .get_frame_interval = ds5_sensor_g_frame_interval, + .set_frame_interval = ds5_sensor_s_frame_interval, +#endif +}; + +static const struct v4l2_subdev_video_ops ds5_sensor_video_ops = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + .g_frame_interval = ds5_sensor_g_frame_interval, + .s_frame_interval = ds5_sensor_s_frame_interval, +#endif + .s_stream = ds5_sensor_s_stream, +}; + +static const struct v4l2_subdev_ops ds5_depth_subdev_ops = { + .pad = &ds5_depth_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +/* InfraRed stream Y8/Y16 */ + +/* FIXME: identical to ds5_depth_pad_ops, use one for both */ +static const struct v4l2_subdev_pad_ops ds5_ir_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .get_frame_desc = ds5_sensor_get_frame_desc, + .enable_streams = ds5_sensor_enable_streams, + .disable_streams = ds5_sensor_disable_streams, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + .get_frame_interval = ds5_sensor_g_frame_interval, + .set_frame_interval = ds5_sensor_s_frame_interval, +#endif +}; + +static const struct v4l2_subdev_ops ds5_ir_subdev_ops = { + .pad = &ds5_ir_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +/* FIXME: identical to ds5_depth_pad_ops, use one for both? */ +static const struct v4l2_subdev_pad_ops ds5_rgb_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .get_frame_desc = ds5_sensor_get_frame_desc, + .enable_streams = ds5_sensor_enable_streams, + .disable_streams = ds5_sensor_disable_streams, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + .get_frame_interval = ds5_sensor_g_frame_interval, + .set_frame_interval = ds5_sensor_s_frame_interval, +#endif +}; + +static const struct v4l2_subdev_ops ds5_rgb_subdev_ops = { + .pad = &ds5_rgb_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +static const struct v4l2_subdev_pad_ops ds5_imu_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .get_frame_desc = ds5_sensor_get_frame_desc, + .enable_streams = ds5_sensor_enable_streams, + .disable_streams = ds5_sensor_disable_streams, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + .get_frame_interval = ds5_sensor_g_frame_interval, + .set_frame_interval = ds5_sensor_s_frame_interval, +#endif +}; + +static const struct v4l2_subdev_ops ds5_imu_subdev_ops = { + .pad = &ds5_imu_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +static int ds5_hw_set_auto_exposure(struct ds5 *state, u32 base, s32 val) +{ + if (val != V4L2_EXPOSURE_APERTURE_PRIORITY && + val != V4L2_EXPOSURE_MANUAL) + return -EINVAL; + + /* + * In firmware color auto exposure setting follow the uvc_menu_info + * exposure_auto_controls numbers, in drivers/media/usb/uvc/uvc_ctrl.c. + */ + if (state->is_rgb && val == V4L2_EXPOSURE_APERTURE_PRIORITY) + val = 8; + + /* + * In firmware depth auto exposure on: 1, off: 0. + */ + if (!state->is_rgb) { + if (val == V4L2_EXPOSURE_APERTURE_PRIORITY) + val = 1; + else if (val == V4L2_EXPOSURE_MANUAL) + val = 0; + } + + return ds5_write(state, base | DS5_AUTO_EXPOSURE_MODE, (u16)val); +} + +/* + * Manual exposure in us + * Depth/Y8: between 100 and 200000 (200ms) + * Color: between 100 and 1000000 (1s) + */ +static int ds5_hw_set_exposure(struct ds5 *state, u32 base, s32 val) +{ + int ret = -1; + + if (val < 1) + val = 1; + if ((state->is_depth || state->is_y8) && val > MAX_DEPTH_EXP) + val = MAX_DEPTH_EXP; + if (state->is_rgb && val > MAX_RGB_EXP) + val = MAX_RGB_EXP; + + /* + * Color and depth uses different unit: + * Color: 1 is 100 us + * Depth: 1 is 1 us + */ + + ret = ds5_write(state, base | DS5_MANUAL_EXPOSURE_MSB, (u16)(val >> 16)); + if (!ret) + ret = ds5_write(state, base | DS5_MANUAL_EXPOSURE_LSB, + (u16)(val & 0xffff)); + + return ret; +} + +#define DS5_MAX_LOG_WAIT 200 +#define DS5_MAX_LOG_SLEEP 10 +#define DS5_MAX_LOG_POLL (DS5_MAX_LOG_WAIT / DS5_MAX_LOG_SLEEP) + +// TODO: why to use DS5_DEPTH_Y_STREAMS_DT? +#define DS5_CAMERA_CID_BASE (V4L2_CTRL_CLASS_CAMERA | DS5_DEPTH_STREAM_DT) + +#define DS5_CAMERA_CID_LOG (DS5_CAMERA_CID_BASE+0) +#define DS5_CAMERA_CID_LASER_POWER (DS5_CAMERA_CID_BASE+1) +#define DS5_CAMERA_CID_MANUAL_LASER_POWER (DS5_CAMERA_CID_BASE+2) +#define DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET (DS5_CAMERA_CID_BASE+3) +#define DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET (DS5_CAMERA_CID_BASE+4) +#define DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET (DS5_CAMERA_CID_BASE+5) +#define DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET (DS5_CAMERA_CID_BASE+6) +#define DS5_CAMERA_CID_FW_VERSION (DS5_CAMERA_CID_BASE+7) +#define DS5_CAMERA_CID_GVD (DS5_CAMERA_CID_BASE+8) +#define DS5_CAMERA_CID_AE_ROI_GET (DS5_CAMERA_CID_BASE+9) +#define DS5_CAMERA_CID_AE_ROI_SET (DS5_CAMERA_CID_BASE+10) +#define DS5_CAMERA_CID_AE_SETPOINT_GET (DS5_CAMERA_CID_BASE+11) +#define DS5_CAMERA_CID_AE_SETPOINT_SET (DS5_CAMERA_CID_BASE+12) +#define DS5_CAMERA_CID_ERB (DS5_CAMERA_CID_BASE+13) +#define DS5_CAMERA_CID_EWB (DS5_CAMERA_CID_BASE+14) +#define DS5_CAMERA_CID_HWMC (DS5_CAMERA_CID_BASE+15) + +#define DS5_CAMERA_CID_PWM (DS5_CAMERA_CID_BASE+22) + +/* the HWMC will remain for legacy tools compatibility, + * HWMC_RW used for UVC compatibility + */ +#define DS5_CAMERA_CID_HWMC_RW (DS5_CAMERA_CID_BASE+32) + +#define DS5_HWMC_DATA 0x4900 +#define DS5_HWMC_STATUS 0x4904 +#define DS5_HWMC_RESP_LEN 0x4908 +#define DS5_HWMC_EXEC 0x490C + +#define DS5_HWMC_STATUS_OK 0 +#define DS5_HWMC_STATUS_ERR 1 +#define DS5_HWMC_STATUS_WIP 2 +#define DS5_HWMC_BUFFER_SIZE 1024 + +enum DS5_HWMC_ERR { + DS5_HWMC_ERR_SUCCESS = 0, + DS5_HWMC_ERR_CMD = -1, + DS5_HWMC_ERR_PARAM = -6, + DS5_HWMC_ERR_NODATA = -21, + DS5_HWMC_ERR_UNKNOWN = -64, + DS5_HWMC_ERR_LAST, +}; + +static int ds5_get_hwmc_status(struct ds5 *state) +{ + int ret = 0; + u16 status = DS5_HWMC_STATUS_WIP; + int retries = 100; + int errorCode; + do { + if (retries != 100) + msleep_range(1); + ret = ds5_read(state, DS5_HWMC_STATUS, &status); + } while (!ret && retries-- && status == DS5_HWMC_STATUS_WIP); + dev_dbg(&state->client->dev, + "%s(): ret: 0x%x, status: 0x%x\n", + __func__, ret, status); + if (ret || status != DS5_HWMC_STATUS_OK) { + if (status == DS5_HWMC_STATUS_ERR) { + ds5_raw_read(state, DS5_HWMC_DATA, &errorCode, sizeof(errorCode)); + return errorCode; + } + } + if (!ret && (status != DS5_HWMC_STATUS_OK)) + ret = DS5_HWMC_ERR_LAST; + + return ret; +} + +static int ds5_get_hwmc(struct ds5 *state, unsigned char *data, + u16 cmdDataLen, u16 *dataLen) +{ + int ret = 0; + u16 tmp_len = 0; + + if (!data) + return -ENOBUFS; + + memset(data, 0, cmdDataLen); + ret = ds5_get_hwmc_status(state); + if (ret) { + dev_dbg(&state->client->dev, + "%s(): HWMC status not clear, ret: %d\n", + __func__, ret); + if (ret != DS5_HWMC_ERR_LAST) { + int *p = (int *)data; + *p = ret; + return 0; + } else { + return ret; + } + } + + ret = ds5_raw_read(state, DS5_HWMC_RESP_LEN, + &tmp_len, sizeof(tmp_len)); /* Read response length */ + if (ret) + return -EBADMSG; + + if (tmp_len > cmdDataLen) + return -ENOBUFS; + + if (tmp_len == 0) { + dev_err(&state->client->dev, + "%s(): HWMC response length is 0\n", __func__); + return -ENODATA; + } + + dev_dbg(&state->client->dev, + "%s(): HWMC read len: %d, lrs_len: %d\n", + __func__, tmp_len, tmp_len - 4); + + ds5_raw_read_with_check(state, DS5_HWMC_DATA, data, tmp_len); /* Read response data */ + if (dataLen) + *dataLen = tmp_len; + return ret; +} + +static int ds5_send_hwmc(struct ds5 *state, + u16 cmdLen, + struct hwm_cmd *cmd) +{ + dev_dbg(&state->client->dev, + "%s(): HWMC header: 0x%x, magic: 0x%x, opcode: 0x%x, " + "cmdLen: %d, param1: %d, param2: %d, param3: %d, param4: %d\n", + __func__, cmd->header, cmd->magic_word, cmd->opcode, + cmdLen, cmd->param1, cmd->param2, cmd->param3, cmd->param4); + + ds5_raw_write_with_check(state, DS5_HWMC_DATA, cmd, cmdLen); /* Write command data */ + + ds5_write_with_check(state, DS5_HWMC_EXEC, 0x01); /* execute cmd */ + + return 0; +} + +static int ds5_set_calibration_data(struct ds5 *state, + struct hwm_cmd *cmd, u16 length) +{ + int ret = -1; + int retries = 10; + u16 status = 2; + + ds5_raw_write_with_check(state, DS5_HWMC_DATA, cmd, length); /* Write command data */ + + ds5_write_with_check(state, DS5_HWMC_EXEC, 0x01); /* execute cmd */ + do { + if (retries != 10) + msleep_range(200); + ret = ds5_read(state, DS5_HWMC_STATUS, &status); + } while (retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to set calibration table %d," + "ret: %d, fw error: %x\n", + __func__, cmd->param1, ret, status); + } + + return ret; +} + +static int ds5_mux_s_stream(struct v4l2_subdev *sd, int on); + +static int ds5_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ds5 *state = container_of(ctrl->handler, struct ds5, + ctrls.handler); + struct v4l2_subdev *sd = &state->mux.sd.subdev; + struct ds5_sensor *sensor = (struct ds5_sensor *)ctrl->priv; + int ret = -EINVAL; + u16 base = DS5_DEPTH_CONTROL_BASE; + + if (sensor) { + switch (sensor->mux_pad) { + case DS5_MUX_PAD_DEPTH: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth); + state->is_rgb = 0; + state->is_depth = 1; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb); + state->is_rgb = 1; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IR: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_y8); + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 1; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IMU: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_imu); + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + default: + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + + } + } + + if (state->is_rgb) + base = DS5_RGB_CONTROL_BASE; +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + else if (state->is_imu) + return -EINVAL; +#endif + v4l2_dbg(3, 1, sd, "ctrl: %s, value: %d\n", ctrl->name, ctrl->val); + dev_dbg(&state->client->dev, "%s(): %s - ctrl: %s, value: %d\n", + __func__, ds5_get_sensor_name(state), ctrl->name, ctrl->val); + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + ret = max96724_switch_link_channel(state->dser_dev); +#endif + + mutex_lock(&state->lock); + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ds5_write(state, base | DS5_MANUAL_GAIN, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = ds5_hw_set_auto_exposure(state, base, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_ABSOLUTE: + ret = ds5_hw_set_exposure(state, base, ctrl->val); + break; + case DS5_CAMERA_CID_LASER_POWER: + if (!state->is_rgb) + ret = ds5_write(state, base | DS5_LASER_POWER, + ctrl->val); + break; + case DS5_CAMERA_CID_MANUAL_LASER_POWER: + if (!state->is_rgb) + ret = ds5_write(state, base | DS5_MANUAL_LASER_POWER, + ctrl->val); + break; + case DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET: + dev_dbg(&state->client->dev, + "%s(): DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET \n", __func__); + if (ctrl->p_new.p) { + struct hwm_cmd *calib_cmd; + dev_dbg(&state->client->dev, + "%s(): table id: 0x%x\n", + __func__, *((u8 *)ctrl->p_new.p + 2)); + if (DEPTH_CALIBRATION_ID == *((u8 *)ctrl->p_new.p + 2)) { + calib_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 256, GFP_KERNEL); + if (!calib_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(calib_cmd, &set_calib_data, sizeof(set_calib_data)); + calib_cmd->header = 276; + calib_cmd->param1 = DEPTH_CALIBRATION_ID; + memcpy(calib_cmd->Data, (u8 *)ctrl->p_new.p, 256); + ret = ds5_set_calibration_data(state, calib_cmd, + sizeof(struct hwm_cmd) + 256); + devm_kfree(&state->client->dev, calib_cmd); + } + } + break; + case DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET: + dev_dbg(&state->client->dev, + "%s(): DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET \n", + __func__); + if (ctrl->p_new.p) { + struct hwm_cmd *calib_cmd; + dev_dbg(&state->client->dev, + "%s(): table id %d\n", + __func__, *((u8 *)ctrl->p_new.p + 2)); + if (COEF_CALIBRATION_ID == *((u8 *)ctrl->p_new.p + 2)) { + calib_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 512, GFP_KERNEL); + if (!calib_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(calib_cmd, &set_calib_data, sizeof (set_calib_data)); + calib_cmd->header = 532; + calib_cmd->param1 = COEF_CALIBRATION_ID; + memcpy(calib_cmd->Data, (u8 *)ctrl->p_new.p, 512); + ret = ds5_set_calibration_data(state, calib_cmd, + sizeof(struct hwm_cmd) + 512); + devm_kfree(&state->client->dev, calib_cmd); + } + } + break; + case DS5_CAMERA_CID_AE_ROI_SET: + if (ctrl->p_new.p_u16) { + struct hwm_cmd ae_roi_cmd; + memcpy(&ae_roi_cmd, &set_ae_roi, sizeof(ae_roi_cmd)); + ae_roi_cmd.param1 = *((u16 *)ctrl->p_new.p_u16); + ae_roi_cmd.param2 = *((u16 *)ctrl->p_new.p_u16 + 1); + ae_roi_cmd.param3 = *((u16 *)ctrl->p_new.p_u16 + 2); + ae_roi_cmd.param4 = *((u16 *)ctrl->p_new.p_u16 + 3); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), + &ae_roi_cmd); + if (!ret) + ret = ds5_get_hwmc_status(state); + } + break; + case DS5_CAMERA_CID_AE_SETPOINT_SET: + if (ctrl->p_new.p_s32) { + struct hwm_cmd *ae_setpoint_cmd; + dev_dbg(&state->client->dev, "%s():0x%x \n", + __func__, *(ctrl->p_new.p_s32)); + ae_setpoint_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 4, GFP_KERNEL); + if (!ae_setpoint_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_setpoint_cmd, &set_ae_setpoint, sizeof (set_ae_setpoint)); + memcpy(ae_setpoint_cmd->Data, (u8 *)ctrl->p_new.p_s32, 4); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd) + 4, + ae_setpoint_cmd); + if (!ret) + ret = ds5_get_hwmc_status(state); + devm_kfree(&state->client->dev, ae_setpoint_cmd); + } + break; + case DS5_CAMERA_CID_ERB: + if (ctrl->p_new.p_u8) { + u16 offset = 0; + u16 size = 0; + u16 len = 0; + struct hwm_cmd *erb_cmd; + + offset = *(ctrl->p_new.p_u8) << 8; + offset |= *(ctrl->p_new.p_u8 + 1); + size = *(ctrl->p_new.p_u8 + 2) << 8; + size |= *(ctrl->p_new.p_u8 + 3); + + dev_dbg(&state->client->dev, "%s(): offset %x, size: %x\n", + __func__, offset, size); + len = sizeof(struct hwm_cmd) + size; + erb_cmd = devm_kzalloc(&state->client->dev, len, GFP_KERNEL); + if (!erb_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(erb_cmd, &erb, sizeof(struct hwm_cmd)); + erb_cmd->param1 = offset; + erb_cmd->param2 = size; + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), erb_cmd); + if (!ret) + ret = ds5_get_hwmc(state, erb_cmd->Data, len, &size); + if (ret) { + dev_err(&state->client->dev, + "%s(): ERB cmd failed, ret: %d," + "requested size: %d, actual size: %d\n", + __func__, ret, erb_cmd->param2, size); + devm_kfree(&state->client->dev, erb_cmd); + return -EAGAIN; + } + + // Actual size returned from FW + *(ctrl->p_new.p_u8 + 2) = (size & 0xFF00) >> 8; + *(ctrl->p_new.p_u8 + 3) = (size & 0x00FF); + + memcpy(ctrl->p_new.p_u8 + 4, erb_cmd->Data + 4, size - 4); + dev_dbg(&state->client->dev, "%s(): 0x%x 0x%x 0x%x 0x%x \n", + __func__, + *(ctrl->p_new.p_u8), + *(ctrl->p_new.p_u8+1), + *(ctrl->p_new.p_u8+2), + *(ctrl->p_new.p_u8+3)); + devm_kfree(&state->client->dev, erb_cmd); + } + break; + case DS5_CAMERA_CID_EWB: + if (ctrl->p_new.p_u8) { + u16 offset = 0; + u16 size = 0; + struct hwm_cmd *ewb_cmd; + + offset = *((u8 *)ctrl->p_new.p_u8) << 8; + offset |= *((u8 *)ctrl->p_new.p_u8 + 1); + size = *((u8 *)ctrl->p_new.p_u8 + 2) << 8; + size |= *((u8 *)ctrl->p_new.p_u8 + 3); + + dev_dbg(&state->client->dev, "%s():0x%x 0x%x 0x%x 0x%x\n", + __func__, + *((u8 *)ctrl->p_new.p_u8), + *((u8 *)ctrl->p_new.p_u8 + 1), + *((u8 *)ctrl->p_new.p_u8 + 2), + *((u8 *)ctrl->p_new.p_u8 + 3)); + + ewb_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + size, + GFP_KERNEL); + if (!ewb_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ewb_cmd, &ewb, sizeof(ewb)); + ewb_cmd->header = 0x14 + size; + ewb_cmd->param1 = offset; // start index + ewb_cmd->param2 = size; // size + memcpy(ewb_cmd->Data, (u8 *)ctrl->p_new.p_u8 + 4, size); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd) + size, ewb_cmd); + if (!ret) + ret = ds5_get_hwmc_status(state); + if (ret) { + dev_err(&state->client->dev, + "%s(): EWB cmd failed, ret: %d," + "requested size: %d, actual size: %d\n", + __func__, ret, ewb_cmd->param2, size); + devm_kfree(&state->client->dev, ewb_cmd); + return -EAGAIN; + } + + devm_kfree(&state->client->dev, ewb_cmd); + } + break; + case DS5_CAMERA_CID_HWMC: + if (ctrl->p_new.p_u8) { + u16 size = 0; + struct hwm_cmd *cmd = (struct hwm_cmd *)ctrl->p_new.p_u8; + size = *((u8 *)ctrl->p_new.p_u8 + 1) << 8; + size |= *((u8 *)ctrl->p_new.p_u8 + 0); + ret = ds5_send_hwmc(state, size + 4, cmd); + ret = ds5_get_hwmc(state, cmd->Data, ctrl->dims[0], &size); + if (ctrl->dims[0] < DS5_HWMC_BUFFER_SIZE) { + ret = -ENODATA; + break; + } + /*This is needed for legacy hwmc */ + size += 4; // SIZE_OF_HW_MONITOR_HEADER + cmd->Data[1000] = (unsigned char)((size) & 0x00FF); + cmd->Data[1001] = (unsigned char)(((size) & 0xFF00) >> 8); + } + break; + case DS5_CAMERA_CID_HWMC_RW: + if (ctrl->p_new.p_u8) { + u16 size = *((u8 *)ctrl->p_new.p_u8 + 1) << 8; + size |= *((u8 *)ctrl->p_new.p_u8 + 0); + ret = ds5_send_hwmc(state, size + 4, + (struct hwm_cmd *)ctrl->p_new.p_u8); + } + break; + case DS5_CAMERA_CID_PWM: + if (state->is_depth) + ret = ds5_write(state, base | DS5_PWM_FREQUENCY, ctrl->val); + break; +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + case V4L2_CID_IPU_SET_SUB_STREAM: + { + u32 val = (*ctrl->p_new.p_s64 & 0xFFFF); + u16 on = val & 0x00FF; + u16 vc_id = (val >> 8) & 0x00FF; + int substream = -1; + if (vc_id < DS5_MUX_PAD_COUNT) + ret = ds5_s_state(state, vc_id); + substream = state->pad_to_substream[state->mux.last_set->mux_pad]; + dev_info(&state->client->dev, "V4L2_CID_IPU_SET_SUB_STREAM %x vc_id:%d, substream:%d, on:%d\n", val, vc_id, substream, on); + if (on == 0xff) + break; + if (vc_id > NR_OF_DS5_STREAMS - 1) + dev_err(&state->client->dev, "invalid vc %d\n", vc_id); + else + d4xx_set_sub_stream[substream] = on; + ret = 0; +#ifndef CONFIG_VIDEO_D4XX_SERDES + ret = ds5_mux_s_stream(sd, on); +#endif + } + break; +#endif +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + case V4L2_CID_LINK_FREQ: { + if ( !sensor && ctrl->p_new.p_u8) + { + /* MTL and RPL/ADL IPU6 CSI-DPHY do NOT share + * the same default link_freq. + * V4L2_CID_LINK_FREQ DS5 mux must be R/W for udev ot set DPHY platform specific link_freq + * via systemd-udevd rules. + */ + if (*ctrl->p_new.p_u8 <= (ARRAY_SIZE(link_freq_menu_items) - 1)) { + struct v4l2_ctrl *link_freq = state->ctrls.link_freq; + dev_info(&state->client->dev, + "user-modified %s index val=%d to user-val=%d", + ctrl->name, + (unsigned int) link_freq->val, + (unsigned int) *ctrl->p_new.p_u8); + link_freq->val = (s32) *ctrl->p_new.p_u8; + ret = 0; + } + } + + } + break; +#endif + } + + mutex_unlock(&state->lock); + + return ret; +} + +static int ds5_get_calibration_data(struct ds5 *state, enum table_id id, + unsigned char *table, unsigned int length) +{ + struct hwm_cmd *cmd; + int ret = -1; + int retries = 3; + u16 status = 2; + u16 table_length; + + cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + length + 4, GFP_KERNEL); + if (!cmd) { + dev_err(&state->client->dev, "%s(): Can't allocate memory\n", __func__); + return -ENOMEM; + } + + memcpy(cmd, &get_calib_data, sizeof(get_calib_data)); + cmd->param1 = id; + ds5_raw_write_with_check(state, DS5_HWMC_DATA, cmd, sizeof(struct hwm_cmd)); /* Write command data */ + ds5_write_with_check(state, DS5_HWMC_EXEC, 0x01); /* execute cmd */ + do { + if (retries != 3) + msleep_range(10); + ret = ds5_read(state, DS5_HWMC_STATUS, &status); + } while (ret && retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to get calibration table %d, fw error: %x\n", + __func__, id, status); + devm_kfree(&state->client->dev, cmd); + return status; + } + + // get table length from fw + ret = ds5_raw_read(state, DS5_HWMC_RESP_LEN, + &table_length, sizeof(table_length)); /* Read response length */ + + // read table + ds5_raw_read_with_check(state, DS5_HWMC_DATA, cmd->Data, table_length); /* Read table data */ + + // first 4 bytes are opcode HWM, not part of calibration table + memcpy(table, cmd->Data + 4, length); + devm_kfree(&state->client->dev, cmd); + return 0; +} + +static int ds5_gvd(struct ds5 *state, unsigned char *data) +{ + struct hwm_cmd cmd; + int ret = -1; + u16 length = 0; + u16 status = 2; + u8 retries = 3; + + memcpy(&cmd, &gvd, sizeof(gvd)); + ds5_raw_write_with_check(state, DS5_HWMC_DATA, &cmd, sizeof(cmd)); /* Write command data */ + ds5_write_with_check(state, DS5_HWMC_EXEC, 0x01); /* execute cmd */ + do { + if (retries != 3) + msleep_range(10); + + ret = ds5_read(state, DS5_HWMC_STATUS, &status); + } while (ret && retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to read GVD, HWM cmd status: %x\n", + __func__, status); + return status; + } + + ret = ds5_raw_read(state, DS5_HWMC_RESP_LEN, &length, sizeof(length)); /* Read response length */ + ds5_raw_read_with_check(state, DS5_HWMC_DATA, data, length); /* Read response data */ + + return ret; +} + +static int ds5_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ds5 *state = container_of(ctrl->handler, struct ds5, + ctrls.handler); + u16 log_prepare[] = {0x0014, 0xcdab, 0x000f, 0x0000, 0x0400, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; + u16 execute_cmd = 0x0001; + unsigned int i; + u32 data; + int ret = 0; + struct ds5_sensor *sensor = (struct ds5_sensor *)ctrl->priv; + u16 base = (state->is_rgb) ? DS5_RGB_CONTROL_BASE : DS5_DEPTH_CONTROL_BASE; + u16 reg; + + if (sensor) { + switch (sensor->mux_pad) { + case DS5_MUX_PAD_DEPTH: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth); + state->is_rgb = 0; + state->is_depth = 1; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb); + state->is_rgb = 1; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IR: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_y8); + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 1; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IMU: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_imu); + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + default: + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + + } + } + base = (state->is_rgb) ? DS5_RGB_CONTROL_BASE : DS5_DEPTH_CONTROL_BASE; + + dev_dbg(&state->client->dev, "%s(): %s - ctrl: %s \n", + __func__, ds5_get_sensor_name(state), ctrl->name); + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + //ret = max96724_switch_link_channel(state->dser_dev); +#endif + + switch (ctrl->id) { + + case V4L2_CID_ANALOGUE_GAIN: + if (state->is_imu) + return -EINVAL; + ret = ds5_read(state, base | DS5_MANUAL_GAIN, ctrl->p_new.p_u16); + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (state->is_imu) + return -EINVAL; + ds5_read(state, base | DS5_AUTO_EXPOSURE_MODE, ®); + *ctrl->p_new.p_u16 = reg; + /* see ds5_hw_set_auto_exposure */ + if (!state->is_rgb) { + if (reg == 1) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_APERTURE_PRIORITY; + else if (reg == 0) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_MANUAL; + } + + if (state->is_rgb && reg == 8) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_APERTURE_PRIORITY; + + break; + + case V4L2_CID_EXPOSURE_ABSOLUTE: + if (state->is_imu) + return -EINVAL; + /* see ds5_hw_set_exposure */ + ds5_read(state, base | DS5_MANUAL_EXPOSURE_MSB, ®); + data = ((u32)reg << 16) & 0xffff0000; + ds5_read(state, base | DS5_MANUAL_EXPOSURE_LSB, ®); + data |= reg; + *ctrl->p_new.p_u32 = data; + break; + + case DS5_CAMERA_CID_LASER_POWER: + if (!state->is_rgb) + ds5_read(state, base | DS5_LASER_POWER, ctrl->p_new.p_u16); + break; + + case DS5_CAMERA_CID_MANUAL_LASER_POWER: + if (!state->is_rgb) + ds5_read(state, base | DS5_MANUAL_LASER_POWER, ctrl->p_new.p_u16); + break; + + case DS5_CAMERA_CID_LOG: + // TODO: wrap HWMonitor command + // 1. prepare and send command + // 2. send command + // 3. execute command + // 4. wait for completion + ret = ds5_raw_write(state, DS5_HWMC_DATA, /* Write command data */ + log_prepare, sizeof(log_prepare)); + if (ret < 0) + return ret; + + ret = ds5_raw_write(state, DS5_HWMC_EXEC, + &execute_cmd, sizeof(execute_cmd)); /* execute cmd */ + if (ret < 0) + return ret; + + for (i = 0; i < DS5_MAX_LOG_POLL; i++) { + ret = ds5_raw_read(state, DS5_HWMC_STATUS, + &data, sizeof(data)); + dev_dbg(&state->client->dev, "%s(): log ready 0x%x\n", + __func__, data); + if (ret < 0) + return ret; + if (!data) + break; + msleep_range(5); + } + +// if (i == DS5_MAX_LOG_POLL) +// return -ETIMEDOUT; + + ret = ds5_raw_read(state, DS5_HWMC_RESP_LEN, &data, sizeof(data)); /* Read response length */ + dev_dbg(&state->client->dev, "%s(): log size 0x%x\n", __func__, data); + if (ret < 0) + return ret; + if (!data) + return 0; + if (data > 1024) + return -ENOBUFS; + ret = ds5_raw_read(state, DS5_HWMC_DATA, + ctrl->p_new.p_u8, data); + break; + case DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET: + ret = ds5_get_calibration_data(state, DEPTH_CALIBRATION_ID, + ctrl->p_new.p_u8, 256); + break; + case DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET: + ret = ds5_get_calibration_data(state, COEF_CALIBRATION_ID, + ctrl->p_new.p_u8, 512); + break; + case DS5_CAMERA_CID_FW_VERSION: + ret = ds5_read(state, DS5_FW_VERSION, &state->fw_version); + ret = ds5_read(state, DS5_FW_BUILD, &state->fw_build); + *ctrl->p_new.p_u32 = state->fw_version << 16; + *ctrl->p_new.p_u32 |= state->fw_build; + break; + case DS5_CAMERA_CID_GVD: + ret = ds5_gvd(state, ctrl->p_new.p_u8); + break; + case DS5_CAMERA_CID_AE_ROI_GET: + if (ctrl->p_new.p_u16) { + u16 len = sizeof(struct hwm_cmd) + 12; + u16 dataLen = 0; + struct hwm_cmd *ae_roi_cmd; + ae_roi_cmd = devm_kzalloc(&state->client->dev, len, GFP_KERNEL); + if (!ae_roi_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_roi_cmd, &get_ae_roi, sizeof(struct hwm_cmd)); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), ae_roi_cmd); + if (ret) { + devm_kfree(&state->client->dev, ae_roi_cmd); + return ret; + } + ret = ds5_get_hwmc(state, ae_roi_cmd->Data, len, &dataLen); + if (!ret && dataLen <= ctrl->dims[0]) + memcpy(ctrl->p_new.p_u16, ae_roi_cmd->Data + 4, 8); + devm_kfree(&state->client->dev, ae_roi_cmd); + } + break; + case DS5_CAMERA_CID_AE_SETPOINT_GET: + if (ctrl->p_new.p_s32) { + u16 len = sizeof(struct hwm_cmd) + 8; + u16 dataLen = 0; + struct hwm_cmd *ae_setpoint_cmd; + ae_setpoint_cmd = devm_kzalloc(&state->client->dev, len, GFP_KERNEL); + if (!ae_setpoint_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_setpoint_cmd, &get_ae_setpoint, sizeof(struct hwm_cmd)); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), ae_setpoint_cmd); + if (ret) { + devm_kfree(&state->client->dev, ae_setpoint_cmd); + return ret; + } + ret = ds5_get_hwmc(state, ae_setpoint_cmd->Data, len, &dataLen); + memcpy(ctrl->p_new.p_s32, ae_setpoint_cmd->Data + 4, 4); + dev_dbg(&state->client->dev, "%s(): len: %d, 0x%x \n", + __func__, dataLen, *(ctrl->p_new.p_s32)); + devm_kfree(&state->client->dev, ae_setpoint_cmd); + } + break; + case DS5_CAMERA_CID_HWMC_RW: + if (ctrl->p_new.p_u8) { + unsigned char *data = (unsigned char *)ctrl->p_new.p_u8; + u16 dataLen = 0; + u16 bufLen = ctrl->dims[0]; + ret = ds5_get_hwmc(state, data, bufLen, &dataLen); + /* ignore empty data calls */ + if (!dataLen) { + dev_dbg(sensor->sd.dev, + "%s(): DS5_CAMERA_CID_HWMC_RW empty call " + " Len: %d (ret: %x)\n", + __func__, dataLen, ret); + ret = 0; + break; + } + if (ret) { + ret = 0; // LRS doesn't expect to get errors with HWMC + break; + } + /* This is needed for librealsense, to align there code with UVC, + * last word is length - 4 bytes header length */ + dataLen -= 4; + data[bufLen - 4] = (unsigned char)(dataLen & 0x00FF); + data[bufLen - 3] = (unsigned char)((dataLen & 0xFF00) >> 8); + data[bufLen - 2] = 0; + data[bufLen - 1] = 0; + } + break; + case DS5_CAMERA_CID_PWM: + if (state->is_depth) + ds5_read(state, base | DS5_PWM_FREQUENCY, ctrl->p_new.p_u16); + break; +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + case V4L2_CID_IPU_QUERY_SUB_STREAM: { + if (sensor) { + int substream = state->pad_to_substream[sensor->mux_pad]; + int vc_id = get_sub_stream_vc_id(substream); + + dev_dbg(sensor->sd.dev, + "%s(): V4L2_CID_IPU_QUERY_SUB_STREAM sensor->mux_pad:%d" + ", vc:[%d] %d\n", + __func__, sensor->mux_pad, vc_id, substream); + *ctrl->p_new.p_s32 = substream; + state->mux.last_set = sensor; + } else { + /* we are in DS5 MUX case */ + *ctrl->p_new.p_s32 = -1; + } + + } + break; +#endif + } + return ret; +} + +static const struct v4l2_ctrl_ops ds5_ctrl_ops = { + .s_ctrl = ds5_s_ctrl, + .g_volatile_ctrl = ds5_g_volatile_ctrl, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_log = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_LOG, + .name = "Logger", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1024}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_laser_power = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_LASER_POWER, + .name = "Laser power on/off", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_manual_laser_power = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_MANUAL_LASER_POWER, + .name = "Manual laser power", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 360, + .step = 30, + .def = 150, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_fw_version = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_FW_VERSION, + .name = "fw version", + .type = V4L2_CTRL_TYPE_U32, + .dims = {1}, + .elem_size = sizeof(u32), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_gvd = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_GVD, + .name = "GVD", + .type = V4L2_CTRL_TYPE_U8, + .dims = {239}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_get_depth_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET, + .name = "get depth calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {256}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_set_depth_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET, + .name = "set depth calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {256}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_get_coeff_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET, + .name = "get coeff calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {512}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_set_coeff_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET, + .name = "set coeff calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {512}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_roi_get = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_ROI_GET, + .name = "ae roi get", + .type = V4L2_CTRL_TYPE_U8, + .dims = {8}, + .elem_size = sizeof(u16), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_roi_set = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_ROI_SET, + .name = "ae roi set", + .type = V4L2_CTRL_TYPE_U8, + .dims = {8}, + .elem_size = sizeof(u16), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_setpoint_get = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_SETPOINT_GET, + .name = "ae setpoint get", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .min = 0, + .max = 4095, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_setpoint_set = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_SETPOINT_SET, + .name = "ae setpoint set", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 4095, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_erb = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_ERB, + .name = "ERB eeprom read", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1020}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ewb = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_EWB, + .name = "EWB eeprom write", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1020}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_hwmc = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_HWMC, + .name = "HWMC", + .type = V4L2_CTRL_TYPE_U8, + .dims = {DS5_HWMC_BUFFER_SIZE + 4}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_hwmc_rw = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_HWMC_RW, + .name = "HWMC_RW", + .type = V4L2_CTRL_TYPE_U8, + .dims = {DS5_HWMC_BUFFER_SIZE}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_pwm = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_PWM, + .name = "PWM Frequency Selector", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +static const struct v4l2_ctrl_config d4xx_controls_link_freq = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_LINK_FREQ, + .name = "V4L2_CID_LINK_FREQ", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(link_freq_menu_items) - 1, + .min = 0, + .step = 0, + .def = 12, // default D4XX_LINK_FREQ_300MHZ + .qmenu_int = link_freq_menu_items, +}; +#endif + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static struct v4l2_ctrl_config d4xx_controls_q_sub_stream = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = NR_OF_DS5_SUB_STREAMS - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream, +}; + +static const struct v4l2_ctrl_config d4xx_controls_s_sub_stream = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_SET_SUB_STREAM, + .name = "set virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER64, + .max = 0xFFFF, + .min = 0, + .def = 0, + .step = 1, +}; +#endif +static int ds5_mux_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + + dev_dbg(sd->dev, "%s(): %s (%p)\n", __func__, sd->name, fh); + + mutex_lock(&state->lock); + if (state->dfu_dev.dfu_state_flag) + { + mutex_unlock(&state->lock); + return -EBUSY; + } + + state->dfu_dev.device_open_count++; + mutex_unlock(&state->lock); + + return 0; +}; + +static int ds5_mux_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + + dev_dbg(sd->dev, "%s(): %s (%p)\n", __func__, sd->name, fh); + mutex_lock(&state->lock); + state->dfu_dev.device_open_count--; + mutex_unlock(&state->lock); + return 0; +}; + +static const struct v4l2_subdev_internal_ops ds5_sensor_internal_ops = { + .open = ds5_mux_open, + .close = ds5_mux_close, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .init_state = ds5_sensor_init_state, +#endif +#endif +}; + +#ifdef CONFIG_VIDEO_D4XX_SERDES + +/* + * FIXME + * temporary solution before changing GMSL data structure or merging all 4 D457 + * sensors into one i2c device. Only first sensor node per max9295 sets up the + * link. + * + * max 24 number from this link: + * https://docs.nvidia.com/jetson/archives/r35.1/DeveloperGuide/text/ + * SD/CameraDevelopment/JetsonVirtualChannelWithGmslCameraFramework.html + * #jetson-agx-xavier-series + */ +#define MAX_DEV_NUM 24 +static struct ds5 *serdes_inited[MAX_DEV_NUM]; +#ifdef CONFIG_OF +static int ds5_board_setup(struct ds5 *state) +{ + struct device *dev = &state->client->dev; + struct device_node *node = dev->of_node; + struct device_node *ser_node; + struct i2c_client *ser_i2c = NULL; + struct device_node *dser_node; + struct i2c_client *dser_i2c = NULL; + struct device_node *gmsl; + int value = 0xFFFF; + const char *str_value; + int err; + int i; + + err = of_property_read_u32(node, "reg", &state->g_ctx.sdev_reg); + if (err < 0) { + dev_err(dev, "reg not found\n"); + goto error; + } + + err = of_property_read_u32(node, "def-addr", + &state->g_ctx.sdev_def); + if (err < 0) { + dev_err(dev, "def-addr not found\n"); + goto error; + } + + ser_node = of_parse_phandle(node, "maxim,gmsl-ser-device", 0); + if (ser_node == NULL) { + /* check compatibility with jetpack */ + ser_node = of_parse_phandle(node, "nvidia,gmsl-ser-device", 0); + if (ser_node == NULL) { + dev_err(dev, "missing %s handle\n", "[maxim|nvidia],gmsl-ser-device"); + goto error; + } + } + err = of_property_read_u32(ser_node, "reg", &state->g_ctx.ser_reg); + dev_dbg(dev, "serializer reg: 0x%x\n", state->g_ctx.ser_reg); + if (err < 0) { + dev_err(dev, "serializer reg not found\n"); + goto error; + } + + ser_i2c = of_find_i2c_device_by_node(ser_node); + of_node_put(ser_node); + + if (ser_i2c == NULL) { + err = -EPROBE_DEFER; + goto error; + } + if (ser_i2c->dev.driver == NULL) { + dev_err(dev, "missing serializer driver\n"); + goto error; + } + + state->ser_dev = &ser_i2c->dev; + + dser_node = of_parse_phandle(node, "maxim,gmsl-dser-device", 0); + if (dser_node == NULL) { + dser_node = of_parse_phandle(node, "nvidia,gmsl-dser-device", 0); + if (dser_node == NULL) { + dev_err(dev, "missing %s handle\n", "[maxim|nvidia],gmsl-dser-device"); + goto error; + } + } + + dser_i2c = of_find_i2c_device_by_node(dser_node); + of_node_put(dser_node); + + if (dser_i2c == NULL) { + err = -EPROBE_DEFER; + goto error; + } + if (dser_i2c->dev.driver == NULL) { + dev_err(dev, "missing deserializer driver\n"); + goto error; + } + + state->dser_dev = &dser_i2c->dev; + + /* populate g_ctx from DT */ + gmsl = of_get_child_by_name(node, "gmsl-link"); + if (gmsl == NULL) { + dev_err(dev, "missing gmsl-link device node\n"); + err = -EINVAL; + goto error; + } + + err = of_property_read_string(gmsl, "dst-csi-port", &str_value); + if (err < 0) { + dev_err(dev, "No dst-csi-port found\n"); + goto error; + } + state->g_ctx.dst_csi_port = + (!strcmp(str_value, "a")) ? GMSL_CSI_PORT_A : GMSL_CSI_PORT_B; + + err = of_property_read_string(gmsl, "src-csi-port", &str_value); + if (err < 0) { + dev_err(dev, "No src-csi-port found\n"); + goto error; + } + state->g_ctx.src_csi_port = + (!strcmp(str_value, "a")) ? GMSL_CSI_PORT_A : GMSL_CSI_PORT_B; + + err = of_property_read_string(gmsl, "csi-mode", &str_value); + if (err < 0) { + dev_err(dev, "No csi-mode found\n"); + goto error; + } + + if (!strcmp(str_value, "1x4")) { + state->g_ctx.csi_mode = GMSL_CSI_1X4_MODE; + } else if (!strcmp(str_value, "2x4")) { + state->g_ctx.csi_mode = GMSL_CSI_2X4_MODE; + } else if (!strcmp(str_value, "4x2")) { + state->g_ctx.csi_mode = GMSL_CSI_4X2_MODE; + } else if (!strcmp(str_value, "2x2")) { + state->g_ctx.csi_mode = GMSL_CSI_2X2_MODE; + } else { + dev_err(dev, "invalid csi mode\n"); + goto error; + } + + err = of_property_read_string(gmsl, "serdes-csi-link", &str_value); + if (err < 0) { + dev_err(dev, "No serdes-csi-link found\n"); + goto error; + } + state->g_ctx.serdes_csi_link = + (!strcmp(str_value, "a")) ? + GMSL_SERDES_CSI_LINK_A : GMSL_SERDES_CSI_LINK_B; + + err = of_property_read_u32(gmsl, "st-vc", &value); + if (err < 0) { + dev_err(dev, "No st-vc info\n"); + goto error; + } + state->g_ctx.st_vc = value; + + err = of_property_read_u32(gmsl, "vc-id", &value); + if (err < 0) { + dev_err(dev, "No vc-id info\n"); + goto error; + } + state->g_ctx.dst_vc = value; + + err = of_property_read_u32(gmsl, "num-lanes", &value); + if (err < 0) { + dev_err(dev, "No num-lanes info\n"); + goto error; + } + state->g_ctx.num_csi_lanes = value; + state->g_ctx.s_dev = dev; + + for (i = 0; i < MAX_DEV_NUM; i++) { + if (!serdes_inited[i]) { + serdes_inited[i] = state; + return 0; + } else if (serdes_inited[i]->ser_dev == state->ser_dev) { + return -ENOTSUPP; + } + } + err = -EINVAL; + dev_err(dev, "cannot handle more than %d D457 cameras\n", MAX_DEV_NUM); + +error: + return err; +} +#else +// ds5mux i2c ser des +// mux a - 2 0x42 0x48 +// mux b - 2 0x44 0x4a +// mux c - 4 0x42 0x48 +// mux d - 4 0x44 0x4a +// axiomtek +// mux a - 2 0x42 0x48 +// mux b - 2 0x44 0x4a +// mux c - 4 0x62 0x68 +// mux d - 4 0x64 0x6a + +static unsigned short ds5_serdes_csi_output(int c_bus, int c_addr); + +static int ds5_board_setup(struct ds5 *state) +{ + struct device *dev = &state->client->dev; + struct serdes_platform_data *pdata = dev->platform_data; + struct i2c_adapter *adapter = state->client->adapter; + int bus = adapter->nr; + int err = 0; + int i; + char suffix = pdata->suffix; + struct serdes_subdev_info *spdata = &pdata->subdev_info[0]; + char serdes_suffix[5]; /* suffix string for subdevs */ + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + /* Derive Deser CSI link mapping */ + switch (spdata->suffix[0]) { + case 'b': + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_B; + break; + case 'c': + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_C; + break; + case 'd': + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_D; + break; + default: + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_A; + break; + }; +#endif + + static struct max9295_pdata max9295_pdata = { + .is_prim_ser = 1, // todo: configurable + .def_addr = 0x40, // todo: configurable + .d4xx_hacks = 1, + }; +#if defined(CONFIG_VIDEO_D4XX_MAX96724) + static struct max96724_pdata max96724_pdata = { + .max_src = 4, + .csi_mode = GMSL_CSI_4X2_MODE, //GMSL_CSI_2X4_MODE, + .d4xx_hacks = 1, + }; + max96724_pdata.src_link = state->g_ctx.serdes_csi_link; + max96724_pdata.csi_phy = (pdata->bus_type == V4L2_MBUS_CSI2_CPHY) ? GMSL_CSI_CPHY : GMSL_CSI_DPHY; + max96724_pdata.csi_out = ds5_serdes_csi_output(bus, pdata->subdev_info[0].board_info.addr); + + static struct i2c_board_info i2c_info_des = { + I2C_BOARD_INFO("d4xx-max96724", 0x27), + .platform_data = &max96724_pdata, + }; +#elif defined(CONFIG_VIDEO_D4XX_MAX96712) + static struct max96724_pdata max96724_pdata = { + .max_src = 4, + .csi_mode = GMSL_CSI_2X4_MODE, //GMSL_CSI_4X2_MODE, + .csi_phy = GMSL_CSI_DPHY, + .csi_out = GMSL_CSI_PORT_A, + .d4xx_hacks = 1, + }; + + max96724_pdata.src_link = state->g_ctx.serdes_csi_link; + + static struct i2c_board_info i2c_info_des = { + I2C_BOARD_INFO("d4xx-max96724", 0x6b), + .platform_data = &max96724_pdata, + }; +#else + static struct max9296_pdata max9296_pdata = { + .max_src = 2, + .csi_mode = GMSL_CSI_2X4_MODE, + .d4xx_hacks = 1, + }; + static struct i2c_board_info i2c_info_des = { + I2C_BOARD_INFO("d4xx-max9296", 0x48), + .platform_data = &max9296_pdata, + }; +#endif + static struct i2c_board_info i2c_info_ser = { + I2C_BOARD_INFO("d4xx-max9295", 0x42), + .platform_data = &max9295_pdata, + }; + + i2c_info_ser.addr = pdata->subdev_info[0].ser_alias; //0x42, 0x44, 0x62, 0x64 + state->ser_i2c = i2c_new_client_device(adapter, &i2c_info_ser); + + i2c_info_des.addr = pdata->subdev_info[0].board_info.addr; //0x48, 0x4a, 0x68, 0x6a + + state->dser_st.bus_nr = bus; + state->dser_st.addr = i2c_info_des.addr; + + /* look for already registered max9296, use same context if found */ + for (i = 0; i < MAX_DEV_NUM; i++) { + if (serdes_inited[i]) { + if ( serdes_inited[i]->dser_st.isolated + && bus == serdes_inited[i]->dser_st.bus_nr + && serdes_inited[i]->dser_st.addr == i2c_info_des.addr ) { + + dev_info(dev, "Isolate unresponsive sensor/serializer AGGREGATED on MAX9296 device 0x%x\n", + i2c_info_des.addr); + state->aggregated = 1; + break; + } else if ( serdes_inited[i]->dser_i2c + && bus == serdes_inited[i]->dser_i2c->adapter->nr + && serdes_inited[i]->dser_i2c->addr == i2c_info_des.addr) { + dev_info(dev, "MAX9296 AGGREGATION found device on 0x%x\n", i2c_info_des.addr); + state->dser_i2c = serdes_inited[i]->dser_i2c; + state->aggregated = 1; + break; + } else if ( pdata->subdev_info[0].aggregated_link ) { + dev_info(dev, "PDATA sensor/serializer AGGREGATED on MAX9296 device 0x%x GMSL port %s\n", + i2c_info_des.addr, + pdata->subdev_info[0].suffix); + state->aggregated = 1; + } + } + } + + /* + * suffix syntaxe for multiple D457 connected to 1 Deser : + * - IPU7 both standalone or aggregated links - + * - IPU6 when standalone when aggregated + */ + if (strnlen(spdata->suffix, sizeof(spdata->suffix)) > 1) { + snprintf(serdes_suffix, sizeof(serdes_suffix), "%s", spdata->suffix); + } else if (state->aggregated) { + suffix += 6; + snprintf(serdes_suffix, sizeof(serdes_suffix), "%c", spdata->suffix); + } else { + snprintf(serdes_suffix, sizeof(serdes_suffix), "%c", spdata->suffix); + } + dev_info(dev, "Init SerDes %s on %d@0x%x<->%d@0x%x\n", + serdes_suffix, + bus, pdata->subdev_info[0].board_info.addr, //48 + bus, pdata->subdev_info[0].ser_alias); //42 + + if (!state->dser_i2c) + state->dser_i2c = i2c_new_client_device(adapter, &i2c_info_des); + + if (state->ser_i2c == NULL) { + err = -EPROBE_DEFER; + dev_err(dev, "missing serializer client\n"); + goto error; + } + if (state->ser_i2c->dev.driver == NULL) { + err = -EPROBE_DEFER; + dev_err(dev, "missing serializer driver\n"); + goto error; + } + if (state->dser_i2c == NULL) { + err = -EPROBE_DEFER; + dev_err(dev, "missing deserializer client\n"); + goto error; + } + if (state->dser_i2c->dev.driver == NULL) { + err = -EPROBE_DEFER; + dev_err(dev, "missing deserializer driver\n"); + goto error; + } + + // reg + + state->g_ctx.sdev_reg = state->client->addr; + state->g_ctx.sdev_def = 0x10;// def-addr TODO: configurable + // Address reassignment for d4xx-a 0x10->0x12 + dev_info(dev, "Address reassignment for %s-%s 0x%x->0x%x\n", + pdata->subdev_info[0].board_info.type, serdes_suffix, + state->g_ctx.sdev_def, state->g_ctx.sdev_reg); + //0x42, 0x44, 0x62, 0x64 + state->g_ctx.ser_reg = pdata->subdev_info[0].ser_alias; + dev_info(dev, "serializer %s: i2c-%d@0x%x\n", + serdes_suffix, + state->ser_i2c->adapter->nr, + state->g_ctx.ser_reg); + + if (err < 0) { + dev_err(dev, "serializer reg not found\n"); + goto error; + } + + state->ser_dev = &state->ser_i2c->dev; + + dev_info(dev, "deserializer %c: i2c-%d@0x%x\n", + suffix, + state->dser_i2c->adapter->nr, + state->dser_i2c->addr); + + + state->dser_dev = &state->dser_i2c->dev; + + /* populate g_ctx from pdata */ +#if defined(CONFIG_VIDEO_D4XX_MAX96724) + state->g_ctx.dst_csi_port = (max96724_pdata.csi_out == GMSL_CSI_PORT_A && + max96724_pdata.csi_mode == GMSL_CSI_2X4_MODE) ? + GMSL_CSI_PORT_B : max96724_pdata.csi_out; +#elif defined(CONFIG_VIDEO_D4XX_MAX96712) + state->g_ctx.dst_csi_port = (max96724_pdata.csi_mode == GMSL_CSI_4X2_MODE) ? GMSL_CSI_PORT_A : GMSL_CSI_PORT_B; +#else + state->g_ctx.dst_csi_port = GMSL_CSI_PORT_A; +#endif + state->g_ctx.src_csi_port = GMSL_CSI_PORT_B; + state->g_ctx.csi_mode = GMSL_CSI_1X4_MODE; + +#if !defined(CONFIG_VIDEO_D4XX_MAX96724) && !defined(CONFIG_VIDEO_D4XX_MAX96712) + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_A; + if (state->aggregated) { // dual aggregated-link fallback + dev_info(dev, "configure GMSL port B\n"); + state->g_ctx.serdes_csi_link = GMSL_SERDES_CSI_LINK_B; + } else { + dev_info(dev, "configure GMSL port A\n"); + } +#endif + state->g_ctx.st_vc = 0; + state->g_ctx.dst_vc = 0; + + state->g_ctx.num_csi_lanes = 2; + state->g_ctx.s_dev = dev; + + for (i = 0; i < MAX_DEV_NUM; i++) { + if (!serdes_inited[i]) { + serdes_inited[i] = state; + return 0; + } else if (serdes_inited[i]->ser_dev == state->ser_dev) { + dev_warn(dev, "same max9295 i2c device (%p) already listed\n",state->ser_dev); + return 0; + } + } + err = -EINVAL; + dev_err(dev, "cannot handle more than %d D457 cameras\n", MAX_DEV_NUM); + +error: + return err; +} + +#endif +static const struct regmap_config ds5_regmap_max9296 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static const struct regmap_config ds5_regmap_max9295 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; +static struct mutex serdes_lock__; + +static int ds5_gmsl_serdes_setup(struct ds5 *state) +{ + int err = 0; + int des_err = 0; + int ser_err = 0; + struct device *dev; + + if (!state || !state->ser_dev || !state->dser_dev || !state->client) + return -EINVAL; + + dev = &state->client->dev; + + mutex_lock(&serdes_lock__); + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + max96724_power_off(state->dser_dev); + /* For now no separate power on required for serializer device */ + max96724_power_on(state->dser_dev); +#else + max9296_power_off(state->dser_dev); + /* For now no separate power on required for serializer device */ + max9296_power_on(state->dser_dev); +#endif + + dev_dbg(dev, "Setup SERDES addressing and control pipeline\n"); + /* setup serdes addressing and control pipeline */ +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + err = max96724_setup_link(state->dser_dev, &state->client->dev); +#else + err = max9296_setup_link(state->dser_dev, &state->client->dev); +#endif + if (err) { + dev_err(dev, "gmsl deserializer link config failed\n"); + goto error; + } + msleep(100); + + ser_err = max9295_setup_control(state->ser_dev); + /* if ser setup failed, graceful deser setup fallback */ + if (ser_err) { + dev_warn(dev, "gmsl serializer invalid source\n"); + err= -ENOTSUPP; + } + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + des_err = max96724_setup_control(state->dser_dev, &state->client->dev); +#else + des_err = max9296_setup_control(state->dser_dev, &state->client->dev); +#endif + if (des_err) { + dev_warn(dev, "gmsl deserializer setup failed\n"); + /* overwrite err only if deser setup also failed */ + err = ( err == -ENOTSUPP) ? err : des_err; + } + +error: + mutex_unlock(&serdes_lock__); + return err; +} + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT +static short sensor_vc[NR_OF_DS5_STREAMS * 4] = {0,6,5,3, 1,4,2,7, 2,7,4,1, 3,5,0,6}; +#else +static short sensor_vc[NR_OF_DS5_STREAMS * 4] = {0,1,2,3, 2,3,0,1, 1,0,3,2, 3,2,1,0}; +#endif +module_param_array(sensor_vc, ushort, NULL, 0444); +MODULE_PARM_DESC(sensor_vc, "VC set for sensors\n" +#ifdef CONFIG_VIDEO_D4XX_MAX967XX_DT_VC_EXT + "\t\tsensor_vc=0,6,5,3,1,4,2,7,2,7,4,1,3,5,0,6"); +#else + "\t\tsensor_vc=0,1,2,3,2,3,0,1,1,0,3,2,3,2,1,0"); +#endif +#else +static short sensor_vc[NR_OF_DS5_STREAMS * 2] = {0,1,2,3, 2,3,0,1}; +module_param_array(sensor_vc, ushort, NULL, 0444); +MODULE_PARM_DESC(sensor_vc, "VC set for sensors\n" + "\t\tsensor_vc=0,1,2,3,2,3,0,1"); +#endif + +//#define PLATFORM_AXIOMTEK 1 +#ifdef PLATFORM_AXIOMTEK +static short serdes_bus[4] = {5, 5, 5, 5}; +#else +static short serdes_bus[4] = {2, 2, 4, 4}; +#endif +module_param_array(serdes_bus, ushort, NULL, 0444); +#if defined(CONFIG_VIDEO_D4XX_MAX96724) +MODULE_PARM_DESC(serdes_bus, "d4xx-max96724 deserializer i2c bus\n" + "\t\tserdes_bus=0,0,1,1"); +#elif defined(CONFIG_VIDEO_D4XX_MAX96712) +MODULE_PARM_DESC(serdes_bus, "d4xx-max96712 deserializer i2c bus\n" + "\t\tserdes_bus=0,0,0,0"); + +#else +MODULE_PARM_DESC(serdes_bus, "d4xx-max9296 deserializer i2c bus\n" + "\t\tserdes_bus=2,2,4,4"); +#endif + +// Deserializer addresses can be 0x40 0x48 0x4a +#ifdef PLATFORM_AXIOMTEK +static unsigned short des_addr[4] = {0x48, 0x4a, 0x68, 0x6c}; +#else +static unsigned short des_addr[4] = {0x48, 0x4a, 0x48, 0x4a}; +#endif +module_param_array(des_addr, ushort, NULL, 0444); +#if defined(CONFIG_VIDEO_D4XX_MAX96724) +MODULE_PARM_DESC(des_addr, "d4xx-max96724 deserializer i2c address\n" + "\t\tdes_addr=0x27,0x27,0x27,0x27"); + +static short des_csi[4] = {0, 2, 0, 2}; +module_param_array(des_csi, ushort, NULL, 0444); +MODULE_PARM_DESC(des_csi, "d4xx-max96724 deserializer csi output\n" + "\t\tdes_csi=0,2,0,2"); +#elif defined(CONFIG_VIDEO_D4XX_MAX96712) +MODULE_PARM_DESC(des_addr, "d4xx-max96712 deserializer i2c address\n" + "\t\tdes_addr=0x27,0x27,0x6b,0x6b"); +#else +MODULE_PARM_DESC(des_addr, "d4xx-max9296 deserializer i2c address\n" + "\t\tdes_addr=0x48,0x4a,0x48,0x4a"); +#endif + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) +static unsigned short ds5_serdes_csi_output(int c_bus, int c_addr) +{ + int i = 0; + for (i = 0; i < 4; i++) + if (serdes_bus[i] == c_bus && + des_addr[i] == c_addr && + des_csi[i] <= GMSL_CSI_PORT_D) + return des_csi[i]; + + return GMSL_CSI_PORT_A; +} +#endif + +static int ds5_i2c_addr_setting(struct i2c_client *c, struct ds5 *state) +{ + int i = 0; + int c_addr_save = c->addr; + int c_bus = c->adapter->nr; + for (i = 0; i < 4; i++) { + if (c_bus == serdes_bus[i]) { + c->addr = des_addr[i]; +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + dev_info(&c->dev, "Set max96724@%d-0x%x Link reset\n", + c_bus, c->addr); +#else + dev_info(&c->dev, "Set max9296@%d-0x%x Link reset\n", + c_bus, c->addr); +#endif + ds5_write_8(state, 0x1000, 0x40); // sensor reset link + } + } + // restore original slave address + c->addr = c_addr_save; + + return 0; +} +#endif +static int ds5_serdes_setup(struct ds5 *state) +{ + int ret = 0; + struct i2c_client *c = state->client; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + int i = 0, c_bus = -1; + int c_bus_new = c->adapter->nr; + + for (i = 0; i < MAX_DEV_NUM; i++) { +#ifdef CONFIG_VIDEO_D4XX_SERDES + if (serdes_inited[i] && serdes_inited[i]->dser_st.isolated) { + c_bus = serdes_inited[i]->dser_st.bus_nr; + if (c_bus == c->adapter->nr) { + dev_info(&c->dev, "Already configured Isolated camera for bus %d\n", c_bus); + c_bus_new = -1; + break; + } + } else if (serdes_inited[i] && serdes_inited[i]->dser_i2c) { +#else + if (serdes_inited[i] && serdes_inited[i]->dser_i2c) { +#endif + c_bus = serdes_inited[i]->dser_i2c->adapter->nr; + if (c_bus == c->adapter->nr) { + dev_info(&c->dev, "Already configured Addressable camera for bus %d\n", c_bus); + c_bus_new = -1; + break; + } + } else { + break; + } + } + + if (c_bus_new >= 0) { + dev_info(&c->dev, "Apply multiple camera i2c addr setting for bus %d\n", c_bus_new); + ret = ds5_i2c_addr_setting(c, state); + if (ret) { + dev_err(&c->dev, "failed apply i2c addr setting\n"); + return ret; + } + } +#endif + ret = ds5_board_setup(state); + if (ret) { + if (ret == -ENOTSUPP) + return 0; + dev_err(&c->dev, "board setup failed\n"); + return ret; + } + dev_info(&c->dev, "gmsl board ds5 setup\n"); + + /* Pair sensor to serializer dev */ + ret = max9295_sdev_pair(state->ser_dev, &state->g_ctx); + if (ret) { + dev_err(&c->dev, "gmsl serializer pairing failed\n"); + return ret; + } + dev_info(&c->dev, "gmsl serdes paired \n"); + + /* Register sensor to deserializer dev */ +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + ret = max96724_sdev_register(state->dser_dev, &state->g_ctx); +#else + ret = max9296_sdev_register(state->dser_dev, &state->g_ctx); +#endif + if (ret) { + dev_err(&c->dev, "gmsl deserializer register failed\n"); + return ret; + } + + ret = ds5_gmsl_serdes_setup(state); + if (ret) { + if (ret == -ENOTSUPP) { + dev_warn(&c->dev, "graceful fallback, gmsl serdes setup\n"); + if (c_bus_new >= 0) + dev_info(&c->dev, "Unresponding serializer on Newly initialized bus %d\n", + c_bus_new); + else + dev_info(&c->dev, "Unresponding serializer on Already initialized bus %d\n", + state->dser_i2c->adapter->nr); + } else + dev_err(&c->dev, "%s gmsl serdes setup failed\n", __func__); + return ret; + } + + ret = max9295_init_settings(state->ser_dev); + if (ret) { + dev_warn(&c->dev, "%s, failed to init max9295 settings\n", + __func__); + return ret; + } + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + ret = max96724_init_settings(state->dser_dev); +#else + ret = max9296_init_settings(state->dser_dev); +#endif + if (ret) { + dev_warn(&c->dev, "%s, failed to init max9x settings\n", + __func__); + return ret; + } + + return ret; +} +#endif +enum state_sid { + DEPTH_SID = 0, + RGB_SID, + IR_SID, + IMU_SID, + MUX_SID = -1 +}; + +static int ds5_ctrl_init(struct ds5 *state, int sid) +{ + const struct v4l2_ctrl_ops *ops = &ds5_ctrl_ops; + struct ds5_ctrls *ctrls = &state->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + struct v4l2_subdev *sd = &state->mux.sd.subdev; + int ret = -1; + struct ds5_sensor *sensor = NULL; + + switch (sid) { + case DEPTH_SID: + hdl = &ctrls->handler_depth; + sensor = &state->depth.sensor; + break; + case RGB_SID: + hdl = &ctrls->handler_rgb; + sensor = &state->rgb.sensor; + break; + case IR_SID: + hdl = &ctrls->handler_y8; + sensor = &state->ir.sensor; + break; + case IMU_SID: + hdl = &ctrls->handler_imu; + sensor = &state->imu.sensor; + break; + default: + /* control for MUX */ + hdl = &ctrls->handler; + sensor = NULL; + break; + } + + dev_dbg(NULL, "%s():%d sid: %d\n", __func__, __LINE__, sid); + ret = v4l2_ctrl_handler_init(hdl, DS5_N_CONTROLS); + if (ret < 0) { + v4l2_err(sd, "cannot init ctrl handler (%d)\n", ret); + return ret; + } + + if (sid == DEPTH_SID || sid == IR_SID) { + ctrls->laser_power = v4l2_ctrl_new_custom(hdl, + &ds5_ctrl_laser_power, + sensor); + ctrls->manual_laser_power = v4l2_ctrl_new_custom(hdl, + &ds5_ctrl_manual_laser_power, + sensor); + } + + /* Total gain */ + if (sid == DEPTH_SID || sid == IR_SID) { + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ANALOGUE_GAIN, + 16, 248, 1, 16); + } else if (sid == RGB_SID) { + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ANALOGUE_GAIN, + 0, 128, 1, 64); + } + + if ((ctrls->gain) && (sid >= DEPTH_SID && sid < IMU_SID)) { + ctrls->gain->priv = sensor; + ctrls->gain->flags = + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + } + if (sid >= DEPTH_SID && sid < IMU_SID) { + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_APERTURE_PRIORITY, + ~((1 << V4L2_EXPOSURE_MANUAL) | + (1 << V4L2_EXPOSURE_APERTURE_PRIORITY)), + V4L2_EXPOSURE_APERTURE_PRIORITY); + + if (ctrls->auto_exp) { + ctrls->auto_exp->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + ctrls->auto_exp->priv = sensor; + } + } + + /* Exposure time: V4L2_CID_EXPOSURE_ABSOLUTE default unit: 100 us. */ + if (sid == DEPTH_SID || sid == IR_SID) { + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 1, MAX_DEPTH_EXP, 1, DEF_DEPTH_EXP); + } else if (sid == RGB_SID) { + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 1, MAX_RGB_EXP, 1, DEF_RGB_EXP); + } + + if ((ctrls->exposure) && (sid >= DEPTH_SID && sid < IMU_SID)) { + ctrls->exposure->priv = sensor; + ctrls->exposure->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + /* override default int type to u32 to match SKU & UVC */ + ctrls->exposure->type = V4L2_CTRL_TYPE_U32; + } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + ctrls->link_freq = v4l2_ctrl_new_custom(hdl, &d4xx_controls_link_freq, sensor); + /* MTL and RPL/ADL IPU6 CSI-DPHY do NOT share + * the same default link_freq. + * V4L2_CID_LINK_FREQ DS5 mux must be R/W for udev ot set DPHY platform specific link_freq + * via systemd-udevd rules. + */ + if (sensor && ctrls->link_freq ) + ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; +#endif +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + if (state->aggregated) { + d4xx_controls_q_sub_stream.def = NR_OF_DS5_SUB_STREAMS; + d4xx_controls_q_sub_stream.min = NR_OF_DS5_SUB_STREAMS; + d4xx_controls_q_sub_stream.max = NR_OF_DS5_SUB_STREAMS * 2 - 1; + } else { + d4xx_controls_q_sub_stream.def = 0; + d4xx_controls_q_sub_stream.min = 0; + d4xx_controls_q_sub_stream.max = NR_OF_DS5_SUB_STREAMS - 1; + } + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream, sensor); + + if (ctrls->query_sub_stream) + ctrls->query_sub_stream->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + + ctrls->set_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_s_sub_stream, sensor); +#endif + if (hdl->error) { + v4l2_err(sd, "error creating controls (%d)\n", hdl->error); + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + // Add these after v4l2_ctrl_handler_setup so they won't be set up + if (sid >= DEPTH_SID && sid < IMU_SID) { + ctrls->log = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_log, sensor); + ctrls->fw_version = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_fw_version, sensor); + ctrls->gvd = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_gvd, sensor); + ctrls->get_depth_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_get_depth_calib, sensor); + ctrls->set_depth_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_set_depth_calib, sensor); + ctrls->get_coeff_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_get_coeff_calib, sensor); + ctrls->set_coeff_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_set_coeff_calib, sensor); + ctrls->ae_roi_get = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_roi_get, sensor); + ctrls->ae_roi_set = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_roi_set, sensor); + ctrls->ae_setpoint_get = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_setpoint_get, sensor); + ctrls->ae_setpoint_set = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_setpoint_set, sensor); + ctrls->erb = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_erb, sensor); + ctrls->ewb = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ewb, sensor); + ctrls->hwmc = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_hwmc, sensor); + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_hwmc_rw, sensor); + } + // DEPTH custom + if (sid == DEPTH_SID) + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_pwm, sensor); + // IMU custom + if (sid == IMU_SID) + ctrls->fw_version = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_fw_version, sensor); + + switch (sid) { + case DEPTH_SID: + state->depth.sensor.sd.ctrl_handler = hdl; + dev_info(state->depth.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d, %p, %s\n", + __func__, __LINE__, + state->depth.sensor.mux_pad, + state->depth.sensor.sd.ctrl_handler, + state->depth.sensor.sd.name); + break; + case RGB_SID: + state->rgb.sensor.sd.ctrl_handler = hdl; + dev_info(state->rgb.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d, %p, %s\n", + __func__, __LINE__, + state->rgb.sensor.mux_pad, + state->rgb.sensor.sd.ctrl_handler, + state->rgb.sensor.sd.name); + break; + case IR_SID: + state->ir.sensor.sd.ctrl_handler = hdl; + dev_info(state->ir.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d, %p, %s\n", + __func__, __LINE__, + state->ir.sensor.mux_pad, + state->ir.sensor.sd.ctrl_handler, + state->ir.sensor.sd.name); + break; + case IMU_SID: + state->imu.sensor.sd.ctrl_handler = hdl; + dev_info(state->imu.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d, %p, %s\n", + __func__, __LINE__, + state->imu.sensor.mux_pad, + state->imu.sensor.sd.ctrl_handler, + state->imu.sensor.sd.name); + break; + default: + state->mux.sd.subdev.ctrl_handler = hdl; +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + dev_info(state->mux.sd.subdev.dev, + "%s():%d set ctrl_handler for MUX: %p, %s, substream id %d \n", + __func__, __LINE__, + state->mux.sd.subdev.ctrl_handler, + state->mux.sd.subdev.name, + d4xx_controls_q_sub_stream.def); +#endif + break; + } + + return 0; +} + +static int ds5_sensor_init(struct i2c_client *c, struct ds5 *state, + struct ds5_sensor *sensor, const struct v4l2_subdev_ops *ops, + const char *name) +{ + struct v4l2_subdev *sd = &sensor->sd; + struct media_entity *entity = &sensor->sd.entity; + struct media_pad *pad = &sensor->pad; + dev_t *dev_num = &state->client->dev.devt; +#ifndef CONFIG_OF + struct serdes_platform_data *dpdata = c->dev.platform_data; + char suffix = dpdata->suffix; +#endif + v4l2_i2c_subdev_init(sd, c, ops); + // See tegracam_v4l2.c tegracam_v4l2subdev_register() + // Set owner to NULL so we can unload the driver module + sd->owner = NULL; + sd->internal_ops = &ds5_sensor_internal_ops; + sd->grp_id = *dev_num; + v4l2_set_subdevdata(sd, state); +#ifndef CONFIG_OF + struct serdes_subdev_info *spdata = &dpdata->subdev_info[0]; + /* + * suffix syntaxe for multiple D457 connected to 1 Deser : + * - IPU7 - + * - IPU6 when standalone when agregated + */ + if (strnlen(spdata->suffix, sizeof(spdata->suffix)) > 1) { + snprintf(sd->name, sizeof(sd->name), "D4XX %s %s", name, spdata->suffix); + }else if (state->aggregated & 1) { + suffix += 6; + snprintf(sd->name, sizeof(sd->name), "D4XX %s %c", name, suffix); + } +#else + snprintf(sd->name, sizeof(sd->name), "D4XX %s %d-%04x", + name, i2c_adapter_id(c->adapter), c->addr); +#endif + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#else + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; +#endif + pad->flags = MEDIA_PAD_FL_SOURCE; + entity->obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; + entity->function = MEDIA_ENT_F_CAM_SENSOR; + + dev_info(&c->dev, + "%s():%d init media_entity %s, type:%x, func:%x\n", + __func__, __LINE__, + sd->name, + entity->obj_type, + entity->function); + + return media_entity_pads_init(entity, 1, pad); +} + +static int ds5_sensor_register(struct ds5 *state, struct ds5_sensor *sensor) +{ + struct v4l2_subdev *sd = &sensor->sd; + struct media_entity *entity = &sensor->sd.entity; + int ret = -1; + +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + dev_err(sd->dev, "failed to init v4l2 subdev stream: %d\n", ret); + return ret; + } +#endif + + // FIXME: is async needed? + ret = v4l2_device_register_subdev(state->mux.sd.subdev.v4l2_dev, sd); + if (ret < 0) { + dev_err(sd->dev, "%s(): %d: %d\n", __func__, __LINE__, ret); + return ret; + } + + ret = media_create_pad_link(entity, 0, + &state->mux.sd.subdev.entity, sensor->mux_pad, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(sd->dev, "%s(): %d: %d\n", __func__, __LINE__, ret); + goto e_sd; + } + + dev_dbg(sd->dev, "%s(): 0 -> %d\n", __func__, sensor->mux_pad); + + sensor->initialized = true; +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) + sensor->routing_initialized = false; +#endif +#endif + + return 0; + +e_sd: + v4l2_device_unregister_subdev(sd); + + return ret; +} + +static void ds5_sensor_remove(struct ds5_sensor *sensor) +{ + v4l2_device_unregister_subdev(&sensor->sd); + + media_entity_cleanup(&sensor->sd.entity); +} + +static int ds5_depth_init(struct i2c_client *c, struct ds5 *state) +{ + /* Which mux pad we're connecting to */ + state->depth.sensor.mux_pad = DS5_MUX_PAD_DEPTH; + return ds5_sensor_init(c, state, &state->depth.sensor, + &ds5_depth_subdev_ops, "depth"); +} + +static int ds5_ir_init(struct i2c_client *c, struct ds5 *state) +{ + state->ir.sensor.mux_pad = DS5_MUX_PAD_IR; + return ds5_sensor_init(c, state, &state->ir.sensor, + &ds5_ir_subdev_ops, "ir"); +} + +static int ds5_rgb_init(struct i2c_client *c, struct ds5 *state) +{ + state->rgb.sensor.mux_pad = DS5_MUX_PAD_RGB; + return ds5_sensor_init(c, state, &state->rgb.sensor, + &ds5_rgb_subdev_ops, "rgb"); +} + +static int ds5_imu_init(struct i2c_client *c, struct ds5 *state) +{ + state->imu.sensor.mux_pad = DS5_MUX_PAD_IMU; + return ds5_sensor_init(c, state, &state->imu.sensor, + &ds5_imu_subdev_ops, "imu"); +} + +/* No locking needed */ +static int ds5_mux_enum_mbus_code(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_mbus_code_enum *mce) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_mbus_code_enum tmp = *mce; + struct v4l2_subdev *remote_sd; + int ret = -1; + + dev_dbg(&state->client->dev, "%s(): %s \n", __func__, sd->name); + switch (mce->pad) { + case DS5_MUX_PAD_IR: + remote_sd = &state->ir.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_IMU: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + if (mce->index >= state->ir.sensor.n_formats + + state->depth.sensor.n_formats) + return -EINVAL; + + /* + * First list Left node / Motion Tracker formats, then depth. + * This should also help because D16 doesn't have a direct + * analog in MIPI CSI-2. + */ + if (mce->index < state->ir.sensor.n_formats) { + remote_sd = &state->ir.sensor.sd; + } else { + tmp.index = mce->index - state->ir.sensor.n_formats; + remote_sd = &state->depth.sensor.sd; + } + + break; + default: + return -EINVAL; + } + + tmp.pad = 0; + if (state->is_rgb) + remote_sd = &state->rgb.sensor.sd; + if (state->is_depth) + remote_sd = &state->depth.sensor.sd; + if (state->is_y8) + remote_sd = &state->ir.sensor.sd; + if (state->is_imu) + remote_sd = &state->imu.sensor.sd; + /* Locks internally */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + ret = ds5_sensor_enum_mbus_code(remote_sd, cfg, &tmp); +#else + ret = ds5_sensor_enum_mbus_code(remote_sd, v4l2_state, &tmp); +#endif + if (!ret) + mce->code = tmp.code; + + return ret; +} +static int ds5_state_to_pad(struct ds5 *state) { + int pad = -1; + if (state->is_depth) + pad = DS5_MUX_PAD_DEPTH; + if (state->is_y8) + pad = DS5_MUX_PAD_IR; + if (state->is_rgb) + pad = DS5_MUX_PAD_RGB; + if (state->is_imu) + pad = DS5_MUX_PAD_IMU; + return pad; +} + +/* No locking needed */ +static int ds5_mux_enum_frame_size(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_frame_size_enum tmp = *fse; + struct v4l2_subdev *remote_sd; + u32 pad = fse->pad; + int ret = -1; + + tmp.pad = 0; + pad = ds5_state_to_pad(state); + + switch (pad) { + case DS5_MUX_PAD_IR: + remote_sd = &state->ir.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_IMU: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + /* + * Assume, that different sensors don't support the same formats + * Try the Depth sensor first, then the Motion Tracker + */ + remote_sd = &state->depth.sensor.sd; + ret = ds5_sensor_enum_frame_size(remote_sd, NULL, &tmp); + if (!ret) { + *fse = tmp; + fse->pad = pad; + return 0; + } + + remote_sd = &state->ir.sensor.sd; + break; + default: + return -EINVAL; + } + + /* Locks internally */ + ret = ds5_sensor_enum_frame_size(remote_sd, NULL, &tmp); + if (!ret) { + *fse = tmp; + fse->pad = pad; + } + + return ret; +} + +/* No locking needed */ +static int ds5_mux_enum_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_frame_interval_enum tmp = *fie; + struct v4l2_subdev *remote_sd; + u32 pad = fie->pad; + int ret = -1; + + tmp.pad = 0; + + dev_dbg(state->depth.sensor.sd.dev, + "%s(): pad %d code %x width %d height %d\n", + __func__, pad, tmp.code, tmp.width, tmp.height); + + pad = ds5_state_to_pad(state); + + switch (pad) { + case DS5_MUX_PAD_IR: + remote_sd = &state->ir.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_IMU: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + /* Similar to ds5_mux_enum_frame_size() above */ + if (state->is_rgb) + remote_sd = &state->rgb.sensor.sd; + else + remote_sd = &state->ir.sensor.sd; + ret = ds5_sensor_enum_frame_interval(remote_sd, NULL, &tmp); + if (!ret) { + *fie = tmp; + fie->pad = pad; + return 0; + } + + remote_sd = &state->ir.sensor.sd; + break; + default: + return -EINVAL; + } + + /* Locks internally */ + ret = ds5_sensor_enum_frame_interval(remote_sd, NULL, &tmp); + if (!ret) { + *fie = tmp; + fie->pad = pad; + } + + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static int _ds5_mux_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_krouting *routing) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + int ret = 0; + unsigned int num_active_routes = 0; + unsigned int i; + + struct v4l2_mbus_framefmt *ffmt; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + for (i = 0; i < routing->num_routes; ++i) { + const struct v4l2_subdev_route *route = &routing->routes[i]; + const struct media_pad *pads = sd->entity.pads; + + if (route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) + num_active_routes++; + } + + if (num_active_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -E2BIG; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret < 0) { + dev_warn(sd->dev, "%s(): %s routing validation error : %d\n", + __func__, sd->name, ret); + return ret; + } + + ret = v4l2_subdev_set_routing(sd, v4l2_state, routing); + if (ret < 0) { + dev_warn(sd->dev, "%s(): %s set routing error : %d\n", + __func__, sd->name, ret); + return ret; + } + + struct v4l2_subdev_stream_configs *stream_configs= &v4l2_state->stream_configs; + + for (unsigned int i = 0; i < stream_configs->num_configs; i++) { + u32 sink_pad = stream_configs->configs[i].pad; + u32 sink_stream = stream_configs->configs[i].stream; + struct v4l2_subdev_state *remote_v4l2_state; + struct ds5_sensor *s = NULL; + + switch (sink_pad) { + case DS5_MUX_PAD_DEPTH: + s = &state->depth.sensor; + ffmt = &s->format; + break; + case DS5_MUX_PAD_RGB: + s = &state->rgb.sensor; + ffmt = &s->format; + break; + case DS5_MUX_PAD_IR: + s = &state->ir.sensor; + ffmt = &s->format; + break; + case DS5_MUX_PAD_IMU: + s = &state->imu.sensor; + ffmt = &s->format; + break; + case DS5_MUX_PAD_EXTERNAL: + break; + default: + dev_err(sd->dev, "Invalid sink pad %u\n", sink_pad); + return -EINVAL; + } + + stream_configs->configs[i].fmt = *ffmt; + + dev_dbg(sd->dev, "%s:%d: stream_configs->configs[%u] pad/stream=%u/%u format: code %x, res %ux%u\n", + __func__, __LINE__, i, sink_pad, sink_stream, + ffmt->code, ffmt->width, ffmt->height); + } + state->routing_initialized = true; + return 0; +} + +static int ds5_mux_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + int ret = 0; + + dev_dbg(sd->dev,"%s:%d: v4l2_state=%p, sd_format_whence=%s\n", + __func__, __LINE__, v4l2_state, + which == V4L2_SUBDEV_FORMAT_TRY ? "V4L2_SUBDEV_FORMAT_TRY" : "V4L2_SUBDEV_FORMAT_ACTIVE" ); + + ret = _ds5_mux_set_routing(sd, v4l2_state, routing); + if (ret < 0) { + dev_warn(sd->dev, "%s():%d: %s add .num_routes=%u failed (sd_state %p)\n", + __func__, __LINE__, sd->name, routing->num_routes, v4l2_state); + } else { + dev_dbg(sd->dev, "%s():%d: %s add .num_routes=%u applied (sd_state %p)\n", + __func__, __LINE__, sd->name, routing->num_routes, v4l2_state); + } + return ret; +} +#endif +#endif + +/* No locking needed */ +static int ds5_mux_set_fmt(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_format *fmt) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_mbus_framefmt *ffmt; + struct ds5_sensor *sensor = state->mux.last_set; + u32 pad = sensor->mux_pad; + int ret = 0; + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + int substream = -1; + + dev_dbg(sd->dev, "%s:%d: fmt->pad:%d, sensor->mux_pad: %d, \ + for sensor: %s\n", + __func__, __LINE__, + fmt->pad, pad, + sensor->sd.name); + + if (pad != DS5_MUX_PAD_EXTERNAL) + ds5_s_state_pad(state, pad); + + sensor = state->mux.last_set; + + switch (pad) { + case DS5_MUX_PAD_DEPTH: + case DS5_MUX_PAD_IR: + case DS5_MUX_PAD_RGB: + case DS5_MUX_PAD_IMU: + //ffmt = &ds5_ffmts[pad]; + ffmt = &sensor->format;//ds5_ffmts[pad]; + break; + case DS5_MUX_PAD_EXTERNAL: + ffmt = &ds5_ffmts[pad]; + break; + default: + return -EINVAL; + } + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ffmt->width = fmt->format.width; + ffmt->height = fmt->format.height; + ffmt->code = fmt->format.code; + } + + fmt->format = *ffmt; + + // substream = pad_to_substream[fmt->pad]; + substream = state->pad_to_substream[pad]; + + if (substream != -1) { + set_sub_stream_fmt(substream, ffmt->code); + set_sub_stream_h(substream, ffmt->height); + set_sub_stream_w(substream, ffmt->width); + set_sub_stream_dt(substream, mbus_code_to_mipi(ffmt->code)); + } + + dev_dbg(sd->dev, "%s(): fmt->pad:%d, sensor->mux_pad: %d, \ + code: 0x%x: %ux%u substream:%d for sensor: %s\n", + __func__, + fmt->pad, pad, fmt->format.code, + fmt->format.width, fmt->format.height, + substream, sensor->sd.name); +#else + struct media_pad *remote_pad; + struct v4l2_subdev *remote_sd; + u32 other_pad, other_stream; + unsigned int i; + + dev_dbg(sd->dev, "%s(): fmt->pad/stream:%u / %u (sensor->mux_pad: %u) \ + code: 0x%x: %ux%u for %s\n", + __func__, + fmt->pad, fmt->stream, pad, + fmt->format.code, fmt->format.width, fmt->format.height, + sd->name); + + /* No transcoding, sink and sensor formats must match.*/ + if (!(sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SOURCE)) { + + switch (fmt->pad) { + case DS5_MUX_PAD_DEPTH: + sensor = &state->depth.sensor; + break; + case DS5_MUX_PAD_IR: + sensor = &state->ir.sensor; + break; + case DS5_MUX_PAD_RGB: + sensor = &state->rgb.sensor; + break; + case DS5_MUX_PAD_IMU: + sensor = &state->imu.sensor; + break; + default: + sensor = state->mux.last_set; + ret = -EINVAL; + break; + } + ffmt = &sensor->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ffmt->width = fmt->format.width; + ffmt->height = fmt->format.height; + ffmt->code = fmt->format.code; + } + + fmt->format = *ffmt; + + dev_dbg(sd->dev, "%s(): %s sink pad/stream: %u/%u, \ + fmt code/size: 0x%x: %ux%u\n", + __func__, + sd->name, fmt->pad, fmt->stream, + ffmt->code, ffmt->width, ffmt->height); + + return ret; + } + + /* Store the format and propagate it to the source pad. */ + ffmt = v4l2_subdev_state_get_format(v4l2_state, fmt->pad, fmt->stream); + if (!ffmt) { + dev_warn(sd->dev, "Failed to get %s format for mux pad %u \n", + sd->name, fmt->pad); + return -EINVAL; + } + *ffmt = fmt->format; + + dev_dbg(sd->dev, "%s(): %s source pad/stream: %u/%u, \ + fmt code/size: 0x%x: %ux%u\n", + __func__, + sd->name, fmt->pad, fmt->stream, + ffmt->code, ffmt->width, ffmt->height); + + /* propagate format to following sink pad */ + ret = v4l2_subdev_routing_find_opposite_end(&v4l2_state->routing, + fmt->pad, + fmt->stream, + &other_pad, + &other_stream); + if (ret) { + dev_warn(sd->dev, "ignored %s opposite stream %s end for mux pad %u\n", + sd->name, fmt->stream, fmt->pad); + return 0; + } + + ffmt = v4l2_subdev_state_get_format(v4l2_state, other_pad, other_stream); + if (!ffmt) { + dev_warn(sd->dev, "Failed to get %s format for mux pad %u \n", + sd->name, other_pad); + return -EINVAL; + } + *ffmt = fmt->format; + + dev_dbg(sd->dev, "%s(): %s sink pad/stream: %u/%u, \ + fmt code/size: 0x%x/%ux%u\n", + __func__, + sd->name, other_pad, other_stream, + ffmt->code, ffmt->width, ffmt->height); + + if (!(sd->entity.pads[other_pad].flags & MEDIA_PAD_FL_SINK)) + return 0; + + if (other_pad < 0 || other_pad >= sd->entity.num_pads) + return -EINVAL; + + remote_pad = media_pad_remote_pad_first(&state->mux.pads[other_pad]); + if (IS_ERR_OR_NULL(remote_pad)) { + dev_err(sd->dev, "Failed to find remote pad for %s %u", + sd->name, other_pad); + return IS_ERR(remote_pad) ? PTR_ERR(remote_pad) : -ENODEV; + } + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + if (!remote_sd) { + dev_err(sd->dev, "Failed to resolve entity %s to subdev", + remote_pad->entity->name); + return -ENODEV; + } + + dev_dbg(sd->dev, "%s(): %s remote pad/stream : %u/%u, \ + fmt code/size: 0x%x/%ux%u\n", + __func__, + remote_sd->name, remote_pad->index, fmt->stream, + ffmt->code, ffmt->width, ffmt->height); + + ret = v4l2_subdev_call(remote_sd, pad, set_fmt, v4l2_state, fmt); + if (ret) { + dev_warn(sd->dev, "ignored %s pad %u -> %s pad %u remote set_fmt (%d)\n", + sd->name, fmt->pad, remote_sd->name, remote_pad->index, ret); + return 0; + } + +#endif + return ret; +} + +/* No locking needed */ +static int ds5_mux_get_fmt(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 10) + struct v4l2_subdev_pad_config *cfg, +#else + struct v4l2_subdev_state *v4l2_state, +#endif + struct v4l2_subdev_format *fmt) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + u32 pad = fmt->pad; + int ret = 0; + struct ds5_sensor *sensor = state->mux.last_set; + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) + pad = sensor->mux_pad; + if (pad != DS5_MUX_PAD_EXTERNAL) + ds5_s_state_pad(state, pad); +#else + pad = ds5_state_to_pad(state); +#endif + sensor = state->mux.last_set; + dev_dbg(sd->dev, "%s(): %u %s %p\n", __func__, pad, ds5_get_sensor_name(state), state->mux.last_set); + + switch (pad) { + case DS5_MUX_PAD_DEPTH: + case DS5_MUX_PAD_IR: + case DS5_MUX_PAD_RGB: + case DS5_MUX_PAD_IMU: + fmt->format = sensor->format; + break; + case DS5_MUX_PAD_EXTERNAL: + fmt->format = ds5_ffmts[pad]; + break; + default: + return -EINVAL; + } + dev_dbg(sd->dev, "%s(): fmt->pad:%d, sensor->mux_pad:%u size:%d-%d, code:0x%x field:%d, color:%d\n", + __func__, fmt->pad, pad, + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.field, fmt->format.colorspace); + return ret; +#else + struct v4l2_mbus_framefmt *ffmt; + struct media_pad *remote_pad; + struct v4l2_subdev *remote_sd; + u32 other_pad, other_stream; + unsigned int i; + + dev_dbg(sd->dev, "%s(): Enter %s pad/stream %u/%u\n", + __func__, sd->name, + fmt->pad, fmt->stream); + + switch (fmt->pad) { + case DS5_MUX_PAD_DEPTH: + sensor = &state->depth.sensor; + break; + case DS5_MUX_PAD_IR: + sensor = &state->ir.sensor; + break; + case DS5_MUX_PAD_RGB: + sensor = &state->rgb.sensor; + break; + case DS5_MUX_PAD_IMU: + sensor = &state->imu.sensor; + break; + case DS5_MUX_PAD_EXTERNAL: + sensor = state->mux.last_set; + break; + default: + return -EINVAL; + } + //fallback get_fmt + fmt->format = sensor->format; + + dev_dbg(sd->dev, + "%s(): %s fallback fmt->pad:%d, fmt->stream:%u, size:%d-%d, code:0x%x field:%d, color:%d\n", + __func__, sd->name, fmt->pad, fmt->stream, + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.field, fmt->format.colorspace); + + mutex_lock(&state->mutex); + + if (IS_ERR_OR_NULL(fmt)) { + dev_err(sd->dev, "Invalid fmt %p", fmt); + ret = -EINVAL; + goto unlock; + } + + if (fmt->pad < 0 || fmt->pad >= sd->entity.num_pads) { + dev_err(sd->dev, "%s invalid pad %d", __func__, fmt->pad); + ret = -EINVAL; + goto unlock; + } + + // get_fmt callback on sink pad + if (!(sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SOURCE)) { + ffmt = v4l2_subdev_state_get_format(v4l2_state, fmt->pad, fmt->stream); + if (!ffmt) { + dev_warn(sd->dev, "Failed to get %s format for mux pad %u \n", + sd->name, fmt->pad); + ret = -EINVAL; + goto unlock; + } + + fmt->format = *ffmt; + + dev_dbg(sd->dev, + "%s(): %s sink fmt->pad:%d, fmt->stream:%u, size:%d-%d, code:0x%x field:%d, color:%d\n", + __func__, sd->name, fmt->pad, fmt->stream, + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.field, fmt->format.colorspace); + goto unlock; + } + + // get_fmt routing propagate source to sink pad format by stream id + ffmt = v4l2_subdev_state_get_opposite_stream_format(v4l2_state, + fmt->pad, fmt->stream); + if (!ffmt) { + dev_warn(sd->dev, "Failed to get %s format for opposite mux pad %u \n", + sd->name, fmt->pad); + ret = -EINVAL; + goto unlock; + } + fmt->format = *ffmt; + dev_dbg(sd->dev, + "%s(): %s source fmt->pad:%d opposite fmt->stream:%u, size:%d-%d, code:0x%x field:%d, color:%d\n", + __func__, sd->name, fmt->pad, fmt->stream, + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.field, fmt->format.colorspace); + + unlock: + mutex_unlock(&state->mutex); + dev_dbg(sd->dev, "%s(): Exit %s pad/stream %u/%u\n", + __func__, sd->name, + fmt->pad, fmt->stream); + + return ret; +#endif +} + +static int ds5_mux_g_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) +/* pad ops */ + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +#else +/* Video ops */ + struct v4l2_subdev_frame_interval *fi) +#endif +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct ds5_sensor *sensor = NULL; + + if (NULL == sd || NULL == fi) + return -EINVAL; + + sensor = state->mux.last_set; + + fi->interval.numerator = 1; + fi->interval.denominator = sensor->config.framerate; + + dev_dbg(sd->dev, "%s(): %s %u\n", __func__, sd->name, + fi->interval.denominator); + + return 0; +} + +static u16 __ds5_probe_framerate(const struct ds5_resolution *res, u16 target) +{ + int i; + u16 framerate; + + for (i = 0; i < res->n_framerates; i++) { + framerate = res->framerates[i]; + if (target <= framerate) + return framerate; + } + + return res->framerates[res->n_framerates - 1]; +} + +static int ds5_mux_s_frame_interval(struct v4l2_subdev *sd, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) +/* pad ops */ + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval *fi) +#else +/* Video ops */ + struct v4l2_subdev_frame_interval *fi) +#endif +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct ds5_sensor *sensor = NULL; + u16 framerate = 1; + + if (NULL == sd || NULL == fi || fi->interval.numerator == 0) + return -EINVAL; + + sensor = state->mux.last_set; + + framerate = fi->interval.denominator / fi->interval.numerator; + framerate = __ds5_probe_framerate(sensor->config.resolution, framerate); + sensor->config.framerate = framerate; + fi->interval.numerator = 1; + fi->interval.denominator = framerate; + + dev_dbg(sd->dev, "%s(): %s %u\n", __func__, sd->name, framerate); + + return 0; +} + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#ifndef CONFIG_VIDEO_D4XX_SERDES +static int d4xx_reset_oneshot(struct ds5 *state) +{ + struct serdes_platform_data *dpdata = state->client->dev.platform_data; + struct i2c_board_info *deser = dpdata->deser_board_info; + + int s_addr = state->client->addr; + int n_addr = deser->addr; + int ret = 0; + + if (n_addr) { + state->client->addr = n_addr; + dev_warn(&state->client->dev, "One-shot reset 0x%x enable auto-link\n", n_addr); + /* One-shot reset enable auto-link */ + ret = max9296_write_8(state, MAX9296_CTRL0, RESET_ONESHOT | AUTO_LINK | LINK_A); + state->client->addr = s_addr; + /* delay to settle link */ + msleep(100); + } + + return ret; +} +#endif +static int ds5_state_to_vc(struct ds5 *state) { + int pad = 0; + if (state->is_depth) { + pad = DS5_MUX_PAD_DEPTH; + } + if (state->is_rgb) { + pad = DS5_MUX_PAD_RGB; + } + if (state->is_y8) { + pad = DS5_MUX_PAD_IR; + } + if (state->is_imu) { + pad = DS5_MUX_PAD_IMU; + } + + return state->pad_to_vc[pad]; +} +#endif +static int ds5_mux_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + u16 streaming, status; + int ret = 0; + unsigned int i = 0; + int restore_val = 0; + u16 config_status_base, stream_status_base, stream_id, vc_id; + struct ds5_sensor *sensor = state->mux.last_set; + + // spare duplicate calls + if (sensor->streaming == on) + return 0; + if (state->is_depth) { + config_status_base = DS5_DEPTH_CONFIG_STATUS; + stream_status_base = DS5_DEPTH_STREAM_STATUS; + stream_id = DS5_STREAM_DEPTH; + vc_id = 0; + } else if (state->is_rgb) { + config_status_base = DS5_RGB_CONFIG_STATUS; + stream_status_base = DS5_RGB_STREAM_STATUS; + stream_id = DS5_STREAM_RGB; + vc_id = 1; + } else if (state->is_y8) { + config_status_base = DS5_IR_CONFIG_STATUS; + stream_status_base = DS5_IR_STREAM_STATUS; + stream_id = DS5_STREAM_IR; + vc_id = 2; + } else if (state->is_imu) { + config_status_base = DS5_IMU_CONFIG_STATUS; + stream_status_base = DS5_IMU_STREAM_STATUS; + stream_id = DS5_STREAM_IMU; + vc_id = 3; + } else { + return -EINVAL; + } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + vc_id = ds5_state_to_vc(state); +#endif +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +#ifdef CONFIG_VIDEO_D4XX_SERDES + vc_id = state->g_ctx.dst_vc; +#endif +#endif + dev_dbg(&state->client->dev, "s_stream for stream %s, vc:%d, SENSOR=%s on = %d\n", + sensor->sd.name, vc_id, ds5_get_sensor_name(state), on); + + restore_val = sensor->streaming; + sensor->streaming = on; + + if (on) { +#ifdef CONFIG_VIDEO_D4XX_SERDES +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + // set manually, need to configure vc in pdata + state->g_ctx.dst_vc = vc_id; +#endif +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + sensor->pipe_id = + max96724_get_available_pipe_id(state->dser_dev, + (int)state->g_ctx.dst_vc, + state->g_ctx.serdes_csi_link); + if (sensor->pipe_id < 0) { + dev_err(&state->client->dev, + "No free pipe in max967xx\n"); +#else + sensor->pipe_id = + max9296_get_available_pipe_id(state->dser_dev, + (int)state->g_ctx.dst_vc); + if (sensor->pipe_id < 0) { + dev_err(&state->client->dev, + "No free pipe in max9296\n"); +#endif + ret = -(ENOSR); + goto restore_s_state; + } +#endif + + ret = ds5_configure(state); + if (ret) + goto restore_s_state; + + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_START | stream_id); + if (ret < 0) + goto restore_s_state; + + // check streaming status from FW + for (i = 0; i < DS5_START_MAX_COUNT; i++) { + ds5_read(state, stream_status_base, &streaming); + ds5_read(state, config_status_base, &status); + if ((status & DS5_STATUS_STREAMING) && + streaming == DS5_STREAM_STREAMING) + break; + + msleep_range(DS5_START_POLL_TIME); + } + + if (DS5_START_MAX_COUNT == i) { + dev_err(&state->client->dev, + "start streaming failed, exit on timeout\n"); + /* notify fw */ + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_STOP | stream_id); + ret = -EAGAIN; + goto restore_s_state; + } else { + dev_dbg(&state->client->dev, "started after %dms\n", + i * DS5_START_POLL_TIME); + } + /* +#ifdef CONFIG_VIDEO_D4XX_SERDES +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + max96724_check_status(state->dser_dev, state->g_ctx.serdes_csi_link); + max9295_check_status(state->ser_dev); +#endif +#endif + */ + } else { // off + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_STOP | stream_id); + if (ret < 0) + goto restore_s_state; + +#ifdef CONFIG_VIDEO_D4XX_SERDES + // reset data path when Y12I streaming is done + if (state->is_y8 && + state->ir.sensor.config.format->data_type == + GMSL_CSI_DT_RGB_888) { +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + max96724_reset_oneshot(state->dser_dev, state->g_ctx.serdes_csi_link); +#else + max9296_reset_oneshot(state->dser_dev); +#endif + } +#ifndef CONFIG_TEGRA_CAMERA_PLATFORM + // reset for IPU6 + streaming = 0; + for (i = 0; i < ARRAY_SIZE(d4xx_set_sub_stream); i++) { + if (d4xx_set_sub_stream[i]) { + streaming = 1; + break; + } + } + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + dev_warn(&state->client->dev, "max96724_reset_oneshot\n"); + max96724_reset_oneshot(state->dser_dev, + state->g_ctx.serdes_csi_link); +#else + if (!streaming) { + dev_warn(&state->client->dev, "max9296_reset_oneshot\n"); + max9296_reset_oneshot(state->dser_dev); + } +#endif +#endif +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + if (max96724_release_pipe(state->dser_dev, sensor->pipe_id) < 0) +#else + if (max9296_release_pipe(state->dser_dev, sensor->pipe_id) < 0) +#endif + dev_warn(&state->client->dev, "release pipe failed\n"); + sensor->pipe_id = -1; +#else +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) + d4xx_reset_oneshot(state); +#endif +#endif + } + + ds5_read(state, config_status_base, &status); + ds5_read(state, stream_status_base, &streaming); + dev_dbg(&state->client->dev, + "%s %s, stream_status 0x%x:%x, config_status 0x%x:%x ret=%d\n", + ds5_get_sensor_name(state), + (on)?"START":"STOP", + stream_status_base, streaming, + config_status_base, status, ret); + + return ret; + +restore_s_state: +#ifdef CONFIG_VIDEO_D4XX_SERDES + if (on && sensor->pipe_id >= 0) { +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + if (max96724_release_pipe(state->dser_dev, sensor->pipe_id) < 0) +#else + if (max9296_release_pipe(state->dser_dev, sensor->pipe_id) < 0) +#endif + dev_warn(&state->client->dev, "release pipe failed\n"); + sensor->pipe_id = -1; + } +#endif + + ds5_read(state, config_status_base, &status); + dev_err(&state->client->dev, + "%s stream toggle failed! %x status 0x%04x\n", + ds5_get_sensor_name(state), restore_val, status); + + sensor->streaming = restore_val; + + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static int ds5_mux_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + unsigned int i; + + desc->num_entries = V4L2_FRAME_DESC_ENTRY_MAX; + + for (i = 0; i < desc->num_entries; i++) { + desc->entry[i].flags = 0; + desc->entry[i].pixelcode = MEDIA_BUS_FMT_FIXED; + desc->entry[i].length = 0; + if (i == desc->num_entries - 1) { + desc->entry[i].pixelcode = 0x12; + desc->entry[i].length = 68; + } + } + return 0; +} + +#else +#define CSI2_SUBDEV_MAX_STREAM_ID 63 + +static int ds5_mux_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + unsigned int i; + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_route *route; + int ret = -EINVAL; + unsigned int num_active_routes = 0; + + if (NULL == sd) + return ret; + + if (pad < 0 || pad >= DS5_MUX_PAD_COUNT) + return ret; + + dev_dbg(sd->dev, "%s(): set desc name=%s pad=%u from each route\n", + __func__, sd->name, pad); + + struct v4l2_subdev_state *v4l2_state = v4l2_subdev_lock_and_get_active_state(sd); + + desc->num_entries = 0; //initialize + + for_each_active_route(&v4l2_state->routing, route) { + if (route->source_pad != pad) + continue; + + if (route->sink_pad >= DS5_MUX_PAD_COUNT) { + ret = -EINVAL; + dev_err(sd->dev, "Found invalid route sink_pad!"); + goto out_unlock; + } + + if (!(route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + + if (num_active_routes >= V4L2_FRAME_DESC_ENTRY_MAX - 2) { + ret = -EINVAL; + dev_err(sd->dev, "Too many routes %u per-CSI ports (max=%u)!", + num_active_routes, + V4L2_FRAME_DESC_ENTRY_MAX); + goto out_unlock; + } + + /* Get sensor based on sink pad from routing + */ + struct ds5_sensor *sensor = NULL; + switch (route->sink_pad) { + case DS5_MUX_PAD_DEPTH: + sensor = &state->depth.sensor; + break; + case DS5_MUX_PAD_IR: + sensor = &state->ir.sensor; + break; + case DS5_MUX_PAD_RGB: + sensor = &state->rgb.sensor; + break; + case DS5_MUX_PAD_IMU: + sensor = &state->imu.sensor; + break; + default: + dev_err(sd->dev, "Invalid sink pad %u for stream %u\n", + route->sink_pad, route->sink_stream); + ret = -EINVAL; + goto out_unlock; + } + + struct ds5_format *mf = sensor->config.format; + + if (state->pad_to_substream[route->sink_pad] != route->sink_stream) + continue; + + dev_dbg(sd->dev, + "sensor %s stream:%u pad:%u size:%dx%d code::0x%x vc:%u " \ + "mbus:0x%x dt:0x%x\n", + sensor->sd.name, route->sink_stream, route->sink_pad, + sensor->format.width, sensor->format.height, sensor->format.code, + state->pad_to_vc[route->sink_pad], + mf->mbus_code, mf->data_type); + + // for Sensor + desc->entry[desc->num_entries].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + desc->entry[desc->num_entries].stream = route->sink_stream; + desc->entry[desc->num_entries].pixelcode = mf->mbus_code; + desc->entry[desc->num_entries].length = 0; + desc->entry[desc->num_entries].bus.csi2.vc = state->pad_to_vc[route->sink_pad]; + desc->entry[desc->num_entries].bus.csi2.dt = mf->data_type; + desc->num_entries++; + } + + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + ret = 0; + + out_unlock: + v4l2_subdev_unlock_state(v4l2_state); + return ret; +} + +static int ds5_mux_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + u32 pad, u64 streams_mask) +{ + + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + u32 sink_pad, sink_stream; + int ret, i; + + if (NULL == sd) + return -EINVAL; + + for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) { + if (streams_mask & BIT_ULL(i)) + break; + } + + ret = v4l2_subdev_routing_find_opposite_end(&v4l2_state->routing, pad, i, + &sink_pad, &sink_stream); + if (ret) + return ret; + + dev_dbg(sd->dev, "%s(): pad/stream=%u/%u\n", + __func__, sink_pad, sink_stream); + + ds5_s_state_pad(state, sink_pad); + + d4xx_set_sub_stream[i] = 1; + + ret = ds5_mux_s_stream(sd, 1); + + return 0; + +} +static int ds5_mux_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + u32 pad, u64 streams_mask) +{ + + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + u32 sink_pad, sink_stream; + int ret, i; + + if (NULL == sd) + return -EINVAL; + + + for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) { + if (streams_mask & BIT_ULL(i)) + break; + } + + ret = v4l2_subdev_routing_find_opposite_end(&v4l2_state->routing, pad, i, + &sink_pad, &sink_stream); + if (ret) + return ret; + + dev_dbg(sd->dev, "%s(): pad/stream=%u/%u\n", + __func__, sink_pad, sink_stream); + + ds5_s_state_pad(state, sink_pad); + + d4xx_set_sub_stream[i] = 0; + + ret = ds5_mux_s_stream(sd, 0); + + return 0; +} +#endif +#endif + +static const struct v4l2_subdev_pad_ops ds5_mux_pad_ops = { + .enum_mbus_code = ds5_mux_enum_mbus_code, + .enum_frame_size = ds5_mux_enum_frame_size, + .enum_frame_interval = ds5_mux_enum_frame_interval, + .get_fmt = ds5_mux_get_fmt, + .set_fmt = ds5_mux_set_fmt, + .get_frame_desc = ds5_mux_get_frame_desc, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .set_routing = ds5_mux_set_routing, + .enable_streams = ds5_mux_enable_streams, + .disable_streams = ds5_mux_disable_streams, +#endif +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + .get_frame_interval = ds5_mux_g_frame_interval, + .set_frame_interval = ds5_mux_s_frame_interval, +#endif +}; + +static const struct v4l2_subdev_core_ops ds5_mux_core_ops = { + //.s_power = ds5_mux_set_power, + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_video_ops ds5_mux_video_ops = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) + .g_frame_interval = ds5_mux_g_frame_interval, + .s_frame_interval = ds5_mux_s_frame_interval, +#endif + .s_stream = ds5_mux_s_stream, +}; + +static const struct media_entity_operations ds5_mux_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_ops ds5_mux_subdev_ops = { + .core = &ds5_mux_core_ops, + .pad = &ds5_mux_pad_ops, + .video = &ds5_mux_video_ops, +}; + +static int ds5_mux_registered(struct v4l2_subdev *sd) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + int ret = ds5_sensor_register(state, &state->depth.sensor); + if (ret < 0) + return ret; + + ret = ds5_sensor_register(state, &state->ir.sensor); + if (ret < 0) + goto e_depth; + + ret = ds5_sensor_register(state, &state->rgb.sensor); + if (ret < 0) + goto e_rgb; + + ret = ds5_sensor_register(state, &state->imu.sensor); + if (ret < 0) + goto e_imu; + + return 0; + +e_imu: + v4l2_device_unregister_subdev(&state->rgb.sensor.sd); + +e_rgb: + v4l2_device_unregister_subdev(&state->ir.sensor.sd); + +e_depth: + v4l2_device_unregister_subdev(&state->depth.sensor.sd); + + return ret; +} + +static void ds5_mux_unregistered(struct v4l2_subdev *sd) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + ds5_sensor_remove(&state->imu.sensor); + ds5_sensor_remove(&state->rgb.sensor); + ds5_sensor_remove(&state->ir.sensor); + ds5_sensor_remove(&state->depth.sensor); +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static int ds5_mux_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + unsigned int i; + int ret = 0; + + if (state->routing_initialized) + return 0; + + state->pad_to_vc[DS5_MUX_PAD_EXTERNAL]= -1; + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + /* Derive Deser CSI link mapping */ + switch (state->g_ctx.serdes_csi_link) { + case GMSL_SERDES_CSI_LINK_A: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + break; + case GMSL_SERDES_CSI_LINK_B: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[4]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[5]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[6]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[7]; + break; + case GMSL_SERDES_CSI_LINK_C: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[8]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[9]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[10]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[11]; + break; + case GMSL_SERDES_CSI_LINK_D: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[12]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[13]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[14]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[15]; + break; + default: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + break; + } +#else + if (!state->aggregated) { + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + } else { + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[4]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[5]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[6]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[7]; + } +#endif + + /* match for IPU/IPU7 CSI2 video capture pads */ + for (i = 0; i < ARRAY_SIZE(state->pad_to_substream); i++) + state->pad_to_substream[i] = -1; + + /* single d4xx + * 0, vc 0, depth A + * 1, vc 1, depth C or vc 0, depth A meta data + * 2, vc 1, RGB A + * 3, vc 2, RGB C or vc 1, RGB A meta data + * 4, vc 2, IR A + * 5, vc 3, IMU A + */ + /* aggregated d4xx + * 6, vc 2, depth B + * 7, vc 3, depth D or vc 2, depth B meta data + * 8, vc 3, RGB B + * 9, vc 3, RGB D or vc 3, RGB B meta data + * 10, vc 0, IR B + * 11, vc 1, IMU B + * 12, vc 0, IR C + * 13, vc 1, IMU C + * 14, vc 0, IR D + * 15, vc 1, IMU D + */ + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + /* Derive Deser CSI link mapping */ + switch (state->g_ctx.serdes_csi_link) { + case GMSL_SERDES_CSI_LINK_A: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + break; + case GMSL_SERDES_CSI_LINK_B: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 6; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 8; + state->pad_to_substream[DS5_MUX_PAD_IR] = 10; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 11; + break; + case GMSL_SERDES_CSI_LINK_C: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 1; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 3; + state->pad_to_substream[DS5_MUX_PAD_IR] = 12; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 13; + break; + case GMSL_SERDES_CSI_LINK_D: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 7; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 9; + state->pad_to_substream[DS5_MUX_PAD_IR] = 14; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 15; + break; + default: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + break; + } +#else + if (!state->aggregated) { + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + } + else { + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 6; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 8; + state->pad_to_substream[DS5_MUX_PAD_IR] = 10; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 11; + } +#endif + + //TODO: add RGB and depth meta data (fmt: 1x68 MEDIA_BUS_FMT_SGRBG8_1X8) as V4L2_SUBDEV_ROUTE_FL_INACTIVE routes + struct v4l2_subdev_route ds5_mux_routes[] = { + //DEPTH + { + .sink_pad = DS5_MUX_PAD_DEPTH, + .sink_stream = state->pad_to_substream[DS5_MUX_PAD_DEPTH], + .source_pad = 0, + .source_stream = state->pad_to_substream[DS5_MUX_PAD_DEPTH], + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + //RGB + { + .sink_pad = DS5_MUX_PAD_RGB, + .sink_stream = state->pad_to_substream[DS5_MUX_PAD_RGB], + .source_pad = 0, + .source_stream = state->pad_to_substream[DS5_MUX_PAD_RGB], + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + //IR + { + .sink_pad = DS5_MUX_PAD_IR, + .sink_stream = state->pad_to_substream[DS5_MUX_PAD_IR], + .source_pad = 0, + .source_stream = state->pad_to_substream[DS5_MUX_PAD_IR], + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + //IMU + { + .sink_pad = DS5_MUX_PAD_IMU, + .sink_stream = state->pad_to_substream[DS5_MUX_PAD_IMU], + .source_pad = 0, + .source_stream = state->pad_to_substream[DS5_MUX_PAD_IMU], + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting ds5_mux_routing = { + .num_routes = ARRAY_SIZE(ds5_mux_routes), + .routes = ds5_mux_routes, + }; + + ret = _ds5_mux_set_routing(sd, v4l2_state, &ds5_mux_routing); + if (ret < 0) { + dev_warn(sd->dev, "%s():%d: %s initial .num_routes=%u ignored (sd_state %p)\n", + __func__, __LINE__, sd->name, ds5_mux_routing.num_routes, v4l2_state); + return 0; + } + + dev_dbg(sd->dev, "%s(): %s initial .num_routes=%u applied (sd_state %p)\n", + __func__, sd->name, ds5_mux_routing.num_routes, v4l2_state); + for (i = 0; i < ARRAY_SIZE(state->pad_to_substream); i++) + if (state->pad_to_substream[i] >= 0) + dev_dbg(sd->dev, "pad[%d]->substream=%d\n", + i, state->pad_to_substream[i]); + return 0; +} +#endif +#endif + +static const struct v4l2_subdev_internal_ops ds5_mux_internal_ops = { + .open = ds5_mux_open, + .close = ds5_mux_close, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + .init_state = ds5_mux_init_state, +#endif +#endif + .registered = ds5_mux_registered, + .unregistered = ds5_mux_unregistered, +}; + +static int ds5_mux_register(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->mux.sd.subdev; + int ret; + +#ifndef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + ret = v4l2_subdev_init_finalize(sd); + if (ret) { + dev_err(sd->dev, "failed to init v4l2 subdev stream: %d\n", ret); + goto subdev_end; + } +#endif + ret = v4l2_async_register_subdev(sd); + if (ret) + dev_err(sd->dev, "v4l register failed: %d", ret); + +subdev_end: + return ret; +} + +static int ds5_hw_init(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->mux.sd.subdev; + u16 mipi_status, n_lanes, phy, drate_min, drate_max; + int ret = ds5_read(state, DS5_MIPI_SUPPORT_LINES, &n_lanes); + if (!ret) + ret = ds5_read(state, DS5_MIPI_SUPPORT_PHY, &phy); + + if (!ret) + ret = ds5_read(state, DS5_MIPI_DATARATE_MIN, &drate_min); + + if (!ret) + ret = ds5_read(state, DS5_MIPI_DATARATE_MAX, &drate_max); + + if (!ret) + dev_dbg(sd->dev, "%s(): %d: %u lanes, phy %x, data rate %u-%u\n", + __func__, __LINE__, n_lanes, phy, drate_min, drate_max); + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + n_lanes = state->mux.sd.numlanes; +#else + n_lanes = 2; +#endif + + ret = ds5_write(state, DS5_MIPI_LANE_NUMS, n_lanes - 1); + if (!ret) + ret = ds5_write(state, DS5_MIPI_LANE_DATARATE, MIPI_LANE_RATE); + + if (!ret) + ret = ds5_read(state, DS5_MIPI_CONF_STATUS, &mipi_status); + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + dev_dbg(sd->dev, "%s(): %d phandle %x node %s status %x\n", __func__, __LINE__, + c->dev.of_node->phandle, c->dev.of_node->full_name, mipi_status); +#endif + + return ret; +} + +static int ds5_mux_init(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->mux.sd.subdev; + struct media_entity *entity = &state->mux.sd.subdev.entity; + struct media_pad *pads = state->mux.pads, *pad; + unsigned int i; + int ret; +#ifndef CONFIG_OF + struct serdes_platform_data *dpdata = c->dev.platform_data; + char suffix = dpdata->suffix; +#endif + v4l2_i2c_subdev_init(sd, c, &ds5_mux_subdev_ops); + // See tegracam_v4l2.c tegracam_v4l2subdev_register() + // Set owner to NULL so we can unload the driver module + sd->owner = NULL; + sd->internal_ops = &ds5_mux_internal_ops; + v4l2_set_subdevdata(sd, state); +#ifdef CONFIG_OF + snprintf(sd->name, sizeof(sd->name), "DS5 mux %d-%04x", + i2c_adapter_id(c->adapter), c->addr); +#else + struct serdes_subdev_info *spdata = &dpdata->subdev_info[0]; + /* + * suffix syntaxe for multiple D457 connected to 1 Deser : + * - IPU7 - + * - IPU6 when standalone when agregated + */ + if (strnlen(spdata->suffix, sizeof(spdata->suffix)) > 1) { + snprintf(sd->name, sizeof(sd->name), "DS5 mux %s", spdata->suffix); + }else if (state->aggregated & 1) { + suffix += 6; + snprintf(sd->name, sizeof(sd->name), "DS5 mux %c", suffix); + } +#endif +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; +#else + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + entity->ops = &ds5_mux_entity_ops; +#endif + entity->obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; + entity->function = MEDIA_ENT_F_VID_MUX; + + dev_info(&c->dev, + "%s():%d init media_entity %s, type:%x, func:%x\n", + __func__, __LINE__, + sd->name, + entity->obj_type, + entity->function); + + pads[0].flags = MEDIA_PAD_FL_SOURCE; + for (i = 1, pad = pads + 1; i < ARRAY_SIZE(state->mux.pads); i++, pad++) + pad->flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(entity, ARRAY_SIZE(state->mux.pads), pads); + if (ret < 0) + return ret; + + /*set for mux*/ + ret = ds5_ctrl_init(state, MUX_SID); + if (ret < 0) + goto e_entity; + + /*set for depth*/ + ret = ds5_ctrl_init(state, DEPTH_SID); + if (ret < 0) + return ret; + /*set for rgb*/ + ret = ds5_ctrl_init(state, RGB_SID); + if (ret < 0) + return ret; + /*set for y8*/ + ret = ds5_ctrl_init(state, IR_SID); + if (ret < 0) + return ret; + /*set for imu*/ + ret = ds5_ctrl_init(state, IMU_SID); + if (ret < 0) + return ret; + + ds5_set_state_last_set(state); + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + if (state->is_depth) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_depth, NULL); +#else + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_depth, NULL, true); +#endif + state->mux.last_set = &state->depth.sensor; + } + else if (state->is_rgb) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_rgb, NULL); +#else + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_rgb, NULL, true); +#endif + state->mux.last_set = &state->rgb.sensor; + } + else if (state->is_y8) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_y8, NULL); +#else + v4l2_ctrl_add_handler(&state->ctrls.handler, + &state->ctrls.handler_y8, NULL, true); +#endif + state->mux.last_set = &state->ir.sensor; + } + else + state->mux.last_set = &state->imu.sensor; + + state->mux.sd.dev = &c->dev; + ret = camera_common_initialize(&state->mux.sd, "d4xx"); + if (ret) { + dev_err(&c->dev, "Failed to initialize d4xx.\n"); + goto e_ctrl; + } +#endif + + return 0; + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +e_ctrl: + v4l2_ctrl_handler_free(sd->ctrl_handler); +#endif +e_entity: + media_entity_cleanup(entity); + + return ret; +} + +#define USE_Y + +static int ds5_fixed_configuration(struct i2c_client *client, struct ds5 *state) +{ + struct ds5_sensor *sensor; + u16 cfg0 = 0, cfg0_md = 0, cfg1 = 0, cfg1_md = 0; + u16 dw = 0, dh = 0, yw = 0, yh = 0, dev_type = 0; + int ret; + + ret = ds5_read(state, DS5_DEPTH_STREAM_DT, &cfg0); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_STREAM_MD, &cfg0_md); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_RES_WIDTH, &dw); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_RES_HEIGHT, &dh); + if (!ret) + ret = ds5_read(state, DS5_IR_STREAM_DT, &cfg1); + if (!ret) + ret = ds5_read(state, DS5_IR_STREAM_MD, &cfg1_md); + if (!ret) + ret = ds5_read(state, DS5_IR_RES_WIDTH, &yw); + if (!ret) + ret = ds5_read(state, DS5_IR_RES_HEIGHT, &yh); + if (!ret) + ret = ds5_read(state, DS5_DEVICE_TYPE, &dev_type); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "%s(): cfg0 %x %ux%u cfg0_md %x %ux%u\n", __func__, + cfg0, dw, dh, cfg0_md, yw, yh); + + dev_dbg(&client->dev, "%s(): cfg1 %x %ux%u cfg1_md %x %ux%u\n", __func__, + cfg1, dw, dh, cfg1_md, yw, yh); + + sensor = &state->depth.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D41X: + sensor->formats = ds5_depth_formats_d41x; + break; + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D45X: + sensor->formats = ds5_depth_formats_d43x; + break; + case DS5_DEVICE_TYPE_D46X: + sensor->formats = ds5_depth_formats_d46x; + break; + default: + sensor->formats = ds5_depth_formats_d46x; + } + sensor->n_formats = 1; + sensor->mux_pad = DS5_MUX_PAD_DEPTH; + + sensor = &state->ir.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D41X: + sensor->formats = ds5_y_formats_41x; + sensor->n_formats = ARRAY_SIZE(ds5_y_formats_41x); + break; + case DS5_DEVICE_TYPE_D45X: + sensor->formats = ds5_y_formats_45x; + sensor->n_formats = ARRAY_SIZE(ds5_y_formats_45x); + break; + default: + sensor->formats = state->variant->formats; + sensor->n_formats = state->variant->n_formats; + } + sensor->mux_pad = DS5_MUX_PAD_IR; + + sensor = &state->rgb.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D46X: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + break; + case DS5_DEVICE_TYPE_D41X: + sensor->formats = &ds5_41x_rgb_format; + sensor->n_formats = DS5_RLT_RGB_N_FORMATS; + break; + case DS5_DEVICE_TYPE_D45X: + sensor->formats = &ds5_rlt_rgb_format; + sensor->n_formats = DS5_RLT_RGB_N_FORMATS; + break; + default: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + } + sensor->mux_pad = DS5_MUX_PAD_RGB; + + sensor = &state->imu.sensor; + + /* For fimware version starting from: 5.16, + IMU will have 32bit axis values. + 5.16.x.y = firmware version: 0x0510 */ + if (state->fw_version >= 0x510) + sensor->formats = ds5_imu_formats_extended; + else + sensor->formats = ds5_imu_formats; + + sensor->n_formats = 1; + sensor->mux_pad = DS5_MUX_PAD_IMU; + + /* Development: set a configuration during probing */ + if ((cfg0 & 0xff00) == 0x1800) { + /* MIPI CSI-2 YUV420 isn't supported by V4L, reconfigure to Y8 */ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = 0, + /* Use template to fill in .field, .colorspace etc. */ + .format = ds5_mbus_framefmt_template, + }; + +//#undef USE_Y +#ifdef USE_Y + /* Override .width, .height, .code */ + fmt.format.width = yw; + fmt.format.height = yh; + fmt.format.code = MEDIA_BUS_FMT_UYVY8_2X8; +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + state->mux.sd.mode_prop_idx = 0; +#endif + state->ir.sensor.streaming = true; + state->depth.sensor.streaming = true; + ret = __ds5_sensor_set_fmt(state, &state->ir.sensor, NULL, &fmt); +#else + fmt.format.width = dw; + fmt.format.height = dh; + fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16; +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + state->mux.sd.mode_prop_idx = 1; +#endif + state->ir.sensor.streaming = false; + state->depth.sensor.streaming = true; + ret = __ds5_sensor_set_fmt(state, &state->depth.sensor, NULL, &fmt); +#endif + if (ret < 0) + return ret; + } + + return 0; +} + +static int ds5_parse_cam(struct i2c_client *client, struct ds5 *state) +{ + int ret; + + ret = ds5_fixed_configuration(client, state); + if (ret < 0) + return ret; + + ds5_sensor_format_init(&state->depth.sensor); + ds5_sensor_format_init(&state->ir.sensor); + ds5_sensor_format_init(&state->rgb.sensor); + ds5_sensor_format_init(&state->imu.sensor); + + return 0; +} + +static void ds5_mux_remove(struct ds5 *state) +{ +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + camera_common_cleanup(&state->mux.sd); +#endif + v4l2_async_unregister_subdev(&state->mux.sd.subdev); + v4l2_ctrl_handler_free(state->mux.sd.subdev.ctrl_handler); + media_entity_cleanup(&state->mux.sd.subdev.entity); +} + +static const struct regmap_config ds5_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_NATIVE, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int ds5_dfu_wait_for_status(struct ds5 *state) +{ + int i, ret = 0; + u16 status; + + for (i = 0; i < DS5_START_MAX_COUNT; i++) { + ds5_read(state, 0x5000, &status); + if (status == 0x0001 || status == 0x0002) { + dev_err(&state->client->dev, + "%s(): dfu failed status(0x%4x)\n", + __func__, status); + ret = -EREMOTEIO; + break; + } + if (!status) + break; + msleep_range(DS5_START_POLL_TIME); + } + + return ret; +}; + +static int ds5_dfu_switch_to_dfu(struct ds5 *state) +{ + int ret; + int i = DS5_START_MAX_COUNT; + u16 status; + + ds5_raw_write_with_check(state, DS5_HWMC_DATA, + &cmd_switch_to_dfu, sizeof(cmd_switch_to_dfu)); /* Write command data */ + ds5_write_with_check(state, DS5_HWMC_EXEC, 0x01); /* execute cmd */ + /*Wait for DFU fw to boot*/ + do { + msleep_range(DS5_START_POLL_TIME*10); + ret = ds5_read(state, 0x5000, &status); + } while (ret && i--); + return ret; +}; + +static int ds5_dfu_wait_for_get_dfu_status(struct ds5 *state, + enum dfu_fw_state exp_state) +{ + int ret = 0; + u16 status, dfu_state_len = 0x0000; + unsigned char dfu_asw_buf[DFU_WAIT_RET_LEN]; + unsigned int dfu_wr_wait_msec = 0; + + do { + ds5_write_with_check(state, 0x5008, 0x0003); // Get Write state + do { + ds5_read_with_check(state, 0x5000, &status); + if (status == 0x0001) { + dev_err(&state->client->dev, + "%s(): Write status error I2C_STATUS_ERROR(1)\n", + __func__); + return -EINVAL; + } else + if (status == 0x0002 && dfu_wr_wait_msec) + msleep_range(dfu_wr_wait_msec); + + } while (status); + + ds5_read_with_check(state, 0x5004, &dfu_state_len); + if (dfu_state_len != DFU_WAIT_RET_LEN) { + dev_err(&state->client->dev, + "%s(): Wrong answer len (%d)\n", __func__, dfu_state_len); + return -EINVAL; + } + ds5_raw_read_with_check(state, 0x4e00, &dfu_asw_buf, DFU_WAIT_RET_LEN); + if (dfu_asw_buf[0]) { + dev_err(&state->client->dev, + "%s(): Wrong dfu_status (%d)\n", __func__, dfu_asw_buf[0]); + return -EINVAL; + } + dfu_wr_wait_msec = (((unsigned int)dfu_asw_buf[3]) << 16) + | (((unsigned int)dfu_asw_buf[2]) << 8) + | dfu_asw_buf[1]; + } while (dfu_asw_buf[4] == dfuDNBUSY && exp_state == dfuDNLOAD_IDLE); + + if (dfu_asw_buf[4] != exp_state) { + dev_notice(&state->client->dev, + "%s(): Wrong dfu_state (%d) while expected(%d)\n", + __func__, dfu_asw_buf[4], exp_state); + ret = -EINVAL; + } + return ret; +}; + +static int ds5_dfu_get_dev_info(struct ds5 *state, struct __fw_status *buf) +{ + int ret = 0; + u16 len = 0; + + ret = ds5_write(state, 0x5008, 0x0002); //Upload DFU cmd + if (!ret) + ret = ds5_dfu_wait_for_status(state); + if (!ret) + ds5_read_with_check(state, 0x5004, &len); + /*Sanity check*/ + if (len == sizeof(struct __fw_status)) { + ds5_raw_read_with_check(state, 0x4e00, buf, len); + } else { + dev_err(&state->client->dev, + "%s(): Wrong state size (%d)\n", + __func__, len); + ret = -EINVAL; + } + return ret; +}; + +static int ds5_dfu_detach(struct ds5 *state) +{ + int ret; + struct __fw_status buf = {0}; + + ds5_write_with_check(state, 0x500c, 0x00); + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuIDLE); + if (!ret) + ret = ds5_dfu_get_dev_info(state, &buf); + dev_notice(&state->client->dev, "%s():DFU ver (0x%x) received\n", + __func__, buf.DFU_version); + dev_notice(&state->client->dev, "%s():FW last version (0x%x) received\n", + __func__, buf.FW_lastVersion); + dev_notice(&state->client->dev, "%s():FW status (%s)\n", + __func__, buf.DFU_isLocked ? "locked" : "unlocked"); + return ret; +}; + +/* When a process reads from our device, this gets called. */ +static ssize_t ds5_dfu_device_read(struct file *flip, + char __user *buffer, size_t len, loff_t *offset) +{ + struct ds5 *state = flip->private_data; + u16 fw_ver, fw_build; + char msg[64]; + int ret = 0; + struct __fw_status f = {0}; + + if (mutex_lock_interruptible(&state->lock)) + return -ERESTARTSYS; + if (state->dfu_dev.dfu_state_flag == DS5_DFU_RECOVERY) { + /* Read device info in recovery mode */ + ret = ds5_dfu_detach(state); + if (ret < 0) + goto e_dfu_read_failed; + ret = ds5_dfu_get_dev_info(state, &f); + if (ret < 0) + goto e_dfu_read_failed; + snprintf(msg, sizeof(msg) , + "DFU info: \trecovery: %02x%02x%02x%02x%02x%02x\n", + f.ivcamSerialNum[0], f.ivcamSerialNum[1], f.ivcamSerialNum[2], + f.ivcamSerialNum[3], f.ivcamSerialNum[4], f.ivcamSerialNum[5] ); + } else { + ret |= ds5_read(state, DS5_FW_VERSION, &fw_ver); + ret |= ds5_read(state, DS5_FW_BUILD, &fw_build); + if (ret < 0) + goto e_dfu_read_failed; + snprintf(msg, sizeof(msg) ,"DFU info: \tver: %d.%d.%d.%d\n", + (fw_ver >> 8) & 0xff, fw_ver & 0xff, + (fw_build >> 8) & 0xff, fw_build & 0xff); + } + + if (copy_to_user(buffer, msg, strlen(msg))) + ret = -EFAULT; + else { + state->dfu_dev.msg_write_once = ~state->dfu_dev.msg_write_once; + ret = strlen(msg) & state->dfu_dev.msg_write_once; + } + +e_dfu_read_failed: + mutex_unlock(&state->lock); + return ret; +}; + +static ssize_t ds5_dfu_device_write(struct file *flip, + const char __user *buffer, size_t len, loff_t *offset) +{ + struct ds5 *state = flip->private_data; + int ret = 0; + (void)offset; + + if (mutex_lock_interruptible(&state->lock)) + return -ERESTARTSYS; + switch (state->dfu_dev.dfu_state_flag) { + + case DS5_DFU_OPEN: + ret = ds5_dfu_switch_to_dfu(state); + if (ret < 0) { + dev_err(&state->client->dev, "%s(): Switch to dfu failed (%d)\n", + __func__, ret); + goto dfu_write_error; + } + /* fallthrough - procceed to recovery */ + __attribute__((__fallthrough__)); + case DS5_DFU_RECOVERY: + ret = ds5_dfu_detach(state); + if (ret < 0) { + dev_err(&state->client->dev, "%s(): Detach failed (%d)\n", + __func__, ret); + goto dfu_write_error; + } + state->dfu_dev.dfu_state_flag = DS5_DFU_IN_PROGRESS; + /* find a better way to reinitialize driver from recovery to operational */ + // state->dfu_dev.init_v4l_f = 1; + /* fallthrough - procceed to download */ + __attribute__((__fallthrough__)); + case DS5_DFU_IN_PROGRESS: { + unsigned int dfu_full_blocks = len / DFU_BLOCK_SIZE; + unsigned int dfu_part_blocks = len % DFU_BLOCK_SIZE; + + while (dfu_full_blocks--) { + if (copy_from_user(state->dfu_dev.dfu_msg, buffer, DFU_BLOCK_SIZE)) { + ret = -EFAULT; + goto dfu_write_error; + } + ret = ds5_raw_write(state, 0x4a00, + state->dfu_dev.dfu_msg, DFU_BLOCK_SIZE); + if (ret < 0) + goto dfu_write_error; + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuDNLOAD_IDLE); + if (ret < 0) + goto dfu_write_error; + buffer += DFU_BLOCK_SIZE; + } + if (copy_from_user(state->dfu_dev.dfu_msg, buffer, dfu_part_blocks)) { + ret = -EFAULT; + goto dfu_write_error; + } + if (dfu_part_blocks) { + ret = ds5_raw_write(state, 0x4a00, + state->dfu_dev.dfu_msg, dfu_part_blocks); + if (!ret) + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuDNLOAD_IDLE); + if (!ret) + ret = ds5_write(state, 0x4a04, 0x00); /*Download complete */ + if (!ret) + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuMANIFEST); + if (ret < 0) + goto dfu_write_error; + state->dfu_dev.dfu_state_flag = DS5_DFU_DONE; + } + if (len) + dev_notice(&state->client->dev, "%s(): DFU block (%d) bytes written\n", + __func__, (int)len); + break; + } + default: + dev_err(&state->client->dev, "%s(): Wrong state (%d)\n", + __func__, state->dfu_dev.dfu_state_flag); + ret = -EINVAL; + goto dfu_write_error; + + }; + mutex_unlock(&state->lock); + return len; + +dfu_write_error: + state->dfu_dev.dfu_state_flag = DS5_DFU_ERROR; + // Reset DFU device to IDLE states + if (!ds5_write(state, 0x5010, 0x0)) + state->dfu_dev.dfu_state_flag = DS5_DFU_IDLE; + mutex_unlock(&state->lock); + return ret; +}; + +static int ds5_dfu_device_open(struct inode *inode, struct file *file) +{ + struct ds5 *state = container_of(inode->i_cdev, struct ds5, + dfu_dev.ds5_cdev); +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + struct i2c_adapter *parent = i2c_parent_is_i2c_adapter( + state->client->adapter); +#endif + mutex_lock(&state->lock); + if (state->dfu_dev.device_open_count) { + mutex_unlock(&state->lock); + return -EBUSY; + } + state->dfu_dev.device_open_count++; + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY) + state->dfu_dev.dfu_state_flag = DS5_DFU_OPEN; + state->dfu_dev.dfu_msg = devm_kzalloc(&state->client->dev, + DFU_BLOCK_SIZE, GFP_KERNEL); + if (!state->dfu_dev.dfu_msg) { + mutex_unlock(&state->lock); + return -ENOMEM; + } + file->private_data = state; +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + /* get i2c controller and set dfu bus clock rate */ + while (parent && i2c_parent_is_i2c_adapter(parent)) + parent = i2c_parent_is_i2c_adapter(state->client->adapter); + + if (!parent) { + mutex_unlock(&state->lock); + return 0; + } + dev_dbg(&state->client->dev, "%s(): i2c-%d bus_clk = %d, set %d\n", + __func__, + i2c_adapter_id(parent), + i2c_get_adapter_bus_clk_rate(parent), + DFU_I2C_BUS_CLK_RATE); + + state->dfu_dev.bus_clk_rate = i2c_get_adapter_bus_clk_rate(parent); + i2c_set_adapter_bus_clk_rate(parent, DFU_I2C_BUS_CLK_RATE); +#endif + mutex_unlock(&state->lock); + return 0; +}; + +static int ds5_v4l_init(struct i2c_client *c, struct ds5 *state) +{ + int ret; + + ret = ds5_parse_cam(c, state); + if (ret < 0) + return ret; + + ret = ds5_depth_init(c, state); + if (ret < 0) + return ret; + + ret = ds5_ir_init(c, state); + if (ret < 0) + goto e_depth; + + ret = ds5_rgb_init(c, state); + if (ret < 0) + goto e_ir; + + ret = ds5_imu_init(c, state); + if (ret < 0) + goto e_rgb; + + ret = ds5_mux_init(c, state); + if (ret < 0) + goto e_imu; + + ret = ds5_hw_init(c, state); + if (ret < 0) + goto e_mux; + + ret = ds5_mux_register(c, state); + if (ret < 0) + goto e_mux; + + return 0; +e_mux: + ds5_mux_remove(state); +e_imu: + media_entity_cleanup(&state->imu.sensor.sd.entity); +e_rgb: + media_entity_cleanup(&state->rgb.sensor.sd.entity); +e_ir: + media_entity_cleanup(&state->ir.sensor.sd.entity); +e_depth: + media_entity_cleanup(&state->depth.sensor.sd.entity); + return ret; +} + +static int ds5_dfu_device_release(struct inode *inode, struct file *file) +{ + struct ds5 *state = container_of(inode->i_cdev, struct ds5, dfu_dev.ds5_cdev); +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + struct i2c_adapter *parent = i2c_parent_is_i2c_adapter( + state->client->adapter); +#endif + int ret = 0, retry = 10; + mutex_lock(&state->lock); + state->dfu_dev.device_open_count--; + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY) + state->dfu_dev.dfu_state_flag = DS5_DFU_IDLE; + /* We disable this section as it has no effect when device in operational + mode and has not enough effect when device in recovery mode */ + // if (state->dfu_dev.dfu_state_flag == DS5_DFU_DONE + // && state->dfu_dev.init_v4l_f) + // ds5_v4l_init(state->client, state); + // state->dfu_dev.init_v4l_f = 0; + if (state->dfu_dev.dfu_msg) + devm_kfree(&state->client->dev, state->dfu_dev.dfu_msg); + state->dfu_dev.dfu_msg = NULL; +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + /* get i2c controller and restore bus clock rate */ + while (parent && i2c_parent_is_i2c_adapter(parent)) + parent = i2c_parent_is_i2c_adapter(state->client->adapter); + if (!parent) { + mutex_unlock(&state->lock); + return 0; + } + dev_dbg(&state->client->dev, "%s(): i2c-%d bus_clk %d, restore to %d\n", + __func__, i2c_adapter_id(parent), + i2c_get_adapter_bus_clk_rate(parent), + state->dfu_dev.bus_clk_rate); + + i2c_set_adapter_bus_clk_rate(parent, state->dfu_dev.bus_clk_rate); +#endif + /* Verify communication */ + do { + ret = ds5_read(state, DS5_FW_VERSION, &state->fw_version); + if (ret) + msleep_range(10); + } while (retry-- && ret != 0 ); + if (ret) { + dev_warn(&state->client->dev, + "%s(): no communication with d4xx\n", __func__); + mutex_unlock(&state->lock); + return ret; + } + ret = ds5_read(state, DS5_FW_BUILD, &state->fw_build); + mutex_unlock(&state->lock); + return ret; +}; + +static const struct file_operations ds5_device_file_ops = { + .owner = THIS_MODULE, + .read = &ds5_dfu_device_read, + .write = &ds5_dfu_device_write, + .open = &ds5_dfu_device_open, + .release = &ds5_dfu_device_release +}; + +struct class *g_ds5_class; +atomic_t primary_chardev = ATOMIC_INIT(0); + +static int ds5_chrdev_init(struct i2c_client *c, struct ds5 *state) +{ + struct cdev *ds5_cdev = &state->dfu_dev.ds5_cdev; + struct class **ds5_class = &state->dfu_dev.ds5_class; +#ifndef CONFIG_OF + struct serdes_platform_data *pdata = c->dev.platform_data; + char suffix = pdata->suffix; +#endif + struct device *chr_dev; + char dev_name[sizeof(DS5_DRIVER_NAME_DFU) + 10]; + dev_t *dev_num = &c->dev.devt; + int ret; + + dev_dbg(&c->dev, "%s()\n", __func__); + /* Request the kernel for N_MINOR devices */ + ret = alloc_chrdev_region(dev_num, 0, 1, DS5_DRIVER_NAME_DFU); + if (ret < 0) + return ret; + + if (!atomic_read(&primary_chardev)) { + dev_dbg(&c->dev, "%s(): : <%d, %d>\n", + __func__, MAJOR(*dev_num), MINOR(*dev_num)); + /* Create a class : appears at /sys/class */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) + *ds5_class = class_create(THIS_MODULE, DS5_DRIVER_NAME_CLASS); +#else + *ds5_class = class_create(DS5_DRIVER_NAME_CLASS); +#endif + dev_warn(&state->client->dev, "%s() class_create\n", __func__); + if (IS_ERR(*ds5_class)) { + dev_err(&c->dev, "Could not create class device\n"); + unregister_chrdev_region(0, 1); + ret = PTR_ERR(*ds5_class); + return ret; + } + g_ds5_class = *ds5_class; + } else + *ds5_class = g_ds5_class; + /* Associate the cdev with a set of file_operations */ + cdev_init(ds5_cdev, &ds5_device_file_ops); + /* Build up the current device number. To be used further */ + *dev_num = MKDEV(MAJOR(*dev_num), MINOR(*dev_num)); + /* Create a device node for this device. */ +#ifndef CONFIG_OF + struct serdes_subdev_info *spdata = &pdata->subdev_info[0]; + /* + * suffix syntaxe for multiple D457 connected to 1 Deser : + * - IPU7/IPU6 upstream both standalone or aggregated links - + * - IPU6 legacy when standalone when aggregated + */ + if (strnlen(spdata->suffix, sizeof(spdata->suffix)) > 1) { + snprintf(dev_name, sizeof(dev_name), "%s-%s", + DS5_DRIVER_NAME_DFU, spdata->suffix); + }else if (state->aggregated & 1) { + suffix += 6; + snprintf(dev_name, sizeof(dev_name), "%s-%c", + DS5_DRIVER_NAME_DFU, suffix); + } +#else + snprintf (dev_name, sizeof(dev_name), "%s-%d-%04x", + DS5_DRIVER_NAME_DFU, i2c_adapter_id(c->adapter), c->addr); +#endif + chr_dev = device_create(*ds5_class, NULL, *dev_num, NULL, dev_name); + if (IS_ERR(chr_dev)) { + ret = PTR_ERR(chr_dev); + dev_err(&c->dev, "Could not create device\n"); + class_destroy(*ds5_class); + unregister_chrdev_region(0, 1); + return ret; + } + cdev_add(ds5_cdev, *dev_num, 1); + atomic_inc(&primary_chardev); + return 0; +}; + +static int ds5_chrdev_remove(struct ds5 *state) +{ + struct class **ds5_class = &state->dfu_dev.ds5_class; + dev_t *dev_num = &state->client->dev.devt; + if (!ds5_class) { + return 0; + } + dev_dbg(&state->client->dev, "%s()\n", __func__); + unregister_chrdev_region(*dev_num, 1); + device_destroy(*ds5_class, *dev_num); + if (atomic_dec_and_test(&primary_chardev)) { + dev_warn(&state->client->dev, "%s() class_destroy\n", __func__); + class_destroy(*ds5_class); + } + return 0; +} + +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT +static void ds5_substream_init(struct ds5 *state) +{ + int i, j; + unsigned int mipi_csi2_type; + s64 *sub_stream = NULL; + state->pad_to_vc[DS5_MUX_PAD_EXTERNAL]= -1; +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + /* Derive Deser CSI link mapping */ + switch (state->g_ctx.serdes_csi_link) { + case GMSL_SERDES_CSI_LINK_A: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + break; + case GMSL_SERDES_CSI_LINK_B: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[4]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[5]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[6]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[7]; + break; + case GMSL_SERDES_CSI_LINK_C: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[8]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[9]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[10]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[11]; + break; + case GMSL_SERDES_CSI_LINK_D: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[12]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[13]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[14]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[15]; + break; + default: + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + break; + } +#else + if (!state->aggregated) { + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[0]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[1]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[2]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[3]; + } else { + state->pad_to_vc[DS5_MUX_PAD_DEPTH] = sensor_vc[4]; + state->pad_to_vc[DS5_MUX_PAD_RGB] = sensor_vc[5]; + state->pad_to_vc[DS5_MUX_PAD_IR] = sensor_vc[6]; + state->pad_to_vc[DS5_MUX_PAD_IMU] = sensor_vc[7]; + } +#endif + + for (i = 0; i < ARRAY_SIZE(state->pad_to_substream); i++) + state->pad_to_substream[i] = -1; + /* match for IPU6 CSI2 BE SOC video capture pads */ +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + /* Derive Deser CSI link mapping */ + switch (state->g_ctx.serdes_csi_link) { + case GMSL_SERDES_CSI_LINK_A: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + break; + case GMSL_SERDES_CSI_LINK_B: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 6; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 8; + state->pad_to_substream[DS5_MUX_PAD_IR] = 10; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 11; + break; + case GMSL_SERDES_CSI_LINK_C: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 1; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 3; + state->pad_to_substream[DS5_MUX_PAD_IR] = 12; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 13; + break; + case GMSL_SERDES_CSI_LINK_D: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 7; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 9; + state->pad_to_substream[DS5_MUX_PAD_IR] = 14; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 15; + break; + default: + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + break; + } +#else + if (!state->aggregated) { + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 0; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 2; + state->pad_to_substream[DS5_MUX_PAD_IR] = 4; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 5; + } + else { + state->pad_to_substream[DS5_MUX_PAD_DEPTH] = 6; + state->pad_to_substream[DS5_MUX_PAD_RGB] = 8; + state->pad_to_substream[DS5_MUX_PAD_IR] = 10; + state->pad_to_substream[DS5_MUX_PAD_IMU] = 11; + } +#endif + dev_info(&state->client->dev, "%s() IPU6 CSI2 BE SOC video capture init : \n", __func__); + for (i = 0; i < ARRAY_SIZE(state->pad_to_substream); i++) + if (state->pad_to_substream[i] >= 0) + dev_info(&state->client->dev, "pad[%d]->substream=%d\n", + i, state->pad_to_substream[i]); + /* + * 0, vc 0, depth + * 1, vc 0, meta data + * 2, vc 1, RGB + * 3, vc 1, meta data + * 4, vc 2, IR + * 5, vc 3, IMU + */ + /* aggreagated */ + /* + * 6, vc 2, depth + * 7, vc 2, meta data + * 8, vc 3, RGB + * 9, vc 3, meta data + * 10, vc 0, IR + * 11, vc 1, IMU + */ + /*DEPTH*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_DEPTH], MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_DEPTH], 480); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_DEPTH], 640); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_DEPTH], mbus_code_to_mipi(MEDIA_BUS_FMT_UYVY8_1X16)); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_DEPTH], state->pad_to_vc[DS5_MUX_PAD_DEPTH]); + /*DEPTH MD*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_DEPTH] + 1, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_DEPTH] + 1, 1); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_DEPTH] + 1, 68); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_DEPTH] + 1, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_DEPTH] + 1, state->pad_to_vc[DS5_MUX_PAD_DEPTH]); + + /*RGB*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_RGB], MEDIA_BUS_FMT_YUYV8_1X16); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_RGB], 640); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_RGB], 480); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_RGB], mbus_code_to_mipi(MEDIA_BUS_FMT_UYVY8_1X16)); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_RGB], state->pad_to_vc[DS5_MUX_PAD_RGB]); + /*RGB MD*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_RGB] + 1, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_RGB] + 1, 1); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_RGB] + 1, 68); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_RGB] + 1, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_RGB] + 1, state->pad_to_vc[DS5_MUX_PAD_RGB]); + /*IR*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_IR], MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_IR], 640); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_IR], 480); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_IR], mbus_code_to_mipi(MEDIA_BUS_FMT_UYVY8_1X16)); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_IR], state->pad_to_vc[DS5_MUX_PAD_IR]); + /*IMU*/ + set_sub_stream_fmt (state->pad_to_substream[DS5_MUX_PAD_IMU], MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h (state->pad_to_substream[DS5_MUX_PAD_IMU], 640); + set_sub_stream_w (state->pad_to_substream[DS5_MUX_PAD_IMU], 480); + set_sub_stream_dt (state->pad_to_substream[DS5_MUX_PAD_IMU], mbus_code_to_mipi(MEDIA_BUS_FMT_UYVY8_1X16)); + set_sub_stream_vc_id(state->pad_to_substream[DS5_MUX_PAD_IMU], state->pad_to_vc[DS5_MUX_PAD_IMU]); +} +#endif + +/* SYSFS attributes */ +#ifdef CONFIG_SYSFS +static ssize_t ds5_fw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + + ds5_read(state, DS5_FW_VERSION, &state->fw_version); + ds5_read(state, DS5_FW_BUILD, &state->fw_build); + + return snprintf(buf, PAGE_SIZE, "D4XX Sensor: %s, Version: %d.%d.%d.%d\n", + ds5_get_sensor_name(state), + (state->fw_version >> 8) & 0xff, state->fw_version & 0xff, + (state->fw_build >> 8) & 0xff, state->fw_build & 0xff); +} + +static DEVICE_ATTR_RO(ds5_fw_ver); + +/* Derive 'device_attribute' structure for a read register's attribute */ +struct dev_ds5_reg_attribute { + struct device_attribute attr; + u16 reg; // register + u8 valid; // validity of above data +}; + +/** Read DS5 register. + * ds5_read_reg_show will actually read register from ds5 while + * ds5_read_reg_store will store register to read + * Example: + * echo -n "0xc03c" >ds5_read_reg + * Read register result: + * cat ds5_read_reg + * Expected: + * reg:0xc93c, result:0x11 + */ +static ssize_t ds5_read_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u16 rbuf; + int n; + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + struct dev_ds5_reg_attribute *ds5_rw_attr = container_of(attr, + struct dev_ds5_reg_attribute, attr); + if (ds5_rw_attr->valid != 1) + return -EINVAL; + ds5_read(state, ds5_rw_attr->reg, &rbuf); + + n = snprintf(buf, PAGE_SIZE, "register:0x%4x, value:0x%02x\n", + ds5_rw_attr->reg, rbuf); + + return n; +} + +/** Read DS5 register - Store reg to attr struct pointer + * ds5_read_reg_show will actually read register from ds5 while + * ds5_read_reg_store will store module, offset and length + */ +static ssize_t ds5_read_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dev_ds5_reg_attribute *ds5_rw_attr = container_of(attr, + struct dev_ds5_reg_attribute, attr); + int rc = -1; + u32 reg; + ds5_rw_attr->valid = 0; + /* Decode input */ + rc = sscanf(buf, "0x%04x", ®); + if (rc != 1) + return -EINVAL; + ds5_rw_attr->reg = reg; + ds5_rw_attr->valid = 1; + return count; +} + +#define DS5_RW_REG_ATTR(_name) \ + struct dev_ds5_reg_attribute dev_attr_##_name = { \ + __ATTR(_name, S_IRUGO | S_IWUSR, \ + ds5_read_reg_show, ds5_read_reg_store), \ + 0, 0 } + +static DS5_RW_REG_ATTR(ds5_read_reg); + +static ssize_t ds5_write_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + + int rc = -1; + u32 reg, w_val = 0; + u16 val = -1; + /* Decode input */ + rc = sscanf(buf, "0x%04x 0x%04x", ®, &w_val); + if (rc != 2) + return -EINVAL; + val = w_val & 0xffff; + mutex_lock(&state->lock); + ds5_write(state, reg, val); + mutex_unlock(&state->lock); + return count; +} + +static DEVICE_ATTR_WO(ds5_write_reg); + +static struct attribute *ds5_attributes[] = { + &dev_attr_ds5_fw_ver.attr, + &dev_attr_ds5_read_reg.attr.attr, + &dev_attr_ds5_write_reg.attr, + NULL +}; + +static const struct attribute_group ds5_attr_group = { + .attrs = ds5_attributes, +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#define NR_DESER 4 + +#ifndef CONFIG_VIDEO_D4XX_SERDES +static const struct regmap_config ds5_regmap_max9296 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static const struct regmap_config ds5_regmap_max9295 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int ds5_i2c_addr_setting(struct i2c_client *c, struct ds5 *state) +{ + int curr_max9296 = c->addr; + int max9296_addrs[MAX9296_NUM] = {0x48, 0x4a, 0x68, 0x6c}; + int i; + u8 val; + int ret; + struct serdes_platform_data *dpdata = c->dev.platform_data; + unsigned short ser_alias; + unsigned short sensor_alias; + + /* TODO: 2x D457 subdev connect to MAX9296 */ + if (dpdata->subdev_num >= 1) { + curr_max9296 = c->addr; + sensor_alias = dpdata->subdev_info[0].board_info.addr; + ser_alias = dpdata->subdev_info[0].ser_alias; + } else { + dev_err(&c->dev, "no subdev found!\n"); + return -EINVAL; + } + + dev_dbg(&c->dev, "curr_max9296 0x%02x, sensor_alias 0x%02x, ser_alias 0x%02x\n", + curr_max9296, sensor_alias, ser_alias); + + /* + * don't reset link, + * check max9296 i2c addr + 1, + * max9295 i2c addr reassigned already. + */ + c->addr = curr_max9296 + 1; + ret = max9295_read_8(state, MAX9295_REG0, &val); + if (!ret) { + max9295_write_8(state, MAX9295_REG0, ser_alias << 1); + msleep_range(1000); /* need this? */ + c->addr = ser_alias; + max9295_write_8(state, MAX9295_I2C_4, sensor_alias << 1); + max9295_write_8(state, MAX9295_I2C_5, D457_I2C_ADDR << 1); + c->addr = sensor_alias; + return 0; + } + + /* i2c addr reassignment for all max9295 */ + for (i = 0; i < MAX9296_NUM; i++) { + c->addr = max9296_addrs[i]; + max9296_write_8(state, MAX9296_CTRL0, RESET_LINK); + } + + for (i = 0; i < MAX9296_NUM; i++) { + /* release reset */ + c->addr = max9296_addrs[i]; + max9296_write_8(state, MAX9296_CTRL0, AUTO_LINK | LINK_A); + msleep_range(1000); + + if (curr_max9296 == max9296_addrs[i]) { + c->addr = MAX9295_I2C_ADDR_DEF; + ret = max9295_read_8(state, MAX9295_REG0, &val); + if (ret < 0) { + dev_err(&c->dev, "no max9295 found for max9296 %x\n", curr_max9296); + continue; + } + max9295_write_8(state, MAX9295_REG0, ser_alias << 1); + msleep_range(1000); // need this? + c->addr = ser_alias; + max9295_write_8(state, MAX9295_I2C_4, sensor_alias << 1); + max9295_write_8(state, MAX9295_I2C_5, D457_I2C_ADDR << 1); + continue; + } + + c->addr = MAX9295_I2C_ADDR_DEF; + ret = max9295_read_8(state, MAX9295_REG0, &val); + if (ret < 0) + continue; + max9295_write_8(state, MAX9295_REG0, (max9296_addrs[i] + 1) << 1); + } + + c->addr = sensor_alias; + + return 0; +} +#endif +#endif //CONFIG_VIDEO_INTEL_IPU6 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int ds5_probe(struct i2c_client *c, const struct i2c_device_id *id) +#else +static int ds5_probe(struct i2c_client *c) +#endif +{ + struct ds5 *state = devm_kzalloc(&c->dev, sizeof(*state), GFP_KERNEL); + u16 rec_state; + int ret, retry, err = 0; +#ifdef CONFIG_OF + const char *str; +#endif + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + + state->client = c; + dev_warn(&c->dev, "Probing driver for D4xx\n"); + +#ifdef CONFIG_VIDEO_D4XX_SERDES + state->dser_st.isolated = false; +#endif + dev_warn(&c->dev, "Probing driver for D45x\n"); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + state->variant = ds5_variants + id->driver_data; +#else + state->variant = ds5_variants; +#endif +#ifdef CONFIG_OF + state->vcc = devm_regulator_get(&c->dev, "vcc"); + if (IS_ERR(state->vcc)) { + ret = PTR_ERR(state->vcc); + dev_warn(&c->dev, "failed %d to get vcc regulator\n", ret); + return ret; + } + + if (state->vcc) { + ret = regulator_enable(state->vcc); + if (ret < 0) { + dev_warn(&c->dev, "failed %d to enable the vcc regulator\n", ret); + return ret; + } + } +#endif + state->regmap = devm_regmap_init_i2c(c, &ds5_regmap_config); + if (IS_ERR(state->regmap)) { + ret = PTR_ERR(state->regmap); + dev_err(&c->dev, "regmap init failed: %d\n", ret); + goto e_regulator; + } + +#ifdef CONFIG_VIDEO_D4XX_SERDES + ret = ds5_serdes_setup(state); + if (ret < 0) { + if (ret == -ENOTSUPP) + dev_warn(&c->dev, "max9295 communication failed : %d\n", ret); + goto e_regulator; + + } +#endif +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU6) || IS_ENABLED(CONFIG_VIDEO_INTEL_IPU7) +#ifndef CONFIG_VIDEO_D4XX_SERDES + state->regmap_max9296 = devm_regmap_init_i2c(c, &ds5_regmap_max9296); + if (IS_ERR(state->regmap_max9296)) { + ret = PTR_ERR(state->regmap_max9296); + dev_err(&c->dev, "regmap max9296 init failed: %d\n", ret); + return ret; + } + + state->regmap_max9295 = devm_regmap_init_i2c(c, &ds5_regmap_max9295); + if (IS_ERR(state->regmap_max9295)) { + ret = PTR_ERR(state->regmap_max9295); + dev_err(&c->dev, "regmap max9295 init failed: %d\n", ret); + return ret; + } + + ret = ds5_i2c_addr_setting(c, state); + if (ret) { + dev_err(&c->dev, "failed apply i2c addr setting\n"); + return ret; + } +#endif +#endif //CONFIG_VIDEO_INTEL_IPU6 + // Verify communication + retry = 5; + do { + ret = ds5_read(state, 0x5020, &rec_state); + } while (retry-- && ret < 0); + if (ret < 0) { + dev_err(&c->dev, + "%s(): cannot communicate with D4XX: %d on addr: 0x%x\n", + __func__, ret, c->addr); + goto e_regulator; + } + + state->is_depth = 0; + state->is_y8 = 0; + state->is_rgb = 0; + state->is_imu = 0; +#ifdef CONFIG_OF + ret = of_property_read_string(c->dev.of_node, "cam-type", &str); + if (!ret && !strncmp(str, "Depth", strlen("Depth"))) { + state->is_depth = 1; + } + if (!ret && !strncmp(str, "Y8", strlen("Y8"))) { + state->is_y8 = 1; + } + if (!ret && !strncmp(str, "RGB", strlen("RGB"))) { + state->is_rgb = 1; + } + if (!ret && !strncmp(str, "IMU", strlen("IMU"))) { + state->is_imu = 1; + } +#else + state->is_depth = 1; +#endif + /* create DFU chardev once */ + if (state->is_depth) { + ret = ds5_chrdev_init(c, state); + if (ret < 0) + goto e_regulator; + } + + ret = ds5_read(state, 0x5020, &rec_state); + if (ret < 0) { + dev_err(&c->dev, "%s(): cannot communicate with D4XX: %d\n", + __func__, ret); + goto e_chardev; + } + + if (rec_state == 0x201) { + dev_info(&c->dev, "%s(): D4XX recovery state\n", __func__); + state->dfu_dev.dfu_state_flag = DS5_DFU_RECOVERY; + /* Override I2C drvdata with state for use in remove function */ + i2c_set_clientdata(c, state); + return 0; + } + + ds5_read_with_check(state, DS5_FW_VERSION, &state->fw_version); + ds5_read_with_check(state, DS5_FW_BUILD, &state->fw_build); + + dev_info(&c->dev, "D4XX Sensor: %s, firmware build: %d.%d.%d.%d\n", + ds5_get_sensor_name(state), + (state->fw_version >> 8) & 0xff, state->fw_version & 0xff, + (state->fw_build >> 8) & 0xff, state->fw_build & 0xff); + + ret = ds5_v4l_init(c, state); + if (ret < 0) + goto e_chardev; + +/* regulators? clocks? + * devm_regulator_bulk_get(&c->dev, DS5_N_SUPPLIES, state->supplies); + * state->clock = devm_clk_get(&c->dev, DS5_CLK_NAME); + * if (IS_ERR(state->clock)) { + * ret = -EPROBE_DEFER; + * goto err; + * } + */ +#ifdef CONFIG_SYSFS + /* Custom sysfs attributes */ + /* create the sysfs file group */ + err = sysfs_create_group(&state->client->dev.kobj, &ds5_attr_group); +#endif +#ifdef CONFIG_VIDEO_INTEL_IPU6_BACKWARD_COMPAT + ds5_substream_init(state); +#endif + return 0; + +e_chardev: + if (state->dfu_dev.ds5_class) + ds5_chrdev_remove(state); + +e_regulator: + if (state->vcc) + regulator_disable(state->vcc); +#ifdef CONFIG_VIDEO_D4XX_SERDES + int i; + int c_bus = c->adapter->nr; + bool graceful_fallback = false; + for (i = 0; i < MAX_DEV_NUM; i++) { + if (serdes_inited[i] + && serdes_inited[i] != state + && state->dser_i2c + && c_bus == serdes_inited[i]->dser_st.bus_nr + && state->dser_i2c->addr == serdes_inited[i]->dser_st.addr + && serdes_inited[i]->dser_st.isolated) { + + dev_info(&c->dev, "Cleanup unresponsive sensor/serializer Isolated on bus %d\n", + c_bus); + graceful_fallback = true; + } + } + + if (!state->g_ctx.serdev_found) + dev_warn(&c->dev, "graceful fallback due to unresponsive max9295, isolated SerDes %s single-link\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link)); + else + dev_warn(&c->dev, "graceful fallback due to unresponsive d4xx, isolated SerDes %s single-link\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link)); + + mutex_lock(&serdes_lock__); + if (state->ser_i2c) { + dev_info(&c->dev, "remove unresponding serializer i2c device 0x%x\n", + state->ser_i2c->addr); + i2c_unregister_device(state->ser_i2c); + } + if (state->dser_i2c && !state->aggregated) { + dev_info(&c->dev, "remove unresponding %s single-link deserializer i2c device 0x%x\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link), + state->dser_i2c->addr); + i2c_unregister_device(state->dser_i2c); + state->dser_st.isolated = true; + } else if (state->dser_i2c && graceful_fallback) { + dev_info(&c->dev, "remove unresponding %s single-link deserializer i2c device 0x%x\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link), + state->dser_i2c->addr); + i2c_unregister_device(state->dser_i2c); + } + mutex_unlock(&serdes_lock__); +#endif + return ret; +} +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) +static int ds5_remove(struct i2c_client *c) +#else +static void ds5_remove(struct i2c_client *c) +#endif +{ +#ifdef CONFIG_VIDEO_D4XX_SERDES + int i, ret; + int c_bus = c->adapter->nr; + bool graceful_fallback = false; +#endif + struct ds5 *state = container_of(i2c_get_clientdata(c), struct ds5, mux.sd.subdev); + if (state && !state->mux.sd.subdev.v4l2_dev) { + state = i2c_get_clientdata(c); + } + +#ifdef CONFIG_VIDEO_D4XX_SERDES + for (i = 0; i < MAX_DEV_NUM; i++) { + if (serdes_inited[i] && state->dser_i2c + && c_bus == serdes_inited[i]->dser_st.bus_nr + && state->dser_i2c->addr == serdes_inited[i]->dser_st.addr + && serdes_inited[i]->dser_st.isolated) { + + dev_info(&c->dev, "Cleanup unresponsive sensor/serializer Isolated on bus %d\n", + c_bus); + graceful_fallback = true; + } + if (serdes_inited[i] && serdes_inited[i] == state) { + serdes_inited[i] = NULL; + mutex_lock(&serdes_lock__); + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + //ret = max96724_switch_link_channel(state->dser_dev); + ret = max9295_reset_control(state->ser_dev); + if (ret) + dev_warn(&c->dev, + "failed in 9295 reset control\n"); + + if (state->dser_i2c) { + dev_info(&c->dev, "ignore 967xx reset control, already remove for bus %d\n", c_bus); + } else { + dev_info(&c->dev, "trigger 967xx reset control on bus %d\n", c_bus); + ret = max96724_reset_control(state->dser_dev, + state->g_ctx.s_dev); + if (ret) + dev_warn(&c->dev, + "failed in 967xx reset control\n"); + } +#else + ret = max9295_reset_control(state->ser_dev); + if (ret) + dev_warn(&c->dev, + "failed in 9295 reset control\n"); + + if (state->dser_i2c) { + dev_info(&c->dev, "ignore 9296 reset control, already remove for bus %d\n", c_bus); + } else { + dev_info(&c->dev, "trigger 9296 reset control on bus %d\n", c_bus); + ret = max9296_reset_control(state->dser_dev, + state->g_ctx.s_dev); + if (ret) + dev_warn(&c->dev, + "failed in 9296 reset control\n"); + } +#endif + ret = max9295_sdev_unpair(state->ser_dev, + state->g_ctx.s_dev); + if (ret) + dev_warn(&c->dev, "failed to unpair sdev\n"); + + if (state->dser_i2c) { + dev_info(&c->dev, "ignore unregister sdev, already remove for bus %d\n", c_bus); + } else { +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + dev_info(&c->dev, "unregister 96724 sdev on bus %d\n", c_bus); + ret = max96724_sdev_unregister(state->dser_dev, + state->g_ctx.s_dev); +#else + dev_info(&c->dev, "unregister 9296 sdev on bus %d\n", c_bus); + ret = max9296_sdev_unregister(state->dser_dev, + state->g_ctx.s_dev); +#endif + if (ret) + dev_warn(&c->dev, + "failed to sdev unregister sdev\n"); + +#if defined(CONFIG_VIDEO_D4XX_MAX96724) || defined(CONFIG_VIDEO_D4XX_MAX96712) + max96724_power_off(state->dser_dev); +#else + max9296_power_off(state->dser_dev); +#endif + } + mutex_unlock(&serdes_lock__); + break; + } + } + if (state->ser_i2c && !state->dser_st.isolated) { + dev_info(&c->dev, "remove unresponding serializer i2c device 0x%x\n", + state->ser_i2c->addr); + i2c_unregister_device(state->ser_i2c); + } + if (state->dser_i2c && !state->aggregated && !state->dser_st.isolated) { + i2c_unregister_device(state->dser_i2c); + dev_info(&c->dev, "remove unresponding %s single-link deserializer i2c device 0x%x\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link), + state->dser_i2c->addr); + } else if (state->dser_i2c && graceful_fallback) { + dev_info(&c->dev, "remove unresponding %s single-link deserializer i2c device 0x%x\n", + d4xx_subdev_csi_link_id(state->g_ctx.serdes_csi_link), + state->dser_i2c->addr); + i2c_unregister_device(state->dser_i2c); + } +#endif +#ifndef CONFIG_TEGRA_CAMERA_PLATFORM + state->is_depth = 1; +#endif + dev_info(&c->dev, "D4XX remove %s\n", + ds5_get_sensor_name(state)); + if (state->vcc) + regulator_disable(state->vcc); +// gpio_free(state->pwdn_gpio); + + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY && \ + state->mux.sd.subdev.v4l2_dev) { +#ifdef CONFIG_SYSFS + sysfs_remove_group(&c->dev.kobj, &ds5_attr_group); +#endif + ds5_mux_remove(state); + } + + if (state->is_depth && state->dfu_dev.ds5_class) { + ds5_chrdev_remove(state); + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + return 0; +#endif +} + +static const struct i2c_device_id ds5_id[] = { + { DS5_DRIVER_NAME, DS5_DS5U }, + { DS5_DRIVER_NAME_ASR, DS5_ASR }, + { DS5_DRIVER_NAME_AWG, DS5_AWG }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ds5_id); + +static const struct of_device_id d4xx_of_match[] = { + { .compatible = "intel,d4xx", }, + { }, +}; +MODULE_DEVICE_TABLE(of, d4xx_of_match); + +static struct i2c_driver ds5_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DS5_DRIVER_NAME + }, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) + .probe = ds5_probe, +#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) + .probe_new = ds5_probe, +#else + .probe = ds5_probe, +#endif + .remove = ds5_remove, + .id_table = ds5_id, +}; + +module_i2c_driver(ds5_i2c_driver); + +MODULE_DESCRIPTION("RealSense D4XX Camera Driver"); +MODULE_AUTHOR("Guennadi Liakhovetski ,\n\ + Nael Masalha ,\n\ + Alexander Gantman ,\n\ + Emil Jahshan ,\n\ + Xin Zhang ,\n\ + Qingwu Zhang ,\n\ + Evgeni Raikhel ,\n\ + Shikun Ding ,\n\ + Florent Pirou ,\n\ + Dmitry Perchanov "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRIVER_VERSION_SUFFIX); diff --git a/drivers/media/i2c/max9x/serdes.c b/drivers/media/i2c/max9x/serdes.c index 0bd01019..f093f9c6 100644 --- a/drivers/media/i2c/max9x/serdes.c +++ b/drivers/media/i2c/max9x/serdes.c @@ -384,6 +384,12 @@ static void *parse_serdes_pdata(struct device *dev) do { struct max9x_csi_link_pdata *csi_link = &des_pdata->csi_links[0]; + dev_dbg(dev, "%s: DESERIALIZER CSI link %u enable %s x%u-lanes map", + __func__, + csi_port, + serdes_pdata->bus_type == V4L2_MBUS_CSI2_CPHY ? "CPHY" : "DPHY", + serdes_pdata->deser_nlanes); + csi_link->link_id = csi_port; csi_link->bus_type = serdes_pdata->bus_type; csi_link->num_lanes = serdes_pdata->deser_nlanes; @@ -2419,6 +2425,15 @@ static int max9x_parse_csi_link_pdata(struct max9x_common *common, csi_link->config.initial_deskew_width = csi_link_pdata->initial_deskew_width; csi_link->config.auto_start = csi_link_pdata->auto_start; + dev_dbg(common->dev, "%s: CSI link %u (%s x%u-lanes) to des_pdata[0..3]=%u, %u, %u, %u", __func__, + csi_link_id, + csi_link->config.bus_type == V4L2_MBUS_CSI2_CPHY ? "CPHY" : "DPHY", + csi_link_pdata->num_lanes, + (&common->csi_link[0])->config.num_lanes, + (&common->csi_link[1])->config.num_lanes, + (&common->csi_link[2])->config.num_lanes, + (&common->csi_link[3])->config.num_lanes); + return 0; } diff --git a/drivers/media/platform/intel/Makefile b/drivers/media/platform/intel/Makefile index 041fcd80..7a37e0b4 100644 --- a/drivers/media/platform/intel/Makefile +++ b/drivers/media/platform/intel/Makefile @@ -5,3 +5,11 @@ obj-$(CONFIG_INTEL_IPU_ACPI) += ipu-acpi.o \ ipu-acpi-pdata.o \ ipu-acpi-common.o + +ifeq ($(CONFIG_VIDEO_D4XX), m) + ccflags-y += -DCONFIG_VIDEO_D4XX +endif + +ifeq ($(CONFIG_VIDEO_MAX9X), m) + ccflags-y += -DCONFIG_VIDEO_MAX9X +endif diff --git a/drivers/media/platform/intel/ipu-acpi-pdata.c b/drivers/media/platform/intel/ipu-acpi-pdata.c index f19d38b6..ab366e4b 100644 --- a/drivers/media/platform/intel/ipu-acpi-pdata.c +++ b/drivers/media/platform/intel/ipu-acpi-pdata.c @@ -20,6 +20,7 @@ #define SENSOR_2X_I2C 5 #define SUFFIX_BASE 97 #define MSG_LEN 128 +#define SUFFIX_BASE_OFFSET 5 // SERDES aggregated-link offset static struct ipu_isys_subdev_pdata *ptr_built_in_pdata; @@ -122,6 +123,9 @@ static void print_serdes_sdinfo(struct serdes_subdev_info *sdinfo) pr_debug("\t\tphy_i2c_addr \t\t= 0x%x", sdinfo->phy_i2c_addr); pr_debug("\t\tser_alias \t\t= 0x%x", sdinfo->ser_alias); pr_debug("\t\tser_phys_addr \t\t= 0x%x", sdinfo->ser_phys_addr); +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + pr_debug("\t\taggregated_link \t= %u", sdinfo->aggregated_link); +#endif pr_debug("\t\tsuffix \t\t\t= %s", sdinfo->suffix); pr_debug("\t\tboard_info.type \t= %s", sdinfo->board_info.type); pr_debug("\t\tboard_info.addr \t= 0x%x", sdinfo->board_info.addr); @@ -608,13 +612,15 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, const char *hid_name, unsigned int lanes, unsigned int addr, + unsigned int port, unsigned int subdev_num) { int i; + int serdes_suffix; + unsigned int suffix_offset; struct serdes_module_pdata *module_pdata[PORT_NR]; struct serdes_subdev_info *serdes_sdinfo; size_t subdev_size = subdev_num * sizeof(*serdes_sdinfo); - unsigned int port = (*pdata)->suffix - SUFFIX_BASE; serdes_sdinfo = kzalloc(subdev_size, GFP_KERNEL); if (!serdes_sdinfo) @@ -640,8 +646,86 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, serdes_sdinfo[i].ser_alias = serdes_info.ser_map_addr + i; serdes_sdinfo[i].phy_i2c_addr = serdes_info.phy_i2c_addr; - snprintf(serdes_sdinfo[i].suffix, sizeof(serdes_sdinfo[i].suffix), "%c-%d", +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + if (!strcmp(sensor_name, D457_NAME)) { + pr_debug("IPU ACPI: %s current i2c_bdf %s serdes %d-%d, prior %s", + sensor_name, + serdes_info.i2c_adapter_bdf, + serdes_info.deser_num, + subdev_num, + serdes_info.i2c_adapter_bdf_2); + pr_debug("IPU ACPI: %s current i2c_addr 0x%x serdes %d-%d, prior 0x%x", + sensor_name, + serdes_info.des_map_addr, + serdes_info.deser_num, + subdev_num, + serdes_info.des_map_addr_2); + pr_debug("IPU ACPI: %s current i2c_addr 0x%x sensor %d-%d, prior 0x%x", + sensor_name, + serdes_info.sensor_map_addr, + serdes_info.deser_num, + subdev_num, + serdes_info.sensor_map_addr_2); + /* define namespacing offset (suffix `e` -> `a-2`, suffix `e` w/ `degree`=90 -> `b-2` ), for : + * - pprunit > 1, set # of deserializer aggregated-link. + * - pprunit = 1, prior sensor_map_addr sensor_map_addr pdata already defined same deserializer, e.g same i2c-bus and i2c-addr, + * maps degree pdata > 90|180|270 to GMSL input offset + * - pprunit = 1, prior d4xx sensor_map_addr pdata already defined same deserializer, e.g same i2c-bus and i2c-addr, + * no degree pdata assumes max9296 and increment by 1 + */ + if (( subdev_num == 1 ) && (i == 0) && ((*pdata)->des_port == 0) && + ( serdes_info.sensor_map_addr == serdes_info.sensor_map_addr_2 ) && + ( !strcmp(serdes_info.i2c_adapter_bdf, serdes_info.i2c_adapter_bdf_2))) { + serdes_sdinfo[i].aggregated_link = 1; + (*pdata)->des_port = 1; + serdes_suffix = (*pdata)->suffix + SUFFIX_BASE_OFFSET + 1 + (*pdata)->des_port; + pr_info("IPU ACPI: Increment %s %c namespacing on aggregated-link sensors %d", + sensor_name, + serdes_suffix, + serdes_info.deser_num); + } else if (( subdev_num == 1 ) && (i == 0) && + ( serdes_info.sensor_map_addr == serdes_info.sensor_map_addr_2 ) && + ( !strcmp(serdes_info.i2c_adapter_bdf, serdes_info.i2c_adapter_bdf_2))) { + serdes_sdinfo[i].aggregated_link = 1; + serdes_suffix = (*pdata)->suffix + SUFFIX_BASE_OFFSET + 1 + (*pdata)->des_port; + pr_info("IPU ACPI: Use degree=%u pdata in %s %c namespacing on aggregated-link sensors %d", + (*pdata)->des_port, + sensor_name, + serdes_suffix, + serdes_info.deser_num); + } else if ((*pdata)->des_port > 0) { + /* - degree == 90|180|270, offset deserializer source link */ + pr_info("IPU ACPI: Use degree=%u pdata in %s %c namespacing", + (*pdata)->des_port, + sensor_name, + serdes_suffix); + serdes_sdinfo[i].aggregated_link = 0; + serdes_suffix = (*pdata)->suffix + SUFFIX_BASE_OFFSET + (*pdata)->des_port; + } else { + serdes_sdinfo[i].aggregated_link = 0; + serdes_suffix = (*pdata)->suffix; + } + snprintf(serdes_sdinfo[i].suffix, sizeof(serdes_sdinfo[i].suffix), "%c-%d", + SUFFIX_BASE + i + (*pdata)->des_port , port); + } else { + if (i >= 1) { + serdes_sdinfo[i].aggregated_link = i; + serdes_suffix = (*pdata)->suffix + i; + pr_info("IPU ACPI: Add %s %cnamespacing on aggregated-link sensors %d", + sensor_name, + serdes_suffix, + serdes_info.deser_num); + } else { + serdes_sdinfo[i].aggregated_link = 0; + serdes_suffix = (*pdata)->suffix; + } + snprintf(serdes_sdinfo[i].suffix, sizeof(serdes_sdinfo[i].suffix), "%c-%d", + SUFFIX_BASE + i, port); + } +#else + snprintf(serdes_sdinfo[i].suffix, sizeof(serdes_sdinfo[i].suffix), "%c-%d", SUFFIX_BASE + i, port); +#endif serdes_sdinfo[i].ser_phys_addr = serdes_info.ser_phys_addr; memcpy(serdes_sdinfo[i].ser_gpio, serdes_info.ser_gpio, sizeof(struct gpiod_lookup) * MAX_SER_GPIO_NUM); @@ -654,7 +738,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, return 0; } -static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, +int set_pdata(struct ipu_isys_subdev_info **sensor_sd, struct device *dev, const char *sensor_name, const char *hid_name, @@ -667,7 +751,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, bool is_dummy, enum connection_type connect, int link_freq, - int des_port) + unsigned int degree) { if (connect == TYPE_DIRECT) { struct sensor_platform_data *pdata; @@ -696,6 +780,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, (*sensor_sd)->i2c.board_info.platform_data = pdata; } else if (connect == TYPE_SERDES) { struct serdes_platform_data *pdata; + unsigned int des_port = (degree == 14) ? 270 : degree; pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -704,6 +789,13 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, pr_debug("IPU ACPI: %s - Serdes connection", __func__); /* use ascii */ if (port >= 0) { +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + if ((!strcmp(sensor_name, D457_NAME)) && + (degree == 90 || + degree == 180 || + degree == 14)) // 14 is equal to 270 (e.g. 255 byte overflow + 15) + des_port = (degree == 14) ? 3 :(degree / 90); +#endif pdata->suffix = port + SUFFIX_BASE; pr_info("IPU ACPI: create %s on mipi port %d", sensor_name, port); @@ -711,12 +803,23 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, pr_err("IPU ACPI: Invalid MIPI Port : %d", port); pdata->link_freq_mbps = link_freq; +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + if ((!strcmp(sensor_name, D457_NAME)) && + (lanes != 2 || + deser_lanes == 0 || + subdev_num != 1)) { + //fallback from PDATA misconfiguration corner-cases that leads d4xx segfault + deser_lanes = 2; + lanes = 2; + subdev_num = 1; + } +#endif pdata->bus_type = (*sensor_sd)->csi2->bus_type; pdata->deser_nlanes = deser_lanes; pdata->ser_nlanes = lanes; pdata->des_port = des_port; strscpy(pdata->ser_name, (*sensor_sd)->i2c.board_info.type, I2C_NAME_SIZE); - set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, hid_name, lanes, addr, subdev_num); + set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, hid_name, lanes, addr, port, subdev_num); (*sensor_sd)->i2c.board_info.platform_data = pdata; pdata->deser_board_info = &(*sensor_sd)->i2c.board_info; @@ -725,7 +828,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, return 0; } -static void set_serdes_info(struct device *dev, const char *sensor_name, +void set_serdes_info(struct device *dev, const char *sensor_name, const char *serdes_name, struct sensor_bios_data *cam_data, int sensor_physical_addr, @@ -743,13 +846,34 @@ static void set_serdes_info(struct device *dev, const char *sensor_name, serdes_info.i2c_num = cam_data->i2c_num; i = 1; + +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + /* prior serializer mapped addr */ + serdes_info.ser_map_addr_2 = serdes_info.ser_map_addr; +#endif /* serializer mapped addr */ serdes_info.ser_map_addr = cam_data->i2c[i++].addr; /* serializer physical addr */ serdes_info.ser_phys_addr = ser_physical_addr; + +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + /* prior serializer mapped addr */ + serdes_info.sensor_map_addr_2 = serdes_info.sensor_map_addr; +#endif /* sensor mapped addr */ serdes_info.sensor_map_addr = cam_data->i2c[i++].addr; +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + /* deserializer mapped addr */ + serdes_info.des_map_addr_2 = serdes_info.des_map_addr; + serdes_info.des_map_addr = cam_data->i2c[cam_data->i2c_num - 3].addr; + + /* serdes mapped i2c-adapter */ + strscpy(&serdes_info.i2c_adapter_bdf_2[0], serdes_info.i2c_adapter_bdf, + sizeof(serdes_info.i2c_adapter_bdf_2)); + strscpy(&serdes_info.i2c_adapter_bdf[0], cam_data->i2c[cam_data->i2c_num - 1].bdf, + sizeof(serdes_info.i2c_adapter_bdf)); +#endif serdes_info.gpio_powerup_seq = 0; serdes_info.phy_i2c_addr = sensor_physical_addr; @@ -766,7 +890,7 @@ static void set_serdes_info(struct device *dev, const char *sensor_name, serdes_info.sensor_dt = sensor_dt; } -static int populate_sensor_pdata(struct device *dev, +int populate_sensor_pdata(struct device *dev, struct ipu_isys_subdev_info **sensor_sd, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, @@ -834,16 +958,18 @@ static int populate_sensor_pdata(struct device *dev, } /* Use last I2C device */ - ret = set_pdata(sensor_sd, dev, sensor_name, hid_name, ctl_data, cam_data->link, - cam_data->lanes, cam_data->i2c[cam_data->i2c_num - 1].addr, - cam_data->pprunit, cam_data->pprval, false, connect, link_freq, cam_data->degree); + ret = set_pdata(sensor_sd, dev, sensor_name, hid_name, ctl_data, + cam_data->link, + cam_data->lanes, + cam_data->i2c[cam_data->i2c_num - 1].addr, + cam_data->pprunit, + cam_data->pprval, + false, connect, link_freq, cam_data->degree); if (ret) return ret; update_pdata(dev, *sensor_sd, connect); - /* Lontium specific */ - return 0; } @@ -922,3 +1048,4 @@ EXPORT_SYMBOL(get_sensor_pdata); MODULE_AUTHOR("Khai Wen, Ng "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("IPU ACPI support"); +MODULE_VERSION(DRIVER_VERSION_SUFFIX); diff --git a/drivers/media/platform/intel/ipu-acpi.c b/drivers/media/platform/intel/ipu-acpi.c index a32b378f..f5251df3 100644 --- a/drivers/media/platform/intel/ipu-acpi.c +++ b/drivers/media/platform/intel/ipu-acpi.c @@ -53,6 +53,11 @@ #include #endif +#if IS_ENABLED(CONFIG_VIDEO_D4XX) +#include +#include +#endif + static LIST_HEAD(devices); static struct ipu_camera_module_data *add_device_to_list( @@ -244,6 +249,28 @@ static const struct ipu_acpi_devices supported_devices[] = { },// AR0234 HID #endif #endif +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + { + .hid_name = "INTC10CD", + .real_driver = D457_NAME, + .get_platform_data = get_sensor_pdata, + .priv_data = NULL, + .priv_size = 0, + .connect = TYPE_SERDES, + .serdes_name = D457_NAME, + .sensor_physical_addr = D457_I2C_ADDRESS, + .link_freq = 1600, + .ser_physical_addr = 0x40, + .ser_gpio = { + { + .chip_hwnum = 0, + .con_id = "reset", + .flags = GPIO_ACTIVE_LOW, + }, + }, + .sensor_dt = MIPI_CSI2_TYPE_YUV422_8, + },// D457 HID +#endif }; static int get_table_index(const char *acpi_name) @@ -277,6 +304,9 @@ static const struct acpi_device_id ipu_acpi_match[] = { #endif #if IS_ENABLED(CONFIG_VIDEO_AR0234) { "INTC0234", 0 }, +#endif +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + { "INTC10CD", 0 }, // D457 HID #endif {}, }; diff --git a/helpers/99-ipu-gmsl-mipi.rules b/helpers/99-ipu-gmsl-mipi.rules new file mode 100755 index 00000000..7a8c099f --- /dev/null +++ b/helpers/99-ipu-gmsl-mipi.rules @@ -0,0 +1,21 @@ +# Device rules for 3rd party GMSL2 ISX031 IMX390 and AR0234 camera modules. +# video links for SDK, binding for ipu7 or ipu6 + +SUBSYSTEM=="video4linux", ATTR{name}=="i2c-isx031-*", RUN+="/bin/bash -c '/usr/share/camera/ipu_max9x_bind.sh -s isx031 -n > /dev/kmsg'" +SUBSYSTEM=="video4linux", ATTR{name}=="i2c-imx390-*", RUN+="/bin/bash -c '/usr/share/camera/ipu_max9x_bind.sh -s imx390 -n > /dev/kmsg'" +SUBSYSTEM=="video4linux", ATTR{name}=="i2c-ar0234-*", RUN+="/bin/bash -c '/usr/share/camera/ipu_max9x_bind.sh -s ar0234 -n > /dev/kmsg'" + +# TEMPORARY : Device rules for Intel RealSense MIPI devices. +# DFU rules +SUBSYSTEM=="d4xx-class", KERNEL=="d4xx-dfu*", GROUP="video", MODE="0660" + +# video links for SDK, binding for ipu7 +SUBSYSTEM=="video4linux", ATTR{name}=="DS5 mux *", RUN+="/bin/bash -c '/usr/share/camera/rs_ipu_d457_bind.sh -n > /dev/kmsg; /usr/share/camera/rs-enum-ipu.sh -n -q > /dev/kmsg'" + +# default max96724 gmsl link_freq override for PTL ipu7 DWC DPHY +# (e.g .used udevadm info -a -p $(udevadm info -q path -n $(media-ctl -e "i2c-isx031-a-0")) ) +#SUBSYSTEM=="video4linux", ATTR{name}=="i2c-isx031-*", RUN+="/usr/bin/v4l2-ctl --set-ctrl v4l2_cid_link_freq=1 -d '%E{DEVNAME}'" +#SUBSYSTEM=="video4linux", ATTR{name}=="i2c-imx390-*", RUN+="/usr/bin/v4l2-ctl --set-ctrl v4l2_cid_link_freq=1 -d '%E{DEVNAME}'" +#SUBSYSTEM=="video4linux", ATTR{name}=="i2c-ar0234-*", RUN+="/usr/bin/v4l2-ctl --set-ctrl v4l2_cid_link_freq=1 -d '%E{DEVNAME}'" +# (e.g .used udevadm info -a -p $(udevadm info -q path -n $(media-ctl -e "DS5 mux a-0")) ) +#SUBSYSTEM=="video4linux", ATTR{name}=="DS5 mux *", RUN+="/usr/bin/v4l2-ctl --set-ctrl v4l2_cid_link_freq=1 -d '%E{DEVNAME}'" \ No newline at end of file diff --git a/helpers/blacklist-intel-ipu.conf b/helpers/blacklist-intel-ipu.conf new file mode 100644 index 00000000..f59cf472 --- /dev/null +++ b/helpers/blacklist-intel-ipu.conf @@ -0,0 +1,7 @@ +# avpoid intel CCG & Canonical MIPI-driect only kernel builtin IPU6 / IPU7 clashes with GMSL2 enabled intel-ipu-dkms +blacklist intel_ipu7_isys +blacklist intel_ipu7_psys +blacklist intel_ipu7 +blacklist intel_ipu6_isys +blacklist intel_ipu6_psys +blacklist intel_ipu6 diff --git a/helpers/d4xx.conf b/helpers/d4xx.conf new file mode 100644 index 00000000..179fa9eb --- /dev/null +++ b/helpers/d4xx.conf @@ -0,0 +1,28 @@ +# uncomment d4xx driver boot options corresponding to your Intel platform +# +# MTL GMSL2 AIC max9296 device generally on i2c-2 and i2c-3 buses +#options d4xx des_addr=0x48,0x4a,0x48,0x4a serdes_bus=2,2,3,3 +# ARL GMSL2 AIC max9296 device generally on i2c-0 and i2c-3 buses +#options d4xx des_addr=0x48,0x4a,0x48,0x4a serdes_bus=0,0,3,3 +# ADL/RPL/ASL GMSL2 AIC max9296 device generally on i2c-4 bus +#options d4xx des_addr=0x48,0x4a,0x68,0x6c serdes_bus=4,4,4,4 +# ARL GMSL2 AIC dual-max96712 device generally on i2c-0 bus +#options d4xx des_addr=0x6b,0x6b,0x29,0x29 serdes_bus=0,0,0,0 + +# PTL GMSL2 AIC dual-max96724 device generally on i2c-0 and i2c-1 buses +options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 +# PTL GMSL2 AIC dual-max96724 device generally on i2c-0 and i2c-1 buses (with VC_EXTENSION enabled) +# try #1 - RGB+DEPTH on the same video pipes +#options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 sensor_vc=0,7,1,6,1,6,0,7,2,5,3,4,3,4,2,5 +# try #2 - RGB+DEPTH on the distinct video pipes +#options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=0,0,1,1 des_csi=0,0,2,2 sensor_vc=0,6,5,3,1,4,2,7,2,7,4,1,3,5,0,6 +# !!WARNING!! Intel Servoss Mountain 1.0 (Fab A) dual-max96724 board cphy CRD connectivity is inverted +# max96724 csi 0 CPHY-2T DA0/DA1 maps to ipu7 isys csi 2 +# max96724 csi 2 CPHY-2T DB0/DB1 maps to ipu7 isys csi 0 +#options d4xx des_addr=0x27,0x27,0x27,0x27 serdes_bus=1,1,2,2 des_csi=2,2,0,0 +# + +#options d4xx dyndbg +#options d4xx-max9295 dyndbg +#options d4xx-max9296 dyndbg +#options d4xx-max96724 dyndbg diff --git a/helpers/ipu_max9x_bind.sh b/helpers/ipu_max9x_bind.sh new file mode 100755 index 00000000..afa18b0c --- /dev/null +++ b/helpers/ipu_max9x_bind.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# +# Dependency: v4l-utils +v4l2_util=$(which v4l2-ctl) +media_util=$(which media-ctl) +if [ -z ${v4l2_util} ]; then + echo "v4l2-ctl not found, install with: sudo apt install v4l-utils" + exit 1 +fi +quiet=0 +while [[ $# -gt 0 ]]; do + case $1 in + -q|--quiet) + quiet=1 + shift + ;; + -m|--mux) + shift + if [ ${#1} -eq 3 ]; then + mux_param=$1 + fi + shift + ;; + -s|--sensor) + sensor=$2 + shift + ;; + -h|--help) + echo "-q -m -h" + ;; + -f) + fmt="$2" + shift + ;; + *) + shift + ;; + esac +done + +sensor="${sensor:-isx031}" +if [ ${sensor} = "isx031" ]; then + fmt="${fmt:-[fmt:UYVY8_1X16/1920x1536]}" + # fmt="${fmt:-[fmt:UYVY8_1X16/1600x1280]}" + # fmt="${fmt:-[fmt:UYVY8_1X16/1024x768]}" +elif [ ${sensor} = "imx390" ]; then + fmt="${fmt:-[fmt:SGRBG12_1X12/1920x1200]}" + # fmt="${fmt:-[fmt:SRGGB12_1X12/1936x1096]}" + # fmt="${fmt:-[fmt:SRGGB12_1X12/1280x720]}" + # fmt="${fmt:-[fmt:SRGGB12_1X12/1024x768]}" +elif [ ${sensor} = "ar0234" ]; then + fmt="${fmt:-[fmt:SGRBG10_1X10/1280x960]}" +fi + +declare entities=() +while IFS= read -r line; do + entities+=("$line") +done < <(media-ctl -p | grep entity | sed -e 's/.*: //;s/ (.*$//') + +declare -A mux_to_index=([a]=0 [b]=1 [c]=2 [d]=3) + +find_entity() { + local name=$1 + for e in "${entities[@]}"; do + if [[ "${e}" = *"${name}"* ]]; then + echo -n "${e}" + return + fi + done + #echo "$1 not found" >&2 + exit 1 +} + +des_node() { + local mux=$1 + case ${mux} in + a-0|b-0|c-0|d-0) echo -n "max9x a";; + a-1|b-1|c-1|d-1) echo -n "max9x b";; + a-2|b-2|c-2|d-2) echo -n "max9x c";; + a-3|b-3|c-3|d-3) echo -n "max9x d";; + a-4|b-4|c-4|d-4) echo -n "max9x e";; + a-5|b-5|c-5|d-5) echo -n "max9x f";; + esac +} +des_src_pad() { + local mux=$1 + echo -n "\"$(des_node ${mux})\":0" +} +des_sink_pad() { + local mux=$1 + echo -n "\"$(des_node ${mux})\":$(( 4 + ${mux_to_index[${mux:0:1}]} ))" +} + +ser_node() { + local mux=$1 + find_entity "max9x ${mux}" +} +ser_src_pad() { + local mux=$1 + echo -n "\"$(ser_node ${mux})\":2" +} +ser_sink_pad() { + local mux=$1 + echo -n "\"$(ser_node ${mux})\":0" +} + +sen_node() { + local mux=$1 + find_entity "${sensor} ${mux}" +} +sen_src_pad() { + local mux=$1 + echo -n "\"$(sen_node ${mux})\":0" +} + +# mapping for ISX031/IMX390/AR0234 max9x entity to IPU7|IPU6 CSI-2 entity matching. +declare -A media_mux_capture_pad=( + [a]=0 + [b]=1 + [c]=2 + [d]=3 +) + +# all available ISX031/IMX390/AR0234 max9x entity, each one represent physically connected camera. +# muxes prefix a, b, c, d referes to max96724 or max9296 aggregated link cameras +# muxes suffix 0, 1, 2, 3, 4, 5 is referes to csi2 mipi port mapping +mux_list=${mux_param:-'a-0 b-0 c-0 d-0 a-1 b-1 c-1 d-1 a-2 b-2 c-2 d-2 a-3 b-3 c-3 d-3 a-4 b-4 c-4 d-4 a-5 b-5 c-5 d-5'} + +# Find media device. +# For case with usb camera plugged in during the boot, +# usb media controller will occupy index 0 +mdev=$(${v4l2_util} --list-devices | grep -A100 ipu | grep media | head -n 1) +[[ -z "${mdev}" ]] && exit 0 + +# IPU7/IPU6 ISYS Capture devices can bind to either on IPU7/IPU6 ISYS CSI subdev 1-16 or 1-8 srcpads +mdev_capdev_count=$(${v4l2_util} -d ${mdev} -A | wc -l) +if [ $((${mdev_capdev_count}-2)) -gt 48 ]; then + declare -A media_mux_capture_link=( [0]=0 [1]=16 [2]=32 [3]=48 [4]=64 [5]=80) +else + declare -A media_mux_capture_link=( [0]=0 [1]=8 [2]=16 [3]=24 [4]=32 [5]=40) +fi + +# Find IPU PCI device gen name +cap_prefix=$(${v4l2_util} --list-devices | grep ipu | grep PCI | sed 's/^\(ipu[6|7]\).*/\1/' | tr '[:lower:]' '[:upper:]') +[[ -z "${cap_prefix}" ]] && exit 0 + +out() { + [[ $quiet -eq 0 ]] && echo -n "${@} " >&2 + "${@}" + [[ $quiet -eq 0 ]] && echo " RET=$?" >&2 +} + +media_ctl_cmd="${media_util} -d ${mdev}" +out media-ctl -r # <- this can be used to clean-up all bindings from media controller +# cache media-ctl output +dot=$($media_ctl_cmd --print-dot) + +des_route="" +csi_route="" +des_suffix="a" +streamid=0 +# loop over all available IMX390/ISX031/AR0234 max9x, each one represent physically connected camera. +for camera in $mux_list; do + e="$(sen_node ${camera})" + if [ -z "${e}" ]; then + continue; + fi + d="$(des_node ${camera})" + if [ "${des_suffix}" != "${d:6:1}" ]; then + des_suffix="${d:6:1}" + streamid=0 + fi + + echo "Bind $cap_prefix to ${sensor} ${camera} through max9x ${des_suffix} .. " >&2 + + csi2="$((${camera:2:1}))" + mux=${camera:0:1} + + # mapping for MAX9x mux entity to IPU7|IPU6 ISYS matching entity. + cap_pad="${media_mux_capture_pad[${mux}]}" + isys_cap="$((${media_mux_capture_link[${csi2}]}+${cap_pad}))" + + out $media_ctl_cmd -l "$(des_src_pad ${camera}) -> \"Intel ${cap_prefix} CSI2 ${csi2}\":0[1]" + out $media_ctl_cmd -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${cap_pad}+1)) -> \"Intel ${cap_prefix} ISYS Capture ${isys_cap}\":0[1]" + + # subdev entity '['pad-number '/' stream-number '->' pad-number '/' stream-number '[' route-flags ']' ']' ; + if [ $streamid -eq 0 ]; then + des_route="$((${cap_pad} + 4))/${streamid}->0/${streamid}[1]" + csi_route="0/${streamid}->$((${cap_pad} + 1))/${streamid}[1]" + else + des_route=${des_route}",$((${cap_pad} + 4))/${streamid}->0/${streamid}[1]" + csi_route=${csi_route}",0/${streamid}->$((${cap_pad} + 1))/${streamid}[1]" + fi + out $media_ctl_cmd -R "\"$(ser_node ${camera})\"[0/0->2/${streamid}[1]]" + out $media_ctl_cmd -R "\"$(des_node ${camera})\"[${des_route}]" + out $media_ctl_cmd -R "\"Intel ${cap_prefix} CSI2 ${csi2}\"[${csi_route}]" + + out $media_ctl_cmd -V "$(sen_src_pad ${camera})/0 ${fmt}" + out $media_ctl_cmd -V "$(ser_sink_pad ${camera})/0 ${fmt}" + out $media_ctl_cmd -V "$(ser_src_pad ${camera})/${streamid} ${fmt}" + out $media_ctl_cmd -V "$(des_sink_pad ${camera})/${streamid} ${fmt}" + out $media_ctl_cmd -V "$(des_src_pad ${camera})/${streamid} ${fmt}" + + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/${streamid} ${fmt}" + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${cap_pad}+1))/${streamid} ${fmt}" + + cap_dev=$($media_ctl_cmd -e "Intel ${cap_prefix} ISYS Capture ${isys_cap}") + dev_ln="/dev/video-${sensor}-${camera}" + out ln -snf ${cap_dev} ${dev_ln} + + # set default v4l2 fmt value +if [ ${sensor} = "isx031" ]; then + out ${v4l2_util} -d ${dev_ln} --set-fmt-video=width=1920,height=1536,pixelformat=UYVY +elif [ ${sensor} = "imx390" ]; then + out ${v4l2_util} -d ${dev_ln} --set-fmt-video=width=1920,height=1200,pixelformat=NV12 +elif [ ${sensor} = "ar0234" ]; then + out ${v4l2_util} -d ${dev_ln} --set-fmt-video=width=1280,height=960,pixelformat=NV12 +fi + # disable ipu link enumeration feature, if exists + [[ -e "$(${v4l2_util} -d $dev_ln -L | grep enumerate_graph_link)" ]] && out ${v4l2_util} -d $dev_ln -c enumerate_graph_link=0 + + # change group + out chown root:video $cap_dev + + # WA: must repeat all the v4l2 set-fmt on Intel IPU7 CSI2 2 v4l2 subdev pad 0 + if [ $streamid -gt 0 ]; then + for i in $(seq 0 $streamid); do out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/$((${i})) ${fmt}"; done + fi + streamid=$(($streamid + 1)) +done diff --git a/helpers/ipu_ops.conf b/helpers/ipu_ops.conf new file mode 100644 index 00000000..5ebd5b45 --- /dev/null +++ b/helpers/ipu_ops.conf @@ -0,0 +1,11 @@ +options intel-ipu6 isys_freq_override=400 ipu_trace_enable=Y +options intel-ipu6-isys csi2_port_optimized=n +options intel-ipu6-psys enable_power_gating=n +#options intel-ipu7-psys dyndbg +#options intel-ipu7-isys dyndbg +#options intel-ipu7 dyndbg +#options intel-ipu6-psys dyndbg +#options intel-ipu6-isys dyndbg +#options intel-ipu6 dyndbg +options ipu_acpi dyndbg +options ipu_acpi_pdata dyndbg diff --git a/helpers/mtbf-test-isx031-gmsl_gst-v4l2src.sh b/helpers/mtbf-test-isx031-gmsl_gst-v4l2src.sh new file mode 100755 index 00000000..ee07af6e --- /dev/null +++ b/helpers/mtbf-test-isx031-gmsl_gst-v4l2src.sh @@ -0,0 +1,688 @@ +#!/bin/bash +# Dependency: gstreamer1.0-plugins-base +gst_util=$(which gst-launch-1.0) +if [ -z ${gst_util} ]; then + echo "Gstreamer1.0 not found, install with: sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-base" + exit 1 +fi +i2c_util=$(which i2ctransfer) +if [ -z ${i2c_util} ]; then + echo "i2ctransfer not found, install with: sudo apt install i2c-tools" + exit 1 +fi + +# Flag to indicate shutdown requested +shutdown_requested=false + +###### to be MODIFIED if max9x AIC GMSL deserializer board is mapped differently +desA_addr=27 +desA_i2c=0 +desA_csi=0 +desA_tuple="b|c" +#-------- +desB_addr=27 +desB_i2c=1 +desB_csi=2 +desB_tuple="a|d" +###### + +nox=false +watch=false +while [[ $# -gt 0 ]]; do + case $1 in + -l|--loop) + shift + loop=$1 + shift + ;; + -t|--testcase) + shift + testcase=$1 + shift + ;; + -d|--duration) + shift + duration=$1 + shift + ;; + -n|--nox) + nox=true + shift + ;; + -w|--watch) + watch=true + shift + ;; + -h|--help) + echo "-t,--testcase <2by2|4by4|6by6|8by8> -d,--duration -l,--loop -n,-nox -w,-watch " + echo "Examples:" + echo " ./mtbf-test-isx031-gmsl_gst-v4l2src.sh -t 2by2 -d 1 -l 1 -n #isx031 a|d-$desA_csi and a|d-$desB_csi GMSL links back-to-back 10s test, 1-loop : gst-launch-1.0 v4l2src ...! fakesink..." + echo " ./mtbf-test-isx031-gmsl_gst-v4l2src.sh -t 4by4 -d 6 -l 10 #isx031 a|b|c|d-$desA_csi and a|b|c|d-$desB_csi GMSL links back-to-back 1min test, 10-loops : gst-launch-1.0 v4l2src ...! xvimagesink..." + echo " ./mtbf-test-isx031-gmsl_gst-v4l2src.sh -t 6by6 -d 180 -l 5 -n #isx031 a|b|c|d-$desA_csi|$desB_csi and b|c-$desB_csi|$desA_csi GMSL links back-to-back 30min test, 5-loops : gst-launch-1.0 v4l2src ...! fakesink..." + echo " ./mtbf-test-isx031-gmsl_gst-v4l2src.sh -t 8by8 -d 360 -l 1 -n -w # isx031 a|b|c|d-$desA_csi|$desB_csi GMSL links back-to-back 1h test, 1-loops and watch max9x link-status : gst-launch-1.0 v4l2src ...! fakesink..." + exit 1 + ;; + *) + shift + ;; + esac +done + +handle_sigterm() { + echo "Shutdown signal received" + pkill gst-launch-1.0 + shutdown_requested=true +} + +# Handle multiple signals with the same handler +trap handle_sigterm SIGTERM SIGINT SIGHUP + +RED='\033[0;31m' +GREEN='\033[0;32m' +GREY='\033[0;37m' +NC='\033[0m' # No Color + +# Helper function to check specific bits +# Usage: check_bit +check_bit() { + local val=$((16#${1#0x})) + local bit=$2 + local label=$3 + if [ $(( (val >> bit) & 1 )) -eq 1 ]; then + echo "1 ($label)" + else + echo "0 ($label)" + fi +} +# Usage: check_cng +check_cnt() { + local val=$((16#${1#0x})) + local bit=$2 + local mask=$((16#${3#0x})) + echo "$(( (val >> bit) & mask ))" +} + +reg_max9x_id () { + local BUS=$1 + local DEV=$2 + + # GMSL a|b|c|d Link/Lock Status + ADDR=0x000D + # Split 16-bit address for w2 transfer (e.g., 0x0005 -> 0x0 0x5) + HIGH_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) >> 8 ))) + LOW_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) & 0xFF ))) + + VAL=$(i2ctransfer -y -f $BUS w2@0x$DEV $HIGH_BYTE $LOW_BYTE r1) + echo $VAL + #return $VAL +} + + +reg_max9x_lnklock () { + local BUS=$1 + local DEV=$2 + + # GMSL a|b|c|d Link/Lock Status + echo "max9x i2c-$BUS@0x$DEV LINK_LOCK Status:" + if [[ $DNAME == "max9296" ]]; then + REGS=("0x001A:3/LNKA_LOCKED" "0x000A:3/LNKB_LOCKED") + else + REGS=("0x001A:3/LNKA_LOCKED" "0x000A:3/LNKB_LOCKED" "0x000B:3/LNKC_LOCKED" "0x000C:3/LNKD_LOCKED" \ + "0x0108:6/VIDA_LOCK" "0x011A:6/VIDB_LOCK" "0x012C:6/VIDC_LOCK" "0x013E:6/VIDD_LOCK") + fi + for REG in "${REGS[@]}"; do + ADDR=${REG%:*} + ARGS=${REG#*:} + BIT=${ARGS%/*} + LBL=${ARGS#*/} + # Split 16-bit address for w2 transfer (e.g., 0x0005 -> 0x0 0x5) + HIGH_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) >> 8 ))) + LOW_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) & 0xFF ))) + + VAL=$(i2ctransfer -y -f $BUS w2@0x$DEV $HIGH_BYTE $LOW_BYTE r1) + RESULT=$(check_bit $VAL $BIT $LBL) + echo " Reg $ADDR bit$BIT: $VAL -> status: $RESULT" + done +} + +reg_max9x_vidlock () { + local BUS=$1 + local DEV=$2 + + # GMSL a|b|c|d video Lock Status + echo "max9x i2c-$BUS@0x$DEV VID_LOCK Status:" + # Mapping: RegAddr:Bit + DEEP_REGS=("0x0108:5/VIDA_PKT_DET" "0x011A:5/VIDB_PKT_DET" "0x012C:5/VIDC_PKT_DET" "0x013E:5/VIDD_PKT_DET") + + for ENTRY in "${DEEP_REGS[@]}"; do + ADDR=${ENTRY%:*} + ARGS=${ENTRY#*:} + BIT=${ARGS%/*} + LBL=${ARGS#*/} + HIGH_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) >> 8 ))) + LOW_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) & 0xFF ))) + + VAL=$(i2ctransfer -y -f $BUS w2@0x$DEV $HIGH_BYTE $LOW_BYTE r1) + RESULT=$(check_bit $VAL $BIT $LBL) + echo " Reg $ADDR bit$BIT: $VAL -> status: $RESULT" + done +} + +reg_max9x_deserr () { + local BUS=$1 + local DEV=$2 + local DNAME=$3 + + # GMSL a|b|c|d video Lock Status + echo "$DNAME i2c-$BUS@0x$DEV ERR_CNT Status:" + # Mapping: RegAddr:Bit + if [[ $DNAME == "max9296" ]]; then + REGS=("0x01dc:5/VPRBS_FAIL" "0x01fc:5/VPRBS_FAIL"\ + "0x0100:0/VIDA_LCRC_ERR" "0x0112:4/VIDB_LCRC_ERR" "0x0124:0/VIDC_LCRC_ERR" "0x0136:0/VIDD_LCRC_ERR") + else + REGS=("0x01dc:5/VPRBS_FAIL" "0x01fc:5/VPRBS_FAIL" \ + "0x0108:4/VIDA_SEQ_ERR" "0x011A:4/VIDB_SEQ_ERR" "0x012C:4/VIDC_SEQ_ERR" "0x013E:4/VIDD_SEQ_ERR" \ + "0x0B1B:2/VIDA_LCRC_ERR" "0x0C1B:2/VIDB_LCRC_ERR" "0x0D1B:2/VIDC_LCRC_ERR" "0x0E1B:2/VIDD_LCRC_ERR") + fi + + for REG in "${REGS[@]}"; do + ADDR=${REG%:*} + ARGS=${REG#*:} + BIT=${ARGS%/*} + LBL=${ARGS#*/} + # Split 16-bit address for w2 transfer (e.g., 0x0005 -> 0x0 0x5) + HIGH_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) >> 8 ))) + LOW_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) & 0xFF ))) + + VAL=$(i2ctransfer -y -f $BUS w2@0x$DEV $HIGH_BYTE $LOW_BYTE r1) + RESULT=$(check_bit $VAL $BIT $LBL) + echo " Reg $ADDR bit$BIT: $VAL -> status: $RESULT" + done + + if [[ $DNAME == "max9296" ]]; then + DEEP_REGS=("0x0022:0-FF/DEC_ERR_A" "0x0023:0-FF/DEC_ERR_B" "0x0025:0-FF/PKT_CNT") + else + DEEP_REGS=("0x0035:0-FF/DEC_ERR_A" "0x0036:0-FF/DEC_ERR_B" "0x0037:0-FF/DEC_ERR_C" "0x0038:0-FF/DEC_ERR_D" \ + "0x0039:0-FF/IDL_ERR_A" "0x003A:0-FF/IDL_ERR_B" "0x003B:0-FF/IDL_ERR_C" "0x003C:0-FF/IDL_ERR_D" \ + "0x0B19:0-FF/CC_CRC_ERR_A" "0x0C19:0-FF/CC_CRC_ERR_B" "0x0D19:0-FF/CC_CRC_ERR_C" "0x0E19:0-FF/CC_CRC_ERR_D" \ + "0x0B1A:0-FF/RC_CRC_ERR_A" "0x0C1A:0-FF/RC_CRC_ERR_B" "0x0D1A:0-FF/RC_CRC_ERR_C" "0x0E1A:0-FF/RC_CRC_ERR_D" \ + "0x0B1C:0-3F/EOM_EYE_WIDTH_A" "0x0C1C:0-3F/EOM_EYE_WIDTH_B" "0x0D1C:0-3F/EOM_EYE_WIDTH_C" "0x0E1C:0-3F/EOM_EYE_WIDTH_D" \ + "0x08D0:0-F/TX0_PKT_CNT" "0x08D2:0-F/PHY0_PKT_CNT" \ + "0x08D1:0-F/TX2_PKT_CNT" "0x08D3:0-F/PHY2_PKT_CNT") + fi + for ENTRY in "${DEEP_REGS[@]}"; do + ADDR=${ENTRY%:*} + ARGS=${ENTRY#*:} + ARG1=${ARGS%/*} + LBL=${ARGS#*/} + ARG2=${ARG1%/*} + BIT=${ARG2%-*} + MSK=${ARG2#*-*} + #echo $BIT + #echo $MASK + HIGH_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) >> 8 ))) + LOW_BYTE=$(printf "0x%x" $(( (16#${ADDR#0x}) & 0xFF ))) + + VAL=$(i2ctransfer -y -f $BUS w2@0x$DEV $HIGH_BYTE $LOW_BYTE r1) + RESULT=$(check_cnt $VAL $BIT $MSK) + echo " Reg $ADDR [$LBL] mask0x$MSK: $VAL -> count: $RESULT" + done +} + +# Wait with timeout (Bash 4.3+) +wait_with_timeout() { + local pid=$1 + local timeout=$2 + local dname=$3 + local bus=$4 + local addr=$5 + local count=0 + local wcount=0 + local ret=0 + + echo "$(reg_max9x_deserr $bus $addr ${dname})" 1>&2 # clear max9x error status + + + while kill -0 "$pid" 2>/dev/null; do + + #echo $count + + err_cnt=$(dmesg | grep -i "error group 3 code 18" | wc -l) + if [[ $(($err_cnt - $err_initial)) -ge 1 ]]; then + err_tailmsg=$(dmesg -T | grep -i "error group 3 code 18" | tail -n 1) + echo "Oops: $err_tailmsg" + echo "Error waiting for PID $pid [HW_ERR_BAD_FRAME_DIM cnt=$(($err_cnt - $err_initial))]" + reg_max9x_deserr $bus $addr ${dname} #Check Deserializer status + return 0 + fi + + if [[ $count -ge $timeout ]]; then + echo "Timeout waiting for PID $pid" + return 1 + fi + + sleep 1 + count=$((count + 1)) + if [[ "$watch" == "true" ]]; then + wcount=$((wcount + 1)) + if [[ $wcount -gt 10 ]]; then + reg_max9x_deserr $bus $addr ${dname} #Check Deserializer status + wcount=0 + fi + fi + done + + wait "$pid" + ret=$? + #echo $ret + if [[ $ret -ne 0 ]]; then + return $((-1)) + else + return 1 + fi +} + +test_v4l2src_1920x1536_2by2() { + local i=$1 + local csi=$2 + local fcnt=$3 + local tout=$4 + local dname=$5 + local bus=$6 + local addr=$7 + local tuple=${8:-'a|d'} + local pid=0 + local ret=0 + local out="videoconvert ! xvimagesink" + + [[ "$nox" == "true" ]] && out="fakesink sync=false enable-last-sample=false" + + echo "Starting ... $dname GMSL $tuple-$csi links $testcase test-run $i with PID: $pid" + + gst-launch-1.0 \ + v4l2src device=/dev/video-isx031-${tuple%|*}-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_0 \ + v4l2src device=/dev/video-isx031-${tuple#*|}-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_1 \ + compositor name=comp \ + sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ + sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ + ! ${out} & + + # Get the PID of the last background process + pid=$! + + if [[ "$watch" == "true" ]]; then + reg_max9x_vidlock $bus $addr #Check Deserializer status + fi + + wait_with_timeout $pid $(($tout+1)) $dname $bus $addr + ret=$? + + # kill any background process + kill $pid 2>/dev/null + + return $ret +} + +test_v4l2src_1920x1536_4by4() { + local i=$1 + local csi=$2 + local fcnt=$3 + local tout=$4 + local dname=$5 + local bus=$6 + local addr=$7 + local tuple=${8:-'a|d'} + local dmux=${9:-'a-0'} + local pid=0 + local ret=0 + local out="videoconvert ! xvimagesink" + + [[ "$nox" == "true" ]] && out="fakesink sync=false enable-last-sample=false" + + echo "Starting... $dname GMSL a|b|c|d-$csi links $testcase test-run $i with PID: $pid" + + gst-launch-1.0 \ + v4l2src device=/dev/video-isx031-a-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_0 \ + v4l2src device=/dev/video-isx031-b-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_1 \ + v4l2src device=/dev/video-isx031-c-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_2 \ + v4l2src device=/dev/video-isx031-d-$csi num-buffers=$fcnt ! \ + 'video/x-raw, width=1920, height=1536, format=UYVY, pixel-aspect-ratio=1/1, framerate=30/1' ! comp.sink_3 \ + compositor name=comp \ + sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ + sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ + sink_2::xpos=0 sink_2::ypos=1536 sink_2::width=1920 sink_2::height=1536 sink_2::zorder=3 \ + sink_3::xpos=1920 sink_3::ypos=1536 sink_3::width=1920 sink_3::height=1536 sink_3::zorder=4 \ + ! ${out} & + + # Get the PID of the last background process + pid=$! + + if [[ "$watch" == "true" ]]; then + reg_max9x_vidlock $bus $addr #Check Deserializer status + fi + + wait_with_timeout $pid $(($tout+1)) $dname $bus $addr + ret=$? + + # kill any background process + kill $pid 2>/dev/null + + return $ret +} + +test_v4l2src_1920x1536_6by6() { + local i=$1 + local csi=$2 + local fcnt=$3 + local tout=$4 + local dname=$5 + local bus=$6 + local addr=$7 + local tuple=${8:-'b|c'} + local pid=0 + local ret=0 + local out="videoconvert ! xvimagesink" + + [[ "$nox" == "true" ]] && out="fakesink sync=false enable-last-sample=false" + + if [[ $csi -eq $desA_csi ]]; then + echo "Starting... $dname GMSL a|b|c|d-$desA_csi and $tuple-$desB_csi links $testcase test-run $i with PID: $pid" + gst-launch-1.0 \ + v4l2src device=/dev/video-isx031-a-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_0 \ + v4l2src device=/dev/video-isx031-b-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_1 \ + v4l2src device=/dev/video-isx031-c-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_2 \ + v4l2src device=/dev/video-isx031-d-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_3 \ + v4l2src device=/dev/video-isx031-${tuple%|*}-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_4 \ + v4l2src device=/dev/video-isx031-${tuple#*|}-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_5 \ + compositor name=comp \ + sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ + sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ + sink_2::xpos=3840 sink_2::ypos=0 sink_2::width=1920 sink_2::height=1536 sink_2::zorder=3 \ + sink_3::xpos=5760 sink_3::ypos=0 sink_3::width=1920 sink_3::height=1536 sink_3::zorder=4 \ + sink_4::xpos=0 sink_4::ypos=1536 sink_4::width=1920 sink_4::height=1536 sink_4::zorder=5 \ + sink_5::xpos=1920 sink_5::ypos=1536 sink_5::width=1920 sink_5::height=1536 sink_5::zorder=6 \ + ! ${out} & + else + echo "Starting... $dname GMSL a|b|c|d-$desB_csi and $tuple-$desA_csi links $testcase test-run $i with PID: $pid" + gst-launch-1.0 \ + v4l2src device=/dev/video-isx031-a-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_0 \ + v4l2src device=/dev/video-isx031-b-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_1 \ + v4l2src device=/dev/video-isx031-c-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_2 \ + v4l2src device=/dev/video-isx031-d-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_3 \ + v4l2src device=/dev/video-isx031-${tuple%|*}-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_4 \ + v4l2src device=/dev/video-isx031-${tuple#*|}-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_5 \ + compositor name=comp \ + sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ + sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ + sink_2::xpos=3840 sink_2::ypos=0 sink_2::width=1920 sink_2::height=1536 sink_2::zorder=3 \ + sink_3::xpos=5760 sink_3::ypos=0 sink_3::width=1920 sink_3::height=1536 sink_3::zorder=4 \ + sink_4::xpos=0 sink_4::ypos=1536 sink_4::width=1920 sink_4::height=1536 sink_4::zorder=5 \ + sink_5::xpos=1920 sink_5::ypos=1536 sink_5::width=1920 sink_5::height=1536 sink_5::zorder=6 \ + ! ${out} & + fi + + # Get the PID of the last background process + pid=$! + + if [[ "$watch" == "true" ]]; then + reg_max9x_vidlock $desA_i2c $desA_addr #Check Deserializer status + reg_max9x_vidlock $desB_i2c $desB_addr #Check Deserializer status + fi + + wait_with_timeout $pid $(($tout+1)) $dname $bus $addr + ret=$? + + # kill any background process + kill $pid 2>/dev/null + + return $ret +} + +test_v4l2src_1920x1536_8by8() { + local i=$1 + local csi=$2 + local fcnt=$3 + local tout=$4 + local dname=$5 + local bus=$6 + local addr=$7 + local tuple=${8:-'b|c'} + local pid=0 + local ret=0 + local out="videoconvert ! xvimagesink" + + [[ "$nox" == "true" ]] && out="fakesink sync=false enable-last-sample=false" + + gst-launch-1.0 \ + v4l2src device=/dev/video-isx031-a-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_0 \ + v4l2src device=/dev/video-isx031-b-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_1 \ + v4l2src device=/dev/video-isx031-c-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_2 \ + v4l2src device=/dev/video-isx031-d-$desA_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_3 \ + v4l2src device=/dev/video-isx031-a-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_4 \ + v4l2src device=/dev/video-isx031-b-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_5 \ + v4l2src device=/dev/video-isx031-c-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_6 \ + v4l2src device=/dev/video-isx031-d-$desB_csi num-buffers=$fcnt ! \ + 'video/x-raw,width=1920,height=1536,format=UYVY,pixel-aspect-ratio=1/1,framerate=30/1' ! comp.sink_7 \ + compositor name=comp \ + sink_0::xpos=0 sink_0::ypos=0 sink_0::width=1920 sink_0::height=1536 sink_0::zorder=1 \ + sink_1::xpos=1920 sink_1::ypos=0 sink_1::width=1920 sink_1::height=1536 sink_1::zorder=2 \ + sink_2::xpos=3840 sink_2::ypos=0 sink_2::width=1920 sink_2::height=1536 sink_2::zorder=3 \ + sink_3::xpos=5760 sink_3::ypos=0 sink_3::width=1920 sink_3::height=1536 sink_3::zorder=4 \ + sink_4::xpos=0 sink_4::ypos=1536 sink_4::width=1920 sink_4::height=1536 sink_4::zorder=5 \ + sink_5::xpos=1920 sink_5::ypos=1536 sink_5::width=1920 sink_5::height=1536 sink_5::zorder=6 \ + sink_6::xpos=3840 sink_6::ypos=1536 sink_6::width=1920 sink_6::height=1536 sink_6::zorder=7 \ + sink_7::xpos=5760 sink_7::ypos=1536 sink_7::width=1920 sink_7::height=1536 sink_7::zorder=8 \ + ! ${out} & + + # Get the PID of the last background process + pid=$! + + echo "Started GMSL a|b|c|d-0 and a|b|c|d-2 links $testcase test-run $i with PID: $pid" + if [[ "$watch" == "true" ]]; then + reg_max9x_vidlock $desA_i2c $desA_addr #Check Deserializer status + reg_max9x_vidlock $desB_i2c $desB_addr #Check Deserializer status + fi + + wait_with_timeout $pid $(($tout+1)) $dname $bus $addr + ret=$? + + # kill any background process + kill $pid 2>/dev/null + + return $ret +} + +# Main loop checks shutdown flag +i=0 +pass=0 +fail=0 +ign=0 +pass1=0 +fail1=0 +ign1=0 +err_baseline=$(dmesg | grep -i "error group 3 code 18" | wc -l) +err_count=$err_baseline + +################################## +framecnt=330 #10s of frames +testvar1=${duration:-1} #default 1=10s +testvar2=${loop:-10} # default one loop +testvar3=${mux:-'a-0'} # watch a-0 deserilizer +testcase=${testcase:-'4by4'} +################################## +timeout=$(($testvar1*10)) +framecnt=$(($framecnt*$testvar1)) + + +echo "${NC}########################################" + +#Check A/B Deserializers DEV_ID +val=$(reg_max9x_id $desA_i2c $desA_addr) +case ${val} in + 0x94) + desA_name="max9296" + ;; + 0xa2|0xa3|0xa4) + desA_name="max96724" + ;; + *) + echo "NO max9x Deserializer Found at i2c-$desA_i2c@0x$desA_addr!" + exit 0 + ;; +esac +echo " Found $desA_name GMSL Deserializer at i2c-$desA_i2c@0x$desA_addr!" + +case "$(reg_max9x_id $desB_i2c $desB_addr)" in + 0x94) + desB_name="max9296" + ;; + 0xa2|0xa3|0xa4) + desB_name="max96724" + ;; + *) + echo "NO max9x Deserializer Found at i2c-$desB_i2c@0x$desB_addr!" + exit 0 + ;; +esac +echo " Found $desB_name GMSL Deserializer at i2c-$desB_i2c@0x$desA_addr!" + +[[ $duration -eq 0 ]] && exit 0 + +#echo "$timeout = $framecnt" + +[[ "$testcase" != "2by2" && "$testcase" != "4by4" && "$testcase" != "6by6" && "$testcase" != "8by8" ]] && exit 0 + +#echo "${testvar3%-*}" +#echo "${testvar3#*-}" + +[[ "${testvar3%-*}" != "a" && "${testvar3%-*}" != "b" && "${testvar3%-*}" != "c" && "${testvar3%-*}" != "d" ]] && exit 0 +[[ ${testvar3#*-} -gt 5 ]] && exit 0 + +echo "GMSL $desA_name A/B back-to-back $testcase #ofFrames=$framecnt [timeout=${timeout} sec streaming 1920x1536@30fps] #ofIteration=$testvar2" +reg_max9x_lnklock $desA_i2c $desA_addr #Check A Deserializer link status +reg_max9x_lnklock $desB_i2c $desB_addr #Check B Deserializer link status + +while [[ "$shutdown_requested" == "false" ]]; do + + # back-to-back Testcases, ignore after 5 attempt for the rest of the test-runner + if [[ $ign -lt 5 ]]; then + + echo -e "\n========================================" + + # Change 2by2 or 6by6 permutation + [[ "${testcase}" == "2by2" && $desA_name == "max9296" ]] && desA_tuple="a|b" + [[ "${testcase}" == "6by6" ]] && desA_tuple="b|c" + + is_ignored=false + err_initial=$(dmesg | grep -i "error group 3 code 18" | wc -l) + test_v4l2src_1920x1536_${testcase} $i $desA_csi $framecnt $(($timeout+2)) $desA_name $desA_i2c $desA_addr $desA_tuple + ret=$? + #echo $ret + if [[ $ret -eq 0 ]]; then + reg_max9x_lnklock $desA_i2c $desA_addr #Check A Deserializer link status + echo "completed $testcase testrun $i unsuccessfully" + fail=$((fail+1)) + else + if [[ $ret -eq 255 ]]; then + echo "ignored $testcase testrun $i" + ign=$((ign+1)) + is_ignored=true + else + echo "completed $testcase testrun $i successfully" + pass=$((pass+1)) + fi + fi + + if [[ "$is_ignored" == "false" ]]; then + sleep 5 + echo "########################################" + fi + + i=$((i+1)) + + fi + + [[ $i -ge $testvar2 ]] && shutdown_requested=true + + # in any testcase if camera are not connected to the respective ports, + # exit after 5 attempt + [[ $ign -ge 5 && $ign1 -ge 5 ]] && exit 0 + + # in 8by8 testcase if camera are not connected to the respective ports, + # exit after 5 attempt + [[ "${testcase}" == "8by8" && $ign -gt 5 ]] && exit 0 + + # ignore after 5 attempt or in 8by8 testcase + [[ "${testcase}" == "8by8" || $ign1 -gt 5 ]] && continue + + # Change 2by2 or 6by6 permutation + [[ "${testcase}" == "2by2" && $desB_name == "max9296" ]] && desB_tuple="a|b" + [[ "${testcase}" == "6by6" ]] && desB_tuple="b|c" + + echo -e "$\n========================================" + is_ignored=false + err_initial=$(dmesg | grep -i "error group 3 code 18" | wc -l) + test_v4l2src_1920x1536_${testcase} $i $desB_csi $framecnt $(($timeout+2)) $desB_name $desB_i2c $desB_addr $desB_tuple + ret=$? + #echo $ret + if [[ $ret -eq 0 ]]; then + reg_max9x_lnklock $desB_i2c $desB_addr #Check B Deserializer link status + echo "completed $testcase testrun $i unsuccessfully" + fail1=$((fail1+1)) + else + if [[ $ret -eq 255 ]]; then + echo "ignored $testcase testrun $i" + ign1=$((ign1+1)) + is_ignored=true + else + echo "completed $testcase testrun $i successfully" + pass1=$((pass1+1)) + fi + fi + + if [[ "$is_ignored" == "false" ]]; then + sleep 5 + echo "########################################" + fi + + i=$((i+1)) + +done + +err_count=$(dmesg | grep -i "error group 3 code 18" | wc -l) +echo "---------------------------------" +echo "GMSL isx031 camera links $testcase Back-to-Back v4l2src Stress-Test" +echo "Testvars: #ofFrames=$framecnt [1920x1536@30fps streaming for ~$timeout sec.] #ofIteration=$testvar2" +[[ "${testcase}" == "2by2" ]] && echo "GMSL $desA_name a|d-$desA_csi testruns=$(($pass+$fail+$ign)) (pass/fail/ignore=$pass/$fail/$ign)" +[[ "${testcase}" == "2by2" ]] && echo "GMSL $desB_name a|d-$desB_csi testruns=$(($pass1+$fail1+$ign1)) (pass/fail/ignore=$pass1/$fail1/$ign1)" +[[ "${testcase}" == "4by4" ]] && echo "GMSL $desA_name a|b|c|d-desA_csi testruns=$(($pass+$fail+$ign)) (pass/fail/ignore=$pass/$fail/$ign)" +[[ "${testcase}" == "4by4" ]] && echo "GMSL $desB_name a|b|c|d-$desB_csi testruns=$(($pass1+$fail1+$ign1)) (pass/fail/ignore=$pass1/$fail1/$ign1)" +[[ "${testcase}" == "6by6" ]] && echo "GMSL $desA_name a|b|c|d-desA_csi and $desB_name b|c-$desB_csi testruns=$(($pass+$fail+$ign)) (pass/fail/ignore=$pass/$fail/$ign)" +[[ "${testcase}" == "6by6" ]] && echo "GMSL $desB_name a|b|c|d-$desB_csi and $desA_name b|c-desA_csi testruns=$(($pass1+$fail1+$ign1)) (pass/fail/ignore=$pass1/$fail1/$ign1)" +[[ "${testcase}" == "8by8" ]] && echo "GMSL $desA_name a|b|c|d-$desA_csi and $desB_name a|b|c|d-$desB_csi testruns=$(($pass+$fail+$ign)) (pass/fail/ignore=$pass/$fail/$ign)" +echo "[HW_ERR_BAD_FRAME_DIM cnt=$(($err_count - $err_baseline)))]" +echo "exiting..." diff --git a/helpers/rs-enum-ipu.sh b/helpers/rs-enum-ipu.sh new file mode 100755 index 00000000..7d91f9d1 --- /dev/null +++ b/helpers/rs-enum-ipu.sh @@ -0,0 +1,222 @@ +#!/bin/bash +# Create symbolic links for video nodes and for metadata nodes - /dev/video-rs-[|-md]-[camera-index] +# This script intended for mipi devices on IPU7. +# After running this script in enumeration mode, it will create links as follow for example: +# Example of the output: +# +# Pantherlake: +#$ ./rs-enum.sh +#Bus Camera Sensor Node Type Video Node RS Link +# ipu7 0 depth Streaming /dev/video4 /dev/video-rs-depth-a-0 +# ipu7 0 depth Metadata /dev/video5 /dev/video-rs-depth-md-a-0 +# ipu7 0 ir Streaming /dev/video8 /dev/video-rs-ir-a-0 +# ipu7 0 imu Streaming /dev/video9 /dev/video-rs-imu-a-0 +# ipu7 0 color Streaming /dev/video6 /dev/video-rs-color-a-0 +# ipu7 0 color Metadata /dev/video7 /dev/video-rs-color-md-a-0 +# i2c 0 d4xx Firmware /dev/d4xx-dfu-a /dev/d4xx-dfu-a-0 +# ipu7 2 depth Streaming /dev/video36 /dev/video-rs-depth-a-2 +# ipu7 2 depth Metadata /dev/video37 /dev/video-rs-depth-md-a-2 +# ipu7 2 ir Streaming /dev/video40 /dev/video-rs-ir-a-2 +# ipu7 2 imu Streaming /dev/video41 /dev/video-rs-imu-a-2 +# ipu7 2 color Streaming /dev/video38 /dev/video-rs-color-a-2 +# ipu7 2 color Metadata /dev/video39 /dev/video-rs-color-md-a-2 +# i2c 2 d4xx Firmware /dev/d4xx-dfu-c /dev/d4xx-dfu-a-2 + +# Dependency: v4l-utils +v4l2_util=$(which v4l2-ctl) +media_util=$(which media-ctl) +if [ -z ${v4l2_util} ]; then + echo "v4l2-ctl not found, install with: sudo apt install v4l-utils" + exit 1 +fi +metadata_enabled=1 +# +# parse command line parameters +# for '-i' parameter, print links only +while [[ $# -gt 0 ]]; do + case $1 in + -i|--info) + info=1 + shift + ;; + -q|--quiet) + quiet=1 + shift + ;; + -m|--mux) + shift + if [ ${#1} -eq 3 ]; then + mux_param=$1 + fi + shift + ;; + -n|--no-metadata) + metadata_enabled=0 + shift + ;; + *) + info=0 + quiet=0 + shift + ;; + esac +done +#set -x +if [[ $info -eq 0 ]]; then + if [ "$(id -u)" -ne 0 ]; then + echo "Please run as root." >&2 + exit 1 + fi +fi + + +# all available DS5 muxes, each one represent physically connected camera. +# muxes prefix a, b, c, d referes to max96724 aggregated link cameras +# muxes suffix 0, 1, 2, 3, 4, 5 is referes to csi2 mipi port mapping + +mux_list=${mux_param:-'a-0 b-0 c-0 d-0 a-1 b-1 c-1 d-1 a-2 b-2 c-2 d-2 a-3 b-3 c-3 d-3 a-4 b-4 c-4 d-4 a-5 b-5 c-5 d-5'} + +# mapping for DS5 mux entity to IPU6/IPU7 CSI-2 entity matching. +declare -A camera_idx=( [a]=0 [b]=1 [c]=2 [d]=3 ) +declare -A mipi_csi2_idx=( [a]=0 [b]=0 [c]=0) +declare -A d4xx_vc_named=([depth]=1 [rgb]=3 [ir]=5 [imu]=6) +declare -A camera_names=( [depth]=depth [rgb]=color [ir]=ir [imu]=imu ) + +camera_vid=("depth" "depth-md" "color" "color-md" "ir" "ir-md" "imu") + +#IPU6 or IPU7 ISYS +mdev=$(${v4l2_util} --list-devices | grep -A100 ipu | grep media) +cap_prefix=$(${v4l2_util} --list-devices | grep ipu | grep PCI | sed 's/^\(ipu[6|7]\).*/\1/' | tr '[:lower:]' '[:upper:]') + +if [ -n "${mdev}" ]; then +[[ $quiet -eq 0 ]] && printf "Bus\tCamera\tSensor\tNode Type\tVideo Node\tRS Link\n" +# cache media-ctl output +dot=$(${media_util} -d ${mdev} --print-dot | grep -v dashed) +# for all d457 muxes a-0, b-0, c-0, a-1, b-1, c-1, a-2, b-2, c-2, +for camera in $mux_list; do + create_dfu_dev=0 + update_mux_link_freq=0 + csi2="$((${camera:2:1}))" + mux=${camera:0:1} + suffix="$((${camera_idx[${mux}]}+(4*${csi2})))" + vpad=0 + [[ "${mux}" == "b" ]] && vpad=$((${vpad}+6)) + [[ "${mux}" == "c" ]] && vpad=$((${vpad}+1)) + [[ "${mux}" == "d" ]] && vpad=$((${vpad}+7)) + + for sens in "${!d4xx_vc_named[@]}"; do + # get sensor binding from media controller + d4xx_sens=$(echo "${dot}" | grep "D4XX $sens $camera" | awk '{print $1}') + + [[ -z $d4xx_sens ]] && continue; # echo "SENS $sens NOT FOUND" && continue + + if [ "${mux}" \= "c" ]; then + # remap IR amd IMU suffix c to CSI2 input 13 and 14 + [[ ${camera_names["${sens}"]} == 'depth' ]] && vpad=1 + [[ ${camera_names["${sens}"]} == 'color' ]] && vpad=1 + [[ ${camera_names["${sens}"]} == 'ir' ]] && vpad=$((${vpad}+7)) + [[ ${camera_names["${sens}"]} == 'imu' ]] && vpad=$((${vpad}+7)) + #echo "${camera_names["${sens}"]}:${d4xx_vc_named[${sens}]} -> $((${d4xx_vc_named[${sens}]}+${vpad}))" + fi + + if [ "${mux}" \= "d" ]; then + # remap IR amd IMU suffix d to CSI2 input 15 and 16 + [[ ${camera_names["${sens}"]} == 'depth' ]] && vpad=7 + [[ ${camera_names["${sens}"]} == 'color' ]] && vpad=7 + [[ ${camera_names["${sens}"]} == 'ir' ]] && vpad=$((${vpad}+3)) + [[ ${camera_names["${sens}"]} == 'imu' ]] && vpad=$((${vpad}+3)) + #echo "${camera_names["${sens}"]}:${d4xx_vc_named[${sens}]} -> $((${d4xx_vc_named[${sens}]}+${vpad}))" + fi + + d4xx_sens_mux=$(echo "${dot}" | grep $d4xx_sens:port0 | awk '{print $3}' | awk -F':' '{print $1}') + csi2=$(echo "${dot}" | grep $d4xx_sens_mux:port0 | awk '{print $3}' | awk -F':' '{print $1}') + vid_nd=$(echo "${dot}" | grep -w "$csi2:port$((${d4xx_vc_named[${sens}]}+${vpad}))" | awk '{print $3}' | awk -F':' '{print $1}') + [[ -z $vid_nd ]] && continue; # echo "SENS $sens NOT FOUND" && continue + + vid=$(echo "${dot}" | grep "${vid_nd}" | grep video | tr '\\n' '\n' | grep video | awk -F'"' '{print $1}') + [[ -z $vid ]] && continue; + dev_entity=$(echo "${dot}" | grep "${vid_nd}" | grep label | awk -F'"' '{print $2}' | awk -F'/dev' '{print $1}' | awk -F'\' '{print $1}') + csi2_pad=$(${media_util} -d ${mdev} -e "${dev_entity}" -p | awk -F'<-' '{print $2}' | awk -F'[' '{print $1}' ) + csi2_entity=$(echo $csi2_pad | awk -F':' '{print $1}' | tr -d '"') + csi2_stream=$(($(echo $csi2_pad | awk -F':' '{print $2}')-1)) + dev_ln="/dev/video-rs-${camera_names["${sens}"]}-${suffix}" + dev_name=$(${v4l2_util} -d $vid -D | grep "Card type" | awk -F':' '{print $2}') + is_route_disabled=$(${media_util} -d ${mdev} -e "${csi2_entity}" -p | grep "0/${csi2_stream} ->" | grep INACTIVE) + if [[ -z ${is_route_disabled} ]]; then + link_state="Stream(id=$csi2_stream)" + else + link_state="Inactive" + fi + mux_nd=$(echo "${dot}" | grep -w "$d4xx_sens_mux:port1" | awk '{print $3}' | awk -F':' '{print $1}') + dev_mux=$(echo "${dot}" | grep "${mux_nd}" | grep subdev | tr '\\n' '\n' | grep subdev | awk -F'"' '{print $1}' | awk -F'|' '{print $1}') + d4xx_subdev_mux=$(echo "${dot}" | grep $d4xx_sens | grep label | tr '\\n' '\n' | grep subdev | awk -F'"' '{print $1}' | awk -F'|' '{print $1}') + csi2_fmt=$(${v4l2_util} -d ${d4xx_subdev_mux} --get-subdev-fmt pad=0,stream=${csi2_stream} | grep "Width" | awk -F': ' '{print $2}' ) + + [[ $quiet -eq 0 ]] && printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$dev_name" ${camera} ${camera_names["${sens}"]} $link_state $vid $dev_ln [$csi2_fmt] + + # create link only in case we choose not only to show it + if [[ $info -eq 0 ]]; then + [[ -e $dev_ln ]] && unlink $dev_ln + ln -s $vid $dev_ln + + # change group + chown root:video $vid + + # enable ipu7 link enumeration feature + ${v4l2_util} -d $dev_ln -c enumerate_graph_link=1 + #echo "--set-fmt-video=width=${csi2_fmt%/*},height=${csi2_fmt#*/}" + [[ -e $dev_ln && ${camera_names["${sens}"]} == 'depth' ]] && ${v4l2_util} -d $dev_ln --set-fmt-video=width=${csi2_fmt%/*},height=${csi2_fmt#*/},pixelformat='Z16 ' + [[ -e $dev_ln && ${camera_names["${sens}"]} == 'color' ]] && ${v4l2_util} -d $dev_ln --set-fmt-video=width=${csi2_fmt%/*},height=${csi2_fmt#*/},pixelformat='YUYV' + [[ -e $dev_ln && ${camera_names["${sens}"]} == 'ir' ]] && ${v4l2_util} -d $dev_ln --set-fmt-video=width=${csi2_fmt%/*},height=${csi2_fmt#*/},pixelformat='Y8I ' + [[ -e $dev_ln && ${camera_names["${sens}"]} == 'imu' ]] && ${v4l2_util} -d $dev_ln --set-fmt-video=width=${csi2_fmt%/*},height=${csi2_fmt#*/},pixelformat='GREY' + fi + + update_mux_link_freq=1 # will update link freq for camera + create_dfu_dev=1 # will create DFU device link for camera + + # metadata link + if [ "$metadata_enabled" -eq 0 ]; then + continue; + fi + # skip IR metadata node for now. + [[ ${camera_names["${sens}"]} == 'ir' ]] && continue + # skip IMU metadata node. + [[ ${camera_names["${sens}"]} == 'imu' ]] && continue + # skip metadata node for c and d + [[ "${mux}" == "c" ]] && continue + [[ "${mux}" == "d" ]] && continue + + vid_num=$(echo $vid | grep -o '[0-9]\+') + dev_md_ln="/dev/video-rs-${camera_names["${sens}"]}-md-${suffix}" + dev_name=$(${v4l2_util} -d "/dev/video$(($vid_num+1))" -D | grep Model | awk -F':' '{print $2}') + + [[ $quiet -eq 0 ]] && printf '%s\t%s\t%s\tMetadata\t/dev/video%s\t%s\n' "$dev_name" ${camera} ${camera_names["${sens}"]} $(($vid_num+1)) $dev_md_ln + # create link only in case we choose not only to show it + if [[ $info -eq 0 ]]; then + [[ -e $dev_md_ln ]] && unlink $dev_md_ln + ln -s "/dev/video$(($vid_num+1))" $dev_md_ln + ${v4l2_util} -d $dev_md_ln -c enumerate_graph_link=3 + fi + + done + + # update link_freq for D457 camera + if [[ ${update_mux_link_freq} -eq 1 ]]; then + ${v4l2_util} -d $dev_mux --set-ctrl v4l2_cid_link_freq=6 + fi + + # DFU device link for camera + if [[ ${create_dfu_dev} -eq 1 ]]; then + dev_dfu_name="/dev/d4xx-dfu-${camera}" + dev_dfu_ln="/dev/d4xx-dfu-${suffix}" + if [[ $info -eq 0 ]]; then + [[ -L "$dev_dfu_ln" ]] && unlink $dev_dfu_ln + ln -s $dev_dfu_name $dev_dfu_ln + fi + [[ $quiet -eq 0 ]] && printf '%s\t%s\t%s\tFirmware \t%s\t%s\n' " i2c " ${camera} "d4xx " $dev_dfu_name $dev_dfu_ln + fi + +done +fi +# end of file + diff --git a/helpers/rs_ipu_d457_bind.sh b/helpers/rs_ipu_d457_bind.sh new file mode 100755 index 00000000..741b9906 --- /dev/null +++ b/helpers/rs_ipu_d457_bind.sh @@ -0,0 +1,351 @@ +#!/bin/bash +# file: rs_ipu7_d457_bind.sh +# This script used for binding media links of v4l2 subdevice entities +# The configuration we had is DS5 mux and IPU7 entities. +# _______ _______ _______ _________ +# |depth|->| mux |->|CSI-2|->|capture| +# |_____| |_____| |_____| |_______| +# +# Full graph can be viewed on http://magjac.com/graphviz-visual-editor/ +# media graph links can be generated by running 'media-ctl --print-dot' +# +# Dependency: v4l-utils +v4l2_util=$(which v4l2-ctl) +media_util=$(which media-ctl) +if [ -z ${v4l2_util} ]; then + echo "v4l2-ctl not found, install with: sudo apt install v4l-utils" + exit 1 +fi +# enable metadata capture nodes for default +metadata_enabled=1 +while [[ $# -gt 0 ]]; do + case $1 in + -q|--quiet) + quiet=1 + shift + ;; + -m|--mux) + shift + if [ ${#1} -ge 3 ]; then + mux_param=$1 + fi + shift + ;; + -n|--no-metadata) + metadata_enabled=0 + shift + ;; + -h|--help) + echo "-n OR -n -m " + exit 1 + ;; + -f) + fmt="$2" + shift + ;; + *) + quiet=0 + shift + ;; + esac +done + +fmt="${fmt:-640x480@1/30}" + +# mapping for DS5 mux entity to IPU6 CSI-2 entity matching. +declare -A media_mux_capture_link=( [0]=0 [1]=16 [2]=32 [3]=48 [4]=64 [5]=80) + +# all available DS5 muxes, each one represent physically connected camera. +# muxes prefix a, b, c, d referes to max96724 aggregated link cameras +# muxes suffix 0, 1, 2, 3, 4, 5 is referes to csi2 mipi port mapping + +mux_list=${mux_param:-'a-0 b-0 c-0 d-0 a-1 b-1 c-1 d-1 a-2 b-2 c-2 d-2 a-3 b-3 c-3 d-3 a-4 b-4 c-4 d-4 a-5 b-5 c-5 d-5'} + +# vc_id mapping for D4XX sensor entity to IPU6|IPU7 CSI-2 pad. +# IPU7 CSI-2 pad 1, vc 0, depth a-? +# IPU7 CSI-2 pad 2, vc 1, depth c-? or meta data a-? +# IPU7 CSI-2 pad 3, vc 1, RGB a-? +# IPU7 CSI-2 pad 4, vc 0, RGB c-? or meta data a-? +# IPU7 CSI-2 pad 5, vc 2, IR a-? +# IPU7 CSI-2 pad 6, vc 3, IMU a-? +# Aggregated-links +# ipu7 CSI-2 pad 7, vc 2, depth b-? +# IPU7 CSI-2 pad 8, vc 3, depth d-? or meta data b-? +# IPU7 CSI-2 pad 9, vc 3, RGB b-? +# IPU7 CSI-2 pad 10, vc 2, RGB d-? or meta data b-? +# IPU7 CSI-2 pad 11, vc 0, IR b-? +# IPU7 CSI-2 pad 12, vc 1, IMU b-? +# IPU7 CSI-2 pad 13, vc 3, IR c-? +# IPU7 CSI-2 pad 14, vc 2, IMU c-? +# IPU7 CSI-2 pad 15, vc 1, IR d-? +# IPU7 CSI-2 pad 16, vc 0, IMU d-? +declare -A media_sensor_csi_route=( [1]=0 [2]=1 [3]=1 [4]=0 [5]=2 [6]=3 [7]=2 [8]=3 [9]=3 [10]=2 [11]=0 [12]=1 [13]=3 [14]=2 [15]=1 [16]=0 ) + +# Find media device. +out() { + [[ $quiet -eq 0 ]] && echo -n "${@} " >&2 + "${@}" + [[ $quiet -eq 0 ]] && echo " RET=$?" >&2 +} + +# For case with usb camera plugged in during the boot, +# usb media controller will occupy index 0 +mdev=$(${v4l2_util} --list-devices | grep -A100 ipu | grep media | head -n 1) +[[ -z "${mdev}" ]] && exit 0 +# Find IPU PCI device gen name +cap_prefix=$(${v4l2_util} --list-devices | grep ipu | grep PCI | sed 's/^\(ipu[6|7]\).*/\1/' | tr '[:lower:]' '[:upper:]') +[[ -z "${cap_prefix}" ]] && exit 0 + +# D4XX requires IPU7/IPU6 ISYS Capture devices to bind to IPU7/IPU6 ISYS CSI subdev srcpads from 1-16 +# exit if IPU7/IPU6 ISYS CSI subdev 1-8 srcpads limitation detected +mdev_capdev_count=$(${v4l2_util} -d ${mdev} -A | wc -l) +[[ $((${mdev_capdev_count}-2)) -le 48 ]] && exit 0 + +clean_symlink_cmd="find /dev -type l -name *video-rs* -delete" +out $clean_symlink_cmd # <- this can be used to clean-up all bindings from librealsense2 + +media_ctl_cmd="${media_util} -d ${mdev}" +[[ -n "${mux_param}" ]] && out $media_ctl_cmd -r # <- this can be used to clean-up all bindings from media controller +# cache media-ctl output +dot=$($media_ctl_cmd --print-dot) + +fmt_rgb="[fmt:YUYV8_1X16/$fmt field:none]" +csi_fmt_rgb="[fmt:YUYV8_1X16/${fmt%@*} field:none]" + +#WA make sure depth and rgb don't exceed +if [ "${fmt%@*}" == "1280x800" ]; then + fmt="1280x720@${fmt#*@}" +fi +fmt_depth="[fmt:UYVY8_1X16/$fmt field:none]" +fmt_ir="[fmt:VYUY8_1X16/$fmt field:none]" +csi_fmt_depth="[fmt:UYVY8_1X16/${fmt%@*} field:none]" +csi_fmt_ir="[fmt:VYUY8_1X16/${fmt%@*} field:none]" + +# For fimware version starting from: 5.16, +# IMU will have 32bit axis values. +# 5.16.x.y = firmware version: 0x0510 +# state->fw_version < 0x510 +#fmt_imu="[fmt:Y8_1X8/32x1 field:none]" +# state->fw_version >= 0x510 +fmt_imu="[fmt:Y8_1X8/38x1@1/30 field:none]" +csi_fmt_imu="[fmt:Y8_1X8/38x1 field:none]" + +count=0 +pre_csi2=0 + +# DS5 MUX. Can be max9296 or max96724 aggregated {a, b, c, d} accross mipi port {0, 1, 2, 3, 4, 5}. +for camera in ${mux_list}; do + + is_mux=$(echo "${dot}" | grep "DS5 mux ${camera}") + # skip for non-existing muxes + [[ -z $is_mux ]] && continue; + + [[ $quiet -eq 0 ]] && echo -n "Bind $cap_prefix to DS5 mux ${camera} ..." + + csi2="$((${camera:2:1}))" + mux=${camera:0:1} + + [[ $csi2 -ne $pre_csi2 ]] && count=0 + + count=$((${count}+1)) + + echo "(count=${count} on $cap_prefix CSI ${csi2})..." + + # mapping for DS5 mux entity to IPU7 ISYS entity matching. + # for DS5 aggregated mux (above a) - use ISYS capture pads with offset 6 + # this offset used as capture sensor set - dept,rgb,ir,imu = 4 + cap_pad="$((${media_mux_capture_link[${csi2}]}))" + isys_pad=0 + ir_active=1 + imu_active=1 + if [ "${mux}" \= "b" ]; then + isys_pad=6 + if [ $count -gt 1 ] && [ -z "${mux_param}" ]; then + ir_active=0 + imu_active=0 + fi + else + if [ "${mux}" \= "c" ]; then + metadata_enabled=0 + isys_pad=1 + if [ $count -gt 1 ] && [ -z "${mux_param}" ]; then + ir_active=0 + imu_active=0 + fi + else + if [ "${mux}" \= "d" ]; then + metadata_enabled=0 + isys_pad=7 + if [ $count -gt 1 ] && [ -z "${mux_param}" ]; then + ir_active=0 + imu_active=0 + fi + fi + fi + fi + cap_pad=$((${cap_pad}+${isys_pad})) + + # DEPTH video streaming node (no metadata) + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${isys_pad}+1)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+0))\":0[1]" 1>/dev/null + # DEPTH metadata node + if [[ $metadata_enabled -eq 1 ]]; then + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${isys_pad}+2)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+1))\":0[1]" 1>/dev/null + fi + + # RGB link video streaming node (no metadata) + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${isys_pad}+3)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+2))\":0[1]" 1>/dev/null + # RGB metadata node + if [[ $metadata_enabled -eq 1 ]]; then + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${cap_pad}+4)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+3))\":0[1]" 1>/dev/null + fi + + stream_id_depth=$((${isys_pad}+0)) + stream_id_rgb=$((${isys_pad}+2)) + isys_pad_depth=$((${isys_pad}+1)) + isys_pad_rgb=$((${isys_pad}+3)) + isys_vc_depth=$((${media_sensor_csi_route[${isys_pad_depth}]}+0)) + isys_vc_rgb=$((${media_sensor_csi_route[${isys_pad_rgb}]}+0)) + + if [ "${mux}" \= "c" ]; then + # remap IR amd IMU suffix c to CSI2 input 13 and 14 + isys_pad=$((${isys_pad}+7)) + cap_pad=$((${cap_pad}+7)) + else + if [ "${mux}" \= "d" ]; then + # remap IR amd IMU suffix d to CSI2 input 15 and 16 + isys_pad=$((${isys_pad}+3)) + cap_pad=$((${cap_pad}+3)) + fi + fi + # IR link video streaming node + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${isys_pad}+5)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+4))\":0[1]" 1>/dev/null + # IMU link streaming node + out $media_ctl_cmd -v -l "\"Intel ${cap_prefix} CSI2 ${csi2}\":$((${isys_pad}+6)) -> \"Intel ${cap_prefix} ISYS Capture $((${cap_pad}+5))\":0[1]" 1>/dev/null + + stream_id_ir=$((${isys_pad}+4)) + stream_id_imu=$((${isys_pad}+5)) + isys_pad_ir=$((${isys_pad}+5)) + isys_pad_imu=$((${isys_pad}+6)) + isys_vc_ir=$((${media_sensor_csi_route[${isys_pad_ir}]}+0)) + isys_vc_imu=$((${media_sensor_csi_route[${isys_pad_imu}]}+0)) + + # bind (enable) link DS5 mux pad 0 to CSI-2 pad 0 + out $media_ctl_cmd -l "\"DS5 mux ${camera}\":0 -> \"Intel ${cap_prefix} CSI2 ${csi2}\":0[1]" + # subdev entity '['pad-number '/' stream-number '->' pad-number '/' stream-number '[' route-flags ']' ']' ; + if [[ $metadata_enabled -eq 1 ]]; then + route_md="0/${stream_id_depth}->${isys_pad_depth}/${stream_id_depth}[1], 0/${stream_id_depth}->$((${isys_pad_depth}+1))/${stream_id_depth}[1], 0/${stream_id_rgb}->${isys_pad_rgb}/${stream_id_rgb}[1], 0/${stream_id_rgb}->$((${isys_pad_rgb}+1))/${stream_id_rgb}[1], 0/${stream_id_ir}->${isys_pad_ir}/${stream_id_ir}[${ir_active}], 0/${stream_id_imu}->${isys_pad_imu}/${stream_id_imu}[${imu_active}]" + + # Set DEPTH+DEPTH-md+RGB+RGB-md+IR+IMU vc_id routes + if [ $count -eq 1 ]; then + csi_route=${route_md} + else + csi_route=${csi_route}", "${route_md} + fi + else + # Set DEPTH+RGB+IR+IMU vc_id routes + route_no_md="0/${stream_id_depth}->${isys_pad_depth}/${stream_id_depth}[1], 0/${stream_id_rgb}->${isys_pad_rgb}/${stream_id_rgb}[1], 0/${stream_id_ir}->${isys_pad_ir}/${stream_id_ir}[${ir_active}], 0/${stream_id_imu}->${isys_pad_imu}/${stream_id_imu}[${imu_active}]" + if [ $count -eq 1 ]; then + csi_route=${route_no_md} + else + csi_route=${csi_route}", "${route_no_md} + + #WA: disable prior IR and IMU if 4 camera are used + if [ "${mux}" \= "d" ] && [ $count -gt 3 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/4\[1|\/4[0|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/5\[1|\/5[0|g' <<< $csi_route)) + fi + #WA: disable prior IR and IMU if 3 camera are used + if [ "${mux}" \= "d" ] && [ $count -gt 2 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/12\[1|\/12[0|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/13\[1|\/13[0|g' <<< $csi_route)) + fi + #WA: enable IR and IMU if 2 camera are used + if [ "${mux}" \= "b" ] && [ $count -lt 3 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/10\[0|\/10[1|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/11\[0|\/11[1|g' <<< $csi_route)) + fi + + if [ "${mux}" \= "c" ] && [ $count -lt 3 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/12\[0|\/12[1|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/13\[0|\/13[1|g' <<< $csi_route)) + fi + + if [ "${mux}" \= "d" ] && [ $count -lt 3 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/14\[0|\/14[1|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/15\[0|\/15[1|g' <<< $csi_route)) + fi + + #WA: disable prior GMSL B or C IR and IMU if 3 or 4 camera are used + if [ "${mux}" \= "c" ] && [ $count -gt 2 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/10\[1|\/10[0|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/11\[1|\/11[0|g' <<< $csi_route)) + fi + + if [ "${mux}" \= "d" ] && [ $count -gt 2 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/12\[1|\/12[0|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/13\[1|\/13[0|g' <<< $csi_route)) + fi + + #WA: disable prior GMSL A IR and IMU if 4 camera are used + if [ "${mux}" \= "d" ] && [ $count -gt 3 ] && [ $ir_active -eq 0 ]; then + csi_route=$(echo $(sed 's|\/4\[1|\/4[0|g' <<< $csi_route)) + csi_route=$(echo $(sed 's|\/5\[1|\/5[0|g' <<< $csi_route)) + fi + + fi + fi + + # example: /usr/bin/media-ctl -d /dev/media0 -R "'Intel ${cap_prefix} CSI2 2'[0/1->3/1,0/0->1/0]" + out $media_ctl_cmd -R "\"DS5 mux ${camera}\"[1/${stream_id_depth}->0/${stream_id_depth}[1], 2/${stream_id_rgb}->0/${stream_id_rgb}[1], 3/${stream_id_ir}->0/${stream_id_ir}[1], 4/${stream_id_imu}->0/${stream_id_imu}[1]]" + out $media_ctl_cmd -R "\"Intel ${cap_prefix} CSI2 ${csi2}\"[${csi_route}]" + + # DEPTH default media bus format + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":${isys_pad_depth}/${stream_id_depth} ${csi_fmt_depth}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":0/${stream_id_depth} ${fmt_depth}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":1/${stream_id_depth} ${fmt_depth}" + out $media_ctl_cmd -V "\"D4XX depth ${camera}\":0/${stream_id_depth} ${fmt_depth}" + # RGB default media bus format + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":${isys_pad_rgb}/${stream_id_rgb} ${csi_fmt_rgb}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":0/${stream_id_rgb} ${fmt_rgb}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":2/${stream_id_rgb} ${fmt_rgb}" + out $media_ctl_cmd -V "\"D4XX rgb ${camera}\":0/${stream_id_rgb} ${fmt_rgb}" + # IR default media bus format + if [[ $ir_active -eq 1 ]]; then + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":${isys_pad_ir}/${stream_id_ir} ${csi_fmt_ir}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":0/${stream_id_ir} ${fmt_ir}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":3/${stream_id_ir} ${fmt_ir}" + fi + out $media_ctl_cmd -V "\"D4XX ir ${camera}\":0/${stream_id_ir} ${fmt_ir}" + # IMU default media bus format + if [[ $imu_active -eq 1 ]]; then + out $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":${isys_pad_imu}/${stream_id_imu} ${csi_fmt_imu}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":0/${stream_id_imu} ${fmt_imu}" + out $media_ctl_cmd -V "\"DS5 mux ${camera}\":4/${stream_id_imu} ${fmt_imu}" + fi + out $media_ctl_cmd -V "\"D4XX imu ${camera}\":0/${stream_id_imu} ${fmt_imu}" + + # WA: must repeat all the v4l2 set-fmt on Intel IPU7/IPU6 CSI2 2 v4l2 subdev pad 0 + if [ ${count} -gt 1 ]; then + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/0 ${csi_fmt_depth}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/2 ${csi_fmt_rgb}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/4 ${csi_fmt_ir}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/5 ${csi_fmt_imu}" 1>/dev/null + + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/6 ${csi_fmt_depth}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/8 ${csi_fmt_rgb}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/10 ${csi_fmt_ir}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/11 ${csi_fmt_imu}" 1>/dev/null + + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/1 ${csi_fmt_depth}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/3 ${csi_fmt_rgb}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/12 ${csi_fmt_ir}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/13 ${csi_fmt_imu}" 1>/dev/null + + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/7 ${csi_fmt_depth}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/9 ${csi_fmt_rgb}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/14 ${csi_fmt_ir}" 1>/dev/null + $media_ctl_cmd -V "\"Intel ${cap_prefix} CSI2 ${csi2}\":0/15 ${csi_fmt_imu}" 1>/dev/null + fi + pre_csi2=$csi2 + +done diff --git a/include/media/gmsl-link.h b/include/media/gmsl-link.h new file mode 100644 index 00000000..de18d921 --- /dev/null +++ b/include/media/gmsl-link.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2020, NVIDIA Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @file + * GMSL API: Gigabit Multimedia Serial Link protocol + * + * @b Description: Defines elements used to set up and use a GMSL link. + */ + +#ifndef __GMSL_LINK_H__ +/** + * \defgroup GMSL Gigabit Multimedia Serial Link (GMSL) + * + * Defines the interface used to control the MAX9295 serializer and + * MAX9296 deserializer modules. + * + * @ingroup serdes_group + * @{ + */ + +#define __GMSL_LINK_H__ + +#define GMSL_CSI_1X4_MODE 0x1 +#define GMSL_CSI_2X4_MODE 0x2 +#define GMSL_CSI_2X2_MODE 0x3 +#define GMSL_CSI_4X2_MODE 0x4 + +#define GMSL_CSI_PORT_A 0x0 +#define GMSL_CSI_PORT_B 0x1 +#define GMSL_CSI_PORT_C 0x2 +#define GMSL_CSI_PORT_D 0x3 +#define GMSL_CSI_PORT_E 0x4 +#define GMSL_CSI_PORT_F 0x5 + +#define GMSL_CSI_CPHY 0x0 +#define GMSL_CSI_DPHY 0x1 + +#define GMSL_SERDES_CSI_LINK_UNKNOWN 0x0 +#define GMSL_SERDES_CSI_LINK_A 0x1 +#define GMSL_SERDES_CSI_LINK_B 0x2 +#define GMSL_SERDES_CSI_LINK_C 0x4 +#define GMSL_SERDES_CSI_LINK_D 0x8 + +/* Didn't find kernel defintions, for now adding here */ +#define GMSL_CSI_DT_RAW_12 0x2C +#define GMSL_CSI_DT_UED_U1 0x30 +#define GMSL_CSI_DT_EMBED 0x12 +#define GMSL_CSI_DT_YUV422_8 0x1E +#define GMSL_CSI_DT_RGB_888 0x24 +#define GMSL_CSI_DT_RAW_8 0x2A + +#define GMSL_ST_ID_UNUSED 0xFF + +/** + * Maximum number of data streams (\ref gmsl_stream elements) in a GMSL link + * (\ref gmsl_link_ctx). + */ +#define GMSL_DEV_MAX_NUM_DATA_STREAMS 4 + +/** + * Holds information about a data stream in a GMSL link (\ref gmsl_link_ctx). + */ +struct gmsl_stream { + __u32 st_id_sel; + __u32 st_data_type; + __u32 des_pipe; +}; + +/** + * Holds the configuration of the GMSL links from a sensor to its serializer to + * its deserializer. + */ +struct gmsl_link_ctx { + __u32 st_vc; /**< Default sensor virtual channel. */ + __u32 dst_vc; /**< Destination virtual channel (user-defined). */ + __u32 src_csi_port; /**< Sensor to serializer CSI port connection. */ + __u32 dst_csi_port; /**< Deserializer to Jetson CSI port connection. */ + __u32 serdes_csi_link; /**< GMSL link between serializer and deserializer + devices. */ + __u32 num_streams; /**< Number of active streams to be mapped + from sensor. */ + __u32 num_csi_lanes; /**< Sensor's CSI lane configuration. */ + __u32 csi_mode; /**< Deserializer CSI mode. */ + __u32 ser_reg; /**< Serializer slave address. */ + __u32 sdev_reg; /**< Sensor proxy slave address. */ + __u32 sdev_def; /**< Sensor default slave address. */ + bool serdev_found; /**< Indicates whether the serializer device for + the specified sensor source was found. Set by + the serializer driver during setup; used by + the deserializer driver to choose certain + configuration settings during setup. */ + struct gmsl_stream streams[GMSL_DEV_MAX_NUM_DATA_STREAMS]; + /*< An array of information about the data streams in the link. */ + struct device *s_dev; /**< Sensor device handle. */ +}; + +/** @} */ + +#endif /* __GMSL_LINK_H__ */ diff --git a/include/media/i2c/d4xx-max9295.h b/include/media/i2c/d4xx-max9295.h new file mode 100644 index 00000000..2942ddbd --- /dev/null +++ b/include/media/i2c/d4xx-max9295.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018, NVIDIA Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @file + * MAX9295 API: For Maxim Integrated MAX9295 serializer + * + * @b Description: Defines elements used to set up and use a + * Maxim Integrated MAX9295 serializer. + */ + +#ifndef __MAX9295_H__ +#define __MAX9295_H__ + +#include +#include + +struct max9295_pdata { + /* primary serializer properties */ + __u32 is_prim_ser; + __u32 def_addr; + __u32 d4xx_hacks; +}; + +/** + * \defgroup max9295 MAX9295 serializer driver + * + * Controls the MAX9295 serializer module. + * + * @ingroup serdes_group + * @{ + */ + +int max9295_set_pipe(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id); + +/** + * @brief Powers on a serializer device and performs the I2C overrides + * for sensor and serializer devices. + * + * The I2C overrides include setting proxy I2C slave addresses for the devices. + * + * Before the client calls this function it must ensure that + * the deserializer device is in link_ex exclusive link mode + * by calling the deserializer driver's max9296_setup_link() function. + * + * @param [in] dev The serializer device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9295_setup_control(struct device *dev); + +/** + * Reverts I2C overrides and resets a serializer device. + * + * @param [in] dev The serializer device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9295_reset_control(struct device *dev); + +/** + * @brief Pairs a sensor device with a serializer device. + * + * To be called by sensor client driver. + * + * @param [in] dev The deserializer device handle. + * @param [in] g_ctx The @ref gmsl_link_ctx structure handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9295_sdev_pair(struct device *dev, struct gmsl_link_ctx *g_ctx); + +/** + * @brief Unpairs a sensor device from a serializer device. + * + * To be called by sensor client driver. + * + * @param [in] dev The serializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9295_sdev_unpair(struct device *dev, struct device *s_dev); + +/** + * Sets up the serializer device's internal pipeline for a specified + * sensor/serializer pair. + * + * @param [in] dev The serializer device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9295_setup_streaming(struct device *dev); + +int max9295_init_settings(struct device *dev); +/** @} */ + +int max9295_set_mfp(struct device *dev, int pin, int val); +int max9295_check_status(struct device *dev); + +#endif /* __MAX9295_H__ */ diff --git a/include/media/i2c/d4xx-max9296.h b/include/media/i2c/d4xx-max9296.h new file mode 100644 index 00000000..27c9cd1f --- /dev/null +++ b/include/media/i2c/d4xx-max9296.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018-2020, NVIDIA Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @file + * MAX9296 API: For Maxim Integrated MAX9296 deserializer + * + * @b Description: Defines elements used to set up and use a + * Maxim Integrated MAX9296 deserializer. + */ + +#ifndef __MAX9296_H__ +#define __MAX9296_H__ + +#include +#include +/** + * \defgroup max9296 MAX9296 deserializer driver + * + * Controls the MAX9296 deserializer module. + * + * @ingroup serdes_group + * @{ + */ +struct max9296_pdata { + bool splitter_enabled; + __u32 max_src; + __u32 csi_mode; + __u32 d4xx_hacks; +}; + +int max9296_get_available_pipe_id(struct device *dev, int vc_id); +int max9296_set_pipe(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id); +int max9296_release_pipe(struct device *dev, int pipe_id); +void max9296_reset_oneshot(struct device *dev); + +/** + * Puts a deserializer device in single exclusive link mode, so link-specific + * I2C overrides can be performed for sensor and serializer devices. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_setup_link(struct device *dev, struct device *s_dev); + +/** + * @brief Sets up a deserializer link's control pipeline. + * + * Puts the deserializer in dual splitter mode. You must call this function + * during device boot, after max9296_setup_link(). + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_setup_control(struct device *dev, struct device *s_dev); + +/** + * @brief Resets a deserializer device's link control pipeline. + * + * The deserializer driver internally decrements the reference count and + * resets the deserializer device if all the source sensor devices are + * powered off, resetting all control and streaming configuration. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_reset_control(struct device *dev, struct device *s_dev); + +/** + * @brief Registers a source sensor device with a deserializer device. + * + * The deserializer driver internally checks all perquisites and compatibility + * factors. If it finds that the registration request is valid, + * it stores the source's @ref gmsl_link_ctx context handle in the source list + * maintained by the deserializer driver instance. + * + * @param [in] dev The deserializer device handle. + * @param [in] g_ctx A @c gmsl_link_ctx structure handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_sdev_register(struct device *dev, struct gmsl_link_ctx *g_ctx); + +/** + * Unregisters a source sensor device from its deserializer device. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_sdev_unregister(struct device *dev, struct device *s_dev); + +/** + * Performs internal pipeline configuration for a link in context to set up + * streaming, and puts the deserializer link in ready-to-stream state. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 or success, or -1 otherwise. + */ +int max9296_setup_streaming(struct device *dev, struct device *s_dev); + +/** + * @brief Enables streaming. + * + * This function is to be called by the sensor client driver. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_start_streaming(struct device *dev, struct device *s_dev); + +/** + * @brief Disables streaming. + * + * This function is to be called by the sensor client driver. + * + * @note Both @c max9296_start_streaming and @c max9296_stop_streaming + * are mainly added to enable and disable sensor streaming on the fly + * while other sensors are active. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_stop_streaming(struct device *dev, struct device *s_dev); + +/** + * @brief Powers on the max9296 deserializer module. + * + * Asserts shared reset GPIO and powers on the regulator; + * maintains the reference count internally for source devices. + * + * @param [in] dev The deserializer device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max9296_power_on(struct device *dev); + +/** + * @brief Powers off the max9296 deserializer module. + * + * Deasserts the shared reset GPIO and powers off the regulator based on + * the reference count. + * + * @param [in] dev The deserializer device handle. + */ +void max9296_power_off(struct device *dev); + +int max9296_init_settings(struct device *dev); +/** @} */ + +#endif /* __MAX9296_H__ */ diff --git a/include/media/i2c/d4xx-max929x_pdata.h b/include/media/i2c/d4xx-max929x_pdata.h new file mode 100644 index 00000000..89a86641 --- /dev/null +++ b/include/media/i2c/d4xx-max929x_pdata.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2024 Intel Corporation */ + +#ifndef MAX929X_H +#define MAX929X_H + +#define MAX929X_NAME "max929x" +#define MAX9295_I2C_ADDRESS 0x40 + +/* set this flag if this module needs serializer initialization */ +#define MAX9295_FL_INIT_SER BIT(0) +/* set this flag if this module has extra powerup sequence */ +#define MAX9295_FL_POWERUP BIT(1) +/* set this flag if this module needs reset signal */ +#define MAX9295_FL_RESET BIT(2) +/* set this flag if it need to init serial clk only */ +#define MAX9295_FL_INIT_SER_CLK BIT(4) + +#endif diff --git a/include/media/i2c/d4xx-max96724.h b/include/media/i2c/d4xx-max96724.h new file mode 100644 index 00000000..8d4a5e37 --- /dev/null +++ b/include/media/i2c/d4xx-max96724.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017-2026, INTEL CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __MAX96724_H__ +#define __MAX96724_H__ + + +#include +#include +/** + * \defgroup max96724 MAX96724 deserializer driver + * + * Controls the MAX96724 deserializer module. + * + * @ingroup serdes_group + * @{ + */ +struct max96724_pdata { + bool splitter_enabled; + __u32 max_src; + __u32 src_link; + __u32 csi_mode; + __u32 d4xx_hacks; + __u32 csi_phy; + __u32 csi_out; +}; + +int max96724_get_available_pipe_id(struct device *dev, int vc_id, u32 src_link); +int max96724_set_pipe(struct device *dev, int pipe_id, u8 data_type1, + u8 data_type2, u32 vc_id, u32 src_link); +int max96724_release_pipe(struct device *dev, int pipe_id); +void max96724_reset_oneshot(struct device *dev, u32 src_link); + +/** + * Puts a deserializer device in single exclusive link mode, so link-specific + * I2C overrides can be performed for sensor and serializer devices. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_setup_link(struct device *dev, struct device *s_dev); + +/** + * @brief Sets up a deserializer link's control pipeline. + * + * Puts the deserializer in dual splitter mode. You must call this function + * during device boot, after max96724_setup_link(). + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_setup_control(struct device *dev, struct device *s_dev); + +/** + * @brief Resets a deserializer device's link control pipeline. + * + * The deserializer driver internally decrements the reference count and + * resets the deserializer device if all the source sensor devices are + * powered off, resetting all control and streaming configuration. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_reset_control(struct device *dev, struct device *s_dev); + +/** + * @brief Registers a source sensor device with a deserializer device. + * + * The deserializer driver internally checks all perquisites and compatibility + * factors. If it finds that the registration request is valid, + * it stores the source's @ref gmsl_link_ctx context handle in the source list + * maintained by the deserializer driver instance. + * + * @param [in] dev The deserializer device handle. + * @param [in] g_ctx A @c gmsl_link_ctx structure handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_sdev_register(struct device *dev, struct gmsl_link_ctx *g_ctx); + +/** + * Unregisters a source sensor device from its deserializer device. + * + * @param [in] dev The deserializer device handle. + * @param [in] s_dev The sensor device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_sdev_unregister(struct device *dev, struct device *s_dev); + +/** + * @brief Powers on the max96724 deserializer module. + * + * Asserts shared reset GPIO and powers on the regulator; + * maintains the reference count internally for source devices. + * + * @param [in] dev The deserializer device handle. + * + * @return 0 for success, or -1 otherwise. + */ +int max96724_power_on(struct device *dev); + +/** + * @brief Powers off the max96724 deserializer module. + * + * Deasserts the shared reset GPIO and powers off the regulator based on + * the reference count. + * + * @param [in] dev The deserializer device handle. + */ +void max96724_power_off(struct device *dev); + +int max96724_init_settings(struct device *dev); +int max96724_set_mfp(struct device *dev, int pin, int val); +int max96724_check_status(struct device *dev, u32 src_link); +int max96724_switch_link_channel(struct device *dev); +/** @} */ + +#endif /* __MAX96724_H__ */ diff --git a/include/media/i2c/d4xx_pdata.h b/include/media/i2c/d4xx_pdata.h new file mode 100644 index 00000000..e0578bc4 --- /dev/null +++ b/include/media/i2c/d4xx_pdata.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2022 Intel Corporation */ + +#ifndef D457_H +#define D457_H + +#define D457_NAME "d4xx" +#define MAX9296_NAME "MAX9296" +#define MAX96724_NAME "MAX96724" + +#define D457_I2C_ADDRESS 0x10 + +#define d4xx_subdev_csi_link_id(link) \ + link == GMSL_SERDES_CSI_LINK_A ? "GMSL A" : \ + GMSL_SERDES_CSI_LINK_B ? "GMSL B" : \ + GMSL_SERDES_CSI_LINK_C ? "GMSL C" : "GMSL D" + +#endif diff --git a/include/media/ipu-acpi-pdata.h b/include/media/ipu-acpi-pdata.h index 53ce05a4..368d038a 100644 --- a/include/media/ipu-acpi-pdata.h +++ b/include/media/ipu-acpi-pdata.h @@ -8,6 +8,10 @@ #include #include +#if IS_ENABLED(CONFIG_VIDEO_D4XX) +#include +#endif + #define CL_EMPTY 0 #define CL_DISCRETE 1 #define CL_LT 5 @@ -106,6 +110,16 @@ struct serdes_local { /* sensor data type */ unsigned int sensor_dt; + +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + /* last deserializer mapped addr */ + unsigned short des_map_addr; + unsigned short des_map_addr_2; + + /* last mapped i2c_adapter */ + char i2c_adapter_bdf[32]; + char i2c_adapter_bdf_2[32]; +#endif }; -#endif \ No newline at end of file +#endif diff --git a/include/media/serdes-pdata.h b/include/media/serdes-pdata.h index 4f6d5fb0..ca321b00 100644 --- a/include/media/serdes-pdata.h +++ b/include/media/serdes-pdata.h @@ -16,6 +16,9 @@ struct serdes_subdev_info { unsigned short ser_phys_addr; unsigned int sensor_dt; struct gpiod_lookup ser_gpio[MAX_SER_GPIO_NUM]; +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + unsigned short aggregated_link; +#endif }; struct serdes_platform_data { diff --git a/patches/0001-dkms-v4l2-core-makefile-adaptation-6.12.patch b/patches/0001-dkms-v4l2-core-makefile-adaptation-6.12.patch new file mode 100644 index 00000000..fffdc07d --- /dev/null +++ b/patches/0001-dkms-v4l2-core-makefile-adaptation-6.12.patch @@ -0,0 +1,37 @@ +From 0484b5aa022b5e44012cf14ffce6537752742f03 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Mon, 17 Nov 2025 07:44:33 -0700 +Subject: [PATCH 01/13] dkms : v4l2-core makefile adaptation 6.12 + +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/v4l2-core/Makefile | 3 +++ + 6.12.0/drivers/media/v4l2-core/v4l2-dev.c | 1 + + 2 files changed, 4 insertions(+) + +diff --git a/6.12.0/drivers/media/v4l2-core/Makefile b/6.12.0/drivers/media/v4l2-core/Makefile +index 2177b9d..82c235f 100644 +--- a/6.12.0/drivers/media/v4l2-core/Makefile ++++ b/6.12.0/drivers/media/v4l2-core/Makefile +@@ -2,6 +2,9 @@ + # + # Makefile for the V4L2 core + # ++# ipu7-dkms appendix ++CC := ${CC} -I ${M}/../../../include-overrides -I ${M}/../tuners -I ${M}/../dvb-core -I ${M}/../dvb-frontends ++ccflags-y := -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" + + ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + ccflags-y += -I$(srctree)/drivers/media/tuners +diff --git a/6.12.0/drivers/media/v4l2-core/v4l2-dev.c b/6.12.0/drivers/media/v4l2-core/v4l2-dev.c +index 3d7711c..7b3e462 100644 +--- a/6.12.0/drivers/media/v4l2-core/v4l2-dev.c ++++ b/6.12.0/drivers/media/v4l2-core/v4l2-dev.c +@@ -1231,3 +1231,4 @@ MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab , Bill Dirks, + MODULE_DESCRIPTION("Video4Linux2 core driver"); + MODULE_LICENSE("GPL"); + MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +-- +2.25.1 + diff --git a/patches/0002-dkms-disable-tuner-build-in-v4l2-core-6.12.patch b/patches/0002-dkms-disable-tuner-build-in-v4l2-core-6.12.patch new file mode 100644 index 00000000..d72e300f --- /dev/null +++ b/patches/0002-dkms-disable-tuner-build-in-v4l2-core-6.12.patch @@ -0,0 +1,24 @@ +From 2a5b0ab4e4310821d1c2948adf90c8e8cb14ae79 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:14:15 -0700 +Subject: [PATCH 02/13] dkms: disable tuner build in v4l2-core 6.12 + +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/v4l2-core/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/6.12.0/drivers/media/v4l2-core/Makefile b/6.12.0/drivers/media/v4l2-core/Makefile +index 82c235f..e5880ff 100644 +--- a/6.12.0/drivers/media/v4l2-core/Makefile ++++ b/6.12.0/drivers/media/v4l2-core/Makefile +@@ -36,5 +36,5 @@ obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o + obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o + obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o + +-obj-$(CONFIG_VIDEO_TUNER) += tuner.o ++# obj-$(CONFIG_VIDEO_TUNER) += tuner.o + obj-$(CONFIG_VIDEO_DEV) += v4l2-dv-timings.o videodev.o +-- +2.25.1 + diff --git a/patches/0003-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch b/patches/0003-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch new file mode 100644 index 00000000..129c4e03 --- /dev/null +++ b/patches/0003-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch @@ -0,0 +1,28 @@ +From c58afe775ccf6d9e6e53ae05d0ed9fc4d1927154 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Mon, 17 Nov 2025 07:45:23 -0700 +Subject: [PATCH 03/13] drivers: media: set v4l2_subdev_enable_streams_api=true + for WA 6.12 + +Signed-off-by: hepengpx +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/v4l2-core/v4l2-subdev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/6.12.0/drivers/media/v4l2-core/v4l2-subdev.c b/6.12.0/drivers/media/v4l2-core/v4l2-subdev.c +index 3a4ba08..f535929 100644 +--- a/6.12.0/drivers/media/v4l2-core/v4l2-subdev.c ++++ b/6.12.0/drivers/media/v4l2-core/v4l2-subdev.c +@@ -32,7 +32,7 @@ + * 'v4l2_subdev_enable_streams_api' to 1 below. + */ + +-static bool v4l2_subdev_enable_streams_api; ++static bool v4l2_subdev_enable_streams_api = true; + #endif + + /* +-- +2.25.1 + diff --git a/patches/0004-dkms-v4l2-core-makefile-adaptation-6.17.patch b/patches/0004-dkms-v4l2-core-makefile-adaptation-6.17.patch index 9db07e09..0d3ecaae 100644 --- a/patches/0004-dkms-v4l2-core-makefile-adaptation-6.17.patch +++ b/patches/0004-dkms-v4l2-core-makefile-adaptation-6.17.patch @@ -1,7 +1,7 @@ -From 9097a7b8bcb983c7afc812af26eb7d313200f849 Mon Sep 17 00:00:00 2001 +From c23ae9d6a748cf8a44d2a22b16aa09fb1a800a77 Mon Sep 17 00:00:00 2001 From: florent pirou Date: Mon, 17 Nov 2025 07:44:33 -0700 -Subject: [PATCH 4/8] dkms : v4l2-core makefile adaptation 6.17 +Subject: [PATCH 04/13] dkms : v4l2-core makefile adaptation 6.17 Signed-off-by: florent pirou --- @@ -10,7 +10,7 @@ Signed-off-by: florent pirou 2 files changed, 4 insertions(+) diff --git a/6.17.0/drivers/media/v4l2-core/Makefile b/6.17.0/drivers/media/v4l2-core/Makefile -index 2177b9d..82c235f 100644 +index 2177b9d..fc41f58 100644 --- a/6.17.0/drivers/media/v4l2-core/Makefile +++ b/6.17.0/drivers/media/v4l2-core/Makefile @@ -2,6 +2,9 @@ @@ -33,5 +33,5 @@ index c369235..1997370 100644 MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); +MODULE_VERSION(DRIVER_VERSION_SUFFIX); -- -2.43.0 +2.25.1 diff --git a/patches/0009-dkms-disable-tuner-build-in-v4l2-core-6.17.patch b/patches/0005-dkms-disable-tuner-build-in-v4l2-core-6.17.patch similarity index 68% rename from patches/0009-dkms-disable-tuner-build-in-v4l2-core-6.17.patch rename to patches/0005-dkms-disable-tuner-build-in-v4l2-core-6.17.patch index 9259b5d8..4661a62b 100644 --- a/patches/0009-dkms-disable-tuner-build-in-v4l2-core-6.17.patch +++ b/patches/0005-dkms-disable-tuner-build-in-v4l2-core-6.17.patch @@ -1,6 +1,7 @@ -From: Khai Wen, Ng -Date: Wed, 12 Feb 2026 11:08:00 +0800 -Subject: [PATCH] dkms: disable tuner build in v4l2-core 6.17 +From cc7236dc1214cb828be6bcfdb485c4ee2cab7caf Mon Sep 17 00:00:00 2001 +From: "Khai Wen, Ng" +Date: Thu, 12 Feb 2026 11:08:00 +0800 +Subject: [PATCH 05/13] dkms: disable tuner build in v4l2-core 6.17 Disable CONFIG_VIDEO_TUNER build as tuner dependencies (mt20xx.h, etc.) are not available in the DKMS build environment. @@ -11,10 +12,10 @@ Signed-off-by: Khai Wen, Ng 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6.17.0/drivers/media/v4l2-core/Makefile b/6.17.0/drivers/media/v4l2-core/Makefile -index 2177b9d..de5ac11 100644 +index fc41f58..623b065 100644 --- a/6.17.0/drivers/media/v4l2-core/Makefile +++ b/6.17.0/drivers/media/v4l2-core/Makefile -@@ -33,5 +33,5 @@ obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o +@@ -36,5 +36,5 @@ obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o @@ -22,5 +23,5 @@ index 2177b9d..de5ac11 100644 +# obj-$(CONFIG_VIDEO_TUNER) += tuner.o obj-$(CONFIG_VIDEO_DEV) += v4l2-dv-timings.o videodev.o -- -2.43.0 +2.25.1 diff --git a/patches/0005-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch b/patches/0006-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch similarity index 83% rename from patches/0005-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch rename to patches/0006-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch index 9d7099f4..346b188d 100644 --- a/patches/0005-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch +++ b/patches/0006-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch @@ -1,7 +1,7 @@ -From 33750bc14545f84d7c26168b956e960b6427b005 Mon Sep 17 00:00:00 2001 +From 7d6515b8b045280828adc87d3fd7be86f79425b5 Mon Sep 17 00:00:00 2001 From: florent pirou Date: Mon, 17 Nov 2025 07:45:23 -0700 -Subject: [PATCH 5/8] drivers: media: set v4l2_subdev_enable_streams_api=true +Subject: [PATCH 06/13] drivers: media: set v4l2_subdev_enable_streams_api=true for WA 6.17 Signed-off-by: hepengpx @@ -24,5 +24,5 @@ index 4fd25fe..60a0556 100644 /* -- -2.43.0 +2.25.1 diff --git a/patches/0007-dkms-v4l2-core-makefile-adaptation-6.18.patch b/patches/0007-dkms-v4l2-core-makefile-adaptation-6.18.patch new file mode 100644 index 00000000..1283f6b9 --- /dev/null +++ b/patches/0007-dkms-v4l2-core-makefile-adaptation-6.18.patch @@ -0,0 +1,37 @@ +From 06e85f57caf2906718d0b6becfd1756de501494b Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Fri, 16 Jan 2026 03:20:05 -0700 +Subject: [PATCH 07/13] dkms : v4l2-core makefile adaptation 6.18 + +Signed-off-by: Florent Pirou +--- + 6.18.0/drivers/media/v4l2-core/Makefile | 3 +++ + 6.18.0/drivers/media/v4l2-core/v4l2-dev.c | 1 + + 2 files changed, 4 insertions(+) + +diff --git a/6.18.0/drivers/media/v4l2-core/Makefile b/6.18.0/drivers/media/v4l2-core/Makefile +index 2177b9d..82c235f 100644 +--- a/6.18.0/drivers/media/v4l2-core/Makefile ++++ b/6.18.0/drivers/media/v4l2-core/Makefile +@@ -2,6 +2,9 @@ + # + # Makefile for the V4L2 core + # ++# ipu7-dkms appendix ++CC := ${CC} -I ${M}/../../../include-overrides -I ${M}/../tuners -I ${M}/../dvb-core -I ${M}/../dvb-frontends ++ccflags-y := -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" + + ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + ccflags-y += -I$(srctree)/drivers/media/tuners +diff --git a/6.18.0/drivers/media/v4l2-core/v4l2-dev.c b/6.18.0/drivers/media/v4l2-core/v4l2-dev.c +index 10a126e..96cc91d 100644 +--- a/6.18.0/drivers/media/v4l2-core/v4l2-dev.c ++++ b/6.18.0/drivers/media/v4l2-core/v4l2-dev.c +@@ -1256,3 +1256,4 @@ MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab , Bill Dirks, + MODULE_DESCRIPTION("Video4Linux2 core driver"); + MODULE_LICENSE("GPL"); + MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +-- +2.25.1 + diff --git a/patches/0008-dkms-disable-tuner-build-in-v4l2-core-6.18.patch b/patches/0008-dkms-disable-tuner-build-in-v4l2-core-6.18.patch new file mode 100644 index 00000000..f5a3d6e5 --- /dev/null +++ b/patches/0008-dkms-disable-tuner-build-in-v4l2-core-6.18.patch @@ -0,0 +1,24 @@ +From 1f78d510db230acc7b26b4dab2a485a514f1f2ee Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:14:28 -0700 +Subject: [PATCH 08/13] dkms: disable tuner build in v4l2-core 6.18 + +Signed-off-by: florent pirou +--- + 6.18.0/drivers/media/v4l2-core/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/6.18.0/drivers/media/v4l2-core/Makefile b/6.18.0/drivers/media/v4l2-core/Makefile +index 82c235f..e5880ff 100644 +--- a/6.18.0/drivers/media/v4l2-core/Makefile ++++ b/6.18.0/drivers/media/v4l2-core/Makefile +@@ -36,5 +36,5 @@ obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o + obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o + obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o + +-obj-$(CONFIG_VIDEO_TUNER) += tuner.o ++# obj-$(CONFIG_VIDEO_TUNER) += tuner.o + obj-$(CONFIG_VIDEO_DEV) += v4l2-dv-timings.o videodev.o +-- +2.25.1 + diff --git a/patches/0009-Revert-media-v4l2-subdev-Make-struct-v4l2_subdev_str.patch b/patches/0009-Revert-media-v4l2-subdev-Make-struct-v4l2_subdev_str.patch new file mode 100644 index 00000000..85959b94 --- /dev/null +++ b/patches/0009-Revert-media-v4l2-subdev-Make-struct-v4l2_subdev_str.patch @@ -0,0 +1,95 @@ +From 00504987e020729a8a87467487289ce204cb53ba Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Fri, 16 Jan 2026 10:10:16 -0700 +Subject: [PATCH 09/13] Revert "media: v4l2-subdev: Make struct + v4l2_subdev_stream_config private" + +This reverts commit 5195b777552d2e2fa735c6cad75797efa132bd60. + +Signed-off-by: Florent Pirou +--- + 6.18.0/drivers/media/v4l2-core/v4l2-subdev.c | 24 ------------------- + 6.18.0/include/media/v4l2-subdev.h | 25 +++++++++++++++++++- + 2 files changed, 24 insertions(+), 25 deletions(-) + +diff --git a/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c b/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c +index 25e66bf..4f64ac1 100644 +--- a/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c ++++ b/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c +@@ -26,30 +26,6 @@ + #include + #include + +-/** +- * struct v4l2_subdev_stream_config - Used for storing stream configuration. +- * +- * @pad: pad number +- * @stream: stream number +- * @enabled: has the stream been enabled with v4l2_subdev_enable_streams() +- * @fmt: &struct v4l2_mbus_framefmt +- * @crop: &struct v4l2_rect to be used for crop +- * @compose: &struct v4l2_rect to be used for compose +- * @interval: frame interval +- * +- * This structure stores configuration for a stream. +- */ +-struct v4l2_subdev_stream_config { +- u32 pad; +- u32 stream; +- bool enabled; +- +- struct v4l2_mbus_framefmt fmt; +- struct v4l2_rect crop; +- struct v4l2_rect compose; +- struct v4l2_fract interval; +-}; +- + #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) + /* + * The Streams API is an experimental feature. To use the Streams API, set +diff --git a/6.18.0/include/media/v4l2-subdev.h b/6.18.0/include/media/v4l2-subdev.h +index e0bb58c..ad37aaf 100644 +--- a/6.18.0/include/media/v4l2-subdev.h ++++ b/6.18.0/include/media/v4l2-subdev.h +@@ -36,7 +36,6 @@ struct v4l2_event_subscription; + struct v4l2_fh; + struct v4l2_subdev; + struct v4l2_subdev_fh; +-struct v4l2_subdev_stream_config; + struct tuner_setup; + struct v4l2_mbus_frame_desc; + struct led_classdev; +@@ -684,6 +683,30 @@ struct v4l2_subdev_pad_config { + struct v4l2_fract interval; + }; + ++/** ++ * struct v4l2_subdev_stream_config - Used for storing stream configuration. ++ * ++ * @pad: pad number ++ * @stream: stream number ++ * @enabled: has the stream been enabled with v4l2_subdev_enable_streams() ++ * @fmt: &struct v4l2_mbus_framefmt ++ * @crop: &struct v4l2_rect to be used for crop ++ * @compose: &struct v4l2_rect to be used for compose ++ * @interval: frame interval ++ * ++ * This structure stores configuration for a stream. ++ */ ++struct v4l2_subdev_stream_config { ++ u32 pad; ++ u32 stream; ++ bool enabled; ++ ++ struct v4l2_mbus_framefmt fmt; ++ struct v4l2_rect crop; ++ struct v4l2_rect compose; ++ struct v4l2_fract interval; ++}; ++ + /** + * struct v4l2_subdev_stream_configs - A collection of stream configs. + * +-- +2.25.1 + diff --git a/patches/0010-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch b/patches/0010-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch new file mode 100644 index 00000000..fbdb142b --- /dev/null +++ b/patches/0010-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch @@ -0,0 +1,27 @@ +From ce0bdb0a496906c9c4b595bfc6f95173c19c1305 Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Fri, 16 Jan 2026 03:22:26 -0700 +Subject: [PATCH 10/13] drivers: media: set v4l2_subdev_enable_streams_api=true + for WA 6.18 + +Signed-off-by: Florent Pirou +--- + 6.18.0/drivers/media/v4l2-core/v4l2-subdev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c b/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c +index 4f64ac1..b2b7125 100644 +--- a/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c ++++ b/6.18.0/drivers/media/v4l2-core/v4l2-subdev.c +@@ -32,7 +32,7 @@ + * 'v4l2_subdev_enable_streams_api' to 1 below. + */ + +-static bool v4l2_subdev_enable_streams_api; ++static bool v4l2_subdev_enable_streams_api = true; + #endif + + /* +-- +2.25.1 + diff --git a/patches/0011-dkms-v4l2-core-makefile-adaptation-7.0.patch b/patches/0011-dkms-v4l2-core-makefile-adaptation-7.0.patch new file mode 100644 index 00000000..8e02640c --- /dev/null +++ b/patches/0011-dkms-v4l2-core-makefile-adaptation-7.0.patch @@ -0,0 +1,37 @@ +From 2bda71610890498595695874cf79714aac8cabdc Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:09:24 -0700 +Subject: [PATCH 11/13] dkms : v4l2-core makefile adaptation 7.0 + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/v4l2-core/Makefile | 3 +++ + 7.0.0/drivers/media/v4l2-core/v4l2-dev.c | 1 + + 2 files changed, 4 insertions(+) + +diff --git a/7.0.0/drivers/media/v4l2-core/Makefile b/7.0.0/drivers/media/v4l2-core/Makefile +index 329f0ea..e7bcbf4 100644 +--- a/7.0.0/drivers/media/v4l2-core/Makefile ++++ b/7.0.0/drivers/media/v4l2-core/Makefile +@@ -2,6 +2,9 @@ + # + # Makefile for the V4L2 core + # ++# ipu7-dkms appendix ++CC := ${CC} -I ${M}/../../../include-overrides -I ${M}/../tuners -I ${M}/../dvb-core -I ${M}/../dvb-frontends ++ccflags-y := -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" + + ccflags-y += -I$(srctree)/drivers/media/dvb-frontends + ccflags-y += -I$(srctree)/drivers/media/tuners +diff --git a/7.0.0/drivers/media/v4l2-core/v4l2-dev.c b/7.0.0/drivers/media/v4l2-core/v4l2-dev.c +index 6ce623a..6a7394c 100644 +--- a/7.0.0/drivers/media/v4l2-core/v4l2-dev.c ++++ b/7.0.0/drivers/media/v4l2-core/v4l2-dev.c +@@ -1256,3 +1256,4 @@ MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab , Bill Dirks, + MODULE_DESCRIPTION("Video4Linux2 core driver"); + MODULE_LICENSE("GPL"); + MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +-- +2.25.1 + diff --git a/patches/0012-dkms-disable-tuner-build-in-v4l2-core-7.0.patch b/patches/0012-dkms-disable-tuner-build-in-v4l2-core-7.0.patch new file mode 100644 index 00000000..9e0ea4ee --- /dev/null +++ b/patches/0012-dkms-disable-tuner-build-in-v4l2-core-7.0.patch @@ -0,0 +1,24 @@ +From 36f284f942076f6cfce62fe5653e71dfbce60502 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:14:40 -0700 +Subject: [PATCH 12/13] dkms: disable tuner build in v4l2-core 7.0 + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/v4l2-core/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/7.0.0/drivers/media/v4l2-core/Makefile b/7.0.0/drivers/media/v4l2-core/Makefile +index e7bcbf4..6ae8206 100644 +--- a/7.0.0/drivers/media/v4l2-core/Makefile ++++ b/7.0.0/drivers/media/v4l2-core/Makefile +@@ -37,5 +37,5 @@ obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o + obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o + obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o + +-obj-$(CONFIG_VIDEO_TUNER) += tuner.o ++# obj-$(CONFIG_VIDEO_TUNER) += tuner.o + obj-$(CONFIG_VIDEO_DEV) += v4l2-dv-timings.o videodev.o +-- +2.25.1 + diff --git a/patches/0013-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch b/patches/0013-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch new file mode 100644 index 00000000..08e45218 --- /dev/null +++ b/patches/0013-drivers-media-set-v4l2_subdev_enable_streams_api-tru.patch @@ -0,0 +1,27 @@ +From 17c5e0eb30598f4c821c1d645fc9cdf63aec4ac3 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:10:52 -0700 +Subject: [PATCH 13/13] drivers: media: set v4l2_subdev_enable_streams_api=true + for WA 7.0 + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/v4l2-core/v4l2-subdev.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/7.0.0/drivers/media/v4l2-core/v4l2-subdev.c b/7.0.0/drivers/media/v4l2-core/v4l2-subdev.c +index 32e6f60..9cf0075 100644 +--- a/7.0.0/drivers/media/v4l2-core/v4l2-subdev.c ++++ b/7.0.0/drivers/media/v4l2-core/v4l2-subdev.c +@@ -56,7 +56,7 @@ struct v4l2_subdev_stream_config { + * 'v4l2_subdev_enable_streams_api' to 1 below. + */ + +-static bool v4l2_subdev_enable_streams_api; ++static bool v4l2_subdev_enable_streams_api = true; + #endif + + /* +-- +2.25.1 + diff --git a/patches/0040-ipu-bridge-6.12-makefile.patch b/patches/0040-ipu-bridge-6.12-makefile.patch deleted file mode 100644 index 62bca464..00000000 --- a/patches/0040-ipu-bridge-6.12-makefile.patch +++ /dev/null @@ -1,37 +0,0 @@ -From: Yew, Chang Ching -Date: Mon, 9 Mar 2026.17:30:00 +0800 -Subject: [PATCH] ipu-bridge: Add scaling direct MIPI HIDS for v6.12 - -Signed-off-by: Yew, Chang Ching ---- - 6.12.0/drivers/media/pci/intel/Kconfig | 5 - - 6.12.0/drivers/media/pci/intel/Makefile | 5 +- - 3 files changed, 187 insertions(+), 38 deletions(-) -diff --git b/6.12.0/drivers/media/pci/intel/Kconfig a/6.12.0/drivers/media/pci/intel/Kconfig -index d9fcddc..3fa2b72 100644 ---- b/6.12.0/drivers/media/pci/intel/Kconfig -+++ a/6.12.0/drivers/media/pci/intel/Kconfig -@@ -1,9 +1,4 @@ - # SPDX-License-Identifier: GPL-2.0-only -- --source "drivers/media/pci/intel/ipu3/Kconfig" --source "drivers/media/pci/intel/ipu6/Kconfig" --source "drivers/media/pci/intel/ivsc/Kconfig" -- - config IPU_BRIDGE - tristate "Intel IPU Bridge" - depends on ACPI || COMPILE_TEST -diff --git b/6.12.0/drivers/media/pci/intel/Makefile a/6.12.0/drivers/media/pci/intel/Makefile -index 3a2cc65..16dc2d2 100644 ---- b/6.12.0/drivers/media/pci/intel/Makefile -+++ a/6.12.0/drivers/media/pci/intel/Makefile -@@ -2,7 +2,4 @@ - # - # Makefile for the IPU drivers - # --obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o --obj-y += ipu3/ --obj-y += ivsc/ --obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ -+obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o -\ No newline at end of file diff --git a/patches/0040-ipu-bridge-6.17-makefile.patch b/patches/0040-ipu-bridge-6.17-makefile.patch deleted file mode 100644 index 5d152b0d..00000000 --- a/patches/0040-ipu-bridge-6.17-makefile.patch +++ /dev/null @@ -1,38 +0,0 @@ -From: Yew, Chang Ching -Date: Tue, 3 Mar 2026 17:30:00 +0800 -Subject: [PATCH] ipu-bridge: Add scaling direct MIPI HIDS - -Signed-off-by: Yew, Chang Ching ---- - 6.17.0/drivers/media/pci/intel/Kconfig | 5 - - 6.17.0/drivers/media/pci/intel/Makefile | 5 +- - 3 files changed, 173 insertions(+), 10 deletions(-) - -diff --git b/6.17.0/drivers/media/pci/intel/Kconfig a/6.17.0/drivers/media/pci/intel/Kconfig -index d9fcddc..3fa2b72 100644 ---- b/6.17.0/drivers/media/pci/intel/Kconfig -+++ a/6.17.0/drivers/media/pci/intel/Kconfig -@@ -1,9 +1,4 @@ - # SPDX-License-Identifier: GPL-2.0-only -- --source "drivers/media/pci/intel/ipu3/Kconfig" --source "drivers/media/pci/intel/ipu6/Kconfig" --source "drivers/media/pci/intel/ivsc/Kconfig" -- - config IPU_BRIDGE - tristate "Intel IPU Bridge" - depends on ACPI || COMPILE_TEST -diff --git b/6.17.0/drivers/media/pci/intel/Makefile a/6.17.0/drivers/media/pci/intel/Makefile -index 3a2cc65..16dc2d2 100644 ---- b/6.17.0/drivers/media/pci/intel/Makefile -+++ a/6.17.0/drivers/media/pci/intel/Makefile -@@ -2,7 +2,4 @@ - # - # Makefile for the IPU drivers - # --obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o --obj-y += ipu3/ --obj-y += ivsc/ --obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ -+obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o -\ No newline at end of file diff --git a/patches/0042-ipu-bridge-6.12-add-sensor.patch b/patches/0042-ipu-bridge-6.12-add-sensor.patch deleted file mode 100644 index ef4e1b68..00000000 --- a/patches/0042-ipu-bridge-6.12-add-sensor.patch +++ /dev/null @@ -1,19 +0,0 @@ - 6.12.0/drivers/media/pci/intel/ipu-bridge.c | 6 ++++++ - 1 file changed, 6 insertions(+) -diff --git a/6.12.0/drivers/media/pci/intel/ipu-bridge.c b/6.12.0/drivers/media/pci/intel/ipu-bridge.c -index 4e4f86a..e680967 100644 ---- a/6.12.0/drivers/media/pci/intel/ipu-bridge.c -+++ b/6.12.0/drivers/media/pci/intel/ipu-bridge.c -@@ -91,6 +91,12 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { - IPU_SENSOR_CONFIG("INTC10C5", 0), - /* Lontium lt6911uxc */ - IPU_SENSOR_CONFIG("INTC10B1", 0), -+ /* D3 Embedded ISX031 */ -+ IPU_SENSOR_CONFIG("INTC113C", 1, 300000000), -+ /* LI AR0830 */ -+ IPU_SENSOR_CONFIG("LIAR0830", 1, 600000000), -+ /* Innodisk AR0822 */ -+ IPU_SENSOR_CONFIG("EV8MOOM1", 1, 600000000), - }; - - static const struct ipu_sensor_config ipu_supported_sensors_dummy[] = { diff --git a/patches/0050-isys-video-let-v4l2-set-default-colorspace.patch b/patches/0050-isys-video-let-v4l2-set-default-colorspace.patch new file mode 100644 index 00000000..04b532a5 --- /dev/null +++ b/patches/0050-isys-video-let-v4l2-set-default-colorspace.patch @@ -0,0 +1,25 @@ +From 4b109df879f8b2a99b0afe7de5a9d55f11e7b813 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Sat, 14 Feb 2026 14:04:59 -0700 +Subject: [PATCH 50/57] isys-video: let v4l2 set default colorspace + +Signed-off-by: florent pirou +--- + ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +index 927d92e..7967db1 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -288,7 +288,6 @@ static void __ipu6_isys_vidioc_try_fmt_vid_cap(struct ipu6_isys_video *av, + &f->fmt.pix.bytesperline, &f->fmt.pix.sizeimage); + + f->fmt.pix.field = V4L2_FIELD_NONE; +- f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; +-- +2.25.1 + diff --git a/patches/0051-ipu6-dkms-add-isys-makefile-adaptation-6.12.patch b/patches/0051-ipu6-dkms-add-isys-makefile-adaptation-6.12.patch new file mode 100644 index 00000000..17574e9e --- /dev/null +++ b/patches/0051-ipu6-dkms-add-isys-makefile-adaptation-6.12.patch @@ -0,0 +1,73 @@ +From d08c02ae48a7e101a083c6c2a88ce4e33cda75cf Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Mon, 12 Jan 2026 03:45:03 -0700 +Subject: [PATCH 51/57] ipu6-dkms: add isys makefile adaptation 6.12 + +*ipu6-isys: compile ISYS RESET and ACPI PDATA support + +Signed-off-by: Florent Pirou +--- + ipu6-drivers/drivers/media/pci/intel/ipu6/Makefile | 13 +++++++++++++ + .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 1 + + ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c | 1 + + .../drivers/media/pci/intel/ipu6/psys/ipu-psys.c | 1 + + 4 files changed, 16 insertions(+) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/Makefile b/ipu6-drivers/drivers/media/pci/intel/ipu6/Makefile +index 67c13c9..5ac7944 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/Makefile ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/Makefile +@@ -1,5 +1,16 @@ + # SPDX-License-Identifier: GPL-2.0-only + ++KERNEL_VERSION := $(shell echo $(KERNELRELEASE) | sed 's/[^0-9.]*\([0-9]\.[0-9]*\)\..*/\1.0/') ++ ++CC := ${CC} -I ${M}/../../../../../../include -I ${M}/../../../../../../$(KERNEL_VERSION)/include-overrides -DCONFIG_DEBUG_FS -DCONFIG_INTEL_IPU_ACPI -DCONFIG_INTEL_IPU6_ACPI -DCONFIG_VIDEO_INTEL_IPU6_ISYS_RESET -DCONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ ++KBUILD_EXTRA_SYMBOLS += ${M}/../../../../../../Module.symvers ++ ++ccflags-y := -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" ++ ++export EXTERNAL_BUILD = 1 ++export CONFIG_VIDEO_INTEL_IPU6 = m ++ + intel-ipu6-y := ipu6.o \ + ipu6-bus.o \ + ipu6-dma.o \ +@@ -22,4 +33,6 @@ intel-ipu6-isys-y := ipu6-isys.o \ + ipu6-isys-dwc-phy.o + + obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o ++ ++subdir-ccflags-y += -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" + obj-$(CONFIG_VIDEO_INTEL_IPU6) += psys/ +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +index 415ff06..eb66918 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +@@ -1774,3 +1774,4 @@ MODULE_LICENSE("GPL"); + MODULE_DESCRIPTION("Intel IPU6 input system driver"); + MODULE_IMPORT_NS(INTEL_IPU6); + MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c +index 738232e..67894a3 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c +@@ -963,3 +963,4 @@ MODULE_AUTHOR("Yunliang Ding "); + MODULE_AUTHOR("Hongju Wang "); + MODULE_LICENSE("GPL"); + MODULE_DESCRIPTION("Intel IPU6 PCI driver"); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/psys/ipu-psys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/psys/ipu-psys.c +index 6423238..66c1c28 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/psys/ipu-psys.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/psys/ipu-psys.c +@@ -2749,3 +2749,4 @@ MODULE_IMPORT_NS(INTEL_IPU6); + MODULE_IMPORT_NS("DMA_BUF"); + MODULE_IMPORT_NS("INTEL_IPU6"); + #endif ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +-- +2.25.1 + diff --git a/patches/0053-ipu6-isys-disable-src_pad-validation-for-dkms.patch b/patches/0052-ipu6-isys-disable-src_pad-validation-for-dkms.patch similarity index 76% rename from patches/0053-ipu6-isys-disable-src_pad-validation-for-dkms.patch rename to patches/0052-ipu6-isys-disable-src_pad-validation-for-dkms.patch index 21fd7677..1ee7d013 100644 --- a/patches/0053-ipu6-isys-disable-src_pad-validation-for-dkms.patch +++ b/patches/0052-ipu6-isys-disable-src_pad-validation-for-dkms.patch @@ -1,15 +1,18 @@ -Subject: [PATCH] ipu6: isys: disable src_pad validation for dkms +From f5f8e9ca3695645166c5eebe434cd5162079194d Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 08:26:40 -0700 +Subject: [PATCH 52/57] ipu6: isys: disable src_pad validation for dkms src_pad field is added by VTG to support UXC use case in BKC kernel. Hence, this patch is only applicable to BKC kernel header file. -Signed-off-by: Khai Wen, Ng +Signed-off-by: florent pirou --- - .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 6 +++--- + ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c -index 415ff067e..f4bc8ca12 100644 +index eb66918..241bdeb 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c @@ -175,7 +175,7 @@ isys_complete_ext_device_registration(struct ipu6_isys *isys, @@ -40,4 +43,5 @@ index 415ff067e..f4bc8ca12 100644 if (ret) return ret; -- -2.43.0 +2.25.1 + diff --git a/patches/0053-Revert-ipu6-isys-lt6911uxc-2-pads-linked-to-ipu-2-po.patch b/patches/0053-Revert-ipu6-isys-lt6911uxc-2-pads-linked-to-ipu-2-po.patch new file mode 100644 index 00000000..719c5019 --- /dev/null +++ b/patches/0053-Revert-ipu6-isys-lt6911uxc-2-pads-linked-to-ipu-2-po.patch @@ -0,0 +1,80 @@ +From 88644a7b7a16a3dbb87cd41918bc566c6929ff1f Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Mon, 19 Jan 2026 09:02:23 -0700 +Subject: [PATCH 53/57] Revert "ipu6-isys: lt6911uxc 2 pads linked to ipu 2 + ports for split mode" + +This reverts commit b1c889b6240723d849da32f615ed8945d08d4e21. + +Change Description: +single camera in bios setting, pprval for dummy port. +create software node and link connection in ipu-brige. +match the connection and port num in v4l2-async. +finally create the media links. + +Signed-off-by: Chen Meng J +Signed-off-by: Florent Pirou +--- + .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +index 241bdeb..d0db7ea 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +@@ -158,7 +158,9 @@ static int isys_isr_one(struct ipu6_bus_device *adev); + static int + isys_complete_ext_device_registration(struct ipu6_isys *isys, + struct v4l2_subdev *sd, ++#if 0 + s16 src_pad, ++#endif + struct ipu6_isys_csi2_config *csi2) + { + struct device *dev = &isys->adev->auxdev.dev; +@@ -175,6 +177,7 @@ isys_complete_ext_device_registration(struct ipu6_isys *isys, + ret = -ENOENT; + goto unregister_subdev; + } ++ + #if 0 + if (src_pad >= 0) + i = (unsigned int)src_pad; +@@ -187,6 +190,7 @@ isys_complete_ext_device_registration(struct ipu6_isys *isys, + goto unregister_subdev; + } + #endif ++ + ret = media_create_pad_link(&sd->entity, i, + &isys->csi2[csi2->port].asd.sd.entity, + 0, MEDIA_LNK_FL_ENABLED | +@@ -273,8 +277,12 @@ static int isys_register_ext_subdev(struct ipu6_isys *isys, + if (!sd_info->csi2) + return 0; + ++#if 0 + /* src_pad is useless for non MIPI split case. Set it to '-1'.*/ + return isys_complete_ext_device_registration(isys, sd, -1, sd_info->csi2); ++#else ++ return isys_complete_ext_device_registration(isys, sd, sd_info->csi2); ++#endif + + skip_put_adapter: + i2c_put_adapter(adapter); +@@ -1008,9 +1016,13 @@ static int isys_notifier_bound(struct v4l2_async_notifier *notifier, + + dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n", + sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); ++#if 0 + ret = isys_complete_ext_device_registration(isys, sd, + -1, + &s_asd->csi2); ++#else ++ ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); ++#endif + if (ret) + return ret; + +-- +2.25.1 + diff --git a/patches/0052-module-version-conditional-symbol-namespace.patch b/patches/0054-module-Add-version-conditional-for-symbol-namespace-.patch similarity index 74% rename from patches/0052-module-version-conditional-symbol-namespace.patch rename to patches/0054-module-Add-version-conditional-for-symbol-namespace-.patch index 9f75c98d..36201dbf 100644 --- a/patches/0052-module-version-conditional-symbol-namespace.patch +++ b/patches/0054-module-Add-version-conditional-for-symbol-namespace-.patch @@ -1,46 +1,34 @@ -From e82bcd12246eed133513993db7422afb9cce6a7d Mon Sep 17 00:00:00 2001 -From: Peter Zijlstra -Date: Mon, 19 Jan 2026 11:41:13 -0700 -Subject: [PATCH 52/52] module: Add version conditional for symbol namespace +From 26c29a1e7f35cd8c735b68a6b48e96a0b856fd4a Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:31:44 -0700 +Subject: [PATCH 54/57] module: Add version conditional for symbol namespace syntax Handle the namespace syntax change between kernel 6.12 and 6.17. In 6.17+, EXPORT_SYMBOL_NS_GPL requires quoted namespace strings. +Signed-off-by: florent pirou --- - .../media/pci/intel/ipu6/ipu6-buttress.c | 70 ++++++++++++++++--- - .../drivers/media/pci/intel/ipu6/ipu6-cpd.c | 12 +++- - .../drivers/media/pci/intel/ipu6/ipu6-dma.c | 54 ++++++++++++--- - .../media/pci/intel/ipu6/ipu6-fw-com.c | 55 ++++++++++++--- - .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 6 +- - .../drivers/media/pci/intel/ipu6/ipu6-mmu.c | 12 +++- - .../drivers/media/pci/intel/ipu6/ipu6.c | 8 ++- + .../media/pci/intel/ipu6/ipu6-buttress.c | 28 ++++++++++++++ + .../drivers/media/pci/intel/ipu6/ipu6-cpd.c | 8 ++++ + .../drivers/media/pci/intel/ipu6/ipu6-dma.c | 36 ++++++++++++++++++ + .../media/pci/intel/ipu6/ipu6-fw-com.c | 37 +++++++++++++++++++ + .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 5 +++ + .../drivers/media/pci/intel/ipu6/ipu6-mmu.c | 8 ++++ + .../drivers/media/pci/intel/ipu6/ipu6.c | 8 ++++ .../drivers/media/pci/intel/ipu6/ipu6.h | 1 + - 8 files changed, 180 insertions(+), 38 deletions(-) + 8 files changed, 131 insertions(+) -diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h -index a1b2e3c..b2c3d4e 100644 ---- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h -+++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h -@@ -7,6 +7,7 @@ - #include - #include - #include -+#include - - #include "ipu6-buttress.h" - diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-buttress.c -index fc18cc6..dc5dbe5 100644 +index fc18cc6..2b89dd9 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-buttress.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -508,7 +508,11 @@ bool ipu6_buttress_auth_done(struct ipu6_device *isp) return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, "INTEL_IPU6"); +#endif @@ -51,9 +39,8 @@ index fc18cc6..dc5dbe5 100644 return ret; } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, "INTEL_IPU6"); +#endif @@ -64,9 +51,8 @@ index fc18cc6..dc5dbe5 100644 dma_unmap_sgtable(&pdev->dev, sgt, DMA_TO_DEVICE, 0); sg_free_table(sgt); } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, "INTEL_IPU6"); +#endif @@ -77,9 +63,8 @@ index fc18cc6..dc5dbe5 100644 return -ETIMEDOUT; } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, "INTEL_IPU6"); +#endif @@ -90,9 +75,8 @@ index fc18cc6..dc5dbe5 100644 WARN_ON_ONCE(1); return -1; } --EXPORT_SYMBOL_NS_GPL(ipu6_get_i2c_bus_id, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_get_i2c_bus_id, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_get_i2c_bus_id, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_get_i2c_bus_id, "INTEL_IPU6"); +#endif @@ -103,9 +87,8 @@ index fc18cc6..dc5dbe5 100644 } local_irq_restore(flags); } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, "INTEL_IPU6"); +#endif @@ -116,9 +99,8 @@ index fc18cc6..dc5dbe5 100644 */ return div_u64(ns, isp->buttress.ref_clk); } --EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, "INTEL_IPU6"); +#endif @@ -126,16 +108,15 @@ index fc18cc6..dc5dbe5 100644 void ipu6_buttress_restore(struct ipu6_device *isp) { diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-cpd.c -index 21c1c12..fd81dc2 100644 +index 21c1c12..ef5ea74 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-cpd.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-cpd.c @@ -216,14 +216,22 @@ int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src) return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, "INTEL_IPU6"); +#endif @@ -145,9 +126,8 @@ index 21c1c12..fd81dc2 100644 ipu6_dma_free(adev, adev->pkg_dir_size, adev->pkg_dir, adev->pkg_dir_dma_addr, 0); } --EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, "INTEL_IPU6"); +#endif @@ -155,16 +135,15 @@ index 21c1c12..fd81dc2 100644 static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd, unsigned long cpd_size, diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-dma.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-dma.c -index b71f66b..aa3e1f7 100644 +index b71f66b..8c8575b 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-dma.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-dma.c @@ -130,7 +130,11 @@ void ipu6_dma_sync_single(struct ipu6_bus_device *sys, dma_addr_t dma_handle, vaddr = info->vaddr + offset; clflush_cache_range(vaddr, size); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_single, "INTEL_IPU6"); +#endif @@ -175,9 +154,8 @@ index b71f66b..aa3e1f7 100644 for_each_sg(sglist, sg, nents, i) clflush_cache_range(page_to_virt(sg_page(sg)), sg->length); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sg, "INTEL_IPU6"); +#endif @@ -186,9 +164,8 @@ index b71f66b..aa3e1f7 100644 { ipu6_dma_sync_sg(sys, sgt->sgl, sgt->orig_nents); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_sync_sgtable, "INTEL_IPU6"); +#endif @@ -199,9 +176,8 @@ index b71f66b..aa3e1f7 100644 return NULL; } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_alloc, "INTEL_IPU6"); +#endif @@ -212,9 +188,8 @@ index b71f66b..aa3e1f7 100644 kfree(info); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_free, "INTEL_IPU6"); +#endif @@ -225,9 +200,8 @@ index b71f66b..aa3e1f7 100644 mmu->tlb_invalidate(mmu); __free_iova(&mmu->dmap->iovad, iova); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sg, "INTEL_IPU6"); +#endif @@ -238,9 +212,8 @@ index b71f66b..aa3e1f7 100644 return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sg, "INTEL_IPU6"); +#endif @@ -251,9 +224,8 @@ index b71f66b..aa3e1f7 100644 return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_map_sgtable, "INTEL_IPU6"); +#endif @@ -263,9 +235,8 @@ index b71f66b..aa3e1f7 100644 { ipu6_dma_unmap_sg(sys, sgt->sgl, sgt->nents, dir, attrs); } --EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_dma_unmap_sgtable, "INTEL_IPU6"); +#endif @@ -273,7 +244,7 @@ index b71f66b..aa3e1f7 100644 /* * Create scatter-list for the already allocated DMA buffer diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-com.c -index 45738f3..7a9fcd9 100644 +index 45738f3..947b281 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-com.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-com.c @@ -10,6 +10,7 @@ @@ -288,9 +259,8 @@ index 45738f3..7a9fcd9 100644 return ctx; } --EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, "INTEL_IPU6"); +#endif @@ -301,9 +271,8 @@ index 45738f3..7a9fcd9 100644 return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, "INTEL_IPU6"); +#endif @@ -314,9 +283,8 @@ index 45738f3..7a9fcd9 100644 return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, "INTEL_IPU6"); +#endif @@ -327,9 +295,8 @@ index 45738f3..7a9fcd9 100644 kfree(ctx); return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, "INTEL_IPU6"); +#endif @@ -340,9 +307,8 @@ index 45738f3..7a9fcd9 100644 return state == SYSCOM_STATE_READY; } --EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, "INTEL_IPU6"); +#endif @@ -353,9 +319,8 @@ index 45738f3..7a9fcd9 100644 return (void *)(q->host_address + index * q->token_size); } --EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, "INTEL_IPU6"); +#endif @@ -366,9 +331,8 @@ index 45738f3..7a9fcd9 100644 writel(wr, q_dmem + FW_COM_WR_REG); } --EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, "INTEL_IPU6"); +#endif @@ -379,9 +343,8 @@ index 45738f3..7a9fcd9 100644 return (void *)(q->host_address + rd * q->token_size); } --EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, "INTEL_IPU6"); +#endif @@ -392,18 +355,16 @@ index 45738f3..7a9fcd9 100644 writel(rd, q_dmem + FW_COM_RD_REG); } --EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, "INTEL_IPU6"); +#endif diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c -index 412afd0..79780d3 100644 +index d0db7ea..526c916 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c -@@ -1773,6 +1773,11 @@ MODULE_AUTHOR("Bingbu Cao "); - MODULE_AUTHOR("Yunliang Ding "); +@@ -1784,6 +1784,11 @@ MODULE_AUTHOR("Yunliang Ding "); MODULE_AUTHOR("Hongju Wang "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Intel IPU6 input system driver"); @@ -414,17 +375,17 @@ index 412afd0..79780d3 100644 +MODULE_IMPORT_NS("INTEL_IPU6"); +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +#endif + MODULE_VERSION(DRIVER_VERSION_SUFFIX); diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-mmu.c -index 57298ac..dc4b4e0 100644 +index 57298ac..ea73dd5 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-mmu.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-mmu.c @@ -518,7 +518,11 @@ int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) return 0; } --EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, "INTEL_IPU6"); +#endif @@ -435,9 +396,8 @@ index 57298ac..dc4b4e0 100644 mmu->ready = false; spin_unlock_irqrestore(&mmu->ready_lock, flags); } --EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, "INTEL_IPU6"); +#endif @@ -445,16 +405,15 @@ index 57298ac..dc4b4e0 100644 static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) { diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c -index 67894a3..6341775 100644 +index 67894a3..42646bc 100644 --- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c +++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.c @@ -302,7 +302,11 @@ void ipu6_configure_spc(struct ipu6_device *isp, ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base, pkg_dir, pkg_dir_dma_addr); } --EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6); + EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6); +#else +EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, "INTEL_IPU6"); +#endif @@ -465,14 +424,26 @@ index 67894a3..6341775 100644 module_pci_driver(ipu6_pci_driver); --MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0) -+MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); + MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); +#else +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +#endif MODULE_AUTHOR("Sakari Ailus "); MODULE_AUTHOR("Tianshu Qiu "); MODULE_AUTHOR("Bingbu Cao "); +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h +index baad7f8..b76ab9d 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6.h +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + + #include "ipu6-buttress.h" + -- -2.43.0 +2.25.1 + diff --git a/patches/0055-ipu6-isys-allow-virtual-channels-on-16-csi2-input-vi.patch b/patches/0055-ipu6-isys-allow-virtual-channels-on-16-csi2-input-vi.patch new file mode 100644 index 00000000..718e3ed6 --- /dev/null +++ b/patches/0055-ipu6-isys-allow-virtual-channels-on-16-csi2-input-vi.patch @@ -0,0 +1,27 @@ +From 437c44be3c27089cb0dcca81d31a97c1011165ec Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Fri, 23 Jan 2026 16:55:59 -0700 +Subject: [PATCH 55/57] ipu6-isys : allow virtual-channels on 16 csi2 input + video capture + +Signed-off-by: florent pirou +--- + ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +index ea609dd..3b4328b 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +@@ -21,7 +21,7 @@ struct ipu6_isys_stream; + #define INVALID_VC_ID -1 + #define NR_OF_CSI2_SINK_PADS 1 + #define CSI2_PAD_SINK 0 +-#define NR_OF_CSI2_SRC_PADS 8 ++#define NR_OF_CSI2_SRC_PADS 16 + #define CSI2_PAD_SRC 1 + #define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS) + +-- +2.25.1 + diff --git a/patches/0056-ipu6-isys-add-d4xx-pixel-format-support.patch b/patches/0056-ipu6-isys-add-d4xx-pixel-format-support.patch new file mode 100644 index 00000000..860003fc --- /dev/null +++ b/patches/0056-ipu6-isys-add-d4xx-pixel-format-support.patch @@ -0,0 +1,144 @@ +From 0159600f2d533dba3a3b3b024cabd2973df91514 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:50:59 -0700 +Subject: [PATCH 56/57] ipu6-isys : add d4xx pixel format support + +Signed-off-by: florent pirou +--- + .../drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 1 + + .../drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 4 ++++ + .../drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c | 6 ++++++ + .../drivers/media/pci/intel/ipu6/ipu6-isys-video.c | 11 +++++++++++ + ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.h | 4 ++-- + 5 files changed, 24 insertions(+), 2 deletions(-) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +index 545180c..0ec08ee 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +@@ -209,6 +209,7 @@ enum ipu6_fw_isys_frame_format_type { + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */ + IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */ + IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */ ++ IPU6_FW_ISYS_FRAME_FORMAT_VYUY, /* 16 bit YUV 422, VYUY interleaved */ + N_IPU6_FW_ISYS_FRAME_FORMAT + }; + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +index 5f2f836..27a5368 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +@@ -26,10 +26,13 @@ + #include "ipu6-platform-isys-csi2-reg.h" + + static const u32 csi2_supported_codes[] = { ++ MEDIA_BUS_FMT_Y8_1X8, ++ MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_RGB565_1X16, + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_YUYV8_1X16, ++ MEDIA_BUS_FMT_VYUY8_1X16, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, +@@ -47,6 +50,7 @@ static const u32 csi2_supported_codes[] = { + MEDIA_BUS_FMT_META_12, + MEDIA_BUS_FMT_META_16, + MEDIA_BUS_FMT_META_24, ++ MEDIA_BUS_FMT_FIXED, + 0 + }; + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +index 0a06de5..76c7e43 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +@@ -26,6 +26,8 @@ unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_META_16: ++ case MEDIA_BUS_FMT_Y10_1X10: ++ case MEDIA_BUS_FMT_VYUY8_1X16: + return 16; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: +@@ -44,6 +46,7 @@ unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_META_8: ++ case MEDIA_BUS_FMT_Y8_1X8: + return 8; + default: + WARN_ON(1); +@@ -60,6 +63,8 @@ unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) + return MIPI_CSI2_DT_RGB888; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: ++ case MEDIA_BUS_FMT_Y10_1X10: ++ case MEDIA_BUS_FMT_VYUY8_1X16: + return MIPI_CSI2_DT_YUV422_8B; + case MEDIA_BUS_FMT_SBGGR16_1X16: + case MEDIA_BUS_FMT_SGBRG16_1X16: +@@ -80,6 +85,7 @@ unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: ++ case MEDIA_BUS_FMT_Y8_1X8: + return MIPI_CSI2_DT_RAW8; + default: + /* return unavailable MIPI data type - 0x3f */ +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +index 7967db1..1ac771a 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -40,6 +40,14 @@ + #include "ipu6-platform-regs.h" + + const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { ++ { V4L2_PIX_FMT_GREY, 8, 8, MEDIA_BUS_FMT_Y8_1X8, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8 }, ++ { V4L2_PIX_FMT_Y10, 16, 10, MEDIA_BUS_FMT_Y10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, ++ { V4L2_PIX_FMT_Y8I, 16, 16, MEDIA_BUS_FMT_VYUY8_1X16, ++ IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, ++ { V4L2_PIX_FMT_Z16, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, ++ IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, + { V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16 }, + { V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, +@@ -86,6 +94,8 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { + IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, + { V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_RGB565 }, ++ { V4L2_PIX_FMT_Y12I, 32, 24, MEDIA_BUS_FMT_RGB888_1X24, ++ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888 }, + { V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888 }, + { V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, +@@ -96,6 +106,7 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { + IPU6_FW_ISYS_FRAME_FORMAT_RAW12, true }, + { V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16, true }, ++ { V4L2_META_FMT_D4XX, 8, 8, MEDIA_BUS_FMT_FIXED, 0 }, + }; + + static int video_open(struct file *file) +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.h +index 4c7ba6e..6a425c8 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.h +@@ -43,8 +43,8 @@ struct ipu6_bus_device; + #define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5 + #define IPU6_ISYS_NUM_RECV_QUEUE 1 + +-#define IPU6_ISYS_MIN_WIDTH 2U +-#define IPU6_ISYS_MIN_HEIGHT 2U ++#define IPU6_ISYS_MIN_WIDTH 1U ++#define IPU6_ISYS_MIN_HEIGHT 1U + #define IPU6_ISYS_MAX_WIDTH 4672U + #define IPU6_ISYS_MAX_HEIGHT 3416U + +-- +2.25.1 + diff --git a/patches/0057-ipu6-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch b/patches/0057-ipu6-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch new file mode 100644 index 00000000..cede6f1b --- /dev/null +++ b/patches/0057-ipu6-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch @@ -0,0 +1,1206 @@ +From 4a2c1f24ff44495435f5a858d5ba09bccce26a0e Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Fri, 23 Jan 2026 18:42:59 -0700 +Subject: [PATCH 57/58] ipu6-isys: add video VIDIOC_S_EXT_CTRLS callbacks by + streamid + +* isys-video: add video VIDIOC_S_EXT_CTRLS callbacks by streamid +* isys-csi: add graph walk subdev to get frame desc by streamid +* isys-video: add V4L2_CID_IPU definitions + +Signed-off-by: florent pirou +--- + .../media/pci/intel/ipu6/ipu6-isys-csi2.c | 314 ++++++++- + .../media/pci/intel/ipu6/ipu6-isys-csi2.h | 8 +- + .../pci/intel/ipu6/ipu6-isys-video-ext.h | 52 ++ + .../media/pci/intel/ipu6/ipu6-isys-video.c | 622 +++++++++++++++++- + .../media/pci/intel/ipu6/ipu6-isys-video.h | 4 + + 5 files changed, 992 insertions(+), 8 deletions(-) + create mode 100644 ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video-ext.h + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +index 27a5368..f4d024e 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +@@ -84,24 +84,36 @@ static const struct ipu6_csi2_error dphy_rx_errors[] = { + s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2) + { + struct media_pad *src_pad; ++ struct v4l2_subdev_state *s_state; + struct v4l2_subdev *ext_sd; + struct device *dev; ++ int ret = -EPIPE; + + if (!csi2) + return -EINVAL; + ++ s_state = v4l2_subdev_get_unlocked_active_state(&csi2->asd.sd); ++ if (!s_state) ++ return ret; ++ + dev = &csi2->isys->adev->auxdev.dev; +- src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); ++ src_pad = media_pad_remote_pad_first(&csi2->asd.sd.entity.pads[CSI2_PAD_SINK]); + if (IS_ERR(src_pad)) { +- dev_err(dev, "can't get source pad of %s (%ld)\n", +- csi2->asd.sd.name, PTR_ERR(src_pad)); +- return PTR_ERR(src_pad); ++ dev_err(dev, ++ "can't get source pad of %s\n", ++ csi2->asd.sd.name); ++ return -EINVAL; + } + + ext_sd = media_entity_to_v4l2_subdev(src_pad->entity); + if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name)) + return -ENODEV; + ++ dev_dbg(dev, ++ "%s:%u -> %s:%u\n", ++ csi2->asd.sd.name, CSI2_PAD_SINK, ++ ext_sd->name, src_pad->index); ++ + return v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0); + } + +@@ -349,6 +361,146 @@ static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, + return ret; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++static int ipu6_isys_csi2_link_validate(struct media_link *link) ++{ ++ ++ struct v4l2_subdev *sd = ++ media_entity_to_v4l2_subdev(link->sink->entity); ++ struct device *dev = sd->entity.graph_obj.mdev->dev; ++ struct ipu6_isys_subdev *asd; ++ struct ipu6_isys_csi2 *csi2; ++ struct v4l2_subdev_state *s_state; ++ struct v4l2_mbus_framefmt *s_fmt; ++ struct v4l2_subdev *s_sd; ++ struct media_pad *s_pad; ++ u32 s_stream, code; ++ int ret = -EPIPE; ++ ++ if (!link->source->entity) ++ return ret; ++ ++ asd = to_ipu6_isys_subdev(sd); ++ csi2 = to_ipu6_isys_csi2(asd); ++ dev = &asd->isys->adev->auxdev.dev; ++ ++ s_sd = media_entity_to_v4l2_subdev(link->source->entity); ++ s_state = v4l2_subdev_get_unlocked_active_state(s_sd); ++ if (!s_state) ++ return ret; ++ ++ dev_dbg(dev, "%s: validating link \"%s\":%u -> \"%s\"\n",__func__, ++ link->source->entity->name, link->source->index, ++ link->sink->entity->name); ++ ++ return 0; ++} ++ ++static int ipu6_isys_csi2_streams_by_sources(struct ipu6_isys_csi2 *csi2, ++ u32 pad, u32 stream, bool on) ++{ ++ struct media_entity *entity = &csi2->asd.sd.entity; ++ struct media_device *mdev = entity->graph_obj.mdev; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_subdev *sd = NULL; ++ struct v4l2_subdev_state *v4l2_state; ++ struct media_graph graph; ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct v4l2_mbus_frame_desc desc; ++ unsigned int i; ++ int ret; ++ ++ if (!mdev) ++ return -EPIPE; ++ ++ mutex_lock(&mdev->graph_mutex); ++ if (media_graph_walk_init(&graph, mdev)) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s: walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_VID_MUX) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->get_frame_desc) ++ continue; ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ ret = sd->ops->pad->get_frame_desc(sd, pad, &desc); ++ if (ret) ++ continue; ++ ++ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) ++ continue; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (stream == desc.entry[i].stream) { ++ desc_entry = &desc.entry[i]; ++ break; ++ } ++ } ++ ++ if (!desc_entry) ++ continue; ++ ++ if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) ++ continue; ++ ++ ++ dev_dbg(dev, "%s: found %s subdev stream:%u vc/dt:%u/0x%x\n", __func__, ++ sd->name, ++ desc_entry->stream, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->disable_streams || !sd->ops->pad->enable_streams) ++ break; ++ ++ v4l2_state = v4l2_subdev_get_unlocked_active_state(sd); ++ if (!v4l2_state) ++ break; ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (on) ++ ret = sd->ops->pad->enable_streams(sd, v4l2_state, pad, BIT_ULL(stream)); ++ else ++ ret = sd->ops->pad->disable_streams(sd, v4l2_state, pad, BIT_ULL(stream)); ++ if (ret) ++ break; ++ ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++ return 0; ++ } ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++ ++error_graph_walk_source: ++ return -EINVAL; ++} ++#endif ++ + /* + * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set + * of streams. +@@ -388,6 +540,14 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, + if (ret) + return ret; + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ if (!ipu6_isys_csi2_streams_by_sources(csi2, sink_pad, sink_stream, true)) { ++ csi2->stream_count++; ++ return 0; ++ } ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "remote stream on fallback\n"); ++#endif + r_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(r_pad->entity); + +@@ -425,6 +585,15 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, + if (ret) + return ret; + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ if (!ipu6_isys_csi2_streams_by_sources(csi2, sink_pad, sink_stream, false)) { ++ if (--csi2->stream_count) ++ return 0; ++ goto ret; ++ } ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "remote stream off fallback\n"); ++#endif + r_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(r_pad->entity); + +@@ -432,7 +601,7 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, + + if (--csi2->stream_count) + return 0; +- ++ret: + dev_dbg(&csi2->isys->adev->auxdev.dev, + "stream off CSI2-%u with %u lanes\n", csi2->port, csi2->nlanes); + +@@ -548,7 +717,11 @@ static const struct v4l2_subdev_ops csi2_sd_ops = { + }; + + static const struct media_entity_operations csi2_entity_ops = { ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ .link_validate = ipu6_isys_csi2_link_validate, ++#else + .link_validate = v4l2_subdev_link_validate, ++#endif + .has_pad_interdep = v4l2_subdev_has_pad_interdep, + }; + +@@ -698,3 +871,134 @@ int ipu6_isys_csi2_get_remote_desc(u32 source_stream, + #endif + return 0; + } ++ ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++int ipu6_isys_csi2_walk_remote_desc(u32 source_stream, ++ struct ipu6_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues) ++{ ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_mbus_frame_desc desc; ++ struct v4l2_subdev *source; ++ struct media_pad *pad; ++ struct media_graph graph; ++ struct media_entity *entity; ++ struct media_device *mdev; ++ struct v4l2_subdev *sd; ++ unsigned int pad_id; ++ u32 stream_id = source_stream; ++ unsigned int i; ++ int ret; ++ ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ csi2->is_multiple = false; ++#endif ++ ++ source = media_entity_to_v4l2_subdev(source_entity); ++ if (!source) ++ return -EPIPE; ++ ++ pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]); ++ if (!pad) ++ return -EPIPE; ++ ++ dev_dbg(dev, "%s: source pad/stream:%u/%u\n", __func__,pad->index, source_stream); ++ pad_id = pad->index; ++ entity = source_entity; ++ mdev = entity->graph_obj.mdev; ++ if (!mdev) { ++ ret = -EPIPE; ++ goto error_graph_walk_source; ++ } ++ ++ mutex_lock(&mdev->graph_mutex); ++ ret = media_graph_walk_init(&graph, mdev); ++ if (ret) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s: walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_CAM_SENSOR) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->get_frame_desc) ++ continue; ++ ++ dev_dbg(dev, "%s(): %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ pad_id, ++ source_stream); ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ ret = sd->ops->pad->get_frame_desc(sd, pad_id, &desc); ++ if (ret) ++ continue; ++ ++ ret = -EINVAL; ++ ++ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) ++ continue; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (source_stream == desc.entry[i].stream) { ++ desc_entry = &desc.entry[i]; ++ break; ++ } ++ } ++ ++ if (!desc_entry) ++ continue; ++ ++ if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) ++ continue; ++ ++ *entry = *desc_entry; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (desc_entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc) ++ (*nr_queues)++; ++ } ++ ++ dev_dbg(dev, "%s: found frame desc stream:%u vc/dt:%u/0x%x\n", __func__, ++ desc_entry->stream, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ ret = 0; ++ break; ++ } ++ ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ csi2->is_multiple = true; ++ dev_dbg(dev, "set csi2->is_multiple is true.\n"); ++#endif ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++error_graph_walk_source: ++ return ret; ++} ++#endif +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +index 3b4328b..f5ef4d4 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +@@ -81,5 +81,11 @@ int ipu6_isys_csi2_get_remote_desc(u32 source_stream, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry, + int *nr_queues); +- ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++int ipu6_isys_csi2_walk_remote_desc(u32 source_stream, ++ struct ipu6_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues); ++#endif + #endif /* IPU6_ISYS_CSI2_H */ +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video-ext.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video-ext.h +new file mode 100644 +index 0000000..66fee7f +--- /dev/null ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video-ext.h +@@ -0,0 +1,52 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2025 Intel Corporation ++ */ ++ ++#define V4L2_CID_IPU_BASE (V4L2_CID_USER_BASE + 0x1080) ++ ++#define V4L2_CID_IPU_STORE_CSI2_HEADER (V4L2_CID_IPU_BASE + 2) ++#define V4L2_CID_IPU_ISYS_COMPRESSION (V4L2_CID_IPU_BASE + 3) ++ ++#define V4L2_CID_IPU_QUERY_SUB_STREAM (V4L2_CID_IPU_BASE + 4) ++#define V4L2_CID_IPU_SET_SUB_STREAM (V4L2_CID_IPU_BASE + 5) ++ ++#define V4L2_CID_IPU_ENUMERATE_LINK (V4L2_CID_IPU_BASE + 6) ++ ++#define VIDIOC_IPU_GET_DRIVER_VERSION \ ++ _IOWR('v', BASE_VIDIOC_PRIVATE + 3, uint32_t) ++ ++enum ipu6_isys_enum_link_state { ++ IPU6_ISYS_LINK_STATE_DISABLED = 0, ++ IPU6_ISYS_LINK_STATE_ENABLED = 1, ++ IPU6_ISYS_LINK_STATE_DONE = 2, ++ IPU6_ISYS_LINK_STATE_MD = 3, ++ IPU6_ISYS_LINK_STATE_MAX, ++}; ++ ++static struct media_pad *other_pad(struct media_pad *pad); ++bool has_src_pad_stream_active(struct v4l2_subdev *sd, u32 stream, u32 pad); ++bool get_src_pad_by_src_stream(struct v4l2_subdev *sd, u32 stream, u32 *s_pad); ++ ++/* needed for callback */ ++extern int ipu6_isys_inherit_ctrls(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_set_fmt_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_enum_frameintervals_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_enum_fmt_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_enum_framesizes_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_set_parm_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu6_isys_get_parm_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++ ++extern int media_pipeline_enumerate_by_stream_cb( ++ struct ipu6_isys_video *av, ++ int (*cb_fn)(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, ++ void *data), ++ void *data); +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +index 1ac771a..53750b8 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -37,6 +37,9 @@ + #include "ipu6-isys-csi2.h" + #include "ipu6-isys-queue.h" + #include "ipu6-isys-video.h" ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++#include "ipu6-isys-video-ext.h" ++#endif + #include "ipu6-platform-regs.h" + + const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { +@@ -122,7 +125,11 @@ static int video_open(struct file *file) + return -EIO; + } + mutex_unlock(&isys->mutex); +- ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ /* ipu_isys inherit remote sd ctrls */ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_ENABLED && media_pad_remote_pad_first(&av->pad)) ++ media_pipeline_enumerate_by_stream_cb(av, ipu6_isys_inherit_ctrls, NULL); ++#endif + return v4l2_fh_open(file); + } + +@@ -188,7 +195,50 @@ static int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) + { + unsigned int i, num_found; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_mbus_code_enum mce; ++ int ret=-EINVAL; + ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) { ++ ++ mce.index = f->index; ++ mce.pad = 0; ++ ++ /* poll remote sensors */ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu6_isys_enum_fmt_subdev, &mce); ++ if (!ret) { ++ ret=-EINVAL; ++ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ if ((ipu6_isys_pfmts[i].is_meta && ++ f->type != V4L2_BUF_TYPE_META_CAPTURE) || ++ (!ipu6_isys_pfmts[i].is_meta && ++ f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)) ++ continue; ++ ++ if (f->mbus_code && f->mbus_code != mce.code) ++ continue; ++ ++ if (mce.code != ipu6_isys_pfmts[i].code) ++ continue; ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x, sd_code::0x%x\n", ++ __func__, ++ ipu6_isys_pfmts[i].code, ++ ipu6_isys_pfmts[i].pixelformat); ++ ++ f->flags = 0; ++ f->pixelformat = ipu6_isys_pfmts[i].pixelformat; ++ ++ return 0; ++ } ++ } ++ return ret; ++ } ++#endif + for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + if ((ipu6_isys_pfmts[i].is_meta && + f->type != V4L2_BUF_TYPE_META_CAPTURE) || +@@ -217,6 +267,48 @@ static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) + { + unsigned int i; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_size_enum fse; ++ int ret = -EINVAL; ++ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) { ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ if (fsize->pixel_format != ipu6_isys_pfmts[i].pixelformat) ++ continue; ++ ++ fse.code = ipu6_isys_pfmts[i].code; ++ fse.index = fsize->index; ++ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu6_isys_enum_framesizes_subdev, &fse); ++ if (ret) ++ continue; ++ ++ if (!fse.max_width && !fse.max_height) { ++ fsize->discrete.width = 0; ++ fsize->discrete.height = 0; ++ return -EINVAL; ++ } ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x, sd_code::0x%x, WxH:%ux%u\n", ++ __func__, ++ ipu6_isys_pfmts[i].code, ++ ipu6_isys_pfmts[i].pixelformat, ++ fse.max_width, ++ fse.max_height); ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; ++ fsize->discrete.width = fse.max_width; ++ fsize->discrete.height = fse.max_height; ++ return 0; ++ } ++ return -EINVAL; ++ } ++#endif + + if (fsize->index > 0) + return -EINVAL; +@@ -239,6 +331,169 @@ static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, + return -EINVAL; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++/* extended v4l2_ctrl_ops */ ++static int ipu6_isys_video_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ipu6_isys_video *av = ctrl->priv; ++ struct ipu6_isys *isys = av->isys; ++ mutex_lock(&isys->mutex); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_IPU_ENUMERATE_LINK: ++ av->enum_link_state = ctrl->val; ++ break; ++ } ++ ++ mutex_unlock(&isys->mutex); ++ return 0; ++} ++ ++static const struct v4l2_ctrl_ops ipu6_isys_video_ctrl_ops = { ++ .s_ctrl = ipu6_isys_video_s_ctrl, ++}; ++ ++static const struct v4l2_ctrl_config ipu6_isys_video_enum_link = { ++ .ops = &ipu6_isys_video_ctrl_ops, ++ .id = V4L2_CID_IPU_ENUMERATE_LINK, ++ .name = "Enumerate graph link", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = IPU6_ISYS_LINK_STATE_MAX, ++ .step = 1, ++ .def = 0, ++}; ++ ++/* extended v4l2_ioctl_ops */ ++static struct media_pad *other_pad(struct media_pad *pad) ++{ ++ struct media_link *link; ++ ++ list_for_each_entry(link, &pad->entity->links, list) { ++ if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) ++ != MEDIA_LNK_FL_DATA_LINK) ++ continue; ++ ++ return link->source == pad ? link->sink : link->source; ++ } ++ ++ WARN_ON(1); ++ return NULL; ++} ++ ++static int ipu6_isys_get_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval fi; ++ int ret = -ENOLINK; ++ ++ a->parm.capture.timeperframe.numerator = 1; ++ a->parm.capture.timeperframe.denominator = 30; ++ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DISABLED) ++ return 0; ++ ++ if (media_pad_remote_pad_first(&av->pad)) { ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu6_isys_get_parm_subdev, &fi); ++ ++ if (!ret) { ++ a->parm.capture.timeperframe.numerator = fi.interval.numerator; ++ a->parm.capture.timeperframe.denominator = fi.interval.denominator; ++ } ++ } ++ return ret; ++} ++ ++static int ipu6_isys_set_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval fi; ++ int ret = -ENOLINK; ++ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DISABLED) ++ return 0; ++ ++ fi.interval.numerator = a->parm.capture.timeperframe.numerator; ++ fi.interval.denominator = a->parm.capture.timeperframe.denominator; ++ ++ if (media_pad_remote_pad_first(&av->pad)) ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu6_isys_set_parm_subdev, &fi); ++ ++ return ret; ++} ++ ++static int ipu6_isys_enum_frameintervals(struct file *file, void *fh, ++ struct v4l2_frmivalenum *intervals) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval_enum fie; ++ unsigned int i, num_found; ++ int ret=-EINVAL; ++ ++ /* poll remote sensors */ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) { ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ if (intervals->pixel_format != ipu6_isys_pfmts[i].pixelformat) ++ continue; ++ ++ fie.code = ipu6_isys_pfmts[i].code; ++ fie.index = intervals->index; ++ fie.width = intervals->width; ++ fie.height = intervals->height; ++ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu6_isys_enum_frameintervals_subdev, &fie); ++ if (ret) ++ continue; ++ ++ if (!fie.interval.numerator && ++ !fie.interval.denominator) { ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = 1; ++ intervals->discrete.denominator = 30; ++ return -EINVAL; ++ } ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x,sd_code::0x%x,WxH:%ux%u,fps:%u/%u\n", ++ __func__, ++ ipu6_isys_pfmts[i].code, ++ ipu6_isys_pfmts[i].pixelformat, ++ fie.width, fie.height, ++ fie.interval.numerator,fie.interval.denominator); ++ ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = fie.interval.numerator; ++ intervals->discrete.denominator = fie.interval.denominator; ++ return 0; ++ } ++ return -EINVAL; ++ } ++ ++ if (intervals->index > 0) ++ return -EINVAL; ++ ++ for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ if (intervals->pixel_format != ipu6_isys_pfmts[i].pixelformat) ++ continue; ++ ++ /* Code found */ ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = 1; ++ intervals->discrete.denominator = 30; ++ ++ return 0; ++ } ++ return -EINVAL; ++} ++#endif ++ + static int ipu6_isys_vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) + { +@@ -314,6 +569,30 @@ static int ipu6_isys_vidioc_try_fmt_vid_cap(struct file *file, void *fh, + + __ipu6_isys_vidioc_try_fmt_vid_cap(av, f); + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) ++ { ++ int ret = 0; ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = 0, ++ }; ++ struct v4l2_mbus_framefmt format = { ++ .width = f->fmt.pix.width, ++ .height = f->fmt.pix.height, ++ .code = ipu6_isys_get_isys_format(f->fmt.pix.pixelformat, 0)->code, ++ .field = f->fmt.pix.field, ++ .colorspace = f->fmt.pix.colorspace, ++ .ycbcr_enc = f->fmt.pix.ycbcr_enc, ++ .quantization = f->fmt.pix.quantization, ++ .xfer_func = f->fmt.pix.xfer_func, ++ }; ++ fmt.format = format; ++ ret = media_pipeline_enumerate_by_stream_cb(av, ipu6_isys_set_fmt_subdev, &fmt); ++ if (ret) ++ return -EINVAL; ++ } ++#endif + return 0; + } + +@@ -343,7 +622,50 @@ static int ipu6_isys_vidioc_s_fmt_vid_cap(struct file *file, void *fh, + { + struct ipu6_isys_video *av = video_drvdata(file); + ++#ifndef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS + ipu6_isys_vidioc_try_fmt_vid_cap(file, fh, f); ++#else ++ struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); ++ struct media_pad *remote_pad = source_pad; ++ struct v4l2_subdev *sd = NULL; ++ ++ if (vb2_is_busy(&av->aq.vbq)) ++ return -EBUSY; ++ ++ __ipu6_isys_vidioc_try_fmt_vid_cap(av, f); ++ ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) { ++ ++ int ret = 0; ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = 0, ++ }; ++ struct v4l2_mbus_framefmt format = { ++ .width = f->fmt.pix.width, ++ .height = f->fmt.pix.height, ++ .code = ipu6_isys_get_isys_format(f->fmt.pix.pixelformat, 0)->code, ++ .field = f->fmt.pix.field, ++ .colorspace = f->fmt.pix.colorspace, ++ .ycbcr_enc = f->fmt.pix.ycbcr_enc, ++ .quantization = f->fmt.pix.quantization, ++ .xfer_func = f->fmt.pix.xfer_func, ++ }; ++ fmt.format = format; ++ ret = media_pipeline_enumerate_by_stream_cb(av, ipu6_isys_set_fmt_subdev, &fmt); ++ if (ret) ++ return -EINVAL; ++ ++ /* TODO: set format for CSI-2 ++ do { ++ // Non-subdev nodes can be safely ignored here. ++ if (!is_media_entity_v4l2_subdev(remote_pad->entity)) ++ continue; ++ // Set only for IPU CSI entities ++ } while ((remote_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]))); ++ */ ++ } ++#endif + av->pix_fmt = f->fmt.pix; + + return 0; +@@ -1151,6 +1473,266 @@ out_media_entity_stop_streaming_firmware: + return ret; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++int ipu6_isys_set_fmt_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_format *fmt = ++ (struct v4l2_subdev_format *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->set_fmt) ++ ret = sd->ops->pad->set_fmt(sd, NULL, fmt); ++ ++ return ret; ++} ++ ++int ipu6_isys_get_parm_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval *fi = ++ (struct v4l2_subdev_frame_interval *)data; ++ struct v4l2_subdev_state *state = v4l2_subdev_get_unlocked_active_state(sd); ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->get_frame_interval) ++ ret = sd->ops->pad->get_frame_interval(sd, state, fi); ++ ++ return ret; ++} ++ ++int ipu6_isys_set_parm_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval *fi = ++ (struct v4l2_subdev_frame_interval *)data; ++ struct v4l2_subdev_state *state = v4l2_subdev_get_unlocked_active_state(sd); ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->set_frame_interval) ++ ret = sd->ops->pad->set_frame_interval(sd, state, fi); ++ ++ return ret; ++} ++ ++int ipu6_isys_enum_fmt_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_mbus_code_enum *mce = ++ (struct v4l2_subdev_mbus_code_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_mbus_code) ++ ret = sd->ops->pad->enum_mbus_code(sd, NULL, mce); ++ ++ return ret; ++} ++ ++int ipu6_isys_enum_framesizes_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_size_enum *fse = ++ (struct v4l2_subdev_frame_size_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_frame_size) ++ ret = sd->ops->pad->enum_frame_size(sd, NULL, fse); ++ ++ return ret; ++} ++ ++int ipu6_isys_enum_frameintervals_subdev(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval_enum *fie = ++ (struct v4l2_subdev_frame_interval_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_frame_interval) ++ ret = sd->ops->pad->enum_frame_interval(sd, NULL, fie); ++ ++ return ret; ++} ++ ++int ipu6_isys_inherit_ctrls(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ ret = v4l2_ctrl_add_handler(&av->ctrl_handler, ++ sd->ctrl_handler, NULL, true); ++ return ret; ++} ++ ++bool has_src_pad_stream_active(struct v4l2_subdev *sd, u32 stream, u32 pad) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_subdev_route *routes; ++ bool has_active_source_stream = false; ++ unsigned int i; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (!state) ++ return has_active_source_stream; ++ ++ dev_dbg(sd->dev, "%s(): check %s pad/stream: %u/%u has V4L2_SUBDEV_ROUTE_FL_ACTIVE flag\n", ++ __func__, ++ sd->name, ++ pad, ++ stream); ++ routes = state->routing.routes; ++ for (i = 0; i < state->routing.num_routes; i++) { ++ dev_dbg(sd->dev, "%s(): %s pad/stream: %u/%u flags=0x%x\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ routes[i].source_stream, ++ routes[i].flags); ++ if (routes[i].source_pad == pad && ++ routes[i].source_stream == stream && ++ routes[i].flags == V4L2_SUBDEV_ROUTE_FL_ACTIVE) { ++ dev_dbg(sd->dev, "%s(): %s pad/stream: %u/%u has V4L2_SUBDEV_ROUTE_FL_ACTIVE flag\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ routes[i].source_stream); ++ has_active_source_stream = true; ++ break; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++ ++ return has_active_source_stream; ++} ++ ++bool get_src_pad_by_src_stream(struct v4l2_subdev *sd, u32 stream, u32 *s_pad) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_subdev_route *routes; ++ bool has_source_pad = false; ++ unsigned int i; ++ ++ if (!s_pad) ++ return has_source_pad; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (!state) ++ return has_source_pad; ++ ++ routes = state->routing.routes; ++ for (i = 0; i < state->routing.num_routes; i++) { ++ if (routes[i].source_stream == stream) { ++ dev_dbg(sd->dev, "%s(): Found %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ stream); ++ *s_pad = routes[i].source_pad; ++ has_source_pad = true; ++ break; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++ return has_source_pad; ++} ++ ++int media_pipeline_enumerate_by_stream_cb( ++ struct ipu6_isys_video *av, ++ int (*cb_fn)(struct ipu6_isys_video *av, ++ struct v4l2_subdev *sd, ++ void *data), ++ void *data) ++{ ++ int i; ++ struct media_graph graph; ++ struct media_entity *entity = &av->vdev.entity; ++ struct media_device *mdev = entity->graph_obj.mdev; ++ struct device *dev = entity->graph_obj.mdev->dev; ++ struct media_pad *remote_pad = media_pad_remote_pad_unique(&av->pad); ++ struct ipu6_isys_stream *stream = av->stream; ++ struct ipu6_isys_subdev *asd; ++ struct v4l2_subdev *sd; ++ unsigned int pad_id; ++ u32 stream_id; ++ int ret = -ENOLINK; ++ ++ if (!remote_pad) { ++ dev_err(dev, ++ "%s:%d no remote pad found\n", ++ __func__, __LINE__); ++ goto error_graph_walk_source; ++ } ++ ++ pad_id = remote_pad->index; ++ sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ if(!sd) { ++ dev_err(dev, ++ "%s():%d no stream found on remote pad %u\n", ++ __func__, __LINE__, pad_id); ++ goto error_graph_walk_source; ++ } ++ stream_id = ipu6_isys_get_src_stream_by_src_pad(sd, pad_id); ++ dev_dbg(dev, ++ "%s(): Check %s (vc=%u) inheritence from %s pad/stream %u/%u\n", ++ __func__, av->vdev.name, av->vc, ++ sd->name, pad_id, stream_id); ++ ++ mutex_lock(&mdev->graph_mutex); ++ ret = media_graph_walk_init(&graph, mdev); ++ if (ret) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s(): walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_CAM_SENSOR) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with src pad on same stream id */ ++ if(!get_src_pad_by_src_stream(sd, stream_id, &pad_id)) ++ continue; ++ ++ /* access only subdevices on source stream active route */ ++ if(!has_src_pad_stream_active(sd, stream_id, pad_id)) ++ continue; ++ ++ dev_dbg(dev, "%s(): %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ pad_id, ++ stream_id); ++ ++ /* call function once */ ++ ret = cb_fn(av, sd, data); ++ break; ++ } ++ av->enum_link_state = IPU6_ISYS_LINK_STATE_DONE; ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++error_graph_walk_source: ++ return ret; ++} ++#endif ++ + static const struct v4l2_ioctl_ops ipu6_v4l2_ioctl_ops = { + .vidioc_querycap = ipu6_isys_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, +@@ -1171,6 +1753,11 @@ static const struct v4l2_ioctl_ops ipu6_v4l2_ioctl_ops = { + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ .vidioc_g_parm = ipu6_isys_get_parm, ++ .vidioc_s_parm = ipu6_isys_set_parm, ++ .vidioc_enum_frameintervals = ipu6_isys_enum_frameintervals, ++#endif + }; + + static const struct media_entity_operations entity_ops = { +@@ -1312,6 +1899,14 @@ int ipu6_isys_setup_video(struct ipu6_isys_video *av, + av->source_stream = route->sink_stream; + v4l2_subdev_unlock_state(state); + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ if (av->enum_link_state == IPU6_ISYS_LINK_STATE_DONE) ++ ret = ipu6_isys_csi2_walk_remote_desc(route->sink_stream, ++ to_ipu6_isys_csi2(asd), ++ *source_entity, &entry, ++ nr_queues); ++ else ++#endif + ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, + to_ipu6_isys_csi2(asd), + *source_entity, &entry, +@@ -1416,9 +2011,29 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av) + if (ret) + goto out_media_entity_cleanup; + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ /* create v4l2 ctrl for capture video node */ ++ ret = v4l2_ctrl_handler_init(&av->ctrl_handler, 0); ++ if (ret) { ++ dev_err(&av->isys->adev->auxdev.dev, ++ "failed to init v4l2 ctrl handler for %s\n", av->vdev.name); ++ goto out_ctrl_handler_cleanup; ++ } ++ v4l2_ctrl_new_custom(&av->ctrl_handler, &ipu6_isys_video_enum_link, av); ++ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "ext-ctrls: %s: set video_enum_link\n", av->vdev.name); ++ ++ av->vdev.ctrl_handler = &av->ctrl_handler; ++ return ret; ++ ++out_ctrl_handler_cleanup: ++ v4l2_ctrl_handler_free(&av->ctrl_handler); ++#else + return ret; ++#endif + +-out_media_entity_cleanup: ++ out_media_entity_cleanup: + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + +@@ -1433,6 +2048,9 @@ out_free_watermark: + + void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) + { ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ v4l2_ctrl_handler_free(&av->ctrl_handler); ++#endif + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + mutex_destroy(&av->mutex); +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +index 5fa1643..7999e79 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +@@ -109,6 +109,10 @@ struct ipu6_isys_video { + unsigned int skipframe; + unsigned int start_streaming; + #endif ++#ifdef CONFIG_VIDEO_INTEL_IPU6_EXT_CTRLS ++ struct v4l2_ctrl_handler ctrl_handler; ++ unsigned int enum_link_state; /* state for link enumeration by vc */ ++#endif + }; + + #define ipu6_isys_queue_to_video(__aq) \ +-- +2.43.0 + diff --git a/patches/0058-ipu6-isys-video-force-START-then-CAPTURE-distinct-st.patch b/patches/0058-ipu6-isys-video-force-START-then-CAPTURE-distinct-st.patch new file mode 100644 index 00000000..d2d77443 --- /dev/null +++ b/patches/0058-ipu6-isys-video-force-START-then-CAPTURE-distinct-st.patch @@ -0,0 +1,294 @@ +From f0a44d52e53cebb8dc50feda5d148c6e354a3029 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Mon, 23 Mar 2026 04:43:04 -0700 +Subject: [PATCH 58/58] ipu6-isys: video: force START then CAPTURE distinct + start_stream_firmware commands + +Change introduced at [start_stream_firmware](https://github.com/intel/ipu6-drivers/commit/f310264660de132ed7dd7634cca8c03714a774ad#diff-5d8bb31c30c9393a0d7b5d59f0389db65f8189d9270aea922df3564c0614cb25R1907) +hands-up sending multiple concurrent/overlapping stream_capture commands +to the IPU6 firmware at several point in time. +.e.g. STREAM_CAPTURE_ACK to STREAM_CAPTURE_DONE overlaps +in the STREAM_START_AND_CAPTURE_ACK to STREAM_START_AND_CAPTURE_DONE interval. + +Reverting the start_stream_firmware to a 2-phase approach +limit this stream_capture accumulation to max two commands +sent the IPU6 firmware at any given point of time. + +* isys-video : remove START_AND_CAPTURE to avoid multi streams start race-conditions +* isys-video: add stream stop and close state message transition +* isys-video : add timeout on initial STREAM_CAPTURE + +Signed-off-by: florent pirou +--- + .../media/pci/intel/ipu6/ipu6-fw-isys.h | 1 + + .../media/pci/intel/ipu6/ipu6-isys-queue.c | 7 +- + .../media/pci/intel/ipu6/ipu6-isys-video.c | 97 +++++++++++-------- + .../media/pci/intel/ipu6/ipu6-isys-video.h | 1 + + .../drivers/media/pci/intel/ipu6/ipu6-isys.c | 2 + + 5 files changed, 61 insertions(+), 47 deletions(-) + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +index 0ec08ee..e08b239 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +@@ -65,6 +65,7 @@ struct ipu6_isys; + */ + #define IPU6_ISYS_OPEN_RETRY 2000 + #define IPU6_ISYS_CLOSE_RETRY 2000 ++#define IPU6_ISYS_CAPTURE_TIMEOUT_JIFFIES msecs_to_jiffies(2) + #define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000) + #ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET + #define IPU6_LIB_CALL_TIMEOUT_JIFFIES_RESET msecs_to_jiffies(200) +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +index 67cf253..60346a6 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +@@ -1023,13 +1023,8 @@ void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb); + addr = ivb->dma_addr; + +- if (info->pin.addr != addr) { +- if (first) +- dev_err(dev, "Unexpected buffer address %pad\n", +- &addr); +- first = false; ++ if (info->pin.addr != addr) + continue; +- } + + if (info->error_info.error == + IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +index 53750b8..c4e39a6 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -886,7 +886,7 @@ bool is_support_vc(struct ipu6_isys_video *av) + } + + #endif +-static int start_stream_firmware(struct ipu6_isys_video *av, ++int start_stream_firmware(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl) + { + struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; +@@ -966,23 +966,11 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + } + + reinit_completion(&stream->stream_start_completion); +-#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET +- if(bl && is_support_vc(av)) { +-#else +- if (bl) { +-#endif +- send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; +- ipu6_fw_isys_dump_frame_buff_set(dev, buf, +- stream_cfg->nof_output_pins); +- ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, +- buf, msg->dma_addr, +- sizeof(*buf), send_type); +- } else { +- send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; +- ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, +- send_type); +- } ++ dev_dbg(dev, "start stream: start\n"); + ++ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; ++ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, ++ send_type); + if (ret < 0) { + dev_err(dev, "can't start streaming (%d)\n", ret); + goto out_stream_close; +@@ -1002,7 +990,11 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + } + #ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET + if (bl && !is_support_vc(av)) { +- dev_dbg(dev, "start stream: capture\n"); ++#else ++ if (bl) { ++#endif ++ reinit_completion(&stream->stream_initialcapture_completion); ++ dev_dbg(dev, "start stream: initial capture\n"); + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; + ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream_cfg->nof_output_pins); + ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, +@@ -1014,14 +1006,21 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + dev_err(dev, "can't queue buffers (%d)\n", ret); + goto out_stream_close; + } ++ ++ tout = wait_for_completion_timeout(&stream->stream_initialcapture_completion, ++ IPU6_ISYS_CAPTURE_TIMEOUT_JIFFIES); ++ if (!tout) { ++ dev_warn(dev, "start initial capture time out\n"); ++ } else if (stream->error) { ++ dev_warn(dev, "start initial capture error: %d\n", stream->error); ++ } else { ++ dev_dbg(dev, "start initial capture : complete\n"); ++ } ++ ret = 0; + } + else + dev_dbg(dev, "start stream: complete\n"); + +-#else +- dev_dbg(dev, "start stream: complete\n"); +-#endif +- + return 0; + + out_stream_close: +@@ -1050,7 +1049,7 @@ out_put_stream_opened: + return ret; + } + +-static void stop_streaming_firmware(struct ipu6_isys_video *av) ++int stop_streaming_firmware(struct ipu6_isys_video *av) + { + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; +@@ -1063,7 +1062,7 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) + + if (ret < 0) { + dev_err(dev, "can't stop stream (%d)\n", ret); +- return; ++ return -EIO; + } + + tout = wait_for_completion_timeout(&stream->stream_stop_completion, +@@ -1072,15 +1071,23 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) + #else + IPU6_FW_CALL_TIMEOUT_JIFFIES); + #endif +- if (!tout) +- dev_warn(dev, "stream stop time out\n"); +- else if (stream->error) +- dev_warn(dev, "stream stop error: %d\n", stream->error); +- else +- dev_dbg(dev, "stop stream: complete\n"); ++ if (!tout) { ++ dev_err(dev, "stream stop time out for entity %s\n", ++ av->vdev.entity.name); ++ ret = -ETIMEDOUT; ++ } else if (stream->error) { ++ dev_err(dev, "stream stop failed for entity %s with error %d\n", ++ av->vdev.entity.name, stream->error); ++ ret = -EIO; ++ } else { ++ dev_dbg(dev, "stop stream complete for entity %s\n", ++ av->vdev.entity.name); ++ ret = 0; ++ } ++ return ret; + } + +-static void close_streaming_firmware(struct ipu6_isys_video *av) ++int close_streaming_firmware(struct ipu6_isys_video *av) + { + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; +@@ -1092,7 +1099,7 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + if (ret < 0) { + dev_err(dev, "can't close stream (%d)\n", ret); +- return; ++ return -EIO; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, +@@ -1101,12 +1108,19 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) + #else + IPU6_FW_CALL_TIMEOUT_JIFFIES); + #endif +- if (!tout) +- dev_warn(dev, "stream close time out\n"); +- else if (stream->error) +- dev_warn(dev, "stream close error: %d\n", stream->error); +- else +- dev_dbg(dev, "close stream: complete\n"); ++ if (!tout) { ++ dev_err(dev, "stream close time out for entity %s\n", ++ av->vdev.entity.name); ++ ret = -ETIMEDOUT; ++ } else if (stream->error) { ++ dev_err(dev, "stream close failed for entity %s with error %d\n", ++ av->vdev.entity.name, stream->error); ++ ret = -EIO; ++ } else { ++ dev_dbg(dev, "close stream complete for entity %s\n", ++ av->vdev.entity.name); ++ ret = 0; ++ } + + #ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET + stream->last_sequence = atomic_read(&stream->sequence); +@@ -1115,6 +1129,7 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) + + #endif + put_stream_opened(av); ++ return ret; + } + + int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, +@@ -1428,7 +1443,7 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + r_pad = media_pad_remote_pad_first(&av->pad); + r_stream = get_remote_pad_stream(r_pad); + if (!state) { +- stop_streaming_firmware(av); ++ ret = stop_streaming_firmware(av); + + /* stop sub-device which connects with video */ + dev_dbg(dev, "disable streams %s pad:%d mask:0x%llx for %s\n", +@@ -1442,7 +1457,7 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + return ret; + } + +- close_streaming_firmware(av); ++ ret = close_streaming_firmware(av); + } else { + ret = start_stream_firmware(av, bl); + if (ret) { +@@ -1468,7 +1483,7 @@ int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + return 0; + + out_media_entity_stop_streaming_firmware: +- stop_streaming_firmware(av); ++ ret = stop_streaming_firmware(av); + + return ret; + } +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +index 7999e79..01009a6 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +@@ -68,6 +68,7 @@ struct ipu6_isys_stream { + struct completion stream_open_completion; + struct completion stream_close_completion; + struct completion stream_start_completion; ++ struct completion stream_initialcapture_completion; + struct completion stream_stop_completion; + struct ipu6_isys *isys; + +diff --git a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +index 526c916..3460cf1 100644 +--- a/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c ++++ b/ipu6-drivers/drivers/media/pci/intel/ipu6/ipu6-isys.c +@@ -370,6 +370,7 @@ static void isys_stream_init(struct ipu6_isys *isys) + mutex_init(&isys->streams[i].mutex); + init_completion(&isys->streams[i].stream_open_completion); + init_completion(&isys->streams[i].stream_close_completion); ++ init_completion(&isys->streams[i].stream_initialcapture_completion); + init_completion(&isys->streams[i].stream_start_completion); + init_completion(&isys->streams[i].stream_stop_completion); + INIT_LIST_HEAD(&isys->streams[i].queues); +@@ -1711,6 +1712,7 @@ static int isys_isr_one(struct ipu6_bus_device *adev) + + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: ++ complete(&stream->stream_initialcapture_completion); + break; + case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: +-- +2.25.1 + diff --git a/patches/0100-isys-video-let-v4l2-set-default-colorspace.patch b/patches/0100-isys-video-let-v4l2-set-default-colorspace.patch new file mode 100644 index 00000000..f44d0523 --- /dev/null +++ b/patches/0100-isys-video-let-v4l2-set-default-colorspace.patch @@ -0,0 +1,25 @@ +From aa9461f0cdcc69bb4441df0539905b9815fe93a6 Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Mon, 19 Jan 2026 10:38:25 -0700 +Subject: [PATCH 100/105] isys-video: let v4l2 set default colorspace + +Signed-off-by: Florent Pirou +--- + ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +index f69e7a6..fdd59bc 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +@@ -257,7 +257,6 @@ static void __ipu_isys_vidioc_try_fmt_vid_cap(struct ipu7_isys_video *av, + &f->fmt.pix.bytesperline, &f->fmt.pix.sizeimage); + + f->fmt.pix.field = V4L2_FIELD_NONE; +- f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; +-- +2.43.0 + diff --git a/patches/0101-ipu7-isys-make-modules-suffix-versioning-same-as-dkm.patch b/patches/0101-ipu7-isys-make-modules-suffix-versioning-same-as-dkm.patch new file mode 100644 index 00000000..e185f19d --- /dev/null +++ b/patches/0101-ipu7-isys-make-modules-suffix-versioning-same-as-dkm.patch @@ -0,0 +1,73 @@ +From b12a8f1ec6a84d5e3232144703c5d797399894d9 Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Sat, 17 Jan 2026 02:10:39 -0700 +Subject: [PATCH 101/105] ipu7-isys: make modules suffix versioning same as + dkms + +Signed-off-by: Florent Pirou +--- + ipu7-drivers/drivers/media/pci/intel/ipu7/Makefile | 12 ++++++++++++ + .../drivers/media/pci/intel/ipu7/ipu7-isys.c | 1 + + ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7.c | 1 + + .../drivers/media/pci/intel/ipu7/psys/ipu-psys.c | 1 + + 4 files changed, 15 insertions(+) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/Makefile b/ipu7-drivers/drivers/media/pci/intel/ipu7/Makefile +index 5041701..a862e2c 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/Makefile ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/Makefile +@@ -1,5 +1,15 @@ + # SPDX-License-Identifier: GPL-2.0 + # Copyright (c) 2017 - 2025 Intel Corporation. ++KERNEL_VERSION := $(shell echo $(KERNELRELEASE) | sed 's/[^0-9.]*\([0-9]\.[0-9]*\)\..*/\1.0/') ++ ++CC := ${CC} -I ${M}/include -I ${M}/$(KERNEL_VERSION)/include-overrides -DCONFIG_DEBUG_FS -DCONFIG_INTEL_IPU_ACPI -DCONFIG_VIDEO_INTEL_IPU7_ISYS_RESET -DCONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ ++KBUILD_EXTRA_SYMBOLS += ${M}/Module.symvers ++ ++ccflags-y += -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" ++ ++export EXTERNAL_BUILD = 1 ++export CONFIG_VIDEO_INTEL_IPU7=m + + is_kernel_lt_6_10 = $(shell if [ $$(printf "6.10\n$(KERNELVERSION)" | sort -V | head -n1) != "6.10" ]; then echo 1; fi) + ifeq ($(is_kernel_lt_6_10), 1) +@@ -32,6 +42,8 @@ intel-ipu7-isys-objs += ipu7-isys-tpg.o + endif + obj-$(CONFIG_VIDEO_INTEL_IPU7) += intel-ipu7-isys.o + ++subdir-ccflags-y += -DDRIVER_VERSION_SUFFIX=\"${DRIVER_VERSION_SUFFIX}\" ++ + obj-$(CONFIG_VIDEO_INTEL_IPU7) += psys/ + + ccflags-y += -I$(src)/ +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.c +index ea46c8c..ee6470a 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.c +@@ -1501,3 +1501,4 @@ MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); + MODULE_IMPORT_NS(INTEL_IPU7); + MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); + #endif ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7.c +index a9f03ed..f48b591 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7.c +@@ -2877,3 +2877,4 @@ MODULE_AUTHOR("Qingwu Zhang "); + MODULE_AUTHOR("Intel"); + MODULE_LICENSE("GPL"); + MODULE_DESCRIPTION("Intel ipu7 pci driver"); ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c +index 838f0b2..b5ff06a 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/psys/ipu-psys.c +@@ -1554,3 +1554,4 @@ MODULE_IMPORT_NS("DMA_BUF"); + MODULE_IMPORT_NS(INTEL_IPU7); + MODULE_IMPORT_NS(DMA_BUF); + #endif ++MODULE_VERSION(DRIVER_VERSION_SUFFIX); +-- +2.43.0 + diff --git a/patches/0102-ipu7-isys-allow-virtual-channels-on-16-csi2-input-vi.patch b/patches/0102-ipu7-isys-allow-virtual-channels-on-16-csi2-input-vi.patch new file mode 100644 index 00000000..63e4df25 --- /dev/null +++ b/patches/0102-ipu7-isys-allow-virtual-channels-on-16-csi2-input-vi.patch @@ -0,0 +1,29 @@ +From 251af50e4701db5fcebce9478650ec18f22838e9 Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Sat, 17 Jan 2026 02:05:34 -0700 +Subject: [PATCH 102/105] ipu7-isys : allow virtual-channels on 16 csi2 input + video capture + +* ipu7-isys : extend per csi2 video captures v4l2 to 16 sensors input streams + +Signed-off-by: Florent Pirou +--- + ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h +index 6c23b80..dfca0d2 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h +@@ -20,7 +20,7 @@ struct ipu7_isys_stream; + #define INVALID_VC_ID -1 + #define IPU7_NR_OF_CSI2_SINK_PADS 1U + #define IPU7_CSI2_PAD_SINK 0U +-#define IPU7_NR_OF_CSI2_SRC_PADS 8U ++#define IPU7_NR_OF_CSI2_SRC_PADS 16U + #define IPU7_CSI2_PAD_SRC 1U + #define IPU7_NR_OF_CSI2_PADS (IPU7_NR_OF_CSI2_SINK_PADS + \ + IPU7_NR_OF_CSI2_SRC_PADS) +-- +2.43.0 + diff --git a/patches/0103-ipu7-isys-add-d4xx-pixel-format-support.patch b/patches/0103-ipu7-isys-add-d4xx-pixel-format-support.patch new file mode 100644 index 00000000..3b4b5768 --- /dev/null +++ b/patches/0103-ipu7-isys-add-d4xx-pixel-format-support.patch @@ -0,0 +1,139 @@ +From ef52957c76686f674a76783e53af6f092e9ebea6 Mon Sep 17 00:00:00 2001 +From: Florent Pirou +Date: Sat, 17 Jan 2026 02:08:28 -0700 +Subject: [PATCH 103/105] ipu7-isys : add d4xx pixel format support + +Signed-off-by: Florent Pirou +--- + .../media/pci/intel/ipu7/abi/ipu7_fw_isys_abi.h | 1 + + .../drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c | 3 +++ + .../drivers/media/pci/intel/ipu7/ipu7-isys-subdev.c | 5 ++++- + .../drivers/media/pci/intel/ipu7/ipu7-isys-video.c | 13 +++++++++++++ + .../drivers/media/pci/intel/ipu7/ipu7-isys.h | 4 ++-- + 5 files changed, 23 insertions(+), 3 deletions(-) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/abi/ipu7_fw_isys_abi.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/abi/ipu7_fw_isys_abi.h +index dc63449..9abf8d1 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/abi/ipu7_fw_isys_abi.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/abi/ipu7_fw_isys_abi.h +@@ -125,6 +125,7 @@ enum ipu7_insys_frame_format_type { + IPU_INSYS_FRAME_FORMAT_ARGB888 = 31, + IPU_INSYS_FRAME_FORMAT_BGRA888 = 32, + IPU_INSYS_FRAME_FORMAT_ABGR888 = 33, ++ IPU_INSYS_FRAME_FORMAT_VYUY = 34, + N_IPU_INSYS_FRAME_FORMAT + }; + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c +index 6ba21c3..75be4a7 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c +@@ -28,11 +28,13 @@ + #include "ipu7-isys-csi-phy.h" + + static const u32 csi2_supported_codes[] = { ++ MEDIA_BUS_FMT_Y8_1X8, + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_RGB565_1X16, + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_YUYV8_1X16, ++ MEDIA_BUS_FMT_VYUY8_1X16, + MEDIA_BUS_FMT_YUYV10_1X20, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, +@@ -46,6 +48,7 @@ static const u32 csi2_supported_codes[] = { + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, ++ MEDIA_BUS_FMT_FIXED, + 0, + }; + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-subdev.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-subdev.c +index 98b6ef6..45be983 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-subdev.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-subdev.c +@@ -19,6 +19,7 @@ + #include "ipu7-isys.h" + #include "ipu7-isys-subdev.h" + ++ + unsigned int ipu7_isys_mbus_code_to_mipi(u32 code) + { + switch (code) { +@@ -30,13 +31,14 @@ unsigned int ipu7_isys_mbus_code_to_mipi(u32 code) + return MIPI_CSI2_DT_YUV422_10B; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: ++ case MEDIA_BUS_FMT_Y10_1X10: ++ case MEDIA_BUS_FMT_VYUY8_1X16: + return MIPI_CSI2_DT_YUV422_8B; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return MIPI_CSI2_DT_RAW12; +- case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: +@@ -46,6 +48,7 @@ unsigned int ipu7_isys_mbus_code_to_mipi(u32 code) + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: ++ case MEDIA_BUS_FMT_Y8_1X8: + return MIPI_CSI2_DT_RAW8; + default: + WARN_ON(1); +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +index fdd59bc..0d35224 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +@@ -41,6 +41,16 @@ + #include "ipu7-platform-regs.h" + + const struct ipu7_isys_pixelformat ipu7_isys_pfmts[] = { ++ { V4L2_PIX_FMT_GREY, 8, 8, MEDIA_BUS_FMT_Y8_1X8, ++ IPU_INSYS_FRAME_FORMAT_RAW8 }, ++ { V4L2_PIX_FMT_Y10, 16, 10, MEDIA_BUS_FMT_Y10_1X10, ++ IPU_INSYS_FRAME_FORMAT_RAW16 }, ++ {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, ++ IPU_INSYS_FRAME_FORMAT_UYVY}, ++ { V4L2_PIX_FMT_Y8I, 16, 16, MEDIA_BUS_FMT_VYUY8_1X16, ++ IPU_INSYS_FRAME_FORMAT_UYVY}, ++ { V4L2_PIX_FMT_Z16, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, ++ IPU_INSYS_FRAME_FORMAT_UYVY}, + {V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, +@@ -87,8 +97,11 @@ const struct ipu7_isys_pixelformat ipu7_isys_pfmts[] = { + IPU_INSYS_FRAME_FORMAT_YUYV}, + {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, + IPU_INSYS_FRAME_FORMAT_RGB565}, ++ {V4L2_PIX_FMT_Y12I, 32, 24, MEDIA_BUS_FMT_RGB888_1X24, ++ IPU_INSYS_FRAME_FORMAT_RGBA888}, + {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, + IPU_INSYS_FRAME_FORMAT_RGBA888}, ++ {V4L2_META_FMT_D4XX, 8, 8, MEDIA_BUS_FMT_FIXED, 0}, + }; + + static int video_open(struct file *file) +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.h +index 2e45258..19eea94 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys.h +@@ -43,8 +43,8 @@ struct dentry; + #define IPU_ISYS_SIZE_SEND_QUEUE 40U + #define IPU_ISYS_NUM_RECV_QUEUE 1U + +-#define IPU_ISYS_MIN_WIDTH 2U +-#define IPU_ISYS_MIN_HEIGHT 2U ++#define IPU_ISYS_MIN_WIDTH 1U ++#define IPU_ISYS_MIN_HEIGHT 1U + #define IPU_ISYS_MAX_WIDTH 8160U + #define IPU_ISYS_MAX_HEIGHT 8190U + +-- +2.43.0 + diff --git a/patches/0104-ipu7-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch b/patches/0104-ipu7-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch new file mode 100644 index 00000000..f08796b6 --- /dev/null +++ b/patches/0104-ipu7-isys-add-video-VIDIOC_S_EXT_CTRLS-callbacks-by-.patch @@ -0,0 +1,1621 @@ +From a40415416e69bd707421278898d0b176f9f60f82 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 10:31:00 -0700 +Subject: [PATCH 104/105] ipu7-isys: add video VIDIOC_S_EXT_CTRLS callbacks by + streamid + +* isys-video: add video VIDIOC_S_EXT_CTRLS callbacks by streamid +* isys-csi: add graph walk subdev to get frame desc by streamid +* isys-csi: add graph walk subdev to enable/disable streams by streamid +* isys-video: add V4L2_CID_IPU definitions + +Signed-off-by: florent pirou +--- + .../media/pci/intel/ipu7/ipu7-isys-csi-phy.c | 85 ++- + .../media/pci/intel/ipu7/ipu7-isys-csi-phy.h | 4 + + .../media/pci/intel/ipu7/ipu7-isys-csi2.c | 481 +++++++++++++- + .../media/pci/intel/ipu7/ipu7-isys-csi2.h | 11 + + .../pci/intel/ipu7/ipu7-isys-video-ext.h | 61 ++ + .../media/pci/intel/ipu7/ipu7-isys-video.c | 624 +++++++++++++++++- + .../media/pci/intel/ipu7/ipu7-isys-video.h | 4 + + 7 files changed, 1256 insertions(+), 14 deletions(-) + create mode 100644 ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video-ext.h + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c +index 97cd47d..cc6f57f 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c +@@ -22,6 +22,9 @@ + #include "ipu7-isys-csi2-regs.h" + #include "ipu7-platform-regs.h" + #include "ipu7-isys-csi-phy.h" ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++#include "ipu7-isys-video-ext.h" ++#endif + + #define PORT_A 0U + #define PORT_B 1U +@@ -276,6 +279,10 @@ static int __dids_config(struct ipu7_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) + + dwc_csi_write(isys, id, VC_EXTENSION, 0x0); + n = find_first_zero_bit(data_ids, N_DATA_IDS); ++ ++ dev_dbg(&isys->adev->auxdev.dev, "config CSI-%u with n/data_ids:%u/%p\n", ++ id, n, data_ids); ++ + if (n == N_DATA_IDS) + return -ENOSPC; + +@@ -296,7 +303,17 @@ static int __dids_config(struct ipu7_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) + return 0; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++/* ++ * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set ++ * of streams. ++ */ ++#define CSI2_SUBDEV_MAX_STREAM_ID 63 ++static int ipu7_isys_csi_ctrl_dids_config(struct ipu7_isys_csi2 *csi2, u32 id, ++ struct v4l2_subdev_state *state, u64 streams_mask) ++#else + static int ipu7_isys_csi_ctrl_dids_config(struct ipu7_isys_csi2 *csi2, u32 id) ++#endif + { + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; +@@ -308,9 +325,52 @@ static int ipu7_isys_csi_ctrl_dids_config(struct ipu7_isys_csi2 *csi2, u32 id) + + pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + if (IS_ERR(pad)) { ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ ret = ipu7_isys_csi2_walk_unique_remote_desc(state, csi2, ++ &csi2->asd.sd.entity, ++ &desc); ++ if (!ret) { ++ ++ ret = -EINVAL; ++ for (i = 0; i < desc.num_entries; i++) { ++ desc_entry = &desc.entry[i]; ++ ++ dev_dbg(dev, "%s: stream:%u, mbus:%x, vc/dt:%u/%x\n", ++ __func__, ++ desc_entry->stream, ++ desc_entry->pixelcode, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ if (desc_entry->bus.csi2.vc < IPU7_NR_OF_CSI2_VC) { ++ ++ ret = __dids_config(csi2, id, desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ if (ret) { ++ dev_warn(dev, "failed to set frame descriptor: %d\n", ret); ++ continue; ++ } ++ } ++ } ++ } ++ ++ if (!ret) ++ return ret; ++ ++fallback: ++ pad = media_pad_remote_pad_first(&csi2->asd.sd.entity.pads[IPU7_CSI2_PAD_SINK]); ++ dev_warn(dev, "fallback on %s first remote pad\n", ++ csi2->asd.sd.name); ++ if (IS_ERR(pad)) { ++ dev_warn(dev, "can't get first remote pad of %s (%ld)\n", ++ csi2->asd.sd.name, PTR_ERR(pad)); ++ return PTR_ERR(pad); ++ } ++#else + dev_warn(dev, "can't get remote source pad of %s (%ld)\n", + csi2->asd.sd.name, PTR_ERR(pad)); + return PTR_ERR(pad); ++#endif + } + + ext_sd = media_entity_to_v4l2_subdev(pad->entity); +@@ -329,11 +389,21 @@ static int ipu7_isys_csi_ctrl_dids_config(struct ipu7_isys_csi2 *csi2, u32 id) + + for (i = 0; i < desc.num_entries; i++) { + desc_entry = &desc.entry[i]; ++ ++ dev_dbg(dev, "%s: stream:%u, mbus:%x, vc/dt:%u/%x\n", ++ __func__, ++ desc_entry->stream, ++ desc_entry->pixelcode, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ + if (desc_entry->bus.csi2.vc < IPU7_NR_OF_CSI2_VC) { + ret = __dids_config(csi2, id, desc_entry->bus.csi2.vc, + desc_entry->bus.csi2.dt); +- if (ret) ++ if (ret) { ++ dev_warn(dev, "failed to set frame descriptor: %d\n", ret); + return ret; ++ } + } + } + +@@ -957,7 +1027,12 @@ static int ipu7_isys_phy_config(struct ipu7_isys *isys, u8 id, u8 lanes, + return 0; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2, ++ struct v4l2_subdev_state *state, u64 streams_mask) ++#else + int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2) ++#endif + { + struct ipu7_isys *isys = csi2->isys; + u32 lanes = csi2->nlanes; +@@ -989,7 +1064,11 @@ int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2) + } + + ipu7_isys_csi_ctrl_cfg(csi2); ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ ipu7_isys_csi_ctrl_dids_config(csi2, id, state, streams_mask); ++#else + ipu7_isys_csi_ctrl_dids_config(csi2, id); ++#endif + + ret = ipu7_isys_phy_config(isys, id, lanes, aggregation); + if (ret < 0) +@@ -1038,4 +1117,8 @@ void ipu7_isys_csi_phy_powerdown(struct ipu7_isys_csi2 *csi2) + if (!is_ipu7(isys->adev->isp->hw_ver) && + csi2->nlanes > 2U && csi2->port == PORT_A) + ipu7_isys_csi_phy_reset(isys, PORT_B); ++ ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ bitmap_clear(data_ids, 0, N_DATA_IDS); ++#endif + } +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.h +index dfdcb61..7e36837 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.h +@@ -11,6 +11,10 @@ struct ipu7_isys; + #define PHY_MODE_DPHY 0U + #define PHY_MODE_CPHY 1U + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2, struct v4l2_subdev_state *state, u64 streams_mask); ++#else + int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2); ++#endif + void ipu7_isys_csi_phy_powerdown(struct ipu7_isys_csi2 *csi2); + #endif +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c +index 75be4a7..aa3e6d2 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.c +@@ -26,6 +26,9 @@ + #include "ipu7-isys-csi2.h" + #include "ipu7-isys-csi2-regs.h" + #include "ipu7-isys-csi-phy.h" ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++#include "ipu7-isys-video-ext.h" ++#endif + + static const u32 csi2_supported_codes[] = { + MEDIA_BUS_FMT_Y8_1X8, +@@ -56,39 +59,65 @@ static const u32 csi2_supported_codes[] = { + s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2) + { + struct media_pad *src_pad; ++ struct v4l2_subdev_state *s_state; ++ struct v4l2_subdev *s_sd; ++ int ret = -EPIPE; + +- src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); +- if (IS_ERR(src_pad)) { ++ s_state = v4l2_subdev_get_unlocked_active_state(&csi2->asd.sd); ++ if (!s_state) ++ return ret; ++ ++ src_pad = media_pad_remote_pad_first(&csi2->asd.sd.entity.pads[IPU7_CSI2_PAD_SINK]); ++ if (!src_pad) { + dev_err(&csi2->isys->adev->auxdev.dev, +- "can't get source pad of %s (%ld)\n", +- csi2->asd.sd.name, PTR_ERR(src_pad)); +- return PTR_ERR(src_pad); ++ "can't get source pad of %s\n", ++ csi2->asd.sd.name); ++ return -EINVAL; + } + ++ s_sd = media_entity_to_v4l2_subdev(src_pad->entity); ++ ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "%s:%u -> %s:%u\n", ++ csi2->asd.sd.name, IPU7_CSI2_PAD_SINK, ++ s_sd->name, src_pad->index); ++ + return v4l2_get_link_freq(src_pad, 0, 0); + } + #else + s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2) + { + struct media_pad *src_pad; ++ struct v4l2_subdev_state *s_state; + struct v4l2_subdev *ext_sd; + struct device *dev; ++ int ret = -EPIPE; + + if (!csi2) + return -EINVAL; + ++ s_state = v4l2_subdev_get_unlocked_active_state(&csi2->asd.sd); ++ if (!s_state) ++ return ret; ++ + dev = &csi2->isys->adev->auxdev.dev; +- src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); ++ src_pad = media_pad_remote_pad_first(&csi2->asd.sd.entity.pads[IPU7_CSI2_PAD_SINK]); + if (IS_ERR(src_pad)) { +- dev_err(dev, "can't get source pad of %s (%ld)\n", +- csi2->asd.sd.name, PTR_ERR(src_pad)); +- return PTR_ERR(src_pad); ++ dev_err(&csi2->isys->adev->auxdev.dev, ++ "can't get source pad of %s\n", ++ csi2->asd.sd.name); ++ return -EINVAL; + } + + ext_sd = media_entity_to_v4l2_subdev(src_pad->entity); + if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name)) + return -ENODEV; + ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "%s:%u -> %s:%u\n", ++ csi2->asd.sd.name, IPU7_CSI2_PAD_SINK, ++ ext_sd->name, src_pad->index); ++ + return v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0); + } + #endif +@@ -182,7 +211,12 @@ static void ipu7_isys_csi2_disable_stream(struct ipu7_isys_csi2 *csi2) + csi2_irq_disable(csi2); + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2, ++ struct v4l2_subdev_state *state, u64 streams_mask) ++#else + static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2) ++#endif + { + struct ipu7_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; +@@ -208,7 +242,11 @@ static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2) + writel(CSI_SENSOR_INPUT, isys_base + offset + CSI2_ADPL_INPUT_MODE); + writel(1, isys_base + offset + CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN); + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ ret = ipu7_isys_csi_phy_powerup(csi2, state, streams_mask); ++#else + ret = ipu7_isys_csi_phy_powerup(csi2); ++#endif + if (ret) { + dev_err(dev, "CSI-%d PHY power up failed %d\n", port, ret); + return ret; +@@ -309,6 +347,146 @@ static int ipu7_isys_csi2_get_sel(struct v4l2_subdev *sd, + return ret; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++static int ipu7_isys_csi2_link_validate(struct media_link *link) ++{ ++ ++ struct v4l2_subdev *sd = ++ media_entity_to_v4l2_subdev(link->sink->entity); ++ struct device *dev = sd->entity.graph_obj.mdev->dev; ++ struct ipu7_isys_subdev *asd; ++ struct ipu7_isys_csi2 *csi2; ++ struct v4l2_subdev_state *s_state; ++ struct v4l2_mbus_framefmt *s_fmt; ++ struct v4l2_subdev *s_sd; ++ struct media_pad *s_pad; ++ u32 s_stream, code; ++ int ret = -EPIPE; ++ ++ if (!link->source->entity) ++ return ret; ++ ++ asd = to_ipu7_isys_subdev(sd); ++ csi2 = to_ipu7_isys_csi2(asd); ++ dev = &asd->isys->adev->auxdev.dev; ++ ++ s_sd = media_entity_to_v4l2_subdev(link->source->entity); ++ s_state = v4l2_subdev_get_unlocked_active_state(s_sd); ++ if (!s_state) ++ return ret; ++ ++ dev_dbg(dev, "%s: validating link \"%s\":%u -> \"%s\"\n",__func__, ++ link->source->entity->name, link->source->index, ++ link->sink->entity->name); ++ ++ return 0; ++} ++ ++int ipu7_isys_csi2_streams_by_sources(struct ipu7_isys_csi2 *csi2, ++ u32 pad, u32 stream, bool on) ++{ ++ struct media_entity *entity = &csi2->asd.sd.entity; ++ struct media_device *mdev = entity->graph_obj.mdev; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_subdev *sd = NULL; ++ struct v4l2_subdev_state *v4l2_state; ++ struct media_graph graph; ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct v4l2_mbus_frame_desc desc; ++ unsigned int i; ++ int ret; ++ ++ if (!mdev) ++ return -EPIPE; ++ ++ mutex_lock(&mdev->graph_mutex); ++ if (media_graph_walk_init(&graph, mdev)) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s: walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_VID_MUX) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->get_frame_desc) ++ continue; ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ ret = sd->ops->pad->get_frame_desc(sd, pad, &desc); ++ if (ret) ++ continue; ++ ++ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) ++ continue; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (stream == desc.entry[i].stream) { ++ desc_entry = &desc.entry[i]; ++ break; ++ } ++ } ++ ++ if (!desc_entry) ++ continue; ++ ++ if (desc_entry->bus.csi2.vc >= IPU7_NR_OF_CSI2_VC) ++ continue; ++ ++ ++ dev_dbg(dev, "%s: found %s subdev stream:%u vc/dt:%u/0x%x\n", __func__, ++ sd->name, ++ desc_entry->stream, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->disable_streams || !sd->ops->pad->enable_streams) ++ break; ++ ++ v4l2_state = v4l2_subdev_get_unlocked_active_state(sd); ++ if (!v4l2_state) ++ break; ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (on) ++ ret = sd->ops->pad->enable_streams(sd, v4l2_state, pad, BIT_ULL(stream)); ++ else ++ ret = sd->ops->pad->disable_streams(sd, v4l2_state, pad, BIT_ULL(stream)); ++ if (ret) ++ break; ++ ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++ return 0; ++ } ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++ ++error_graph_walk_source: ++ return -EINVAL; ++} ++#endif ++ + /* + * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set + * of streams. +@@ -330,7 +508,11 @@ static int ipu7_isys_csi2_enable_streams(struct v4l2_subdev *sd, + dev_dbg(&csi2->isys->adev->auxdev.dev, + "stream on CSI2-%u with %u lanes\n", csi2->port, + csi2->nlanes); ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ ret = ipu7_isys_csi2_enable_stream(csi2, state, streams_mask); ++#else + ret = ipu7_isys_csi2_enable_stream(csi2); ++#endif + if (ret) + return ret; + } +@@ -345,6 +527,14 @@ static int ipu7_isys_csi2_enable_streams(struct v4l2_subdev *sd, + if (ret) + return ret; + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ if (!ipu7_isys_csi2_streams_by_sources(csi2, sink_pad, sink_stream, true)) { ++ csi2->stream_count++; ++ return 0; ++ } ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "remote stream on fallback\n"); ++#endif + rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(rp->entity); + +@@ -382,6 +572,15 @@ static int ipu7_isys_csi2_disable_streams(struct v4l2_subdev *sd, + if (ret) + return ret; + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ if (!ipu7_isys_csi2_streams_by_sources(csi2, sink_pad, sink_stream, false)) { ++ if (--csi2->stream_count) ++ return 0; ++ goto ret; ++ } ++ dev_dbg(&csi2->isys->adev->auxdev.dev, ++ "remote stream off fallback\n"); ++#endif + rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(rp->entity); + +@@ -389,7 +588,7 @@ static int ipu7_isys_csi2_disable_streams(struct v4l2_subdev *sd, + + if (--csi2->stream_count) + return 0; +- ++ret: + dev_dbg(&csi2->isys->adev->auxdev.dev, + "stream off CSI2-%u with %u lanes\n", csi2->port, csi2->nlanes); + +@@ -415,7 +614,11 @@ static const struct v4l2_subdev_ops csi2_sd_ops = { + }; + + static const struct media_entity_operations csi2_entity_ops = { ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ .link_validate = ipu7_isys_csi2_link_validate, ++#else + .link_validate = v4l2_subdev_link_validate, ++#endif + .has_pad_interdep = v4l2_subdev_has_pad_interdep, + }; + +@@ -571,3 +774,261 @@ int ipu7_isys_csi2_get_remote_desc(u32 source_stream, + + return 0; + } ++ ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++int ipu7_isys_csi2_walk_remote_desc_by_stream(u32 source_stream, ++ struct ipu7_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues) ++{ ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_mbus_frame_desc desc; ++ struct v4l2_subdev *source; ++ struct media_pad *pad; ++ struct media_graph graph; ++ struct media_entity *entity; ++ struct media_device *mdev; ++ struct v4l2_subdev *sd; ++ unsigned int pad_id; ++ u32 stream_id = source_stream; ++ unsigned int i; ++ int ret; ++ ++ source = media_entity_to_v4l2_subdev(source_entity); ++ if (!source) ++ return -EPIPE; ++ ++ pad = media_pad_remote_pad_first(&csi2->asd.pad[IPU7_CSI2_PAD_SINK]); ++ if (!pad) ++ return -EPIPE; ++ ++ dev_dbg(dev, "%s: source pad/stream:%u/%u\n", __func__,pad->index, source_stream); ++ pad_id = pad->index; ++ entity = source_entity; ++ mdev = entity->graph_obj.mdev; ++ if (!mdev) { ++ ret = -EPIPE; ++ goto error_graph_walk_source; ++ } ++ ++ mutex_lock(&mdev->graph_mutex); ++ ret = media_graph_walk_init(&graph, mdev); ++ if (ret) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s: walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_VID_MUX) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->get_frame_desc) ++ continue; ++ ++ dev_dbg(dev, "%s(): %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ pad_id, ++ source_stream); ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ ret = sd->ops->pad->get_frame_desc(sd, pad_id, &desc); ++ if (ret) ++ continue; ++ ++ ret = -EINVAL; ++ ++ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) ++ continue; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (source_stream == desc.entry[i].stream) { ++ desc_entry = &desc.entry[i]; ++ break; ++ } ++ } ++ ++ if (!desc_entry) ++ continue; ++ ++ if (desc_entry->bus.csi2.vc >= IPU7_NR_OF_CSI2_VC) ++ continue; ++ ++ *entry = *desc_entry; ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (desc_entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc) ++ (*nr_queues)++; ++ } ++ ++ dev_dbg(dev, "%s: found frame desc stream:%u vc/dt:%u/0x%x\n", __func__, ++ desc_entry->stream, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ ret = 0; ++ break; ++ } ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++error_graph_walk_source: ++ return ret; ++} ++ ++int ipu7_isys_csi2_walk_unique_remote_desc(struct v4l2_subdev_state *state, ++ struct ipu7_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc *desc) ++{ ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_mbus_frame_desc _desc; ++ struct v4l2_subdev *source; ++ struct media_pad *pad; ++ struct media_graph graph; ++ struct media_entity *entity; ++ struct media_device *mdev; ++ struct v4l2_subdev *sd; ++ struct v4l2_subdev_route *routes; ++ unsigned int pad_id; ++ unsigned int i, j; ++ int ret, skip = 0; ++ ++ if (!desc) ++ return -EPIPE; ++ ++ source = media_entity_to_v4l2_subdev(source_entity); ++ if (!source) ++ return -EPIPE; ++ ++ pad = media_pad_remote_pad_first(&csi2->asd.pad[IPU7_CSI2_PAD_SINK]); ++ if (!pad) ++ return -EPIPE; ++ ++ pad_id = pad->index; ++ entity = source_entity; ++ mdev = entity->graph_obj.mdev; ++ if (!mdev) { ++ ret = -EPIPE; ++ goto error_graph_walk_source; ++ } ++ ++ mutex_lock(&mdev->graph_mutex); ++ ret = media_graph_walk_init(&graph, mdev); ++ if (ret) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s: walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_VID_MUX) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with get_frame_desc callback */ ++ if (!sd->ops || !sd->ops->pad || !sd->ops->pad->get_frame_desc) ++ continue; ++ ++ /* v4l2_subdev_call doesn't call, access directly */ ++ ret = sd->ops->pad->get_frame_desc(sd, pad_id, &_desc); ++ if (ret) ++ continue; ++ ++ if (_desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) ++ continue; ++ ++ routes = state->routing.routes; ++ for (i = 0; i < _desc.num_entries; i++) { ++ ++ desc_entry = &_desc.entry[i]; ++ ++ skip = 0; ++ for (j = 0; j < state->routing.num_routes; j++) { ++ ++ /* ignore inactive routes */ ++ if (routes[j].source_stream == desc_entry->stream && ++ routes[j].flags != V4L2_SUBDEV_ROUTE_FL_ACTIVE) { ++ skip = 1; ++ break; ++ } ++ } ++ if (skip) ++ continue; ++ ++ for (j = 0; j < desc->num_entries; j++) { ++ ++ /* ignore duplicates frame desc */ ++ if (desc_entry->pixelcode == desc->entry[j].pixelcode && ++ desc_entry->bus.csi2.vc == desc->entry[j].bus.csi2.vc && ++ desc_entry->bus.csi2.dt == desc->entry[j].bus.csi2.dt) { ++ skip = 1; ++ break; ++ } ++ } ++ if (skip) ++ continue; ++ ++ /* don't exceed v4l2 api */ ++ if (desc->num_entries >= V4L2_FRAME_DESC_ENTRY_MAX) ++ continue; ++ ++ dev_dbg(dev, "%s: found frame desc stream:%u vc/dt:%u/0x%x\n", __func__, ++ desc_entry->stream, ++ desc_entry->bus.csi2.vc, ++ desc_entry->bus.csi2.dt); ++ ++ desc->entry[desc->num_entries] = *desc_entry; ++ desc->num_entries++; ++ } ++ } ++ ++ desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; ++ ++ if (desc->num_entries) ++ ret = 0; ++ else ++ ret = -EINVAL; ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++error_graph_walk_source: ++ return ret; ++} ++#endif +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h +index dfca0d2..7397de2 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi2.h +@@ -61,4 +61,15 @@ int ipu7_isys_csi2_get_remote_desc(u32 source_stream, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry, + int *nr_queues); ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++int ipu7_isys_csi2_walk_remote_desc_by_stream(u32 source_stream, ++ struct ipu7_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues); ++int ipu7_isys_csi2_walk_unique_remote_desc(struct v4l2_subdev_state *state, ++ struct ipu7_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc *desc); ++#endif + #endif /* IPU7_ISYS_CSI2_H */ +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video-ext.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video-ext.h +new file mode 100644 +index 0000000..7487242 +--- /dev/null ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video-ext.h +@@ -0,0 +1,61 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2025 Intel Corporation ++ */ ++ ++#ifndef IPU7_ISYS_VIDEO_EXT_H ++#define IPU7_ISYS_VIDEO_EXT_H ++ ++#define V4L2_CID_IPU_BASE (V4L2_CID_USER_BASE + 0x1080) ++ ++#define V4L2_CID_IPU_STORE_CSI2_HEADER (V4L2_CID_IPU_BASE + 2) ++#define V4L2_CID_IPU_ISYS_COMPRESSION (V4L2_CID_IPU_BASE + 3) ++ ++#define V4L2_CID_IPU_QUERY_SUB_STREAM (V4L2_CID_IPU_BASE + 4) ++#define V4L2_CID_IPU_SET_SUB_STREAM (V4L2_CID_IPU_BASE + 5) ++ ++#define V4L2_CID_IPU_ENUMERATE_LINK (V4L2_CID_IPU_BASE + 6) ++ ++#define VIDIOC_IPU_GET_DRIVER_VERSION \ ++ _IOWR('v', BASE_VIDIOC_PRIVATE + 3, uint32_t) ++ ++enum ipu7_isys_enum_link_state { ++ IPU7_ISYS_LINK_STATE_DISABLED = 0, ++ IPU7_ISYS_LINK_STATE_ENABLED = 1, ++ IPU7_ISYS_LINK_STATE_DONE = 2, ++ IPU7_ISYS_LINK_STATE_MD = 3, ++ IPU7_ISYS_LINK_STATE_MAX, ++}; ++ ++static struct media_pad *other_pad(struct media_pad *pad); ++bool has_src_pad_stream_active(struct v4l2_subdev *sd, u32 stream, u32 pad); ++bool get_src_pad_by_src_stream(struct v4l2_subdev *sd, u32 stream, u32 *s_pad); ++ ++/* needed for callback */ ++extern int ipu7_isys_inherit_ctrls(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_set_fmt_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_enum_frameintervals_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_enum_fmt_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_enum_framesizes_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_set_parm_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++extern int ipu7_isys_get_parm_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data); ++ ++extern int media_pipeline_enumerate_by_stream_cb( ++ struct ipu7_isys_video *av, ++ int (*cb_fn)(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, ++ void *data), ++ void *data); ++ ++extern int ipu7_isys_csi2_streams_by_sources(struct ipu7_isys_csi2 *csi2, ++ u32 pad, u32 stream, bool on); ++ ++ ++#endif /* IPU7_ISYS_VIDEO_EXT_H */ +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +index 0d35224..e60e8c8 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.c +@@ -38,6 +38,9 @@ + #include "ipu7-fw-isys.h" + #include "ipu7-isys.h" + #include "ipu7-isys-video.h" ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++#include "ipu7-isys-video-ext.h" ++#endif + #include "ipu7-platform-regs.h" + + const struct ipu7_isys_pixelformat ipu7_isys_pfmts[] = { +@@ -106,8 +109,8 @@ const struct ipu7_isys_pixelformat ipu7_isys_pfmts[] = { + + static int video_open(struct file *file) + { +-#ifdef CONFIG_VIDEO_INTEL_IPU7_ISYS_RESET + struct ipu7_isys_video *av = video_drvdata(file); ++#ifdef CONFIG_VIDEO_INTEL_IPU7_ISYS_RESET + struct ipu7_isys *isys = av->isys; + struct ipu7_bus_device *adev = isys->adev; + +@@ -119,6 +122,11 @@ static int video_open(struct file *file) + } + mutex_unlock(&isys->reset_mutex); + ++#endif ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ /* ipu_isys inherit remote sd ctrls */ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_ENABLED && media_pad_remote_pad_first(&av->pad)) ++ media_pipeline_enumerate_by_stream_cb(av, ipu7_isys_inherit_ctrls, NULL); + #endif + return v4l2_fh_open(file); + } +@@ -143,6 +151,7 @@ static int video_release(struct file *file) + } + + #endif ++ + const struct ipu7_isys_pixelformat *ipu7_isys_get_isys_format(u32 pixelformat) + { + unsigned int i; +@@ -172,7 +181,47 @@ static int ipu7_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) + { + unsigned int i, num_found; ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ struct ipu7_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_mbus_code_enum mce; ++ int ret=-EINVAL; ++ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) { + ++ mce.index = f->index; ++ mce.pad = 0; ++ ++ /* poll remote sensors */ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu7_isys_enum_fmt_subdev, &mce); ++ if (!ret) { ++ ret=-EINVAL; ++ for (i = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { ++ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) ++ continue; ++ ++ if (f->mbus_code && f->mbus_code != mce.code) ++ continue; ++ ++ if (mce.code != ipu7_isys_pfmts[i].code) ++ continue; ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x, sd_code::0x%x\n", ++ __func__, ++ ipu7_isys_pfmts[i].code, ++ ipu7_isys_pfmts[i].pixelformat); ++ ++ f->flags = 0; ++ f->pixelformat = ipu7_isys_pfmts[i].pixelformat; ++ ++ return 0; ++ } ++ } ++ return ret; ++ } ++#endif + for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + continue; +@@ -198,6 +247,48 @@ static int ipu7_isys_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) + { + unsigned int i; ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ struct ipu7_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_size_enum fse; ++ int ret = -EINVAL; ++ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) { ++ ++ for (i = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { ++ if (fsize->pixel_format != ipu7_isys_pfmts[i].pixelformat) ++ continue; ++ ++ fse.code = ipu7_isys_pfmts[i].code; ++ fse.index = fsize->index; ++ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu7_isys_enum_framesizes_subdev, &fse); ++ if (ret) ++ continue; ++ ++ if (!fse.max_width && !fse.max_height) { ++ fsize->discrete.width = 0; ++ fsize->discrete.height = 0; ++ return -EINVAL; ++ } ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x, sd_code::0x%x, WxH:%ux%u\n", ++ __func__, ++ ipu7_isys_pfmts[i].code, ++ ipu7_isys_pfmts[i].pixelformat, ++ fse.max_width, ++ fse.max_height); ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; ++ fsize->discrete.width = fse.max_width; ++ fsize->discrete.height = fse.max_height; ++ return 0; ++ } ++ return -EINVAL; ++ } ++#endif + + if (fsize->index > 0) + return -EINVAL; +@@ -220,6 +311,169 @@ static int ipu7_isys_vidioc_enum_framesizes(struct file *file, void *fh, + return -EINVAL; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++/* extended v4l2_ctrl_ops */ ++static int ipu7_isys_video_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ipu7_isys_video *av = ctrl->priv; ++ struct ipu7_isys *isys = av->isys; ++ mutex_lock(&isys->mutex); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_IPU_ENUMERATE_LINK: ++ av->enum_link_state = ctrl->val; ++ break; ++ } ++ ++ mutex_unlock(&isys->mutex); ++ return 0; ++} ++ ++static const struct v4l2_ctrl_ops ipu7_isys_video_ctrl_ops = { ++ .s_ctrl = ipu7_isys_video_s_ctrl, ++}; ++ ++static const struct v4l2_ctrl_config ipu7_isys_video_enum_link = { ++ .ops = &ipu7_isys_video_ctrl_ops, ++ .id = V4L2_CID_IPU_ENUMERATE_LINK, ++ .name = "Enumerate graph link", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 0, ++ .max = IPU7_ISYS_LINK_STATE_MAX, ++ .step = 1, ++ .def = 0, ++}; ++ ++/* extended v4l2_ioctl_ops */ ++static struct media_pad *other_pad(struct media_pad *pad) ++{ ++ struct media_link *link; ++ ++ list_for_each_entry(link, &pad->entity->links, list) { ++ if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) ++ != MEDIA_LNK_FL_DATA_LINK) ++ continue; ++ ++ return link->source == pad ? link->sink : link->source; ++ } ++ ++ WARN_ON(1); ++ return NULL; ++} ++ ++static int ipu7_isys_get_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ struct ipu7_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval fi; ++ int ret = -ENOLINK; ++ ++ a->parm.capture.timeperframe.numerator = 1; ++ a->parm.capture.timeperframe.denominator = 30; ++ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DISABLED) ++ return 0; ++ ++ if (media_pad_remote_pad_first(&av->pad)) { ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu7_isys_get_parm_subdev, &fi); ++ ++ if (!ret) { ++ a->parm.capture.timeperframe.numerator = fi.interval.numerator; ++ a->parm.capture.timeperframe.denominator = fi.interval.denominator; ++ } ++ } ++ return ret; ++} ++ ++static int ipu7_isys_set_parm(struct file *file, void *fh, ++ struct v4l2_streamparm *a) ++{ ++ struct ipu7_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval fi; ++ int ret = -ENOLINK; ++ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DISABLED) ++ return 0; ++ ++ fi.interval.numerator = a->parm.capture.timeperframe.numerator; ++ fi.interval.denominator = a->parm.capture.timeperframe.denominator; ++ ++ if (media_pad_remote_pad_first(&av->pad)) ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu7_isys_set_parm_subdev, &fi); ++ ++ return ret; ++} ++ ++static int ipu7_isys_enum_frameintervals(struct file *file, void *fh, ++ struct v4l2_frmivalenum *intervals) ++{ ++ struct ipu7_isys_video *av = video_drvdata(file); ++ struct v4l2_subdev_frame_interval_enum fie; ++ unsigned int i, num_found; ++ int ret=-EINVAL; ++ ++ /* poll remote sensors */ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) { ++ ++ for (i = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { ++ if (intervals->pixel_format != ipu7_isys_pfmts[i].pixelformat) ++ continue; ++ ++ fie.code = ipu7_isys_pfmts[i].code; ++ fie.index = intervals->index; ++ fie.width = intervals->width; ++ fie.height = intervals->height; ++ ++ ret = media_pipeline_enumerate_by_stream_cb(av, ++ ipu7_isys_enum_frameintervals_subdev, &fie); ++ if (ret) ++ continue; ++ ++ if (!fie.interval.numerator && ++ !fie.interval.denominator) { ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = 1; ++ intervals->discrete.denominator = 30; ++ return -EINVAL; ++ } ++ ++ /* Code found */ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: sd_mbus:0x%x,sd_code::0x%x,WxH:%ux%u,fps:%u/%u\n", ++ __func__, ++ ipu7_isys_pfmts[i].code, ++ ipu7_isys_pfmts[i].pixelformat, ++ fie.width, fie.height, ++ fie.interval.numerator,fie.interval.denominator); ++ ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = fie.interval.numerator; ++ intervals->discrete.denominator = fie.interval.denominator; ++ return 0; ++ } ++ return -EINVAL; ++ } ++ ++ if (intervals->index > 0) ++ return -EINVAL; ++ ++ for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { ++ if (intervals->pixel_format != ipu7_isys_pfmts[i].pixelformat) ++ continue; ++ ++ /* Code found */ ++ intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; ++ intervals->discrete.numerator = 1; ++ intervals->discrete.denominator = 30; ++ ++ return 0; ++ } ++ return -EINVAL; ++} ++#endif ++ + static int ipu7_isys_vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) + { +@@ -285,6 +539,30 @@ static int ipu7_isys_vidioc_try_fmt_vid_cap(struct file *file, void *fh, + + __ipu_isys_vidioc_try_fmt_vid_cap(av, f); + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) ++ { ++ int ret = 0; ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = 0, ++ }; ++ struct v4l2_mbus_framefmt format = { ++ .width = f->fmt.pix.width, ++ .height = f->fmt.pix.height, ++ .code = ipu7_isys_get_isys_format(f->fmt.pix.pixelformat)->code, ++ .field = f->fmt.pix.field, ++ .colorspace = f->fmt.pix.colorspace, ++ .ycbcr_enc = f->fmt.pix.ycbcr_enc, ++ .quantization = f->fmt.pix.quantization, ++ .xfer_func = f->fmt.pix.xfer_func, ++ }; ++ fmt.format = format; ++ ret = media_pipeline_enumerate_by_stream_cb(av, ipu7_isys_set_fmt_subdev, &fmt); ++ if (ret) ++ return -EINVAL; ++ } ++#endif + return 0; + } + +@@ -293,9 +571,51 @@ static int ipu7_isys_vidioc_s_fmt_vid_cap(struct file *file, void *fh, + { + struct ipu7_isys_video *av = video_drvdata(file); + ++#ifndef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS + ipu7_isys_vidioc_try_fmt_vid_cap(file, fh, f); +- av->pix_fmt = f->fmt.pix; ++#else ++ struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); ++ struct media_pad *remote_pad = source_pad; ++ struct v4l2_subdev *sd = NULL; + ++ if (vb2_is_busy(&av->aq.vbq)) ++ return -EBUSY; ++ ++ __ipu_isys_vidioc_try_fmt_vid_cap(av, f); ++ ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) { ++ ++ int ret = 0; ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_ACTIVE, ++ .pad = 0, ++ }; ++ struct v4l2_mbus_framefmt format = { ++ .width = f->fmt.pix.width, ++ .height = f->fmt.pix.height, ++ .code = ipu7_isys_get_isys_format(f->fmt.pix.pixelformat)->code, ++ .field = f->fmt.pix.field, ++ .colorspace = f->fmt.pix.colorspace, ++ .ycbcr_enc = f->fmt.pix.ycbcr_enc, ++ .quantization = f->fmt.pix.quantization, ++ .xfer_func = f->fmt.pix.xfer_func, ++ }; ++ fmt.format = format; ++ ret = media_pipeline_enumerate_by_stream_cb(av, ipu7_isys_set_fmt_subdev, &fmt); ++ if (ret) ++ return -EINVAL; ++ ++ /* TODO: set format for CSI-2 ++ do { ++ // Non-subdev nodes can be safely ignored here. ++ if (!is_media_entity_v4l2_subdev(remote_pad->entity)) ++ continue; ++ // Set only for IPU CSI entities ++ } while ((remote_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]))); ++ */ ++ } ++#endif ++ av->pix_fmt = f->fmt.pix; + return 0; + } + +@@ -377,7 +697,9 @@ static int link_validate(struct media_link *link) + } + + v4l2_subdev_unlock_state(s_state); +- ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ //av->enum_link_state = IPU7_ISYS_LINK_STATE_DONE; ++#endif + return 0; + unlock: + v4l2_subdev_unlock_state(s_state); +@@ -958,6 +1280,266 @@ out_media_entity_stop_streaming_firmware: + return ret; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++int ipu7_isys_set_fmt_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_format *fmt = ++ (struct v4l2_subdev_format *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->set_fmt) ++ ret = sd->ops->pad->set_fmt(sd, NULL, fmt); ++ ++ return ret; ++} ++ ++int ipu7_isys_get_parm_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval *fi = ++ (struct v4l2_subdev_frame_interval *)data; ++ struct v4l2_subdev_state *state = v4l2_subdev_get_unlocked_active_state(sd); ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->get_frame_interval) ++ ret = sd->ops->pad->get_frame_interval(sd, state, fi); ++ ++ return ret; ++} ++ ++int ipu7_isys_set_parm_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval *fi = ++ (struct v4l2_subdev_frame_interval *)data; ++ struct v4l2_subdev_state *state = v4l2_subdev_get_unlocked_active_state(sd); ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->set_frame_interval) ++ ret = sd->ops->pad->set_frame_interval(sd, state, fi); ++ ++ return ret; ++} ++ ++int ipu7_isys_enum_fmt_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_mbus_code_enum *mce = ++ (struct v4l2_subdev_mbus_code_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_mbus_code) ++ ret = sd->ops->pad->enum_mbus_code(sd, NULL, mce); ++ ++ return ret; ++} ++ ++int ipu7_isys_enum_framesizes_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_size_enum *fse = ++ (struct v4l2_subdev_frame_size_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_frame_size) ++ ret = sd->ops->pad->enum_frame_size(sd, NULL, fse); ++ ++ return ret; ++} ++ ++int ipu7_isys_enum_frameintervals_subdev(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ struct v4l2_subdev_frame_interval_enum *fie = ++ (struct v4l2_subdev_frame_interval_enum *)data; ++ /* v4l2_subdev_call doesn't call, access directly */ ++ if (sd && sd->ops && sd->ops->pad && sd->ops->pad->enum_frame_interval) ++ ret = sd->ops->pad->enum_frame_interval(sd, NULL, fie); ++ ++ return ret; ++} ++ ++int ipu7_isys_inherit_ctrls(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, void *data) ++{ ++ int ret = 0; ++ ret = v4l2_ctrl_add_handler(&av->ctrl_handler, ++ sd->ctrl_handler, NULL, true); ++ return ret; ++} ++ ++bool has_src_pad_stream_active(struct v4l2_subdev *sd, u32 stream, u32 pad) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_subdev_route *routes; ++ bool has_active_source_stream = false; ++ unsigned int i; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (!state) ++ return has_active_source_stream; ++ ++ dev_dbg(sd->dev, "%s(): check %s pad/stream: %u/%u has V4L2_SUBDEV_ROUTE_FL_ACTIVE flag\n", ++ __func__, ++ sd->name, ++ pad, ++ stream); ++ routes = state->routing.routes; ++ for (i = 0; i < state->routing.num_routes; i++) { ++ dev_dbg(sd->dev, "%s(): %s pad/stream: %u/%u flags=0x%x\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ routes[i].source_stream, ++ routes[i].flags); ++ if (routes[i].source_pad == pad && ++ routes[i].source_stream == stream && ++ routes[i].flags == V4L2_SUBDEV_ROUTE_FL_ACTIVE) { ++ dev_dbg(sd->dev, "%s(): %s pad/stream: %u/%u has V4L2_SUBDEV_ROUTE_FL_ACTIVE flag\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ routes[i].source_stream); ++ has_active_source_stream = true; ++ break; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++ ++ return has_active_source_stream; ++} ++ ++bool get_src_pad_by_src_stream(struct v4l2_subdev *sd, u32 stream, u32 *s_pad) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_subdev_route *routes; ++ bool has_source_pad = false; ++ unsigned int i; ++ ++ if (!s_pad) ++ return has_source_pad; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (!state) ++ return has_source_pad; ++ ++ routes = state->routing.routes; ++ for (i = 0; i < state->routing.num_routes; i++) { ++ if (routes[i].source_stream == stream) { ++ dev_dbg(sd->dev, "%s(): Found %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ routes[i].source_pad, ++ stream); ++ *s_pad = routes[i].source_pad; ++ has_source_pad = true; ++ break; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++ return has_source_pad; ++} ++ ++int media_pipeline_enumerate_by_stream_cb( ++ struct ipu7_isys_video *av, ++ int (*cb_fn)(struct ipu7_isys_video *av, ++ struct v4l2_subdev *sd, ++ void *data), ++ void *data) ++{ ++ int i; ++ struct media_graph graph; ++ struct media_entity *entity = &av->vdev.entity; ++ struct media_device *mdev = entity->graph_obj.mdev; ++ struct device *dev = entity->graph_obj.mdev->dev; ++ struct media_pad *remote_pad = media_pad_remote_pad_unique(&av->pad); ++ struct ipu7_isys_stream *stream = av->stream; ++ struct ipu7_isys_subdev *asd; ++ struct v4l2_subdev *sd; ++ unsigned int pad_id; ++ u32 stream_id; ++ int ret = -ENOLINK; ++ ++ if (!remote_pad) { ++ dev_err(dev, ++ "%s:%d no remote pad found\n", ++ __func__, __LINE__); ++ goto error_graph_walk_source; ++ } ++ ++ pad_id = remote_pad->index; ++ sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ if(!sd) { ++ dev_err(dev, ++ "%s():%d no stream found on remote pad %u\n", ++ __func__, __LINE__, pad_id); ++ goto error_graph_walk_source; ++ } ++ stream_id = ipu7_isys_get_src_stream_by_src_pad(sd, pad_id); ++ dev_dbg(dev, ++ "%s(): Check %s (vc=%u) inheritence from %s pad/stream %u/%u\n", ++ __func__, av->vdev.name, av->vc, ++ sd->name, pad_id, stream_id); ++ ++ mutex_lock(&mdev->graph_mutex); ++ ret = media_graph_walk_init(&graph, mdev); ++ if (ret) ++ goto error_graph_walk_start_enum; ++ ++ media_graph_walk_start(&graph, entity); ++ while ((entity = media_graph_walk_next(&graph))) { ++ dev_dbg(dev, ++ "%s(): walk entity name: %s, type:%x, func:%x\n", ++ __func__, ++ entity->name, ++ entity->obj_type, ++ entity->function); ++ ++ sd = (entity->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV && \ ++ entity->function == MEDIA_ENT_F_CAM_SENSOR) \ ++ ? media_entity_to_v4l2_subdev(entity) : NULL; ++ ++ if (!sd) ++ continue; ++ if (!strlen(sd->name)) ++ continue; ++ ++ /* pre-filter sub-devices */ ++ if (!(sd->flags & V4L2_SUBDEV_FL_STREAMS)) ++ continue; ++ ++ /* access only subdevices with src pad on same stream id */ ++ if(!get_src_pad_by_src_stream(sd, stream_id, &pad_id)) ++ continue; ++ ++ /* access only subdevices on source stream active route */ ++ if(!has_src_pad_stream_active(sd, stream_id, pad_id)) ++ continue; ++ ++ dev_dbg(dev, "%s(): %s pad/stream: %u/%u\n", ++ __func__, ++ sd->name, ++ pad_id, ++ stream_id); ++ ++ /* call function once */ ++ ret = cb_fn(av, sd, data); ++ break; ++ } ++ av->enum_link_state = IPU7_ISYS_LINK_STATE_DONE; ++ ++error_graph_walk_start_enum: ++ media_graph_walk_cleanup(&graph); ++ mutex_unlock(&mdev->graph_mutex); ++error_graph_walk_source: ++ return ret; ++} ++#endif ++ + static const struct v4l2_ioctl_ops ipu7_v4l2_ioctl_ops = { + .vidioc_querycap = ipu7_isys_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = ipu7_isys_vidioc_enum_fmt, +@@ -974,6 +1556,11 @@ static const struct v4l2_ioctl_ops ipu7_v4l2_ioctl_ops = { + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ .vidioc_g_parm = ipu7_isys_get_parm, ++ .vidioc_s_parm = ipu7_isys_set_parm, ++ .vidioc_enum_frameintervals = ipu7_isys_enum_frameintervals, ++#endif + }; + + static const struct media_entity_operations entity_ops = { +@@ -1121,6 +1708,14 @@ int ipu7_isys_setup_video(struct ipu7_isys_video *av, + + v4l2_subdev_unlock_state(state); + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ if (av->enum_link_state == IPU7_ISYS_LINK_STATE_DONE) ++ ret = ipu7_isys_csi2_walk_remote_desc_by_stream(route->sink_stream, ++ to_ipu7_isys_csi2(asd), ++ *source_entity, &entry, ++ nr_queues); ++ else ++#endif + ret = ipu7_isys_csi2_get_remote_desc(route->sink_stream, + to_ipu7_isys_csi2(asd), + *source_entity, &entry, +@@ -1218,7 +1813,27 @@ int ipu7_isys_video_init(struct ipu7_isys_video *av) + if (ret) + goto out_media_entity_cleanup; + ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ /* create v4l2 ctrl for capture video node */ ++ ret = v4l2_ctrl_handler_init(&av->ctrl_handler, 0); ++ if (ret) { ++ dev_err(&av->isys->adev->auxdev.dev, ++ "failed to init v4l2 ctrl handler for %s\n", av->vdev.name); ++ goto out_ctrl_handler_cleanup; ++ } ++ v4l2_ctrl_new_custom(&av->ctrl_handler, &ipu7_isys_video_enum_link, av); ++ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "ext-ctrls: %s: set video_enum_link\n", av->vdev.name); ++ ++ av->vdev.ctrl_handler = &av->ctrl_handler; ++ return ret; ++ ++out_ctrl_handler_cleanup: ++ v4l2_ctrl_handler_free(&av->ctrl_handler); ++#else + return ret; ++#endif + + out_media_entity_cleanup: + vb2_video_unregister_device(&av->vdev); +@@ -1235,6 +1850,9 @@ out_mutex_destroy: + + void ipu7_isys_video_cleanup(struct ipu7_isys_video *av) + { ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ v4l2_ctrl_handler_free(&av->ctrl_handler); ++#endif + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + mutex_destroy(&av->mutex); +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.h b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.h +index e6d1da2..c36be8d 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.h ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-video.h +@@ -97,6 +97,10 @@ struct ipu7_isys_video { + unsigned int skipframe; + unsigned int start_streaming; + #endif ++#ifdef CONFIG_VIDEO_INTEL_IPU7_EXT_CTRLS ++ struct v4l2_ctrl_handler ctrl_handler; ++ unsigned int enum_link_state; /* state for link enumeration by vc */ ++#endif + }; + + #define ipu7_isys_queue_to_video(__aq) \ +-- +2.43.0 + diff --git a/patches/0105-isys-queue-ignore-HW_ERR_BAD_FRAME_DIM-led-expected-.patch b/patches/0105-isys-queue-ignore-HW_ERR_BAD_FRAME_DIM-led-expected-.patch new file mode 100644 index 00000000..101b6dd7 --- /dev/null +++ b/patches/0105-isys-queue-ignore-HW_ERR_BAD_FRAME_DIM-led-expected-.patch @@ -0,0 +1,28 @@ +From fe3d41df4632fac80ad860e4d8f4a7f23a850475 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Sun, 26 Apr 2026 09:52:35 -0700 +Subject: [PATCH 105/105] isys-queue : ignore HW_ERR_BAD_FRAME_DIM led expected + buffers noise" + +--- + ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-queue.c | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-queue.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-queue.c +index ddf7ae3..2c12a2c 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-queue.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-queue.c +@@ -1138,10 +1138,6 @@ void ipu7_isys_queue_buf_ready(struct ipu7_isys_stream *stream, + addr = ivb->dma_addr; + + if (info->pin.addr != addr) { +- if (first) +- dev_err(dev, "Unexpected buffer address %pad\n", +- &addr); +- + first = false; + continue; + } +-- +2.43.0 + diff --git a/patches/0106-ipu7-isys-enable-cphy-dphy-5-4-bits-vc-extension.patch b/patches/0106-ipu7-isys-enable-cphy-dphy-5-4-bits-vc-extension.patch new file mode 100644 index 00000000..228a13e8 --- /dev/null +++ b/patches/0106-ipu7-isys-enable-cphy-dphy-5-4-bits-vc-extension.patch @@ -0,0 +1,39 @@ +From 12091ca1671bba71ea11b12d353c861f6a95dc06 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Wed, 6 May 2026 08:53:02 -0700 +Subject: [PATCH 106/106] ipu7-isys: enable cphy/dphy 5/4-bits vc-extension + +VC_EXTENSION register activates extra bits for virtual channel. +The Virtual Channel Extension (VCX) field is common to both options, +but is a 2-bit field for D-PHY and a 3-bit field for C-PHY. + +Signed-off-by: florent pirou +--- + .../drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c +index cc6f57f..4badb6a 100644 +--- a/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c ++++ b/ipu7-drivers/drivers/media/pci/intel/ipu7/ipu7-isys-csi-phy.c +@@ -274,10 +274,15 @@ static int __dids_config(struct ipu7_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) + u8 lo, hi; + int ret; + +- dev_dbg(&isys->adev->auxdev.dev, "config CSI-%u with vc:%u dt:0x%02x\n", ++#ifdef CONFIG_INTEL_IPU_VC_EXT ++ dev_info(&isys->adev->auxdev.dev, "config CSI-%u with vc_ext:%u [vc_msb:%u vc_lsb:%u] dt:0x%02x\n", ++ id, vc, vc & GENMASK(4, 2), vc & GENMASK(1, 0), dt); ++#else ++ dev_info(&isys->adev->auxdev.dev, "config CSI-%u with vc:%u dt:0x%02x\n", + id, vc, dt); +- ++#endif + dwc_csi_write(isys, id, VC_EXTENSION, 0x0); ++ + n = find_first_zero_bit(data_ids, N_DATA_IDS); + + dev_dbg(&isys->adev->auxdev.dev, "config CSI-%u with n/data_ids:%u/%p\n", +-- +2.43.0 + diff --git a/patches/0039-ipu-bridge-port-6.12.0-to-bkc.patch b/patches/0200-ipu-bridge-port-6.12.0-to-bkc.patch similarity index 53% rename from patches/0039-ipu-bridge-port-6.12.0-to-bkc.patch rename to patches/0200-ipu-bridge-port-6.12.0-to-bkc.patch index 51c8b2da..c82f69f4 100644 --- a/patches/0039-ipu-bridge-port-6.12.0-to-bkc.patch +++ b/patches/0200-ipu-bridge-port-6.12.0-to-bkc.patch @@ -1,9 +1,17 @@ +From 6cd562af29bfa35419c6791c5cc22ef939b730d3 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:56:24 -0700 +Subject: [PATCH 200/212] ipu-bridge : port 6.12.0 to bkc + +Signed-off-by: florent pirou +--- 6.12.0/drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) -diff --git b/6.12.0/drivers/media/pci/intel/ipu-bridge.c a/6.12.0/drivers/media/pci/intel/ipu-bridge.c + +diff --git a/6.12.0/drivers/media/pci/intel/ipu-bridge.c b/6.12.0/drivers/media/pci/intel/ipu-bridge.c index a0e9a71..4e921c7 100644 ---- b/6.12.0/drivers/media/pci/intel/ipu-bridge.c -+++ a/6.12.0/drivers/media/pci/intel/ipu-bridge.c +--- a/6.12.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/6.12.0/drivers/media/pci/intel/ipu-bridge.c @@ -59,6 +59,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Omnivision OV2740 */ @@ -13,3 +21,6 @@ index a0e9a71..4e921c7 100644 /* Omnivision OV8865 */ IPU_SENSOR_CONFIG("INT347A", 1, 360000000), /* Omnivision OV7251 */ +-- +2.25.1 + diff --git a/patches/0201-ipu-bridge-6.12-makefile.patch b/patches/0201-ipu-bridge-6.12-makefile.patch new file mode 100644 index 00000000..e56c0ab2 --- /dev/null +++ b/patches/0201-ipu-bridge-6.12-makefile.patch @@ -0,0 +1,42 @@ +From dbeb7ea2b32db8ff79047bf581a2485b61e163b3 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:57:25 -0700 +Subject: [PATCH 201/212] ipu-bridge : 6.12 makefile + +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/pci/intel/Kconfig | 5 ----- + 6.12.0/drivers/media/pci/intel/Makefile | 5 +---- + 2 files changed, 1 insertion(+), 9 deletions(-) + +diff --git a/6.12.0/drivers/media/pci/intel/Kconfig b/6.12.0/drivers/media/pci/intel/Kconfig +index d9fcddc..3fa2b72 100644 +--- a/6.12.0/drivers/media/pci/intel/Kconfig ++++ b/6.12.0/drivers/media/pci/intel/Kconfig +@@ -1,9 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only +- +-source "drivers/media/pci/intel/ipu3/Kconfig" +-source "drivers/media/pci/intel/ipu6/Kconfig" +-source "drivers/media/pci/intel/ivsc/Kconfig" +- + config IPU_BRIDGE + tristate "Intel IPU Bridge" + depends on ACPI || COMPILE_TEST +diff --git a/6.12.0/drivers/media/pci/intel/Makefile b/6.12.0/drivers/media/pci/intel/Makefile +index 3a2cc65..16dc2d2 100644 +--- a/6.12.0/drivers/media/pci/intel/Makefile ++++ b/6.12.0/drivers/media/pci/intel/Makefile +@@ -2,7 +2,4 @@ + # + # Makefile for the IPU drivers + # +-obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +-obj-y += ipu3/ +-obj-y += ivsc/ +-obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ ++obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +\ No newline at end of file +-- +2.25.1 + diff --git a/patches/0041-ipu-bridge-6.12-bkc-backport.patch b/patches/0202-ipu-bridge-6.12-bkc-backport.patch similarity index 85% rename from patches/0041-ipu-bridge-6.12-bkc-backport.patch rename to patches/0202-ipu-bridge-6.12-bkc-backport.patch index cae8d352..34669231 100644 --- a/patches/0041-ipu-bridge-6.12-bkc-backport.patch +++ b/patches/0202-ipu-bridge-6.12-bkc-backport.patch @@ -1,10 +1,18 @@ - 6.12.0/drivers/media/pci/intel/ipu-bridge.c | 102 ++++++++++++++++++++++++---- - 1 file changed, 89 insertions(+), 13 deletions(-) +From 1aa28e272a8161282a486be8c280a8f804f1a70f Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:58:38 -0700 +Subject: [PATCH 202/212] ipu-bridge : 6.12 bkc backport + +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/pci/intel/ipu-bridge.c | 100 +++++++++++++++++--- + 1 file changed, 87 insertions(+), 13 deletions(-) + diff --git a/6.12.0/drivers/media/pci/intel/ipu-bridge.c b/6.12.0/drivers/media/pci/intel/ipu-bridge.c -index a0e9a71..4e4f86a 100644 +index 4e921c7..4e4f86a 100644 --- a/6.12.0/drivers/media/pci/intel/ipu-bridge.c +++ b/6.12.0/drivers/media/pci/intel/ipu-bridge.c -@@ -83,6 +85,17 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { +@@ -85,6 +85,17 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000), /* Omnivision OV8856 */ IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), @@ -22,7 +30,7 @@ index a0e9a71..4e4f86a 100644 }; static const struct ipu_property_names prop_names = { -@@ -313,6 +326,7 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) +@@ -315,6 +326,7 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) } sensor->link = ssdb.link; @@ -30,7 +38,7 @@ index a0e9a71..4e4f86a 100644 sensor->lanes = ssdb.lanes; sensor->mclkspeed = ssdb.mclkspeed; sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); -@@ -472,18 +486,49 @@ static void ipu_bridge_init_swnode_group(struct ipu_sensor *sensor) +@@ -474,18 +486,49 @@ static void ipu_bridge_init_swnode_group(struct ipu_sensor *sensor) } } @@ -85,7 +93,7 @@ index a0e9a71..4e4f86a 100644 nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( sensor->node_names.endpoint, &nodes[SWNODE_SENSOR_PORT], -@@ -664,7 +709,7 @@ static void ipu_bridge_unregister_sensors(struct ipu_bridge *bridge) +@@ -666,7 +709,7 @@ static void ipu_bridge_unregister_sensors(struct ipu_bridge *bridge) } static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, @@ -94,7 +102,7 @@ index a0e9a71..4e4f86a 100644 { struct fwnode_handle *fwnode, *primary; struct ipu_sensor *sensor; -@@ -691,31 +736,53 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, +@@ -693,31 +736,53 @@ static int ipu_bridge_connect_sensor(const struct ipu_sensor_config *cfg, if (ret) goto err_put_adev; @@ -154,7 +162,7 @@ index a0e9a71..4e4f86a 100644 ret = ipu_bridge_instantiate_ivsc(sensor); if (ret) -@@ -748,7 +815,16 @@ static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) +@@ -750,7 +815,16 @@ static int ipu_bridge_connect_sensors(struct ipu_bridge *bridge) const struct ipu_sensor_config *cfg = &ipu_supported_sensors[i]; @@ -172,3 +180,6 @@ index a0e9a71..4e4f86a 100644 if (ret) goto err_unregister_sensors; } +-- +2.25.1 + diff --git a/patches/0203-ipu-bridge-6.12-add-sensors.patch b/patches/0203-ipu-bridge-6.12-add-sensors.patch new file mode 100644 index 00000000..20b8f81a --- /dev/null +++ b/patches/0203-ipu-bridge-6.12-add-sensors.patch @@ -0,0 +1,30 @@ +From 905a2805e458f1c3b3deef016fb43bcd7e80d2a0 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 05:59:32 -0700 +Subject: [PATCH 203/212] ipu-bridge : 6.12 add sensors + +Signed-off-by: florent pirou +--- + 6.12.0/drivers/media/pci/intel/ipu-bridge.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/6.12.0/drivers/media/pci/intel/ipu-bridge.c b/6.12.0/drivers/media/pci/intel/ipu-bridge.c +index 4e4f86a..66983ff 100644 +--- a/6.12.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/6.12.0/drivers/media/pci/intel/ipu-bridge.c +@@ -47,6 +47,12 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* D3 Embedded ISX031 */ ++ IPU_SENSOR_CONFIG("INTC113C", 1, 300000000), ++ /* LI AR0830 */ ++ IPU_SENSOR_CONFIG("LIAR0830", 1, 600000000), ++ /* Innodisk AR0822 */ ++ IPU_SENSOR_CONFIG("EV8MOOM1", 1, 600000000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +-- +2.25.1 + diff --git a/patches/0204-ipu-bridge-6.17-makefile.patch b/patches/0204-ipu-bridge-6.17-makefile.patch new file mode 100644 index 00000000..1aea6b37 --- /dev/null +++ b/patches/0204-ipu-bridge-6.17-makefile.patch @@ -0,0 +1,42 @@ +From 9522d4dcc2932d53e2022b5c6c321fd1236512ea Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 06:08:59 -0700 +Subject: [PATCH 204/212] ipu-bridge : 6.17 makefile + +Signed-off-by: florent pirou +--- + 6.17.0/drivers/media/pci/intel/Kconfig | 5 ----- + 6.17.0/drivers/media/pci/intel/Makefile | 5 +---- + 2 files changed, 1 insertion(+), 9 deletions(-) + +diff --git a/6.17.0/drivers/media/pci/intel/Kconfig b/6.17.0/drivers/media/pci/intel/Kconfig +index d9fcddc..3fa2b72 100644 +--- a/6.17.0/drivers/media/pci/intel/Kconfig ++++ b/6.17.0/drivers/media/pci/intel/Kconfig +@@ -1,9 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only +- +-source "drivers/media/pci/intel/ipu3/Kconfig" +-source "drivers/media/pci/intel/ipu6/Kconfig" +-source "drivers/media/pci/intel/ivsc/Kconfig" +- + config IPU_BRIDGE + tristate "Intel IPU Bridge" + depends on ACPI || COMPILE_TEST +diff --git a/6.17.0/drivers/media/pci/intel/Makefile b/6.17.0/drivers/media/pci/intel/Makefile +index 3a2cc65..16dc2d2 100644 +--- a/6.17.0/drivers/media/pci/intel/Makefile ++++ b/6.17.0/drivers/media/pci/intel/Makefile +@@ -2,7 +2,4 @@ + # + # Makefile for the IPU drivers + # +-obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +-obj-y += ipu3/ +-obj-y += ivsc/ +-obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ ++obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +\ No newline at end of file +-- +2.25.1 + diff --git a/patches/0041-ipu-bridge-6.17-bkc-backport.patch b/patches/0205-ipu-bridge-6.17-bkc-backport.patch similarity index 86% rename from patches/0041-ipu-bridge-6.17-bkc-backport.patch rename to patches/0205-ipu-bridge-6.17-bkc-backport.patch index 928c14e8..13a1e748 100644 --- a/patches/0041-ipu-bridge-6.17-bkc-backport.patch +++ b/patches/0205-ipu-bridge-6.17-bkc-backport.patch @@ -1,3 +1,13 @@ +From ff6bbad886d1d48d96162382bfed79074b5d57e3 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:52:11 -0700 +Subject: [PATCH 205/212] ipu-bridge : 6.17 bkc backport + +Signed-off-by: florent pirou +--- + 6.17.0/drivers/media/pci/intel/ipu-bridge.c | 25 ++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + diff --git a/6.17.0/drivers/media/pci/intel/ipu-bridge.c b/6.17.0/drivers/media/pci/intel/ipu-bridge.c index 4e57935..3c947d1 100644 --- a/6.17.0/drivers/media/pci/intel/ipu-bridge.c @@ -71,3 +81,6 @@ index 4e57935..3c947d1 100644 } static void ipu_bridge_init_swnode_names(struct ipu_sensor *sensor) +-- +2.25.1 + diff --git a/patches/0042-ipu-bridge-6.17-add-sensor.patch b/patches/0206-ipu-bridge-6.17-add-sensors.patch similarity index 50% rename from patches/0042-ipu-bridge-6.17-add-sensor.patch rename to patches/0206-ipu-bridge-6.17-add-sensors.patch index 7e7b45ba..7e5cdc54 100644 --- a/patches/0042-ipu-bridge-6.17-add-sensor.patch +++ b/patches/0206-ipu-bridge-6.17-add-sensors.patch @@ -1,19 +1,30 @@ +From b41a2e9f0aff4dfa923000d49dc05a778c37b535 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:55:49 -0700 +Subject: [PATCH 206/212] ipu-bridge : 6.17 add sensors + +Signed-off-by: florent pirou +--- 6.17.0/drivers/media/pci/intel/ipu-bridge.c | 6 ++++++ 1 file changed, 6 insertions(+) + diff --git a/6.17.0/drivers/media/pci/intel/ipu-bridge.c b/6.17.0/drivers/media/pci/intel/ipu-bridge.c -index 3c947d1..36e8f10 100644 +index 3c947d1..e9c8422 100644 --- a/6.17.0/drivers/media/pci/intel/ipu-bridge.c +++ b/6.17.0/drivers/media/pci/intel/ipu-bridge.c -@@ -97,6 +97,12 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { - IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), - /* Lontium lt6911gxd */ - IPU_SENSOR_CONFIG("INTC1124", 0), +@@ -51,6 +51,12 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { + /* D3 Embedded ISX031 */ + IPU_SENSOR_CONFIG("INTC113C", 1, 300000000), + /* LI AR0830 */ + IPU_SENSOR_CONFIG("LIAR0830", 1, 600000000), + /* Innodisk AR0822 */ + IPU_SENSOR_CONFIG("EV8MOOM1", 1, 600000000), - }; - - static const struct ipu_property_names prop_names = { + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +-- +2.25.1 + diff --git a/patches/0207-ipu-bridge-6.18-makefile.patch b/patches/0207-ipu-bridge-6.18-makefile.patch new file mode 100644 index 00000000..8d5a70ae --- /dev/null +++ b/patches/0207-ipu-bridge-6.18-makefile.patch @@ -0,0 +1,42 @@ +From b45b649b285b1faaf6adc25e596862010c0ac6ad Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 06:09:09 -0700 +Subject: [PATCH 207/212] ipu-bridge : 6.18 makefile + +Signed-off-by: florent pirou +--- + 6.18.0/drivers/media/pci/intel/Kconfig | 5 ----- + 6.18.0/drivers/media/pci/intel/Makefile | 5 +---- + 2 files changed, 1 insertion(+), 9 deletions(-) + +diff --git a/6.18.0/drivers/media/pci/intel/Kconfig b/6.18.0/drivers/media/pci/intel/Kconfig +index d9fcddc..3fa2b72 100644 +--- a/6.18.0/drivers/media/pci/intel/Kconfig ++++ b/6.18.0/drivers/media/pci/intel/Kconfig +@@ -1,9 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only +- +-source "drivers/media/pci/intel/ipu3/Kconfig" +-source "drivers/media/pci/intel/ipu6/Kconfig" +-source "drivers/media/pci/intel/ivsc/Kconfig" +- + config IPU_BRIDGE + tristate "Intel IPU Bridge" + depends on ACPI || COMPILE_TEST +diff --git a/6.18.0/drivers/media/pci/intel/Makefile b/6.18.0/drivers/media/pci/intel/Makefile +index 3a2cc65..16dc2d2 100644 +--- a/6.18.0/drivers/media/pci/intel/Makefile ++++ b/6.18.0/drivers/media/pci/intel/Makefile +@@ -2,7 +2,4 @@ + # + # Makefile for the IPU drivers + # +-obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +-obj-y += ipu3/ +-obj-y += ivsc/ +-obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ ++obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +\ No newline at end of file +-- +2.25.1 + diff --git a/patches/0208-ipu-bridge-6.18-bkc-backport.patch b/patches/0208-ipu-bridge-6.18-bkc-backport.patch new file mode 100644 index 00000000..bc95c5e0 --- /dev/null +++ b/patches/0208-ipu-bridge-6.18-bkc-backport.patch @@ -0,0 +1,86 @@ +From 228aac68727091672dbb7fdbae1bb89b59ffe9fb Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:52:42 -0700 +Subject: [PATCH 208/212] ipu-bridge : 6.18 bkc backport + +Signed-off-by: florent pirou +--- + 6.18.0/drivers/media/pci/intel/ipu-bridge.c | 25 ++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + +diff --git a/6.18.0/drivers/media/pci/intel/ipu-bridge.c b/6.18.0/drivers/media/pci/intel/ipu-bridge.c +index 4e57935..1c60df1 100644 +--- a/6.18.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/6.18.0/drivers/media/pci/intel/ipu-bridge.c +@@ -35,6 +35,9 @@ + */ + #define IVSC_DEV_NAME "intel_vsc" + ++#define PHY_MODE_DPHY 0 ++#define PHY_MODE_CPHY 1 ++ + /* + * Extend this array with ACPI Hardware IDs of devices known to be working + * plus the number of link-frequencies expected by their drivers, along with +@@ -92,6 +95,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), ++ /* Lontium lt6911gxd */ ++ IPU_SENSOR_CONFIG("INTC1124", 0), + }; + + static const struct ipu_property_names prop_names = { +@@ -312,6 +317,7 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) + + sensor->link = ssdb.link; + sensor->lanes = ssdb.lanes; ++ sensor->phyconfig = ssdb.phyconfig; + sensor->mclkspeed = ssdb.mclkspeed; + sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); + sensor->orientation = ipu_bridge_parse_orientation(adev); +@@ -330,6 +336,7 @@ static void ipu_bridge_create_fwnode_properties( + { + struct ipu_property_names *names = &sensor->prop_names; + struct software_node *nodes = sensor->swnodes; ++ u8 bus_type; + + sensor->prop_names = prop_names; + +@@ -387,9 +394,16 @@ static void ipu_bridge_create_fwnode_properties( + PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref); + } + ++ if (sensor->phyconfig == PHY_MODE_DPHY) ++ bus_type = V4L2_FWNODE_BUS_TYPE_CSI2_DPHY; ++ else if (sensor->phyconfig == PHY_MODE_CPHY) ++ bus_type = V4L2_FWNODE_BUS_TYPE_CSI2_CPHY; ++ else ++ bus_type = V4L2_FWNODE_BUS_TYPE_GUESS; ++ + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, +- V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); ++ bus_type); + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, sensor->lanes); +@@ -409,6 +423,15 @@ static void ipu_bridge_create_fwnode_properties( + sensor->ipu_properties[1] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->remote_ref); ++ ++ /* ++ * TODO: Remove the bus_type property for IPU ++ * 1. keep fwnode property list no change. ++ * 2. IPU driver needs to get bus_type from remote sensor ep. ++ */ ++ sensor->ipu_properties[2] = PROPERTY_ENTRY_U32( ++ sensor->prop_names.bus_type, ++ bus_type); + } + + static void ipu_bridge_init_swnode_names(struct ipu_sensor *sensor) +-- +2.25.1 + diff --git a/patches/0209-ipu-bridge-6.18-add-sensors.patch b/patches/0209-ipu-bridge-6.18-add-sensors.patch new file mode 100644 index 00000000..bed1034f --- /dev/null +++ b/patches/0209-ipu-bridge-6.18-add-sensors.patch @@ -0,0 +1,30 @@ +From 17039446833ec3e35d220c66c1d0a2cc0bbb7638 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:56:02 -0700 +Subject: [PATCH 209/212] ipu-bridge : 6.18 add sensors + +Signed-off-by: florent pirou +--- + 6.18.0/drivers/media/pci/intel/ipu-bridge.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/6.18.0/drivers/media/pci/intel/ipu-bridge.c b/6.18.0/drivers/media/pci/intel/ipu-bridge.c +index 1c60df1..88bec73 100644 +--- a/6.18.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/6.18.0/drivers/media/pci/intel/ipu-bridge.c +@@ -51,6 +51,12 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* D3 Embedded ISX031 */ ++ IPU_SENSOR_CONFIG("INTC113C", 1, 300000000), ++ /* LI AR0830 */ ++ IPU_SENSOR_CONFIG("LIAR0830", 1, 600000000), ++ /* Innodisk AR0822 */ ++ IPU_SENSOR_CONFIG("EV8MOOM1", 1, 600000000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +-- +2.25.1 + diff --git a/patches/0210-ipu-bridge-7.0-makefile.patch b/patches/0210-ipu-bridge-7.0-makefile.patch new file mode 100644 index 00000000..eb4c4fcc --- /dev/null +++ b/patches/0210-ipu-bridge-7.0-makefile.patch @@ -0,0 +1,42 @@ +From 0559982cdecf828adc47e3d6a350e230d0cf6c50 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 06:09:20 -0700 +Subject: [PATCH 210/212] ipu-bridge : 7.0 makefile + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/pci/intel/Kconfig | 5 ----- + 7.0.0/drivers/media/pci/intel/Makefile | 5 +---- + 2 files changed, 1 insertion(+), 9 deletions(-) + +diff --git a/7.0.0/drivers/media/pci/intel/Kconfig b/7.0.0/drivers/media/pci/intel/Kconfig +index 3f14ca1..99e9869 100644 +--- a/7.0.0/drivers/media/pci/intel/Kconfig ++++ b/7.0.0/drivers/media/pci/intel/Kconfig +@@ -1,9 +1,4 @@ + # SPDX-License-Identifier: GPL-2.0-only +- +-source "drivers/media/pci/intel/ipu3/Kconfig" +-source "drivers/media/pci/intel/ipu6/Kconfig" +-source "drivers/media/pci/intel/ivsc/Kconfig" +- + config IPU_BRIDGE + tristate "Intel IPU Bridge" + depends on ACPI +diff --git a/7.0.0/drivers/media/pci/intel/Makefile b/7.0.0/drivers/media/pci/intel/Makefile +index 3a2cc65..16dc2d2 100644 +--- a/7.0.0/drivers/media/pci/intel/Makefile ++++ b/7.0.0/drivers/media/pci/intel/Makefile +@@ -2,7 +2,4 @@ + # + # Makefile for the IPU drivers + # +-obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +-obj-y += ipu3/ +-obj-y += ivsc/ +-obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ ++obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o +\ No newline at end of file +-- +2.25.1 + diff --git a/patches/0211-ipu-bridge-7.0-bkc-backport.patch b/patches/0211-ipu-bridge-7.0-bkc-backport.patch new file mode 100644 index 00000000..3d372cb6 --- /dev/null +++ b/patches/0211-ipu-bridge-7.0-bkc-backport.patch @@ -0,0 +1,86 @@ +From 0c6d1785c2b5fedf76cf39ec13ef556751594cfb Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:52:55 -0700 +Subject: [PATCH 211/212] ipu-bridge : 7.0 bkc backport + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/pci/intel/ipu-bridge.c | 25 +++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + +diff --git a/7.0.0/drivers/media/pci/intel/ipu-bridge.c b/7.0.0/drivers/media/pci/intel/ipu-bridge.c +index 32cc95a..7d0614c 100644 +--- a/7.0.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/7.0.0/drivers/media/pci/intel/ipu-bridge.c +@@ -36,6 +36,9 @@ + */ + #define IVSC_DEV_NAME "intel_vsc" + ++#define PHY_MODE_DPHY 0 ++#define PHY_MODE_CPHY 1 ++ + /* + * Extend this array with ACPI Hardware IDs of devices known to be working + * plus the number of link-frequencies expected by their drivers, along with +@@ -97,6 +100,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("SONY471A", 1, 200000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), ++ /* Lontium lt6911gxd */ ++ IPU_SENSOR_CONFIG("INTC1124", 0), + }; + + /* +@@ -345,6 +350,7 @@ int ipu_bridge_parse_ssdb(struct acpi_device *adev, struct ipu_sensor *sensor) + + sensor->link = ssdb.link; + sensor->lanes = ssdb.lanes; ++ sensor->phyconfig = ssdb.phyconfig; + sensor->mclkspeed = ssdb.mclkspeed; + sensor->rotation = ipu_bridge_parse_rotation(adev, &ssdb); + sensor->orientation = ipu_bridge_parse_orientation(adev); +@@ -363,6 +369,7 @@ static void ipu_bridge_create_fwnode_properties( + { + struct ipu_property_names *names = &sensor->prop_names; + struct software_node *nodes = sensor->swnodes; ++ u8 bus_type; + + sensor->prop_names = prop_names; + +@@ -420,9 +427,16 @@ static void ipu_bridge_create_fwnode_properties( + PROPERTY_ENTRY_REF_ARRAY("lens-focus", sensor->vcm_ref); + } + ++ if (sensor->phyconfig == PHY_MODE_DPHY) ++ bus_type = V4L2_FWNODE_BUS_TYPE_CSI2_DPHY; ++ else if (sensor->phyconfig == PHY_MODE_CPHY) ++ bus_type = V4L2_FWNODE_BUS_TYPE_CSI2_CPHY; ++ else ++ bus_type = V4L2_FWNODE_BUS_TYPE_GUESS; ++ + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, +- V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); ++ bus_type); + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, sensor->lanes); +@@ -442,6 +456,15 @@ static void ipu_bridge_create_fwnode_properties( + sensor->ipu_properties[1] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->remote_ref); ++ ++ /* ++ * TODO: Remove the bus_type property for IPU ++ * 1. keep fwnode property list no change. ++ * 2. IPU driver needs to get bus_type from remote sensor ep. ++ */ ++ sensor->ipu_properties[2] = PROPERTY_ENTRY_U32( ++ sensor->prop_names.bus_type, ++ bus_type); + } + + static void ipu_bridge_init_swnode_names(struct ipu_sensor *sensor) +-- +2.25.1 + diff --git a/patches/0212-ipu-bridge-7.0-add-sensors.patch b/patches/0212-ipu-bridge-7.0-add-sensors.patch new file mode 100644 index 00000000..e4cc078a --- /dev/null +++ b/patches/0212-ipu-bridge-7.0-add-sensors.patch @@ -0,0 +1,30 @@ +From ae386d240250c8700afad129f9cf78dbccf970a0 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 28 Apr 2026 07:56:15 -0700 +Subject: [PATCH 212/212] ipu-bridge : 7.0 add sensors + +Signed-off-by: florent pirou +--- + 7.0.0/drivers/media/pci/intel/ipu-bridge.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/7.0.0/drivers/media/pci/intel/ipu-bridge.c b/7.0.0/drivers/media/pci/intel/ipu-bridge.c +index 7d0614c..21c379f 100644 +--- a/7.0.0/drivers/media/pci/intel/ipu-bridge.c ++++ b/7.0.0/drivers/media/pci/intel/ipu-bridge.c +@@ -52,6 +52,12 @@ + * Please keep the list sorted by ACPI HID. + */ + static const struct ipu_sensor_config ipu_supported_sensors[] = { ++ /* D3 Embedded ISX031 */ ++ IPU_SENSOR_CONFIG("INTC113C", 1, 300000000), ++ /* LI AR0830 */ ++ IPU_SENSOR_CONFIG("LIAR0830", 1, 600000000), ++ /* Innodisk AR0822 */ ++ IPU_SENSOR_CONFIG("EV8MOOM1", 1, 600000000), + /* Himax HM11B1 */ + IPU_SENSOR_CONFIG("HIMX11B1", 1, 384000000), + /* Himax HM2170 */ +-- +2.25.1 + diff --git a/patches/libv4l/0001-media-ctl-add-pad-support-2-digits-stream-id.patch b/patches/libv4l/0001-media-ctl-add-pad-support-2-digits-stream-id.patch new file mode 100644 index 00000000..7eef7230 --- /dev/null +++ b/patches/libv4l/0001-media-ctl-add-pad-support-2-digits-stream-id.patch @@ -0,0 +1,40 @@ +From 87db5a82cd6f71ac73b825ad335ae306ce64ce60 Mon Sep 17 00:00:00 2001 +From: florent pirou +Date: Tue, 3 Mar 2026 08:08:47 -0700 +Subject: [PATCH] media-ctl: add pad support 2-digits stream-id + +* v4l2: workaround pad stream-id single-digit limit +``` +media-ctl -d /dev/media0 -V '"Intel IPU7 CSI2 2":"11/10" [fmt:VYUY8_1X16/640x480 field:none]' -v +... +Enumerating pads and links +Expected '[' + + "Intel IPU7 CSI2 2":"11/10" [fmt:VYUY8_1X16/640x480 field:none] + ^ + Unable to parse format + Unable to setup formats: Invalid argument (22) + +``` + +Signed-off-by: florent pirou +--- + utils/media-ctl/libmediactl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/utils/media-ctl/libmediactl.c b/utils/media-ctl/libmediactl.c +index 64ac8cf1..1d5f9b81 100644 +--- a/utils/media-ctl/libmediactl.c ++++ b/utils/media-ctl/libmediactl.c +@@ -909,7 +909,7 @@ struct media_pad *media_parse_pad_stream(struct media_device *media, + *stream = 0; + } + +- for (; isspace(*p); ++p); ++ for (p = ep + 1; isspace(*p); ++p); + + if (endp) + *endp = (char*)p; +-- +2.25.1 +