From dc6f462ac3416a102c5ca4ebdbfe2044d095b3b5 Mon Sep 17 00:00:00 2001 From: zouxiaoh Date: Fri, 6 Dec 2024 17:44:08 +0800 Subject: [PATCH] TWL PV release on kernel 6.12 (#308) * Intel IMX390&TI960 enabled on MTL, iot kernel 6.11-rc3 Signed-off-by: zouxiaoh * TWL PV release on kernel 6.12 Signed-off-by: zouxiaoh --------- Signed-off-by: zouxiaoh --- ...-i2c-Add-ar0234-camera-sensor-driver.patch | 1121 +++ .../0001-media-pci-intel-psys-driver.patch | 8433 +++++++++++++++++ ...-media-i2c-add-support-for-lt6911uxe.patch | 707 ++ .../0003-INT3472-Support-LT6911UXE.patch | 73 + ...se-module-parameter-to-set-isys-freq.patch | 45 + ...se-module-parameter-to-set-psys-freq.patch | 45 + .../0006-media-pci-Enable-ISYS-reset.patch | 626 ++ ...add-support-for-ar0234-and-lt6911uxe.patch | 60 + ...media-i2c-remove-useless-header-file.patch | 25 + ...-lt6911uxe-driver-for-upstream-and-b.patch | 877 ++ ...-do-not-handle-interrupts-when-devic.patch | 77 + ...l-ipu6-remove-buttress-ish-structure.patch | 164 + ...-media-i2c-add-support-for-lt6911uxc.patch | 48 + ...1uxc-driver-and-enable-in-ipu-bridge.patch | 739 ++ kernel_patches/patch_6.12_mainline/README | 34 + 15 files changed, 13074 insertions(+) create mode 100644 kernel_patches/patch_6.12_mainline/0001-media-i2c-Add-ar0234-camera-sensor-driver.patch create mode 100644 kernel_patches/patch_6.12_mainline/0001-media-pci-intel-psys-driver.patch create mode 100644 kernel_patches/patch_6.12_mainline/0002-media-i2c-add-support-for-lt6911uxe.patch create mode 100644 kernel_patches/patch_6.12_mainline/0003-INT3472-Support-LT6911UXE.patch create mode 100644 kernel_patches/patch_6.12_mainline/0004-upstream-Use-module-parameter-to-set-isys-freq.patch create mode 100644 kernel_patches/patch_6.12_mainline/0005-upstream-Use-module-parameter-to-set-psys-freq.patch create mode 100644 kernel_patches/patch_6.12_mainline/0006-media-pci-Enable-ISYS-reset.patch create mode 100644 kernel_patches/patch_6.12_mainline/0007-media-i2c-add-support-for-ar0234-and-lt6911uxe.patch create mode 100644 kernel_patches/patch_6.12_mainline/0008-driver-media-i2c-remove-useless-header-file.patch create mode 100644 kernel_patches/patch_6.12_mainline/0010-media-i2c-update-lt6911uxe-driver-for-upstream-and-b.patch create mode 100644 kernel_patches/patch_6.12_mainline/0011-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch create mode 100644 kernel_patches/patch_6.12_mainline/0012-media-intel-ipu6-remove-buttress-ish-structure.patch create mode 100644 kernel_patches/patch_6.12_mainline/0013-media-i2c-add-support-for-lt6911uxc.patch create mode 100644 kernel_patches/patch_6.12_mainline/0014-media-i2c-add-lt6911uxc-driver-and-enable-in-ipu-bridge.patch create mode 100644 kernel_patches/patch_6.12_mainline/README diff --git a/kernel_patches/patch_6.12_mainline/0001-media-i2c-Add-ar0234-camera-sensor-driver.patch b/kernel_patches/patch_6.12_mainline/0001-media-i2c-Add-ar0234-camera-sensor-driver.patch new file mode 100644 index 000000000000..d8ceb5d8a59c --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0001-media-i2c-Add-ar0234-camera-sensor-driver.patch @@ -0,0 +1,1121 @@ +From 2c1a23a47608dde726a0b2689d6c4bf39f83179c Mon Sep 17 00:00:00 2001 +From: Dongcheng Yan +Date: Thu, 6 Jun 2024 16:00:02 +0800 +Subject: [PATCH] media: i2c: Add ar0234 camera sensor driver + +The driver is implemented with V4L2 framework, +and supports following features: + + - manual exposure and analog/digital gain control + - vblank/hblank control + - vflip/hflip control + - runtime PM support + - 1280x960 at 30FPS + +Signed-off-by: Dongcheng Yan +Signed-off-by: zouxiaoh +--- + drivers/media/i2c/ar0234.c | 1077 ++++++++++++++++++++++++++ + drivers/media/pci/intel/ipu-bridge.c | 2 + + 2 files changed, 1079 insertions(+) + create mode 100644 drivers/media/i2c/ar0234.c + +diff --git a/drivers/media/i2c/ar0234.c b/drivers/media/i2c/ar0234.c +new file mode 100644 +index 000000000000..80fe5ffd1c64 +--- /dev/null ++++ b/drivers/media/i2c/ar0234.c +@@ -0,0 +1,1077 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2019 - 2024 Intel Corporation. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* Chip ID */ ++#define AR0234_REG_CHIP_ID CCI_REG16(0x3000) ++#define AR0234_CHIP_ID 0x0a56 ++ ++#define AR0234_REG_MODE_SELECT CCI_REG16(0x301a) ++#define AR0234_REG_VTS CCI_REG16(0x300a) ++#define AR0234_REG_EXPOSURE CCI_REG16(0x3012) ++#define AR0234_REG_ANALOG_GAIN CCI_REG16(0x3060) ++#define AR0234_REG_GLOBAL_GAIN CCI_REG16(0x305e) ++#define AR0234_REG_ORIENTATION CCI_REG16(0x3040) ++#define AR0234_REG_TEST_PATTERN CCI_REG16(0x0600) ++ ++#define AR0234_EXPOSURE_MIN 0 ++#define AR0234_EXPOSURE_MAX_MARGIN 80 ++#define AR0234_EXPOSURE_STEP 1 ++ ++#define AR0234_ANALOG_GAIN_MIN 0 ++#define AR0234_ANALOG_GAIN_MAX 0x7f ++#define AR0234_ANALOG_GAIN_STEP 1 ++#define AR0234_ANALOG_GAIN_DEFAULT 0xe ++ ++#define AR0234_GLOBAL_GAIN_MIN 0 ++#define AR0234_GLOBAL_GAIN_MAX 0x7ff ++#define AR0234_GLOBAL_GAIN_STEP 1 ++#define AR0234_GLOBAL_GAIN_DEFAULT 0x80 ++ ++#define AR0234_NATIVE_WIDTH 1920 ++#define AR0234_NATIVE_HEIGHT 1080 ++#define AR0234_COMMON_WIDTH 1280 ++#define AR0234_COMMON_HEIGHT 960 ++#define AR0234_PIXEL_ARRAY_LEFT 320 ++#define AR0234_PIXEL_ARRAY_TOP 60 ++#define AR0234_ORIENTATION_HFLIP BIT(14) ++#define AR0234_ORIENTATION_VFLIP BIT(15) ++ ++#define AR0234_VTS_DEFAULT 0x04c4 ++#define AR0234_VTS_MAX 0xffff ++#define AR0234_HTS_DEFAULT 0x04c4 ++#define AR0234_PPL_DEFAULT 3498 ++ ++#define AR0234_MODE_RESET 0x00d9 ++#define AR0234_MODE_STANDBY 0x2058 ++#define AR0234_MODE_STREAMING 0x205c ++ ++#define AR0234_PIXEL_RATE 128000000ULL ++#define AR0234_XCLK_FREQ 19200000ULL ++ ++#define AR0234_TEST_PATTERN_DISABLE 0 ++#define AR0234_TEST_PATTERN_SOLID_COLOR 1 ++#define AR0234_TEST_PATTERN_COLOR_BARS 2 ++#define AR0234_TEST_PATTERN_GREY_COLOR 3 ++#define AR0234_TEST_PATTERN_WALKING 256 ++ ++#define to_ar0234(_sd) container_of(_sd, struct ar0234, sd) ++ ++struct ar0234_reg_list { ++ u32 num_of_regs; ++ const struct cci_reg_sequence *regs; ++}; ++ ++struct ar0234_mode { ++ u32 width; ++ u32 height; ++ u32 hts; ++ u32 vts_def; ++ u32 code; ++ /* Sensor register settings for this mode */ ++ const struct ar0234_reg_list reg_list; ++}; ++ ++static const struct cci_reg_sequence mode_1280x960_10bit_2lane[] = { ++ { CCI_REG16(0x3f4c), 0x121f }, ++ { CCI_REG16(0x3f4e), 0x121f }, ++ { CCI_REG16(0x3f50), 0x0b81 }, ++ { CCI_REG16(0x31e0), 0x0003 }, ++ { CCI_REG16(0x30b0), 0x0028 }, ++ /* R0x3088 specify the sequencer RAM access address. */ ++ { CCI_REG16(0x3088), 0x8000 }, ++ /* R0x3086 write the sequencer RAM. */ ++ { CCI_REG16(0x3086), 0xc1ae }, ++ { CCI_REG16(0x3086), 0x327f }, ++ { CCI_REG16(0x3086), 0x5780 }, ++ { CCI_REG16(0x3086), 0x272f }, ++ { CCI_REG16(0x3086), 0x7416 }, ++ { CCI_REG16(0x3086), 0x7e13 }, ++ { CCI_REG16(0x3086), 0x8000 }, ++ { CCI_REG16(0x3086), 0x307e }, ++ { CCI_REG16(0x3086), 0xff80 }, ++ { CCI_REG16(0x3086), 0x20c3 }, ++ { CCI_REG16(0x3086), 0xb00e }, ++ { CCI_REG16(0x3086), 0x8190 }, ++ { CCI_REG16(0x3086), 0x1643 }, ++ { CCI_REG16(0x3086), 0x1651 }, ++ { CCI_REG16(0x3086), 0x9d3e }, ++ { CCI_REG16(0x3086), 0x9545 }, ++ { CCI_REG16(0x3086), 0x2209 }, ++ { CCI_REG16(0x3086), 0x3781 }, ++ { CCI_REG16(0x3086), 0x9016 }, ++ { CCI_REG16(0x3086), 0x4316 }, ++ { CCI_REG16(0x3086), 0x7f90 }, ++ { CCI_REG16(0x3086), 0x8000 }, ++ { CCI_REG16(0x3086), 0x387f }, ++ { CCI_REG16(0x3086), 0x1380 }, ++ { CCI_REG16(0x3086), 0x233b }, ++ { CCI_REG16(0x3086), 0x7f93 }, ++ { CCI_REG16(0x3086), 0x4502 }, ++ { CCI_REG16(0x3086), 0x8000 }, ++ { CCI_REG16(0x3086), 0x7fb0 }, ++ { CCI_REG16(0x3086), 0x8d66 }, ++ { CCI_REG16(0x3086), 0x7f90 }, ++ { CCI_REG16(0x3086), 0x8192 }, ++ { CCI_REG16(0x3086), 0x3c16 }, ++ { CCI_REG16(0x3086), 0x357f }, ++ { CCI_REG16(0x3086), 0x9345 }, ++ { CCI_REG16(0x3086), 0x0280 }, ++ { CCI_REG16(0x3086), 0x007f }, ++ { CCI_REG16(0x3086), 0xb08d }, ++ { CCI_REG16(0x3086), 0x667f }, ++ { CCI_REG16(0x3086), 0x9081 }, ++ { CCI_REG16(0x3086), 0x8237 }, ++ { CCI_REG16(0x3086), 0x4502 }, ++ { CCI_REG16(0x3086), 0x3681 }, ++ { CCI_REG16(0x3086), 0x8044 }, ++ { CCI_REG16(0x3086), 0x1631 }, ++ { CCI_REG16(0x3086), 0x4374 }, ++ { CCI_REG16(0x3086), 0x1678 }, ++ { CCI_REG16(0x3086), 0x7b7d }, ++ { CCI_REG16(0x3086), 0x4502 }, ++ { CCI_REG16(0x3086), 0x450a }, ++ { CCI_REG16(0x3086), 0x7e12 }, ++ { CCI_REG16(0x3086), 0x8180 }, ++ { CCI_REG16(0x3086), 0x377f }, ++ { CCI_REG16(0x3086), 0x1045 }, ++ { CCI_REG16(0x3086), 0x0a0e }, ++ { CCI_REG16(0x3086), 0x7fd4 }, ++ { CCI_REG16(0x3086), 0x8024 }, ++ { CCI_REG16(0x3086), 0x0e82 }, ++ { CCI_REG16(0x3086), 0x9cc2 }, ++ { CCI_REG16(0x3086), 0xafa8 }, ++ { CCI_REG16(0x3086), 0xaa03 }, ++ { CCI_REG16(0x3086), 0x430d }, ++ { CCI_REG16(0x3086), 0x2d46 }, ++ { CCI_REG16(0x3086), 0x4316 }, ++ { CCI_REG16(0x3086), 0x5f16 }, ++ { CCI_REG16(0x3086), 0x530d }, ++ { CCI_REG16(0x3086), 0x1660 }, ++ { CCI_REG16(0x3086), 0x401e }, ++ { CCI_REG16(0x3086), 0x2904 }, ++ { CCI_REG16(0x3086), 0x2984 }, ++ { CCI_REG16(0x3086), 0x81e7 }, ++ { CCI_REG16(0x3086), 0x816f }, ++ { CCI_REG16(0x3086), 0x1706 }, ++ { CCI_REG16(0x3086), 0x81e7 }, ++ { CCI_REG16(0x3086), 0x7f81 }, ++ { CCI_REG16(0x3086), 0x5c0d }, ++ { CCI_REG16(0x3086), 0x5754 }, ++ { CCI_REG16(0x3086), 0x495f }, ++ { CCI_REG16(0x3086), 0x5305 }, ++ { CCI_REG16(0x3086), 0x5307 }, ++ { CCI_REG16(0x3086), 0x4d2b }, ++ { CCI_REG16(0x3086), 0xf810 }, ++ { CCI_REG16(0x3086), 0x164c }, ++ { CCI_REG16(0x3086), 0x0755 }, ++ { CCI_REG16(0x3086), 0x562b }, ++ { CCI_REG16(0x3086), 0xb82b }, ++ { CCI_REG16(0x3086), 0x984e }, ++ { CCI_REG16(0x3086), 0x1129 }, ++ { CCI_REG16(0x3086), 0x9460 }, ++ { CCI_REG16(0x3086), 0x5c09 }, ++ { CCI_REG16(0x3086), 0x5c1b }, ++ { CCI_REG16(0x3086), 0x4002 }, ++ { CCI_REG16(0x3086), 0x4500 }, ++ { CCI_REG16(0x3086), 0x4580 }, ++ { CCI_REG16(0x3086), 0x29b6 }, ++ { CCI_REG16(0x3086), 0x7f80 }, ++ { CCI_REG16(0x3086), 0x4004 }, ++ { CCI_REG16(0x3086), 0x7f88 }, ++ { CCI_REG16(0x3086), 0x4109 }, ++ { CCI_REG16(0x3086), 0x5c0b }, ++ { CCI_REG16(0x3086), 0x29b2 }, ++ { CCI_REG16(0x3086), 0x4115 }, ++ { CCI_REG16(0x3086), 0x5c03 }, ++ { CCI_REG16(0x3086), 0x4105 }, ++ { CCI_REG16(0x3086), 0x5f2b }, ++ { CCI_REG16(0x3086), 0x902b }, ++ { CCI_REG16(0x3086), 0x8081 }, ++ { CCI_REG16(0x3086), 0x6f40 }, ++ { CCI_REG16(0x3086), 0x1041 }, ++ { CCI_REG16(0x3086), 0x0160 }, ++ { CCI_REG16(0x3086), 0x29a2 }, ++ { CCI_REG16(0x3086), 0x29a3 }, ++ { CCI_REG16(0x3086), 0x5f4d }, ++ { CCI_REG16(0x3086), 0x1c17 }, ++ { CCI_REG16(0x3086), 0x0281 }, ++ { CCI_REG16(0x3086), 0xe729 }, ++ { CCI_REG16(0x3086), 0x8345 }, ++ { CCI_REG16(0x3086), 0x8840 }, ++ { CCI_REG16(0x3086), 0x0f7f }, ++ { CCI_REG16(0x3086), 0x8a40 }, ++ { CCI_REG16(0x3086), 0x2345 }, ++ { CCI_REG16(0x3086), 0x8024 }, ++ { CCI_REG16(0x3086), 0x4008 }, ++ { CCI_REG16(0x3086), 0x7f88 }, ++ { CCI_REG16(0x3086), 0x5d29 }, ++ { CCI_REG16(0x3086), 0x9288 }, ++ { CCI_REG16(0x3086), 0x102b }, ++ { CCI_REG16(0x3086), 0x0489 }, ++ { CCI_REG16(0x3086), 0x165c }, ++ { CCI_REG16(0x3086), 0x4386 }, ++ { CCI_REG16(0x3086), 0x170b }, ++ { CCI_REG16(0x3086), 0x5c03 }, ++ { CCI_REG16(0x3086), 0x8a48 }, ++ { CCI_REG16(0x3086), 0x4d4e }, ++ { CCI_REG16(0x3086), 0x2b80 }, ++ { CCI_REG16(0x3086), 0x4c09 }, ++ { CCI_REG16(0x3086), 0x4119 }, ++ { CCI_REG16(0x3086), 0x816f }, ++ { CCI_REG16(0x3086), 0x4110 }, ++ { CCI_REG16(0x3086), 0x4001 }, ++ { CCI_REG16(0x3086), 0x6029 }, ++ { CCI_REG16(0x3086), 0x8229 }, ++ { CCI_REG16(0x3086), 0x8329 }, ++ { CCI_REG16(0x3086), 0x435c }, ++ { CCI_REG16(0x3086), 0x055f }, ++ { CCI_REG16(0x3086), 0x4d1c }, ++ { CCI_REG16(0x3086), 0x81e7 }, ++ { CCI_REG16(0x3086), 0x4502 }, ++ { CCI_REG16(0x3086), 0x8180 }, ++ { CCI_REG16(0x3086), 0x7f80 }, ++ { CCI_REG16(0x3086), 0x410a }, ++ { CCI_REG16(0x3086), 0x9144 }, ++ { CCI_REG16(0x3086), 0x1609 }, ++ { CCI_REG16(0x3086), 0x2fc3 }, ++ { CCI_REG16(0x3086), 0xb130 }, ++ { CCI_REG16(0x3086), 0xc3b1 }, ++ { CCI_REG16(0x3086), 0x0343 }, ++ { CCI_REG16(0x3086), 0x164a }, ++ { CCI_REG16(0x3086), 0x0a43 }, ++ { CCI_REG16(0x3086), 0x160b }, ++ { CCI_REG16(0x3086), 0x4316 }, ++ { CCI_REG16(0x3086), 0x8f43 }, ++ { CCI_REG16(0x3086), 0x1690 }, ++ { CCI_REG16(0x3086), 0x4316 }, ++ { CCI_REG16(0x3086), 0x7f81 }, ++ { CCI_REG16(0x3086), 0x450a }, ++ { CCI_REG16(0x3086), 0x410f }, ++ { CCI_REG16(0x3086), 0x7f83 }, ++ { CCI_REG16(0x3086), 0x5d29 }, ++ { CCI_REG16(0x3086), 0x4488 }, ++ { CCI_REG16(0x3086), 0x102b }, ++ { CCI_REG16(0x3086), 0x0453 }, ++ { CCI_REG16(0x3086), 0x0d40 }, ++ { CCI_REG16(0x3086), 0x2345 }, ++ { CCI_REG16(0x3086), 0x0240 }, ++ { CCI_REG16(0x3086), 0x087f }, ++ { CCI_REG16(0x3086), 0x8053 }, ++ { CCI_REG16(0x3086), 0x0d89 }, ++ { CCI_REG16(0x3086), 0x165c }, ++ { CCI_REG16(0x3086), 0x4586 }, ++ { CCI_REG16(0x3086), 0x170b }, ++ { CCI_REG16(0x3086), 0x5c05 }, ++ { CCI_REG16(0x3086), 0x8a60 }, ++ { CCI_REG16(0x3086), 0x4b91 }, ++ { CCI_REG16(0x3086), 0x4416 }, ++ { CCI_REG16(0x3086), 0x09c1 }, ++ { CCI_REG16(0x3086), 0x2ca9 }, ++ { CCI_REG16(0x3086), 0xab30 }, ++ { CCI_REG16(0x3086), 0x51b3 }, ++ { CCI_REG16(0x3086), 0x3d5a }, ++ { CCI_REG16(0x3086), 0x7e3d }, ++ { CCI_REG16(0x3086), 0x7e19 }, ++ { CCI_REG16(0x3086), 0x8000 }, ++ { CCI_REG16(0x3086), 0x8b1f }, ++ { CCI_REG16(0x3086), 0x2a1f }, ++ { CCI_REG16(0x3086), 0x83a2 }, ++ { CCI_REG16(0x3086), 0x7516 }, ++ { CCI_REG16(0x3086), 0xad33 }, ++ { CCI_REG16(0x3086), 0x450a }, ++ { CCI_REG16(0x3086), 0x7f53 }, ++ { CCI_REG16(0x3086), 0x8023 }, ++ { CCI_REG16(0x3086), 0x8c66 }, ++ { CCI_REG16(0x3086), 0x7f13 }, ++ { CCI_REG16(0x3086), 0x8184 }, ++ { CCI_REG16(0x3086), 0x1481 }, ++ { CCI_REG16(0x3086), 0x8031 }, ++ { CCI_REG16(0x3086), 0x3d64 }, ++ { CCI_REG16(0x3086), 0x452a }, ++ { CCI_REG16(0x3086), 0x9451 }, ++ { CCI_REG16(0x3086), 0x9e96 }, ++ { CCI_REG16(0x3086), 0x3d2b }, ++ { CCI_REG16(0x3086), 0x3d1b }, ++ { CCI_REG16(0x3086), 0x529f }, ++ { CCI_REG16(0x3086), 0x0e3d }, ++ { CCI_REG16(0x3086), 0x083d }, ++ { CCI_REG16(0x3086), 0x167e }, ++ { CCI_REG16(0x3086), 0x307e }, ++ { CCI_REG16(0x3086), 0x1175 }, ++ { CCI_REG16(0x3086), 0x163e }, ++ { CCI_REG16(0x3086), 0x970e }, ++ { CCI_REG16(0x3086), 0x82b2 }, ++ { CCI_REG16(0x3086), 0x3d7f }, ++ { CCI_REG16(0x3086), 0xac3e }, ++ { CCI_REG16(0x3086), 0x4502 }, ++ { CCI_REG16(0x3086), 0x7e11 }, ++ { CCI_REG16(0x3086), 0x7fd0 }, ++ { CCI_REG16(0x3086), 0x8000 }, ++ { CCI_REG16(0x3086), 0x8c66 }, ++ { CCI_REG16(0x3086), 0x7f90 }, ++ { CCI_REG16(0x3086), 0x8194 }, ++ { CCI_REG16(0x3086), 0x3f44 }, ++ { CCI_REG16(0x3086), 0x1681 }, ++ { CCI_REG16(0x3086), 0x8416 }, ++ { CCI_REG16(0x3086), 0x2c2c }, ++ { CCI_REG16(0x3086), 0x2c2c }, ++ { CCI_REG16(0x302a), 0x0005 }, ++ { CCI_REG16(0x302c), 0x0001 }, ++ { CCI_REG16(0x302e), 0x0003 }, ++ { CCI_REG16(0x3030), 0x0032 }, ++ { CCI_REG16(0x3036), 0x000a }, ++ { CCI_REG16(0x3038), 0x0001 }, ++ { CCI_REG16(0x30b0), 0x0028 }, ++ { CCI_REG16(0x31b0), 0x0082 }, ++ { CCI_REG16(0x31b2), 0x005c }, ++ { CCI_REG16(0x31b4), 0x5248 }, ++ { CCI_REG16(0x31b6), 0x3257 }, ++ { CCI_REG16(0x31b8), 0x904b }, ++ { CCI_REG16(0x31ba), 0x030b }, ++ { CCI_REG16(0x31bc), 0x8e09 }, ++ { CCI_REG16(0x3354), 0x002b }, ++ { CCI_REG16(0x31d0), 0x0000 }, ++ { CCI_REG16(0x31ae), 0x0204 }, ++ { CCI_REG16(0x3002), 0x0080 }, ++ { CCI_REG16(0x3004), 0x0148 }, ++ { CCI_REG16(0x3006), 0x043f }, ++ { CCI_REG16(0x3008), 0x0647 }, ++ { CCI_REG16(0x3064), 0x1802 }, ++ { CCI_REG16(0x300a), 0x04c4 }, ++ { CCI_REG16(0x300c), 0x04c4 }, ++ { CCI_REG16(0x30a2), 0x0001 }, ++ { CCI_REG16(0x30a6), 0x0001 }, ++ { CCI_REG16(0x3012), 0x010c }, ++ { CCI_REG16(0x3786), 0x0006 }, ++ { CCI_REG16(0x31ae), 0x0202 }, ++ { CCI_REG16(0x3088), 0x8050 }, ++ { CCI_REG16(0x3086), 0x9237 }, ++ { CCI_REG16(0x3044), 0x0410 }, ++ { CCI_REG16(0x3094), 0x03d4 }, ++ { CCI_REG16(0x3096), 0x0280 }, ++ { CCI_REG16(0x30ba), 0x7606 }, ++ { CCI_REG16(0x30b0), 0x0028 }, ++ { CCI_REG16(0x30ba), 0x7600 }, ++ { CCI_REG16(0x30fe), 0x002a }, ++ { CCI_REG16(0x31de), 0x0410 }, ++ { CCI_REG16(0x3ed6), 0x1435 }, ++ { CCI_REG16(0x3ed8), 0x9865 }, ++ { CCI_REG16(0x3eda), 0x7698 }, ++ { CCI_REG16(0x3edc), 0x99ff }, ++ { CCI_REG16(0x3ee2), 0xbb88 }, ++ { CCI_REG16(0x3ee4), 0x8836 }, ++ { CCI_REG16(0x3ef0), 0x1cf0 }, ++ { CCI_REG16(0x3ef2), 0x0000 }, ++ { CCI_REG16(0x3ef8), 0x6166 }, ++ { CCI_REG16(0x3efa), 0x3333 }, ++ { CCI_REG16(0x3efc), 0x6634 }, ++ { CCI_REG16(0x3088), 0x81ba }, ++ { CCI_REG16(0x3086), 0x3d02 }, ++ { CCI_REG16(0x3276), 0x05dc }, ++ { CCI_REG16(0x3f00), 0x9d05 }, ++ { CCI_REG16(0x3ed2), 0xfa86 }, ++ { CCI_REG16(0x3eee), 0xa4fe }, ++ { CCI_REG16(0x3ecc), 0x6e42 }, ++ { CCI_REG16(0x3ecc), 0x0e42 }, ++ { CCI_REG16(0x3eec), 0x0c0c }, ++ { CCI_REG16(0x3ee8), 0xaae4 }, ++ { CCI_REG16(0x3ee6), 0x3363 }, ++ { CCI_REG16(0x3ee6), 0x3363 }, ++ { CCI_REG16(0x3ee8), 0xaae4 }, ++ { CCI_REG16(0x3ee8), 0xaae4 }, ++ { CCI_REG16(0x3180), 0xc24f }, ++ { CCI_REG16(0x3102), 0x5000 }, ++ { CCI_REG16(0x3060), 0x000d }, ++ { CCI_REG16(0x3ed0), 0xff44 }, ++ { CCI_REG16(0x3ed2), 0xaa86 }, ++ { CCI_REG16(0x3ed4), 0x031f }, ++ { CCI_REG16(0x3eee), 0xa4aa }, ++}; ++ ++static const char * const ar0234_test_pattern_menu[] = { ++ "Disabled", ++ "Color Bars", ++ "Solid Color", ++ "Grey Color Bars", ++ "Walking 1s", ++}; ++ ++static const int ar0234_test_pattern_val[] = { ++ AR0234_TEST_PATTERN_DISABLE, ++ AR0234_TEST_PATTERN_COLOR_BARS, ++ AR0234_TEST_PATTERN_SOLID_COLOR, ++ AR0234_TEST_PATTERN_GREY_COLOR, ++ AR0234_TEST_PATTERN_WALKING, ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ 360000000ULL, ++}; ++ ++static const struct ar0234_mode supported_modes[] = { ++ { ++ .width = AR0234_COMMON_WIDTH, ++ .height = AR0234_COMMON_HEIGHT, ++ .hts = AR0234_HTS_DEFAULT, ++ .vts_def = AR0234_VTS_DEFAULT, ++ .code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mode_1280x960_10bit_2lane), ++ .regs = mode_1280x960_10bit_2lane, ++ }, ++ }, ++}; ++ ++struct ar0234 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ ++ /* V4L2 Controls */ ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *exposure; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hflip; ++ struct regmap *regmap; ++ unsigned long link_freq_bitmap; ++ const struct ar0234_mode *cur_mode; ++}; ++ ++static int ar0234_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ar0234 *ar0234 = ++ container_of(ctrl->handler, struct ar0234, ctrl_handler); ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ s64 exposure_max, exposure_def; ++ struct v4l2_subdev_state *state; ++ const struct v4l2_mbus_framefmt *format; ++ int ret; ++ ++ state = v4l2_subdev_get_locked_active_state(&ar0234->sd); ++ format = v4l2_subdev_state_get_format(state, 0); ++ ++ /* Propagate change of current control to all related controls */ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ /* Update max exposure while meeting expected vblanking */ ++ exposure_max = format->height + ctrl->val - ++ AR0234_EXPOSURE_MAX_MARGIN; ++ exposure_def = format->height - AR0234_EXPOSURE_MAX_MARGIN; ++ __v4l2_ctrl_modify_range(ar0234->exposure, ++ ar0234->exposure->minimum, ++ exposure_max, ar0234->exposure->step, ++ exposure_def); ++ } ++ ++ /* V4L2 controls values will be applied only when power is already up */ ++ if (!pm_runtime_get_if_in_use(&client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(ar0234->regmap, AR0234_REG_ANALOG_GAIN, ++ ctrl->val, NULL); ++ break; ++ ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = cci_write(ar0234->regmap, AR0234_REG_GLOBAL_GAIN, ++ ctrl->val, NULL); ++ break; ++ ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(ar0234->regmap, AR0234_REG_EXPOSURE, ++ ctrl->val, NULL); ++ break; ++ ++ case V4L2_CID_VBLANK: ++ ret = cci_write(ar0234->regmap, AR0234_REG_VTS, ++ ar0234->cur_mode->height + ctrl->val, NULL); ++ break; ++ ++ case V4L2_CID_HFLIP: ++ case V4L2_CID_VFLIP: ++ u64 reg; ++ ++ ret = cci_read(ar0234->regmap, AR0234_REG_ORIENTATION, ++ ®, NULL); ++ if (ret) ++ break; ++ ++ reg &= ~(AR0234_ORIENTATION_HFLIP | ++ AR0234_ORIENTATION_VFLIP); ++ if (ar0234->hflip->val) ++ reg |= AR0234_ORIENTATION_HFLIP; ++ if (ar0234->vflip->val) ++ reg |= AR0234_ORIENTATION_VFLIP; ++ ++ ret = cci_write(ar0234->regmap, AR0234_REG_ORIENTATION, ++ reg, NULL); ++ break; ++ ++ case V4L2_CID_TEST_PATTERN: ++ ret = cci_write(ar0234->regmap, AR0234_REG_TEST_PATTERN, ++ ar0234_test_pattern_val[ctrl->val], NULL); ++ break; ++ ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops ar0234_ctrl_ops = { ++ .s_ctrl = ar0234_set_ctrl, ++}; ++ ++static int ar0234_init_controls(struct ar0234 *ar0234) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ s64 exposure_max, vblank_max, vblank_def, hblank; ++ u32 link_freq_size; ++ int ret; ++ ++ ctrl_hdlr = &ar0234->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); ++ if (ret) ++ return ret; ++ ++ link_freq_size = ARRAY_SIZE(link_freq_menu_items) - 1; ++ ar0234->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &ar0234_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ link_freq_size, 0, ++ link_freq_menu_items); ++ if (ar0234->link_freq) ++ ar0234->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ AR0234_ANALOG_GAIN_MIN, AR0234_ANALOG_GAIN_MAX, ++ AR0234_ANALOG_GAIN_STEP, AR0234_ANALOG_GAIN_DEFAULT); ++ v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ AR0234_GLOBAL_GAIN_MIN, AR0234_GLOBAL_GAIN_MAX, ++ AR0234_GLOBAL_GAIN_STEP, AR0234_GLOBAL_GAIN_DEFAULT); ++ ++ exposure_max = ar0234->cur_mode->vts_def - AR0234_EXPOSURE_MAX_MARGIN; ++ ar0234->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ AR0234_EXPOSURE_MIN, exposure_max, ++ AR0234_EXPOSURE_STEP, ++ exposure_max); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ AR0234_PIXEL_RATE, AR0234_PIXEL_RATE, 1, ++ AR0234_PIXEL_RATE); ++ ++ vblank_max = AR0234_VTS_MAX - ar0234->cur_mode->height; ++ vblank_def = ar0234->cur_mode->vts_def - ar0234->cur_mode->height; ++ ar0234->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_VBLANK, 0, vblank_max, 1, ++ vblank_def); ++ hblank = AR0234_PPL_DEFAULT - ar0234->cur_mode->width; ++ ar0234->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_HBLANK, hblank, hblank, 1, ++ hblank); ++ if (ar0234->hblank) ++ ar0234->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ ar0234->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ar0234->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ar0234_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(ar0234_test_pattern_menu) - 1, ++ 0, 0, ar0234_test_pattern_menu); ++ ++ if (ctrl_hdlr->error) ++ return ctrl_hdlr->error; ++ ++ ret = v4l2_fwnode_device_parse(&client->dev, &props); ++ if (ret) ++ return ret; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ar0234_ctrl_ops, ++ &props); ++ if (ret) ++ return ret; ++ ++ ar0234->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++} ++ ++static void ar0234_update_pad_format(const struct ar0234_mode *mode, ++ struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->width = mode->width; ++ fmt->height = mode->height; ++ fmt->code = mode->code; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int ar0234_start_streaming(struct ar0234 *ar0234) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ const struct ar0234_reg_list *reg_list; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(&client->dev); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * Setting 0x301A.bit[0] will initiate a reset sequence: ++ * the frame being generated will be truncated. ++ */ ++ ret = cci_write(ar0234->regmap, AR0234_REG_MODE_SELECT, ++ AR0234_MODE_RESET, NULL); ++ if (ret) { ++ dev_err(&client->dev, "failed to reset"); ++ goto err_rpm_put; ++ } ++ ++ usleep_range(1000, 1500); ++ ++ reg_list = &ar0234->cur_mode->reg_list; ++ ret = cci_multi_reg_write(ar0234->regmap, reg_list->regs, ++ reg_list->num_of_regs, NULL); ++ if (ret) { ++ dev_err(&client->dev, "failed to set mode"); ++ goto err_rpm_put; ++ } ++ ++ ret = __v4l2_ctrl_handler_setup(ar0234->sd.ctrl_handler); ++ if (ret) ++ goto err_rpm_put; ++ ++ ret = cci_write(ar0234->regmap, AR0234_REG_MODE_SELECT, ++ AR0234_MODE_STREAMING, NULL); ++ if (ret) { ++ dev_err(&client->dev, "failed to start stream"); ++ goto err_rpm_put; ++ } ++ ++ return 0; ++ ++err_rpm_put: ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static int ar0234_stop_streaming(struct ar0234 *ar0234) ++{ ++ int ret; ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ ++ ret = cci_write(ar0234->regmap, AR0234_REG_MODE_SELECT, ++ AR0234_MODE_STANDBY, NULL); ++ if (ret < 0) ++ dev_err(&client->dev, "failed to stop stream"); ++ ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static int ar0234_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ar0234 *ar0234 = to_ar0234(sd); ++ struct v4l2_subdev_state *state; ++ int ret = 0; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ ++ if (enable) ++ ret = ar0234_start_streaming(ar0234); ++ else ++ ret = ar0234_stop_streaming(ar0234); ++ ++ /* vflip and hflip cannot change during streaming */ ++ __v4l2_ctrl_grab(ar0234->vflip, enable); ++ __v4l2_ctrl_grab(ar0234->hflip, enable); ++ v4l2_subdev_unlock_state(state); ++ ++ return ret; ++} ++ ++static int ar0234_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct ar0234 *ar0234 = to_ar0234(sd); ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ struct v4l2_rect *crop; ++ const struct ar0234_mode *mode; ++ s64 hblank; ++ int ret; ++ ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), ++ width, height, ++ fmt->format.width, ++ fmt->format.height); ++ ++ crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad); ++ crop->width = mode->width; ++ crop->height = mode->height; ++ ++ ar0234_update_pad_format(mode, &fmt->format); ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ ar0234->cur_mode = mode; ++ ++ hblank = AR0234_PPL_DEFAULT - mode->width; ++ ret = __v4l2_ctrl_modify_range(ar0234->hblank, hblank, hblank, ++ 1, hblank); ++ if (ret) { ++ dev_err(&client->dev, "HB ctrl range update failed"); ++ return ret; ++ } ++ ++ /* Update limits and set FPS to default */ ++ ret = __v4l2_ctrl_modify_range(ar0234->vblank, 0, ++ AR0234_VTS_MAX - mode->height, 1, ++ mode->vts_def - mode->height); ++ if (ret) { ++ dev_err(&client->dev, "VB ctrl range update failed"); ++ return ret; ++ } ++ ++ ret = __v4l2_ctrl_s_ctrl(ar0234->vblank, mode->vts_def - mode->height); ++ if (ret) { ++ dev_err(&client->dev, "VB ctrl set failed"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ar0234_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->index > 0) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SGRBG10_1X10; ++ ++ return 0; ++} ++ ++static int ar0234_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(supported_modes)) ++ return -EINVAL; ++ ++ if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) ++ return -EINVAL; ++ ++ fse->min_width = supported_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = supported_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int ar0234_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.top = AR0234_PIXEL_ARRAY_TOP; ++ sel->r.left = AR0234_PIXEL_ARRAY_LEFT; ++ sel->r.width = AR0234_COMMON_WIDTH; ++ sel->r.height = AR0234_COMMON_HEIGHT; ++ break; ++ ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *v4l2_subdev_state_get_crop(state, 0); ++ break; ++ ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = AR0234_NATIVE_WIDTH; ++ sel->r.height = AR0234_NATIVE_HEIGHT; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ar0234_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .pad = 0, ++ .format = { ++ .code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .width = AR0234_COMMON_WIDTH, ++ .height = AR0234_COMMON_HEIGHT, ++ }, ++ }; ++ ++ ar0234_set_format(sd, sd_state, &fmt); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops ar0234_video_ops = { ++ .s_stream = ar0234_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops ar0234_pad_ops = { ++ .set_fmt = ar0234_set_format, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .enum_mbus_code = ar0234_enum_mbus_code, ++ .enum_frame_size = ar0234_enum_frame_size, ++ .get_selection = ar0234_get_selection, ++}; ++ ++static const struct v4l2_subdev_core_ops ar0234_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_ops ar0234_subdev_ops = { ++ .core = &ar0234_core_ops, ++ .video = &ar0234_video_ops, ++ .pad = &ar0234_pad_ops, ++}; ++ ++static const struct media_entity_operations ar0234_subdev_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static const struct v4l2_subdev_internal_ops ar0234_internal_ops = { ++ .init_state = ar0234_init_state, ++}; ++ ++static int ar0234_parse_fwnode(struct ar0234 *ar0234, struct device *dev) ++{ ++ struct fwnode_handle *endpoint; ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ int ret; ++ ++ endpoint = ++ fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, ++ FWNODE_GRAPH_ENDPOINT_NEXT); ++ if (!endpoint) { ++ dev_err(dev, "endpoint node not found"); ++ return -EPROBE_DEFER; ++ } ++ ++ ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); ++ if (ret) { ++ dev_err(dev, "parsing endpoint node failed"); ++ goto out_err; ++ } ++ ++ /* Check the number of MIPI CSI2 data lanes */ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != 2 && ++ bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { ++ dev_err(dev, "only 2 or 4 data lanes are currently supported"); ++ goto out_err; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, ++ bus_cfg.nr_of_link_frequencies, ++ link_freq_menu_items, ++ ARRAY_SIZE(link_freq_menu_items), ++ &ar0234->link_freq_bitmap); ++ if (ret) ++ goto out_err; ++ ++out_err: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ fwnode_handle_put(endpoint); ++ return ret; ++} ++ ++static int ar0234_identify_module(struct ar0234 *ar0234) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); ++ int ret; ++ u64 val; ++ ++ ret = cci_read(ar0234->regmap, AR0234_REG_CHIP_ID, &val, NULL); ++ if (ret) ++ return ret; ++ ++ if (val != AR0234_CHIP_ID) { ++ dev_err(&client->dev, "chip id mismatch: %x!=%llx", ++ AR0234_CHIP_ID, val); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static void ar0234_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ar0234 *ar0234 = to_ar0234(sd); ++ ++ v4l2_async_unregister_subdev(&ar0234->sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&ar0234->sd.entity); ++ v4l2_ctrl_handler_free(&ar0234->ctrl_handler); ++ pm_runtime_disable(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static int ar0234_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct ar0234 *ar0234; ++ struct clk *xclk; ++ u32 xclk_freq; ++ int ret; ++ ++ ar0234 = devm_kzalloc(&client->dev, sizeof(*ar0234), GFP_KERNEL); ++ if (!ar0234) ++ return -ENOMEM; ++ ++ ret = ar0234_parse_fwnode(ar0234, dev); ++ if (ret) ++ return ret; ++ ++ ar0234->regmap = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(ar0234->regmap)) ++ return dev_err_probe(dev, PTR_ERR(ar0234->regmap), ++ "failed to init CCI"); ++ ++ v4l2_i2c_subdev_init(&ar0234->sd, client, &ar0234_subdev_ops); ++ ++ xclk = devm_clk_get(dev, NULL); ++ if (IS_ERR(xclk)) { ++ if (PTR_ERR(xclk) != -EPROBE_DEFER) ++ dev_err(dev, "failed to get xclk %ld", PTR_ERR(xclk)); ++ return PTR_ERR(xclk); ++ } ++ ++ xclk_freq = clk_get_rate(xclk); ++ if (xclk_freq != AR0234_XCLK_FREQ) { ++ dev_err(dev, "xclk frequency not supported: %d Hz", xclk_freq); ++ return -EINVAL; ++ } ++ ++ /* Check module identity */ ++ ret = ar0234_identify_module(ar0234); ++ if (ret) { ++ dev_err(dev, "failed to find sensor: %d", ret); ++ return ret; ++ } ++ ++ ar0234->cur_mode = &supported_modes[0]; ++ ret = ar0234_init_controls(ar0234); ++ if (ret) { ++ dev_err(&client->dev, "failed to init controls: %d", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ ar0234->sd.internal_ops = &ar0234_internal_ops; ++ ar0234->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ ar0234->sd.entity.ops = &ar0234_subdev_entity_ops; ++ ar0234->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ar0234->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(&ar0234->sd.entity, 1, &ar0234->pad); ++ if (ret) { ++ dev_err(&client->dev, "failed to init entity pads: %d", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ ar0234->sd.state_lock = ar0234->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&ar0234->sd); ++ if (ret < 0) { ++ dev_err(dev, "v4l2 subdev init error: %d", ret); ++ goto probe_error_media_entity_cleanup; ++ } ++ ++ /* ++ * Device is already turned on by i2c-core with ACPI domain PM. ++ * Enable runtime PM and turn off the device. ++ */ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_enable(&client->dev); ++ pm_runtime_idle(&client->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&ar0234->sd); ++ if (ret < 0) { ++ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ++ ret); ++ goto probe_error_rpm; ++ } ++ ++ return 0; ++probe_error_rpm: ++ pm_runtime_disable(&client->dev); ++ v4l2_subdev_cleanup(&ar0234->sd); ++ ++probe_error_media_entity_cleanup: ++ media_entity_cleanup(&ar0234->sd.entity); ++ ++probe_error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(ar0234->sd.ctrl_handler); ++ ++ return ret; ++} ++ ++static const struct acpi_device_id ar0234_acpi_ids[] = { ++ { "INTC10C0" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(acpi, ar0234_acpi_ids); ++ ++static struct i2c_driver ar0234_i2c_driver = { ++ .driver = { ++ .name = "ar0234", ++ .acpi_match_table = ACPI_PTR(ar0234_acpi_ids), ++ }, ++ .probe = ar0234_probe, ++ .remove = ar0234_remove, ++}; ++ ++module_i2c_driver(ar0234_i2c_driver); ++ ++MODULE_DESCRIPTION("ON Semiconductor ar0234 sensor driver"); ++MODULE_AUTHOR("Dongcheng Yan "); ++MODULE_AUTHOR("Hao Yao "); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +index a0e9a71580b5..b6322bb230c3 100644 +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -83,6 +83,8 @@ 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), ++ /* OnSemiconductor ar0234 */ ++ IPU_SENSOR_CONFIG("INTC10C0", 1, 360000000), + }; + + static const struct ipu_property_names prop_names = { +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0001-media-pci-intel-psys-driver.patch b/kernel_patches/patch_6.12_mainline/0001-media-pci-intel-psys-driver.patch new file mode 100644 index 000000000000..5c5c77c2c1c3 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0001-media-pci-intel-psys-driver.patch @@ -0,0 +1,8433 @@ +From c2517dfb497a61f64d73630215733ea919da2d7e Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 6 Dec 2024 14:31:48 +0800 +Subject: [PATCH] media: pci: intel: psys driver + +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/psys/Makefile | 27 + + .../media/pci/intel/ipu6/psys/ipu-fw-psys.c | 434 +++++ + .../media/pci/intel/ipu6/psys/ipu-fw-psys.h | 382 ++++ + .../pci/intel/ipu6/psys/ipu-fw-resources.c | 103 ++ + .../pci/intel/ipu6/psys/ipu-platform-psys.h | 78 + + .../intel/ipu6/psys/ipu-platform-resources.h | 103 ++ + .../pci/intel/ipu6/psys/ipu-psys-compat32.c | 222 +++ + drivers/media/pci/intel/ipu6/psys/ipu-psys.c | 1575 +++++++++++++++++ + drivers/media/pci/intel/ipu6/psys/ipu-psys.h | 324 ++++ + .../media/pci/intel/ipu6/psys/ipu-resources.c | 868 +++++++++ + .../pci/intel/ipu6/psys/ipu6-fw-resources.c | 607 +++++++ + .../pci/intel/ipu6/psys/ipu6-l-scheduler.c | 620 +++++++ + .../intel/ipu6/psys/ipu6-platform-resources.h | 196 ++ + drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c | 557 ++++++ + drivers/media/pci/intel/ipu6/psys/ipu6-ppg.h | 38 + + .../media/pci/intel/ipu6/psys/ipu6-psys-gpc.c | 209 +++ + drivers/media/pci/intel/ipu6/psys/ipu6-psys.c | 1049 +++++++++++ + .../pci/intel/ipu6/psys/ipu6ep-fw-resources.c | 393 ++++ + .../ipu6/psys/ipu6ep-platform-resources.h | 42 + + .../pci/intel/ipu6/psys/ipu6se-fw-resources.c | 194 ++ + .../ipu6/psys/ipu6se-platform-resources.h | 103 ++ + include/uapi/linux/ipu-psys.h | 121 ++ + 22 files changed, 8245 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/psys/Makefile + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-fw-resources.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-platform-psys.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-platform-resources.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-psys-compat32.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-psys.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-psys.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu-resources.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-fw-resources.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-l-scheduler.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-platform-resources.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-ppg.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-psys-gpc.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6-psys.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6ep-fw-resources.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6ep-platform-resources.h + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6se-fw-resources.c + create mode 100644 drivers/media/pci/intel/ipu6/psys/ipu6se-platform-resources.h + create mode 100644 include/uapi/linux/ipu-psys.h + +diff --git a/drivers/media/pci/intel/ipu6/psys/Makefile b/drivers/media/pci/intel/ipu6/psys/Makefile +new file mode 100644 +index 000000000000..465fb82bd932 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/Makefile +@@ -0,0 +1,27 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++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) ++ifneq ($(EXTERNAL_BUILD), 1) ++src := $(srctree)/$(src) ++endif ++endif ++ ++intel-ipu6-psys-objs += ipu-psys.o \ ++ ipu6-psys.o \ ++ ipu-resources.o \ ++ ipu6-l-scheduler.o \ ++ ipu6-ppg.o ++ ++intel-ipu6-psys-objs += ipu-fw-resources.o \ ++ ipu6-fw-resources.o \ ++ ipu6se-fw-resources.o \ ++ ipu6ep-fw-resources.o \ ++ ipu-fw-psys.o ++ ++obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-psys.o ++ ++ifeq ($(is_kernel_lt_6_10), 1) ++ccflags-y += -I$(src)/../ipu6/ ++endif ++ccflags-y += -I$(src)/../ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.c b/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.c +new file mode 100644 +index 000000000000..07d9e57d3feb +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.c +@@ -0,0 +1,434 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2016 - 2024 Intel Corporation ++ ++#include ++ ++#include ++ ++#include ++#include "ipu6-fw-com.h" ++#include "ipu-fw-psys.h" ++#include "ipu-psys.h" ++ ++int ipu_fw_psys_pg_start(struct ipu_psys_kcmd *kcmd) ++{ ++ kcmd->kpg->pg->state = IPU_FW_PSYS_PROCESS_GROUP_STARTED; ++ return 0; ++} ++ ++int ipu_fw_psys_pg_disown(struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_fw_psys_cmd *psys_cmd; ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ int ret = 0; ++ ++ psys_cmd = ipu6_send_get_token(kcmd->fh->psys->fwcom, 0); ++ if (!psys_cmd) { ++ dev_err(dev, "%s failed to get token!\n", __func__); ++ kcmd->pg_user = NULL; ++ ret = -ENODATA; ++ goto out; ++ } ++ psys_cmd->command = IPU_FW_PSYS_PROCESS_GROUP_CMD_START; ++ psys_cmd->msg = 0; ++ psys_cmd->context_handle = kcmd->kpg->pg->ipu_virtual_address; ++ ipu6_send_put_token(kcmd->fh->psys->fwcom, 0); ++ ++out: ++ return ret; ++} ++ ++int ipu_fw_psys_ppg_suspend(struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ struct ipu_fw_psys_cmd *psys_cmd; ++ int ret = 0; ++ ++ /* ppg suspend cmd uses QUEUE_DEVICE_ID instead of QUEUE_COMMAND_ID */ ++ psys_cmd = ipu6_send_get_token(kcmd->fh->psys->fwcom, 1); ++ if (!psys_cmd) { ++ dev_err(dev, "%s failed to get token!\n", __func__); ++ kcmd->pg_user = NULL; ++ ret = -ENODATA; ++ goto out; ++ } ++ psys_cmd->command = IPU_FW_PSYS_PROCESS_GROUP_CMD_SUSPEND; ++ psys_cmd->msg = 0; ++ psys_cmd->context_handle = kcmd->kpg->pg->ipu_virtual_address; ++ ipu6_send_put_token(kcmd->fh->psys->fwcom, 1); ++ ++out: ++ return ret; ++} ++ ++int ipu_fw_psys_ppg_resume(struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ struct ipu_fw_psys_cmd *psys_cmd; ++ int ret = 0; ++ ++ psys_cmd = ipu6_send_get_token(kcmd->fh->psys->fwcom, 0); ++ if (!psys_cmd) { ++ dev_err(dev, "%s failed to get token!\n", __func__); ++ kcmd->pg_user = NULL; ++ ret = -ENODATA; ++ goto out; ++ } ++ psys_cmd->command = IPU_FW_PSYS_PROCESS_GROUP_CMD_RESUME; ++ psys_cmd->msg = 0; ++ psys_cmd->context_handle = kcmd->kpg->pg->ipu_virtual_address; ++ ipu6_send_put_token(kcmd->fh->psys->fwcom, 0); ++ ++out: ++ return ret; ++} ++ ++int ipu_fw_psys_pg_abort(struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ struct ipu_fw_psys_cmd *psys_cmd; ++ int ret = 0; ++ ++ psys_cmd = ipu6_send_get_token(kcmd->fh->psys->fwcom, 0); ++ if (!psys_cmd) { ++ dev_err(dev, "%s failed to get token!\n", __func__); ++ kcmd->pg_user = NULL; ++ ret = -ENODATA; ++ goto out; ++ } ++ psys_cmd->command = IPU_FW_PSYS_PROCESS_GROUP_CMD_STOP; ++ psys_cmd->msg = 0; ++ psys_cmd->context_handle = kcmd->kpg->pg->ipu_virtual_address; ++ ipu6_send_put_token(kcmd->fh->psys->fwcom, 0); ++ ++out: ++ return ret; ++} ++ ++int ipu_fw_psys_pg_submit(struct ipu_psys_kcmd *kcmd) ++{ ++ kcmd->kpg->pg->state = IPU_FW_PSYS_PROCESS_GROUP_BLOCKED; ++ return 0; ++} ++ ++int ipu_fw_psys_rcv_event(struct ipu_psys *psys, ++ struct ipu_fw_psys_event *event) ++{ ++ void *rcv; ++ ++ rcv = ipu6_recv_get_token(psys->fwcom, 0); ++ if (!rcv) ++ return 0; ++ ++ memcpy(event, rcv, sizeof(*event)); ++ ipu6_recv_put_token(psys->fwcom, 0); ++ return 1; ++} ++ ++int ipu_fw_psys_terminal_set(struct ipu_fw_psys_terminal *terminal, ++ int terminal_idx, ++ struct ipu_psys_kcmd *kcmd, ++ u32 buffer, unsigned int size) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ u32 type; ++ u32 buffer_state; ++ ++ type = terminal->terminal_type; ++ ++ switch (type) { ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM_CONTROL_INIT: ++ buffer_state = IPU_FW_PSYS_BUFFER_UNDEFINED; ++ break; ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_STREAM: ++ case IPU_FW_PSYS_TERMINAL_TYPE_DATA_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_STATE_IN: ++ buffer_state = IPU_FW_PSYS_BUFFER_FULL; ++ break; ++ case IPU_FW_PSYS_TERMINAL_TYPE_DATA_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_STATE_OUT: ++ buffer_state = IPU_FW_PSYS_BUFFER_EMPTY; ++ break; ++ default: ++ dev_err(dev, "unknown terminal type: 0x%x\n", type); ++ return -EAGAIN; ++ } ++ ++ if (type == IPU_FW_PSYS_TERMINAL_TYPE_DATA_IN || ++ type == IPU_FW_PSYS_TERMINAL_TYPE_DATA_OUT) { ++ struct ipu_fw_psys_data_terminal *dterminal = ++ (struct ipu_fw_psys_data_terminal *)terminal; ++ dterminal->connection_type = IPU_FW_PSYS_CONNECTION_MEMORY; ++ dterminal->frame.data_bytes = size; ++ if (!ipu_fw_psys_pg_get_protocol(kcmd)) ++ dterminal->frame.data = buffer; ++ else ++ dterminal->frame.data_index = terminal_idx; ++ dterminal->frame.buffer_state = buffer_state; ++ } else { ++ struct ipu_fw_psys_param_terminal *pterminal = ++ (struct ipu_fw_psys_param_terminal *)terminal; ++ if (!ipu_fw_psys_pg_get_protocol(kcmd)) ++ pterminal->param_payload.buffer = buffer; ++ else ++ pterminal->param_payload.terminal_index = terminal_idx; ++ } ++ return 0; ++} ++ ++void ipu_fw_psys_pg_dump(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd, const char *note) ++{ ++ ipu6_fw_psys_pg_dump(psys, kcmd, note); ++} ++ ++int ipu_fw_psys_pg_get_id(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->ID; ++} ++ ++int ipu_fw_psys_pg_get_terminal_count(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->terminal_count; ++} ++ ++int ipu_fw_psys_pg_get_size(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->size; ++} ++ ++int ipu_fw_psys_pg_set_ipu_vaddress(struct ipu_psys_kcmd *kcmd, ++ dma_addr_t vaddress) ++{ ++ kcmd->kpg->pg->ipu_virtual_address = vaddress; ++ return 0; ++} ++ ++struct ipu_fw_psys_terminal *ipu_fw_psys_pg_get_terminal(struct ipu_psys_kcmd ++ *kcmd, int index) ++{ ++ struct ipu_fw_psys_terminal *terminal; ++ u16 *terminal_offset_table; ++ ++ terminal_offset_table = ++ (uint16_t *)((char *)kcmd->kpg->pg + ++ kcmd->kpg->pg->terminals_offset); ++ terminal = (struct ipu_fw_psys_terminal *) ++ ((char *)kcmd->kpg->pg + terminal_offset_table[index]); ++ return terminal; ++} ++ ++void ipu_fw_psys_pg_set_token(struct ipu_psys_kcmd *kcmd, u64 token) ++{ ++ kcmd->kpg->pg->token = (u64)token; ++} ++ ++u64 ipu_fw_psys_pg_get_token(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->token; ++} ++ ++int ipu_fw_psys_pg_get_protocol(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->protocol_version; ++} ++ ++int ipu_fw_psys_ppg_set_buffer_set(struct ipu_psys_kcmd *kcmd, ++ struct ipu_fw_psys_terminal *terminal, ++ int terminal_idx, u32 buffer) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ struct ipu_fw_psys_buffer_set *buf_set = kcmd->kbuf_set->buf_set; ++ u32 buffer_state; ++ u32 *buffer_ptr; ++ u32 type; ++ ++ type = terminal->terminal_type; ++ ++ switch (type) { ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM: ++ case IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM_CONTROL_INIT: ++ buffer_state = IPU_FW_PSYS_BUFFER_UNDEFINED; ++ break; ++ case IPU_FW_PSYS_TERMINAL_TYPE_PARAM_STREAM: ++ case IPU_FW_PSYS_TERMINAL_TYPE_DATA_IN: ++ case IPU_FW_PSYS_TERMINAL_TYPE_STATE_IN: ++ buffer_state = IPU_FW_PSYS_BUFFER_FULL; ++ break; ++ case IPU_FW_PSYS_TERMINAL_TYPE_DATA_OUT: ++ case IPU_FW_PSYS_TERMINAL_TYPE_STATE_OUT: ++ buffer_state = IPU_FW_PSYS_BUFFER_EMPTY; ++ break; ++ default: ++ dev_err(dev, "unknown terminal type: 0x%x\n", type); ++ return -EAGAIN; ++ } ++ ++ buffer_ptr = (u32 *)((char *)buf_set + sizeof(*buf_set) + ++ terminal_idx * sizeof(*buffer_ptr)); ++ ++ *buffer_ptr = buffer; ++ ++ if (type == IPU_FW_PSYS_TERMINAL_TYPE_DATA_IN || ++ type == IPU_FW_PSYS_TERMINAL_TYPE_DATA_OUT) { ++ struct ipu_fw_psys_data_terminal *dterminal = ++ (struct ipu_fw_psys_data_terminal *)terminal; ++ dterminal->frame.buffer_state = buffer_state; ++ } ++ ++ return 0; ++} ++ ++size_t ipu_fw_psys_ppg_get_buffer_set_size(struct ipu_psys_kcmd *kcmd) ++{ ++ return (sizeof(struct ipu_fw_psys_buffer_set) + ++ kcmd->kpg->pg->terminal_count * sizeof(u32)); ++} ++ ++int ++ipu_fw_psys_ppg_buffer_set_vaddress(struct ipu_fw_psys_buffer_set *buf_set, ++ u32 vaddress) ++{ ++ buf_set->ipu_virtual_address = vaddress; ++ return 0; ++} ++ ++int ipu_fw_psys_ppg_buffer_set_set_kernel_enable_bitmap( ++ struct ipu_fw_psys_buffer_set *buf_set, ++ u32 *kernel_enable_bitmap) ++{ ++ memcpy(buf_set->kernel_enable_bitmap, (u8 *)kernel_enable_bitmap, ++ sizeof(buf_set->kernel_enable_bitmap)); ++ return 0; ++} ++ ++struct ipu_fw_psys_buffer_set * ++ipu_fw_psys_ppg_create_buffer_set(struct ipu_psys_kcmd *kcmd, ++ void *kaddr, u32 frame_counter) ++{ ++ struct ipu_fw_psys_buffer_set *buffer_set = NULL; ++ unsigned int i; ++ ++ buffer_set = (struct ipu_fw_psys_buffer_set *)kaddr; ++ ++ /* ++ * Set base struct members ++ */ ++ buffer_set->ipu_virtual_address = 0; ++ buffer_set->process_group_handle = kcmd->kpg->pg->ipu_virtual_address; ++ buffer_set->frame_counter = frame_counter; ++ buffer_set->terminal_count = kcmd->kpg->pg->terminal_count; ++ ++ /* ++ * Initialize adjacent buffer addresses ++ */ ++ for (i = 0; i < buffer_set->terminal_count; i++) { ++ u32 *buffer = ++ (u32 *)((char *)buffer_set + ++ sizeof(*buffer_set) + sizeof(u32) * i); ++ ++ *buffer = 0; ++ } ++ ++ return buffer_set; ++} ++ ++int ipu_fw_psys_ppg_enqueue_bufs(struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &kcmd->fh->psys->adev->auxdev.dev; ++ struct ipu_fw_psys_cmd *psys_cmd; ++ unsigned int queue_id; ++ int ret = 0; ++ unsigned int size; ++ ++ if (ipu_ver == IPU6_VER_6SE) ++ size = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ else ++ size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ queue_id = kcmd->kpg->pg->base_queue_id; ++ ++ if (queue_id >= size) ++ return -EINVAL; ++ ++ psys_cmd = ipu6_send_get_token(kcmd->fh->psys->fwcom, queue_id); ++ if (!psys_cmd) { ++ dev_err(dev, "%s failed to get token!\n", __func__); ++ kcmd->pg_user = NULL; ++ return -ENODATA; ++ } ++ ++ psys_cmd->command = IPU_FW_PSYS_PROCESS_GROUP_CMD_RUN; ++ psys_cmd->msg = 0; ++ psys_cmd->context_handle = kcmd->kbuf_set->buf_set->ipu_virtual_address; ++ ++ ipu6_send_put_token(kcmd->fh->psys->fwcom, queue_id); ++ ++ return ret; ++} ++ ++u8 ipu_fw_psys_ppg_get_base_queue_id(struct ipu_psys_kcmd *kcmd) ++{ ++ return kcmd->kpg->pg->base_queue_id; ++} ++ ++void ipu_fw_psys_ppg_set_base_queue_id(struct ipu_psys_kcmd *kcmd, u8 queue_id) ++{ ++ kcmd->kpg->pg->base_queue_id = queue_id; ++} ++ ++int ipu_fw_psys_open(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ int retry = IPU_PSYS_OPEN_RETRY, retval; ++ ++ retval = ipu6_fw_com_open(psys->fwcom); ++ if (retval) { ++ dev_err(dev, "fw com open failed.\n"); ++ return retval; ++ } ++ ++ do { ++ usleep_range(IPU_PSYS_OPEN_TIMEOUT_US, ++ IPU_PSYS_OPEN_TIMEOUT_US + 10); ++ retval = ipu6_fw_com_ready(psys->fwcom); ++ if (retval) { ++ dev_dbg(dev, "psys port open ready!\n"); ++ break; ++ } ++ retry--; ++ } while (retry > 0); ++ ++ if (!retry) { ++ dev_err(dev, "psys port open ready failed\n"); ++ ipu6_fw_com_close(psys->fwcom); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++int ipu_fw_psys_close(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ int retval; ++ ++ retval = ipu6_fw_com_close(psys->fwcom); ++ if (retval) { ++ dev_err(dev, "fw com close failed.\n"); ++ return retval; ++ } ++ return retval; ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.h b/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.h +new file mode 100644 +index 000000000000..2d20448140f3 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.h +@@ -0,0 +1,382 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2016 - 2024 Intel Corporation */ ++ ++#ifndef IPU_FW_PSYS_H ++#define IPU_FW_PSYS_H ++ ++#include "ipu6-platform-resources.h" ++#include "ipu6se-platform-resources.h" ++#include "ipu6ep-platform-resources.h" ++ ++#define IPU_FW_PSYS_CMD_QUEUE_SIZE 0x20 ++#define IPU_FW_PSYS_EVENT_QUEUE_SIZE 0x40 ++ ++#define IPU_FW_PSYS_CMD_BITS 64 ++#define IPU_FW_PSYS_EVENT_BITS 128 ++ ++enum { ++ IPU_FW_PSYS_EVENT_TYPE_SUCCESS = 0, ++ IPU_FW_PSYS_EVENT_TYPE_UNKNOWN_ERROR = 1, ++ IPU_FW_PSYS_EVENT_TYPE_RET_REM_OBJ_NOT_FOUND = 2, ++ IPU_FW_PSYS_EVENT_TYPE_RET_REM_OBJ_TOO_BIG = 3, ++ IPU_FW_PSYS_EVENT_TYPE_RET_REM_OBJ_DDR_TRANS_ERR = 4, ++ IPU_FW_PSYS_EVENT_TYPE_RET_REM_OBJ_NULL_PKG_DIR_ADDR = 5, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_LOAD_FRAME_ERR = 6, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_LOAD_FRAGMENT_ERR = 7, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_PROCESS_COUNT_ZERO = 8, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_PROCESS_INIT_ERR = 9, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_ABORT = 10, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_NULL = 11, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_VALIDATION_ERR = 12, ++ IPU_FW_PSYS_EVENT_TYPE_PROC_GRP_INVALID_FRAME = 13 ++}; ++ ++enum { ++ IPU_FW_PSYS_EVENT_QUEUE_MAIN_ID, ++ IPU_FW_PSYS_N_PSYS_EVENT_QUEUE_ID ++}; ++ ++enum { ++ IPU_FW_PSYS_PROCESS_GROUP_ERROR = 0, ++ IPU_FW_PSYS_PROCESS_GROUP_CREATED, ++ IPU_FW_PSYS_PROCESS_GROUP_READY, ++ IPU_FW_PSYS_PROCESS_GROUP_BLOCKED, ++ IPU_FW_PSYS_PROCESS_GROUP_STARTED, ++ IPU_FW_PSYS_PROCESS_GROUP_RUNNING, ++ IPU_FW_PSYS_PROCESS_GROUP_STALLED, ++ IPU_FW_PSYS_PROCESS_GROUP_STOPPED, ++ IPU_FW_PSYS_N_PROCESS_GROUP_STATES ++}; ++ ++enum { ++ IPU_FW_PSYS_CONNECTION_MEMORY = 0, ++ IPU_FW_PSYS_CONNECTION_MEMORY_STREAM, ++ IPU_FW_PSYS_CONNECTION_STREAM, ++ IPU_FW_PSYS_N_CONNECTION_TYPES ++}; ++ ++enum { ++ IPU_FW_PSYS_BUFFER_NULL = 0, ++ IPU_FW_PSYS_BUFFER_UNDEFINED, ++ IPU_FW_PSYS_BUFFER_EMPTY, ++ IPU_FW_PSYS_BUFFER_NONEMPTY, ++ IPU_FW_PSYS_BUFFER_FULL, ++ IPU_FW_PSYS_N_BUFFER_STATES ++}; ++ ++enum { ++ IPU_FW_PSYS_TERMINAL_TYPE_DATA_IN = 0, ++ IPU_FW_PSYS_TERMINAL_TYPE_DATA_OUT, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_STREAM, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_IN, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_CACHED_OUT, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_IN, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SPATIAL_OUT, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_IN, ++ IPU_FW_PSYS_TERMINAL_TYPE_PARAM_SLICED_OUT, ++ IPU_FW_PSYS_TERMINAL_TYPE_STATE_IN, ++ IPU_FW_PSYS_TERMINAL_TYPE_STATE_OUT, ++ IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM, ++ IPU_FW_PSYS_TERMINAL_TYPE_PROGRAM_CONTROL_INIT, ++ IPU_FW_PSYS_N_TERMINAL_TYPES ++}; ++ ++enum { ++ IPU_FW_PSYS_COL_DIMENSION = 0, ++ IPU_FW_PSYS_ROW_DIMENSION = 1, ++ IPU_FW_PSYS_N_DATA_DIMENSION = 2 ++}; ++ ++enum { ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_NOP = 0, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_SUBMIT, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_ATTACH, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_DETACH, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_START, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_DISOWN, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_RUN, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_STOP, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_SUSPEND, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_RESUME, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_ABORT, ++ IPU_FW_PSYS_PROCESS_GROUP_CMD_RESET, ++ IPU_FW_PSYS_N_PROCESS_GROUP_CMDS ++}; ++ ++enum { ++ IPU_FW_PSYS_PROCESS_GROUP_PROTOCOL_LEGACY = 0, ++ IPU_FW_PSYS_PROCESS_GROUP_PROTOCOL_PPG, ++ IPU_FW_PSYS_PROCESS_GROUP_N_PROTOCOLS ++}; ++ ++struct __packed ipu_fw_psys_process_group { ++ u64 token; ++ u64 private_token; ++ u32 routing_bitmap[IPU_FW_PSYS_RBM_NOF_ELEMS]; ++ u32 kernel_bitmap[IPU_FW_PSYS_KBM_NOF_ELEMS]; ++ u32 size; ++ u32 psys_server_init_cycles; ++ u32 pg_load_start_ts; ++ u32 pg_load_cycles; ++ u32 pg_init_cycles; ++ u32 pg_processing_cycles; ++ u32 pg_next_frame_init_cycles; ++ u32 pg_complete_cycles; ++ u32 ID; ++ u32 state; ++ u32 ipu_virtual_address; ++ u32 resource_bitmap; ++ u16 fragment_count; ++ u16 fragment_state; ++ u16 fragment_limit; ++ u16 processes_offset; ++ u16 terminals_offset; ++ u8 process_count; ++ u8 terminal_count; ++ u8 subgraph_count; ++ u8 protocol_version; ++ u8 base_queue_id; ++ u8 num_queues; ++ u8 mask_irq; ++ u8 error_handling_enable; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_PROCESS_GROUP_STRUCT]; ++}; ++ ++struct ipu_fw_psys_srv_init { ++ void *host_ddr_pkg_dir; ++ u32 ddr_pkg_dir_address; ++ u32 pkg_dir_size; ++ ++ u32 icache_prefetch_sp; ++ u32 icache_prefetch_isp; ++}; ++ ++struct __packed ipu_fw_psys_cmd { ++ u16 command; ++ u16 msg; ++ u32 context_handle; ++}; ++ ++struct __packed ipu_fw_psys_event { ++ u16 status; ++ u16 command; ++ u32 context_handle; ++ u64 token; ++}; ++ ++struct ipu_fw_psys_terminal { ++ u32 terminal_type; ++ s16 parent_offset; ++ u16 size; ++ u16 tm_index; ++ u8 ID; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_TERMINAL_STRUCT]; ++}; ++ ++struct ipu_fw_psys_param_payload { ++ u64 host_buffer; ++ u32 buffer; ++ u32 terminal_index; ++}; ++ ++struct ipu_fw_psys_param_terminal { ++ struct ipu_fw_psys_terminal base; ++ struct ipu_fw_psys_param_payload param_payload; ++ u16 param_section_desc_offset; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_PARAM_TERMINAL_STRUCT]; ++}; ++ ++struct ipu_fw_psys_frame { ++ u32 buffer_state; ++ u32 access_type; ++ u32 pointer_state; ++ u32 access_scope; ++ u32 data; ++ u32 data_index; ++ u32 data_bytes; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_FRAME_STRUCT]; ++}; ++ ++struct ipu_fw_psys_frame_descriptor { ++ u32 frame_format_type; ++ u32 plane_count; ++ u32 plane_offsets[IPU_FW_PSYS_N_FRAME_PLANES]; ++ u32 stride[1]; ++ u32 ts_offsets[IPU_FW_PSYS_N_FRAME_PLANES]; ++ u16 dimension[2]; ++ u16 size; ++ u8 bpp; ++ u8 bpe; ++ u8 is_compressed; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_FRAME_DESC_STRUCT]; ++}; ++ ++struct ipu_fw_psys_stream { ++ u64 dummy; ++}; ++ ++struct ipu_fw_psys_data_terminal { ++ struct ipu_fw_psys_terminal base; ++ struct ipu_fw_psys_frame_descriptor frame_descriptor; ++ struct ipu_fw_psys_frame frame; ++ struct ipu_fw_psys_stream stream; ++ u32 reserved; ++ u32 connection_type; ++ u16 fragment_descriptor_offset; ++ u8 kernel_id; ++ u8 subgraph_id; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_DATA_TERMINAL_STRUCT]; ++}; ++ ++struct ipu_fw_psys_buffer_set { ++ u64 token; ++ u32 kernel_enable_bitmap[IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS]; ++ u32 terminal_enable_bitmap[IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS]; ++ u32 routing_enable_bitmap[IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS]; ++ u32 rbm[IPU_FW_PSYS_RBM_NOF_ELEMS]; ++ u32 ipu_virtual_address; ++ u32 process_group_handle; ++ u16 terminal_count; ++ u8 frame_counter; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_BUFFER_SET_STRUCT]; ++}; ++ ++struct ipu_fw_psys_program_group_manifest { ++ u32 kernel_bitmap[IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS]; ++ u32 ID; ++ u16 program_manifest_offset; ++ u16 terminal_manifest_offset; ++ u16 private_data_offset; ++ u16 rbm_manifest_offset; ++ u16 size; ++ u8 alignment; ++ u8 kernel_count; ++ u8 program_count; ++ u8 terminal_count; ++ u8 subgraph_count; ++ u8 reserved[5]; ++}; ++ ++struct ipu_fw_generic_program_manifest { ++ u16 *dev_chn_size; ++ u16 *dev_chn_offset; ++ u16 *ext_mem_size; ++ u16 *ext_mem_offset; ++ u8 cell_id; ++ u8 cells[IPU_FW_PSYS_PROCESS_MAX_CELLS]; ++ u8 cell_type_id; ++ u8 *is_dfm_relocatable; ++ u32 *dfm_port_bitmap; ++ u32 *dfm_active_port_bitmap; ++}; ++ ++struct ipu_fw_resource_definitions { ++ u32 num_cells; ++ u32 num_cells_type; ++ const u8 *cells; ++ u32 num_dev_channels; ++ const u16 *dev_channels; ++ ++ u32 num_ext_mem_types; ++ u32 num_ext_mem_ids; ++ const u16 *ext_mem_ids; ++ ++ u32 num_dfm_ids; ++ const u16 *dfms; ++ ++ u32 cell_mem_row; ++ const u8 *cell_mem; ++}; ++ ++struct ipu6_psys_hw_res_variant { ++ unsigned int queue_num; ++ unsigned int cell_num; ++ int (*set_proc_dev_chn)(struct ipu_fw_psys_process *ptr, u16 offset, ++ u16 value); ++ int (*set_proc_dfm_bitmap)(struct ipu_fw_psys_process *ptr, ++ u16 id, u32 bitmap, u32 active_bitmap); ++ int (*set_proc_ext_mem)(struct ipu_fw_psys_process *ptr, ++ u16 type_id, u16 mem_id, u16 offset); ++ int (*get_pgm_by_proc)(struct ipu_fw_generic_program_manifest *gen_pm, ++ const struct ipu_fw_psys_program_group_manifest ++ *pg_manifest, ++ struct ipu_fw_psys_process *process); ++}; ++struct ipu_psys_kcmd; ++struct ipu_psys; ++int ipu_fw_psys_pg_start(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_disown(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_abort(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_submit(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_ppg_suspend(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_ppg_resume(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_load_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_init_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_processing_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_server_init_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_next_frame_init_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_complete_cycles(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_rcv_event(struct ipu_psys *psys, ++ struct ipu_fw_psys_event *event); ++int ipu_fw_psys_terminal_set(struct ipu_fw_psys_terminal *terminal, ++ int terminal_idx, ++ struct ipu_psys_kcmd *kcmd, ++ u32 buffer, unsigned int size); ++void ipu_fw_psys_pg_dump(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd, const char *note); ++int ipu_fw_psys_pg_get_id(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_get_terminal_count(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_get_size(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_pg_set_ipu_vaddress(struct ipu_psys_kcmd *kcmd, ++ dma_addr_t vaddress); ++struct ipu_fw_psys_terminal *ipu_fw_psys_pg_get_terminal(struct ipu_psys_kcmd ++ *kcmd, int index); ++void ipu_fw_psys_pg_set_token(struct ipu_psys_kcmd *kcmd, u64 token); ++u64 ipu_fw_psys_pg_get_token(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_ppg_set_buffer_set(struct ipu_psys_kcmd *kcmd, ++ struct ipu_fw_psys_terminal *terminal, ++ int terminal_idx, u32 buffer); ++size_t ipu_fw_psys_ppg_get_buffer_set_size(struct ipu_psys_kcmd *kcmd); ++int ++ipu_fw_psys_ppg_buffer_set_vaddress(struct ipu_fw_psys_buffer_set *buf_set, ++ u32 vaddress); ++int ipu_fw_psys_ppg_buffer_set_set_kernel_enable_bitmap( ++ struct ipu_fw_psys_buffer_set *buf_set, u32 *kernel_enable_bitmap); ++struct ipu_fw_psys_buffer_set * ++ipu_fw_psys_ppg_create_buffer_set(struct ipu_psys_kcmd *kcmd, ++ void *kaddr, u32 frame_counter); ++int ipu_fw_psys_ppg_enqueue_bufs(struct ipu_psys_kcmd *kcmd); ++u8 ipu_fw_psys_ppg_get_base_queue_id(struct ipu_psys_kcmd *kcmd); ++void ipu_fw_psys_ppg_set_base_queue_id(struct ipu_psys_kcmd *kcmd, u8 queue_id); ++int ipu_fw_psys_pg_get_protocol(struct ipu_psys_kcmd *kcmd); ++int ipu_fw_psys_open(struct ipu_psys *psys); ++int ipu_fw_psys_close(struct ipu_psys *psys); ++ ++/* common resource interface for both abi and api mode */ ++int ipu_fw_psys_set_process_cell_id(struct ipu_fw_psys_process *ptr, u8 index, ++ u8 value); ++u8 ipu_fw_psys_get_process_cell_id(struct ipu_fw_psys_process *ptr, u8 index); ++int ipu_fw_psys_clear_process_cell(struct ipu_fw_psys_process *ptr); ++int ipu_fw_psys_set_proc_dev_chn(struct ipu_fw_psys_process *ptr, u16 offset, ++ u16 value); ++int ipu_fw_psys_set_process_ext_mem(struct ipu_fw_psys_process *ptr, ++ u16 type_id, u16 mem_id, u16 offset); ++int ipu_fw_psys_get_program_manifest_by_process( ++ struct ipu_fw_generic_program_manifest *gen_pm, ++ const struct ipu_fw_psys_program_group_manifest *pg_manifest, ++ struct ipu_fw_psys_process *process); ++int ipu6_fw_psys_set_proc_dev_chn(struct ipu_fw_psys_process *ptr, u16 offset, ++ u16 value); ++int ipu6_fw_psys_set_proc_dfm_bitmap(struct ipu_fw_psys_process *ptr, ++ u16 id, u32 bitmap, ++ u32 active_bitmap); ++int ipu6_fw_psys_set_process_ext_mem(struct ipu_fw_psys_process *ptr, ++ u16 type_id, u16 mem_id, u16 offset); ++int ipu6_fw_psys_get_program_manifest_by_process( ++ struct ipu_fw_generic_program_manifest *gen_pm, ++ const struct ipu_fw_psys_program_group_manifest *pg_manifest, ++ struct ipu_fw_psys_process *process); ++void ipu6_fw_psys_pg_dump(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd, const char *note); ++void ipu6_psys_hw_res_variant_init(void); ++#endif /* IPU_FW_PSYS_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-fw-resources.c b/drivers/media/pci/intel/ipu6/psys/ipu-fw-resources.c +new file mode 100644 +index 000000000000..dba859777434 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-fw-resources.c +@@ -0,0 +1,103 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2015 - 2024 Intel Corporation ++ ++#include ++#include ++ ++#include "ipu-psys.h" ++#include "ipu-fw-psys.h" ++#include "ipu6-platform-resources.h" ++#include "ipu6se-platform-resources.h" ++ ++/********** Generic resource handling **********/ ++ ++/* ++ * Extension library gives byte offsets to its internal structures. ++ * use those offsets to update fields. Without extension lib access ++ * structures directly. ++ */ ++const struct ipu6_psys_hw_res_variant *var = &hw_var; ++ ++int ipu_fw_psys_set_process_cell_id(struct ipu_fw_psys_process *ptr, u8 index, ++ u8 value) ++{ ++ struct ipu_fw_psys_process_group *parent = ++ (struct ipu_fw_psys_process_group *)((char *)ptr + ++ ptr->parent_offset); ++ ++ ptr->cells[index] = value; ++ parent->resource_bitmap |= 1 << value; ++ ++ return 0; ++} ++ ++u8 ipu_fw_psys_get_process_cell_id(struct ipu_fw_psys_process *ptr, u8 index) ++{ ++ return ptr->cells[index]; ++} ++ ++int ipu_fw_psys_clear_process_cell(struct ipu_fw_psys_process *ptr) ++{ ++ struct ipu_fw_psys_process_group *parent; ++ u8 cell_id = ipu_fw_psys_get_process_cell_id(ptr, 0); ++ int retval = -1; ++ u8 value; ++ ++ parent = (struct ipu_fw_psys_process_group *)((char *)ptr + ++ ptr->parent_offset); ++ ++ value = var->cell_num; ++ if ((1 << cell_id) != 0 && ++ ((1 << cell_id) & parent->resource_bitmap)) { ++ ipu_fw_psys_set_process_cell_id(ptr, 0, value); ++ parent->resource_bitmap &= ~(1 << cell_id); ++ retval = 0; ++ } ++ ++ return retval; ++} ++ ++int ipu_fw_psys_set_proc_dev_chn(struct ipu_fw_psys_process *ptr, u16 offset, ++ u16 value) ++{ ++ if (var->set_proc_dev_chn) ++ return var->set_proc_dev_chn(ptr, offset, value); ++ ++ WARN(1, "ipu6 psys res var is not initialised correctly."); ++ return 0; ++} ++ ++int ipu_fw_psys_set_proc_dfm_bitmap(struct ipu_fw_psys_process *ptr, ++ u16 id, u32 bitmap, ++ u32 active_bitmap) ++{ ++ if (var->set_proc_dfm_bitmap) ++ return var->set_proc_dfm_bitmap(ptr, id, bitmap, ++ active_bitmap); ++ ++ WARN(1, "ipu6 psys res var is not initialised correctly."); ++ return 0; ++} ++ ++int ipu_fw_psys_set_process_ext_mem(struct ipu_fw_psys_process *ptr, ++ u16 type_id, u16 mem_id, u16 offset) ++{ ++ if (var->set_proc_ext_mem) ++ return var->set_proc_ext_mem(ptr, type_id, mem_id, offset); ++ ++ WARN(1, "ipu6 psys res var is not initialised correctly."); ++ return 0; ++} ++ ++int ipu_fw_psys_get_program_manifest_by_process( ++ struct ipu_fw_generic_program_manifest *gen_pm, ++ const struct ipu_fw_psys_program_group_manifest *pg_manifest, ++ struct ipu_fw_psys_process *process) ++{ ++ if (var->get_pgm_by_proc) ++ return var->get_pgm_by_proc(gen_pm, pg_manifest, process); ++ ++ WARN(1, "ipu6 psys res var is not initialised correctly."); ++ return 0; ++} ++ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-platform-psys.h b/drivers/media/pci/intel/ipu6/psys/ipu-platform-psys.h +new file mode 100644 +index 000000000000..dc2cae37710d +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-platform-psys.h +@@ -0,0 +1,78 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2020 - 2024 Intel Corporation */ ++ ++#ifndef IPU_PLATFORM_PSYS_H ++#define IPU_PLATFORM_PSYS_H ++ ++#include "ipu-psys.h" ++#include ++ ++#define IPU_PSYS_BUF_SET_POOL_SIZE 8 ++#define IPU_PSYS_BUF_SET_MAX_SIZE 1024 ++ ++struct ipu_fw_psys_buffer_set; ++ ++enum ipu_psys_cmd_state { ++ KCMD_STATE_PPG_NEW, ++ KCMD_STATE_PPG_START, ++ KCMD_STATE_PPG_ENQUEUE, ++ KCMD_STATE_PPG_STOP, ++ KCMD_STATE_PPG_COMPLETE ++}; ++ ++struct ipu_psys_scheduler { ++ struct list_head ppgs; ++ struct mutex bs_mutex; /* Protects buf_set field */ ++ struct list_head buf_sets; ++}; ++ ++enum ipu_psys_ppg_state { ++ PPG_STATE_START = (1 << 0), ++ PPG_STATE_STARTING = (1 << 1), ++ PPG_STATE_STARTED = (1 << 2), ++ PPG_STATE_RUNNING = (1 << 3), ++ PPG_STATE_SUSPEND = (1 << 4), ++ PPG_STATE_SUSPENDING = (1 << 5), ++ PPG_STATE_SUSPENDED = (1 << 6), ++ PPG_STATE_RESUME = (1 << 7), ++ PPG_STATE_RESUMING = (1 << 8), ++ PPG_STATE_RESUMED = (1 << 9), ++ PPG_STATE_STOP = (1 << 10), ++ PPG_STATE_STOPPING = (1 << 11), ++ PPG_STATE_STOPPED = (1 << 12), ++}; ++ ++struct ipu_psys_ppg { ++ struct ipu_psys_pg *kpg; ++ struct ipu_psys_fh *fh; ++ struct list_head list; ++ struct list_head sched_list; ++ u64 token; ++ void *manifest; ++ struct mutex mutex; /* Protects kcmd and ppg state field */ ++ struct list_head kcmds_new_list; ++ struct list_head kcmds_processing_list; ++ struct list_head kcmds_finished_list; ++ enum ipu_psys_ppg_state state; ++ u32 pri_base; ++ int pri_dynamic; ++}; ++ ++struct ipu_psys_buffer_set { ++ struct list_head list; ++ struct ipu_fw_psys_buffer_set *buf_set; ++ size_t size; ++ size_t buf_set_size; ++ dma_addr_t dma_addr; ++ void *kaddr; ++ struct ipu_psys_kcmd *kcmd; ++}; ++ ++int ipu_psys_kcmd_start(struct ipu_psys *psys, struct ipu_psys_kcmd *kcmd); ++void ipu_psys_kcmd_complete(struct ipu_psys_ppg *kppg, ++ struct ipu_psys_kcmd *kcmd, ++ int error); ++int ipu_psys_fh_init(struct ipu_psys_fh *fh); ++int ipu_psys_fh_deinit(struct ipu_psys_fh *fh); ++ ++#endif /* IPU_PLATFORM_PSYS_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-platform-resources.h b/drivers/media/pci/intel/ipu6/psys/ipu-platform-resources.h +new file mode 100644 +index 000000000000..e2e908ddfc38 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-platform-resources.h +@@ -0,0 +1,103 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2018 - 2024 Intel Corporation */ ++ ++#ifndef IPU_PLATFORM_RESOURCES_COMMON_H ++#define IPU_PLATFORM_RESOURCES_COMMON_H ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_PROGRAM_MANIFEST 0 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_PROCESS_STRUCT 0 ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_PROCESS_GROUP_STRUCT 2 ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_PROGRAM_MANIFEST_EXT 2 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_TERMINAL_STRUCT 5 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_PARAM_TERMINAL_STRUCT 6 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_DATA_TERMINAL_STRUCT 3 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_FRAME_DESC_STRUCT 3 ++#define IPU_FW_PSYS_N_FRAME_PLANES 6 ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_FRAME_STRUCT 4 ++ ++#define IPU_FW_PSYS_N_PADDING_UINT8_IN_BUFFER_SET_STRUCT 1 ++ ++#define IPU_FW_PSYS_MAX_INPUT_DEC_RESOURCES 4 ++#define IPU_FW_PSYS_MAX_OUTPUT_DEC_RESOURCES 4 ++ ++#define IPU_FW_PSYS_PROCESS_MAX_CELLS 1 ++#define IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS 4 ++#define IPU_FW_PSYS_RBM_NOF_ELEMS 5 ++#define IPU_FW_PSYS_KBM_NOF_ELEMS 4 ++ ++struct ipu_fw_psys_process { ++ s16 parent_offset; ++ u8 size; ++ u8 cell_dependencies_offset; ++ u8 terminal_dependencies_offset; ++ u8 process_extension_offset; ++ u8 ID; ++ u8 program_idx; ++ u8 state; ++ u8 cells[IPU_FW_PSYS_PROCESS_MAX_CELLS]; ++ u8 cell_dependency_count; ++ u8 terminal_dependency_count; ++}; ++ ++struct ipu_fw_psys_program_manifest { ++ u32 kernel_bitmap[IPU_FW_PSYS_KERNEL_BITMAP_NOF_ELEMS]; ++ s16 parent_offset; ++ u8 program_dependency_offset; ++ u8 terminal_dependency_offset; ++ u8 size; ++ u8 program_extension_offset; ++ u8 program_type; ++ u8 ID; ++ u8 cells[IPU_FW_PSYS_PROCESS_MAX_CELLS]; ++ u8 cell_type_id; ++ u8 program_dependency_count; ++ u8 terminal_dependency_count; ++}; ++ ++/* platform specific resource interface */ ++struct ipu_psys_resource_pool; ++struct ipu_psys_resource_alloc; ++struct ipu_fw_psys_process_group; ++int ipu_psys_allocate_resources(const struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ struct ipu_psys_resource_alloc *alloc, ++ struct ipu_psys_resource_pool *pool); ++int ipu_psys_move_resources(const struct device *dev, ++ struct ipu_psys_resource_alloc *alloc, ++ struct ipu_psys_resource_pool *source_pool, ++ struct ipu_psys_resource_pool *target_pool); ++ ++void ipu_psys_resource_copy(struct ipu_psys_resource_pool *src, ++ struct ipu_psys_resource_pool *dest); ++ ++int ipu_psys_try_allocate_resources(struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ struct ipu_psys_resource_pool *pool); ++ ++void ipu_psys_reset_process_cell(const struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ int process_count); ++void ipu_psys_free_resources(struct ipu_psys_resource_alloc *alloc, ++ struct ipu_psys_resource_pool *pool); ++ ++int ipu_fw_psys_set_proc_dfm_bitmap(struct ipu_fw_psys_process *ptr, ++ u16 id, u32 bitmap, ++ u32 active_bitmap); ++ ++int ipu_psys_allocate_cmd_queue_resource(struct ipu_psys_resource_pool *pool); ++void ipu_psys_free_cmd_queue_resource(struct ipu_psys_resource_pool *pool, ++ u8 queue_id); ++ ++extern const struct ipu_fw_resource_definitions *ipu6_res_defs; ++extern const struct ipu_fw_resource_definitions *ipu6se_res_defs; ++extern const struct ipu_fw_resource_definitions *ipu6ep_res_defs; ++extern struct ipu6_psys_hw_res_variant hw_var; ++#endif /* IPU_PLATFORM_RESOURCES_COMMON_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-psys-compat32.c b/drivers/media/pci/intel/ipu6/psys/ipu-psys-compat32.c +new file mode 100644 +index 000000000000..32350ca2ae6b +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-psys-compat32.c +@@ -0,0 +1,222 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2013 - 2024 Intel Corporation ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "ipu-psys.h" ++ ++static long native_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ++{ ++ long ret = -ENOTTY; ++ ++ if (file->f_op->unlocked_ioctl) ++ ret = file->f_op->unlocked_ioctl(file, cmd, arg); ++ ++ return ret; ++} ++ ++struct ipu_psys_buffer32 { ++ u64 len; ++ union { ++ int fd; ++ compat_uptr_t userptr; ++ u64 reserved; ++ } base; ++ u32 data_offset; ++ u32 bytes_used; ++ u32 flags; ++ u32 reserved[2]; ++} __packed; ++ ++struct ipu_psys_command32 { ++ u64 issue_id; ++ u64 user_token; ++ u32 priority; ++ compat_uptr_t pg_manifest; ++ compat_uptr_t buffers; ++ int pg; ++ u32 pg_manifest_size; ++ u32 bufcount; ++ u32 min_psys_freq; ++ u32 frame_counter; ++ u32 reserved[2]; ++} __packed; ++ ++struct ipu_psys_manifest32 { ++ u32 index; ++ u32 size; ++ compat_uptr_t manifest; ++ u32 reserved[5]; ++} __packed; ++ ++static int ++get_ipu_psys_command32(struct ipu_psys_command *kp, ++ struct ipu_psys_command32 __user *up) ++{ ++ compat_uptr_t pgm, bufs; ++ bool access_ok; ++ ++ access_ok = access_ok(up, sizeof(struct ipu_psys_command32)); ++ if (!access_ok || get_user(kp->issue_id, &up->issue_id) || ++ get_user(kp->user_token, &up->user_token) || ++ get_user(kp->priority, &up->priority) || ++ get_user(pgm, &up->pg_manifest) || ++ get_user(bufs, &up->buffers) || ++ get_user(kp->pg, &up->pg) || ++ get_user(kp->pg_manifest_size, &up->pg_manifest_size) || ++ get_user(kp->bufcount, &up->bufcount) || ++ get_user(kp->min_psys_freq, &up->min_psys_freq) || ++ get_user(kp->frame_counter, &up->frame_counter) ++ ) ++ return -EFAULT; ++ ++ kp->pg_manifest = compat_ptr(pgm); ++ kp->buffers = compat_ptr(bufs); ++ ++ return 0; ++} ++ ++static int ++get_ipu_psys_buffer32(struct ipu_psys_buffer *kp, ++ struct ipu_psys_buffer32 __user *up) ++{ ++ compat_uptr_t ptr; ++ bool access_ok; ++ ++ access_ok = access_ok(up, sizeof(struct ipu_psys_buffer32)); ++ if (!access_ok || get_user(kp->len, &up->len) || ++ get_user(ptr, &up->base.userptr) || ++ get_user(kp->data_offset, &up->data_offset) || ++ get_user(kp->bytes_used, &up->bytes_used) || ++ get_user(kp->flags, &up->flags)) ++ return -EFAULT; ++ ++ kp->base.userptr = compat_ptr(ptr); ++ ++ return 0; ++} ++ ++static int ++put_ipu_psys_buffer32(struct ipu_psys_buffer *kp, ++ struct ipu_psys_buffer32 __user *up) ++{ ++ bool access_ok; ++ ++ access_ok = access_ok(up, sizeof(struct ipu_psys_buffer32)); ++ if (!access_ok || put_user(kp->len, &up->len) || ++ put_user(kp->base.fd, &up->base.fd) || ++ put_user(kp->data_offset, &up->data_offset) || ++ put_user(kp->bytes_used, &up->bytes_used) || ++ put_user(kp->flags, &up->flags)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static int ++get_ipu_psys_manifest32(struct ipu_psys_manifest *kp, ++ struct ipu_psys_manifest32 __user *up) ++{ ++ compat_uptr_t ptr; ++ bool access_ok; ++ ++ access_ok = access_ok(up, sizeof(struct ipu_psys_manifest32)); ++ if (!access_ok || get_user(kp->index, &up->index) || ++ get_user(kp->size, &up->size) || get_user(ptr, &up->manifest)) ++ return -EFAULT; ++ ++ kp->manifest = compat_ptr(ptr); ++ ++ return 0; ++} ++ ++static int ++put_ipu_psys_manifest32(struct ipu_psys_manifest *kp, ++ struct ipu_psys_manifest32 __user *up) ++{ ++ compat_uptr_t ptr = (u32)((unsigned long)kp->manifest); ++ bool access_ok; ++ ++ access_ok = access_ok(up, sizeof(struct ipu_psys_manifest32)); ++ if (!access_ok || put_user(kp->index, &up->index) || ++ put_user(kp->size, &up->size) || put_user(ptr, &up->manifest)) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++#define IPU_IOC_GETBUF32 _IOWR('A', 4, struct ipu_psys_buffer32) ++#define IPU_IOC_PUTBUF32 _IOWR('A', 5, struct ipu_psys_buffer32) ++#define IPU_IOC_QCMD32 _IOWR('A', 6, struct ipu_psys_command32) ++#define IPU_IOC_CMD_CANCEL32 _IOWR('A', 8, struct ipu_psys_command32) ++#define IPU_IOC_GET_MANIFEST32 _IOWR('A', 9, struct ipu_psys_manifest32) ++ ++long ipu_psys_compat_ioctl32(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ union { ++ struct ipu_psys_buffer buf; ++ struct ipu_psys_command cmd; ++ struct ipu_psys_event ev; ++ struct ipu_psys_manifest m; ++ } karg; ++ int compatible_arg = 1; ++ int err = 0; ++ void __user *up = compat_ptr(arg); ++ ++ switch (cmd) { ++ case IPU_IOC_GETBUF32: ++ cmd = IPU_IOC_GETBUF; ++ break; ++ case IPU_IOC_PUTBUF32: ++ cmd = IPU_IOC_PUTBUF; ++ break; ++ case IPU_IOC_QCMD32: ++ cmd = IPU_IOC_QCMD; ++ break; ++ case IPU_IOC_GET_MANIFEST32: ++ cmd = IPU_IOC_GET_MANIFEST; ++ break; ++ } ++ ++ switch (cmd) { ++ case IPU_IOC_GETBUF: ++ case IPU_IOC_PUTBUF: ++ err = get_ipu_psys_buffer32(&karg.buf, up); ++ compatible_arg = 0; ++ break; ++ case IPU_IOC_QCMD: ++ err = get_ipu_psys_command32(&karg.cmd, up); ++ compatible_arg = 0; ++ break; ++ case IPU_IOC_GET_MANIFEST: ++ err = get_ipu_psys_manifest32(&karg.m, up); ++ compatible_arg = 0; ++ break; ++ } ++ if (err) ++ return err; ++ ++ if (compatible_arg) { ++ err = native_ioctl(file, cmd, (unsigned long)up); ++ } else { ++ err = native_ioctl(file, cmd, (unsigned long)&karg); ++ } ++ ++ if (err) ++ return err; ++ ++ switch (cmd) { ++ case IPU_IOC_GETBUF: ++ err = put_ipu_psys_buffer32(&karg.buf, up); ++ break; ++ case IPU_IOC_GET_MANIFEST: ++ err = put_ipu_psys_manifest32(&karg.m, up); ++ break; ++ } ++ return err; ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-psys.c b/drivers/media/pci/intel/ipu6/psys/ipu-psys.c +new file mode 100644 +index 000000000000..d3422ea20e51 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-psys.c +@@ -0,0 +1,1575 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2013 - 2024 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ipu6.h" ++#include "ipu6-mmu.h" ++#include "ipu6-bus.h" ++#include "ipu6-buttress.h" ++#include "ipu6-cpd.h" ++#include "ipu-fw-psys.h" ++#include "ipu-psys.h" ++#include "ipu6-platform-regs.h" ++#include "ipu6-fw-com.h" ++ ++static bool async_fw_init; ++module_param(async_fw_init, bool, 0664); ++MODULE_PARM_DESC(async_fw_init, "Enable asynchronous firmware initialization"); ++ ++#define SYSCOM_BUTTRESS_FW_PARAMS_PSYS_OFFSET 7 ++ ++#define IPU_PSYS_NUM_DEVICES 4 ++ ++#define IPU_PSYS_MAX_NUM_DESCS 1024 ++#define IPU_PSYS_MAX_NUM_BUFS 1024 ++#define IPU_PSYS_MAX_NUM_BUFS_LRU 12 ++ ++static int psys_runtime_pm_resume(struct device *dev); ++static int psys_runtime_pm_suspend(struct device *dev); ++ ++static dev_t ipu_psys_dev_t; ++static DECLARE_BITMAP(ipu_psys_devices, IPU_PSYS_NUM_DEVICES); ++static DEFINE_MUTEX(ipu_psys_mutex); ++ ++static struct fw_init_task { ++ struct delayed_work work; ++ struct ipu_psys *psys; ++} fw_init_task; ++ ++static void ipu6_psys_remove(struct auxiliary_device *auxdev); ++ ++static const struct bus_type ipu6_psys_bus = { ++ .name = "intel-ipu6-psys", ++}; ++ ++#define PKG_DIR_ENT_LEN_FOR_PSYS 2 ++#define PKG_DIR_SIZE_MASK_FOR_PSYS GENMASK(23, 0) ++ ++enum ipu6_version ipu_ver; ++ ++static u32 ipu6_cpd_pkg_dir_get_address(const u64 *pkg_dir, int pkg_dir_idx) ++{ ++ return pkg_dir[++pkg_dir_idx * PKG_DIR_ENT_LEN_FOR_PSYS]; ++} ++ ++static u32 ipu6_cpd_pkg_dir_get_num_entries(const u64 *pkg_dir) ++{ ++ return pkg_dir[1]; ++} ++ ++static u32 ipu6_cpd_pkg_dir_get_size(const u64 *pkg_dir, int pkg_dir_idx) ++{ ++ return pkg_dir[++pkg_dir_idx * PKG_DIR_ENT_LEN_FOR_PSYS + 1] & ++ PKG_DIR_SIZE_MASK_FOR_PSYS; ++} ++ ++#define PKG_DIR_ID_SHIFT 48 ++#define PKG_DIR_ID_MASK 0x7f ++ ++static u32 ipu6_cpd_pkg_dir_get_type(const u64 *pkg_dir, int pkg_dir_idx) ++{ ++ return pkg_dir[++pkg_dir_idx * PKG_DIR_ENT_LEN_FOR_PSYS + 1] >> ++ PKG_DIR_ID_SHIFT & PKG_DIR_ID_MASK; ++} ++ ++/* ++ * These are some trivial wrappers that save us from open-coding some ++ * common patterns and also that's were we have some checking (for the ++ * time being) ++ */ ++static void ipu_desc_add(struct ipu_psys_fh *fh, struct ipu_psys_desc *desc) ++{ ++ fh->num_descs++; ++ ++ WARN_ON_ONCE(fh->num_descs >= IPU_PSYS_MAX_NUM_DESCS); ++ list_add(&desc->list, &fh->descs_list); ++} ++ ++static void ipu_desc_del(struct ipu_psys_fh *fh, struct ipu_psys_desc *desc) ++{ ++ fh->num_descs--; ++ list_del_init(&desc->list); ++} ++ ++static void ipu_buffer_add(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ fh->num_bufs++; ++ ++ WARN_ON_ONCE(fh->num_bufs >= IPU_PSYS_MAX_NUM_BUFS); ++ list_add(&kbuf->list, &fh->bufs_list); ++} ++ ++static void ipu_buffer_del(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ fh->num_bufs--; ++ list_del_init(&kbuf->list); ++} ++ ++static void ipu_buffer_lru_add(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ fh->num_bufs_lru++; ++ list_add_tail(&kbuf->list, &fh->bufs_lru); ++} ++ ++static void ipu_buffer_lru_del(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ fh->num_bufs_lru--; ++ list_del_init(&kbuf->list); ++} ++ ++static struct ipu_psys_kbuffer *ipu_psys_kbuffer_alloc(void) ++{ ++ struct ipu_psys_kbuffer *kbuf; ++ ++ kbuf = kzalloc(sizeof(*kbuf), GFP_KERNEL); ++ if (!kbuf) ++ return NULL; ++ ++ atomic_set(&kbuf->map_count, 0); ++ INIT_LIST_HEAD(&kbuf->list); ++ return kbuf; ++} ++ ++static struct ipu_psys_desc *ipu_psys_desc_alloc(int fd) ++{ ++ struct ipu_psys_desc *desc; ++ ++ desc = kzalloc(sizeof(*desc), GFP_KERNEL); ++ if (!desc) ++ return NULL; ++ ++ desc->fd = fd; ++ INIT_LIST_HEAD(&desc->list); ++ return desc; ++} ++ ++struct ipu_psys_pg *__get_pg_buf(struct ipu_psys *psys, size_t pg_size) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_pg *kpg; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&psys->pgs_lock, flags); ++ list_for_each_entry(kpg, &psys->pgs, list) { ++ if (!kpg->pg_size && kpg->size >= pg_size) { ++ kpg->pg_size = pg_size; ++ spin_unlock_irqrestore(&psys->pgs_lock, flags); ++ return kpg; ++ } ++ } ++ spin_unlock_irqrestore(&psys->pgs_lock, flags); ++ /* no big enough buffer available, allocate new one */ ++ kpg = kzalloc(sizeof(*kpg), GFP_KERNEL); ++ if (!kpg) ++ return NULL; ++ ++ kpg->pg = dma_alloc_attrs(dev, pg_size, &kpg->pg_dma_addr, ++ GFP_KERNEL, 0); ++ if (!kpg->pg) { ++ kfree(kpg); ++ return NULL; ++ } ++ ++ kpg->pg_size = pg_size; ++ kpg->size = pg_size; ++ spin_lock_irqsave(&psys->pgs_lock, flags); ++ list_add(&kpg->list, &psys->pgs); ++ spin_unlock_irqrestore(&psys->pgs_lock, flags); ++ ++ return kpg; ++} ++ ++static struct ipu_psys_desc *psys_desc_lookup(struct ipu_psys_fh *fh, int fd) ++{ ++ struct ipu_psys_desc *desc; ++ ++ list_for_each_entry(desc, &fh->descs_list, list) { ++ if (desc->fd == fd) ++ return desc; ++ } ++ ++ return NULL; ++} ++ ++static bool dmabuf_cmp(struct dma_buf *lb, struct dma_buf *rb) ++{ ++ return lb == rb && lb->size == rb->size; ++} ++ ++static struct ipu_psys_kbuffer *psys_buf_lookup(struct ipu_psys_fh *fh, int fd) ++{ ++ struct ipu_psys_kbuffer *kbuf; ++ struct dma_buf *dma_buf; ++ ++ dma_buf = dma_buf_get(fd); ++ if (IS_ERR(dma_buf)) ++ return NULL; ++ ++ /* ++ * First lookup so-called `active` list, that is the list of ++ * referenced buffers ++ */ ++ list_for_each_entry(kbuf, &fh->bufs_list, list) { ++ if (dmabuf_cmp(kbuf->dbuf, dma_buf)) { ++ dma_buf_put(dma_buf); ++ return kbuf; ++ } ++ } ++ ++ /* ++ * We didn't find anything on the `active` list, try the LRU list ++ * (list of unreferenced buffers) and possibly resurrect a buffer ++ */ ++ list_for_each_entry(kbuf, &fh->bufs_lru, list) { ++ if (dmabuf_cmp(kbuf->dbuf, dma_buf)) { ++ dma_buf_put(dma_buf); ++ ipu_buffer_lru_del(fh, kbuf); ++ ipu_buffer_add(fh, kbuf); ++ return kbuf; ++ } ++ } ++ ++ dma_buf_put(dma_buf); ++ return NULL; ++} ++ ++struct ipu_psys_kbuffer *ipu_psys_lookup_kbuffer(struct ipu_psys_fh *fh, int fd) ++{ ++ struct ipu_psys_desc *desc; ++ ++ desc = psys_desc_lookup(fh, fd); ++ if (!desc) ++ return NULL; ++ ++ return desc->kbuf; ++} ++ ++struct ipu_psys_kbuffer * ++ipu_psys_lookup_kbuffer_by_kaddr(struct ipu_psys_fh *fh, void *kaddr) ++{ ++ struct ipu_psys_kbuffer *kbuffer; ++ ++ list_for_each_entry(kbuffer, &fh->bufs_list, list) { ++ if (kbuffer->kaddr == kaddr) ++ return kbuffer; ++ } ++ ++ return NULL; ++} ++ ++static int ipu_psys_get_userpages(struct ipu_dma_buf_attach *attach) ++{ ++ struct vm_area_struct *vma; ++ unsigned long start, end; ++ int npages, array_size; ++ struct page **pages; ++ struct sg_table *sgt; ++ int ret = -ENOMEM; ++ int nr = 0; ++ u32 flags; ++ ++ start = (unsigned long)attach->userptr; ++ end = PAGE_ALIGN(start + attach->len); ++ npages = (end - (start & PAGE_MASK)) >> PAGE_SHIFT; ++ array_size = npages * sizeof(struct page *); ++ ++ sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; ++ ++ WARN_ON_ONCE(attach->npages); ++ ++ pages = kvzalloc(array_size, GFP_KERNEL); ++ if (!pages) ++ goto free_sgt; ++ ++ mmap_read_lock(current->mm); ++ vma = vma_lookup(current->mm, start); ++ if (unlikely(!vma)) { ++ ret = -EFAULT; ++ goto error_up_read; ++ } ++ mmap_read_unlock(current->mm); ++ ++ flags = FOLL_WRITE | FOLL_FORCE | FOLL_LONGTERM; ++ nr = pin_user_pages_fast(start & PAGE_MASK, npages, ++ flags, pages); ++ if (nr < npages) ++ goto error; ++ ++ attach->pages = pages; ++ attach->npages = npages; ++ ++ ret = sg_alloc_table_from_pages(sgt, pages, npages, ++ start & ~PAGE_MASK, attach->len, ++ GFP_KERNEL); ++ if (ret < 0) ++ goto error; ++ ++ attach->sgt = sgt; ++ ++ return 0; ++ ++error_up_read: ++ mmap_read_unlock(current->mm); ++error: ++ if (nr) ++ unpin_user_pages(pages, nr); ++ kvfree(pages); ++free_sgt: ++ kfree(sgt); ++ ++ pr_err("failed to get userpages:%d\n", ret); ++ ++ return ret; ++} ++ ++static void ipu_psys_put_userpages(struct ipu_dma_buf_attach *attach) ++{ ++ if (!attach || !attach->userptr || !attach->sgt) ++ return; ++ ++ unpin_user_pages(attach->pages, attach->npages); ++ ++ kvfree(attach->pages); ++ ++ sg_free_table(attach->sgt); ++ kfree(attach->sgt); ++ attach->sgt = NULL; ++} ++ ++static int ipu_dma_buf_attach(struct dma_buf *dbuf, ++ struct dma_buf_attachment *attach) ++{ ++ struct ipu_psys_kbuffer *kbuf = dbuf->priv; ++ struct ipu_dma_buf_attach *ipu_attach; ++ int ret; ++ ++ ipu_attach = kzalloc(sizeof(*ipu_attach), GFP_KERNEL); ++ if (!ipu_attach) ++ return -ENOMEM; ++ ++ ipu_attach->len = kbuf->len; ++ ipu_attach->userptr = kbuf->userptr; ++ ++ ret = ipu_psys_get_userpages(ipu_attach); ++ if (ret) { ++ kfree(ipu_attach); ++ return ret; ++ } ++ ++ attach->priv = ipu_attach; ++ return 0; ++} ++ ++static void ipu_dma_buf_detach(struct dma_buf *dbuf, ++ struct dma_buf_attachment *attach) ++{ ++ struct ipu_dma_buf_attach *ipu_attach = attach->priv; ++ ++ ipu_psys_put_userpages(ipu_attach); ++ kfree(ipu_attach); ++ attach->priv = NULL; ++} ++ ++static struct sg_table *ipu_dma_buf_map(struct dma_buf_attachment *attach, ++ enum dma_data_direction dir) ++{ ++ struct ipu_dma_buf_attach *ipu_attach = attach->priv; ++ unsigned long attrs; ++ int ret; ++ ++ attrs = DMA_ATTR_SKIP_CPU_SYNC; ++ ret = dma_map_sgtable(attach->dev, ipu_attach->sgt, dir, attrs); ++ if (ret < 0) { ++ dev_dbg(attach->dev, "buf map failed\n"); ++ ++ return ERR_PTR(-EIO); ++ } ++ ++ /* ++ * Initial cache flush to avoid writing dirty pages for buffers which ++ * are later marked as IPU_BUFFER_FLAG_NO_FLUSH. ++ */ ++ dma_sync_sg_for_device(attach->dev, ipu_attach->sgt->sgl, ++ ipu_attach->sgt->orig_nents, DMA_BIDIRECTIONAL); ++ ++ return ipu_attach->sgt; ++} ++ ++static void ipu_dma_buf_unmap(struct dma_buf_attachment *attach, ++ struct sg_table *sgt, enum dma_data_direction dir) ++{ ++ dma_unmap_sgtable(attach->dev, sgt, dir, DMA_ATTR_SKIP_CPU_SYNC); ++} ++ ++static int ipu_dma_buf_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma) ++{ ++ return -ENOTTY; ++} ++ ++static void ipu_dma_buf_release(struct dma_buf *buf) ++{ ++ struct ipu_psys_kbuffer *kbuf = buf->priv; ++ ++ if (!kbuf) ++ return; ++ ++ if (kbuf->db_attach) ++ ipu_psys_put_userpages(kbuf->db_attach->priv); ++ ++ kfree(kbuf); ++} ++ ++static int ipu_dma_buf_begin_cpu_access(struct dma_buf *dma_buf, ++ enum dma_data_direction dir) ++{ ++ return -ENOTTY; ++} ++ ++static int ipu_dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map) ++{ ++ struct dma_buf_attachment *attach; ++ struct ipu_dma_buf_attach *ipu_attach; ++ ++ if (list_empty(&dmabuf->attachments)) ++ return -EINVAL; ++ ++ attach = list_last_entry(&dmabuf->attachments, ++ struct dma_buf_attachment, node); ++ ipu_attach = attach->priv; ++ ++ if (!ipu_attach || !ipu_attach->pages || !ipu_attach->npages) ++ return -EINVAL; ++ ++ map->vaddr = vm_map_ram(ipu_attach->pages, ipu_attach->npages, 0); ++ map->is_iomem = false; ++ if (!map->vaddr) ++ return -EINVAL; ++ ++ return 0; ++} ++ ++static void ipu_dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map) ++{ ++ struct dma_buf_attachment *attach; ++ struct ipu_dma_buf_attach *ipu_attach; ++ ++ if (WARN_ON(list_empty(&dmabuf->attachments))) ++ return; ++ ++ attach = list_last_entry(&dmabuf->attachments, ++ struct dma_buf_attachment, node); ++ ipu_attach = attach->priv; ++ ++ if (WARN_ON(!ipu_attach || !ipu_attach->pages || !ipu_attach->npages)) ++ return; ++ ++ vm_unmap_ram(map->vaddr, ipu_attach->npages); ++} ++ ++static const struct dma_buf_ops ipu_dma_buf_ops = { ++ .attach = ipu_dma_buf_attach, ++ .detach = ipu_dma_buf_detach, ++ .map_dma_buf = ipu_dma_buf_map, ++ .unmap_dma_buf = ipu_dma_buf_unmap, ++ .release = ipu_dma_buf_release, ++ .begin_cpu_access = ipu_dma_buf_begin_cpu_access, ++ .mmap = ipu_dma_buf_mmap, ++ .vmap = ipu_dma_buf_vmap, ++ .vunmap = ipu_dma_buf_vunmap, ++}; ++ ++static int ipu_psys_open(struct inode *inode, struct file *file) ++{ ++ struct ipu_psys *psys = inode_to_ipu_psys(inode); ++ struct ipu_psys_fh *fh; ++ int rval; ++ ++ fh = kzalloc(sizeof(*fh), GFP_KERNEL); ++ if (!fh) ++ return -ENOMEM; ++ ++ fh->psys = psys; ++ ++ file->private_data = fh; ++ ++ mutex_init(&fh->mutex); ++ INIT_LIST_HEAD(&fh->bufs_list); ++ INIT_LIST_HEAD(&fh->descs_list); ++ INIT_LIST_HEAD(&fh->bufs_lru); ++ init_waitqueue_head(&fh->wait); ++ ++ rval = ipu_psys_fh_init(fh); ++ if (rval) ++ goto open_failed; ++ ++ mutex_lock(&psys->mutex); ++ list_add_tail(&fh->list, &psys->fhs); ++ mutex_unlock(&psys->mutex); ++ ++ return 0; ++ ++open_failed: ++ mutex_destroy(&fh->mutex); ++ kfree(fh); ++ return rval; ++} ++ ++static inline void ipu_psys_kbuf_unmap(struct ipu_psys_kbuffer *kbuf) ++{ ++ if (!kbuf) ++ return; ++ ++ kbuf->valid = false; ++ if (kbuf->kaddr) { ++ struct iosys_map dmap; ++ ++ iosys_map_set_vaddr(&dmap, kbuf->kaddr); ++ dma_buf_vunmap_unlocked(kbuf->dbuf, &dmap); ++ } ++ if (!IS_ERR_OR_NULL(kbuf->sgt)) ++ dma_buf_unmap_attachment_unlocked(kbuf->db_attach, ++ kbuf->sgt, ++ DMA_BIDIRECTIONAL); ++ if (!IS_ERR_OR_NULL(kbuf->db_attach)) ++ dma_buf_detach(kbuf->dbuf, kbuf->db_attach); ++ dma_buf_put(kbuf->dbuf); ++ ++ kbuf->db_attach = NULL; ++ kbuf->dbuf = NULL; ++ kbuf->sgt = NULL; ++} ++ ++static void __ipu_psys_unmapbuf(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ /* From now on it is not safe to use this kbuffer */ ++ ipu_psys_kbuf_unmap(kbuf); ++ ipu_buffer_del(fh, kbuf); ++ if (!kbuf->userptr) ++ kfree(kbuf); ++} ++ ++static int ipu_psys_unmapbuf_locked(int fd, struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kbuffer *kbuf; ++ struct ipu_psys_desc *desc; ++ ++ desc = psys_desc_lookup(fh, fd); ++ if (WARN_ON_ONCE(!desc)) { ++ dev_err(dev, "descriptor not found: %d\n", fd); ++ return -EINVAL; ++ } ++ ++ kbuf = desc->kbuf; ++ /* descriptor is gone now */ ++ ipu_desc_del(fh, desc); ++ kfree(desc); ++ ++ if (WARN_ON_ONCE(!kbuf || !kbuf->dbuf)) { ++ dev_err(dev, ++ "descriptor with no buffer: %d\n", fd); ++ return -EINVAL; ++ } ++ ++ /* Wait for final UNMAP */ ++ if (!atomic_dec_and_test(&kbuf->map_count)) ++ return 0; ++ ++ __ipu_psys_unmapbuf(fh, kbuf); ++ return 0; ++} ++ ++static int ipu_psys_release(struct inode *inode, struct file *file) ++{ ++ struct ipu_psys *psys = inode_to_ipu_psys(inode); ++ struct ipu_psys_fh *fh = file->private_data; ++ ++ mutex_lock(&fh->mutex); ++ while (!list_empty(&fh->descs_list)) { ++ struct ipu_psys_desc *desc; ++ ++ desc = list_first_entry(&fh->descs_list, ++ struct ipu_psys_desc, ++ list); ++ ++ ipu_desc_del(fh, desc); ++ kfree(desc); ++ } ++ ++ while (!list_empty(&fh->bufs_lru)) { ++ struct ipu_psys_kbuffer *kbuf; ++ ++ kbuf = list_first_entry(&fh->bufs_lru, ++ struct ipu_psys_kbuffer, ++ list); ++ ++ ipu_buffer_lru_del(fh, kbuf); ++ __ipu_psys_unmapbuf(fh, kbuf); ++ } ++ ++ while (!list_empty(&fh->bufs_list)) { ++ struct dma_buf_attachment *db_attach; ++ struct ipu_psys_kbuffer *kbuf; ++ ++ kbuf = list_first_entry(&fh->bufs_list, ++ struct ipu_psys_kbuffer, ++ list); ++ ++ ipu_buffer_del(fh, kbuf); ++ db_attach = kbuf->db_attach; ++ ++ /* Unmap and release buffers */ ++ if (kbuf->dbuf && db_attach) { ++ ipu_psys_kbuf_unmap(kbuf); ++ } else { ++ if (db_attach) ++ ipu_psys_put_userpages(db_attach->priv); ++ kfree(kbuf); ++ } ++ } ++ mutex_unlock(&fh->mutex); ++ ++ mutex_lock(&psys->mutex); ++ list_del(&fh->list); ++ ++ mutex_unlock(&psys->mutex); ++ ipu_psys_fh_deinit(fh); ++ ++ mutex_lock(&psys->mutex); ++ if (list_empty(&psys->fhs)) ++ psys->power_gating = 0; ++ mutex_unlock(&psys->mutex); ++ mutex_destroy(&fh->mutex); ++ kfree(fh); ++ ++ return 0; ++} ++ ++static int ipu_psys_getbuf(struct ipu_psys_buffer *buf, struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys_kbuffer *kbuf; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_desc *desc; ++ DEFINE_DMA_BUF_EXPORT_INFO(exp_info); ++ struct dma_buf *dbuf; ++ int ret; ++ ++ if (!buf->base.userptr) { ++ dev_err(dev, "Buffer allocation not supported\n"); ++ return -EINVAL; ++ } ++ ++ kbuf = ipu_psys_kbuffer_alloc(); ++ if (!kbuf) ++ return -ENOMEM; ++ ++ kbuf->len = buf->len; ++ kbuf->userptr = buf->base.userptr; ++ kbuf->flags = buf->flags; ++ ++ exp_info.ops = &ipu_dma_buf_ops; ++ exp_info.size = kbuf->len; ++ exp_info.flags = O_RDWR; ++ exp_info.priv = kbuf; ++ ++ dbuf = dma_buf_export(&exp_info); ++ if (IS_ERR(dbuf)) { ++ kfree(kbuf); ++ return PTR_ERR(dbuf); ++ } ++ ++ ret = dma_buf_fd(dbuf, 0); ++ if (ret < 0) { ++ dma_buf_put(dbuf); ++ return ret; ++ } ++ ++ buf->base.fd = ret; ++ buf->flags &= ~IPU_BUFFER_FLAG_USERPTR; ++ buf->flags |= IPU_BUFFER_FLAG_DMA_HANDLE; ++ kbuf->flags = buf->flags; ++ ++ desc = ipu_psys_desc_alloc(ret); ++ if (!desc) { ++ dma_buf_put(dbuf); ++ return -ENOMEM; ++ } ++ ++ kbuf->dbuf = dbuf; ++ ++ mutex_lock(&fh->mutex); ++ ipu_desc_add(fh, desc); ++ ipu_buffer_add(fh, kbuf); ++ mutex_unlock(&fh->mutex); ++ ++ dev_dbg(dev, "IOC_GETBUF: userptr %p size %llu to fd %d", ++ buf->base.userptr, buf->len, buf->base.fd); ++ ++ return 0; ++} ++ ++static int ipu_psys_putbuf(struct ipu_psys_buffer *buf, struct ipu_psys_fh *fh) ++{ ++ return 0; ++} ++ ++static void ipu_psys_kbuffer_lru(struct ipu_psys_fh *fh, ++ struct ipu_psys_kbuffer *kbuf) ++{ ++ ipu_buffer_del(fh, kbuf); ++ ipu_buffer_lru_add(fh, kbuf); ++ ++ while (fh->num_bufs_lru > IPU_PSYS_MAX_NUM_BUFS_LRU) { ++ kbuf = list_first_entry(&fh->bufs_lru, ++ struct ipu_psys_kbuffer, ++ list); ++ ++ ipu_buffer_lru_del(fh, kbuf); ++ __ipu_psys_unmapbuf(fh, kbuf); ++ } ++} ++ ++struct ipu_psys_kbuffer *ipu_psys_mapbuf_locked(int fd, struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kbuffer *kbuf; ++ struct ipu_psys_desc *desc; ++ struct dma_buf *dbuf; ++ struct iosys_map dmap = { ++ .is_iomem = false, ++ }; ++ ++ dbuf = dma_buf_get(fd); ++ if (IS_ERR(dbuf)) ++ return NULL; ++ ++ desc = psys_desc_lookup(fh, fd); ++ if (!desc) { ++ desc = ipu_psys_desc_alloc(fd); ++ if (!desc) ++ goto desc_alloc_fail; ++ ipu_desc_add(fh, desc); ++ } ++ ++ kbuf = psys_buf_lookup(fh, fd); ++ if (!kbuf) { ++ kbuf = ipu_psys_kbuffer_alloc(); ++ if (!kbuf) ++ goto buf_alloc_fail; ++ ipu_buffer_add(fh, kbuf); ++ } ++ ++ /* If this descriptor references no buffer or new buffer */ ++ if (desc->kbuf != kbuf) { ++ if (desc->kbuf) { ++ /* ++ * Un-reference old buffer and possibly put it on ++ * the LRU list ++ */ ++ if (atomic_dec_and_test(&desc->kbuf->map_count)) ++ ipu_psys_kbuffer_lru(fh, desc->kbuf); ++ } ++ ++ /* Grab reference of the new buffer */ ++ atomic_inc(&kbuf->map_count); ++ } ++ ++ desc->kbuf = kbuf; ++ ++ if (kbuf->sgt) { ++ dev_dbg(dev, "fd %d has been mapped!\n", fd); ++ dma_buf_put(dbuf); ++ goto mapbuf_end; ++ } ++ ++ kbuf->dbuf = dbuf; ++ ++ if (kbuf->len == 0) ++ kbuf->len = kbuf->dbuf->size; ++ ++ kbuf->db_attach = dma_buf_attach(kbuf->dbuf, dev); ++ if (IS_ERR(kbuf->db_attach)) { ++ dev_dbg(dev, "dma buf attach failed\n"); ++ goto kbuf_map_fail; ++ } ++ ++ kbuf->sgt = dma_buf_map_attachment_unlocked(kbuf->db_attach, ++ DMA_BIDIRECTIONAL); ++ if (IS_ERR_OR_NULL(kbuf->sgt)) { ++ kbuf->sgt = NULL; ++ dev_dbg(dev, "dma buf map attachment failed\n"); ++ goto kbuf_map_fail; ++ } ++ ++ kbuf->dma_addr = sg_dma_address(kbuf->sgt->sgl); ++ ++ /* no need vmap for imported dmabufs */ ++ if (!kbuf->userptr) ++ goto mapbuf_end; ++ ++ if (dma_buf_vmap_unlocked(kbuf->dbuf, &dmap)) { ++ dev_dbg(dev, "dma buf vmap failed\n"); ++ goto kbuf_map_fail; ++ } ++ kbuf->kaddr = dmap.vaddr; ++ ++ dev_dbg(dev, "%s kbuf %p fd %d with len %llu mapped\n", ++ __func__, kbuf, fd, kbuf->len); ++ ++mapbuf_end: ++ kbuf->valid = true; ++ return kbuf; ++ ++kbuf_map_fail: ++ ipu_buffer_del(fh, kbuf); ++ ipu_psys_kbuf_unmap(kbuf); ++ dbuf = ERR_PTR(-EINVAL); ++ if (!kbuf->userptr) ++ kfree(kbuf); ++ ++buf_alloc_fail: ++ ipu_desc_del(fh, desc); ++ kfree(desc); ++ ++desc_alloc_fail: ++ if (!IS_ERR(dbuf)) ++ dma_buf_put(dbuf); ++ return NULL; ++} ++ ++static long ipu_psys_mapbuf(int fd, struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys_kbuffer *kbuf; ++ ++ mutex_lock(&fh->mutex); ++ kbuf = ipu_psys_mapbuf_locked(fd, fh); ++ mutex_unlock(&fh->mutex); ++ ++ dev_dbg(&fh->psys->adev->auxdev.dev, "IOC_MAPBUF\n"); ++ ++ return kbuf ? 0 : -EINVAL; ++} ++ ++static long ipu_psys_unmapbuf(int fd, struct ipu_psys_fh *fh) ++{ ++ long ret; ++ ++ mutex_lock(&fh->mutex); ++ ret = ipu_psys_unmapbuf_locked(fd, fh); ++ mutex_unlock(&fh->mutex); ++ ++ dev_dbg(&fh->psys->adev->auxdev.dev, "IOC_UNMAPBUF\n"); ++ ++ return ret; ++} ++ ++static __poll_t ipu_psys_poll(struct file *file, ++ struct poll_table_struct *wait) ++{ ++ struct ipu_psys_fh *fh = file->private_data; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ __poll_t ret = 0; ++ ++ dev_dbg(dev, "ipu psys poll\n"); ++ ++ poll_wait(file, &fh->wait, wait); ++ ++ if (ipu_get_completed_kcmd(fh)) ++ ret = POLLIN; ++ ++ dev_dbg(dev, "ipu psys poll ret %u\n", ret); ++ ++ return ret; ++} ++ ++static long ipu_get_manifest(struct ipu_psys_manifest *manifest, ++ struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu6_bus_device *adev = psys->adev; ++ struct ipu6_device *isp = adev->isp; ++ struct ipu6_cpd_client_pkg_hdr *client_pkg; ++ u32 entries; ++ void *host_fw_data; ++ dma_addr_t dma_fw_data; ++ u32 client_pkg_offset; ++ ++ host_fw_data = (void *)isp->cpd_fw->data; ++ dma_fw_data = sg_dma_address(adev->fw_sgt.sgl); ++ entries = ipu6_cpd_pkg_dir_get_num_entries(adev->pkg_dir); ++ if (!manifest || manifest->index > entries - 1) { ++ dev_err(dev, "invalid argument\n"); ++ return -EINVAL; ++ } ++ ++ if (!ipu6_cpd_pkg_dir_get_size(adev->pkg_dir, manifest->index) || ++ ipu6_cpd_pkg_dir_get_type(adev->pkg_dir, manifest->index) < ++ IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE) { ++ dev_dbg(dev, "invalid pkg dir entry\n"); ++ return -ENOENT; ++ } ++ ++ client_pkg_offset = ipu6_cpd_pkg_dir_get_address(adev->pkg_dir, ++ manifest->index); ++ client_pkg_offset -= dma_fw_data; ++ ++ client_pkg = host_fw_data + client_pkg_offset; ++ manifest->size = client_pkg->pg_manifest_size; ++ ++ if (!manifest->manifest) ++ return 0; ++ ++ if (copy_to_user(manifest->manifest, ++ (uint8_t *)client_pkg + client_pkg->pg_manifest_offs, ++ manifest->size)) { ++ return -EFAULT; ++ } ++ ++ return 0; ++} ++ ++static long ipu_psys_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ union { ++ struct ipu_psys_buffer buf; ++ struct ipu_psys_command cmd; ++ struct ipu_psys_event ev; ++ struct ipu_psys_capability caps; ++ struct ipu_psys_manifest m; ++ } karg; ++ struct ipu_psys_fh *fh = file->private_data; ++ long err = 0; ++ void __user *up = (void __user *)arg; ++ bool copy = (cmd != IPU_IOC_MAPBUF && cmd != IPU_IOC_UNMAPBUF); ++ ++ if (copy) { ++ if (_IOC_SIZE(cmd) > sizeof(karg)) ++ return -ENOTTY; ++ ++ if (_IOC_DIR(cmd) & _IOC_WRITE) { ++ err = copy_from_user(&karg, up, _IOC_SIZE(cmd)); ++ if (err) ++ return -EFAULT; ++ } ++ } ++ ++ switch (cmd) { ++ case IPU_IOC_MAPBUF: ++ err = ipu_psys_mapbuf(arg, fh); ++ break; ++ case IPU_IOC_UNMAPBUF: ++ err = ipu_psys_unmapbuf(arg, fh); ++ break; ++ case IPU_IOC_QUERYCAP: ++ karg.caps = fh->psys->caps; ++ break; ++ case IPU_IOC_GETBUF: ++ err = ipu_psys_getbuf(&karg.buf, fh); ++ break; ++ case IPU_IOC_PUTBUF: ++ err = ipu_psys_putbuf(&karg.buf, fh); ++ break; ++ case IPU_IOC_QCMD: ++ err = ipu_psys_kcmd_new(&karg.cmd, fh); ++ break; ++ case IPU_IOC_DQEVENT: ++ err = ipu_ioctl_dqevent(&karg.ev, fh, file->f_flags); ++ break; ++ case IPU_IOC_GET_MANIFEST: ++ err = ipu_get_manifest(&karg.m, fh); ++ break; ++ default: ++ err = -ENOTTY; ++ break; ++ } ++ ++ if (err) ++ return err; ++ ++ if (copy && _IOC_DIR(cmd) & _IOC_READ) ++ if (copy_to_user(up, &karg, _IOC_SIZE(cmd))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static const struct file_operations ipu_psys_fops = { ++ .open = ipu_psys_open, ++ .release = ipu_psys_release, ++ .unlocked_ioctl = ipu_psys_ioctl, ++ .poll = ipu_psys_poll, ++ .owner = THIS_MODULE, ++}; ++ ++static void ipu_psys_dev_release(struct device *dev) ++{ ++} ++ ++#ifdef CONFIG_PM ++static int psys_runtime_pm_resume(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ struct ipu_psys *psys = ipu6_bus_get_drvdata(adev); ++ unsigned long flags; ++ int retval; ++ ++ if (!psys) ++ return 0; ++ ++ spin_lock_irqsave(&psys->ready_lock, flags); ++ if (psys->ready) { ++ spin_unlock_irqrestore(&psys->ready_lock, flags); ++ return 0; ++ } ++ spin_unlock_irqrestore(&psys->ready_lock, flags); ++ ++ retval = ipu6_mmu_hw_init(adev->mmu); ++ if (retval) ++ return retval; ++ ++ if (async_fw_init && !psys->fwcom) { ++ dev_err(dev, ++ "%s: asynchronous firmware init not finished, skipping\n", ++ __func__); ++ return 0; ++ } ++ ++ if (!ipu6_buttress_auth_done(adev->isp)) { ++ dev_dbg(dev, "%s: not yet authenticated, skipping\n", __func__); ++ return 0; ++ } ++ ++ ipu_psys_setup_hw(psys); ++ ++ ipu_psys_subdomains_power(psys, 1); ++ ipu6_configure_spc(adev->isp, ++ &psys->pdata->ipdata->hw_variant, ++ IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX, ++ psys->pdata->base, adev->pkg_dir, ++ adev->pkg_dir_dma_addr); ++ ++ retval = ipu_fw_psys_open(psys); ++ if (retval) { ++ dev_err(dev, "Failed to open abi.\n"); ++ return retval; ++ } ++ ++ spin_lock_irqsave(&psys->ready_lock, flags); ++ psys->ready = 1; ++ spin_unlock_irqrestore(&psys->ready_lock, flags); ++ ++ return 0; ++} ++ ++static int psys_runtime_pm_suspend(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ struct ipu_psys *psys = ipu6_bus_get_drvdata(adev); ++ unsigned long flags; ++ int rval; ++ ++ if (!psys) ++ return 0; ++ ++ if (!psys->ready) ++ return 0; ++ ++ spin_lock_irqsave(&psys->ready_lock, flags); ++ psys->ready = 0; ++ spin_unlock_irqrestore(&psys->ready_lock, flags); ++ ++ /* ++ * We can trace failure but better to not return an error. ++ * At suspend we are progressing towards psys power gated state. ++ * Any hang / failure inside psys will be forgotten soon. ++ */ ++ rval = ipu_fw_psys_close(psys); ++ if (rval) ++ dev_err(dev, "Device close failure: %d\n", rval); ++ ++ ipu_psys_subdomains_power(psys, 0); ++ ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++ return 0; ++} ++ ++/* The following PM callbacks are needed to enable runtime PM in IPU PCI ++ * device resume, otherwise, runtime PM can't work in PCI resume from ++ * S3 state. ++ */ ++static int psys_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++static int psys_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static const struct dev_pm_ops psys_pm_ops = { ++ .runtime_suspend = psys_runtime_pm_suspend, ++ .runtime_resume = psys_runtime_pm_resume, ++ .suspend = psys_suspend, ++ .resume = psys_resume, ++}; ++ ++#define PSYS_PM_OPS (&psys_pm_ops) ++#else ++#define PSYS_PM_OPS NULL ++#endif ++ ++static int ipu_psys_sched_cmd(void *ptr) ++{ ++ struct ipu_psys *psys = ptr; ++ size_t pending = 0; ++ ++ while (1) { ++ wait_event_interruptible(psys->sched_cmd_wq, ++ (kthread_should_stop() || ++ (pending = ++ atomic_read(&psys->wakeup_count)))); ++ ++ if (kthread_should_stop()) ++ break; ++ ++ if (pending == 0) ++ continue; ++ ++ mutex_lock(&psys->mutex); ++ atomic_set(&psys->wakeup_count, 0); ++ ipu_psys_run_next(psys); ++ mutex_unlock(&psys->mutex); ++ } ++ ++ return 0; ++} ++ ++static void start_sp(struct ipu6_bus_device *adev) ++{ ++ struct ipu_psys *psys = ipu6_bus_get_drvdata(adev); ++ void __iomem *spc_regs_base = psys->pdata->base + ++ psys->pdata->ipdata->hw_variant.spc_offset; ++ u32 val = 0; ++ ++ val |= IPU6_PSYS_SPC_STATUS_START | ++ IPU6_PSYS_SPC_STATUS_RUN | ++ IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; ++ val |= psys->icache_prefetch_sp ? ++ IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH : 0; ++ writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); ++} ++ ++static int query_sp(struct ipu6_bus_device *adev) ++{ ++ struct ipu_psys *psys = ipu6_bus_get_drvdata(adev); ++ void __iomem *spc_regs_base = psys->pdata->base + ++ psys->pdata->ipdata->hw_variant.spc_offset; ++ u32 val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); ++ ++ /* return true when READY == 1, START == 0 */ ++ val &= IPU6_PSYS_SPC_STATUS_READY | IPU6_PSYS_SPC_STATUS_START; ++ ++ return val == IPU6_PSYS_SPC_STATUS_READY; ++} ++ ++static int ipu_psys_fw_init(struct ipu_psys *psys) ++{ ++ struct ipu6_fw_syscom_queue_config *queue_cfg; ++ struct device *dev = &psys->adev->auxdev.dev; ++ unsigned int size; ++ struct ipu6_fw_syscom_queue_config fw_psys_event_queue_cfg[] = { ++ { ++ IPU_FW_PSYS_EVENT_QUEUE_SIZE, ++ sizeof(struct ipu_fw_psys_event) ++ } ++ }; ++ struct ipu_fw_psys_srv_init server_init = { ++ .ddr_pkg_dir_address = 0, ++ .host_ddr_pkg_dir = NULL, ++ .pkg_dir_size = 0, ++ .icache_prefetch_sp = psys->icache_prefetch_sp, ++ .icache_prefetch_isp = psys->icache_prefetch_isp, ++ }; ++ struct ipu6_fw_com_cfg fwcom = { ++ .num_output_queues = IPU_FW_PSYS_N_PSYS_EVENT_QUEUE_ID, ++ .output = fw_psys_event_queue_cfg, ++ .specific_addr = &server_init, ++ .specific_size = sizeof(server_init), ++ .cell_start = start_sp, ++ .cell_ready = query_sp, ++ .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_PSYS_OFFSET, ++ }; ++ int i; ++ ++ size = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ if (ipu_ver == IPU6_VER_6 || ipu_ver == IPU6_VER_6EP || ++ ipu_ver == IPU6_VER_6EP_MTL) ++ size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ ++ queue_cfg = devm_kzalloc(dev, sizeof(*queue_cfg) * size, ++ GFP_KERNEL); ++ if (!queue_cfg) ++ return -ENOMEM; ++ ++ for (i = 0; i < size; i++) { ++ queue_cfg[i].queue_size = IPU_FW_PSYS_CMD_QUEUE_SIZE; ++ queue_cfg[i].token_size = sizeof(struct ipu_fw_psys_cmd); ++ } ++ ++ fwcom.input = queue_cfg; ++ fwcom.num_input_queues = size; ++ fwcom.dmem_addr = psys->pdata->ipdata->hw_variant.dmem_offset; ++ ++ psys->fwcom = ipu6_fw_com_prepare(&fwcom, psys->adev, ++ psys->pdata->base); ++ if (!psys->fwcom) { ++ dev_err(dev, "psys fw com prepare failed\n"); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static void run_fw_init_work(struct work_struct *work) ++{ ++ struct fw_init_task *task = (struct fw_init_task *)work; ++ struct ipu_psys *psys = task->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ int rval; ++ ++ rval = ipu_psys_fw_init(psys); ++ ++ if (rval) { ++ dev_err(dev, "FW init failed(%d)\n", rval); ++ ipu6_psys_remove(&psys->adev->auxdev); ++ } else { ++ dev_info(dev, "FW init done\n"); ++ } ++} ++ ++static int ipu6_psys_probe(struct auxiliary_device *auxdev, ++ const struct auxiliary_device_id *auxdev_id) ++{ ++ struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); ++ struct device *dev = &auxdev->dev; ++ struct ipu_psys_pg *kpg, *kpg0; ++ struct ipu_psys *psys; ++ unsigned int minor; ++ int i, rval = -E2BIG; ++ ++ if (!adev->isp->bus_ready_to_probe) ++ return -EPROBE_DEFER; ++ ++ if (!adev->pkg_dir) ++ return -EPROBE_DEFER; ++ ++ ipu_ver = adev->isp->hw_ver; ++ ++ rval = alloc_chrdev_region(&ipu_psys_dev_t, 0, ++ IPU_PSYS_NUM_DEVICES, IPU6_PSYS_NAME); ++ if (rval) { ++ dev_err(dev, "can't alloc psys chrdev region (%d)\n", ++ rval); ++ return rval; ++ } ++ ++ rval = ipu6_mmu_hw_init(adev->mmu); ++ if (rval) ++ goto out_unregister_chr_region; ++ ++ mutex_lock(&ipu_psys_mutex); ++ ++ minor = find_next_zero_bit(ipu_psys_devices, IPU_PSYS_NUM_DEVICES, 0); ++ if (minor == IPU_PSYS_NUM_DEVICES) { ++ dev_err(dev, "too many devices\n"); ++ goto out_unlock; ++ } ++ ++ psys = devm_kzalloc(dev, sizeof(*psys), GFP_KERNEL); ++ if (!psys) { ++ rval = -ENOMEM; ++ goto out_unlock; ++ } ++ ++ adev->auxdrv_data = ++ (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; ++ adev->auxdrv = to_auxiliary_drv(dev->driver); ++ ++ psys->adev = adev; ++ psys->pdata = adev->pdata; ++ psys->icache_prefetch_sp = 0; ++ ++ psys->power_gating = 0; ++ ++ cdev_init(&psys->cdev, &ipu_psys_fops); ++ psys->cdev.owner = ipu_psys_fops.owner; ++ ++ rval = cdev_add(&psys->cdev, MKDEV(MAJOR(ipu_psys_dev_t), minor), 1); ++ if (rval) { ++ dev_err(dev, "cdev_add failed (%d)\n", rval); ++ goto out_unlock; ++ } ++ ++ set_bit(minor, ipu_psys_devices); ++ ++ spin_lock_init(&psys->ready_lock); ++ spin_lock_init(&psys->pgs_lock); ++ psys->ready = 0; ++ psys->timeout = IPU_PSYS_CMD_TIMEOUT_MS; ++ ++ mutex_init(&psys->mutex); ++ INIT_LIST_HEAD(&psys->fhs); ++ INIT_LIST_HEAD(&psys->pgs); ++ INIT_LIST_HEAD(&psys->started_kcmds_list); ++ ++ init_waitqueue_head(&psys->sched_cmd_wq); ++ atomic_set(&psys->wakeup_count, 0); ++ /* ++ * Create a thread to schedule commands sent to IPU firmware. ++ * The thread reduces the coupling between the command scheduler ++ * and queueing commands from the user to driver. ++ */ ++ psys->sched_cmd_thread = kthread_run(ipu_psys_sched_cmd, psys, ++ "psys_sched_cmd"); ++ ++ if (IS_ERR(psys->sched_cmd_thread)) { ++ psys->sched_cmd_thread = NULL; ++ mutex_destroy(&psys->mutex); ++ goto out_unlock; ++ } ++ ++ dev_set_drvdata(dev, psys); ++ ++ rval = ipu_psys_resource_pool_init(&psys->resource_pool_running); ++ if (rval < 0) { ++ dev_err(&psys->dev, ++ "unable to alloc process group resources\n"); ++ goto out_mutex_destroy; ++ } ++ ++ ipu6_psys_hw_res_variant_init(); ++ ++ /* allocate and map memory for process groups */ ++ for (i = 0; i < IPU_PSYS_PG_POOL_SIZE; i++) { ++ kpg = kzalloc(sizeof(*kpg), GFP_KERNEL); ++ if (!kpg) ++ goto out_free_pgs; ++ kpg->pg = dma_alloc_attrs(dev, IPU_PSYS_PG_MAX_SIZE, ++ &kpg->pg_dma_addr, ++ GFP_KERNEL, 0); ++ if (!kpg->pg) { ++ kfree(kpg); ++ goto out_free_pgs; ++ } ++ kpg->size = IPU_PSYS_PG_MAX_SIZE; ++ list_add(&kpg->list, &psys->pgs); ++ } ++ ++ psys->caps.pg_count = ipu6_cpd_pkg_dir_get_num_entries(adev->pkg_dir); ++ ++ dev_info(dev, "pkg_dir entry count:%d\n", psys->caps.pg_count); ++ if (async_fw_init) { ++ INIT_DELAYED_WORK((struct delayed_work *)&fw_init_task, ++ run_fw_init_work); ++ fw_init_task.psys = psys; ++ schedule_delayed_work((struct delayed_work *)&fw_init_task, 0); ++ } else { ++ rval = ipu_psys_fw_init(psys); ++ } ++ ++ if (rval) { ++ dev_err(dev, "FW init failed(%d)\n", rval); ++ goto out_free_pgs; ++ } ++ ++ psys->dev.bus = &ipu6_psys_bus; ++ psys->dev.parent = dev; ++ psys->dev.devt = MKDEV(MAJOR(ipu_psys_dev_t), minor); ++ psys->dev.release = ipu_psys_dev_release; ++ dev_set_name(&psys->dev, "ipu-psys%d", minor); ++ rval = device_register(&psys->dev); ++ if (rval < 0) { ++ dev_err(dev, "psys device_register failed\n"); ++ goto out_release_fw_com; ++ } ++ ++ /* Add the hw stepping information to caps */ ++ strscpy(psys->caps.dev_model, IPU6_MEDIA_DEV_MODEL_NAME, ++ sizeof(psys->caps.dev_model)); ++ ++ mutex_unlock(&ipu_psys_mutex); ++ ++ dev_info(dev, "psys probe minor: %d\n", minor); ++ ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++ return 0; ++ ++out_release_fw_com: ++ ipu6_fw_com_release(psys->fwcom, 1); ++out_free_pgs: ++ list_for_each_entry_safe(kpg, kpg0, &psys->pgs, list) { ++ dma_free_attrs(dev, kpg->size, kpg->pg, kpg->pg_dma_addr, 0); ++ kfree(kpg); ++ } ++ ++ ipu_psys_resource_pool_cleanup(&psys->resource_pool_running); ++out_mutex_destroy: ++ mutex_destroy(&psys->mutex); ++ cdev_del(&psys->cdev); ++ if (psys->sched_cmd_thread) { ++ kthread_stop(psys->sched_cmd_thread); ++ psys->sched_cmd_thread = NULL; ++ } ++out_unlock: ++ /* Safe to call even if the init is not called */ ++ mutex_unlock(&ipu_psys_mutex); ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++out_unregister_chr_region: ++ unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); ++ ++ return rval; ++} ++ ++static void ipu6_psys_remove(struct auxiliary_device *auxdev) ++{ ++ struct device *dev = &auxdev->dev; ++ struct ipu_psys *psys = dev_get_drvdata(&auxdev->dev); ++ struct ipu_psys_pg *kpg, *kpg0; ++ ++ unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); ++ ++ if (psys->sched_cmd_thread) { ++ kthread_stop(psys->sched_cmd_thread); ++ psys->sched_cmd_thread = NULL; ++ } ++ ++ mutex_lock(&ipu_psys_mutex); ++ ++ list_for_each_entry_safe(kpg, kpg0, &psys->pgs, list) { ++ dma_free_attrs(dev, kpg->size, kpg->pg, kpg->pg_dma_addr, 0); ++ kfree(kpg); ++ } ++ ++ if (psys->fwcom && ipu6_fw_com_release(psys->fwcom, 1)) ++ dev_err(dev, "fw com release failed.\n"); ++ ++ kfree(psys->server_init); ++ kfree(psys->syscom_config); ++ ++ ipu_psys_resource_pool_cleanup(&psys->resource_pool_running); ++ ++ device_unregister(&psys->dev); ++ ++ clear_bit(MINOR(psys->cdev.dev), ipu_psys_devices); ++ cdev_del(&psys->cdev); ++ ++ mutex_unlock(&ipu_psys_mutex); ++ ++ mutex_destroy(&psys->mutex); ++ ++ dev_info(dev, "removed\n"); ++} ++ ++static irqreturn_t psys_isr_threaded(struct ipu6_bus_device *adev) ++{ ++ struct ipu_psys *psys = ipu6_bus_get_drvdata(adev); ++ struct device *dev = &psys->adev->auxdev.dev; ++ void __iomem *base = psys->pdata->base; ++ u32 status; ++ int r; ++ ++ mutex_lock(&psys->mutex); ++ r = pm_runtime_get_if_in_use(dev); ++ if (!r || WARN_ON_ONCE(r < 0)) { ++ mutex_unlock(&psys->mutex); ++ return IRQ_NONE; ++ } ++ ++ status = readl(base + IPU6_REG_PSYS_GPDEV_IRQ_STATUS); ++ writel(status, base + IPU6_REG_PSYS_GPDEV_IRQ_CLEAR); ++ ++ if (status & IPU6_PSYS_GPDEV_IRQ_FWIRQ(IPU6_PSYS_GPDEV_FWIRQ0)) { ++ writel(0, base + IPU6_REG_PSYS_GPDEV_FWIRQ(0)); ++ ipu_psys_handle_events(psys); ++ } ++ ++ pm_runtime_put(dev); ++ mutex_unlock(&psys->mutex); ++ ++ return status ? IRQ_HANDLED : IRQ_NONE; ++} ++ ++static const struct ipu6_auxdrv_data ipu6_psys_auxdrv_data = { ++ .isr_threaded = psys_isr_threaded, ++ .wake_isr_thread = true, ++}; ++ ++static const struct auxiliary_device_id ipu6_psys_id_table[] = { ++ { ++ .name = "intel_ipu6.psys", ++ .driver_data = (kernel_ulong_t)&ipu6_psys_auxdrv_data, ++ }, ++ { } ++}; ++MODULE_DEVICE_TABLE(auxiliary, ipu6_psys_id_table); ++ ++static struct auxiliary_driver ipu6_psys_aux_driver = { ++ .name = IPU6_PSYS_NAME, ++ .probe = ipu6_psys_probe, ++ .remove = ipu6_psys_remove, ++ .id_table = ipu6_psys_id_table, ++ .driver = { ++ .pm = &psys_pm_ops, ++ }, ++}; ++module_auxiliary_driver(ipu6_psys_aux_driver); ++ ++MODULE_AUTHOR("Antti Laakso "); ++MODULE_AUTHOR("Bin Han "); ++MODULE_AUTHOR("Renwei Wu "); ++MODULE_AUTHOR("Jianxu Zheng "); ++MODULE_AUTHOR("Xia Wu "); ++MODULE_AUTHOR("Bingbu Cao "); ++MODULE_AUTHOR("Zaikuo Wang "); ++MODULE_AUTHOR("Yunliang Ding "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Intel ipu processing system driver"); ++MODULE_IMPORT_NS(DMA_BUF); ++MODULE_IMPORT_NS(INTEL_IPU6); +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-psys.h b/drivers/media/pci/intel/ipu6/psys/ipu-psys.h +new file mode 100644 +index 000000000000..a552ff08dd96 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-psys.h +@@ -0,0 +1,324 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2013 - 2024 Intel Corporation */ ++ ++#ifndef IPU_PSYS_H ++#define IPU_PSYS_H ++ ++#include ++#include ++ ++#include ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu-fw-psys.h" ++#include "ipu-platform-psys.h" ++ ++/* PSYS Info bits*/ ++#define IPU_REG_PSYS_INFO_SEG_CMEM_MASTER(a) (0x2c + ((a) * 12)) ++#define IPU_REG_PSYS_INFO_SEG_XMEM_MASTER(a) (0x5c + ((a) * 12)) ++ ++#define IPU_PSYS_REG_SPC_STATUS_CTRL 0x0 ++#define IPU_PSYS_REG_SPC_START_PC 0x4 ++#define IPU_PSYS_REG_SPC_ICACHE_BASE 0x10 ++#define IPU_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14 ++ ++#define IPU_PSYS_SPC_STATUS_START BIT(1) ++#define IPU_PSYS_SPC_STATUS_RUN BIT(3) ++#define IPU_PSYS_SPC_STATUS_READY BIT(5) ++#define IPU_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) ++#define IPU_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) ++ ++#define IPU_PSYS_REG_SPP0_STATUS_CTRL 0x20000 ++ ++#define IPU_INFO_ENABLE_SNOOP BIT(0) ++#define IPU_INFO_DEC_FORCE_FLUSH BIT(1) ++#define IPU_INFO_DEC_PASS_THRU BIT(2) ++#define IPU_INFO_ZLW BIT(3) ++#define IPU_INFO_STREAM_ID_SET(id) (((id) & 0x1f) << 4) ++#define IPU_INFO_REQUEST_DESTINATION_IOSF BIT(9) ++#define IPU_INFO_IMR_BASE BIT(10) ++#define IPU_INFO_IMR_DESTINED BIT(11) ++ ++#define IPU_INFO_REQUEST_DESTINATION_PRIMARY IPU_INFO_REQUEST_DESTINATION_IOSF ++ ++#define IPU_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000 ++#define IPU_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000 ++#define IPU_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc) ++#define IPU_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc) ++#define IPU_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc) ++ ++enum ipu_device_ab_group1_target_id { ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R16_GP, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER, ++ IPU_DEVICE_AB_GROUP1_TARGET_ID_R18_AB, ++}; ++ ++/* IRQ-related registers in PSYS */ ++#define IPU_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200 ++#define IPU_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204 ++#define IPU_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208 ++#define IPU_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c ++#define IPU_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210 ++#define IPU_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214 ++/* There are 8 FW interrupts, n = 0..7 */ ++#define IPU_PSYS_GPDEV_FWIRQ0 5 ++#define IPU_PSYS_GPDEV_FWIRQ1 6 ++#define IPU_PSYS_GPDEV_FWIRQ2 7 ++#define IPU_PSYS_GPDEV_FWIRQ3 8 ++#define IPU_PSYS_GPDEV_FWIRQ4 9 ++#define IPU_PSYS_GPDEV_FWIRQ5 10 ++#define IPU_PSYS_GPDEV_FWIRQ6 11 ++#define IPU_PSYS_GPDEV_FWIRQ7 12 ++#define IPU_PSYS_GPDEV_IRQ_FWIRQ(n) (1 << (n)) ++#define IPU_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100) ++ ++/* ++ * psys subdomains power request regs ++ */ ++enum ipu_device_buttress_psys_domain_pos { ++ IPU_PSYS_SUBDOMAIN_ISA = 0, ++ IPU_PSYS_SUBDOMAIN_PSA = 1, ++ IPU_PSYS_SUBDOMAIN_BB = 2, ++ IPU_PSYS_SUBDOMAIN_IDSP1 = 3, /* only in IPU6M */ ++ IPU_PSYS_SUBDOMAIN_IDSP2 = 4, /* only in IPU6M */ ++}; ++ ++#define IPU_PSYS_SUBDOMAINS_POWER_MASK (BIT(IPU_PSYS_SUBDOMAIN_ISA) | \ ++ BIT(IPU_PSYS_SUBDOMAIN_PSA) | \ ++ BIT(IPU_PSYS_SUBDOMAIN_BB)) ++ ++#define IPU_PSYS_SUBDOMAINS_POWER_REQ 0xa0 ++#define IPU_PSYS_SUBDOMAINS_POWER_STATUS 0xa4 ++ ++#define IPU_PSYS_CMD_TIMEOUT_MS 2000 ++#define IPU_PSYS_OPEN_TIMEOUT_US 50 ++#define IPU_PSYS_OPEN_RETRY (10000 / IPU_PSYS_OPEN_TIMEOUT_US) ++ ++#define IPU_PSYS_PG_POOL_SIZE 16 ++#define IPU_PSYS_PG_MAX_SIZE 8192 ++#define IPU_MAX_PSYS_CMD_BUFFERS 32 ++#define IPU_PSYS_EVENT_CMD_COMPLETE IPU_FW_PSYS_EVENT_TYPE_SUCCESS ++#define IPU_PSYS_EVENT_FRAGMENT_COMPLETE IPU_FW_PSYS_EVENT_TYPE_SUCCESS ++#define IPU_PSYS_CLOSE_TIMEOUT_US 50 ++#define IPU_PSYS_CLOSE_TIMEOUT (100000 / IPU_PSYS_CLOSE_TIMEOUT_US) ++#define IPU_MAX_RESOURCES 128 ++ ++extern enum ipu6_version ipu_ver; ++ ++/* Opaque structure. Do not access fields. */ ++struct ipu_resource { ++ u32 id; ++ int elements; /* Number of elements available to allocation */ ++ unsigned long *bitmap; /* Allocation bitmap, a bit for each element */ ++}; ++ ++enum ipu_resource_type { ++ IPU_RESOURCE_DEV_CHN = 0, ++ IPU_RESOURCE_EXT_MEM, ++ IPU_RESOURCE_DFM ++}; ++ ++/* Allocation of resource(s) */ ++/* Opaque structure. Do not access fields. */ ++struct ipu_resource_alloc { ++ enum ipu_resource_type type; ++ struct ipu_resource *resource; ++ int elements; ++ int pos; ++}; ++ ++/* ++ * This struct represents all of the currently allocated ++ * resources from IPU model. It is used also for allocating ++ * resources for the next set of PGs to be run on IPU ++ * (ie. those PGs which are not yet being run and which don't ++ * yet reserve real IPU resources). ++ * Use larger array to cover existing resource quantity ++ */ ++ ++/* resource size may need expand for new resource model */ ++struct ipu_psys_resource_pool { ++ u32 cells; /* Bitmask of cells allocated */ ++ struct ipu_resource dev_channels[16]; ++ struct ipu_resource ext_memory[32]; ++ struct ipu_resource dfms[16]; ++ DECLARE_BITMAP(cmd_queues, 32); ++ /* Protects cmd_queues bitmap */ ++ spinlock_t queues_lock; ++}; ++ ++/* ++ * This struct keeps book of the resources allocated for a specific PG. ++ * It is used for freeing up resources from struct ipu_psys_resources ++ * when the PG is released from IPU (or model of IPU). ++ */ ++struct ipu_psys_resource_alloc { ++ u32 cells; /* Bitmask of cells needed */ ++ struct ipu_resource_alloc ++ resource_alloc[IPU_MAX_RESOURCES]; ++ int resources; ++}; ++ ++struct task_struct; ++struct ipu_psys { ++ struct ipu_psys_capability caps; ++ struct cdev cdev; ++ struct device dev; ++ ++ struct mutex mutex; /* Psys various */ ++ int ready; /* psys fw status */ ++ bool icache_prefetch_sp; ++ bool icache_prefetch_isp; ++ spinlock_t ready_lock; /* protect psys firmware state */ ++ spinlock_t pgs_lock; /* Protect pgs list access */ ++ struct list_head fhs; ++ struct list_head pgs; ++ struct list_head started_kcmds_list; ++ struct ipu6_psys_pdata *pdata; ++ struct ipu6_bus_device *adev; ++ struct ia_css_syscom_context *dev_ctx; ++ struct ia_css_syscom_config *syscom_config; ++ struct ia_css_psys_server_init *server_init; ++ struct task_struct *sched_cmd_thread; ++ wait_queue_head_t sched_cmd_wq; ++ atomic_t wakeup_count; /* Psys schedule thread wakeup count */ ++ ++ /* Resources needed to be managed for process groups */ ++ struct ipu_psys_resource_pool resource_pool_running; ++ ++ const struct firmware *fw; ++ struct sg_table fw_sgt; ++ u64 *pkg_dir; ++ dma_addr_t pkg_dir_dma_addr; ++ unsigned int pkg_dir_size; ++ unsigned long timeout; ++ ++ int active_kcmds, started_kcmds; ++ void *fwcom; ++ ++ int power_gating; ++}; ++ ++struct ipu_psys_fh { ++ struct ipu_psys *psys; ++ struct mutex mutex; /* Protects bufs_list & kcmds fields */ ++ struct list_head list; ++ /* Holds all buffers that this fh owns */ ++ struct list_head bufs_list; ++ /* Holds all descriptors (fd:kbuffer associations) */ ++ struct list_head descs_list; ++ struct list_head bufs_lru; ++ wait_queue_head_t wait; ++ struct ipu_psys_scheduler sched; ++ ++ u32 num_bufs; ++ u32 num_descs; ++ u32 num_bufs_lru; ++}; ++ ++struct ipu_psys_pg { ++ struct ipu_fw_psys_process_group *pg; ++ size_t size; ++ size_t pg_size; ++ dma_addr_t pg_dma_addr; ++ struct list_head list; ++ struct ipu_psys_resource_alloc resource_alloc; ++}; ++ ++struct ipu6_psys_constraint { ++ struct list_head list; ++ unsigned int min_freq; ++}; ++ ++struct ipu_psys_kcmd { ++ struct ipu_psys_fh *fh; ++ struct list_head list; ++ struct ipu_psys_buffer_set *kbuf_set; ++ enum ipu_psys_cmd_state state; ++ void *pg_manifest; ++ size_t pg_manifest_size; ++ struct ipu_psys_kbuffer **kbufs; ++ struct ipu_psys_buffer *buffers; ++ size_t nbuffers; ++ struct ipu_fw_psys_process_group *pg_user; ++ struct ipu_psys_pg *kpg; ++ u64 user_token; ++ u64 issue_id; ++ u32 priority; ++ u32 kernel_enable_bitmap[4]; ++ u32 terminal_enable_bitmap[4]; ++ u32 routing_enable_bitmap[4]; ++ u32 rbm[5]; ++ struct ipu6_psys_constraint constraint; ++ struct ipu_psys_event ev; ++ struct timer_list watchdog; ++}; ++ ++struct ipu_dma_buf_attach { ++ struct device *dev; ++ u64 len; ++ void *userptr; ++ struct sg_table *sgt; ++ struct page **pages; ++ size_t npages; ++}; ++ ++struct ipu_psys_kbuffer { ++ u64 len; ++ void *userptr; ++ void *kaddr; ++ struct list_head list; ++ dma_addr_t dma_addr; ++ struct sg_table *sgt; ++ struct dma_buf_attachment *db_attach; ++ struct dma_buf *dbuf; ++ u32 flags; ++ /* The number of times this buffer is mapped */ ++ atomic_t map_count; ++ bool valid; /* True when buffer is usable */ ++}; ++ ++struct ipu_psys_desc { ++ struct ipu_psys_kbuffer *kbuf; ++ struct list_head list; ++ u32 fd; ++}; ++ ++#define inode_to_ipu_psys(inode) \ ++ container_of((inode)->i_cdev, struct ipu_psys, cdev) ++ ++void ipu_psys_setup_hw(struct ipu_psys *psys); ++void ipu_psys_subdomains_power(struct ipu_psys *psys, bool on); ++void ipu_psys_handle_events(struct ipu_psys *psys); ++int ipu_psys_kcmd_new(struct ipu_psys_command *cmd, struct ipu_psys_fh *fh); ++void ipu_psys_run_next(struct ipu_psys *psys); ++struct ipu_psys_pg *__get_pg_buf(struct ipu_psys *psys, size_t pg_size); ++struct ipu_psys_kbuffer * ++ipu_psys_lookup_kbuffer(struct ipu_psys_fh *fh, int fd); ++struct ipu_psys_kbuffer * ++ipu_psys_mapbuf_locked(int fd, struct ipu_psys_fh *fh); ++struct ipu_psys_kbuffer * ++ipu_psys_lookup_kbuffer_by_kaddr(struct ipu_psys_fh *fh, void *kaddr); ++int ipu_psys_resource_pool_init(struct ipu_psys_resource_pool *pool); ++void ipu_psys_resource_pool_cleanup(struct ipu_psys_resource_pool *pool); ++struct ipu_psys_kcmd *ipu_get_completed_kcmd(struct ipu_psys_fh *fh); ++long ipu_ioctl_dqevent(struct ipu_psys_event *event, ++ struct ipu_psys_fh *fh, unsigned int f_flags); ++ ++#endif /* IPU_PSYS_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu-resources.c b/drivers/media/pci/intel/ipu6/psys/ipu-resources.c +new file mode 100644 +index 000000000000..824791856bc8 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu-resources.c +@@ -0,0 +1,868 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2015 - 2024 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ipu-fw-psys.h" ++#include "ipu-psys.h" ++ ++struct ipu6_psys_hw_res_variant hw_var; ++void ipu6_psys_hw_res_variant_init(void) ++{ ++ if (ipu_ver == IPU6_VER_6SE) { ++ hw_var.queue_num = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ hw_var.cell_num = IPU6SE_FW_PSYS_N_CELL_ID; ++ } else if (ipu_ver == IPU6_VER_6) { ++ hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ hw_var.cell_num = IPU6_FW_PSYS_N_CELL_ID; ++ } else if (ipu_ver == IPU6_VER_6EP || ipu_ver == IPU6_VER_6EP_MTL) { ++ hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ hw_var.cell_num = IPU6EP_FW_PSYS_N_CELL_ID; ++ } else { ++ WARN(1, "ipu6 psys res var is not initialised correctly."); ++ } ++ ++ hw_var.set_proc_dev_chn = ipu6_fw_psys_set_proc_dev_chn; ++ hw_var.set_proc_dfm_bitmap = ipu6_fw_psys_set_proc_dfm_bitmap; ++ hw_var.set_proc_ext_mem = ipu6_fw_psys_set_process_ext_mem; ++ hw_var.get_pgm_by_proc = ++ ipu6_fw_psys_get_program_manifest_by_process; ++} ++ ++static const struct ipu_fw_resource_definitions *get_res(void) ++{ ++ if (ipu_ver == IPU6_VER_6SE) ++ return ipu6se_res_defs; ++ ++ if (ipu_ver == IPU6_VER_6EP || ipu_ver == IPU6_VER_6EP_MTL) ++ return ipu6ep_res_defs; ++ ++ return ipu6_res_defs; ++} ++ ++static int ipu_resource_init(struct ipu_resource *res, u32 id, int elements) ++{ ++ if (elements <= 0) { ++ res->bitmap = NULL; ++ return 0; ++ } ++ ++ res->bitmap = bitmap_zalloc(elements, GFP_KERNEL); ++ if (!res->bitmap) ++ return -ENOMEM; ++ res->elements = elements; ++ res->id = id; ++ return 0; ++} ++ ++static unsigned long ++ipu_resource_alloc_with_pos(struct ipu_resource *res, int n, ++ int pos, ++ struct ipu_resource_alloc *alloc, ++ enum ipu_resource_type type) ++{ ++ unsigned long p; ++ ++ if (n <= 0) { ++ alloc->elements = 0; ++ return 0; ++ } ++ ++ if (!res->bitmap || pos >= res->elements) ++ return (unsigned long)(-ENOSPC); ++ ++ p = bitmap_find_next_zero_area(res->bitmap, res->elements, pos, n, 0); ++ alloc->resource = NULL; ++ ++ if (p != pos) ++ return (unsigned long)(-ENOSPC); ++ bitmap_set(res->bitmap, p, n); ++ alloc->resource = res; ++ alloc->elements = n; ++ alloc->pos = p; ++ alloc->type = type; ++ ++ return pos; ++} ++ ++static unsigned long ++ipu_resource_alloc(struct ipu_resource *res, int n, ++ struct ipu_resource_alloc *alloc, ++ enum ipu_resource_type type) ++{ ++ unsigned long p; ++ ++ if (n <= 0) { ++ alloc->elements = 0; ++ return 0; ++ } ++ ++ if (!res->bitmap) ++ return (unsigned long)(-ENOSPC); ++ ++ p = bitmap_find_next_zero_area(res->bitmap, res->elements, 0, n, 0); ++ alloc->resource = NULL; ++ ++ if (p >= res->elements) ++ return (unsigned long)(-ENOSPC); ++ bitmap_set(res->bitmap, p, n); ++ alloc->resource = res; ++ alloc->elements = n; ++ alloc->pos = p; ++ alloc->type = type; ++ ++ return p; ++} ++ ++static void ipu_resource_free(struct ipu_resource_alloc *alloc) ++{ ++ if (alloc->elements <= 0) ++ return; ++ ++ if (alloc->type == IPU_RESOURCE_DFM) ++ *alloc->resource->bitmap &= ~(unsigned long)(alloc->elements); ++ else ++ bitmap_clear(alloc->resource->bitmap, alloc->pos, ++ alloc->elements); ++ alloc->resource = NULL; ++} ++ ++static void ipu_resource_cleanup(struct ipu_resource *res) ++{ ++ bitmap_free(res->bitmap); ++ res->bitmap = NULL; ++} ++ ++/********** IPU PSYS-specific resource handling **********/ ++int ipu_psys_resource_pool_init(struct ipu_psys_resource_pool *pool) ++{ ++ int i, j, k, ret; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ res_defs = get_res(); ++ ++ spin_lock_init(&pool->queues_lock); ++ pool->cells = 0; ++ ++ for (i = 0; i < res_defs->num_dev_channels; i++) { ++ ret = ipu_resource_init(&pool->dev_channels[i], i, ++ res_defs->dev_channels[i]); ++ if (ret) ++ goto error; ++ } ++ ++ for (j = 0; j < res_defs->num_ext_mem_ids; j++) { ++ ret = ipu_resource_init(&pool->ext_memory[j], j, ++ res_defs->ext_mem_ids[j]); ++ if (ret) ++ goto memory_error; ++ } ++ ++ for (k = 0; k < res_defs->num_dfm_ids; k++) { ++ ret = ipu_resource_init(&pool->dfms[k], k, res_defs->dfms[k]); ++ if (ret) ++ goto dfm_error; ++ } ++ ++ spin_lock(&pool->queues_lock); ++ if (ipu_ver == IPU6_VER_6SE) ++ bitmap_zero(pool->cmd_queues, ++ IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID); ++ else ++ bitmap_zero(pool->cmd_queues, ++ IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID); ++ spin_unlock(&pool->queues_lock); ++ ++ return 0; ++ ++dfm_error: ++ for (k--; k >= 0; k--) ++ ipu_resource_cleanup(&pool->dfms[k]); ++ ++memory_error: ++ for (j--; j >= 0; j--) ++ ipu_resource_cleanup(&pool->ext_memory[j]); ++ ++error: ++ for (i--; i >= 0; i--) ++ ipu_resource_cleanup(&pool->dev_channels[i]); ++ return ret; ++} ++ ++void ipu_psys_resource_copy(struct ipu_psys_resource_pool *src, ++ struct ipu_psys_resource_pool *dest) ++{ ++ int i; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ res_defs = get_res(); ++ ++ dest->cells = src->cells; ++ for (i = 0; i < res_defs->num_dev_channels; i++) ++ *dest->dev_channels[i].bitmap = *src->dev_channels[i].bitmap; ++ ++ for (i = 0; i < res_defs->num_ext_mem_ids; i++) ++ *dest->ext_memory[i].bitmap = *src->ext_memory[i].bitmap; ++ ++ for (i = 0; i < res_defs->num_dfm_ids; i++) ++ *dest->dfms[i].bitmap = *src->dfms[i].bitmap; ++} ++ ++void ipu_psys_resource_pool_cleanup(struct ipu_psys_resource_pool ++ *pool) ++{ ++ u32 i; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ res_defs = get_res(); ++ for (i = 0; i < res_defs->num_dev_channels; i++) ++ ipu_resource_cleanup(&pool->dev_channels[i]); ++ ++ for (i = 0; i < res_defs->num_ext_mem_ids; i++) ++ ipu_resource_cleanup(&pool->ext_memory[i]); ++ ++ for (i = 0; i < res_defs->num_dfm_ids; i++) ++ ipu_resource_cleanup(&pool->dfms[i]); ++} ++ ++static int __alloc_one_resrc(const struct device *dev, ++ struct ipu_fw_psys_process *process, ++ struct ipu_resource *resource, ++ struct ipu_fw_generic_program_manifest *pm, ++ u32 resource_id, ++ struct ipu_psys_resource_alloc *alloc) ++{ ++ const u16 resource_req = pm->dev_chn_size[resource_id]; ++ const u16 resource_offset_req = pm->dev_chn_offset[resource_id]; ++ unsigned long retl; ++ ++ if (!resource_req) ++ return -ENXIO; ++ ++ if (alloc->resources >= IPU_MAX_RESOURCES) { ++ dev_err(dev, "out of resource handles\n"); ++ return -ENOSPC; ++ } ++ if (resource_offset_req != (u16)(-1)) ++ retl = ipu_resource_alloc_with_pos ++ (resource, ++ resource_req, ++ resource_offset_req, ++ &alloc->resource_alloc[alloc->resources], ++ IPU_RESOURCE_DEV_CHN); ++ else ++ retl = ipu_resource_alloc ++ (resource, resource_req, ++ &alloc->resource_alloc[alloc->resources], ++ IPU_RESOURCE_DEV_CHN); ++ if (IS_ERR_VALUE(retl)) { ++ dev_dbg(dev, "out of device channel resources\n"); ++ return (int)retl; ++ } ++ alloc->resources++; ++ ++ return 0; ++} ++ ++static int ipu_psys_allocate_one_dfm(const struct device *dev, ++ struct ipu_fw_psys_process *process, ++ struct ipu_resource *resource, ++ struct ipu_fw_generic_program_manifest *pm, ++ u32 resource_id, ++ struct ipu_psys_resource_alloc *alloc) ++{ ++ u32 dfm_bitmap_req = pm->dfm_port_bitmap[resource_id]; ++ u32 active_dfm_bitmap_req = pm->dfm_active_port_bitmap[resource_id]; ++ const u8 is_relocatable = pm->is_dfm_relocatable[resource_id]; ++ struct ipu_resource_alloc *alloc_resource; ++ unsigned long p = 0; ++ ++ if (!dfm_bitmap_req) ++ return -ENXIO; ++ ++ if (alloc->resources >= IPU_MAX_RESOURCES) { ++ dev_err(dev, "out of resource handles\n"); ++ return -ENOSPC; ++ } ++ ++ if (!resource->bitmap) ++ return -ENOSPC; ++ ++ if (!is_relocatable) { ++ if (*resource->bitmap & dfm_bitmap_req) { ++ dev_warn(dev, ++ "out of dfm resources, req 0x%x, get 0x%lx\n", ++ dfm_bitmap_req, *resource->bitmap); ++ return -ENOSPC; ++ } ++ *resource->bitmap |= dfm_bitmap_req; ++ } else { ++ unsigned int n = hweight32(dfm_bitmap_req); ++ ++ p = bitmap_find_next_zero_area(resource->bitmap, ++ resource->elements, 0, n, 0); ++ ++ if (p >= resource->elements) ++ return -ENOSPC; ++ ++ bitmap_set(resource->bitmap, p, n); ++ dfm_bitmap_req = dfm_bitmap_req << p; ++ active_dfm_bitmap_req = active_dfm_bitmap_req << p; ++ } ++ ++ alloc_resource = &alloc->resource_alloc[alloc->resources]; ++ alloc_resource->resource = resource; ++ /* Using elements to indicate the bitmap */ ++ alloc_resource->elements = dfm_bitmap_req; ++ alloc_resource->pos = p; ++ alloc_resource->type = IPU_RESOURCE_DFM; ++ ++ alloc->resources++; ++ ++ return 0; ++} ++ ++/* ++ * ext_mem_type_id is a generic type id for memory (like DMEM, VMEM) ++ * ext_mem_bank_id is detailed type id for memory (like DMEM0, DMEM1 etc.) ++ */ ++static int __alloc_mem_resrc(const struct device *dev, ++ struct ipu_fw_psys_process *process, ++ struct ipu_resource *resource, ++ struct ipu_fw_generic_program_manifest *pm, ++ u32 ext_mem_type_id, u32 ext_mem_bank_id, ++ struct ipu_psys_resource_alloc *alloc) ++{ ++ const u16 memory_resource_req = pm->ext_mem_size[ext_mem_type_id]; ++ const u16 memory_offset_req = pm->ext_mem_offset[ext_mem_type_id]; ++ ++ unsigned long retl; ++ ++ if (!memory_resource_req) ++ return -ENXIO; ++ ++ if (alloc->resources >= IPU_MAX_RESOURCES) { ++ dev_err(dev, "out of resource handles\n"); ++ return -ENOSPC; ++ } ++ if (memory_offset_req != (u16)(-1)) ++ retl = ipu_resource_alloc_with_pos ++ (resource, ++ memory_resource_req, memory_offset_req, ++ &alloc->resource_alloc[alloc->resources], ++ IPU_RESOURCE_EXT_MEM); ++ else ++ retl = ipu_resource_alloc ++ (resource, memory_resource_req, ++ &alloc->resource_alloc[alloc->resources], ++ IPU_RESOURCE_EXT_MEM); ++ if (IS_ERR_VALUE(retl)) { ++ dev_dbg(dev, "out of memory resources\n"); ++ return (int)retl; ++ } ++ ++ alloc->resources++; ++ ++ return 0; ++} ++ ++int ipu_psys_allocate_cmd_queue_resource(struct ipu_psys_resource_pool *pool) ++{ ++ unsigned long p; ++ int size, start; ++ ++ size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ start = IPU6_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID; ++ ++ if (ipu_ver == IPU6_VER_6SE) { ++ size = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID; ++ start = IPU6SE_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID; ++ } ++ ++ spin_lock(&pool->queues_lock); ++ /* find available cmd queue from ppg0_cmd_id */ ++ p = bitmap_find_next_zero_area(pool->cmd_queues, size, start, 1, 0); ++ ++ if (p >= size) { ++ spin_unlock(&pool->queues_lock); ++ return -ENOSPC; ++ } ++ ++ bitmap_set(pool->cmd_queues, p, 1); ++ spin_unlock(&pool->queues_lock); ++ ++ return p; ++} ++ ++void ipu_psys_free_cmd_queue_resource(struct ipu_psys_resource_pool *pool, ++ u8 queue_id) ++{ ++ spin_lock(&pool->queues_lock); ++ bitmap_clear(pool->cmd_queues, queue_id, 1); ++ spin_unlock(&pool->queues_lock); ++} ++ ++int ipu_psys_try_allocate_resources(struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ struct ipu_psys_resource_pool *pool) ++{ ++ u32 id, idx; ++ u32 mem_type_id; ++ int ret, i; ++ u16 *process_offset_table; ++ u8 processes; ++ u32 cells = 0; ++ struct ipu_psys_resource_alloc *alloc; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ if (!pg) ++ return -EINVAL; ++ process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset); ++ processes = pg->process_count; ++ ++ alloc = kzalloc(sizeof(*alloc), GFP_KERNEL); ++ if (!alloc) ++ return -ENOMEM; ++ ++ res_defs = get_res(); ++ for (i = 0; i < processes; i++) { ++ u32 cell; ++ struct ipu_fw_psys_process *process = ++ (struct ipu_fw_psys_process *) ++ ((char *)pg + process_offset_table[i]); ++ struct ipu_fw_generic_program_manifest pm; ++ ++ memset(&pm, 0, sizeof(pm)); ++ ++ if (!process) { ++ dev_err(dev, "can not get process\n"); ++ ret = -ENOENT; ++ goto free_out; ++ } ++ ++ ret = ipu_fw_psys_get_program_manifest_by_process ++ (&pm, pg_manifest, process); ++ if (ret < 0) { ++ dev_err(dev, "can not get manifest\n"); ++ goto free_out; ++ } ++ ++ if (pm.cell_id == res_defs->num_cells && ++ pm.cell_type_id == res_defs->num_cells_type) { ++ cell = res_defs->num_cells; ++ } else if ((pm.cell_id != res_defs->num_cells && ++ pm.cell_type_id == res_defs->num_cells_type)) { ++ cell = pm.cell_id; ++ } else { ++ /* Find a free cell of desired type */ ++ u32 type = pm.cell_type_id; ++ ++ for (cell = 0; cell < res_defs->num_cells; cell++) ++ if (res_defs->cells[cell] == type && ++ ((pool->cells | cells) & (1 << cell)) == 0) ++ break; ++ if (cell >= res_defs->num_cells) { ++ dev_dbg(dev, "no free cells of right type\n"); ++ ret = -ENOSPC; ++ goto free_out; ++ } ++ } ++ if (cell < res_defs->num_cells) ++ cells |= 1 << cell; ++ if (pool->cells & cells) { ++ dev_dbg(dev, "out of cell resources\n"); ++ ret = -ENOSPC; ++ goto free_out; ++ } ++ ++ if (pm.dev_chn_size) { ++ for (id = 0; id < res_defs->num_dev_channels; id++) { ++ ret = __alloc_one_resrc(dev, process, ++ &pool->dev_channels[id], ++ &pm, id, alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ } ++ } ++ ++ if (pm.dfm_port_bitmap) { ++ for (id = 0; id < res_defs->num_dfm_ids; id++) { ++ ret = ipu_psys_allocate_one_dfm ++ (dev, process, ++ &pool->dfms[id], &pm, id, alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ } ++ } ++ ++ if (pm.ext_mem_size) { ++ for (mem_type_id = 0; ++ mem_type_id < res_defs->num_ext_mem_types; ++ mem_type_id++) { ++ u32 bank = res_defs->num_ext_mem_ids; ++ ++ if (cell != res_defs->num_cells) { ++ idx = res_defs->cell_mem_row * cell + ++ mem_type_id; ++ bank = res_defs->cell_mem[idx]; ++ } ++ ++ if (bank == res_defs->num_ext_mem_ids) ++ continue; ++ ++ ret = __alloc_mem_resrc(dev, process, ++ &pool->ext_memory[bank], ++ &pm, mem_type_id, bank, ++ alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ } ++ } ++ } ++ ++ pool->cells |= cells; ++ ++ kfree(alloc); ++ return 0; ++ ++free_out: ++ dev_dbg(dev, "failed to try_allocate resource\n"); ++ kfree(alloc); ++ return ret; ++} ++ ++/* ++ * Allocate resources for pg from `pool'. Mark the allocated ++ * resources into `alloc'. Returns 0 on success, -ENOSPC ++ * if there are no enough resources, in which cases resources ++ * are not allocated at all, or some other error on other conditions. ++ */ ++int ipu_psys_allocate_resources(const struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ struct ipu_psys_resource_alloc ++ *alloc, struct ipu_psys_resource_pool ++ *pool) ++{ ++ u32 id; ++ u32 mem_type_id; ++ int ret, i; ++ u16 *process_offset_table; ++ u8 processes; ++ u32 cells = 0; ++ int p, idx; ++ u32 bmp, a_bmp; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ if (!pg) ++ return -EINVAL; ++ ++ res_defs = get_res(); ++ process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset); ++ processes = pg->process_count; ++ ++ for (i = 0; i < processes; i++) { ++ u32 cell; ++ struct ipu_fw_psys_process *process = ++ (struct ipu_fw_psys_process *) ++ ((char *)pg + process_offset_table[i]); ++ struct ipu_fw_generic_program_manifest pm; ++ ++ memset(&pm, 0, sizeof(pm)); ++ if (!process) { ++ dev_err(dev, "can not get process\n"); ++ ret = -ENOENT; ++ goto free_out; ++ } ++ ++ ret = ipu_fw_psys_get_program_manifest_by_process ++ (&pm, pg_manifest, process); ++ if (ret < 0) { ++ dev_err(dev, "can not get manifest\n"); ++ goto free_out; ++ } ++ ++ if (pm.cell_id == res_defs->num_cells && ++ pm.cell_type_id == res_defs->num_cells_type) { ++ cell = res_defs->num_cells; ++ } else if ((pm.cell_id != res_defs->num_cells && ++ pm.cell_type_id == res_defs->num_cells_type)) { ++ cell = pm.cell_id; ++ } else { ++ /* Find a free cell of desired type */ ++ u32 type = pm.cell_type_id; ++ ++ for (cell = 0; cell < res_defs->num_cells; cell++) ++ if (res_defs->cells[cell] == type && ++ ((pool->cells | cells) & (1 << cell)) == 0) ++ break; ++ if (cell >= res_defs->num_cells) { ++ dev_dbg(dev, "no free cells of right type\n"); ++ ret = -ENOSPC; ++ goto free_out; ++ } ++ ret = ipu_fw_psys_set_process_cell_id(process, 0, cell); ++ if (ret) ++ goto free_out; ++ } ++ if (cell < res_defs->num_cells) ++ cells |= 1 << cell; ++ if (pool->cells & cells) { ++ dev_dbg(dev, "out of cell resources\n"); ++ ret = -ENOSPC; ++ goto free_out; ++ } ++ ++ if (pm.dev_chn_size) { ++ for (id = 0; id < res_defs->num_dev_channels; id++) { ++ ret = __alloc_one_resrc(dev, process, ++ &pool->dev_channels[id], ++ &pm, id, alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ ++ idx = alloc->resources - 1; ++ p = alloc->resource_alloc[idx].pos; ++ ret = ipu_fw_psys_set_proc_dev_chn(process, id, ++ p); ++ if (ret) ++ goto free_out; ++ } ++ } ++ ++ if (pm.dfm_port_bitmap) { ++ for (id = 0; id < res_defs->num_dfm_ids; id++) { ++ ret = ipu_psys_allocate_one_dfm(dev, process, ++ &pool->dfms[id], ++ &pm, id, alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ ++ idx = alloc->resources - 1; ++ p = alloc->resource_alloc[idx].pos; ++ bmp = pm.dfm_port_bitmap[id]; ++ bmp = bmp << p; ++ a_bmp = pm.dfm_active_port_bitmap[id]; ++ a_bmp = a_bmp << p; ++ ret = ipu_fw_psys_set_proc_dfm_bitmap(process, ++ id, bmp, ++ a_bmp); ++ if (ret) ++ goto free_out; ++ } ++ } ++ ++ if (pm.ext_mem_size) { ++ for (mem_type_id = 0; ++ mem_type_id < res_defs->num_ext_mem_types; ++ mem_type_id++) { ++ u32 bank = res_defs->num_ext_mem_ids; ++ ++ if (cell != res_defs->num_cells) { ++ idx = res_defs->cell_mem_row * cell + ++ mem_type_id; ++ bank = res_defs->cell_mem[idx]; ++ } ++ if (bank == res_defs->num_ext_mem_ids) ++ continue; ++ ++ ret = __alloc_mem_resrc(dev, process, ++ &pool->ext_memory[bank], ++ &pm, mem_type_id, ++ bank, alloc); ++ if (ret == -ENXIO) ++ continue; ++ ++ if (ret) ++ goto free_out; ++ ++ /* no return value check here because fw api ++ * will do some checks, and would return ++ * non-zero except mem_type_id == 0. ++ * This maybe caused by that above flow of ++ * allocating mem_bank_id is improper. ++ */ ++ idx = alloc->resources - 1; ++ p = alloc->resource_alloc[idx].pos; ++ ipu_fw_psys_set_process_ext_mem(process, ++ mem_type_id, ++ bank, p); ++ } ++ } ++ } ++ alloc->cells |= cells; ++ pool->cells |= cells; ++ return 0; ++ ++free_out: ++ dev_err(dev, "failed to allocate resources, ret %d\n", ret); ++ ipu_psys_reset_process_cell(dev, pg, pg_manifest, i + 1); ++ ipu_psys_free_resources(alloc, pool); ++ return ret; ++} ++ ++int ipu_psys_move_resources(const struct device *dev, ++ struct ipu_psys_resource_alloc *alloc, ++ struct ipu_psys_resource_pool ++ *source_pool, struct ipu_psys_resource_pool ++ *target_pool) ++{ ++ int i; ++ ++ if (target_pool->cells & alloc->cells) { ++ dev_dbg(dev, "out of cell resources\n"); ++ return -ENOSPC; ++ } ++ ++ for (i = 0; i < alloc->resources; i++) { ++ unsigned long bitmap = 0; ++ unsigned int id = alloc->resource_alloc[i].resource->id; ++ unsigned long fbit, end; ++ ++ switch (alloc->resource_alloc[i].type) { ++ case IPU_RESOURCE_DEV_CHN: ++ bitmap_set(&bitmap, alloc->resource_alloc[i].pos, ++ alloc->resource_alloc[i].elements); ++ if (*target_pool->dev_channels[id].bitmap & bitmap) ++ return -ENOSPC; ++ break; ++ case IPU_RESOURCE_EXT_MEM: ++ end = alloc->resource_alloc[i].elements + ++ alloc->resource_alloc[i].pos; ++ ++ fbit = find_next_bit(target_pool->ext_memory[id].bitmap, ++ end, alloc->resource_alloc[i].pos); ++ /* if find_next_bit returns "end" it didn't find 1bit */ ++ if (end != fbit) ++ return -ENOSPC; ++ break; ++ case IPU_RESOURCE_DFM: ++ bitmap = alloc->resource_alloc[i].elements; ++ if (*target_pool->dfms[id].bitmap & bitmap) ++ return -ENOSPC; ++ break; ++ default: ++ dev_err(dev, "Illegal resource type\n"); ++ return -EINVAL; ++ } ++ } ++ ++ for (i = 0; i < alloc->resources; i++) { ++ u32 id = alloc->resource_alloc[i].resource->id; ++ ++ switch (alloc->resource_alloc[i].type) { ++ case IPU_RESOURCE_DEV_CHN: ++ bitmap_set(target_pool->dev_channels[id].bitmap, ++ alloc->resource_alloc[i].pos, ++ alloc->resource_alloc[i].elements); ++ ipu_resource_free(&alloc->resource_alloc[i]); ++ alloc->resource_alloc[i].resource = ++ &target_pool->dev_channels[id]; ++ break; ++ case IPU_RESOURCE_EXT_MEM: ++ bitmap_set(target_pool->ext_memory[id].bitmap, ++ alloc->resource_alloc[i].pos, ++ alloc->resource_alloc[i].elements); ++ ipu_resource_free(&alloc->resource_alloc[i]); ++ alloc->resource_alloc[i].resource = ++ &target_pool->ext_memory[id]; ++ break; ++ case IPU_RESOURCE_DFM: ++ *target_pool->dfms[id].bitmap |= ++ alloc->resource_alloc[i].elements; ++ *alloc->resource_alloc[i].resource->bitmap &= ++ ~(alloc->resource_alloc[i].elements); ++ alloc->resource_alloc[i].resource = ++ &target_pool->dfms[id]; ++ break; ++ default: ++ /* ++ * Just keep compiler happy. This case failed already ++ * in above loop. ++ */ ++ break; ++ } ++ } ++ ++ target_pool->cells |= alloc->cells; ++ source_pool->cells &= ~alloc->cells; ++ ++ return 0; ++} ++ ++void ipu_psys_reset_process_cell(const struct device *dev, ++ struct ipu_fw_psys_process_group *pg, ++ void *pg_manifest, ++ int process_count) ++{ ++ int i; ++ u16 *process_offset_table; ++ const struct ipu_fw_resource_definitions *res_defs; ++ ++ if (!pg) ++ return; ++ ++ res_defs = get_res(); ++ process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset); ++ for (i = 0; i < process_count; i++) { ++ struct ipu_fw_psys_process *process = ++ (struct ipu_fw_psys_process *) ++ ((char *)pg + process_offset_table[i]); ++ struct ipu_fw_generic_program_manifest pm; ++ int ret; ++ ++ if (!process) ++ break; ++ ++ ret = ipu_fw_psys_get_program_manifest_by_process(&pm, ++ pg_manifest, ++ process); ++ if (ret < 0) { ++ dev_err(dev, "can not get manifest\n"); ++ break; ++ } ++ if ((pm.cell_id != res_defs->num_cells && ++ pm.cell_type_id == res_defs->num_cells_type)) ++ continue; ++ /* no return value check here because if finding free cell ++ * failed, process cell would not set then calling clear_cell ++ * will return non-zero. ++ */ ++ ipu_fw_psys_clear_process_cell(process); ++ } ++} ++ ++/* Free resources marked in `alloc' from `resources' */ ++void ipu_psys_free_resources(struct ipu_psys_resource_alloc ++ *alloc, struct ipu_psys_resource_pool *pool) ++{ ++ unsigned int i; ++ ++ pool->cells &= ~alloc->cells; ++ alloc->cells = 0; ++ for (i = 0; i < alloc->resources; i++) ++ ipu_resource_free(&alloc->resource_alloc[i]); ++ alloc->resources = 0; ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-fw-resources.c b/drivers/media/pci/intel/ipu6/psys/ipu6-fw-resources.c +new file mode 100644 +index 000000000000..cc89184346a5 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-fw-resources.c +@@ -0,0 +1,607 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2015 - 2024 Intel Corporation ++ ++#include ++#include ++ ++#include "ipu-psys.h" ++#include "ipu-fw-psys.h" ++#include "ipu6-platform-resources.h" ++ ++/* resources table */ ++ ++/* ++ * Cell types by cell IDs ++ */ ++static const u8 ipu6_fw_psys_cell_types[IPU6_FW_PSYS_N_CELL_ID] = { ++ IPU6_FW_PSYS_SP_CTRL_TYPE_ID, ++ IPU6_FW_PSYS_VP_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* X2B_MD */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* ICA_MEDIUM */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* X2B_SVE_RGBIR */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* PAF */ ++ IPU6_FW_PSYS_GDC_TYPE_ID, ++ IPU6_FW_PSYS_TNR_TYPE_ID, ++}; ++ ++static const u16 ipu6_fw_num_dev_channels[IPU6_FW_PSYS_N_DEV_CHN_ID] = { ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT0_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_READ_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_INTERNAL_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_ISA_MAX_SIZE, ++}; ++ ++static const u16 ipu6_fw_psys_mem_size[IPU6_FW_PSYS_N_MEM_ID] = { ++ IPU6_FW_PSYS_VMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_TRANSFER_VMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_MAX_SIZE, ++ IPU6_FW_PSYS_LB_VMEM_MAX_SIZE, ++ IPU6_FW_PSYS_BAMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM1_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM2_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM3_MAX_SIZE, ++ IPU6_FW_PSYS_PMEM0_MAX_SIZE ++}; ++ ++static const u16 ipu6_fw_psys_dfms[IPU6_FW_PSYS_N_DEV_DFM_ID] = { ++ IPU6_FW_PSYS_DEV_DFM_BB_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_BB_EMPTY_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_LB_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID_MAX_SIZE, ++}; ++ ++static const u8 ++ipu6_fw_psys_c_mem[IPU6_FW_PSYS_N_CELL_ID][IPU6_FW_PSYS_N_MEM_TYPE_ID] = { ++ { ++ /* IPU6_FW_PSYS_SP0_ID */ ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_DMEM0_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_SP1_ID */ ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_DMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_VP0_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_DMEM3_ID, ++ IPU6_FW_PSYS_VMEM0_ID, ++ IPU6_FW_PSYS_BAMEM0_ID, ++ IPU6_FW_PSYS_PMEM0_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC1_ID BNLM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC2_ID DM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC3_ID ACM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC4_ID GTC YUV1 */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC5_ID OFS pin main */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC6_ID OFS pin display */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC7_ID OFS pin pp */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC8_ID GAMMASTAR */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC9_ID GLTM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC10_ID XNR */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_ICA_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_LSC_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_DPC_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_SIS_A_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_SIS_B_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_B2B_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_B2R_ID and ISA_R2I_SIE */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_R2I_DS_A_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_R2I_DS_B_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AWB_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AE_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AF_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_DOL_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_X2B_MD_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_ICA_MEDIUM_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_X2B_SVE_RGBIR_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_PAF_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_BB_ACC_GDC0_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_BB_ACC_TNR_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ } ++}; ++ ++static const struct ipu_fw_resource_definitions ipu6_defs = { ++ .cells = ipu6_fw_psys_cell_types, ++ .num_cells = IPU6_FW_PSYS_N_CELL_ID, ++ .num_cells_type = IPU6_FW_PSYS_N_CELL_TYPE_ID, ++ ++ .dev_channels = ipu6_fw_num_dev_channels, ++ .num_dev_channels = IPU6_FW_PSYS_N_DEV_CHN_ID, ++ ++ .num_ext_mem_types = IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID, ++ .num_ext_mem_ids = IPU6_FW_PSYS_N_MEM_ID, ++ .ext_mem_ids = ipu6_fw_psys_mem_size, ++ ++ .num_dfm_ids = IPU6_FW_PSYS_N_DEV_DFM_ID, ++ ++ .dfms = ipu6_fw_psys_dfms, ++ ++ .cell_mem_row = IPU6_FW_PSYS_N_MEM_TYPE_ID, ++ .cell_mem = &ipu6_fw_psys_c_mem[0][0], ++}; ++ ++const struct ipu_fw_resource_definitions *ipu6_res_defs = &ipu6_defs; ++ ++/********** Generic resource handling **********/ ++ ++int ipu6_fw_psys_set_proc_dev_chn(struct ipu_fw_psys_process *ptr, u16 offset, ++ u16 value) ++{ ++ struct ipu6_fw_psys_process_ext *pm_ext; ++ u8 ps_ext_offset; ++ ++ ps_ext_offset = ptr->process_extension_offset; ++ if (!ps_ext_offset) ++ return -EINVAL; ++ ++ pm_ext = (struct ipu6_fw_psys_process_ext *)((u8 *)ptr + ps_ext_offset); ++ ++ pm_ext->dev_chn_offset[offset] = value; ++ ++ return 0; ++} ++ ++int ipu6_fw_psys_set_proc_dfm_bitmap(struct ipu_fw_psys_process *ptr, ++ u16 id, u32 bitmap, ++ u32 active_bitmap) ++{ ++ struct ipu6_fw_psys_process_ext *pm_ext; ++ u8 ps_ext_offset; ++ ++ ps_ext_offset = ptr->process_extension_offset; ++ if (!ps_ext_offset) ++ return -EINVAL; ++ ++ pm_ext = (struct ipu6_fw_psys_process_ext *)((u8 *)ptr + ps_ext_offset); ++ ++ pm_ext->dfm_port_bitmap[id] = bitmap; ++ pm_ext->dfm_active_port_bitmap[id] = active_bitmap; ++ ++ return 0; ++} ++ ++int ipu6_fw_psys_set_process_ext_mem(struct ipu_fw_psys_process *ptr, ++ u16 type_id, u16 mem_id, u16 offset) ++{ ++ struct ipu6_fw_psys_process_ext *pm_ext; ++ u8 ps_ext_offset; ++ ++ ps_ext_offset = ptr->process_extension_offset; ++ if (!ps_ext_offset) ++ return -EINVAL; ++ ++ pm_ext = (struct ipu6_fw_psys_process_ext *)((u8 *)ptr + ps_ext_offset); ++ ++ pm_ext->ext_mem_offset[type_id] = offset; ++ pm_ext->ext_mem_id[type_id] = mem_id; ++ ++ return 0; ++} ++ ++static struct ipu_fw_psys_program_manifest * ++get_program_manifest(const struct ipu_fw_psys_program_group_manifest *manifest, ++ const unsigned int program_index) ++{ ++ struct ipu_fw_psys_program_manifest *prg_manifest_base; ++ u8 *program_manifest = NULL; ++ u8 program_count; ++ unsigned int i; ++ ++ program_count = manifest->program_count; ++ ++ prg_manifest_base = (struct ipu_fw_psys_program_manifest *) ++ ((char *)manifest + manifest->program_manifest_offset); ++ if (program_index < program_count) { ++ program_manifest = (u8 *)prg_manifest_base; ++ for (i = 0; i < program_index; i++) ++ program_manifest += ++ ((struct ipu_fw_psys_program_manifest *) ++ program_manifest)->size; ++ } ++ ++ return (struct ipu_fw_psys_program_manifest *)program_manifest; ++} ++ ++int ipu6_fw_psys_get_program_manifest_by_process( ++ struct ipu_fw_generic_program_manifest *gen_pm, ++ const struct ipu_fw_psys_program_group_manifest *pg_manifest, ++ struct ipu_fw_psys_process *process) ++{ ++ u32 program_id = process->program_idx; ++ struct ipu_fw_psys_program_manifest *pm; ++ struct ipu6_fw_psys_program_manifest_ext *pm_ext; ++ ++ pm = get_program_manifest(pg_manifest, program_id); ++ ++ if (!pm) ++ return -ENOENT; ++ ++ if (pm->program_extension_offset) { ++ pm_ext = (struct ipu6_fw_psys_program_manifest_ext *) ++ ((u8 *)pm + pm->program_extension_offset); ++ ++ gen_pm->dev_chn_size = pm_ext->dev_chn_size; ++ gen_pm->dev_chn_offset = pm_ext->dev_chn_offset; ++ gen_pm->ext_mem_size = pm_ext->ext_mem_size; ++ gen_pm->ext_mem_offset = (u16 *)pm_ext->ext_mem_offset; ++ gen_pm->is_dfm_relocatable = pm_ext->is_dfm_relocatable; ++ gen_pm->dfm_port_bitmap = pm_ext->dfm_port_bitmap; ++ gen_pm->dfm_active_port_bitmap = ++ pm_ext->dfm_active_port_bitmap; ++ } ++ ++ memcpy(gen_pm->cells, pm->cells, sizeof(pm->cells)); ++ gen_pm->cell_id = pm->cells[0]; ++ gen_pm->cell_type_id = pm->cell_type_id; ++ ++ return 0; ++} ++ ++#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG) || \ ++ (defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE)) ++void ipu6_fw_psys_pg_dump(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd, const char *note) ++{ ++ struct ipu_fw_psys_process_group *pg = kcmd->kpg->pg; ++ struct device *dev = &psys->adev->auxdev.dev; ++ u32 pgid = pg->ID; ++ u8 processes = pg->process_count; ++ u16 *process_offset_table = (u16 *)((char *)pg + pg->processes_offset); ++ unsigned int p, chn, mem, mem_id; ++ unsigned int mem_type, max_mem_id, dev_chn; ++ ++ if (ipu_ver == IPU6_VER_6SE) { ++ mem_type = IPU6SE_FW_PSYS_N_DATA_MEM_TYPE_ID; ++ max_mem_id = IPU6SE_FW_PSYS_N_MEM_ID; ++ dev_chn = IPU6SE_FW_PSYS_N_DEV_CHN_ID; ++ } else if (ipu_ver == IPU6_VER_6 || ipu_ver == IPU6_VER_6EP || ++ ipu_ver == IPU6_VER_6EP_MTL) { ++ mem_type = IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID; ++ max_mem_id = IPU6_FW_PSYS_N_MEM_ID; ++ dev_chn = IPU6_FW_PSYS_N_DEV_CHN_ID; ++ } else { ++ WARN(1, "%s ipu_ver:[%u] is unsupported!\n", __func__, ipu_ver); ++ return; ++ } ++ ++ dev_dbg(dev, "%s %s pgid %i has %i processes:\n", ++ __func__, note, pgid, processes); ++ ++ for (p = 0; p < processes; p++) { ++ struct ipu_fw_psys_process *process = ++ (struct ipu_fw_psys_process *) ++ ((char *)pg + process_offset_table[p]); ++ struct ipu6_fw_psys_process_ext *pm_ext = ++ (struct ipu6_fw_psys_process_ext *)((u8 *)process ++ + process->process_extension_offset); ++ dev_dbg(dev, "\t process %i size=%u", p, process->size); ++ if (!process->process_extension_offset) ++ continue; ++ ++ for (mem = 0; mem < mem_type; mem++) { ++ mem_id = pm_ext->ext_mem_id[mem]; ++ if (mem_id != max_mem_id) ++ dev_dbg(dev, "\t mem type %u id %d offset=0x%x", ++ mem, mem_id, ++ pm_ext->ext_mem_offset[mem]); ++ } ++ for (chn = 0; chn < dev_chn; chn++) { ++ if (pm_ext->dev_chn_offset[chn] != (u16)(-1)) ++ dev_dbg(dev, "\t dev_chn[%u]=0x%x\n", ++ chn, pm_ext->dev_chn_offset[chn]); ++ } ++ } ++} ++#else ++void ipu6_fw_psys_pg_dump(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd, const char *note) ++{ ++ if (ipu_ver == IPU6_VER_6SE || ipu_ver == IPU6_VER_6 || ++ ipu_ver == IPU6_VER_6EP || ipu_ver == IPU6_VER_6EP_MTL) ++ return; ++ ++ WARN(1, "%s ipu_ver:[%u] is unsupported!\n", __func__, ipu_ver); ++} ++#endif +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-l-scheduler.c b/drivers/media/pci/intel/ipu6/psys/ipu6-l-scheduler.c +new file mode 100644 +index 000000000000..5e82135f0dde +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-l-scheduler.c +@@ -0,0 +1,620 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2020 - 2024 Intel Corporation ++ ++#include "ipu-psys.h" ++#include "ipu6-ppg.h" ++ ++extern bool enable_power_gating; ++ ++struct sched_list { ++ struct list_head list; ++ /* to protect the list */ ++ struct mutex lock; ++}; ++ ++static struct sched_list start_list = { ++ .list = LIST_HEAD_INIT(start_list.list), ++ .lock = __MUTEX_INITIALIZER(start_list.lock), ++}; ++ ++static struct sched_list stop_list = { ++ .list = LIST_HEAD_INIT(stop_list.list), ++ .lock = __MUTEX_INITIALIZER(stop_list.lock), ++}; ++ ++static struct sched_list *get_sc_list(enum SCHED_LIST type) ++{ ++ /* for debug purposes */ ++ WARN_ON(type != SCHED_START_LIST && type != SCHED_STOP_LIST); ++ ++ if (type == SCHED_START_LIST) ++ return &start_list; ++ return &stop_list; ++} ++ ++static bool is_kppg_in_list(struct ipu_psys_ppg *kppg, struct list_head *head) ++{ ++ struct ipu_psys_ppg *tmp; ++ ++ list_for_each_entry(tmp, head, sched_list) { ++ if (kppg == tmp) ++ return true; ++ } ++ ++ return false; ++} ++ ++void ipu_psys_scheduler_remove_kppg(struct ipu_psys_ppg *kppg, ++ enum SCHED_LIST type) ++{ ++ struct sched_list *sc_list = get_sc_list(type); ++ struct ipu_psys_ppg *tmp0, *tmp1; ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ ++ mutex_lock(&sc_list->lock); ++ list_for_each_entry_safe(tmp0, tmp1, &sc_list->list, sched_list) { ++ if (tmp0 == kppg) { ++ dev_dbg(dev, "remove from %s list, kppg(%d 0x%p) state %d\n", ++ type == SCHED_START_LIST ? "start" : "stop", ++ kppg->kpg->pg->ID, kppg, kppg->state); ++ list_del_init(&kppg->sched_list); ++ } ++ } ++ mutex_unlock(&sc_list->lock); ++} ++ ++void ipu_psys_scheduler_add_kppg(struct ipu_psys_ppg *kppg, ++ enum SCHED_LIST type) ++{ ++ int cur_pri = kppg->pri_base + kppg->pri_dynamic; ++ struct sched_list *sc_list = get_sc_list(type); ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *tmp0, *tmp1; ++ ++ dev_dbg(dev, ++ "add to %s list, kppg(%d 0x%p) state %d prio(%d %d) fh 0x%p\n", ++ type == SCHED_START_LIST ? "start" : "stop", ++ kppg->kpg->pg->ID, kppg, kppg->state, ++ kppg->pri_base, kppg->pri_dynamic, kppg->fh); ++ ++ mutex_lock(&sc_list->lock); ++ if (list_empty(&sc_list->list)) { ++ list_add(&kppg->sched_list, &sc_list->list); ++ goto out; ++ } ++ ++ if (is_kppg_in_list(kppg, &sc_list->list)) { ++ dev_dbg(dev, "kppg already in list\n"); ++ goto out; ++ } ++ ++ list_for_each_entry_safe(tmp0, tmp1, &sc_list->list, sched_list) { ++ int tmp_pri = tmp0->pri_base + tmp0->pri_dynamic; ++ ++ dev_dbg(dev, ++ "found kppg(%d 0x%p), state %d pri(%d %d) fh 0x%p\n", ++ tmp0->kpg->pg->ID, tmp0, tmp0->state, ++ tmp0->pri_base, tmp0->pri_dynamic, tmp0->fh); ++ ++ if (type == SCHED_START_LIST && tmp_pri > cur_pri) { ++ list_add(&kppg->sched_list, tmp0->sched_list.prev); ++ goto out; ++ } else if (type == SCHED_STOP_LIST && tmp_pri < cur_pri) { ++ list_add(&kppg->sched_list, tmp0->sched_list.prev); ++ goto out; ++ } ++ } ++ ++ list_add_tail(&kppg->sched_list, &sc_list->list); ++out: ++ mutex_unlock(&sc_list->lock); ++} ++ ++static int ipu_psys_detect_resource_contention(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys_resource_pool *try_res_pool; ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ int ret = 0; ++ int state; ++ ++ try_res_pool = kzalloc(sizeof(*try_res_pool), GFP_KERNEL); ++ if (IS_ERR_OR_NULL(try_res_pool)) ++ return -ENOMEM; ++ ++ mutex_lock(&kppg->mutex); ++ state = kppg->state; ++ mutex_unlock(&kppg->mutex); ++ if (state == PPG_STATE_STARTED || state == PPG_STATE_RUNNING || ++ state == PPG_STATE_RESUMED) ++ goto exit; ++ ++ ret = ipu_psys_resource_pool_init(try_res_pool); ++ if (ret < 0) { ++ dev_err(dev, "unable to alloc pg resources\n"); ++ WARN_ON(1); ++ goto exit; ++ } ++ ++ ipu_psys_resource_copy(&psys->resource_pool_running, try_res_pool); ++ ret = ipu_psys_try_allocate_resources(dev, kppg->kpg->pg, ++ kppg->manifest, ++ try_res_pool); ++ ++ ipu_psys_resource_pool_cleanup(try_res_pool); ++exit: ++ kfree(try_res_pool); ++ ++ return ret; ++} ++ ++static void ipu_psys_scheduler_ppg_sort(struct ipu_psys *psys, bool *stopping) ++{ ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (kppg->state == PPG_STATE_START || ++ kppg->state == PPG_STATE_RESUME) { ++ ipu_psys_scheduler_add_kppg(kppg, ++ SCHED_START_LIST); ++ } else if (kppg->state == PPG_STATE_RUNNING) { ++ ipu_psys_scheduler_add_kppg(kppg, ++ SCHED_STOP_LIST); ++ } else if (kppg->state == PPG_STATE_SUSPENDING || ++ kppg->state == PPG_STATE_STOPPING) { ++ /* there are some suspending/stopping ppgs */ ++ *stopping = true; ++ } else if (kppg->state == PPG_STATE_RESUMING || ++ kppg->state == PPG_STATE_STARTING) { ++ /* how about kppg are resuming/starting? */ ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++} ++ ++static void ipu_psys_scheduler_update_start_ppg_priority(void) ++{ ++ struct sched_list *sc_list = get_sc_list(SCHED_START_LIST); ++ struct ipu_psys_ppg *kppg, *tmp; ++ ++ mutex_lock(&sc_list->lock); ++ if (!list_empty(&sc_list->list)) ++ list_for_each_entry_safe(kppg, tmp, &sc_list->list, sched_list) ++ kppg->pri_dynamic--; ++ mutex_unlock(&sc_list->lock); ++} ++ ++static bool ipu_psys_scheduler_switch_ppg(struct ipu_psys *psys) ++{ ++ struct sched_list *sc_list = get_sc_list(SCHED_STOP_LIST); ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *kppg; ++ bool resched = false; ++ ++ mutex_lock(&sc_list->lock); ++ if (list_empty(&sc_list->list)) { ++ /* some ppgs are RESUMING/STARTING */ ++ dev_dbg(dev, "no candidated stop ppg\n"); ++ mutex_unlock(&sc_list->lock); ++ return false; ++ } ++ kppg = list_first_entry(&sc_list->list, struct ipu_psys_ppg, ++ sched_list); ++ mutex_unlock(&sc_list->lock); ++ ++ mutex_lock(&kppg->mutex); ++ if (!(kppg->state & PPG_STATE_STOP)) { ++ dev_dbg(dev, "s_change:%s: %p %d -> %d\n", ++ __func__, kppg, kppg->state, PPG_STATE_SUSPEND); ++ kppg->state = PPG_STATE_SUSPEND; ++ resched = true; ++ } ++ mutex_unlock(&kppg->mutex); ++ ++ return resched; ++} ++ ++/* ++ * search all kppgs and sort them into start_list and stop_list, alway start ++ * first kppg(high priority) in start_list; ++ * if there is resource contention, it would switch kppgs in stop_list ++ * to suspend state one by one ++ */ ++static bool ipu_psys_scheduler_ppg_start(struct ipu_psys *psys) ++{ ++ struct sched_list *sc_list = get_sc_list(SCHED_START_LIST); ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *kppg, *kppg0; ++ bool stopping_existed = false; ++ int ret; ++ ++ ipu_psys_scheduler_ppg_sort(psys, &stopping_existed); ++ ++ mutex_lock(&sc_list->lock); ++ if (list_empty(&sc_list->list)) { ++ dev_dbg(dev, "no ppg to start\n"); ++ mutex_unlock(&sc_list->lock); ++ return false; ++ } ++ ++ list_for_each_entry_safe(kppg, kppg0, ++ &sc_list->list, sched_list) { ++ mutex_unlock(&sc_list->lock); ++ ++ ret = ipu_psys_detect_resource_contention(kppg); ++ if (ret < 0) { ++ dev_dbg(dev, "ppg %d resource detect failed(%d)\n", ++ kppg->kpg->pg->ID, ret); ++ /* ++ * switch out other ppg in 2 cases: ++ * 1. resource contention ++ * 2. no suspending/stopping ppg ++ */ ++ if (ret == -ENOSPC) { ++ if (!stopping_existed && ++ ipu_psys_scheduler_switch_ppg(psys)) { ++ return true; ++ } ++ dev_dbg(dev, "ppg is suspending/stopping\n"); ++ } else { ++ dev_err(dev, "detect resource error %d\n", ret); ++ } ++ } else { ++ kppg->pri_dynamic = 0; ++ ++ mutex_lock(&kppg->mutex); ++ if (kppg->state == PPG_STATE_START) ++ ipu_psys_ppg_start(kppg); ++ else ++ ipu_psys_ppg_resume(kppg); ++ mutex_unlock(&kppg->mutex); ++ ++ ipu_psys_scheduler_remove_kppg(kppg, ++ SCHED_START_LIST); ++ ipu_psys_scheduler_update_start_ppg_priority(); ++ } ++ mutex_lock(&sc_list->lock); ++ } ++ mutex_unlock(&sc_list->lock); ++ ++ return false; ++} ++ ++static bool ipu_psys_scheduler_ppg_enqueue_bufset(struct ipu_psys *psys) ++{ ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg; ++ struct ipu_psys_fh *fh; ++ bool resched = false; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry(kppg, &sched->ppgs, list) { ++ if (ipu_psys_ppg_enqueue_bufsets(kppg)) ++ resched = true; ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ return resched; ++} ++ ++/* ++ * This function will check all kppgs within fhs, and if kppg state ++ * is STOP or SUSPEND, l-scheduler will call ppg function to stop ++ * or suspend it and update stop list ++ */ ++ ++static bool ipu_psys_scheduler_ppg_halt(struct ipu_psys *psys) ++{ ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ bool stopping_exit = false; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (kppg->state & PPG_STATE_STOP) { ++ ipu_psys_ppg_stop(kppg); ++ ipu_psys_scheduler_remove_kppg(kppg, ++ SCHED_STOP_LIST); ++ } else if (kppg->state == PPG_STATE_SUSPEND) { ++ ipu_psys_ppg_suspend(kppg); ++ ipu_psys_scheduler_remove_kppg(kppg, ++ SCHED_STOP_LIST); ++ } else if (kppg->state == PPG_STATE_SUSPENDING || ++ kppg->state == PPG_STATE_STOPPING) { ++ stopping_exit = true; ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ return stopping_exit; ++} ++ ++static void ipu_psys_update_ppg_state_by_kcmd(struct ipu_psys *psys, ++ struct ipu_psys_ppg *kppg, ++ struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ int old_ppg_state = kppg->state; ++ ++ /* ++ * Respond kcmd when ppg is in stable state: ++ * STARTED/RESUMED/RUNNING/SUSPENDED/STOPPED ++ */ ++ if (kppg->state == PPG_STATE_STARTED || ++ kppg->state == PPG_STATE_RESUMED || ++ kppg->state == PPG_STATE_RUNNING) { ++ if (kcmd->state == KCMD_STATE_PPG_START) ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ else if (kcmd->state == KCMD_STATE_PPG_STOP) ++ kppg->state = PPG_STATE_STOP; ++ } else if (kppg->state == PPG_STATE_SUSPENDED) { ++ if (kcmd->state == KCMD_STATE_PPG_START) ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ else if (kcmd->state == KCMD_STATE_PPG_STOP) ++ /* ++ * Record the previous state ++ * because here need resume at first ++ */ ++ kppg->state |= PPG_STATE_STOP; ++ else if (kcmd->state == KCMD_STATE_PPG_ENQUEUE) ++ kppg->state = PPG_STATE_RESUME; ++ } else if (kppg->state == PPG_STATE_STOPPED) { ++ if (kcmd->state == KCMD_STATE_PPG_START) ++ kppg->state = PPG_STATE_START; ++ else if (kcmd->state == KCMD_STATE_PPG_STOP) ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ else if (kcmd->state == KCMD_STATE_PPG_ENQUEUE) { ++ dev_err(dev, "ppg %p stopped!\n", kppg); ++ ipu_psys_kcmd_complete(kppg, kcmd, -EIO); ++ } ++ } ++ ++ if (old_ppg_state != kppg->state) ++ dev_dbg(dev, "s_change:%s: %p %d -> %d\n", ++ __func__, kppg, old_ppg_state, kppg->state); ++} ++ ++static void ipu_psys_scheduler_kcmd_set(struct ipu_psys *psys) ++{ ++ struct ipu_psys_kcmd *kcmd; ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (list_empty(&kppg->kcmds_new_list)) { ++ mutex_unlock(&kppg->mutex); ++ continue; ++ }; ++ ++ kcmd = list_first_entry(&kppg->kcmds_new_list, ++ struct ipu_psys_kcmd, list); ++ ipu_psys_update_ppg_state_by_kcmd(psys, kppg, kcmd); ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++} ++ ++static bool is_ready_to_enter_power_gating(struct ipu_psys *psys) ++{ ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (!list_empty(&kppg->kcmds_new_list) || ++ !list_empty(&kppg->kcmds_processing_list)) { ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ return false; ++ } ++ if (!(kppg->state == PPG_STATE_RUNNING || ++ kppg->state == PPG_STATE_STOPPED || ++ kppg->state == PPG_STATE_SUSPENDED)) { ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ return false; ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ return true; ++} ++ ++static bool has_pending_kcmd(struct ipu_psys *psys) ++{ ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (!list_empty(&kppg->kcmds_new_list) || ++ !list_empty(&kppg->kcmds_processing_list)) { ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ return true; ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ return false; ++} ++ ++static bool ipu_psys_scheduler_exit_power_gating(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ ++ /* Assume power gating process can be aborted directly during START */ ++ if (psys->power_gating == PSYS_POWER_GATED) { ++ dev_dbg(dev, "powergating: exit ---\n"); ++ ipu_psys_exit_power_gating(psys); ++ } ++ psys->power_gating = PSYS_POWER_NORMAL; ++ return false; ++} ++ ++static bool ipu_psys_scheduler_enter_power_gating(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ if (!enable_power_gating) ++ return false; ++ ++ if (psys->power_gating == PSYS_POWER_NORMAL && ++ is_ready_to_enter_power_gating(psys)) { ++ /* Enter power gating */ ++ dev_dbg(dev, "powergating: enter +++\n"); ++ psys->power_gating = PSYS_POWER_GATING; ++ } ++ ++ if (psys->power_gating != PSYS_POWER_GATING) ++ return false; ++ ++ /* Suspend ppgs one by one */ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (kppg->state == PPG_STATE_RUNNING) { ++ kppg->state = PPG_STATE_SUSPEND; ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ return true; ++ } ++ ++ if (kppg->state != PPG_STATE_SUSPENDED && ++ kppg->state != PPG_STATE_STOPPED) { ++ /* Can't enter power gating */ ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ /* Need re-run l-scheduler to suspend ppg? */ ++ return (kppg->state & PPG_STATE_STOP || ++ kppg->state == PPG_STATE_SUSPEND); ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ psys->power_gating = PSYS_POWER_GATED; ++ ipu_psys_enter_power_gating(psys); ++ ++ return false; ++} ++ ++void ipu_psys_run_next(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ /* Wake up scheduler due to unfinished work */ ++ bool need_trigger = false; ++ /* Wait FW callback if there are stopping/suspending/running ppg */ ++ bool wait_fw_finish = false; ++ /* ++ * Code below will crash if fhs is empty. Normally this ++ * shouldn't happen. ++ */ ++ if (list_empty(&psys->fhs)) { ++ WARN_ON(1); ++ return; ++ } ++ ++ /* Abort power gating process */ ++ if (psys->power_gating != PSYS_POWER_NORMAL && ++ has_pending_kcmd(psys)) ++ need_trigger = ipu_psys_scheduler_exit_power_gating(psys); ++ ++ /* Handle kcmd and related ppg switch */ ++ if (psys->power_gating == PSYS_POWER_NORMAL) { ++ ipu_psys_scheduler_kcmd_set(psys); ++ wait_fw_finish = ipu_psys_scheduler_ppg_halt(psys); ++ need_trigger |= ipu_psys_scheduler_ppg_start(psys); ++ need_trigger |= ipu_psys_scheduler_ppg_enqueue_bufset(psys); ++ } ++ if (!(need_trigger || wait_fw_finish)) { ++ /* Nothing to do, enter power gating */ ++ need_trigger = ipu_psys_scheduler_enter_power_gating(psys); ++ if (psys->power_gating == PSYS_POWER_GATING) ++ wait_fw_finish = ipu_psys_scheduler_ppg_halt(psys); ++ } ++ ++ if (need_trigger && !wait_fw_finish) { ++ dev_dbg(dev, "scheduler: wake up\n"); ++ atomic_set(&psys->wakeup_count, 1); ++ wake_up_interruptible(&psys->sched_cmd_wq); ++ } ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-platform-resources.h b/drivers/media/pci/intel/ipu6/psys/ipu6-platform-resources.h +new file mode 100644 +index 000000000000..4e275d32740a +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-platform-resources.h +@@ -0,0 +1,196 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2018 - 2024 Intel Corporation */ ++ ++#ifndef IPU6_PLATFORM_RESOURCES_H ++#define IPU6_PLATFORM_RESOURCES_H ++ ++#include ++#include ++#include "ipu-platform-resources.h" ++ ++#define IPU6_FW_PSYS_N_PADDING_UINT8_IN_PROCESS_EXT_STRUCT 0 ++ ++enum { ++ IPU6_FW_PSYS_CMD_QUEUE_COMMAND_ID = 0, ++ IPU6_FW_PSYS_CMD_QUEUE_DEVICE_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG1_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG2_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG3_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG4_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG5_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG6_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG7_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG8_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG9_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG10_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG11_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG12_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG13_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG14_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG15_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG16_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG17_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG18_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG19_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG20_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG21_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG22_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG23_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG24_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG25_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG26_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG27_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG28_COMMAND_ID, ++ IPU6_FW_PSYS_CMD_QUEUE_PPG29_COMMAND_ID, ++ IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID ++}; ++ ++enum { ++ IPU6_FW_PSYS_TRANSFER_VMEM0_TYPE_ID = 0, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_TYPE_ID, ++ IPU6_FW_PSYS_LB_VMEM_TYPE_ID, ++ IPU6_FW_PSYS_DMEM_TYPE_ID, ++ IPU6_FW_PSYS_VMEM_TYPE_ID, ++ IPU6_FW_PSYS_BAMEM_TYPE_ID, ++ IPU6_FW_PSYS_PMEM_TYPE_ID, ++ IPU6_FW_PSYS_N_MEM_TYPE_ID ++}; ++ ++enum ipu6_mem_id { ++ IPU6_FW_PSYS_VMEM0_ID = 0, /* ISP0 VMEM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, /* TRANSFER VMEM 0 */ ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, /* TRANSFER VMEM 1 */ ++ IPU6_FW_PSYS_LB_VMEM_ID, /* LB VMEM */ ++ IPU6_FW_PSYS_BAMEM0_ID, /* ISP0 BAMEM */ ++ IPU6_FW_PSYS_DMEM0_ID, /* SPC0 Dmem */ ++ IPU6_FW_PSYS_DMEM1_ID, /* SPP0 Dmem */ ++ IPU6_FW_PSYS_DMEM2_ID, /* SPP1 Dmem */ ++ IPU6_FW_PSYS_DMEM3_ID, /* ISP0 Dmem */ ++ IPU6_FW_PSYS_PMEM0_ID, /* ISP0 PMEM */ ++ IPU6_FW_PSYS_N_MEM_ID ++}; ++ ++enum { ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT0_ID = 0, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_READ_ID, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_ID, ++ IPU6_FW_PSYS_DEV_CHN_DMA_INTERNAL_ID, ++ IPU6_FW_PSYS_DEV_CHN_DMA_ISA_ID, ++ IPU6_FW_PSYS_N_DEV_CHN_ID ++}; ++ ++enum { ++ IPU6_FW_PSYS_SP_CTRL_TYPE_ID = 0, ++ IPU6_FW_PSYS_SP_SERVER_TYPE_ID, ++ IPU6_FW_PSYS_VP_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_GDC_TYPE_ID, ++ IPU6_FW_PSYS_TNR_TYPE_ID, ++ IPU6_FW_PSYS_N_CELL_TYPE_ID ++}; ++ ++enum { ++ IPU6_FW_PSYS_SP0_ID = 0, ++ IPU6_FW_PSYS_VP0_ID, ++ IPU6_FW_PSYS_PSA_ACC_BNLM_ID, ++ IPU6_FW_PSYS_PSA_ACC_DM_ID, ++ IPU6_FW_PSYS_PSA_ACC_ACM_ID, ++ IPU6_FW_PSYS_PSA_ACC_GTC_YUV1_ID, ++ IPU6_FW_PSYS_BB_ACC_OFS_PIN_MAIN_ID, ++ IPU6_FW_PSYS_BB_ACC_OFS_PIN_DISPLAY_ID, ++ IPU6_FW_PSYS_BB_ACC_OFS_PIN_PP_ID, ++ IPU6_FW_PSYS_PSA_ACC_GAMMASTAR_ID, ++ IPU6_FW_PSYS_PSA_ACC_GLTM_ID, ++ IPU6_FW_PSYS_PSA_ACC_XNR_ID, ++ IPU6_FW_PSYS_PSA_VCSC_ID, /* VCSC */ ++ IPU6_FW_PSYS_ISA_ICA_ID, ++ IPU6_FW_PSYS_ISA_LSC_ID, ++ IPU6_FW_PSYS_ISA_DPC_ID, ++ IPU6_FW_PSYS_ISA_SIS_A_ID, ++ IPU6_FW_PSYS_ISA_SIS_B_ID, ++ IPU6_FW_PSYS_ISA_B2B_ID, ++ IPU6_FW_PSYS_ISA_B2R_R2I_SIE_ID, ++ IPU6_FW_PSYS_ISA_R2I_DS_A_ID, ++ IPU6_FW_PSYS_ISA_R2I_DS_B_ID, ++ IPU6_FW_PSYS_ISA_AWB_ID, ++ IPU6_FW_PSYS_ISA_AE_ID, ++ IPU6_FW_PSYS_ISA_AF_ID, ++ IPU6_FW_PSYS_ISA_DOL_ID, ++ IPU6_FW_PSYS_ISA_ICA_MEDIUM_ID, ++ IPU6_FW_PSYS_ISA_X2B_MD_ID, ++ IPU6_FW_PSYS_ISA_X2B_SVE_RGBIR_ID, ++ IPU6_FW_PSYS_ISA_PAF_ID, ++ IPU6_FW_PSYS_BB_ACC_GDC0_ID, ++ IPU6_FW_PSYS_BB_ACC_TNR_ID, ++ IPU6_FW_PSYS_N_CELL_ID ++}; ++ ++enum { ++ IPU6_FW_PSYS_DEV_DFM_BB_FULL_PORT_ID = 0, ++ IPU6_FW_PSYS_DEV_DFM_BB_EMPTY_PORT_ID, ++ IPU6_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID, ++ IPU6_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID, ++ IPU6_FW_PSYS_DEV_DFM_LB_FULL_PORT_ID, ++ IPU6_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID, ++}; ++ ++/* Excluding PMEM */ ++#define IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID (IPU6_FW_PSYS_N_MEM_TYPE_ID - 1) ++#define IPU6_FW_PSYS_N_DEV_DFM_ID \ ++ (IPU6_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID + 1) ++ ++#define IPU6_FW_PSYS_VMEM0_MAX_SIZE 0x0800 ++/* Transfer VMEM0 words, ref HAS Transfer*/ ++#define IPU6_FW_PSYS_TRANSFER_VMEM0_MAX_SIZE 0x0800 ++/* Transfer VMEM1 words, ref HAS Transfer*/ ++#define IPU6_FW_PSYS_TRANSFER_VMEM1_MAX_SIZE 0x0800 ++#define IPU6_FW_PSYS_LB_VMEM_MAX_SIZE 0x0400 /* LB VMEM words */ ++#define IPU6_FW_PSYS_BAMEM0_MAX_SIZE 0x0800 ++#define IPU6_FW_PSYS_DMEM0_MAX_SIZE 0x4000 ++#define IPU6_FW_PSYS_DMEM1_MAX_SIZE 0x1000 ++#define IPU6_FW_PSYS_DMEM2_MAX_SIZE 0x1000 ++#define IPU6_FW_PSYS_DMEM3_MAX_SIZE 0x1000 ++#define IPU6_FW_PSYS_PMEM0_MAX_SIZE 0x0500 ++ ++#define IPU6_FW_PSYS_DEV_CHN_DMA_EXT0_MAX_SIZE 30 ++#define IPU6_FW_PSYS_DEV_CHN_GDC_MAX_SIZE 0 ++#define IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_READ_MAX_SIZE 30 ++#define IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_MAX_SIZE 43 ++#define IPU6_FW_PSYS_DEV_CHN_DMA_INTERNAL_MAX_SIZE 8 ++#define IPU6_FW_PSYS_DEV_CHN_DMA_IPFD_MAX_SIZE 0 ++#define IPU6_FW_PSYS_DEV_CHN_DMA_ISA_MAX_SIZE 2 ++ ++#define IPU6_FW_PSYS_DEV_DFM_BB_FULL_PORT_ID_MAX_SIZE 32 ++#define IPU6_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID_MAX_SIZE 32 ++#define IPU6_FW_PSYS_DEV_DFM_LB_FULL_PORT_ID_MAX_SIZE 32 ++#define IPU6_FW_PSYS_DEV_DFM_BB_EMPTY_PORT_ID_MAX_SIZE 32 ++#define IPU6_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID_MAX_SIZE 32 ++#define IPU6_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID_MAX_SIZE 32 ++ ++struct ipu6_fw_psys_program_manifest_ext { ++ u32 dfm_port_bitmap[IPU6_FW_PSYS_N_DEV_DFM_ID]; ++ u32 dfm_active_port_bitmap[IPU6_FW_PSYS_N_DEV_DFM_ID]; ++ u16 ext_mem_size[IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID]; ++ u16 ext_mem_offset[IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID]; ++ u16 dev_chn_size[IPU6_FW_PSYS_N_DEV_CHN_ID]; ++ u16 dev_chn_offset[IPU6_FW_PSYS_N_DEV_CHN_ID]; ++ u8 is_dfm_relocatable[IPU6_FW_PSYS_N_DEV_DFM_ID]; ++ u8 dec_resources_input[IPU_FW_PSYS_MAX_INPUT_DEC_RESOURCES]; ++ u8 dec_resources_input_terminal[IPU_FW_PSYS_MAX_INPUT_DEC_RESOURCES]; ++ u8 dec_resources_output[IPU_FW_PSYS_MAX_OUTPUT_DEC_RESOURCES]; ++ u8 dec_resources_output_terminal[IPU_FW_PSYS_MAX_OUTPUT_DEC_RESOURCES]; ++ u8 padding[IPU_FW_PSYS_N_PADDING_UINT8_IN_PROGRAM_MANIFEST_EXT]; ++}; ++ ++struct ipu6_fw_psys_process_ext { ++ u32 dfm_port_bitmap[IPU6_FW_PSYS_N_DEV_DFM_ID]; ++ u32 dfm_active_port_bitmap[IPU6_FW_PSYS_N_DEV_DFM_ID]; ++ u16 ext_mem_offset[IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID]; ++ u16 dev_chn_offset[IPU6_FW_PSYS_N_DEV_CHN_ID]; ++ u8 ext_mem_id[IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID]; ++}; ++ ++#endif /* IPU6_PLATFORM_RESOURCES_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c b/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c +new file mode 100644 +index 000000000000..fa47656d37b9 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c +@@ -0,0 +1,557 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2020 - 2024 Intel Corporation ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "ipu6-ppg.h" ++ ++static bool enable_suspend_resume; ++module_param(enable_suspend_resume, bool, 0664); ++MODULE_PARM_DESC(enable_suspend_resume, "enable fw ppg suspend/resume api"); ++ ++static struct ipu_psys_kcmd * ++ipu_psys_ppg_get_kcmd(struct ipu_psys_ppg *kppg, enum ipu_psys_cmd_state state) ++{ ++ struct ipu_psys_kcmd *kcmd; ++ ++ if (list_empty(&kppg->kcmds_new_list)) ++ return NULL; ++ ++ list_for_each_entry(kcmd, &kppg->kcmds_new_list, list) { ++ if (kcmd->state == state) ++ return kcmd; ++ } ++ ++ return NULL; ++} ++ ++struct ipu_psys_kcmd *ipu_psys_ppg_get_stop_kcmd(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys_kcmd *kcmd; ++ ++ WARN(!mutex_is_locked(&kppg->mutex), "ppg locking error"); ++ ++ if (list_empty(&kppg->kcmds_processing_list)) ++ return NULL; ++ ++ list_for_each_entry(kcmd, &kppg->kcmds_processing_list, list) { ++ if (kcmd->state == KCMD_STATE_PPG_STOP) ++ return kcmd; ++ } ++ ++ return NULL; ++} ++ ++static struct ipu_psys_buffer_set * ++__get_buf_set(struct ipu_psys_fh *fh, size_t buf_set_size) ++{ ++ struct device *dev = &fh->psys->adev->auxdev.dev; ++ struct ipu_psys_buffer_set *kbuf_set; ++ struct ipu_psys_scheduler *sched = &fh->sched; ++ ++ mutex_lock(&sched->bs_mutex); ++ list_for_each_entry(kbuf_set, &sched->buf_sets, list) { ++ if (!kbuf_set->buf_set_size && ++ kbuf_set->size >= buf_set_size) { ++ kbuf_set->buf_set_size = buf_set_size; ++ mutex_unlock(&sched->bs_mutex); ++ return kbuf_set; ++ } ++ } ++ ++ mutex_unlock(&sched->bs_mutex); ++ /* no suitable buffer available, allocate new one */ ++ kbuf_set = kzalloc(sizeof(*kbuf_set), GFP_KERNEL); ++ if (!kbuf_set) ++ return NULL; ++ ++ kbuf_set->kaddr = dma_alloc_attrs(dev, buf_set_size, ++ &kbuf_set->dma_addr, GFP_KERNEL, 0); ++ if (!kbuf_set->kaddr) { ++ kfree(kbuf_set); ++ return NULL; ++ } ++ ++ kbuf_set->buf_set_size = buf_set_size; ++ kbuf_set->size = buf_set_size; ++ mutex_lock(&sched->bs_mutex); ++ list_add(&kbuf_set->list, &sched->buf_sets); ++ mutex_unlock(&sched->bs_mutex); ++ ++ return kbuf_set; ++} ++ ++static struct ipu_psys_buffer_set * ++ipu_psys_create_buffer_set(struct ipu_psys_kcmd *kcmd, ++ struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys_fh *fh = kcmd->fh; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_buffer_set *kbuf_set; ++ size_t buf_set_size; ++ u32 *keb; ++ ++ buf_set_size = ipu_fw_psys_ppg_get_buffer_set_size(kcmd); ++ ++ kbuf_set = __get_buf_set(fh, buf_set_size); ++ if (!kbuf_set) { ++ dev_err(dev, "failed to create buffer set\n"); ++ return NULL; ++ } ++ ++ kbuf_set->buf_set = ipu_fw_psys_ppg_create_buffer_set(kcmd, ++ kbuf_set->kaddr, ++ 0); ++ ++ ipu_fw_psys_ppg_buffer_set_vaddress(kbuf_set->buf_set, ++ kbuf_set->dma_addr); ++ keb = kcmd->kernel_enable_bitmap; ++ ipu_fw_psys_ppg_buffer_set_set_kernel_enable_bitmap(kbuf_set->buf_set, ++ keb); ++ ++ return kbuf_set; ++} ++ ++int ipu_psys_ppg_get_bufset(struct ipu_psys_kcmd *kcmd, ++ struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_buffer_set *kbuf_set; ++ unsigned int i; ++ int ret; ++ ++ kbuf_set = ipu_psys_create_buffer_set(kcmd, kppg); ++ if (!kbuf_set) { ++ ret = -EINVAL; ++ goto error; ++ } ++ kcmd->kbuf_set = kbuf_set; ++ kbuf_set->kcmd = kcmd; ++ ++ for (i = 0; i < kcmd->nbuffers; i++) { ++ struct ipu_fw_psys_terminal *terminal; ++ u32 buffer; ++ ++ terminal = ipu_fw_psys_pg_get_terminal(kcmd, i); ++ if (!terminal) ++ continue; ++ ++ buffer = (u32)kcmd->kbufs[i]->dma_addr + ++ kcmd->buffers[i].data_offset; ++ ++ ret = ipu_fw_psys_ppg_set_buffer_set(kcmd, terminal, i, buffer); ++ if (ret) { ++ dev_err(dev, "Unable to set bufset\n"); ++ goto error; ++ } ++ } ++ ++ return 0; ++ ++error: ++ dev_err(dev, "failed to get buffer set\n"); ++ return ret; ++} ++ ++void ipu_psys_ppg_complete(struct ipu_psys *psys, struct ipu_psys_ppg *kppg) ++{ ++ struct device *dev; ++ u8 queue_id; ++ int old_ppg_state; ++ ++ if (!psys || !kppg) ++ return; ++ ++ dev = &psys->adev->auxdev.dev; ++ ++ mutex_lock(&kppg->mutex); ++ old_ppg_state = kppg->state; ++ if (kppg->state == PPG_STATE_STOPPING) { ++ struct ipu_psys_kcmd tmp_kcmd = { ++ .kpg = kppg->kpg, ++ }; ++ ++ kppg->state = PPG_STATE_STOPPED; ++ ipu_psys_free_resources(&kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ queue_id = ipu_fw_psys_ppg_get_base_queue_id(&tmp_kcmd); ++ ipu_psys_free_cmd_queue_resource(&psys->resource_pool_running, queue_id); ++ pm_runtime_put(dev); ++ } else { ++ if (kppg->state == PPG_STATE_SUSPENDING) { ++ kppg->state = PPG_STATE_SUSPENDED; ++ ipu_psys_free_resources(&kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ } else if (kppg->state == PPG_STATE_STARTED || ++ kppg->state == PPG_STATE_RESUMED) { ++ kppg->state = PPG_STATE_RUNNING; ++ } ++ ++ /* Kick l-scheduler thread for FW callback, ++ * also for checking if need to enter power gating ++ */ ++ atomic_set(&psys->wakeup_count, 1); ++ wake_up_interruptible(&psys->sched_cmd_wq); ++ } ++ if (old_ppg_state != kppg->state) ++ dev_dbg(dev, "s_change:%s: %p %d -> %d\n", __func__, kppg, ++ old_ppg_state, kppg->state); ++ ++ mutex_unlock(&kppg->mutex); ++} ++ ++int ipu_psys_ppg_start(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg, ++ KCMD_STATE_PPG_START); ++ unsigned int i; ++ int ret; ++ ++ if (!kcmd) { ++ dev_err(dev, "failed to find start kcmd!\n"); ++ return -EINVAL; ++ } ++ ++ dev_dbg(dev, "start ppg id %d, addr 0x%p\n", ++ ipu_fw_psys_pg_get_id(kcmd), kppg); ++ ++ kppg->state = PPG_STATE_STARTING; ++ for (i = 0; i < kcmd->nbuffers; i++) { ++ struct ipu_fw_psys_terminal *terminal; ++ ++ terminal = ipu_fw_psys_pg_get_terminal(kcmd, i); ++ if (!terminal) ++ continue; ++ ++ ret = ipu_fw_psys_terminal_set(terminal, i, kcmd, 0, ++ kcmd->buffers[i].len); ++ if (ret) { ++ dev_err(dev, "Unable to set terminal\n"); ++ return ret; ++ } ++ } ++ ++ ret = ipu_fw_psys_pg_submit(kcmd); ++ if (ret) { ++ dev_err(dev, "failed to submit kcmd!\n"); ++ return ret; ++ } ++ ++ ret = ipu_psys_allocate_resources(dev, kcmd->kpg->pg, kcmd->pg_manifest, ++ &kcmd->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ if (ret) { ++ dev_err(dev, "alloc resources failed!\n"); ++ return ret; ++ } ++ ++ ret = pm_runtime_get_sync(dev); ++ if (ret < 0) { ++ dev_err(dev, "failed to power on psys\n"); ++ goto error; ++ } ++ ++ ret = ipu_psys_kcmd_start(psys, kcmd); ++ if (ret) { ++ ipu_psys_kcmd_complete(kppg, kcmd, -EIO); ++ goto error; ++ } ++ ++ dev_dbg(dev, "s_change:%s: %p %d -> %d\n", ++ __func__, kppg, kppg->state, PPG_STATE_STARTED); ++ kppg->state = PPG_STATE_STARTED; ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ ++ return 0; ++ ++error: ++ pm_runtime_put_noidle(dev); ++ ipu_psys_reset_process_cell(dev, kcmd->kpg->pg, kcmd->pg_manifest, ++ kcmd->kpg->pg->process_count); ++ ipu_psys_free_resources(&kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ ++ dev_err(dev, "failed to start ppg\n"); ++ return ret; ++} ++ ++int ipu_psys_ppg_resume(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd tmp_kcmd = { ++ .kpg = kppg->kpg, ++ .fh = kppg->fh, ++ }; ++ int ret; ++ ++ dev_dbg(dev, "resume ppg id %d, addr 0x%p\n", ++ ipu_fw_psys_pg_get_id(&tmp_kcmd), kppg); ++ ++ kppg->state = PPG_STATE_RESUMING; ++ if (enable_suspend_resume) { ++ ret = ipu_psys_allocate_resources(dev, kppg->kpg->pg, ++ kppg->manifest, ++ &kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ if (ret) { ++ dev_err(dev, "failed to allocate res\n"); ++ return -EIO; ++ } ++ ++ ret = ipu_fw_psys_ppg_resume(&tmp_kcmd); ++ if (ret) { ++ dev_err(dev, "failed to resume ppg\n"); ++ goto error; ++ } ++ } else { ++ kppg->kpg->pg->state = IPU_FW_PSYS_PROCESS_GROUP_READY; ++ ret = ipu_fw_psys_pg_submit(&tmp_kcmd); ++ if (ret) { ++ dev_err(dev, "failed to submit kcmd!\n"); ++ return ret; ++ } ++ ++ ret = ipu_psys_allocate_resources(dev, kppg->kpg->pg, ++ kppg->manifest, ++ &kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ if (ret) { ++ dev_err(dev, "failed to allocate res\n"); ++ return ret; ++ } ++ ++ ret = ipu_psys_kcmd_start(psys, &tmp_kcmd); ++ if (ret) { ++ dev_err(dev, "failed to start kcmd!\n"); ++ goto error; ++ } ++ } ++ dev_dbg(dev, "s_change:%s: %p %d -> %d\n", ++ __func__, kppg, kppg->state, PPG_STATE_RESUMED); ++ kppg->state = PPG_STATE_RESUMED; ++ ++ return 0; ++ ++error: ++ ipu_psys_reset_process_cell(dev, kppg->kpg->pg, kppg->manifest, ++ kppg->kpg->pg->process_count); ++ ipu_psys_free_resources(&kppg->kpg->resource_alloc, ++ &psys->resource_pool_running); ++ ++ return ret; ++} ++ ++int ipu_psys_ppg_stop(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg, ++ KCMD_STATE_PPG_STOP); ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd kcmd_temp; ++ int ppg_id, ret = 0; ++ ++ if (kcmd) { ++ list_move_tail(&kcmd->list, &kppg->kcmds_processing_list); ++ } else { ++ dev_dbg(dev, "Exceptional stop happened!\n"); ++ kcmd_temp.kpg = kppg->kpg; ++ kcmd_temp.fh = kppg->fh; ++ kcmd = &kcmd_temp; ++ /* delete kppg in stop list to avoid this ppg resuming */ ++ ipu_psys_scheduler_remove_kppg(kppg, SCHED_STOP_LIST); ++ } ++ ++ ppg_id = ipu_fw_psys_pg_get_id(kcmd); ++ dev_dbg(dev, "stop ppg(%d, addr 0x%p)\n", ppg_id, kppg); ++ ++ if (kppg->state & PPG_STATE_SUSPENDED) { ++ if (enable_suspend_resume) { ++ dev_dbg(dev, "need resume before stop!\n"); ++ kcmd_temp.kpg = kppg->kpg; ++ kcmd_temp.fh = kppg->fh; ++ ret = ipu_fw_psys_ppg_resume(&kcmd_temp); ++ if (ret) ++ dev_err(dev, "ppg(%d) failed to resume\n", ++ ppg_id); ++ } else if (kcmd != &kcmd_temp) { ++ u8 queue_id = ipu_fw_psys_ppg_get_base_queue_id(kcmd); ++ ++ ipu_psys_free_cmd_queue_resource(&psys->resource_pool_running, ++ queue_id); ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ dev_dbg(dev, "s_change:%s %p %d -> %d\n", __func__, ++ kppg, kppg->state, PPG_STATE_STOPPED); ++ pm_runtime_put(dev); ++ kppg->state = PPG_STATE_STOPPED; ++ ++ return 0; ++ } else { ++ return 0; ++ } ++ } ++ dev_dbg(dev, "s_change:%s %p %d -> %d\n", __func__, kppg, kppg->state, ++ PPG_STATE_STOPPING); ++ kppg->state = PPG_STATE_STOPPING; ++ ret = ipu_fw_psys_pg_abort(kcmd); ++ if (ret) ++ dev_err(dev, "ppg(%d) failed to abort\n", ppg_id); ++ ++ return ret; ++} ++ ++int ipu_psys_ppg_suspend(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd tmp_kcmd = { ++ .kpg = kppg->kpg, ++ .fh = kppg->fh, ++ }; ++ int ppg_id = ipu_fw_psys_pg_get_id(&tmp_kcmd); ++ int ret = 0; ++ ++ dev_dbg(dev, "suspend ppg(%d, addr 0x%p)\n", ppg_id, kppg); ++ ++ dev_dbg(dev, "s_change:%s %p %d -> %d\n", __func__, kppg, kppg->state, ++ PPG_STATE_SUSPENDING); ++ kppg->state = PPG_STATE_SUSPENDING; ++ if (enable_suspend_resume) ++ ret = ipu_fw_psys_ppg_suspend(&tmp_kcmd); ++ else ++ ret = ipu_fw_psys_pg_abort(&tmp_kcmd); ++ if (ret) ++ dev_err(dev, "failed to %s ppg(%d)\n", ++ enable_suspend_resume ? "suspend" : "stop", ret); ++ ++ return ret; ++} ++ ++static bool ipu_psys_ppg_is_bufset_existing(struct ipu_psys_ppg *kppg) ++{ ++ return !list_empty(&kppg->kcmds_new_list); ++} ++ ++/* ++ * ipu_psys_ppg_enqueue_bufsets - enqueue buffer sets to firmware ++ * Sometimes, if the ppg is at suspended state, this function will return true ++ * to reschedule and let the resume command scheduled before the buffer sets ++ * enqueuing. ++ */ ++bool ipu_psys_ppg_enqueue_bufsets(struct ipu_psys_ppg *kppg) ++{ ++ struct ipu_psys_kcmd *kcmd, *kcmd0; ++ struct ipu_psys *psys = kppg->fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ bool need_resume = false; ++ ++ mutex_lock(&kppg->mutex); ++ ++ if (kppg->state & (PPG_STATE_STARTED | PPG_STATE_RESUMED | ++ PPG_STATE_RUNNING)) { ++ if (ipu_psys_ppg_is_bufset_existing(kppg)) { ++ list_for_each_entry_safe(kcmd, kcmd0, ++ &kppg->kcmds_new_list, list) { ++ int ret; ++ ++ if (kcmd->state != KCMD_STATE_PPG_ENQUEUE) { ++ need_resume = true; ++ break; ++ } ++ ++ ret = ipu_fw_psys_ppg_enqueue_bufs(kcmd); ++ if (ret) { ++ dev_err(dev, ++ "kppg 0x%p fail to qbufset %d", ++ kppg, ret); ++ break; ++ } ++ list_move_tail(&kcmd->list, ++ &kppg->kcmds_processing_list); ++ dev_dbg(dev, ++ "kppg %d %p queue kcmd 0x%p fh 0x%p\n", ++ ipu_fw_psys_pg_get_id(kcmd), ++ kppg, kcmd, kcmd->fh); ++ } ++ } ++ } ++ ++ mutex_unlock(&kppg->mutex); ++ return need_resume; ++} ++ ++void ipu_psys_enter_power_gating(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ /* ++ * Only for SUSPENDED kppgs, STOPPED kppgs has already ++ * power down and new kppgs might come now. ++ */ ++ if (kppg->state != PPG_STATE_SUSPENDED) { ++ mutex_unlock(&kppg->mutex); ++ continue; ++ } ++ ++ pm_runtime_put(dev); ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++} ++ ++void ipu_psys_exit_power_gating(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ int ret = 0; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ mutex_lock(&fh->mutex); ++ sched = &fh->sched; ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ /* Only for SUSPENDED kppgs */ ++ if (kppg->state != PPG_STATE_SUSPENDED) { ++ mutex_unlock(&kppg->mutex); ++ continue; ++ } ++ ++ ret = pm_runtime_get_sync(dev); ++ if (ret < 0) { ++ dev_err(dev, "failed to power gating\n"); ++ pm_runtime_put_noidle(dev); ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.h b/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.h +new file mode 100644 +index 000000000000..676a4eadc23d +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.h +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 - 2024 Intel Corporation ++ */ ++ ++#ifndef IPU6_PPG_H ++#define IPU6_PPG_H ++ ++#include "ipu-psys.h" ++/* starting from '2' in case of someone passes true or false */ ++enum SCHED_LIST { ++ SCHED_START_LIST = 2, ++ SCHED_STOP_LIST ++}; ++ ++enum ipu_psys_power_gating_state { ++ PSYS_POWER_NORMAL = 0, ++ PSYS_POWER_GATING, ++ PSYS_POWER_GATED ++}; ++ ++int ipu_psys_ppg_get_bufset(struct ipu_psys_kcmd *kcmd, ++ struct ipu_psys_ppg *kppg); ++struct ipu_psys_kcmd *ipu_psys_ppg_get_stop_kcmd(struct ipu_psys_ppg *kppg); ++void ipu_psys_scheduler_remove_kppg(struct ipu_psys_ppg *kppg, ++ enum SCHED_LIST type); ++void ipu_psys_scheduler_add_kppg(struct ipu_psys_ppg *kppg, ++ enum SCHED_LIST type); ++int ipu_psys_ppg_start(struct ipu_psys_ppg *kppg); ++int ipu_psys_ppg_resume(struct ipu_psys_ppg *kppg); ++int ipu_psys_ppg_stop(struct ipu_psys_ppg *kppg); ++int ipu_psys_ppg_suspend(struct ipu_psys_ppg *kppg); ++void ipu_psys_ppg_complete(struct ipu_psys *psys, struct ipu_psys_ppg *kppg); ++bool ipu_psys_ppg_enqueue_bufsets(struct ipu_psys_ppg *kppg); ++void ipu_psys_enter_power_gating(struct ipu_psys *psys); ++void ipu_psys_exit_power_gating(struct ipu_psys *psys); ++ ++#endif /* IPU6_PPG_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-psys-gpc.c b/drivers/media/pci/intel/ipu6/psys/ipu6-psys-gpc.c +new file mode 100644 +index 000000000000..ff1925693558 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-psys-gpc.c +@@ -0,0 +1,209 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2020 - 2024 Intel Corporation ++ ++#ifdef CONFIG_DEBUG_FS ++#include ++#include ++ ++#include "ipu-psys.h" ++#include "ipu-platform-regs.h" ++ ++/* ++ * GPC (Gerneral Performance Counters) ++ */ ++#define IPU_PSYS_GPC_NUM 16 ++ ++#ifndef CONFIG_PM ++#define pm_runtime_get_sync(d) 0 ++#define pm_runtime_put(d) 0 ++#endif ++ ++struct ipu_psys_gpc { ++ bool enable; ++ unsigned int route; ++ unsigned int source; ++ unsigned int sense; ++ unsigned int gpcindex; ++ void *prit; ++}; ++ ++struct ipu_psys_gpcs { ++ bool gpc_enable; ++ struct ipu_psys_gpc gpc[IPU_PSYS_GPC_NUM]; ++ void *prit; ++}; ++ ++static int ipu6_psys_gpc_global_enable_get(void *data, u64 *val) ++{ ++ struct ipu_psys_gpcs *psys_gpcs = data; ++ struct ipu_psys *psys = psys_gpcs->prit; ++ ++ mutex_lock(&psys->mutex); ++ ++ *val = psys_gpcs->gpc_enable; ++ ++ mutex_unlock(&psys->mutex); ++ return 0; ++} ++ ++static int ipu6_psys_gpc_global_enable_set(void *data, u64 val) ++{ ++ struct ipu_psys_gpcs *psys_gpcs = data; ++ struct ipu_psys *psys = psys_gpcs->prit; ++ void __iomem *base; ++ int idx, res; ++ ++ if (val != 0 && val != 1) ++ return -EINVAL; ++ ++ if (!psys || !psys->pdata || !psys->pdata->base) ++ return -EINVAL; ++ ++ mutex_lock(&psys->mutex); ++ ++ base = psys->pdata->base + IPU_GPC_BASE; ++ ++ res = pm_runtime_get_sync(&psys->adev->dev); ++ if (res < 0) { ++ pm_runtime_put(&psys->adev->dev); ++ mutex_unlock(&psys->mutex); ++ return res; ++ } ++ ++ if (val == 0) { ++ writel(0x0, base + IPU_GPREG_TRACE_TIMER_RST); ++ writel(0x0, base + IPU_CDC_MMU_GPC_OVERALL_ENABLE); ++ writel(0xffff, base + IPU_CDC_MMU_GPC_SOFT_RESET); ++ psys_gpcs->gpc_enable = false; ++ for (idx = 0; idx < IPU_PSYS_GPC_NUM; idx++) { ++ psys_gpcs->gpc[idx].enable = 0; ++ psys_gpcs->gpc[idx].sense = 0; ++ psys_gpcs->gpc[idx].route = 0; ++ psys_gpcs->gpc[idx].source = 0; ++ } ++ pm_runtime_put(&psys->adev->dev); ++ } else { ++ /* Set gpc reg and start all gpc here. ++ * RST free running local timer. ++ */ ++ writel(0x0, base + IPU_GPREG_TRACE_TIMER_RST); ++ writel(0x1, base + IPU_GPREG_TRACE_TIMER_RST); ++ ++ for (idx = 0; idx < IPU_PSYS_GPC_NUM; idx++) { ++ /* Enable */ ++ writel(psys_gpcs->gpc[idx].enable, ++ base + IPU_CDC_MMU_GPC_ENABLE0 + 4 * idx); ++ /* Setting (route/source/sense) */ ++ writel((psys_gpcs->gpc[idx].sense ++ << IPU_GPC_SENSE_OFFSET) ++ + (psys_gpcs->gpc[idx].route ++ << IPU_GPC_ROUTE_OFFSET) ++ + (psys_gpcs->gpc[idx].source ++ << IPU_GPC_SOURCE_OFFSET), ++ base + IPU_CDC_MMU_GPC_CNT_SEL0 + 4 * idx); ++ } ++ ++ /* Soft reset and Overall Enable. */ ++ writel(0x0, base + IPU_CDC_MMU_GPC_OVERALL_ENABLE); ++ writel(0xffff, base + IPU_CDC_MMU_GPC_SOFT_RESET); ++ writel(0x1, base + IPU_CDC_MMU_GPC_OVERALL_ENABLE); ++ ++ psys_gpcs->gpc_enable = true; ++ } ++ ++ mutex_unlock(&psys->mutex); ++ return 0; ++} ++ ++DEFINE_SIMPLE_ATTRIBUTE(psys_gpc_globe_enable_fops, ++ ipu6_psys_gpc_global_enable_get, ++ ipu6_psys_gpc_global_enable_set, "%llu\n"); ++ ++static int ipu6_psys_gpc_count_get(void *data, u64 *val) ++{ ++ struct ipu_psys_gpc *psys_gpc = data; ++ struct ipu_psys *psys = psys_gpc->prit; ++ void __iomem *base; ++ int res; ++ ++ if (!psys || !psys->pdata || !psys->pdata->base) ++ return -EINVAL; ++ ++ mutex_lock(&psys->mutex); ++ ++ base = psys->pdata->base + IPU_GPC_BASE; ++ ++ res = pm_runtime_get_sync(&psys->adev->dev); ++ if (res < 0) { ++ pm_runtime_put(&psys->adev->dev); ++ mutex_unlock(&psys->mutex); ++ return res; ++ } ++ ++ *val = readl(base + IPU_CDC_MMU_GPC_VALUE0 + 4 * psys_gpc->gpcindex); ++ ++ mutex_unlock(&psys->mutex); ++ return 0; ++} ++ ++DEFINE_SIMPLE_ATTRIBUTE(psys_gpc_count_fops, ++ ipu6_psys_gpc_count_get, ++ NULL, "%llu\n"); ++ ++int ipu_psys_gpc_init_debugfs(struct ipu_psys *psys) ++{ ++ struct dentry *gpcdir; ++ struct dentry *dir; ++ struct dentry *file; ++ int idx; ++ char gpcname[10]; ++ struct ipu_psys_gpcs *psys_gpcs; ++ ++ psys_gpcs = devm_kzalloc(&psys->dev, sizeof(*psys_gpcs), GFP_KERNEL); ++ if (!psys_gpcs) ++ return -ENOMEM; ++ ++ gpcdir = debugfs_create_dir("gpc", psys->debugfsdir); ++ if (IS_ERR(gpcdir)) ++ return -ENOMEM; ++ ++ psys_gpcs->prit = psys; ++ file = debugfs_create_file("enable", 0600, gpcdir, psys_gpcs, ++ &psys_gpc_globe_enable_fops); ++ if (IS_ERR(file)) ++ goto err; ++ ++ for (idx = 0; idx < IPU_PSYS_GPC_NUM; idx++) { ++ sprintf(gpcname, "gpc%d", idx); ++ dir = debugfs_create_dir(gpcname, gpcdir); ++ if (IS_ERR(dir)) ++ goto err; ++ ++ debugfs_create_bool("enable", 0600, dir, ++ &psys_gpcs->gpc[idx].enable); ++ ++ debugfs_create_u32("source", 0600, dir, ++ &psys_gpcs->gpc[idx].source); ++ ++ debugfs_create_u32("route", 0600, dir, ++ &psys_gpcs->gpc[idx].route); ++ ++ debugfs_create_u32("sense", 0600, dir, ++ &psys_gpcs->gpc[idx].sense); ++ ++ psys_gpcs->gpc[idx].gpcindex = idx; ++ psys_gpcs->gpc[idx].prit = psys; ++ file = debugfs_create_file("count", 0400, dir, ++ &psys_gpcs->gpc[idx], ++ &psys_gpc_count_fops); ++ if (IS_ERR(file)) ++ goto err; ++ } ++ ++ return 0; ++ ++err: ++ debugfs_remove_recursive(gpcdir); ++ return -ENOMEM; ++} ++#endif +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6-psys.c b/drivers/media/pci/intel/ipu6/psys/ipu6-psys.c +new file mode 100644 +index 000000000000..ea442b0fb50e +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6-psys.c +@@ -0,0 +1,1049 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2020 - 2024 Intel Corporation ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ipu6.h" ++#include "ipu-psys.h" ++#include "ipu6-ppg.h" ++#include "ipu6-platform-regs.h" ++#include "ipu6-platform-buttress-regs.h" ++ ++MODULE_IMPORT_NS(DMA_BUF); ++ ++static bool early_pg_transfer; ++module_param(early_pg_transfer, bool, 0664); ++MODULE_PARM_DESC(early_pg_transfer, ++ "Copy PGs back to user after resource allocation"); ++ ++bool enable_power_gating = true; ++module_param(enable_power_gating, bool, 0664); ++MODULE_PARM_DESC(enable_power_gating, "enable power gating"); ++ ++static void ipu6_set_sp_info_bits(void __iomem *base) ++{ ++ int i; ++ ++ writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); ++ ++ for (i = 0; i < 4; i++) ++ writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ base + IPU_REG_PSYS_INFO_SEG_CMEM_MASTER(i)); ++ for (i = 0; i < 4; i++) ++ writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ base + IPU_REG_PSYS_INFO_SEG_XMEM_MASTER(i)); ++} ++ ++#define PSYS_SUBDOMAINS_STATUS_WAIT_COUNT 1000 ++void ipu_psys_subdomains_power(struct ipu_psys *psys, bool on) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ unsigned int i; ++ u32 val; ++ ++ /* power domain req */ ++ dev_dbg(dev, "power %s psys sub-domains", on ? "UP" : "DOWN"); ++ if (on) ++ writel(IPU_PSYS_SUBDOMAINS_POWER_MASK, ++ psys->adev->isp->base + IPU_PSYS_SUBDOMAINS_POWER_REQ); ++ else ++ writel(0x0, ++ psys->adev->isp->base + IPU_PSYS_SUBDOMAINS_POWER_REQ); ++ ++ i = 0; ++ do { ++ usleep_range(10, 20); ++ val = readl(psys->adev->isp->base + ++ IPU_PSYS_SUBDOMAINS_POWER_STATUS); ++ if (!(val & BIT(31))) { ++ dev_dbg(dev, "PS sub-domains req done with status 0x%x", ++ val); ++ break; ++ } ++ i++; ++ } while (i < PSYS_SUBDOMAINS_STATUS_WAIT_COUNT); ++ ++ if (i == PSYS_SUBDOMAINS_STATUS_WAIT_COUNT) ++ dev_warn(dev, "Psys sub-domains %s req timeout!", ++ on ? "UP" : "DOWN"); ++} ++ ++void ipu_psys_setup_hw(struct ipu_psys *psys) ++{ ++ void __iomem *base = psys->pdata->base; ++ void __iomem *spc_regs_base = ++ base + psys->pdata->ipdata->hw_variant.spc_offset; ++ void __iomem *psys_iommu0_ctrl; ++ u32 irqs; ++ const u8 r3 = IPU_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG; ++ const u8 r4 = IPU_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR; ++ const u8 r5 = IPU_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL; ++ ++ if (!psys->adev->isp->secure_mode) { ++ /* configure access blocker for non-secure mode */ ++ writel(NCI_AB_ACCESS_MODE_RW, ++ base + IPU_REG_DMA_TOP_AB_GROUP1_BASE_ADDR + ++ IPU_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(r3)); ++ writel(NCI_AB_ACCESS_MODE_RW, ++ base + IPU_REG_DMA_TOP_AB_GROUP1_BASE_ADDR + ++ IPU_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(r4)); ++ writel(NCI_AB_ACCESS_MODE_RW, ++ base + IPU_REG_DMA_TOP_AB_GROUP1_BASE_ADDR + ++ IPU_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(r5)); ++ } ++ psys_iommu0_ctrl = base + ++ psys->pdata->ipdata->hw_variant.mmu_hw[0].offset + ++ IPU6_MMU_INFO_OFFSET; ++ writel(IPU_INFO_REQUEST_DESTINATION_IOSF, psys_iommu0_ctrl); ++ ++ ipu6_set_sp_info_bits(spc_regs_base + IPU_PSYS_REG_SPC_STATUS_CTRL); ++ ipu6_set_sp_info_bits(spc_regs_base + IPU_PSYS_REG_SPP0_STATUS_CTRL); ++ ++ /* Enable FW interrupt #0 */ ++ writel(0, base + IPU_REG_PSYS_GPDEV_FWIRQ(0)); ++ irqs = IPU6_PSYS_GPDEV_IRQ_FWIRQ(IPU_PSYS_GPDEV_FWIRQ0); ++ writel(irqs, base + IPU_REG_PSYS_GPDEV_IRQ_EDGE); ++ writel(irqs, base + IPU_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE); ++ writel(0xffffffff, base + IPU_REG_PSYS_GPDEV_IRQ_CLEAR); ++ writel(irqs, base + IPU_REG_PSYS_GPDEV_IRQ_MASK); ++ writel(irqs, base + IPU_REG_PSYS_GPDEV_IRQ_ENABLE); ++} ++ ++static struct ipu_psys_ppg *ipu_psys_identify_kppg(struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_psys_scheduler *sched = &kcmd->fh->sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ ++ mutex_lock(&kcmd->fh->mutex); ++ if (list_empty(&sched->ppgs)) ++ goto not_found; ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ if (ipu_fw_psys_pg_get_token(kcmd) ++ != kppg->token) ++ continue; ++ mutex_unlock(&kcmd->fh->mutex); ++ return kppg; ++ } ++ ++not_found: ++ mutex_unlock(&kcmd->fh->mutex); ++ return NULL; ++} ++ ++/* ++ * Called to free up all resources associated with a kcmd. ++ * After this the kcmd doesn't anymore exist in the driver. ++ */ ++static void ipu_psys_kcmd_free(struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_psys_ppg *kppg; ++ struct ipu_psys_scheduler *sched; ++ ++ if (!kcmd) ++ return; ++ ++ kppg = ipu_psys_identify_kppg(kcmd); ++ sched = &kcmd->fh->sched; ++ ++ if (kcmd->kbuf_set) { ++ mutex_lock(&sched->bs_mutex); ++ kcmd->kbuf_set->buf_set_size = 0; ++ mutex_unlock(&sched->bs_mutex); ++ kcmd->kbuf_set = NULL; ++ } ++ ++ if (kppg) { ++ mutex_lock(&kppg->mutex); ++ if (!list_empty(&kcmd->list)) ++ list_del(&kcmd->list); ++ mutex_unlock(&kppg->mutex); ++ } ++ ++ kfree(kcmd->pg_manifest); ++ kfree(kcmd->kbufs); ++ kfree(kcmd->buffers); ++ kfree(kcmd); ++} ++ ++static struct ipu_psys_kcmd *ipu_psys_copy_cmd(struct ipu_psys_command *cmd, ++ struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd; ++ struct ipu_psys_kbuffer *kpgbuf; ++ unsigned int i; ++ int ret, prevfd, fd; ++ ++ fd = prevfd = -1; ++ ++ if (cmd->bufcount > IPU_MAX_PSYS_CMD_BUFFERS) ++ return NULL; ++ ++ if (!cmd->pg_manifest_size) ++ return NULL; ++ ++ kcmd = kzalloc(sizeof(*kcmd), GFP_KERNEL); ++ if (!kcmd) ++ return NULL; ++ ++ kcmd->state = KCMD_STATE_PPG_NEW; ++ kcmd->fh = fh; ++ INIT_LIST_HEAD(&kcmd->list); ++ ++ mutex_lock(&fh->mutex); ++ fd = cmd->pg; ++ kpgbuf = ipu_psys_lookup_kbuffer(fh, fd); ++ if (!kpgbuf || !kpgbuf->sgt) { ++ dev_err(dev, "%s kbuf %p with fd %d not found.\n", ++ __func__, kpgbuf, fd); ++ mutex_unlock(&fh->mutex); ++ goto error; ++ } ++ ++ /* check and remap if possibe */ ++ kpgbuf = ipu_psys_mapbuf_locked(fd, fh); ++ if (!kpgbuf || !kpgbuf->sgt) { ++ dev_err(dev, "%s remap failed\n", __func__); ++ mutex_unlock(&fh->mutex); ++ goto error; ++ } ++ mutex_unlock(&fh->mutex); ++ ++ kcmd->pg_user = kpgbuf->kaddr; ++ kcmd->kpg = __get_pg_buf(psys, kpgbuf->len); ++ if (!kcmd->kpg) ++ goto error; ++ ++ memcpy(kcmd->kpg->pg, kcmd->pg_user, kcmd->kpg->pg_size); ++ ++ kcmd->pg_manifest = kzalloc(cmd->pg_manifest_size, GFP_KERNEL); ++ if (!kcmd->pg_manifest) ++ goto error; ++ ++ ret = copy_from_user(kcmd->pg_manifest, cmd->pg_manifest, ++ cmd->pg_manifest_size); ++ if (ret) ++ goto error; ++ ++ kcmd->pg_manifest_size = cmd->pg_manifest_size; ++ ++ kcmd->user_token = cmd->user_token; ++ kcmd->issue_id = cmd->issue_id; ++ kcmd->priority = cmd->priority; ++ if (kcmd->priority >= IPU_PSYS_CMD_PRIORITY_NUM) ++ goto error; ++ ++ /* ++ * Kenel enable bitmap be used only. ++ */ ++ memcpy(kcmd->kernel_enable_bitmap, cmd->kernel_enable_bitmap, ++ sizeof(cmd->kernel_enable_bitmap)); ++ ++ kcmd->nbuffers = ipu_fw_psys_pg_get_terminal_count(kcmd); ++ kcmd->buffers = kcalloc(kcmd->nbuffers, sizeof(*kcmd->buffers), ++ GFP_KERNEL); ++ if (!kcmd->buffers) ++ goto error; ++ ++ kcmd->kbufs = kcalloc(kcmd->nbuffers, sizeof(kcmd->kbufs[0]), ++ GFP_KERNEL); ++ if (!kcmd->kbufs) ++ goto error; ++ ++ /* should be stop cmd for ppg */ ++ if (!cmd->buffers) { ++ kcmd->state = KCMD_STATE_PPG_STOP; ++ return kcmd; ++ } ++ ++ if (!cmd->bufcount || kcmd->nbuffers > cmd->bufcount) ++ goto error; ++ ++ ret = copy_from_user(kcmd->buffers, cmd->buffers, ++ kcmd->nbuffers * sizeof(*kcmd->buffers)); ++ if (ret) ++ goto error; ++ ++ for (i = 0; i < kcmd->nbuffers; i++) { ++ struct ipu_fw_psys_terminal *terminal; ++ ++ terminal = ipu_fw_psys_pg_get_terminal(kcmd, i); ++ if (!terminal) ++ continue; ++ ++ if (!(kcmd->buffers[i].flags & IPU_BUFFER_FLAG_DMA_HANDLE)) { ++ kcmd->state = KCMD_STATE_PPG_START; ++ continue; ++ } ++ if (kcmd->state == KCMD_STATE_PPG_START) { ++ dev_err(dev, "buffer.flags & DMA_HANDLE must be 0\n"); ++ goto error; ++ } ++ ++ mutex_lock(&fh->mutex); ++ fd = kcmd->buffers[i].base.fd; ++ kpgbuf = ipu_psys_lookup_kbuffer(fh, fd); ++ if (!kpgbuf || !kpgbuf->sgt) { ++ dev_err(dev, ++ "%s kcmd->buffers[%d] %p fd %d not found.\n", ++ __func__, i, kpgbuf, fd); ++ mutex_unlock(&fh->mutex); ++ goto error; ++ } ++ ++ kpgbuf = ipu_psys_mapbuf_locked(fd, fh); ++ if (!kpgbuf || !kpgbuf->sgt) { ++ dev_err(dev, "%s remap failed\n", ++ __func__); ++ mutex_unlock(&fh->mutex); ++ goto error; ++ } ++ mutex_unlock(&fh->mutex); ++ kcmd->kbufs[i] = kpgbuf; ++ if (!kcmd->kbufs[i] || !kcmd->kbufs[i]->sgt || ++ kcmd->kbufs[i]->len < kcmd->buffers[i].bytes_used) ++ goto error; ++ if ((kcmd->kbufs[i]->flags & ++ IPU_BUFFER_FLAG_NO_FLUSH) || ++ (kcmd->buffers[i].flags & ++ IPU_BUFFER_FLAG_NO_FLUSH) || ++ prevfd == kcmd->buffers[i].base.fd) ++ continue; ++ ++ prevfd = kcmd->buffers[i].base.fd; ++ dma_sync_sg_for_device(dev, kcmd->kbufs[i]->sgt->sgl, ++ kcmd->kbufs[i]->sgt->orig_nents, ++ DMA_BIDIRECTIONAL); ++ } ++ ++ if (kcmd->state != KCMD_STATE_PPG_START) ++ kcmd->state = KCMD_STATE_PPG_ENQUEUE; ++ ++ return kcmd; ++error: ++ ipu_psys_kcmd_free(kcmd); ++ ++ dev_dbg(dev, "failed to copy cmd\n"); ++ ++ return NULL; ++} ++ ++static struct ipu_psys_buffer_set * ++ipu_psys_lookup_kbuffer_set(struct ipu_psys *psys, u32 addr) ++{ ++ struct ipu_psys_fh *fh; ++ struct ipu_psys_buffer_set *kbuf_set; ++ struct ipu_psys_scheduler *sched; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ sched = &fh->sched; ++ mutex_lock(&sched->bs_mutex); ++ list_for_each_entry(kbuf_set, &sched->buf_sets, list) { ++ if (kbuf_set->buf_set && ++ kbuf_set->buf_set->ipu_virtual_address == addr) { ++ mutex_unlock(&sched->bs_mutex); ++ return kbuf_set; ++ } ++ } ++ mutex_unlock(&sched->bs_mutex); ++ } ++ ++ return NULL; ++} ++ ++static struct ipu_psys_ppg *ipu_psys_lookup_ppg(struct ipu_psys *psys, ++ dma_addr_t pg_addr) ++{ ++ struct ipu_psys_scheduler *sched; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_fh *fh; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ sched = &fh->sched; ++ mutex_lock(&fh->mutex); ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ if (pg_addr != kppg->kpg->pg_dma_addr) ++ continue; ++ mutex_unlock(&fh->mutex); ++ return kppg; ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ return NULL; ++} ++ ++#define BUTTRESS_FREQ_CTL_QOS_FLOOR_SHIFT 8 ++static void ipu_buttress_set_psys_ratio(struct ipu6_device *isp, ++ unsigned int psys_divisor, ++ unsigned int psys_qos_floor) ++{ ++ struct ipu6_buttress_ctrl *ctrl = isp->psys->ctrl; ++ ++ mutex_lock(&isp->buttress.power_mutex); ++ ++ if (ctrl->ratio == psys_divisor && ctrl->qos_floor == psys_qos_floor) ++ goto out_mutex_unlock; ++ ++ ctrl->ratio = psys_divisor; ++ ctrl->qos_floor = psys_qos_floor; ++ ++ if (ctrl->started) { ++ /* ++ * According to documentation driver initiates DVFS ++ * transition by writing wanted ratio, floor ratio and start ++ * bit. No need to stop PS first ++ */ ++ writel(BUTTRESS_FREQ_CTL_START | ++ ctrl->qos_floor << BUTTRESS_FREQ_CTL_QOS_FLOOR_SHIFT | ++ psys_divisor, isp->base + BUTTRESS_REG_PS_FREQ_CTL); ++ } ++ ++out_mutex_unlock: ++ mutex_unlock(&isp->buttress.power_mutex); ++} ++ ++static void ipu_buttress_set_psys_freq(struct ipu6_device *isp, ++ unsigned int freq) ++{ ++ unsigned int psys_ratio = freq / BUTTRESS_PS_FREQ_STEP; ++ ++ dev_dbg(&isp->psys->auxdev.dev, "freq:%u\n", freq); ++ ++ ipu_buttress_set_psys_ratio(isp, psys_ratio, psys_ratio); ++} ++ ++static void ++ipu_buttress_add_psys_constraint(struct ipu6_device *isp, ++ struct ipu6_psys_constraint *constraint) ++{ ++ struct ipu6_buttress *b = &isp->buttress; ++ ++ mutex_lock(&b->cons_mutex); ++ list_add(&constraint->list, &b->constraints); ++ mutex_unlock(&b->cons_mutex); ++} ++ ++static void ++ipu_buttress_remove_psys_constraint(struct ipu6_device *isp, ++ struct ipu6_psys_constraint *constraint) ++{ ++ struct ipu6_buttress *b = &isp->buttress; ++ struct ipu6_psys_constraint *c; ++ unsigned int min_freq = 0; ++ ++ mutex_lock(&b->cons_mutex); ++ list_del(&constraint->list); ++ ++ list_for_each_entry(c, &b->constraints, list) ++ if (c->min_freq > min_freq) ++ min_freq = c->min_freq; ++ ++ ipu_buttress_set_psys_freq(isp, min_freq); ++ mutex_unlock(&b->cons_mutex); ++} ++ ++/* ++ * Move kcmd into completed state (due to running finished or failure). ++ * Fill up the event struct and notify waiters. ++ */ ++void ipu_psys_kcmd_complete(struct ipu_psys_ppg *kppg, ++ struct ipu_psys_kcmd *kcmd, int error) ++{ ++ struct ipu_psys_fh *fh = kcmd->fh; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ ++ kcmd->ev.type = IPU_PSYS_EVENT_TYPE_CMD_COMPLETE; ++ kcmd->ev.user_token = kcmd->user_token; ++ kcmd->ev.issue_id = kcmd->issue_id; ++ kcmd->ev.error = error; ++ list_move_tail(&kcmd->list, &kppg->kcmds_finished_list); ++ ++ if (kcmd->constraint.min_freq) ++ ipu_buttress_remove_psys_constraint(psys->adev->isp, ++ &kcmd->constraint); ++ ++ if (!early_pg_transfer && kcmd->pg_user && kcmd->kpg->pg) { ++ struct ipu_psys_kbuffer *kbuf; ++ ++ kbuf = ipu_psys_lookup_kbuffer_by_kaddr(kcmd->fh, ++ kcmd->pg_user); ++ if (kbuf && kbuf->valid) ++ memcpy(kcmd->pg_user, ++ kcmd->kpg->pg, kcmd->kpg->pg_size); ++ else ++ dev_dbg(dev, "Skipping unmapped buffer\n"); ++ } ++ ++ kcmd->state = KCMD_STATE_PPG_COMPLETE; ++ wake_up_interruptible(&fh->wait); ++} ++ ++/* ++ * Submit kcmd into psys queue. If running fails, complete the kcmd ++ * with an error. ++ * ++ * Found a runnable PG. Move queue to the list tail for round-robin ++ * scheduling and run the PG. Start the watchdog timer if the PG was ++ * started successfully. Enable PSYS power if requested. ++ */ ++int ipu_psys_kcmd_start(struct ipu_psys *psys, struct ipu_psys_kcmd *kcmd) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ int ret; ++ ++ if (early_pg_transfer && kcmd->pg_user && kcmd->kpg->pg) ++ memcpy(kcmd->pg_user, kcmd->kpg->pg, kcmd->kpg->pg_size); ++ ++ ret = ipu_fw_psys_pg_start(kcmd); ++ if (ret) { ++ dev_err(dev, "failed to start kcmd!\n"); ++ return ret; ++ } ++ ++ ipu_fw_psys_pg_dump(psys, kcmd, "run"); ++ ++ ret = ipu_fw_psys_pg_disown(kcmd); ++ if (ret) { ++ dev_err(dev, "failed to start kcmd!\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipu_psys_kcmd_send_to_ppg_start(struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_psys_fh *fh = kcmd->fh; ++ struct ipu_psys_scheduler *sched = &fh->sched; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *kppg; ++ struct ipu_psys_resource_pool *rpr; ++ int queue_id; ++ int ret; ++ ++ rpr = &psys->resource_pool_running; ++ ++ kppg = kzalloc(sizeof(*kppg), GFP_KERNEL); ++ if (!kppg) ++ return -ENOMEM; ++ ++ kppg->fh = fh; ++ kppg->kpg = kcmd->kpg; ++ kppg->state = PPG_STATE_START; ++ kppg->pri_base = kcmd->priority; ++ kppg->pri_dynamic = 0; ++ INIT_LIST_HEAD(&kppg->list); ++ ++ mutex_init(&kppg->mutex); ++ INIT_LIST_HEAD(&kppg->kcmds_new_list); ++ INIT_LIST_HEAD(&kppg->kcmds_processing_list); ++ INIT_LIST_HEAD(&kppg->kcmds_finished_list); ++ INIT_LIST_HEAD(&kppg->sched_list); ++ ++ kppg->manifest = kzalloc(kcmd->pg_manifest_size, GFP_KERNEL); ++ if (!kppg->manifest) { ++ kfree(kppg); ++ return -ENOMEM; ++ } ++ memcpy(kppg->manifest, kcmd->pg_manifest, ++ kcmd->pg_manifest_size); ++ ++ queue_id = ipu_psys_allocate_cmd_queue_resource(rpr); ++ if (queue_id == -ENOSPC) { ++ dev_err(dev, "no available queue\n"); ++ kfree(kppg->manifest); ++ kfree(kppg); ++ mutex_unlock(&psys->mutex); ++ return -ENOMEM; ++ } ++ ++ /* ++ * set token as start cmd will immediately be followed by a ++ * enqueue cmd so that kppg could be retrieved. ++ */ ++ kppg->token = (u64)kcmd->kpg; ++ ipu_fw_psys_pg_set_token(kcmd, kppg->token); ++ ipu_fw_psys_ppg_set_base_queue_id(kcmd, queue_id); ++ ret = ipu_fw_psys_pg_set_ipu_vaddress(kcmd, ++ kcmd->kpg->pg_dma_addr); ++ if (ret) { ++ ipu_psys_free_cmd_queue_resource(rpr, queue_id); ++ kfree(kppg->manifest); ++ kfree(kppg); ++ return -EIO; ++ } ++ memcpy(kcmd->pg_user, kcmd->kpg->pg, kcmd->kpg->pg_size); ++ ++ mutex_lock(&fh->mutex); ++ list_add_tail(&kppg->list, &sched->ppgs); ++ mutex_unlock(&fh->mutex); ++ ++ mutex_lock(&kppg->mutex); ++ list_add(&kcmd->list, &kppg->kcmds_new_list); ++ mutex_unlock(&kppg->mutex); ++ ++ dev_dbg(dev, "START ppg(%d, 0x%p) kcmd 0x%p, queue %d\n", ++ ipu_fw_psys_pg_get_id(kcmd), kppg, kcmd, queue_id); ++ ++ /* Kick l-scheduler thread */ ++ atomic_set(&psys->wakeup_count, 1); ++ wake_up_interruptible(&psys->sched_cmd_wq); ++ ++ return 0; ++} ++ ++static int ipu_psys_kcmd_send_to_ppg(struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_psys_fh *fh = kcmd->fh; ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *kppg; ++ struct ipu_psys_resource_pool *rpr; ++ unsigned long flags; ++ u8 id; ++ bool resche = true; ++ ++ rpr = &psys->resource_pool_running; ++ if (kcmd->state == KCMD_STATE_PPG_START) ++ return ipu_psys_kcmd_send_to_ppg_start(kcmd); ++ ++ kppg = ipu_psys_identify_kppg(kcmd); ++ spin_lock_irqsave(&psys->pgs_lock, flags); ++ kcmd->kpg->pg_size = 0; ++ spin_unlock_irqrestore(&psys->pgs_lock, flags); ++ if (!kppg) { ++ dev_err(dev, "token not match\n"); ++ return -EINVAL; ++ } ++ ++ kcmd->kpg = kppg->kpg; ++ ++ dev_dbg(dev, "%s ppg(%d, 0x%p) kcmd %p\n", ++ (kcmd->state == KCMD_STATE_PPG_STOP) ? ++ "STOP" : "ENQUEUE", ++ ipu_fw_psys_pg_get_id(kcmd), kppg, kcmd); ++ ++ if (kcmd->state == KCMD_STATE_PPG_STOP) { ++ mutex_lock(&kppg->mutex); ++ if (kppg->state == PPG_STATE_STOPPED) { ++ dev_dbg(dev, "kppg 0x%p stopped!\n", kppg); ++ id = ipu_fw_psys_ppg_get_base_queue_id(kcmd); ++ ipu_psys_free_cmd_queue_resource(rpr, id); ++ ipu_psys_kcmd_complete(kppg, kcmd, 0); ++ pm_runtime_put(dev); ++ resche = false; ++ } else { ++ list_add(&kcmd->list, &kppg->kcmds_new_list); ++ } ++ mutex_unlock(&kppg->mutex); ++ } else { ++ int ret; ++ ++ ret = ipu_psys_ppg_get_bufset(kcmd, kppg); ++ if (ret) ++ return ret; ++ ++ mutex_lock(&kppg->mutex); ++ list_add_tail(&kcmd->list, &kppg->kcmds_new_list); ++ mutex_unlock(&kppg->mutex); ++ } ++ ++ if (resche) { ++ /* Kick l-scheduler thread */ ++ atomic_set(&psys->wakeup_count, 1); ++ wake_up_interruptible(&psys->sched_cmd_wq); ++ } ++ return 0; ++} ++ ++int ipu_psys_kcmd_new(struct ipu_psys_command *cmd, struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd; ++ size_t pg_size; ++ int ret; ++ ++ kcmd = ipu_psys_copy_cmd(cmd, fh); ++ if (!kcmd) ++ return -EINVAL; ++ ++ pg_size = ipu_fw_psys_pg_get_size(kcmd); ++ if (pg_size > kcmd->kpg->pg_size) { ++ dev_dbg(dev, "pg size mismatch %lu %lu\n", pg_size, ++ kcmd->kpg->pg_size); ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ if (ipu_fw_psys_pg_get_protocol(kcmd) != ++ IPU_FW_PSYS_PROCESS_GROUP_PROTOCOL_PPG) { ++ dev_err(dev, "No support legacy pg now\n"); ++ ret = -EINVAL; ++ goto error; ++ } ++ ++ if (cmd->min_psys_freq) { ++ kcmd->constraint.min_freq = cmd->min_psys_freq; ++ ipu_buttress_add_psys_constraint(psys->adev->isp, ++ &kcmd->constraint); ++ } ++ ++ ret = ipu_psys_kcmd_send_to_ppg(kcmd); ++ if (ret) ++ goto error; ++ ++ dev_dbg(dev, "IOC_QCMD: user_token:%llx issue_id:0x%llx pri:%d\n", ++ cmd->user_token, cmd->issue_id, cmd->priority); ++ ++ return 0; ++ ++error: ++ ipu_psys_kcmd_free(kcmd); ++ ++ return ret; ++} ++ ++static bool ipu_psys_kcmd_is_valid(struct ipu_psys *psys, ++ struct ipu_psys_kcmd *kcmd) ++{ ++ struct ipu_psys_fh *fh; ++ struct ipu_psys_kcmd *kcmd0; ++ struct ipu_psys_ppg *kppg, *tmp; ++ struct ipu_psys_scheduler *sched; ++ ++ list_for_each_entry(fh, &psys->fhs, list) { ++ sched = &fh->sched; ++ mutex_lock(&fh->mutex); ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ continue; ++ } ++ list_for_each_entry_safe(kppg, tmp, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ list_for_each_entry(kcmd0, ++ &kppg->kcmds_processing_list, ++ list) { ++ if (kcmd0 == kcmd) { ++ mutex_unlock(&kppg->mutex); ++ mutex_unlock(&fh->mutex); ++ return true; ++ } ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ mutex_unlock(&fh->mutex); ++ } ++ ++ return false; ++} ++ ++void ipu_psys_handle_events(struct ipu_psys *psys) ++{ ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd; ++ struct ipu_fw_psys_event event; ++ struct ipu_psys_ppg *kppg; ++ bool error; ++ u32 hdl; ++ u16 cmd, status; ++ int res; ++ ++ do { ++ memset(&event, 0, sizeof(event)); ++ if (!ipu_fw_psys_rcv_event(psys, &event)) ++ break; ++ ++ if (!event.context_handle) ++ break; ++ ++ dev_dbg(dev, "ppg event: 0x%x, %d, status %d\n", ++ event.context_handle, event.command, event.status); ++ ++ error = false; ++ /* ++ * event.command == CMD_RUN shows this is fw processing frame ++ * done as pPG mode, and event.context_handle should be pointer ++ * of buffer set; so we make use of this pointer to lookup ++ * kbuffer_set and kcmd ++ */ ++ hdl = event.context_handle; ++ cmd = event.command; ++ status = event.status; ++ ++ kppg = NULL; ++ kcmd = NULL; ++ if (cmd == IPU_FW_PSYS_PROCESS_GROUP_CMD_RUN) { ++ struct ipu_psys_buffer_set *kbuf_set; ++ /* ++ * Need change ppg state when the 1st running is done ++ * (after PPG started/resumed) ++ */ ++ kbuf_set = ipu_psys_lookup_kbuffer_set(psys, hdl); ++ if (kbuf_set) ++ kcmd = kbuf_set->kcmd; ++ if (!kbuf_set || !kcmd) ++ error = true; ++ else ++ kppg = ipu_psys_identify_kppg(kcmd); ++ } else if (cmd == IPU_FW_PSYS_PROCESS_GROUP_CMD_STOP || ++ cmd == IPU_FW_PSYS_PROCESS_GROUP_CMD_SUSPEND || ++ cmd == IPU_FW_PSYS_PROCESS_GROUP_CMD_RESUME) { ++ /* ++ * STOP/SUSPEND/RESUME cmd event would run this branch; ++ * only stop cmd queued by user has stop_kcmd and need ++ * to notify user to dequeue. ++ */ ++ kppg = ipu_psys_lookup_ppg(psys, hdl); ++ if (kppg) { ++ mutex_lock(&kppg->mutex); ++ if (kppg->state == PPG_STATE_STOPPING) { ++ kcmd = ipu_psys_ppg_get_stop_kcmd(kppg); ++ if (!kcmd) ++ error = true; ++ } ++ mutex_unlock(&kppg->mutex); ++ } ++ } else { ++ dev_err(dev, "invalid event\n"); ++ continue; ++ } ++ ++ if (error || !kppg) { ++ dev_err(dev, "event error, command %d\n", cmd); ++ break; ++ } ++ ++ dev_dbg(dev, "event to kppg 0x%p, kcmd 0x%p\n", kppg, kcmd); ++ ++ ipu_psys_ppg_complete(psys, kppg); ++ ++ if (kcmd && ipu_psys_kcmd_is_valid(psys, kcmd)) { ++ res = (status == IPU_PSYS_EVENT_CMD_COMPLETE || ++ status == IPU_PSYS_EVENT_FRAGMENT_COMPLETE) ? ++ 0 : -EIO; ++ mutex_lock(&kppg->mutex); ++ ipu_psys_kcmd_complete(kppg, kcmd, res); ++ mutex_unlock(&kppg->mutex); ++ } ++ } while (1); ++} ++ ++int ipu_psys_fh_init(struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_buffer_set *kbuf_set, *kbuf_set_tmp; ++ struct ipu_psys_scheduler *sched = &fh->sched; ++ int i; ++ ++ mutex_init(&sched->bs_mutex); ++ INIT_LIST_HEAD(&sched->buf_sets); ++ INIT_LIST_HEAD(&sched->ppgs); ++ ++ /* allocate and map memory for buf_sets */ ++ for (i = 0; i < IPU_PSYS_BUF_SET_POOL_SIZE; i++) { ++ kbuf_set = kzalloc(sizeof(*kbuf_set), GFP_KERNEL); ++ if (!kbuf_set) ++ goto out_free_buf_sets; ++ kbuf_set->kaddr = dma_alloc_attrs(dev, ++ IPU_PSYS_BUF_SET_MAX_SIZE, ++ &kbuf_set->dma_addr, ++ GFP_KERNEL, 0); ++ if (!kbuf_set->kaddr) { ++ kfree(kbuf_set); ++ goto out_free_buf_sets; ++ } ++ kbuf_set->size = IPU_PSYS_BUF_SET_MAX_SIZE; ++ list_add(&kbuf_set->list, &sched->buf_sets); ++ } ++ ++ return 0; ++ ++out_free_buf_sets: ++ list_for_each_entry_safe(kbuf_set, kbuf_set_tmp, ++ &sched->buf_sets, list) { ++ dma_free_attrs(dev, kbuf_set->size, kbuf_set->kaddr, ++ kbuf_set->dma_addr, 0); ++ list_del(&kbuf_set->list); ++ kfree(kbuf_set); ++ } ++ mutex_destroy(&sched->bs_mutex); ++ ++ return -ENOMEM; ++} ++ ++int ipu_psys_fh_deinit(struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_ppg *kppg, *kppg0; ++ struct ipu_psys_kcmd *kcmd, *kcmd0; ++ struct ipu_psys_buffer_set *kbuf_set, *kbuf_set0; ++ struct ipu_psys_scheduler *sched = &fh->sched; ++ struct ipu_psys_resource_pool *rpr; ++ struct ipu_psys_resource_alloc *alloc; ++ u8 id; ++ ++ mutex_lock(&fh->mutex); ++ if (!list_empty(&sched->ppgs)) { ++ list_for_each_entry_safe(kppg, kppg0, &sched->ppgs, list) { ++ unsigned long flags; ++ ++ mutex_lock(&kppg->mutex); ++ if (!(kppg->state & ++ (PPG_STATE_STOPPED | ++ PPG_STATE_STOPPING))) { ++ struct ipu_psys_kcmd tmp = { ++ .kpg = kppg->kpg, ++ }; ++ ++ rpr = &psys->resource_pool_running; ++ alloc = &kppg->kpg->resource_alloc; ++ id = ipu_fw_psys_ppg_get_base_queue_id(&tmp); ++ ipu_psys_ppg_stop(kppg); ++ ipu_psys_free_resources(alloc, rpr); ++ ipu_psys_free_cmd_queue_resource(rpr, id); ++ dev_dbg(dev, ++ "s_change:%s %p %d -> %d\n", __func__, ++ kppg, kppg->state, PPG_STATE_STOPPED); ++ kppg->state = PPG_STATE_STOPPED; ++ if (psys->power_gating != PSYS_POWER_GATED) ++ pm_runtime_put(dev); ++ } ++ list_del(&kppg->list); ++ mutex_unlock(&kppg->mutex); ++ ++ list_for_each_entry_safe(kcmd, kcmd0, ++ &kppg->kcmds_new_list, list) { ++ kcmd->pg_user = NULL; ++ mutex_unlock(&fh->mutex); ++ ipu_psys_kcmd_free(kcmd); ++ mutex_lock(&fh->mutex); ++ } ++ ++ list_for_each_entry_safe(kcmd, kcmd0, ++ &kppg->kcmds_processing_list, ++ list) { ++ kcmd->pg_user = NULL; ++ mutex_unlock(&fh->mutex); ++ ipu_psys_kcmd_free(kcmd); ++ mutex_lock(&fh->mutex); ++ } ++ ++ list_for_each_entry_safe(kcmd, kcmd0, ++ &kppg->kcmds_finished_list, ++ list) { ++ kcmd->pg_user = NULL; ++ mutex_unlock(&fh->mutex); ++ ipu_psys_kcmd_free(kcmd); ++ mutex_lock(&fh->mutex); ++ } ++ ++ spin_lock_irqsave(&psys->pgs_lock, flags); ++ kppg->kpg->pg_size = 0; ++ spin_unlock_irqrestore(&psys->pgs_lock, flags); ++ ++ mutex_destroy(&kppg->mutex); ++ kfree(kppg->manifest); ++ kfree(kppg); ++ } ++ } ++ mutex_unlock(&fh->mutex); ++ ++ mutex_lock(&sched->bs_mutex); ++ list_for_each_entry_safe(kbuf_set, kbuf_set0, &sched->buf_sets, list) { ++ dma_free_attrs(dev, kbuf_set->size, kbuf_set->kaddr, ++ kbuf_set->dma_addr, 0); ++ list_del(&kbuf_set->list); ++ kfree(kbuf_set); ++ } ++ mutex_unlock(&sched->bs_mutex); ++ mutex_destroy(&sched->bs_mutex); ++ ++ return 0; ++} ++ ++struct ipu_psys_kcmd *ipu_get_completed_kcmd(struct ipu_psys_fh *fh) ++{ ++ struct ipu_psys_scheduler *sched = &fh->sched; ++ struct device *dev = &fh->psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd; ++ struct ipu_psys_ppg *kppg; ++ ++ mutex_lock(&fh->mutex); ++ if (list_empty(&sched->ppgs)) { ++ mutex_unlock(&fh->mutex); ++ return NULL; ++ } ++ ++ list_for_each_entry(kppg, &sched->ppgs, list) { ++ mutex_lock(&kppg->mutex); ++ if (list_empty(&kppg->kcmds_finished_list)) { ++ mutex_unlock(&kppg->mutex); ++ continue; ++ } ++ ++ kcmd = list_first_entry(&kppg->kcmds_finished_list, ++ struct ipu_psys_kcmd, list); ++ mutex_unlock(&fh->mutex); ++ mutex_unlock(&kppg->mutex); ++ dev_dbg(dev, "get completed kcmd 0x%p\n", kcmd); ++ return kcmd; ++ } ++ mutex_unlock(&fh->mutex); ++ ++ return NULL; ++} ++ ++long ipu_ioctl_dqevent(struct ipu_psys_event *event, ++ struct ipu_psys_fh *fh, unsigned int f_flags) ++{ ++ struct ipu_psys *psys = fh->psys; ++ struct device *dev = &psys->adev->auxdev.dev; ++ struct ipu_psys_kcmd *kcmd = NULL; ++ int rval; ++ ++ dev_dbg(dev, "IOC_DQEVENT\n"); ++ ++ if (!(f_flags & O_NONBLOCK)) { ++ rval = wait_event_interruptible(fh->wait, ++ (kcmd = ++ ipu_get_completed_kcmd(fh))); ++ if (rval == -ERESTARTSYS) ++ return rval; ++ } ++ ++ if (!kcmd) { ++ kcmd = ipu_get_completed_kcmd(fh); ++ if (!kcmd) ++ return -ENODATA; ++ } ++ ++ *event = kcmd->ev; ++ ipu_psys_kcmd_free(kcmd); ++ ++ return 0; ++} +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6ep-fw-resources.c b/drivers/media/pci/intel/ipu6/psys/ipu6ep-fw-resources.c +new file mode 100644 +index 000000000000..4a1a86989d2e +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6ep-fw-resources.c +@@ -0,0 +1,393 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2020 - 2024 Intel Corporation ++ ++#include ++#include ++ ++#include "ipu-psys.h" ++#include "ipu-fw-psys.h" ++#include "ipu6-platform-resources.h" ++#include "ipu6ep-platform-resources.h" ++ ++/* resources table */ ++ ++/* ++ * Cell types by cell IDs ++ */ ++static const u8 ipu6ep_fw_psys_cell_types[IPU6EP_FW_PSYS_N_CELL_ID] = { ++ IPU6_FW_PSYS_SP_CTRL_TYPE_ID, ++ IPU6_FW_PSYS_VP_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_OSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_PSA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* AF */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* X2B_MD */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* X2B_SVE_RGBIR */ ++ IPU6_FW_PSYS_ACC_ISA_TYPE_ID, /* PAF */ ++ IPU6_FW_PSYS_GDC_TYPE_ID, ++ IPU6_FW_PSYS_TNR_TYPE_ID, ++}; ++ ++static const u16 ipu6ep_fw_num_dev_channels[IPU6_FW_PSYS_N_DEV_CHN_ID] = { ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT0_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_READ_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_INTERNAL_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_CHN_DMA_ISA_MAX_SIZE, ++}; ++ ++static const u16 ipu6ep_fw_psys_mem_size[IPU6_FW_PSYS_N_MEM_ID] = { ++ IPU6_FW_PSYS_VMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_TRANSFER_VMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_MAX_SIZE, ++ IPU6_FW_PSYS_LB_VMEM_MAX_SIZE, ++ IPU6_FW_PSYS_BAMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM0_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM1_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM2_MAX_SIZE, ++ IPU6_FW_PSYS_DMEM3_MAX_SIZE, ++ IPU6_FW_PSYS_PMEM0_MAX_SIZE ++}; ++ ++static const u16 ipu6ep_fw_psys_dfms[IPU6_FW_PSYS_N_DEV_DFM_ID] = { ++ IPU6_FW_PSYS_DEV_DFM_BB_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_BB_EMPTY_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_LB_FULL_PORT_ID_MAX_SIZE, ++ IPU6_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID_MAX_SIZE, ++}; ++ ++static const u8 ++ipu6ep_fw_psys_c_mem[IPU6EP_FW_PSYS_N_CELL_ID][IPU6_FW_PSYS_N_MEM_TYPE_ID] = { ++ { ++ /* IPU6_FW_PSYS_SP0_ID */ ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_DMEM0_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_SP1_ID */ ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_DMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_VP0_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_DMEM3_ID, ++ IPU6_FW_PSYS_VMEM0_ID, ++ IPU6_FW_PSYS_BAMEM0_ID, ++ IPU6_FW_PSYS_PMEM0_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC1_ID BNLM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC2_ID DM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC3_ID ACM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC4_ID GTC YUV1 */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC5_ID OFS pin main */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC6_ID OFS pin display */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC7_ID OFS pin pp */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC8_ID GAMMASTAR */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC9_ID GLTM */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ACC10_ID XNR */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_ICA_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_LSC_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_DPC_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_SIS_A_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_SIS_B_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_B2B_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_B2R_ID and ISA_R2I_SIE */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_R2I_DS_A_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AWB_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AE_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_AF_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_X2B_MD_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_X2B_SVE_RGBIR_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_ISA_PAF_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_LB_VMEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_BB_ACC_GDC0_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ }, ++ { ++ /* IPU6_FW_PSYS_BB_ACC_TNR_ID */ ++ IPU6_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6_FW_PSYS_TRANSFER_VMEM1_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ IPU6_FW_PSYS_N_MEM_ID, ++ } ++}; ++ ++static const struct ipu_fw_resource_definitions ipu6ep_defs = { ++ .cells = ipu6ep_fw_psys_cell_types, ++ .num_cells = IPU6EP_FW_PSYS_N_CELL_ID, ++ .num_cells_type = IPU6_FW_PSYS_N_CELL_TYPE_ID, ++ ++ .dev_channels = ipu6ep_fw_num_dev_channels, ++ .num_dev_channels = IPU6_FW_PSYS_N_DEV_CHN_ID, ++ ++ .num_ext_mem_types = IPU6_FW_PSYS_N_DATA_MEM_TYPE_ID, ++ .num_ext_mem_ids = IPU6_FW_PSYS_N_MEM_ID, ++ .ext_mem_ids = ipu6ep_fw_psys_mem_size, ++ ++ .num_dfm_ids = IPU6_FW_PSYS_N_DEV_DFM_ID, ++ ++ .dfms = ipu6ep_fw_psys_dfms, ++ ++ .cell_mem_row = IPU6_FW_PSYS_N_MEM_TYPE_ID, ++ .cell_mem = &ipu6ep_fw_psys_c_mem[0][0], ++}; ++ ++const struct ipu_fw_resource_definitions *ipu6ep_res_defs = &ipu6ep_defs; +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6ep-platform-resources.h b/drivers/media/pci/intel/ipu6/psys/ipu6ep-platform-resources.h +new file mode 100644 +index 000000000000..842014973206 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6ep-platform-resources.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2020 - 2024 Intel Corporation */ ++ ++#ifndef IPU6EP_PLATFORM_RESOURCES_H ++#define IPU6EP_PLATFORM_RESOURCES_H ++ ++#include ++#include ++ ++enum { ++ IPU6EP_FW_PSYS_SP0_ID = 0, ++ IPU6EP_FW_PSYS_VP0_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_BNLM_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_DM_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_ACM_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_GTC_YUV1_ID, ++ IPU6EP_FW_PSYS_BB_ACC_OFS_PIN_MAIN_ID, ++ IPU6EP_FW_PSYS_BB_ACC_OFS_PIN_DISPLAY_ID, ++ IPU6EP_FW_PSYS_BB_ACC_OFS_PIN_PP_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_GAMMASTAR_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_GLTM_ID, ++ IPU6EP_FW_PSYS_PSA_ACC_XNR_ID, ++ IPU6EP_FW_PSYS_PSA_VCSC_ID, /* VCSC */ ++ IPU6EP_FW_PSYS_ISA_ICA_ID, ++ IPU6EP_FW_PSYS_ISA_LSC_ID, ++ IPU6EP_FW_PSYS_ISA_DPC_ID, ++ IPU6EP_FW_PSYS_ISA_SIS_A_ID, ++ IPU6EP_FW_PSYS_ISA_SIS_B_ID, ++ IPU6EP_FW_PSYS_ISA_B2B_ID, ++ IPU6EP_FW_PSYS_ISA_B2R_R2I_SIE_ID, ++ IPU6EP_FW_PSYS_ISA_R2I_DS_A_ID, ++ IPU6EP_FW_PSYS_ISA_AWB_ID, ++ IPU6EP_FW_PSYS_ISA_AE_ID, ++ IPU6EP_FW_PSYS_ISA_AF_ID, ++ IPU6EP_FW_PSYS_ISA_X2B_MD_ID, ++ IPU6EP_FW_PSYS_ISA_X2B_SVE_RGBIR_ID, ++ IPU6EP_FW_PSYS_ISA_PAF_ID, ++ IPU6EP_FW_PSYS_BB_ACC_GDC0_ID, ++ IPU6EP_FW_PSYS_BB_ACC_TNR_ID, ++ IPU6EP_FW_PSYS_N_CELL_ID ++}; ++#endif /* IPU6EP_PLATFORM_RESOURCES_H */ +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6se-fw-resources.c b/drivers/media/pci/intel/ipu6/psys/ipu6se-fw-resources.c +new file mode 100644 +index 000000000000..41b6ba65cf4b +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6se-fw-resources.c +@@ -0,0 +1,194 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (C) 2015 - 2024 Intel Corporation ++ ++#include ++#include ++ ++#include "ipu-psys.h" ++#include "ipu-fw-psys.h" ++#include "ipu6se-platform-resources.h" ++ ++/* resources table */ ++ ++/* ++ * Cell types by cell IDs ++ */ ++/* resources table */ ++ ++/* ++ * Cell types by cell IDs ++ */ ++static const u8 ipu6se_fw_psys_cell_types[IPU6SE_FW_PSYS_N_CELL_ID] = { ++ IPU6SE_FW_PSYS_SP_CTRL_TYPE_ID, ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_ICA_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_LSC_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_DPC_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_B2B_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_BNLM_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_DM_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_R2I_SIE_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_R2I_DS_A_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_R2I_DS_B_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_AWB_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_AE_ID */ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, /* IPU6SE_FW_PSYS_ISA_AF_ID*/ ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID /* PAF */ ++}; ++ ++static const u16 ipu6se_fw_num_dev_channels[IPU6SE_FW_PSYS_N_DEV_CHN_ID] = { ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT0_MAX_SIZE, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_READ_MAX_SIZE, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_MAX_SIZE, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_ISA_MAX_SIZE, ++}; ++ ++static const u16 ipu6se_fw_psys_mem_size[IPU6SE_FW_PSYS_N_MEM_ID] = { ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_MAX_SIZE, ++ IPU6SE_FW_PSYS_LB_VMEM_MAX_SIZE, ++ IPU6SE_FW_PSYS_DMEM0_MAX_SIZE, ++ IPU6SE_FW_PSYS_DMEM1_MAX_SIZE ++}; ++ ++static const u16 ipu6se_fw_psys_dfms[IPU6SE_FW_PSYS_N_DEV_DFM_ID] = { ++ IPU6SE_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID_MAX_SIZE, ++ IPU6SE_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID_MAX_SIZE ++}; ++ ++static const u8 ++ipu6se_fw_psys_c_mem[IPU6SE_FW_PSYS_N_CELL_ID][IPU6SE_FW_PSYS_N_MEM_TYPE_ID] = { ++ { /* IPU6SE_FW_PSYS_SP0_ID */ ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_DMEM0_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_ICA_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_LSC_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_DPC_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_B2B_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ ++ { /* IPU6SE_FW_PSYS_ISA_BNLM_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_DM_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_R2I_SIE_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_R2I_DS_A_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_R2I_DS_B_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_AWB_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_AE_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_AF_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ }, ++ { /* IPU6SE_FW_PSYS_ISA_PAF_ID */ ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID, ++ IPU6SE_FW_PSYS_LB_VMEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ IPU6SE_FW_PSYS_N_MEM_ID, ++ } ++}; ++ ++static const struct ipu_fw_resource_definitions ipu6se_defs = { ++ .cells = ipu6se_fw_psys_cell_types, ++ .num_cells = IPU6SE_FW_PSYS_N_CELL_ID, ++ .num_cells_type = IPU6SE_FW_PSYS_N_CELL_TYPE_ID, ++ ++ .dev_channels = ipu6se_fw_num_dev_channels, ++ .num_dev_channels = IPU6SE_FW_PSYS_N_DEV_CHN_ID, ++ ++ .num_ext_mem_types = IPU6SE_FW_PSYS_N_DATA_MEM_TYPE_ID, ++ .num_ext_mem_ids = IPU6SE_FW_PSYS_N_MEM_ID, ++ .ext_mem_ids = ipu6se_fw_psys_mem_size, ++ ++ .num_dfm_ids = IPU6SE_FW_PSYS_N_DEV_DFM_ID, ++ ++ .dfms = ipu6se_fw_psys_dfms, ++ ++ .cell_mem_row = IPU6SE_FW_PSYS_N_MEM_TYPE_ID, ++ .cell_mem = &ipu6se_fw_psys_c_mem[0][0], ++}; ++ ++const struct ipu_fw_resource_definitions *ipu6se_res_defs = &ipu6se_defs; +diff --git a/drivers/media/pci/intel/ipu6/psys/ipu6se-platform-resources.h b/drivers/media/pci/intel/ipu6/psys/ipu6se-platform-resources.h +new file mode 100644 +index 000000000000..81c618de0f9a +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/psys/ipu6se-platform-resources.h +@@ -0,0 +1,103 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2018 - 2024 Intel Corporation */ ++ ++#ifndef IPU6SE_PLATFORM_RESOURCES_H ++#define IPU6SE_PLATFORM_RESOURCES_H ++ ++#include ++#include ++#include "ipu-platform-resources.h" ++ ++#define IPU6SE_FW_PSYS_N_PADDING_UINT8_IN_PROCESS_EXT_STRUCT 1 ++ ++enum { ++ IPU6SE_FW_PSYS_CMD_QUEUE_COMMAND_ID = 0, ++ IPU6SE_FW_PSYS_CMD_QUEUE_DEVICE_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG1_COMMAND_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG2_COMMAND_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG3_COMMAND_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG4_COMMAND_ID, ++ IPU6SE_FW_PSYS_CMD_QUEUE_PPG5_COMMAND_ID, ++ IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID ++}; ++ ++enum { ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_TYPE_ID = 0, ++ IPU6SE_FW_PSYS_LB_VMEM_TYPE_ID, ++ IPU6SE_FW_PSYS_DMEM_TYPE_ID, ++ IPU6SE_FW_PSYS_VMEM_TYPE_ID, ++ IPU6SE_FW_PSYS_BAMEM_TYPE_ID, ++ IPU6SE_FW_PSYS_PMEM_TYPE_ID, ++ IPU6SE_FW_PSYS_N_MEM_TYPE_ID ++}; ++ ++enum ipu6se_mem_id { ++ IPU6SE_FW_PSYS_TRANSFER_VMEM0_ID = 0, /* TRANSFER VMEM 0 */ ++ IPU6SE_FW_PSYS_LB_VMEM_ID, /* LB VMEM */ ++ IPU6SE_FW_PSYS_DMEM0_ID, /* SPC0 Dmem */ ++ IPU6SE_FW_PSYS_DMEM1_ID, /* SPP0 Dmem */ ++ IPU6SE_FW_PSYS_N_MEM_ID ++}; ++ ++enum { ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT0_ID = 0, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_READ_ID, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_ID, ++ IPU6SE_FW_PSYS_DEV_CHN_DMA_ISA_ID, ++ IPU6SE_FW_PSYS_N_DEV_CHN_ID ++}; ++ ++enum { ++ IPU6SE_FW_PSYS_SP_CTRL_TYPE_ID = 0, ++ IPU6SE_FW_PSYS_SP_SERVER_TYPE_ID, ++ IPU6SE_FW_PSYS_ACC_ISA_TYPE_ID, ++ IPU6SE_FW_PSYS_N_CELL_TYPE_ID ++}; ++ ++enum { ++ IPU6SE_FW_PSYS_SP0_ID = 0, ++ IPU6SE_FW_PSYS_ISA_ICA_ID, ++ IPU6SE_FW_PSYS_ISA_LSC_ID, ++ IPU6SE_FW_PSYS_ISA_DPC_ID, ++ IPU6SE_FW_PSYS_ISA_B2B_ID, ++ IPU6SE_FW_PSYS_ISA_BNLM_ID, ++ IPU6SE_FW_PSYS_ISA_DM_ID, ++ IPU6SE_FW_PSYS_ISA_R2I_SIE_ID, ++ IPU6SE_FW_PSYS_ISA_R2I_DS_A_ID, ++ IPU6SE_FW_PSYS_ISA_R2I_DS_B_ID, ++ IPU6SE_FW_PSYS_ISA_AWB_ID, ++ IPU6SE_FW_PSYS_ISA_AE_ID, ++ IPU6SE_FW_PSYS_ISA_AF_ID, ++ IPU6SE_FW_PSYS_ISA_PAF_ID, ++ IPU6SE_FW_PSYS_N_CELL_ID ++}; ++ ++enum { ++ IPU6SE_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID = 0, ++ IPU6SE_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID, ++}; ++ ++/* Excluding PMEM */ ++#define IPU6SE_FW_PSYS_N_DATA_MEM_TYPE_ID (IPU6SE_FW_PSYS_N_MEM_TYPE_ID - 1) ++#define IPU6SE_FW_PSYS_N_DEV_DFM_ID \ ++ (IPU6SE_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID + 1) ++#define IPU6SE_FW_PSYS_VMEM0_MAX_SIZE 0x0800 ++/* Transfer VMEM0 words, ref HAS Transfer*/ ++#define IPU6SE_FW_PSYS_TRANSFER_VMEM0_MAX_SIZE 0x0800 ++#define IPU6SE_FW_PSYS_LB_VMEM_MAX_SIZE 0x0400 /* LB VMEM words */ ++#define IPU6SE_FW_PSYS_DMEM0_MAX_SIZE 0x4000 ++#define IPU6SE_FW_PSYS_DMEM1_MAX_SIZE 0x1000 ++ ++#define IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT0_MAX_SIZE 22 ++#define IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_READ_MAX_SIZE 22 ++#define IPU6SE_FW_PSYS_DEV_CHN_DMA_EXT1_WRITE_MAX_SIZE 22 ++#define IPU6SE_FW_PSYS_DEV_CHN_DMA_IPFD_MAX_SIZE 0 ++#define IPU6SE_FW_PSYS_DEV_CHN_DMA_ISA_MAX_SIZE 2 ++ ++#define IPU6SE_FW_PSYS_DEV_DFM_ISL_FULL_PORT_ID_MAX_SIZE 32 ++#define IPU6SE_FW_PSYS_DEV_DFM_LB_FULL_PORT_ID_MAX_SIZE 32 ++#define IPU6SE_FW_PSYS_DEV_DFM_ISL_EMPTY_PORT_ID_MAX_SIZE 32 ++#define IPU6SE_FW_PSYS_DEV_DFM_LB_EMPTY_PORT_ID_MAX_SIZE 32 ++ ++#endif /* IPU6SE_PLATFORM_RESOURCES_H */ +diff --git a/include/uapi/linux/ipu-psys.h b/include/uapi/linux/ipu-psys.h +new file mode 100644 +index 000000000000..30538e706062 +--- /dev/null ++++ b/include/uapi/linux/ipu-psys.h +@@ -0,0 +1,121 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* Copyright (C) 2013 - 2020 Intel Corporation */ ++ ++#ifndef _UAPI_IPU_PSYS_H ++#define _UAPI_IPU_PSYS_H ++ ++#ifdef __KERNEL__ ++#include ++#else ++#include ++#endif ++ ++struct ipu_psys_capability { ++ uint32_t version; ++ uint8_t driver[20]; ++ uint32_t pg_count; ++ uint8_t dev_model[32]; ++ uint32_t reserved[17]; ++} __attribute__ ((packed)); ++ ++struct ipu_psys_event { ++ uint32_t type; /* IPU_PSYS_EVENT_TYPE_ */ ++ uint64_t user_token; ++ uint64_t issue_id; ++ uint32_t buffer_idx; ++ uint32_t error; ++ int32_t reserved[2]; ++} __attribute__ ((packed)); ++ ++#define IPU_PSYS_EVENT_TYPE_CMD_COMPLETE 1 ++#define IPU_PSYS_EVENT_TYPE_BUFFER_COMPLETE 2 ++ ++/** ++ * struct ipu_psys_buffer - for input/output terminals ++ * @len: total allocated size @ base address ++ * @userptr: user pointer ++ * @fd: DMA-BUF handle ++ * @data_offset:offset to valid data ++ * @bytes_used: amount of valid data including offset ++ * @flags: flags ++ */ ++struct ipu_psys_buffer { ++ uint64_t len; ++ union { ++ int fd; ++ void __user *userptr; ++ uint64_t reserved; ++ } base; ++ uint32_t data_offset; ++ uint32_t bytes_used; ++ uint32_t flags; ++ uint32_t reserved[2]; ++} __attribute__ ((packed)); ++ ++#define IPU_BUFFER_FLAG_INPUT (1 << 0) ++#define IPU_BUFFER_FLAG_OUTPUT (1 << 1) ++#define IPU_BUFFER_FLAG_MAPPED (1 << 2) ++#define IPU_BUFFER_FLAG_NO_FLUSH (1 << 3) ++#define IPU_BUFFER_FLAG_DMA_HANDLE (1 << 4) ++#define IPU_BUFFER_FLAG_USERPTR (1 << 5) ++ ++#define IPU_PSYS_CMD_PRIORITY_HIGH 0 ++#define IPU_PSYS_CMD_PRIORITY_MED 1 ++#define IPU_PSYS_CMD_PRIORITY_LOW 2 ++#define IPU_PSYS_CMD_PRIORITY_NUM 3 ++ ++/** ++ * struct ipu_psys_command - processing command ++ * @issue_id: unique id for the command set by user ++ * @user_token: token of the command ++ * @priority: priority of the command ++ * @pg_manifest: userspace pointer to program group manifest ++ * @buffers: userspace pointers to array of psys dma buf structs ++ * @pg: process group DMA-BUF handle ++ * @pg_manifest_size: size of program group manifest ++ * @bufcount: number of buffers in buffers array ++ * @min_psys_freq: minimum psys frequency in MHz used for this cmd ++ * @frame_counter: counter of current frame synced between isys and psys ++ * @kernel_enable_bitmap: enable bits for each individual kernel ++ * @terminal_enable_bitmap: enable bits for each individual terminals ++ * @routing_enable_bitmap: enable bits for each individual routing ++ * @rbm: enable bits for routing ++ * ++ * Specifies a processing command with input and output buffers. ++ */ ++struct ipu_psys_command { ++ uint64_t issue_id; ++ uint64_t user_token; ++ uint32_t priority; ++ void __user *pg_manifest; ++ struct ipu_psys_buffer __user *buffers; ++ int pg; ++ uint32_t pg_manifest_size; ++ uint32_t bufcount; ++ uint32_t min_psys_freq; ++ uint32_t frame_counter; ++ uint32_t kernel_enable_bitmap[4]; ++ uint32_t terminal_enable_bitmap[4]; ++ uint32_t routing_enable_bitmap[4]; ++ uint32_t rbm[5]; ++ uint32_t reserved[2]; ++} __attribute__ ((packed)); ++ ++struct ipu_psys_manifest { ++ uint32_t index; ++ uint32_t size; ++ void __user *manifest; ++ uint32_t reserved[5]; ++} __attribute__ ((packed)); ++ ++#define IPU_IOC_QUERYCAP _IOR('A', 1, struct ipu_psys_capability) ++#define IPU_IOC_MAPBUF _IOWR('A', 2, int) ++#define IPU_IOC_UNMAPBUF _IOWR('A', 3, int) ++#define IPU_IOC_GETBUF _IOWR('A', 4, struct ipu_psys_buffer) ++#define IPU_IOC_PUTBUF _IOWR('A', 5, struct ipu_psys_buffer) ++#define IPU_IOC_QCMD _IOWR('A', 6, struct ipu_psys_command) ++#define IPU_IOC_DQEVENT _IOWR('A', 7, struct ipu_psys_event) ++#define IPU_IOC_CMD_CANCEL _IOWR('A', 8, struct ipu_psys_command) ++#define IPU_IOC_GET_MANIFEST _IOWR('A', 9, struct ipu_psys_manifest) ++ ++#endif /* _UAPI_IPU_PSYS_H */ +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0002-media-i2c-add-support-for-lt6911uxe.patch b/kernel_patches/patch_6.12_mainline/0002-media-i2c-add-support-for-lt6911uxe.patch new file mode 100644 index 000000000000..132e2cfbf952 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0002-media-i2c-add-support-for-lt6911uxe.patch @@ -0,0 +1,707 @@ +From 854165d0939f2f977d5548b2ccc77f0b8887479d Mon Sep 17 00:00:00 2001 +From: Dongcheng Yan +Date: Mon, 3 Jun 2024 13:49:38 +0800 +Subject: [PATCH] media: i2c: add support for lt6911uxe + +Signed-off-by: Dongcheng Yan +Signed-off-by: zouxiaoh +--- + drivers/media/i2c/lt6911uxe.c | 672 +++++++++++++++++++++++++++ + drivers/media/pci/intel/ipu-bridge.c | 2 + + 2 files changed, 674 insertions(+) + create mode 100644 drivers/media/i2c/lt6911uxe.c + +diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c +new file mode 100644 +index 000000000000..660a558478de +--- /dev/null ++++ b/drivers/media/i2c/lt6911uxe.c +@@ -0,0 +1,672 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2023 - 2024 Intel Corporation. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define LT6911UXE_CHIP_ID 0x2102 ++#define REG_CHIP_ID_H CCI_REG8(0xe100) ++#define REG_CHIP_ID_L CCI_REG8(0xe101) ++ ++#define REG_ENABLE_I2C CCI_REG8(0xe0ee) ++#define REG_PIX_CLK CCI_REG24(0xe085) ++#define REG_BYTE_CLK CCI_REG24(0xe092) ++#define REG_H_TOTAL CCI_REG16(0xe088) ++#define REG_V_TOTAL CCI_REG16(0xe08a) ++#define REG_HALF_H_ACTIVE CCI_REG16(0xe08c) ++#define REG_V_ACTIVE CCI_REG16(0xe08e) ++#define REG_MIPI_TX_CTRL CCI_REG8(0xe0b0) ++#define REG_MIPI_LANES CCI_REG8(0xe095) ++ ++/* Interrupts */ ++#define REG_INT_HDMI CCI_REG8(0xe084) ++#define INT_HDMI_DISCONNECT 0x0 ++#define INT_HDMI_STABLE 0x1 ++#define REG_INT_AUDIO CCI_REG8(0x86a5) ++#define REG_AUDIO_FS_H CCI_REG8(0xe090) ++#define REG_AUDIO_FS_L CCI_REG8(0xe091) ++#define AUDIO_SR_HIGH 0x55 ++#define AUDIO_SR_LOW 0xaa ++#define INT_AUDIO_DISCONNECT 0x3 ++ ++#define LT6911UXE_DEFAULT_WIDTH 3840 ++#define LT6911UXE_DEFAULT_HEIGHT 2160 ++#define LT9611_PAGE_CONTROL 0xff ++ ++#define LT6911UXE_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_BASE | 0x1001) ++#define LT6911UXE_CID_AUDIO_PRESENT (V4L2_CID_USER_BASE | 0x1002) ++ ++static const struct regmap_range_cfg lt9611uxe_ranges[] = { ++ { ++ .name = "register_range", ++ .range_min = 0, ++ .range_max = 0xffff, ++ .selector_reg = LT9611_PAGE_CONTROL, ++ .selector_mask = 0xff, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct regmap_config lt9611uxe_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0xffff, ++ .ranges = lt9611uxe_ranges, ++ .num_ranges = ARRAY_SIZE(lt9611uxe_ranges), ++}; ++ ++struct lt6911uxe_mode { ++ u32 width; ++ u32 height; ++ u32 code; ++ s32 lanes; ++ u32 fps; ++ s64 link_freq; ++ /* Audio sample rate*/ ++ u32 sample_rate; ++}; ++ ++struct lt6911uxe { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *sampling_rate; ++ struct v4l2_ctrl *audio_present; ++ ++ struct lt6911uxe_mode *cur_mode; ++ struct regmap *regmap; ++ unsigned int irq_pin_flags; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *irq_gpio; ++ struct gpio_desc *detect_gpio; ++}; ++ ++static const struct v4l2_event lt6911uxe_ev_source_change = { ++ .type = V4L2_EVENT_SOURCE_CHANGE, ++ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, ++}; ++ ++static const struct v4l2_event lt6911uxe_ev_stream_end = { ++ .type = V4L2_EVENT_EOS, ++}; ++ ++static inline struct lt6911uxe *to_lt6911uxe(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct lt6911uxe, sd); ++} ++ ++static const struct v4l2_ctrl_config lt6911uxe_audio_sampling_rate = { ++ .id = LT6911UXE_CID_AUDIO_SAMPLING_RATE, ++ .name = "Audio Sampling Rate", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 32000, ++ .max = 192000, ++ .step = 100, ++ .def = 48000, ++ .flags = V4L2_CTRL_FLAG_READ_ONLY, ++}; ++ ++static const struct v4l2_ctrl_config lt6911uxe_audio_present = { ++ .id = LT6911UXE_CID_AUDIO_PRESENT, ++ .name = "Audio Present", ++ .type = V4L2_CTRL_TYPE_BOOLEAN, ++ .min = 0, ++ .max = 1, ++ .step = 1, ++ .def = 0, ++ .flags = V4L2_CTRL_FLAG_READ_ONLY, ++}; ++ ++static u64 get_pixel_rate(struct lt6911uxe *lt6911uxe) ++{ ++ u64 pixel_rate; ++ ++ if (!lt6911uxe->cur_mode->width) ++ return 0; ++ ++ pixel_rate = (u64)lt6911uxe->cur_mode->width * ++ lt6911uxe->cur_mode->height * ++ lt6911uxe->cur_mode->fps * 16; ++ do_div(pixel_rate, lt6911uxe->cur_mode->lanes); ++ ++ return pixel_rate; ++} ++ ++static int lt6911uxe_get_audio_sampling_rate(struct lt6911uxe *lt6911uxe) ++{ ++ int audio_fs, idx; ++ u64 fs_h, fs_l; ++ static const int eps = 1500; ++ static const int rates_default[] = { ++ 32000, 44100, 48000, 88200, 96000, 176400, 192000 ++ }; ++ ++ cci_read(lt6911uxe->regmap, REG_AUDIO_FS_H, &fs_h, NULL); ++ cci_read(lt6911uxe->regmap, REG_AUDIO_FS_H, &fs_l, NULL); ++ audio_fs = ((fs_h << 8) | fs_l); ++ ++ /* audio_fs is an approximation of sample rate - search nearest */ ++ for (idx = 0; idx < ARRAY_SIZE(rates_default); ++idx) { ++ if ((rates_default[idx] - eps < audio_fs) && ++ (rates_default[idx] + eps > audio_fs)) ++ return rates_default[idx]; ++ } ++ ++ return 0; ++} ++ ++static int lt6911uxe_video_status_update(struct lt6911uxe *lt6911uxe) ++{ ++ u64 int_event; ++ u64 byte_clock, pixel_clk, fps, lanes; ++ u64 htotal, vtotal, half_width, height; ++ ++ /* Read interrupt event */ ++ cci_read(lt6911uxe->regmap, REG_INT_HDMI, &int_event, NULL); ++ switch (int_event) { ++ case INT_HDMI_STABLE: ++ cci_read(lt6911uxe->regmap, REG_BYTE_CLK, &byte_clock, NULL); ++ byte_clock *= 1000; ++ cci_read(lt6911uxe->regmap, REG_PIX_CLK, &pixel_clk, NULL); ++ pixel_clk *= 1000; ++ ++ cci_read(lt6911uxe->regmap, REG_H_TOTAL, &htotal, NULL); ++ cci_read(lt6911uxe->regmap, REG_V_TOTAL, &vtotal, NULL); ++ cci_read(lt6911uxe->regmap, REG_HALF_H_ACTIVE, ++ &half_width, NULL); ++ cci_read(lt6911uxe->regmap, REG_V_ACTIVE, &height, NULL); ++ cci_read(lt6911uxe->regmap, REG_MIPI_LANES, &lanes, NULL); ++ ++ if (htotal && vtotal) ++ fps = div_u64(pixel_clk, htotal * vtotal); ++ ++ lt6911uxe->cur_mode->height = height; ++ lt6911uxe->cur_mode->width = half_width * 2; ++ lt6911uxe->cur_mode->fps = fps; ++ lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; ++ lt6911uxe->cur_mode->lanes = lanes; ++ /* Mipi Clock Rate = ByteClock × 4, defined in lt6911uxe spec */ ++ lt6911uxe->cur_mode->link_freq = byte_clock * 4; ++ v4l2_subdev_notify_event(<6911uxe->sd, ++ <6911uxe_ev_source_change); ++ break; ++ case INT_HDMI_DISCONNECT: ++ cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ lt6911uxe->cur_mode->height = 0; ++ lt6911uxe->cur_mode->width = 0; ++ lt6911uxe->cur_mode->fps = fps; ++ lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; ++ lt6911uxe->cur_mode->lanes = 4; ++ lt6911uxe->cur_mode->link_freq = 0; ++ v4l2_subdev_notify_event(<6911uxe->sd, ++ <6911uxe_ev_stream_end); ++ break; ++ default: ++ return -ENOLINK; ++ } ++ ++ return 0; ++} ++ ++static void lt6911uxe_audio_status_update(struct lt6911uxe *lt6911uxe) ++{ ++ u64 int_event; ++ int audio_fs; ++ ++ /* Read interrupt event */ ++ cci_read(lt6911uxe->regmap, REG_INT_AUDIO, &int_event, NULL); ++ switch (int_event) { ++ case AUDIO_SR_HIGH: ++ case AUDIO_SR_LOW: ++ audio_fs = lt6911uxe_get_audio_sampling_rate(lt6911uxe); ++ lt6911uxe->cur_mode->sample_rate = audio_fs; ++ break; ++ ++ case INT_AUDIO_DISCONNECT: ++ default: ++ /* use the default value for avoiding problem */ ++ lt6911uxe->cur_mode->sample_rate = 0; ++ break; ++ } ++ ++} ++ ++static int lt6911uxe_init_controls(struct lt6911uxe *lt6911uxe) ++{ ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ int ret; ++ ++ ctrl_hdlr = <6911uxe->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); ++ if (ret) ++ return ret; ++ ++ lt6911uxe->link_freq = ++ v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, ++ sizeof(lt6911uxe->cur_mode->link_freq), ++ 0, <6911uxe->cur_mode->link_freq); ++ ++ lt6911uxe->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, ++ V4L2_CID_PIXEL_RATE, ++ get_pixel_rate(lt6911uxe), ++ get_pixel_rate(lt6911uxe), 1, ++ get_pixel_rate(lt6911uxe)); ++ ++ /* custom v4l2 audio controls */ ++ lt6911uxe->sampling_rate = ++ v4l2_ctrl_new_custom(ctrl_hdlr, ++ <6911uxe_audio_sampling_rate, ++ NULL); ++ ++ lt6911uxe->audio_present = ++ v4l2_ctrl_new_custom(ctrl_hdlr, ++ <6911uxe_audio_present, ++ NULL); ++ ++ if (ctrl_hdlr->error) { ++ ret = ctrl_hdlr->error; ++ goto hdlr_free; ++ } ++ lt6911uxe->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++hdlr_free: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ return ret; ++} ++ ++static void lt6911uxe_update_pad_format(const struct lt6911uxe_mode *mode, ++ struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->width = mode->width; ++ fmt->height = mode->height; ++ fmt->code = mode->code; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int lt6911uxe_start_streaming(struct lt6911uxe *lt6911uxe) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(&client->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = __v4l2_ctrl_handler_setup(lt6911uxe->sd.ctrl_handler); ++ if (ret) ++ goto err_rpm_put; ++ ++ cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x1, &ret); ++ if (ret) { ++ dev_err(&client->dev, "failed to start stream\n"); ++ goto err_rpm_put; ++ } ++ ++ return 0; ++ ++err_rpm_put: ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static int lt6911uxe_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ struct v4l2_subdev_state *state; ++ int ret = 0; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (enable) ++ ret = lt6911uxe_start_streaming(lt6911uxe); ++ else ++ cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ ++ v4l2_subdev_unlock_state(state); ++ ++ return ret; ++} ++ ++static int lt6911uxe_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ u64 pixel_rate; ++ ++ lt6911uxe_video_status_update(lt6911uxe); ++ lt6911uxe_audio_status_update(lt6911uxe); ++ lt6911uxe_update_pad_format(lt6911uxe->cur_mode, &fmt->format); ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ pixel_rate = get_pixel_rate(lt6911uxe); ++ __v4l2_ctrl_modify_range(lt6911uxe->pixel_rate, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ __v4l2_ctrl_s_ctrl(lt6911uxe->audio_present, ++ (lt6911uxe->cur_mode->sample_rate != 0)); ++ ++ if (lt6911uxe->cur_mode->sample_rate) ++ __v4l2_ctrl_s_ctrl(lt6911uxe->sampling_rate, ++ lt6911uxe->cur_mode->sample_rate); ++ ++ return 0; ++} ++ ++static int lt6911uxe_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ ++ code->code = lt6911uxe->cur_mode->code; ++ ++ return 0; ++} ++ ++static int lt6911uxe_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ ++ fse->min_width = lt6911uxe->cur_mode->width; ++ fse->max_width = fse->min_width; ++ fse->min_height = lt6911uxe->cur_mode->height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int lt6911uxe_enum_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ ++ fie->interval.numerator = 1; ++ fie->interval.denominator = lt6911uxe->cur_mode->fps; ++ ++ return 0; ++} ++ ++static int lt6911uxe_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .pad = 0, ++ .format = { ++ .code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .width = LT6911UXE_DEFAULT_WIDTH, ++ .height = LT6911UXE_DEFAULT_HEIGHT, ++ }, ++ }; ++ ++ lt6911uxe_set_format(sd, sd_state, &fmt); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops lt6911uxe_video_ops = { ++ .s_stream = lt6911uxe_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops lt6911uxe_pad_ops = { ++ .set_fmt = lt6911uxe_set_format, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .enum_mbus_code = lt6911uxe_enum_mbus_code, ++ .enum_frame_size = lt6911uxe_enum_frame_size, ++ .enum_frame_interval = lt6911uxe_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_core_ops lt6911uxe_subdev_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_ops lt6911uxe_subdev_ops = { ++ .core = <6911uxe_subdev_core_ops, ++ .video = <6911uxe_video_ops, ++ .pad = <6911uxe_pad_ops, ++}; ++ ++static const struct media_entity_operations lt6911uxe_subdev_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static const struct v4l2_subdev_internal_ops lt6911uxe_internal_ops = { ++ .init_state = lt6911uxe_init_state, ++}; ++ ++static void lt6911uxe_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ ++ v4l2_async_unregister_subdev(<6911uxe->sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(<6911uxe->sd.entity); ++ v4l2_ctrl_handler_free(<6911uxe->ctrl_handler); ++ pm_runtime_disable(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static int lt6911uxe_identify_module(struct lt6911uxe *lt6911uxe) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); ++ u64 val_h, val_l; ++ u16 val; ++ ++ /* Chip ID should be confirmed when the I2C slave is active */ ++ cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x1, NULL); ++ cci_read(lt6911uxe->regmap, REG_CHIP_ID_H, &val_h, NULL); ++ cci_read(lt6911uxe->regmap, REG_CHIP_ID_L, &val_l, NULL); ++ cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ ++ val = ((val_h << 8) | val_l); ++ ++ if (val != LT6911UXE_CHIP_ID) { ++ dev_err(&client->dev, "chip id mismatch: %x!=%x\n", ++ LT6911UXE_CHIP_ID, val); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static int lt6911uxe_parse_gpio(struct lt6911uxe *lt6911uxe, struct device *dev) ++{ ++ lt6911uxe->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN); ++ if (IS_ERR(lt6911uxe->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->reset_gpio), ++ "failed to get reset gpio\n"); ++ ++ lt6911uxe->irq_gpio = devm_gpiod_get(dev, "readystat", GPIOD_IN); ++ if (IS_ERR(lt6911uxe->irq_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->irq_gpio), ++ "failed to get ready_stat gpio\n"); ++ ++ lt6911uxe->detect_gpio = devm_gpiod_get(dev, "hdmidetect", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(lt6911uxe->detect_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->detect_gpio), ++ "failed to get hdmi_detect gpio\n"); ++ ++ return 0; ++} ++ ++static irqreturn_t lt6911uxe_threaded_irq_fn(int irq, void *dev_id) ++{ ++ struct v4l2_subdev *sd = dev_id; ++ struct lt6911uxe *lt6911uxe; ++ ++ if (!sd) ++ return IRQ_NONE; ++ ++ lt6911uxe = to_lt6911uxe(sd); ++ if (!lt6911uxe) { ++ dev_err(sd->dev, "Invalid lt6911uxe state argument!\n"); ++ return IRQ_NONE; ++ } ++ ++ lt6911uxe_video_status_update(lt6911uxe); ++ lt6911uxe_audio_status_update(lt6911uxe); ++ ++ return IRQ_HANDLED; ++} ++ ++static void lt6911uxe_reset(struct lt6911uxe *lt6911uxe, int power) ++{ ++ if (lt6911uxe->reset_gpio || lt6911uxe->irq_gpio) ++ return; ++ ++ gpiod_set_value_cansleep(lt6911uxe->reset_gpio, power); ++ gpiod_set_value_cansleep(lt6911uxe->irq_gpio, power); ++} ++ ++static int lt6911uxe_probe(struct i2c_client *client) ++{ ++ struct lt6911uxe *lt6911uxe; ++ int ret; ++ ++ lt6911uxe = devm_kzalloc(&client->dev, sizeof(struct lt6911uxe), ++ GFP_KERNEL); ++ if (!lt6911uxe) ++ return -ENOMEM; ++ ++ lt6911uxe->cur_mode = devm_kzalloc(&client->dev, ++ sizeof(struct lt6911uxe_mode), ++ GFP_KERNEL); ++ if (!lt6911uxe->cur_mode) ++ return -ENOMEM; ++ ++ lt6911uxe->regmap = devm_regmap_init_i2c(client, ++ <9611uxe_regmap_config); ++ if (IS_ERR(lt6911uxe->regmap)) ++ return dev_err_probe(&client->dev, PTR_ERR(lt6911uxe->regmap), ++ "failed to init CCI\n"); ++ ++ v4l2_i2c_subdev_init(<6911uxe->sd, client, <6911uxe_subdev_ops); ++ ++ ret = lt6911uxe_parse_gpio(lt6911uxe, &client->dev); ++ if (ret) { ++ dev_err(&client->dev, "failed to get GPIO control: %d\n", ret); ++ return ret; ++ } ++ ++ lt6911uxe_reset(lt6911uxe, 1); ++ usleep_range(10000, 10500); ++ ++ ret = lt6911uxe_identify_module(lt6911uxe); ++ if (ret) { ++ dev_err(&client->dev, "failed to find chip: %d\n", ret); ++ return ret; ++ } ++ ++ /* Setting irq */ ++ lt6911uxe->irq_pin_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | ++ IRQF_ONESHOT; ++ ++ ret = devm_request_threaded_irq(&client->dev, ++ gpiod_to_irq(lt6911uxe->irq_gpio), ++ NULL, lt6911uxe_threaded_irq_fn, ++ lt6911uxe->irq_pin_flags, ++ "irq_gpio", lt6911uxe); ++ if (ret) { ++ dev_err(&client->dev, "IRQ request failed! ret: %d\n", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ ret = lt6911uxe_init_controls(lt6911uxe); ++ if (ret) { ++ dev_info(&client->dev, "Could not init control %d!\n", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ lt6911uxe->sd.state_lock = lt6911uxe->ctrl_handler.lock; ++ lt6911uxe->sd.dev = &client->dev; ++ lt6911uxe->sd.internal_ops = <6911uxe_internal_ops; ++ lt6911uxe->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ lt6911uxe->sd.entity.ops = <6911uxe_subdev_entity_ops; ++ lt6911uxe->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ lt6911uxe->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(<6911uxe->sd.entity, 1, <6911uxe->pad); ++ if (ret) { ++ dev_err(&client->dev, "Init entity pads failed: %d\n", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ ret = v4l2_subdev_init_finalize(<6911uxe->sd); ++ if (ret < 0) { ++ dev_err(&client->dev, "v4l2 subdev init error: %d\n", ret); ++ goto probe_error_media_entity_cleanup; ++ } ++ ++ /* ++ * Device is already turned on by i2c-core with ACPI domain PM. ++ * Enable runtime PM and turn off the device. ++ */ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_enable(&client->dev); ++ pm_runtime_idle(&client->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(<6911uxe->sd); ++ if (ret < 0) { ++ dev_err(&client->dev, "failed to register V4L2 subdev: %d\n", ++ ret); ++ goto rpm; ++ } ++ ++ return 0; ++ ++rpm: ++ pm_runtime_disable(&client->dev); ++ v4l2_subdev_cleanup(<6911uxe->sd); ++ ++probe_error_media_entity_cleanup: ++ media_entity_cleanup(<6911uxe->sd.entity); ++ ++probe_error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(lt6911uxe->sd.ctrl_handler); ++ return ret; ++} ++ ++static const struct acpi_device_id lt6911uxe_acpi_ids[] = { ++ {"INTC10C5"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(acpi, lt6911uxe_acpi_ids); ++ ++static struct i2c_driver lt6911uxe_i2c_driver = { ++ .driver = { ++ .name = "lt6911uxe", ++ .acpi_match_table = ACPI_PTR(lt6911uxe_acpi_ids), ++ }, ++ .probe = lt6911uxe_probe, ++ .remove = lt6911uxe_remove, ++}; ++ ++module_i2c_driver(lt6911uxe_i2c_driver); ++ ++MODULE_AUTHOR("Yan Dongcheng "); ++MODULE_DESCRIPTION("Lontium lt6911uxe HDMI to MIPI Bridge Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +index b6322bb230c3..07afadbe550a 100644 +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -85,6 +85,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* OnSemiconductor ar0234 */ + IPU_SENSOR_CONFIG("INTC10C0", 1, 360000000), ++ /* Lontium lt6911uxe */ ++ IPU_SENSOR_CONFIG("INTC10C5", 0), + }; + + static const struct ipu_property_names prop_names = { +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0003-INT3472-Support-LT6911UXE.patch b/kernel_patches/patch_6.12_mainline/0003-INT3472-Support-LT6911UXE.patch new file mode 100644 index 000000000000..455388f33e71 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0003-INT3472-Support-LT6911UXE.patch @@ -0,0 +1,73 @@ +From 732579bc9711cc86227bc5de5bbd61949d99dced Mon Sep 17 00:00:00 2001 +From: linya14x +Date: Tue, 25 Jun 2024 11:34:04 +0800 +Subject: [PATCH] INT3472: Support LT6911UXE + +Signed-off-by: linya14x +Signed-off-by: zouxiaoh +--- + drivers/platform/x86/intel/int3472/common.h | 2 ++ + drivers/platform/x86/intel/int3472/discrete.c | 18 ++++++++++++++++-- + 2 files changed, 18 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h +index 145dec66df64..1d667e1187d2 100644 +--- a/drivers/platform/x86/intel/int3472/common.h ++++ b/drivers/platform/x86/intel/int3472/common.h +@@ -22,6 +22,8 @@ + #define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b + #define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c + #define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d ++#define INT3472_GPIO_TYPE_READY_STAT 0x13 ++#define INT3472_GPIO_TYPE_HDMI_DETECT 0x14 + + #define INT3472_PDEV_MAX_NAME_LEN 23 + #define INT3472_MAX_SENSOR_GPIOS 3 +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index 07b302e09340..94095fdc09e5 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -148,6 +148,14 @@ static void int3472_get_func_and_polarity(u8 type, const char **func, u32 *polar + *func = "power-enable"; + *polarity = GPIO_ACTIVE_HIGH; + break; ++ case INT3472_GPIO_TYPE_READY_STAT: ++ *func = "readystat"; ++ *polarity = GPIO_LOOKUP_FLAGS_DEFAULT; ++ break; ++ case INT3472_GPIO_TYPE_HDMI_DETECT: ++ *func = "hdmidetect"; ++ *polarity = GPIO_LOOKUP_FLAGS_DEFAULT; ++ break; + default: + *func = "unknown"; + *polarity = GPIO_ACTIVE_HIGH; +@@ -239,9 +247,15 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + switch (type) { + case INT3472_GPIO_TYPE_RESET: + case INT3472_GPIO_TYPE_POWERDOWN: ++ case INT3472_GPIO_TYPE_READY_STAT: ++ case INT3472_GPIO_TYPE_HDMI_DETECT: + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, func, polarity); +- if (ret) ++ if (ret) { + err_msg = "Failed to map GPIO pin to sensor\n"; ++ dev_warn(int3472->dev, ++ "Failed to map GPIO pin to sensor, type %02x, func %s, polarity %u\n", ++ type, func, polarity); ++ } + + break; + case INT3472_GPIO_TYPE_CLK_ENABLE: +@@ -345,7 +359,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) + return ret; + } + +- if (cldb.control_logic_type != 1) { ++ if (cldb.control_logic_type != 1 && cldb.control_logic_type != 5) { + dev_err(&pdev->dev, "Unsupported control logic type %u\n", + cldb.control_logic_type); + return -EINVAL; +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0004-upstream-Use-module-parameter-to-set-isys-freq.patch b/kernel_patches/patch_6.12_mainline/0004-upstream-Use-module-parameter-to-set-isys-freq.patch new file mode 100644 index 000000000000..64d7ee723b04 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0004-upstream-Use-module-parameter-to-set-isys-freq.patch @@ -0,0 +1,45 @@ +From f6643528b1f3b64012d39d84916f4f5098b05aa4 Mon Sep 17 00:00:00 2001 +From: Dongcheng Yan +Date: Tue, 30 Jul 2024 11:03:10 +0800 +Subject: [PATCH] upstream: Use module parameter to set isys freq + +Signed-off-by: Hongju Wang +Signed-off-by: Dongcheng Yan +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/ipu6.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c +index bbd646378ab3..f6d42a432d9f 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6.c ++++ b/drivers/media/pci/intel/ipu6/ipu6.c +@@ -33,6 +33,10 @@ + #include "ipu6-platform-isys-csi2-reg.h" + #include "ipu6-platform-regs.h" + ++static unsigned int isys_freq_override; ++module_param(isys_freq_override, uint, 0660); ++MODULE_PARM_DESC(isys_freq_override, "Override ISYS freq(mhz)"); ++ + #define IPU6_PCI_BAR 0 + + struct ipu6_cell_program { +@@ -387,6 +391,14 @@ ipu6_isys_init(struct pci_dev *pdev, struct device *parent, + pdata->base = base; + pdata->ipdata = ipdata; + ++ /* Override the isys freq */ ++ if (isys_freq_override >= BUTTRESS_MIN_FORCE_IS_FREQ && ++ isys_freq_override <= BUTTRESS_MAX_FORCE_IS_FREQ) { ++ ctrl->ratio = isys_freq_override / BUTTRESS_IS_FREQ_STEP; ++ dev_dbg(&pdev->dev, "Override the isys freq:%u(mhz)\n", ++ isys_freq_override); ++ } ++ + isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU6_ISYS_NAME); + if (IS_ERR(isys_adev)) { +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0005-upstream-Use-module-parameter-to-set-psys-freq.patch b/kernel_patches/patch_6.12_mainline/0005-upstream-Use-module-parameter-to-set-psys-freq.patch new file mode 100644 index 000000000000..4f967897de18 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0005-upstream-Use-module-parameter-to-set-psys-freq.patch @@ -0,0 +1,45 @@ +From adf7cdb02005e1d0875396679b2e1fb5663d1b31 Mon Sep 17 00:00:00 2001 +From: Dongcheng Yan +Date: Tue, 30 Jul 2024 16:50:07 +0800 +Subject: [PATCH] upstream: Use module parameter to set psys freq + +Signed-off-by: Dongcheng Yan +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/ipu6.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c +index f6d42a432d9f..4bb270d9fe2a 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6.c ++++ b/drivers/media/pci/intel/ipu6/ipu6.c +@@ -37,6 +37,10 @@ static unsigned int isys_freq_override; + module_param(isys_freq_override, uint, 0660); + MODULE_PARM_DESC(isys_freq_override, "Override ISYS freq(mhz)"); + ++static unsigned int psys_freq_override; ++module_param(psys_freq_override, uint, 0660); ++MODULE_PARM_DESC(psys_freq_override, "Override PSYS freq(mhz)"); ++ + #define IPU6_PCI_BAR 0 + + struct ipu6_cell_program { +@@ -445,6 +449,15 @@ ipu6_psys_init(struct pci_dev *pdev, struct device *parent, + pdata->base = base; + pdata->ipdata = ipdata; + ++ /* Override the isys freq */ ++ if (psys_freq_override >= BUTTRESS_MIN_FORCE_PS_FREQ && ++ psys_freq_override <= BUTTRESS_MAX_FORCE_PS_FREQ) { ++ ctrl->ratio = psys_freq_override / BUTTRESS_PS_FREQ_STEP; ++ ctrl->qos_floor = psys_freq_override; ++ dev_dbg(&pdev->dev, "Override the psys freq:%u(mhz)\n", ++ psys_freq_override); ++ } ++ + psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU6_PSYS_NAME); + if (IS_ERR(psys_adev)) { +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0006-media-pci-Enable-ISYS-reset.patch b/kernel_patches/patch_6.12_mainline/0006-media-pci-Enable-ISYS-reset.patch new file mode 100644 index 000000000000..d4d7eb895cc5 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0006-media-pci-Enable-ISYS-reset.patch @@ -0,0 +1,626 @@ +From 902d5c84f7978b31948d3155e69c1d9d9143122d Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Mon, 2 Sep 2024 15:18:13 +0800 +Subject: [PATCH] media: pci: Enable ISYS reset + +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/Kconfig | 9 + + drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 3 + + .../media/pci/intel/ipu6/ipu6-isys-queue.c | 251 ++++++++++++++++++ + .../media/pci/intel/ipu6/ipu6-isys-queue.h | 3 + + .../media/pci/intel/ipu6/ipu6-isys-video.c | 81 +++++- + .../media/pci/intel/ipu6/ipu6-isys-video.h | 8 + + drivers/media/pci/intel/ipu6/ipu6-isys.c | 10 + + drivers/media/pci/intel/ipu6/ipu6-isys.h | 5 + + 8 files changed, 369 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig +index 40e20f0aa5ae..f5975240e5c3 100644 +--- a/drivers/media/pci/intel/ipu6/Kconfig ++++ b/drivers/media/pci/intel/ipu6/Kconfig +@@ -17,3 +17,12 @@ config VIDEO_INTEL_IPU6 + + To compile this driver, say Y here! It contains 2 modules - + intel_ipu6 and intel_ipu6_isys. ++ ++config VIDEO_INTEL_IPU6_ISYS_RESET ++ depends on VIDEO_INTEL_IPU6 ++ bool "Enable isys reset" ++ default n ++ help ++ Support ISYS reset in IPU6. ++ ++ To compile this driver, say Y here! +diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +index b60f02076d8a..e6ee161bd058 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +@@ -66,6 +66,9 @@ struct ipu6_isys; + #define IPU6_ISYS_OPEN_RETRY 2000 + #define IPU6_ISYS_CLOSE_RETRY 2000 + #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) ++#endif + + enum ipu6_fw_isys_resp_type { + IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0, +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +index 03dbb0e0ea79..e0b778633123 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +@@ -10,6 +10,9 @@ + #include + #include + #include ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++#include ++#endif + + #include + #include +@@ -20,6 +23,9 @@ + #include "ipu6-fw-isys.h" + #include "ipu6-isys.h" + #include "ipu6-isys-video.h" ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++#include "ipu6-cpd.h" ++#endif + + static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], +@@ -187,6 +193,15 @@ static int buffer_list_get(struct ipu6_isys_stream *stream, + ib = list_last_entry(&aq->incoming, + struct ipu6_isys_buffer, head); + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ if (av->skipframe) { ++ atomic_set(&ib->skipframe_flag, 1); ++ av->skipframe--; ++ } else { ++ atomic_set(&ib->skipframe_flag, 0); ++ } ++#endif + dev_dbg(dev, "buffer: %s: buffer %u\n", + ipu6_isys_queue_to_video(aq)->vdev.name, + ipu6_isys_buffer_to_vb2_buffer(ib)->index); +@@ -339,6 +354,19 @@ static void buf_queue(struct vb2_buffer *vb) + list_add(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ mutex_lock(&av->isys->reset_mutex); ++ while (av->isys->in_reset) { ++ mutex_unlock(&av->isys->reset_mutex); ++ dev_dbg(dev, "buffer: %s: wait for reset\n", av->vdev.name); ++ usleep_range(10000, 11000); ++ mutex_lock(&av->isys->reset_mutex); ++ } ++ mutex_unlock(&av->isys->reset_mutex); ++ /* ip may be cleared in ipu reset */ ++ stream = av->stream; ++ ++#endif + if (!media_pipe || !vb->vb2_queue->start_streaming_called) { + dev_dbg(dev, "media pipeline is not ready for %s\n", + av->vdev.name); +@@ -570,6 +598,9 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) + + out: + mutex_unlock(&stream->mutex); ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ av->start_streaming = 1; ++#endif + + return 0; + +@@ -591,12 +622,211 @@ out_return_buffers: + return ret; + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++static void reset_stop_streaming(struct ipu6_isys_video *av) ++{ ++ struct ipu6_isys_queue *aq = &av->aq; ++ struct ipu6_isys_stream *stream = av->stream; ++ ++ mutex_lock(&stream->mutex); ++ ++ ipu6_isys_update_stream_watermark(av, false); ++ ++ mutex_lock(&av->isys->stream_mutex); ++ if (stream->nr_streaming == stream->nr_queues && stream->streaming) ++ ipu6_isys_video_set_streaming(av, 0, NULL); ++ mutex_unlock(&av->isys->stream_mutex); ++ ++ stream->nr_streaming--; ++ list_del(&aq->node); ++ stream->streaming = 0; ++ mutex_unlock(&stream->mutex); ++ ++ ipu6_isys_stream_cleanup(av); ++ ++ return_buffers(aq, VB2_BUF_STATE_ERROR); ++ ++ ipu6_isys_fw_close(av->isys); ++} ++ ++static int reset_start_streaming(struct ipu6_isys_video *av) ++{ ++ struct ipu6_isys_queue *aq = &av->aq; ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ unsigned long flags; ++ int ret; ++ ++ dev_dbg(dev, "%s: start streaming\n", av->vdev.name); ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ while (!list_empty(&aq->active)) { ++ struct ipu6_isys_buffer *ib = list_first_entry(&aq->active, ++ struct ipu6_isys_buffer, head); ++ ++ list_del(&ib->head); ++ list_add_tail(&ib->head, &aq->incoming); ++ } ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ av->skipframe = 1; ++ ret = start_streaming(&aq->vbq, 0); ++ if (ret) { ++ dev_dbg(dev, ++ "%s: start streaming failed in reset ! set av->start_streaming = 0.\n", ++ av->vdev.name); ++ av->start_streaming = 0; ++ } else ++ av->start_streaming = 1; ++ ++ return ret; ++} ++ ++static int ipu_isys_reset(struct ipu6_isys_video *self_av, ++ struct ipu6_isys_stream *self_stream) ++{ ++ struct ipu6_isys *isys = self_av->isys; ++ struct ipu6_bus_device *adev = isys->adev; ++ struct ipu6_device *isp = adev->isp; ++ struct ipu6_isys_video *av = NULL; ++ struct ipu6_isys_stream *stream = NULL; ++ struct device *dev = &adev->auxdev.dev; ++ int ret, i, j; ++ int has_streaming = 0; ++ const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = ++ &isys->pdata->ipdata->csi2; ++ ++ dev_dbg(dev, "%s\n", __func__); ++ ++ mutex_lock(&isys->reset_mutex); ++ if (isys->in_reset) { ++ mutex_unlock(&isys->reset_mutex); ++ return 0; ++ } ++ isys->in_reset = true; ++ ++ while (isys->in_stop_streaming) { ++ dev_dbg(dev, "isys reset: %s: wait for stop\n", ++ self_av->vdev.name); ++ mutex_unlock(&isys->reset_mutex); ++ usleep_range(10000, 11000); ++ mutex_lock(&isys->reset_mutex); ++ } ++ ++ mutex_unlock(&isys->reset_mutex); ++ ++ for (i = 0; i < csi2_pdata->nports; i++) { ++ for (j = 0; j < NR_OF_CSI2_SRC_PADS; j++) { ++ av = &isys->csi2[i].av[j]; ++ if (av == self_av) ++ continue; ++ ++ stream = av->stream; ++ if (!stream || stream == self_stream) ++ continue; ++ ++ if (!stream->streaming && !stream->nr_streaming) ++ continue; ++ ++ av->reset = true; ++ has_streaming = true; ++ reset_stop_streaming(av); ++ } ++ } ++ ++ if (!has_streaming) ++ goto end_of_reset; ++ ++ dev_dbg(dev, "ipu reset, power cycle\n"); ++ /* bus_pm_runtime_suspend() */ ++ /* isys_runtime_pm_suspend() */ ++ dev->bus->pm->runtime_suspend(dev); ++ ++ /* ipu_suspend */ ++ isp->pdev->driver->driver.pm->runtime_suspend(&isp->pdev->dev); ++ ++ /* ipu_runtime_resume */ ++ isp->pdev->driver->driver.pm->runtime_resume(&isp->pdev->dev); ++ ++ /* bus_pm_runtime_resume() */ ++ /* isys_runtime_pm_resume() */ ++ dev->bus->pm->runtime_resume(dev); ++ ++ ipu6_cleanup_fw_msg_bufs(isys); ++ ++ if (isys->fwcom) { ++ dev_err(dev, "Clearing old context\n"); ++ ipu6_fw_isys_cleanup(isys); ++ } ++ ++ ret = ipu6_fw_isys_init(av->isys, ++ isys->pdata->ipdata->num_parallel_streams); ++ if (ret < 0) ++ dev_err(dev, "ipu fw isys init failed\n"); ++ ++ dev_dbg(dev, "restart streams\n"); ++ ++ for (j = 0; j < csi2_pdata->nports; j++) { ++ for (i = 0; i < NR_OF_CSI2_SRC_PADS; i++) { ++ av = &isys->csi2[j].av[i]; ++ if (!av->reset) ++ continue; ++ ++ av->reset = false; ++ reset_start_streaming(av); ++ } ++ } ++ ++end_of_reset: ++ mutex_lock(&isys->reset_mutex); ++ isys->in_reset = false; ++ mutex_unlock(&isys->reset_mutex); ++ dev_dbg(dev, "reset done\n"); ++ ++ return 0; ++} ++ ++#endif + static void stop_streaming(struct vb2_queue *q) + { + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_stream *stream = av->stream; + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ ++ dev_dbg(dev, "stop: %s: enter\n", av->vdev.name); ++ ++ mutex_lock(&av->isys->reset_mutex); ++ while (av->isys->in_reset || av->isys->in_stop_streaming) { ++ mutex_unlock(&av->isys->reset_mutex); ++ dev_dbg(dev, "stop: %s: wait for in_reset = %d\n", ++ av->vdev.name, av->isys->in_reset); ++ dev_dbg(dev, "stop: %s: wait for in_stop = %d\n", ++ av->vdev.name, av->isys->in_stop_streaming); ++ usleep_range(10000, 11000); ++ mutex_lock(&av->isys->reset_mutex); ++ } ++ ++ if (!av->start_streaming) { ++ mutex_unlock(&av->isys->reset_mutex); ++ return; ++ } ++ ++ av->isys->in_stop_streaming = true; ++ mutex_unlock(&av->isys->reset_mutex); ++ ++ stream = av->stream; ++ if (!stream) { ++ dev_err(dev, "stop: %s: ip cleard!\n", av->vdev.name); ++ return_buffers(aq, VB2_BUF_STATE_ERROR); ++ mutex_lock(&av->isys->reset_mutex); ++ av->isys->in_stop_streaming = false; ++ mutex_unlock(&av->isys->reset_mutex); ++ return; ++ } ++#endif ++ + mutex_lock(&stream->mutex); + + ipu6_isys_update_stream_watermark(av, false); +@@ -616,6 +846,22 @@ static void stop_streaming(struct vb2_queue *q) + return_buffers(aq, VB2_BUF_STATE_ERROR); + + ipu6_isys_fw_close(av->isys); ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ ++ av->start_streaming = 0; ++ mutex_lock(&av->isys->reset_mutex); ++ av->isys->in_stop_streaming = false; ++ mutex_unlock(&av->isys->reset_mutex); ++ ++ if (av->isys->need_reset) { ++ if (!stream->nr_streaming) ++ ipu_isys_reset(av, stream); ++ else ++ av->isys->need_reset = 0; ++ } ++ ++ dev_dbg(dev, "stop: %s: exit\n", av->vdev.name); ++#endif + } + + static unsigned int +@@ -699,6 +945,11 @@ void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) + * to the userspace when it is de-queued + */ + atomic_set(&ib->str2mmio_flag, 0); ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ } else if (atomic_read(&ib->skipframe_flag)) { ++ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); ++ atomic_set(&ib->skipframe_flag, 0); ++#endif + } else { + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +index 95cfd4869d93..ffbf781b578b 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +@@ -33,6 +33,9 @@ struct ipu6_isys_queue { + struct ipu6_isys_buffer { + struct list_head head; + atomic_t str2mmio_flag; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ atomic_t skipframe_flag; ++#endif + }; + + struct ipu6_isys_video_buffer { +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +index b37561352ead..9142280c299a 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -17,6 +17,9 @@ + #include + #include + #include ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++#include ++#endif + + #include + #include +@@ -112,6 +115,25 @@ static int video_open(struct file *file) + return v4l2_fh_open(file); + } + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++static int video_release(struct file *file) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "release: %s: enter\n", av->vdev.name); ++ mutex_lock(&av->isys->reset_mutex); ++ while (av->isys->in_reset) { ++ mutex_unlock(&av->isys->reset_mutex); ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "release: %s: wait for reset\n", av->vdev.name); ++ usleep_range(10000, 11000); ++ mutex_lock(&av->isys->reset_mutex); ++ } ++ mutex_unlock(&av->isys->reset_mutex); ++ return vb2_fop_release(file); ++} ++ ++#endif + const struct ipu6_isys_pixelformat * + ipu6_isys_get_isys_format(u32 pixelformat, u32 type) + { +@@ -596,7 +618,11 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + } + + reinit_completion(&stream->stream_start_completion); +- ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; ++ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, ++ send_type); ++#else + if (bl) { + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + ipu6_fw_isys_dump_frame_buff_set(dev, buf, +@@ -609,6 +635,7 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + send_type); + } ++#endif + + if (ret < 0) { + dev_err(dev, "can't start streaming (%d)\n", ret); +@@ -627,7 +654,25 @@ static int start_stream_firmware(struct ipu6_isys_video *av, + ret = -EIO; + goto out_stream_close; + } ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ if (bl) { ++ dev_dbg(dev, "start stream: 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, ++ buf, msg->dma_addr, ++ sizeof(*buf), ++ send_type); ++ ++ if (ret < 0) { ++ dev_err(dev, "can't queue buffers (%d)\n", ret); ++ goto out_stream_close; ++ } ++ } ++ ++#else + dev_dbg(dev, "start stream: complete\n"); ++#endif + + return 0; + +@@ -674,7 +719,11 @@ static void stop_streaming_firmware(struct ipu6_isys_video *av) + } + + tout = wait_for_completion_timeout(&stream->stream_stop_completion, ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ IPU6_LIB_CALL_TIMEOUT_JIFFIES_RESET); ++#else + IPU6_FW_CALL_TIMEOUT_JIFFIES); ++#endif + if (!tout) + dev_warn(dev, "stream stop time out\n"); + else if (stream->error) +@@ -699,7 +748,11 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ IPU6_LIB_CALL_TIMEOUT_JIFFIES_RESET); ++#else + IPU6_FW_CALL_TIMEOUT_JIFFIES); ++#endif + if (!tout) + dev_warn(dev, "stream close time out\n"); + else if (stream->error) +@@ -707,6 +760,12 @@ static void close_streaming_firmware(struct ipu6_isys_video *av) + else + dev_dbg(dev, "close stream: complete\n"); + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ stream->last_sequence = atomic_read(&stream->sequence); ++ dev_dbg(dev, "IPU_ISYS_RESET: ip->last_sequence = %d\n", ++ stream->last_sequence); ++ ++#endif + put_stream_opened(av); + } + +@@ -721,7 +780,18 @@ int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, + return -EINVAL; + + stream->nr_queues = nr_queues; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ if (av->isys->in_reset) { ++ atomic_set(&stream->sequence, stream->last_sequence); ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "atomic_set : stream->last_sequence = %d\n", ++ stream->last_sequence); ++ } else { ++ atomic_set(&stream->sequence, 0); ++ } ++# else + atomic_set(&stream->sequence, 0); ++#endif + + stream->seq_index = 0; + memset(stream->seq, 0, sizeof(stream->seq)); +@@ -1089,7 +1159,11 @@ static const struct v4l2_file_operations isys_fops = { + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = video_open, ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ .release = video_release, ++#else + .release = vb2_fop_release, ++#endif + }; + + int ipu6_isys_fw_open(struct ipu6_isys *isys) +@@ -1305,6 +1379,11 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av) + av->pix_fmt = format.fmt.pix; + __ipu6_isys_vidioc_try_fmt_meta_cap(av, &format_meta); + av->meta_fmt = format_meta.fmt.meta; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ av->reset = false; ++ av->skipframe = 0; ++ av->start_streaming = 0; ++#endif + + set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); + video_set_drvdata(&av->vdev, av); +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +index 1d945be2b879..0dfa4774b802 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +@@ -51,6 +51,9 @@ struct ipu6_isys_stream { + struct mutex mutex; + struct media_entity *source_entity; + atomic_t sequence; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ int last_sequence; ++#endif + unsigned int seq_index; + struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; + int stream_source; +@@ -101,6 +104,11 @@ struct ipu6_isys_video { + u32 source_stream; + u8 vc; + u8 dt; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ unsigned int reset; ++ unsigned int skipframe; ++ unsigned int start_streaming; ++#endif + }; + + #define ipu6_isys_queue_to_video(__aq) \ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c +index c4aff2e2009b..d210cda1fa36 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys.c ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c +@@ -1085,6 +1085,10 @@ static int isys_probe(struct auxiliary_device *auxdev, + + mutex_init(&isys->mutex); + mutex_init(&isys->stream_mutex); ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ mutex_init(&isys->reset_mutex); ++ isys->in_reset = false; ++#endif + + spin_lock_init(&isys->listlock); + INIT_LIST_HEAD(&isys->framebuflist); +@@ -1127,6 +1131,9 @@ static int isys_probe(struct auxiliary_device *auxdev, + if (ret) + goto free_fw_msg_bufs; + ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ mutex_destroy(&isys->reset_mutex); ++#endif + ipu6_mmu_hw_cleanup(adev->mmu); + + return 0; +@@ -1180,6 +1187,9 @@ static void isys_remove(struct auxiliary_device *auxdev) + isys_iwake_watermark_cleanup(isys); + mutex_destroy(&isys->stream_mutex); + mutex_destroy(&isys->mutex); ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ mutex_destroy(&isys->reset_mutex); ++#endif + } + + struct fwmsg { +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h +index 86dfc2eff5d0..982eb5d280e1 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-isys.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h +@@ -167,6 +167,11 @@ struct ipu6_isys { + struct list_head framebuflist_fw; + struct v4l2_async_notifier notifier; + struct isys_iwake_watermark iwake_watermark; ++#ifdef CONFIG_VIDEO_INTEL_IPU6_ISYS_RESET ++ struct mutex reset_mutex; ++ bool in_reset; ++ bool in_stop_streaming; ++#endif + }; + + struct isys_fw_msgs { +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0007-media-i2c-add-support-for-ar0234-and-lt6911uxe.patch b/kernel_patches/patch_6.12_mainline/0007-media-i2c-add-support-for-ar0234-and-lt6911uxe.patch new file mode 100644 index 000000000000..57a041bcccf1 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0007-media-i2c-add-support-for-ar0234-and-lt6911uxe.patch @@ -0,0 +1,60 @@ +From d40d15f9edf62243f3b5d4cbbed908050e6035af Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Mon, 2 Sep 2024 15:45:47 +0800 +Subject: [PATCH] media: i2c: add support for ar0234 and lt6911uxe + +Signed-off-by: zouxiaoh +--- + drivers/media/i2c/Kconfig | 22 ++++++++++++++++++++++ + drivers/media/i2c/Makefile | 2 ++ + 2 files changed, 24 insertions(+) + +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 8ba096b8ebca..0d314a92ea4f 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -51,6 +51,28 @@ config VIDEO_ALVIUM_CSI2 + To compile this driver as a module, choose M here: the + module will be called alvium-csi2. + ++config VIDEO_LT6911UXE ++ tristate "Lontium LT6911UXE decoder" ++ depends on ACPI || COMPILE_TEST ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor-level driver for the Lontium ++ LT6911UXE HDMI to MIPI CSI-2 bridge. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called lt6911uxe. ++ ++config VIDEO_AR0234 ++ tristate "ON Semiconductor AR0234 sensor support" ++ depends on ACPI || COMPILE_TEST ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor driver for the ON Semiconductor ++ AR0234 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ar0234. ++ + config VIDEO_AR0521 + tristate "ON Semiconductor AR0521 sensor support" + help +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index fbb988bd067a..bf06c7fb08f6 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -19,6 +19,8 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o + obj-$(CONFIG_VIDEO_AK881X) += ak881x.o + obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o + obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o ++obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o ++obj-$(CONFIG_VIDEO_AR0234) += ar0234.o + obj-$(CONFIG_VIDEO_AR0521) += ar0521.o + obj-$(CONFIG_VIDEO_BT819) += bt819.o + obj-$(CONFIG_VIDEO_BT856) += bt856.o +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0008-driver-media-i2c-remove-useless-header-file.patch b/kernel_patches/patch_6.12_mainline/0008-driver-media-i2c-remove-useless-header-file.patch new file mode 100644 index 000000000000..4786888c967e --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0008-driver-media-i2c-remove-useless-header-file.patch @@ -0,0 +1,25 @@ +From 8a178885dabc3dadb332c7917715a6424936520f Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Tue, 8 Oct 2024 11:25:07 +0800 +Subject: [PATCH] driver: media: i2c: remove useless header file + +Signed-off-by: zouxiaoh +--- + drivers/media/i2c/ar0234.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/drivers/media/i2c/ar0234.c b/drivers/media/i2c/ar0234.c +index 80fe5ffd1c64..ab7628c69664 100644 +--- a/drivers/media/i2c/ar0234.c ++++ b/drivers/media/i2c/ar0234.c +@@ -7,7 +7,6 @@ + #include + #include + #include +-#include + + #include + #include +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0010-media-i2c-update-lt6911uxe-driver-for-upstream-and-b.patch b/kernel_patches/patch_6.12_mainline/0010-media-i2c-update-lt6911uxe-driver-for-upstream-and-b.patch new file mode 100644 index 000000000000..c925ff469c08 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0010-media-i2c-update-lt6911uxe-driver-for-upstream-and-b.patch @@ -0,0 +1,877 @@ +From 6badf9ee326182d4d0b57171c3f3a53914daf521 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Wed, 20 Nov 2024 15:39:32 +0800 +Subject: [PATCH] media: i2c: update lt6911uxe for upstream and bug fix + +Signed-off-by: zouxiaoh +--- + drivers/media/i2c/lt6911uxe.c | 584 ++++++++++++++++------------------ + 1 file changed, 278 insertions(+), 306 deletions(-) + +diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c +index 660a558478de..a64ea290c8ad 100644 +--- a/drivers/media/i2c/lt6911uxe.c ++++ b/drivers/media/i2c/lt6911uxe.c +@@ -3,7 +3,6 @@ + + #include + #include +-#include + #include + #include + #include +@@ -17,8 +16,7 @@ + #include + + #define LT6911UXE_CHIP_ID 0x2102 +-#define REG_CHIP_ID_H CCI_REG8(0xe100) +-#define REG_CHIP_ID_L CCI_REG8(0xe101) ++#define REG_CHIP_ID CCI_REG16(0xe100) + + #define REG_ENABLE_I2C CCI_REG8(0xe0ee) + #define REG_PIX_CLK CCI_REG24(0xe085) +@@ -27,26 +25,21 @@ + #define REG_V_TOTAL CCI_REG16(0xe08a) + #define REG_HALF_H_ACTIVE CCI_REG16(0xe08c) + #define REG_V_ACTIVE CCI_REG16(0xe08e) ++#define REG_MIPI_FORMAT CCI_REG8(0xe096) + #define REG_MIPI_TX_CTRL CCI_REG8(0xe0b0) +-#define REG_MIPI_LANES CCI_REG8(0xe095) + + /* Interrupts */ + #define REG_INT_HDMI CCI_REG8(0xe084) +-#define INT_HDMI_DISCONNECT 0x0 +-#define INT_HDMI_STABLE 0x1 +-#define REG_INT_AUDIO CCI_REG8(0x86a5) +-#define REG_AUDIO_FS_H CCI_REG8(0xe090) +-#define REG_AUDIO_FS_L CCI_REG8(0xe091) +-#define AUDIO_SR_HIGH 0x55 +-#define AUDIO_SR_LOW 0xaa +-#define INT_AUDIO_DISCONNECT 0x3 ++#define INT_VIDEO_DISAPPEAR 0x0 ++#define INT_VIDEO_READY 0x1 + + #define LT6911UXE_DEFAULT_WIDTH 3840 + #define LT6911UXE_DEFAULT_HEIGHT 2160 ++#define LT6911UXE_DEFAULT_LANES 4 ++#define LT6911UXE_DEFAULT_FPS 30 ++#define LT6911UXE_MAX_LINK_FREQ 297000000 + #define LT9611_PAGE_CONTROL 0xff +- +-#define LT6911UXE_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_BASE | 0x1001) +-#define LT6911UXE_CID_AUDIO_PRESENT (V4L2_CID_USER_BASE | 0x1002) ++#define YUV422_8_BIT 0x7 + + static const struct regmap_range_cfg lt9611uxe_ranges[] = { + { +@@ -73,11 +66,9 @@ struct lt6911uxe_mode { + u32 width; + u32 height; + u32 code; +- s32 lanes; + u32 fps; ++ u32 lanes; + s64 link_freq; +- /* Audio sample rate*/ +- u32 sample_rate; + }; + + struct lt6911uxe { +@@ -86,15 +77,11 @@ struct lt6911uxe { + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; +- struct v4l2_ctrl *sampling_rate; +- struct v4l2_ctrl *audio_present; + +- struct lt6911uxe_mode *cur_mode; ++ struct lt6911uxe_mode cur_mode; + struct regmap *regmap; +- unsigned int irq_pin_flags; + struct gpio_desc *reset_gpio; + struct gpio_desc *irq_gpio; +- struct gpio_desc *detect_gpio; + }; + + static const struct v4l2_event lt6911uxe_ev_source_change = { +@@ -111,145 +98,123 @@ static inline struct lt6911uxe *to_lt6911uxe(struct v4l2_subdev *sd) + return container_of(sd, struct lt6911uxe, sd); + } + +-static const struct v4l2_ctrl_config lt6911uxe_audio_sampling_rate = { +- .id = LT6911UXE_CID_AUDIO_SAMPLING_RATE, +- .name = "Audio Sampling Rate", +- .type = V4L2_CTRL_TYPE_INTEGER, +- .min = 32000, +- .max = 192000, +- .step = 100, +- .def = 48000, +- .flags = V4L2_CTRL_FLAG_READ_ONLY, +-}; +- +-static const struct v4l2_ctrl_config lt6911uxe_audio_present = { +- .id = LT6911UXE_CID_AUDIO_PRESENT, +- .name = "Audio Present", +- .type = V4L2_CTRL_TYPE_BOOLEAN, +- .min = 0, +- .max = 1, +- .step = 1, +- .def = 0, +- .flags = V4L2_CTRL_FLAG_READ_ONLY, ++static const struct lt6911uxe_mode default_mode = { ++ .width = LT6911UXE_DEFAULT_WIDTH, ++ .height = LT6911UXE_DEFAULT_HEIGHT, ++ .code = MEDIA_BUS_FMT_UYVY8_1X16, ++ .fps = LT6911UXE_DEFAULT_FPS, ++ .lanes = LT6911UXE_DEFAULT_LANES, ++ .link_freq = LT6911UXE_MAX_LINK_FREQ, + }; + +-static u64 get_pixel_rate(struct lt6911uxe *lt6911uxe) ++static s64 get_pixel_rate(struct lt6911uxe *lt6911uxe) + { +- u64 pixel_rate; ++ s64 pixel_rate; + +- if (!lt6911uxe->cur_mode->width) +- return 0; +- +- pixel_rate = (u64)lt6911uxe->cur_mode->width * +- lt6911uxe->cur_mode->height * +- lt6911uxe->cur_mode->fps * 16; +- do_div(pixel_rate, lt6911uxe->cur_mode->lanes); ++ pixel_rate = (s64)lt6911uxe->cur_mode.width * ++ lt6911uxe->cur_mode.height * ++ lt6911uxe->cur_mode.fps * 16; ++ do_div(pixel_rate, lt6911uxe->cur_mode.lanes); + + return pixel_rate; + } + +-static int lt6911uxe_get_audio_sampling_rate(struct lt6911uxe *lt6911uxe) +-{ +- int audio_fs, idx; +- u64 fs_h, fs_l; +- static const int eps = 1500; +- static const int rates_default[] = { +- 32000, 44100, 48000, 88200, 96000, 176400, 192000 +- }; +- +- cci_read(lt6911uxe->regmap, REG_AUDIO_FS_H, &fs_h, NULL); +- cci_read(lt6911uxe->regmap, REG_AUDIO_FS_H, &fs_l, NULL); +- audio_fs = ((fs_h << 8) | fs_l); +- +- /* audio_fs is an approximation of sample rate - search nearest */ +- for (idx = 0; idx < ARRAY_SIZE(rates_default); ++idx) { +- if ((rates_default[idx] - eps < audio_fs) && +- (rates_default[idx] + eps > audio_fs)) +- return rates_default[idx]; +- } +- +- return 0; +-} +- +-static int lt6911uxe_video_status_update(struct lt6911uxe *lt6911uxe) ++static int lt6911uxe_status_update(struct lt6911uxe *lt6911uxe) + { ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + u64 int_event; +- u64 byte_clock, pixel_clk, fps, lanes; ++ u64 byte_clk, pixel_clk, fps, format; + u64 htotal, vtotal, half_width, height; ++ int timeout_cnt = 3; ++ int ret = 0; + + /* Read interrupt event */ +- cci_read(lt6911uxe->regmap, REG_INT_HDMI, &int_event, NULL); ++ cci_read(lt6911uxe->regmap, REG_INT_HDMI, &int_event, &ret); ++ while (ret && timeout_cnt--) { ++ ret = cci_read(lt6911uxe->regmap, REG_INT_HDMI, ++ &int_event, NULL); ++ } ++ ++ if (ret) ++ return dev_err_probe(&client->dev, ret, ++ "failed to read interrupt event\n"); ++ ++ /* TODO: add audio ready and disappear type */ + switch (int_event) { +- case INT_HDMI_STABLE: +- cci_read(lt6911uxe->regmap, REG_BYTE_CLK, &byte_clock, NULL); +- byte_clock *= 1000; +- cci_read(lt6911uxe->regmap, REG_PIX_CLK, &pixel_clk, NULL); ++ case INT_VIDEO_READY: ++ cci_read(lt6911uxe->regmap, REG_BYTE_CLK, &byte_clk, &ret); ++ byte_clk *= 1000; ++ cci_read(lt6911uxe->regmap, REG_PIX_CLK, &pixel_clk, &ret); + pixel_clk *= 1000; + +- cci_read(lt6911uxe->regmap, REG_H_TOTAL, &htotal, NULL); +- cci_read(lt6911uxe->regmap, REG_V_TOTAL, &vtotal, NULL); ++ if (ret || byte_clk == 0 || pixel_clk == 0) { ++ dev_err(&client->dev, ++ "invalid ByteClock or PixelClock\n"); ++ return -EINVAL; ++ } ++ ++ cci_read(lt6911uxe->regmap, REG_H_TOTAL, &htotal, &ret); ++ cci_read(lt6911uxe->regmap, REG_V_TOTAL, &vtotal, &ret); ++ if (ret || htotal == 0 || vtotal == 0) { ++ dev_err(&client->dev, "invalid htotal or vtotal\n"); ++ return -EINVAL; ++ } ++ ++ fps = div_u64(pixel_clk, htotal * vtotal); ++ if (fps > 60) { ++ dev_err(&client->dev, ++ "max fps is 60, current fps: %llu\n", fps); ++ return -EINVAL; ++ } ++ + cci_read(lt6911uxe->regmap, REG_HALF_H_ACTIVE, +- &half_width, NULL); +- cci_read(lt6911uxe->regmap, REG_V_ACTIVE, &height, NULL); +- cci_read(lt6911uxe->regmap, REG_MIPI_LANES, &lanes, NULL); +- +- if (htotal && vtotal) +- fps = div_u64(pixel_clk, htotal * vtotal); +- +- lt6911uxe->cur_mode->height = height; +- lt6911uxe->cur_mode->width = half_width * 2; +- lt6911uxe->cur_mode->fps = fps; +- lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; +- lt6911uxe->cur_mode->lanes = lanes; +- /* Mipi Clock Rate = ByteClock × 4, defined in lt6911uxe spec */ +- lt6911uxe->cur_mode->link_freq = byte_clock * 4; ++ &half_width, &ret); ++ cci_read(lt6911uxe->regmap, REG_V_ACTIVE, &height, &ret); ++ if (ret || half_width == 0 || half_width * 2 > 3840 || ++ height == 0 || height > 2160) { ++ dev_err(&client->dev, "invalid width or height\n"); ++ return -EINVAL; ++ } ++ ++ /* ++ * Get MIPI format, YUV422_8_BIT is expected in lt6911uxe ++ */ ++ cci_read(lt6911uxe->regmap, REG_MIPI_FORMAT, &format, &ret); ++ if (format != YUV422_8_BIT) { ++ dev_err(&client->dev, "invalid MIPI format\n"); ++ return -EINVAL; ++ } ++ lt6911uxe->cur_mode.height = height; ++ lt6911uxe->cur_mode.width = half_width * 2; ++ lt6911uxe->cur_mode.fps = fps; ++ /* MIPI Clock Rate = ByteClock × 4, defined in lt6911uxe spec */ ++ lt6911uxe->cur_mode.link_freq = byte_clk * 4; + v4l2_subdev_notify_event(<6911uxe->sd, + <6911uxe_ev_source_change); + break; +- case INT_HDMI_DISCONNECT: ++ ++ case INT_VIDEO_DISAPPEAR: + cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); +- lt6911uxe->cur_mode->height = 0; +- lt6911uxe->cur_mode->width = 0; +- lt6911uxe->cur_mode->fps = fps; +- lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; +- lt6911uxe->cur_mode->lanes = 4; +- lt6911uxe->cur_mode->link_freq = 0; ++ lt6911uxe->cur_mode.height = 0; ++ lt6911uxe->cur_mode.width = 0; ++ lt6911uxe->cur_mode.fps = 0; ++ lt6911uxe->cur_mode.link_freq = 0; + v4l2_subdev_notify_event(<6911uxe->sd, + <6911uxe_ev_stream_end); + break; +- default: +- return -ENOLINK; +- } +- +- return 0; +-} + +-static void lt6911uxe_audio_status_update(struct lt6911uxe *lt6911uxe) +-{ +- u64 int_event; +- int audio_fs; +- +- /* Read interrupt event */ +- cci_read(lt6911uxe->regmap, REG_INT_AUDIO, &int_event, NULL); +- switch (int_event) { +- case AUDIO_SR_HIGH: +- case AUDIO_SR_LOW: +- audio_fs = lt6911uxe_get_audio_sampling_rate(lt6911uxe); +- lt6911uxe->cur_mode->sample_rate = audio_fs; +- break; +- +- case INT_AUDIO_DISCONNECT: + default: +- /* use the default value for avoiding problem */ +- lt6911uxe->cur_mode->sample_rate = 0; +- break; ++ return -ENOLINK; + } + ++ return ret; + } + ++/* TODO: add audio sampling rate and present control */ + static int lt6911uxe_init_controls(struct lt6911uxe *lt6911uxe) + { + struct v4l2_ctrl_handler *ctrl_hdlr; ++ s64 pixel_rate; + int ret; + + ctrl_hdlr = <6911uxe->ctrl_handler; +@@ -259,25 +224,14 @@ static int lt6911uxe_init_controls(struct lt6911uxe *lt6911uxe) + + lt6911uxe->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, +- sizeof(lt6911uxe->cur_mode->link_freq), +- 0, <6911uxe->cur_mode->link_freq); ++ sizeof(lt6911uxe->cur_mode.link_freq), ++ 0, <6911uxe->cur_mode.link_freq); + ++ pixel_rate = get_pixel_rate(lt6911uxe); + lt6911uxe->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, + V4L2_CID_PIXEL_RATE, +- get_pixel_rate(lt6911uxe), +- get_pixel_rate(lt6911uxe), 1, +- get_pixel_rate(lt6911uxe)); +- +- /* custom v4l2 audio controls */ +- lt6911uxe->sampling_rate = +- v4l2_ctrl_new_custom(ctrl_hdlr, +- <6911uxe_audio_sampling_rate, +- NULL); +- +- lt6911uxe->audio_present = +- v4l2_ctrl_new_custom(ctrl_hdlr, +- <6911uxe_audio_present, +- NULL); ++ pixel_rate, pixel_rate, 1, ++ pixel_rate); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; +@@ -286,6 +240,7 @@ static int lt6911uxe_init_controls(struct lt6911uxe *lt6911uxe) + lt6911uxe->sd.ctrl_handler = ctrl_hdlr; + + return 0; ++ + hdlr_free: + v4l2_ctrl_handler_free(ctrl_hdlr); + return ret; +@@ -300,22 +255,21 @@ static void lt6911uxe_update_pad_format(const struct lt6911uxe_mode *mode, + fmt->field = V4L2_FIELD_NONE; + } + +-static int lt6911uxe_start_streaming(struct lt6911uxe *lt6911uxe) ++static int lt6911uxe_enable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) + { +- struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); ++ struct i2c_client *client = v4l2_get_subdevdata(sd); ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + int ret; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + +- ret = __v4l2_ctrl_handler_setup(lt6911uxe->sd.ctrl_handler); +- if (ret) +- goto err_rpm_put; +- + cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x1, &ret); + if (ret) { +- dev_err(&client->dev, "failed to start stream\n"); ++ dev_err(&client->dev, "failed to start stream: %d\n", ret); + goto err_rpm_put; + } + +@@ -326,21 +280,20 @@ err_rpm_put: + return ret; + } + +-static int lt6911uxe_s_stream(struct v4l2_subdev *sd, int enable) ++static int lt6911uxe_disable_streams(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ u32 pad, u64 streams_mask) + { + struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); +- struct v4l2_subdev_state *state; +- int ret = 0; +- +- state = v4l2_subdev_lock_and_get_active_state(sd); +- if (enable) +- ret = lt6911uxe_start_streaming(lt6911uxe); +- else +- cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); ++ int ret; + +- v4l2_subdev_unlock_state(state); ++ ret = cci_write(lt6911uxe->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ if (ret) ++ dev_err(&client->dev, "failed to stop stream: %d\n", ret); + +- return ret; ++ pm_runtime_put(&client->dev); ++ return 0; + } + + static int lt6911uxe_set_format(struct v4l2_subdev *sd, +@@ -348,11 +301,8 @@ static int lt6911uxe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_format *fmt) + { + struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); +- u64 pixel_rate; +- +- lt6911uxe_video_status_update(lt6911uxe); +- lt6911uxe_audio_status_update(lt6911uxe); +- lt6911uxe_update_pad_format(lt6911uxe->cur_mode, &fmt->format); ++ u64 pixel_rate, link_freq; ++ lt6911uxe_update_pad_format(<6911uxe->cur_mode, &fmt->format); + *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return 0; +@@ -360,12 +310,23 @@ static int lt6911uxe_set_format(struct v4l2_subdev *sd, + pixel_rate = get_pixel_rate(lt6911uxe); + __v4l2_ctrl_modify_range(lt6911uxe->pixel_rate, pixel_rate, + pixel_rate, 1, pixel_rate); +- __v4l2_ctrl_s_ctrl(lt6911uxe->audio_present, +- (lt6911uxe->cur_mode->sample_rate != 0)); + +- if (lt6911uxe->cur_mode->sample_rate) +- __v4l2_ctrl_s_ctrl(lt6911uxe->sampling_rate, +- lt6911uxe->cur_mode->sample_rate); ++ link_freq = lt6911uxe->cur_mode.link_freq; ++ __v4l2_ctrl_modify_range(lt6911uxe->link_freq, link_freq, ++ link_freq, 1, link_freq); ++ ++ return 0; ++} ++ ++static int lt6911uxe_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); ++ else ++ lt6911uxe_update_pad_format(<6911uxe->cur_mode, &fmt->format); + + return 0; + } +@@ -376,7 +337,10 @@ static int lt6911uxe_enum_mbus_code(struct v4l2_subdev *sd, + { + struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + +- code->code = lt6911uxe->cur_mode->code; ++ if (code->index) ++ return -EINVAL; ++ ++ code->code = lt6911uxe->cur_mode.code; + + return 0; + } +@@ -387,9 +351,15 @@ static int lt6911uxe_enum_frame_size(struct v4l2_subdev *sd, + { + struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + +- fse->min_width = lt6911uxe->cur_mode->width; ++ if (fse->index != 0) ++ return -EINVAL; ++ ++ if (fse->code != MEDIA_BUS_FMT_UYVY8_1X16) ++ return -EINVAL; ++ ++ fse->min_width = lt6911uxe->cur_mode.width; + fse->max_width = fse->min_width; +- fse->min_height = lt6911uxe->cur_mode->height; ++ fse->min_height = lt6911uxe->cur_mode.height; + fse->max_height = fse->min_height; + + return 0; +@@ -401,8 +371,11 @@ static int lt6911uxe_enum_frame_interval(struct v4l2_subdev *sd, + { + struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + ++ if (fie->index != 0) ++ return -EINVAL; ++ + fie->interval.numerator = 1; +- fie->interval.denominator = lt6911uxe->cur_mode->fps; ++ fie->interval.denominator = lt6911uxe->cur_mode.fps; + + return 0; + } +@@ -411,30 +384,27 @@ static int lt6911uxe_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) + { + struct v4l2_subdev_format fmt = { +- .which = V4L2_SUBDEV_FORMAT_TRY, +- .pad = 0, +- .format = { +- .code = MEDIA_BUS_FMT_UYVY8_1X16, +- .width = LT6911UXE_DEFAULT_WIDTH, +- .height = LT6911UXE_DEFAULT_HEIGHT, +- }, ++ .which = sd_state ? V4L2_SUBDEV_FORMAT_TRY ++ : V4L2_SUBDEV_FORMAT_ACTIVE, + }; ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + +- lt6911uxe_set_format(sd, sd_state, &fmt); +- +- return 0; ++ return lt6911uxe_set_format(sd, sd_state, &fmt); + } + + static const struct v4l2_subdev_video_ops lt6911uxe_video_ops = { +- .s_stream = lt6911uxe_s_stream, ++ .s_stream = v4l2_subdev_s_stream_helper, + }; + + static const struct v4l2_subdev_pad_ops lt6911uxe_pad_ops = { + .set_fmt = lt6911uxe_set_format, +- .get_fmt = v4l2_subdev_get_fmt, ++ .get_fmt = lt6911uxe_get_format, ++ .enable_streams = lt6911uxe_enable_streams, ++ .disable_streams = lt6911uxe_disable_streams, + .enum_mbus_code = lt6911uxe_enum_mbus_code, + .enum_frame_size = lt6911uxe_enum_frame_size, + .enum_frame_interval = lt6911uxe_enum_frame_interval, ++ .get_frame_interval = v4l2_subdev_get_frame_interval, + }; + + static const struct v4l2_subdev_core_ops lt6911uxe_subdev_core_ops = { +@@ -456,59 +426,61 @@ static const struct v4l2_subdev_internal_ops lt6911uxe_internal_ops = { + .init_state = lt6911uxe_init_state, + }; + +-static void lt6911uxe_remove(struct i2c_client *client) +-{ +- struct v4l2_subdev *sd = i2c_get_clientdata(client); +- struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); +- +- v4l2_async_unregister_subdev(<6911uxe->sd); +- v4l2_subdev_cleanup(sd); +- media_entity_cleanup(<6911uxe->sd.entity); +- v4l2_ctrl_handler_free(<6911uxe->ctrl_handler); +- pm_runtime_disable(&client->dev); +- pm_runtime_set_suspended(&client->dev); +-} +- +-static int lt6911uxe_identify_module(struct lt6911uxe *lt6911uxe) ++static int lt6911uxe_fwnode_parse(struct lt6911uxe *lt6911uxe, ++ struct device *dev) + { +- struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); +- u64 val_h, val_l; +- u16 val; ++ struct fwnode_handle *endpoint; ++ struct v4l2_fwnode_endpoint bus_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ int ret; + +- /* Chip ID should be confirmed when the I2C slave is active */ +- cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x1, NULL); +- cci_read(lt6911uxe->regmap, REG_CHIP_ID_H, &val_h, NULL); +- cci_read(lt6911uxe->regmap, REG_CHIP_ID_L, &val_l, NULL); +- cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, ++ FWNODE_GRAPH_ENDPOINT_NEXT); ++ if (!endpoint) ++ return dev_err_probe(dev, -EPROBE_DEFER, ++ "endpoint node not found\n"); + +- val = ((val_h << 8) | val_l); ++ ret = v4l2_fwnode_endpoint_parse(endpoint, &bus_cfg); ++ fwnode_handle_put(endpoint); ++ if (ret) { ++ dev_err(dev, "failed to parse endpoint node: %d\n", ret); ++ goto out_err; ++ } + +- if (val != LT6911UXE_CHIP_ID) { +- dev_err(&client->dev, "chip id mismatch: %x!=%x\n", +- LT6911UXE_CHIP_ID, val); +- return -ENXIO; ++ /* ++ * Check the number of MIPI CSI2 data lanes, ++ * lt6911uxe only support 4 lanes. ++ */ ++ if (bus_cfg.bus.mipi_csi2.num_data_lanes != LT6911UXE_DEFAULT_LANES) { ++ dev_err(dev, "only 4 data lanes are currently supported\n"); ++ goto out_err; + } + + return 0; ++ ++out_err: ++ v4l2_fwnode_endpoint_free(&bus_cfg); ++ return ret; + } + +-static int lt6911uxe_parse_gpio(struct lt6911uxe *lt6911uxe, struct device *dev) ++static int lt6911uxe_identify_module(struct lt6911uxe *lt6911uxe, ++ struct device *dev) + { +- lt6911uxe->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN); +- if (IS_ERR(lt6911uxe->reset_gpio)) +- return dev_err_probe(dev, PTR_ERR(lt6911uxe->reset_gpio), +- "failed to get reset gpio\n"); ++ u64 val; ++ int ret = 0; + +- lt6911uxe->irq_gpio = devm_gpiod_get(dev, "readystat", GPIOD_IN); +- if (IS_ERR(lt6911uxe->irq_gpio)) +- return dev_err_probe(dev, PTR_ERR(lt6911uxe->irq_gpio), +- "failed to get ready_stat gpio\n"); ++ /* Chip ID should be confirmed when the I2C slave is active */ ++ cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x1, &ret); ++ cci_read(lt6911uxe->regmap, REG_CHIP_ID, &val, &ret); ++ cci_write(lt6911uxe->regmap, REG_ENABLE_I2C, 0x0, &ret); ++ if (ret) ++ return dev_err_probe(dev, ret, "fail to read chip id\n"); + +- lt6911uxe->detect_gpio = devm_gpiod_get(dev, "hdmidetect", +- GPIOD_OUT_HIGH); +- if (IS_ERR(lt6911uxe->detect_gpio)) +- return dev_err_probe(dev, PTR_ERR(lt6911uxe->detect_gpio), +- "failed to get hdmi_detect gpio\n"); ++ if (val != LT6911UXE_CHIP_ID) { ++ return dev_err_probe(dev, -ENXIO, "chip id mismatch: %x!=%x\n", ++ LT6911UXE_CHIP_ID, (u16)val); ++ } + + return 0; + } +@@ -516,142 +488,142 @@ static int lt6911uxe_parse_gpio(struct lt6911uxe *lt6911uxe, struct device *dev) + static irqreturn_t lt6911uxe_threaded_irq_fn(int irq, void *dev_id) + { + struct v4l2_subdev *sd = dev_id; +- struct lt6911uxe *lt6911uxe; +- +- if (!sd) +- return IRQ_NONE; +- +- lt6911uxe = to_lt6911uxe(sd); +- if (!lt6911uxe) { +- dev_err(sd->dev, "Invalid lt6911uxe state argument!\n"); +- return IRQ_NONE; +- } ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); ++ struct v4l2_subdev_state *state; + +- lt6911uxe_video_status_update(lt6911uxe); +- lt6911uxe_audio_status_update(lt6911uxe); ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ lt6911uxe_status_update(lt6911uxe); ++ v4l2_subdev_unlock_state(state); + + return IRQ_HANDLED; + } + +-static void lt6911uxe_reset(struct lt6911uxe *lt6911uxe, int power) ++static void lt6911uxe_remove(struct i2c_client *client) + { +- if (lt6911uxe->reset_gpio || lt6911uxe->irq_gpio) +- return; ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct lt6911uxe *lt6911uxe = to_lt6911uxe(sd); + +- gpiod_set_value_cansleep(lt6911uxe->reset_gpio, power); +- gpiod_set_value_cansleep(lt6911uxe->irq_gpio, power); ++ free_irq(gpiod_to_irq(lt6911uxe->irq_gpio), lt6911uxe); ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(<6911uxe->ctrl_handler); ++ pm_runtime_disable(&client->dev); ++ pm_runtime_set_suspended(&client->dev); + } + + static int lt6911uxe_probe(struct i2c_client *client) + { + struct lt6911uxe *lt6911uxe; ++ struct device *dev = &client->dev; ++ u64 irq_pin_flags; + int ret; + +- lt6911uxe = devm_kzalloc(&client->dev, sizeof(struct lt6911uxe), +- GFP_KERNEL); ++ lt6911uxe = devm_kzalloc(dev, sizeof(*lt6911uxe), GFP_KERNEL); + if (!lt6911uxe) + return -ENOMEM; + +- lt6911uxe->cur_mode = devm_kzalloc(&client->dev, +- sizeof(struct lt6911uxe_mode), +- GFP_KERNEL); +- if (!lt6911uxe->cur_mode) +- return -ENOMEM; ++ /* define default mode: 4k@60fps, changed when interrupt occurs. */ ++ lt6911uxe->cur_mode = default_mode; + + lt6911uxe->regmap = devm_regmap_init_i2c(client, + <9611uxe_regmap_config); + if (IS_ERR(lt6911uxe->regmap)) +- return dev_err_probe(&client->dev, PTR_ERR(lt6911uxe->regmap), ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->regmap), + "failed to init CCI\n"); + + v4l2_i2c_subdev_init(<6911uxe->sd, client, <6911uxe_subdev_ops); + +- ret = lt6911uxe_parse_gpio(lt6911uxe, &client->dev); +- if (ret) { +- dev_err(&client->dev, "failed to get GPIO control: %d\n", ret); +- return ret; +- } ++ lt6911uxe->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN); ++ if (IS_ERR(lt6911uxe->reset_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->reset_gpio), ++ "failed to get reset gpio\n"); + +- lt6911uxe_reset(lt6911uxe, 1); +- usleep_range(10000, 10500); ++ lt6911uxe->irq_gpio = devm_gpiod_get(dev, "readystat", GPIOD_IN); ++ if (IS_ERR(lt6911uxe->irq_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxe->irq_gpio), ++ "failed to get ready_stat gpio\n"); + +- ret = lt6911uxe_identify_module(lt6911uxe); +- if (ret) { +- dev_err(&client->dev, "failed to find chip: %d\n", ret); ++ ret = lt6911uxe_fwnode_parse(lt6911uxe, dev); ++ if (ret) + return ret; +- } + +- /* Setting irq */ +- lt6911uxe->irq_pin_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | +- IRQF_ONESHOT; +- +- ret = devm_request_threaded_irq(&client->dev, +- gpiod_to_irq(lt6911uxe->irq_gpio), +- NULL, lt6911uxe_threaded_irq_fn, +- lt6911uxe->irq_pin_flags, +- "irq_gpio", lt6911uxe); +- if (ret) { +- dev_err(&client->dev, "IRQ request failed! ret: %d\n", ret); +- goto probe_error_v4l2_ctrl_handler_free; +- } ++ usleep_range(10000, 10500); ++ ++ ret = lt6911uxe_identify_module(lt6911uxe, dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to find chip\n"); + + ret = lt6911uxe_init_controls(lt6911uxe); +- if (ret) { +- dev_info(&client->dev, "Could not init control %d!\n", ret); +- goto probe_error_v4l2_ctrl_handler_free; +- } ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to init control\n"); + +- lt6911uxe->sd.state_lock = lt6911uxe->ctrl_handler.lock; +- lt6911uxe->sd.dev = &client->dev; ++ lt6911uxe->sd.dev = dev; + lt6911uxe->sd.internal_ops = <6911uxe_internal_ops; + lt6911uxe->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + lt6911uxe->sd.entity.ops = <6911uxe_subdev_entity_ops; +- lt6911uxe->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ lt6911uxe->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + lt6911uxe->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(<6911uxe->sd.entity, 1, <6911uxe->pad); + if (ret) { +- dev_err(&client->dev, "Init entity pads failed: %d\n", ret); +- goto probe_error_v4l2_ctrl_handler_free; +- } +- +- ret = v4l2_subdev_init_finalize(<6911uxe->sd); +- if (ret < 0) { +- dev_err(&client->dev, "v4l2 subdev init error: %d\n", ret); +- goto probe_error_media_entity_cleanup; ++ dev_err(dev, "failed to init entity pads: %d\n", ret); ++ goto err_v4l2_ctrl_handler_free; + } + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ +- pm_runtime_set_active(&client->dev); +- pm_runtime_enable(&client->dev); +- pm_runtime_idle(&client->dev); ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ pm_runtime_idle(dev); ++ ++ ret = v4l2_subdev_init_finalize(<6911uxe->sd); ++ if (ret) { ++ dev_err(dev, "failed to init v4l2 subdev: %d\n", ret); ++ goto err_media_entity_cleanup; ++ } ++ ++ /* Setting irq */ ++ irq_pin_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | ++ IRQF_ONESHOT; ++ ++ ret = request_threaded_irq(gpiod_to_irq(lt6911uxe->irq_gpio), NULL, ++ lt6911uxe_threaded_irq_fn, ++ irq_pin_flags, NULL, lt6911uxe); ++ if (ret) { ++ dev_err(dev, "failed to request IRQ: %d\n", ret); ++ goto err_subdev_cleanup; ++ } + + ret = v4l2_async_register_subdev_sensor(<6911uxe->sd); +- if (ret < 0) { +- dev_err(&client->dev, "failed to register V4L2 subdev: %d\n", +- ret); +- goto rpm; ++ if (ret) { ++ dev_err(dev, "failed to register V4L2 subdev: %d\n", ret); ++ goto err_free_irq; + } + + return 0; + +-rpm: +- pm_runtime_disable(&client->dev); ++err_free_irq: ++ free_irq(gpiod_to_irq(lt6911uxe->irq_gpio), lt6911uxe); ++ ++err_subdev_cleanup: + v4l2_subdev_cleanup(<6911uxe->sd); + +-probe_error_media_entity_cleanup: ++err_media_entity_cleanup: ++ pm_runtime_disable(dev); ++ pm_runtime_set_suspended(dev); + media_entity_cleanup(<6911uxe->sd.entity); + +-probe_error_v4l2_ctrl_handler_free: ++err_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(lt6911uxe->sd.ctrl_handler); ++ + return ret; + } + + static const struct acpi_device_id lt6911uxe_acpi_ids[] = { +- {"INTC10C5"}, ++ { "INTC10C5" }, + {} + }; + MODULE_DEVICE_TABLE(acpi, lt6911uxe_acpi_ids); +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0011-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch b/kernel_patches/patch_6.12_mainline/0011-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch new file mode 100644 index 000000000000..1a8006a01d44 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0011-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch @@ -0,0 +1,77 @@ +From 6a92440c987bb2a9fd062e0fbcd79d956bb88752 Mon Sep 17 00:00:00 2001 +From: Stanislaw Gruszka +Date: Thu, 31 Oct 2024 11:23:21 +0100 +Subject: [PATCH] media: intel/ipu6: do not handle interrupts when device + is disabled + +Some IPU6 devices have shared interrupts. We need to handle properly +case when interrupt is triggered from other device on shared irq line +and IPU6 itself disabled. In such case we get 0xffffffff from +ISR_STATUS register and handle all irq's cases, for what we are not +not prepared and usually hang the whole system. + +To avoid the issue use pm_runtime_get_if_active() to check if +the device is enabled and prevent suspending it when we handle irq +until the end of irq. Additionally use synchronize_irq() in suspend + +Signed-off-by: Stanislaw Gruszka +Reviewed-by: Hans de Goede +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/ipu6-buttress.c | 13 +++++++++---- + drivers/media/pci/intel/ipu6/ipu6.c | 3 +++ + 2 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +index e47f84c30e10..edaa285283a1 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c ++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +@@ -345,12 +345,16 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) + u32 disable_irqs = 0; + u32 irq_status; + u32 i, count = 0; ++ int active; + +- pm_runtime_get_noresume(&isp->pdev->dev); ++ active = pm_runtime_get_if_active(&isp->pdev->dev); ++ if (!active) ++ return IRQ_NONE; + + irq_status = readl(isp->base + reg_irq_sts); +- if (!irq_status) { +- pm_runtime_put_noidle(&isp->pdev->dev); ++ if (irq_status == 0 || WARN_ON_ONCE(irq_status == 0xffffffffu)) { ++ if (active > 0) ++ pm_runtime_put_noidle(&isp->pdev->dev); + return IRQ_NONE; + } + +@@ -426,7 +430,8 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) + writel(BUTTRESS_IRQS & ~disable_irqs, + isp->base + BUTTRESS_REG_ISR_ENABLE); + +- pm_runtime_put(&isp->pdev->dev); ++ if (active > 0) ++ pm_runtime_put(&isp->pdev->dev); + + return ret; + } +diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c +index 4bb270d9fe2a..08d1eb0f0223 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6.c ++++ b/drivers/media/pci/intel/ipu6/ipu6.c +@@ -783,6 +783,9 @@ static void ipu6_pci_reset_done(struct pci_dev *pdev) + */ + static int ipu6_suspend(struct device *dev) + { ++ struct pci_dev *pdev = to_pci_dev(dev); ++ ++ synchronize_irq(pdev->irq); + return 0; + } + +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0012-media-intel-ipu6-remove-buttress-ish-structure.patch b/kernel_patches/patch_6.12_mainline/0012-media-intel-ipu6-remove-buttress-ish-structure.patch new file mode 100644 index 000000000000..ed424ce239e0 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0012-media-intel-ipu6-remove-buttress-ish-structure.patch @@ -0,0 +1,164 @@ +From ba4dc3f84de77be3761c5053718315439b71c398 Mon Sep 17 00:00:00 2001 +From: Stanislaw Gruszka +Date: Tue, 5 Nov 2024 08:27:50 +0100 +Subject: [PATCH] media: intel/ipu6: remove buttress ish structure + +The buttress ipc ish structure is not effectively used on IPU6 - data +is nullified on init. Remove the ish structure and handing of related +interrupts to cleanup the code. + +Signed-off-by: Stanislaw Gruszka +Signed-off-by: Sakari Ailus +Signed-off-by: Hans Verkuil +Signed-off-by: zouxiaoh +--- + drivers/media/pci/intel/ipu6/ipu6-buttress.c | 29 +++---------------- + drivers/media/pci/intel/ipu6/ipu6-buttress.h | 6 ---- + .../intel/ipu6/ipu6-platform-buttress-regs.h | 2 -- + 3 files changed, 4 insertions(+), 33 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +index edaa285283a1..761e63262a8c 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-buttress.c ++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +@@ -214,20 +214,17 @@ static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, + } + + static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, +- enum ipu6_buttress_ipc_domain ipc_domain, + struct ipu6_ipc_buttress_bulk_msg *msgs, + u32 size) + { + unsigned long tx_timeout_jiffies, rx_timeout_jiffies; + unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; + struct ipu6_buttress *b = &isp->buttress; +- struct ipu6_buttress_ipc *ipc; ++ struct ipu6_buttress_ipc *ipc = &b->cse; + u32 val; + int ret; + int tout; + +- ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish; +- + mutex_lock(&b->ipc_mutex); + + ret = ipu6_buttress_ipc_validity_open(isp, ipc); +@@ -305,7 +302,6 @@ out: + + static int + ipu6_buttress_ipc_send(struct ipu6_device *isp, +- enum ipu6_buttress_ipc_domain ipc_domain, + u32 ipc_msg, u32 size, bool require_resp, + u32 expected_resp) + { +@@ -316,7 +312,7 @@ ipu6_buttress_ipc_send(struct ipu6_device *isp, + .expected_resp = expected_resp, + }; + +- return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1); ++ return ipu6_buttress_ipc_send_bulk(isp, &msg, 1); + } + + static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) +@@ -385,25 +381,12 @@ irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) + complete(&b->cse.recv_complete); + } + +- if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) { +- dev_dbg(&isp->pdev->dev, +- "BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n"); +- ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data); +- complete(&b->ish.recv_complete); +- } +- + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->cse.send_complete); + } + +- if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) { +- dev_dbg(&isp->pdev->dev, +- "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); +- complete(&b->ish.send_complete); +- } +- + if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + ipu6_buttress_get_secure_mode(isp)) + dev_err(&isp->pdev->dev, +@@ -655,7 +638,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) + */ + dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); + +- ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, ++ ret = ipu6_buttress_ipc_send(isp, + BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); +@@ -697,7 +680,7 @@ int ipu6_buttress_authenticate(struct ipu6_device *isp) + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); +- ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, ++ ret = ipu6_buttress_ipc_send(isp, + BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); +@@ -838,9 +821,7 @@ int ipu6_buttress_init(struct ipu6_device *isp) + mutex_init(&b->auth_mutex); + mutex_init(&b->cons_mutex); + mutex_init(&b->ipc_mutex); +- init_completion(&b->ish.send_complete); + init_completion(&b->cse.send_complete); +- init_completion(&b->ish.recv_complete); + init_completion(&b->cse.recv_complete); + + b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; +@@ -852,8 +833,6 @@ int ipu6_buttress_init(struct ipu6_device *isp) + b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + +- /* no ISH on IPU6 */ +- memset(&b->ish, 0, sizeof(b->ish)); + INIT_LIST_HEAD(&b->constraints); + + isp->secure_mode = ipu6_buttress_get_secure_mode(isp); +diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h +index 9b6f56958be7..482978c2a09d 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-buttress.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h +@@ -46,18 +46,12 @@ struct ipu6_buttress_ipc { + struct ipu6_buttress { + struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; + struct ipu6_buttress_ipc cse; +- struct ipu6_buttress_ipc ish; + struct list_head constraints; + u32 wdt_cached_value; + bool force_suspend; + u32 ref_clk; + }; + +-enum ipu6_buttress_ipc_domain { +- IPU6_BUTTRESS_IPC_CSE, +- IPU6_BUTTRESS_IPC_ISH, +-}; +- + struct ipu6_ipc_buttress_bulk_msg { + u32 cmd; + u32 expected_resp; +diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +index 20f27011df43..efd65e494c16 100644 +--- a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h ++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +@@ -219,8 +219,6 @@ enum { + BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ) + + #define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ +- BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ +- BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH | \ + BUTTRESS_ISR_SAI_VIOLATION) + #endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ +-- +2.34.1 + diff --git a/kernel_patches/patch_6.12_mainline/0013-media-i2c-add-support-for-lt6911uxc.patch b/kernel_patches/patch_6.12_mainline/0013-media-i2c-add-support-for-lt6911uxc.patch new file mode 100644 index 000000000000..93cb35079e8d --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0013-media-i2c-add-support-for-lt6911uxc.patch @@ -0,0 +1,48 @@ +From 1603fc83b2df1b3cd72b808825b4f9de0f689b18 Mon Sep 17 00:00:00 2001 +From: linya14x +Date: Wed, 27 Nov 2024 16:47:51 +0800 +Subject: [PATCH] media: i2c: add support for lt6911uxc + +Signed-off-by: linya14x +--- + drivers/media/i2c/Kconfig | 11 +++++++++++ + drivers/media/i2c/Makefile | 1 + + 2 files changed, 12 insertions(+) + +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 0d314a92ea4f..7c8ff44afd8c 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -51,6 +51,17 @@ config VIDEO_ALVIUM_CSI2 + To compile this driver as a module, choose M here: the + module will be called alvium-csi2. + ++config VIDEO_LT6911UXC ++ tristate "Lontium LT6911UXC decoder" ++ depends on ACPI || COMPILE_TEST ++ select V4L2_CCI_I2C ++ help ++ This is a Video4Linux2 sensor-level driver for the Lontium ++ LT6911UXC HDMI to MIPI CSI-2 bridge. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called lt6911uxc. ++ + config VIDEO_LT6911UXE + tristate "Lontium LT6911UXE decoder" + depends on ACPI || COMPILE_TEST +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index bf06c7fb08f6..e78f0d877194 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -19,6 +19,7 @@ obj-$(CONFIG_VIDEO_AK7375) += ak7375.o + obj-$(CONFIG_VIDEO_AK881X) += ak881x.o + obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o + obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o ++obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxc.o + obj-$(CONFIG_VIDEO_LT6911UXE) += lt6911uxe.o + obj-$(CONFIG_VIDEO_AR0234) += ar0234.o + obj-$(CONFIG_VIDEO_AR0521) += ar0521.o +-- +2.43.0 + diff --git a/kernel_patches/patch_6.12_mainline/0014-media-i2c-add-lt6911uxc-driver-and-enable-in-ipu-bridge.patch b/kernel_patches/patch_6.12_mainline/0014-media-i2c-add-lt6911uxc-driver-and-enable-in-ipu-bridge.patch new file mode 100644 index 000000000000..e2ded89795b1 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/0014-media-i2c-add-lt6911uxc-driver-and-enable-in-ipu-bridge.patch @@ -0,0 +1,739 @@ +From bf143d3ad6bfc8e1de51496f0c93115dff8fba0f Mon Sep 17 00:00:00 2001 +From: linya14x +Date: Tue, 3 Dec 2024 12:09:27 +0800 +Subject: [PATCH] media: i2c: add lt6911uxc driver and enable in ipu-bridge + +Signed-off-by: linya14x +--- + drivers/media/i2c/lt6911uxc.c | 705 +++++++++++++++++++++++++++ + drivers/media/pci/intel/ipu-bridge.c | 2 + + 2 files changed, 707 insertions(+) + create mode 100644 drivers/media/i2c/lt6911uxc.c + +diff --git a/drivers/media/i2c/lt6911uxc.c b/drivers/media/i2c/lt6911uxc.c +new file mode 100644 +index 000000000000..614343e0a447 +--- /dev/null ++++ b/drivers/media/i2c/lt6911uxc.c +@@ -0,0 +1,705 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2022-2024 Intel Corporation. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* FPS registers */ ++#define MASK_FMI_FREQ2 0x0FFFFF ++ ++#define LT6911UXC_CHIP_ID 0x1704 ++#define LT6911UXC_REG_CHIP_ID CCI_REG16(0x8100) ++ ++#define REG_BKB0_A2_REG CCI_REG8(0xB0A2) ++ ++#define REG_ENABLE_I2C CCI_REG8(0x80EE) ++#define REG_DISABLE_WD CCI_REG8(0x8010) ++#define REG_PIX_CLK CCI_REG24(0x8750) ++#define REG_BYTE_CLK CCI_REG24(0x8548) ++#define REG_INT_RESPOND CCI_REG8(0x86A6) ++ ++#define REG_AD_HALF_PCLK CCI_REG8(0x8540) ++ ++#define REG_H_TOTAL CCI_REG16(0x867C) /* horizontal half total pixel */ ++#define REG_V_TOTAL CCI_REG16(0x867A) /* vertical total lines */ ++ ++#define REG_H_ACTIVE CCI_REG16(0x8680) /* horizontal half active pixel */ ++#define REG_V_ACTIVE CCI_REG16(0x867E) /* vertical active lines */ ++ ++#define REG_MIPI_TX_CTRL CCI_REG8(0x811D) ++#define REG_MIPI_LANES CCI_REG8(0x86A2) ++ ++#define REG_INT_HDMI CCI_REG8(0x86A3) ++#define INT_HDMI_DISCONNECT 0x88 ++#define INT_HDMI_STABLE 0x55 ++ ++ ++#define REG_INT_AUDIO CCI_REG8(0x86A5) ++#define INT_AUDIO_SR_HIGH CCI_REG8(0x55) ++#define INT_AUDIO_SR_LOW CCI_REG8(0xAA) ++#define REG_AUDIO_SR CCI_REG8(0xB0AB) ++ ++#define INT_AUDIO_DISCONNECT 0x88 ++ ++#define LT6911UXC_DEFAULT_WIDTH 3840 ++#define LT6911UXC_DEFAULT_HEIGHT 2160 ++#define LT6911_PAGE_CONTROL 0xff ++ ++#define LT6911UXC_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_BASE | 0x1007) ++#define LT6911UXC_CID_AUDIO_PRESENT (V4L2_CID_USER_BASE | 0x1008) ++ ++static const struct regmap_range_cfg lt6911uxc_ranges[] = { ++ { ++ .name = "register_range", ++ .range_min = 0, ++ .range_max = 0xffff, ++ .selector_reg = LT6911_PAGE_CONTROL, ++ .selector_mask = 0xff, ++ .selector_shift = 0, ++ .window_start = 0, ++ .window_len = 0x100, ++ }, ++}; ++ ++static const struct regmap_config lt6911uxc_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0xffff, ++ .ranges = lt6911uxc_ranges, ++ .num_ranges = ARRAY_SIZE(lt6911uxc_ranges), ++}; ++ ++struct lt6911uxc_mode { ++ u32 width; ++ u32 height; ++ u32 code; ++ s32 lanes; ++ u32 fps; ++ s64 link_freq; ++ ++ /* Audio sample rate*/ ++ u32 audio_sampling_rate; ++}; ++ ++struct lt6911uxc { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *audio_sampling_rate; ++ struct v4l2_ctrl *audio_present; ++ struct lt6911uxc_mode *cur_mode; ++ struct regmap *regmap; ++ unsigned int irq_pin_flags; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *irq_gpio; ++ struct gpio_desc *detect_gpio; ++ ++ bool auxiliary_port; ++}; ++ ++static const struct v4l2_event lt6911uxc_ev_source_change = { ++ .type = V4L2_EVENT_SOURCE_CHANGE, ++ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, ++}; ++ ++static const struct v4l2_event lt6911uxc_ev_stream_end = { ++ .type = V4L2_EVENT_EOS, ++}; ++ ++static inline struct lt6911uxc *to_lt6911uxc(struct v4l2_subdev *sd) ++{ ++ return container_of(sd, struct lt6911uxc, sd); ++} ++ ++static const struct v4l2_ctrl_config lt6911uxc_audio_sampling_rate = { ++ .id = LT6911UXC_CID_AUDIO_SAMPLING_RATE, ++ .name = "Audio Sampling Rate", ++ .type = V4L2_CTRL_TYPE_INTEGER, ++ .min = 32000, ++ .max = 192000, ++ .step = 100, ++ .def = 48000, ++ .flags = V4L2_CTRL_FLAG_READ_ONLY, ++}; ++ ++static const struct v4l2_ctrl_config lt6911uxc_audio_present = { ++ .id = LT6911UXC_CID_AUDIO_PRESENT, ++ .name = "Audio Present", ++ .type = V4L2_CTRL_TYPE_BOOLEAN, ++ .min = 0, ++ .max = 1, ++ .step = 1, ++ .def = 0, ++ .flags = V4L2_CTRL_FLAG_READ_ONLY, ++}; ++ ++static u64 get_pixel_rate(struct lt6911uxc *lt6911uxc) ++{ ++ u64 pixel_rate; ++ ++ if (!lt6911uxc->cur_mode->width) ++ return 0; ++ ++ pixel_rate = (u64)lt6911uxc->cur_mode->width * ++ lt6911uxc->cur_mode->height * ++ lt6911uxc->cur_mode->fps * 16; ++ do_div(pixel_rate, lt6911uxc->cur_mode->lanes); ++ ++ return pixel_rate; ++} ++ ++static int lt6911uxc_get_audio_sampling_rate(struct lt6911uxc *lt6911uxc) ++{ ++ int audio_fs, idx; ++ u64 fs; ++ static const int eps = 1500; ++ static const int rates_default[] = { ++ 32000, 44100, 48000, 88200, 96000, 176400, 192000 ++ }; ++ ++ cci_read(lt6911uxc->regmap, REG_AUDIO_SR, &fs, NULL); ++ audio_fs = fs * 1000; ++ ++ /* audio_fs is an approximation of sample rate - search nearest */ ++ for (idx = 0; idx < ARRAY_SIZE(rates_default); ++idx) { ++ if ((rates_default[idx] - eps < audio_fs) && ++ (rates_default[idx] + eps > audio_fs)) ++ return rates_default[idx]; ++ } ++ ++ return 0; ++} ++ ++static int lt6911uxc_status_update(struct lt6911uxc *lt6911uxc) ++{ ++ u64 int_event; ++ u64 byte_clock; ++ u64 tmds_clk; ++ u64 pixel_clk, is_hdmi_2_0, fps, lanes; ++ u64 htotal, vtotal, half_width, height; ++ ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x1, NULL); ++ cci_write(lt6911uxc->regmap, REG_DISABLE_WD, 0x0, NULL); ++ /* Read interrupt event */ ++ cci_read(lt6911uxc->regmap, REG_INT_HDMI, &int_event, NULL); ++ switch (int_event) { ++ case INT_HDMI_STABLE: ++ dev_info(&client->dev, "Video signal stable\n"); ++ ++ cci_write(lt6911uxc->regmap, REG_AD_HALF_PCLK, 0x1B, NULL); ++ usleep_range(10000, 10100); ++ ++ cci_read(lt6911uxc->regmap, REG_BYTE_CLK, &byte_clock, NULL); ++ byte_clock = (byte_clock & MASK_FMI_FREQ2) * 1000; ++ ++ cci_read(lt6911uxc->regmap, REG_PIX_CLK, &tmds_clk, NULL); ++ tmds_clk = tmds_clk & MASK_FMI_FREQ2; ++ ++ cci_read(lt6911uxc->regmap, REG_BKB0_A2_REG, &is_hdmi_2_0, NULL); ++ is_hdmi_2_0 = is_hdmi_2_0 & (1<<0) ? !0 : !!0; ++ pixel_clk = (is_hdmi_2_0 ? 4 * tmds_clk : tmds_clk) * 1000; ++ ++ cci_read(lt6911uxc->regmap, REG_H_TOTAL, &htotal, NULL); ++ cci_read(lt6911uxc->regmap, REG_V_TOTAL, &vtotal, NULL); ++ cci_read(lt6911uxc->regmap, REG_H_ACTIVE, &half_width, NULL); ++ cci_read(lt6911uxc->regmap, REG_V_ACTIVE, &height, NULL); ++ cci_read(lt6911uxc->regmap, REG_MIPI_LANES, &lanes, NULL); ++ ++ if (htotal && vtotal) ++ fps = div_u64(pixel_clk, htotal * 2 * vtotal); ++ ++ lt6911uxc->cur_mode->height = height; ++ lt6911uxc->cur_mode->width = half_width * 2; ++ lt6911uxc->cur_mode->fps = fps; ++ lt6911uxc->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; ++ lt6911uxc->cur_mode->lanes = lanes; ++ lt6911uxc->cur_mode->link_freq = byte_clock; ++ ++ ++ if (lt6911uxc->cur_mode->lanes == 8) { ++ /* 4K60fps with 2 MIPI ports*/ ++ if (lt6911uxc->cur_mode->width >= 3840) ++ lt6911uxc->cur_mode->width /= 2; /* YUV422 */ ++ ++ lt6911uxc->cur_mode->lanes /= 2; ++ lt6911uxc->cur_mode->link_freq /= 2; ++ } ++ v4l2_subdev_notify_event(<6911uxc->sd, ++ <6911uxc_ev_source_change); ++ ++ dev_dbg(&client->dev, "Pixel Clk:%llu, %lld\n", ++ pixel_clk, lt6911uxc->cur_mode->link_freq); ++ dev_dbg(&client->dev, ++ "width:%u, height:%u, fps:%u, lanes: %d\n", ++ lt6911uxc->cur_mode->width, ++ lt6911uxc->cur_mode->height, ++ lt6911uxc->cur_mode->fps, ++ lt6911uxc->cur_mode->lanes); ++ break; ++ case INT_HDMI_DISCONNECT: ++ cci_write(lt6911uxc->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ lt6911uxc->cur_mode->height = 0; ++ lt6911uxc->cur_mode->width = 0; ++ lt6911uxc->cur_mode->fps = fps; ++ ++ lt6911uxc->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; ++ ++ lt6911uxc->cur_mode->lanes = 4; ++ lt6911uxc->cur_mode->link_freq = 0; ++ v4l2_subdev_notify_event(<6911uxc->sd, ++ <6911uxc_ev_stream_end); ++ ++ dev_info(&client->dev, "Video signal disconnected\n"); ++ break; ++ default: ++ dev_dbg(&client->dev, "Unhandled video= 0x%02llX\n", int_event); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ return -ENOLINK; ++ } ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ return 0; ++} ++ ++static int lt6911uxc_init_controls(struct lt6911uxc *lt6911uxc) ++{ ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ int ret; ++ ++ ctrl_hdlr = <6911uxc->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); ++ if (ret) ++ return ret; ++ ++ lt6911uxc->link_freq = ++ v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, ++ sizeof(lt6911uxc->cur_mode->link_freq), ++ 0, <6911uxc->cur_mode->link_freq); ++ ++ lt6911uxc->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, ++ V4L2_CID_PIXEL_RATE, ++ get_pixel_rate(lt6911uxc), ++ get_pixel_rate(lt6911uxc), 1, ++ get_pixel_rate(lt6911uxc)); ++ ++ /* custom v4l2 audio controls */ ++ lt6911uxc->audio_sampling_rate = ++ v4l2_ctrl_new_custom(ctrl_hdlr, ++ <6911uxc_audio_sampling_rate, ++ NULL); ++ ++ lt6911uxc->audio_present = ++ v4l2_ctrl_new_custom(ctrl_hdlr, ++ <6911uxc_audio_present, ++ NULL); ++ if (ctrl_hdlr->error) { ++ ret = ctrl_hdlr->error; ++ goto hdlr_free; ++ } ++ ++ lt6911uxc->sd.ctrl_handler = ctrl_hdlr; ++ return 0; ++hdlr_free: ++ v4l2_ctrl_handler_free(ctrl_hdlr); ++ return ret; ++} ++ ++static void lt6911uxc_update_pad_format(const struct lt6911uxc_mode *mode, ++ struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->width = mode->width; ++ fmt->height = mode->height; ++ fmt->code = mode->code; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int lt6911uxc_start_streaming(struct lt6911uxc *lt6911uxc) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); ++ int ret; ++ ++ if (lt6911uxc->auxiliary_port == true) ++ return 0; ++ ++ ret = pm_runtime_resume_and_get(&client->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = __v4l2_ctrl_handler_setup(lt6911uxc->sd.ctrl_handler); ++ if (ret) ++ goto err_rpm_put; ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x1, NULL); ++ cci_write(lt6911uxc->regmap, REG_DISABLE_WD, 0x00, NULL); ++ cci_write(lt6911uxc->regmap, REG_MIPI_TX_CTRL, 0xFB, NULL); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ ++ if (ret) { ++ dev_err(&client->dev, "failed to start stream\n"); ++ goto err_rpm_put; ++ } ++ ++ return 0; ++ ++err_rpm_put: ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static int lt6911uxc_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ struct v4l2_subdev_state *state; ++ int ret = 0; ++ ++ if (lt6911uxc->auxiliary_port == true) ++ return 0; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (enable) { ++ dev_dbg(sd->dev, "[%s()], start streaming.\n", __func__); ++ ret = lt6911uxc_start_streaming(lt6911uxc); ++ ++ } else { ++ dev_dbg(sd->dev, "[%s()], stop streaming.\n", __func__); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x1, NULL); ++ cci_write(lt6911uxc->regmap, REG_DISABLE_WD, 0x00, NULL); ++ cci_write(lt6911uxc->regmap, REG_MIPI_TX_CTRL, 0x0, NULL); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ } ++ v4l2_subdev_unlock_state(state); ++ ++ return ret; ++} ++ ++static int lt6911uxc_set_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ u64 pixel_rate, link_freq; ++ ++ lt6911uxc_update_pad_format(lt6911uxc->cur_mode, &fmt->format); ++ ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format; ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ return 0; ++ ++ pixel_rate = get_pixel_rate(lt6911uxc); ++ ++ __v4l2_ctrl_modify_range(lt6911uxc->pixel_rate, pixel_rate, ++ pixel_rate, 1, pixel_rate); ++ ++ link_freq = lt6911uxc->cur_mode->link_freq; ++ ++ __v4l2_ctrl_modify_range(lt6911uxc->link_freq, link_freq, ++ link_freq, 1, link_freq); ++ ++ return 0; ++} ++ ++static int lt6911uxc_get_format(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++ fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad); ++ else ++ lt6911uxc_update_pad_format(lt6911uxc->cur_mode, &fmt->format); ++ ++ return 0; ++} ++ ++static int lt6911uxc_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ ++ code->code = lt6911uxc->cur_mode->code; ++ return 0; ++} ++ ++static int lt6911uxc_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ ++ fse->min_width = lt6911uxc->cur_mode->width; ++ fse->max_width = fse->min_width; ++ fse->min_height = lt6911uxc->cur_mode->height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int lt6911uxc_enum_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_interval_enum *fie) ++{ ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ ++ fie->interval.numerator = 1; ++ fie->interval.denominator = lt6911uxc->cur_mode->fps; ++ ++ return 0; ++} ++ ++static int lt6911uxc_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ struct v4l2_subdev_format fmt = { ++ .which = sd_state ? V4L2_SUBDEV_FORMAT_TRY ++ : V4L2_SUBDEV_FORMAT_ACTIVE, ++ }; ++ ++ return lt6911uxc_set_format(sd, sd_state, &fmt); ++} ++ ++static const struct v4l2_subdev_video_ops lt6911uxc_video_ops = { ++ .s_stream = lt6911uxc_s_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops lt6911uxc_pad_ops = { ++ .set_fmt = lt6911uxc_set_format, ++ .get_fmt = lt6911uxc_get_format, ++ .enum_mbus_code = lt6911uxc_enum_mbus_code, ++ .enum_frame_size = lt6911uxc_enum_frame_size, ++ .enum_frame_interval = lt6911uxc_enum_frame_interval, ++}; ++ ++static const struct v4l2_subdev_core_ops lt6911uxc_subdev_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_ops lt6911uxc_subdev_ops = { ++ .core = <6911uxc_subdev_core_ops, ++ .video = <6911uxc_video_ops, ++ .pad = <6911uxc_pad_ops, ++}; ++ ++static const struct media_entity_operations lt6911uxc_subdev_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static const struct v4l2_subdev_internal_ops lt6911uxc_internal_ops = { ++ .init_state = lt6911uxc_init_state, ++}; ++ ++static void lt6911uxc_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ ++ if (!lt6911uxc->auxiliary_port) { ++ v4l2_async_unregister_subdev(<6911uxc->sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(<6911uxc->sd.entity); ++ v4l2_ctrl_handler_free(<6911uxc->ctrl_handler); ++ pm_runtime_disable(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++ } ++} ++ ++static int lt6911uxc_identify_module(struct lt6911uxc *lt6911uxc, ++ struct device *dev) ++{ ++ u64 val; ++ int ret = 0; ++ ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x1, NULL); ++ cci_read(lt6911uxc->regmap, LT6911UXC_REG_CHIP_ID, &val, NULL); ++ cci_write(lt6911uxc->regmap, REG_ENABLE_I2C, 0x0, NULL); ++ if (ret) ++ return dev_err_probe(dev, ret, "fail to read chip id\n"); ++ ++ if (val != LT6911UXC_CHIP_ID) { ++ return dev_err_probe(dev, -ENXIO, "chip id mismatch: %x!=%x\n", ++ LT6911UXC_CHIP_ID, (u16)val); ++ } ++ ++ return 0; ++} ++ ++static int lt6911uxc_parse_gpio(struct lt6911uxc *lt6911uxc, struct device *dev) ++{ ++ lt6911uxc->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN); ++ if (IS_ERR(lt6911uxc->reset_gpio)) { ++ lt6911uxc->auxiliary_port = true; ++ return dev_err_probe(dev, PTR_ERR(lt6911uxc->reset_gpio), ++ "failed to get reset gpio\n"); ++ } ++ ++ lt6911uxc->irq_gpio = devm_gpiod_get(dev, "readystat", GPIOD_IN); ++ if (IS_ERR(lt6911uxc->irq_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxc->irq_gpio), ++ "failed to get ready_stat gpio\n"); ++ ++ lt6911uxc->detect_gpio = devm_gpiod_get(dev, "hdmidetect", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(lt6911uxc->detect_gpio)) ++ return dev_err_probe(dev, PTR_ERR(lt6911uxc->detect_gpio), ++ "failed to get hdmi_detect gpio\n"); ++ ++ return 0; ++} ++ ++static irqreturn_t lt6911uxc_threaded_irq_fn(int irq, void *dev_id) ++{ ++ struct v4l2_subdev *sd = dev_id; ++ struct lt6911uxc *lt6911uxc = to_lt6911uxc(sd); ++ struct v4l2_subdev_state *state; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ lt6911uxc_status_update(lt6911uxc); ++ v4l2_subdev_unlock_state(state); ++ ++ return IRQ_HANDLED; ++} ++ ++static int lt6911uxc_probe(struct i2c_client *client) ++{ ++ struct device *dev = &client->dev; ++ struct lt6911uxc *lt6911uxc; ++ int ret; ++ ++ lt6911uxc = devm_kzalloc(&client->dev, sizeof(struct lt6911uxc), ++ GFP_KERNEL); ++ if (!lt6911uxc) ++ return -ENOMEM; ++ ++ lt6911uxc->cur_mode = devm_kzalloc(&client->dev, ++ sizeof(struct lt6911uxc_mode), ++ GFP_KERNEL); ++ if (!lt6911uxc->cur_mode) ++ return -ENOMEM; ++ ++ lt6911uxc->regmap = devm_regmap_init_i2c(client, ++ <6911uxc_regmap_config); ++ ++ if (IS_ERR(lt6911uxc->regmap)) ++ return dev_err_probe(&client->dev, PTR_ERR(lt6911uxc->regmap), ++ "failed to init CCI\n"); ++ ++ v4l2_i2c_subdev_init(<6911uxc->sd, client, <6911uxc_subdev_ops); ++ ++ ret = lt6911uxc_parse_gpio(lt6911uxc, &client->dev); ++ if (ret) { ++ dev_err(&client->dev, "failed to get GPIO control: %d\n", ret); ++ return ret; ++ } ++ ++ usleep_range(10000, 10500); ++ ++ ret = lt6911uxc_identify_module(lt6911uxc, dev); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to find chip\n"); ++ ++ ret = lt6911uxc_init_controls(lt6911uxc); ++ if (ret) ++ return dev_err_probe(dev, ret, "failed to init control\n"); ++ ++ lt6911uxc->sd.dev = &client->dev; ++ lt6911uxc->sd.internal_ops = <6911uxc_internal_ops; ++ lt6911uxc->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS; ++ lt6911uxc->sd.entity.ops = <6911uxc_subdev_entity_ops; ++ lt6911uxc->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ lt6911uxc->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(<6911uxc->sd.entity, 1, <6911uxc->pad); ++ if (ret) { ++ dev_err(&client->dev, "Init entity pads failed:%d\n", ret); ++ goto err_v4l2_ctrl_handler_free; ++ } ++ ++ /* ++ * Device is already turned on by i2c-core with ACPI domain PM. ++ * Enable runtime PM and turn off the device. ++ */ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_enable(&client->dev); ++ pm_runtime_idle(&client->dev); ++ ++ ret = v4l2_subdev_init_finalize(<6911uxc->sd); ++ if (ret) { ++ dev_err(dev, "failed to init v4l2 subdev: %d\n", ret); ++ goto err_media_entity_cleanup; ++ } ++ ++ /* Setting irq */ ++ lt6911uxc->irq_pin_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | ++ IRQF_ONESHOT; ++ ++ ret = request_threaded_irq(gpiod_to_irq(lt6911uxc->irq_gpio), NULL, ++ lt6911uxc_threaded_irq_fn, ++ lt6911uxc->irq_pin_flags, NULL, lt6911uxc); ++ if (ret) { ++ dev_err(dev, "failed to request IRQ: %d\n", ret); ++ goto err_subdev_cleanup; ++ } ++ ++ ret = v4l2_async_register_subdev_sensor(<6911uxc->sd); ++ if (ret) { ++ dev_err(dev, "failed to register V4L2 subdev: %d\n", ret); ++ goto err_free_irq; ++ } ++ ++ dev_info(&client->dev, "%s Probe Succeeded", lt6911uxc->sd.name); ++ ++ return 0; ++ ++err_free_irq: ++ free_irq(gpiod_to_irq(lt6911uxc->irq_gpio), lt6911uxc); ++ ++err_subdev_cleanup: ++ v4l2_subdev_cleanup(<6911uxc->sd); ++ ++err_media_entity_cleanup: ++ pm_runtime_disable(dev); ++ pm_runtime_set_suspended(dev); ++ media_entity_cleanup(<6911uxc->sd.entity); ++ ++err_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(lt6911uxc->sd.ctrl_handler); ++ ++ return ret; ++} ++ ++static const struct acpi_device_id lt6911uxc_acpi_ids[] = { ++ { "INTC10B1"}, ++ {} ++}; ++MODULE_DEVICE_TABLE(acpi, lt6911uxc_acpi_ids); ++ ++static struct i2c_driver lt6911uxc_i2c_driver = { ++ .driver = { ++ .name = "lt6911uxc", ++ .acpi_match_table = ACPI_PTR(lt6911uxc_acpi_ids), ++ }, ++ .probe = lt6911uxc_probe, ++ .remove = lt6911uxc_remove, ++}; ++ ++module_i2c_driver(lt6911uxc_i2c_driver); ++ ++MODULE_AUTHOR("Zou, Xiaohong xiaohong.zou@intel.com"); ++MODULE_DESCRIPTION("lt6911uxc HDMI to MIPI Bridge Driver"); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c +index 07afadbe550a..cfd5d89f4224 100644 +--- a/drivers/media/pci/intel/ipu-bridge.c ++++ b/drivers/media/pci/intel/ipu-bridge.c +@@ -87,6 +87,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { + IPU_SENSOR_CONFIG("INTC10C0", 1, 360000000), + /* Lontium lt6911uxe */ + IPU_SENSOR_CONFIG("INTC10C5", 0), ++ /* Lontium lt6911uxc */ ++ IPU_SENSOR_CONFIG("INTC10B1", 0), + }; + + static const struct ipu_property_names prop_names = { +-- +2.43.0 + diff --git a/kernel_patches/patch_6.12_mainline/README b/kernel_patches/patch_6.12_mainline/README new file mode 100644 index 000000000000..fe41ffe71c68 --- /dev/null +++ b/kernel_patches/patch_6.12_mainline/README @@ -0,0 +1,34 @@ +build kernel driver with community kernel version 6.11 but not iot kernel + +1. checkout community kernel 6.12 +2. copy patches to kernel source tree and apply them + a. for ar0234 & lt6911uxc & lt6911uxe: + git am 0001-media-i2c-Add-ar0234-camera-sensor-driver.patch + git am 0002-media-i2c-add-support-for-lt6911uxe.patch + git am 0003-INT3472-Support-LT6911UXE.patch + git am 0004-upstream-Use-module-parameter-to-set-isys-freq.patch + git am 0005-upstream-Use-module-parameter-to-set-psys-freq.patch + git am 0006-media-pci-Enable-ISYS-reset.patch + git am 0007-media-i2c-add-support-for-ar0234-and-lti6911uxe.patch + git am 0008-driver-media-i2c-remove-useless-header-file.patch + git am 0010-media-i2c-update-lt6911uxe-driver-for-upstream-and-b.patch + git am 0011-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch + git am 0012-media-intel-ipu6-remove-buttress-ish-structure.patch + git am 0013-media-i2c-add-support-for-lt6911uxc.patch + git am 0014-media-i2c-add-lt6911uxc-driver-and-enable-in-ipu-bridge.patch + + b. for PSYS driver (the patch be integrated in NEX kernel 6.12): + git am 0001-media-pci-intel-psys-driver.patch + + c. for USB device need to apply below 2 patches, they are merged to kernel 6.13. + 0001-media-intel-ipu6-do-not-handle-interrupts-when-devic.patch + 0002-media-intel-ipu6-remove-buttress-ish-structure.patch + +3. build kernel driver + copy kernel config to kernel source dir + + KERNEL_BUILD_LABEL="-for-upstream" + make kernelversion + echo "" | make -j16 LOCALVERSION=${KERNEL_BUILD_LABEL} oldconfig + make -j16 LOCALVERSION=${KERNEL_BUILD_LABEL} + make -j16 LOCALVERSION=${KERNEL_BUILD_LABEL} bindeb-pkg