From 0160a04208b462b7591d9ebf9b5b26782a157258 Mon Sep 17 00:00:00 2001 From: jiabinhe Date: Tue, 24 Sep 2024 09:55:14 +0800 Subject: [PATCH 1/7] support IMX390 and ISX031 Signed-off-by: jiabinhe --- drivers/media/i2c/ar0234.c | 2289 +++++++ drivers/media/i2c/d4xx.c | 5400 +++++++++++++++++ drivers/media/i2c/imx390-mode-1280x960-CROP.h | 3089 ++++++++++ drivers/media/i2c/imx390.c | 2046 +++++++ .../imx390_mode_1920x1200HDR3_CUST_PWL12.h | 3891 ++++++++++++ drivers/media/i2c/isx031.c | 636 ++ drivers/media/i2c/lt6911uxc.c | 1563 +++++ drivers/media/i2c/lt6911uxe.c | 1590 +++++ drivers/media/i2c/ti953-ser.c | 193 + drivers/media/i2c/ti953.h | 160 + drivers/media/i2c/ti960-des.c | 1740 ++++++ drivers/media/i2c/ti960-reg.h | 153 + drivers/media/i2c/ti964-reg.h | 128 + drivers/media/i2c/ti964.c | 1342 ++++ drivers/media/pci/intel/Kconfig | 64 + drivers/media/pci/intel/Makefile | 11 +- drivers/media/pci/intel/ipu-bus.c | 12 - drivers/media/pci/intel/ipu-buttress.c | 55 +- drivers/media/pci/intel/ipu-buttress.h | 1 + drivers/media/pci/intel/ipu-cpd.c | 12 - drivers/media/pci/intel/ipu-dma.c | 88 - drivers/media/pci/intel/ipu-fw-com.c | 8 - .../media/pci/intel/ipu-isys-csi2-be-soc.c | 175 +- drivers/media/pci/intel/ipu-isys-csi2-be.c | 57 - drivers/media/pci/intel/ipu-isys-csi2-be.h | 14 +- drivers/media/pci/intel/ipu-isys-csi2.c | 230 +- drivers/media/pci/intel/ipu-isys-csi2.h | 10 +- drivers/media/pci/intel/ipu-isys-media.h | 80 - drivers/media/pci/intel/ipu-isys-queue.c | 532 +- drivers/media/pci/intel/ipu-isys-queue.h | 23 +- drivers/media/pci/intel/ipu-isys-subdev.c | 216 +- drivers/media/pci/intel/ipu-isys-subdev.h | 63 - drivers/media/pci/intel/ipu-isys-video.c | 895 +-- drivers/media/pci/intel/ipu-isys-video.h | 53 +- drivers/media/pci/intel/ipu-isys.c | 1107 +--- drivers/media/pci/intel/ipu-isys.h | 55 +- drivers/media/pci/intel/ipu-mmu.c | 8 - drivers/media/pci/intel/ipu-pdata.h | 3 + drivers/media/pci/intel/ipu-trace.c | 23 - drivers/media/pci/intel/ipu.c | 219 +- drivers/media/pci/intel/ipu.h | 17 +- drivers/media/pci/intel/ipu6/Makefile | 29 +- .../intel/ipu6/ipu-platform-isys-csi2-reg.h | 4 +- .../media/pci/intel/ipu6/ipu-platform-isys.h | 3 + drivers/media/pci/intel/ipu6/ipu-platform.h | 8 +- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 25 +- .../media/pci/intel/ipu6/ipu6-isys-dwc-phy.c | 25 - drivers/media/pci/intel/ipu6/ipu6-isys-gpc.c | 7 - drivers/media/pci/intel/ipu6/ipu6-isys-phy.c | 91 +- drivers/media/pci/intel/ipu6/ipu6-isys-phy.h | 5 + drivers/media/pci/intel/psys/Makefile | 35 + drivers/media/pci/intel/psys/ipu-fw-psys.c | 431 ++ drivers/media/pci/intel/psys/ipu-fw-psys.h | 382 ++ .../media/pci/intel/psys/ipu-fw-resources.c | 103 + .../media/pci/intel/psys/ipu-platform-psys.h | 78 + .../pci/intel/psys/ipu-platform-resources.h | 103 + .../media/pci/intel/psys/ipu-psys-compat32.c | 222 + drivers/media/pci/intel/psys/ipu-psys.c | 1775 ++++++ drivers/media/pci/intel/psys/ipu-psys.h | 224 + drivers/media/pci/intel/psys/ipu-resources.c | 868 +++ .../media/pci/intel/psys/ipu6-fw-resources.c | 609 ++ .../media/pci/intel/psys/ipu6-l-scheduler.c | 615 ++ .../pci/intel/psys/ipu6-platform-resources.h | 196 + drivers/media/pci/intel/psys/ipu6-ppg.c | 561 ++ drivers/media/pci/intel/psys/ipu6-ppg.h | 38 + drivers/media/pci/intel/psys/ipu6-psys-gpc.c | 209 + drivers/media/pci/intel/psys/ipu6-psys.c | 1020 ++++ .../pci/intel/psys/ipu6ep-fw-resources.c | 393 ++ .../intel/psys/ipu6ep-platform-resources.h | 42 + .../pci/intel/psys/ipu6se-fw-resources.c | 194 + .../intel/psys/ipu6se-platform-resources.h | 103 + drivers/media/platform/intel/Kconfig | 24 + drivers/media/platform/intel/Makefile | 16 + .../media/platform/intel/ipu6-acpi-common.c | 367 ++ .../media/platform/intel/ipu6-acpi-pdata.c | 986 +++ drivers/media/platform/intel/ipu6-acpi.c | 232 + .../media/platform/intel/ipu6-adlrvp-pdata.c | 748 +++ .../media/platform/intel/ipu6-tglrvp-pdata.c | 613 ++ include/media/ar0234.h | 24 + include/media/d4xx_pdata.h | 30 + include/media/imx390.h | 24 + include/media/ipu-acpi-pdata.h | 113 + include/media/ipu-acpi.h | 205 + include/media/ipu-isys.h | 59 +- include/media/isx031.h | 25 + include/media/lt6911uxc.h | 24 + include/media/lt6911uxe.h | 24 + include/media/ti960.h | 113 + include/media/ti964.h | 71 + include/uapi/linux/ipu-isys.h | 3 + 90 files changed, 37931 insertions(+), 2419 deletions(-) create mode 100644 drivers/media/i2c/ar0234.c create mode 100644 drivers/media/i2c/d4xx.c create mode 100644 drivers/media/i2c/imx390-mode-1280x960-CROP.h create mode 100644 drivers/media/i2c/imx390.c create mode 100644 drivers/media/i2c/imx390_mode_1920x1200HDR3_CUST_PWL12.h create mode 100644 drivers/media/i2c/isx031.c create mode 100644 drivers/media/i2c/lt6911uxc.c create mode 100644 drivers/media/i2c/lt6911uxe.c create mode 100644 drivers/media/i2c/ti953-ser.c create mode 100644 drivers/media/i2c/ti953.h create mode 100644 drivers/media/i2c/ti960-des.c create mode 100644 drivers/media/i2c/ti960-reg.h create mode 100644 drivers/media/i2c/ti964-reg.h create mode 100644 drivers/media/i2c/ti964.c create mode 100644 drivers/media/pci/intel/Kconfig create mode 100644 drivers/media/pci/intel/psys/Makefile create mode 100644 drivers/media/pci/intel/psys/ipu-fw-psys.c create mode 100644 drivers/media/pci/intel/psys/ipu-fw-psys.h create mode 100644 drivers/media/pci/intel/psys/ipu-fw-resources.c create mode 100644 drivers/media/pci/intel/psys/ipu-platform-psys.h create mode 100644 drivers/media/pci/intel/psys/ipu-platform-resources.h create mode 100644 drivers/media/pci/intel/psys/ipu-psys-compat32.c create mode 100644 drivers/media/pci/intel/psys/ipu-psys.c create mode 100644 drivers/media/pci/intel/psys/ipu-psys.h create mode 100644 drivers/media/pci/intel/psys/ipu-resources.c create mode 100644 drivers/media/pci/intel/psys/ipu6-fw-resources.c create mode 100644 drivers/media/pci/intel/psys/ipu6-l-scheduler.c create mode 100644 drivers/media/pci/intel/psys/ipu6-platform-resources.h create mode 100644 drivers/media/pci/intel/psys/ipu6-ppg.c create mode 100644 drivers/media/pci/intel/psys/ipu6-ppg.h create mode 100644 drivers/media/pci/intel/psys/ipu6-psys-gpc.c create mode 100644 drivers/media/pci/intel/psys/ipu6-psys.c create mode 100644 drivers/media/pci/intel/psys/ipu6ep-fw-resources.c create mode 100644 drivers/media/pci/intel/psys/ipu6ep-platform-resources.h create mode 100644 drivers/media/pci/intel/psys/ipu6se-fw-resources.c create mode 100644 drivers/media/pci/intel/psys/ipu6se-platform-resources.h create mode 100644 drivers/media/platform/intel/Kconfig create mode 100644 drivers/media/platform/intel/Makefile create mode 100644 drivers/media/platform/intel/ipu6-acpi-common.c create mode 100644 drivers/media/platform/intel/ipu6-acpi-pdata.c create mode 100644 drivers/media/platform/intel/ipu6-acpi.c create mode 100644 drivers/media/platform/intel/ipu6-adlrvp-pdata.c create mode 100644 drivers/media/platform/intel/ipu6-tglrvp-pdata.c create mode 100644 include/media/ar0234.h create mode 100644 include/media/d4xx_pdata.h create mode 100644 include/media/imx390.h create mode 100644 include/media/ipu-acpi-pdata.h create mode 100644 include/media/ipu-acpi.h create mode 100644 include/media/isx031.h create mode 100644 include/media/lt6911uxc.h create mode 100644 include/media/lt6911uxe.h create mode 100644 include/media/ti960.h create mode 100644 include/media/ti964.h diff --git a/drivers/media/i2c/ar0234.c b/drivers/media/i2c/ar0234.c new file mode 100644 index 000000000000..37dfcb17b63f --- /dev/null +++ b/drivers/media/i2c/ar0234.c @@ -0,0 +1,2289 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019-2024 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AR0234_REG_VALUE_08BIT 1 +#define AR0234_REG_VALUE_16BIT 2 + +#define AR0234_LINK_FREQ_360MHZ 360000000ULL +#define AR0234_LINK_FREQ_300MHZ 300000000ULL +#define AR0234_LINK_FREQ_288MHZ 288000000ULL +#define AR0234_LINK_FREQ_240MHZ 240000000ULL +#define AR0234_LINK_FREQ_22_5MHZ 22500000ULL + +#define FSERIAL_CLK_4_LANE 240000000ULL +#define FSERIAL_CLK_2_LANE 180000000ULL /* use as pixel rate */ + +#define PIX_CLK_4_LANE 60000000ULL +#define PIX_CLK_2_LANE 45000000ULL + +#define AR0234_REG_CHIP_ID 0x3000 +#define AR0234_CHIP_ID 0x0A56 + +#define AR0234_REG_MODE_SELECT 0x301A +#define AR0234_MODE_STANDBY 0x2058 +#define AR0234_MODE_STREAMING 0x205C + +/* vertical-timings from sensor */ +#define AR0234_REG_VTS 0x300A +#define AR0234_VTS_MAX 0xffff + +/* Exposure controls from sensor */ +#define AR0234_REG_EXPOSURE 0x3012 +#define AR0234_EXPOSURE_MIN 0 +#define AR0234_EXPOSURE_MAX_MARGIN 80 +#define AR0234_EXPOSURE_STEP 1 + +/* Analog gain controls from sensor */ +#define AR0234_REG_ANALOG_GAIN 0x3060 +#define AR0234_ANAL_GAIN_MIN 0 +#define AR0234_ANAL_GAIN_MAX 0x7f +#define AR0234_ANAL_GAIN_STEP 1 +#define AR0234_ANAL_GAIN_DEFAULT 0xe + +/* Digital gain controls from sensor */ +#define AR0234_REG_GLOBAL_GAIN 0x305E +#define AR0234_DGTL_GAIN_MIN 0 +#define AR0234_DGTL_GAIN_MAX 0x7ff +#define AR0234_DGTL_GAIN_STEP 1 +#define AR0234_DGTL_GAIN_DEFAULT 0x80 + +#define AR0234_REG_LED_FLASH_CONTROL 0x3270 +#define AR0234_LED_FLASH_EN 0x100 +#define AR0234_LED_DELAY 0xff + +#define AR0234_REG_IMAGE_ORIENTATION 0x301D +#define AR0234_HFLIP_BIT 0x0 +#define AR0234_VFLIP_BIT 0x1 + +#define WIN_WIDTH 1280 +#define WIN_HEIGHT 960 + +#define AR0234_CID_CSI_PORT (V4L2_CID_USER_BASE | 0x1001) +#define AR0234_CID_I2C_BUS (V4L2_CID_USER_BASE | 0x1002) +#define AR0234_CID_I2C_ID (V4L2_CID_USER_BASE | 0x1003) +#define AR0234_CID_I2C_SLAVE_ADDRESS (V4L2_CID_USER_BASE | 0x1004) +#define AR0234_CID_FPS (V4L2_CID_USER_BASE | 0x1005) +#define AR0234_CID_FRAME_INTERVAL (V4L2_CID_USER_BASE | 0x1006) + +#define to_ar0234(_sd) container_of(_sd, struct ar0234, sd) + +enum { + AR0234_LINK_FREQ_360MBPS, + AR0234_LINK_FREQ_300MBPS, + AR0234_LINK_FREQ_288MBPS, + AR0234_LINK_FREQ_240MBPS, + AR0234_LINK_FREQ_22_5MBPS, +}; + +struct ar0234_reg { + u16 address; + u16 val; +}; + +struct ar0234_reg_list { + u32 num_of_regs; + const struct ar0234_reg *regs; +}; + +struct ar0234_link_freq_config { + const struct ar0234_reg_list reg_list; +}; + +struct ar0234_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* MEDIA_BUS_FMT */ + u32 code; + + /* MIPI_LANES */ + s32 lanes; + + /* MODE_FPS*/ + u32 fps; + + /* bit per pixel */ + u32 bpp; + + /* Sensor register settings for this resolution */ + const struct ar0234_reg_list reg_list; +}; + +static const struct ar0234_reg freq_1280x960_10bit_4lane_300M[] = { + {0x302A, 0x0005}, + {0x302C, 0x0002}, + {0x302E, 0x0009}, + {0x3030, 0x00C8}, + {0x3036, 0x000A}, + {0x3038, 0x0002}, + {0x31B0, 0x005C}, + {0x31B2, 0x0046}, + {0x31B4, 0x31C6}, + {0x31B6, 0x2190}, + {0x31B8, 0x6049}, + {0x31BA, 0x0208}, + {0x31BC, 0x8A06}, + {0x31AE, 0x0204}, + {0x3002, 0x0080}, + {0x3004, 0x0148}, + {0x3006, 0x043F}, + {0x3008, 0x0647}, + {0x300A, 0x0983}, + {0x300C, 0x0268}, + {0x3012, 0x093E}, + {0x31AC, 0x0A0A}, + {0x306E, 0x9010}, + {0x30A2, 0x0001}, + {0x30A6, 0x0001}, + {0x3082, 0x0003}, + {0x3040, 0x0000}, + {0x31D0, 0x0000}, +}; + +static const struct ar0234_reg freq_1280x960_8bit_4lane_240M[] = { + {0x302A, 0x0008}, + {0x302C, 0x0001}, + {0x302E, 0x0009}, + {0x3030, 0x00A0}, + {0x3036, 0x0008}, + {0x3038, 0x0002}, + {0x31B0, 0x005B}, + {0x31B2, 0x0046}, + {0x31B4, 0x1206}, + {0x31B6, 0x2193}, + {0x31B8, 0x604B}, + {0x31BA, 0x0188}, + {0x31BC, 0x8A06}, + {0x31AE, 0x0204}, + {0x3002, 0x0080}, + {0x3004, 0x0148}, + {0x3006, 0x043F}, + {0x3008, 0x0647}, + {0x300A, 0x0983}, + {0x300C, 0x0268}, + {0x3012, 0x093E}, + {0x31AC, 0x0A08}, + {0x306E, 0x9010}, + {0x30A2, 0x0001}, + {0x30A6, 0x0001}, + {0x3082, 0x0003}, + {0x3040, 0x0000}, + {0x31D0, 0x0000}, + {0x3354, 0x002a}, + {0x3354, 0x002a}, + {0x3354, 0x002a}, +}; + +static const struct ar0234_reg freq_1280x960_10bit_2lane_360M[] = { + {0x302A, 0x0005}, + {0x302C, 0x0004}, + {0x302E, 0x0003}, + {0x3030, 0x0050}, + {0x3036, 0x000A}, + {0x3038, 0x0002}, + {0x31B0, 0x006E}, + {0x31B2, 0x0050}, + {0x31B4, 0x4207}, + {0x31B6, 0x2213}, + {0x31B8, 0x704A}, + {0x31BA, 0x0289}, + {0x31BC, 0x8C08}, + {0x31AE, 0x0202}, + {0x3002, 0x0080}, + {0x3004, 0x0148}, + {0x3006, 0x043F}, + {0x3008, 0x0647}, + {0x300A, 0x05B5}, + {0x300C, 0x0268}, + {0x3012, 0x058C}, + {0x31AC, 0x0A0A}, + {0x306E, 0x9010}, + {0x30A2, 0x0001}, + {0x30A6, 0x0001}, + {0x3082, 0x0003}, + {0x3040, 0x0000}, + {0x31D0, 0x0000}, +}; + +static const struct ar0234_reg freq_1280x960_8bit_2lane_288M[] = { + {0x302A, 0x0008}, + {0x302C, 0x0002}, + {0x302E, 0x0003}, + {0x3030, 0x0040}, + {0x3036, 0x0008}, + {0x3038, 0x0002}, + {0x31B0, 0x006A}, + {0x31B2, 0x004F}, + {0x31B4, 0x1207}, + {0x31B6, 0x2216}, + {0x31B8, 0x704B}, + {0x31BA, 0x0209}, + {0x31BC, 0x8C08}, + {0x31AE, 0x0202}, + {0x3002, 0x0080}, + {0x3004, 0x0148}, + {0x3006, 0x043F}, + {0x3008, 0x0647}, + {0x300A, 0x05B5}, + {0x300C, 0x0268}, + {0x3012, 0x058C}, + {0x31AC, 0x0A08}, + {0x306E, 0x9010}, + {0x30A2, 0x0001}, + {0x30A6, 0x0001}, + {0x3082, 0x0003}, + {0x3040, 0x0000}, + {0x31D0, 0x0000}, + {0x3354, 0x002a}, + {0x3354, 0x002a}, + {0x3354, 0x002a}, +}; + +static const struct ar0234_reg freq_1920x1200_10bit_2lane_22_5M[] = { +{0x302A, 0x0005}, //VT_PIX_CLK_DIV = 5 +{0x302C, 0x0002}, //VT_SYS_CLK_DIV = 2 +{0x302E, 0x0003}, //PRE_PLL_CLK_DIV = 3 +{0x3030, 0x0032}, //PLL_MULTIPLIER = 50 +{0x3036, 0x000A}, //OP_PIX_CLK_DIV = 10 +{0x3038, 0x0002}, //OP_SYS_CLK_DIV = 2 +{0x31B0, 0x004C}, //FRAME_PREAMBLE = 76 +{0x31B2, 0x003C}, //LINE_PREAMBLE = 60 +{0x31B4, 0x31C5}, //MIPI_TIMING_0 = 12741 +{0x31B6, 0x214C}, //MIPI_TIMING_1 = 8524 +{0x31B8, 0x5048}, //MIPI_TIMING_2 = 20552 +{0x31BA, 0x0186}, //MIPI_TIMING_3 = 390 +{0x31BC, 0x0805}, //MIPI_TIMING_4 = 2053 +{0x3354, 0x002B}, //MIPI_CNTRL = 43 + +{0x301A, 0x2058}, //RESET_REGISTER = 8280 +{0x31AE, 0x0202}, //SERIAL_FORMAT = 514 +{0x3002, 0x0008}, //Y_ADDR_START = 8 +{0x3004, 0x0008}, //X_ADDR_START = 8 +{0x3006, 0x04B7}, //Y_ADDR_END = 1207 +{0x3008, 0x0787}, //X_ADDR_END = 1927 +{0x300A, 0x04C4}, //FRAME_LENGTH_LINES = 1220 +{0x300C, 0x0264}, //LINE_LENGTH_PCK = 612 +{0x3012, 0x0024}, //COARSE_INTEGRATION_TIME = 36 +{0x31AC, 0x0A0A}, //DATA_FORMAT_BITS = 2570 +{0x306E, 0x9010}, //DATAPATH_SELECT = 36880 +{0x30A2, 0x0001}, //X_ODD_INC = 1 +{0x30A6, 0x0001}, //Y_ODD_INC = 1 +{0x3082, 0x0003}, //OPERATION_MODE_CTRL = 3 +{0x3040, 0x0000}, //READ_MODE = 0 +{0x31D0, 0x0000}, //COMPANDING = 0 +{0x301A, 0x205C}, //RESET_REGISTER = 8284 +}; + +static const struct ar0234_reg mode_1280x960_8bit_4lane[] = { + // Reset { + {0x301a, 0x00d9}, + {0x0000, 0x00c8}, + // 1D-DDC_Parameters { + {0x3F4C, 0x121F}, + {0x3F4E, 0x121F}, + {0x3F50, 0x0B81}, + {0x0000, 0x00c8}, + // 1D-DDC_Parameters } + // Reset } + // AR0234CS REV2 Sequencer-brt_spots_ablo_gnd_11feb19 { + {0x3088, 0x8000}, + {0x3086, 0xC1AE}, + {0x3086, 0x327F}, + {0x3086, 0x5780}, + {0x3086, 0x272F}, + {0x3086, 0x7416}, + {0x3086, 0x7E13}, + {0x3086, 0x8000}, + {0x3086, 0x307E}, + {0x3086, 0xFF80}, + {0x3086, 0x20C3}, + {0x3086, 0xB00E}, + {0x3086, 0x8190}, + {0x3086, 0x1643}, + {0x3086, 0x1651}, + {0x3086, 0x9D3E}, + {0x3086, 0x9545}, + {0x3086, 0x2209}, + {0x3086, 0x3781}, + {0x3086, 0x9016}, + {0x3086, 0x4316}, + {0x3086, 0x7F90}, + {0x3086, 0x8000}, + {0x3086, 0x387F}, + {0x3086, 0x1380}, + {0x3086, 0x233B}, + {0x3086, 0x7F93}, + {0x3086, 0x4502}, + {0x3086, 0x8000}, + {0x3086, 0x7FB0}, + {0x3086, 0x8D66}, + {0x3086, 0x7F90}, + {0x3086, 0x8192}, + {0x3086, 0x3C16}, + {0x3086, 0x357F}, + {0x3086, 0x9345}, + {0x3086, 0x0280}, + {0x3086, 0x007F}, + {0x3086, 0xB08D}, + {0x3086, 0x667F}, + {0x3086, 0x9081}, + {0x3086, 0x8237}, + {0x3086, 0x4502}, + {0x3086, 0x3681}, + {0x3086, 0x8044}, + {0x3086, 0x1631}, + {0x3086, 0x4374}, + {0x3086, 0x1678}, + {0x3086, 0x7B7D}, + {0x3086, 0x4502}, + {0x3086, 0x450A}, + {0x3086, 0x7E12}, + {0x3086, 0x8180}, + {0x3086, 0x377F}, + {0x3086, 0x1045}, + {0x3086, 0x0A0E}, + {0x3086, 0x7FD4}, + {0x3086, 0x8024}, + {0x3086, 0x0E82}, + {0x3086, 0x9CC2}, + {0x3086, 0xAFA8}, + {0x3086, 0xAA03}, + {0x3086, 0x430D}, + {0x3086, 0x2D46}, + {0x3086, 0x4316}, + {0x3086, 0x5F16}, + {0x3086, 0x530D}, + {0x3086, 0x1660}, + {0x3086, 0x401E}, + {0x3086, 0x2904}, + {0x3086, 0x2984}, + {0x3086, 0x81E7}, + {0x3086, 0x816F}, + {0x3086, 0x1706}, + {0x3086, 0x81E7}, + {0x3086, 0x7F81}, + {0x3086, 0x5C0D}, + {0x3086, 0x5754}, + {0x3086, 0x495F}, + {0x3086, 0x5305}, + {0x3086, 0x5307}, + {0x3086, 0x4D2B}, + {0x3086, 0xF810}, + {0x3086, 0x164C}, + {0x3086, 0x0755}, + {0x3086, 0x562B}, + {0x3086, 0xB82B}, + {0x3086, 0x984E}, + {0x3086, 0x1129}, + {0x3086, 0x9460}, + {0x3086, 0x5C09}, + {0x3086, 0x5C1B}, + {0x3086, 0x4002}, + {0x3086, 0x4500}, + {0x3086, 0x4580}, + {0x3086, 0x29B6}, + {0x3086, 0x7F80}, + {0x3086, 0x4004}, + {0x3086, 0x7F88}, + {0x3086, 0x4109}, + {0x3086, 0x5C0B}, + {0x3086, 0x29B2}, + {0x3086, 0x4115}, + {0x3086, 0x5C03}, + {0x3086, 0x4105}, + {0x3086, 0x5F2B}, + {0x3086, 0x902B}, + {0x3086, 0x8081}, + {0x3086, 0x6F40}, + {0x3086, 0x1041}, + {0x3086, 0x0160}, + {0x3086, 0x29A2}, + {0x3086, 0x29A3}, + {0x3086, 0x5F4D}, + {0x3086, 0x1C17}, + {0x3086, 0x0281}, + {0x3086, 0xE729}, + {0x3086, 0x8345}, + {0x3086, 0x8840}, + {0x3086, 0x0F7F}, + {0x3086, 0x8A40}, + {0x3086, 0x2345}, + {0x3086, 0x8024}, + {0x3086, 0x4008}, + {0x3086, 0x7F88}, + {0x3086, 0x5D29}, + {0x3086, 0x9288}, + {0x3086, 0x102B}, + {0x3086, 0x0489}, + {0x3086, 0x165C}, + {0x3086, 0x4386}, + {0x3086, 0x170B}, + {0x3086, 0x5C03}, + {0x3086, 0x8A48}, + {0x3086, 0x4D4E}, + {0x3086, 0x2B80}, + {0x3086, 0x4C09}, + {0x3086, 0x4119}, + {0x3086, 0x816F}, + {0x3086, 0x4110}, + {0x3086, 0x4001}, + {0x3086, 0x6029}, + {0x3086, 0x8229}, + {0x3086, 0x8329}, + {0x3086, 0x435C}, + {0x3086, 0x055F}, + {0x3086, 0x4D1C}, + {0x3086, 0x81E7}, + {0x3086, 0x4502}, + {0x3086, 0x8180}, + {0x3086, 0x7F80}, + {0x3086, 0x410A}, + {0x3086, 0x9144}, + {0x3086, 0x1609}, + {0x3086, 0x2FC3}, + {0x3086, 0xB130}, + {0x3086, 0xC3B1}, + {0x3086, 0x0343}, + {0x3086, 0x164A}, + {0x3086, 0x0A43}, + {0x3086, 0x160B}, + {0x3086, 0x4316}, + {0x3086, 0x8F43}, + {0x3086, 0x1690}, + {0x3086, 0x4316}, + {0x3086, 0x7F81}, + {0x3086, 0x450A}, + {0x3086, 0x410F}, + {0x3086, 0x7F83}, + {0x3086, 0x5D29}, + {0x3086, 0x4488}, + {0x3086, 0x102B}, + {0x3086, 0x0453}, + {0x3086, 0x0D40}, + {0x3086, 0x2345}, + {0x3086, 0x0240}, + {0x3086, 0x087F}, + {0x3086, 0x8053}, + {0x3086, 0x0D89}, + {0x3086, 0x165C}, + {0x3086, 0x4586}, + {0x3086, 0x170B}, + {0x3086, 0x5C05}, + {0x3086, 0x8A60}, + {0x3086, 0x4B91}, + {0x3086, 0x4416}, + {0x3086, 0x09C1}, + {0x3086, 0x2CA9}, + {0x3086, 0xAB30}, + {0x3086, 0x51B3}, + {0x3086, 0x3D5A}, + {0x3086, 0x7E3D}, + {0x3086, 0x7E19}, + {0x3086, 0x8000}, + {0x3086, 0x8B1F}, + {0x3086, 0x2A1F}, + {0x3086, 0x83A2}, + {0x3086, 0x7516}, + {0x3086, 0xAD33}, + {0x3086, 0x450A}, + {0x3086, 0x7F53}, + {0x3086, 0x8023}, + {0x3086, 0x8C66}, + {0x3086, 0x7F13}, + {0x3086, 0x8184}, + {0x3086, 0x1481}, + {0x3086, 0x8031}, + {0x3086, 0x3D64}, + {0x3086, 0x452A}, + {0x3086, 0x9451}, + {0x3086, 0x9E96}, + {0x3086, 0x3D2B}, + {0x3086, 0x3D1B}, + {0x3086, 0x529F}, + {0x3086, 0x0E3D}, + {0x3086, 0x083D}, + {0x3086, 0x167E}, + {0x3086, 0x307E}, + {0x3086, 0x1175}, + {0x3086, 0x163E}, + {0x3086, 0x970E}, + {0x3086, 0x82B2}, + {0x3086, 0x3D7F}, + {0x3086, 0xAC3E}, + {0x3086, 0x4502}, + {0x3086, 0x7E11}, + {0x3086, 0x7FD0}, + {0x3086, 0x8000}, + {0x3086, 0x8C66}, + {0x3086, 0x7F90}, + {0x3086, 0x8194}, + {0x3086, 0x3F44}, + {0x3086, 0x1681}, + {0x3086, 0x8416}, + {0x3086, 0x2C2C}, + {0x3086, 0x2C2C}, + // AR0234CS REV2 Sequencer-brt_spots_ablo_gnd_11feb19 } + // RECOMMENDED_SETTINGS-Pxlclk90MHz { + {0x3044, 0x0410}, + {0x3094, 0x03D4}, + {0x3096, 0x0480}, + {0x30BA, 0x7602}, + {0x30FE, 0x002A}, + {0x31DE, 0x0410}, + {0x3ED6, 0x1435}, + {0x3ED8, 0x9865}, + {0x3EDA, 0x7698}, + {0x3EDC, 0x99FF}, + {0x3EE2, 0xBB88}, + {0x3EE4, 0x8836}, + {0x3EF0, 0x1CF0}, + {0x3EF2, 0x0000}, + {0x3EF8, 0x6166}, + {0x3EFA, 0x3333}, + {0x3EFC, 0x6634}, + {0x3276, 0x05DC}, + {0x3F00, 0x9D05}, + {0x3EEE, 0xA4FE}, + {0x30BA, 0x7602}, + // Rowbanding_Settings { + {0x3EEC, 0x0C0C}, + {0x3EE8, 0xAAE4}, + // Rowbanding_Settings } + // Gain Table 90MHz { + // Gain Table 90MHz } + {0x3102, 0x5000}, + // AutoExposure Maximum Analog Gain 16x { + {0x3EEE, 0xA4AA}, + // AutoExposure Maximum Analog Gain 16x } + // RECOMMENDED_SETTINGS-Pxlclk90MHz } + {0x3270, 0x0100}, + {0x3270, 0x0100}, + {0x3270, 0x0100}, +}; + +static const struct ar0234_reg mode_1280x960_10bit_2lane[] = { + {0x0000, 0x00C8}, + {0x301A, 0x00D9}, + {0x0000, 0x00C8}, + {0x3F4C, 0x121F}, + {0x3F4E, 0x121F}, + {0x3F50, 0x0B81}, + {0x31E0, 0x0003}, + {0x31E0, 0x0003}, + {0x30B0, 0x0028}, + {0x0000, 0x00C8}, + {0x3088, 0x8000}, + {0x3086, 0xC1AE}, + {0x3086, 0x327F}, + {0x3086, 0x5780}, + {0x3086, 0x272F}, + {0x3086, 0x7416}, + {0x3086, 0x7E13}, + {0x3086, 0x8000}, + {0x3086, 0x307E}, + {0x3086, 0xFF80}, + {0x3086, 0x20C3}, + {0x3086, 0xB00E}, + {0x3086, 0x8190}, + {0x3086, 0x1643}, + {0x3086, 0x1651}, + {0x3086, 0x9D3E}, + {0x3086, 0x9545}, + {0x3086, 0x2209}, + {0x3086, 0x3781}, + {0x3086, 0x9016}, + {0x3086, 0x4316}, + {0x3086, 0x7F90}, + {0x3086, 0x8000}, + {0x3086, 0x387F}, + {0x3086, 0x1380}, + {0x3086, 0x233B}, + {0x3086, 0x7F93}, + {0x3086, 0x4502}, + {0x3086, 0x8000}, + {0x3086, 0x7FB0}, + {0x3086, 0x8D66}, + {0x3086, 0x7F90}, + {0x3086, 0x8192}, + {0x3086, 0x3C16}, + {0x3086, 0x357F}, + {0x3086, 0x9345}, + {0x3086, 0x0280}, + {0x3086, 0x007F}, + {0x3086, 0xB08D}, + {0x3086, 0x667F}, + {0x3086, 0x9081}, + {0x3086, 0x8237}, + {0x3086, 0x4502}, + {0x3086, 0x3681}, + {0x3086, 0x8044}, + {0x3086, 0x1631}, + {0x3086, 0x4374}, + {0x3086, 0x1678}, + {0x3086, 0x7B7D}, + {0x3086, 0x4502}, + {0x3086, 0x450A}, + {0x3086, 0x7E12}, + {0x3086, 0x8180}, + {0x3086, 0x377F}, + {0x3086, 0x1045}, + {0x3086, 0x0A0E}, + {0x3086, 0x7FD4}, + {0x3086, 0x8024}, + {0x3086, 0x0E82}, + {0x3086, 0x9CC2}, + {0x3086, 0xAFA8}, + {0x3086, 0xAA03}, + {0x3086, 0x430D}, + {0x3086, 0x2D46}, + {0x3086, 0x4316}, + {0x3086, 0x5F16}, + {0x3086, 0x530D}, + {0x3086, 0x1660}, + {0x3086, 0x401E}, + {0x3086, 0x2904}, + {0x3086, 0x2984}, + {0x3086, 0x81E7}, + {0x3086, 0x816F}, + {0x3086, 0x1706}, + {0x3086, 0x81E7}, + {0x3086, 0x7F81}, + {0x3086, 0x5C0D}, + {0x3086, 0x5754}, + {0x3086, 0x495F}, + {0x3086, 0x5305}, + {0x3086, 0x5307}, + {0x3086, 0x4D2B}, + {0x3086, 0xF810}, + {0x3086, 0x164C}, + {0x3086, 0x0755}, + {0x3086, 0x562B}, + {0x3086, 0xB82B}, + {0x3086, 0x984E}, + {0x3086, 0x1129}, + {0x3086, 0x9460}, + {0x3086, 0x5C09}, + {0x3086, 0x5C1B}, + {0x3086, 0x4002}, + {0x3086, 0x4500}, + {0x3086, 0x4580}, + {0x3086, 0x29B6}, + {0x3086, 0x7F80}, + {0x3086, 0x4004}, + {0x3086, 0x7F88}, + {0x3086, 0x4109}, + {0x3086, 0x5C0B}, + {0x3086, 0x29B2}, + {0x3086, 0x4115}, + {0x3086, 0x5C03}, + {0x3086, 0x4105}, + {0x3086, 0x5F2B}, + {0x3086, 0x902B}, + {0x3086, 0x8081}, + {0x3086, 0x6F40}, + {0x3086, 0x1041}, + {0x3086, 0x0160}, + {0x3086, 0x29A2}, + {0x3086, 0x29A3}, + {0x3086, 0x5F4D}, + {0x3086, 0x1C17}, + {0x3086, 0x0281}, + {0x3086, 0xE729}, + {0x3086, 0x8345}, + {0x3086, 0x8840}, + {0x3086, 0x0F7F}, + {0x3086, 0x8A40}, + {0x3086, 0x2345}, + {0x3086, 0x8024}, + {0x3086, 0x4008}, + {0x3086, 0x7F88}, + {0x3086, 0x5D29}, + {0x3086, 0x9288}, + {0x3086, 0x102B}, + {0x3086, 0x0489}, + {0x3086, 0x165C}, + {0x3086, 0x4386}, + {0x3086, 0x170B}, + {0x3086, 0x5C03}, + {0x3086, 0x8A48}, + {0x3086, 0x4D4E}, + {0x3086, 0x2B80}, + {0x3086, 0x4C09}, + {0x3086, 0x4119}, + {0x3086, 0x816F}, + {0x3086, 0x4110}, + {0x3086, 0x4001}, + {0x3086, 0x6029}, + {0x3086, 0x8229}, + {0x3086, 0x8329}, + {0x3086, 0x435C}, + {0x3086, 0x055F}, + {0x3086, 0x4D1C}, + {0x3086, 0x81E7}, + {0x3086, 0x4502}, + {0x3086, 0x8180}, + {0x3086, 0x7F80}, + {0x3086, 0x410A}, + {0x3086, 0x9144}, + {0x3086, 0x1609}, + {0x3086, 0x2FC3}, + {0x3086, 0xB130}, + {0x3086, 0xC3B1}, + {0x3086, 0x0343}, + {0x3086, 0x164A}, + {0x3086, 0x0A43}, + {0x3086, 0x160B}, + {0x3086, 0x4316}, + {0x3086, 0x8F43}, + {0x3086, 0x1690}, + {0x3086, 0x4316}, + {0x3086, 0x7F81}, + {0x3086, 0x450A}, + {0x3086, 0x410F}, + {0x3086, 0x7F83}, + {0x3086, 0x5D29}, + {0x3086, 0x4488}, + {0x3086, 0x102B}, + {0x3086, 0x0453}, + {0x3086, 0x0D40}, + {0x3086, 0x2345}, + {0x3086, 0x0240}, + {0x3086, 0x087F}, + {0x3086, 0x8053}, + {0x3086, 0x0D89}, + {0x3086, 0x165C}, + {0x3086, 0x4586}, + {0x3086, 0x170B}, + {0x3086, 0x5C05}, + {0x3086, 0x8A60}, + {0x3086, 0x4B91}, + {0x3086, 0x4416}, + {0x3086, 0x09C1}, + {0x3086, 0x2CA9}, + {0x3086, 0xAB30}, + {0x3086, 0x51B3}, + {0x3086, 0x3D5A}, + {0x3086, 0x7E3D}, + {0x3086, 0x7E19}, + {0x3086, 0x8000}, + {0x3086, 0x8B1F}, + {0x3086, 0x2A1F}, + {0x3086, 0x83A2}, + {0x3086, 0x7516}, + {0x3086, 0xAD33}, + {0x3086, 0x450A}, + {0x3086, 0x7F53}, + {0x3086, 0x8023}, + {0x3086, 0x8C66}, + {0x3086, 0x7F13}, + {0x3086, 0x8184}, + {0x3086, 0x1481}, + {0x3086, 0x8031}, + {0x3086, 0x3D64}, + {0x3086, 0x452A}, + {0x3086, 0x9451}, + {0x3086, 0x9E96}, + {0x3086, 0x3D2B}, + {0x3086, 0x3D1B}, + {0x3086, 0x529F}, + {0x3086, 0x0E3D}, + {0x3086, 0x083D}, + {0x3086, 0x167E}, + {0x3086, 0x307E}, + {0x3086, 0x1175}, + {0x3086, 0x163E}, + {0x3086, 0x970E}, + {0x3086, 0x82B2}, + {0x3086, 0x3D7F}, + {0x3086, 0xAC3E}, + {0x3086, 0x4502}, + {0x3086, 0x7E11}, + {0x3086, 0x7FD0}, + {0x3086, 0x8000}, + {0x3086, 0x8C66}, + {0x3086, 0x7F90}, + {0x3086, 0x8194}, + {0x3086, 0x3F44}, + {0x3086, 0x1681}, + {0x3086, 0x8416}, + {0x3086, 0x2C2C}, + {0x3086, 0x2C2C}, + {0x302A, 0x0005}, + {0x302C, 0x0001}, + {0x302E, 0x0003}, + {0x3030, 0x0032}, + {0x3036, 0x000A}, + {0x3038, 0x0001}, + {0x30B0, 0x0028}, + {0x31B0, 0x0082}, + {0x31B2, 0x005C}, + {0x31B4, 0x5248}, + {0x31B6, 0x3257}, + {0x31B8, 0x904B}, + {0x31BA, 0x030B}, + {0x31BC, 0x8E09}, + {0x3354, 0x002B}, + {0x31D0, 0x0000}, + {0x31AE, 0x0204}, + {0x3002, 0x00D0}, + {0x3004, 0x0148}, + {0x3006, 0x048F}, + {0x3008, 0x0647}, + {0x3064, 0x1802}, + {0x300A, 0x04C4}, + {0x300C, 0x04C4}, + {0x30A2, 0x0001}, + {0x30A6, 0x0001}, + {0x3012, 0x010C}, + {0x3786, 0x0006}, + {0x31AE, 0x0202}, + {0x3088, 0x8050}, + {0x3086, 0x9237}, + {0x3044, 0x0410}, + {0x3094, 0x03D4}, + {0x3096, 0x0280}, + {0x30BA, 0x7606}, + {0x30B0, 0x0028}, + {0x30BA, 0x7600}, + {0x30FE, 0x002A}, + {0x31DE, 0x0410}, + {0x3ED6, 0x1435}, + {0x3ED8, 0x9865}, + {0x3EDA, 0x7698}, + {0x3EDC, 0x99FF}, + {0x3EE2, 0xBB88}, + {0x3EE4, 0x8836}, + {0x3EF0, 0x1CF0}, + {0x3EF2, 0x0000}, + {0x3EF8, 0x6166}, + {0x3EFA, 0x3333}, + {0x3EFC, 0x6634}, + {0x3088, 0x81BA}, + {0x3086, 0x3D02}, + {0x3276, 0x05DC}, + {0x3F00, 0x9D05}, + {0x3ED2, 0xFA86}, + {0x3EEE, 0xA4FE}, + {0x3ECC, 0x6E42}, + {0x3ECC, 0x0E42}, + {0x3EEC, 0x0C0C}, + {0x3EE8, 0xAAE4}, + {0x3EE6, 0x3363}, + {0x3EE6, 0x3363}, + {0x3EE8, 0xAAE4}, + {0x3EE8, 0xAAE4}, + {0x3180, 0xC24F}, + {0x3102, 0x5000}, + {0x3060, 0x000D}, + {0x3ED0, 0xFF44}, + {0x3ED2, 0xAA86}, + {0x3ED4, 0x031F}, + {0x3EEE, 0xA4AA}, +}; + +static const struct ar0234_reg mode_1920x1200_10bit_2lane[] = { + {0x0000, 0x00c8}, + {0x301A, 0x00D9}, // RESET_REGISTER + {0x0000, 0x00c8}, + {0x3F4C, 0x121F}, // RESERVED_MFR_3F4C + {0x3F4E, 0x121F}, // RESERVED_MFR_3F4E + {0x3F50, 0x0B81}, // RESERVED_MFR_3F50 + {0x31E0, 0x0003}, // PIX_DEF_ID + {0x31E0, 0x0003}, // PIX_DEF_ID + {0x30B0, 0x0028}, // DIGITAL_TEST + {0x0000, 0x00c8}, + {0x3088, 0x8000}, // SEQ_CTRL_PORT + {0x3086, 0xC1AE}, // SEQ_DATA_PORT + {0x3086, 0x327F}, // SEQ_DATA_PORT + {0x3086, 0x5780}, // SEQ_DATA_PORT + {0x3086, 0x272F}, // SEQ_DATA_PORT + {0x3086, 0x7416}, // SEQ_DATA_PORT + {0x3086, 0x7E13}, // SEQ_DATA_PORT + {0x3086, 0x8000}, // SEQ_DATA_PORT + {0x3086, 0x307E}, // SEQ_DATA_PORT + {0x3086, 0xFF80}, // SEQ_DATA_PORT + {0x3086, 0x20C3}, // SEQ_DATA_PORT + {0x3086, 0xB00E}, // SEQ_DATA_PORT + {0x3086, 0x8190}, // SEQ_DATA_PORT + {0x3086, 0x1643}, // SEQ_DATA_PORT + {0x3086, 0x1651}, // SEQ_DATA_PORT + {0x3086, 0x9D3E}, // SEQ_DATA_PORT + {0x3086, 0x9545}, // SEQ_DATA_PORT + {0x3086, 0x2209}, // SEQ_DATA_PORT + {0x3086, 0x3781}, // SEQ_DATA_PORT + {0x3086, 0x9016}, // SEQ_DATA_PORT + {0x3086, 0x4316}, // SEQ_DATA_PORT + {0x3086, 0x7F90}, // SEQ_DATA_PORT + {0x3086, 0x8000}, // SEQ_DATA_PORT + {0x3086, 0x387F}, // SEQ_DATA_PORT + {0x3086, 0x1380}, // SEQ_DATA_PORT + {0x3086, 0x233B}, // SEQ_DATA_PORT + {0x3086, 0x7F93}, // SEQ_DATA_PORT + {0x3086, 0x4502}, // SEQ_DATA_PORT + {0x3086, 0x8000}, // SEQ_DATA_PORT + {0x3086, 0x7FB0}, // SEQ_DATA_PORT + {0x3086, 0x8D66}, // SEQ_DATA_PORT + {0x3086, 0x7F90}, // SEQ_DATA_PORT + {0x3086, 0x8192}, // SEQ_DATA_PORT + {0x3086, 0x3C16}, // SEQ_DATA_PORT + {0x3086, 0x357F}, // SEQ_DATA_PORT + {0x3086, 0x9345}, // SEQ_DATA_PORT + {0x3086, 0x0280}, // SEQ_DATA_PORT + {0x3086, 0x007F}, // SEQ_DATA_PORT + {0x3086, 0xB08D}, // SEQ_DATA_PORT + {0x3086, 0x667F}, // SEQ_DATA_PORT + {0x3086, 0x9081}, // SEQ_DATA_PORT + {0x3086, 0x8237}, // SEQ_DATA_PORT + {0x3086, 0x4502}, // SEQ_DATA_PORT + {0x3086, 0x3681}, // SEQ_DATA_PORT + {0x3086, 0x8044}, // SEQ_DATA_PORT + {0x3086, 0x1631}, // SEQ_DATA_PORT + {0x3086, 0x4374}, // SEQ_DATA_PORT + {0x3086, 0x1678}, // SEQ_DATA_PORT + {0x3086, 0x7B7D}, // SEQ_DATA_PORT + {0x3086, 0x4502}, // SEQ_DATA_PORT + {0x3086, 0x450A}, // SEQ_DATA_PORT + {0x3086, 0x7E12}, // SEQ_DATA_PORT + {0x3086, 0x8180}, // SEQ_DATA_PORT + {0x3086, 0x377F}, // SEQ_DATA_PORT + {0x3086, 0x1045}, // SEQ_DATA_PORT + {0x3086, 0x0A0E}, // SEQ_DATA_PORT + {0x3086, 0x7FD4}, // SEQ_DATA_PORT + {0x3086, 0x8024}, // SEQ_DATA_PORT + {0x3086, 0x0E82}, // SEQ_DATA_PORT + {0x3086, 0x9CC2}, // SEQ_DATA_PORT + {0x3086, 0xAFA8}, // SEQ_DATA_PORT + {0x3086, 0xAA03}, // SEQ_DATA_PORT + {0x3086, 0x430D}, // SEQ_DATA_PORT + {0x3086, 0x2D46}, // SEQ_DATA_PORT + {0x3086, 0x4316}, // SEQ_DATA_PORT + {0x3086, 0x5F16}, // SEQ_DATA_PORT + {0x3086, 0x530D}, // SEQ_DATA_PORT + {0x3086, 0x1660}, // SEQ_DATA_PORT + {0x3086, 0x401E}, // SEQ_DATA_PORT + {0x3086, 0x2904}, // SEQ_DATA_PORT + {0x3086, 0x2984}, // SEQ_DATA_PORT + {0x3086, 0x81E7}, // SEQ_DATA_PORT + {0x3086, 0x816F}, // SEQ_DATA_PORT + {0x3086, 0x1706}, // SEQ_DATA_PORT + {0x3086, 0x81E7}, // SEQ_DATA_PORT + {0x3086, 0x7F81}, // SEQ_DATA_PORT + {0x3086, 0x5C0D}, // SEQ_DATA_PORT + {0x3086, 0x5754}, // SEQ_DATA_PORT + {0x3086, 0x495F}, // SEQ_DATA_PORT + {0x3086, 0x5305}, // SEQ_DATA_PORT + {0x3086, 0x5307}, // SEQ_DATA_PORT + {0x3086, 0x4D2B}, // SEQ_DATA_PORT + {0x3086, 0xF810}, // SEQ_DATA_PORT + {0x3086, 0x164C}, // SEQ_DATA_PORT + {0x3086, 0x0755}, // SEQ_DATA_PORT + {0x3086, 0x562B}, // SEQ_DATA_PORT + {0x3086, 0xB82B}, // SEQ_DATA_PORT + {0x3086, 0x984E}, // SEQ_DATA_PORT + {0x3086, 0x1129}, // SEQ_DATA_PORT + {0x3086, 0x9460}, // SEQ_DATA_PORT + {0x3086, 0x5C09}, // SEQ_DATA_PORT + {0x3086, 0x5C1B}, // SEQ_DATA_PORT + {0x3086, 0x4002}, // SEQ_DATA_PORT + {0x3086, 0x4500}, // SEQ_DATA_PORT + {0x3086, 0x4580}, // SEQ_DATA_PORT + {0x3086, 0x29B6}, // SEQ_DATA_PORT + {0x3086, 0x7F80}, // SEQ_DATA_PORT + {0x3086, 0x4004}, // SEQ_DATA_PORT + {0x3086, 0x7F88}, // SEQ_DATA_PORT + {0x3086, 0x4109}, // SEQ_DATA_PORT + {0x3086, 0x5C0B}, // SEQ_DATA_PORT + {0x3086, 0x29B2}, // SEQ_DATA_PORT + {0x3086, 0x4115}, // SEQ_DATA_PORT + {0x3086, 0x5C03}, // SEQ_DATA_PORT + {0x3086, 0x4105}, // SEQ_DATA_PORT + {0x3086, 0x5F2B}, // SEQ_DATA_PORT + {0x3086, 0x902B}, // SEQ_DATA_PORT + {0x3086, 0x8081}, // SEQ_DATA_PORT + {0x3086, 0x6F40}, // SEQ_DATA_PORT + {0x3086, 0x1041}, // SEQ_DATA_PORT + {0x3086, 0x0160}, // SEQ_DATA_PORT + {0x3086, 0x29A2}, // SEQ_DATA_PORT + {0x3086, 0x29A3}, // SEQ_DATA_PORT + {0x3086, 0x5F4D}, // SEQ_DATA_PORT + {0x3086, 0x1C17}, // SEQ_DATA_PORT + {0x3086, 0x0281}, // SEQ_DATA_PORT + {0x3086, 0xE729}, // SEQ_DATA_PORT + {0x3086, 0x8345}, // SEQ_DATA_PORT + {0x3086, 0x8840}, // SEQ_DATA_PORT + {0x3086, 0x0F7F}, // SEQ_DATA_PORT + {0x3086, 0x8A40}, // SEQ_DATA_PORT + {0x3086, 0x2345}, // SEQ_DATA_PORT + {0x3086, 0x8024}, // SEQ_DATA_PORT + {0x3086, 0x4008}, // SEQ_DATA_PORT + {0x3086, 0x7F88}, // SEQ_DATA_PORT + {0x3086, 0x5D29}, // SEQ_DATA_PORT + {0x3086, 0x9288}, // SEQ_DATA_PORT + {0x3086, 0x102B}, // SEQ_DATA_PORT + {0x3086, 0x0489}, // SEQ_DATA_PORT + {0x3086, 0x165C}, // SEQ_DATA_PORT + {0x3086, 0x4386}, // SEQ_DATA_PORT + {0x3086, 0x170B}, // SEQ_DATA_PORT + {0x3086, 0x5C03}, // SEQ_DATA_PORT + {0x3086, 0x8A48}, // SEQ_DATA_PORT + {0x3086, 0x4D4E}, // SEQ_DATA_PORT + {0x3086, 0x2B80}, // SEQ_DATA_PORT + {0x3086, 0x4C09}, // SEQ_DATA_PORT + {0x3086, 0x4119}, // SEQ_DATA_PORT + {0x3086, 0x816F}, // SEQ_DATA_PORT + {0x3086, 0x4110}, // SEQ_DATA_PORT + {0x3086, 0x4001}, // SEQ_DATA_PORT + {0x3086, 0x6029}, // SEQ_DATA_PORT + {0x3086, 0x8229}, // SEQ_DATA_PORT + {0x3086, 0x8329}, // SEQ_DATA_PORT + {0x3086, 0x435C}, // SEQ_DATA_PORT + {0x3086, 0x055F}, // SEQ_DATA_PORT + {0x3086, 0x4D1C}, // SEQ_DATA_PORT + {0x3086, 0x81E7}, // SEQ_DATA_PORT + {0x3086, 0x4502}, // SEQ_DATA_PORT + {0x3086, 0x8180}, // SEQ_DATA_PORT + {0x3086, 0x7F80}, // SEQ_DATA_PORT + {0x3086, 0x410A}, // SEQ_DATA_PORT + {0x3086, 0x9144}, // SEQ_DATA_PORT + {0x3086, 0x1609}, // SEQ_DATA_PORT + {0x3086, 0x2FC3}, // SEQ_DATA_PORT + {0x3086, 0xB130}, // SEQ_DATA_PORT + {0x3086, 0xC3B1}, // SEQ_DATA_PORT + {0x3086, 0x0343}, // SEQ_DATA_PORT + {0x3086, 0x164A}, // SEQ_DATA_PORT + {0x3086, 0x0A43}, // SEQ_DATA_PORT + {0x3086, 0x160B}, // SEQ_DATA_PORT + {0x3086, 0x4316}, // SEQ_DATA_PORT + {0x3086, 0x8F43}, // SEQ_DATA_PORT + {0x3086, 0x1690}, // SEQ_DATA_PORT + {0x3086, 0x4316}, // SEQ_DATA_PORT + {0x3086, 0x7F81}, // SEQ_DATA_PORT + {0x3086, 0x450A}, // SEQ_DATA_PORT + {0x3086, 0x410F}, // SEQ_DATA_PORT + {0x3086, 0x7F83}, // SEQ_DATA_PORT + {0x3086, 0x5D29}, // SEQ_DATA_PORT + {0x3086, 0x4488}, // SEQ_DATA_PORT + {0x3086, 0x102B}, // SEQ_DATA_PORT + {0x3086, 0x0453}, // SEQ_DATA_PORT + {0x3086, 0x0D40}, // SEQ_DATA_PORT + {0x3086, 0x2345}, // SEQ_DATA_PORT + {0x3086, 0x0240}, // SEQ_DATA_PORT + {0x3086, 0x087F}, // SEQ_DATA_PORT + {0x3086, 0x8053}, // SEQ_DATA_PORT + {0x3086, 0x0D89}, // SEQ_DATA_PORT + {0x3086, 0x165C}, // SEQ_DATA_PORT + {0x3086, 0x4586}, // SEQ_DATA_PORT + {0x3086, 0x170B}, // SEQ_DATA_PORT + {0x3086, 0x5C05}, // SEQ_DATA_PORT + {0x3086, 0x8A60}, // SEQ_DATA_PORT + {0x3086, 0x4B91}, // SEQ_DATA_PORT + {0x3086, 0x4416}, // SEQ_DATA_PORT + {0x3086, 0x09C1}, // SEQ_DATA_PORT + {0x3086, 0x2CA9}, // SEQ_DATA_PORT + {0x3086, 0xAB30}, // SEQ_DATA_PORT + {0x3086, 0x51B3}, // SEQ_DATA_PORT + {0x3086, 0x3D5A}, // SEQ_DATA_PORT + {0x3086, 0x7E3D}, // SEQ_DATA_PORT + {0x3086, 0x7E19}, // SEQ_DATA_PORT + {0x3086, 0x8000}, // SEQ_DATA_PORT + {0x3086, 0x8B1F}, // SEQ_DATA_PORT + {0x3086, 0x2A1F}, // SEQ_DATA_PORT + {0x3086, 0x83A2}, // SEQ_DATA_PORT + {0x3086, 0x7516}, // SEQ_DATA_PORT + {0x3086, 0xAD33}, // SEQ_DATA_PORT + {0x3086, 0x450A}, // SEQ_DATA_PORT + {0x3086, 0x7F53}, // SEQ_DATA_PORT + {0x3086, 0x8023}, // SEQ_DATA_PORT + {0x3086, 0x8C66}, // SEQ_DATA_PORT + {0x3086, 0x7F13}, // SEQ_DATA_PORT + {0x3086, 0x8184}, // SEQ_DATA_PORT + {0x3086, 0x1481}, // SEQ_DATA_PORT + {0x3086, 0x8031}, // SEQ_DATA_PORT + {0x3086, 0x3D64}, // SEQ_DATA_PORT + {0x3086, 0x452A}, // SEQ_DATA_PORT + {0x3086, 0x9451}, // SEQ_DATA_PORT + {0x3086, 0x9E96}, // SEQ_DATA_PORT + {0x3086, 0x3D2B}, // SEQ_DATA_PORT + {0x3086, 0x3D1B}, // SEQ_DATA_PORT + {0x3086, 0x529F}, // SEQ_DATA_PORT + {0x3086, 0x0E3D}, // SEQ_DATA_PORT + {0x3086, 0x083D}, // SEQ_DATA_PORT + {0x3086, 0x167E}, // SEQ_DATA_PORT + {0x3086, 0x307E}, // SEQ_DATA_PORT + {0x3086, 0x1175}, // SEQ_DATA_PORT + {0x3086, 0x163E}, // SEQ_DATA_PORT + {0x3086, 0x970E}, // SEQ_DATA_PORT + {0x3086, 0x82B2}, // SEQ_DATA_PORT + {0x3086, 0x3D7F}, // SEQ_DATA_PORT + {0x3086, 0xAC3E}, // SEQ_DATA_PORT + {0x3086, 0x4502}, // SEQ_DATA_PORT + {0x3086, 0x7E11}, // SEQ_DATA_PORT + {0x3086, 0x7FD0}, // SEQ_DATA_PORT + {0x3086, 0x8000}, // SEQ_DATA_PORT + {0x3086, 0x8C66}, // SEQ_DATA_PORT + {0x3086, 0x7F90}, // SEQ_DATA_PORT + {0x3086, 0x8194}, // SEQ_DATA_PORT + {0x3086, 0x3F44}, // SEQ_DATA_PORT + {0x3086, 0x1681}, // SEQ_DATA_PORT + {0x3086, 0x8416}, // SEQ_DATA_PORT + {0x3086, 0x2C2C}, // SEQ_DATA_PORT + {0x3086, 0x2C2C}, // SEQ_DATA_PORT + {0x302A, 0x0005}, // VT_PIX_CLK_DIV + {0x302C, 0x0002}, // VT_SYS_CLK_DIV + {0x302E, 0x0003}, // PRE_PLL_CLK_DIV + {0x3030, 0x0032}, // PLL_MULTIPLIER + {0x3036, 0x000A}, // OP_PIX_CLK_DIV + {0x3038, 0x0002}, // OP_SYS_CLK_DIV + {0x30B0, 0x0028}, // DIGITAL_TEST + {0x31B0, 0x0082}, // FRAME_PREAMBLE + {0x31B2, 0x005C}, // LINE_PREAMBLE + {0x31B4, 0x5248}, // MIPI_TIMING_0 + {0x31B6, 0x3257}, // MIPI_TIMING_1 + {0x31B8, 0x904B}, // MIPI_TIMING_2 + {0x31BA, 0x030B}, // MIPI_TIMING_3 + {0x31BC, 0x8E09}, // MIPI_TIMING_4 + {0x3354, 0x002B}, // MIPI_CNTRL + {0x31D0, 0x0000}, // COMPANDING + {0x31AE, 0x0204}, // SERIAL_FORMAT + {0x3002, 0x0008}, // Y_ADDR_START + {0x3004, 0x0008}, // X_ADDR_START + {0x3006, 0x04B7}, // Y_ADDR_END + {0x3008, 0x0787}, // X_ADDR_END + {0x3064, 0x1802}, // SMIA_TEST + {0x300A, 0x04C4}, // FRAME_LENGTH_LINES + {0x300C, 0x0264}, // LINE_LENGTH_PCK + {0x30A2, 0x0001}, // X_ODD_INC + {0x30A6, 0x0001}, // Y_ODD_INC + {0x3012, 0x02DC}, // COARSE_INTEGRATION_TIME + {0x3786, 0x0006}, // DIGITAL_CTRL_1 + {0x31AE, 0x0202}, // SERIAL_FORMAT + {0x3044, 0x0410}, // RESERVED_MFR_3044 + {0x3094, 0x03D4}, // RESERVED_MFR_3094 + {0x3096, 0x0480}, // RESERVED_MFR_3096 + {0x30BA, 0x7606}, // RESERVED_MFR_30BA + {0x30B0, 0x0028}, // DIGITAL_TEST + {0x30BA, 0x7600}, // RESERVED_MFR_30BA + {0x30FE, 0x002A}, // NOISE_PEDESTAL + {0x31DE, 0x0410}, // RESERVED_MFR_31DE + {0x3ED6, 0x1435}, // RESERVED_MFR_3ED6 + {0x3ED8, 0x9865}, // RESERVED_MFR_3ED8 + {0x3EDA, 0x7698}, // RESERVED_MFR_3EDA + {0x3EDC, 0x99FF}, // RESERVED_MFR_3EDC + {0x3EE2, 0xBB88}, // RESERVED_MFR_3EE2 + {0x3EE4, 0x8836}, // RESERVED_MFR_3EE4 + {0x3EF0, 0x1CF0}, // RESERVED_MFR_3EF0 + {0x3EF2, 0x0000}, // RESERVED_MFR_3EF2 + {0x3EF8, 0x6166}, // RESERVED_MFR_3EF8 + {0x3EFA, 0x3333}, // RESERVED_MFR_3EFA + {0x3EFC, 0x6634}, // RESERVED_MFR_3EFC + {0x3088, 0x81BA}, // SEQ_CTRL_PORT + {0x3086, 0x3D02}, // SEQ_DATA_PORT + {0x3276, 0x05DC}, // RESERVED_MFR_3276 + {0x3F00, 0x9D05}, // RESERVED_MFR_3F00 + {0x3ED2, 0xFA86}, // RESERVED_MFR_3ED2 + {0x3EEE, 0xA4FE}, // RESERVED_MFR_3EEE + {0x3ECC, 0x6D42}, // RESERVED_MFR_3ECC + {0x3ECC, 0x0D42}, // RESERVED_MFR_3ECC + {0x3EEC, 0x0C0C}, // RESERVED_MFR_3EEC + {0x3EE8, 0xAAE4}, // RESERVED_MFR_3EE8 + {0x3EE6, 0x3363}, // RESERVED_MFR_3EE6 + {0x3EE6, 0x3363}, // RESERVED_MFR_3EE6 + {0x3EE8, 0xAAE4}, // RESERVED_MFR_3EE8 + {0x3EE8, 0xAAE4}, // RESERVED_MFR_3EE8 + {0x3180, 0xC24F}, // DELTA_DK_CONTROL + {0x3102, 0x5000}, // AE_LUMA_TARGET_REG + {0x3060, 0x000D}, // ANALOG_GAIN + {0x3ED0, 0xFF44}, // RESERVED_MFR_3ED0 + {0x3ED2, 0xAA86}, // RESERVED_MFR_3ED2 + {0x3ED4, 0x031F}, // RESERVED_MFR_3ED4 + {0x3EEE, 0xA4AA}, // RESERVED_MFR_3EEE +}; + +static const s64 link_freq_menu_items[] = { + AR0234_LINK_FREQ_360MHZ, + AR0234_LINK_FREQ_300MHZ, + AR0234_LINK_FREQ_288MHZ, + AR0234_LINK_FREQ_240MHZ, + AR0234_LINK_FREQ_22_5MHZ, +}; + +static const struct ar0234_link_freq_config link_freq_configs[] = { + [AR0234_LINK_FREQ_360MBPS] = { + .reg_list = { + .num_of_regs = + ARRAY_SIZE(freq_1280x960_10bit_2lane_360M), + .regs = freq_1280x960_10bit_2lane_360M, + } + }, + [AR0234_LINK_FREQ_300MBPS] = { + .reg_list = { + .num_of_regs = + ARRAY_SIZE(freq_1280x960_10bit_4lane_300M), + .regs = freq_1280x960_10bit_4lane_300M, + } + }, + [AR0234_LINK_FREQ_288MBPS] = { + .reg_list = { + .num_of_regs = + ARRAY_SIZE(freq_1280x960_8bit_2lane_288M), + .regs = freq_1280x960_8bit_2lane_288M, + } + }, + [AR0234_LINK_FREQ_240MBPS] = { + .reg_list = { + .num_of_regs = + ARRAY_SIZE(freq_1280x960_8bit_4lane_240M), + .regs = freq_1280x960_8bit_4lane_240M, + } + }, + [AR0234_LINK_FREQ_22_5MBPS] = { + .reg_list = { + .num_of_regs = + ARRAY_SIZE(freq_1920x1200_10bit_2lane_22_5M), + .regs = freq_1920x1200_10bit_2lane_22_5M, + } + }, +}; + +static const struct ar0234_mode supported_modes[] = { + { + .width = WIN_WIDTH, + .height = WIN_HEIGHT, + .hts = 2464, + .vts_def = 2435, + .vts_min = 2435, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .lanes = 2, + .fps = 30, + .bpp = 10, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x960_10bit_2lane), + .regs = mode_1280x960_10bit_2lane, + }, + .link_freq_index = -1, + }, + { + .width = WIN_WIDTH, + .height = WIN_HEIGHT, + .hts = 2464, + .vts_def = 2435, + .vts_min = 2435, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .lanes = 4, + .fps = 40, + .bpp = 8, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1280x960_8bit_4lane), + .regs = mode_1280x960_8bit_4lane, + }, + .link_freq_index = AR0234_LINK_FREQ_240MBPS, + }, + { + .width = 1920, + .height = 1200, + .hts = 2464, + .vts_def = 2435, + .vts_min = 2435, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .lanes = 2, + .fps = 30, + .bpp = 10, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1920x1200_10bit_2lane), + .regs = mode_1920x1200_10bit_2lane, + }, + .link_freq_index = AR0234_LINK_FREQ_22_5MBPS, + }, +}; + +static u32 supported_formats[] = { + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGRBG8_1X8, +}; + +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 *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *analogue_gain; + struct v4l2_ctrl *digital_gain; + struct v4l2_ctrl *strobe_source; + struct v4l2_ctrl *strobe; + struct v4l2_ctrl *strobe_stop; + struct v4l2_ctrl *timeout; + struct v4l2_ctrl *csi_port; + struct v4l2_ctrl *i2c_bus; + struct v4l2_ctrl *i2c_id; + struct v4l2_ctrl *i2c_slave_address; + struct v4l2_ctrl *fps; + struct v4l2_ctrl *frame_interval; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + + /* Current mode */ + const struct ar0234_mode *cur_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + struct ar0234_platform_data *platform_data; + + s64 sub_stream; +}; + +static int ar0234_read_reg(struct ar0234 *ar0234, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) { + dev_err(&client->dev, "%s: invalid length %d. i2c read register failed\n", + __func__, len); + return -EINVAL; + } + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "%s: i2c read register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + return -EIO; + } + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int ar0234_write_reg(struct ar0234 *ar0234, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + u8 buf[6]; + + if (reg == 0) { + msleep(val); + return 0; + } + + if (len > 4) { + dev_err(&client->dev, "%s: invalid length %d. i2c write register failed\n", + __func__, len); + return -EINVAL; + } + + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) { + dev_err(&client->dev, "%s: i2c write register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + return -EIO; + } + + return 0; +} + +static int ar0234_write_reg_list(struct ar0234 *ar0234, + const struct ar0234_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = ar0234_write_reg(ar0234, r_list->regs[i].address, + AR0234_REG_VALUE_16BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int ar0234_update_digital_gain(struct ar0234 *ar0234, u32 d_gain) +{ + return ar0234_write_reg(ar0234, AR0234_REG_GLOBAL_GAIN, + AR0234_REG_VALUE_16BIT, d_gain); +} + +static u64 get_pixel_rate(struct ar0234 *ar0234) +{ + u64 pixel_rate; + + if (ar0234->cur_mode->lanes == 4) + pixel_rate = FSERIAL_CLK_4_LANE; + else if (ar0234->cur_mode->lanes == 2) + pixel_rate = FSERIAL_CLK_2_LANE; + else + pixel_rate = FSERIAL_CLK_4_LANE; + + return pixel_rate; +} + +/* + * from table 1, AND9820-D.pdf. + * for context A, hblank = LLP(0x300C) - active data time. + */ +static u64 get_hblank(struct ar0234 *ar0234) +{ + u64 hblank; + u64 pixel_rate; + u64 pixel_clk; + + if (ar0234->cur_mode->lanes == 4) { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } else if (ar0234->cur_mode->lanes == 2) { + pixel_rate = FSERIAL_CLK_2_LANE; + pixel_clk = PIX_CLK_2_LANE; + } else { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } + + /* + * for pixel clock is ar0234 internal, + * return hblank in the numbers of pixel rate. + */ + hblank = 0x384 * (pixel_rate / pixel_clk); + + return hblank; +} + +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; + int ret = 0; + u32 val; + + /* Propagate change of current control to all related controls */ + if (ctrl->id == V4L2_CID_VBLANK) { + /* Update max exposure while meeting expected vblanking */ + exposure_max = ar0234->cur_mode->height + ctrl->val - + AR0234_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ar0234->exposure, + ar0234->exposure->minimum, + exposure_max, ar0234->exposure->step, + ar0234->cur_mode->height - + AR0234_EXPOSURE_MAX_MARGIN); + } + + /* 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 = ar0234_write_reg(ar0234, AR0234_REG_ANALOG_GAIN, + AR0234_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_DIGITAL_GAIN: + ret = ar0234_update_digital_gain(ar0234, ctrl->val); + break; + + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ar0234_write_reg(ar0234, AR0234_REG_EXPOSURE, + AR0234_REG_VALUE_16BIT, ctrl->val); + break; + + case V4L2_CID_VBLANK: + ret = ar0234_write_reg(ar0234, AR0234_REG_VTS, + AR0234_REG_VALUE_16BIT, + ar0234->cur_mode->height + ctrl->val); + dev_dbg(&client->dev, "set vblank %d\n", ar0234->cur_mode->height + ctrl->val); + break; + case V4L2_CID_FLASH_STROBE_SOURCE: + dev_dbg(&client->dev, "set led flash source %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_STROBE: + if (ar0234->platform_data->gpios[0] != -1) { + if (ar0234->strobe_source->val == + V4L2_FLASH_STROBE_SOURCE_SOFTWARE) + gpio_set_value(ar0234->platform_data->gpios[0], 1); + } + dev_info(&client->dev, "turn on led %d\n", ctrl->val); + + break; + + case V4L2_CID_FLASH_STROBE_STOP: + if (ar0234->platform_data->gpios[0] != -1) { + if (ar0234->strobe_source->val == + V4L2_FLASH_STROBE_SOURCE_SOFTWARE) + gpio_set_value(ar0234->platform_data->gpios[0], 0); + } + dev_info(&client->dev, "turn off led %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_TIMEOUT: + ret = ar0234_read_reg(ar0234, AR0234_REG_LED_FLASH_CONTROL, + AR0234_REG_VALUE_16BIT, &val); + + ret = ar0234_write_reg(ar0234, AR0234_REG_LED_FLASH_CONTROL, + AR0234_REG_VALUE_16BIT, + (AR0234_LED_DELAY & ctrl->val) | ((~AR0234_LED_DELAY) & val)); + dev_info(&client->dev, "set led delay %d\n", + AR0234_LED_DELAY & ctrl->val); + break; + case V4L2_CID_VFLIP: + ret = ar0234_read_reg(ar0234, AR0234_REG_IMAGE_ORIENTATION, + AR0234_REG_VALUE_08BIT, &val); + + val &= ~(0x1 << AR0234_VFLIP_BIT); + val |= ctrl->val << AR0234_VFLIP_BIT; + ret = ar0234_write_reg(ar0234, AR0234_REG_IMAGE_ORIENTATION, + AR0234_REG_VALUE_08BIT, + val); + dev_info(&client->dev, "set vflip %d\n", ctrl->val); + break; + case V4L2_CID_HFLIP: + ret = ar0234_read_reg(ar0234, AR0234_REG_IMAGE_ORIENTATION, + AR0234_REG_VALUE_08BIT, &val); + + val &= ~(0x1 << AR0234_HFLIP_BIT); + val |= ctrl->val << AR0234_HFLIP_BIT; + ret = ar0234_write_reg(ar0234, AR0234_REG_IMAGE_ORIENTATION, + AR0234_REG_VALUE_08BIT, + val); + dev_info(&client->dev, "set hflip %d\n", ctrl->val); + 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 struct v4l2_ctrl_config ar0234_csi_port = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_CSI_PORT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "CSI port", + .min = 0, + .max = 5, + .def = 1, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config ar0234_i2c_bus = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_I2C_BUS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C bus", + .min = 0, + .max = MINORMASK, + .def = 0, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config ar0234_i2c_id = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_I2C_ID, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C id", + .min = 0x10, + .max = 0x77, + .def = 0x10, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config ar0234_i2c_slave_address = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_I2C_SLAVE_ADDRESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C slave address", + .min = 0x0, + .max = 0x7f, + .def = 0x10, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config ar0234_fps = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_FPS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "fps", + .min = 10, + .max = 120, + .def = 30, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config ar0234_frame_interval = { + .ops = &ar0234_ctrl_ops, + .id = AR0234_CID_FRAME_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "frame interval", + .min = 0, + .max = 1000, + .def = 25, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static int ar0234_init_controls(struct ar0234 *ar0234) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 hblank; + int ret; + + ctrl_hdlr = &ar0234->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &ar0234->mutex; + ar0234->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (ar0234->link_freq) + ar0234->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ar0234->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_VBLANK, + 0, + AR0234_VTS_MAX - ar0234->cur_mode->height, 1, + ar0234->cur_mode->vts_def - ar0234->cur_mode->height); + v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + AR0234_ANAL_GAIN_MIN, AR0234_ANAL_GAIN_MAX, + AR0234_ANAL_GAIN_STEP, AR0234_ANAL_GAIN_DEFAULT); + v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + AR0234_DGTL_GAIN_MIN, AR0234_DGTL_GAIN_MAX, + AR0234_DGTL_GAIN_STEP, AR0234_DGTL_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); + ar0234->strobe_source = v4l2_ctrl_new_std_menu( + ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_FLASH_STROBE_SOURCE, + 1, 0, 1); + ar0234->strobe = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_FLASH_STROBE, 0, 0, 0, 0); + ar0234->strobe_stop = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_FLASH_STROBE_STOP, 0, 0, 0, 0); + ar0234->timeout = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_FLASH_TIMEOUT, -128, 127, 1, 0); + + ar0234_csi_port.def = ar0234->platform_data->port; + ar0234->csi_port = + v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_csi_port, NULL); + ar0234_i2c_bus.def = i2c_adapter_id(client->adapter); + ar0234->i2c_bus = + v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_i2c_bus, NULL); + ar0234_i2c_id.def = client->addr; + ar0234->i2c_id = v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_i2c_id, NULL); + ar0234_i2c_slave_address.def = ar0234->platform_data->i2c_slave_address; + ar0234->i2c_slave_address = v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_i2c_slave_address, NULL); + ar0234_fps.def = ar0234->cur_mode->fps; + ar0234->fps = v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_fps, NULL); + ar0234_frame_interval.def = 1000 / ar0234->cur_mode->fps; + ar0234->frame_interval = v4l2_ctrl_new_custom(ctrl_hdlr, &ar0234_frame_interval, NULL); + + ar0234->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_PIXEL_RATE, get_pixel_rate(ar0234), get_pixel_rate(ar0234), + 1, get_pixel_rate(ar0234)); + if (ar0234->pixel_rate) + ar0234->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hblank = get_hblank(ar0234); + 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->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 1); + ar0234->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &ar0234_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + 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 link_freq_index, ret; + + reg_list = &ar0234->cur_mode->reg_list; + ret = ar0234_write_reg_list(ar0234, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set mode"); + return ret; + } + + link_freq_index = ar0234->cur_mode->link_freq_index; + if (link_freq_index >= 0) { + reg_list = &link_freq_configs[link_freq_index].reg_list; + ret = ar0234_write_reg_list(ar0234, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set plls"); + return ret; + } + } + + ret = __v4l2_ctrl_handler_setup(ar0234->sd.ctrl_handler); + if (ret) + return ret; + + ret = ar0234_write_reg(ar0234, AR0234_REG_MODE_SELECT, + AR0234_REG_VALUE_16BIT, AR0234_MODE_STREAMING); + if (ret) { + dev_err(&client->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void ar0234_stop_streaming(struct ar0234 *ar0234) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + + if (ar0234_write_reg(ar0234, AR0234_REG_MODE_SELECT, + AR0234_REG_VALUE_16BIT, AR0234_MODE_STANDBY)) + dev_err(&client->dev, "failed to set stream"); + /* + * turn off flash, clear possible noise. + */ + if (ar0234->platform_data->gpios[0] != -1) + gpio_set_value(ar0234->platform_data->gpios[0], 0); +} + +static int ar0234_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ar0234 *ar0234 = to_ar0234(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + if (ar0234->streaming == enable) + return 0; + + mutex_lock(&ar0234->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&ar0234->mutex); + return ret; + } + + ret = ar0234_start_streaming(ar0234); + if (ret) { + enable = 0; + ar0234_stop_streaming(ar0234); + pm_runtime_put(&client->dev); + } + } else { + ar0234_stop_streaming(ar0234); + pm_runtime_put(&client->dev); + } + + ar0234->streaming = enable; + mutex_unlock(&ar0234->mutex); + + return ret; +} + +static int ar0234_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fival) +{ + struct ar0234 *ar0234 = to_ar0234(sd); + + fival->pad = 0; + fival->interval.numerator = 1; + fival->interval.denominator = ar0234->cur_mode->fps; + + return 0; +} + +static int __maybe_unused ar0234_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ar0234 *ar0234 = to_ar0234(sd); + + mutex_lock(&ar0234->mutex); + if (ar0234->streaming) + ar0234_stop_streaming(ar0234); + + mutex_unlock(&ar0234->mutex); + + return 0; +} + +static int __maybe_unused ar0234_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ar0234 *ar0234 = to_ar0234(sd); + int ret; + + mutex_lock(&ar0234->mutex); + if (ar0234->streaming) { + ret = ar0234_start_streaming(ar0234); + if (ret) { + ar0234->streaming = false; + ar0234_stop_streaming(ar0234); + mutex_unlock(&ar0234->mutex); + return ret; + } + } + + mutex_unlock(&ar0234->mutex); + + return 0; +} + +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); + const struct ar0234_mode *mode; + s32 vblank_def; + s64 hblank; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) { + if (supported_modes[i].width != fmt->format.width + || supported_modes[i].height != fmt->format.height) { + dev_dbg(&client->dev, "resolution doesn't match\n"); + continue; + } + if (supported_modes[i].code != fmt->format.code) { + dev_dbg(&client->dev, "pixel format doesn't match\n"); + continue; + } + mode = &supported_modes[i]; + break; + } + + if (i >= ARRAY_SIZE(supported_modes)) + mode = &supported_modes[0]; + + mutex_lock(&ar0234->mutex); + ar0234_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; + } else { + ar0234->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ar0234->link_freq, mode->link_freq_index); + __v4l2_ctrl_modify_range(ar0234->pixel_rate, + get_pixel_rate(ar0234), + get_pixel_rate(ar0234), + 1, + get_pixel_rate(ar0234)); + + hblank = get_hblank(ar0234); + __v4l2_ctrl_modify_range(ar0234->hblank, + hblank, + hblank, + 1, + hblank); + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(ar0234->vblank, + 0, + AR0234_VTS_MAX - mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ar0234->vblank, vblank_def); + + __v4l2_ctrl_s_ctrl(ar0234->fps, mode->fps); + + __v4l2_ctrl_s_ctrl(ar0234->frame_interval, 1000 / mode->fps); + } + + mutex_unlock(&ar0234->mutex); + + return 0; +} + +static int ar0234_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ar0234 *ar0234 = to_ar0234(sd); + + mutex_lock(&ar0234->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&ar0234->sd, + sd_state, fmt->pad); + else + ar0234_update_pad_format(ar0234->cur_mode, &fmt->format); + + mutex_unlock(&ar0234->mutex); + + 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 >= ARRAY_SIZE(supported_formats)) + return -EINVAL; + + code->code = supported_formats[code->index]; + + 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; + + 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_frame_rate[] = { 40, 20 }; + +static int ar0234_enum_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + int mode_size = ARRAY_SIZE(supported_modes); + int i; + + if (fie->index >= ARRAY_SIZE(ar0234_frame_rate)) + return -EINVAL; + + for (i = 0; i < mode_size; i++) + if (fie->code == supported_modes[i].code + && fie->width == supported_modes[i].width + && fie->height == supported_modes[i].height) + break; + + if (i == mode_size) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = ar0234_frame_rate[fie->index]; + + return 0; +} + +static int ar0234_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ar0234 *ar0234 = to_ar0234(sd); + + mutex_lock(&ar0234->mutex); + ar0234_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->state, 0)); + mutex_unlock(&ar0234->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops ar0234_video_ops = { + .s_stream = ar0234_set_stream, + .g_frame_interval = ar0234_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops ar0234_pad_ops = { + .set_fmt = ar0234_set_format, + .get_fmt = ar0234_get_format, + .enum_mbus_code = ar0234_enum_mbus_code, + .enum_frame_size = ar0234_enum_frame_size, + .enum_frame_interval = ar0234_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops ar0234_subdev_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 = { + .open = ar0234_open, +}; + +static int ar0234_identify_module(struct ar0234 *ar0234) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ar0234->sd); + int ret; + u32 val; + + ret = ar0234_read_reg(ar0234, AR0234_REG_CHIP_ID, + AR0234_REG_VALUE_16BIT, &val); + if (ret) + return ret; + + if (val != AR0234_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + 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(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&ar0234->mutex); + +} + +static irqreturn_t ar0234_threaded_irq_fn(int irq, void *dev_id) +{ + struct ar0234 *ar0234 = dev_id; + + if ((ar0234->platform_data->gpios[0] != -1) && (ar0234->platform_data->irq_pin != -1)) { + mutex_lock(&ar0234->mutex); + if (ar0234->streaming == false) { + gpio_set_value(ar0234->platform_data->gpios[0], 0); + goto ar0234_irq_handled; + } + + if (ar0234->strobe_source->val == V4L2_FLASH_STROBE_SOURCE_EXTERNAL) { + gpio_set_value(ar0234->platform_data->gpios[0], + gpio_get_value(ar0234->platform_data->irq_pin)); + } + +ar0234_irq_handled: + mutex_unlock(&ar0234->mutex); + } + + return IRQ_HANDLED; +} + +static int ar0234_probe(struct i2c_client *client) +{ + struct ar0234 *ar0234; + int ret; + + ar0234 = devm_kzalloc(&client->dev, sizeof(*ar0234), GFP_KERNEL); + if (!ar0234) + return -ENOMEM; + + ar0234->platform_data = client->dev.platform_data; + if (ar0234->platform_data == NULL) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + v4l2_i2c_subdev_init(&ar0234->sd, client, &ar0234_subdev_ops); + ret = ar0234_identify_module(ar0234); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + if (ar0234->platform_data->suffix) + snprintf(ar0234->sd.name, + sizeof(ar0234->sd.name), "ar0234 %c", + ar0234->platform_data->suffix); + + mutex_init(&ar0234->mutex); + 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; + 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; + } + + if ((ar0234->platform_data->gpios[0] != -1) && (ar0234->platform_data->irq_pin != -1)) { + ret = devm_gpio_request(&client->dev, + ar0234->platform_data->irq_pin, + ar0234->platform_data->irq_pin_name); + if (ret) { + dev_err(&client->dev, "IRQ pin request failed!\n"); + goto probe_error_v4l2_ctrl_handler_free; + } + gpio_direction_input(ar0234->platform_data->irq_pin); + ret = devm_request_threaded_irq(&client->dev, + gpio_to_irq(ar0234->platform_data->irq_pin), + NULL, ar0234_threaded_irq_fn, + ar0234->platform_data->irq_pin_flags, + ar0234->platform_data->irq_pin_name, + ar0234); + if (ret) { + dev_err(&client->dev, "IRQ request failed!\n"); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = devm_gpio_request_one(&client->dev, + ar0234->platform_data->gpios[0], + GPIOF_OUT_INIT_LOW, "LED"); + if (ret) { + dev_err(&client->dev, "LED GPIO pin request failed!\n"); + goto probe_error_v4l2_ctrl_handler_free; + } + } + + 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_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); + dev_err(&client->dev, "%s Probe Succeeded", ar0234->sd.name); + + return 0; + +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); + mutex_destroy(&ar0234->mutex); + dev_err(&client->dev, "%s Probe Failed", ar0234->sd.name); + + return ret; +} + +static const struct dev_pm_ops ar0234_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ar0234_suspend, ar0234_resume) +}; + +static const struct i2c_device_id ar0234_id_table[] = { + { "ar0234", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, ar0234_id_table); + +static struct i2c_driver ar0234_i2c_driver = { + .driver = { + .name = "ar0234", + .pm = &ar0234_pm_ops, + }, + .probe_new = ar0234_probe, + .remove = ar0234_remove, + .id_table = ar0234_id_table, +}; + +module_i2c_driver(ar0234_i2c_driver); + +MODULE_AUTHOR("Chang, Ying "); +MODULE_DESCRIPTION("ar0234 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/d4xx.c b/drivers/media/i2c/d4xx.c new file mode 100644 index 000000000000..ec67a879ee86 --- /dev/null +++ b/drivers/media/i2c/d4xx.c @@ -0,0 +1,5400 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ds5.c - Intel(R) RealSense(TM) D4XX camera driver + * + * Copyright (c) 2017-2024, INTEL CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +//#define DS5_DRIVER_NAME "DS5 RealSense camera driver" +#define DS5_DRIVER_NAME "d4xx" +#define DS5_DRIVER_NAME_AWG "d4xx-awg" +#define DS5_DRIVER_NAME_ASR "d4xx-asr" +#define DS5_DRIVER_NAME_CLASS "d4xx-class" +#define DS5_DRIVER_NAME_DFU "d4xx-dfu" + +#define DS5_MIPI_SUPPORT_LINES 0x0300 +#define DS5_MIPI_SUPPORT_PHY 0x0304 +#define DS5_MIPI_DATARATE_MIN 0x0308 +#define DS5_MIPI_DATARATE_MAX 0x030A +#define DS5_FW_VERSION 0x030C +#define DS5_FW_BUILD 0x030E +#define DS5_DEVICE_TYPE 0x0310 +#define DS5_DEVICE_TYPE_D45X 6 +#define DS5_DEVICE_TYPE_D43X 5 +#define DS5_DEVICE_TYPE_D46X 4 + +#define DS5_MIPI_LANE_NUMS 0x0400 +#define DS5_MIPI_LANE_DATARATE 0x0402 +#define DS5_MIPI_CONF_STATUS 0x0500 + +#define DS5_START_STOP_STREAM 0x1000 +#define DS5_DEPTH_STREAM_STATUS 0x1004 +#define DS5_RGB_STREAM_STATUS 0x1008 +#define DS5_IMU_STREAM_STATUS 0x100C +#define DS5_IR_STREAM_STATUS 0x1014 + +#define DS5_STREAM_DEPTH 0x0 +#define DS5_STREAM_RGB 0x1 +#define DS5_STREAM_IMU 0x2 +#define DS5_STREAM_IR 0x4 +#define DS5_STREAM_STOP 0x100 +#define DS5_STREAM_START 0x200 +#define DS5_STREAM_IDLE 0x1 +#define DS5_STREAM_STREAMING 0x2 + +#define DS5_DEPTH_STREAM_DT 0x4000 +#define DS5_DEPTH_STREAM_MD 0x4002 +#define DS5_DEPTH_RES_WIDTH 0x4004 +#define DS5_DEPTH_RES_HEIGHT 0x4008 +#define DS5_DEPTH_FPS 0x400C +#define DS5_DEPTH_OVERRIDE 0x401C + +#define DS5_RGB_STREAM_DT 0x4020 +#define DS5_RGB_STREAM_MD 0x4022 +#define DS5_RGB_RES_WIDTH 0x4024 +#define DS5_RGB_RES_HEIGHT 0x4028 +#define DS5_RGB_FPS 0x402C + +#define DS5_IMU_STREAM_DT 0x4040 +#define DS5_IMU_STREAM_MD 0x4042 +#define DS5_IMU_RES_WIDTH 0x4044 +#define DS5_IMU_RES_HEIGHT 0x4048 +#define DS5_IMU_FPS 0x404C + +#define DS5_IR_STREAM_DT 0x4080 +#define DS5_IR_STREAM_MD 0x4082 +#define DS5_IR_RES_WIDTH 0x4084 +#define DS5_IR_RES_HEIGHT 0x4088 +#define DS5_IR_FPS 0x408C +#define DS5_IR_OVERRIDE 0x409C + +#define DS5_DEPTH_CONTROL_BASE 0x4100 +#define DS5_RGB_CONTROL_BASE 0x4200 +#define DS5_MANUAL_EXPOSURE_LSB 0x0000 +#define DS5_MANUAL_EXPOSURE_MSB 0x0002 +#define DS5_MANUAL_GAIN 0x0004 +#define DS5_LASER_POWER 0x0008 +#define DS5_AUTO_EXPOSURE_MODE 0x000C +#define DS5_EXPOSURE_ROI_TOP 0x0010 +#define DS5_EXPOSURE_ROI_LEFT 0x0014 +#define DS5_EXPOSURE_ROI_BOTTOM 0x0018 +#define DS5_EXPOSURE_ROI_RIGHT 0x001C +#define DS5_MANUAL_LASER_POWER 0x0024 +#define DS5_PWM_FREQUENCY 0x0028 + +#define DS5_DEPTH_CONFIG_STATUS 0x4800 +#define DS5_RGB_CONFIG_STATUS 0x4802 +#define DS5_IMU_CONFIG_STATUS 0x4804 +#define DS5_IR_CONFIG_STATUS 0x4808 + +#define DS5_STATUS_STREAMING 0x1 +#define DS5_STATUS_INVALID_DT 0x2 +#define DS5_STATUS_INVALID_RES 0x4 +#define DS5_STATUS_INVALID_FPS 0x8 + +#define MIPI_LANE_RATE 1000 + +#define MAX_DEPTH_EXP 200000 +#define MAX_RGB_EXP 10000 +#define DEF_DEPTH_EXP 33000 +#define DEF_RGB_EXP 1660 + +/* Currently both depth and IR use VC 0 */ +#define DS5_DEPTH_VCHAN_N 0 +#define DS5_MOTION_T_VCHAN_N 0 +//#define DS5_DEBUG_VCHAN_N 1 +//#define DS5_MOTION_T_VCHAN_N 2 + +#define MAX9295_REG0 0x0000 +#define MAX9295_I2C_4 0x0044 +#define MAX9295_I2C_5 0x0045 + +#define MAX9296_CTRL0 0x0010 +#define RESET_LINK (0x1 << 6) +#define RESET_ONESHOT (0x1 << 5) +#define AUTO_LINK (0x1 << 4) +#define DUAL_LINK (0x0) +#define LINK_A (0x1) +#define LINK_B (0x2) +#define SPLITTER (0x3) +#define MAX9296_NUM (4) + +#define MAX9295_I2C_ADDR_DEF 0x40 +#define D457_I2C_ADDR 0x10 + +enum ds5_mux_pad { + DS5_MUX_PAD_EXTERNAL, + DS5_MUX_PAD_DEPTH_A, + DS5_MUX_PAD_RGB_A, + DS5_MUX_PAD_MOTION_T_A, + DS5_MUX_PAD_IMU_A, + DS5_MUX_PAD_DEPTH_B, + DS5_MUX_PAD_RGB_B, + DS5_MUX_PAD_MOTION_T_B, + DS5_MUX_PAD_IMU_B, + DS5_MUX_PAD_COUNT, +}; + +enum ds5_sid { + DS5_SID_MUX_A = -1, + DS5_SID_DEPTH_A, + DS5_SID_RGB_A, + DS5_SID_MOTION_T_A, + DS5_SID_IMU_A, + DS5_SID_DEPTH_B, + DS5_SID_RGB_B, +}; + +#define DS5_N_CONTROLS 8 + +#define CSI2_MAX_VIRTUAL_CHANNELS 4 + +#define DFU_WAIT_RET_LEN 6 + +#define DS5_START_POLL_TIME 10 +#define DS5_START_MAX_TIME 1000 +#define DS5_START_MAX_COUNT (DS5_START_MAX_TIME / DS5_START_POLL_TIME) + +#define MAX_D457_COUNT 5 +#define D457_MAX_SUBSTREAM 10 + +/* DFU definition section */ +#define DFU_MAGIC_NUMBER "/0x01/0x02/0x03/0x04" +#define DFU_BLOCK_SIZE 1024 +#define ds5_read_with_check(state, addr, val) {\ + if (ds5_read(state, addr, val)) \ + return -EINVAL; } +#define ds5_raw_read_with_check(state, addr, buf, size) {\ + if (ds5_raw_read(state, addr, buf, size)) \ + return -EINVAL; } +#define ds5_write_with_check(state, addr, val) {\ + if (ds5_write(state, addr, val)) \ + return -EINVAL; } +#define ds5_raw_write_with_check(state, addr, buf, size) {\ + if (ds5_raw_write(state, addr, buf, size)) \ + return -EINVAL; } +#define max9296_write_8_with_check(state, addr, buf) {\ + if (max9296_write_8(state, addr, buf)) \ + return -EINVAL; \ + } +#define max9295_write_8_with_check(state, addr, buf) {\ + if (max9295_write_8(state, addr, buf)) \ + return -EINVAL; \ + } +#define D4XX_LINK_FREQ_360MHZ 360000000ULL +#define D4XX_LINK_FREQ_300MHZ 300000000ULL +#define D4XX_LINK_FREQ_288MHZ 288000000ULL +#define D4XX_LINK_FREQ_240MHZ 240000000ULL +#define D4XX_LINK_FREQ_225MHZ 22500000ULL + +enum dfu_fw_state { + appIDLE = 0x0000, + appDETACH = 0x0001, + dfuIDLE = 0x0002, + dfuDNLOAD_SYNC = 0x0003, + dfuDNBUSY = 0x0004, + dfuDNLOAD_IDLE = 0x0005, + dfuMANIFEST_SYNC = 0x0006, + dfuMANIFEST = 0x0007, + dfuMANIFEST_WAIT_RESET = 0x0008, + dfuUPLOAD_IDLE = 0x0009, + dfuERROR = 0x000a +}; + +enum dfu_state { + DS5_DFU_IDLE = 0, + DS5_DFU_RECOVERY, + DS5_DFU_OPEN, + DS5_DFU_IN_PROGRESS, + DS5_DFU_DONE, + DS5_DFU_ERROR +} dfu_state_t; + +struct hwm_cmd { + u16 header; + u16 magic_word; + u32 opcode; + u32 param1; + u32 param2; + u32 param3; + u32 param4; + unsigned char Data[]; +}; + +static const struct hwm_cmd cmd_switch_to_dfu = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x1e, + .param1 = 0x01, +}; + +enum table_id { + COEF_CALIBRATION_ID = 0x19, + DEPTH_CALIBRATION_ID = 0x1f, + RGB_CALIBRATION_ID = 0x20, + IMU_CALIBRATION_ID = 0x22 +} table_id_t; + +static const struct hwm_cmd get_calib_data = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x15, + .param1 = 0x00, //table_id +}; + +static const struct hwm_cmd set_calib_data = { + .header = 0x0114, + .magic_word = 0xCDAB, + .opcode = 0x62, + .param1 = 0x00, //table_id + .param2 = 0x02, //region +}; + +static const struct hwm_cmd gvd = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x10, +}; + +static const struct hwm_cmd set_ae_roi = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x44, +}; + +static const struct hwm_cmd get_ae_roi = { + .header = 0x014, + .magic_word = 0xCDAB, + .opcode = 0x45, +}; + +static const struct hwm_cmd set_ae_setpoint = { + .header = 0x18, + .magic_word = 0xCDAB, + .opcode = 0x2B, + .param1 = 0xa, // AE control +}; + +static const struct hwm_cmd get_ae_setpoint = { + .header = 0x014, + .magic_word = 0xCDAB, + .opcode = 0x2C, + .param1 = 0xa, // AE control + .param2 = 0, // get current +}; + +static const struct hwm_cmd erb = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x17, +}; + +static const struct hwm_cmd ewb = { + .header = 0x14, + .magic_word = 0xCDAB, + .opcode = 0x18, +}; + +static const s64 link_freq_menu_items[] = { + D4XX_LINK_FREQ_360MHZ, + D4XX_LINK_FREQ_300MHZ, + D4XX_LINK_FREQ_288MHZ, + D4XX_LINK_FREQ_240MHZ, + D4XX_LINK_FREQ_225MHZ, +}; + +struct __fw_status { + uint32_t spare1; + uint32_t FW_lastVersion; + uint32_t FW_highestVersion; + uint16_t FW_DownloadStatus; + uint16_t DFU_isLocked; + uint16_t DFU_version; + uint8_t ivcamSerialNum[8]; + uint8_t spare2[42]; +}; + +/*************************/ + +struct ds5_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl_handler handler_depth; + struct v4l2_ctrl_handler handler_depth_b; + struct v4l2_ctrl_handler handler_rgb; + struct v4l2_ctrl_handler handler_rgb_b; + struct v4l2_ctrl_handler handler_y8; + struct v4l2_ctrl_handler handler_imu; + struct { + struct v4l2_ctrl *log; + struct v4l2_ctrl *fw_version; + struct v4l2_ctrl *gvd; + struct v4l2_ctrl *get_depth_calib; + struct v4l2_ctrl *set_depth_calib; + struct v4l2_ctrl *get_coeff_calib; + struct v4l2_ctrl *set_coeff_calib; + struct v4l2_ctrl *ae_roi_get; + struct v4l2_ctrl *ae_roi_set; + struct v4l2_ctrl *ae_setpoint_get; + struct v4l2_ctrl *ae_setpoint_set; + struct v4l2_ctrl *erb; + struct v4l2_ctrl *ewb; + struct v4l2_ctrl *hwmc; + struct v4l2_ctrl *laser_power; + struct v4l2_ctrl *manual_laser_power; + struct v4l2_ctrl *auto_exp; + struct v4l2_ctrl *exposure; + /* in DS5 manual gain only works with manual exposure */ + struct v4l2_ctrl *gain; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *query_sub_stream; + struct v4l2_ctrl *set_sub_stream; + }; +}; + +struct ds5_resolution { + u16 width; + u16 height; + u8 n_framerates; + const u16 *framerates; +}; + +struct ds5_format { + unsigned int n_resolutions; + const struct ds5_resolution *resolutions; + u32 mbus_code; + u8 data_type; +}; + +struct ds5_sensor { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt format; + u16 mux_pad; + struct { + const struct ds5_format *format; + const struct ds5_resolution *resolution; + u16 framerate; + } config; + bool streaming; + /*struct ds5_vchan *vchan;*/ + const struct ds5_format *formats; + unsigned int n_formats; +}; + +struct ds5_des { + struct v4l2_subdev sd; + struct media_pad pads[2]; + struct v4l2_mbus_framefmt format; + u16 mux_pad; +}; + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +#include +#define ds5_mux_subdev camera_common_data +#else +struct ds5_mux_subdev { + struct v4l2_subdev subdev; +}; +#endif + +struct ds5_variant { + const struct ds5_format *formats; + unsigned int n_formats; +}; + +struct ds5_dfu_dev { + struct cdev ds5_cdev; + struct class *ds5_class; + int device_open_count; + enum dfu_state dfu_state_flag; + unsigned char *dfu_msg; + u16 msg_write_once; + unsigned char init_v4l_f; +}; + +enum { + DS5_DS5U, + DS5_ASR, + DS5_AWG, +}; + +#define NR_OF_DS5_PADS 7 +#define NR_OF_DS5_STREAMS (NR_OF_DS5_PADS - 1) + +struct v4l2_mbus_framefmt ds5_ffmts[NR_OF_DS5_PADS]; + +struct ds5 { + struct { + struct ds5_sensor sensor; + } depth; + struct { + struct ds5_sensor sensor; + } depth_b; + struct { + struct ds5_sensor sensor; + } motion_t; + struct { + struct ds5_sensor sensor; + } rgb; + struct { + struct ds5_sensor sensor; + } rgb_b; + struct { + struct ds5_sensor sensor; + } imu; + struct { + struct ds5_des des; + } max9296; + struct { + struct ds5_mux_subdev sd; + struct media_pad pads[DS5_MUX_PAD_COUNT]; + struct ds5_sensor *last_set; + } mux; + struct ds5_ctrls ctrls; + struct ds5_dfu_dev dfu_dev; + bool power; + struct i2c_client *client; + /*struct ds5_vchan virtual_channels[CSI2_MAX_VIRTUAL_CHANNELS];*/ + /* All below pointers are used for writing, cannot be const */ + struct mutex lock; + struct regmap *regmap; + struct regmap *regmap_max9296; + struct regmap *regmap_max9295; + struct regulator *vcc; + const struct ds5_variant *variant; + int is_depth; + int is_depth_b; + int is_y8; + int is_rgb; + int is_rgb_b; + int is_imu; + u16 fw_version; + u16 fw_build; +}; + +struct ds5_counters { + unsigned int n_res; + unsigned int n_fmt; + unsigned int n_ctrl; +}; + +#define ds5_from_depth_sd(sd) container_of(sd, struct ds5, depth.sd) +#define ds5_from_motion_t_sd(sd) container_of(sd, struct ds5, motion_t.sd) +#define ds5_from_rgb_sd(sd) container_of(sd, struct ds5, rgb.sd) + +static inline void msleep_range(unsigned int delay_base) +{ + usleep_range(delay_base * 1000, delay_base * 1000 + 500); +} + +static int max9296_write_8(struct ds5 *state, u16 reg, u8 val) +{ + int ret; + + ret = regmap_raw_write(state->regmap_max9296, reg, &val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, val); + + return ret; +} + +static int max9296_read_8(struct ds5 *state, u16 reg, u8 *val) +{ + int ret; + + ret = regmap_raw_read(state->regmap_max9296, reg, val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c read 0x%04x = 0x%x\n", + __func__, reg, *val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, *val); + + return ret; +} +static int max9295_write_8(struct ds5 *state, u16 reg, u8 val) +{ + int ret; + + ret = regmap_raw_write(state->regmap_max9295, reg, &val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c write failed %d, 0x%04x = 0x%x\n", + __func__, ret, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c write 0x%04x: 0x%x\n", + __func__, reg, val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, val); + + return ret; +} + +static int max9295_read_8(struct ds5 *state, u16 reg, u8 *val) +{ + int ret; + + ret = regmap_raw_read(state->regmap_max9295, reg, val, 1); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_info(&state->client->dev, "%s(): i2c read 0x%04x = 0x%x\n", + __func__, reg, *val); + + dev_dbg(&state->client->dev, "%s(): (%d), 0x%02x 0x%04x = 0x%02x\n", + __func__, ret, state->client->addr, reg, *val); + + return ret; +} + +static int ds5_write(struct ds5 *state, u16 reg, u16 val) +{ + int ret; + u8 value[2]; + + value[1] = val >> 8; + value[0] = val & 0x00FF; + + dev_dbg(&state->client->dev, + "%s(): writing to register: 0x%02x, (0x%04x) value1: 0x%x, value2:0x%x\n", + __func__, state->client->addr, reg, value[1], value[0]); + + ret = regmap_raw_write(state->regmap, reg, value, sizeof(value)); + if (ret < 0) + dev_err(&state->client->dev, + "%s(): i2c write failed %d,0x%02x ,0x%04x = 0x%x\n", + __func__, ret, state->client->addr, reg, val); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s():0x%02x i2c write 0x%04x: 0x%x\n", + __func__, state->client->addr, reg, val); + + return ret; +} + +static int ds5_raw_write(struct ds5 *state, u16 reg, + const void *val, size_t val_len) +{ + int ret = regmap_raw_write(state->regmap, reg, val, val_len); + if (ret < 0) + dev_err(&state->client->dev, + "%s(): i2c raw write failed %d, %04x size(%d) bytes\n", + __func__, ret, reg, (int)val_len); + else + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, + "%s(): i2c raw write 0x%04x: %d bytes\n", + __func__, reg, (int)val_len); + + return ret; +} + +static int ds5_read(struct ds5 *state, u16 reg, u16 *val) +{ + int ret = regmap_raw_read(state->regmap, reg, val, 2); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + else { + if (state->dfu_dev.dfu_state_flag == DS5_DFU_IDLE) + dev_dbg(&state->client->dev, "%s(): i2c read 0x%04x: 0x%x\n", + __func__, reg, *val); + } + + return ret; +} + +static int ds5_raw_read(struct ds5 *state, u16 reg, void *val, size_t val_len) +{ + int ret = regmap_raw_read(state->regmap, reg, val, val_len); + if (ret < 0) + dev_err(&state->client->dev, "%s(): i2c read failed %d, 0x%04x\n", + __func__, ret, reg); + + return ret; +} + +static int pad_to_substream[DS5_MUX_PAD_COUNT]; + +static s64 d4xx_query_sub_stream[MAX_D457_COUNT][D457_MAX_SUBSTREAM] = {0}; + +static void set_sub_stream_fmt(s64 *query_sub_stream, int index, u32 code) +{ + query_sub_stream[index] &= 0xFFFFFFFFFFFF0000; + query_sub_stream[index] |= code; +} + +static void set_sub_stream_h(s64 *query_sub_stream, int index, u32 height) +{ + s64 val = height; + + val &= 0xFFFF; + query_sub_stream[index] &= 0xFFFFFFFF0000FFFF; + query_sub_stream[index] |= val << 16; +} + +static void set_sub_stream_w(s64 *query_sub_stream, int index, u32 width) +{ + s64 val = width; + + val &= 0xFFFF; + query_sub_stream[index] &= 0xFFFF0000FFFFFFFF; + query_sub_stream[index] |= val << 32; +} + +static void set_sub_stream_dt(s64 *query_sub_stream, int index, u32 dt) +{ + s64 val = dt; + + val &= 0xFF; + query_sub_stream[index] &= 0xFF00FFFFFFFFFFFF; + query_sub_stream[index] |= val << 48; +} + +static void set_sub_stream_vc_id(s64 *query_sub_stream, int index, u32 vc_id) +{ + s64 val = vc_id; + + val &= 0xFF; + query_sub_stream[index] &= 0x00FFFFFFFFFFFFFF; + query_sub_stream[index] |= val << 56; +} + +static int get_sub_stream_vc_id(const s64 *query_sub_stream, int index) +{ + s64 val = 0; + val = query_sub_stream[index] >> 56; + val &= 0xFF; + return (int)val; +} + +static u8 d4xx_set_sub_stream[] = { + 0, 0, 0, 0, 0, 0 +}; + +/* Pad ops */ + +static const u16 ds5_default_framerate = 30; + +// ********************** +// FIXME: D16 width must be doubled, because an 8-bit format is used. Check how +// the Tegra driver propagates resolutions and formats. +// ********************** + +//TODO: keep 6, till 5 is supported by FW +static const u16 ds5_framerates[] = {5, 30}; + +#define DS5_FRAMERATE_DEFAULT_IDX 1 + +static const u16 ds5_framerate_30 = 30; + +static const u16 ds5_framerate_15_30[] = {15, 30}; + +static const u16 ds5_framerate_25 = 25; + +static const u16 ds5_depth_framerate_to_30[] = {5, 15, 30}; +static const u16 ds5_framerate_to_30[] = {5, 10, 15, 30}; +static const u16 ds5_framerate_to_60[] = {5, 15, 30, 60}; +static const u16 ds5_framerate_to_90[] = {5, 15, 30, 60, 90}; +static const u16 ds5_framerate_100[] = {100}; +static const u16 ds5_framerate_90[] = {90}; +static const u16 ds5_imu_framerates[] = {50, 100, 200, 400}; + +static const struct ds5_resolution d43x_depth_sizes[] = { + { + .width = 1280, + .height = 720, + .framerates = ds5_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_depth_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 848, + .height = 100, + .framerates = ds5_framerate_100, + .n_framerates = ARRAY_SIZE(ds5_framerate_100), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 256, + .height = 144, + .framerates = ds5_framerate_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_90), + }, +}; + +static const struct ds5_resolution d46x_depth_sizes[] = { + { + .width = 1280, + .height = 960, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, +}; + +static const struct ds5_resolution y8_sizes[] = { + { + .width = 1280, + .height = 720, + .framerates = ds5_depth_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_depth_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + } +}; + +static const struct ds5_resolution ds5_rlt_rgb_sizes[] = { + { + .width = 1280, + .height = 800, + .framerates = ds5_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_30), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_framerate_to_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_30), + }, { + .width = 848, + .height = 480, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 640, + .height = 360, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 480, + .height = 270, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 424, + .height = 240, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, +}; + +static const struct ds5_resolution ds5_onsemi_rgb_sizes[] = { + { + .width = 640, + .height = 480, + .framerates = ds5_framerate_to_90, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_90), + }, { + .width = 960, + .height = 720, + .framerates = ds5_framerate_to_60, + .n_framerates = ARRAY_SIZE(ds5_framerate_to_60), + }, { + .width = 1280, + .height = 720, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 1920, + .height = 1080, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, { + .width = 2048, + .height = 1536, + .framerates = ds5_framerates, + .n_framerates = ARRAY_SIZE(ds5_framerates), + }, +}; + +static const struct ds5_resolution ds5_size_w10 = { + .width = 1920, + .height = 1080, + .framerates = &ds5_framerate_30, + .n_framerates = 1, +}; + +static const struct ds5_resolution d43x_calibration_sizes[] = { + { + .width = 1280, + .height = 800, + .framerates = ds5_framerate_15_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_30), + }, +}; + +static const struct ds5_resolution d46x_calibration_sizes[] = { + { + .width = 1600, + .height = 1300, + .framerates = ds5_framerate_15_30, + .n_framerates = ARRAY_SIZE(ds5_framerate_15_30), + }, +}; + +static const struct ds5_resolution ds5_size_imu[] = { + { + .width = 32, + .height = 1, + .framerates = ds5_imu_framerates, + .n_framerates = ARRAY_SIZE(ds5_imu_framerates), + }, +}; + +static const struct ds5_format ds5_depth_formats_d43x[] = { + { + // TODO: 0x31 is replaced with 0x1e since it caused low FPS in Jetson. + .data_type = 0x1e, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(d43x_depth_sizes), + .resolutions = d43x_depth_sizes, + }, { + .data_type = 0x2a, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(d43x_depth_sizes), + .resolutions = d43x_depth_sizes, + }, { + .data_type = 0x24, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d43x_calibration_sizes), + .resolutions = d43x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_depth_formats_d46x[] = { + { + // TODO: 0x31 is replaced with 0x1e since it caused low FPS in Jetson. + .data_type = 0x1e, /* Z16 */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(d46x_depth_sizes), + .resolutions = d46x_depth_sizes, + }, { + /* First format: default */ + .data_type = 0x2a, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(d46x_depth_sizes), + .resolutions = d46x_depth_sizes, + }, { + .data_type = 0x24, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d43x_calibration_sizes), + .resolutions = d43x_calibration_sizes, + }, +}; + +#define DS5_DEPTH_N_FORMATS 1 + +static const struct ds5_format ds5_y_formats_ds5u[] = { + { + /* First format: default */ + .data_type = 0x2a, /* Y8 */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = 0x1e, /* Y8I */ + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .n_resolutions = ARRAY_SIZE(y8_sizes), + .resolutions = y8_sizes, + }, { + .data_type = 0x24, /* 24-bit Calibration */ + .mbus_code = MEDIA_BUS_FMT_RGB888_1X24, /* FIXME */ + .n_resolutions = ARRAY_SIZE(d43x_calibration_sizes), + .resolutions = d43x_calibration_sizes, + }, +}; + +static const struct ds5_format ds5_rlt_rgb_format = { + .data_type = 0x1e, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .n_resolutions = ARRAY_SIZE(ds5_rlt_rgb_sizes), + .resolutions = ds5_rlt_rgb_sizes, +}; +#define DS5_RLT_RGB_N_FORMATS 1 + +static const struct ds5_format ds5_onsemi_rgb_format = { + .data_type = 0x1e, /* UYVY */ + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .n_resolutions = ARRAY_SIZE(ds5_onsemi_rgb_sizes), + .resolutions = ds5_onsemi_rgb_sizes, +}; +#define DS5_ONSEMI_RGB_N_FORMATS 1 + +static const struct ds5_variant ds5_variants[] = { + [DS5_DS5U] = { + .formats = ds5_y_formats_ds5u, + .n_formats = ARRAY_SIZE(ds5_y_formats_ds5u), + }, +}; + +static const struct ds5_format ds5_imu_formats[] = { + { + /* First format: default */ + .data_type = 0x2a, /* IMU DT */ + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .n_resolutions = ARRAY_SIZE(ds5_size_imu), + .resolutions = ds5_size_imu, + }, +}; + +static const struct v4l2_mbus_framefmt ds5_mbus_framefmt_template = { + .width = 0, + .height = 0, + .code = MEDIA_BUS_FMT_FIXED, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +/* Get readable sensor name */ +static const char *ds5_get_sensor_name(struct ds5 *state) +{ + static const char *sensor_name[] = {"unknown", "RGB", "DEPTH", "Y8", "IMU"}; + int sensor_id = state->is_rgb | state->is_rgb_b; + sensor_id += (state->is_depth | state->is_depth_b) * 2; + sensor_id += state->is_y8 * 3 + state->is_imu * 4; + + if (sensor_id >= (sizeof(sensor_name)/sizeof(*sensor_name))) + sensor_id = 0; + + return sensor_name[sensor_id]; +} + +static void ds5_set_state_last_set(struct ds5 *state) +{ + dev_info(&state->client->dev, "%s(): %s\n", + __func__, ds5_get_sensor_name(state)); + + if (state->is_depth) + state->mux.last_set = &state->depth.sensor; + else if (state->is_rgb) + state->mux.last_set = &state->rgb.sensor; + else if (state->is_rgb_b) + state->mux.last_set = &state->rgb_b.sensor; + else if (state->is_depth_b) + state->mux.last_set = &state->depth_b.sensor; + else if (state->is_y8) + state->mux.last_set = &state->motion_t.sensor; + else + state->mux.last_set = &state->imu.sensor; +} + +/* This is needed for .get_fmt() + * and if streaming is started without .set_fmt() + */ +static void ds5_sensor_format_init(struct ds5_sensor *sensor) +{ + const struct ds5_format *fmt; + struct v4l2_mbus_framefmt *ffmt; + unsigned int i; + + if (sensor->config.format) + return; + + dev_info(sensor->sd.dev, "%s(): on pad %u\n", __func__, sensor->mux_pad); + + ffmt = &sensor->format; + *ffmt = ds5_mbus_framefmt_template; + /* Use the first format */ + fmt = sensor->formats; + ffmt->code = fmt->mbus_code; + /* and the first resolution */ + ffmt->width = fmt->resolutions->width; + ffmt->height = fmt->resolutions->height; + + sensor->config.format = fmt; + sensor->config.resolution = fmt->resolutions; + /* Set default framerate to 30, or to 1st one if not supported */ + for (i = 0; i < fmt->resolutions->n_framerates; i++) { + if (fmt->resolutions->framerates[i] == ds5_framerate_30 /* fps */) { + sensor->config.framerate = ds5_framerate_30; + return; + } + } + sensor->config.framerate = fmt->resolutions->framerates[0]; +} + +/* No locking needed for enumeration methods */ +static int ds5_sensor_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_mbus_code_enum *mce) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + //struct ds5_vchan *vchan = sensor->vchan; + dev_info(sensor->sd.dev, "%s(): sensor %s pad: %d index: %d\n", + __func__, sensor->sd.name, mce->pad, mce->index); + if (mce->pad) + return -EINVAL; + + if (mce->index >= sensor->n_formats) + return -EINVAL; + + mce->code = sensor->formats[mce->index].mbus_code; + + return 0; +} + +static int ds5_sensor_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + const struct ds5_format *fmt; + unsigned int i; + + dev_info(sensor->sd.dev, "%s(): sensor %s is %s\n", + __func__, sensor->sd.name, ds5_get_sensor_name(state)); + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) + if (fse->code == fmt->mbus_code) + break; + + if (i == sensor->n_formats) + return -EINVAL; + + if (fse->index >= fmt->n_resolutions) + return -EINVAL; + + fse->min_width = fse->max_width = fmt->resolutions[fse->index].width; + fse->min_height = fse->max_height = fmt->resolutions[fse->index].height; + + return 0; +} + +static int ds5_sensor_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + const struct ds5_format *fmt; + const struct ds5_resolution *res; + unsigned int i; + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) + if (fie->code == fmt->mbus_code) + break; + + if (i == sensor->n_formats) + return -EINVAL; + + for (i = 0, res = fmt->resolutions; i < fmt->n_resolutions; i++, res++) + if (res->width == fie->width && res->height == fie->height) + break; + + if (i == fmt->n_resolutions) + return -EINVAL; + + if (fie->index >= res->n_framerates) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = res->framerates[fie->index]; + + return 0; +} + +static int ds5_sensor_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_format *fmt) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + + if (fmt->pad) + return -EINVAL; + + mutex_lock(&state->lock); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(sd, v4l2_state, fmt->pad); + else + fmt->format = sensor->format; + + mutex_unlock(&state->lock); + + dev_info(sd->dev, "%s(): pad %x, code %x, res %ux%u\n", + __func__, fmt->pad, fmt->format.code, + fmt->format.width, fmt->format.height); + + return 0; +} + +/* Called with lock held */ +static const struct ds5_format *ds5_sensor_find_format( + struct ds5_sensor *sensor, + struct v4l2_mbus_framefmt *ffmt, + const struct ds5_resolution **best) +{ + const struct ds5_resolution *res; + const struct ds5_format *fmt; + unsigned long best_delta = ~0; + unsigned int i; + + for (i = 0, fmt = sensor->formats; i < sensor->n_formats; i++, fmt++) { + if (fmt->mbus_code == ffmt->code) + break; + } + dev_info(sensor->sd.dev, "%s(): mbus_code = %x, code = %x \n", + __func__, fmt->mbus_code, ffmt->code); + + if (i == sensor->n_formats) { + /* Not found, use default */ + dev_dbg(sensor->sd.dev, "%s:%d Not found, use default\n", + __func__, __LINE__); + fmt = sensor->formats; + } + for (i = 0, res = fmt->resolutions; i < fmt->n_resolutions; i++, res++) { + unsigned long delta = abs(ffmt->width * ffmt->height - + res->width * res->height); + if (delta < best_delta) { + best_delta = delta; + *best = res; + } + } + + ffmt->code = fmt->mbus_code; + ffmt->width = (*best)->width; + ffmt->height = (*best)->height; + + ffmt->field = V4L2_FIELD_NONE; + /* Should we use V4L2_COLORSPACE_RAW for Y12I? */ + ffmt->colorspace = V4L2_COLORSPACE_SRGB; + + return fmt; +} + +#define MIPI_CSI2_TYPE_NULL 0x10 +#define MIPI_CSI2_TYPE_BLANKING 0x11 +#define MIPI_CSI2_TYPE_EMBEDDED8 0x12 +#define MIPI_CSI2_TYPE_YUV422_8 0x1e +#define MIPI_CSI2_TYPE_YUV422_10 0x1f +#define MIPI_CSI2_TYPE_RGB565 0x22 +#define MIPI_CSI2_TYPE_RGB888 0x24 +#define MIPI_CSI2_TYPE_RAW6 0x28 +#define MIPI_CSI2_TYPE_RAW7 0x29 +#define MIPI_CSI2_TYPE_RAW8 0x2a +#define MIPI_CSI2_TYPE_RAW10 0x2b +#define MIPI_CSI2_TYPE_RAW12 0x2c +#define MIPI_CSI2_TYPE_RAW14 0x2d +/* 1-8 */ +#define MIPI_CSI2_TYPE_USER_DEF(i) (0x30 + (i) - 1) + +static unsigned int mbus_code_to_mipi(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB565_1X16: + return MIPI_CSI2_TYPE_RGB565; + case MEDIA_BUS_FMT_RGB888_1X24: + return MIPI_CSI2_TYPE_RGB888; + case MEDIA_BUS_FMT_YUYV10_1X20: + return MIPI_CSI2_TYPE_YUV422_10; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + return MIPI_CSI2_TYPE_YUV422_8; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return MIPI_CSI2_TYPE_RAW12; + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + return MIPI_CSI2_TYPE_RAW10; + case MEDIA_BUS_FMT_Y8_1X8: + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + return MIPI_CSI2_TYPE_RAW8; + case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: + case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: + case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: + case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: + return MIPI_CSI2_TYPE_USER_DEF(1); + default: + WARN_ON(1); + return -EINVAL; + } +} + +static s64 *match_d4xx_sub_stream_qmenu(const s64 *qmenu_int) +{ + int i = 0; + s64 *matched_qmenu_int = NULL; + + for (i = 0; i < MAX_D457_COUNT; i++) { + if (d4xx_query_sub_stream[i] == qmenu_int) { + matched_qmenu_int = d4xx_query_sub_stream[i]; + break; + } + } + + return matched_qmenu_int; +} + +static int __ds5_sensor_set_fmt(struct ds5 *state, struct ds5_sensor *sensor, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf;// = &fmt->format; + int substream = -1; + s64 *query_sub_stream = NULL; + + dev_dbg(sensor->sd.dev, "%s(): state %p, " + "sensor %p, fmt %p, fmt->format %p\n", + __func__, state, sensor, fmt, &fmt->format); + + mf = &fmt->format; + + if (fmt->pad) + return -EINVAL; + + mutex_lock(&state->lock); + + sensor->config.format = ds5_sensor_find_format(sensor, mf, + &sensor->config.resolution); + //r = DS5_FRAMERATE_DEFAULT_IDX < sensor->config.resolution->n_framerates ? + // DS5_FRAMERATE_DEFAULT_IDX : 0; + /* FIXME: check if a framerate has been set */ + //sensor->config.framerate = sensor->config.resolution->framerates[r]; + + if (v4l2_state && fmt->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(&sensor->sd, v4l2_state, fmt->pad) = *mf; + + else +// FIXME: use this format in .s_stream() + sensor->format = *mf; + + state->mux.last_set = sensor; + + mutex_unlock(&state->lock); + + substream = pad_to_substream[sensor->mux_pad]; + + if (substream != -1) { + query_sub_stream = match_d4xx_sub_stream_qmenu(state->ctrls.query_sub_stream->qmenu_int); + if (query_sub_stream) { + set_sub_stream_fmt(query_sub_stream, substream, mf->code); + set_sub_stream_h(query_sub_stream, substream, mf->height); + set_sub_stream_w(query_sub_stream, substream, mf->width); + set_sub_stream_dt(query_sub_stream, substream, mbus_code_to_mipi(mf->code)); + } else { + dev_err(sensor->sd.dev, + "failed to find sub stream fmt info: %s\n", + sensor->sd.name); + } + } + + dev_info(sensor->sd.dev, "%s(): fmt->pad: %d, sensor->mux_pad: %d, code: 0x%x, %ux%u substream:%d\n", __func__, + fmt->pad, sensor->mux_pad, fmt->format.code, + fmt->format.width, fmt->format.height, substream); + + return 0; +} + +static int ds5_sensor_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_format *fmt) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + struct ds5 *state = v4l2_get_subdevdata(sd); + + return __ds5_sensor_set_fmt(state, sensor, v4l2_state, fmt); +} + +static int ds5_configure(struct ds5 *state) +{ + struct ds5_sensor *sensor; + u16 fmt, md_fmt, vc_id; + u16 dt_addr, md_addr, override_addr, fps_addr, width_addr, height_addr; + int ret; + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + + if (state->is_depth) { + sensor = &state->depth.sensor; + dt_addr = DS5_DEPTH_STREAM_DT; + md_addr = DS5_DEPTH_STREAM_MD; + override_addr = DS5_DEPTH_OVERRIDE; + fps_addr = DS5_DEPTH_FPS; + width_addr = DS5_DEPTH_RES_WIDTH; + height_addr = DS5_DEPTH_RES_HEIGHT; + md_fmt = 0x12; + vc_id = 0; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_rgb) { + sensor = &state->rgb.sensor; + dt_addr = DS5_RGB_STREAM_DT; + md_addr = DS5_RGB_STREAM_MD; + override_addr = 0; + fps_addr = DS5_RGB_FPS; + width_addr = DS5_RGB_RES_WIDTH; + height_addr = DS5_RGB_RES_HEIGHT; + md_fmt = 0x12; + vc_id = 1; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_y8) { + sensor = &state->motion_t.sensor; + dt_addr = DS5_IR_STREAM_DT; + md_addr = DS5_IR_STREAM_MD; + override_addr = DS5_IR_OVERRIDE; + fps_addr = DS5_IR_FPS; + width_addr = DS5_IR_RES_WIDTH; + height_addr = DS5_IR_RES_HEIGHT; + md_fmt = 0x12; + vc_id = 2; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_imu) { + sensor = &state->imu.sensor; + dt_addr = DS5_IMU_STREAM_DT; + md_addr = DS5_IMU_STREAM_MD; + override_addr = 0; + fps_addr = DS5_IMU_FPS; + width_addr = DS5_IMU_RES_WIDTH; + height_addr = DS5_IMU_RES_HEIGHT; + vc_id = 3; + md_fmt = 0x0; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_depth_b) { + sensor = &state->depth_b.sensor; + + dt_addr = DS5_DEPTH_STREAM_DT; + md_addr = DS5_DEPTH_STREAM_MD; + override_addr = DS5_DEPTH_OVERRIDE; + fps_addr = DS5_DEPTH_FPS; + width_addr = DS5_DEPTH_RES_WIDTH; + height_addr = DS5_DEPTH_RES_HEIGHT; + md_fmt = 0x12; + vc_id = 2; + state->client->addr = dpdata->subdev_info[1].board_info.addr; + } else if (state->is_rgb_b) { + sensor = &state->rgb_b.sensor; + dt_addr = DS5_RGB_STREAM_DT; + md_addr = DS5_RGB_STREAM_MD; + override_addr = 0; + fps_addr = DS5_RGB_FPS; + width_addr = DS5_RGB_RES_WIDTH; + height_addr = DS5_RGB_RES_HEIGHT; + md_fmt = 0x12; + vc_id = 3; + state->client->addr = dpdata->subdev_info[1].board_info.addr; + } else { + return -EINVAL; + } + + fmt = sensor->streaming ? sensor->config.format->data_type : 0; + + /* + * Set depth stream Z16 data type as 0x31 + * Set IR stream Y8I data type as 0x32 + */ + if ((state->is_depth || state->is_depth_b) && fmt != 0) + ret = ds5_write(state, dt_addr, 0x31); + else if (state->is_y8 && fmt != 0 && + sensor->config.format->data_type == 0x1E) + ret = ds5_write(state, dt_addr, 0x32); + else + ret = ds5_write(state, dt_addr, fmt); + if (ret < 0) + return ret; + + if (state->is_depth_b || state->is_rgb_b) + ret = ds5_write(state, md_addr, ((vc_id % 2) << 8) | md_fmt); + else + ret = ds5_write(state, md_addr, (vc_id << 8) | md_fmt); + + if (ret < 0) + return ret; + + if (!sensor->streaming) + return ret; + + if (override_addr != 0) { + ret = ds5_write(state, override_addr, fmt); + if (ret < 0) + return ret; + } + + ret = ds5_write(state, fps_addr, sensor->config.framerate); + if (ret < 0) + return ret; + + ret = ds5_write(state, width_addr, sensor->config.resolution->width); + if (ret < 0) + return ret; + + ret = ds5_write(state, height_addr, sensor->config.resolution->height); + if (ret < 0) + return ret; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ds5_depth_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +}; + +static int ds5_sensor_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + if (NULL == sd || NULL == fi) + return -EINVAL; + + fi->interval.numerator = 1; + fi->interval.denominator = sensor->config.framerate; + + dev_info(sd->dev, "%s(): %s %u\n", __func__, sd->name, + fi->interval.denominator); + + return 0; +} +static u16 __ds5_probe_framerate(const struct ds5_resolution *res, u16 target); + +static int ds5_sensor_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + u16 framerate = 1; + + if (NULL == sd || NULL == fi || fi->interval.numerator == 0) + return -EINVAL; + + framerate = fi->interval.denominator / fi->interval.numerator; + framerate = __ds5_probe_framerate(sensor->config.resolution, framerate); + sensor->config.framerate = framerate; + fi->interval.numerator = 1; + fi->interval.denominator = framerate; + + dev_info(sd->dev, "%s(): %s %u\n", __func__, sd->name, framerate); + + return 0; +} + +static int ds5_sensor_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ds5_sensor *sensor = container_of(sd, struct ds5_sensor, sd); + + dev_info(sensor->sd.dev, "%s(): sensor: name=%s state=%d\n", __func__, sensor->sd.name, on); + + sensor->streaming = on; + + return 0; +} + +static const struct v4l2_subdev_video_ops ds5_sensor_video_ops = { + .g_frame_interval = ds5_sensor_g_frame_interval, + .s_frame_interval = ds5_sensor_s_frame_interval, + .s_stream = ds5_sensor_s_stream, +}; + +static const struct v4l2_subdev_ops ds5_depth_subdev_ops = { + .pad = &ds5_depth_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +/* Motion detection */ + +/* FIXME: identical to ds5_depth_pad_ops, use one for both */ +static const struct v4l2_subdev_pad_ops ds5_motion_t_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +}; + +static const struct v4l2_subdev_ops ds5_motion_t_subdev_ops = { + .pad = &ds5_motion_t_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +/* FIXME: identical to ds5_depth_pad_ops, use one for both? */ +static const struct v4l2_subdev_pad_ops ds5_rgb_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +}; + +static const struct v4l2_subdev_ops ds5_rgb_subdev_ops = { + .pad = &ds5_rgb_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +static const struct v4l2_subdev_pad_ops ds5_imu_pad_ops = { + .enum_mbus_code = ds5_sensor_enum_mbus_code, + .enum_frame_size = ds5_sensor_enum_frame_size, + .enum_frame_interval = ds5_sensor_enum_frame_interval, + .get_fmt = ds5_sensor_get_fmt, + .set_fmt = ds5_sensor_set_fmt, +}; + +static const struct v4l2_subdev_ops ds5_imu_subdev_ops = { + .pad = &ds5_imu_pad_ops, + .video = &ds5_sensor_video_ops, +}; + +static int ds5_hw_set_auto_exposure(struct ds5 *state, u32 base, s32 val) +{ + if (val != V4L2_EXPOSURE_APERTURE_PRIORITY && + val != V4L2_EXPOSURE_MANUAL) + return -EINVAL; + + /* + * In firmware color auto exposure setting follow the uvc_menu_info + * exposure_auto_controls numbers, in drivers/media/usb/uvc/uvc_ctrl.c. + */ + if ((state->is_rgb || state->is_rgb_b) && val == V4L2_EXPOSURE_APERTURE_PRIORITY) + val = 8; + + /* + * In firmware depth auto exposure on: 1, off: 0. + */ + if (!(state->is_rgb & state->is_rgb_b)) { + if (val == V4L2_EXPOSURE_APERTURE_PRIORITY) + val = 1; + else if (val == V4L2_EXPOSURE_MANUAL) + val = 0; + } + + return ds5_write(state, base | DS5_AUTO_EXPOSURE_MODE, (u16)val); +} + +/* + * Manual exposure in us + * Depth/Y8: between 100 and 200000 (200ms) + * Color: between 100 and 1000000 (1s) + */ +static int ds5_hw_set_exposure(struct ds5 *state, u32 base, s32 val) +{ + int ret = -1; + + if (val < 1) + val = 1; + if ((state->is_depth || state->is_depth_b || state->is_y8) && val > MAX_DEPTH_EXP) + val = MAX_DEPTH_EXP; + if ((state->is_rgb || state->is_rgb_b) && val > MAX_RGB_EXP) + val = MAX_RGB_EXP; + + /* + * Color and depth uses different unit: + * Color: 1 is 100 us + * Depth: 1 is 1 us + */ + + ret = ds5_write(state, base | DS5_MANUAL_EXPOSURE_MSB, (u16)(val >> 16)); + if (!ret) + ret = ds5_write(state, base | DS5_MANUAL_EXPOSURE_LSB, + (u16)(val & 0xffff)); + + return ret; +} + +#define DS5_MAX_LOG_WAIT 200 +#define DS5_MAX_LOG_SLEEP 10 +#define DS5_MAX_LOG_POLL (DS5_MAX_LOG_WAIT / DS5_MAX_LOG_SLEEP) + +// TODO: why to use DS5_DEPTH_Y_STREAMS_DT? +#define DS5_CAMERA_CID_BASE (V4L2_CTRL_CLASS_CAMERA | DS5_DEPTH_STREAM_DT) + +#define DS5_CAMERA_CID_LOG (DS5_CAMERA_CID_BASE+0) +#define DS5_CAMERA_CID_LASER_POWER (DS5_CAMERA_CID_BASE+1) +#define DS5_CAMERA_CID_MANUAL_LASER_POWER (DS5_CAMERA_CID_BASE+2) +#define DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET (DS5_CAMERA_CID_BASE+3) +#define DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET (DS5_CAMERA_CID_BASE+4) +#define DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET (DS5_CAMERA_CID_BASE+5) +#define DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET (DS5_CAMERA_CID_BASE+6) +#define DS5_CAMERA_CID_FW_VERSION (DS5_CAMERA_CID_BASE+7) +#define DS5_CAMERA_CID_GVD (DS5_CAMERA_CID_BASE+8) +#define DS5_CAMERA_CID_AE_ROI_GET (DS5_CAMERA_CID_BASE+9) +#define DS5_CAMERA_CID_AE_ROI_SET (DS5_CAMERA_CID_BASE+10) +#define DS5_CAMERA_CID_AE_SETPOINT_GET (DS5_CAMERA_CID_BASE+11) +#define DS5_CAMERA_CID_AE_SETPOINT_SET (DS5_CAMERA_CID_BASE+12) +#define DS5_CAMERA_CID_ERB (DS5_CAMERA_CID_BASE+13) +#define DS5_CAMERA_CID_EWB (DS5_CAMERA_CID_BASE+14) +#define DS5_CAMERA_CID_HWMC (DS5_CAMERA_CID_BASE+15) + +#define DS5_CAMERA_CID_PWM (DS5_CAMERA_CID_BASE+22) + +/* the HWMC will remain for legacy tools compatibility, + * HWMC_RW used for UVC compatibility + */ +#define DS5_CAMERA_CID_HWMC_RW (DS5_CAMERA_CID_BASE+32) + +static int ds5_send_hwmc(struct ds5 *state, u16 cmdLen, struct hwm_cmd *cmd, + bool isRead, u16 *dataLen) +{ + int ret = 0; + u16 status = 2; + int retries = 100; + int errorCode; + int iter = retries; + + dev_dbg(&state->client->dev, + "%s(): HWMC header: 0x%x, magic: 0x%x, opcode: 0x%x, " + "param1: %d, param2: %d, param3: %d, param4: %d\n", + __func__, cmd->header, cmd->magic_word, cmd->opcode, + cmd->param1, cmd->param2, cmd->param3, cmd->param4); + + ds5_raw_write_with_check(state, 0x4900, cmd, cmdLen); + + ds5_write_with_check(state, 0x490C, 0x01); /* execute cmd */ + do { + if (iter != retries) + msleep_range(10); + ret = ds5_read(state, 0x4904, &status); + } while (iter-- && status != 0); + + if (ret || status != 0) { + ds5_raw_read(state, 0x4900, &errorCode, 4); + dev_err(&state->client->dev, + "%s(): HWMC failed, ret: %d, status: %x, error code: %d\n", + __func__, ret, status, errorCode); + ret = -EAGAIN; + } + + if (isRead) { + if (*dataLen == 0) { + ret = regmap_raw_read(state->regmap, 0x4908, dataLen, sizeof(u16)); + if (ret) + return -EAGAIN; + } + + dev_dbg(&state->client->dev, "%s(): HWMC read len: %d\n", + __func__, *dataLen); + // First 4 bytes of cmd->Data after read will include opcode + ds5_raw_read_with_check(state, 0x4900, cmd->Data, *dataLen); + + /*This is neede for libreealsense, to align there code with UVC*/ + cmd->Data[1000] = (unsigned char)((*dataLen) & 0x00FF); + cmd->Data[1001] = (unsigned char)(((*dataLen) & 0xFF00) >> 8); + } + + return 0; +} + +#define DS5_HWMC_DATA 0x4900 +#define DS5_HWMC_STATUS 0x4904 +#define DS5_HWMC_RESP_LEN 0x4908 +#define DS5_HWMC_EXEC 0x490C + +#define DS5_HWMC_STATUS_OK 0 +#define DS5_HWMC_STATUS_ERR 1 +#define DS5_HWMC_STATUS_WIP 2 +#define DS5_HWMC_BUFFER_SIZE 1024 + +static int ds5_get_hwmc(struct ds5 *state, unsigned char *data) +{ + int ret = 0; + u16 status = DS5_HWMC_STATUS_WIP; + int retries = 100; + int errorCode; + u16 tmp_len = 0; + const int SIZE_OF_HW_MONITOR_HEADER = 4; + + memset(data, 0, DS5_HWMC_BUFFER_SIZE); + + do { + if (retries != 100) + msleep_range(1); + ret = ds5_read(state, DS5_HWMC_STATUS, &status); + } while (!ret && retries-- && status != DS5_HWMC_STATUS_OK); + + if (ret || status != DS5_HWMC_STATUS_OK) { + if (status == DS5_HWMC_STATUS_ERR) { + ds5_raw_read(state, DS5_HWMC_DATA, &errorCode, sizeof(errorCode)); + dev_err(&state->client->dev, + "%s(): HWMC failed, ret: %d, error code: %d\n", + __func__, ret, errorCode); + } else { + dev_err(&state->client->dev, + "%s(): HWMC failed because of timeout, ret: %d\n", + __func__, ret); + } + return -EAGAIN; + } + + ret = regmap_raw_read(state->regmap, DS5_HWMC_RESP_LEN, + &tmp_len, sizeof(tmp_len)); + if (ret) + return -EAGAIN; + + if (tmp_len > DS5_HWMC_BUFFER_SIZE) + return -ENOBUFS; + + dev_dbg(&state->client->dev, + "%s(): HWMC read len: %d, lrs_len: %d\n", + __func__, tmp_len, tmp_len - SIZE_OF_HW_MONITOR_HEADER); + + ds5_raw_read_with_check(state, DS5_HWMC_DATA, data, tmp_len); + + /* This is needed for librealsense, to align there code with UVC, + * last word is length - 4 bytes header length + */ + tmp_len -= SIZE_OF_HW_MONITOR_HEADER; + data[DS5_HWMC_BUFFER_SIZE - 4] = (unsigned char)(tmp_len & 0x00FF); + data[DS5_HWMC_BUFFER_SIZE - 3] = (unsigned char)((tmp_len & 0xFF00) >> 8); + data[DS5_HWMC_BUFFER_SIZE - 2] = 0; + data[DS5_HWMC_BUFFER_SIZE - 1] = 0; + + return 0; +} + +static int ds5_set_calibration_data(struct ds5 *state, + struct hwm_cmd *cmd, u16 length) +{ + int ret = -1; + int retries = 10; + u16 status = 2; + + ds5_raw_write_with_check(state, 0x4900, cmd, length); + + ds5_write_with_check(state, 0x490c, 0x01); /* execute cmd */ + do { + if (retries != 10) + msleep_range(200); + ret = ds5_read(state, 0x4904, &status); + } while (retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to set calibration table %d," + "ret: %d, fw error: %x\n", + __func__, cmd->param1, ret, status); + } + + return -EINVAL; +} + +static int ds5_mux_s_stream(struct v4l2_subdev *sd, int on); + +static int ds5_s_state(struct ds5 *state, int vc) +{ + int ret = 0; + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + + dev_dbg(&state->client->dev, "%s(): set state for vc: %d\n", __func__, vc); + + switch (vc) { + case 0: + state->is_depth = 1; + state->is_depth_b = 0; + state->is_rgb = 0; + state->is_rgb_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case 1: + state->is_depth = 0; + state->is_depth_b = 0; + state->is_rgb = 1; + state->is_rgb_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case 2: + state->is_depth = 0; + state->is_rgb = 0; + if (dpdata->subdev_num == 1) { + state->is_y8 = 1; //default case: vc2:state is IR + state->is_imu = 0; + } else if (dpdata->subdev_num == 2) { + state->is_depth_b = 1; //A+A case: vc2:state is depth_b + state->is_rgb_b = 0; + } + break; + case 3: + state->is_depth = 0; + state->is_rgb = 0; + if (dpdata->subdev_num == 1) { + state->is_y8 = 0; + state->is_imu = 0; //default case: vc3:state is IMU + } else if (dpdata->subdev_num == 2) { + state->is_depth_b = 0; + state->is_rgb_b = 1; //A+A case: vc3:state is rgb_b + } + break; + default: + dev_warn(&state->client->dev, "%s(): unknown vc: %d\n", __func__, vc); + ret = -EINVAL; + break; + } + ds5_set_state_last_set(state); + return ret; +} + +static int ds5_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ds5 *state = container_of(ctrl->handler, struct ds5, + ctrls.handler); + struct v4l2_subdev *sd = &state->mux.sd.subdev; + struct ds5_sensor *sensor = (struct ds5_sensor *)ctrl->priv; + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + int ret = -EINVAL; + u16 base = DS5_DEPTH_CONTROL_BASE; + u32 val; + u16 on; + u16 vc_id; + + if (sensor) { + switch (sensor->mux_pad) { + case DS5_MUX_PAD_DEPTH_B: + state->client->addr = dpdata->subdev_info[1].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth_b); + state->is_rgb_b = 0; + state->is_depth_b = 1; + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB_B: + state->client->addr = dpdata->subdev_info[1].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb_b); + state->is_rgb_b = 1; + state->is_depth_b = 0; + state->is_rgb = 0; + state->is_depth = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_DEPTH_A: + state->client->addr = dpdata->subdev_info[0].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth); + state->is_rgb = 0; + state->is_depth = 1; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB_A: + state->client->addr = dpdata->subdev_info[0].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb); + state->is_rgb = 1; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_MOTION_T_A: + state->client->addr = dpdata->subdev_info[0].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_y8); + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 1; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IMU_A: + state->client->addr = dpdata->subdev_info[0].board_info.addr; + state = container_of(ctrl->handler, struct ds5, ctrls.handler_imu); + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + default: + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + + } + } + + if (state->is_rgb || state->is_rgb_b) + base = DS5_RGB_CONTROL_BASE; + + v4l2_dbg(3, 1, sd, "ctrl: %s, value: %d\n", ctrl->name, ctrl->val); + dev_dbg(&state->client->dev, "%s(): %s - ctrl: %s, value: %d\n", + __func__, ds5_get_sensor_name(state), ctrl->name, ctrl->val); + + mutex_lock(&state->lock); + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + ret = ds5_write(state, base | DS5_MANUAL_GAIN, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_AUTO: + ret = ds5_hw_set_auto_exposure(state, base, ctrl->val); + break; + + case V4L2_CID_EXPOSURE_ABSOLUTE: + ret = ds5_hw_set_exposure(state, base, ctrl->val); + break; + case DS5_CAMERA_CID_LASER_POWER: + if (!(state->is_rgb & state->is_rgb_b)) + ret = ds5_write(state, base | DS5_LASER_POWER, + ctrl->val); + break; + case DS5_CAMERA_CID_MANUAL_LASER_POWER: + if (!(state->is_rgb & state->is_rgb_b)) + ret = ds5_write(state, base | DS5_MANUAL_LASER_POWER, + ctrl->val); + break; + case DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET: { + struct hwm_cmd *calib_cmd; + + dev_dbg(&state->client->dev, + "%s(): DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET \n", + __func__); + dev_dbg(&state->client->dev, + "%s(): table id: 0x%x\n", + __func__, *((u8 *)ctrl->p_new.p + 2)); + if (ctrl->p_new.p && DEPTH_CALIBRATION_ID == + *((u8 *)ctrl->p_new.p + 2)) { + calib_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 256, GFP_KERNEL); + if (!calib_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(calib_cmd, &set_calib_data, sizeof(set_calib_data)); + calib_cmd->header = 276; + calib_cmd->param1 = DEPTH_CALIBRATION_ID; + memcpy (calib_cmd->Data, (u8 *)ctrl->p_new.p, 256); + ret = ds5_set_calibration_data(state, calib_cmd, + sizeof(struct hwm_cmd) + 256); + devm_kfree(&state->client->dev, calib_cmd); + } + break; + } + case DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET: { + struct hwm_cmd *calib_cmd; + + dev_dbg(&state->client->dev, + "%s(): DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET \n", + __func__); + dev_dbg(&state->client->dev, + "%s(): table id %d\n", + __func__, *((u8 *)ctrl->p_new.p + 2)); + if (ctrl->p_new.p && COEF_CALIBRATION_ID == + *((u8 *)ctrl->p_new.p + 2)) { + calib_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 512, GFP_KERNEL); + if (!calib_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy (calib_cmd, &set_calib_data, sizeof (set_calib_data)); + calib_cmd->header = 532; + calib_cmd->param1 = COEF_CALIBRATION_ID; + memcpy(calib_cmd->Data, (u8 *)ctrl->p_new.p, 512); + ret = ds5_set_calibration_data(state, calib_cmd, + sizeof(struct hwm_cmd) + 512); + devm_kfree(&state->client->dev, calib_cmd); + } + } + break; + case DS5_CAMERA_CID_AE_ROI_SET: { + struct hwm_cmd ae_roi_cmd; + + memcpy(&ae_roi_cmd, &set_ae_roi, sizeof(ae_roi_cmd)); + ae_roi_cmd.param1 = *((u16 *)ctrl->p_new.p_u16); + ae_roi_cmd.param2 = *((u16 *)ctrl->p_new.p_u16 + 1); + ae_roi_cmd.param3 = *((u16 *)ctrl->p_new.p_u16 + 2); + ae_roi_cmd.param4 = *((u16 *)ctrl->p_new.p_u16 + 3); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), + &ae_roi_cmd, false, NULL); + break; + } + case DS5_CAMERA_CID_AE_SETPOINT_SET: { + struct hwm_cmd *ae_setpoint_cmd; + + if (ctrl->p_new.p_s32) { + dev_dbg(&state->client->dev, "%s():0x%x \n", + __func__, *(ctrl->p_new.p_s32)); + ae_setpoint_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 4, GFP_KERNEL); + if (!ae_setpoint_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_setpoint_cmd, &set_ae_setpoint, sizeof (set_ae_setpoint)); + memcpy(ae_setpoint_cmd->Data, (u8 *)ctrl->p_new.p_s32, 4); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd) + 4, + ae_setpoint_cmd, false, NULL); + devm_kfree(&state->client->dev, ae_setpoint_cmd); + } + break; + } + case DS5_CAMERA_CID_ERB: + if (ctrl->p_new.p_u8) { + u16 offset = 0; + u16 size = 0; + struct hwm_cmd *erb_cmd; + + offset = *(ctrl->p_new.p_u8) << 8; + offset |= *(ctrl->p_new.p_u8 + 1); + size = *(ctrl->p_new.p_u8 + 2) << 8; + size |= *(ctrl->p_new.p_u8 + 3); + + dev_dbg(&state->client->dev, "%s(): offset %x, size: %x\n", + __func__, offset, size); + + erb_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + size, GFP_KERNEL); + if (!erb_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(erb_cmd, &erb, sizeof(struct hwm_cmd)); + erb_cmd->param1 = offset; + erb_cmd->param2 = size; + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), + erb_cmd, true, &size); + + if (ret) { + dev_err(&state->client->dev, + "%s(): ERB cmd failed, ret: %d," + "requested size: %d, actual size: %d\n", + __func__, ret, erb_cmd->param2, size); + devm_kfree(&state->client->dev, erb_cmd); + return -EAGAIN; + } + + // Actual size returned from FW + *(ctrl->p_new.p_u8 + 2) = (size & 0xFF00) >> 8; + *(ctrl->p_new.p_u8 + 3) = (size & 0x00FF); + + memcpy(ctrl->p_new.p_u8 + 4, erb_cmd->Data + 4, size - 4); + dev_dbg(&state->client->dev, "%s(): 0x%x 0x%x 0x%x 0x%x \n", + __func__, + *(ctrl->p_new.p_u8), + *(ctrl->p_new.p_u8+1), + *(ctrl->p_new.p_u8+2), + *(ctrl->p_new.p_u8+3)); + devm_kfree(&state->client->dev, erb_cmd); + } + break; + case DS5_CAMERA_CID_EWB: + if (ctrl->p_new.p_u8) { + u16 offset = 0; + u16 size = 0; + struct hwm_cmd *ewb_cmd; + + offset = *((u8 *)ctrl->p_new.p_u8) << 8; + offset |= *((u8 *)ctrl->p_new.p_u8 + 1); + size = *((u8 *)ctrl->p_new.p_u8 + 2) << 8; + size |= *((u8 *)ctrl->p_new.p_u8 + 3); + + dev_dbg(&state->client->dev, "%s():0x%x 0x%x 0x%x 0x%x\n", + __func__, + *((u8 *)ctrl->p_new.p_u8), + *((u8 *)ctrl->p_new.p_u8 + 1), + *((u8 *)ctrl->p_new.p_u8 + 2), + *((u8 *)ctrl->p_new.p_u8 + 3)); + + ewb_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + size, + GFP_KERNEL); + if (!ewb_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ewb_cmd, &ewb, sizeof(ewb)); + ewb_cmd->header = 0x14 + size; + ewb_cmd->param1 = offset; // start index + ewb_cmd->param2 = size; // size + memcpy(ewb_cmd->Data, (u8 *)ctrl->p_new.p_u8 + 4, size); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd) + size, + ewb_cmd, false, NULL); + + if (ret) { + dev_err(&state->client->dev, + "%s(): EWB cmd failed, ret: %d," + "requested size: %d, actual size: %d\n", + __func__, ret, ewb_cmd->param2, size); + devm_kfree(&state->client->dev, ewb_cmd); + return -EAGAIN; + } + + devm_kfree(&state->client->dev, ewb_cmd); + } + break; + case DS5_CAMERA_CID_HWMC: + if (ctrl->p_new.p_u8) { + u16 dataLen = 0; + u16 size = 0; + + size = *((u8 *)ctrl->p_new.p_u8 + 1) << 8; + size |= *((u8 *)ctrl->p_new.p_u8 + 0); + dev_dbg(&state->client->dev, + "%s(): HWMC size %d\n", + __func__, size); + ret = ds5_send_hwmc(state, size + 4, + (struct hwm_cmd *)ctrl->p_new.p_u8, true, &dataLen); + } + break; + case DS5_CAMERA_CID_HWMC_RW: + if (ctrl->p_new.p_u8) { + u16 size = *((u8 *)ctrl->p_new.p_u8 + 1) << 8; + + size |= *((u8 *)ctrl->p_new.p_u8 + 0); + ret = ds5_send_hwmc(state, size + 4, + (struct hwm_cmd *)ctrl->p_new.p_u8, false, NULL); + } + break; + case DS5_CAMERA_CID_PWM: + if (state->is_depth || state->is_depth_b) + ret = ds5_write(state, base | DS5_PWM_FREQUENCY, ctrl->val); + break; + case V4L2_CID_IPU_SET_SUB_STREAM: + val = (*ctrl->p_new.p_s64 & 0xFFFF); + dev_info(&state->client->dev, "V4L2_CID_IPU_SET_SUB_STREAM %x\n", val); + vc_id = (val >> 8) & 0x00FF; + on = val & 0x00FF; + if (vc_id < DS5_MUX_PAD_COUNT) + ret = ds5_s_state(state, vc_id); + if (on == 0xff) + break; + if (vc_id > NR_OF_DS5_STREAMS - 1) + dev_err(&state->client->dev, "invalid vc %d\n", vc_id); + else + d4xx_set_sub_stream[vc_id] = on; + + if (on == 0) + ret = ds5_s_state(state, vc_id); + + ret = ds5_mux_s_stream(sd, on); + break; + } + + mutex_unlock(&state->lock); + + return ret; +} + +static int ds5_get_calibration_data(struct ds5 *state, enum table_id id, + unsigned char *table, unsigned int length) +{ + struct hwm_cmd *cmd; + int ret = -1; + int retries = 3; + u16 status = 2; + u16 table_length; + + cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + length + 4, GFP_KERNEL); + if (!cmd) { + dev_err(&state->client->dev, "%s(): Can't allocate memory\n", __func__); + return -ENOMEM; + } + + memcpy(cmd, &get_calib_data, sizeof(get_calib_data)); + cmd->param1 = id; + ds5_raw_write_with_check(state, 0x4900, cmd, sizeof(struct hwm_cmd)); + ds5_write_with_check(state, 0x490c, 0x01); /* execute cmd */ + do { + if (retries != 3) + msleep_range(10); + ret = ds5_read(state, 0x4904, &status); + } while (ret && retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to get calibration table %d, fw error: %x\n", + __func__, id, status); + devm_kfree(&state->client->dev, cmd); + return status; + } + + // get table length from fw + ret = regmap_raw_read(state->regmap, 0x4908, + &table_length, sizeof(table_length)); + + // read table + ds5_raw_read_with_check(state, 0x4900, cmd->Data, table_length); + + // first 4 bytes are opcode HWM, not part of calibration table + memcpy(table, cmd->Data + 4, length); + devm_kfree(&state->client->dev, cmd); + return 0; +} + +static int ds5_gvd(struct ds5 *state, unsigned char *data) +{ + struct hwm_cmd cmd; + int ret = -1; + u16 length = 0; + u16 status = 2; + u8 retries = 3; + + memcpy(&cmd, &gvd, sizeof(gvd)); + ds5_raw_write_with_check(state, 0x4900, &cmd, sizeof(cmd)); + ds5_write_with_check(state, 0x490c, 0x01); /* execute cmd */ + do { + if (retries != 3) + msleep_range(10); + + ret = ds5_read(state, 0x4904, &status); + } while (ret && retries-- && status != 0); + + if (ret || status != 0) { + dev_err(&state->client->dev, + "%s(): Failed to read GVD, HWM cmd status: %x\n", + __func__, status); + return status; + } + + ret = regmap_raw_read(state->regmap, 0x4908, &length, sizeof(length)); + ds5_raw_read_with_check(state, 0x4900, data, length); + + return ret; +} + +static int ds5_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ds5 *state = container_of(ctrl->handler, struct ds5, + ctrls.handler); + u16 log_prepare[] = {0x0014, 0xcdab, 0x000f, 0x0000, 0x0400, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; + u16 execute_cmd = 0x0001; + unsigned int i; + u32 data; + int ret = 0; + struct ds5_sensor *sensor = (struct ds5_sensor *)ctrl->priv; + u16 base = (state->is_rgb) ? DS5_RGB_CONTROL_BASE : DS5_DEPTH_CONTROL_BASE; + u16 reg; + + if (sensor) { + switch (sensor->mux_pad) { + case DS5_MUX_PAD_DEPTH_B: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth_b); + state->is_rgb = 0; + state->is_rgb_b = 0; + state->is_depth = 0; + state->is_depth_b = 1; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB_B: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb_b); + state->is_rgb_b = 1; + state->is_depth = 0; + state->is_rgb = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_DEPTH_A: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_depth); + state->is_rgb = 0; + state->is_depth = 1; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_RGB_A: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_rgb); + state->is_rgb = 1; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 0; + break; + case DS5_MUX_PAD_MOTION_T_A: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_y8); + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 1; + state->is_imu = 0; + break; + case DS5_MUX_PAD_IMU_A: + state = container_of(ctrl->handler, struct ds5, ctrls.handler_imu); + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + default: + state->is_rgb = 0; + state->is_depth = 0; + state->is_rgb_b = 0; + state->is_depth_b = 0; + state->is_y8 = 0; + state->is_imu = 1; + break; + + } + } + base = (state->is_rgb || state->is_rgb_b) ? DS5_RGB_CONTROL_BASE : DS5_DEPTH_CONTROL_BASE; + + dev_dbg(&state->client->dev, "%s(): %s - ctrl: %s \n", + __func__, ds5_get_sensor_name(state), ctrl->name); + + switch (ctrl->id) { + + case V4L2_CID_ANALOGUE_GAIN: + if (state->is_imu) + return -EINVAL; + ret = ds5_read(state, base | DS5_MANUAL_GAIN, ctrl->p_new.p_u16); + break; + + case V4L2_CID_EXPOSURE_AUTO: + if (state->is_imu) + return -EINVAL; + ds5_read(state, base | DS5_AUTO_EXPOSURE_MODE, ®); + *ctrl->p_new.p_u16 = reg; + /* see ds5_hw_set_auto_exposure */ + if (!(state->is_rgb & state->is_rgb_b)) { + if (reg == 1) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_APERTURE_PRIORITY; + else if (reg == 0) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_MANUAL; + } + + if ((state->is_rgb || state->is_rgb_b) && reg == 8) + *ctrl->p_new.p_u16 = V4L2_EXPOSURE_APERTURE_PRIORITY; + + break; + + case V4L2_CID_EXPOSURE_ABSOLUTE: + if (state->is_imu) + return -EINVAL; + /* see ds5_hw_set_exposure */ + ds5_read(state, base | DS5_MANUAL_EXPOSURE_MSB, ®); + data = ((u32)reg << 16) & 0xffff0000; + ds5_read(state, base | DS5_MANUAL_EXPOSURE_LSB, ®); + data |= reg; + *ctrl->p_new.p_u32 = data; + break; + + case DS5_CAMERA_CID_LASER_POWER: + if (!(state->is_rgb & state->is_rgb_b)) + ds5_read(state, base | DS5_LASER_POWER, ctrl->p_new.p_u16); + break; + + case DS5_CAMERA_CID_MANUAL_LASER_POWER: + if (!(state->is_rgb & state->is_rgb_b)) + ds5_read(state, base | DS5_MANUAL_LASER_POWER, ctrl->p_new.p_u16); + break; + + case DS5_CAMERA_CID_LOG: + // TODO: wrap HWMonitor command + // 1. prepare and send command + // 2. send command + // 3. execute command + // 4. wait for ccompletion + ret = regmap_raw_write(state->regmap, 0x4900, + log_prepare, sizeof(log_prepare)); + if (ret < 0) + return ret; + + ret = regmap_raw_write(state->regmap, 0x490C, + &execute_cmd, sizeof(execute_cmd)); + if (ret < 0) + return ret; + + for (i = 0; i < DS5_MAX_LOG_POLL; i++) { + ret = regmap_raw_read(state->regmap, 0x4904, + &data, sizeof(data)); + dev_dbg(&state->client->dev, "%s(): log ready 0x%x\n", + __func__, data); + if (ret < 0) + return ret; + if (!data) + break; + msleep_range(5); + } + +// if (i == DS5_MAX_LOG_POLL) +// return -ETIMEDOUT; + + ret = regmap_raw_read(state->regmap, 0x4908, + &data, sizeof(data)); + dev_dbg(&state->client->dev, "%s(): log size 0x%x\n", + __func__, data); + if (ret < 0) + return ret; + if (!data) + return 0; + if (data > 1024) + return -ENOBUFS; + ret = regmap_raw_read(state->regmap, 0x4900, + ctrl->p_new.p_u8, data); + break; + case DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET: + ret = ds5_get_calibration_data(state, DEPTH_CALIBRATION_ID, + ctrl->p_new.p_u8, 256); + break; + case DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET: + ret = ds5_get_calibration_data(state, COEF_CALIBRATION_ID, + ctrl->p_new.p_u8, 512); + break; + case DS5_CAMERA_CID_FW_VERSION: + ret = ds5_read(state, DS5_FW_VERSION, &state->fw_version); + ret = ds5_read(state, DS5_FW_BUILD, &state->fw_build); + *ctrl->p_new.p_u32 = state->fw_version << 16; + *ctrl->p_new.p_u32 |= state->fw_build; + break; + case DS5_CAMERA_CID_GVD: + ret = ds5_gvd(state, ctrl->p_new.p_u8); + break; + case DS5_CAMERA_CID_AE_ROI_GET: { + u16 len = 0; + struct hwm_cmd *ae_roi_cmd; + ae_roi_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 12, GFP_KERNEL); + if (!ae_roi_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_roi_cmd, &get_ae_roi, sizeof(struct hwm_cmd)); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), + ae_roi_cmd, true, &len); + memcpy(ctrl->p_new.p_u16, ae_roi_cmd->Data + 4, 8); + devm_kfree(&state->client->dev, ae_roi_cmd); + } + break; + case DS5_CAMERA_CID_AE_SETPOINT_GET: { + u16 len = 0; + struct hwm_cmd *ae_setpoint_cmd; + ae_setpoint_cmd = devm_kzalloc(&state->client->dev, + sizeof(struct hwm_cmd) + 8, GFP_KERNEL); + if (!ae_setpoint_cmd) { + dev_err(&state->client->dev, + "%s(): Can't allocate memory for 0x%x\n", + __func__, ctrl->id); + ret = -ENOMEM; + break; + } + memcpy(ae_setpoint_cmd, &get_ae_setpoint, sizeof(struct hwm_cmd)); + ret = ds5_send_hwmc(state, sizeof(struct hwm_cmd), + ae_setpoint_cmd, true, &len); + memcpy(ctrl->p_new.p_s32, ae_setpoint_cmd->Data + 4, 4); + dev_dbg(&state->client->dev, "%s(): 0x%x \n", + __func__, + *(ctrl->p_new.p_s32)); + devm_kfree(&state->client->dev, ae_setpoint_cmd); + } + break; + case DS5_CAMERA_CID_HWMC_RW: + ds5_get_hwmc(state, ctrl->p_new.p_u8); + break; + case DS5_CAMERA_CID_PWM: + if (state->is_depth || state->is_depth_b) + ds5_read(state, base | DS5_PWM_FREQUENCY, ctrl->p_new.p_u16); + break; + case V4L2_CID_IPU_QUERY_SUB_STREAM: { + if (sensor) { + int vc_id = get_sub_stream_vc_id(state->ctrls.query_sub_stream->qmenu_int, + pad_to_substream[sensor->mux_pad]); + + dev_dbg(sensor->sd.dev, + "%s(): V4L2_CID_IPU_QUERY_SUB_STREAM sensor->mux_pad:%d vc:[%d]\n", + __func__, sensor->mux_pad, vc_id); + *ctrl->p_new.p_s32 = pad_to_substream[sensor->mux_pad]; + state->mux.last_set = sensor; + } else { + /* we are in DS5 MUX case */ + *ctrl->p_new.p_s32 = -1; + } + } + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ds5_ctrl_ops = { + .s_ctrl = ds5_s_ctrl, + .g_volatile_ctrl = ds5_g_volatile_ctrl, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_log = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_LOG, + .name = "Logger", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1024}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_laser_power = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_LASER_POWER, + .name = "Laser power on/off", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_manual_laser_power = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_MANUAL_LASER_POWER, + .name = "Manual laser power", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 360, + .step = 30, + .def = 150, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_fw_version = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_FW_VERSION, + .name = "fw version", + .type = V4L2_CTRL_TYPE_U32, + .dims = {1}, + .elem_size = sizeof(u32), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_gvd = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_GVD, + .name = "GVD", + .type = V4L2_CTRL_TYPE_U8, + .dims = {239}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_get_depth_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_DEPTH_CALIBRATION_TABLE_GET, + .name = "get depth calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {256}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_set_depth_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_DEPTH_CALIBRATION_TABLE_SET, + .name = "set depth calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {256}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_get_coeff_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_COEFF_CALIBRATION_TABLE_GET, + .name = "get coeff calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {512}, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_set_coeff_calib = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_COEFF_CALIBRATION_TABLE_SET, + .name = "set coeff calib", + .type = V4L2_CTRL_TYPE_U8, + .dims = {512}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_roi_get = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_ROI_GET, + .name = "ae roi get", + .type = V4L2_CTRL_TYPE_U8, + .dims = {8}, + .elem_size = sizeof(u16), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_roi_set = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_ROI_SET, + .name = "ae roi set", + .type = V4L2_CTRL_TYPE_U8, + .dims = {8}, + .elem_size = sizeof(u16), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_setpoint_get = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_SETPOINT_GET, + .name = "ae setpoint get", + .type = V4L2_CTRL_TYPE_INTEGER, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ae_setpoint_set = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_AE_SETPOINT_SET, + .name = "ae setpoint set", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 4095, + .step = 1, + .def = 0, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_erb = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_ERB, + .name = "ERB eeprom read", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1020}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_ewb = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_EWB, + .name = "EWB eeprom write", + .type = V4L2_CTRL_TYPE_U8, + .dims = {1020}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_hwmc = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_HWMC, + .name = "HWMC", + .type = V4L2_CTRL_TYPE_U8, + .dims = {DS5_HWMC_BUFFER_SIZE + 4}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .step = 1, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_hwmc_rw = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_HWMC_RW, + .name = "HWMC_RW", + .type = V4L2_CTRL_TYPE_U8, + .dims = {DS5_HWMC_BUFFER_SIZE}, + .elem_size = sizeof(u8), + .min = 0, + .max = 0xFFFFFFFF, + .def = 240, + .step = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config ds5_ctrl_pwm = { + .ops = &ds5_ctrl_ops, + .id = DS5_CAMERA_CID_PWM, + .name = "PWM Frequency Selector", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1, + .step = 1, + .def = 1, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, +}; + +static const struct v4l2_ctrl_config d4xx_controls_link_freq = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_LINK_FREQ, + .name = "V4L2_CID_LINK_FREQ", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(link_freq_menu_items) - 1, + .min = 0, + .step = 0, + .def = 1, + .qmenu_int = link_freq_menu_items, +}; + +static const struct v4l2_ctrl_config d4xx_controls_q_sub_stream_a = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(d4xx_query_sub_stream[0]) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream[0], +}; + +static const struct v4l2_ctrl_config d4xx_controls_q_sub_stream_b = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(d4xx_query_sub_stream[1]) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream[1], +}; + +static const struct v4l2_ctrl_config d4xx_controls_q_sub_stream_c = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(d4xx_query_sub_stream[2]) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream[2], +}; + +static const struct v4l2_ctrl_config d4xx_controls_q_sub_stream_d = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(d4xx_query_sub_stream[3]) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream[3], +}; + +static const struct v4l2_ctrl_config d4xx_controls_q_sub_stream_e = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(d4xx_query_sub_stream[4]) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = d4xx_query_sub_stream[4], +}; + +static const struct v4l2_ctrl_config d4xx_controls_s_sub_stream = { + .ops = &ds5_ctrl_ops, + .id = V4L2_CID_IPU_SET_SUB_STREAM, + .name = "set virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER64, + .max = 0xFFFF, + .min = 0, + .def = 0, + .step = 1, +}; + +static int ds5_mux_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + + dev_info(sd->dev, "%s(): %s (%p)\n", __func__, sd->name, fh); + if (state->dfu_dev.dfu_state_flag) + return -EBUSY; + state->dfu_dev.device_open_count++; + + return 0; +}; + +static int ds5_mux_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + + dev_info(sd->dev, "%s(): %s (%p)\n", __func__, sd->name, fh); + state->dfu_dev.device_open_count--; + return 0; +}; + +static const struct v4l2_subdev_internal_ops ds5_sensor_internal_ops = { + .open = ds5_mux_open, + .close = ds5_mux_close, +}; + +static int ds5_ctrl_init(struct ds5 *state, int sid) +{ + const struct v4l2_ctrl_ops *ops = &ds5_ctrl_ops; + struct ds5_ctrls *ctrls = &state->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + struct v4l2_subdev *sd = &state->mux.sd.subdev; + int ret = -1; + char d4xx_name; + struct ds5_sensor *sensor = NULL; + + switch (sid) { + case DS5_SID_DEPTH_A: + hdl = &ctrls->handler_depth; + sensor = &state->depth.sensor; + break; + case DS5_SID_RGB_A: + hdl = &ctrls->handler_rgb; + sensor = &state->rgb.sensor; + break; + case DS5_SID_MOTION_T_A: + hdl = &ctrls->handler_y8; + sensor = &state->motion_t.sensor; + break; + case DS5_SID_IMU_A: + hdl = &ctrls->handler_imu; + sensor = &state->imu.sensor; + break; + case DS5_SID_DEPTH_B: + hdl = &ctrls->handler_depth_b; + sensor = &state->depth_b.sensor; + break; + case DS5_SID_RGB_B: + hdl = &ctrls->handler_rgb_b; + sensor = &state->rgb_b.sensor; + break; + default: + hdl = &ctrls->handler; + sensor = NULL; + break; + } + + dev_info(NULL, "%s(), line %d sid: %d\n", __func__, __LINE__, sid); + ret = v4l2_ctrl_handler_init(hdl, DS5_N_CONTROLS); + if (ret < 0) { + v4l2_err(sd, "cannot init ctrl handler (%d)\n", ret); + return ret; + } + + if (sid == DS5_SID_DEPTH_A || sid == DS5_SID_MOTION_T_A || sid == DS5_SID_DEPTH_B) { + ctrls->laser_power = v4l2_ctrl_new_custom(hdl, + &ds5_ctrl_laser_power, + sensor); + ctrls->manual_laser_power = v4l2_ctrl_new_custom(hdl, + &ds5_ctrl_manual_laser_power, + sensor); + } + + /* Total gain */ + if (sid == DS5_SID_DEPTH_A || sid == DS5_SID_MOTION_T_A || sid == DS5_SID_DEPTH_B) { + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ANALOGUE_GAIN, + 16, 248, 1, 16); + } else if (sid == DS5_SID_RGB_A || sid == DS5_SID_RGB_B) { + ctrls->gain = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_ANALOGUE_GAIN, + 0, 128, 1, 64); + } + + if ((ctrls->gain) && (sid >= DS5_SID_DEPTH_A && sid <= DS5_SID_RGB_B)) { + ctrls->gain->priv = sensor; + ctrls->gain->flags = + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + } + if (sid >= DS5_SID_DEPTH_A && sid <= DS5_SID_RGB_B) { + + ctrls->auto_exp = v4l2_ctrl_new_std_menu(hdl, ops, + V4L2_CID_EXPOSURE_AUTO, + V4L2_EXPOSURE_APERTURE_PRIORITY, + ~((1 << V4L2_EXPOSURE_MANUAL) | + (1 << V4L2_EXPOSURE_APERTURE_PRIORITY)), + V4L2_EXPOSURE_APERTURE_PRIORITY); + + if (ctrls->auto_exp) { + ctrls->auto_exp->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + ctrls->auto_exp->priv = sensor; + } + } + /* Exposure time: V4L2_CID_EXPOSURE_ABSOLUTE default unit: 100 us. */ + if (sid == DS5_SID_DEPTH_A || sid == DS5_SID_MOTION_T_A || sid == DS5_SID_DEPTH_B) { + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 1, MAX_DEPTH_EXP, 1, DEF_DEPTH_EXP); + } else if (sid == DS5_SID_RGB_A || sid == DS5_SID_RGB_B) { + ctrls->exposure = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + 1, MAX_RGB_EXP, 1, DEF_RGB_EXP); + } + + if ((ctrls->exposure) && (sid >= DS5_SID_DEPTH_A && sid <= DS5_SID_RGB_B)) { + ctrls->exposure->priv = sensor; + ctrls->exposure->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + /* override default int type to u32 to match SKU & UVC */ + ctrls->exposure->type = V4L2_CTRL_TYPE_U32; + } + + ctrls->link_freq = v4l2_ctrl_new_custom(hdl, &d4xx_controls_link_freq, sensor); + dev_info(sd->dev, "%s(): %p\n", __func__, ctrls->link_freq); + if (ctrls->link_freq) + ctrls->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + d4xx_name = (sd->name)[strlen(sd->name)-1]; + dev_info(sd->dev, "%s(): d4xx_name is %c\n", __func__, d4xx_name); + switch (d4xx_name) { + case 'a': + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream_a, sensor); + break; + + case 'b': + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream_b, sensor); + break; + + case 'c': + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream_c, sensor); + break; + + case 'd': + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream_d, sensor); + break; + + case 'e': + ctrls->query_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_q_sub_stream_e, sensor); + break; + + default: + dev_err(state->rgb.sensor.sd.dev, + "%s():!! can not get d4xx_controls_q_sub_stream: sd->name is %s\n", + __func__, sd->name); + break; + } + ctrls->query_sub_stream->flags |= + V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + + ctrls->set_sub_stream = v4l2_ctrl_new_custom(hdl, &d4xx_controls_s_sub_stream, sensor); + dev_info(NULL, "%s(), line %d\n", __func__, __LINE__); + if (hdl->error) { + v4l2_err(sd, "error creating controls (%d)\n", hdl->error); + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + // ret = v4l2_ctrl_handler_setup(hdl); + // if (ret < 0) { + // dev_err(&state->client->dev, + // "failed to set default values for controls\n"); + // v4l2_ctrl_handler_free(hdl); + // return ret; + // } + + // Add these after v4l2_ctrl_handler_setup so they won't be set up + if (sid >= DS5_SID_DEPTH_A && sid <= DS5_SID_RGB_B) { + ctrls->log = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_log, sensor); + ctrls->fw_version = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_fw_version, sensor); + ctrls->gvd = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_gvd, sensor); + ctrls->get_depth_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_get_depth_calib, sensor); + ctrls->set_depth_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_set_depth_calib, sensor); + ctrls->get_coeff_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_get_coeff_calib, sensor); + ctrls->set_coeff_calib = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_set_coeff_calib, sensor); + ctrls->ae_roi_get = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_roi_get, sensor); + ctrls->ae_roi_set = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_roi_set, sensor); + ctrls->ae_setpoint_get = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_setpoint_get, sensor); + ctrls->ae_setpoint_set = + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ae_setpoint_set, sensor); + ctrls->erb = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_erb, sensor); + ctrls->ewb = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_ewb, sensor); + ctrls->hwmc = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_hwmc, sensor); + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_hwmc_rw, sensor); + } + // DEPTH custom + if (sid == DS5_SID_DEPTH_A || sid == DS5_SID_DEPTH_B) + v4l2_ctrl_new_custom(hdl, &ds5_ctrl_pwm, sensor); + // IMU custom + if (sid == DS5_SID_IMU_A) + ctrls->fw_version = v4l2_ctrl_new_custom(hdl, &ds5_ctrl_fw_version, sensor); + + switch (sid) { + case DS5_SID_DEPTH_A: + state->depth.sensor.sd.ctrl_handler = hdl; + dev_info(state->depth.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->depth.sensor.mux_pad); + break; + case DS5_SID_RGB_A: + state->rgb.sensor.sd.ctrl_handler = hdl; + dev_info(state->rgb.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->rgb.sensor.mux_pad); + break; + case DS5_SID_MOTION_T_A: + state->motion_t.sensor.sd.ctrl_handler = hdl; + dev_info(state->motion_t.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->motion_t.sensor.mux_pad); + break; + case DS5_SID_IMU_A: + state->imu.sensor.sd.ctrl_handler = hdl; + dev_info(state->imu.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->imu.sensor.mux_pad); + break; + case DS5_SID_DEPTH_B: + state->depth_b.sensor.sd.ctrl_handler = hdl; + dev_info(state->depth_b.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->depth_b.sensor.mux_pad); + break; + case DS5_SID_RGB_B: + state->rgb_b.sensor.sd.ctrl_handler = hdl; + dev_info(state->rgb_b.sensor.sd.dev, + "%s():%d set ctrl_handler pad:%d\n", + __func__, __LINE__, state->rgb_b.sensor.mux_pad); + break; + default: + state->mux.sd.subdev.ctrl_handler = hdl; + dev_info(state->mux.sd.subdev.dev, + "%s():%d set ctrl_handler for MUX\n", __func__, __LINE__); + break; + } + + return 0; +} + +static int ds5_sensor_init(struct i2c_client *c, struct ds5 *state, + struct ds5_sensor *sensor, const struct v4l2_subdev_ops *ops, + const char *name) +{ + struct v4l2_subdev *sd = &sensor->sd; + struct media_entity *entity = &sensor->sd.entity; + struct media_pad *pad = &sensor->pad; + dev_t *dev_num = &state->client->dev.devt; + struct d4xx_pdata *dpdata = c->dev.platform_data; + + v4l2_i2c_subdev_init(sd, c, ops); + sd->owner = NULL; + sd->internal_ops = &ds5_sensor_internal_ops; + sd->grp_id = *dev_num; + v4l2_set_subdevdata(sd, state); + /* + * TODO: suffix for 2 D457 connected to 1 Deser + */ + dev_dbg(&state->client->dev, "%s() sensor->mux_pad = %d\n", __func__, sensor->mux_pad); + + if (sensor->mux_pad >= DS5_MUX_PAD_DEPTH_A && sensor->mux_pad <= DS5_MUX_PAD_IMU_A) + snprintf(sd->name, sizeof(sd->name), "D4XX %s %c", name, dpdata->subdev_info[0].suffix); + else if (sensor->mux_pad == DS5_MUX_PAD_RGB_B || sensor->mux_pad == DS5_MUX_PAD_DEPTH_B) + snprintf(sd->name, sizeof(sd->name), "D4XX %s %c", name, dpdata->subdev_info[1].suffix); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pad->flags = MEDIA_PAD_FL_SOURCE; + entity->obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; + entity->function = MEDIA_ENT_F_CAM_SENSOR; + return media_entity_pads_init(entity, 1, pad); +} + +static int ds5_sensor_register(struct ds5 *state, struct ds5_sensor *sensor) +{ + struct v4l2_subdev *sd = &sensor->sd; + struct media_entity *entity = &sensor->sd.entity; + int ret = -1; + + dev_dbg(sd->dev, "%s(): sd->name = %s sensor->mux_pad = %d\n", __func__, sd->name, sensor->mux_pad); + // FIXME: is async needed? + ret = v4l2_device_register_subdev(state->mux.sd.subdev.v4l2_dev, sd); + if (ret < 0) { + dev_err(sd->dev, "%s(): %d: %d\n", __func__, __LINE__, ret); + return ret; + } + + ret = media_create_pad_link(entity, 0, + &state->mux.sd.subdev.entity, sensor->mux_pad, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(sd->dev, "%s(): %d: %d\n", __func__, __LINE__, ret); + goto e_sd; + } + + dev_info(sd->dev, "%s(): 0 -> %d\n", __func__, sensor->mux_pad); + + return 0; + +e_sd: + v4l2_device_unregister_subdev(sd); + + return ret; +} + +static void ds5_sensor_remove(struct ds5_sensor *sensor) +{ + v4l2_device_unregister_subdev(&sensor->sd); + + media_entity_cleanup(&sensor->sd.entity); +} + +static int ds5_depth_init(struct i2c_client *c, struct ds5 *state) +{ + /* Which mux pad we're connecting to */ + state->depth.sensor.mux_pad = DS5_MUX_PAD_DEPTH_A; + return ds5_sensor_init(c, state, &state->depth.sensor, + &ds5_depth_subdev_ops, "depth"); +} + +static int ds5_depth_b_init(struct i2c_client *c, struct ds5 *state) +{ + /* Which mux pad we're connecting to */ + state->depth_b.sensor.mux_pad = DS5_MUX_PAD_DEPTH_B; + return ds5_sensor_init(c, state, &state->depth_b.sensor, + &ds5_depth_subdev_ops, "depth"); +} + +static int ds5_motion_t_init(struct i2c_client *c, struct ds5 *state) +{ + state->motion_t.sensor.mux_pad = DS5_MUX_PAD_MOTION_T_A; + return ds5_sensor_init(c, state, &state->motion_t.sensor, + &ds5_motion_t_subdev_ops, "motion detection"); +} + +static int ds5_rgb_init(struct i2c_client *c, struct ds5 *state) +{ + state->rgb.sensor.mux_pad = DS5_MUX_PAD_RGB_A; + return ds5_sensor_init(c, state, &state->rgb.sensor, + &ds5_rgb_subdev_ops, "rgb"); +} + +static int ds5_rgb_b_init(struct i2c_client *c, struct ds5 *state) +{ + state->rgb_b.sensor.mux_pad = DS5_MUX_PAD_RGB_B; + return ds5_sensor_init(c, state, &state->rgb_b.sensor, + &ds5_rgb_subdev_ops, "rgb"); +} + +static int ds5_imu_init(struct i2c_client *c, struct ds5 *state) +{ + state->imu.sensor.mux_pad = DS5_MUX_PAD_IMU_A; + return ds5_sensor_init(c, state, &state->imu.sensor, + &ds5_imu_subdev_ops, "imu"); +} + +/* No locking needed */ +static int ds5_mux_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_mbus_code_enum *mce) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_mbus_code_enum tmp = *mce; + struct v4l2_subdev *remote_sd; + int ret = -1; + + dev_info(&state->client->dev, "%s(): %s \n", __func__, sd->name); + switch (mce->pad) { + case DS5_MUX_PAD_DEPTH_B: + remote_sd = &state->depth_b.sensor.sd; + break; + case DS5_MUX_PAD_RGB_B: + remote_sd = &state->rgb_b.sensor.sd; + break; + case DS5_MUX_PAD_MOTION_T_A: + remote_sd = &state->motion_t.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH_A: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB_A: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_IMU_A: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + if (mce->index >= state->motion_t.sensor.n_formats + + state->depth.sensor.n_formats) + return -EINVAL; + + /* + * First list Left node / Motion Tracker formats, then depth. + * This should also help because D16 doesn't have a direct + * analog in MIPI CSI-2. + */ + if (mce->index < state->motion_t.sensor.n_formats) { + remote_sd = &state->motion_t.sensor.sd; + } else { + tmp.index = mce->index - state->motion_t.sensor.n_formats; + remote_sd = &state->depth.sensor.sd; + } + + break; + default: + return -EINVAL; + } + + tmp.pad = 0; + if (state->is_rgb) + remote_sd = &state->rgb.sensor.sd; + if (state->is_depth) + remote_sd = &state->depth.sensor.sd; + if (state->is_rgb_b) + remote_sd = &state->rgb_b.sensor.sd; + if (state->is_depth_b) + remote_sd = &state->depth_b.sensor.sd; + if (state->is_y8) + remote_sd = &state->motion_t.sensor.sd; + if (state->is_imu) + remote_sd = &state->imu.sensor.sd; + + /* Locks internally */ + ret = ds5_sensor_enum_mbus_code(remote_sd, v4l2_state, &tmp); + if (!ret) + mce->code = tmp.code; + + return ret; +} + +/* No locking needed */ +static int ds5_mux_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_frame_size_enum tmp = *fse; + struct v4l2_subdev *remote_sd; + u32 pad = fse->pad; + int ret = -1; + + tmp.pad = 0; + + if (state->is_depth) + pad = DS5_MUX_PAD_DEPTH_A; + if (state->is_y8) + pad = DS5_MUX_PAD_MOTION_T_A; + if (state->is_rgb) + pad = DS5_MUX_PAD_RGB_A; + if (state->is_imu) + pad = DS5_MUX_PAD_IMU_A; + if (state->is_depth_b) + pad = DS5_MUX_PAD_DEPTH_B; + if (state->is_rgb_b) + pad = DS5_MUX_PAD_RGB_B; + + switch (pad) { + case DS5_MUX_PAD_MOTION_T_A: + remote_sd = &state->motion_t.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH_A: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB_A: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH_B: + remote_sd = &state->depth_b.sensor.sd; + break; + case DS5_MUX_PAD_RGB_B: + remote_sd = &state->rgb_b.sensor.sd; + break; + case DS5_MUX_PAD_IMU_A: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + /* + * Assume, that different sensors don't support the same formats + * Try the Depth sensor first, then the Motion Tracker + */ + remote_sd = &state->depth.sensor.sd; + ret = ds5_sensor_enum_frame_size(remote_sd, NULL, &tmp); + if (!ret) { + *fse = tmp; + fse->pad = pad; + return 0; + } + + remote_sd = &state->motion_t.sensor.sd; + break; + default: + return -EINVAL; + } + + /* Locks internally */ + ret = ds5_sensor_enum_frame_size(remote_sd, NULL, &tmp); + if (!ret) { + *fse = tmp; + fse->pad = pad; + } + + return ret; +} + +/* No locking needed */ +static int ds5_mux_enum_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_subdev_frame_interval_enum tmp = *fie; + struct v4l2_subdev *remote_sd; + u32 pad = fie->pad; + int ret = -1; + + tmp.pad = 0; + + dev_err(state->depth.sensor.sd.dev, + "%s(): pad %d code %x width %d height %d\n", + __func__, pad, tmp.code, tmp.width, tmp.height); + + if (state->is_depth) + pad = DS5_MUX_PAD_DEPTH_A; + if (state->is_y8) + pad = DS5_MUX_PAD_MOTION_T_A; + if (state->is_rgb) + pad = DS5_MUX_PAD_RGB_A; + if (state->is_imu) + pad = DS5_MUX_PAD_IMU_A; + if (state->is_depth_b) + pad = DS5_MUX_PAD_DEPTH_B; + if (state->is_rgb_b) + pad = DS5_MUX_PAD_RGB_B; + + switch (pad) { + case DS5_MUX_PAD_DEPTH_B: + remote_sd = &state->depth_b.sensor.sd; + break; + case DS5_MUX_PAD_RGB_B: + remote_sd = &state->rgb_b.sensor.sd; + break; + case DS5_MUX_PAD_MOTION_T_A: + remote_sd = &state->motion_t.sensor.sd; + break; + case DS5_MUX_PAD_DEPTH_A: + remote_sd = &state->depth.sensor.sd; + break; + case DS5_MUX_PAD_RGB_A: + remote_sd = &state->rgb.sensor.sd; + break; + case DS5_MUX_PAD_IMU_A: + remote_sd = &state->imu.sensor.sd; + break; + case DS5_MUX_PAD_EXTERNAL: + /* Similar to ds5_mux_enum_frame_size() above */ + if (state->is_rgb) + remote_sd = &state->rgb.sensor.sd; + else if (state->is_rgb_b) + remote_sd = &state->rgb_b.sensor.sd; + else + remote_sd = &state->motion_t.sensor.sd; + ret = ds5_sensor_enum_frame_interval(remote_sd, NULL, &tmp); + if (!ret) { + *fie = tmp; + fie->pad = pad; + return 0; + } + + remote_sd = &state->motion_t.sensor.sd; + break; + default: + return -EINVAL; + } + + /* Locks internally */ + ret = ds5_sensor_enum_frame_interval(remote_sd, NULL, &tmp); + if (!ret) { + *fie = tmp; + fie->pad = pad; + } + + return ret; +} + +/* No locking needed */ +static int ds5_mux_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_format *fmt) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct v4l2_mbus_framefmt *ffmt; + struct ds5_sensor *sensor = state->mux.last_set; + u32 pad = sensor->mux_pad; + s64 *query_sub_stream = NULL; + int ret = 0; + int substream = -1; + if (pad != DS5_MUX_PAD_EXTERNAL) + ds5_s_state(state, pad - 1); + sensor = state->mux.last_set; + switch (pad) { + case DS5_MUX_PAD_DEPTH_A: + case DS5_MUX_PAD_DEPTH_B: + case DS5_MUX_PAD_MOTION_T_A: + case DS5_MUX_PAD_RGB_A: + case DS5_MUX_PAD_RGB_B: + case DS5_MUX_PAD_IMU_A: + //ffmt = &ds5_ffmts[pad]; + ffmt = &sensor->format;//ds5_ffmts[pad]; + break; + case DS5_MUX_PAD_EXTERNAL: + ffmt = &ds5_ffmts[pad]; + break; + default: + return -EINVAL; + } + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ffmt->width = fmt->format.width; + ffmt->height = fmt->format.height; + ffmt->code = fmt->format.code; + } + fmt->format = *ffmt; + + // substream = pad_to_substream[fmt->pad]; + substream = pad_to_substream[pad]; + + if (substream != -1) { + query_sub_stream = match_d4xx_sub_stream_qmenu(state->ctrls.query_sub_stream->qmenu_int); + if (query_sub_stream) { + set_sub_stream_fmt(query_sub_stream, substream, ffmt->code); + set_sub_stream_h(query_sub_stream, substream, ffmt->height); + set_sub_stream_w(query_sub_stream, substream, ffmt->width); + set_sub_stream_dt(query_sub_stream, substream, mbus_code_to_mipi(ffmt->code)); + } else { + dev_err(sd->dev, + "failed to find sub stream fmt info: %s\n", + sensor->sd.name); + } + } + + dev_info(sd->dev, "%s(): fmt->pad:%d, sensor->mux_pad: %d, \ + code: 0x%x: %ux%u substream:%d for sensor: %s\n", + __func__, + fmt->pad, pad, fmt->format.code, + fmt->format.width, fmt->format.height, + substream, sensor->sd.name); + + return ret; +} + +/* No locking needed */ +static int ds5_mux_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *v4l2_state, + struct v4l2_subdev_format *fmt) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + // u32 pad = fmt->pad; + int ret = 0; + struct ds5_sensor *sensor = state->mux.last_set; + u32 pad = sensor->mux_pad; + if (pad != DS5_MUX_PAD_EXTERNAL) + ds5_s_state(state, pad - 1); + sensor = state->mux.last_set; + dev_info(sd->dev, "%s(): %u %s %p\n", __func__, pad, ds5_get_sensor_name(state), state->mux.last_set); + + switch (pad) { + case DS5_MUX_PAD_DEPTH_A: + case DS5_MUX_PAD_DEPTH_B: + case DS5_MUX_PAD_MOTION_T_A: + case DS5_MUX_PAD_RGB_A: + case DS5_MUX_PAD_RGB_B: + case DS5_MUX_PAD_IMU_A: + //fmt->format = ds5_ffmts[pad]; + fmt->format = sensor->format;//ds5_ffmts[pad]; + break; + case DS5_MUX_PAD_EXTERNAL: + fmt->format = ds5_ffmts[pad]; + break; + default: + return -EINVAL; + } + + dev_dbg(sd->dev, "%s(): fmt->pad:%d, sensor->mux_pad:%u size:%d-%d, code:0x%x field:%d, color:%d\n", + __func__, fmt->pad, pad, + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.field, fmt->format.colorspace); + return ret; +} + +/* Video ops */ +static int ds5_mux_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct ds5_sensor *sensor = NULL; + + if (NULL == sd || NULL == fi) + return -EINVAL; + + sensor = state->mux.last_set; + + fi->interval.numerator = 1; + fi->interval.denominator = sensor->config.framerate; + + dev_info(sd->dev, "%s(): %s %u\n", __func__, sd->name, + fi->interval.denominator); + + return 0; +} + +static u16 __ds5_probe_framerate(const struct ds5_resolution *res, u16 target) +{ + int i; + u16 framerate; + + for (i = 0; i < res->n_framerates; i++) { + framerate = res->framerates[i]; + if (target <= framerate) + return framerate; + } + + return res->framerates[res->n_framerates - 1]; +} + +static int ds5_mux_s_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fi) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + struct ds5_sensor *sensor = NULL; + u16 framerate = 1; + + if (NULL == sd || NULL == fi || fi->interval.numerator == 0) + return -EINVAL; + + sensor = state->mux.last_set; + + framerate = fi->interval.denominator / fi->interval.numerator; + framerate = __ds5_probe_framerate(sensor->config.resolution, framerate); + sensor->config.framerate = framerate; + fi->interval.numerator = 1; + fi->interval.denominator = framerate; + + dev_info(sd->dev, "%s(): %s %u\n", __func__, sd->name, framerate); + + return 0; +} + +static int d4xx_reset_oneshot(struct ds5 *state) +{ + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + struct i2c_board_info *deser = dpdata->deser_board_info; + + int s_addr = state->client->addr; + int n_addr = deser->addr; + int ret = 0; + u8 val = 0; + + if (n_addr) { + state->client->addr = n_addr; + dev_warn(&state->client->dev, + "One-shot reset n_addr = 0x%x s_addr=0x%x\n", n_addr, s_addr); + /* One-shot reset enable auto-link */ + /* 0x23 a+a case*/ + ret = max9296_read_8(state, MAX9296_CTRL0, &val); + dev_dbg(&state->client->dev, "read current mode: %d\n", val); + ret = max9296_write_8(state, MAX9296_CTRL0, RESET_ONESHOT | val); + state->client->addr = s_addr; + /* delay to settle link */ + msleep(100); + } + + return ret; +} + +static int ds5_mux_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + u16 streaming, status; + int ret = 0; + unsigned int i = 0; + int restore_val = 0; + u16 config_status_base, stream_status_base, stream_id, vc_id; + struct ds5_sensor *sensor; + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + + // spare duplicate calls + if (state->mux.last_set->streaming == on) + return 0; + if (state->is_depth) { + sensor = &state->depth.sensor; + config_status_base = DS5_DEPTH_CONFIG_STATUS; + stream_status_base = DS5_DEPTH_STREAM_STATUS; + stream_id = DS5_STREAM_DEPTH; + vc_id = 0; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_rgb) { + sensor = &state->rgb.sensor; + config_status_base = DS5_RGB_CONFIG_STATUS; + stream_status_base = DS5_RGB_STREAM_STATUS; + stream_id = DS5_STREAM_RGB; + vc_id = 1; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_depth_b) { + sensor = &state->depth_b.sensor; + config_status_base = DS5_DEPTH_CONFIG_STATUS; + stream_status_base = DS5_DEPTH_STREAM_STATUS; + stream_id = DS5_STREAM_DEPTH; + vc_id = 2; + state->client->addr = dpdata->subdev_info[1].board_info.addr; + } else if (state->is_rgb_b) { + sensor = &state->rgb_b.sensor; + config_status_base = DS5_RGB_CONFIG_STATUS; + stream_status_base = DS5_RGB_STREAM_STATUS; + stream_id = DS5_STREAM_RGB; + vc_id = 3; + state->client->addr = dpdata->subdev_info[1].board_info.addr; + } else if (state->is_y8) { + sensor = &state->motion_t.sensor; + config_status_base = DS5_IR_CONFIG_STATUS; + stream_status_base = DS5_IR_STREAM_STATUS; + stream_id = DS5_STREAM_IR; + vc_id = 2; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else if (state->is_imu) { + sensor = &state->imu.sensor; + config_status_base = DS5_IMU_CONFIG_STATUS; + stream_status_base = DS5_IMU_STREAM_STATUS; + stream_id = DS5_STREAM_IMU; + vc_id = 3; + state->client->addr = dpdata->subdev_info[0].board_info.addr; + } else { + return -EINVAL; + } + + dev_warn(&state->client->dev, "s_stream for stream %s, on = %d\n", + state->mux.last_set->sd.name, on); + + restore_val = state->mux.last_set->streaming; + state->mux.last_set->streaming = on; + sensor->streaming = on; + + if (on) { + + ret = ds5_configure(state); + if (ret) + goto restore_s_state; + + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_START | stream_id); + if (ret < 0) + goto restore_s_state; + + // check streaming status from FW + for (i = 0; i < DS5_START_MAX_COUNT; i++) { + ds5_read(state, stream_status_base, &streaming); + ds5_read(state, config_status_base, &status); + if ((status & DS5_STATUS_STREAMING) && + streaming == DS5_STREAM_STREAMING) + break; + + msleep_range(DS5_START_POLL_TIME); + } + + if (i == DS5_START_MAX_COUNT) { + dev_err(&state->client->dev, + "start streaming failed, exit on timeout\n"); + /* notify fw */ + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_STOP | stream_id); + ret = -EAGAIN; + goto restore_s_state; + } else { + dev_dbg(&state->client->dev, "started after %dms\n", + i * DS5_START_POLL_TIME); + } + } else { // off + ret = ds5_write(state, DS5_START_STOP_STREAM, + DS5_STREAM_STOP | stream_id); + if (ret < 0) + goto restore_s_state; + + if (!state->depth.sensor.streaming + && !state->depth_b.sensor.streaming + && !state->rgb.sensor.streaming + && !state->rgb_b.sensor.streaming + && !state->motion_t.sensor.streaming + && !state->imu.sensor.streaming) + d4xx_reset_oneshot(state); + } + + ds5_read(state, config_status_base, &status); + ds5_read(state, stream_status_base, &streaming); + dev_info(&state->client->dev, + "%s %s, stream_status 0x%x:%x, config_status 0x%x:%x\n", + ds5_get_sensor_name(state), + (on)?"START":"STOP", + stream_status_base, streaming, + config_status_base, status); + + return ret; + +restore_s_state: + + ds5_read(state, config_status_base, &status); + dev_err(&state->client->dev, + "%s stream toggle failed! %x status 0x%04x\n", + ds5_get_sensor_name(state), restore_val, status); + + state->mux.last_set->streaming = restore_val; + sensor->streaming = restore_val; + + return ret; +} + +//static int ds5_set_power(struct ds5 *state, int on) +//{ +// int ret = 0; +// +// mutex_lock(&state->lock); +// +// if (state->power != !on) { +// mutex_unlock(&state->lock); +// return 0; +// } +// +//// gpio_set_value_cansleep(state->pwdn_gpio, on); +// +// dev_info(&state->client->dev, "%s(): power %d\n", __func__, on); +// +// usleep_range(100, 200); +// +// if (on) { +// state->power = true; +// } else { +// state->power = false; +// } +// +// mutex_unlock(&state->lock); +// +// /* TODO: Restore controls when powering on */ +// //if (on) +// // ret = v4l2_ctrl_handler_setup(&state->ctrls.handler); +// +// return ret; +//} + +/* Core ops */ +/*static int ds5_mux_set_power(struct v4l2_subdev *sd, int on) +{ + struct ds5 *state = container_of(sd, struct ds5, mux.sd.subdev); + + return ds5_set_power(state, on); +}*/ + +#define DS5_N_STREAMS 4 +#define DS5_PAD_SOURCE 0 + +static int ds5_mux_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + unsigned int i; + + desc->num_entries = V4L2_FRAME_DESC_ENTRY_MAX; + + for (i = 0; i < desc->num_entries; i++) { + desc->entry[i].flags = 0; + desc->entry[i].pixelcode = MEDIA_BUS_FMT_FIXED; + desc->entry[i].length = 0; + if (i == desc->num_entries - 1) { + desc->entry[i].pixelcode = 0x12; + desc->entry[i].length = 68; + } + } + return 0; +} + +static const struct v4l2_subdev_pad_ops ds5_mux_pad_ops = { + .enum_mbus_code = ds5_mux_enum_mbus_code, + .enum_frame_size = ds5_mux_enum_frame_size, + .enum_frame_interval = ds5_mux_enum_frame_interval, + .get_fmt = ds5_mux_get_fmt, + .set_fmt = ds5_mux_set_fmt, + .get_frame_desc = ds5_mux_get_frame_desc, +}; + +static const struct v4l2_subdev_core_ops ds5_mux_core_ops = { + //.s_power = ds5_mux_set_power, + .log_status = v4l2_ctrl_subdev_log_status, +}; + +static const struct v4l2_subdev_video_ops ds5_mux_video_ops = { + .g_frame_interval = ds5_mux_g_frame_interval, + .s_frame_interval = ds5_mux_s_frame_interval, + .s_stream = ds5_mux_s_stream, +}; + +static const struct v4l2_subdev_ops ds5_mux_subdev_ops = { + .core = &ds5_mux_core_ops, + .pad = &ds5_mux_pad_ops, + .video = &ds5_mux_video_ops, +}; + +#if 0 /* function not used */ +static int ds5_des_register(struct ds5 *state, struct ds5_des *des) +{ + struct v4l2_subdev *sd = &des->sd; + int ret; + + // FIXME: is async needed? + ret = v4l2_device_register_subdev(state->mux.sd.subdev.v4l2_dev, sd); + if (ret < 0) { + return ret; + } + + //ret = media_create_pad_link(entity, 1, &state->mux.sd.subdev.entity, des->mux_pad, + // MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + //if (ret < 0) { + // goto e_sd; + // } + + dev_info(sd->dev, "%s(): 0 -> %d\n", __func__, des->mux_pad); + + return 0; + +e_sd: + v4l2_device_unregister_subdev(sd); + + return ret; +} +#endif + +static int ds5_mux_registered(struct v4l2_subdev *sd) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + + int ret = ds5_sensor_register(state, &state->depth.sensor); + if (ret < 0) + return ret; + + ret = ds5_sensor_register(state, &state->rgb.sensor); + if (ret < 0) + goto e_rgb; + + if (dpdata->subdev_num == 1) { + ret = ds5_sensor_register(state, &state->motion_t.sensor); + if (ret < 0) + goto e_depth; + + ret = ds5_sensor_register(state, &state->imu.sensor); + if (ret < 0) + goto e_imu; + } else if (dpdata->subdev_num == 2) { + ret = ds5_sensor_register(state, &state->depth_b.sensor); + if (ret < 0) + dev_err(&state->client->dev, + "error: ds5_sensor_register state->depth_b.sensor\n"); + + ret = ds5_sensor_register(state, &state->rgb_b.sensor); + if (ret < 0) + dev_err(&state->client->dev, + "error: ds5_sensor_register state->rgb_b.sensor\n"); + } + return 0; + +e_imu: + v4l2_device_unregister_subdev(&state->rgb.sensor.sd); + +e_rgb: + v4l2_device_unregister_subdev(&state->motion_t.sensor.sd); + +e_depth: + v4l2_device_unregister_subdev(&state->depth.sensor.sd); + + return ret; +} + +static void ds5_mux_unregistered(struct v4l2_subdev *sd) +{ + struct ds5 *state = v4l2_get_subdevdata(sd); + struct d4xx_pdata *dpdata = state->client->dev.platform_data; + + ds5_sensor_remove(&state->rgb.sensor); + ds5_sensor_remove(&state->depth.sensor); + if (dpdata->subdev_num == 1) { + ds5_sensor_remove(&state->motion_t.sensor); + ds5_sensor_remove(&state->imu.sensor); + } else if (dpdata->subdev_num == 2) { + ds5_sensor_remove(&state->depth_b.sensor); + ds5_sensor_remove(&state->rgb_b.sensor); + } +} + +static const struct v4l2_subdev_internal_ops ds5_mux_internal_ops = { + .open = ds5_mux_open, + .close = ds5_mux_close, + .registered = ds5_mux_registered, + .unregistered = ds5_mux_unregistered, +}; + +static int ds5_mux_register(struct i2c_client *c, struct ds5 *state) +{ + return v4l2_async_register_subdev(&state->mux.sd.subdev); +} + +static int ds5_hw_init(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->mux.sd.subdev; + dev_dbg(&state->client->dev, "ds5_hw_init :sd->name = %s\n", sd->name); + u16 mipi_status, n_lanes, phy, drate_min, drate_max; + int ret = ds5_read(state, DS5_MIPI_SUPPORT_LINES, &n_lanes); + if (!ret) + ret = ds5_read(state, DS5_MIPI_SUPPORT_PHY, &phy); + + if (!ret) + ret = ds5_read(state, DS5_MIPI_DATARATE_MIN, &drate_min); + + if (!ret) + ret = ds5_read(state, DS5_MIPI_DATARATE_MAX, &drate_max); + + n_lanes = 2; + + ret = ds5_write(state, DS5_MIPI_LANE_NUMS, n_lanes - 1); + if (!ret) + ret = ds5_write(state, DS5_MIPI_LANE_DATARATE, MIPI_LANE_RATE); + + ret = ds5_read(state, DS5_MIPI_CONF_STATUS, &mipi_status); + + return ret; +} + +#if 0 /* not used function */ +static int ds5_des_init(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->max9296.des.sd; + struct media_entity *entity = &state->max9296.des.sd.entity; + struct media_pad *pads = state->max9296.des.pads; + dev_t *dev_num = &state->client->dev.devt; + + state->max9296.des.mux_pad = DS5_MUX_PAD_EXTERNAL; + + dev_info(sd->dev, "%s(): %p %s %p %p", __func__, c, c->name, state, state->client); + + v4l2_i2c_subdev_init(sd, c, &ds5_mux_subdev_ops); + sd->owner = THIS_MODULE; + sd->internal_ops = &ds5_sensor_internal_ops; + sd->grp_id = *dev_num; + v4l2_set_subdevdata(sd, state); + snprintf(sd->name, sizeof(sd->name), "D4XX %s %d-%04x", + "max9296", i2c_adapter_id(c->adapter), 0x48); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + pads[0].flags = MEDIA_PAD_FL_SOURCE; + pads[1].flags = MEDIA_PAD_FL_SINK; + entity->obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; + entity->function = MEDIA_ENT_F_CAM_SENSOR; + return media_entity_pads_init(entity, 2, pads); +} +#endif +static int ds5_mux_init(struct i2c_client *c, struct ds5 *state) +{ + struct v4l2_subdev *sd = &state->mux.sd.subdev; + struct media_entity *entity = &state->mux.sd.subdev.entity; + struct media_pad *pads = state->mux.pads, *pad; + unsigned int i; + int ret; + + struct d4xx_pdata *dpdata = c->dev.platform_data; + v4l2_i2c_subdev_init(sd, c, &ds5_mux_subdev_ops); + // Set owner to NULL so we can unload the driver module + sd->owner = NULL; + sd->internal_ops = &ds5_mux_internal_ops; + v4l2_set_subdevdata(sd, state); + snprintf(sd->name, sizeof(sd->name), "DS5 mux %c", dpdata->suffix); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + entity->obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; + entity->function = MEDIA_ENT_F_CAM_SENSOR; + + pads[0].flags = MEDIA_PAD_FL_SOURCE; + for (i = 1, pad = pads + 1; i < ARRAY_SIZE(state->mux.pads); i++, pad++) + pad->flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(entity, ARRAY_SIZE(state->mux.pads), pads); + if (ret < 0) + return ret; + + /*set for mux*/ + ret = ds5_ctrl_init(state, DS5_SID_MUX_A); + if (ret < 0) + return ret; + /*set for depth*/ + ret = ds5_ctrl_init(state, DS5_SID_DEPTH_A); + if (ret < 0) + return ret; + /*set for rgb*/ + ret = ds5_ctrl_init(state, DS5_SID_RGB_A); + if (ret < 0) + return ret; + /*set for y8*/ + ret = ds5_ctrl_init(state, DS5_SID_MOTION_T_A); + if (ret < 0) + return ret; + /*set for imu*/ + ret = ds5_ctrl_init(state, DS5_SID_IMU_A); + if (ret < 0) + return ret; + if (dpdata->subdev_num == 2) { + /*set for depth_b*/ + ret = ds5_ctrl_init(state, DS5_SID_DEPTH_B); + if (ret < 0) + return ret; + /*set for rgb_b*/ + ret = ds5_ctrl_init(state, DS5_SID_RGB_B); + if (ret < 0) + return ret; + } + + ds5_set_state_last_set(state); + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + state->mux.sd.dev = &c->dev; + ret = camera_common_initialize(&state->mux.sd, "d4xx"); + if (ret) { + dev_err(&c->dev, "Failed to initialize d4xx.\n"); + goto e_ctrl; + } +#endif + + return 0; + +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM +e_ctrl: + v4l2_ctrl_handler_free(sd->ctrl_handler); +#endif + media_entity_cleanup(entity); + + return ret; +} + +#define USE_Y + +static int ds5_fixed_configuration(struct i2c_client *client, struct ds5 *state) +{ + struct ds5_sensor *sensor; + u16 cfg0 = 0, cfg0_md = 0, cfg1 = 0, cfg1_md = 0; + u16 dw = 0, dh = 0, yw = 0, yh = 0, dev_type = 0; + int ret; + + ret = ds5_read(state, DS5_DEPTH_STREAM_DT, &cfg0); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_STREAM_MD, &cfg0_md); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_RES_WIDTH, &dw); + if (!ret) + ret = ds5_read(state, DS5_DEPTH_RES_HEIGHT, &dh); + if (!ret) + ret = ds5_read(state, DS5_IR_STREAM_DT, &cfg1); + if (!ret) + ret = ds5_read(state, DS5_IR_STREAM_MD, &cfg1_md); + if (!ret) + ret = ds5_read(state, DS5_IR_RES_WIDTH, &yw); + if (!ret) + ret = ds5_read(state, DS5_IR_RES_HEIGHT, &yh); + if (!ret) + ret = ds5_read(state, DS5_DEVICE_TYPE, &dev_type); + if (ret < 0) + return ret; + + dev_info(&client->dev, "%s(): cfg0 %x %ux%u cfg0_md %x %ux%u\n", __func__, + cfg0, dw, dh, cfg0_md, yw, yh); + + dev_info(&client->dev, "%s(): cfg1 %x %ux%u cfg1_md %x %ux%u\n", __func__, + cfg1, dw, dh, cfg1_md, yw, yh); + + sensor = &state->depth.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D45X: + sensor->formats = ds5_depth_formats_d43x; + break; + case DS5_DEVICE_TYPE_D46X: + sensor->formats = ds5_depth_formats_d46x; + break; + default: + sensor->formats = ds5_depth_formats_d46x; + } + sensor->n_formats = 1; + sensor->mux_pad = DS5_MUX_PAD_DEPTH_A; + + sensor = &state->depth_b.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D45X: + sensor->formats = ds5_depth_formats_d43x; + break; + case DS5_DEVICE_TYPE_D46X: + sensor->formats = ds5_depth_formats_d46x; + break; + default: + sensor->formats = ds5_depth_formats_d46x; + } + sensor->n_formats = 1; + sensor->mux_pad = DS5_MUX_PAD_DEPTH_B; + + sensor = &state->motion_t.sensor; + sensor->formats = state->variant->formats; + sensor->n_formats = state->variant->n_formats; + sensor->mux_pad = DS5_MUX_PAD_MOTION_T_A; + switch (dev_type) { + // case DS5_DEVICE_TYPE_D45X: + case DS5_DEVICE_TYPE_D43X: { + unsigned int *calib_resolutions_size = + (unsigned int *)&(sensor->formats[ARRAY_SIZE(ds5_y_formats_ds5u)-1].n_resolutions); + const struct ds5_resolution **calib_resolutions = (const struct ds5_resolution **) + &(sensor->formats[ARRAY_SIZE(ds5_y_formats_ds5u)-1].resolutions); + *calib_resolutions_size = ARRAY_SIZE(d43x_calibration_sizes), + *calib_resolutions = d43x_calibration_sizes; + break; + } + case DS5_DEVICE_TYPE_D46X: { + dev_info(&client->dev, "%s(): DS5_DEVICE_TYPE_D46X for calib\n", __func__); + /*unsigned int *calib_resolutions_size = + * (unsigned int *)&(sensor->formats[ARRAY_SIZE(ds5_y_formats_ds5u)-1].n_resolutions); + *const struct ds5_resolution **calib_resolutions = (const struct ds5_resolution**) + * &(sensor->formats[ARRAY_SIZE(ds5_y_formats_ds5u)-1].resolutions); + **calib_resolutions_size = ARRAY_SIZE(d46x_calibration_sizes), + **calib_resolutions = d46x_calibration_sizes; + */ + break; + } + } + + sensor = &state->rgb.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D46X: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + break; + case DS5_DEVICE_TYPE_D45X: + sensor->formats = &ds5_rlt_rgb_format; + sensor->n_formats = DS5_RLT_RGB_N_FORMATS; + break; + default: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + } + sensor->mux_pad = DS5_MUX_PAD_RGB_A; + + sensor = &state->rgb_b.sensor; + switch (dev_type) { + case DS5_DEVICE_TYPE_D43X: + case DS5_DEVICE_TYPE_D46X: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + break; + case DS5_DEVICE_TYPE_D45X: + sensor->formats = &ds5_rlt_rgb_format; + sensor->n_formats = DS5_RLT_RGB_N_FORMATS; + break; + default: + sensor->formats = &ds5_onsemi_rgb_format; + sensor->n_formats = DS5_ONSEMI_RGB_N_FORMATS; + } + sensor->mux_pad = DS5_MUX_PAD_RGB_B; + + /*sensor->formats = &ds5_onsemi_rgb_format; + *sensor->n_formats = DS5_RGB_N_FORMATS; + *sensor->mux_pad = DS5_MUX_PAD_RGB; + */ + + sensor = &state->imu.sensor; + sensor->formats = ds5_imu_formats; + sensor->n_formats = 1; + sensor->mux_pad = DS5_MUX_PAD_IMU_A; + + /* Development: set a configuration during probing */ + if ((cfg0 & 0xff00) == 0x1800) { + /* MIPI CSI-2 YUV420 isn't supported by V4L, reconfigure to Y8 */ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = 0, + /* Use template to fill in .field, .colorspace etc. */ + .format = ds5_mbus_framefmt_template, + }; + +//#undef USE_Y + /* Override .width, .height, .code */ + fmt.format.width = yw; + fmt.format.height = yh; + fmt.format.code = MEDIA_BUS_FMT_UYVY8_2X8; + + //state->mux.sd.mode_prop_idx = 0; + state->motion_t.sensor.streaming = true; + state->depth.sensor.streaming = true; + ret = __ds5_sensor_set_fmt(state, &state->motion_t.sensor, NULL, &fmt); + if (ret < 0) + return ret; + } + + return 0; +} + +static int ds5_parse_cam(struct i2c_client *client, struct ds5 *state) +{ + int ret; + + ret = ds5_fixed_configuration(client, state); + if (ret < 0) + return ret; + + ds5_sensor_format_init(&state->depth.sensor); + ds5_sensor_format_init(&state->motion_t.sensor); + ds5_sensor_format_init(&state->rgb.sensor); + ds5_sensor_format_init(&state->imu.sensor); + if ((&(state->depth_b) != NULL) && (&(state->rgb_b) != NULL)) { + ds5_sensor_format_init(&state->depth_b.sensor); + ds5_sensor_format_init(&state->rgb_b.sensor); + } + + return 0; +} + +static void ds5_mux_remove(struct ds5 *state) +{ +#ifdef CONFIG_TEGRA_CAMERA_PLATFORM + camera_common_cleanup(&state->mux.sd); +#endif + v4l2_async_unregister_subdev(&state->mux.sd.subdev); + v4l2_ctrl_handler_free(state->mux.sd.subdev.ctrl_handler); + media_entity_cleanup(&state->mux.sd.subdev.entity); +} + +static const struct regmap_config ds5_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_NATIVE, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int ds5_dfu_wait_for_status(struct ds5 *state) +{ + int i, ret = 0; + u16 status; + + for (i = 0; i < DS5_START_MAX_COUNT; i++) { + ds5_read(state, 0x5000, &status); + if (status == 0x0001 || status == 0x0002) { + dev_err(&state->client->dev, + "%s(): dfu failed status(0x%4x)\n", + __func__, status); + ret = -EREMOTEIO; + break; + } + if (!status) + break; + msleep_range(DS5_START_POLL_TIME); + } + + return ret; +}; + +static int ds5_dfu_switch_to_dfu(struct ds5 *state) +{ + int ret; + int i = DS5_START_MAX_COUNT; + u16 status; + + ds5_raw_write_with_check(state, 0x4900, + &cmd_switch_to_dfu, sizeof(cmd_switch_to_dfu)); + ds5_write_with_check(state, 0x490c, 0x01); /* execute cmd */ + /*Wait for DFU fw to boot*/ + do { + msleep_range(DS5_START_POLL_TIME*10); + ret = ds5_read(state, 0x5000, &status); + } while (ret && i--); + return ret; +}; + +static int ds5_dfu_wait_for_get_dfu_status(struct ds5 *state, + enum dfu_fw_state exp_state) +{ + int ret = 0; + u16 status, dfu_state_len = 0x0000; + unsigned char dfu_asw_buf[DFU_WAIT_RET_LEN]; + unsigned int dfu_wr_wait_msec = 0; + + do { + ds5_write_with_check(state, 0x5008, 0x0003); // Get Write state + do { + ds5_read_with_check(state, 0x5000, &status); + if (status == 0x0001) { + dev_err(&state->client->dev, + "%s(): Write status error I2C_STATUS_ERROR(1)\n", + __func__); + return -EINVAL; + } else + if (status == 0x0002 && dfu_wr_wait_msec) + msleep_range(dfu_wr_wait_msec); + + } while (status); + + ds5_read_with_check(state, 0x5004, &dfu_state_len); + if (dfu_state_len != DFU_WAIT_RET_LEN) { + dev_err(&state->client->dev, + "%s(): Wrong answer len (%d)\n", __func__, dfu_state_len); + return -EINVAL; + } + ds5_raw_read_with_check(state, 0x4e00, &dfu_asw_buf, DFU_WAIT_RET_LEN); + if (dfu_asw_buf[0]) { + dev_err(&state->client->dev, + "%s(): Wrong dfu_status (%d)\n", __func__, dfu_asw_buf[0]); + return -EINVAL; + } + dfu_wr_wait_msec = (((unsigned int)dfu_asw_buf[3]) << 16) + | (((unsigned int)dfu_asw_buf[2]) << 8) + | dfu_asw_buf[1]; + } while (dfu_asw_buf[4] == dfuDNBUSY && exp_state == dfuDNLOAD_IDLE); + + if (dfu_asw_buf[4] != exp_state) { + dev_notice(&state->client->dev, + "%s(): Wrong dfu_state (%d) while expected(%d)\n", + __func__, dfu_asw_buf[4], exp_state); + ret = -EINVAL; + } + return ret; +}; + +static int ds5_dfu_get_dev_info(struct ds5 *state, struct __fw_status *buf) +{ + int ret = 0; + u16 len = 0; + + ret = ds5_write(state, 0x5008, 0x0002); //Upload DFU cmd + if (!ret) + ret = ds5_dfu_wait_for_status(state); + if (!ret) + ds5_read_with_check(state, 0x5004, &len); + /*Sanity check*/ + if (len == sizeof(struct __fw_status)) { + ds5_raw_read_with_check(state, 0x4e00, buf, len); + } else { + dev_err(&state->client->dev, + "%s(): Wrong state size (%d)\n", + __func__, len); + ret = -EINVAL; + } + return ret; +}; + +static int ds5_dfu_detach(struct ds5 *state) +{ + int ret; + struct __fw_status buf = {0}; + + ds5_write_with_check(state, 0x500c, 0x00); + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuIDLE); + if (!ret) + ret = ds5_dfu_get_dev_info(state, &buf); + dev_notice(&state->client->dev, "%s():DFU ver (0x%x) received\n", + __func__, buf.DFU_version); + dev_notice(&state->client->dev, "%s():FW last version (0x%x) received\n", + __func__, buf.FW_lastVersion); + dev_notice(&state->client->dev, "%s():FW status (%s)\n", + __func__, buf.DFU_isLocked ? "locked" : "unlocked"); + return ret; +}; + +/* When a process reads from our device, this gets called. */ +static ssize_t ds5_dfu_device_read(struct file *flip, + char __user *buffer, size_t len, loff_t *offset) +{ + struct ds5 *state = flip->private_data; + u16 fw_ver, fw_build; + char msg[32]; + int ret = 0; + + if (mutex_lock_interruptible(&state->lock)) + return -ERESTARTSYS; + ret |= ds5_read(state, DS5_FW_VERSION, &fw_ver); + ret |= ds5_read(state, DS5_FW_BUILD, &fw_build); + if (ret < 0) + goto e_dfu_read_failed; + snprintf(msg, sizeof(msg), "DFU info: \tver: %d.%d.%d.%d\n", + (fw_ver >> 8) & 0xff, fw_ver & 0xff, + (fw_build >> 8) & 0xff, fw_build & 0xff); + + if (copy_to_user(buffer, msg, strlen(msg))) + ret = -EFAULT; + else { + state->dfu_dev.msg_write_once = ~state->dfu_dev.msg_write_once; + ret = strlen(msg) & state->dfu_dev.msg_write_once; + } + +e_dfu_read_failed: + mutex_unlock(&state->lock); + return ret; +}; + +static ssize_t ds5_dfu_device_write(struct file *flip, + const char __user *buffer, size_t len, loff_t *offset) +{ + struct ds5 *state = flip->private_data; + int ret = 0; + + if (mutex_lock_interruptible(&state->lock)) + return -ERESTARTSYS; + switch (state->dfu_dev.dfu_state_flag) { + + case DS5_DFU_OPEN: + ret = ds5_dfu_switch_to_dfu(state); + if (ret < 0) { + dev_err(&state->client->dev, "%s(): Switch to dfu failed (%d)\n", + __func__, ret); + goto dfu_write_error; + } + /* fallthrough - procceed to recovery */ + __attribute__((__fallthrough__)); + case DS5_DFU_RECOVERY: + ret = ds5_dfu_detach(state); + if (ret < 0) { + dev_err(&state->client->dev, "%s(): Detach failed (%d)\n", + __func__, ret); + goto dfu_write_error; + } + state->dfu_dev.dfu_state_flag = DS5_DFU_IN_PROGRESS; + state->dfu_dev.init_v4l_f = 1; + /* fallthrough - procceed to download */ + __attribute__((__fallthrough__)); + case DS5_DFU_IN_PROGRESS: { + unsigned int dfu_full_blocks = len / DFU_BLOCK_SIZE; + unsigned int dfu_part_blocks = len % DFU_BLOCK_SIZE; + + while (dfu_full_blocks--) { + if (copy_from_user(state->dfu_dev.dfu_msg, buffer, DFU_BLOCK_SIZE)) { + ret = -EFAULT; + goto dfu_write_error; + } + ret = ds5_raw_write(state, 0x4a00, + state->dfu_dev.dfu_msg, DFU_BLOCK_SIZE); + if (ret < 0) + goto dfu_write_error; + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuDNLOAD_IDLE); + if (ret < 0) + goto dfu_write_error; + buffer += DFU_BLOCK_SIZE; + } + if (copy_from_user(state->dfu_dev.dfu_msg, buffer, dfu_part_blocks)) { + ret = -EFAULT; + goto dfu_write_error; + } + if (dfu_part_blocks) { + ret = ds5_raw_write(state, 0x4a00, + state->dfu_dev.dfu_msg, dfu_part_blocks); + if (!ret) + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuDNLOAD_IDLE); + if (!ret) + ret = ds5_write(state, 0x4a04, 0x00); /*Download complete */ + if (!ret) + ret = ds5_dfu_wait_for_get_dfu_status(state, dfuMANIFEST); + if (ret < 0) + goto dfu_write_error; + state->dfu_dev.dfu_state_flag = DS5_DFU_DONE; + } + dev_notice(&state->client->dev, "%s(): DFU block (%d) bytes written\n", + __func__, (int)len); + break; + } + default: + dev_err(&state->client->dev, "%s(): Wrong state (%d)\n", + __func__, state->dfu_dev.dfu_state_flag); + ret = -EINVAL; + goto dfu_write_error; + + }; + mutex_unlock(&state->lock); + return len; + +dfu_write_error: + state->dfu_dev.dfu_state_flag = DS5_DFU_ERROR; + // Reset DFU device to IDLE states + ret = ds5_write(state, 0x5010, 0x0); + if (!ret) + state->dfu_dev.dfu_state_flag = DS5_DFU_IDLE; + mutex_unlock(&state->lock); + return ret; +}; + +static int ds5_dfu_device_open(struct inode *inode, struct file *file) +{ + struct ds5 *state = container_of(inode->i_cdev, struct ds5, + dfu_dev.ds5_cdev); + + if (state->dfu_dev.device_open_count) + return -EBUSY; + state->dfu_dev.device_open_count++; + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY) + state->dfu_dev.dfu_state_flag = DS5_DFU_OPEN; + state->dfu_dev.dfu_msg = devm_kzalloc(&state->client->dev, + DFU_BLOCK_SIZE, GFP_KERNEL); + if (!state->dfu_dev.dfu_msg) + return -ENOMEM; + + file->private_data = state; + return 0; +}; + +static int ds5_v4l_init(struct i2c_client *c, struct ds5 *state) +{ + int ret; + struct d4xx_pdata *dpdata = c->dev.platform_data; + + ret = ds5_parse_cam(c, state); + if (ret < 0) + return ret; + + ret = ds5_depth_init(c, state); + if (ret < 0) + return ret; + + ret = ds5_motion_t_init(c, state); + if (ret < 0) + goto e_depth; + + ret = ds5_rgb_init(c, state); + if (ret < 0) + goto e_motion_t; + + if (dpdata->subdev_num == 2) { + ret = ds5_depth_b_init(c, state); + if (ret < 0) + return ret; + + ret = ds5_rgb_b_init(c, state); + if (ret < 0) + goto e_depth_b; + } + + ret = ds5_imu_init(c, state); + if (ret < 0) + goto e_rgb; + + ret = ds5_mux_init(c, state); + if (ret < 0) + goto e_imu; + + ret = ds5_hw_init(c, state); + if (ret < 0) + goto e_mux; + + ret = ds5_mux_register(c, state); + if (ret < 0) + goto e_mux; + + return 0; +e_mux: + ds5_mux_remove(state); +e_imu: + media_entity_cleanup(&state->imu.sensor.sd.entity); +e_rgb: + media_entity_cleanup(&state->rgb.sensor.sd.entity); +e_motion_t: + media_entity_cleanup(&state->motion_t.sensor.sd.entity); +e_depth_b: + media_entity_cleanup(&state->depth_b.sensor.sd.entity); +e_depth: + media_entity_cleanup(&state->depth.sensor.sd.entity); + + return ret; +} + +static int ds5_dfu_device_release(struct inode *inode, struct file *file) +{ + struct ds5 *state = container_of(inode->i_cdev, struct ds5, dfu_dev.ds5_cdev); + + state->dfu_dev.device_open_count--; + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY) + state->dfu_dev.dfu_state_flag = DS5_DFU_IDLE; + if (state->dfu_dev.dfu_state_flag == DS5_DFU_DONE + && state->dfu_dev.init_v4l_f) + ds5_v4l_init(state->client, state); + state->dfu_dev.init_v4l_f = 0; + if (state->dfu_dev.dfu_msg) + devm_kfree(&state->client->dev, state->dfu_dev.dfu_msg); + state->dfu_dev.dfu_msg = NULL; + + return 0; +}; + +static const struct file_operations ds5_device_file_ops = { + .owner = THIS_MODULE, + .read = &ds5_dfu_device_read, + .write = &ds5_dfu_device_write, + .open = &ds5_dfu_device_open, + .release = &ds5_dfu_device_release +}; + +struct class *g_ds5_class; +atomic_t primary_chardev = ATOMIC_INIT(0); + +static int ds5_chrdev_init(struct i2c_client *c, struct ds5 *state) +{ + struct cdev *ds5_cdev = &state->dfu_dev.ds5_cdev; + struct class **ds5_class = &state->dfu_dev.ds5_class; + struct d4xx_pdata *pdata = c->dev.platform_data; + + struct device *chr_dev; + char dev_name[sizeof(DS5_DRIVER_NAME_DFU) + 8]; + dev_t *dev_num = &c->dev.devt; + int ret; + + dev_dbg(&c->dev, "%s()\n", __func__); + /* Request the kernel for N_MINOR devices */ + ret = alloc_chrdev_region(dev_num, 0, 1, DS5_DRIVER_NAME_DFU); + if (ret < 0) + return ret; + + if (!atomic_cmpxchg(&primary_chardev, 0, MAJOR(*dev_num))) { + dev_dbg(&c->dev, "%s(): : <%d, %d>\n", + __func__, MAJOR(*dev_num), MINOR(*dev_num)); + /* Create a class : appears at /sys/class */ + *ds5_class = class_create(THIS_MODULE, DS5_DRIVER_NAME_CLASS); + if (IS_ERR(*ds5_class)) { + dev_err(&c->dev, "Could not create class device\n"); + unregister_chrdev_region(0, 1); + ret = PTR_ERR(*ds5_class); + return ret; + } + g_ds5_class = *ds5_class; + } else + *ds5_class = g_ds5_class; + /* Associate the cdev with a set of file_operations */ + cdev_init(ds5_cdev, &ds5_device_file_ops); + /* Build up the current device number. To be used further */ + *dev_num = MKDEV(MAJOR(*dev_num), MINOR(*dev_num)); + /* Create a device node for this device. */ + snprintf(dev_name, sizeof(dev_name), "%s-%c", + DS5_DRIVER_NAME_DFU, pdata->suffix); + chr_dev = device_create(*ds5_class, NULL, *dev_num, NULL, dev_name); + if (IS_ERR(chr_dev)) { + ret = PTR_ERR(chr_dev); + dev_err(&c->dev, "Could not create device\n"); + class_destroy(*ds5_class); + unregister_chrdev_region(0, 1); + return ret; + } + cdev_add(ds5_cdev, *dev_num, 1); + return 0; +}; + +static int ds5_chrdev_remove(struct ds5 *state) +{ + struct class **ds5_class = &state->dfu_dev.ds5_class; + dev_t *dev_num = &state->client->dev.devt; + if (!ds5_class) { + return 0; + } + dev_dbg(&state->client->dev, "%s()\n", __func__); + unregister_chrdev_region(*dev_num, 1); + device_destroy(*ds5_class, *dev_num); + if (atomic_cmpxchg(&primary_chardev, MAJOR(*dev_num), 0) == MAJOR(*dev_num)) + class_destroy(*ds5_class); + return 0; +} + +static void ds5_substream_init(void) +{ + int i, j; + unsigned int mipi_csi2_type; + s64 *sub_stream = NULL; + /* + * 0, vc 0, depth + * 1, vc 0, meta data + * 2, vc 1, RGB + * 3, vc 1, meta data + * 4, vc 2, IR + * 5, vc 3, IMU + * 6, vc 2, depth_b + * 7, vc 3,rgb_b + */ + for (j = 0; j < MAX_D457_COUNT; j++) { + sub_stream = d4xx_query_sub_stream[j]; + mipi_csi2_type = mbus_code_to_mipi(MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_fmt(sub_stream, 0, MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h(sub_stream, 0, 480); + set_sub_stream_w(sub_stream, 0, 640); + set_sub_stream_dt(sub_stream, 0, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 0, 0); + + set_sub_stream_fmt(sub_stream, 1, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h(sub_stream, 1, 1); + set_sub_stream_w(sub_stream, 1, 68); + set_sub_stream_dt(sub_stream, 1, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(sub_stream, 1, 0); + + /*RGB*/ + set_sub_stream_fmt(sub_stream, 2, MEDIA_BUS_FMT_YUYV8_1X16); + set_sub_stream_h(sub_stream, 2, 640); + set_sub_stream_w(sub_stream, 2, 480); + set_sub_stream_dt(sub_stream, 2, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 2, 1); + + set_sub_stream_fmt(sub_stream, 3, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h(sub_stream, 3, 1); + set_sub_stream_w(sub_stream, 3, 68); + set_sub_stream_dt(sub_stream, 3, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(sub_stream, 3, 1); + /*IR*/ + set_sub_stream_fmt(sub_stream, 4, MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h(sub_stream, 4, 640); + set_sub_stream_w(sub_stream, 4, 480); + set_sub_stream_dt(sub_stream, 4, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 4, 2); + + set_sub_stream_fmt(sub_stream, 5, MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h(sub_stream, 5, 640); + set_sub_stream_w(sub_stream, 5, 480); + set_sub_stream_dt(sub_stream, 5, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 5, 3); + + /*add DEPTH_b*/ + set_sub_stream_fmt(sub_stream, 6, MEDIA_BUS_FMT_UYVY8_1X16); + set_sub_stream_h(sub_stream, 6, 480); + set_sub_stream_w(sub_stream, 6, 640); + set_sub_stream_dt(sub_stream, 6, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 6, 2); + + set_sub_stream_fmt(sub_stream, 7, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h(sub_stream, 7, 1); + set_sub_stream_w(sub_stream, 7, 68); + set_sub_stream_dt(sub_stream, 7, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(sub_stream, 7, 2); + + /*add RGB_b*/ + set_sub_stream_fmt(sub_stream, 8, MEDIA_BUS_FMT_YUYV8_1X16); + set_sub_stream_h(sub_stream, 8, 640); + set_sub_stream_w(sub_stream, 8, 480); + set_sub_stream_dt(sub_stream, 8, mipi_csi2_type); + set_sub_stream_vc_id(sub_stream, 8, 3); + + set_sub_stream_fmt(sub_stream, 9, MEDIA_BUS_FMT_SGRBG8_1X8); + set_sub_stream_h(sub_stream, 9, 1); + set_sub_stream_w(sub_stream, 9, 68); + set_sub_stream_dt(sub_stream, 9, MIPI_CSI2_TYPE_EMBEDDED8); + set_sub_stream_vc_id(sub_stream, 9, 3); + } + + for (i = 0; i < DS5_MUX_PAD_COUNT; i++) + pad_to_substream[i] = -1; + + pad_to_substream[DS5_MUX_PAD_DEPTH_A] = 0; + pad_to_substream[DS5_MUX_PAD_RGB_A] = 2; + pad_to_substream[DS5_MUX_PAD_MOTION_T_A] = 4; + pad_to_substream[DS5_MUX_PAD_IMU_A] = 5; + pad_to_substream[DS5_MUX_PAD_DEPTH_B] = 6; + pad_to_substream[DS5_MUX_PAD_RGB_B] = 8;//7 metadata +} + +/* SYSFS attributes */ +#ifdef CONFIG_SYSFS +static ssize_t ds5_fw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + + ds5_read(state, DS5_FW_VERSION, &state->fw_version); + ds5_read(state, DS5_FW_BUILD, &state->fw_build); + + return snprintf(buf, PAGE_SIZE, "D4XX Sensor: %s, Version: %d.%d.%d.%d\n", + ds5_get_sensor_name(state), + (state->fw_version >> 8) & 0xff, state->fw_version & 0xff, + (state->fw_build >> 8) & 0xff, state->fw_build & 0xff); +} + +static DEVICE_ATTR_RO(ds5_fw_ver); + +/* Derive 'device_attribute' structure for a read register's attribute */ +struct dev_ds5_reg_attribute { + struct device_attribute attr; + u16 reg; // register + u8 valid; // validity of above data +}; + +/** Read DS5 register. + * ds5_read_reg_show will actually read register from ds5 while + * ds5_read_reg_store will store register to read + * Example: + * echo -n "0xc03c" >ds5_read_reg + * Read register result: + * cat ds5_read_reg + * Expected: + * reg:0xc93c, result:0x11 + */ +static ssize_t ds5_read_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u16 rbuf; + int n; + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + struct dev_ds5_reg_attribute *ds5_rw_attr = container_of(attr, + struct dev_ds5_reg_attribute, attr); + if (ds5_rw_attr->valid != 1) + return -EINVAL; + ds5_read(state, ds5_rw_attr->reg, &rbuf); + + n = snprintf(buf, PAGE_SIZE, "register:0x%4x, value:0x%02x\n", + ds5_rw_attr->reg, rbuf); + + return n; +} + +/** Read DS5 register - Store reg to attr struct pointer + * ds5_read_reg_show will actually read register from ds5 while + * ds5_read_reg_store will store module, offset and length + */ +static ssize_t ds5_read_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dev_ds5_reg_attribute *ds5_rw_attr = container_of(attr, + struct dev_ds5_reg_attribute, attr); + int rc = -1; + u32 reg; + + ds5_rw_attr->valid = 0; + /* Decode input */ + rc = sscanf(buf, "0x%04x", ®); + if (rc != 1) + return -EINVAL; + ds5_rw_attr->reg = reg; + ds5_rw_attr->valid = 1; + return count; +} + +#define DS5_RW_REG_ATTR(_name) \ + struct dev_ds5_reg_attribute dev_attr_##_name = { \ + __ATTR(_name, S_IRUGO | S_IWUSR, \ + ds5_read_reg_show, ds5_read_reg_store), \ + 0, 0 } + +static DS5_RW_REG_ATTR(ds5_read_reg); + +static ssize_t ds5_write_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *c = to_i2c_client(dev); + struct ds5 *state = container_of(i2c_get_clientdata(c), + struct ds5, mux.sd.subdev); + + int rc = -1; + u32 reg, w_val = 0; + u16 val = -1; + /* Decode input */ + rc = sscanf(buf, "0x%04x 0x%04x", ®, &w_val); + if (rc != 2) + return -EINVAL; + val = w_val & 0xffff; + mutex_lock(&state->lock); + ds5_write(state, reg, val); + mutex_unlock(&state->lock); + return count; +} + +static DEVICE_ATTR_WO(ds5_write_reg); + +static struct attribute *ds5_attributes[] = { + &dev_attr_ds5_fw_ver.attr, + &dev_attr_ds5_read_reg.attr.attr, + &dev_attr_ds5_write_reg.attr, + NULL +}; + +static const struct attribute_group ds5_attr_group = { + .attrs = ds5_attributes, +}; +#endif + +#define NR_DESER 4 + +static const struct regmap_config ds5_regmap_max9296 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static const struct regmap_config ds5_regmap_max9295 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int ds5_i2c_addr_setting(struct i2c_client *c, struct ds5 *state) +{ + int curr_max9296 = c->addr; + int max9296_addrs[MAX9296_NUM] = {0x48, 0x4a, 0x68, 0x6c}; + int i; + u8 val; + int ret; + struct d4xx_pdata *dpdata = c->dev.platform_data; + unsigned short ser_alias; + unsigned short sensor_alias; + + unsigned short ser_alias_b; + unsigned short sensor_alias_b; + + /* TODO: 2x D457 subdev connect to MAX9296 */ + if (dpdata->subdev_num == 1) { + curr_max9296 = c->addr; + sensor_alias = dpdata->subdev_info[0].board_info.addr; + ser_alias = dpdata->subdev_info[0].ser_alias; + } else if (dpdata->subdev_num == 2) { + curr_max9296 = c->addr; + sensor_alias = dpdata->subdev_info[0].board_info.addr; + ser_alias = dpdata->subdev_info[0].ser_alias; + sensor_alias_b = dpdata->subdev_info[1].board_info.addr; + ser_alias_b = dpdata->subdev_info[1].ser_alias; + } else { + dev_err(&c->dev, "no subdev found!\n"); + return -EINVAL; + } + + dev_dbg(&c->dev, "subdev_num = %u, max9296 = 0x%02x\n", dpdata->subdev_num, curr_max9296); + dev_dbg(&c->dev, "sensor_alias = 0x%02x, ser_alias = 0x%02x\n", sensor_alias, ser_alias); + dev_dbg(&c->dev, "sensor_alias_b = 0x%02x, ser_alias_b = 0x%02x\n", sensor_alias_b, ser_alias_b); + + for (i = 0; i < MAX9296_NUM; i++) { + if (curr_max9296 < max9296_addrs[i]) { + c->addr = max9296_addrs[i]; + max9296_write_8(state, MAX9296_CTRL0, RESET_LINK); + } + } + + for (i = 0; i < MAX9296_NUM; i++) { + if (curr_max9296 == max9296_addrs[i]) { + if (dpdata->subdev_num == 1) { + c->addr = curr_max9296; + max9296_write_8(state, MAX9296_CTRL0, AUTO_LINK | LINK_A); + msleep_range(1000); + + c->addr = MAX9295_I2C_ADDR_DEF; + ret = max9295_read_8(state, MAX9295_REG0, &val); + if (ret < 0) { + dev_err(&c->dev, "no max9295 found for max9296 0x%02x\n", curr_max9296); + continue; + } + + ret = max9295_read_8(state, MAX9295_REG0, &val); + max9295_write_8(state, MAX9295_REG0, ser_alias << 1); + msleep_range(1000); // need this? + c->addr = ser_alias; + max9295_write_8(state, MAX9295_I2C_4, sensor_alias << 1); + max9295_write_8(state, MAX9295_I2C_5, D457_I2C_ADDR << 1); + break; + } + + if (dpdata->subdev_num == 2) { + //link a + c->addr = curr_max9296; + max9296_write_8(state, MAX9296_CTRL0, RESET_ONESHOT | LINK_A); + msleep_range(1000); + + c->addr = MAX9295_I2C_ADDR_DEF; + + max9295_write_8(state, MAX9295_REG0, ser_alias_b << 1); + + c->addr = ser_alias_b; + ret = max9295_read_8(state, MAX9295_REG0, &val); + max9295_write_8(state, MAX9295_I2C_4, sensor_alias_b << 1); + max9295_write_8(state, MAX9295_I2C_5, D457_I2C_ADDR << 1); + + // Packets are transmitted over GMSLA in splitter mode. + max9295_write_8(state, 0x7B, 0x31); + max9295_write_8(state, 0x83, 0x31); + max9295_write_8(state, 0x8B, 0x31); + max9295_write_8(state, 0x93, 0x31); + max9295_write_8(state, 0xA3, 0x31); + max9295_write_8(state, 0xAB, 0x31); + + c->addr = ser_alias_b; + ret = max9295_read_8(state, MAX9295_REG0, &val); + + //link b + c->addr = curr_max9296; + max9296_write_8(state, MAX9296_CTRL0, RESET_ONESHOT | LINK_B); + msleep_range(1000); + + c->addr = MAX9295_I2C_ADDR_DEF; + ret = max9295_read_8(state, MAX9295_REG0, &val); + max9295_write_8(state, MAX9295_REG0, ser_alias << 1); + msleep_range(1000); // need this? + c->addr = ser_alias; + max9295_write_8(state, MAX9295_I2C_4, sensor_alias << 1); + max9295_write_8(state, MAX9295_I2C_5, D457_I2C_ADDR << 1); + + c->addr = max9296_addrs[i]; + + max9296_write_8(state, MAX9296_CTRL0, RESET_ONESHOT | SPLITTER); + msleep_range(1000); + + break; + } + continue; + } + } + + c->addr = sensor_alias; + + return 0; +} + +static int ds5_probe(struct i2c_client *c, const struct i2c_device_id *id) +{ + struct ds5 *state = devm_kzalloc(&c->dev, sizeof(*state), GFP_KERNEL); + u16 rec_state; + int ret, retry, err = 0; + const char *str; + + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + + dev_warn(&c->dev, "Driver addr 0x%x\n", c->addr); + + state->client = c; + dev_warn(&c->dev, "Probing new driver for D45x\n"); + dev_warn(&c->dev, "Driver data NAEL %d\n", (int)id->driver_data); + state->variant = ds5_variants + id->driver_data; + + state->vcc = devm_regulator_get(&c->dev, "vcc"); + if (IS_ERR(state->vcc)) { + ret = PTR_ERR(state->vcc); + dev_warn(&c->dev, "failed %d to get vcc regulator\n", ret); + return ret; + } + + if (state->vcc) { + ret = regulator_enable(state->vcc); + if (ret < 0) { + dev_warn(&c->dev, "failed %d to enable the vcc regulator\n", ret); + return ret; + } + } + state->regmap = devm_regmap_init_i2c(c, &ds5_regmap_config); + if (IS_ERR(state->regmap)) { + ret = PTR_ERR(state->regmap); + dev_err(&c->dev, "regmap init failed: %d\n", ret); + goto e_regulator; + } + + state->regmap_max9296 = devm_regmap_init_i2c(c, &ds5_regmap_max9296); + if (IS_ERR(state->regmap_max9296)) { + ret = PTR_ERR(state->regmap_max9296); + dev_err(&c->dev, "regmap max9296 init failed: %d\n", ret); + return ret; + } + + state->regmap_max9295 = devm_regmap_init_i2c(c, &ds5_regmap_max9295); + if (IS_ERR(state->regmap_max9295)) { + ret = PTR_ERR(state->regmap_max9295); + dev_err(&c->dev, "regmap max9295 init failed: %d\n", ret); + return ret; + } + + ret = ds5_i2c_addr_setting(c, state); + if (ret) { + dev_err(&c->dev, "failed apply i2c addr setting\n"); + return ret; + } + + // Verify communication + retry = 10; + do { + ret = ds5_read(state, 0x5020, &rec_state); + } while (retry-- && ret < 0); + if (ret < 0) { + dev_err(&c->dev, + "%s(): cannot communicate with D4XX: %d on addr: 0x%x\n", + __func__, ret, c->addr); + goto e_regulator; + } + + state->is_depth = 0; + state->is_y8 = 0; + state->is_rgb = 0; + state->is_depth_b = 0; + state->is_rgb_b = 0; + state->is_imu = 0; +#ifdef CONFIG_OF + ret = of_property_read_string(c->dev.of_node, "cam-type", &str); + if (!ret && !strncmp(str, "Depth", strlen("Depth"))) { + state->is_depth = 1; + } + if (!ret && !strncmp(str, "Y8", strlen("Y8"))) { + state->is_y8 = 1; + } + if (!ret && !strncmp(str, "RGB", strlen("RGB"))) { + state->is_rgb = 1; + } + if (!ret && !strncmp(str, "IMU", strlen("IMU"))) { + state->is_imu = 1; + } +#else + state->is_depth = 1; +#endif + /* create DFU chardev once */ + if (state->is_depth) { + ret = ds5_chrdev_init(c, state); + if (ret < 0) + goto e_regulator; + } + + ret = ds5_read(state, 0x5020, &rec_state); + if (ret < 0) { + dev_err(&c->dev, "%s(): cannot communicate with D4XX: %d\n", + __func__, ret); + goto e_chardev; + } + + if (rec_state == 0x201) { + dev_info(&c->dev, "%s(): D4XX recovery state\n", __func__); + state->dfu_dev.dfu_state_flag = DS5_DFU_RECOVERY; + return 0; + } + + ds5_read_with_check(state, DS5_FW_VERSION, &state->fw_version); + ds5_read_with_check(state, DS5_FW_BUILD, &state->fw_build); + + dev_info(&c->dev, "D4XX Sensor: %s, firmware build: %d.%d.%d.%d\n", + ds5_get_sensor_name(state), + (state->fw_version >> 8) & 0xff, state->fw_version & 0xff, + (state->fw_build >> 8) & 0xff, state->fw_build & 0xff); + + ret = ds5_v4l_init(c, state); + if (ret < 0) + goto e_chardev; + /* Override I2C drvdata */ + /* i2c_set_clientdata(c, state); */ + +/* regulators? clocks? + * devm_regulator_bulk_get(&c->dev, DS5_N_SUPPLIES, state->supplies); + * state->clock = devm_clk_get(&c->dev, DS5_CLK_NAME); + * if (IS_ERR(state->clock)) { + * ret = -EPROBE_DEFER; + * goto err; + * } + */ +#ifdef CONFIG_SYSFS + /* Custom sysfs attributes */ + /* create the sysfs file group */ + err = sysfs_create_group(&state->client->dev.kobj, &ds5_attr_group); +#endif + ds5_substream_init(); + return 0; + +e_chardev: + if (state->dfu_dev.ds5_class) + ds5_chrdev_remove(state); +e_regulator: + if (state->vcc) + regulator_disable(state->vcc); + return ret; +} +static void ds5_remove(struct i2c_client *c) +{ + struct ds5 *state = container_of(i2c_get_clientdata(c), struct ds5, mux.sd.subdev); + + dev_info(&c->dev, "D4XX remove %s\n", + ds5_get_sensor_name(state)); + if (state->vcc) + regulator_disable(state->vcc); +// gpio_free(state->pwdn_gpio); + if (state->is_depth) + ds5_chrdev_remove(state); + + if (state->dfu_dev.dfu_state_flag != DS5_DFU_RECOVERY) { +#ifdef CONFIG_SYSFS + sysfs_remove_group(&c->dev.kobj, &ds5_attr_group); +#endif + ds5_mux_remove(state); + } + +} + +static const struct i2c_device_id ds5_id[] = { + { DS5_DRIVER_NAME, DS5_DS5U }, + { DS5_DRIVER_NAME_ASR, DS5_ASR }, + { DS5_DRIVER_NAME_AWG, DS5_AWG }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ds5_id); + +static struct i2c_driver ds5_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DS5_DRIVER_NAME + }, + .probe = ds5_probe, + .remove = ds5_remove, + .id_table = ds5_id, +}; + +module_i2c_driver(ds5_i2c_driver); + +MODULE_DESCRIPTION("Intel RealSense D4XX Camera Driver"); +MODULE_AUTHOR("Guennadi Liakhovetski ,\n\ + Nael Masalha ,\n\ + Alexander Gantman ,\n\ + Emil Jahshan ,\n\ + Xin Zhang ,\n\ + Qingwu Zhang ,\n\ + Evgeni Raikhel ,\n\ + Shikun Ding "); +MODULE_AUTHOR("Dmitry Perchanov "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0.2.13"); diff --git a/drivers/media/i2c/imx390-mode-1280x960-CROP.h b/drivers/media/i2c/imx390-mode-1280x960-CROP.h new file mode 100644 index 000000000000..5488c832c8ff --- /dev/null +++ b/drivers/media/i2c/imx390-mode-1280x960-CROP.h @@ -0,0 +1,3089 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + * @author George Vigelette + * + * imx390 v4l2 driver for tgl + */ + +#ifndef __IMX390_MODE_1280X960_CROP_H +#define __IMX390_MODE_1280X960_CROP_H + +static const struct imx390_reg imx390_mode_1280x960CROP[] = { + {0x2E18, 0x00}, /* disable rear embedded data line */ + {0x000C, 0xF2}, + {0x000E, 0x00}, + {0x0010, 0xF2}, + {0x0011, 0x02}, + {0x0012, 0x00}, + {0x0018, 0x15}, + {0x0019, 0x00}, + {0x001A, 0x0C}, + {0x001B, 0x00}, + {0x0038, 0x00}, + {0x003C, 0x00}, + {0x003D, 0x00}, + {0x003E, 0x00}, + {0x0040, 0x00}, + {0x0041, 0x00}, + {0x0042, 0x00}, + {0x0044, 0x00}, + {0x0045, 0x00}, + {0x0046, 0x00}, + {0x0048, 0x00}, + {0x0049, 0x00}, + {0x004A, 0x00}, + {0x004C, 0x00}, + {0x004D, 0x00}, + {0x004E, 0x00}, + {0x0050, 0x00}, + {0x0051, 0x00}, + {0x0052, 0x00}, + {0x0054, 0x00}, + {0x0055, 0x00}, + {0x0056, 0x00}, + {0x0058, 0x00}, + {0x0059, 0x00}, + {0x005A, 0x00}, + {0x005C, 0x00}, + {0x005D, 0x00}, + {0x005E, 0x00}, + {0x0060, 0x00}, + {0x0061, 0x00}, + {0x0062, 0x00}, + {0x0064, 0x00}, + {0x0065, 0x00}, + {0x0066, 0x00}, + {0x0068, 0x00}, + {0x0069, 0x00}, + {0x006A, 0x00}, + {0x0074, 0x00}, /* V_REVERSE and H_REVERSE */ + {0x0078, 0x01}, /* CROP_ON 0x00 */ + {0x007C, 0x08}, /* CROP_H_OFFSET 0x00 */ + {0x007D, 0x00}, + {0x0080, 0x08}, /* CROP_V_OFFSET 0x00 */ + {0x0081, 0x00}, + {0x00F4, 0x1C}, + {0x00F5, 0xF8}, + {0x00F6, 0x01}, + {0x00F8, 0x03}, + {0x00F9, 0x00}, + {0x00FA, 0x00}, + {0x00FB, 0x00}, + {0x0114, 0x00}, + {0x0115, 0x01}, + {0x0118, 0x20}, + {0x0119, 0x03}, + {0x011A, 0x00}, + {0x011B, 0x41}, + {0x011C, 0x80}, + {0x011D, 0x00}, + {0x0120, 0x20}, + {0x0121, 0x00}, + {0x0122, 0x00}, + {0x0123, 0x44}, + {0x0124, 0x00}, + {0x0125, 0x01}, + {0x0128, 0xAC}, + {0x0129, 0x0D}, + {0x012A, 0x00}, + {0x012B, 0xA4}, + {0x012C, 0x00}, + {0x012D, 0x01}, + {0x0130, 0xC4}, + {0x0131, 0x09}, + {0x0132, 0x00}, + {0x0133, 0xDA}, + {0x013B, 0x01}, + {0x01C4, 0x00}, + {0x01C5, 0x00}, + {0x01CC, 0x01}, + {0x01D0, 0x09}, + {0x01D4, 0x01}, + {0x0332, 0x18}, + {0x0333, 0x00}, + {0x0390, 0x00}, + {0x0391, 0x00}, + {0x0392, 0x00}, + {0x03C0, 0x01}, /* SM_CROP_ON_APL 0x00 */ + {0x2000, 0x55}, + {0x2001, 0x55}, + {0x2002, 0x55}, + {0x2003, 0x05}, + {0x2004, 0x02}, + {0x2008, 0x65}, + {0x2009, 0x04}, + {0x200A, 0x00}, + {0x200C, 0x30}, + {0x200D, 0x11}, + {0x2010, 0x04}, + {0x2014, 0x01}, + {0x2018, 0x02}, + {0x2019, 0x04}, + {0x201A, 0x00}, + {0x201C, 0x21}, + {0x201D, 0x11}, + {0x201E, 0x00}, + {0x201F, 0x00}, + {0x2020, 0xBC}, + {0x2021, 0x00}, + {0x2022, 0x7F}, + {0x2023, 0x00}, + {0x2024, 0xBA}, + {0x2025, 0x00}, + {0x2026, 0x81}, + {0x2027, 0x00}, + {0x2028, 0x7D}, + {0x2029, 0x90}, + {0x202A, 0x05}, + {0x202C, 0xFC}, + {0x202D, 0x02}, + {0x202E, 0x25}, + {0x202F, 0x03}, + {0x2030, 0x05}, + {0x2031, 0x02}, + {0x2032, 0xCA}, + {0x2033, 0x02}, + {0x2034, 0xFC}, + {0x2035, 0x02}, + {0x2036, 0x25}, + {0x2037, 0x03}, + {0x2038, 0x25}, + {0x2039, 0x97}, + {0x203A, 0xEC}, + {0x203B, 0x01}, + {0x203C, 0xF5}, + {0x203D, 0x8E}, + {0x203E, 0x0C}, + {0x203F, 0x2D}, + {0x2040, 0x69}, + {0x2041, 0x01}, + {0x2042, 0x8E}, + {0x2043, 0x01}, + {0x2044, 0x0C}, + {0x2045, 0x02}, + {0x2046, 0x31}, + {0x2047, 0x02}, + {0x2048, 0x6A}, + {0x2049, 0x01}, + {0x204A, 0x8E}, + {0x204B, 0x01}, + {0x204C, 0x0D}, + {0x204D, 0x02}, + {0x204E, 0x31}, + {0x204F, 0x02}, + {0x2050, 0x7B}, + {0x2051, 0x00}, + {0x2052, 0x7D}, + {0x2053, 0x00}, + {0x2054, 0x95}, + {0x2055, 0x00}, + {0x2056, 0x97}, + {0x2057, 0x00}, + {0x2058, 0xAD}, + {0x2059, 0x00}, + {0x205A, 0xAF}, + {0x205B, 0x00}, + {0x205C, 0x92}, + {0x205D, 0x00}, + {0x205E, 0x94}, + {0x205F, 0x00}, + {0x2060, 0x8E}, + {0x2061, 0x00}, + {0x2062, 0x90}, + {0x2063, 0x00}, + {0x2064, 0xB1}, + {0x2065, 0x00}, + {0x2066, 0xB3}, + {0x2067, 0x00}, + {0x2068, 0x08}, + {0x2069, 0x00}, + {0x206A, 0x04}, + {0x206B, 0x00}, + {0x206C, 0x84}, + {0x206D, 0x00}, + {0x206E, 0x80}, + {0x206F, 0x00}, + {0x2070, 0x04}, + {0x2071, 0x00}, + {0x2072, 0x46}, + {0x2073, 0x00}, + {0x2074, 0xE9}, + {0x2075, 0x01}, + {0x2076, 0x74}, + {0x2077, 0x02}, + {0x2078, 0x80}, + {0x2079, 0x00}, + {0x207A, 0xC1}, + {0x207B, 0x00}, + {0x207C, 0xFF}, + {0x207D, 0x03}, + {0x207E, 0xFF}, + {0x207F, 0x03}, + {0x2080, 0x78}, + {0x2081, 0x00}, + {0x2082, 0x6A}, + {0x2083, 0x01}, + {0x2084, 0xE4}, + {0x2085, 0x01}, + {0x2086, 0x2B}, + {0x2087, 0x03}, + {0x2088, 0x00}, + {0x2089, 0x00}, + {0x208A, 0xFF}, + {0x208B, 0x03}, + {0x208C, 0xFF}, + {0x208D, 0x03}, + {0x208E, 0xFF}, + {0x208F, 0x03}, + {0x2090, 0x7D}, + {0x2091, 0x00}, + {0x2092, 0x62}, + {0x2093, 0x01}, + {0x2094, 0xE9}, + {0x2095, 0x01}, + {0x2096, 0x00}, + {0x2097, 0x00}, + {0x2098, 0x7C}, + {0x2099, 0x00}, + {0x209A, 0x21}, + {0x209B, 0x03}, + {0x209C, 0xE9}, + {0x209D, 0x01}, + {0x209E, 0x21}, + {0x209F, 0x03}, + {0x20A0, 0xFF}, + {0x20A1, 0x03}, + {0x20A2, 0xFF}, + {0x20A3, 0x03}, + {0x20A4, 0xFF}, + {0x20A5, 0x03}, + {0x20A6, 0xFF}, + {0x20A7, 0x03}, + {0x20A8, 0xFF}, + {0x20A9, 0x03}, + {0x20AA, 0xFF}, + {0x20AB, 0x03}, + {0x20AC, 0xFF}, + {0x20AD, 0x03}, + {0x20AE, 0xFF}, + {0x20AF, 0x03}, + {0x20B0, 0xFF}, + {0x20B1, 0x03}, + {0x20B2, 0xFF}, + {0x20B3, 0x03}, + {0x20B4, 0x87}, + {0x20B5, 0xCC}, + {0x20B6, 0x87}, + {0x20B7, 0x08}, + {0x20B8, 0xF4}, + {0x20B9, 0xA5}, + {0x20BA, 0x07}, + {0x20BC, 0x1F}, + {0x20BD, 0x01}, + {0x20BE, 0xF6}, + {0x20BF, 0x00}, + {0x20C0, 0x90}, + {0x20C1, 0x01}, + {0x20C2, 0x67}, + {0x20C3, 0x01}, + {0x20C4, 0xFF}, + {0x20C5, 0x03}, + {0x20C6, 0xFF}, + {0x20C7, 0x03}, + {0x20C8, 0x33}, + {0x20C9, 0x02}, + {0x20CA, 0x0A}, + {0x20CB, 0x02}, + {0x20CC, 0x7F}, + {0x20CD, 0x00}, + {0x20CE, 0xD2}, + {0x20CF, 0x00}, + {0x20D0, 0x81}, + {0x20D1, 0x00}, + {0x20D2, 0x87}, + {0x20D3, 0x00}, + {0x20D4, 0x09}, + {0x20D5, 0x00}, + {0x20D8, 0x7F}, + {0x20D9, 0x00}, + {0x20DA, 0x62}, + {0x20DB, 0x01}, + {0x20DC, 0x7F}, + {0x20DD, 0x00}, + {0x20DE, 0x62}, + {0x20DF, 0x01}, + {0x20E0, 0x65}, + {0x20E1, 0x00}, + {0x20E2, 0x75}, + {0x20E3, 0x00}, + {0x20E4, 0xE0}, + {0x20E5, 0x00}, + {0x20E6, 0xF0}, + {0x20E7, 0x00}, + {0x20E8, 0x4C}, + {0x20E9, 0x01}, + {0x20EA, 0x5C}, + {0x20EB, 0x01}, + {0x20EC, 0xD1}, + {0x20ED, 0x01}, + {0x20EE, 0xE1}, + {0x20EF, 0x01}, + {0x20F0, 0x93}, + {0x20F1, 0x02}, + {0x20F2, 0xA3}, + {0x20F3, 0x02}, + {0x20F4, 0x0D}, + {0x20F5, 0x03}, + {0x20F6, 0x1D}, + {0x20F7, 0x03}, + {0x20F8, 0x57}, + {0x20F9, 0x00}, + {0x20FA, 0x7B}, + {0x20FB, 0x00}, + {0x20FC, 0xD2}, + {0x20FD, 0x00}, + {0x20FE, 0xF6}, + {0x20FF, 0x00}, + {0x2100, 0x3E}, + {0x2101, 0x01}, + {0x2102, 0x60}, + {0x2103, 0x01}, + {0x2104, 0xC3}, + {0x2105, 0x01}, + {0x2106, 0xE5}, + {0x2107, 0x01}, + {0x2108, 0x85}, + {0x2109, 0x02}, + {0x210A, 0xA9}, + {0x210B, 0x02}, + {0x210C, 0xFF}, + {0x210D, 0x02}, + {0x210E, 0x21}, + {0x210F, 0x03}, + {0x2110, 0xFF}, + {0x2111, 0x03}, + {0x2112, 0x00}, + {0x2113, 0x00}, + {0x2114, 0xFF}, + {0x2115, 0x03}, + {0x2116, 0xFF}, + {0x2117, 0x03}, + {0x2118, 0xFF}, + {0x2119, 0x03}, + {0x211A, 0xFF}, + {0x211B, 0x03}, + {0x211C, 0xFF}, + {0x211D, 0x03}, + {0x211E, 0xFF}, + {0x211F, 0x03}, + {0x2120, 0xFF}, + {0x2121, 0x03}, + {0x2122, 0xFF}, + {0x2123, 0x03}, + {0x2124, 0xFF}, + {0x2125, 0x03}, + {0x2126, 0xFF}, + {0x2127, 0x03}, + {0x2128, 0x7D}, + {0x2129, 0x90}, + {0x212A, 0xD5}, + {0x212B, 0x07}, + {0x212C, 0x64}, + {0x212D, 0x01}, + {0x2130, 0x5F}, + {0x2131, 0x7D}, + {0x2132, 0x05}, + {0x2134, 0x78}, + {0x2135, 0x00}, + {0x2136, 0x76}, + {0x2137, 0x00}, + {0x2138, 0xF3}, + {0x2139, 0x00}, + {0x213A, 0xF1}, + {0x213B, 0x00}, + {0x213C, 0xA6}, + {0x213D, 0x02}, + {0x213E, 0xA4}, + {0x213F, 0x02}, + {0x2140, 0x7D}, + {0x2141, 0x00}, + {0x2142, 0x8D}, + {0x2143, 0x00}, + {0x2144, 0xA1}, + {0x2145, 0x01}, + {0x2146, 0xB1}, + {0x2147, 0x01}, + {0x2148, 0xAB}, + {0x2149, 0x02}, + {0x214A, 0xBB}, + {0x214B, 0x02}, + {0x214C, 0x17}, + {0x214D, 0x5C}, + {0x214E, 0x00}, + {0x2150, 0x00}, + {0x2151, 0x00}, + {0x2152, 0xF8}, + {0x2153, 0x00}, + {0x2154, 0xBE}, + {0x2155, 0x00}, + {0x2156, 0x7D}, + {0x2157, 0x00}, + {0x2158, 0x25}, + {0x2159, 0x00}, + {0x215A, 0x7D}, + {0x215B, 0x00}, + {0x215C, 0x62}, + {0x215D, 0x01}, + {0x215E, 0xFF}, + {0x215F, 0x03}, + {0x2160, 0x26}, + {0x2161, 0x00}, + {0x2162, 0x7D}, + {0x2163, 0x00}, + {0x2164, 0x63}, + {0x2165, 0x01}, + {0x2166, 0xFF}, + {0x2167, 0x03}, + {0x2168, 0xCB}, + {0x2169, 0x02}, + {0x216A, 0xCF}, + {0x216B, 0x02}, + {0x216C, 0xFF}, + {0x216D, 0x03}, + {0x216E, 0xFF}, + {0x216F, 0x03}, + {0x2170, 0xFF}, + {0x2171, 0x03}, + {0x2172, 0xFF}, + {0x2173, 0x03}, + {0x2174, 0xFF}, + {0x2175, 0x03}, + {0x2176, 0xFF}, + {0x2177, 0x03}, + {0x2178, 0x7E}, + {0x2179, 0x00}, + {0x217A, 0xBD}, + {0x217B, 0x00}, + {0x217C, 0xEC}, + {0x217D, 0x01}, + {0x217E, 0x7B}, + {0x217F, 0x02}, + {0x2180, 0xD1}, + {0x2181, 0x02}, + {0x2182, 0x25}, + {0x2183, 0x03}, + {0x2184, 0x7F}, + {0x2185, 0x00}, + {0x2186, 0xBD}, + {0x2187, 0x00}, + {0x2188, 0xED}, + {0x2189, 0x01}, + {0x218A, 0x7B}, + {0x218B, 0x02}, + {0x218C, 0xD2}, + {0x218D, 0x02}, + {0x218E, 0x25}, + {0x218F, 0x03}, + {0x2190, 0xFF}, + {0x2191, 0x03}, + {0x2192, 0xFF}, + {0x2193, 0x03}, + {0x2194, 0xE9}, + {0x2195, 0x01}, + {0x2196, 0x21}, + {0x2197, 0x03}, + {0x2198, 0x17}, + {0x2199, 0xFC}, + {0x219A, 0x7F}, + {0x219B, 0x01}, + {0x219C, 0xFF}, + {0x219D, 0x03}, + {0x21A0, 0x1B}, + {0x21A1, 0x1B}, + {0x21A2, 0x1B}, + {0x21A3, 0x1B}, + {0x21A4, 0x2E}, + {0x21A5, 0x80}, + {0x21A6, 0x00}, + {0x21A8, 0x04}, + {0x21A9, 0x98}, + {0x21AA, 0x60}, + {0x21AB, 0x03}, + {0x21AC, 0x7F}, + {0x21AD, 0x80}, + {0x21AE, 0x09}, + {0x21B0, 0x1C}, + {0x21B1, 0x00}, + {0x21B2, 0xA0}, + {0x21B3, 0x00}, + {0x21B4, 0x0C}, + {0x21B5, 0x00}, + {0x21B6, 0x2D}, + {0x21B7, 0x00}, + {0x21B8, 0x20}, + {0x21B9, 0x00}, + {0x21BA, 0x02}, + {0x21BB, 0x00}, + {0x21BC, 0xCC}, + {0x21BD, 0x00}, + {0x21BE, 0x4A}, + {0x21BF, 0x00}, + {0x21C0, 0xD0}, + {0x21C1, 0x00}, + {0x21C2, 0x44}, + {0x21C3, 0x00}, + {0x21C4, 0x00}, + {0x21C5, 0xE0}, + {0x21C6, 0x00}, + {0x21C8, 0x11}, + {0x21C9, 0x00}, + {0x21CA, 0x02}, + {0x21CC, 0x08}, + {0x21CD, 0xC0}, + {0x21CE, 0x0C}, + {0x21D0, 0x44}, + {0x21D1, 0x00}, + {0x21D2, 0x02}, + {0x21D4, 0x02}, + {0x21D5, 0x20}, + {0x21D6, 0x2C}, + {0x21D8, 0xFE}, + {0x21D9, 0x9D}, + {0x21DA, 0xDF}, + {0x21DB, 0x03}, + {0x21DC, 0x62}, + {0x21DD, 0x01}, + {0x21DE, 0x7F}, + {0x21DF, 0x00}, + {0x21E0, 0xB7}, + {0x21E1, 0x01}, + {0x21E2, 0xB5}, + {0x21E3, 0x01}, + {0x21E4, 0xC1}, + {0x21E5, 0x02}, + {0x21E6, 0xBF}, + {0x21E7, 0x02}, + {0x21E8, 0xB3}, + {0x21E9, 0x0D}, + {0x21EA, 0x00}, + {0x21EB, 0x04}, + {0x21EC, 0x90}, + {0x21ED, 0x07}, + {0x21EE, 0x58}, + {0x21EF, 0x04}, + {0x21F0, 0x54}, + {0x21F1, 0x04}, + {0x21F4, 0x02}, + {0x21F5, 0x00}, + {0x21F6, 0x00}, + {0x21F8, 0x3C}, + {0x21F9, 0x00}, + {0x21FC, 0x28}, + {0x21FD, 0x00}, + {0x21FE, 0x3C}, + {0x21FF, 0x00}, + {0x2200, 0x00}, + {0x2204, 0x4C}, + {0x2205, 0x04}, + {0x2206, 0x65}, + {0x2207, 0x04}, + {0x2208, 0x0A}, + {0x2209, 0x00}, + {0x220C, 0x47}, + {0x220D, 0x00}, + {0x220E, 0x1F}, + {0x220F, 0x00}, + {0x2210, 0x17}, + {0x2211, 0x00}, + {0x2212, 0x0F}, + {0x2213, 0x00}, + {0x2214, 0x17}, + {0x2215, 0x00}, + {0x2216, 0x47}, + {0x2217, 0x00}, + {0x2218, 0x0F}, + {0x2219, 0x00}, + {0x221A, 0x0F}, + {0x221B, 0x00}, + {0x221C, 0x03}, + {0x2220, 0x20}, + {0x2221, 0x20}, + {0x2222, 0x22}, + {0x2223, 0x02}, + {0x2224, 0xA7}, + {0x2225, 0xAA}, + {0x2226, 0x80}, + {0x2227, 0x08}, + {0x2228, 0x01}, + {0x22B2, 0x92}, + {0x22B4, 0x20}, + {0x22B5, 0x00}, + {0x22B6, 0x20}, + {0x22B7, 0x00}, + {0x22B8, 0x20}, + {0x22B9, 0x00}, + {0x22BA, 0x20}, + {0x22BB, 0x00}, + {0x22BC, 0x20}, + {0x22BD, 0x00}, + {0x22BE, 0x20}, + {0x22BF, 0x00}, + {0x22C0, 0x20}, + {0x22C1, 0x00}, + {0x22C2, 0x20}, + {0x22C3, 0x00}, + {0x22C4, 0x20}, + {0x22C5, 0x00}, + {0x22C6, 0x20}, + {0x22C7, 0x00}, + {0x22C8, 0x20}, + {0x22C9, 0x00}, + {0x22CA, 0x20}, + {0x22CB, 0x00}, + {0x22CC, 0x20}, + {0x22CD, 0x00}, + {0x22CE, 0x20}, + {0x22CF, 0x00}, + {0x22DA, 0x00}, + {0x2308, 0x01}, + {0x2311, 0x09}, + {0x2318, 0x40}, + {0x2319, 0xCD}, + {0x231A, 0x54}, + {0x2324, 0x10}, + {0x2325, 0x00}, + {0x2328, 0x00}, + {0x2354, 0x0C}, + {0x23C0, 0x5D}, + {0x244C, 0x00}, + {0x244D, 0x02}, + {0x244E, 0x54}, + {0x244F, 0x02}, + {0x24A0, 0x00}, + {0x24DA, 0x6F}, + {0x24DB, 0x00}, + {0x24DC, 0x62}, + {0x24DD, 0x01}, + {0x24EA, 0x32}, + {0x24EB, 0x00}, + {0x24EC, 0xDC}, + {0x24ED, 0x00}, + {0x24FA, 0x32}, + {0x24FB, 0x00}, + {0x24FC, 0xDD}, + {0x24FD, 0x00}, + {0x254A, 0x15}, + {0x254B, 0x01}, + {0x255A, 0x15}, + {0x255B, 0x01}, + {0x2560, 0x01}, + {0x2561, 0x00}, + {0x2562, 0x2A}, + {0x2563, 0x00}, + {0x2564, 0xF8}, + {0x2565, 0x00}, + {0x2566, 0x15}, + {0x2567, 0x01}, + {0x2568, 0x0C}, + {0x2569, 0x02}, + {0x256A, 0x31}, + {0x256B, 0x02}, + {0x2578, 0x90}, + {0x2579, 0x01}, + {0x257A, 0x92}, + {0x257B, 0x01}, + {0x257C, 0xB8}, + {0x257D, 0x02}, + {0x257E, 0xBA}, + {0x257F, 0x02}, + {0x2584, 0x90}, + {0x2585, 0x01}, + {0x2586, 0x92}, + {0x2587, 0x01}, + {0x2588, 0xB8}, + {0x2589, 0x02}, + {0x258A, 0xBA}, + {0x258B, 0x02}, + {0x26B8, 0x10}, + {0x26B9, 0x00}, + {0x26BA, 0x33}, + {0x26BB, 0x00}, + {0x26BC, 0x89}, + {0x26BD, 0x00}, + {0x26BE, 0xB0}, + {0x26BF, 0x00}, + {0x26C4, 0x4E}, + {0x26C5, 0x00}, + {0x26C8, 0xC9}, + {0x26C9, 0x00}, + {0x26CC, 0x35}, + {0x26CD, 0x01}, + {0x26D0, 0xBA}, + {0x26D1, 0x01}, + {0x26D4, 0x7C}, + {0x26D5, 0x02}, + {0x26D8, 0xF6}, + {0x26D9, 0x02}, + {0x26DE, 0x51}, + {0x26DF, 0x00}, + {0x26E0, 0x7F}, + {0x26E1, 0x00}, + {0x26E2, 0xCC}, + {0x26E3, 0x00}, + {0x26E4, 0xF8}, + {0x26E5, 0x00}, + {0x26E6, 0x38}, + {0x26E7, 0x01}, + {0x26E8, 0x65}, + {0x26E9, 0x01}, + {0x26EA, 0xBD}, + {0x26EB, 0x01}, + {0x26EE, 0x7F}, + {0x26EF, 0x02}, + {0x26F0, 0xAB}, + {0x26F1, 0x02}, + {0x26F2, 0xF9}, + {0x26F3, 0x02}, + {0x2722, 0x59}, + {0x2723, 0x02}, + {0x2938, 0x55}, + {0x2939, 0x00}, + {0x293A, 0x17}, + {0x293B, 0x00}, + {0x293C, 0xD0}, + {0x293D, 0x00}, + {0x293E, 0x91}, + {0x293F, 0x00}, + {0x2940, 0x3C}, + {0x2941, 0x01}, + {0x2942, 0x0C}, + {0x2943, 0x01}, + {0x2944, 0xC1}, + {0x2945, 0x01}, + {0x2946, 0x76}, + {0x2947, 0x01}, + {0x2948, 0x83}, + {0x2949, 0x02}, + {0x294A, 0xFB}, + {0x294B, 0x01}, + {0x294C, 0xFD}, + {0x294D, 0x02}, + {0x294E, 0xBF}, + {0x294F, 0x02}, + {0x2A06, 0xFF}, + {0x2A07, 0x03}, + {0x2A20, 0x00}, + {0x2A21, 0x00}, + {0x2A22, 0x7D}, + {0x2A23, 0x00}, + {0x2B11, 0x19}, + {0x2B13, 0x15}, + {0x2B14, 0x14}, + {0x2B15, 0x13}, + {0x2B16, 0x12}, + {0x2B17, 0x11}, + {0x2B18, 0x10}, + {0x2B19, 0x0F}, + {0x2B1A, 0x0E}, + {0x2B1B, 0x0D}, + {0x2B1C, 0x0C}, + {0x2B1D, 0x0B}, + {0x2B1E, 0x0A}, + {0x2B1F, 0x09}, + {0x2B20, 0x08}, + {0x2B21, 0x07}, + {0x2B22, 0x06}, + {0x2B23, 0x05}, + {0x2B24, 0x04}, + {0x2B25, 0x03}, + {0x2B26, 0x03}, + {0x2B38, 0x01}, + {0x2B45, 0xE3}, + {0x2B50, 0x01}, + {0x2B51, 0x00}, + {0x2B6D, 0x47}, + {0x2B70, 0x02}, + {0x2B71, 0x02}, + {0x2B72, 0x02}, + {0x2B7F, 0x7F}, + {0x2B80, 0x94}, + {0x2B81, 0x06}, + {0x2B87, 0x1B}, + {0x2B88, 0x1B}, + {0x2B89, 0x17}, + {0x2B8A, 0x12}, + {0x2B8B, 0x12}, + {0x2B8D, 0x2B}, + {0x2B8E, 0x2B}, + {0x2B8F, 0x2B}, + {0x2B90, 0x7F}, + {0x2B91, 0x1F}, + {0x2B94, 0x7F}, + {0x2B95, 0x27}, + {0x2B98, 0x7F}, + {0x2B99, 0x57}, + {0x2BA8, 0xBC}, + {0x2BA9, 0x62}, + {0x2BC1, 0x70}, + {0x2BC5, 0x80}, + {0x2BD5, 0x30}, + {0x2BD6, 0xF0}, + {0x2BD8, 0xDB}, + {0x2BD9, 0xF6}, + {0x2BDA, 0x63}, + {0x2BDB, 0x0C}, + {0x2BDC, 0x5C}, + {0x2C98, 0xE1}, + {0x2C99, 0x2E}, + {0x2C9B, 0x86}, + {0x2CA9, 0x80}, + {0x2CAA, 0x01}, + {0x2D39, 0x0E}, + {0x2D54, 0x00}, + {0x2D5B, 0x58}, + {0x2D64, 0x02}, /* GRBG */ + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x23}, + {0x3003, 0xA1}, + {0x3004, 0x00}, + {0x3005, 0x20}, + {0x3006, 0x84}, + {0x3007, 0x00}, + {0x3008, 0x06}, + {0x3009, 0xB4}, + {0x300A, 0x1F}, + {0x300B, 0x00}, + {0x300C, 0x00}, + {0x300D, 0x1B}, + {0x300E, 0x90}, + {0x300F, 0x97}, + {0x3010, 0x00}, + {0x3011, 0x00}, + {0x3012, 0x21}, + {0x3013, 0x21}, + {0x3014, 0x00}, + {0x3015, 0x20}, + {0x3016, 0x84}, + {0x3017, 0x00}, + {0x3018, 0x30}, + {0x3019, 0x09}, + {0x301A, 0x46}, + {0x301B, 0x00}, + {0x3070, 0xC1}, + {0x3071, 0x81}, + {0x3072, 0x29}, + {0x3073, 0x81}, + {0x3410, 0x00}, /* crop_h_size */ + {0x3411, 0x05}, + {0x3418, 0xBA}, /* crop_v_size */ + {0x3419, 0x03}, + {0x34C0, 0xD3}, + {0x34C1, 0x00}, + {0x34C2, 0xD3}, + {0x34C3, 0x00}, + {0x34C4, 0xD3}, + {0x34C5, 0x00}, + {0x34C6, 0xD3}, + {0x34C7, 0x00}, + {0x34C8, 0xE2}, + {0x34C9, 0x21}, + {0x34CA, 0xE0}, + {0x34CB, 0x1F}, + {0x34CC, 0x06}, + {0x34CD, 0x20}, + {0x34CE, 0x28}, + {0x34CF, 0x1F}, + {0x3584, 0x00}, + {0x3586, 0x00}, + {0x3587, 0x01}, + {0x3588, 0xE6}, + {0x3589, 0x00}, + {0x3590, 0x00}, + {0x3591, 0x00}, + {0x3594, 0x40}, + {0x3598, 0x03}, + {0x3599, 0x00}, + {0x359A, 0x80}, + {0x359B, 0x00}, + {0x359C, 0x00}, + {0x359D, 0x01}, + {0x359E, 0x00}, + {0x359F, 0x02}, + {0x35A0, 0x00}, + {0x35A1, 0x04}, + {0x35A2, 0x20}, + {0x35A3, 0x00}, + {0x35A4, 0x40}, + {0x35A5, 0x00}, + {0x35A6, 0x80}, + {0x35A7, 0x00}, + {0x35A8, 0x00}, + {0x35A9, 0x01}, + {0x35AA, 0x3A}, + {0x35AB, 0x00}, + {0x35AC, 0x80}, + {0x35AD, 0x00}, + {0x35AE, 0x00}, + {0x35AF, 0x01}, + {0x35B0, 0x00}, + {0x35B1, 0x02}, + {0x35B2, 0x00}, + {0x35B3, 0x04}, + {0x35B4, 0x02}, + {0x35B5, 0x00}, + {0x35B6, 0x04}, + {0x35B7, 0x00}, + {0x35B8, 0x08}, + {0x35B9, 0x00}, + {0x35BA, 0x10}, + {0x35BB, 0x00}, + {0x35BC, 0x03}, + {0x35BD, 0x00}, + {0x35C8, 0x00}, + {0x35C9, 0x01}, + {0x35CA, 0x00}, + {0x35CB, 0x04}, + {0x35CC, 0x00}, + {0x35CD, 0x10}, + {0x35CE, 0x00}, + {0x35CF, 0x40}, + {0x35D0, 0x00}, + {0x35D1, 0x0C}, + {0x35D2, 0x00}, + {0x35D3, 0x0C}, + {0x35D4, 0x00}, + {0x35D5, 0x0C}, + {0x35D6, 0x00}, + {0x35D7, 0x0C}, + {0x35D8, 0x00}, + {0x35D9, 0x00}, + {0x35DA, 0x08}, + {0x35DB, 0x00}, + {0x35DC, 0xD8}, + {0x35DD, 0x0E}, + {0x35F0, 0x00}, + {0x35F1, 0x10}, + {0x35F2, 0x00}, + {0x35F3, 0x10}, + {0x35F4, 0x00}, + {0x35F5, 0x10}, + {0x35F6, 0x00}, + {0x35F7, 0x03}, + {0x35F8, 0x00}, + {0x35F9, 0x01}, + {0x35FA, 0x38}, + {0x35FB, 0x00}, + {0x35FC, 0xB3}, + {0x35FD, 0x01}, + {0x35FE, 0x00}, + {0x35FF, 0x00}, + {0x3600, 0x04}, + {0x3601, 0x06}, + {0x3604, 0x03}, + {0x3605, 0x00}, + {0x3608, 0x03}, + {0x3609, 0x00}, + {0x360C, 0x00}, + {0x360D, 0x00}, + {0x3610, 0x10}, + {0x3611, 0x01}, + {0x3612, 0x00}, + {0x3613, 0x00}, + {0x3614, 0x00}, + {0x3615, 0x00}, + {0x361C, 0x00}, + {0x361D, 0x01}, + {0x361E, 0x00}, + {0x361F, 0x01}, + {0x3620, 0x01}, + {0x3621, 0x00}, + {0x3622, 0xB0}, + {0x3623, 0x04}, + {0x3624, 0xDC}, + {0x3625, 0x05}, + {0x3626, 0x00}, + {0x3627, 0x01}, + {0x3628, 0xFF}, + {0x3629, 0x0F}, + {0x362A, 0x00}, + {0x362B, 0x10}, + {0x362C, 0x00}, + {0x362D, 0x01}, + {0x3630, 0x40}, + {0x3631, 0x00}, + {0x3632, 0x40}, + {0x3633, 0x00}, + {0x3634, 0x40}, + {0x3635, 0x00}, + {0x3636, 0x40}, + {0x3637, 0x00}, + {0x3638, 0x40}, + {0x3639, 0x00}, + {0x363A, 0x40}, + {0x363B, 0x00}, + {0x363C, 0x40}, + {0x363D, 0x00}, + {0x363E, 0x40}, + {0x363F, 0x00}, + {0x36C4, 0x99}, + {0x36C5, 0x09}, + {0x36C6, 0x18}, + {0x36C7, 0x07}, + {0x36C8, 0x65}, + {0x36C9, 0x0E}, + {0x36CC, 0x99}, + {0x36CD, 0x01}, + {0x36CE, 0x47}, + {0x36CF, 0x00}, + {0x36D0, 0x04}, + {0x36D1, 0x00}, + {0x36D4, 0x65}, + {0x36D5, 0x0E}, + {0x36D6, 0xA4}, + {0x36D7, 0x0A}, + {0x36D8, 0x65}, + {0x36D9, 0x0E}, + {0x36DC, 0x65}, + {0x36DD, 0x0E}, + {0x36DE, 0xA4}, + {0x36DF, 0x0A}, + {0x36E0, 0x65}, + {0x36E1, 0x0E}, + {0x36E4, 0x65}, + {0x36E5, 0x0E}, + {0x36E6, 0xA4}, + {0x36E7, 0x0A}, + {0x36E8, 0x65}, + {0x36E9, 0x0E}, + {0x36EE, 0x00}, + {0x36EF, 0x00}, + {0x36F0, 0x00}, + {0x36F1, 0x80}, + {0x36F8, 0x00}, + {0x3702, 0x03}, + {0x3703, 0x04}, + {0x3704, 0x08}, + {0x370E, 0x0E}, + {0x3718, 0x62}, + {0x3719, 0x4A}, + {0x371A, 0x38}, + {0x371B, 0x20}, + {0x371C, 0x64}, + {0x371D, 0x42}, + {0x371E, 0x32}, + {0x371F, 0x1B}, + {0x3720, 0x9C}, + {0x3721, 0xA4}, + {0x3722, 0xAC}, + {0x3723, 0xB4}, + {0x3748, 0xAA}, + {0x3749, 0x96}, + {0x374A, 0x7D}, + {0x374B, 0x69}, + {0x37C0, 0x00}, + {0x37C1, 0x00}, + {0x37C2, 0x00}, + {0x37C4, 0x00}, + {0x37C5, 0x00}, + {0x37C6, 0x00}, + {0x37C8, 0x00}, + {0x37C9, 0x00}, + {0x37CA, 0x00}, + {0x37CC, 0x00}, + {0x37CD, 0x00}, + {0x37CE, 0x00}, + {0x37D0, 0x00}, + {0x37D1, 0x00}, + {0x37D2, 0x00}, + {0x37D4, 0x00}, + {0x37D5, 0x00}, + {0x37D6, 0x00}, + {0x37D8, 0x00}, + {0x37D9, 0x00}, + {0x37DA, 0x00}, + {0x37DC, 0x00}, + {0x37DD, 0x00}, + {0x37DE, 0x00}, + {0x37E0, 0x00}, + {0x37E1, 0x00}, + {0x37E2, 0x00}, + {0x37E4, 0x00}, + {0x37E5, 0x00}, + {0x37E6, 0x00}, + {0x37E8, 0x00}, + {0x37E9, 0x00}, + {0x37EA, 0x00}, + {0x37EC, 0x00}, + {0x37ED, 0x00}, + {0x37EE, 0x00}, + {0x37F0, 0x00}, + {0x37F4, 0x00}, + {0x37F5, 0x1E}, + {0x37F6, 0x34}, + {0x37F7, 0x00}, + {0x37F8, 0xFF}, + {0x37F9, 0xFF}, + {0x37FA, 0x03}, + {0x37FC, 0x00}, + {0x37FD, 0x00}, + {0x37FE, 0x04}, + {0x3800, 0xFF}, + {0x3801, 0xFF}, + {0x3802, 0x03}, + {0x3804, 0x00}, + {0x3805, 0x00}, + {0x3806, 0x04}, + {0x3808, 0x00}, + {0x3809, 0x00}, + {0x380A, 0x00}, + {0x380C, 0x00}, + {0x380D, 0x00}, + {0x380E, 0x00}, + {0x3810, 0x00}, + {0x3811, 0x00}, + {0x3812, 0x00}, + {0x3814, 0x00}, + {0x3815, 0x00}, + {0x3816, 0x00}, + {0x3818, 0x00}, + {0x3819, 0x00}, + {0x381A, 0x00}, + {0x381C, 0x00}, + {0x381D, 0x00}, + {0x381E, 0x00}, + {0x3820, 0x00}, + {0x3821, 0x00}, + {0x3822, 0x00}, + {0x3824, 0x00}, + {0x3825, 0x00}, + {0x3826, 0x00}, + {0x3828, 0x00}, + {0x3829, 0x00}, + {0x382A, 0x00}, + {0x382C, 0x00}, + {0x382D, 0x00}, + {0x382E, 0x00}, + {0x3830, 0x00}, + {0x3831, 0x00}, + {0x3832, 0x00}, + {0x3834, 0x00}, + {0x3835, 0x00}, + {0x3836, 0x00}, + {0x3838, 0x00}, + {0x3839, 0x00}, + {0x383A, 0x00}, + {0x383B, 0x00}, + {0x383C, 0x00}, + {0x383D, 0x00}, + {0x383E, 0x00}, + {0x383F, 0x00}, + {0x3840, 0x00}, + {0x3841, 0x00}, + {0x3842, 0x00}, + {0x3843, 0x00}, + {0x3844, 0x00}, + {0x3845, 0x00}, + {0x3846, 0x00}, + {0x3847, 0x00}, + {0x3848, 0x00}, + {0x3849, 0x00}, + {0x384A, 0x00}, + {0x384B, 0x00}, + {0x384C, 0x00}, + {0x384D, 0x00}, + {0x384E, 0x00}, + {0x384F, 0x00}, + {0x3850, 0xFF}, + {0x3851, 0x0F}, + {0x3852, 0x00}, + {0x3853, 0x10}, + {0x3854, 0xFF}, + {0x3855, 0x0F}, + {0x3856, 0x00}, + {0x3857, 0x10}, + {0x3858, 0xFF}, + {0x3859, 0x0F}, + {0x385A, 0x00}, + {0x385B, 0x10}, + {0x385C, 0x02}, + {0x385D, 0x00}, + {0x385E, 0x06}, + {0x385F, 0x00}, + {0x3860, 0x06}, + {0x3861, 0x00}, + {0x3862, 0x08}, + {0x3863, 0x00}, + {0x3864, 0x02}, + {0x3865, 0x00}, + {0x38A0, 0x01}, + {0x38A1, 0x01}, + {0x38A2, 0x00}, + {0x38A3, 0x01}, + {0x38A4, 0x07}, + {0x38A5, 0x00}, + {0x38A6, 0x04}, + {0x38A7, 0x05}, + {0x38A8, 0x00}, + {0x38A9, 0x00}, + {0x38AC, 0x00}, + {0x38AD, 0x00}, + {0x38AE, 0x01}, + {0x38B0, 0x02}, + {0x38B2, 0x22}, + {0x38B3, 0x00}, + {0x38B4, 0x17}, + {0x38B5, 0x00}, + {0x38B6, 0x11}, + {0x38B7, 0x00}, + {0x38B8, 0x0E}, + {0x38B9, 0x00}, + {0x38BA, 0x2A}, + {0x38BB, 0x00}, + {0x38BC, 0x1C}, + {0x38BD, 0x00}, + {0x38BE, 0x14}, + {0x38BF, 0x00}, + {0x38C0, 0x10}, + {0x38C1, 0x00}, + {0x38C2, 0x31}, + {0x38C3, 0x00}, + {0x38C4, 0x21}, + {0x38C5, 0x00}, + {0x38C6, 0x18}, + {0x38C7, 0x00}, + {0x38C8, 0x12}, + {0x38C9, 0x00}, + {0x38CA, 0x3C}, + {0x38CB, 0x00}, + {0x38CC, 0x29}, + {0x38CD, 0x00}, + {0x38CE, 0x1D}, + {0x38CF, 0x00}, + {0x38D0, 0x15}, + {0x38D1, 0x00}, + {0x38D2, 0x4E}, + {0x38D3, 0x00}, + {0x38D4, 0x35}, + {0x38D5, 0x00}, + {0x38D6, 0x26}, + {0x38D7, 0x00}, + {0x38D8, 0x1A}, + {0x38D9, 0x00}, + {0x38DA, 0x69}, + {0x38DB, 0x00}, + {0x38DC, 0x48}, + {0x38DD, 0x00}, + {0x38DE, 0x33}, + {0x38DF, 0x00}, + {0x38E0, 0x22}, + {0x38E1, 0x00}, + {0x38E2, 0x93}, + {0x38E3, 0x00}, + {0x38E4, 0x64}, + {0x38E5, 0x00}, + {0x38E6, 0x48}, + {0x38E7, 0x00}, + {0x38E8, 0x30}, + {0x38E9, 0x00}, + {0x38EA, 0xD3}, + {0x38EB, 0x00}, + {0x38EC, 0x90}, + {0x38ED, 0x00}, + {0x38EE, 0x69}, + {0x38EF, 0x00}, + {0x38F0, 0x49}, + {0x38F1, 0x00}, + {0x38F2, 0x39}, + {0x38F3, 0x01}, + {0x38F4, 0xD5}, + {0x38F5, 0x00}, + {0x38F6, 0x9F}, + {0x38F7, 0x00}, + {0x38F8, 0x75}, + {0x38F9, 0x00}, + {0x38FA, 0x00}, + {0x38FB, 0x01}, + {0x38FC, 0x00}, + {0x38FD, 0x01}, + {0x38FE, 0x00}, + {0x38FF, 0x01}, + {0x3900, 0x00}, + {0x3901, 0x01}, + {0x3902, 0x70}, + {0x3903, 0x00}, + {0x3904, 0x30}, + {0x3905, 0x00}, + {0x3906, 0x25}, + {0x3907, 0x00}, + {0x3908, 0x20}, + {0x3909, 0x00}, + {0x390A, 0xB2}, + {0x390B, 0x00}, + {0x390C, 0x80}, + {0x390D, 0x00}, + {0x390E, 0x70}, + {0x390F, 0x00}, + {0x3910, 0x50}, + {0x3911, 0x00}, + {0x3912, 0xB2}, + {0x3913, 0x00}, + {0x3914, 0x80}, + {0x3915, 0x00}, + {0x3916, 0x70}, + {0x3917, 0x00}, + {0x3918, 0x50}, + {0x3919, 0x00}, + {0x391A, 0xB2}, + {0x391B, 0x00}, + {0x391C, 0x80}, + {0x391D, 0x00}, + {0x391E, 0x70}, + {0x391F, 0x00}, + {0x3920, 0x50}, + {0x3921, 0x00}, + {0x3922, 0x40}, + {0x3923, 0x00}, + {0x3924, 0x40}, + {0x3925, 0x00}, + {0x3926, 0x40}, + {0x3927, 0x00}, + {0x3928, 0x40}, + {0x3929, 0x00}, + {0x392A, 0x80}, + {0x392B, 0x00}, + {0x392C, 0x80}, + {0x392D, 0x00}, + {0x392E, 0x80}, + {0x392F, 0x00}, + {0x3930, 0x80}, + {0x3931, 0x00}, + {0x3932, 0x80}, + {0x3933, 0x80}, + {0x3934, 0x80}, + {0x3940, 0x01}, + {0x3941, 0x01}, + {0x3942, 0x00}, + {0x3943, 0x01}, + {0x3944, 0x07}, + {0x3945, 0x00}, + {0x3946, 0x04}, + {0x3947, 0x05}, + {0x3948, 0x00}, + {0x3949, 0x00}, + {0x394C, 0x00}, + {0x394D, 0x00}, + {0x394E, 0x01}, + {0x3950, 0x03}, + {0x3952, 0x14}, + {0x3953, 0x00}, + {0x3954, 0x0F}, + {0x3955, 0x00}, + {0x3956, 0x0E}, + {0x3957, 0x00}, + {0x3958, 0x0E}, + {0x3959, 0x00}, + {0x395A, 0x19}, + {0x395B, 0x00}, + {0x395C, 0x11}, + {0x395D, 0x00}, + {0x395E, 0x0F}, + {0x395F, 0x00}, + {0x3960, 0x0E}, + {0x3961, 0x00}, + {0x3962, 0x1C}, + {0x3963, 0x00}, + {0x3964, 0x13}, + {0x3965, 0x00}, + {0x3966, 0x0F}, + {0x3967, 0x00}, + {0x3968, 0x0E}, + {0x3969, 0x00}, + {0x396A, 0x23}, + {0x396B, 0x00}, + {0x396C, 0x15}, + {0x396D, 0x00}, + {0x396E, 0x11}, + {0x396F, 0x00}, + {0x3970, 0x0E}, + {0x3971, 0x00}, + {0x3972, 0x2E}, + {0x3973, 0x00}, + {0x3974, 0x1A}, + {0x3975, 0x00}, + {0x3976, 0x14}, + {0x3977, 0x00}, + {0x3978, 0x0F}, + {0x3979, 0x00}, + {0x397A, 0x3E}, + {0x397B, 0x00}, + {0x397C, 0x23}, + {0x397D, 0x00}, + {0x397E, 0x1A}, + {0x397F, 0x00}, + {0x3980, 0x12}, + {0x3981, 0x00}, + {0x3982, 0x56}, + {0x3983, 0x00}, + {0x3984, 0x31}, + {0x3985, 0x00}, + {0x3986, 0x25}, + {0x3987, 0x00}, + {0x3988, 0x1A}, + {0x3989, 0x00}, + {0x398A, 0x7B}, + {0x398B, 0x00}, + {0x398C, 0x49}, + {0x398D, 0x00}, + {0x398E, 0x39}, + {0x398F, 0x00}, + {0x3990, 0x2C}, + {0x3991, 0x00}, + {0x3992, 0xB4}, + {0x3993, 0x00}, + {0x3994, 0x75}, + {0x3995, 0x00}, + {0x3996, 0x61}, + {0x3997, 0x00}, + {0x3998, 0x53}, + {0x3999, 0x00}, + {0x399A, 0x00}, + {0x399B, 0x01}, + {0x399C, 0x00}, + {0x399D, 0x01}, + {0x399E, 0x00}, + {0x399F, 0x01}, + {0x39A0, 0x00}, + {0x39A1, 0x01}, + {0x39A2, 0x70}, + {0x39A3, 0x00}, + {0x39A4, 0x30}, + {0x39A5, 0x00}, + {0x39A6, 0x25}, + {0x39A7, 0x00}, + {0x39A8, 0x20}, + {0x39A9, 0x00}, + {0x39AA, 0xB2}, + {0x39AB, 0x00}, + {0x39AC, 0x80}, + {0x39AD, 0x00}, + {0x39AE, 0x70}, + {0x39AF, 0x00}, + {0x39B0, 0x80}, + {0x39B1, 0x00}, + {0x39B2, 0xB2}, + {0x39B3, 0x00}, + {0x39B4, 0x80}, + {0x39B5, 0x00}, + {0x39B6, 0x70}, + {0x39B7, 0x00}, + {0x39B8, 0x80}, + {0x39B9, 0x00}, + {0x39BA, 0xB2}, + {0x39BB, 0x00}, + {0x39BC, 0x80}, + {0x39BD, 0x00}, + {0x39BE, 0x70}, + {0x39BF, 0x00}, + {0x39C0, 0x80}, + {0x39C1, 0x00}, + {0x39C2, 0x40}, + {0x39C3, 0x00}, + {0x39C4, 0x40}, + {0x39C5, 0x00}, + {0x39C6, 0x40}, + {0x39C7, 0x00}, + {0x39C8, 0x40}, + {0x39C9, 0x00}, + {0x39CA, 0x80}, + {0x39CB, 0x00}, + {0x39CC, 0x80}, + {0x39CD, 0x00}, + {0x39CE, 0x80}, + {0x39CF, 0x00}, + {0x39D0, 0x80}, + {0x39D1, 0x00}, + {0x39D2, 0x80}, + {0x39D3, 0x80}, + {0x39D4, 0x80}, + {0x39E0, 0x01}, + {0x39E1, 0x00}, + {0x39E4, 0x40}, + {0x39E5, 0x01}, + {0x39E6, 0x01}, + {0x39E8, 0x00}, + {0x39E9, 0x01}, + {0x39EA, 0x00}, + {0x39EB, 0x00}, + {0x39EC, 0x01}, + {0x39ED, 0x00}, + {0x39EE, 0x01}, + {0x39F0, 0x03}, + {0x39F1, 0x04}, + {0x39F2, 0x0E}, + {0x39F4, 0x1C}, + {0x39F5, 0x00}, + {0x39F6, 0x13}, + {0x39F7, 0x00}, + {0x39F8, 0x0D}, + {0x39F9, 0x00}, + {0x39FA, 0x07}, + {0x39FB, 0x00}, + {0x39FC, 0x38}, + {0x39FD, 0x00}, + {0x39FE, 0x1C}, + {0x39FF, 0x00}, + {0x3A00, 0x11}, + {0x3A01, 0x00}, + {0x3A02, 0x08}, + {0x3A03, 0x00}, + {0x3A04, 0x4A}, + {0x3A05, 0x00}, + {0x3A06, 0x23}, + {0x3A07, 0x00}, + {0x3A08, 0x15}, + {0x3A09, 0x00}, + {0x3A0A, 0x09}, + {0x3A0B, 0x00}, + {0x3A0C, 0x65}, + {0x3A0D, 0x00}, + {0x3A0E, 0x2D}, + {0x3A0F, 0x00}, + {0x3A10, 0x1A}, + {0x3A11, 0x00}, + {0x3A12, 0x0B}, + {0x3A13, 0x00}, + {0x3A14, 0x8D}, + {0x3A15, 0x00}, + {0x3A16, 0x3D}, + {0x3A17, 0x00}, + {0x3A18, 0x23}, + {0x3A19, 0x00}, + {0x3A1A, 0x0E}, + {0x3A1B, 0x00}, + {0x3A1C, 0xC5}, + {0x3A1D, 0x00}, + {0x3A1E, 0x55}, + {0x3A1F, 0x00}, + {0x3A20, 0x30}, + {0x3A21, 0x00}, + {0x3A22, 0x13}, + {0x3A23, 0x00}, + {0x3A24, 0x16}, + {0x3A25, 0x01}, + {0x3A26, 0x76}, + {0x3A27, 0x00}, + {0x3A28, 0x42}, + {0x3A29, 0x00}, + {0x3A2A, 0x1A}, + {0x3A2B, 0x00}, + {0x3A2C, 0x88}, + {0x3A2D, 0x01}, + {0x3A2E, 0xA7}, + {0x3A2F, 0x00}, + {0x3A30, 0x5D}, + {0x3A31, 0x00}, + {0x3A32, 0x24}, + {0x3A33, 0x00}, + {0x3A34, 0x2A}, + {0x3A35, 0x02}, + {0x3A36, 0xEB}, + {0x3A37, 0x00}, + {0x3A38, 0x83}, + {0x3A39, 0x00}, + {0x3A3A, 0x32}, + {0x3A3B, 0x00}, + {0x3A3C, 0x00}, + {0x3A3D, 0x01}, + {0x3A3E, 0x00}, + {0x3A3F, 0x01}, + {0x3A40, 0x00}, + {0x3A41, 0x01}, + {0x3A42, 0x00}, + {0x3A43, 0x01}, + {0x3A44, 0x80}, + {0x3A45, 0x00}, + {0x3A46, 0x50}, + {0x3A47, 0x00}, + {0x3A48, 0x30}, + {0x3A49, 0x00}, + {0x3A4A, 0x20}, + {0x3A4B, 0x00}, + {0x3A4C, 0x99}, + {0x3A4D, 0x00}, + {0x3A4E, 0x80}, + {0x3A4F, 0x00}, + {0x3A50, 0x80}, + {0x3A51, 0x00}, + {0x3A52, 0x80}, + {0x3A53, 0x00}, + {0x3A54, 0x99}, + {0x3A55, 0x00}, + {0x3A56, 0x80}, + {0x3A57, 0x00}, + {0x3A58, 0x80}, + {0x3A59, 0x00}, + {0x3A5A, 0x80}, + {0x3A5B, 0x00}, + {0x3A5C, 0x99}, + {0x3A5D, 0x00}, + {0x3A5E, 0x80}, + {0x3A5F, 0x00}, + {0x3A60, 0x80}, + {0x3A61, 0x00}, + {0x3A62, 0x80}, + {0x3A63, 0x00}, + {0x3A64, 0x1C}, + {0x3A65, 0x00}, + {0x3A66, 0x13}, + {0x3A67, 0x00}, + {0x3A68, 0x0D}, + {0x3A69, 0x00}, + {0x3A6A, 0x07}, + {0x3A6B, 0x00}, + {0x3A6C, 0x0C}, + {0x3A6D, 0x00}, + {0x3A6E, 0x09}, + {0x3A6F, 0x00}, + {0x3A70, 0x06}, + {0x3A71, 0x00}, + {0x3A72, 0x03}, + {0x3A73, 0x00}, + {0x3A74, 0x1F}, + {0x3A75, 0x00}, + {0x3A76, 0x1B}, + {0x3A77, 0x00}, + {0x3A78, 0x0F}, + {0x3A79, 0x00}, + {0x3A7A, 0x08}, + {0x3A7B, 0x00}, + {0x3A7C, 0x80}, + {0x3A7D, 0x00}, + {0x3A7E, 0x80}, + {0x3A7F, 0x00}, + {0x3A80, 0x80}, + {0x3A81, 0x00}, + {0x3A82, 0x80}, + {0x3A83, 0x00}, + {0x3A84, 0x09}, + {0x3A85, 0x00}, + {0x3A86, 0x04}, + {0x3A87, 0x00}, + {0x3A88, 0x03}, + {0x3A89, 0x00}, + {0x3A8A, 0x01}, + {0x3A8B, 0x00}, + {0x3A8C, 0x19}, + {0x3A8D, 0x01}, + {0x3A8E, 0xD2}, + {0x3A8F, 0x00}, + {0x3A90, 0x8C}, + {0x3A91, 0x00}, + {0x3A92, 0x64}, + {0x3A93, 0x00}, + {0x3A94, 0xFF}, + {0x3A95, 0x00}, + {0x3A96, 0xD2}, + {0x3A97, 0x00}, + {0x3A98, 0x8C}, + {0x3A99, 0x00}, + {0x3A9A, 0x64}, + {0x3A9B, 0x00}, + {0x3A9C, 0x08}, + {0x3A9D, 0x10}, + {0x3A9E, 0x80}, + {0x3A9F, 0x80}, + {0x3AA0, 0x80}, + {0x3AA1, 0x04}, + {0x3AA2, 0x05}, + {0x3AC0, 0x01}, + {0x3AC4, 0x81}, + {0x3AC5, 0x00}, + {0x3AC6, 0x00}, + {0x3AC7, 0x00}, + {0x3AC8, 0x00}, + {0x3AC9, 0x00}, + {0x3ACA, 0x00}, + {0x3ACB, 0x00}, + {0x3ACC, 0x02}, + {0x3ACD, 0x00}, + {0x3ACE, 0x81}, + {0x3ACF, 0x00}, + {0x3AD0, 0x00}, + {0x3AD1, 0x00}, + {0x3AD2, 0xFD}, + {0x3AD3, 0x03}, + {0x3AD4, 0x02}, + {0x3AD5, 0x00}, + {0x3AD6, 0x00}, + {0x3AD7, 0x00}, + {0x3AD8, 0x81}, + {0x3AD9, 0x00}, + {0x3ADA, 0xFD}, + {0x3ADB, 0x03}, + {0x3ADC, 0xFF}, + {0x3ADD, 0x03}, + {0x3ADE, 0x01}, + {0x3ADF, 0x00}, + {0x3AE0, 0x01}, + {0x3AE1, 0x00}, + {0x3AE2, 0x7E}, + {0x3AE3, 0x00}, + {0x3AF4, 0x00}, + {0x3AF6, 0x40}, + {0x3AF7, 0x1E}, + {0x3AF8, 0x01}, + {0x3AFA, 0x63}, + {0x3AFB, 0x09}, + {0x3AFC, 0x11}, + {0x3AFD, 0x09}, + {0x3AFE, 0x00}, + {0x3AFF, 0x00}, + {0x3B00, 0x00}, + {0x3B01, 0x00}, + {0x3B02, 0x84}, + {0x3B03, 0x06}, + {0x3B04, 0x30}, + {0x3B05, 0x06}, + {0x3B06, 0x00}, + {0x3B07, 0x00}, + {0x3B08, 0x00}, + {0x3B09, 0x00}, + {0x3B0A, 0x00}, + {0x3B0B, 0x00}, + {0x3B0C, 0x00}, + {0x3B0D, 0x00}, + {0x3B0E, 0x00}, + {0x3B0F, 0x00}, + {0x3B10, 0x00}, + {0x3B11, 0x00}, + {0x3B12, 0x00}, + {0x3B13, 0x00}, + {0x3B14, 0x00}, + {0x3B15, 0x00}, + {0x3B16, 0x00}, + {0x3B17, 0x00}, + {0x3B18, 0x00}, + {0x3B19, 0x00}, + {0x3B1A, 0x00}, + {0x3B1B, 0x00}, + {0x3B1C, 0x00}, + {0x3B1D, 0x00}, + {0x3B1E, 0x00}, + {0x3B1F, 0x00}, + {0x3B20, 0x00}, + {0x3B21, 0x00}, + {0x3B22, 0x00}, + {0x3B23, 0x00}, + {0x3B24, 0x00}, + {0x3B25, 0x00}, + {0x3B26, 0x00}, + {0x3B27, 0x00}, + {0x3B28, 0x00}, + {0x3B29, 0x00}, + {0x3B2A, 0x00}, + {0x3B2C, 0x00}, + {0x3B2E, 0x00}, + {0x3B30, 0x00}, + {0x3B32, 0x0C}, + {0x4000, 0xD1}, + {0x4001, 0xC0}, + {0x4002, 0xC0}, + {0x4003, 0xB8}, + {0x4004, 0xC0}, + {0x4005, 0xB8}, + {0x4006, 0xB9}, + {0x4007, 0xB7}, + {0x4008, 0xB0}, + {0x4009, 0xAB}, + {0x400A, 0xAC}, + {0x400B, 0xAB}, + {0x400C, 0xA8}, + {0x400D, 0xA6}, + {0x400E, 0xA6}, + {0x400F, 0xA5}, + {0x4010, 0xA2}, + {0x4011, 0xA0}, + {0x4012, 0xA0}, + {0x4013, 0x9F}, + {0x4014, 0xA4}, + {0x4015, 0xA2}, + {0x4016, 0xA2}, + {0x4017, 0x9C}, + {0x4018, 0xA8}, + {0x4019, 0xA6}, + {0x401A, 0xA8}, + {0x401B, 0xAA}, + {0x401C, 0xB0}, + {0x401D, 0xAE}, + {0x401E, 0xAE}, + {0x401F, 0xAE}, + {0x4020, 0xBA}, + {0x4021, 0xAE}, + {0x4022, 0xAF}, + {0x4023, 0xAE}, + {0x4024, 0xC6}, + {0x4025, 0xBD}, + {0x4026, 0xBD}, + {0x4027, 0xBA}, + {0x4028, 0xB0}, + {0x4029, 0xA9}, + {0x402A, 0xAA}, + {0x402B, 0xA8}, + {0x402C, 0x9F}, + {0x402D, 0x9C}, + {0x402E, 0x9C}, + {0x402F, 0x9B}, + {0x4030, 0x93}, + {0x4031, 0x91}, + {0x4032, 0x92}, + {0x4033, 0x91}, + {0x4034, 0x8D}, + {0x4035, 0x8C}, + {0x4036, 0x8C}, + {0x4037, 0x8C}, + {0x4038, 0x8F}, + {0x4039, 0x8E}, + {0x403A, 0x8E}, + {0x403B, 0x8E}, + {0x403C, 0x98}, + {0x403D, 0x96}, + {0x403E, 0x96}, + {0x403F, 0x95}, + {0x4040, 0xA4}, + {0x4041, 0xA0}, + {0x4042, 0xA0}, + {0x4043, 0x9E}, + {0x4044, 0xB3}, + {0x4045, 0xAE}, + {0x4046, 0xAF}, + {0x4047, 0xAB}, + {0x4048, 0xC2}, + {0x4049, 0xB7}, + {0x404A, 0xB8}, + {0x404B, 0xB5}, + {0x404C, 0xAB}, + {0x404D, 0xA4}, + {0x404E, 0xA5}, + {0x404F, 0xA3}, + {0x4050, 0x99}, + {0x4051, 0x96}, + {0x4052, 0x96}, + {0x4053, 0x96}, + {0x4054, 0x8B}, + {0x4055, 0x8A}, + {0x4056, 0x8A}, + {0x4057, 0x8A}, + {0x4058, 0x82}, + {0x4059, 0x81}, + {0x405A, 0x81}, + {0x405B, 0x81}, + {0x405C, 0x85}, + {0x405D, 0x86}, + {0x405E, 0x85}, + {0x405F, 0x85}, + {0x4060, 0x90}, + {0x4061, 0x90}, + {0x4062, 0x8F}, + {0x4063, 0x8F}, + {0x4064, 0x9D}, + {0x4065, 0x9B}, + {0x4066, 0x9B}, + {0x4067, 0x9A}, + {0x4068, 0xAF}, + {0x4069, 0xAA}, + {0x406A, 0xAC}, + {0x406B, 0xAA}, + {0x406C, 0xC2}, + {0x406D, 0xB7}, + {0x406E, 0xB8}, + {0x406F, 0xB5}, + {0x4070, 0xAB}, + {0x4071, 0xA4}, + {0x4072, 0xA4}, + {0x4073, 0xA3}, + {0x4074, 0x99}, + {0x4075, 0x96}, + {0x4076, 0x96}, + {0x4077, 0x96}, + {0x4078, 0x8B}, + {0x4079, 0x8A}, + {0x407A, 0x8A}, + {0x407B, 0x8A}, + {0x407C, 0x82}, + {0x407D, 0x82}, + {0x407E, 0x82}, + {0x407F, 0x82}, + {0x4080, 0x85}, + {0x4081, 0x86}, + {0x4082, 0x86}, + {0x4083, 0x86}, + {0x4084, 0x90}, + {0x4085, 0x90}, + {0x4086, 0x8F}, + {0x4087, 0x8F}, + {0x4088, 0x9D}, + {0x4089, 0x9B}, + {0x408A, 0x9B}, + {0x408B, 0x99}, + {0x408C, 0xAE}, + {0x408D, 0xAA}, + {0x408E, 0xAA}, + {0x408F, 0xA7}, + {0x4090, 0xC7}, + {0x4091, 0xBA}, + {0x4092, 0xBC}, + {0x4093, 0xB9}, + {0x4094, 0xB1}, + {0x4095, 0xA8}, + {0x4096, 0xA8}, + {0x4097, 0xA7}, + {0x4098, 0x9F}, + {0x4099, 0x9B}, + {0x409A, 0x9B}, + {0x409B, 0x9B}, + {0x409C, 0x93}, + {0x409D, 0x91}, + {0x409E, 0x91}, + {0x409F, 0x91}, + {0x40A0, 0x8D}, + {0x40A1, 0x8C}, + {0x40A2, 0x8C}, + {0x40A3, 0x8C}, + {0x40A4, 0x8E}, + {0x40A5, 0x8E}, + {0x40A6, 0x8D}, + {0x40A7, 0x8D}, + {0x40A8, 0x96}, + {0x40A9, 0x95}, + {0x40AA, 0x95}, + {0x40AB, 0x94}, + {0x40AC, 0xA2}, + {0x40AD, 0x9F}, + {0x40AE, 0x9F}, + {0x40AF, 0x9D}, + {0x40B0, 0xB1}, + {0x40B1, 0xAC}, + {0x40B2, 0xAB}, + {0x40B3, 0xAA}, + {0x40B4, 0xD3}, + {0x40B5, 0xBC}, + {0x40B6, 0xBD}, + {0x40B7, 0xBC}, + {0x40B8, 0xC1}, + {0x40B9, 0xB7}, + {0x40BA, 0xB7}, + {0x40BB, 0xB5}, + {0x40BC, 0xB0}, + {0x40BD, 0xAA}, + {0x40BE, 0xAA}, + {0x40BF, 0xAA}, + {0x40C0, 0xA8}, + {0x40C1, 0xA4}, + {0x40C2, 0xA4}, + {0x40C3, 0xA4}, + {0x40C4, 0xA2}, + {0x40C5, 0x9F}, + {0x40C6, 0x9F}, + {0x40C7, 0x9F}, + {0x40C8, 0xA3}, + {0x40C9, 0xA0}, + {0x40CA, 0xA0}, + {0x40CB, 0xA0}, + {0x40CC, 0xA6}, + {0x40CD, 0xA3}, + {0x40CE, 0xA3}, + {0x40CF, 0xA2}, + {0x40D0, 0xAF}, + {0x40D1, 0xAB}, + {0x40D2, 0xAA}, + {0x40D3, 0xA8}, + {0x40D4, 0xBA}, + {0x40D5, 0xAE}, + {0x40D6, 0xAE}, + {0x40D7, 0xAB}, + {0x4100, 0xBD}, + {0x4101, 0xBA}, + {0x4102, 0xBD}, + {0x4103, 0xB7}, + {0x4104, 0xB7}, + {0x4105, 0xB7}, + {0x4106, 0xB8}, + {0x4107, 0xB5}, + {0x4108, 0xAB}, + {0x4109, 0xAA}, + {0x410A, 0xAC}, + {0x410B, 0xAB}, + {0x410C, 0xA4}, + {0x410D, 0xA5}, + {0x410E, 0xA5}, + {0x410F, 0xA4}, + {0x4110, 0x9F}, + {0x4111, 0xA0}, + {0x4112, 0xA0}, + {0x4113, 0x9F}, + {0x4114, 0xA0}, + {0x4115, 0xA0}, + {0x4116, 0xA0}, + {0x4117, 0x9F}, + {0x4118, 0xA1}, + {0x4119, 0xA1}, + {0x411A, 0xA1}, + {0x411B, 0xA0}, + {0x411C, 0xA7}, + {0x411D, 0xA6}, + {0x411E, 0xA6}, + {0x411F, 0xA6}, + {0x4120, 0xA7}, + {0x4121, 0xA6}, + {0x4122, 0xA6}, + {0x4123, 0xA3}, + {0x4124, 0xB9}, + {0x4125, 0xB9}, + {0x4126, 0xBA}, + {0x4127, 0xB8}, + {0x4128, 0xA6}, + {0x4129, 0xA7}, + {0x412A, 0xA7}, + {0x412B, 0xA6}, + {0x412C, 0x9B}, + {0x412D, 0x9B}, + {0x412E, 0x9B}, + {0x412F, 0x9B}, + {0x4130, 0x91}, + {0x4131, 0x92}, + {0x4132, 0x92}, + {0x4133, 0x91}, + {0x4134, 0x8C}, + {0x4135, 0x8C}, + {0x4136, 0x8C}, + {0x4137, 0x8C}, + {0x4138, 0x8D}, + {0x4139, 0x8D}, + {0x413A, 0x8D}, + {0x413B, 0x8D}, + {0x413C, 0x93}, + {0x413D, 0x93}, + {0x413E, 0x93}, + {0x413F, 0x92}, + {0x4140, 0x9A}, + {0x4141, 0x9A}, + {0x4142, 0x9A}, + {0x4143, 0x99}, + {0x4144, 0xA7}, + {0x4145, 0xA5}, + {0x4146, 0xA6}, + {0x4147, 0xA6}, + {0x4148, 0xB8}, + {0x4149, 0xB4}, + {0x414A, 0xB4}, + {0x414B, 0xB3}, + {0x414C, 0xA3}, + {0x414D, 0xA2}, + {0x414E, 0xA3}, + {0x414F, 0xA2}, + {0x4150, 0x96}, + {0x4151, 0x96}, + {0x4152, 0x96}, + {0x4153, 0x96}, + {0x4154, 0x8A}, + {0x4155, 0x8A}, + {0x4156, 0x8A}, + {0x4157, 0x8A}, + {0x4158, 0x82}, + {0x4159, 0x82}, + {0x415A, 0x82}, + {0x415B, 0x82}, + {0x415C, 0x84}, + {0x415D, 0x85}, + {0x415E, 0x84}, + {0x415F, 0x84}, + {0x4160, 0x8D}, + {0x4161, 0x8D}, + {0x4162, 0x8D}, + {0x4163, 0x8D}, + {0x4164, 0x96}, + {0x4165, 0x96}, + {0x4166, 0x96}, + {0x4167, 0x95}, + {0x4168, 0xA5}, + {0x4169, 0xA2}, + {0x416A, 0xA3}, + {0x416B, 0xA2}, + {0x416C, 0xB7}, + {0x416D, 0xB3}, + {0x416E, 0xB5}, + {0x416F, 0xB4}, + {0x4170, 0xA4}, + {0x4171, 0xA2}, + {0x4172, 0xA3}, + {0x4173, 0xA2}, + {0x4174, 0x97}, + {0x4175, 0x96}, + {0x4176, 0x96}, + {0x4177, 0x96}, + {0x4178, 0x8B}, + {0x4179, 0x8A}, + {0x417A, 0x8A}, + {0x417B, 0x8A}, + {0x417C, 0x81}, + {0x417D, 0x81}, + {0x417E, 0x81}, + {0x417F, 0x81}, + {0x4180, 0x84}, + {0x4181, 0x84}, + {0x4182, 0x84}, + {0x4183, 0x84}, + {0x4184, 0x8C}, + {0x4185, 0x8D}, + {0x4186, 0x8D}, + {0x4187, 0x8D}, + {0x4188, 0x95}, + {0x4189, 0x96}, + {0x418A, 0x96}, + {0x418B, 0x95}, + {0x418C, 0xA1}, + {0x418D, 0xA1}, + {0x418E, 0xA1}, + {0x418F, 0xA0}, + {0x4190, 0xBC}, + {0x4191, 0xB8}, + {0x4192, 0xB8}, + {0x4193, 0xB9}, + {0x4194, 0xA8}, + {0x4195, 0xA5}, + {0x4196, 0xA6}, + {0x4197, 0xA5}, + {0x4198, 0x9C}, + {0x4199, 0x9A}, + {0x419A, 0x9A}, + {0x419B, 0x9A}, + {0x419C, 0x91}, + {0x419D, 0x91}, + {0x419E, 0x91}, + {0x419F, 0x91}, + {0x41A0, 0x8B}, + {0x41A1, 0x8B}, + {0x41A2, 0x8B}, + {0x41A3, 0x8B}, + {0x41A4, 0x8C}, + {0x41A5, 0x8C}, + {0x41A6, 0x8C}, + {0x41A7, 0x8C}, + {0x41A8, 0x91}, + {0x41A9, 0x92}, + {0x41AA, 0x91}, + {0x41AB, 0x91}, + {0x41AC, 0x98}, + {0x41AD, 0x99}, + {0x41AE, 0x99}, + {0x41AF, 0x98}, + {0x41B0, 0xA3}, + {0x41B1, 0xA3}, + {0x41B2, 0xA3}, + {0x41B3, 0xA2}, + {0x41B4, 0xC1}, + {0x41B5, 0xB8}, + {0x41B6, 0xB9}, + {0x41B7, 0xBA}, + {0x41B8, 0xB8}, + {0x41B9, 0xB4}, + {0x41BA, 0xB4}, + {0x41BB, 0xB4}, + {0x41BC, 0xAA}, + {0x41BD, 0xA7}, + {0x41BE, 0xA7}, + {0x41BF, 0xA8}, + {0x41C0, 0xA4}, + {0x41C1, 0xA2}, + {0x41C2, 0xA2}, + {0x41C3, 0xA3}, + {0x41C4, 0x9E}, + {0x41C5, 0x9D}, + {0x41C6, 0x9D}, + {0x41C7, 0x9D}, + {0x41C8, 0x9E}, + {0x41C9, 0x9D}, + {0x41CA, 0x9D}, + {0x41CB, 0x9D}, + {0x41CC, 0x9E}, + {0x41CD, 0x9E}, + {0x41CE, 0x9E}, + {0x41CF, 0x9E}, + {0x41D0, 0xA3}, + {0x41D1, 0xA3}, + {0x41D2, 0xA2}, + {0x41D3, 0xA1}, + {0x41D4, 0xA7}, + {0x41D5, 0xA7}, + {0x41D6, 0xA7}, + {0x41D7, 0xA3}, + {0x4200, 0xCE}, + {0x4201, 0xC0}, + {0x4202, 0xC1}, + {0x4203, 0xB9}, + {0x4204, 0xC3}, + {0x4205, 0xB9}, + {0x4206, 0xBC}, + {0x4207, 0xBD}, + {0x4208, 0xB3}, + {0x4209, 0xAE}, + {0x420A, 0xAF}, + {0x420B, 0xAE}, + {0x420C, 0xAA}, + {0x420D, 0xA8}, + {0x420E, 0xA8}, + {0x420F, 0xA6}, + {0x4210, 0xA4}, + {0x4211, 0xA2}, + {0x4212, 0xA2}, + {0x4213, 0xA0}, + {0x4214, 0xA4}, + {0x4215, 0xA3}, + {0x4216, 0xA2}, + {0x4217, 0xA0}, + {0x4218, 0xA7}, + {0x4219, 0xA5}, + {0x421A, 0xA3}, + {0x421B, 0xA1}, + {0x421C, 0xB0}, + {0x421D, 0xA8}, + {0x421E, 0xA8}, + {0x421F, 0xA6}, + {0x4220, 0xB4}, + {0x4221, 0xAA}, + {0x4222, 0xA5}, + {0x4223, 0xA3}, + {0x4224, 0xC7}, + {0x4225, 0xBC}, + {0x4226, 0xBE}, + {0x4227, 0xBC}, + {0x4228, 0xB0}, + {0x4229, 0xA9}, + {0x422A, 0xA9}, + {0x422B, 0xA8}, + {0x422C, 0xA0}, + {0x422D, 0x9D}, + {0x422E, 0x9D}, + {0x422F, 0x9C}, + {0x4230, 0x94}, + {0x4231, 0x93}, + {0x4232, 0x93}, + {0x4233, 0x92}, + {0x4234, 0x8E}, + {0x4235, 0x8D}, + {0x4236, 0x8D}, + {0x4237, 0x8C}, + {0x4238, 0x8F}, + {0x4239, 0x8E}, + {0x423A, 0x8E}, + {0x423B, 0x8D}, + {0x423C, 0x96}, + {0x423D, 0x94}, + {0x423E, 0x94}, + {0x423F, 0x92}, + {0x4240, 0xA1}, + {0x4241, 0x9C}, + {0x4242, 0x9C}, + {0x4243, 0x99}, + {0x4244, 0xB0}, + {0x4245, 0xA8}, + {0x4246, 0xAB}, + {0x4247, 0xA7}, + {0x4248, 0xC3}, + {0x4249, 0xB7}, + {0x424A, 0xB7}, + {0x424B, 0xBC}, + {0x424C, 0xAB}, + {0x424D, 0xA4}, + {0x424E, 0xA5}, + {0x424F, 0xA5}, + {0x4250, 0x9A}, + {0x4251, 0x97}, + {0x4252, 0x97}, + {0x4253, 0x98}, + {0x4254, 0x8C}, + {0x4255, 0x8B}, + {0x4256, 0x8B}, + {0x4257, 0x8B}, + {0x4258, 0x82}, + {0x4259, 0x82}, + {0x425A, 0x82}, + {0x425B, 0x82}, + {0x425C, 0x85}, + {0x425D, 0x85}, + {0x425E, 0x85}, + {0x425F, 0x84}, + {0x4260, 0x8F}, + {0x4261, 0x8E}, + {0x4262, 0x8E}, + {0x4263, 0x8D}, + {0x4264, 0x9B}, + {0x4265, 0x98}, + {0x4266, 0x98}, + {0x4267, 0x95}, + {0x4268, 0xAE}, + {0x4269, 0xA5}, + {0x426A, 0xA7}, + {0x426B, 0xA2}, + {0x426C, 0xC2}, + {0x426D, 0xB7}, + {0x426E, 0xB8}, + {0x426F, 0xB9}, + {0x4270, 0xAA}, + {0x4271, 0xA4}, + {0x4272, 0xA4}, + {0x4273, 0xA5}, + {0x4274, 0x99}, + {0x4275, 0x96}, + {0x4276, 0x97}, + {0x4277, 0x98}, + {0x4278, 0x8B}, + {0x4279, 0x8A}, + {0x427A, 0x8A}, + {0x427B, 0x8B}, + {0x427C, 0x81}, + {0x427D, 0x81}, + {0x427E, 0x81}, + {0x427F, 0x82}, + {0x4280, 0x84}, + {0x4281, 0x84}, + {0x4282, 0x84}, + {0x4283, 0x84}, + {0x4284, 0x8E}, + {0x4285, 0x8E}, + {0x4286, 0x8D}, + {0x4287, 0x8C}, + {0x4288, 0x9A}, + {0x4289, 0x97}, + {0x428A, 0x97}, + {0x428B, 0x95}, + {0x428C, 0xAA}, + {0x428D, 0xA3}, + {0x428E, 0xA3}, + {0x428F, 0xA2}, + {0x4290, 0xC7}, + {0x4291, 0xBA}, + {0x4292, 0xC0}, + {0x4293, 0xC3}, + {0x4294, 0xB0}, + {0x4295, 0xA7}, + {0x4296, 0xA7}, + {0x4297, 0xA9}, + {0x4298, 0x9F}, + {0x4299, 0x9B}, + {0x429A, 0x9B}, + {0x429B, 0x9D}, + {0x429C, 0x93}, + {0x429D, 0x91}, + {0x429E, 0x91}, + {0x429F, 0x92}, + {0x42A0, 0x8C}, + {0x42A1, 0x8B}, + {0x42A2, 0x8B}, + {0x42A3, 0x8C}, + {0x42A4, 0x8D}, + {0x42A5, 0x8C}, + {0x42A6, 0x8C}, + {0x42A7, 0x8C}, + {0x42A8, 0x94}, + {0x42A9, 0x93}, + {0x42AA, 0x92}, + {0x42AB, 0x91}, + {0x42AC, 0x9E}, + {0x42AD, 0x9B}, + {0x42AE, 0x9B}, + {0x42AF, 0x98}, + {0x42B0, 0xAC}, + {0x42B1, 0xA6}, + {0x42B2, 0xA6}, + {0x42B3, 0xA2}, + {0x42B4, 0xCE}, + {0x42B5, 0xBA}, + {0x42B6, 0xBC}, + {0x42B7, 0xB7}, + {0x42B8, 0xC5}, + {0x42B9, 0xB5}, + {0x42BA, 0xBA}, + {0x42BB, 0xC0}, + {0x42BC, 0xB1}, + {0x42BD, 0xA8}, + {0x42BE, 0xAE}, + {0x42BF, 0xAF}, + {0x42C0, 0xA7}, + {0x42C1, 0xA3}, + {0x42C2, 0xA3}, + {0x42C3, 0xA5}, + {0x42C4, 0xA0}, + {0x42C5, 0x9D}, + {0x42C6, 0x9D}, + {0x42C7, 0x9F}, + {0x42C8, 0xA0}, + {0x42C9, 0x9E}, + {0x42CA, 0x9E}, + {0x42CB, 0x9F}, + {0x42CC, 0xA2}, + {0x42CD, 0xA0}, + {0x42CE, 0xA0}, + {0x42CF, 0xA0}, + {0x42D0, 0xA8}, + {0x42D1, 0xA5}, + {0x42D2, 0xA5}, + {0x42D3, 0xA2}, + {0x42D4, 0xB3}, + {0x42D5, 0xAA}, + {0x42D6, 0xAB}, + {0x42D7, 0xA3}, + {0x42D8, 0x00}, + {0x42D9, 0x00}, + {0x4300, 0xA2}, + {0x4301, 0xAE}, + {0x4302, 0xAD}, + {0x4303, 0xB5}, + {0x4304, 0x95}, + {0x4305, 0x9A}, + {0x4306, 0x98}, + {0x4307, 0x9B}, + {0x4308, 0x8D}, + {0x4309, 0x90}, + {0x430A, 0x8F}, + {0x430B, 0x91}, + {0x430C, 0x86}, + {0x430D, 0x88}, + {0x430E, 0x87}, + {0x430F, 0x89}, + {0x4310, 0x86}, + {0x4311, 0x87}, + {0x4312, 0x86}, + {0x4313, 0x88}, + {0x4314, 0x89}, + {0x4315, 0x88}, + {0x4316, 0x88}, + {0x4317, 0x8E}, + {0x4318, 0x90}, + {0x4319, 0x8F}, + {0x431A, 0x8C}, + {0x431B, 0x8C}, + {0x431C, 0x9C}, + {0x431D, 0x99}, + {0x431E, 0x98}, + {0x431F, 0x99}, + {0x4320, 0xAB}, + {0x4321, 0xB0}, + {0x4322, 0xAD}, + {0x4323, 0xAF}, + {0x4324, 0x9B}, + {0x4325, 0x9F}, + {0x4326, 0x9E}, + {0x4327, 0xA1}, + {0x4328, 0x8E}, + {0x4329, 0x91}, + {0x432A, 0x90}, + {0x432B, 0x93}, + {0x432C, 0x86}, + {0x432D, 0x88}, + {0x432E, 0x87}, + {0x432F, 0x89}, + {0x4330, 0x82}, + {0x4331, 0x84}, + {0x4332, 0x83}, + {0x4333, 0x84}, + {0x4334, 0x82}, + {0x4335, 0x82}, + {0x4336, 0x82}, + {0x4337, 0x83}, + {0x4338, 0x85}, + {0x4339, 0x84}, + {0x433A, 0x84}, + {0x433B, 0x85}, + {0x433C, 0x8A}, + {0x433D, 0x89}, + {0x433E, 0x88}, + {0x433F, 0x89}, + {0x4340, 0x93}, + {0x4341, 0x91}, + {0x4342, 0x91}, + {0x4343, 0x93}, + {0x4344, 0xA0}, + {0x4345, 0x9E}, + {0x4346, 0x9D}, + {0x4347, 0xA1}, + {0x4348, 0x95}, + {0x4349, 0x9B}, + {0x434A, 0x9A}, + {0x434B, 0x9C}, + {0x434C, 0x8A}, + {0x434D, 0x8D}, + {0x434E, 0x8C}, + {0x434F, 0x8D}, + {0x4350, 0x83}, + {0x4351, 0x85}, + {0x4352, 0x84}, + {0x4353, 0x85}, + {0x4354, 0x80}, + {0x4355, 0x81}, + {0x4356, 0x81}, + {0x4357, 0x81}, + {0x4358, 0x80}, + {0x4359, 0x80}, + {0x435A, 0x80}, + {0x435B, 0x80}, + {0x435C, 0x82}, + {0x435D, 0x81}, + {0x435E, 0x81}, + {0x435F, 0x81}, + {0x4360, 0x85}, + {0x4361, 0x84}, + {0x4362, 0x84}, + {0x4363, 0x85}, + {0x4364, 0x8D}, + {0x4365, 0x8B}, + {0x4366, 0x8B}, + {0x4367, 0x8D}, + {0x4368, 0x98}, + {0x4369, 0x98}, + {0x436A, 0x95}, + {0x436B, 0x98}, + {0x436C, 0x95}, + {0x436D, 0x9A}, + {0x436E, 0x99}, + {0x436F, 0x9A}, + {0x4370, 0x8A}, + {0x4371, 0x8D}, + {0x4372, 0x8C}, + {0x4373, 0x8C}, + {0x4374, 0x83}, + {0x4375, 0x85}, + {0x4376, 0x84}, + {0x4377, 0x84}, + {0x4378, 0x80}, + {0x4379, 0x80}, + {0x437A, 0x80}, + {0x437B, 0x80}, + {0x437C, 0x7F}, + {0x437D, 0x7F}, + {0x437E, 0x7F}, + {0x437F, 0x7F}, + {0x4380, 0x81}, + {0x4381, 0x80}, + {0x4382, 0x80}, + {0x4383, 0x81}, + {0x4384, 0x84}, + {0x4385, 0x83}, + {0x4386, 0x83}, + {0x4387, 0x84}, + {0x4388, 0x8B}, + {0x4389, 0x8A}, + {0x438A, 0x8A}, + {0x438B, 0x8C}, + {0x438C, 0x97}, + {0x438D, 0x96}, + {0x438E, 0x96}, + {0x438F, 0x99}, + {0x4390, 0x99}, + {0x4391, 0x9F}, + {0x4392, 0x9E}, + {0x4393, 0x9D}, + {0x4394, 0x8D}, + {0x4395, 0x90}, + {0x4396, 0x90}, + {0x4397, 0x8F}, + {0x4398, 0x85}, + {0x4399, 0x87}, + {0x439A, 0x87}, + {0x439B, 0x86}, + {0x439C, 0x81}, + {0x439D, 0x83}, + {0x439E, 0x82}, + {0x439F, 0x82}, + {0x43A0, 0x80}, + {0x43A1, 0x81}, + {0x43A2, 0x81}, + {0x43A3, 0x81}, + {0x43A4, 0x82}, + {0x43A5, 0x82}, + {0x43A6, 0x82}, + {0x43A7, 0x82}, + {0x43A8, 0x86}, + {0x43A9, 0x85}, + {0x43AA, 0x85}, + {0x43AB, 0x87}, + {0x43AC, 0x8D}, + {0x43AD, 0x8D}, + {0x43AE, 0x8D}, + {0x43AF, 0x90}, + {0x43B0, 0x9A}, + {0x43B1, 0x9A}, + {0x43B2, 0x9B}, + {0x43B3, 0x9D}, + {0x43B4, 0xA0}, + {0x43B5, 0xAD}, + {0x43B6, 0xAC}, + {0x43B7, 0xAA}, + {0x43B8, 0x93}, + {0x43B9, 0x97}, + {0x43BA, 0x97}, + {0x43BB, 0x96}, + {0x43BC, 0x8B}, + {0x43BD, 0x8E}, + {0x43BE, 0x8E}, + {0x43BF, 0x8C}, + {0x43C0, 0x83}, + {0x43C1, 0x85}, + {0x43C2, 0x85}, + {0x43C3, 0x84}, + {0x43C4, 0x82}, + {0x43C5, 0x84}, + {0x43C6, 0x83}, + {0x43C7, 0x83}, + {0x43C8, 0x83}, + {0x43C9, 0x84}, + {0x43CA, 0x84}, + {0x43CB, 0x85}, + {0x43CC, 0x8A}, + {0x43CD, 0x8A}, + {0x43CE, 0x8A}, + {0x43CF, 0x8C}, + {0x43D0, 0x92}, + {0x43D1, 0x93}, + {0x43D2, 0x93}, + {0x43D3, 0x96}, + {0x43D4, 0x9F}, + {0x43D5, 0xA6}, + {0x43D6, 0xA5}, + {0x43D7, 0xAA}, + {0x4400, 0xA1}, + {0x4401, 0xAB}, + {0x4402, 0xA7}, + {0x4403, 0xB0}, + {0x4404, 0x91}, + {0x4405, 0x96}, + {0x4406, 0x94}, + {0x4407, 0x99}, + {0x4408, 0x8A}, + {0x4409, 0x8E}, + {0x440A, 0x8C}, + {0x440B, 0x8F}, + {0x440C, 0x85}, + {0x440D, 0x86}, + {0x440E, 0x86}, + {0x440F, 0x88}, + {0x4410, 0x85}, + {0x4411, 0x86}, + {0x4412, 0x85}, + {0x4413, 0x87}, + {0x4414, 0x88}, + {0x4415, 0x87}, + {0x4416, 0x87}, + {0x4417, 0x89}, + {0x4418, 0x91}, + {0x4419, 0x8F}, + {0x441A, 0x8F}, + {0x441B, 0x90}, + {0x441C, 0x9C}, + {0x441D, 0x9B}, + {0x441E, 0x9A}, + {0x441F, 0x9A}, + {0x4420, 0xB3}, + {0x4421, 0xB1}, + {0x4422, 0xB0}, + {0x4423, 0xB2}, + {0x4424, 0x96}, + {0x4425, 0x9C}, + {0x4426, 0x9A}, + {0x4427, 0x9E}, + {0x4428, 0x8B}, + {0x4429, 0x8F}, + {0x442A, 0x8E}, + {0x442B, 0x91}, + {0x442C, 0x84}, + {0x442D, 0x87}, + {0x442E, 0x86}, + {0x442F, 0x88}, + {0x4430, 0x82}, + {0x4431, 0x83}, + {0x4432, 0x82}, + {0x4433, 0x84}, + {0x4434, 0x82}, + {0x4435, 0x82}, + {0x4436, 0x82}, + {0x4437, 0x83}, + {0x4438, 0x84}, + {0x4439, 0x84}, + {0x443A, 0x84}, + {0x443B, 0x84}, + {0x443C, 0x8B}, + {0x443D, 0x89}, + {0x443E, 0x89}, + {0x443F, 0x89}, + {0x4440, 0x95}, + {0x4441, 0x93}, + {0x4442, 0x93}, + {0x4443, 0x93}, + {0x4444, 0xA2}, + {0x4445, 0xA2}, + {0x4446, 0xA1}, + {0x4447, 0xA0}, + {0x4448, 0x8F}, + {0x4449, 0x97}, + {0x444A, 0x97}, + {0x444B, 0x98}, + {0x444C, 0x87}, + {0x444D, 0x8B}, + {0x444E, 0x8A}, + {0x444F, 0x8B}, + {0x4450, 0x81}, + {0x4451, 0x83}, + {0x4452, 0x83}, + {0x4453, 0x84}, + {0x4454, 0x7F}, + {0x4455, 0x80}, + {0x4456, 0x80}, + {0x4457, 0x81}, + {0x4458, 0x80}, + {0x4459, 0x80}, + {0x445A, 0x80}, + {0x445B, 0x80}, + {0x445C, 0x82}, + {0x445D, 0x81}, + {0x445E, 0x81}, + {0x445F, 0x81}, + {0x4460, 0x87}, + {0x4461, 0x85}, + {0x4462, 0x85}, + {0x4463, 0x86}, + {0x4464, 0x90}, + {0x4465, 0x8E}, + {0x4466, 0x8E}, + {0x4467, 0x8E}, + {0x4468, 0x9B}, + {0x4469, 0x9C}, + {0x446A, 0x9A}, + {0x446B, 0x9A}, + {0x446C, 0x91}, + {0x446D, 0x97}, + {0x446E, 0x95}, + {0x446F, 0x95}, + {0x4470, 0x87}, + {0x4471, 0x8A}, + {0x4472, 0x8A}, + {0x4473, 0x89}, + {0x4474, 0x81}, + {0x4475, 0x83}, + {0x4476, 0x83}, + {0x4477, 0x83}, + {0x4478, 0x7F}, + {0x4479, 0x80}, + {0x447A, 0x80}, + {0x447B, 0x80}, + {0x447C, 0x80}, + {0x447D, 0x80}, + {0x447E, 0x80}, + {0x447F, 0x7F}, + {0x4480, 0x81}, + {0x4481, 0x81}, + {0x4482, 0x81}, + {0x4483, 0x81}, + {0x4484, 0x85}, + {0x4485, 0x85}, + {0x4486, 0x85}, + {0x4487, 0x85}, + {0x4488, 0x8E}, + {0x4489, 0x8D}, + {0x448A, 0x8D}, + {0x448B, 0x8E}, + {0x448C, 0x9D}, + {0x448D, 0x9C}, + {0x448E, 0x9C}, + {0x448F, 0x9C}, + {0x4490, 0x94}, + {0x4491, 0x9B}, + {0x4492, 0x9A}, + {0x4493, 0x97}, + {0x4494, 0x8A}, + {0x4495, 0x8E}, + {0x4496, 0x8E}, + {0x4497, 0x8C}, + {0x4498, 0x84}, + {0x4499, 0x86}, + {0x449A, 0x86}, + {0x449B, 0x84}, + {0x449C, 0x81}, + {0x449D, 0x83}, + {0x449E, 0x83}, + {0x449F, 0x81}, + {0x44A0, 0x81}, + {0x44A1, 0x82}, + {0x44A2, 0x82}, + {0x44A3, 0x81}, + {0x44A4, 0x83}, + {0x44A5, 0x83}, + {0x44A6, 0x83}, + {0x44A7, 0x83}, + {0x44A8, 0x88}, + {0x44A9, 0x88}, + {0x44AA, 0x88}, + {0x44AB, 0x88}, + {0x44AC, 0x91}, + {0x44AD, 0x91}, + {0x44AE, 0x91}, + {0x44AF, 0x92}, + {0x44B0, 0xA0}, + {0x44B1, 0xA0}, + {0x44B2, 0xA0}, + {0x44B3, 0xA0}, + {0x44B4, 0x9E}, + {0x44B5, 0xA9}, + {0x44B6, 0xA8}, + {0x44B7, 0xA3}, + {0x44B8, 0x90}, + {0x44B9, 0x95}, + {0x44BA, 0x95}, + {0x44BB, 0x92}, + {0x44BC, 0x8A}, + {0x44BD, 0x8E}, + {0x44BE, 0x8E}, + {0x44BF, 0x8B}, + {0x44C0, 0x84}, + {0x44C1, 0x86}, + {0x44C2, 0x86}, + {0x44C3, 0x84}, + {0x44C4, 0x84}, + {0x44C5, 0x85}, + {0x44C6, 0x85}, + {0x44C7, 0x84}, + {0x44C8, 0x86}, + {0x44C9, 0x87}, + {0x44CA, 0x87}, + {0x44CB, 0x86}, + {0x44CC, 0x8D}, + {0x44CD, 0x8E}, + {0x44CE, 0x8E}, + {0x44CF, 0x8D}, + {0x44D0, 0x98}, + {0x44D1, 0x98}, + {0x44D2, 0x99}, + {0x44D3, 0x9A}, + {0x44D4, 0xA9}, + {0x44D5, 0xAA}, + {0x44D6, 0xAA}, + {0x44D7, 0xAD}, + {0x4500, 0x9F}, + {0x4501, 0xA8}, + {0x4502, 0xA5}, + {0x4503, 0xAF}, + {0x4504, 0x8F}, + {0x4505, 0x96}, + {0x4506, 0x92}, + {0x4507, 0x94}, + {0x4508, 0x89}, + {0x4509, 0x8D}, + {0x450A, 0x8A}, + {0x450B, 0x8E}, + {0x450C, 0x84}, + {0x450D, 0x85}, + {0x450E, 0x84}, + {0x450F, 0x87}, + {0x4510, 0x84}, + {0x4511, 0x85}, + {0x4512, 0x84}, + {0x4513, 0x86}, + {0x4514, 0x87}, + {0x4515, 0x86}, + {0x4516, 0x86}, + {0x4517, 0x88}, + {0x4518, 0x8F}, + {0x4519, 0x8D}, + {0x451A, 0x8D}, + {0x451B, 0x8F}, + {0x451C, 0x9A}, + {0x451D, 0x9A}, + {0x451E, 0x98}, + {0x451F, 0x9A}, + {0x4520, 0xAF}, + {0x4521, 0xAF}, + {0x4522, 0xB2}, + {0x4523, 0xB1}, + {0x4524, 0x95}, + {0x4525, 0x9B}, + {0x4526, 0x97}, + {0x4527, 0x9C}, + {0x4528, 0x8A}, + {0x4529, 0x8E}, + {0x452A, 0x8D}, + {0x452B, 0x90}, + {0x452C, 0x84}, + {0x452D, 0x86}, + {0x452E, 0x85}, + {0x452F, 0x87}, + {0x4530, 0x81}, + {0x4531, 0x82}, + {0x4532, 0x82}, + {0x4533, 0x83}, + {0x4534, 0x81}, + {0x4535, 0x81}, + {0x4536, 0x81}, + {0x4537, 0x82}, + {0x4538, 0x84}, + {0x4539, 0x83}, + {0x453A, 0x83}, + {0x453B, 0x84}, + {0x453C, 0x8A}, + {0x453D, 0x88}, + {0x453E, 0x88}, + {0x453F, 0x89}, + {0x4540, 0x94}, + {0x4541, 0x92}, + {0x4542, 0x91}, + {0x4543, 0x92}, + {0x4544, 0xA1}, + {0x4545, 0xA0}, + {0x4546, 0x9C}, + {0x4547, 0x9D}, + {0x4548, 0x8F}, + {0x4549, 0x96}, + {0x454A, 0x95}, + {0x454B, 0x92}, + {0x454C, 0x87}, + {0x454D, 0x8A}, + {0x454E, 0x89}, + {0x454F, 0x8A}, + {0x4550, 0x81}, + {0x4551, 0x83}, + {0x4552, 0x82}, + {0x4553, 0x83}, + {0x4554, 0x7F}, + {0x4555, 0x80}, + {0x4556, 0x80}, + {0x4557, 0x81}, + {0x4558, 0x7F}, + {0x4559, 0x80}, + {0x455A, 0x7F}, + {0x455B, 0x80}, + {0x455C, 0x81}, + {0x455D, 0x81}, + {0x455E, 0x81}, + {0x455F, 0x81}, + {0x4560, 0x86}, + {0x4561, 0x85}, + {0x4562, 0x85}, + {0x4563, 0x85}, + {0x4564, 0x8F}, + {0x4565, 0x8D}, + {0x4566, 0x8D}, + {0x4567, 0x8D}, + {0x4568, 0x99}, + {0x4569, 0x9A}, + {0x456A, 0x97}, + {0x456B, 0x99}, + {0x456C, 0x90}, + {0x456D, 0x95}, + {0x456E, 0x93}, + {0x456F, 0x92}, + {0x4570, 0x87}, + {0x4571, 0x8A}, + {0x4572, 0x88}, + {0x4573, 0x87}, + {0x4574, 0x81}, + {0x4575, 0x83}, + {0x4576, 0x82}, + {0x4577, 0x82}, + {0x4578, 0x7F}, + {0x4579, 0x80}, + {0x457A, 0x80}, + {0x457B, 0x80}, + {0x457C, 0x80}, + {0x457D, 0x80}, + {0x457E, 0x80}, + {0x457F, 0x80}, + {0x4580, 0x81}, + {0x4581, 0x81}, + {0x4582, 0x81}, + {0x4583, 0x81}, + {0x4584, 0x85}, + {0x4585, 0x85}, + {0x4586, 0x84}, + {0x4587, 0x85}, + {0x4588, 0x8E}, + {0x4589, 0x8D}, + {0x458A, 0x8C}, + {0x458B, 0x8D}, + {0x458C, 0x9B}, + {0x458D, 0x9B}, + {0x458E, 0x9A}, + {0x458F, 0x98}, + {0x4590, 0x94}, + {0x4591, 0x9A}, + {0x4592, 0x94}, + {0x4593, 0x90}, + {0x4594, 0x8A}, + {0x4595, 0x8D}, + {0x4596, 0x8C}, + {0x4597, 0x89}, + {0x4598, 0x84}, + {0x4599, 0x86}, + {0x459A, 0x85}, + {0x459B, 0x83}, + {0x459C, 0x82}, + {0x459D, 0x83}, + {0x459E, 0x82}, + {0x459F, 0x80}, + {0x45A0, 0x81}, + {0x45A1, 0x82}, + {0x45A2, 0x81}, + {0x45A3, 0x80}, + {0x45A4, 0x83}, + {0x45A5, 0x83}, + {0x45A6, 0x83}, + {0x45A7, 0x83}, + {0x45A8, 0x88}, + {0x45A9, 0x87}, + {0x45AA, 0x87}, + {0x45AB, 0x88}, + {0x45AC, 0x91}, + {0x45AD, 0x90}, + {0x45AE, 0x90}, + {0x45AF, 0x91}, + {0x45B0, 0x9F}, + {0x45B1, 0x9F}, + {0x45B2, 0x9E}, + {0x45B3, 0x9F}, + {0x45B4, 0x9F}, + {0x45B5, 0xA8}, + {0x45B6, 0xA6}, + {0x45B7, 0xA7}, + {0x45B8, 0x8D}, + {0x45B9, 0x95}, + {0x45BA, 0x90}, + {0x45BB, 0x8A}, + {0x45BC, 0x89}, + {0x45BD, 0x8D}, + {0x45BE, 0x88}, + {0x45BF, 0x86}, + {0x45C0, 0x84}, + {0x45C1, 0x86}, + {0x45C2, 0x85}, + {0x45C3, 0x82}, + {0x45C4, 0x84}, + {0x45C5, 0x85}, + {0x45C6, 0x85}, + {0x45C7, 0x83}, + {0x45C8, 0x86}, + {0x45C9, 0x86}, + {0x45CA, 0x86}, + {0x45CB, 0x85}, + {0x45CC, 0x8E}, + {0x45CD, 0x8D}, + {0x45CE, 0x8D}, + {0x45CF, 0x8C}, + {0x45D0, 0x99}, + {0x45D1, 0x98}, + {0x45D2, 0x98}, + {0x45D3, 0x98}, + {0x45D4, 0xA6}, + {0x45D5, 0xA9}, + {0x45D6, 0xA7}, + {0x45D7, 0xAC}, +}; + +#endif diff --git a/drivers/media/i2c/imx390.c b/drivers/media/i2c/imx390.c new file mode 100644 index 000000000000..bb68227b93ad --- /dev/null +++ b/drivers/media/i2c/imx390.c @@ -0,0 +1,2046 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021-2024 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMX390_LINK_FREQ_360MHZ 360000000ULL +#define IMX390_LINK_FREQ_300MHZ 300000000ULL +#define IMX390_LINK_FREQ_288MHZ 288000000ULL +#define IMX390_LINK_FREQ_240MHZ 240000000ULL + +#define FIXED_POINT_SCALING_FACTOR (1ULL << 22) + +#define IMX390_REG_VALUE_08BIT 1 +#define IMX390_REG_VALUE_16BIT 2 + +#define IMX390_REG_CHIP_ID 0x0330 +#define IMX390_CHIP_ID 0x0 + +/* vertical-timings from sensor */ +#define IMX390_REG_VTS 0x300A +#define IMX390_VTS_MAX 0xffff + +#define IMX390_CLK_FREQ (1485000) + +/* Exposure controls from sensor */ +#define IMX390_REG_EXPOSURE 0x3012 +#define IMX390_EXPOSURE_MIN (30 * FIXED_POINT_SCALING_FACTOR / 1000000) +#define IMX390_EXPOSURE_MAX (33333 * FIXED_POINT_SCALING_FACTOR / 1000000) +#define IMX390_EXPOSURE_DEF (11000 * FIXED_POINT_SCALING_FACTOR / 1000000) +#define IMX390_EXPOSURE_STEP (1 * FIXED_POINT_SCALING_FACTOR / 1000000) + +/* Analog gain controls from sensor */ +#define IMX390_ANAL_GAIN_MIN 0 +#define IMX390_ANAL_GAIN_MAX 0x64 +#define IMX390_ANAL_GAIN_STEP 1 +#define IMX390_ANAL_GAIN_DEFAULT 0x1c + +/* Digital gain controls from sensor */ +#define IMX390_DGTL_GAIN_MIN 0 +#define IMX390_DGTL_GAIN_MAX 0x7ff +#define IMX390_DGTL_GAIN_STEP 1 +#define IMX390_DGTL_GAIN_DEFAULT 0x80 + +#define IMX390_GAIN_MIN 0 +#define IMX390_GAIN_DEFAULT 0x80 + +#define IMX390_RED_BALANCE_MIN 0 +#define IMX390_RED_BALANCE_MAX 0xfff +#define IMX390_RED_BALANCE_STEP 1 +#define IMX390_RED_BALANCE_DEF 0x100 + +#define IMX390_BLUE_BALANCE_MIN 0 +#define IMX390_BLUE_BALANCE_MAX 0xfff +#define IMX390_BLUE_BALANCE_STEP 1 +#define IMX390_BLUE_BALANCE_DEF 0x100 + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define IMX390_CID_CSI_PORT (V4L2_CID_USER_BASE | 0x1001) +#define IMX390_CID_I2C_BUS (V4L2_CID_USER_BASE | 0x1002) +#define IMX390_CID_I2C_ID (V4L2_CID_USER_BASE | 0x1003) +#define IMX390_CID_I2C_SLAVE_ADDRESS (V4L2_CID_USER_BASE | 0x1004) +#define IMX390_CID_FPS (V4L2_CID_USER_BASE | 0x1005) +#define IMX390_CID_FRAME_INTERVAL (V4L2_CID_USER_BASE | 0x1006) + +/* + * dummy CID + */ +#define V4L2_CID_IMX390_BASE (V4L2_CID_USER_BASE + 0x2050) + +#define V4L2_CID_FRAME_LENGTH_LINES (V4L2_CID_IMX390_BASE + 1) +#define V4L2_CID_LINE_LENGTH_PIXELS (V4L2_CID_IMX390_BASE + 2) +#define IMX390_CID_SENSOR_THERMAL_DATA (V4L2_CID_IMX390_BASE + 3) + +/* + * Select sensor mode directly, driver programs media pad + * formats as in configuration file + */ +#define IMX390_CID_SENSOR_MODE (V4L2_CID_IMX390_BASE + 4) + +/* IMX230 HDR specific controls */ +#define IMX390_CID_IMX230_HDR_MODE (V4L2_CID_IMX390_BASE + 5) +#define IMX390_CID_IMX230_HDR_ET_RATIO (V4L2_CID_IMX390_BASE + 6) + +/* Set multi-exposure frame in HDR with different exposure value */ +#define IMX390_CID_EXPOSURE_SHS1 (V4L2_CID_IMX390_BASE + 8) +#define IMX390_CID_EXPOSURE_SHS2 (V4L2_CID_IMX390_BASE + 9) +#define IMX390_CID_EXPOSURE_SHS3 (V4L2_CID_IMX390_BASE + 10) +#define IMX390_CID_EXPOSURE_RHS1 (V4L2_CID_IMX390_BASE + 11) +#define IMX390_CID_EXPOSURE_RHS2 (V4L2_CID_IMX390_BASE + 12) + +/* Switch to enable/disable PDAF settings */ +#define IMX390_CID_SENSOR_PDAF (V4L2_CID_IMX390_BASE + 13) + +/* Set multi-digital gain */ +#define IMX390_CID_DIGITAL_GAIN_L (V4L2_CID_IMX390_BASE + 14) +#define IMX390_CID_DIGITAL_GAIN_S (V4L2_CID_IMX390_BASE + 15) +#define IMX390_CID_DIGITAL_GAIN_VS (V4L2_CID_IMX390_BASE + 16) + +/* Get sensor bit linear */ +#define IMX390_CID_SENSOR_BIT_LINEAR (V4L2_CID_IMX390_BASE + 17) + +/* set sensor msb align*/ +#define IMX390_CID_MSB_ALIGN (V4L2_CID_IMX390_BASE + 18) + +/* enable/disable auto exposure */ +#define IMX390_CID_AUTO_EXPOSURE_DEBUG (V4L2_CID_IMX390_BASE + 19) + +/* set analog gain for HDR frames */ +#define IMX390_CID_ANALOG_GAIN_L (V4L2_CID_IMX390_BASE + 20) +#define IMX390_CID_ANALOG_GAIN_S (V4L2_CID_IMX390_BASE + 21) +#define IMX390_CID_ANALOG_GAIN_VS (V4L2_CID_IMX390_BASE + 22) + +/* Set exposure mode: Linear mode or 2-/3-/4-HDR mode */ +#define IMX390_CID_EXPOSURE_MODE (V4L2_CID_IMX390_BASE + 23) + +/* Set HDR mode exposure ratio */ +#define IMX390_CID_EXPOSURE_HDR_RATIO (V4L2_CID_IMX390_BASE + 24) + +/* choose hcg/lcg for linear analog */ +#define IMX390_CID_ANALOG_LINEAR_CG (V4L2_CID_IMX390_BASE + 25) + +/* Digital gain controls from sensor */ +#define IMX390_DUMMY_MIN 0 +#define IMX390_DUMMY_MAX 0x7ff +#define IMX390_DUMMY_STEP 1 +#define IMX390_DUMMY_DEF 0x1 + +/* + * end of dummy CID + */ + +#define to_imx390(_sd) container_of(_sd, struct imx390, sd) + +/** + * Register addresses (see data sheet/register map) + */ +enum { + IMX390_REG_STANDBY = 0x0000, + IMX390_REG_REG_HOLD = 0x0008, + IMX390_REG_SHS1 = 0x000c, + IMX390_REG_SHS2 = 0x0010, + IMX390_REG_AGAIN_SP1H = 0x0018, + IMX390_REG_AGAIN_SP1L = 0x001a, + + /* default bayer order RGGB */ + IMX390_REG_WBGAIN_R = 0x0030, + IMX390_REG_WBGAIN_GR = 0x0032, + IMX390_REG_WBGAIN_GB = 0x0034, + IMX390_REG_WBGAIN_B = 0x0036, + + IMX390_REG_OBB_CLAMP_CTRL_SEL = 0x0083, + IMX390_REG_REAR_EMBDATA_LINE = 0x2E18, + IMX390_REG_REV1 = 0x3060, + IMX390_REG_REV2 = 0x3064, + IMX390_REG_REV3 = 0x3067, + IMX390_REG_WBGAIN_FORCE_X1 = 0x36A8, +}; + +enum { + IMX390_LINK_FREQ_360MBPS, + IMX390_LINK_FREQ_300MBPS, + IMX390_LINK_FREQ_288MBPS, + IMX390_LINK_FREQ_240MBPS, +}; + +struct imx390_reg { + u16 address; + u16 val; +}; + +struct imx390_reg_list { + u32 num_of_regs; + const struct imx390_reg *regs; +}; + +struct imx390_link_freq_config { + const struct imx390_reg_list reg_list; +}; + +struct imx390_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + bool hdr_en; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* MEDIA_BUS_FMT */ + u32 code; + + /* MIPI_LANES */ + s32 lanes; + + /* MODE_FPS*/ + u32 fps; + + /* bit per pixel */ + u32 bpp; + + /* Sensor register settings for this resolution */ + const struct imx390_reg_list reg_list; +}; + +struct imx390 { + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *mipi_lanes; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *analogue_gain; + struct v4l2_ctrl *digital_gain; + struct v4l2_ctrl *gain; + struct v4l2_ctrl *strobe_source; + struct v4l2_ctrl *strobe; + struct v4l2_ctrl *strobe_stop; + struct v4l2_ctrl *timeout; + struct v4l2_ctrl *csi_port; + struct v4l2_ctrl *i2c_bus; + struct v4l2_ctrl *i2c_id; + struct v4l2_ctrl *i2c_slave_address; + struct v4l2_ctrl *fps; + struct v4l2_ctrl *frame_interval; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *red_balance; + struct v4l2_ctrl *blue_balance; + struct v4l2_ctrl *dummy_exp_shs1; + struct v4l2_ctrl *dummy_exp_shs2; + struct v4l2_ctrl *dummy_exp_shs3; + struct v4l2_ctrl *dummy_exp_rhs1; + struct v4l2_ctrl *dummy_exp_rhs2; + struct v4l2_ctrl *dummy_dg_l; + struct v4l2_ctrl *dummy_dg_s; + struct v4l2_ctrl *dummy_dg_vs; + struct v4l2_ctrl *dummy_ag_l; + struct v4l2_ctrl *dummy_ag_s; + struct v4l2_ctrl *dummy_ag_vs; + struct v4l2_ctrl *lsc_pattern; + + /* Current mode */ + const struct imx390_mode *cur_mode; + /* Previous mode */ + const struct imx390_mode *pre_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; + + struct imx390_platform_data *platform_data; +}; + +#include "imx390-mode-1280x960-CROP.h" +#include "imx390_mode_1920x1200HDR3_CUST_PWL12.h" + +static int imx390_group_hold_enable(struct imx390 *imx390, s32 val); + +enum { + LSC_PATTERN_DISABLED = 0, + LSC_PATTERN_UNITY, + LSC_PATTERN_TABLE, +}; + +static const char * const lsc_qmenu[] = { + "Disabled", + "all 0x80", + "table", +}; + +static const struct imx390_reg imx390_lsc_pattern_unity[] = { + {0x4000, 0x80}, + {0x4001, 0x80}, + {0x4002, 0x80}, + {0x4003, 0x80}, + {0x4004, 0x80}, + {0x4005, 0x80}, + {0x4006, 0x80}, + {0x4007, 0x80}, + {0x4008, 0x80}, + {0x4009, 0x80}, + {0x400A, 0x80}, + {0x400B, 0x80}, + {0x400C, 0x80}, + {0x400D, 0x80}, + {0x400E, 0x80}, + {0x400F, 0x80}, + {0x4010, 0x80}, + {0x4011, 0x80}, + {0x4012, 0x80}, + {0x4013, 0x80}, + {0x4014, 0x80}, + {0x4015, 0x80}, + {0x4016, 0x80}, + {0x4017, 0x80}, + {0x4018, 0x80}, + {0x4019, 0x80}, + {0x401A, 0x80}, + {0x401B, 0x80}, + {0x401C, 0x80}, + {0x401D, 0x80}, + {0x401E, 0x80}, + {0x401F, 0x80}, + {0x4020, 0x80}, + {0x4021, 0x80}, + {0x4022, 0x80}, + {0x4023, 0x80}, + {0x4024, 0x80}, + {0x4025, 0x80}, + {0x4026, 0x80}, + {0x4027, 0x80}, + {0x4028, 0x80}, + {0x4029, 0x80}, + {0x402A, 0x80}, + {0x402B, 0x80}, + {0x402C, 0x80}, + {0x402D, 0x80}, + {0x402E, 0x80}, + {0x402F, 0x80}, + {0x4030, 0x80}, + {0x4031, 0x80}, + {0x4032, 0x80}, + {0x4033, 0x80}, + {0x4034, 0x80}, + {0x4035, 0x80}, + {0x4036, 0x80}, + {0x4037, 0x80}, + {0x4038, 0x80}, + {0x4039, 0x80}, + {0x403A, 0x80}, + {0x403B, 0x80}, + {0x403C, 0x80}, + {0x403D, 0x80}, + {0x403E, 0x80}, + {0x403F, 0x80}, + {0x4040, 0x80}, + {0x4041, 0x80}, + {0x4042, 0x80}, + {0x4043, 0x80}, + {0x4044, 0x80}, + {0x4045, 0x80}, + {0x4046, 0x80}, + {0x4047, 0x80}, + {0x4048, 0x80}, + {0x4049, 0x80}, + {0x404A, 0x80}, + {0x404B, 0x80}, + {0x404C, 0x80}, + {0x404D, 0x80}, + {0x404E, 0x80}, + {0x404F, 0x80}, + {0x4050, 0x80}, + {0x4051, 0x80}, + {0x4052, 0x80}, + {0x4053, 0x80}, + {0x4054, 0x80}, + {0x4055, 0x80}, + {0x4056, 0x80}, + {0x4057, 0x80}, + {0x4058, 0x80}, + {0x4059, 0x80}, + {0x405A, 0x80}, + {0x405B, 0x80}, + {0x405C, 0x80}, + {0x405D, 0x80}, + {0x405E, 0x80}, + {0x405F, 0x80}, + {0x4060, 0x80}, + {0x4061, 0x80}, + {0x4062, 0x80}, + {0x4063, 0x80}, + {0x4064, 0x80}, + {0x4065, 0x80}, + {0x4066, 0x80}, + {0x4067, 0x80}, + {0x4068, 0x80}, + {0x4069, 0x80}, + {0x406A, 0x80}, + {0x406B, 0x80}, + {0x406C, 0x80}, + {0x406D, 0x80}, + {0x406E, 0x80}, + {0x406F, 0x80}, + {0x4070, 0x80}, + {0x4071, 0x80}, + {0x4072, 0x80}, + {0x4073, 0x80}, + {0x4074, 0x80}, + {0x4075, 0x80}, + {0x4076, 0x80}, + {0x4077, 0x80}, + {0x4078, 0x80}, + {0x4079, 0x80}, + {0x407A, 0x80}, + {0x407B, 0x80}, + {0x407C, 0x80}, + {0x407D, 0x80}, + {0x407E, 0x80}, + {0x407F, 0x80}, + {0x4080, 0x80}, + {0x4081, 0x80}, + {0x4082, 0x80}, + {0x4083, 0x80}, + {0x4084, 0x80}, + {0x4085, 0x80}, + {0x4086, 0x80}, + {0x4087, 0x80}, + {0x4088, 0x80}, + {0x4089, 0x80}, + {0x408A, 0x80}, + {0x408B, 0x80}, + {0x408C, 0x80}, + {0x408D, 0x80}, + {0x408E, 0x80}, + {0x408F, 0x80}, + {0x4090, 0x80}, + {0x4091, 0x80}, + {0x4092, 0x80}, + {0x4093, 0x80}, + {0x4094, 0x80}, + {0x4095, 0x80}, + {0x4096, 0x80}, + {0x4097, 0x80}, + {0x4098, 0x80}, + {0x4099, 0x80}, + {0x409A, 0x80}, + {0x409B, 0x80}, + {0x409C, 0x80}, + {0x409D, 0x80}, + {0x409E, 0x80}, + {0x409F, 0x80}, + {0x40A0, 0x80}, + {0x40A1, 0x80}, + {0x40A2, 0x80}, + {0x40A3, 0x80}, + {0x40A4, 0x80}, + {0x40A5, 0x80}, + {0x40A6, 0x80}, + {0x40A7, 0x80}, + {0x40A8, 0x80}, + {0x40A9, 0x80}, + {0x40AA, 0x80}, + {0x40AB, 0x80}, + {0x40AC, 0x80}, + {0x40AD, 0x80}, + {0x40AE, 0x80}, + {0x40AF, 0x80}, + {0x40B0, 0x80}, + {0x40B1, 0x80}, + {0x40B2, 0x80}, + {0x40B3, 0x80}, + {0x40B4, 0x80}, + {0x40B5, 0x80}, + {0x40B6, 0x80}, + {0x40B7, 0x80}, + {0x40B8, 0x80}, + {0x40B9, 0x80}, + {0x40BA, 0x80}, + {0x40BB, 0x80}, + {0x40BC, 0x80}, + {0x40BD, 0x80}, + {0x40BE, 0x80}, + {0x40BF, 0x80}, + {0x40C0, 0x80}, + {0x40C1, 0x80}, + {0x40C2, 0x80}, + {0x40C3, 0x80}, + {0x40C4, 0x80}, + {0x40C5, 0x80}, + {0x40C6, 0x80}, + {0x40C7, 0x80}, + {0x40C8, 0x80}, + {0x40C9, 0x80}, + {0x40CA, 0x80}, + {0x40CB, 0x80}, + {0x40CC, 0x80}, + {0x40CD, 0x80}, + {0x40CE, 0x80}, + {0x40CF, 0x80}, + {0x40D0, 0x80}, + {0x40D1, 0x80}, + {0x40D2, 0x80}, + {0x40D3, 0x80}, + {0x40D4, 0x80}, + {0x40D5, 0x80}, + {0x40D6, 0x80}, + {0x40D7, 0x80}, +}; + +static const struct imx390_reg_list lsc_unity_list = { + .num_of_regs = ARRAY_SIZE(imx390_lsc_pattern_unity), + .regs = imx390_lsc_pattern_unity, +}; + +static const struct imx390_reg imx390_lsc_pattern_vendor_def[] = { + {0x01D0, 0x01}, /* SHD_ON */ + {0x3AF6, 0x00}, /* SHD_DIFF_ACCURACY */ + {0x4000, 0x64}, /* SHD_KNOT_1_R0 */ + {0x4001, 0x5D}, /* SHD_KNOT_1_GR0 */ + {0x4002, 0x5D}, /* SHD_KNOT_1_GB0 */ + {0x4003, 0x5B}, /* SHD_KNOT_1_B0 */ + {0x4004, 0x6D}, /* SHD_KNOT_1_R1 */ + {0x4005, 0x69}, /* SHD_KNOT_1_GR1 */ + {0x4006, 0x69}, /* SHD_KNOT_1_GB1 */ + {0x4007, 0x67}, /* SHD_KNOT_1_B1 */ + {0x4008, 0x74}, /* SHD_KNOT_1_R2 */ + {0x4009, 0x71}, /* SHD_KNOT_1_GR2 */ + {0x400A, 0x71}, /* SHD_KNOT_1_GB2 */ + {0x400B, 0x70}, /* SHD_KNOT_1_B2 */ + {0x400C, 0x77}, /* SHD_KNOT_1_R3 */ + {0x400D, 0x76}, /* SHD_KNOT_1_GR3 */ + {0x400E, 0x76}, /* SHD_KNOT_1_GB3 */ + {0x400F, 0x75}, /* SHD_KNOT_1_B3 */ + {0x4010, 0x78}, /* SHD_KNOT_1_R4 */ + {0x4011, 0x77}, /* SHD_KNOT_1_GR4 */ + {0x4012, 0x77}, /* SHD_KNOT_1_GB4 */ + {0x4013, 0x76}, /* SHD_KNOT_1_B4 */ + {0x4014, 0x77}, /* SHD_KNOT_1_R5 */ + {0x4015, 0x75}, /* SHD_KNOT_1_GR5 */ + {0x4016, 0x75}, /* SHD_KNOT_1_GB5 */ + {0x4017, 0x74}, /* SHD_KNOT_1_B5 */ + {0x4018, 0x72}, /* SHD_KNOT_1_R6 */ + {0x4019, 0x70}, /* SHD_KNOT_1_GR6 */ + {0x401A, 0x70}, /* SHD_KNOT_1_GB6 */ + {0x401B, 0x6F}, /* SHD_KNOT_1_B6 */ + {0x401C, 0x6B}, /* SHD_KNOT_1_R7 */ + {0x401D, 0x67}, /* SHD_KNOT_1_GR7 */ + {0x401E, 0x67}, /* SHD_KNOT_1_GB7 */ + {0x401F, 0x66}, /* SHD_KNOT_1_B7 */ + {0x4020, 0x61}, /* SHD_KNOT_1_R8 */ + {0x4021, 0x5B}, /* SHD_KNOT_1_GR8 */ + {0x4022, 0x5B}, /* SHD_KNOT_1_GB8 */ + {0x4023, 0x59}, /* SHD_KNOT_1_B8 */ + {0x4024, 0x69}, /* SHD_KNOT_1_R9 */ + {0x4025, 0x63}, /* SHD_KNOT_1_GR9 */ + {0x4026, 0x63}, /* SHD_KNOT_1_GB9 */ + {0x4027, 0x62}, /* SHD_KNOT_1_B9 */ + {0x4028, 0x71}, /* SHD_KNOT_1_R10 */ + {0x4029, 0x6E}, /* SHD_KNOT_1_GR10 */ + {0x402A, 0x6E}, /* SHD_KNOT_1_GB10 */ + {0x402B, 0x6D}, /* SHD_KNOT_1_B10 */ + {0x402C, 0x78}, /* SHD_KNOT_1_R11 */ + {0x402D, 0x76}, /* SHD_KNOT_1_GR11 */ + {0x402E, 0x76}, /* SHD_KNOT_1_GB11 */ + {0x402F, 0x76}, /* SHD_KNOT_1_B11 */ + {0x4030, 0x7C}, /* SHD_KNOT_1_R12 */ + {0x4031, 0x7B}, /* SHD_KNOT_1_GR12 */ + {0x4032, 0x7B}, /* SHD_KNOT_1_GB12 */ + {0x4033, 0x7B}, /* SHD_KNOT_1_B12 */ + {0x4034, 0x7D}, /* SHD_KNOT_1_R13 */ + {0x4035, 0x7D}, /* SHD_KNOT_1_GR13 */ + {0x4036, 0x7D}, /* SHD_KNOT_1_GB13 */ + {0x4037, 0x7C}, /* SHD_KNOT_1_B13 */ + {0x4038, 0x7B}, /* SHD_KNOT_1_R14 */ + {0x4039, 0x7B}, /* SHD_KNOT_1_GR14 */ + {0x403A, 0x7B}, /* SHD_KNOT_1_GB14 */ + {0x403B, 0x7A}, /* SHD_KNOT_1_B14 */ + {0x403C, 0x77}, /* SHD_KNOT_1_R15 */ + {0x403D, 0x75}, /* SHD_KNOT_1_GR15 */ + {0x403E, 0x76}, /* SHD_KNOT_1_GB15 */ + {0x403F, 0x75}, /* SHD_KNOT_1_B15 */ + {0x4040, 0x70}, /* SHD_KNOT_1_R16 */ + {0x4041, 0x6D}, /* SHD_KNOT_1_GR16 */ + {0x4042, 0x6D}, /* SHD_KNOT_1_GB16 */ + {0x4043, 0x6C}, /* SHD_KNOT_1_B16 */ + {0x4044, 0x67}, /* SHD_KNOT_1_R17 */ + {0x4045, 0x61}, /* SHD_KNOT_1_GR17 */ + {0x4046, 0x61}, /* SHD_KNOT_1_GB17 */ + {0x4047, 0x60}, /* SHD_KNOT_1_B17 */ + {0x4048, 0x6B}, /* SHD_KNOT_1_R18 */ + {0x4049, 0x66}, /* SHD_KNOT_1_GR18 */ + {0x404A, 0x66}, /* SHD_KNOT_1_GB18 */ + {0x404B, 0x65}, /* SHD_KNOT_1_B18 */ + {0x404C, 0x74}, /* SHD_KNOT_1_R19 */ + {0x404D, 0x71}, /* SHD_KNOT_1_GR19 */ + {0x404E, 0x71}, /* SHD_KNOT_1_GB19 */ + {0x404F, 0x70}, /* SHD_KNOT_1_B19 */ + {0x4050, 0x7B}, /* SHD_KNOT_1_R20 */ + {0x4051, 0x7A}, /* SHD_KNOT_1_GR20 */ + {0x4052, 0x7A}, /* SHD_KNOT_1_GB20 */ + {0x4053, 0x79}, /* SHD_KNOT_1_B20 */ + {0x4054, 0x7F}, /* SHD_KNOT_1_R21 */ + {0x4055, 0x7E}, /* SHD_KNOT_1_GR21 */ + {0x4056, 0x7E}, /* SHD_KNOT_1_GB21 */ + {0x4057, 0x7E}, /* SHD_KNOT_1_B21 */ + {0x4058, 0x80}, /* SHD_KNOT_1_R22 */ + {0x4059, 0x80}, /* SHD_KNOT_1_GR22 */ + {0x405A, 0x80}, /* SHD_KNOT_1_GB22 */ + {0x405B, 0x80}, /* SHD_KNOT_1_B22 */ + {0x405C, 0x7E}, /* SHD_KNOT_1_R23 */ + {0x405D, 0x7E}, /* SHD_KNOT_1_GR23 */ + {0x405E, 0x7E}, /* SHD_KNOT_1_GB23 */ + {0x405F, 0x7D}, /* SHD_KNOT_1_B23 */ + {0x4060, 0x7A}, /* SHD_KNOT_1_R24 */ + {0x4061, 0x79}, /* SHD_KNOT_1_GR24 */ + {0x4062, 0x79}, /* SHD_KNOT_1_GB24 */ + {0x4063, 0x78}, /* SHD_KNOT_1_B24 */ + {0x4064, 0x73}, /* SHD_KNOT_1_R25 */ + {0x4065, 0x70}, /* SHD_KNOT_1_GR25 */ + {0x4066, 0x70}, /* SHD_KNOT_1_GB25 */ + {0x4067, 0x6F}, /* SHD_KNOT_1_B25 */ + {0x4068, 0x69}, /* SHD_KNOT_1_R26 */ + {0x4069, 0x64}, /* SHD_KNOT_1_GR26 */ + {0x406A, 0x64}, /* SHD_KNOT_1_GB26 */ + {0x406B, 0x63}, /* SHD_KNOT_1_B26 */ + {0x406C, 0x6B}, /* SHD_KNOT_1_R27 */ + {0x406D, 0x66}, /* SHD_KNOT_1_GR27 */ + {0x406E, 0x66}, /* SHD_KNOT_1_GB27 */ + {0x406F, 0x65}, /* SHD_KNOT_1_B27 */ + {0x4070, 0x74}, /* SHD_KNOT_1_R28 */ + {0x4071, 0x71}, /* SHD_KNOT_1_GR28 */ + {0x4072, 0x71}, /* SHD_KNOT_1_GB28 */ + {0x4073, 0x70}, /* SHD_KNOT_1_B28 */ + {0x4074, 0x7B}, /* SHD_KNOT_1_R29 */ + {0x4075, 0x79}, /* SHD_KNOT_1_GR29 */ + {0x4076, 0x79}, /* SHD_KNOT_1_GB29 */ + {0x4077, 0x79}, /* SHD_KNOT_1_B29 */ + {0x4078, 0x7E}, /* SHD_KNOT_1_R30 */ + {0x4079, 0x7D}, /* SHD_KNOT_1_GR30 */ + {0x407A, 0x7D}, /* SHD_KNOT_1_GB30 */ + {0x407B, 0x7D}, /* SHD_KNOT_1_B30 */ + {0x407C, 0x7F}, /* SHD_KNOT_1_R31 */ + {0x407D, 0x7F}, /* SHD_KNOT_1_GR31 */ + {0x407E, 0x7F}, /* SHD_KNOT_1_GB31 */ + {0x407F, 0x7F}, /* SHD_KNOT_1_B31 */ + {0x4080, 0x7E}, /* SHD_KNOT_1_R32 */ + {0x4081, 0x7D}, /* SHD_KNOT_1_GR32 */ + {0x4082, 0x7D}, /* SHD_KNOT_1_GB32 */ + {0x4083, 0x7D}, /* SHD_KNOT_1_B32 */ + {0x4084, 0x7A}, /* SHD_KNOT_1_R33 */ + {0x4085, 0x78}, /* SHD_KNOT_1_GR33 */ + {0x4086, 0x78}, /* SHD_KNOT_1_GB33 */ + {0x4087, 0x78}, /* SHD_KNOT_1_B33 */ + {0x4088, 0x73}, /* SHD_KNOT_1_R34 */ + {0x4089, 0x70}, /* SHD_KNOT_1_GR34 */ + {0x408A, 0x70}, /* SHD_KNOT_1_GB34 */ + {0x408B, 0x6F}, /* SHD_KNOT_1_B34 */ + {0x408C, 0x69}, /* SHD_KNOT_1_R35 */ + {0x408D, 0x64}, /* SHD_KNOT_1_GR35 */ + {0x408E, 0x64}, /* SHD_KNOT_1_GB35 */ + {0x408F, 0x63}, /* SHD_KNOT_1_B35 */ + {0x4090, 0x68}, /* SHD_KNOT_1_R36 */ + {0x4091, 0x62}, /* SHD_KNOT_1_GR36 */ + {0x4092, 0x62}, /* SHD_KNOT_1_GB36 */ + {0x4093, 0x61}, /* SHD_KNOT_1_B36 */ + {0x4094, 0x71}, /* SHD_KNOT_1_R37 */ + {0x4095, 0x6D}, /* SHD_KNOT_1_GR37 */ + {0x4096, 0x6D}, /* SHD_KNOT_1_GB37 */ + {0x4097, 0x6D}, /* SHD_KNOT_1_B37 */ + {0x4098, 0x77}, /* SHD_KNOT_1_R38 */ + {0x4099, 0x75}, /* SHD_KNOT_1_GR38 */ + {0x409A, 0x75}, /* SHD_KNOT_1_GB38 */ + {0x409B, 0x75}, /* SHD_KNOT_1_B38 */ + {0x409C, 0x7B}, /* SHD_KNOT_1_R39 */ + {0x409D, 0x7A}, /* SHD_KNOT_1_GR39 */ + {0x409E, 0x7A}, /* SHD_KNOT_1_GB39 */ + {0x409F, 0x7A}, /* SHD_KNOT_1_B39 */ + {0x40A0, 0x7C}, /* SHD_KNOT_1_R40 */ + {0x40A1, 0x7B}, /* SHD_KNOT_1_GR40 */ + {0x40A2, 0x7B}, /* SHD_KNOT_1_GB40 */ + {0x40A3, 0x7B}, /* SHD_KNOT_1_B40 */ + {0x40A4, 0x7B}, /* SHD_KNOT_1_R41 */ + {0x40A5, 0x79}, /* SHD_KNOT_1_GR41 */ + {0x40A6, 0x79}, /* SHD_KNOT_1_GB41 */ + {0x40A7, 0x79}, /* SHD_KNOT_1_B41 */ + {0x40A8, 0x77}, /* SHD_KNOT_1_R42 */ + {0x40A9, 0x74}, /* SHD_KNOT_1_GR42 */ + {0x40AA, 0x74}, /* SHD_KNOT_1_GB42 */ + {0x40AB, 0x74}, /* SHD_KNOT_1_B42 */ + {0x40AC, 0x70}, /* SHD_KNOT_1_R43 */ + {0x40AD, 0x6C}, /* SHD_KNOT_1_GR43 */ + {0x40AE, 0x6C}, /* SHD_KNOT_1_GB43 */ + {0x40AF, 0x6C}, /* SHD_KNOT_1_B43 */ + {0x40B0, 0x66}, /* SHD_KNOT_1_R44 */ + {0x40B1, 0x60}, /* SHD_KNOT_1_GR44 */ + {0x40B2, 0x60}, /* SHD_KNOT_1_GB44 */ + {0x40B3, 0x5F}, /* SHD_KNOT_1_B44 */ + {0x40B4, 0x62}, /* SHD_KNOT_1_R45 */ + {0x40B5, 0x5B}, /* SHD_KNOT_1_GR45 */ + {0x40B6, 0x5B}, /* SHD_KNOT_1_GB45 */ + {0x40B7, 0x5A}, /* SHD_KNOT_1_B45 */ + {0x40B8, 0x6B}, /* SHD_KNOT_1_R46 */ + {0x40B9, 0x66}, /* SHD_KNOT_1_GR46 */ + {0x40BA, 0x66}, /* SHD_KNOT_1_GB46 */ + {0x40BB, 0x65}, /* SHD_KNOT_1_B46 */ + {0x40BC, 0x71}, /* SHD_KNOT_1_R47 */ + {0x40BD, 0x6E}, /* SHD_KNOT_1_GR47 */ + {0x40BE, 0x6E}, /* SHD_KNOT_1_GB47 */ + {0x40BF, 0x6D}, /* SHD_KNOT_1_B47 */ + {0x40C0, 0x75}, /* SHD_KNOT_1_R48 */ + {0x40C1, 0x72}, /* SHD_KNOT_1_GR48 */ + {0x40C2, 0x72}, /* SHD_KNOT_1_GB48 */ + {0x40C3, 0x72}, /* SHD_KNOT_1_B48 */ + {0x40C4, 0x76}, /* SHD_KNOT_1_R49 */ + {0x40C5, 0x74}, /* SHD_KNOT_1_GR49 */ + {0x40C6, 0x74}, /* SHD_KNOT_1_GB49 */ + {0x40C7, 0x74}, /* SHD_KNOT_1_B49 */ + {0x40C8, 0x75}, /* SHD_KNOT_1_R50 */ + {0x40C9, 0x72}, /* SHD_KNOT_1_GR50 */ + {0x40CA, 0x72}, /* SHD_KNOT_1_GB50 */ + {0x40CB, 0x72}, /* SHD_KNOT_1_B50 */ + {0x40CC, 0x71}, /* SHD_KNOT_1_R51 */ + {0x40CD, 0x6D}, /* SHD_KNOT_1_GR51 */ + {0x40CE, 0x6D}, /* SHD_KNOT_1_GB51 */ + {0x40CF, 0x6D}, /* SHD_KNOT_1_B51 */ + {0x40D0, 0x6A}, /* SHD_KNOT_1_R52 */ + {0x40D1, 0x64}, /* SHD_KNOT_1_GR52 */ + {0x40D2, 0x64}, /* SHD_KNOT_1_GB52 */ + {0x40D3, 0x64}, /* SHD_KNOT_1_B52 */ + {0x40D4, 0x61}, /* SHD_KNOT_1_R53 */ + {0x40D5, 0x59}, /* SHD_KNOT_1_GR53 */ + {0x40D6, 0x59}, /* SHD_KNOT_1_GB53 */ + {0x40D7, 0x58}, /* SHD_KNOT_1_B53 */ + {0x4300, 0x8E}, /* SHD_KNOT_DIFF_1_R0 */ + {0x4301, 0x8B}, /* SHD_KNOT_DIFF_1_GR0 */ + {0x4302, 0x8B}, /* SHD_KNOT_DIFF_1_GB0 */ + {0x4303, 0x8C}, /* SHD_KNOT_DIFF_1_B0 */ + {0x4304, 0x88}, /* SHD_KNOT_DIFF_1_R1 */ + {0x4305, 0x88}, /* SHD_KNOT_DIFF_1_GR1 */ + {0x4306, 0x88}, /* SHD_KNOT_DIFF_1_GB1 */ + {0x4307, 0x88}, /* SHD_KNOT_DIFF_1_B1 */ + {0x4308, 0x88}, /* SHD_KNOT_DIFF_1_R2 */ + {0x4309, 0x84}, /* SHD_KNOT_DIFF_1_GR2 */ + {0x430A, 0x84}, /* SHD_KNOT_DIFF_1_GB2 */ + {0x430B, 0x85}, /* SHD_KNOT_DIFF_1_B2 */ + {0x430C, 0x83}, /* SHD_KNOT_DIFF_1_R3 */ + {0x430D, 0x82}, /* SHD_KNOT_DIFF_1_GR3 */ + {0x430E, 0x82}, /* SHD_KNOT_DIFF_1_GB3 */ + {0x430F, 0x82}, /* SHD_KNOT_DIFF_1_B3 */ + {0x4310, 0x84}, /* SHD_KNOT_DIFF_1_R4 */ + {0x4311, 0x83}, /* SHD_KNOT_DIFF_1_GR4 */ + {0x4312, 0x83}, /* SHD_KNOT_DIFF_1_GB4 */ + {0x4313, 0x83}, /* SHD_KNOT_DIFF_1_B4 */ + {0x4314, 0x84}, /* SHD_KNOT_DIFF_1_R5 */ + {0x4315, 0x85}, /* SHD_KNOT_DIFF_1_GR5 */ + {0x4316, 0x85}, /* SHD_KNOT_DIFF_1_GB5 */ + {0x4317, 0x84}, /* SHD_KNOT_DIFF_1_B5 */ + {0x4318, 0x86}, /* SHD_KNOT_DIFF_1_R6 */ + {0x4319, 0x87}, /* SHD_KNOT_DIFF_1_GR6 */ + {0x431A, 0x87}, /* SHD_KNOT_DIFF_1_GB6 */ + {0x431B, 0x87}, /* SHD_KNOT_DIFF_1_B6 */ + {0x431C, 0x8D}, /* SHD_KNOT_DIFF_1_R7 */ + {0x431D, 0x8B}, /* SHD_KNOT_DIFF_1_GR7 */ + {0x431E, 0x8B}, /* SHD_KNOT_DIFF_1_GB7 */ + {0x431F, 0x8C}, /* SHD_KNOT_DIFF_1_B7 */ + {0x4320, 0x93}, /* SHD_KNOT_DIFF_1_R8 */ + {0x4321, 0x91}, /* SHD_KNOT_DIFF_1_GR8 */ + {0x4322, 0x90}, /* SHD_KNOT_DIFF_1_GB8 */ + {0x4323, 0x91}, /* SHD_KNOT_DIFF_1_B8 */ + {0x4324, 0x8A}, /* SHD_KNOT_DIFF_1_R9 */ + {0x4325, 0x89}, /* SHD_KNOT_DIFF_1_GR9 */ + {0x4326, 0x89}, /* SHD_KNOT_DIFF_1_GB9 */ + {0x4327, 0x8A}, /* SHD_KNOT_DIFF_1_B9 */ + {0x4328, 0x87}, /* SHD_KNOT_DIFF_1_R10 */ + {0x4329, 0x85}, /* SHD_KNOT_DIFF_1_GR10 */ + {0x432A, 0x85}, /* SHD_KNOT_DIFF_1_GB10 */ + {0x432B, 0x86}, /* SHD_KNOT_DIFF_1_B10 */ + {0x432C, 0x84}, /* SHD_KNOT_DIFF_1_R11 */ + {0x432D, 0x82}, /* SHD_KNOT_DIFF_1_GR11 */ + {0x432E, 0x82}, /* SHD_KNOT_DIFF_1_GB11 */ + {0x432F, 0x83}, /* SHD_KNOT_DIFF_1_B11 */ + {0x4330, 0x82}, /* SHD_KNOT_DIFF_1_R12 */ + {0x4331, 0x81}, /* SHD_KNOT_DIFF_1_GR12 */ + {0x4332, 0x81}, /* SHD_KNOT_DIFF_1_GB12 */ + {0x4333, 0x81}, /* SHD_KNOT_DIFF_1_B12 */ + {0x4334, 0x81}, /* SHD_KNOT_DIFF_1_R13 */ + {0x4335, 0x81}, /* SHD_KNOT_DIFF_1_GR13 */ + {0x4336, 0x81}, /* SHD_KNOT_DIFF_1_GB13 */ + {0x4337, 0x81}, /* SHD_KNOT_DIFF_1_B13 */ + {0x4338, 0x82}, /* SHD_KNOT_DIFF_1_R14 */ + {0x4339, 0x82}, /* SHD_KNOT_DIFF_1_GR14 */ + {0x433A, 0x82}, /* SHD_KNOT_DIFF_1_GB14 */ + {0x433B, 0x82}, /* SHD_KNOT_DIFF_1_B14 */ + {0x433C, 0x85}, /* SHD_KNOT_DIFF_1_R15 */ + {0x433D, 0x85}, /* SHD_KNOT_DIFF_1_GR15 */ + {0x433E, 0x85}, /* SHD_KNOT_DIFF_1_GB15 */ + {0x433F, 0x85}, /* SHD_KNOT_DIFF_1_B15 */ + {0x4340, 0x8A}, /* SHD_KNOT_DIFF_1_R16 */ + {0x4341, 0x89}, /* SHD_KNOT_DIFF_1_GR16 */ + {0x4342, 0x89}, /* SHD_KNOT_DIFF_1_GB16 */ + {0x4343, 0x89}, /* SHD_KNOT_DIFF_1_B16 */ + {0x4344, 0x91}, /* SHD_KNOT_DIFF_1_R17 */ + {0x4345, 0x8E}, /* SHD_KNOT_DIFF_1_GR17 */ + {0x4346, 0x8E}, /* SHD_KNOT_DIFF_1_GB17 */ + {0x4347, 0x8F}, /* SHD_KNOT_DIFF_1_B17 */ + {0x4348, 0x8A}, /* SHD_KNOT_DIFF_1_R18 */ + {0x4349, 0x87}, /* SHD_KNOT_DIFF_1_GR18 */ + {0x434A, 0x87}, /* SHD_KNOT_DIFF_1_GB18 */ + {0x434B, 0x89}, /* SHD_KNOT_DIFF_1_B18 */ + {0x434C, 0x85}, /* SHD_KNOT_DIFF_1_R19 */ + {0x434D, 0x84}, /* SHD_KNOT_DIFF_1_GR19 */ + {0x434E, 0x84}, /* SHD_KNOT_DIFF_1_GB19 */ + {0x434F, 0x85}, /* SHD_KNOT_DIFF_1_B19 */ + {0x4350, 0x82}, /* SHD_KNOT_DIFF_1_R20 */ + {0x4351, 0x82}, /* SHD_KNOT_DIFF_1_GR20 */ + {0x4352, 0x82}, /* SHD_KNOT_DIFF_1_GB20 */ + {0x4353, 0x82}, /* SHD_KNOT_DIFF_1_B20 */ + {0x4354, 0x81}, /* SHD_KNOT_DIFF_1_R21 */ + {0x4355, 0x81}, /* SHD_KNOT_DIFF_1_GR21 */ + {0x4356, 0x81}, /* SHD_KNOT_DIFF_1_GB21 */ + {0x4357, 0x81}, /* SHD_KNOT_DIFF_1_B21 */ + {0x4358, 0x80}, /* SHD_KNOT_DIFF_1_R22 */ + {0x4359, 0x80}, /* SHD_KNOT_DIFF_1_GR22 */ + {0x435A, 0x80}, /* SHD_KNOT_DIFF_1_GB22 */ + {0x435B, 0x80}, /* SHD_KNOT_DIFF_1_B22 */ + {0x435C, 0x81}, /* SHD_KNOT_DIFF_1_R23 */ + {0x435D, 0x81}, /* SHD_KNOT_DIFF_1_GR23 */ + {0x435E, 0x81}, /* SHD_KNOT_DIFF_1_GB23 */ + {0x435F, 0x81}, /* SHD_KNOT_DIFF_1_B23 */ + {0x4360, 0x84}, /* SHD_KNOT_DIFF_1_R24 */ + {0x4361, 0x84}, /* SHD_KNOT_DIFF_1_GR24 */ + {0x4362, 0x84}, /* SHD_KNOT_DIFF_1_GB24 */ + {0x4363, 0x83}, /* SHD_KNOT_DIFF_1_B24 */ + {0x4364, 0x88}, /* SHD_KNOT_DIFF_1_R25 */ + {0x4365, 0x88}, /* SHD_KNOT_DIFF_1_GR25 */ + {0x4366, 0x88}, /* SHD_KNOT_DIFF_1_GB25 */ + {0x4367, 0x87}, /* SHD_KNOT_DIFF_1_B25 */ + {0x4368, 0x8E}, /* SHD_KNOT_DIFF_1_R26 */ + {0x4369, 0x8D}, /* SHD_KNOT_DIFF_1_GR26 */ + {0x436A, 0x8D}, /* SHD_KNOT_DIFF_1_GB26 */ + {0x436B, 0x8C}, /* SHD_KNOT_DIFF_1_B26 */ + {0x436C, 0x87}, /* SHD_KNOT_DIFF_1_R27 */ + {0x436D, 0x86}, /* SHD_KNOT_DIFF_1_GR27 */ + {0x436E, 0x86}, /* SHD_KNOT_DIFF_1_GB27 */ + {0x436F, 0x88}, /* SHD_KNOT_DIFF_1_B27 */ + {0x4370, 0x85}, /* SHD_KNOT_DIFF_1_R28 */ + {0x4371, 0x84}, /* SHD_KNOT_DIFF_1_GR28 */ + {0x4372, 0x84}, /* SHD_KNOT_DIFF_1_GB28 */ + {0x4373, 0x85}, /* SHD_KNOT_DIFF_1_B28 */ + {0x4374, 0x83}, /* SHD_KNOT_DIFF_1_R29 */ + {0x4375, 0x82}, /* SHD_KNOT_DIFF_1_GR29 */ + {0x4376, 0x82}, /* SHD_KNOT_DIFF_1_GB29 */ + {0x4377, 0x82}, /* SHD_KNOT_DIFF_1_B29 */ + {0x4378, 0x81}, /* SHD_KNOT_DIFF_1_R30 */ + {0x4379, 0x80}, /* SHD_KNOT_DIFF_1_GR30 */ + {0x437A, 0x80}, /* SHD_KNOT_DIFF_1_GB30 */ + {0x437B, 0x80}, /* SHD_KNOT_DIFF_1_B30 */ + {0x437C, 0x80}, /* SHD_KNOT_DIFF_1_R31 */ + {0x437D, 0x80}, /* SHD_KNOT_DIFF_1_GR31 */ + {0x437E, 0x80}, /* SHD_KNOT_DIFF_1_GB31 */ + {0x437F, 0x80}, /* SHD_KNOT_DIFF_1_B31 */ + {0x4380, 0x81}, /* SHD_KNOT_DIFF_1_R32 */ + {0x4381, 0x81}, /* SHD_KNOT_DIFF_1_GR32 */ + {0x4382, 0x81}, /* SHD_KNOT_DIFF_1_GB32 */ + {0x4383, 0x80}, /* SHD_KNOT_DIFF_1_B32 */ + {0x4384, 0x84}, /* SHD_KNOT_DIFF_1_R33 */ + {0x4385, 0x84}, /* SHD_KNOT_DIFF_1_GR33 */ + {0x4386, 0x84}, /* SHD_KNOT_DIFF_1_GB33 */ + {0x4387, 0x83}, /* SHD_KNOT_DIFF_1_B33 */ + {0x4388, 0x88}, /* SHD_KNOT_DIFF_1_R34 */ + {0x4389, 0x88}, /* SHD_KNOT_DIFF_1_GR34 */ + {0x438A, 0x88}, /* SHD_KNOT_DIFF_1_GB34 */ + {0x438B, 0x87}, /* SHD_KNOT_DIFF_1_B34 */ + {0x438C, 0x8E}, /* SHD_KNOT_DIFF_1_R35 */ + {0x438D, 0x8E}, /* SHD_KNOT_DIFF_1_GR35 */ + {0x438E, 0x8E}, /* SHD_KNOT_DIFF_1_GB35 */ + {0x438F, 0x8C}, /* SHD_KNOT_DIFF_1_B35 */ + {0x4390, 0x89}, /* SHD_KNOT_DIFF_1_R36 */ + {0x4391, 0x88}, /* SHD_KNOT_DIFF_1_GR36 */ + {0x4392, 0x88}, /* SHD_KNOT_DIFF_1_GB36 */ + {0x4393, 0x8A}, /* SHD_KNOT_DIFF_1_B36 */ + {0x4394, 0x86}, /* SHD_KNOT_DIFF_1_R37 */ + {0x4395, 0x85}, /* SHD_KNOT_DIFF_1_GR37 */ + {0x4396, 0x85}, /* SHD_KNOT_DIFF_1_GB37 */ + {0x4397, 0x86}, /* SHD_KNOT_DIFF_1_B37 */ + {0x4398, 0x83}, /* SHD_KNOT_DIFF_1_R38 */ + {0x4399, 0x83}, /* SHD_KNOT_DIFF_1_GR38 */ + {0x439A, 0x83}, /* SHD_KNOT_DIFF_1_GB38 */ + {0x439B, 0x83}, /* SHD_KNOT_DIFF_1_B38 */ + {0x439C, 0x81}, /* SHD_KNOT_DIFF_1_R39 */ + {0x439D, 0x81}, /* SHD_KNOT_DIFF_1_GR39 */ + {0x439E, 0x81}, /* SHD_KNOT_DIFF_1_GB39 */ + {0x439F, 0x81}, /* SHD_KNOT_DIFF_1_B39 */ + {0x43A0, 0x81}, /* SHD_KNOT_DIFF_1_R40 */ + {0x43A1, 0x81}, /* SHD_KNOT_DIFF_1_GR40 */ + {0x43A2, 0x81}, /* SHD_KNOT_DIFF_1_GB40 */ + {0x43A3, 0x81}, /* SHD_KNOT_DIFF_1_B40 */ + {0x43A4, 0x82}, /* SHD_KNOT_DIFF_1_R41 */ + {0x43A5, 0x82}, /* SHD_KNOT_DIFF_1_GR41 */ + {0x43A6, 0x82}, /* SHD_KNOT_DIFF_1_GB41 */ + {0x43A7, 0x81}, /* SHD_KNOT_DIFF_1_B41 */ + {0x43A8, 0x84}, /* SHD_KNOT_DIFF_1_R42 */ + {0x43A9, 0x85}, /* SHD_KNOT_DIFF_1_GR42 */ + {0x43AA, 0x85}, /* SHD_KNOT_DIFF_1_GB42 */ + {0x43AB, 0x83}, /* SHD_KNOT_DIFF_1_B42 */ + {0x43AC, 0x8A}, /* SHD_KNOT_DIFF_1_R43 */ + {0x43AD, 0x8A}, /* SHD_KNOT_DIFF_1_GR43 */ + {0x43AE, 0x8A}, /* SHD_KNOT_DIFF_1_GB43 */ + {0x43AF, 0x88}, /* SHD_KNOT_DIFF_1_B43 */ + {0x43B0, 0x93}, /* SHD_KNOT_DIFF_1_R44 */ + {0x43B1, 0x91}, /* SHD_KNOT_DIFF_1_GR44 */ + {0x43B2, 0x91}, /* SHD_KNOT_DIFF_1_GB44 */ + {0x43B3, 0x8F}, /* SHD_KNOT_DIFF_1_B44 */ + {0x43B4, 0x8B}, /* SHD_KNOT_DIFF_1_R45 */ + {0x43B5, 0x8A}, /* SHD_KNOT_DIFF_1_GR45 */ + {0x43B6, 0x8A}, /* SHD_KNOT_DIFF_1_GB45 */ + {0x43B7, 0x8C}, /* SHD_KNOT_DIFF_1_B45 */ + {0x43B8, 0x89}, /* SHD_KNOT_DIFF_1_R46 */ + {0x43B9, 0x87}, /* SHD_KNOT_DIFF_1_GR46 */ + {0x43BA, 0x87}, /* SHD_KNOT_DIFF_1_GB46 */ + {0x43BB, 0x89}, /* SHD_KNOT_DIFF_1_B46 */ + {0x43BC, 0x84}, /* SHD_KNOT_DIFF_1_R47 */ + {0x43BD, 0x84}, /* SHD_KNOT_DIFF_1_GR47 */ + {0x43BE, 0x84}, /* SHD_KNOT_DIFF_1_GB47 */ + {0x43BF, 0x84}, /* SHD_KNOT_DIFF_1_B47 */ + {0x43C0, 0x83}, /* SHD_KNOT_DIFF_1_R48 */ + {0x43C1, 0x83}, /* SHD_KNOT_DIFF_1_GR48 */ + {0x43C2, 0x83}, /* SHD_KNOT_DIFF_1_GB48 */ + {0x43C3, 0x83}, /* SHD_KNOT_DIFF_1_B48 */ + {0x43C4, 0x82}, /* SHD_KNOT_DIFF_1_R49 */ + {0x43C5, 0x83}, /* SHD_KNOT_DIFF_1_GR49 */ + {0x43C6, 0x83}, /* SHD_KNOT_DIFF_1_GB49 */ + {0x43C7, 0x82}, /* SHD_KNOT_DIFF_1_B49 */ + {0x43C8, 0x83}, /* SHD_KNOT_DIFF_1_R50 */ + {0x43C9, 0x84}, /* SHD_KNOT_DIFF_1_GR50 */ + {0x43CA, 0x84}, /* SHD_KNOT_DIFF_1_GB50 */ + {0x43CB, 0x83}, /* SHD_KNOT_DIFF_1_B50 */ + {0x43CC, 0x86}, /* SHD_KNOT_DIFF_1_R51 */ + {0x43CD, 0x86}, /* SHD_KNOT_DIFF_1_GR51 */ + {0x43CE, 0x86}, /* SHD_KNOT_DIFF_1_GB51 */ + {0x43CF, 0x85}, /* SHD_KNOT_DIFF_1_B51 */ + {0x43D0, 0x8E}, /* SHD_KNOT_DIFF_1_R52 */ + {0x43D1, 0x8D}, /* SHD_KNOT_DIFF_1_GR52 */ + {0x43D2, 0x8D}, /* SHD_KNOT_DIFF_1_GB52 */ + {0x43D3, 0x8B}, /* SHD_KNOT_DIFF_1_B52 */ + {0x43D4, 0x94}, /* SHD_KNOT_DIFF_1_R53 */ + {0x43D5, 0x93}, /* SHD_KNOT_DIFF_1_GR53 */ + {0x43D6, 0x93}, /* SHD_KNOT_DIFF_1_GB53 */ + {0x43D7, 0x91}, /* SHD_KNOT_DIFF_1_B53 */ + {0x34C0, 0xDD}, /* AE_SENSRATIO_SP1H_SP1L_R */ + {0x34C1, 0x00}, /* AE_SENSRATIO_SP1H_SP1L_R */ + {0x34C2, 0xF1}, /* AE_SENSRATIO_SP1H_SP1L_GR */ + {0x34C3, 0x00}, /* AE_SENSRATIO_SP1H_SP1L_GR */ + {0x34C4, 0xEC}, /* AE_SENSRATIO_SP1H_SP1L_GB */ + {0x34C5, 0x00}, /* AE_SENSRATIO_SP1H_SP1L_GB */ + {0x34C6, 0xE5}, /* AE_SENSRATIO_SP1H_SP1L_B */ + {0x34C7, 0x00}, /* AE_SENSRATIO_SP1H_SP1L_B */ + {0x34C8, 0xE7}, /* AE_SENSRATIO_SP1H_SP2_R */ + {0x34C9, 0x1B}, /* AE_SENSRATIO_SP1H_SP2_R */ + {0x34CA, 0x02}, /* AE_SENSRATIO_SP1H_SP2_GR */ + {0x34CB, 0x1D}, /* AE_SENSRATIO_SP1H_SP2_GR */ + {0x34CC, 0x5E}, /* AE_SENSRATIO_SP1H_SP2_GB */ + {0x34CD, 0x1C}, /* AE_SENSRATIO_SP1H_SP2_GB */ + {0x34CE, 0x4B}, /* AE_SENSRATIO_SP1H_SP2_B */ + {0x34CF, 0x1B}, /* AE_SENSRATIO_SP1H_SP2_B */ + {0x3053, 0x00}, /* "OTP_SENSRATIOEN " */ + {0x3630, 0x40}, /* WDC_SGAIN_ADJ_SP1_R */ + {0x3631, 0x00}, /* WDC_SGAIN_ADJ_SP1_R */ + {0x3632, 0x40}, /* WDC_SGAIN_ADJ_SP1_GR */ + {0x3633, 0x00}, /* WDC_SGAIN_ADJ_SP1_GR */ + {0x3634, 0x40}, /* WDC_SGAIN_ADJ_SP1_GB */ + {0x3635, 0x00}, /* WDC_SGAIN_ADJ_SP1_GB */ + {0x3636, 0x40}, /* WDC_SGAIN_ADJ_SP1_B */ + {0x3637, 0x00}, /* WDC_SGAIN_ADJ_SP1_B */ + {0x3638, 0x40}, /* WDC_SGAIN_ADJ_SP2_R */ + {0x3639, 0x00}, /* WDC_SGAIN_ADJ_SP2_R */ + {0x363A, 0x40}, /* WDC_SGAIN_ADJ_SP2_GR */ + {0x363B, 0x00}, /* WDC_SGAIN_ADJ_SP2_GR */ + {0x363C, 0x40}, /* WDC_SGAIN_ADJ_SP2_GB */ + {0x363D, 0x00}, /* WDC_SGAIN_ADJ_SP2_GB */ + {0x363E, 0x40}, /* WDC_SGAIN_ADJ_SP2_B */ + {0x363F, 0x00}, /* WDC_SGAIN_ADJ_SP2_B */ + {0x3838, 0xB3}, /* OBB_CLAMP_OFFSET_R_SP1H */ + {0x3839, 0xFF}, /* OBB_CLAMP_OFFSET_R_SP1H */ + {0x383A, 0xB5}, /* OBB_CLAMP_OFFSET_GR_SP1H */ + {0x383B, 0xFF}, /* OBB_CLAMP_OFFSET_GR_SP1H */ + {0x383C, 0xB4}, /* OBB_CLAMP_OFFSET_GB_SP1H */ + {0x383D, 0xFF}, /* OBB_CLAMP_OFFSET_GB_SP1H */ + {0x383E, 0xB7}, /* OBB_CLAMP_OFFSET_B_SP1H */ + {0x383F, 0xFF}, /* OBB_CLAMP_OFFSET_B_SP1H */ + {0x3840, 0xE3}, /* OBB_CLAMP_OFFSET_R_SP1L */ + {0x3841, 0xFF}, /* OBB_CLAMP_OFFSET_R_SP1L */ + {0x3842, 0xE6}, /* OBB_CLAMP_OFFSET_GR_SP1L */ + {0x3843, 0xFF}, /* OBB_CLAMP_OFFSET_GR_SP1L */ + {0x3844, 0xF9}, /* OBB_CLAMP_OFFSET_GB_SP1L */ + {0x3845, 0xFF}, /* OBB_CLAMP_OFFSET_GB_SP1L */ + {0x3846, 0xFE}, /* OBB_CLAMP_OFFSET_B_SP1L */ + {0x3847, 0xFF}, /* OBB_CLAMP_OFFSET_B_SP1L */ + {0x3848, 0xC9}, /* OBB_CLAMP_OFFSET_R_SP2 */ + {0x3849, 0xFF}, /* OBB_CLAMP_OFFSET_R_SP2 */ + {0x384A, 0xCA}, /* OBB_CLAMP_OFFSET_GR_SP2 */ + {0x384B, 0xFF}, /* OBB_CLAMP_OFFSET_GR_SP2 */ + {0x384C, 0xE5}, /* OBB_CLAMP_OFFSET_GB_SP2 */ + {0x384D, 0xFF}, /* OBB_CLAMP_OFFSET_GB_SP2 */ + {0x384E, 0xF0}, /* OBB_CLAMP_OFFSET_B_SP2 */ + {0x384F, 0xFF}, /* OBB_CLAMP_OFFSET_B_SP2 */ +}; + +static const struct imx390_reg_list lsc_vendor_def_list = { + .num_of_regs = ARRAY_SIZE(imx390_lsc_pattern_vendor_def), + .regs = imx390_lsc_pattern_vendor_def, +}; + +static const s64 link_freq_menu_items[] = { + IMX390_LINK_FREQ_360MHZ, + IMX390_LINK_FREQ_300MHZ, + IMX390_LINK_FREQ_288MHZ, + IMX390_LINK_FREQ_240MHZ, +}; + +static const struct imx390_mode supported_modes[] = { + { + .width = 1280, + .height = 960, + .hdr_en = false, + .hts = 2464, + .vts_def = 2435, + .vts_min = 2435, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .lanes = 4, + .fps = 30, + .bpp = 12, + .reg_list = { + .num_of_regs = ARRAY_SIZE(imx390_mode_1280x960CROP), + .regs = imx390_mode_1280x960CROP, + }, + .link_freq_index = -1, + }, + { + .width = 1920, + .height = 1200, + .hdr_en = true, + .hts = 2464, + .vts_def = 2435, + .vts_min = 2435, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .lanes = 4, + .fps = 30, + .bpp = 12, + .reg_list = { + .num_of_regs = ARRAY_SIZE(imx390_mode_1920x1200HDR3_CUST_PWL12), + .regs = imx390_mode_1920x1200HDR3_CUST_PWL12, + }, + .link_freq_index = -1, + }, +}; + +static u32 supported_formats[] = { + MEDIA_BUS_FMT_SGRBG12_1X12, +}; + +static int imx390_read_reg(struct imx390 *imx390, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) { + dev_err(&client->dev, "%s: invalid length %d. i2c read register failed\n", + __func__, len); + return -EINVAL; + } + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "%s: i2c read register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + return -EIO; + } + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int imx390_write_reg(struct imx390 *imx390, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + u8 buf[6]; + + if (len > 4) { + dev_err(&client->dev, "%s: invalid length %d. i2c write register failed\n", + __func__, len); + return -EINVAL; + } + + dev_dbg(&client->dev, "%s, reg %x len %x, val %x\n", __func__, reg, len, val); + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) { + dev_err(&client->dev, "%s: i2c write register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + return -EIO; + } + + return 0; +} + +static int imx390_write_reg_list(struct imx390 *imx390, + const struct imx390_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + ret = imx390_write_reg(imx390, r_list->regs[i].address, + IMX390_REG_VALUE_08BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static int imx390_is_hdr(struct imx390 *imx390) +{ + // int mode_ix = self->s_data->sensor_mode_id; + // return imx390_modes_formats[mode_ix].hdr_en; + + if (imx390->cur_mode->hdr_en) + return 1; + + return 0; +} + +static int imx390_group_hold_enable(struct imx390 *imx390, s32 val) +{ + int ret = 0; + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + + dev_dbg(&client->dev, "group hold: %d", val); + + ret = imx390_write_reg(imx390, IMX390_REG_REG_HOLD, IMX390_REG_VALUE_08BIT, val ? 1:0); + + if (ret) + dev_err(&client->dev, "failed to set group hold"); + + return ret; +} + +/** + * imx390 gain is 0 to 30 in .3db steps. + * + * @param self driver instance + * @param val gain + * + * @return 0 on success + */ +static int imx390_gain_set(struct imx390 *imx390, s64 val) +{ + u16 gain = 0; + u32 prevgain = 0; + + gain = (u16)val; // * 10 / 3 / FIXED_POINT_SCALING_FACTOR; + + if (gain > 100) + gain = 100; + + if (gain < 0) + gain = 0; + + imx390_read_reg(imx390, IMX390_REG_AGAIN_SP1H, IMX390_REG_VALUE_08BIT, &prevgain); + + imx390_group_hold_enable(imx390, 1); + + imx390_write_reg(imx390, IMX390_REG_AGAIN_SP1H, IMX390_REG_VALUE_08BIT, gain & 0xff); + imx390_write_reg(imx390, IMX390_REG_AGAIN_SP1H + 1, IMX390_REG_VALUE_08BIT, (gain >> 8) & 0xff); + + imx390_group_hold_enable(imx390, 0); + + imx390_read_reg(imx390, IMX390_REG_AGAIN_SP1H, IMX390_REG_VALUE_08BIT, &prevgain); + + return 0; +} + +static u64 get_pixel_rate(struct imx390 *imx390) +{ + u64 pixel_rate = 72000000; + + return pixel_rate; +} + +/* + * from table 1, AND9820-D.pdf. + * for context A, hblank = LLP(0x300C) - active data time. + */ +static u64 get_hblank(struct imx390 *imx390) +{ + u64 hblank = 0; + + return hblank; +} + +static int imx390_exposure_raw_set(struct imx390 *self, u32 exp) +{ + /* This should never be called in HDR mode but we'll put check + * in to be safe. + */ + if (imx390_is_hdr(self)) + return 0; + + imx390_group_hold_enable(self, 1); + + struct imx390_reg exposure_array[] = { + /* 20 bit value 0xc, 0xd, 0xe */ + {IMX390_REG_SHS1, exp & 0xff}, + {IMX390_REG_SHS1 + 1, (exp & 0xff00) >> 8}, + {IMX390_REG_SHS1 + 2, (exp & 0xf0000) >> 16}, + + /* 20 bit value 0x10, 0x11, 0x12 */ + {IMX390_REG_SHS2, exp & 0xff}, + {IMX390_REG_SHS2 + 1, (exp & 0xff00) >> 8}, + {IMX390_REG_SHS2 + 2, (exp & 0xf0000) >> 16}, + }; + + const struct imx390_reg_list exp_list = { + .num_of_regs = ARRAY_SIZE(exposure_array), + .regs = exposure_array, + }; + + /* True means to print the register values. This is a small + * table so it's OK. + */ + imx390_write_reg_list(self, &exp_list); + imx390_group_hold_enable(self, 0); + return 0; +} + +static int imx390_exposure_set(struct imx390 *self, s64 val) +{ + u32 coarse_time; + u32 reg; + u32 pixclk = 72000000; + u32 linelen = self->cur_mode->width; + + /* This is figuring out how many lines are output for the + * desired exposure time. + */ + /* pixel clock * TIME / line_length */ + coarse_time = pixclk * val / linelen / FIXED_POINT_SCALING_FACTOR; + + /* The 390 is configured such that the SHS registers are the + * difference between VMAX and the exposure time expressed as + * lines. + */ + /* FRAME_LENGTH is VMAX */ + /* VMAX=1125 */ + reg = 1125 - coarse_time; + /* The data sheet says values of 0 and 1 are prohibited...and + * also says that the default value is 1... + */ + if (reg < 2) + reg = 2; + else if (reg >= 0x100000) + reg = 0x100000 - 1; + + return imx390_exposure_raw_set(self, reg); +} + +static int imx390_white_balance_set(struct imx390 *self) +{ + u16 cf00, cf01, cf10, cf11; + u16 red, blue; + u16 max_chroma, r, g, b; + struct i2c_client *client = v4l2_get_subdevdata(&self->sd); + + dev_dbg(&client->dev, "%s\n", __func__); + red = *self->red_balance->p_new.p_s32; + blue = *self->blue_balance->p_new.p_s32; + + max_chroma = MAX(MAX(red, 0x100), blue); + r = (max_chroma * 0x100) / red; + g = max_chroma; + b = (max_chroma * 0x100) / blue; + + cf00 = IMX390_REG_WBGAIN_R; + cf01 = IMX390_REG_WBGAIN_GR; + cf10 = IMX390_REG_WBGAIN_GB; + cf11 = IMX390_REG_WBGAIN_B; + + imx390_group_hold_enable(self, 1); + dev_dbg(&client->dev, "self->cur_mode->code[%x] MEDIA_BUS_FMT_SGRBG12_1X12[%x]\n", self->cur_mode->code, MEDIA_BUS_FMT_SGRBG12_1X12); + if (self->cur_mode->code == MEDIA_BUS_FMT_SGRBG12_1X12) { + imx390_write_reg(self, IMX390_REG_WBGAIN_FORCE_X1, IMX390_REG_VALUE_08BIT, 0); + + imx390_write_reg(self, cf00, IMX390_REG_VALUE_08BIT, r & 0xff); + imx390_write_reg(self, cf00 + 1, IMX390_REG_VALUE_08BIT, (r & 0xff00) >> 8); + imx390_write_reg(self, cf01, IMX390_REG_VALUE_08BIT, g & 0xff); + imx390_write_reg(self, cf01 + 1, IMX390_REG_VALUE_08BIT, (g & 0xff00) >> 8); + imx390_write_reg(self, cf10, IMX390_REG_VALUE_08BIT, g & 0xff); + imx390_write_reg(self, cf10 + 1, IMX390_REG_VALUE_08BIT, (g & 0xff00) >> 8); + imx390_write_reg(self, cf11, IMX390_REG_VALUE_08BIT, b & 0xff); + imx390_write_reg(self, cf11 + 1, IMX390_REG_VALUE_08BIT, (b & 0xff00) >> 8); + } + imx390_group_hold_enable(self, 0); + + return 0; +} + +/* + * imx390_set_lsc_pattern + * len shading correction pattern + */ +static int imx390_set_lsc_pattern(struct imx390 *self, int val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&self->sd); + int ret = 0; + + if (val == LSC_PATTERN_UNITY) + ret = imx390_write_reg_list(self, &lsc_unity_list); + + if (val == LSC_PATTERN_TABLE) + ret = imx390_write_reg_list(self, &lsc_vendor_def_list); + + if (!ret) + dev_dbg(&client->dev, + "%s : LSC PATTERN control success\n", __func__); + else + dev_err(&client->dev, "%s ret = %d\n", __func__, ret); + + return ret; +} + +static int imx390_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx390 *imx390 = container_of(ctrl->handler, + struct imx390, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + int ret = 0; + + switch (ctrl->id) { + case IMX390_CID_EXPOSURE_SHS1: + case IMX390_CID_EXPOSURE_SHS2: + case IMX390_CID_EXPOSURE_SHS3: + case IMX390_CID_EXPOSURE_RHS1: + case IMX390_CID_EXPOSURE_RHS2: + case IMX390_CID_DIGITAL_GAIN_L: + case IMX390_CID_DIGITAL_GAIN_S: + case IMX390_CID_DIGITAL_GAIN_VS: + case IMX390_CID_ANALOG_GAIN_L: + case IMX390_CID_ANALOG_GAIN_S: + case IMX390_CID_ANALOG_GAIN_VS: + case V4L2_CID_DIGITAL_GAIN: + case V4L2_CID_GAIN: + ret = 0; + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = imx390_gain_set(imx390, *ctrl->p_new.p_s64); + break; + case V4L2_CID_EXPOSURE: + ret = imx390_exposure_set(imx390, *ctrl->p_new.p_s64); + break; + case V4L2_CID_VBLANK: + ret = imx390_write_reg(imx390, IMX390_REG_VTS, + IMX390_REG_VALUE_16BIT, + imx390->cur_mode->height + ctrl->val); + break; + case V4L2_CID_RED_BALANCE: + case V4L2_CID_BLUE_BALANCE: + ret = imx390_white_balance_set(imx390); + break; + case V4L2_CID_TEST_PATTERN: + ret = imx390_set_lsc_pattern(imx390, ctrl->val); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx390_ctrl_ops = { + .s_ctrl = imx390_set_ctrl, +}; + +static int imx390_init_controls(struct imx390 *imx390) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 hblank; + int ret; + struct v4l2_ctrl_config cfg = { 0 }; + + ctrl_hdlr = &imx390->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = &imx390->mutex; + imx390->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (imx390->link_freq) + imx390->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx390->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_VBLANK, + 0, + IMX390_VTS_MAX - imx390->cur_mode->height, 1, + imx390->cur_mode->vts_def - imx390->cur_mode->height); + + imx390->gain = v4l2_ctrl_new_std( + ctrl_hdlr, + &imx390_ctrl_ops, + V4L2_CID_GAIN, IMX390_GAIN_MIN, + IMX390_DGTL_GAIN_MAX * IMX390_ANAL_GAIN_MAX, 1, + IMX390_GAIN_DEFAULT); + + imx390->analogue_gain = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX390_ANAL_GAIN_MIN, IMX390_ANAL_GAIN_MAX, + IMX390_ANAL_GAIN_STEP, IMX390_ANAL_GAIN_DEFAULT); + + imx390->digital_gain = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX390_DGTL_GAIN_MIN, IMX390_DGTL_GAIN_MAX, + IMX390_DGTL_GAIN_STEP, IMX390_DGTL_GAIN_DEFAULT); + + imx390->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX390_EXPOSURE_MIN, + IMX390_EXPOSURE_MAX, + IMX390_EXPOSURE_STEP, + IMX390_EXPOSURE_DEF); + + imx390->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_PIXEL_RATE, get_pixel_rate(imx390), get_pixel_rate(imx390), + 1, get_pixel_rate(imx390)); + + if (imx390->pixel_rate) + imx390->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hblank = get_hblank(imx390); + imx390->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (imx390->hblank) + imx390->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx390->red_balance = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_RED_BALANCE, + IMX390_RED_BALANCE_MIN, + IMX390_RED_BALANCE_MAX, + IMX390_RED_BALANCE_STEP, + IMX390_RED_BALANCE_DEF); + + imx390->blue_balance = v4l2_ctrl_new_std(ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_BLUE_BALANCE, + IMX390_BLUE_BALANCE_MIN, + IMX390_BLUE_BALANCE_MAX, + IMX390_BLUE_BALANCE_STEP, + IMX390_BLUE_BALANCE_DEF); + + cfg.ops = &imx390_ctrl_ops; + cfg.id = IMX390_CID_EXPOSURE_SHS1; + cfg.name = "IMX390_CID_EXPOSURE_SHS1"; + cfg.type = V4L2_CTRL_TYPE_INTEGER; + cfg.max = IMX390_DUMMY_MAX; + cfg.min = IMX390_DUMMY_MIN; + cfg.step = IMX390_DUMMY_STEP; + cfg.def = IMX390_DUMMY_DEF; + cfg.qmenu = 0; cfg.elem_size = 0; + imx390->dummy_exp_shs1 = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_EXPOSURE_SHS2; + cfg.name = "IMX390_CID_EXPOSURE_SHS2"; + imx390->dummy_exp_shs2 = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_EXPOSURE_SHS3; + cfg.name = "IMX390_CID_EXPOSURE_SHS3"; + imx390->dummy_exp_shs3 = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_EXPOSURE_RHS1; + cfg.name = "IMX390_CID_EXPOSURE_RHS1"; + imx390->dummy_exp_rhs1 = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_EXPOSURE_RHS2; + cfg.name = "IMX390_CID_EXPOSURE_RHS2"; + imx390->dummy_exp_rhs2 = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_DIGITAL_GAIN_L; + cfg.name = "IMX390_CID_DIGITAL_GAIN_L"; + imx390->dummy_dg_l = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_DIGITAL_GAIN_S; + cfg.name = "IMX390_CID_DIGITAL_GAIN_S"; + imx390->dummy_dg_s = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_DIGITAL_GAIN_VS; + cfg.name = "IMX390_CID_DIGITAL_GAIN_VS"; + imx390->dummy_dg_vs = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_ANALOG_GAIN_L; + cfg.name = "IMX390_CID_ANALOG_GAIN_L"; + imx390->dummy_ag_l = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_ANALOG_GAIN_S; + cfg.name = "IMX390_CID_ANALOG_GAIN_S"; + imx390->dummy_ag_s = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + cfg.id = IMX390_CID_ANALOG_GAIN_VS; + cfg.name = "IMX390_CID_ANALOG_GAIN_VS"; + imx390->dummy_ag_vs = v4l2_ctrl_new_custom(ctrl_hdlr, &cfg, NULL); + + imx390->lsc_pattern = v4l2_ctrl_new_std_menu_items( + ctrl_hdlr, &imx390_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(lsc_qmenu) - 1, 0, 0, lsc_qmenu); + + if (ctrl_hdlr->error) + return ctrl_hdlr->error; + + imx390->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static void imx390_update_pad_format(const struct imx390_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 imx390_start_streaming(struct imx390 *imx390) +{ + int retries, ret; + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + const struct imx390_reg_list *reg_list; + + if (imx390->cur_mode != imx390->pre_mode) { + reg_list = &imx390->cur_mode->reg_list; + ret = imx390_write_reg_list(imx390, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set stream mode"); + return ret; + } + imx390->pre_mode = imx390->cur_mode; + } else + dev_dbg(&client->dev, "same mode, skip write reg list"); + + /* + * WA: i2c write to IMX390_REG_STANDBY no response randomly, + * pipeline fails to start. + * retries 1000 times, wait for i2c recover, pipeline started + * with extra delay, instead of fails. + */ + retries = 1000; + do { + ret = imx390_write_reg(imx390, IMX390_REG_STANDBY, + IMX390_REG_VALUE_08BIT, 0); + if (ret) + dev_err(&client->dev, "retry to write STANDBY"); + } while (ret && retries--); + + if (ret) { + dev_err(&client->dev, "failed to set stream"); + return ret; + } + + return 0; +} + +static void imx390_stop_streaming(struct imx390 *imx390) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + + if (imx390_write_reg(imx390, IMX390_REG_STANDBY, + IMX390_REG_VALUE_08BIT, 1)) + dev_err(&client->dev, "failed to set stream"); +} + +static int imx390_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx390 *imx390 = to_imx390(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + if (imx390->streaming == enable) + return 0; + + mutex_lock(&imx390->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&imx390->mutex); + return ret; + } + + ret = imx390_start_streaming(imx390); + if (ret) { + enable = 0; + imx390_stop_streaming(imx390); + pm_runtime_put(&client->dev); + } + } else { + imx390_stop_streaming(imx390); + pm_runtime_put(&client->dev); + } + + imx390->streaming = enable; + + mutex_unlock(&imx390->mutex); + + return ret; +} + +static int imx390_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fival) +{ + struct imx390 *imx390 = to_imx390(sd); + + fival->pad = 0; + fival->interval.numerator = 1; + fival->interval.denominator = imx390->cur_mode->fps; + + return 0; +} + +static int __maybe_unused imx390_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx390 *imx390 = to_imx390(sd); + + mutex_lock(&imx390->mutex); + if (imx390->streaming) + imx390_stop_streaming(imx390); + + mutex_unlock(&imx390->mutex); + + return 0; +} + +static int __maybe_unused imx390_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx390 *imx390 = to_imx390(sd); + const struct imx390_reg_list *reg_list; + int ret; + + reg_list = &imx390->cur_mode->reg_list; + ret = imx390_write_reg_list(imx390, reg_list); + if (ret) { + dev_err(&client->dev, "resume: failed to apply cur mode"); + return ret; + } + + mutex_lock(&imx390->mutex); + if (imx390->streaming) { + ret = imx390_start_streaming(imx390); + if (ret) { + imx390->streaming = false; + imx390_stop_streaming(imx390); + mutex_unlock(&imx390->mutex); + return ret; + } + } + + mutex_unlock(&imx390->mutex); + + return 0; +} + +static int imx390_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx390 *imx390 = to_imx390(sd); + const struct imx390_mode *mode; + s32 vblank_def; + s64 hblank; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) + if (supported_modes[i].code == fmt->format.code) { + if (supported_modes[i].width == fmt->format.width + && supported_modes[i].height == fmt->format.height) { + mode = &supported_modes[i]; + break; + + } + } + + if (i >= ARRAY_SIZE(supported_modes)) + mode = &supported_modes[0]; + + mutex_lock(&imx390->mutex); + + fmt->format.code = supported_formats[0]; + + imx390_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; + } else { + imx390->cur_mode = mode; + __v4l2_ctrl_s_ctrl(imx390->link_freq, mode->link_freq_index); + __v4l2_ctrl_modify_range(imx390->pixel_rate, + get_pixel_rate(imx390), + get_pixel_rate(imx390), + 1, + get_pixel_rate(imx390)); + + hblank = get_hblank(imx390); + __v4l2_ctrl_modify_range(imx390->hblank, + hblank, + hblank, + 1, + hblank); + + /* Update limits and set FPS to default */ + vblank_def = mode->vts_def - mode->height; + __v4l2_ctrl_modify_range(imx390->vblank, + 0, + IMX390_VTS_MAX - mode->height, 1, + vblank_def); + //__v4l2_ctrl_s_ctrl(imx390->vblank, vblank_def); + } + + mutex_unlock(&imx390->mutex); + + return 0; +} + +static int imx390_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct imx390 *imx390 = to_imx390(sd); + + mutex_lock(&imx390->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&imx390->sd, sd_state, + fmt->pad); + else + imx390_update_pad_format(imx390->cur_mode, &fmt->format); + + mutex_unlock(&imx390->mutex); + + return 0; +} + +static int imx390_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + unsigned int i; + + desc->num_entries = V4L2_FRAME_DESC_ENTRY_MAX; + + for (i = 0; i < desc->num_entries; i++) { + desc->entry[i].flags = 0; + desc->entry[i].pixelcode = MEDIA_BUS_FMT_FIXED; + desc->entry[i].length = 0; + } + + return 0; +} + +static int imx390_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(supported_formats)) + return -EINVAL; + + code->code = supported_formats[code->index]; + + return 0; +} + +static int imx390_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; + + 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 imx390_frame_rate[] = { 40, 20 }; + +static int imx390_enum_frame_interval(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_interval_enum *fie) +{ + int mode_size = ARRAY_SIZE(supported_modes); + int i; + + if (fie->index >= ARRAY_SIZE(imx390_frame_rate)) + return -EINVAL; + + for (i = 0; i < mode_size; i++) + if (fie->code == supported_modes[i].code + && fie->width == supported_modes[i].width + && fie->height == supported_modes[i].height) + break; + + if (i == mode_size) + return -EINVAL; + + fie->interval.numerator = 1; + fie->interval.denominator = imx390_frame_rate[fie->index]; + + return 0; +} + +static int imx390_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct imx390 *imx390 = to_imx390(sd); + + mutex_lock(&imx390->mutex); + imx390_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->state, 0)); + mutex_unlock(&imx390->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops imx390_video_ops = { + .s_stream = imx390_set_stream, + .g_frame_interval = imx390_g_frame_interval, +}; + +static const struct v4l2_subdev_pad_ops imx390_pad_ops = { + .set_fmt = imx390_set_format, + .get_fmt = imx390_get_format, + .get_frame_desc = imx390_get_frame_desc, + .enum_mbus_code = imx390_enum_mbus_code, + .enum_frame_size = imx390_enum_frame_size, + .enum_frame_interval = imx390_enum_frame_interval, +}; + +static const struct v4l2_subdev_ops imx390_subdev_ops = { + .video = &imx390_video_ops, + .pad = &imx390_pad_ops, +}; + +static const struct media_entity_operations imx390_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops imx390_internal_ops = { + .open = imx390_open, +}; + +static int imx390_identify_module(struct imx390 *imx390) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); + int ret; + u32 val; + + ret = imx390_read_reg(imx390, IMX390_REG_CHIP_ID, + IMX390_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return 0; + + /* chip id not known yet */ + if (val != IMX390_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + IMX390_CHIP_ID, val); + return -ENXIO; + } + + return 0; +} + +static void imx390_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx390 *imx390 = to_imx390(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + pm_runtime_disable(&client->dev); + mutex_destroy(&imx390->mutex); + +} +static int imx390_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd; + struct imx390 *imx390; + const struct imx390_reg_list *reg_list; + int ret; + + imx390 = devm_kzalloc(&client->dev, sizeof(*imx390), GFP_KERNEL); + if (!imx390) + return -ENOMEM; + + imx390->platform_data = client->dev.platform_data; + if (imx390->platform_data == NULL) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + /* initialize subdevice */ + sd = &imx390->sd; + v4l2_i2c_subdev_init(sd, client, &imx390_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &imx390_internal_ops; + sd->entity.ops = &imx390_subdev_entity_ops; + + /* initialize subdev media pad */ + imx390->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &imx390->pad); + if (ret < 0) { + dev_err(&client->dev, + "%s : media entity init Failed %d\n", __func__, ret); + return ret; + } + + ret = imx390_identify_module(imx390); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + if (imx390->platform_data->suffix) + snprintf(imx390->sd.name, + sizeof(imx390->sd.name), "imx390 %c", + imx390->platform_data->suffix); + + mutex_init(&imx390->mutex); + + /* 1920x1200 default */ + imx390->cur_mode = &supported_modes[1]; + imx390->pre_mode = imx390->cur_mode; + + reg_list = &imx390->cur_mode->reg_list; + ret = imx390_write_reg_list(imx390, reg_list); + if (ret) { + dev_err(&client->dev, "failed to apply preset mode"); + return ret; + } + + ret = imx390_init_controls(imx390); + if (ret) { + dev_err(&client->dev, "failed to init controls: %d", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&imx390->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %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); + dev_err(&client->dev, "Probe Succeeded"); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&imx390->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(imx390->sd.ctrl_handler); + mutex_destroy(&imx390->mutex); + dev_err(&client->dev, "Probe Failed"); + + return ret; +} + +static const struct dev_pm_ops imx390_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx390_suspend, imx390_resume) +}; + +static const struct i2c_device_id imx390_id_table[] = { + { "imx390", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, imx390_id_table); + +static struct i2c_driver imx390_i2c_driver = { + .driver = { + .name = "imx390", + .pm = &imx390_pm_ops, + }, + .probe_new = imx390_probe, + .remove = imx390_remove, + .id_table = imx390_id_table, +}; + +module_i2c_driver(imx390_i2c_driver); + +MODULE_AUTHOR("Chang, Ying "); +MODULE_DESCRIPTION("imx390 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/imx390_mode_1920x1200HDR3_CUST_PWL12.h b/drivers/media/i2c/imx390_mode_1920x1200HDR3_CUST_PWL12.h new file mode 100644 index 000000000000..b2b373b6eeaf --- /dev/null +++ b/drivers/media/i2c/imx390_mode_1920x1200HDR3_CUST_PWL12.h @@ -0,0 +1,3891 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/** + * @author George Vigelette + * + * imx390 v4l2 driver for tgl + **/ + +#ifndef __GRBG_1920x1200_HDR3_CUST_PWL12_M4_30_27M_H +#define __GRBG_1920x1200_HDR3_CUST_PWL12_M4_30_27M_H + +static const struct imx390_reg imx390_mode_1920x1200HDR3_CUST_PWL12[] = { +{0x2E18, 0x00}, /* disable rear embedded data line */ +{0x000C, 0x45}, +{0x000D, 0x03}, +{0x000E, 0x00}, +{0x0010, 0x45}, +{0x0011, 0x03}, +{0x0012, 0x00}, +{0x0018, 0x1C}, +{0x0019, 0x00}, +{0x001A, 0x0C}, +{0x001B, 0x00}, +{0x0038, 0x00}, +{0x003C, 0x00}, +{0x003D, 0x00}, +{0x003E, 0x00}, +{0x0040, 0x00}, +{0x0041, 0x00}, +{0x0042, 0x00}, +{0x0044, 0x00}, +{0x0045, 0x00}, +{0x0046, 0x00}, +{0x0048, 0x00}, +{0x0049, 0x00}, +{0x004A, 0x00}, +{0x004C, 0x00}, +{0x004D, 0x00}, +{0x004E, 0x00}, +{0x0050, 0x00}, +{0x0051, 0x00}, +{0x0052, 0x00}, +{0x0054, 0x00}, +{0x0055, 0x00}, +{0x0056, 0x00}, +{0x0058, 0x00}, +{0x0059, 0x00}, +{0x005A, 0x00}, +{0x005C, 0x00}, +{0x005D, 0x00}, +{0x005E, 0x00}, +{0x0060, 0x00}, +{0x0061, 0x00}, +{0x0062, 0x00}, +{0x0064, 0x00}, +{0x0065, 0x00}, +{0x0066, 0x00}, +{0x0068, 0x00}, +{0x0069, 0x00}, +{0x006A, 0x00}, +{0x0078, 0x01}, +{0x007C, 0x08}, +{0x007D, 0x00}, +{0x0080, 0x08}, +{0x0081, 0x00}, +{0x0090, 0x00}, +{0x00F4, 0x1C}, +{0x00F5, 0xF8}, +{0x00F6, 0x01}, +{0x00F8, 0x03}, +{0x00F9, 0x01}, +{0x00FA, 0x00}, +{0x00FB, 0x02}, +{0x0114, 0x00}, +{0x0115, 0x01}, +{0x0118, 0x20}, +{0x0119, 0x03}, +{0x011A, 0x00}, +{0x011B, 0x41}, +{0x011C, 0x80}, +{0x011D, 0x00}, +{0x0120, 0x20}, +{0x0121, 0x00}, +{0x0122, 0x00}, +{0x0123, 0x44}, +{0x0124, 0x00}, +{0x0125, 0x01}, +{0x0128, 0x17}, +{0x0129, 0x06}, +{0x012A, 0x00}, +{0x012B, 0xA4}, +{0x012C, 0x00}, +{0x012D, 0x01}, +{0x0130, 0xD0}, +{0x0131, 0x07}, +{0x0132, 0x00}, +{0x0133, 0xDA}, +{0x0134, 0x00}, +{0x0136, 0xF0}, +{0x0137, 0x00}, +{0x013A, 0x00}, +{0x013B, 0x00}, +{0x013C, 0x00}, +{0x013D, 0x00}, +{0x013E, 0x00}, +{0x0140, 0x00}, +{0x0141, 0x00}, +{0x0142, 0x00}, +{0x0144, 0xDF}, +{0x0145, 0x01}, +{0x0146, 0x00}, +{0x0148, 0xDF}, +{0x0149, 0x01}, +{0x014A, 0x00}, +{0x014C, 0x9E}, +{0x014D, 0x05}, +{0x014E, 0x00}, +{0x0150, 0x45}, +{0x0151, 0x03}, +{0x0152, 0x00}, +{0x0154, 0xDB}, +{0x0155, 0x10}, +{0x0156, 0x00}, +{0x0158, 0xD6}, +{0x0159, 0x04}, +{0x015A, 0x00}, +{0x015C, 0x91}, +{0x015D, 0x32}, +{0x015E, 0x00}, +{0x0160, 0x98}, +{0x0161, 0x06}, +{0x0162, 0x00}, +{0x0164, 0xB4}, +{0x0165, 0x97}, +{0x0166, 0x00}, +{0x0168, 0x8F}, +{0x0169, 0x08}, +{0x016A, 0x00}, +{0x016C, 0x1C}, +{0x016D, 0xC7}, +{0x016E, 0x01}, +{0x0170, 0xC3}, +{0x0171, 0x0A}, +{0x0172, 0x00}, +{0x0174, 0x55}, +{0x0175, 0x55}, +{0x0176, 0x05}, +{0x0178, 0x3B}, +{0x0179, 0x0D}, +{0x017A, 0x00}, +{0x017C, 0xFF}, +{0x017D, 0xFF}, +{0x017E, 0x0F}, +{0x0180, 0xFF}, +{0x0181, 0x0F}, +{0x0182, 0x00}, +{0x0184, 0xFF}, +{0x0185, 0xFF}, +{0x0186, 0x0F}, +{0x0188, 0xFF}, +{0x0189, 0x0F}, +{0x018A, 0x00}, +{0x018C, 0xFF}, +{0x018D, 0xFF}, +{0x018E, 0xFF}, +{0x0190, 0xFF}, +{0x0191, 0x0F}, +{0x0192, 0x00}, +{0x0194, 0xFF}, +{0x0195, 0x0F}, +{0x0196, 0x00}, +{0x0198, 0x00}, +{0x0199, 0x00}, +{0x019A, 0x00}, +{0x019B, 0x01}, +{0x019C, 0xE8}, +{0x019D, 0x98}, +{0x019E, 0x5F}, +{0x019F, 0x00}, +{0x01A0, 0xD7}, +{0x01A1, 0xB2}, +{0x01A2, 0x23}, +{0x01A3, 0x00}, +{0x01A4, 0xB0}, +{0x01A5, 0x54}, +{0x01A6, 0x0D}, +{0x01A7, 0x00}, +{0x01A8, 0x63}, +{0x01A9, 0xFA}, +{0x01AA, 0x04}, +{0x01AB, 0x00}, +{0x01AC, 0xE4}, +{0x01AD, 0xDB}, +{0x01AE, 0x01}, +{0x01AF, 0x00}, +{0x01B0, 0xB5}, +{0x01B1, 0xB1}, +{0x01B2, 0x00}, +{0x01B3, 0x00}, +{0x01B4, 0x5C}, +{0x01B5, 0x42}, +{0x01B6, 0x00}, +{0x01B7, 0x00}, +{0x01B8, 0xC8}, +{0x01B9, 0x18}, +{0x01BA, 0x00}, +{0x01BB, 0x00}, +{0x01BC, 0x00}, +{0x01BD, 0x00}, +{0x01BE, 0x00}, +{0x01BF, 0x00}, +{0x01C0, 0x00}, +{0x01C1, 0x00}, +{0x01C2, 0x00}, +{0x01C3, 0x00}, +{0x01C4, 0x00}, +{0x01C5, 0x00}, +{0x01CC, 0x01}, +{0x01D0, 0x09}, +{0x01D4, 0x01}, +{0x0332, 0x9B}, +{0x0333, 0x03}, +{0x0390, 0x00}, +{0x0391, 0x00}, +{0x0392, 0x00}, +{0x03C0, 0x01}, +{0x2000, 0x55}, +{0x2001, 0x55}, +{0x2002, 0x55}, +{0x2003, 0x05}, +{0x2004, 0x02}, +{0x2008, 0xE2}, +{0x2009, 0x04}, +{0x200A, 0x00}, +{0x200C, 0xE4}, +{0x200D, 0x0C}, +{0x2010, 0x04}, +{0x2014, 0x01}, +{0x2018, 0xA0}, +{0x2019, 0x04}, +{0x201A, 0x00}, +{0x201C, 0x01}, +{0x201D, 0x13}, +{0x201E, 0x00}, +{0x201F, 0x00}, +{0x2020, 0xBC}, +{0x2021, 0x00}, +{0x2022, 0x7F}, +{0x2023, 0x00}, +{0x2024, 0xBA}, +{0x2025, 0x00}, +{0x2026, 0x81}, +{0x2027, 0x00}, +{0x2028, 0x7D}, +{0x2029, 0x90}, +{0x202A, 0x05}, +{0x202C, 0xFC}, +{0x202D, 0x02}, +{0x202E, 0x25}, +{0x202F, 0x03}, +{0x2030, 0x05}, +{0x2031, 0x02}, +{0x2032, 0xCA}, +{0x2033, 0x02}, +{0x2034, 0xFC}, +{0x2035, 0x02}, +{0x2036, 0x25}, +{0x2037, 0x03}, +{0x2038, 0xF8}, +{0x2039, 0xE4}, +{0x203A, 0xE3}, +{0x203B, 0x01}, +{0x203C, 0xF5}, +{0x203D, 0x8E}, +{0x203E, 0x0C}, +{0x203F, 0x2D}, +{0x2040, 0x69}, +{0x2041, 0x01}, +{0x2042, 0x8E}, +{0x2043, 0x01}, +{0x2044, 0x0C}, +{0x2045, 0x02}, +{0x2046, 0x31}, +{0x2047, 0x02}, +{0x2048, 0x6A}, +{0x2049, 0x01}, +{0x204A, 0x8E}, +{0x204B, 0x01}, +{0x204C, 0x0D}, +{0x204D, 0x02}, +{0x204E, 0x31}, +{0x204F, 0x02}, +{0x2050, 0x7B}, +{0x2051, 0x00}, +{0x2052, 0x7D}, +{0x2053, 0x00}, +{0x2054, 0x95}, +{0x2055, 0x00}, +{0x2056, 0x97}, +{0x2057, 0x00}, +{0x2058, 0xAD}, +{0x2059, 0x00}, +{0x205A, 0xAF}, +{0x205B, 0x00}, +{0x205C, 0x92}, +{0x205D, 0x00}, +{0x205E, 0x94}, +{0x205F, 0x00}, +{0x2060, 0x8E}, +{0x2061, 0x00}, +{0x2062, 0x90}, +{0x2063, 0x00}, +{0x2064, 0xB1}, +{0x2065, 0x00}, +{0x2066, 0xB3}, +{0x2067, 0x00}, +{0x2068, 0x08}, +{0x2069, 0x00}, +{0x206A, 0x04}, +{0x206B, 0x00}, +{0x206C, 0x84}, +{0x206D, 0x00}, +{0x206E, 0x80}, +{0x206F, 0x00}, +{0x2070, 0x04}, +{0x2071, 0x00}, +{0x2072, 0x46}, +{0x2073, 0x00}, +{0x2074, 0xE9}, +{0x2075, 0x01}, +{0x2076, 0x74}, +{0x2077, 0x02}, +{0x2078, 0x80}, +{0x2079, 0x00}, +{0x207A, 0xC1}, +{0x207B, 0x00}, +{0x207C, 0xFF}, +{0x207D, 0x03}, +{0x207E, 0xFF}, +{0x207F, 0x03}, +{0x2080, 0x78}, +{0x2081, 0x00}, +{0x2082, 0x6A}, +{0x2083, 0x01}, +{0x2084, 0xE4}, +{0x2085, 0x01}, +{0x2086, 0x2B}, +{0x2087, 0x03}, +{0x2088, 0x00}, +{0x2089, 0x00}, +{0x208A, 0xFF}, +{0x208B, 0x03}, +{0x208C, 0xFF}, +{0x208D, 0x03}, +{0x208E, 0xFF}, +{0x208F, 0x03}, +{0x2090, 0x7D}, +{0x2091, 0x00}, +{0x2092, 0x62}, +{0x2093, 0x01}, +{0x2094, 0xE9}, +{0x2095, 0x01}, +{0x2096, 0x00}, +{0x2097, 0x00}, +{0x2098, 0x7C}, +{0x2099, 0x00}, +{0x209A, 0x21}, +{0x209B, 0x03}, +{0x209C, 0xE9}, +{0x209D, 0x01}, +{0x209E, 0x21}, +{0x209F, 0x03}, +{0x20A0, 0xFF}, +{0x20A1, 0x03}, +{0x20A2, 0xFF}, +{0x20A3, 0x03}, +{0x20A4, 0xFF}, +{0x20A5, 0x03}, +{0x20A6, 0xFF}, +{0x20A7, 0x03}, +{0x20A8, 0xFF}, +{0x20A9, 0x03}, +{0x20AA, 0xFF}, +{0x20AB, 0x03}, +{0x20AC, 0xFF}, +{0x20AD, 0x03}, +{0x20AE, 0xFF}, +{0x20AF, 0x03}, +{0x20B0, 0xFF}, +{0x20B1, 0x03}, +{0x20B2, 0xFF}, +{0x20B3, 0x03}, +{0x20B4, 0x87}, +{0x20B5, 0xCC}, +{0x20B6, 0x87}, +{0x20B7, 0x08}, +{0x20B8, 0xF4}, +{0x20B9, 0xA5}, +{0x20BA, 0x07}, +{0x20BC, 0x1F}, +{0x20BD, 0x01}, +{0x20BE, 0xF6}, +{0x20BF, 0x00}, +{0x20C0, 0x90}, +{0x20C1, 0x01}, +{0x20C2, 0x67}, +{0x20C3, 0x01}, +{0x20C4, 0xFF}, +{0x20C5, 0x03}, +{0x20C6, 0xFF}, +{0x20C7, 0x03}, +{0x20C8, 0x33}, +{0x20C9, 0x02}, +{0x20CA, 0x0A}, +{0x20CB, 0x02}, +{0x20CC, 0x7F}, +{0x20CD, 0x00}, +{0x20CE, 0xD2}, +{0x20CF, 0x00}, +{0x20D0, 0x81}, +{0x20D1, 0x00}, +{0x20D2, 0x87}, +{0x20D3, 0x00}, +{0x20D4, 0x09}, +{0x20D5, 0x00}, +{0x20D8, 0x7F}, +{0x20D9, 0x00}, +{0x20DA, 0x62}, +{0x20DB, 0x01}, +{0x20DC, 0x7F}, +{0x20DD, 0x00}, +{0x20DE, 0x62}, +{0x20DF, 0x01}, +{0x20E0, 0x65}, +{0x20E1, 0x00}, +{0x20E2, 0x75}, +{0x20E3, 0x00}, +{0x20E4, 0xE0}, +{0x20E5, 0x00}, +{0x20E6, 0xF0}, +{0x20E7, 0x00}, +{0x20E8, 0x4C}, +{0x20E9, 0x01}, +{0x20EA, 0x5C}, +{0x20EB, 0x01}, +{0x20EC, 0xD1}, +{0x20ED, 0x01}, +{0x20EE, 0xE1}, +{0x20EF, 0x01}, +{0x20F0, 0x93}, +{0x20F1, 0x02}, +{0x20F2, 0xA3}, +{0x20F3, 0x02}, +{0x20F4, 0x0D}, +{0x20F5, 0x03}, +{0x20F6, 0x1D}, +{0x20F7, 0x03}, +{0x20F8, 0x57}, +{0x20F9, 0x00}, +{0x20FA, 0x7B}, +{0x20FB, 0x00}, +{0x20FC, 0xD2}, +{0x20FD, 0x00}, +{0x20FE, 0xF6}, +{0x20FF, 0x00}, +{0x2100, 0x3E}, +{0x2101, 0x01}, +{0x2102, 0x60}, +{0x2103, 0x01}, +{0x2104, 0xC3}, +{0x2105, 0x01}, +{0x2106, 0xE5}, +{0x2107, 0x01}, +{0x2108, 0x85}, +{0x2109, 0x02}, +{0x210A, 0xA9}, +{0x210B, 0x02}, +{0x210C, 0xFF}, +{0x210D, 0x02}, +{0x210E, 0x21}, +{0x210F, 0x03}, +{0x2110, 0xFF}, +{0x2111, 0x03}, +{0x2112, 0x00}, +{0x2113, 0x00}, +{0x2114, 0xFF}, +{0x2115, 0x03}, +{0x2116, 0xFF}, +{0x2117, 0x03}, +{0x2118, 0xFF}, +{0x2119, 0x03}, +{0x211A, 0xFF}, +{0x211B, 0x03}, +{0x211C, 0xFF}, +{0x211D, 0x03}, +{0x211E, 0xFF}, +{0x211F, 0x03}, +{0x2120, 0xFF}, +{0x2121, 0x03}, +{0x2122, 0xFF}, +{0x2123, 0x03}, +{0x2124, 0xFF}, +{0x2125, 0x03}, +{0x2126, 0xFF}, +{0x2127, 0x03}, +{0x2128, 0x7D}, +{0x2129, 0x90}, +{0x212A, 0xD5}, +{0x212B, 0x07}, +{0x212C, 0x64}, +{0x212D, 0x01}, +{0x2130, 0x5F}, +{0x2131, 0x7D}, +{0x2132, 0x05}, +{0x2134, 0x78}, +{0x2135, 0x00}, +{0x2136, 0x76}, +{0x2137, 0x00}, +{0x2138, 0xF3}, +{0x2139, 0x00}, +{0x213A, 0xF1}, +{0x213B, 0x00}, +{0x213C, 0xA6}, +{0x213D, 0x02}, +{0x213E, 0xA4}, +{0x213F, 0x02}, +{0x2140, 0x7D}, +{0x2141, 0x00}, +{0x2142, 0x8D}, +{0x2143, 0x00}, +{0x2144, 0xA1}, +{0x2145, 0x01}, +{0x2146, 0xB1}, +{0x2147, 0x01}, +{0x2148, 0xAB}, +{0x2149, 0x02}, +{0x214A, 0xBB}, +{0x214B, 0x02}, +{0x214C, 0x17}, +{0x214D, 0x5C}, +{0x214E, 0x00}, +{0x2150, 0x00}, +{0x2151, 0x00}, +{0x2152, 0xF8}, +{0x2153, 0x00}, +{0x2154, 0x45}, +{0x2155, 0x00}, +{0x2156, 0x00}, +{0x2157, 0x00}, +{0x2158, 0x25}, +{0x2159, 0x00}, +{0x215A, 0x7D}, +{0x215B, 0x00}, +{0x215C, 0x62}, +{0x215D, 0x01}, +{0x215E, 0xFF}, +{0x215F, 0x03}, +{0x2160, 0x26}, +{0x2161, 0x00}, +{0x2162, 0x7D}, +{0x2163, 0x00}, +{0x2164, 0x63}, +{0x2165, 0x01}, +{0x2166, 0xFF}, +{0x2167, 0x03}, +{0x2168, 0xCB}, +{0x2169, 0x02}, +{0x216A, 0xCF}, +{0x216B, 0x02}, +{0x216C, 0xFF}, +{0x216D, 0x03}, +{0x216E, 0xFF}, +{0x216F, 0x03}, +{0x2170, 0xFF}, +{0x2171, 0x03}, +{0x2172, 0xFF}, +{0x2173, 0x03}, +{0x2174, 0xFF}, +{0x2175, 0x03}, +{0x2176, 0xFF}, +{0x2177, 0x03}, +{0x2178, 0x7E}, +{0x2179, 0x00}, +{0x217A, 0xBD}, +{0x217B, 0x00}, +{0x217C, 0xEC}, +{0x217D, 0x01}, +{0x217E, 0x7B}, +{0x217F, 0x02}, +{0x2180, 0xD1}, +{0x2181, 0x02}, +{0x2182, 0x25}, +{0x2183, 0x03}, +{0x2184, 0x7F}, +{0x2185, 0x00}, +{0x2186, 0xBD}, +{0x2187, 0x00}, +{0x2188, 0xED}, +{0x2189, 0x01}, +{0x218A, 0x7B}, +{0x218B, 0x02}, +{0x218C, 0xD2}, +{0x218D, 0x02}, +{0x218E, 0x25}, +{0x218F, 0x03}, +{0x2190, 0xFF}, +{0x2191, 0x03}, +{0x2192, 0xFF}, +{0x2193, 0x03}, +{0x2194, 0xE9}, +{0x2195, 0x01}, +{0x2196, 0x21}, +{0x2197, 0x03}, +{0x2198, 0x17}, +{0x2199, 0xFC}, +{0x219A, 0x7F}, +{0x219B, 0x01}, +{0x219C, 0xFF}, +{0x219D, 0x03}, +{0x21A0, 0x1B}, +{0x21A1, 0x1B}, +{0x21A2, 0x1B}, +{0x21A3, 0x01}, +{0x21A4, 0x2E}, +{0x21A5, 0x80}, +{0x21A6, 0x00}, +{0x21A8, 0x04}, +{0x21A9, 0x98}, +{0x21AA, 0x60}, +{0x21AB, 0x03}, +{0x21AC, 0x7F}, +{0x21AD, 0x80}, +{0x21AE, 0x09}, +{0x21B0, 0x1C}, +{0x21B1, 0x00}, +{0x21B2, 0xA0}, +{0x21B3, 0x00}, +{0x21B4, 0x0C}, +{0x21B5, 0x00}, +{0x21B6, 0x2D}, +{0x21B7, 0x00}, +{0x21B8, 0x20}, +{0x21B9, 0x00}, +{0x21BA, 0x02}, +{0x21BB, 0x00}, +{0x21BC, 0xCC}, +{0x21BD, 0x00}, +{0x21BE, 0x4A}, +{0x21BF, 0x00}, +{0x21C0, 0xD0}, +{0x21C1, 0x00}, +{0x21C2, 0x44}, +{0x21C3, 0x00}, +{0x21C4, 0x00}, +{0x21C5, 0xE0}, +{0x21C6, 0x00}, +{0x21C8, 0x11}, +{0x21C9, 0x00}, +{0x21CA, 0x02}, +{0x21CC, 0x08}, +{0x21CD, 0xC0}, +{0x21CE, 0x0C}, +{0x21D0, 0x44}, +{0x21D1, 0x00}, +{0x21D2, 0x02}, +{0x21D4, 0x02}, +{0x21D5, 0x20}, +{0x21D6, 0x2C}, +{0x21D8, 0xFE}, +{0x21D9, 0x9D}, +{0x21DA, 0xDF}, +{0x21DB, 0x03}, +{0x21DC, 0x62}, +{0x21DD, 0x01}, +{0x21DE, 0x7F}, +{0x21DF, 0x00}, +{0x21E0, 0xB7}, +{0x21E1, 0x01}, +{0x21E2, 0xB5}, +{0x21E3, 0x01}, +{0x21E4, 0xC1}, +{0x21E5, 0x02}, +{0x21E6, 0xBF}, +{0x21E7, 0x02}, +{0x21E8, 0xB3}, +{0x21E9, 0x4D}, +{0x21EA, 0x00}, +{0x21EB, 0x04}, +{0x21EC, 0x90}, +{0x21ED, 0x07}, +{0x21EE, 0xD0}, +{0x21EF, 0x04}, +{0x21F0, 0xCC}, +{0x21F1, 0x04}, +{0x21F4, 0x02}, +{0x21F5, 0x00}, +{0x21F6, 0x00}, +{0x21F8, 0x00}, +{0x21F9, 0x00}, +{0x21FC, 0x28}, +{0x21FD, 0x00}, +{0x21FE, 0x00}, +{0x21FF, 0x00}, +{0x2200, 0x00}, +{0x2204, 0x56}, +{0x2205, 0x04}, +{0x2206, 0xE2}, +{0x2207, 0x04}, +{0x2208, 0x0A}, +{0x2209, 0x00}, +{0x220C, 0x47}, +{0x220D, 0x00}, +{0x220E, 0x1F}, +{0x220F, 0x00}, +{0x2210, 0x17}, +{0x2211, 0x00}, +{0x2212, 0x0F}, +{0x2213, 0x00}, +{0x2214, 0x17}, +{0x2215, 0x00}, +{0x2216, 0x47}, +{0x2217, 0x00}, +{0x2218, 0x0F}, +{0x2219, 0x00}, +{0x221A, 0x0F}, +{0x221B, 0x00}, +{0x221C, 0x03}, +{0x2220, 0x20}, +{0x2221, 0x10}, +{0x2222, 0x11}, +{0x2223, 0x01}, +{0x2224, 0xC7}, +{0x2225, 0xAA}, +{0x2226, 0x80}, +{0x2227, 0x08}, +{0x2228, 0x01}, +{0x2260, 0xFF}, +{0x2261, 0x1F}, +{0x2262, 0x00}, +{0x2263, 0x00}, +{0x2264, 0x00}, +{0x2265, 0x00}, +{0x2266, 0xFF}, +{0x2267, 0x1F}, +{0x2268, 0x00}, +{0x2269, 0x00}, +{0x226A, 0xFF}, +{0x226B, 0x1F}, +{0x226C, 0x00}, +{0x226D, 0x00}, +{0x226E, 0xFF}, +{0x226F, 0x1F}, +{0x227C, 0xFF}, +{0x227D, 0x1F}, +{0x227E, 0x00}, +{0x227F, 0x10}, +{0x2280, 0xFF}, +{0x2281, 0x1F}, +{0x2282, 0x00}, +{0x2283, 0x10}, +{0x2284, 0xFF}, +{0x2285, 0x1F}, +{0x2286, 0x00}, +{0x2287, 0x10}, +{0x22B2, 0x92}, +{0x22B4, 0x20}, +{0x22B5, 0x00}, +{0x22B6, 0x20}, +{0x22B7, 0x00}, +{0x22B8, 0x20}, +{0x22B9, 0x00}, +{0x22BA, 0x20}, +{0x22BB, 0x00}, +{0x22BC, 0x20}, +{0x22BD, 0x00}, +{0x22BE, 0x20}, +{0x22BF, 0x00}, +{0x22C0, 0x20}, +{0x22C1, 0x00}, +{0x22C2, 0x20}, +{0x22C3, 0x00}, +{0x22C4, 0x20}, +{0x22C5, 0x00}, +{0x22C6, 0x20}, +{0x22C7, 0x00}, +{0x22C8, 0x20}, +{0x22C9, 0x00}, +{0x22CA, 0x20}, +{0x22CB, 0x00}, +{0x22CC, 0x20}, +{0x22CD, 0x00}, +{0x22CE, 0x20}, +{0x22CF, 0x00}, +{0x22DA, 0x00}, +{0x22EF, 0x82}, +{0x2308, 0x01}, +{0x2310, 0x73}, +{0x2311, 0x89}, +{0x2318, 0x40}, +{0x2319, 0xCD}, +{0x231A, 0x54}, +{0x2324, 0x20}, +{0x2325, 0x00}, +{0x2328, 0x00}, +{0x234A, 0x9F}, +{0x234B, 0x07}, +{0x2354, 0x0C}, +{0x23C0, 0x5D}, +{0x23C2, 0x02}, +{0x244C, 0x00}, +{0x244D, 0x02}, +{0x244E, 0x54}, +{0x244F, 0x02}, +{0x24A0, 0x00}, +{0x24A4, 0x16}, +{0x24A5, 0x01}, +{0x24A6, 0xA6}, +{0x24A7, 0x02}, +{0x24A8, 0xD5}, +{0x24A9, 0x02}, +{0x24BC, 0x17}, +{0x24BD, 0x01}, +{0x24BE, 0xA7}, +{0x24BF, 0x02}, +{0x24C0, 0xD5}, +{0x24C1, 0x02}, +{0x24DA, 0x6F}, +{0x24DB, 0x00}, +{0x24DC, 0x62}, +{0x24DD, 0x01}, +{0x24EA, 0x32}, +{0x24EB, 0x00}, +{0x24EC, 0xDC}, +{0x24ED, 0x00}, +{0x24FA, 0x32}, +{0x24FB, 0x00}, +{0x24FC, 0xDD}, +{0x24FD, 0x00}, +{0x254A, 0x15}, +{0x254B, 0x01}, +{0x255A, 0x15}, +{0x255B, 0x01}, +{0x2560, 0x01}, +{0x2561, 0x00}, +{0x2562, 0x2A}, +{0x2563, 0x00}, +{0x2564, 0xF8}, +{0x2565, 0x00}, +{0x2566, 0x15}, +{0x2567, 0x01}, +{0x2568, 0x0C}, +{0x2569, 0x02}, +{0x256A, 0x31}, +{0x256B, 0x02}, +{0x2578, 0x90}, +{0x2579, 0x01}, +{0x257A, 0x92}, +{0x257B, 0x01}, +{0x257C, 0xB8}, +{0x257D, 0x02}, +{0x257E, 0xBA}, +{0x257F, 0x02}, +{0x2584, 0x90}, +{0x2585, 0x01}, +{0x2586, 0x92}, +{0x2587, 0x01}, +{0x2588, 0xB8}, +{0x2589, 0x02}, +{0x258A, 0xBA}, +{0x258B, 0x02}, +{0x267A, 0xF8}, +{0x267B, 0x00}, +{0x267C, 0x16}, +{0x267D, 0x01}, +{0x267E, 0xA6}, +{0x267F, 0x02}, +{0x2680, 0xD5}, +{0x2681, 0x02}, +{0x2690, 0xF8}, +{0x2691, 0x00}, +{0x2694, 0xA6}, +{0x2695, 0x02}, +{0x2696, 0x16}, +{0x2697, 0x01}, +{0x269A, 0xD5}, +{0x269B, 0x02}, +{0x26B8, 0x10}, +{0x26B9, 0x00}, +{0x26BA, 0x33}, +{0x26BB, 0x00}, +{0x26BC, 0x89}, +{0x26BD, 0x00}, +{0x26BE, 0xB0}, +{0x26BF, 0x00}, +{0x26C4, 0x4E}, +{0x26C5, 0x00}, +{0x26C8, 0xC9}, +{0x26C9, 0x00}, +{0x26CC, 0x35}, +{0x26CD, 0x01}, +{0x26D0, 0xBA}, +{0x26D1, 0x01}, +{0x26D4, 0x7C}, +{0x26D5, 0x02}, +{0x26D8, 0xF6}, +{0x26D9, 0x02}, +{0x26DE, 0x51}, +{0x26DF, 0x00}, +{0x26E0, 0x7F}, +{0x26E1, 0x00}, +{0x26E2, 0xCC}, +{0x26E3, 0x00}, +{0x26E4, 0xF8}, +{0x26E5, 0x00}, +{0x26E6, 0x38}, +{0x26E7, 0x01}, +{0x26E8, 0x65}, +{0x26E9, 0x01}, +{0x26EA, 0xBD}, +{0x26EB, 0x01}, +{0x26EE, 0x7F}, +{0x26EF, 0x02}, +{0x26F0, 0xAB}, +{0x26F1, 0x02}, +{0x26F2, 0xF9}, +{0x26F3, 0x02}, +{0x2722, 0x59}, +{0x2723, 0x02}, +{0x2938, 0x55}, +{0x2939, 0x00}, +{0x293A, 0x17}, +{0x293B, 0x00}, +{0x293C, 0xD0}, +{0x293D, 0x00}, +{0x293E, 0x91}, +{0x293F, 0x00}, +{0x2940, 0x3C}, +{0x2941, 0x01}, +{0x2942, 0x0C}, +{0x2943, 0x01}, +{0x2944, 0xC1}, +{0x2945, 0x01}, +{0x2946, 0x76}, +{0x2947, 0x01}, +{0x2948, 0x83}, +{0x2949, 0x02}, +{0x294A, 0xFB}, +{0x294B, 0x01}, +{0x294C, 0xFD}, +{0x294D, 0x02}, +{0x294E, 0xBF}, +{0x294F, 0x02}, +{0x2A06, 0x25}, +{0x2A07, 0x03}, +{0x2A0C, 0xBE}, +{0x2A0D, 0x00}, +{0x2A0E, 0x7D}, +{0x2A0F, 0x00}, +{0x2A20, 0x00}, +{0x2A21, 0x00}, +{0x2A22, 0x7D}, +{0x2A23, 0x00}, +{0x2B11, 0x19}, +{0x2B13, 0x15}, +{0x2B14, 0x14}, +{0x2B15, 0x13}, +{0x2B16, 0x12}, +{0x2B17, 0x11}, +{0x2B18, 0x10}, +{0x2B19, 0x0F}, +{0x2B1A, 0x0E}, +{0x2B1B, 0x0D}, +{0x2B1C, 0x0C}, +{0x2B1D, 0x0B}, +{0x2B1E, 0x0A}, +{0x2B1F, 0x09}, +{0x2B20, 0x08}, +{0x2B21, 0x07}, +{0x2B22, 0x06}, +{0x2B23, 0x05}, +{0x2B24, 0x04}, +{0x2B25, 0x03}, +{0x2B26, 0x03}, +{0x2B38, 0x01}, +{0x2B45, 0xE3}, +{0x2B50, 0x01}, +{0x2B51, 0x00}, +{0x2B6D, 0x47}, +{0x2B70, 0x04}, +{0x2B71, 0x02}, +{0x2B72, 0x02}, +{0x2B7B, 0x42}, +{0x2B7F, 0x7F}, +{0x2B80, 0x94}, +{0x2B81, 0x06}, +{0x2B87, 0x1B}, +{0x2B88, 0x1A}, +{0x2B89, 0x17}, +{0x2B8A, 0x12}, +{0x2B8B, 0x01}, +{0x2B8D, 0x2B}, +{0x2B8E, 0x2B}, +{0x2B8F, 0x1B}, +{0x2B90, 0xBF}, +{0x2B91, 0x0F}, +{0x2B92, 0x73}, +{0x2B93, 0x0E}, +{0x2B94, 0xBF}, +{0x2B95, 0x07}, +{0x2B96, 0x73}, +{0x2B97, 0x0E}, +{0x2B98, 0xBF}, +{0x2B99, 0x57}, +{0x2B9B, 0x2A}, +{0x2BA8, 0xBC}, +{0x2BA9, 0x62}, +{0x2BC5, 0x80}, +{0x2BD5, 0x30}, +{0x2BD6, 0xF0}, +{0x2BD8, 0x5B}, +{0x2BD9, 0xF0}, +{0x2BDA, 0x21}, +{0x2BDB, 0x0E}, +{0x2BDC, 0x5E}, +{0x2BFE, 0x02}, +{0x2BFF, 0x00}, +{0x2C98, 0xE1}, +{0x2C99, 0x2E}, +{0x2C9B, 0x80}, +{0x2CA9, 0x80}, +{0x2CAA, 0x01}, +{0x2CBF, 0x08}, +{0x2D39, 0x0E}, +{0x2D50, 0x80}, +{0x2D54, 0x00}, +{0x2D5B, 0x58}, +{0x2D64, 0x66}, // GRBG +{0x2D65, 0x80}, // GRBG +{0x2DFC, 0x00}, // disable spmg +{0x2DFD, 0x01}, +{0x2E24, 0x01}, // disable spmg +{0x3000, 0x00}, +{0x3001, 0x00}, +{0x3002, 0x23}, +{0x3003, 0xA1}, +{0x3004, 0x00}, +{0x3005, 0x20}, +{0x3006, 0x6E}, +{0x3007, 0x00}, +{0x3008, 0x06}, +{0x3009, 0xB4}, +{0x300A, 0x1F}, +{0x300B, 0x00}, +{0x300C, 0x00}, +{0x300D, 0x1B}, +{0x300E, 0x90}, +{0x300F, 0x97}, +{0x3010, 0x00}, +{0x3011, 0x00}, +{0x3012, 0x11}, +{0x3013, 0x21}, +{0x3014, 0x00}, +{0x3015, 0x20}, +{0x3016, 0x94}, +{0x3017, 0x00}, +{0x3018, 0x30}, +{0x3019, 0x09}, +{0x301A, 0x46}, +{0x301B, 0x00}, +{0x30A0, 0xCD}, +{0x30A1, 0x0C}, +{0x30A2, 0xBA}, +{0x30A3, 0x0C}, +{0x30A4, 0x5F}, +{0x30A5, 0x00}, +{0x30A6, 0x43}, +{0x30A7, 0x00}, +{0x30A8, 0xC8}, +{0x30A9, 0x0A}, +{0x30AA, 0x0B}, +{0x30AB, 0x08}, +{0x30AC, 0x4B}, +{0x30AD, 0x03}, +{0x30AE, 0x37}, +{0x30AF, 0x03}, +{0x30B0, 0x65}, +{0x30B1, 0x09}, +{0x30B2, 0x7A}, +{0x30B3, 0x09}, +{0x30B4, 0x20}, +{0x30B5, 0x00}, +{0x30B6, 0x28}, +{0x30B7, 0x00}, +{0x30B8, 0x91}, +{0x30B9, 0x04}, +{0x30BA, 0x75}, +{0x30BB, 0x05}, +{0x30BC, 0x7C}, +{0x30BD, 0x01}, +{0x30BE, 0x87}, +{0x30BF, 0x01}, +{0x3370, 0x01}, +{0x3374, 0x00}, +{0x3375, 0x00}, +{0x3376, 0x01}, +{0x3377, 0x00}, +{0x3410, 0x80}, +{0x3411, 0x07}, +{0x3418, 0xB0}, +{0x3419, 0x04}, +{0x3450, 0x00}, /* use continuous clock */ +{0x34BE, 0x7A}, +{0x34BF, 0x02}, +{0x3584, 0x00}, +{0x3586, 0x00}, +{0x3587, 0x01}, +{0x3588, 0xE6}, +{0x3589, 0x00}, +{0x3590, 0x00}, +{0x3591, 0x00}, +{0x3594, 0x40}, +{0x3598, 0x03}, +{0x3599, 0x00}, +{0x359A, 0x80}, +{0x359B, 0x00}, +{0x359C, 0x00}, +{0x359D, 0x01}, +{0x359E, 0x00}, +{0x359F, 0x02}, +{0x35A0, 0x00}, +{0x35A1, 0x04}, +{0x35A2, 0x20}, +{0x35A3, 0x00}, +{0x35A4, 0x40}, +{0x35A5, 0x00}, +{0x35A6, 0x80}, +{0x35A7, 0x00}, +{0x35A8, 0x00}, +{0x35A9, 0x01}, +{0x35AC, 0x80}, +{0x35AD, 0x00}, +{0x35AE, 0x00}, +{0x35AF, 0x01}, +{0x35B0, 0x00}, +{0x35B1, 0x02}, +{0x35B2, 0x00}, +{0x35B3, 0x04}, +{0x35B4, 0x02}, +{0x35B5, 0x00}, +{0x35B6, 0x04}, +{0x35B7, 0x00}, +{0x35B8, 0x08}, +{0x35B9, 0x00}, +{0x35BA, 0x10}, +{0x35BB, 0x00}, +{0x35C8, 0x00}, +{0x35C9, 0x01}, +{0x35CA, 0x00}, +{0x35CB, 0x04}, +{0x35CC, 0x00}, +{0x35CD, 0x10}, +{0x35CE, 0x00}, +{0x35CF, 0x40}, +{0x35D0, 0x00}, +{0x35D1, 0x0C}, +{0x35D2, 0x00}, +{0x35D3, 0x0C}, +{0x35D4, 0x00}, +{0x35D5, 0x0C}, +{0x35D6, 0x00}, +{0x35D7, 0x0C}, +{0x35D8, 0x00}, +{0x35D9, 0x00}, +{0x35DA, 0x08}, +{0x35DB, 0x00}, +{0x35DC, 0xD8}, +{0x35DD, 0x0E}, +{0x35F0, 0x00}, +{0x35F1, 0x10}, +{0x35F2, 0x00}, +{0x35F3, 0x10}, +{0x35F4, 0x00}, +{0x35F5, 0x10}, +{0x35F6, 0x00}, +{0x35F7, 0x04}, +{0x35F8, 0x00}, +{0x35F9, 0x03}, +{0x35FA, 0x38}, +{0x35FB, 0x00}, +{0x35FC, 0xB3}, +{0x35FD, 0x01}, +{0x35FE, 0x00}, +{0x35FF, 0x00}, +{0x3600, 0x05}, +{0x3601, 0x06}, +{0x3604, 0x03}, +{0x3605, 0x00}, +{0x3608, 0x03}, +{0x3609, 0x00}, +{0x360C, 0x01}, +{0x360D, 0x00}, +{0x3610, 0x10}, +{0x3611, 0x01}, +{0x3612, 0x00}, +{0x3613, 0x00}, +{0x3614, 0x00}, +{0x3615, 0x00}, +{0x361C, 0x00}, +{0x361D, 0x01}, +{0x361E, 0x01}, +{0x361F, 0x00}, +{0x3620, 0x00}, +{0x3621, 0x01}, +{0x3622, 0xB0}, +{0x3623, 0x04}, +{0x3624, 0xDC}, +{0x3625, 0x05}, +{0x3626, 0x00}, +{0x3627, 0x01}, +{0x3628, 0xFF}, +{0x3629, 0x0F}, +{0x362A, 0x00}, +{0x362B, 0x10}, +{0x362C, 0x00}, +{0x362D, 0x01}, +{0x3630, 0x42}, +{0x3631, 0x00}, +{0x3632, 0x43}, +{0x3633, 0x00}, +{0x3634, 0x43}, +{0x3635, 0x00}, +{0x3636, 0x43}, +{0x3637, 0x00}, +{0x3638, 0x47}, +{0x3639, 0x00}, +{0x363A, 0x46}, +{0x363B, 0x00}, +{0x363C, 0x45}, +{0x363D, 0x00}, +{0x363E, 0x46}, +{0x363F, 0x00}, +{0x3650, 0x00}, // set to default from 0x01 FSYNC Enable/Disable +{0x36C4, 0xFF}, +{0x36C5, 0x0F}, +{0x36C6, 0xFF}, +{0x36C7, 0x0F}, +{0x36C8, 0xFF}, +{0x36C9, 0x0F}, +{0x36CC, 0x00}, +{0x36CD, 0x00}, +{0x36CE, 0x00}, +{0x36CF, 0x00}, +{0x36D0, 0x00}, +{0x36D1, 0x00}, +{0x36D4, 0xFF}, +{0x36D5, 0x0F}, +{0x36D6, 0xFF}, +{0x36D7, 0x0F}, +{0x36D8, 0xFF}, +{0x36D9, 0x0F}, +{0x36DC, 0xFF}, +{0x36DD, 0x0F}, +{0x36DE, 0xFF}, +{0x36DF, 0x0F}, +{0x36E0, 0xFF}, +{0x36E1, 0x0F}, +{0x36E4, 0xFF}, +{0x36E5, 0x0F}, +{0x36E6, 0xFF}, +{0x36E7, 0x0F}, +{0x36E8, 0xFF}, +{0x36E9, 0x0F}, +{0x36EE, 0x00}, +{0x36EF, 0x00}, +{0x36F0, 0x00}, +{0x36F1, 0x80}, +{0x36F8, 0x00}, +{0x3700, 0x03}, +{0x3701, 0x05}, +{0x3702, 0x03}, +{0x3703, 0x04}, +{0x3704, 0x08}, +{0x3705, 0x03}, +{0x3706, 0x03}, +{0x3707, 0x03}, +{0x3708, 0x03}, +{0x3709, 0x03}, +{0x370A, 0x03}, +{0x370B, 0x03}, +{0x370C, 0x03}, +{0x370D, 0x03}, +{0x370E, 0x0E}, +{0x3718, 0x62}, +{0x3719, 0x4A}, +{0x371A, 0x38}, +{0x371B, 0x20}, +{0x371C, 0x64}, +{0x371D, 0x42}, +{0x371E, 0x32}, +{0x371F, 0x1B}, +{0x3720, 0x98}, +{0x3721, 0xA0}, +{0x3722, 0xA8}, +{0x3723, 0xB0}, +{0x3748, 0xA5}, +{0x3749, 0x9B}, +{0x374A, 0x91}, +{0x374B, 0x7D}, +{0x37C0, 0x00}, +{0x37C1, 0x00}, +{0x37C2, 0x00}, +{0x37C4, 0x00}, +{0x37C5, 0x00}, +{0x37C6, 0x00}, +{0x37C8, 0x00}, +{0x37C9, 0x00}, +{0x37CA, 0x00}, +{0x37CC, 0x00}, +{0x37CD, 0x00}, +{0x37CE, 0x00}, +{0x37D0, 0x00}, +{0x37D1, 0x00}, +{0x37D2, 0x00}, +{0x37D4, 0x00}, +{0x37D5, 0x00}, +{0x37D6, 0x00}, +{0x37D8, 0x00}, +{0x37D9, 0x00}, +{0x37DA, 0x00}, +{0x37DC, 0x00}, +{0x37DD, 0x00}, +{0x37DE, 0x00}, +{0x37E0, 0x00}, +{0x37E1, 0x00}, +{0x37E2, 0x00}, +{0x37E4, 0x00}, +{0x37E5, 0x00}, +{0x37E6, 0x00}, +{0x37E8, 0x00}, +{0x37E9, 0x00}, +{0x37EA, 0x00}, +{0x37EC, 0x00}, +{0x37ED, 0x00}, +{0x37EE, 0x00}, +{0x37F0, 0x00}, +{0x37F4, 0x00}, +{0x37F5, 0x1E}, +{0x37F6, 0x34}, +{0x37F7, 0x00}, +{0x37F8, 0xFF}, +{0x37F9, 0xFF}, +{0x37FA, 0x03}, +{0x37FC, 0x00}, +{0x37FD, 0x00}, +{0x37FE, 0x04}, +{0x3800, 0xFF}, +{0x3801, 0xFF}, +{0x3802, 0x03}, +{0x3804, 0x00}, +{0x3805, 0x00}, +{0x3806, 0x04}, +{0x3808, 0x00}, +{0x3809, 0x00}, +{0x380A, 0x00}, +{0x380C, 0x00}, +{0x380D, 0x00}, +{0x380E, 0x00}, +{0x3810, 0x00}, +{0x3811, 0x00}, +{0x3812, 0x00}, +{0x3814, 0x00}, +{0x3815, 0x00}, +{0x3816, 0x00}, +{0x3818, 0x00}, +{0x3819, 0x00}, +{0x381A, 0x00}, +{0x381C, 0x00}, +{0x381D, 0x00}, +{0x381E, 0x00}, +{0x3820, 0x00}, +{0x3821, 0x00}, +{0x3822, 0x00}, +{0x3824, 0x00}, +{0x3825, 0x00}, +{0x3826, 0x00}, +{0x3828, 0x00}, +{0x3829, 0x00}, +{0x382A, 0x00}, +{0x382C, 0x00}, +{0x382D, 0x00}, +{0x382E, 0x00}, +{0x3830, 0x00}, +{0x3831, 0x00}, +{0x3832, 0x00}, +{0x3834, 0x00}, +{0x3835, 0x00}, +{0x3836, 0x00}, +{0x3838, 0xE7}, +{0x3839, 0xFF}, +{0x383A, 0x0C}, +{0x383B, 0x00}, +{0x383C, 0xFD}, +{0x383D, 0xFF}, +{0x383E, 0xF7}, +{0x383F, 0xFF}, +{0x3840, 0x00}, +{0x3841, 0x00}, +{0x3842, 0x00}, +{0x3843, 0x00}, +{0x3844, 0x00}, +{0x3845, 0x00}, +{0x3846, 0x00}, +{0x3847, 0x00}, +{0x3848, 0xB6}, +{0x3849, 0xFF}, +{0x384A, 0xB6}, +{0x384B, 0xFF}, +{0x384C, 0xB3}, +{0x384D, 0xFF}, +{0x384E, 0xAF}, +{0x384F, 0xFF}, +{0x3850, 0xFF}, +{0x3851, 0x0F}, +{0x3852, 0x00}, +{0x3853, 0x10}, +{0x3854, 0xFF}, +{0x3855, 0x0F}, +{0x3856, 0x00}, +{0x3857, 0x10}, +{0x3858, 0xFF}, +{0x3859, 0x0F}, +{0x385A, 0x00}, +{0x385B, 0x10}, +{0x385C, 0x02}, +{0x385D, 0x00}, +{0x385E, 0x06}, +{0x385F, 0x00}, +{0x3860, 0x06}, +{0x3861, 0x00}, +{0x3862, 0x08}, +{0x3863, 0x00}, +{0x3864, 0x02}, +{0x3865, 0x00}, +{0x3870, 0x00}, +{0x3871, 0x01}, +{0x38A0, 0x01}, +{0x38A1, 0x01}, +{0x38A2, 0x00}, +{0x38A3, 0x01}, +{0x38A4, 0x03}, +{0x38A5, 0x00}, +{0x38A6, 0x04}, +{0x38A7, 0x04}, +{0x38A8, 0x00}, +{0x38A9, 0x00}, +{0x38AC, 0x00}, +{0x38AD, 0x00}, +{0x38AE, 0x01}, +{0x38B0, 0x02}, +{0x38B2, 0x06}, +{0x38B3, 0x00}, +{0x38B4, 0x02}, +{0x38B5, 0x00}, +{0x38B6, 0x01}, +{0x38B7, 0x00}, +{0x38B8, 0x01}, +{0x38B9, 0x00}, +{0x38BA, 0x20}, +{0x38BB, 0x00}, +{0x38BC, 0x14}, +{0x38BD, 0x00}, +{0x38BE, 0x0C}, +{0x38BF, 0x00}, +{0x38C0, 0x09}, +{0x38C1, 0x00}, +{0x38C2, 0x27}, +{0x38C3, 0x00}, +{0x38C4, 0x20}, +{0x38C5, 0x00}, +{0x38C6, 0x13}, +{0x38C7, 0x00}, +{0x38C8, 0x0C}, +{0x38C9, 0x00}, +{0x38CA, 0x35}, +{0x38CB, 0x00}, +{0x38CC, 0x25}, +{0x38CD, 0x00}, +{0x38CE, 0x1B}, +{0x38CF, 0x00}, +{0x38D0, 0x11}, +{0x38D1, 0x00}, +{0x38D2, 0x4E}, +{0x38D3, 0x00}, +{0x38D4, 0x31}, +{0x38D5, 0x00}, +{0x38D6, 0x25}, +{0x38D7, 0x00}, +{0x38D8, 0x18}, +{0x38D9, 0x00}, +{0x38DA, 0x6E}, +{0x38DB, 0x00}, +{0x38DC, 0x46}, +{0x38DD, 0x00}, +{0x38DE, 0x32}, +{0x38DF, 0x00}, +{0x38E0, 0x22}, +{0x38E1, 0x00}, +{0x38E2, 0x93}, +{0x38E3, 0x00}, +{0x38E4, 0x5F}, +{0x38E5, 0x00}, +{0x38E6, 0x44}, +{0x38E7, 0x00}, +{0x38E8, 0x31}, +{0x38E9, 0x00}, +{0x38EA, 0xD8}, +{0x38EB, 0x00}, +{0x38EC, 0x8D}, +{0x38ED, 0x00}, +{0x38EE, 0x6A}, +{0x38EF, 0x00}, +{0x38F0, 0x49}, +{0x38F1, 0x00}, +{0x38F2, 0x22}, +{0x38F3, 0x01}, +{0x38F4, 0xC8}, +{0x38F5, 0x00}, +{0x38F6, 0x8E}, +{0x38F7, 0x00}, +{0x38F8, 0x60}, +{0x38F9, 0x00}, +{0x38FA, 0x00}, +{0x38FB, 0x01}, +{0x38FC, 0x00}, +{0x38FD, 0x01}, +{0x38FE, 0x00}, +{0x38FF, 0x01}, +{0x3900, 0x00}, +{0x3901, 0x01}, +{0x3902, 0x60}, +{0x3903, 0x00}, +{0x3904, 0x25}, +{0x3905, 0x00}, +{0x3906, 0x18}, +{0x3907, 0x00}, +{0x3908, 0x10}, +{0x3909, 0x00}, +{0x390A, 0xFF}, +{0x390B, 0x00}, +{0x390C, 0xD5}, +{0x390D, 0x00}, +{0x390E, 0xAA}, +{0x390F, 0x00}, +{0x3910, 0x85}, +{0x3911, 0x00}, +{0x3912, 0xFF}, +{0x3913, 0x00}, +{0x3914, 0xD5}, +{0x3915, 0x00}, +{0x3916, 0xAA}, +{0x3917, 0x00}, +{0x3918, 0x85}, +{0x3919, 0x00}, +{0x391A, 0xFF}, +{0x391B, 0x00}, +{0x391C, 0xD5}, +{0x391D, 0x00}, +{0x391E, 0xAA}, +{0x391F, 0x00}, +{0x3920, 0x85}, +{0x3921, 0x00}, +{0x3922, 0x40}, +{0x3923, 0x00}, +{0x3924, 0x40}, +{0x3925, 0x00}, +{0x3926, 0x40}, +{0x3927, 0x00}, +{0x3928, 0x40}, +{0x3929, 0x00}, +{0x392A, 0x80}, +{0x392B, 0x00}, +{0x392C, 0x80}, +{0x392D, 0x00}, +{0x392E, 0x80}, +{0x392F, 0x00}, +{0x3930, 0x80}, +{0x3931, 0x00}, +{0x3932, 0x4C}, +{0x3933, 0x4C}, +{0x3934, 0x4C}, +{0x3940, 0x01}, +{0x3941, 0x01}, +{0x3942, 0x00}, +{0x3943, 0x01}, +{0x3944, 0x03}, +{0x3945, 0x00}, +{0x3946, 0x04}, +{0x3947, 0x04}, +{0x3948, 0x00}, +{0x3949, 0x00}, +{0x394C, 0x00}, +{0x394D, 0x00}, +{0x394E, 0x01}, +{0x3950, 0x03}, +{0x3952, 0x05}, +{0x3953, 0x00}, +{0x3954, 0x02}, +{0x3955, 0x00}, +{0x3956, 0x02}, +{0x3957, 0x00}, +{0x3958, 0x01}, +{0x3959, 0x00}, +{0x395A, 0x12}, +{0x395B, 0x00}, +{0x395C, 0x09}, +{0x395D, 0x00}, +{0x395E, 0x07}, +{0x395F, 0x00}, +{0x3960, 0x04}, +{0x3961, 0x00}, +{0x3962, 0x1A}, +{0x3963, 0x00}, +{0x3964, 0x0E}, +{0x3965, 0x00}, +{0x3966, 0x09}, +{0x3967, 0x00}, +{0x3968, 0x06}, +{0x3969, 0x00}, +{0x396A, 0x21}, +{0x396B, 0x00}, +{0x396C, 0x11}, +{0x396D, 0x00}, +{0x396E, 0x0C}, +{0x396F, 0x00}, +{0x3970, 0x08}, +{0x3971, 0x00}, +{0x3972, 0x29}, +{0x3973, 0x00}, +{0x3974, 0x18}, +{0x3975, 0x00}, +{0x3976, 0x11}, +{0x3977, 0x00}, +{0x3978, 0x0D}, +{0x3979, 0x00}, +{0x397A, 0x3A}, +{0x397B, 0x00}, +{0x397C, 0x21}, +{0x397D, 0x00}, +{0x397E, 0x19}, +{0x397F, 0x00}, +{0x3980, 0x12}, +{0x3981, 0x00}, +{0x3982, 0x52}, +{0x3983, 0x00}, +{0x3984, 0x2F}, +{0x3985, 0x00}, +{0x3986, 0x24}, +{0x3987, 0x00}, +{0x3988, 0x18}, +{0x3989, 0x00}, +{0x398A, 0x78}, +{0x398B, 0x00}, +{0x398C, 0x44}, +{0x398D, 0x00}, +{0x398E, 0x34}, +{0x398F, 0x00}, +{0x3990, 0x27}, +{0x3991, 0x00}, +{0x3992, 0xA1}, +{0x3993, 0x00}, +{0x3994, 0x61}, +{0x3995, 0x00}, +{0x3996, 0x4E}, +{0x3997, 0x00}, +{0x3998, 0x37}, +{0x3999, 0x00}, +{0x399A, 0x00}, +{0x399B, 0x01}, +{0x399C, 0x00}, +{0x399D, 0x01}, +{0x399E, 0x00}, +{0x399F, 0x01}, +{0x39A0, 0x00}, +{0x39A1, 0x01}, +{0x39A2, 0x60}, +{0x39A3, 0x00}, +{0x39A4, 0x20}, +{0x39A5, 0x00}, +{0x39A6, 0x15}, +{0x39A7, 0x00}, +{0x39A8, 0x10}, +{0x39A9, 0x00}, +{0x39AA, 0xFF}, +{0x39AB, 0x00}, +{0x39AC, 0xD5}, +{0x39AD, 0x00}, +{0x39AE, 0xAA}, +{0x39AF, 0x00}, +{0x39B0, 0x85}, +{0x39B1, 0x00}, +{0x39B2, 0xFF}, +{0x39B3, 0x00}, +{0x39B4, 0xD5}, +{0x39B5, 0x00}, +{0x39B6, 0xAA}, +{0x39B7, 0x00}, +{0x39B8, 0x85}, +{0x39B9, 0x00}, +{0x39BA, 0xFF}, +{0x39BB, 0x00}, +{0x39BC, 0xD5}, +{0x39BD, 0x00}, +{0x39BE, 0xAA}, +{0x39BF, 0x00}, +{0x39C0, 0x85}, +{0x39C1, 0x00}, +{0x39C2, 0x40}, +{0x39C3, 0x00}, +{0x39C4, 0x40}, +{0x39C5, 0x00}, +{0x39C6, 0x40}, +{0x39C7, 0x00}, +{0x39C8, 0x40}, +{0x39C9, 0x00}, +{0x39CA, 0x80}, +{0x39CB, 0x00}, +{0x39CC, 0x80}, +{0x39CD, 0x00}, +{0x39CE, 0x80}, +{0x39CF, 0x00}, +{0x39D0, 0x80}, +{0x39D1, 0x00}, +{0x39D2, 0x4C}, +{0x39D3, 0x4C}, +{0x39D4, 0x4C}, +{0x39E0, 0x01}, +{0x39E1, 0x01}, +{0x39E4, 0x40}, +{0x39E5, 0x01}, +{0x39E6, 0x01}, +{0x39E8, 0x00}, +{0x39E9, 0x01}, +{0x39EA, 0x00}, +{0x39EB, 0x00}, +{0x39EC, 0x01}, +{0x39ED, 0x00}, +{0x39EE, 0x01}, +{0x39F0, 0x03}, +{0x39F1, 0x04}, +{0x39F2, 0x0E}, +{0x39F4, 0x0B}, +{0x39F5, 0x00}, +{0x39F6, 0x07}, +{0x39F7, 0x00}, +{0x39F8, 0x05}, +{0x39F9, 0x00}, +{0x39FA, 0x02}, +{0x39FB, 0x00}, +{0x39FC, 0x34}, +{0x39FD, 0x00}, +{0x39FE, 0x1B}, +{0x39FF, 0x00}, +{0x3A00, 0x13}, +{0x3A01, 0x00}, +{0x3A02, 0x09}, +{0x3A03, 0x00}, +{0x3A04, 0x4D}, +{0x3A05, 0x00}, +{0x3A06, 0x22}, +{0x3A07, 0x00}, +{0x3A08, 0x14}, +{0x3A09, 0x00}, +{0x3A0A, 0x09}, +{0x3A0B, 0x00}, +{0x3A0C, 0x61}, +{0x3A0D, 0x00}, +{0x3A0E, 0x22}, +{0x3A0F, 0x00}, +{0x3A10, 0x15}, +{0x3A11, 0x00}, +{0x3A12, 0x0A}, +{0x3A13, 0x00}, +{0x3A14, 0x6D}, +{0x3A15, 0x00}, +{0x3A16, 0x24}, +{0x3A17, 0x00}, +{0x3A18, 0x16}, +{0x3A19, 0x00}, +{0x3A1A, 0x0B}, +{0x3A1B, 0x00}, +{0x3A1C, 0x6F}, +{0x3A1D, 0x00}, +{0x3A1E, 0x26}, +{0x3A1F, 0x00}, +{0x3A20, 0x18}, +{0x3A21, 0x00}, +{0x3A22, 0x0E}, +{0x3A23, 0x00}, +{0x3A24, 0x72}, +{0x3A25, 0x00}, +{0x3A26, 0x2B}, +{0x3A27, 0x00}, +{0x3A28, 0x1E}, +{0x3A29, 0x00}, +{0x3A2A, 0x13}, +{0x3A2B, 0x00}, +{0x3A2C, 0x7B}, +{0x3A2D, 0x00}, +{0x3A2E, 0x37}, +{0x3A2F, 0x00}, +{0x3A30, 0x29}, +{0x3A31, 0x00}, +{0x3A32, 0x1F}, +{0x3A33, 0x00}, +{0x3A34, 0x94}, +{0x3A35, 0x00}, +{0x3A36, 0x4E}, +{0x3A37, 0x00}, +{0x3A38, 0x42}, +{0x3A39, 0x00}, +{0x3A3A, 0x36}, +{0x3A3B, 0x00}, +{0x3A3C, 0x00}, +{0x3A3D, 0x01}, +{0x3A3E, 0x00}, +{0x3A3F, 0x01}, +{0x3A40, 0x00}, +{0x3A41, 0x01}, +{0x3A42, 0x00}, +{0x3A43, 0x01}, +{0x3A44, 0x70}, +{0x3A45, 0x00}, +{0x3A46, 0x25}, +{0x3A47, 0x00}, +{0x3A48, 0x18}, +{0x3A49, 0x00}, +{0x3A4A, 0x10}, +{0x3A4B, 0x00}, +{0x3A4C, 0xFF}, +{0x3A4D, 0x00}, +{0x3A4E, 0xD5}, +{0x3A4F, 0x00}, +{0x3A50, 0xAA}, +{0x3A51, 0x00}, +{0x3A52, 0x85}, +{0x3A53, 0x00}, +{0x3A54, 0xFF}, +{0x3A55, 0x00}, +{0x3A56, 0xD5}, +{0x3A57, 0x00}, +{0x3A58, 0xAA}, +{0x3A59, 0x00}, +{0x3A5A, 0x85}, +{0x3A5B, 0x00}, +{0x3A5C, 0xFF}, +{0x3A5D, 0x00}, +{0x3A5E, 0xD5}, +{0x3A5F, 0x00}, +{0x3A60, 0xAA}, +{0x3A61, 0x00}, +{0x3A62, 0x85}, +{0x3A63, 0x00}, +{0x3A64, 0x1C}, +{0x3A65, 0x00}, +{0x3A66, 0x13}, +{0x3A67, 0x00}, +{0x3A68, 0x0D}, +{0x3A69, 0x00}, +{0x3A6A, 0x07}, +{0x3A6B, 0x00}, +{0x3A6C, 0x0D}, +{0x3A6D, 0x00}, +{0x3A6E, 0x0B}, +{0x3A6F, 0x00}, +{0x3A70, 0x06}, +{0x3A71, 0x00}, +{0x3A72, 0x05}, +{0x3A73, 0x00}, +{0x3A74, 0x19}, +{0x3A75, 0x00}, +{0x3A76, 0x14}, +{0x3A77, 0x00}, +{0x3A78, 0x0F}, +{0x3A79, 0x00}, +{0x3A7A, 0x0A}, +{0x3A7B, 0x00}, +{0x3A7C, 0x80}, +{0x3A7D, 0x00}, +{0x3A7E, 0x80}, +{0x3A7F, 0x00}, +{0x3A80, 0x80}, +{0x3A81, 0x00}, +{0x3A82, 0x80}, +{0x3A83, 0x00}, +{0x3A84, 0x08}, +{0x3A85, 0x00}, +{0x3A86, 0x05}, +{0x3A87, 0x00}, +{0x3A88, 0x04}, +{0x3A89, 0x00}, +{0x3A8A, 0x03}, +{0x3A8B, 0x00}, +{0x3A8C, 0xCD}, +{0x3A8D, 0x00}, +{0x3A8E, 0xAA}, +{0x3A8F, 0x00}, +{0x3A90, 0x8C}, +{0x3A91, 0x00}, +{0x3A92, 0x64}, +{0x3A93, 0x00}, +{0x3A94, 0xCD}, +{0x3A95, 0x00}, +{0x3A96, 0xAA}, +{0x3A97, 0x00}, +{0x3A98, 0x8C}, +{0x3A99, 0x00}, +{0x3A9A, 0x64}, +{0x3A9B, 0x00}, +{0x3A9C, 0x08}, +{0x3A9D, 0x10}, +{0x3A9E, 0x4C}, +{0x3A9F, 0x4C}, +{0x3AA0, 0x4C}, +{0x3AA1, 0x04}, +{0x3AA2, 0x04}, +{0x3AC0, 0x01}, +{0x3AC4, 0x81}, +{0x3AC5, 0x00}, +{0x3AC6, 0x00}, +{0x3AC7, 0x00}, +{0x3AC8, 0x00}, +{0x3AC9, 0x00}, +{0x3ACA, 0x00}, +{0x3ACB, 0x00}, +{0x3ACC, 0x02}, +{0x3ACD, 0x00}, +{0x3ACE, 0x81}, +{0x3ACF, 0x00}, +{0x3AD0, 0x00}, +{0x3AD1, 0x00}, +{0x3AD2, 0xFD}, +{0x3AD3, 0x03}, +{0x3AD4, 0x02}, +{0x3AD5, 0x00}, +{0x3AD6, 0x00}, +{0x3AD7, 0x00}, +{0x3AD8, 0x81}, +{0x3AD9, 0x00}, +{0x3ADA, 0xFD}, +{0x3ADB, 0x03}, +{0x3ADC, 0xFF}, +{0x3ADD, 0x03}, +{0x3ADE, 0x01}, +{0x3ADF, 0x00}, +{0x3AE0, 0x01}, +{0x3AE1, 0x00}, +{0x3AE2, 0x7E}, +{0x3AE3, 0x00}, +{0x3AF4, 0x00}, +{0x3AF6, 0x40}, +{0x3AF7, 0x1E}, +{0x3AF8, 0x00}, +{0x3AFA, 0x00}, +{0x3AFB, 0x00}, +{0x3AFC, 0x00}, +{0x3AFD, 0x00}, +{0x3AFE, 0x00}, +{0x3AFF, 0x00}, +{0x3B00, 0x00}, +{0x3B01, 0x00}, +{0x3B02, 0x00}, +{0x3B03, 0x00}, +{0x3B04, 0x00}, +{0x3B05, 0x00}, +{0x3B06, 0x00}, +{0x3B07, 0x00}, +{0x3B08, 0x00}, +{0x3B09, 0x00}, +{0x3B0A, 0x00}, +{0x3B0B, 0x00}, +{0x3B0C, 0x00}, +{0x3B0D, 0x00}, +{0x3B0E, 0x00}, +{0x3B0F, 0x00}, +{0x3B10, 0x00}, +{0x3B11, 0x00}, +{0x3B12, 0x00}, +{0x3B13, 0x00}, +{0x3B14, 0x00}, +{0x3B15, 0x00}, +{0x3B16, 0x00}, +{0x3B17, 0x00}, +{0x3B18, 0x00}, +{0x3B19, 0x00}, +{0x3B1A, 0x00}, +{0x3B1B, 0x00}, +{0x3B1C, 0x00}, +{0x3B1D, 0x00}, +{0x3B1E, 0x00}, +{0x3B1F, 0x00}, +{0x3B20, 0x00}, +{0x3B21, 0x00}, +{0x3B22, 0x00}, +{0x3B23, 0x00}, +{0x3B24, 0x00}, +{0x3B25, 0x00}, +{0x3B26, 0x00}, +{0x3B27, 0x00}, +{0x3B28, 0x00}, +{0x3B29, 0x00}, +{0x3B2A, 0x00}, +{0x3B2C, 0x00}, +{0x3B2E, 0x00}, +{0x3B30, 0x00}, +{0x3B32, 0x0C}, +{0x4000, 0xD0}, +{0x4001, 0xC8}, +{0x4002, 0xC8}, +{0x4003, 0xC6}, +{0x4004, 0xBA}, +{0x4005, 0xB5}, +{0x4006, 0xB5}, +{0x4007, 0xB4}, +{0x4008, 0xAA}, +{0x4009, 0xA7}, +{0x400A, 0xA7}, +{0x400B, 0xA7}, +{0x400C, 0xA1}, +{0x400D, 0x9F}, +{0x400E, 0x9F}, +{0x400F, 0x9F}, +{0x4010, 0x9C}, +{0x4011, 0x9B}, +{0x4012, 0x9B}, +{0x4013, 0x9B}, +{0x4014, 0x9F}, +{0x4015, 0x9E}, +{0x4016, 0x9D}, +{0x4017, 0x9E}, +{0x4018, 0xA7}, +{0x4019, 0xA4}, +{0x401A, 0xA4}, +{0x401B, 0xA4}, +{0x401C, 0xB5}, +{0x401D, 0xB0}, +{0x401E, 0xB0}, +{0x401F, 0xB0}, +{0x4020, 0xC9}, +{0x4021, 0xC1}, +{0x4022, 0xC1}, +{0x4023, 0xC0}, +{0x4024, 0xC3}, +{0x4025, 0xBB}, +{0x4026, 0xBC}, +{0x4027, 0xBA}, +{0x4028, 0xA5}, +{0x4029, 0xA1}, +{0x402A, 0xA1}, +{0x402B, 0xA0}, +{0x402C, 0x99}, +{0x402D, 0x97}, +{0x402E, 0x97}, +{0x402F, 0x96}, +{0x4030, 0x8F}, +{0x4031, 0x8E}, +{0x4032, 0x8E}, +{0x4033, 0x8E}, +{0x4034, 0x8A}, +{0x4035, 0x8A}, +{0x4036, 0x8A}, +{0x4037, 0x8A}, +{0x4038, 0x8D}, +{0x4039, 0x8D}, +{0x403A, 0x8D}, +{0x403B, 0x8D}, +{0x403C, 0x96}, +{0x403D, 0x94}, +{0x403E, 0x94}, +{0x403F, 0x94}, +{0x4040, 0xA2}, +{0x4041, 0x9E}, +{0x4042, 0x9E}, +{0x4043, 0x9D}, +{0x4044, 0xBC}, +{0x4045, 0xB5}, +{0x4046, 0xB5}, +{0x4047, 0xB4}, +{0x4048, 0xBC}, +{0x4049, 0xB5}, +{0x404A, 0xB5}, +{0x404B, 0xB3}, +{0x404C, 0xA3}, +{0x404D, 0x9F}, +{0x404E, 0x9F}, +{0x404F, 0x9E}, +{0x4050, 0x95}, +{0x4051, 0x93}, +{0x4052, 0x93}, +{0x4053, 0x93}, +{0x4054, 0x89}, +{0x4055, 0x88}, +{0x4056, 0x88}, +{0x4057, 0x88}, +{0x4058, 0x81}, +{0x4059, 0x81}, +{0x405A, 0x81}, +{0x405B, 0x81}, +{0x405C, 0x86}, +{0x405D, 0x86}, +{0x405E, 0x86}, +{0x405F, 0x86}, +{0x4060, 0x92}, +{0x4061, 0x90}, +{0x4062, 0x90}, +{0x4063, 0x90}, +{0x4064, 0x9E}, +{0x4065, 0x9B}, +{0x4066, 0x9B}, +{0x4067, 0x9A}, +{0x4068, 0xB5}, +{0x4069, 0xAE}, +{0x406A, 0xAE}, +{0x406B, 0xAE}, +{0x406C, 0xBE}, +{0x406D, 0xB6}, +{0x406E, 0xB7}, +{0x406F, 0xB5}, +{0x4070, 0xA4}, +{0x4071, 0xA0}, +{0x4072, 0xA0}, +{0x4073, 0x9F}, +{0x4074, 0x96}, +{0x4075, 0x94}, +{0x4076, 0x94}, +{0x4077, 0x94}, +{0x4078, 0x8A}, +{0x4079, 0x8A}, +{0x407A, 0x8A}, +{0x407B, 0x8A}, +{0x407C, 0x83}, +{0x407D, 0x83}, +{0x407E, 0x83}, +{0x407F, 0x83}, +{0x4080, 0x88}, +{0x4081, 0x87}, +{0x4082, 0x87}, +{0x4083, 0x88}, +{0x4084, 0x93}, +{0x4085, 0x91}, +{0x4086, 0x91}, +{0x4087, 0x91}, +{0x4088, 0xA0}, +{0x4089, 0x9C}, +{0x408A, 0x9C}, +{0x408B, 0x9C}, +{0x408C, 0xB6}, +{0x408D, 0xAF}, +{0x408E, 0xAF}, +{0x408F, 0xAF}, +{0x4090, 0xCA}, +{0x4091, 0xC1}, +{0x4092, 0xC1}, +{0x4093, 0xBF}, +{0x4094, 0xAB}, +{0x4095, 0xA5}, +{0x4096, 0xA5}, +{0x4097, 0xA4}, +{0x4098, 0x9E}, +{0x4099, 0x9A}, +{0x409A, 0x9A}, +{0x409B, 0x9A}, +{0x409C, 0x94}, +{0x409D, 0x92}, +{0x409E, 0x92}, +{0x409F, 0x92}, +{0x40A0, 0x8F}, +{0x40A1, 0x8E}, +{0x40A2, 0x8E}, +{0x40A3, 0x8E}, +{0x40A4, 0x92}, +{0x40A5, 0x90}, +{0x40A6, 0x90}, +{0x40A7, 0x90}, +{0x40A8, 0x9A}, +{0x40A9, 0x97}, +{0x40AA, 0x97}, +{0x40AB, 0x97}, +{0x40AC, 0xA6}, +{0x40AD, 0xA0}, +{0x40AE, 0xA0}, +{0x40AF, 0xA0}, +{0x40B0, 0xC1}, +{0x40B1, 0xB9}, +{0x40B2, 0xB9}, +{0x40B3, 0xB8}, +{0x40B4, 0xDD}, +{0x40B5, 0xD2}, +{0x40B6, 0xD2}, +{0x40B7, 0xCF}, +{0x40B8, 0xC6}, +{0x40B9, 0xBF}, +{0x40BA, 0xBF}, +{0x40BB, 0xBD}, +{0x40BC, 0xB5}, +{0x40BD, 0xB0}, +{0x40BE, 0xB0}, +{0x40BF, 0xAF}, +{0x40C0, 0xAB}, +{0x40C1, 0xA8}, +{0x40C2, 0xA8}, +{0x40C3, 0xA7}, +{0x40C4, 0xA7}, +{0x40C5, 0xA3}, +{0x40C6, 0xA3}, +{0x40C7, 0xA3}, +{0x40C8, 0xA9}, +{0x40C9, 0xA6}, +{0x40CA, 0xA6}, +{0x40CB, 0xA6}, +{0x40CC, 0xB0}, +{0x40CD, 0xAB}, +{0x40CE, 0xAB}, +{0x40CF, 0xAB}, +{0x40D0, 0xBF}, +{0x40D1, 0xB7}, +{0x40D2, 0xB7}, +{0x40D3, 0xB7}, +{0x40D4, 0xD2}, +{0x40D5, 0xC8}, +{0x40D6, 0xC7}, +{0x40D7, 0xC7}, +{0x4100, 0x80}, +{0x4101, 0x80}, +{0x4102, 0x80}, +{0x4103, 0x80}, +{0x4104, 0x80}, +{0x4105, 0x80}, +{0x4106, 0x80}, +{0x4107, 0x80}, +{0x4108, 0x80}, +{0x4109, 0x80}, +{0x410A, 0x80}, +{0x410B, 0x80}, +{0x410C, 0x80}, +{0x410D, 0x80}, +{0x410E, 0x80}, +{0x410F, 0x80}, +{0x4110, 0x80}, +{0x4111, 0x80}, +{0x4112, 0x80}, +{0x4113, 0x80}, +{0x4114, 0x80}, +{0x4115, 0x80}, +{0x4116, 0x80}, +{0x4117, 0x80}, +{0x4118, 0x80}, +{0x4119, 0x80}, +{0x411A, 0x80}, +{0x411B, 0x80}, +{0x411C, 0x80}, +{0x411D, 0x80}, +{0x411E, 0x80}, +{0x411F, 0x80}, +{0x4120, 0x80}, +{0x4121, 0x80}, +{0x4122, 0x80}, +{0x4123, 0x80}, +{0x4124, 0x80}, +{0x4125, 0x80}, +{0x4126, 0x80}, +{0x4127, 0x80}, +{0x4128, 0x80}, +{0x4129, 0x80}, +{0x412A, 0x80}, +{0x412B, 0x80}, +{0x412C, 0x80}, +{0x412D, 0x80}, +{0x412E, 0x80}, +{0x412F, 0x80}, +{0x4130, 0x80}, +{0x4131, 0x80}, +{0x4132, 0x80}, +{0x4133, 0x80}, +{0x4134, 0x80}, +{0x4135, 0x80}, +{0x4136, 0x80}, +{0x4137, 0x80}, +{0x4138, 0x80}, +{0x4139, 0x80}, +{0x413A, 0x80}, +{0x413B, 0x80}, +{0x413C, 0x80}, +{0x413D, 0x80}, +{0x413E, 0x80}, +{0x413F, 0x80}, +{0x4140, 0x80}, +{0x4141, 0x80}, +{0x4142, 0x80}, +{0x4143, 0x80}, +{0x4144, 0x80}, +{0x4145, 0x80}, +{0x4146, 0x80}, +{0x4147, 0x80}, +{0x4148, 0x80}, +{0x4149, 0x80}, +{0x414A, 0x80}, +{0x414B, 0x80}, +{0x414C, 0x80}, +{0x414D, 0x80}, +{0x414E, 0x80}, +{0x414F, 0x80}, +{0x4150, 0x80}, +{0x4151, 0x80}, +{0x4152, 0x80}, +{0x4153, 0x80}, +{0x4154, 0x80}, +{0x4155, 0x80}, +{0x4156, 0x80}, +{0x4157, 0x80}, +{0x4158, 0x80}, +{0x4159, 0x80}, +{0x415A, 0x80}, +{0x415B, 0x80}, +{0x415C, 0x80}, +{0x415D, 0x80}, +{0x415E, 0x80}, +{0x415F, 0x80}, +{0x4160, 0x80}, +{0x4161, 0x80}, +{0x4162, 0x80}, +{0x4163, 0x80}, +{0x4164, 0x80}, +{0x4165, 0x80}, +{0x4166, 0x80}, +{0x4167, 0x80}, +{0x4168, 0x80}, +{0x4169, 0x80}, +{0x416A, 0x80}, +{0x416B, 0x80}, +{0x416C, 0x80}, +{0x416D, 0x80}, +{0x416E, 0x80}, +{0x416F, 0x80}, +{0x4170, 0x80}, +{0x4171, 0x80}, +{0x4172, 0x80}, +{0x4173, 0x80}, +{0x4174, 0x80}, +{0x4175, 0x80}, +{0x4176, 0x80}, +{0x4177, 0x80}, +{0x4178, 0x80}, +{0x4179, 0x80}, +{0x417A, 0x80}, +{0x417B, 0x80}, +{0x417C, 0x80}, +{0x417D, 0x80}, +{0x417E, 0x80}, +{0x417F, 0x80}, +{0x4180, 0x80}, +{0x4181, 0x80}, +{0x4182, 0x80}, +{0x4183, 0x80}, +{0x4184, 0x80}, +{0x4185, 0x80}, +{0x4186, 0x80}, +{0x4187, 0x80}, +{0x4188, 0x80}, +{0x4189, 0x80}, +{0x418A, 0x80}, +{0x418B, 0x80}, +{0x418C, 0x80}, +{0x418D, 0x80}, +{0x418E, 0x80}, +{0x418F, 0x80}, +{0x4190, 0x80}, +{0x4191, 0x80}, +{0x4192, 0x80}, +{0x4193, 0x80}, +{0x4194, 0x80}, +{0x4195, 0x80}, +{0x4196, 0x80}, +{0x4197, 0x80}, +{0x4198, 0x80}, +{0x4199, 0x80}, +{0x419A, 0x80}, +{0x419B, 0x80}, +{0x419C, 0x80}, +{0x419D, 0x80}, +{0x419E, 0x80}, +{0x419F, 0x80}, +{0x41A0, 0x80}, +{0x41A1, 0x80}, +{0x41A2, 0x80}, +{0x41A3, 0x80}, +{0x41A4, 0x80}, +{0x41A5, 0x80}, +{0x41A6, 0x80}, +{0x41A7, 0x80}, +{0x41A8, 0x80}, +{0x41A9, 0x80}, +{0x41AA, 0x80}, +{0x41AB, 0x80}, +{0x41AC, 0x80}, +{0x41AD, 0x80}, +{0x41AE, 0x80}, +{0x41AF, 0x80}, +{0x41B0, 0x80}, +{0x41B1, 0x80}, +{0x41B2, 0x80}, +{0x41B3, 0x80}, +{0x41B4, 0x80}, +{0x41B5, 0x80}, +{0x41B6, 0x80}, +{0x41B7, 0x80}, +{0x41B8, 0x80}, +{0x41B9, 0x80}, +{0x41BA, 0x80}, +{0x41BB, 0x80}, +{0x41BC, 0x80}, +{0x41BD, 0x80}, +{0x41BE, 0x80}, +{0x41BF, 0x80}, +{0x41C0, 0x80}, +{0x41C1, 0x80}, +{0x41C2, 0x80}, +{0x41C3, 0x80}, +{0x41C4, 0x80}, +{0x41C5, 0x80}, +{0x41C6, 0x80}, +{0x41C7, 0x80}, +{0x41C8, 0x80}, +{0x41C9, 0x80}, +{0x41CA, 0x80}, +{0x41CB, 0x80}, +{0x41CC, 0x80}, +{0x41CD, 0x80}, +{0x41CE, 0x80}, +{0x41CF, 0x80}, +{0x41D0, 0x80}, +{0x41D1, 0x80}, +{0x41D2, 0x80}, +{0x41D3, 0x80}, +{0x41D4, 0x80}, +{0x41D5, 0x80}, +{0x41D6, 0x80}, +{0x41D7, 0x80}, +{0x4200, 0x80}, +{0x4201, 0x80}, +{0x4202, 0x80}, +{0x4203, 0x80}, +{0x4204, 0x80}, +{0x4205, 0x80}, +{0x4206, 0x80}, +{0x4207, 0x80}, +{0x4208, 0x80}, +{0x4209, 0x80}, +{0x420A, 0x80}, +{0x420B, 0x80}, +{0x420C, 0x80}, +{0x420D, 0x80}, +{0x420E, 0x80}, +{0x420F, 0x80}, +{0x4210, 0x80}, +{0x4211, 0x80}, +{0x4212, 0x80}, +{0x4213, 0x80}, +{0x4214, 0x80}, +{0x4215, 0x80}, +{0x4216, 0x80}, +{0x4217, 0x80}, +{0x4218, 0x80}, +{0x4219, 0x80}, +{0x421A, 0x80}, +{0x421B, 0x80}, +{0x421C, 0x80}, +{0x421D, 0x80}, +{0x421E, 0x80}, +{0x421F, 0x80}, +{0x4220, 0x80}, +{0x4221, 0x80}, +{0x4222, 0x80}, +{0x4223, 0x80}, +{0x4224, 0x80}, +{0x4225, 0x80}, +{0x4226, 0x80}, +{0x4227, 0x80}, +{0x4228, 0x80}, +{0x4229, 0x80}, +{0x422A, 0x80}, +{0x422B, 0x80}, +{0x422C, 0x80}, +{0x422D, 0x80}, +{0x422E, 0x80}, +{0x422F, 0x80}, +{0x4230, 0x80}, +{0x4231, 0x80}, +{0x4232, 0x80}, +{0x4233, 0x80}, +{0x4234, 0x80}, +{0x4235, 0x80}, +{0x4236, 0x80}, +{0x4237, 0x80}, +{0x4238, 0x80}, +{0x4239, 0x80}, +{0x423A, 0x80}, +{0x423B, 0x80}, +{0x423C, 0x80}, +{0x423D, 0x80}, +{0x423E, 0x80}, +{0x423F, 0x80}, +{0x4240, 0x80}, +{0x4241, 0x80}, +{0x4242, 0x80}, +{0x4243, 0x80}, +{0x4244, 0x80}, +{0x4245, 0x80}, +{0x4246, 0x80}, +{0x4247, 0x80}, +{0x4248, 0x80}, +{0x4249, 0x80}, +{0x424A, 0x80}, +{0x424B, 0x80}, +{0x424C, 0x80}, +{0x424D, 0x80}, +{0x424E, 0x80}, +{0x424F, 0x80}, +{0x4250, 0x80}, +{0x4251, 0x80}, +{0x4252, 0x80}, +{0x4253, 0x80}, +{0x4254, 0x80}, +{0x4255, 0x80}, +{0x4256, 0x80}, +{0x4257, 0x80}, +{0x4258, 0x80}, +{0x4259, 0x80}, +{0x425A, 0x80}, +{0x425B, 0x80}, +{0x425C, 0x80}, +{0x425D, 0x80}, +{0x425E, 0x80}, +{0x425F, 0x80}, +{0x4260, 0x80}, +{0x4261, 0x80}, +{0x4262, 0x80}, +{0x4263, 0x80}, +{0x4264, 0x80}, +{0x4265, 0x80}, +{0x4266, 0x80}, +{0x4267, 0x80}, +{0x4268, 0x80}, +{0x4269, 0x80}, +{0x426A, 0x80}, +{0x426B, 0x80}, +{0x426C, 0x80}, +{0x426D, 0x80}, +{0x426E, 0x80}, +{0x426F, 0x80}, +{0x4270, 0x80}, +{0x4271, 0x80}, +{0x4272, 0x80}, +{0x4273, 0x80}, +{0x4274, 0x80}, +{0x4275, 0x80}, +{0x4276, 0x80}, +{0x4277, 0x80}, +{0x4278, 0x80}, +{0x4279, 0x80}, +{0x427A, 0x80}, +{0x427B, 0x80}, +{0x427C, 0x80}, +{0x427D, 0x80}, +{0x427E, 0x80}, +{0x427F, 0x80}, +{0x4280, 0x80}, +{0x4281, 0x80}, +{0x4282, 0x80}, +{0x4283, 0x80}, +{0x4284, 0x80}, +{0x4285, 0x80}, +{0x4286, 0x80}, +{0x4287, 0x80}, +{0x4288, 0x80}, +{0x4289, 0x80}, +{0x428A, 0x80}, +{0x428B, 0x80}, +{0x428C, 0x80}, +{0x428D, 0x80}, +{0x428E, 0x80}, +{0x428F, 0x80}, +{0x4290, 0x80}, +{0x4291, 0x80}, +{0x4292, 0x80}, +{0x4293, 0x80}, +{0x4294, 0x80}, +{0x4295, 0x80}, +{0x4296, 0x80}, +{0x4297, 0x80}, +{0x4298, 0x80}, +{0x4299, 0x80}, +{0x429A, 0x80}, +{0x429B, 0x80}, +{0x429C, 0x80}, +{0x429D, 0x80}, +{0x429E, 0x80}, +{0x429F, 0x80}, +{0x42A0, 0x80}, +{0x42A1, 0x80}, +{0x42A2, 0x80}, +{0x42A3, 0x80}, +{0x42A4, 0x80}, +{0x42A5, 0x80}, +{0x42A6, 0x80}, +{0x42A7, 0x80}, +{0x42A8, 0x80}, +{0x42A9, 0x80}, +{0x42AA, 0x80}, +{0x42AB, 0x80}, +{0x42AC, 0x80}, +{0x42AD, 0x80}, +{0x42AE, 0x80}, +{0x42AF, 0x80}, +{0x42B0, 0x80}, +{0x42B1, 0x80}, +{0x42B2, 0x80}, +{0x42B3, 0x80}, +{0x42B4, 0x80}, +{0x42B5, 0x80}, +{0x42B6, 0x80}, +{0x42B7, 0x80}, +{0x42B8, 0x80}, +{0x42B9, 0x80}, +{0x42BA, 0x80}, +{0x42BB, 0x80}, +{0x42BC, 0x80}, +{0x42BD, 0x80}, +{0x42BE, 0x80}, +{0x42BF, 0x80}, +{0x42C0, 0x80}, +{0x42C1, 0x80}, +{0x42C2, 0x80}, +{0x42C3, 0x80}, +{0x42C4, 0x80}, +{0x42C5, 0x80}, +{0x42C6, 0x80}, +{0x42C7, 0x80}, +{0x42C8, 0x80}, +{0x42C9, 0x80}, +{0x42CA, 0x80}, +{0x42CB, 0x80}, +{0x42CC, 0x80}, +{0x42CD, 0x80}, +{0x42CE, 0x80}, +{0x42CF, 0x80}, +{0x42D0, 0x80}, +{0x42D1, 0x80}, +{0x42D2, 0x80}, +{0x42D3, 0x80}, +{0x42D4, 0x80}, +{0x42D5, 0x80}, +{0x42D6, 0x80}, +{0x42D7, 0x80}, +{0x42D8, 0x00}, +{0x42D9, 0x00}, +{0x4300, 0x8C}, +{0x4301, 0x88}, +{0x4302, 0x88}, +{0x4303, 0x8A}, +{0x4304, 0x88}, +{0x4305, 0x84}, +{0x4306, 0x84}, +{0x4307, 0x86}, +{0x4308, 0x84}, +{0x4309, 0x82}, +{0x430A, 0x82}, +{0x430B, 0x82}, +{0x430C, 0x81}, +{0x430D, 0x7F}, +{0x430E, 0x7F}, +{0x430F, 0x80}, +{0x4310, 0x7F}, +{0x4311, 0x7F}, +{0x4312, 0x7F}, +{0x4313, 0x80}, +{0x4314, 0x80}, +{0x4315, 0x80}, +{0x4316, 0x7F}, +{0x4317, 0x80}, +{0x4318, 0x83}, +{0x4319, 0x82}, +{0x431A, 0x82}, +{0x431B, 0x82}, +{0x431C, 0x83}, +{0x431D, 0x83}, +{0x431E, 0x83}, +{0x431F, 0x83}, +{0x4320, 0x88}, +{0x4321, 0x87}, +{0x4322, 0x86}, +{0x4323, 0x88}, +{0x4324, 0x8A}, +{0x4325, 0x86}, +{0x4326, 0x86}, +{0x4327, 0x88}, +{0x4328, 0x85}, +{0x4329, 0x82}, +{0x432A, 0x82}, +{0x432B, 0x84}, +{0x432C, 0x81}, +{0x432D, 0x80}, +{0x432E, 0x80}, +{0x432F, 0x81}, +{0x4330, 0x80}, +{0x4331, 0x7F}, +{0x4332, 0x7F}, +{0x4333, 0x80}, +{0x4334, 0x7F}, +{0x4335, 0x7F}, +{0x4336, 0x7F}, +{0x4337, 0x80}, +{0x4338, 0x80}, +{0x4339, 0x7F}, +{0x433A, 0x7F}, +{0x433B, 0x80}, +{0x433C, 0x81}, +{0x433D, 0x80}, +{0x433E, 0x80}, +{0x433F, 0x81}, +{0x4340, 0x82}, +{0x4341, 0x82}, +{0x4342, 0x82}, +{0x4343, 0x83}, +{0x4344, 0x85}, +{0x4345, 0x85}, +{0x4346, 0x85}, +{0x4347, 0x86}, +{0x4348, 0x88}, +{0x4349, 0x86}, +{0x434A, 0x86}, +{0x434B, 0x87}, +{0x434C, 0x84}, +{0x434D, 0x82}, +{0x434E, 0x82}, +{0x434F, 0x83}, +{0x4350, 0x81}, +{0x4351, 0x80}, +{0x4352, 0x80}, +{0x4353, 0x81}, +{0x4354, 0x80}, +{0x4355, 0x7F}, +{0x4356, 0x7F}, +{0x4357, 0x80}, +{0x4358, 0x80}, +{0x4359, 0x80}, +{0x435A, 0x80}, +{0x435B, 0x80}, +{0x435C, 0x80}, +{0x435D, 0x80}, +{0x435E, 0x80}, +{0x435F, 0x80}, +{0x4360, 0x80}, +{0x4361, 0x80}, +{0x4362, 0x80}, +{0x4363, 0x80}, +{0x4364, 0x82}, +{0x4365, 0x82}, +{0x4366, 0x82}, +{0x4367, 0x83}, +{0x4368, 0x87}, +{0x4369, 0x86}, +{0x436A, 0x86}, +{0x436B, 0x87}, +{0x436C, 0x89}, +{0x436D, 0x87}, +{0x436E, 0x87}, +{0x436F, 0x88}, +{0x4370, 0x85}, +{0x4371, 0x83}, +{0x4372, 0x83}, +{0x4373, 0x84}, +{0x4374, 0x82}, +{0x4375, 0x81}, +{0x4376, 0x81}, +{0x4377, 0x81}, +{0x4378, 0x80}, +{0x4379, 0x80}, +{0x437A, 0x80}, +{0x437B, 0x80}, +{0x437C, 0x80}, +{0x437D, 0x80}, +{0x437E, 0x80}, +{0x437F, 0x80}, +{0x4380, 0x80}, +{0x4381, 0x80}, +{0x4382, 0x80}, +{0x4383, 0x80}, +{0x4384, 0x81}, +{0x4385, 0x81}, +{0x4386, 0x81}, +{0x4387, 0x80}, +{0x4388, 0x83}, +{0x4389, 0x83}, +{0x438A, 0x83}, +{0x438B, 0x83}, +{0x438C, 0x87}, +{0x438D, 0x87}, +{0x438E, 0x86}, +{0x438F, 0x86}, +{0x4390, 0x8C}, +{0x4391, 0x88}, +{0x4392, 0x89}, +{0x4393, 0x8C}, +{0x4394, 0x87}, +{0x4395, 0x85}, +{0x4396, 0x85}, +{0x4397, 0x86}, +{0x4398, 0x83}, +{0x4399, 0x82}, +{0x439A, 0x82}, +{0x439B, 0x83}, +{0x439C, 0x81}, +{0x439D, 0x81}, +{0x439E, 0x81}, +{0x439F, 0x81}, +{0x43A0, 0x81}, +{0x43A1, 0x81}, +{0x43A2, 0x81}, +{0x43A3, 0x81}, +{0x43A4, 0x80}, +{0x43A5, 0x81}, +{0x43A6, 0x81}, +{0x43A7, 0x80}, +{0x43A8, 0x81}, +{0x43A9, 0x82}, +{0x43AA, 0x82}, +{0x43AB, 0x81}, +{0x43AC, 0x84}, +{0x43AD, 0x84}, +{0x43AE, 0x84}, +{0x43AF, 0x84}, +{0x43B0, 0x88}, +{0x43B1, 0x88}, +{0x43B2, 0x88}, +{0x43B3, 0x88}, +{0x43B4, 0x8F}, +{0x43B5, 0x8B}, +{0x43B6, 0x8C}, +{0x43B7, 0x90}, +{0x43B8, 0x8A}, +{0x43B9, 0x87}, +{0x43BA, 0x88}, +{0x43BB, 0x8B}, +{0x43BC, 0x85}, +{0x43BD, 0x84}, +{0x43BE, 0x84}, +{0x43BF, 0x85}, +{0x43C0, 0x83}, +{0x43C1, 0x82}, +{0x43C2, 0x83}, +{0x43C3, 0x83}, +{0x43C4, 0x82}, +{0x43C5, 0x81}, +{0x43C6, 0x81}, +{0x43C7, 0x82}, +{0x43C8, 0x81}, +{0x43C9, 0x81}, +{0x43CA, 0x81}, +{0x43CB, 0x80}, +{0x43CC, 0x83}, +{0x43CD, 0x83}, +{0x43CE, 0x82}, +{0x43CF, 0x82}, +{0x43D0, 0x86}, +{0x43D1, 0x85}, +{0x43D2, 0x85}, +{0x43D3, 0x86}, +{0x43D4, 0x8A}, +{0x43D5, 0x89}, +{0x43D6, 0x89}, +{0x43D7, 0x89}, +{0x4400, 0x80}, +{0x4401, 0x80}, +{0x4402, 0x80}, +{0x4403, 0x80}, +{0x4404, 0x80}, +{0x4405, 0x80}, +{0x4406, 0x80}, +{0x4407, 0x80}, +{0x4408, 0x80}, +{0x4409, 0x80}, +{0x440A, 0x80}, +{0x440B, 0x80}, +{0x440C, 0x80}, +{0x440D, 0x80}, +{0x440E, 0x80}, +{0x440F, 0x80}, +{0x4410, 0x80}, +{0x4411, 0x80}, +{0x4412, 0x80}, +{0x4413, 0x80}, +{0x4414, 0x80}, +{0x4415, 0x80}, +{0x4416, 0x80}, +{0x4417, 0x80}, +{0x4418, 0x80}, +{0x4419, 0x80}, +{0x441A, 0x80}, +{0x441B, 0x80}, +{0x441C, 0x80}, +{0x441D, 0x80}, +{0x441E, 0x80}, +{0x441F, 0x80}, +{0x4420, 0x80}, +{0x4421, 0x80}, +{0x4422, 0x80}, +{0x4423, 0x80}, +{0x4424, 0x80}, +{0x4425, 0x80}, +{0x4426, 0x80}, +{0x4427, 0x80}, +{0x4428, 0x80}, +{0x4429, 0x80}, +{0x442A, 0x80}, +{0x442B, 0x80}, +{0x442C, 0x80}, +{0x442D, 0x80}, +{0x442E, 0x80}, +{0x442F, 0x80}, +{0x4430, 0x80}, +{0x4431, 0x80}, +{0x4432, 0x80}, +{0x4433, 0x80}, +{0x4434, 0x80}, +{0x4435, 0x80}, +{0x4436, 0x80}, +{0x4437, 0x80}, +{0x4438, 0x80}, +{0x4439, 0x80}, +{0x443A, 0x80}, +{0x443B, 0x80}, +{0x443C, 0x80}, +{0x443D, 0x80}, +{0x443E, 0x80}, +{0x443F, 0x80}, +{0x4440, 0x80}, +{0x4441, 0x80}, +{0x4442, 0x80}, +{0x4443, 0x80}, +{0x4444, 0x80}, +{0x4445, 0x80}, +{0x4446, 0x80}, +{0x4447, 0x80}, +{0x4448, 0x80}, +{0x4449, 0x80}, +{0x444A, 0x80}, +{0x444B, 0x80}, +{0x444C, 0x80}, +{0x444D, 0x80}, +{0x444E, 0x80}, +{0x444F, 0x80}, +{0x4450, 0x80}, +{0x4451, 0x80}, +{0x4452, 0x80}, +{0x4453, 0x80}, +{0x4454, 0x80}, +{0x4455, 0x80}, +{0x4456, 0x80}, +{0x4457, 0x80}, +{0x4458, 0x80}, +{0x4459, 0x80}, +{0x445A, 0x80}, +{0x445B, 0x80}, +{0x445C, 0x80}, +{0x445D, 0x80}, +{0x445E, 0x80}, +{0x445F, 0x80}, +{0x4460, 0x80}, +{0x4461, 0x80}, +{0x4462, 0x80}, +{0x4463, 0x80}, +{0x4464, 0x80}, +{0x4465, 0x80}, +{0x4466, 0x80}, +{0x4467, 0x80}, +{0x4468, 0x80}, +{0x4469, 0x80}, +{0x446A, 0x80}, +{0x446B, 0x80}, +{0x446C, 0x80}, +{0x446D, 0x80}, +{0x446E, 0x80}, +{0x446F, 0x80}, +{0x4470, 0x80}, +{0x4471, 0x80}, +{0x4472, 0x80}, +{0x4473, 0x80}, +{0x4474, 0x80}, +{0x4475, 0x80}, +{0x4476, 0x80}, +{0x4477, 0x80}, +{0x4478, 0x80}, +{0x4479, 0x80}, +{0x447A, 0x80}, +{0x447B, 0x80}, +{0x447C, 0x80}, +{0x447D, 0x80}, +{0x447E, 0x80}, +{0x447F, 0x80}, +{0x4480, 0x80}, +{0x4481, 0x80}, +{0x4482, 0x80}, +{0x4483, 0x80}, +{0x4484, 0x80}, +{0x4485, 0x80}, +{0x4486, 0x80}, +{0x4487, 0x80}, +{0x4488, 0x80}, +{0x4489, 0x80}, +{0x448A, 0x80}, +{0x448B, 0x80}, +{0x448C, 0x80}, +{0x448D, 0x80}, +{0x448E, 0x80}, +{0x448F, 0x80}, +{0x4490, 0x80}, +{0x4491, 0x80}, +{0x4492, 0x80}, +{0x4493, 0x80}, +{0x4494, 0x80}, +{0x4495, 0x80}, +{0x4496, 0x80}, +{0x4497, 0x80}, +{0x4498, 0x80}, +{0x4499, 0x80}, +{0x449A, 0x80}, +{0x449B, 0x80}, +{0x449C, 0x80}, +{0x449D, 0x80}, +{0x449E, 0x80}, +{0x449F, 0x80}, +{0x44A0, 0x80}, +{0x44A1, 0x80}, +{0x44A2, 0x80}, +{0x44A3, 0x80}, +{0x44A4, 0x80}, +{0x44A5, 0x80}, +{0x44A6, 0x80}, +{0x44A7, 0x80}, +{0x44A8, 0x80}, +{0x44A9, 0x80}, +{0x44AA, 0x80}, +{0x44AB, 0x80}, +{0x44AC, 0x80}, +{0x44AD, 0x80}, +{0x44AE, 0x80}, +{0x44AF, 0x80}, +{0x44B0, 0x80}, +{0x44B1, 0x80}, +{0x44B2, 0x80}, +{0x44B3, 0x80}, +{0x44B4, 0x80}, +{0x44B5, 0x80}, +{0x44B6, 0x80}, +{0x44B7, 0x80}, +{0x44B8, 0x80}, +{0x44B9, 0x80}, +{0x44BA, 0x80}, +{0x44BB, 0x80}, +{0x44BC, 0x80}, +{0x44BD, 0x80}, +{0x44BE, 0x80}, +{0x44BF, 0x80}, +{0x44C0, 0x80}, +{0x44C1, 0x80}, +{0x44C2, 0x80}, +{0x44C3, 0x80}, +{0x44C4, 0x80}, +{0x44C5, 0x80}, +{0x44C6, 0x80}, +{0x44C7, 0x80}, +{0x44C8, 0x80}, +{0x44C9, 0x80}, +{0x44CA, 0x80}, +{0x44CB, 0x80}, +{0x44CC, 0x80}, +{0x44CD, 0x80}, +{0x44CE, 0x80}, +{0x44CF, 0x80}, +{0x44D0, 0x80}, +{0x44D1, 0x80}, +{0x44D2, 0x80}, +{0x44D3, 0x80}, +{0x44D4, 0x80}, +{0x44D5, 0x80}, +{0x44D6, 0x80}, +{0x44D7, 0x80}, +{0x4500, 0x80}, +{0x4501, 0x80}, +{0x4502, 0x80}, +{0x4503, 0x80}, +{0x4504, 0x80}, +{0x4505, 0x80}, +{0x4506, 0x80}, +{0x4507, 0x80}, +{0x4508, 0x80}, +{0x4509, 0x80}, +{0x450A, 0x80}, +{0x450B, 0x80}, +{0x450C, 0x80}, +{0x450D, 0x80}, +{0x450E, 0x80}, +{0x450F, 0x80}, +{0x4510, 0x80}, +{0x4511, 0x80}, +{0x4512, 0x80}, +{0x4513, 0x80}, +{0x4514, 0x80}, +{0x4515, 0x80}, +{0x4516, 0x80}, +{0x4517, 0x80}, +{0x4518, 0x80}, +{0x4519, 0x80}, +{0x451A, 0x80}, +{0x451B, 0x80}, +{0x451C, 0x80}, +{0x451D, 0x80}, +{0x451E, 0x80}, +{0x451F, 0x80}, +{0x4520, 0x80}, +{0x4521, 0x80}, +{0x4522, 0x80}, +{0x4523, 0x80}, +{0x4524, 0x80}, +{0x4525, 0x80}, +{0x4526, 0x80}, +{0x4527, 0x80}, +{0x4528, 0x80}, +{0x4529, 0x80}, +{0x452A, 0x80}, +{0x452B, 0x80}, +{0x452C, 0x80}, +{0x452D, 0x80}, +{0x452E, 0x80}, +{0x452F, 0x80}, +{0x4530, 0x80}, +{0x4531, 0x80}, +{0x4532, 0x80}, +{0x4533, 0x80}, +{0x4534, 0x80}, +{0x4535, 0x80}, +{0x4536, 0x80}, +{0x4537, 0x80}, +{0x4538, 0x80}, +{0x4539, 0x80}, +{0x453A, 0x80}, +{0x453B, 0x80}, +{0x453C, 0x80}, +{0x453D, 0x80}, +{0x453E, 0x80}, +{0x453F, 0x80}, +{0x4540, 0x80}, +{0x4541, 0x80}, +{0x4542, 0x80}, +{0x4543, 0x80}, +{0x4544, 0x80}, +{0x4545, 0x80}, +{0x4546, 0x80}, +{0x4547, 0x80}, +{0x4548, 0x80}, +{0x4549, 0x80}, +{0x454A, 0x80}, +{0x454B, 0x80}, +{0x454C, 0x80}, +{0x454D, 0x80}, +{0x454E, 0x80}, +{0x454F, 0x80}, +{0x4550, 0x80}, +{0x4551, 0x80}, +{0x4552, 0x80}, +{0x4553, 0x80}, +{0x4554, 0x80}, +{0x4555, 0x80}, +{0x4556, 0x80}, +{0x4557, 0x80}, +{0x4558, 0x80}, +{0x4559, 0x80}, +{0x455A, 0x80}, +{0x455B, 0x80}, +{0x455C, 0x80}, +{0x455D, 0x80}, +{0x455E, 0x80}, +{0x455F, 0x80}, +{0x4560, 0x80}, +{0x4561, 0x80}, +{0x4562, 0x80}, +{0x4563, 0x80}, +{0x4564, 0x80}, +{0x4565, 0x80}, +{0x4566, 0x80}, +{0x4567, 0x80}, +{0x4568, 0x80}, +{0x4569, 0x80}, +{0x456A, 0x80}, +{0x456B, 0x80}, +{0x456C, 0x80}, +{0x456D, 0x80}, +{0x456E, 0x80}, +{0x456F, 0x80}, +{0x4570, 0x80}, +{0x4571, 0x80}, +{0x4572, 0x80}, +{0x4573, 0x80}, +{0x4574, 0x80}, +{0x4575, 0x80}, +{0x4576, 0x80}, +{0x4577, 0x80}, +{0x4578, 0x80}, +{0x4579, 0x80}, +{0x457A, 0x80}, +{0x457B, 0x80}, +{0x457C, 0x80}, +{0x457D, 0x80}, +{0x457E, 0x80}, +{0x457F, 0x80}, +{0x4580, 0x80}, +{0x4581, 0x80}, +{0x4582, 0x80}, +{0x4583, 0x80}, +{0x4584, 0x80}, +{0x4585, 0x80}, +{0x4586, 0x80}, +{0x4587, 0x80}, +{0x4588, 0x80}, +{0x4589, 0x80}, +{0x458A, 0x80}, +{0x458B, 0x80}, +{0x458C, 0x80}, +{0x458D, 0x80}, +{0x458E, 0x80}, +{0x458F, 0x80}, +{0x4590, 0x80}, +{0x4591, 0x80}, +{0x4592, 0x80}, +{0x4593, 0x80}, +{0x4594, 0x80}, +{0x4595, 0x80}, +{0x4596, 0x80}, +{0x4597, 0x80}, +{0x4598, 0x80}, +{0x4599, 0x80}, +{0x459A, 0x80}, +{0x459B, 0x80}, +{0x459C, 0x80}, +{0x459D, 0x80}, +{0x459E, 0x80}, +{0x459F, 0x80}, +{0x45A0, 0x80}, +{0x45A1, 0x80}, +{0x45A2, 0x80}, +{0x45A3, 0x80}, +{0x45A4, 0x80}, +{0x45A5, 0x80}, +{0x45A6, 0x80}, +{0x45A7, 0x80}, +{0x45A8, 0x80}, +{0x45A9, 0x80}, +{0x45AA, 0x80}, +{0x45AB, 0x80}, +{0x45AC, 0x80}, +{0x45AD, 0x80}, +{0x45AE, 0x80}, +{0x45AF, 0x80}, +{0x45B0, 0x80}, +{0x45B1, 0x80}, +{0x45B2, 0x80}, +{0x45B3, 0x80}, +{0x45B4, 0x80}, +{0x45B5, 0x80}, +{0x45B6, 0x80}, +{0x45B7, 0x80}, +{0x45B8, 0x80}, +{0x45B9, 0x80}, +{0x45BA, 0x80}, +{0x45BB, 0x80}, +{0x45BC, 0x80}, +{0x45BD, 0x80}, +{0x45BE, 0x80}, +{0x45BF, 0x80}, +{0x45C0, 0x80}, +{0x45C1, 0x80}, +{0x45C2, 0x80}, +{0x45C3, 0x80}, +{0x45C4, 0x80}, +{0x45C5, 0x80}, +{0x45C6, 0x80}, +{0x45C7, 0x80}, +{0x45C8, 0x80}, +{0x45C9, 0x80}, +{0x45CA, 0x80}, +{0x45CB, 0x80}, +{0x45CC, 0x80}, +{0x45CD, 0x80}, +{0x45CE, 0x80}, +{0x45CF, 0x80}, +{0x45D0, 0x80}, +{0x45D1, 0x80}, +{0x45D2, 0x80}, +{0x45D3, 0x80}, +{0x45D4, 0x80}, +{0x45D5, 0x80}, +{0x45D6, 0x80}, +{0x45D7, 0x80}, +{0x7000, 0xAB}, +{0x7001, 0xBA}, +{0x7002, 0x40}, +{0x7003, 0x02}, +{0x7004, 0x00}, +{0x7005, 0x00}, +{0x7006, 0x00}, +{0x7007, 0x00}, +{0x7008, 0x00}, +{0x7009, 0x00}, +{0x700A, 0x00}, +{0x700B, 0x00}, +{0x700C, 0x00}, +{0x700D, 0x00}, +{0x700E, 0x00}, +{0x700F, 0x00}, +{0x7010, 0x55}, +{0x7011, 0x88}, +{0x7012, 0x40}, +{0x7013, 0x01}, +{0x7014, 0x72}, +{0x7015, 0xF1}, +{0x7016, 0x02}, +{0x7017, 0xF8}, +{0x7018, 0x00}, +{0x7019, 0x00}, +{0x701A, 0x00}, +{0x701B, 0x00}, +{0x701C, 0x00}, +{0x701D, 0x00}, +{0x701E, 0x00}, +{0x701F, 0x00}, +{0x7020, 0x00}, +{0x7021, 0x00}, +{0x7022, 0x00}, +{0x7023, 0x00}, +{0x7024, 0x00}, +{0x7025, 0x00}, +{0x7026, 0x00}, +{0x7027, 0x00}, +{0x7028, 0x00}, +{0x7029, 0x00}, +{0x702A, 0x00}, +{0x702B, 0x00}, +{0x702C, 0x00}, +{0x702D, 0x00}, +{0x702E, 0x00}, +{0x702F, 0x00}, +{0x7030, 0x00}, +{0x7031, 0x00}, +{0x7032, 0x00}, +{0x7033, 0x00}, +{0x7034, 0x00}, +{0x7035, 0x00}, +{0x7036, 0x00}, +{0x7037, 0x00}, +{0x7038, 0x00}, +{0x7039, 0x00}, +{0x703A, 0x00}, +{0x703B, 0x00}, +{0x703C, 0x00}, +{0x703D, 0x00}, +{0x703E, 0x00}, +{0x703F, 0x00}, +{0x7040, 0x00}, +{0x7041, 0x00}, +{0x7042, 0x00}, +{0x7043, 0x00}, +{0x7044, 0x00}, +{0x7045, 0x00}, +{0x7046, 0x00}, +{0x7047, 0x00}, +{0x7048, 0x00}, +{0x7049, 0x00}, +{0x704A, 0x00}, +{0x704B, 0x00}, +{0x704C, 0x00}, +{0x704D, 0x00}, +{0x704E, 0x00}, +{0x704F, 0x00}, +{0x7050, 0x00}, +{0x7051, 0x00}, +{0x7052, 0x00}, +{0x7053, 0x00}, +{0x7054, 0x00}, +{0x7055, 0x00}, +{0x7056, 0x00}, +{0x7057, 0x00}, +{0x7058, 0x00}, +{0x7059, 0x00}, +{0x705A, 0x00}, +{0x705B, 0x00}, +{0x705C, 0x00}, +{0x705D, 0x00}, +{0x705E, 0x00}, +{0x705F, 0x00}, +{0x7060, 0x00}, +{0x7061, 0x00}, +{0x7062, 0x00}, +{0x7063, 0x00}, +{0x7064, 0x00}, +{0x7065, 0x00}, +{0x7066, 0x00}, +{0x7067, 0x00}, +{0x7068, 0x00}, +{0x7069, 0x00}, +{0x706A, 0x00}, +{0x706B, 0x00}, +{0x706C, 0x00}, +{0x706D, 0x00}, +{0x706E, 0x00}, +{0x706F, 0x00}, +{0x7070, 0x00}, +{0x7071, 0x00}, +{0x7072, 0x00}, +{0x7073, 0x00}, +{0x7074, 0x00}, +{0x7075, 0x00}, +{0x7076, 0x00}, +{0x7077, 0x00}, +{0x7078, 0x00}, +{0x7079, 0x00}, +{0x707A, 0x00}, +{0x707B, 0x00}, +{0x707C, 0x00}, +{0x707D, 0x00}, +{0x707E, 0x00}, +{0x707F, 0x00}, +{0x7080, 0x00}, +{0x7081, 0x00}, +{0x7082, 0x00}, +{0x7083, 0x00}, +{0x7084, 0x00}, +{0x7085, 0x00}, +{0x7086, 0x00}, +{0x7087, 0x00}, +{0x7088, 0x00}, +{0x7089, 0x00}, +{0x708A, 0x00}, +{0x708B, 0x00}, +{0x708C, 0x00}, +{0x708D, 0x00}, +{0x708E, 0x00}, +{0x708F, 0x00}, +{0x7090, 0x00}, +{0x7091, 0xF0}, +{0x7092, 0x02}, +{0x7093, 0xF8}, +{0x7094, 0x8D}, +{0x7095, 0xF6}, +{0x7096, 0xFA}, +{0x7097, 0xFF}, +{0x7098, 0xF0}, +{0x7099, 0xB5}, +{0x709A, 0x04}, +{0x709B, 0x46}, +{0x709C, 0x8F}, +{0x709D, 0xB0}, +{0x709E, 0x5F}, +{0x709F, 0x48}, +{0x70A0, 0x0C}, +{0x70A1, 0x90}, +{0x70A2, 0x5F}, +{0x70A3, 0x48}, +{0x70A4, 0x06}, +{0x70A5, 0x90}, +{0x70A6, 0x20}, +{0x70A7, 0x46}, +{0x70A8, 0x34}, +{0x70A9, 0x30}, +{0x70AA, 0x0B}, +{0x70AB, 0x90}, +{0x70AC, 0x5B}, +{0x70AD, 0x48}, +{0x70AE, 0x5A}, +{0x70AF, 0x49}, +{0x70B0, 0x26}, +{0x70B1, 0x46}, +{0x70B2, 0x66}, +{0x70B3, 0x30}, +{0x70B4, 0x3A}, +{0x70B5, 0x31}, +{0x70B6, 0x3C}, +{0x70B7, 0x36}, +{0x70B8, 0x05}, +{0x70B9, 0x90}, +{0x70BA, 0x0A}, +{0x70BB, 0x30}, +{0x70BC, 0x04}, +{0x70BD, 0x90}, +{0x70BE, 0x59}, +{0x70BF, 0x48}, +{0x70C0, 0x55}, +{0x70C1, 0x4A}, +{0x70C2, 0x40}, +{0x70C3, 0x6E}, +{0x70C4, 0xC0}, +{0x70C5, 0x07}, +{0x70C6, 0x7D}, +{0x70C7, 0xD1}, +{0x70C8, 0x17}, +{0x70C9, 0x88}, +{0x70CA, 0x0A}, +{0x70CB, 0x5E}, +{0x70CC, 0x0D}, +{0x70CD, 0x92}, +{0x70CE, 0x53}, +{0x70CF, 0x49}, +{0x70D0, 0x55}, +{0x70D1, 0x48}, +{0x70D2, 0x94}, +{0x70D3, 0x31}, +{0x70D4, 0x89}, +{0x70D5, 0x6B}, +{0x70D6, 0x80}, +{0x70D7, 0x68}, +{0x70D8, 0x09}, +{0x70D9, 0x02}, +{0x70DA, 0x00}, +{0x70DB, 0x03}, +{0x70DC, 0x09}, +{0x70DD, 0x0E}, +{0x70DE, 0x00}, +{0x70DF, 0x0B}, +{0x70E0, 0x49}, +{0x70E1, 0x1C}, +{0x70E2, 0x48}, +{0x70E3, 0x43}, +{0x70E4, 0x4D}, +{0x70E5, 0x49}, +{0x70E6, 0x6C}, +{0x70E7, 0x39}, +{0x70E8, 0x8A}, +{0x70E9, 0x6A}, +{0x70EA, 0x07}, +{0x70EB, 0x92}, +{0x70EC, 0xCA}, +{0x70ED, 0x6A}, +{0x70EE, 0x00}, +{0x70EF, 0x21}, +{0x70F0, 0xC9}, +{0x70F1, 0x43}, +{0x70F2, 0x03}, +{0x70F3, 0x92}, +{0x70F4, 0x00}, +{0x70F5, 0x22}, +{0x70F6, 0x00}, +{0x70F7, 0x91}, +{0x70F8, 0x01}, +{0x70F9, 0x92}, +{0x70FA, 0x39}, +{0x70FB, 0x46}, +{0x70FC, 0x8F}, +{0x70FD, 0xF6}, +{0x70FE, 0xCE}, +{0x70FF, 0xFB}, +{0x7100, 0x01}, +{0x7101, 0x22}, +{0x7102, 0x00}, +{0x7103, 0x23}, +{0x7104, 0x8C}, +{0x7105, 0xF6}, +{0x7106, 0x02}, +{0x7107, 0xFA}, +{0x7108, 0x00}, +{0x7109, 0x21}, +{0x710A, 0x05}, +{0x710B, 0x46}, +{0x710C, 0x01}, +{0x710D, 0x91}, +{0x710E, 0x00}, +{0x710F, 0x90}, +{0x7110, 0x39}, +{0x7111, 0x46}, +{0x7112, 0x07}, +{0x7113, 0x98}, +{0x7114, 0x8F}, +{0x7115, 0xF6}, +{0x7116, 0xC2}, +{0x7117, 0xFB}, +{0x7118, 0x0D}, +{0x7119, 0x9A}, +{0x711A, 0xD3}, +{0x711B, 0x17}, +{0x711C, 0x80}, +{0x711D, 0x18}, +{0x711E, 0x59}, +{0x711F, 0x41}, +{0x7120, 0x01}, +{0x7121, 0x22}, +{0x7122, 0x00}, +{0x7123, 0x23}, +{0x7124, 0x8C}, +{0x7125, 0xF6}, +{0x7126, 0xCD}, +{0x7127, 0xF9}, +{0x7128, 0x07}, +{0x7129, 0x90}, +{0x712A, 0x00}, +{0x712B, 0x20}, +{0x712C, 0x01}, +{0x712D, 0x90}, +{0x712E, 0x00}, +{0x712F, 0x95}, +{0x7130, 0x39}, +{0x7131, 0x46}, +{0x7132, 0x03}, +{0x7133, 0x98}, +{0x7134, 0x8F}, +{0x7135, 0xF6}, +{0x7136, 0xB2}, +{0x7137, 0xFB}, +{0x7138, 0x01}, +{0x7139, 0x22}, +{0x713A, 0x00}, +{0x713B, 0x23}, +{0x713C, 0x8C}, +{0x713D, 0xF6}, +{0x713E, 0xE6}, +{0x713F, 0xF9}, +{0x7140, 0x02}, +{0x7141, 0x46}, +{0x7142, 0x07}, +{0x7143, 0x98}, +{0x7144, 0x00}, +{0x7145, 0x23}, +{0x7146, 0x81}, +{0x7147, 0x0B}, +{0x7148, 0x80}, +{0x7149, 0x04}, +{0x714A, 0x7A}, +{0x714B, 0xF6}, +{0x714C, 0x54}, +{0x714D, 0xF8}, +{0x714E, 0x37}, +{0x714F, 0x4A}, +{0x7150, 0x00}, +{0x7151, 0x23}, +{0x7152, 0x00}, +{0x7153, 0x92}, +{0x7154, 0x01}, +{0x7155, 0x93}, +{0x7156, 0x01}, +{0x7157, 0x22}, +{0x7158, 0x8C}, +{0x7159, 0xF6}, +{0x715A, 0xD8}, +{0x715B, 0xF9}, +{0x715C, 0x05}, +{0x715D, 0x46}, +{0x715E, 0x60}, +{0x715F, 0x68}, +{0x7160, 0x00}, +{0x7161, 0x23}, +{0x7162, 0x01}, +{0x7163, 0x0C}, +{0x7164, 0x00}, +{0x7165, 0x04}, +{0x7166, 0xE2}, +{0x7167, 0x68}, +{0x7168, 0x7A}, +{0x7169, 0xF6}, +{0x716A, 0x45}, +{0x716B, 0xF8}, +{0x716C, 0x00}, +{0x716D, 0x22}, +{0x716E, 0xD2}, +{0x716F, 0x43}, +{0x7170, 0x00}, +{0x7171, 0x23}, +{0x7172, 0x00}, +{0x7173, 0x92}, +{0x7174, 0x01}, +{0x7175, 0x93}, +{0x7176, 0x1A}, +{0x7177, 0x46}, +{0x7178, 0x8C}, +{0x7179, 0xF6}, +{0x717A, 0xC8}, +{0x717B, 0xF9}, +{0x717C, 0x29}, +{0x717D, 0x46}, +{0x717E, 0x8F}, +{0x717F, 0xF6}, +{0x7180, 0x8D}, +{0x7181, 0xFB}, +{0x7182, 0x8A}, +{0x7183, 0x03}, +{0x7184, 0x80}, +{0x7185, 0x0C}, +{0x7186, 0x10}, +{0x7187, 0x43}, +{0x7188, 0x00}, +{0x7189, 0x22}, +{0x718A, 0xD2}, +{0x718B, 0x43}, +{0x718C, 0x00}, +{0x718D, 0x23}, +{0x718E, 0x00}, +{0x718F, 0x92}, +{0x7190, 0x89}, +{0x7191, 0x0C}, +{0x7192, 0x01}, +{0x7193, 0x93}, +{0x7194, 0x1A}, +{0x7195, 0x46}, +{0x7196, 0x8C}, +{0x7197, 0xF6}, +{0x7198, 0xB9}, +{0x7199, 0xF9}, +{0x719A, 0x00}, +{0x719B, 0x24}, +{0x719C, 0x03}, +{0x719D, 0x90}, +{0x719E, 0x0C}, +{0x719F, 0x98}, +{0x71A0, 0x61}, +{0x71A1, 0x00}, +{0x71A2, 0x45}, +{0x71A3, 0x5A}, +{0x71A4, 0x06}, +{0x71A5, 0x98}, +{0x71A6, 0x22}, +{0x71A7, 0x4A}, +{0x71A8, 0x40}, +{0x71A9, 0x5A}, +{0x71AA, 0x00}, +{0x71AB, 0x21}, +{0x71AC, 0x8C}, +{0x71AD, 0xF6}, +{0x71AE, 0xBE}, +{0x71AF, 0xF9}, +{0x71B0, 0x07}, +{0x71B1, 0x46}, +{0x71B2, 0x28}, +{0x71B3, 0x46}, +{0x71B4, 0x03}, +{0x71B5, 0x99}, +{0x71B6, 0x8F}, +{0x71B7, 0xF6}, +{0x71B8, 0x71}, +{0x71B9, 0xFB}, +{0x71BA, 0x3A}, +{0x71BB, 0x46}, +{0x71BC, 0x00}, +{0x71BD, 0x23}, +{0x71BE, 0x79}, +{0x71BF, 0xF6}, +{0x71C0, 0xCA}, +{0x71C1, 0xFF}, +{0x71C2, 0x00}, +{0x71C3, 0xE0}, +{0x71C4, 0x0F}, +{0x71C5, 0xE0}, +{0x71C6, 0x8A}, +{0x71C7, 0x02}, +{0x71C8, 0x80}, +{0x71C9, 0x0D}, +{0x71CA, 0x10}, +{0x71CB, 0x43}, +{0x71CC, 0x19}, +{0x71CD, 0x4A}, +{0x71CE, 0x00}, +{0x71CF, 0x23}, +{0x71D0, 0x00}, +{0x71D1, 0x92}, +{0x71D2, 0x89}, +{0x71D3, 0x0D}, +{0x71D4, 0x01}, +{0x71D5, 0x93}, +{0x71D6, 0x40}, +{0x71D7, 0x22}, +{0x71D8, 0x8C}, +{0x71D9, 0xF6}, +{0x71DA, 0x98}, +{0x71DB, 0xF9}, +{0x71DC, 0xA1}, +{0x71DD, 0x00}, +{0x71DE, 0x64}, +{0x71DF, 0x1C}, +{0x71E0, 0x70}, +{0x71E1, 0x50}, +{0x71E2, 0x04}, +{0x71E3, 0x2C}, +{0x71E4, 0xDB}, +{0x71E5, 0xD3}, +{0x71E6, 0x14}, +{0x71E7, 0x4D}, +{0x71E8, 0x00}, +{0x71E9, 0x24}, +{0x71EA, 0x0B}, +{0x71EB, 0x98}, +{0x71EC, 0x67}, +{0x71ED, 0x00}, +{0x71EE, 0xC0}, +{0x71EF, 0x5B}, +{0x71F0, 0x2A}, +{0x71F1, 0x46}, +{0x71F2, 0x40}, +{0x71F3, 0x21}, +{0x71F4, 0x8C}, +{0x71F5, 0xF6}, +{0x71F6, 0x9A}, +{0x71F7, 0xF9}, +{0x71F8, 0x05}, +{0x71F9, 0x99}, +{0x71FA, 0x0E}, +{0x71FB, 0x4A}, +{0x71FC, 0xC8}, +{0x71FD, 0x53}, +{0x71FE, 0xA7}, +{0x71FF, 0x00}, +{0x7200, 0xF0}, +{0x7201, 0x59}, +{0x7202, 0x40}, +{0x7203, 0x21}, +{0x7204, 0x8C}, +{0x7205, 0xF6}, +{0x7206, 0x7B}, +{0x7207, 0xF9}, +{0x7208, 0x04}, +{0x7209, 0x99}, +{0x720A, 0x64}, +{0x720B, 0x1C}, +{0x720C, 0xC8}, +{0x720D, 0x51}, +{0x720E, 0x04}, +{0x720F, 0x2C}, +{0x7210, 0xEB}, +{0x7211, 0xD3}, +{0x7212, 0x0F}, +{0x7213, 0xB0}, +{0x7214, 0xF0}, +{0x7215, 0xBD}, +{0x7216, 0x00}, +{0x7217, 0x00}, +{0x7218, 0x76}, +{0x7219, 0x69}, +{0x721A, 0x18}, +{0x721B, 0x00}, +{0x721C, 0xEC}, +{0x721D, 0x58}, +{0x721E, 0x18}, +{0x721F, 0x00}, +{0x7220, 0x38}, +{0x7221, 0x36}, +{0x7222, 0x18}, +{0x7223, 0x00}, +{0x7224, 0x00}, +{0x7225, 0x35}, +{0x7226, 0x18}, +{0x7227, 0x00}, +{0x7228, 0x00}, +{0x7229, 0x20}, +{0x722A, 0x18}, +{0x722B, 0x00}, +{0x722C, 0xFF}, +{0x722D, 0xFF}, +{0x722E, 0xFF}, +{0x722F, 0x3F}, +{0x7230, 0xFF}, +{0x7231, 0x07}, +{0x7232, 0x00}, +{0x7233, 0x00}, +{0x7234, 0xFF}, +{0x7235, 0xFF}, +{0x7236, 0x07}, +{0x7237, 0x00}, +{0x7238, 0xFF}, +{0x7239, 0x1F}, +{0x723A, 0x00}, +{0x723B, 0x00}, +{0x723C, 0x01}, +{0x723D, 0xF6}, +{0x723E, 0x45}, +{0x723F, 0x12}, +}; + +#endif diff --git a/drivers/media/i2c/isx031.c b/drivers/media/i2c/isx031.c new file mode 100644 index 000000000000..d6359d2c6aa6 --- /dev/null +++ b/drivers/media/i2c/isx031.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define to_isx031(_sd) container_of(_sd, struct isx031, sd) + +#define ISX031_REG_MODE_SELECT 0x8A01 +#define ISX031_MODE_STANDBY 0x00 +#define ISX031_MODE_STREAMING 0x80 + +#define ISX031_REG_CHIP_ID 0x6005 +#define ISX031_CHIP_ID 0x05 + +struct isx031_reg { + enum { + ISX031_REG_LEN_DELAY = 0, + ISX031_REG_LEN_08BIT = 1, + ISX031_REG_LEN_16BIT = 2, + } mode; + u16 address; + u16 val; +}; + +struct isx031_reg_list { + u32 num_of_regs; + const struct isx031_reg *regs; +}; + +struct isx031_link_freq_config { + const struct isx031_reg_list reg_list; +}; + +struct isx031_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* MEDIA_BUS_FMT */ + u32 code; + + /* MODE_FPS*/ + u32 fps; + + /* Sensor register settings for this resolution */ + const struct isx031_reg_list reg_list; +}; + +struct isx031 { + struct v4l2_subdev sd; + struct media_pad pad; + + /* Current mode */ + const struct isx031_mode *cur_mode; + /* Previous mode */ + const struct isx031_mode *pre_mode; + + /* To serialize asynchronus callbacks */ + struct mutex mutex; + + /* i2c client */ + struct i2c_client *client; + + struct isx031_platform_data *platform_data; + + /* Streaming on/off */ + bool streaming; +}; + +static const struct isx031_reg isx031_init_reg[] = { + {ISX031_REG_LEN_08BIT, 0xFFFF, 0x00}, // select mode + {ISX031_REG_LEN_DELAY, 0x0000, 0x32}, // delay 50 ms + {ISX031_REG_LEN_08BIT, 0x0171, 0x00}, // close F_EBD + {ISX031_REG_LEN_08BIT, 0x0172, 0x00}, // close R_EBD + {ISX031_REG_LEN_08BIT, 0x8A01, 0x00}, // mode transition to start-up state + {ISX031_REG_LEN_DELAY, 0x0000, 0x64}, // delay 100 ms +}; + +static const struct isx031_reg isx031_1920_1536_30fps_reg[] = { + {ISX031_REG_LEN_08BIT, 0x8AA8, 0x01}, // crop enable + {ISX031_REG_LEN_08BIT, 0x8AAA, 0x80}, // H size = 1920 + {ISX031_REG_LEN_08BIT, 0x8AAB, 0x07}, + {ISX031_REG_LEN_08BIT, 0x8AAC, 0x00}, // H croped 0 + {ISX031_REG_LEN_08BIT, 0x8AAD, 0x00}, + {ISX031_REG_LEN_08BIT, 0x8AAE, 0x00}, // V size 1536 + {ISX031_REG_LEN_08BIT, 0x8AAF, 0x06}, + {ISX031_REG_LEN_08BIT, 0x8AB0, 0x00}, // V cropped 0 + {ISX031_REG_LEN_08BIT, 0x8AB1, 0x00}, + {ISX031_REG_LEN_08BIT, 0x8ADA, 0x03}, // DCROP_DATA_SEL + {ISX031_REG_LEN_08BIT, 0xBF04, 0x01}, + {ISX031_REG_LEN_08BIT, 0xBF06, 0x80}, + {ISX031_REG_LEN_08BIT, 0xBF07, 0x07}, + {ISX031_REG_LEN_08BIT, 0xBF08, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF09, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF0A, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF0B, 0x06}, + {ISX031_REG_LEN_08BIT, 0xBF0C, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF0D, 0x00}, +}; + +static const struct isx031_reg isx031_1920_1080_30fps_reg[] = { + {ISX031_REG_LEN_08BIT, 0x8AA8, 0x01}, // crop enable + {ISX031_REG_LEN_08BIT, 0x8AAA, 0x80}, // H size = 1920 + {ISX031_REG_LEN_08BIT, 0x8AAB, 0x07}, + {ISX031_REG_LEN_08BIT, 0x8AAC, 0x00}, // H croped 0 + {ISX031_REG_LEN_08BIT, 0x8AAD, 0x00}, + {ISX031_REG_LEN_08BIT, 0x8AAE, 0x38}, // V size 1080 + {ISX031_REG_LEN_08BIT, 0x8AAF, 0x04}, + {ISX031_REG_LEN_08BIT, 0x8AB0, 0xE4}, // V cropped 228*2 + {ISX031_REG_LEN_08BIT, 0x8AB1, 0x00}, + {ISX031_REG_LEN_08BIT, 0x8ADA, 0x03}, // DCROP_DATA_SEL + {ISX031_REG_LEN_08BIT, 0xBF04, 0x01}, + {ISX031_REG_LEN_08BIT, 0xBF06, 0x80}, + {ISX031_REG_LEN_08BIT, 0xBF07, 0x07}, + {ISX031_REG_LEN_08BIT, 0xBF08, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF09, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF0A, 0x38}, + {ISX031_REG_LEN_08BIT, 0xBF0B, 0x04}, + {ISX031_REG_LEN_08BIT, 0xBF0C, 0xE4}, + {ISX031_REG_LEN_08BIT, 0xBF0D, 0x00}, +}; + +static const struct isx031_reg isx031_1280_720_30fps_reg[] = { + {ISX031_REG_LEN_08BIT, 0x8AA8, 0x01}, // crop enable + {ISX031_REG_LEN_08BIT, 0x8AAA, 0x00}, // H size = 1280 + {ISX031_REG_LEN_08BIT, 0x8AAB, 0x05}, + {ISX031_REG_LEN_08BIT, 0x8AAC, 0x40}, // H croped 320*2 + {ISX031_REG_LEN_08BIT, 0x8AAD, 0x01}, + {ISX031_REG_LEN_08BIT, 0x8AAE, 0xD0}, // V size 720 + {ISX031_REG_LEN_08BIT, 0x8AAF, 0x02}, + {ISX031_REG_LEN_08BIT, 0x8AB0, 0x98}, // V cropped 408*2 + {ISX031_REG_LEN_08BIT, 0x8AB1, 0x01}, + {ISX031_REG_LEN_08BIT, 0x8ADA, 0x03}, // DCROP_DATA_SEL + {ISX031_REG_LEN_08BIT, 0xBF04, 0x01}, + {ISX031_REG_LEN_08BIT, 0xBF06, 0x00}, + {ISX031_REG_LEN_08BIT, 0xBF07, 0x05}, + {ISX031_REG_LEN_08BIT, 0xBF08, 0x40}, + {ISX031_REG_LEN_08BIT, 0xBF09, 0x01}, + {ISX031_REG_LEN_08BIT, 0xBF0A, 0xD0}, + {ISX031_REG_LEN_08BIT, 0xBF0B, 0x02}, + {ISX031_REG_LEN_08BIT, 0xBF0C, 0x98}, + {ISX031_REG_LEN_08BIT, 0xBF0D, 0x01}, +}; + +static const struct isx031_reg_list isx031_init_reg_list = { + .num_of_regs = ARRAY_SIZE(isx031_init_reg), + .regs = isx031_init_reg, +}; + +static const struct isx031_reg_list isx031_1920_1536_30fps_reg_list = { + .num_of_regs = ARRAY_SIZE(isx031_1920_1536_30fps_reg), + .regs = isx031_1920_1536_30fps_reg, +}; + +static const struct isx031_reg_list isx031_1920_1080_30fps_reg_list = { + .num_of_regs = ARRAY_SIZE(isx031_1920_1080_30fps_reg), + .regs = isx031_1920_1080_30fps_reg, +}; + +static const struct isx031_reg_list isx031_1280_720_30fps_reg_list = { + .num_of_regs = ARRAY_SIZE(isx031_1280_720_30fps_reg), + .regs = isx031_1280_720_30fps_reg, +}; + +static const struct isx031_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .fps = 30, + .reg_list = isx031_1920_1080_30fps_reg_list, + }, + { + .width = 1280, + .height = 720, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .fps = 30, + .reg_list = isx031_1280_720_30fps_reg_list, + }, + { + .width = 1920, + .height = 1536, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .fps = 30, + .reg_list = isx031_1920_1536_30fps_reg_list, + }, +}; + +static int isx031_read_reg(struct isx031 *isx031, u16 reg, u16 len, u32 *val) +{ + struct i2c_client *client = isx031->client; + struct i2c_msg msgs[2]; + u8 addr_buf[2]; + u8 data_buf[4] = {0}; + int ret; + + if (len > 4) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = sizeof(addr_buf); + msgs[0].buf = addr_buf; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_buf[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(data_buf); + + return 0; +} + +static int isx031_write_reg(struct isx031 *isx031, u16 reg, u16 len, u32 val) +{ + struct i2c_client *client = isx031->client; + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + dev_dbg(&client->dev, "%s, reg %x len %x, val %x\n", __func__, reg, len, val); + put_unaligned_be16(reg, buf); + put_unaligned_be32(val << 8 * (4 - len), buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int isx031_write_reg_list(struct isx031 *isx031, + const struct isx031_reg_list *r_list) +{ + struct i2c_client *client = v4l2_get_subdevdata(&isx031->sd); + unsigned int i; + int ret; + + for (i = 0; i < r_list->num_of_regs; i++) { + if (r_list->regs[i].mode == ISX031_REG_LEN_DELAY) { + msleep(r_list->regs[i].val); + continue; + } + ret = isx031_write_reg(isx031, r_list->regs[i].address, + ISX031_REG_LEN_08BIT, + r_list->regs[i].val); + if (ret) { + dev_err_ratelimited(&client->dev, + "failed to write reg 0x%4.4x. error = %d", + r_list->regs[i].address, ret); + return ret; + } + } + + return 0; +} + +static void isx031_update_pad_format(const struct isx031_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 isx031_start_streaming(struct isx031 *isx031) +{ + int retries, ret; + struct i2c_client *client = isx031->client; + const struct isx031_reg_list *reg_list; + + if (isx031->cur_mode != isx031->pre_mode) { + reg_list = &isx031->cur_mode->reg_list; + ret = isx031_write_reg_list(isx031, reg_list); + if (ret) { + dev_err(&client->dev, "failed to set stream mode"); + return ret; + } + isx031->pre_mode = isx031->cur_mode; + } else { + dev_dbg(&client->dev, "same mode, skip write reg list"); + } + + ret = isx031_write_reg(isx031, ISX031_REG_MODE_SELECT, 1, + ISX031_MODE_STREAMING); + + if (ret) { + dev_err(&client->dev, "failed to start stream"); + return ret; + } + + return 0; +} + +static void isx031_stop_streaming(struct isx031 *isx031) +{ + struct i2c_client *client = isx031->client; + + if (isx031_write_reg(isx031, ISX031_REG_MODE_SELECT, 1, + ISX031_MODE_STANDBY)) + dev_err(&client->dev, "failed to stop stream"); + msleep(50); +} + +static int isx031_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct isx031 *isx031 = to_isx031(sd); + struct i2c_client *client = isx031->client; + int ret = 0; + + if (isx031->streaming == enable) + return 0; + + mutex_lock(&isx031->mutex); + if (enable) { + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&client->dev); + mutex_unlock(&isx031->mutex); + return ret; + } + + ret = isx031_start_streaming(isx031); + if (ret) { + enable = 0; + isx031_stop_streaming(isx031); + pm_runtime_put(&client->dev); + } + } else { + isx031_stop_streaming(isx031); + pm_runtime_put(&client->dev); + } + + isx031->streaming = enable; + + mutex_unlock(&isx031->mutex); + + return ret; +} + +static int __maybe_unused isx031_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct isx031 *isx031 = to_isx031(sd); + + mutex_lock(&isx031->mutex); + if (isx031->streaming) + isx031_stop_streaming(isx031); + + mutex_unlock(&isx031->mutex); + + return 0; +} + +static int __maybe_unused isx031_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct isx031 *isx031 = to_isx031(sd); + const struct isx031_reg_list *reg_list; + int ret; + + reg_list = &isx031->cur_mode->reg_list; + ret = isx031_write_reg_list(isx031, reg_list); + if (ret) { + dev_err(&client->dev, "resume: failed to apply cur mode"); + return ret; + } + mutex_lock(&isx031->mutex); + if (isx031->streaming) { + ret = isx031_start_streaming(isx031); + if (ret) { + isx031->streaming = false; + isx031_stop_streaming(isx031); + mutex_unlock(&isx031->mutex); + return ret; + } + } + + mutex_unlock(&isx031->mutex); + + return 0; +} + +static int isx031_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct isx031 *isx031 = to_isx031(sd); + const struct isx031_mode *mode; + int ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_modes); i++) + if (supported_modes[i].code == fmt->format.code && + supported_modes[i].width == fmt->format.width && + supported_modes[i].height == fmt->format.height) { + mode = &supported_modes[i]; + break; + } + + if (i >= ARRAY_SIZE(supported_modes)) + mode = &supported_modes[0]; + + mutex_lock(&isx031->mutex); + + isx031_update_pad_format(mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; + } else { + isx031->cur_mode = mode; + } + + if (isx031->cur_mode != isx031->pre_mode) { + ret = isx031_write_reg_list(isx031, &isx031->cur_mode->reg_list); + if (ret) + dev_err(&isx031->client->dev, "failed to set stream mode"); + else + isx031->pre_mode = isx031->cur_mode; + } + + mutex_unlock(&isx031->mutex); + + return 0; +} + +static int isx031_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct isx031 *isx031 = to_isx031(sd); + + mutex_lock(&isx031->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(&isx031->sd, sd_state, + fmt->pad); + else + isx031_update_pad_format(isx031->cur_mode, &fmt->format); + + mutex_unlock(&isx031->mutex); + + return 0; +} + +static int isx031_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct isx031 *isx031 = to_isx031(sd); + + mutex_lock(&isx031->mutex); + isx031_update_pad_format(&supported_modes[0], + v4l2_subdev_get_try_format(sd, fh->state, 0)); + mutex_unlock(&isx031->mutex); + + return 0; +} + +static const struct v4l2_subdev_video_ops isx031_video_ops = { + .s_stream = isx031_set_stream, +}; + +static const struct v4l2_subdev_pad_ops isx031_pad_ops = { + .set_fmt = isx031_set_format, + .get_fmt = isx031_get_format, +}; + +static const struct v4l2_subdev_ops isx031_subdev_ops = { + .video = &isx031_video_ops, + .pad = &isx031_pad_ops, +}; + +static const struct media_entity_operations isx031_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops isx031_internal_ops = { + .open = isx031_open, +}; + +static int isx031_identify_module(struct isx031 *isx031) +{ + struct i2c_client *client = isx031->client; + int ret; + u32 val; + + ret = isx031_read_reg(isx031, ISX031_REG_CHIP_ID, + ISX031_REG_LEN_08BIT, &val); + if (ret) + return ret; + /* chip id not known yet */ + if (val != ISX031_CHIP_ID) { + dev_err(&client->dev, "read chip id: %x != %x", + val, ISX031_CHIP_ID); + return -ENXIO; + } + + return isx031_write_reg_list(isx031, &isx031_init_reg_list); +} + +static void isx031_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct isx031 *isx031 = to_isx031(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + pm_runtime_disable(&client->dev); + mutex_destroy(&isx031->mutex); + +} + +static int isx031_probe(struct i2c_client *client) +{ + struct v4l2_subdev *sd; + struct isx031 *isx031; + const struct isx031_reg_list *reg_list; + int ret; + + isx031 = devm_kzalloc(&client->dev, sizeof(*isx031), GFP_KERNEL); + if (!isx031) + return -ENOMEM; + + isx031->client = client; + isx031->platform_data = client->dev.platform_data; + if (isx031->platform_data == NULL) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + /* initialize subdevice */ + sd = &isx031->sd; + v4l2_i2c_subdev_init(sd, client, &isx031_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->internal_ops = &isx031_internal_ops; + sd->entity.ops = &isx031_subdev_entity_ops; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* initialize subdev media pad */ + isx031->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, 1, &isx031->pad); + if (ret < 0) { + dev_err(&client->dev, + "%s : media entity init Failed %d\n", __func__, ret); + return ret; + } + + ret = isx031_identify_module(isx031); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d", ret); + return ret; + } + + if (isx031->platform_data->suffix) + snprintf(isx031->sd.name, sizeof(isx031->sd.name), "isx031 %c", + isx031->platform_data->suffix); + + mutex_init(&isx031->mutex); + + /* 1920x1536 default */ + isx031->cur_mode = NULL; + isx031->pre_mode = &supported_modes[0]; + reg_list = &isx031->pre_mode->reg_list; + ret = isx031_write_reg_list(isx031, reg_list); + if (ret) { + dev_err(&client->dev, "failed to apply preset mode"); + goto probe_error_media_entity_cleanup; + } + isx031->cur_mode = isx031->pre_mode; + ret = v4l2_async_register_subdev_sensor(&isx031->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %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); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(&isx031->sd.entity); + pm_runtime_disable(&client->dev); + mutex_destroy(&isx031->mutex); + + return ret; +} + +static const struct dev_pm_ops isx031_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(isx031_suspend, isx031_resume) +}; + +static const struct i2c_device_id isx031_id_table[] = { + { "isx031", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, isx031_id_table); + +static struct i2c_driver isx031_i2c_driver = { + .driver = { + .name = "isx031", + .pm = &isx031_pm_ops, + }, + .probe_new = isx031_probe, + .remove = isx031_remove, + .id_table = isx031_id_table, +}; + +module_i2c_driver(isx031_i2c_driver); + +MODULE_AUTHOR("Hao Yao "); +MODULE_DESCRIPTION("isx031 sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/lt6911uxc.c b/drivers/media/i2c/lt6911uxc.c new file mode 100644 index 000000000000..c08174f4d2cf --- /dev/null +++ b/drivers/media/i2c/lt6911uxc.c @@ -0,0 +1,1563 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022-2023 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* v4l2 debug level */ +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define FSERIAL_CLK_4_LANE 240000000ULL +#define FSERIAL_CLK_2_LANE 144000000ULL + +#define PIX_CLK_4_LANE 60000000ULL +#define PIX_CLK_2_LANE 18000000ULL + +// LT6911UXC Register Setting + +#define LT6911UXC_REG_VALUE_08BIT 1 +#define LT6911UXC_REG_VALUE_16BIT 2 +#define LT6911UXC_CHIP_ID 0x1704 + +#define LT6911UXC_CID_CSI_PORT (V4L2_CID_USER_BASE | 0x1001) +#define LT6911UXC_CID_I2C_BUS (V4L2_CID_USER_BASE | 0x1002) +#define LT6911UXC_CID_I2C_ID (V4L2_CID_USER_BASE | 0x1003) +#define LT6911UXC_CID_I2C_SLAVE_ADDRESS (V4L2_CID_USER_BASE | 0x1004) +#define LT6911UXC_CID_FPS (V4L2_CID_USER_BASE | 0x1005) +#define LT6911UXC_CID_FRAME_INTERVAL (V4L2_CID_USER_BASE | 0x1006) +#define LT6911UXC_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_BASE | 0x1007) +#define LT6911UXC_CID_AUDIO_PRESENT (V4L2_CID_USER_BASE | 0x1008) + +#define REG_CHIP_ID 0x8100 + +/* Control */ +#define REG_BANK 0xFF + +#define REG_ENABLE_I2C 0x80EE +#define REG_DISABLE_WD 0x8010 + +/* Resolution registers */ +#define REG_H_TOTAL 0x867C /* horizontal half total pixel */ +#define REG_H_ACTIVE 0x8680 /* horizontal half active pixel */ +#define REG_H_FP_0P5 0x8678 /* horizontal half front porch pixel */ +#define REG_H_BP_0P5 0x8676 /* horizontal half back porch pixel */ +#define REG_H_SW_0P5 0x8672 /* hsync half length pixel */ +#define REG_V_TOTAL 0x867A /* vertical total lines */ +#define REG_V_ACTIVE 0x867E /* vertical active lines */ +#define REG_V_BP 0x8674 /* vertical back porch lines */ +#define REG_V_FP 0x8675 /* vertical front porch lines */ +#define REG_V_SW 0x8671 /* vsync length lines */ +#define REG_SYNC_POL 0x8670 /* hsync/vsync polarity flags */ +#define REG_BKB0_A2_REG 0xB0A2 +#define REG_FM1_FREQ_IN2 0x8548 +#define REG_FM1_FREQ_IN1 0x8549 +#define REG_FM1_FREQ_IN0 0x854A + +#define REG_AD_HALF_PCLK 0x8540 + +#define REG_TMDS_CLK_IN2 0x8750 +#define REG_TMDS_CLK_IN1 0x8751 +#define REG_TMDS_CLK_IN0 0x8752 + +/* MIPI-TX */ +#define REG_MIPI_TX_CTRL 0x811D +#define REG_MIPI_LANES 0x86A2 +#define REG_MIPI_CLK_MODE 0xD468 + +/* Audio sample rate */ +#define REG_INT_AUDIO 0x86A5 +#define AUDIO_DISCONNECT 0x88 +#define AUDIO_SR_HIGH 0x55 +#define AUDIO_SR_LOW 0xAA +#define REG_AUDIO_SR 0xB0AB + +/* Interrupts */ +#define REG_INT_HDMI 0x86A3 +#define REG_INT_RESPOND 0x86A6 +#define INT_HDMI_STABLE 0x55 +#define INT_HDMI_DISCONNECT 0x88 + +#define REG_INT_AUDIO 0x86A5 +#define INT_AUDIO_DISCONNECT 0x88 +#define INT_AUDIO_SR_HIGH 0x55 +#define INT_AUDIO_SR_LOW 0xAA + +/* FPS registers */ +#define MASK_FMI_FREQ2 0x0F +#define MASK_VSYNC_POL (1 << 1) +#define MASK_HSYNC_POL (1 << 0) + +/* Frame ID registers */ +#define REG_FRAME_ID 0xD40E +#define REG_FRAME_STATUS 0xD414 + +#define LT69111UXC_IRQ_MODE +#define MAX_MIPI_PORT_USE 3 + +static const struct v4l2_dv_timings_cap lt6911uxc_timings_cap_4kp30 = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + /* Pixel clock from REF_01 p. 20. Min/max height/width are unknown */ + V4L2_INIT_BT_TIMINGS( + 160, 3840, /* min/max width */ + 120, 2160, /* min/max height */ + 25000000, 297000000, /* min/max pixelclock */ + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_CUSTOM | + V4L2_DV_BT_CAP_REDUCED_BLANKING) +}; + +struct lt6911uxc_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* MEDIA_BUS_FMT */ + u32 code; + + /* REG_MIPI_LANES */ + s32 lanes; + + /* MODE_FPS*/ + u32 fps; + + /* Bit per pixel */ + u32 bpp; + + /* Pixel rate*/ + s64 pixel_clk; + + /* Byte rate*/ + u32 byte_clk; + + /* Audio sample rate*/ + u32 audio_sample_rate; +}; + +struct lt6911uxc_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *audio_sampling_rate_ctrl; + struct v4l2_ctrl *audio_present_ctrl; + struct v4l2_ctrl *csi_port; + struct v4l2_ctrl *i2c_bus; + struct v4l2_ctrl *i2c_id; + struct v4l2_ctrl *i2c_slave_address; + struct v4l2_ctrl *fps; + struct v4l2_ctrl *frame_interval; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *analogue_gain; + struct v4l2_ctrl *digital_gain; + struct v4l2_ctrl *strobe_source; + struct v4l2_ctrl *strobe; + struct v4l2_ctrl *strobe_stop; + struct v4l2_ctrl *timeout; + struct v4l2_ctrl *hblank; + + struct v4l2_dv_timings timings; + struct v4l2_dv_timings detected_timings; + + struct i2c_client *i2c_client; + + struct media_pad pad; + struct mutex mutex; + struct lt6911uxc_platform_data *platform_data; + + /* Current mode */ + struct lt6911uxc_mode *cur_mode; + + bool streaming; + + u8 bank_i2c; + bool enable_i2c; + + u32 mbus_fmt_code; /* current media bus format */ + + u32 thread_run; + struct task_struct *poll_task; + bool auxiliary_port; + + s64 sub_stream; +}; + +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_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct lt6911uxc_state, sd); +} + +static void lt6911uxc_reg_bank(struct v4l2_subdev *sd, u8 bank) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + struct i2c_client *client = lt6911uxc->i2c_client; + int ret; + struct i2c_msg msg; + u8 data[2]; + u8 address; + + if (lt6911uxc->bank_i2c == bank) + return; + dev_dbg(&client->dev, "i2c: change register bank to 0x%02X\n", + bank); + + address = 0xFF; + msg.addr = client->addr; + msg.buf = data; + msg.len = 2; + msg.flags = 0; + + data[0] = address; + data[1] = bank; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) { + dev_info(&client->dev, "%s: switch to bank 0x%x from 0x%x failed\n", + __func__, bank, client->addr); + return; + } + lt6911uxc->bank_i2c = bank; +} + +static void lt6911uxc_i2c_wr8(struct v4l2_subdev *sd, u16 reg, u8 val) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + struct i2c_client *client = lt6911uxc->i2c_client; + int ret; + struct i2c_msg msg; + u8 data[2]; + u8 address; + + /* write register bank offset */ + u8 bank = (reg >> 8) & 0xFF; + + lt6911uxc_reg_bank(sd, bank); + address = reg & 0xFF; + msg.addr = client->addr; + msg.buf = data; + msg.len = 2; + msg.flags = 0; + + data[0] = address; + data[1] = val; + + ret = i2c_transfer(client->adapter, &msg, 1); + + if (ret != 1) { + dev_info(&client->dev, "%s: write register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + } + dev_dbg(&client->dev, "i2c: write register: 0x%04X = 0x%02X\n", + reg, val); +} + +static void lt6911uxc_i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + struct i2c_client *client = lt6911uxc->i2c_client; + int ret; + u8 reg_addr[1] = { (u8)(reg & 0xff) }; + u8 bank_addr = (u8)((reg >> 8) & 0xFF); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, /* write */ + .len = 1, + .buf = reg_addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, /* read n bytes */ + .len = n, + .buf = values, + }, + }; + + /* write register bank offset */ + lt6911uxc_reg_bank(sd, bank_addr); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_info(&client->dev, "%s: read register 0x%04X from 0x%x failed\n", + __func__, reg, client->addr); + } +} + +static u8 lt6911uxc_i2c_rd8(struct v4l2_subdev *sd, u16 reg) +{ + u8 val = 0; + + lt6911uxc_i2c_rd(sd, reg, &val, 1); + + dev_dbg(sd->dev, "i2c: read 0x%04X = 0x%02X\n", reg, val); + return val; +} + +static u16 lt6911uxc_i2c_rd16(struct v4l2_subdev *sd, u16 reg) +{ + u16 val = 0; + u8 tmp; + + lt6911uxc_i2c_rd(sd, reg, (u8 *)&val, 2); + /* high byte always at lower address -> swap */ + tmp = val & 0xFF; + val = (val >> 8) | tmp << 8; + + dev_dbg(sd->dev, "i2c: read 0x%04X = 0x%04X\n", reg, val); + return val; +} + +static void lt6911uxc_ext_control( + struct lt6911uxc_state *lt6911uxc, + bool enable) +{ + if (!lt6911uxc) + return; + + if (lt6911uxc->enable_i2c == enable) + return; + + lt6911uxc->enable_i2c = enable; + if (enable) { + lt6911uxc_i2c_wr8(<6911uxc->sd, REG_ENABLE_I2C, 0x01); + lt6911uxc_i2c_wr8(<6911uxc->sd, REG_DISABLE_WD, 0x00); + } else + lt6911uxc_i2c_wr8(<6911uxc->sd, REG_ENABLE_I2C, 0x00); +} + +static int lt6911uxc_csi_enable(struct v4l2_subdev *sd, bool enable) +{ + if (enable) + lt6911uxc_i2c_wr8(sd, REG_MIPI_TX_CTRL, 0xFB); + else + lt6911uxc_i2c_wr8(sd, REG_MIPI_TX_CTRL, 0x00); + return 0; +} + +static int lt6911uxc_get_audio_sampling_rate(struct lt6911uxc_state *lt6911uxc) +{ + int audio_fs, idx; + static const int eps = 1500; + static const int rates_default[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + audio_fs = lt6911uxc_i2c_rd8(<6911uxc->sd, REG_AUDIO_SR) * 1000; + dev_dbg(<6911uxc->i2c_client->dev, "%s: Audio sample rate %d [Hz]\n", + __func__, audio_fs); + + /* 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]; + } + dev_info(<6911uxc->i2c_client->dev, "%s: unhandled sampling rate %d [Hz]", + __func__, audio_fs); + return 0; +} + +static int lt6911uxc_log_status(struct v4l2_subdev *sd) +{ + +#ifdef TIMINGS_ENABLE + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + v4l2_info(sd, "----- Timings -----\n"); + if (!<6911uxc->detected_timings.bt.width) { + v4l2_info(sd, "no video detected\n"); + } else { + v4l2_print_dv_timings(sd->name, "detected format: ", + <6911uxc->detected_timings, true); + } + v4l2_print_dv_timings(sd->name, "configured format: ", + <6911uxc->timings, true); +#endif + return 0; +} + +static int lt6911uxc_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + dev_info(sd->dev, "%s():\n", __func__); + + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + default: + return -EINVAL; + } +} + +static const struct v4l2_dv_timings_cap *lt6911uxc_g_timings_cap( + struct lt6911uxc_state *lt6911uxc) +{ + return <6911uxc_timings_cap_4kp30; +} + +static int lt6911uxc_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + *status = 0; + *status |= lt6911uxc->streaming ? V4L2_IN_ST_NO_SIGNAL : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + return 0; +} + +static int __maybe_unused lt6911uxc_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s():\n", __func__); + + if (!v4l2_valid_dv_timings(timings, lt6911uxc_g_timings_cap(lt6911uxc), + NULL, NULL)) { + v4l2_err(sd, "%s: timings out of range\n", __func__); + return -EINVAL; + } + + v4l2_find_dv_timings_cap(timings, lt6911uxc_g_timings_cap(lt6911uxc), 0, + NULL, NULL); + memset(timings->bt.reserved, 0, sizeof(timings->bt.reserved)); + lt6911uxc->timings = *timings; + return 0; +} + +static int lt6911uxc_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s():\n", __func__); + + *timings = lt6911uxc->timings; + return 0; +} + +static int __maybe_unused lt6911uxc_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s():\n", __func__); + + if (false == lt6911uxc->streaming) { + v4l2_warn(sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + + if (!v4l2_valid_dv_timings(<6911uxc->detected_timings, + lt6911uxc_g_timings_cap(lt6911uxc), NULL, NULL)) { + v4l2_warn(sd, "%s: timings out of range\n", __func__); + return -ERANGE; + } + + *timings = lt6911uxc->detected_timings; + return 0; +} + +static const struct v4l2_ctrl_config lt6911uxc_ctrl_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_ctrl_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 __maybe_unused get_hblank(struct lt6911uxc_state *lt6911uxc) +{ + u64 hblank; + u64 pixel_rate; + u64 pixel_clk; + + if (lt6911uxc->cur_mode->lanes == 4) { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } else if (lt6911uxc->cur_mode->lanes == 2) { + pixel_rate = FSERIAL_CLK_2_LANE; + pixel_clk = PIX_CLK_2_LANE; + } else { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } + + if (pixel_clk) + hblank = 0x128 * (pixel_rate / pixel_clk); + else + hblank = 1184; + + return hblank; +} + +static int lt6911uxc_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct lt6911uxc_state *lt6911uxc = container_of(ctrl->handler, + struct lt6911uxc_state, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); + s64 exposure_max; + int ret = 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 = 1; + __v4l2_ctrl_modify_range(lt6911uxc->exposure, + lt6911uxc->exposure->minimum, + exposure_max, + lt6911uxc->exposure->step, + lt6911uxc->cur_mode->height - 1); + } + + /* 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: + dev_dbg(&client->dev, "set analogue gain.\n"); + break; + + case V4L2_CID_DIGITAL_GAIN: + dev_dbg(&client->dev, "set digital gain.\n"); + break; + + case V4L2_CID_EXPOSURE: + dev_dbg(&client->dev, "set exposure time.\n"); + break; + + case V4L2_CID_VBLANK: + dev_dbg(&client->dev, "set vblank %d\n", + lt6911uxc->cur_mode->height + ctrl->val); + break; + case V4L2_CID_FLASH_STROBE_SOURCE: + dev_dbg(&client->dev, "set led flash source %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_STROBE: + dev_dbg(&client->dev, "set flash strobe.\n"); + break; + + case V4L2_CID_FLASH_STROBE_STOP: + dev_dbg(&client->dev, "turn off led %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_TIMEOUT: + dev_dbg(&client->dev, "set led delay\n"); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops lt6911uxc_ctrl_ops = { + .s_ctrl = lt6911uxc_set_ctrl, +}; + +static struct v4l2_ctrl_config lt6911uxc_csi_port = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_CSI_PORT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "CSI port", + .min = 0, + .max = 5, + .def = 1, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxc_i2c_bus = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_I2C_BUS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C bus", + .min = 0, + .max = MINORMASK, + .def = 0, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxc_i2c_id = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_I2C_ID, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C id", + .min = 0x10, + .max = 0x77, + .def = 0x10, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxc_i2c_slave_address = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_I2C_SLAVE_ADDRESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C slave address", + .min = 0x0, + .max = 0x7f, + .def = 0x2b, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxc_fps = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_FPS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "fps", + .min = 25, + .max = 60, + .def = 30, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxc_frame_interval = { + .ops = <6911uxc_ctrl_ops, + .id = LT6911UXC_CID_FRAME_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "frame interval", + .min = 16, + .max = 40, + .def = 33, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static u64 get_pixel_rate(struct lt6911uxc_state *lt6911uxc) +{ + u64 pixel_rate = 995328000ULL; /* default value: 4K@30 */ + + if (lt6911uxc->cur_mode->lanes) { + 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_init_controls(struct lt6911uxc_state *lt6911uxc) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 hblank; + int ret; + + ctrl_hdlr = <6911uxc->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = <6911uxc->mutex; + lt6911uxc->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_LINK_FREQ, + sizeof(lt6911uxc->cur_mode->pixel_clk), + 0, <6911uxc->cur_mode->pixel_clk); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set link_freq ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxc->link_freq) + lt6911uxc->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + lt6911uxc->vblank = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_VBLANK, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set vblank ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc->analogue_gain = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set analogue_gain ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc->digital_gain = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set digital_gain ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc->exposure = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set exposure ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + lt6911uxc_csi_port.def = lt6911uxc->platform_data->port; + lt6911uxc->csi_port = + v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxc_csi_port, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set csi_port ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc_i2c_bus.def = i2c_adapter_id(client->adapter); + lt6911uxc->i2c_bus = + v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxc_i2c_bus, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_bus ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc_i2c_id.def = client->addr; + lt6911uxc->i2c_id = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxc_i2c_id, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_id ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc_i2c_slave_address.def = + lt6911uxc->platform_data->i2c_slave_address; + lt6911uxc->i2c_slave_address = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxc_i2c_slave_address, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_slave_address ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc_fps.def = lt6911uxc->cur_mode->fps; + lt6911uxc->fps = v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxc_fps, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set fps ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + if (lt6911uxc->cur_mode->fps) + lt6911uxc_frame_interval.def = 1000 / lt6911uxc->cur_mode->fps; + else + lt6911uxc_frame_interval.def = 33; + + lt6911uxc->frame_interval = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxc_frame_interval, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set frame_interval ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_PIXEL_RATE, + get_pixel_rate(lt6911uxc), + get_pixel_rate(lt6911uxc), 1, + get_pixel_rate(lt6911uxc)); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set pixel_rate ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxc->pixel_rate) + lt6911uxc->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hblank = 1; + lt6911uxc->hblank = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxc_ctrl_ops, + V4L2_CID_HBLANK, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set hblank ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxc->hblank) + lt6911uxc->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* custom v4l2 audio controls */ + lt6911uxc->audio_sampling_rate_ctrl = v4l2_ctrl_new_custom( + ctrl_hdlr, <6911uxc_ctrl_audio_sampling_rate, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set audio sampling rate ctrl, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + lt6911uxc->audio_present_ctrl = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxc_ctrl_audio_present, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set audio present ctrl, error = %d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxc->sd.ctrl_handler = ctrl_hdlr; + return 0; +} + +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_state *lt6911uxc) +{ + int ret = 0; + + if (lt6911uxc->auxiliary_port == true) + return 0; + + lt6911uxc_ext_control(lt6911uxc, true); + lt6911uxc_csi_enable(<6911uxc->sd, true); + lt6911uxc_ext_control(lt6911uxc, false); + + ret = __v4l2_ctrl_handler_setup(lt6911uxc->sd.ctrl_handler); + if (ret) + return ret; + + return 0; +} + +static void lt6911uxc_stop_streaming(struct lt6911uxc_state *lt6911uxc) +{ + if (lt6911uxc->auxiliary_port == true) + return; + + /*The fps of 1080p60fps will be dropped to half when the CSI disabled. */ + lt6911uxc_ext_control(lt6911uxc, true); + lt6911uxc_csi_enable(<6911uxc->sd, false); + lt6911uxc_ext_control(lt6911uxc, false); +} + +static int lt6911uxc_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + int ret = 0; + + if (lt6911uxc->streaming == enable) + return 0; + + if (lt6911uxc->auxiliary_port == true) + return 0; + + mutex_lock(<6911uxc->mutex); + if (enable) { + dev_dbg(sd->dev, "[%s()], start streaming.\n", __func__); + ret = lt6911uxc_start_streaming(lt6911uxc); + if (ret) { + enable = 0; + lt6911uxc_stop_streaming(lt6911uxc); + } + } else { + dev_dbg(sd->dev, "[%s()], stop streaming.\n", __func__); + lt6911uxc_stop_streaming(lt6911uxc); + } + mutex_unlock(<6911uxc->mutex); + + lt6911uxc->streaming = enable; + + return ret; +} + +static int lt6911uxc_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fival) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + fival->pad = 0; + fival->interval.numerator = 1; + fival->interval.denominator = lt6911uxc->cur_mode->fps; + + return 0; +} + +static int lt6911uxc_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + s32 vblank_def; + s64 hblank; + + mutex_lock(<6911uxc->mutex); + lt6911uxc_update_pad_format(lt6911uxc->cur_mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; + } else { + __v4l2_ctrl_s_ctrl(lt6911uxc->link_freq, + lt6911uxc->cur_mode->link_freq_index); + __v4l2_ctrl_modify_range(lt6911uxc->pixel_rate, + 25000000, 297000000, 1, + get_pixel_rate(lt6911uxc)); + + hblank = get_hblank(lt6911uxc); + __v4l2_ctrl_modify_range(lt6911uxc->hblank, + hblank, + hblank, + 1, + hblank); + + /* Update limits and set FPS to default */ + vblank_def = lt6911uxc->cur_mode->vts_def - lt6911uxc->cur_mode->height; + __v4l2_ctrl_modify_range(lt6911uxc->vblank, + 0, + 0xffff - lt6911uxc->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(lt6911uxc->vblank, vblank_def); + + __v4l2_ctrl_s_ctrl(lt6911uxc->fps, lt6911uxc->cur_mode->fps); + + if (lt6911uxc->cur_mode->fps) + __v4l2_ctrl_s_ctrl(lt6911uxc->frame_interval, 1000 / lt6911uxc->cur_mode->fps); + else + __v4l2_ctrl_s_ctrl(lt6911uxc->frame_interval, 33); + } + mutex_unlock(<6911uxc->mutex); + + return 0; +} + +static int lt6911uxc_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + mutex_lock(<6911uxc->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(<6911uxc->sd, sd_state, + fmt->pad); + else + lt6911uxc_update_pad_format(lt6911uxc->cur_mode, &fmt->format); + + mutex_unlock(<6911uxc->mutex); + + 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_state *lt6911uxc = to_state(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_state *lt6911uxc = to_state(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_state *lt6911uxc = to_state(sd); + + fie->interval.numerator = 1; + fie->interval.denominator = lt6911uxc->cur_mode->fps; + + return 0; +} + +static int lt6911uxc_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + lt6911uxc_update_pad_format(lt6911uxc->cur_mode, + v4l2_subdev_get_try_format(sd, fh->state, 0)); + + return 0; +} + +static const struct v4l2_subdev_internal_ops lt6911uxc_subdev_internal_ops = { + .open = lt6911uxc_open, +}; + +static const struct v4l2_subdev_video_ops lt6911uxc_video_ops = { + .s_stream = lt6911uxc_set_stream, + .g_frame_interval = lt6911uxc_g_frame_interval, + .g_input_status = lt6911uxc_g_input_status, +// .s_dv_timings = lt6911uxc_s_dv_timings, + .g_dv_timings = lt6911uxc_g_dv_timings, +// .query_dv_timings = lt6911uxc_query_dv_timings, + .s_stream = lt6911uxc_set_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 struct v4l2_subdev_core_ops lt6911uxc_subdev_core_ops = { + .log_status = lt6911uxc_log_status, + .subscribe_event = lt6911uxc_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 = { + .open = lt6911uxc_open, +}; + +static int lt6911uxc_identify_module(struct lt6911uxc_state *lt6911uxc) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); + u16 val; + + val = lt6911uxc_i2c_rd16(<6911uxc->sd, REG_CHIP_ID); + + if (val != LT6911UXC_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + LT6911UXC_CHIP_ID, val); + return -ENXIO; + } + dev_info(&client->dev, + "Found lt6911uxc Bridge Chip, ID is 0x%x\n", val); + return 0; +} + +static void lt6911uxc_remove(struct i2c_client *client) +{ +} + +static int lt6911uxc_video_status_update(struct lt6911uxc_state *lt6911uxc) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); + u16 val = 0; + u8 int_event = 0; + u32 tmds_clk = 0; + u32 byte_clock = 0, pixel_clk = 0; + u8 is_hdmi_2_0 = 0; + u32 htotal = 0, vtotal = 0; + u32 width = 0, height = 0; + u32 fps = 0, lanes = 4; + u8 fm0 = 0, fm1 = 0, fm2 = 0; + + /* Read interrupt event */ + int_event = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_INT_HDMI); + switch (int_event) { + case INT_HDMI_STABLE: + dev_info(&client->dev, "Video signal stable\n"); + + /* byte clock / MIPI clock */ + lt6911uxc_i2c_wr8(<6911uxc->sd, + REG_AD_HALF_PCLK, 0x1B); + usleep_range(10000, 10100); + fm2 = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_FM1_FREQ_IN2) & MASK_FMI_FREQ2; + fm1 = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_FM1_FREQ_IN1); + fm0 = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_FM1_FREQ_IN0); + + byte_clock = (fm2<<16 | fm1<<8 | fm0) * 1000; + + /* TMDS clock / Pixel clock*/ + val = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_TMDS_CLK_IN2); + tmds_clk |= ((val & 0x0f) << 16); + val = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_TMDS_CLK_IN1); + tmds_clk |= (val << 8); + val = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_TMDS_CLK_IN0); + tmds_clk |= val; + + val = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_BKB0_A2_REG); + is_hdmi_2_0 = val & (1<<0) ? !0 : !!0; + pixel_clk = (is_hdmi_2_0 ? 4 * tmds_clk : tmds_clk) * 1000; + + /* video frame size */ + htotal = lt6911uxc_i2c_rd16(<6911uxc->sd, + REG_H_TOTAL) * 2; + vtotal = lt6911uxc_i2c_rd16(<6911uxc->sd, + REG_V_TOTAL); + width = lt6911uxc_i2c_rd16(<6911uxc->sd, + REG_H_ACTIVE) * 2; + height = lt6911uxc_i2c_rd16(<6911uxc->sd, + REG_V_ACTIVE); + if (htotal && vtotal) + fps = pixel_clk / (htotal * vtotal); + lanes = lt6911uxc_i2c_rd8(<6911uxc->sd, + REG_MIPI_LANES); + + lt6911uxc->cur_mode->height = height; + lt6911uxc->cur_mode->fps = fps; + lt6911uxc->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; + + if (lanes == 8) { + /* 4K60fps with 2 MIPI ports*/ + if (width >= 3840) + lt6911uxc->cur_mode->width = width/2; /* YUV422 */ + else + lt6911uxc->cur_mode->width = width; /* YUV420 */ + lt6911uxc->cur_mode->lanes = lanes/2; + lt6911uxc->cur_mode->pixel_clk = pixel_clk/2; + lt6911uxc->cur_mode->byte_clk = byte_clock/2; + } else { + lt6911uxc->cur_mode->width = width; + lt6911uxc->cur_mode->lanes = lanes; + lt6911uxc->cur_mode->pixel_clk = pixel_clk; + lt6911uxc->cur_mode->byte_clk = byte_clock; + } + v4l2_subdev_notify_event(<6911uxc->sd, + <6911uxc_ev_source_change); + + dev_dbg(&client->dev, "Pixel Clk:%u, %lld\n", + pixel_clk, lt6911uxc->cur_mode->pixel_clk); + 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: + lt6911uxc->cur_mode->width = 0; + lt6911uxc->cur_mode->height = 0; + lt6911uxc->cur_mode->fps = 30; + lt6911uxc->cur_mode->pixel_clk = 0; + lt6911uxc->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; + lt6911uxc->cur_mode->byte_clk = 0; + lt6911uxc->cur_mode->lanes = 4; + 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%02X\n", int_event); + return -ENOLINK; + } + + return 0; +} + +static void lt6911uxc_audio_status_update(struct lt6911uxc_state *lt6911uxc) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxc->sd); + u8 int_event; + int audio_fs = 0; + + /* read interrupt event */ + int_event = lt6911uxc_i2c_rd8(<6911uxc->sd, REG_INT_AUDIO); + dev_dbg(&client->dev, "Audio event:0x%02X\n", int_event); + + switch (int_event) { + case AUDIO_DISCONNECT: + dev_dbg(&client->dev, "Audio signal disconnected\n"); + audio_fs = 0; + lt6911uxc->cur_mode->audio_sample_rate = 0; + break; + case AUDIO_SR_HIGH: + case AUDIO_SR_LOW: + audio_fs = lt6911uxc_get_audio_sampling_rate(lt6911uxc); + lt6911uxc->cur_mode->audio_sample_rate = audio_fs; + dev_dbg(&client->dev, + "Sampling rate changed: %d\n", audio_fs); + break; + default: + dev_dbg(&client->dev, "Unhandled audio= 0x%02X\n", int_event); + // use the default value for avoiding problem + lt6911uxc->cur_mode->audio_sample_rate = 0; + break; + } + + __v4l2_ctrl_s_ctrl(lt6911uxc->audio_present_ctrl, + (lt6911uxc->cur_mode->audio_sample_rate != 0)); + + if (lt6911uxc->cur_mode->audio_sample_rate) + __v4l2_ctrl_s_ctrl(lt6911uxc->audio_sampling_rate_ctrl, + lt6911uxc->cur_mode->audio_sample_rate); +} + +static void lt6911uxc_check_status(struct lt6911uxc_state *lt6911uxc) +{ + mutex_lock(<6911uxc->mutex); + lt6911uxc_ext_control(lt6911uxc, true); + lt6911uxc_video_status_update(lt6911uxc); + lt6911uxc_audio_status_update(lt6911uxc); + lt6911uxc_i2c_wr8(<6911uxc->sd, REG_INT_RESPOND, 1); + lt6911uxc_ext_control(lt6911uxc, false); + mutex_unlock(<6911uxc->mutex); +} + +static irqreturn_t lt6911uxc_threaded_irq_fn(int irq, void *dev_id) +{ + struct v4l2_subdev *sd = dev_id; + struct lt6911uxc_state *lt6911uxc; + + if (!sd) { + dev_err(NULL, "Invalid dev_id argument!\n"); + return IRQ_NONE; + } + + lt6911uxc = to_state(sd); + if (!lt6911uxc) { + dev_err(sd->dev, "Invalid lt6911uxc state argument!\n"); + return IRQ_NONE; + } + + dev_dbg(sd->dev, "%s in kthread %d\n", __func__, current->pid); + lt6911uxc_check_status(lt6911uxc); + return IRQ_HANDLED; +} + +static int lt6911uxc_probe(struct i2c_client *client) +{ + struct lt6911uxc_state *lt6911uxc; + struct v4l2_subdev *sd; + int ret; + + lt6911uxc = devm_kzalloc(&client->dev, sizeof(struct lt6911uxc_state), + 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; + + memset(lt6911uxc->cur_mode, 0, sizeof(struct lt6911uxc_mode)); + lt6911uxc->cur_mode->width = 3840, + lt6911uxc->cur_mode->height = 2160, + lt6911uxc->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16, + lt6911uxc->cur_mode->lanes = 4, + lt6911uxc->cur_mode->fps = 30, + lt6911uxc->cur_mode->bpp = 8, + lt6911uxc->cur_mode->pixel_clk = 297000000, + lt6911uxc->cur_mode->byte_clk = 168000000, + lt6911uxc->cur_mode->audio_sample_rate = 48000, + + lt6911uxc->platform_data = client->dev.platform_data; + if (lt6911uxc->platform_data == NULL) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + lt6911uxc->i2c_client = client; + sd = <6911uxc->sd; + v4l2_i2c_subdev_init(sd, client, <6911uxc_subdev_ops); + + if (lt6911uxc->platform_data->suffix) + snprintf(lt6911uxc->sd.name, + sizeof(lt6911uxc->sd.name), "lt6911uxc %c", + lt6911uxc->platform_data->suffix); + + mutex_init(<6911uxc->mutex); + + if (-1 != lt6911uxc->platform_data->reset_pin) + if (!gpio_get_value(lt6911uxc->platform_data->reset_pin)) + gpio_set_value(lt6911uxc->platform_data->reset_pin, 1); + + msleep(50); + + if (-1 != lt6911uxc->platform_data->irq_pin) { + lt6911uxc->auxiliary_port = false; + dev_info(&client->dev, "Probing lt6911uxc chip...\n"); + + lt6911uxc_ext_control(lt6911uxc, true); + ret = lt6911uxc_identify_module(lt6911uxc); + if (ret) { + dev_err(&client->dev, "failed to find chip: %d", ret); + return ret; + } + lt6911uxc_ext_control(lt6911uxc, false); + + ret = lt6911uxc_init_controls(lt6911uxc); + if (ret) { + dev_info(&client->dev, "Could not init control %d!\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + lt6911uxc->sd.internal_ops = <6911uxc_internal_ops; + lt6911uxc->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + 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 probe_error_v4l2_ctrl_handler_free; + } + + /* Setting irq */ + ret = devm_gpio_request_one(&client->dev, + lt6911uxc->platform_data->irq_pin, + GPIOF_OUT_INIT_HIGH, "Interrupt signal"); + if (ret) { + dev_err(&client->dev, "IRQ pin %d (name: %s) request failed! ret: %d\n", + lt6911uxc->platform_data->irq_pin, + lt6911uxc->platform_data->irq_pin_name, ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = gpio_direction_input(lt6911uxc->platform_data->irq_pin); + if (ret) { + dev_err(&client->dev, "Set gpio pin %d direction input failed! ret: %d\n", + lt6911uxc->platform_data->irq_pin, ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = devm_request_threaded_irq(&client->dev, + gpio_to_irq(lt6911uxc->platform_data->irq_pin), + NULL, lt6911uxc_threaded_irq_fn, + lt6911uxc->platform_data->irq_pin_flags, + lt6911uxc->platform_data->irq_pin_name, lt6911uxc); + if (ret) { + dev_err(&client->dev, "IRQ request failed! ret: %d\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + /* Check the current status */ + usleep_range(200000, 205000); + lt6911uxc_check_status(lt6911uxc); + /* Stop to transmit MIPI data firstly waiting for IPU ready */ + lt6911uxc_stop_streaming(lt6911uxc); + } else { + /* 4K60fps mode, the setting needs to be fixed on 1920x2160@60fps*/ + lt6911uxc->auxiliary_port = true; + lt6911uxc->cur_mode->width = 1920, + lt6911uxc->cur_mode->height = 2160, + lt6911uxc->cur_mode->fps = 60, + + ret = lt6911uxc_init_controls(lt6911uxc); + if (ret) { + dev_info(&client->dev, "Could not init control %d!\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + lt6911uxc->sd.internal_ops = <6911uxc_internal_ops; + lt6911uxc->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + 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 probe_error_v4l2_ctrl_handler_free; + } + } + + lt6911uxc->sd.dev = &client->dev; + lt6911uxc->sd.internal_ops = <6911uxc_subdev_internal_ops; + lt6911uxc->sd.flags |= + V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + ret = v4l2_async_register_subdev_sensor(<6911uxc->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %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); + dev_info(&client->dev, "End to probe lt6911uxc Bridge Chip.\n"); + dev_info(&client->dev, "%s Probe Succeeded", lt6911uxc->sd.name); + + return 0; + +probe_error_media_entity_cleanup: + media_entity_cleanup(<6911uxc->sd.entity); + +probe_error_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(lt6911uxc->sd.ctrl_handler); + mutex_destroy(<6911uxc->mutex); + dev_err(&client->dev, "%s Probe Failed", lt6911uxc->sd.name); + + return ret; +} + +static int lt6911uxc_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct lt6911uxc_state *lt6911uxc = to_state(sd); + + if (-1 != lt6911uxc->platform_data->reset_pin) + if (gpio_get_value(lt6911uxc->platform_data->reset_pin)) + gpio_set_value(lt6911uxc->platform_data->reset_pin, 0); + + mutex_lock(<6911uxc->mutex); + if (lt6911uxc->streaming) + lt6911uxc_stop_streaming(lt6911uxc); + + mutex_unlock(<6911uxc->mutex); + dev_dbg(sd->dev, "suspend streaming...\n"); + return 0; +} + +static int lt6911uxc_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct lt6911uxc_state *lt6911uxc = to_state(sd); + int ret; + + if (-1 != lt6911uxc->platform_data->reset_pin) + if (!gpio_get_value(lt6911uxc->platform_data->reset_pin)) + gpio_set_value(lt6911uxc->platform_data->reset_pin, 1); + + usleep_range(200000, 205000); + //recheck the current HDMI status in case changed + lt6911uxc_check_status(lt6911uxc); + + mutex_lock(<6911uxc->mutex); + if (lt6911uxc->streaming) { + ret = lt6911uxc_start_streaming(lt6911uxc); + if (ret) { + lt6911uxc->streaming = false; + lt6911uxc_stop_streaming(lt6911uxc); + mutex_unlock(<6911uxc->mutex); + return ret; + } + } + mutex_unlock(<6911uxc->mutex); + dev_dbg(sd->dev, "resume streaming...\n"); + return 0; +} + +static const struct dev_pm_ops lt6911uxc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lt6911uxc_suspend, lt6911uxc_resume) +}; + +static const struct i2c_device_id lt6911uxc_id_table[] = { + { "lt6911uxc", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, lt6911uxc_id_table); + +static struct i2c_driver lt6911uxc_i2c_driver = { + .driver = { + .name = "lt6911uxc", + .pm = <6911uxc_pm_ops, + }, + .probe_new = lt6911uxc_probe, + .remove = lt6911uxc_remove, + .id_table = lt6911uxc_id_table, +}; + +module_i2c_driver(lt6911uxc_i2c_driver); + +MODULE_AUTHOR("Fu Wei "); +MODULE_DESCRIPTION("lt6911uxc HDMI to MIPI Bridge Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c new file mode 100644 index 000000000000..0102a652ff4f --- /dev/null +++ b/drivers/media/i2c/lt6911uxe.c @@ -0,0 +1,1590 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2023 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* v4l2 debug level */ +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define FSERIAL_CLK_4_LANE 240000000ULL +#define FSERIAL_CLK_2_LANE 144000000ULL + +#define PIX_CLK_4_LANE 60000000ULL +#define PIX_CLK_2_LANE 18000000ULL + +// LT6911UXE Register Setting + +#define LT6911UXE_REG_VALUE_08BIT 1 +#define LT6911UXE_REG_VALUE_16BIT 2 +#define LT6911UXE_CHIP_ID 0x2102 + +#define LT6911UXE_CID_CSI_PORT (V4L2_CID_USER_BASE | 0x1001) +#define LT6911UXE_CID_I2C_BUS (V4L2_CID_USER_BASE | 0x1002) +#define LT6911UXE_CID_I2C_ID (V4L2_CID_USER_BASE | 0x1003) +#define LT6911UXE_CID_I2C_SLAVE_ADDRESS (V4L2_CID_USER_BASE | 0x1004) +#define LT6911UXE_CID_FPS (V4L2_CID_USER_BASE | 0x1005) +#define LT6911UXE_CID_FRAME_INTERVAL (V4L2_CID_USER_BASE | 0x1006) +#define LT6911UXE_CID_AUDIO_SAMPLING_RATE (V4L2_CID_USER_BASE | 0x1007) +#define LT6911UXE_CID_AUDIO_PRESENT (V4L2_CID_USER_BASE | 0x1008) + +#define REG_CHIP_ID_H 0xE100 +#define REG_CHIP_ID_L 0xE101 + +/* Control */ +#define REG_BANK 0xFF + +#define REG_ENABLE_I2C 0xE0EE +//#define REG_DISABLE_WD 0x8010 + +/* Resolution registers */ +#define REG_H_TOTAL_H 0xE088 /* horizontal half total pixel */ +#define REG_H_TOTAL_L 0xE089 /* horizontal half total pixel */ + +#define REG_H_ACTIVE_H 0xE08C /* horizontal half active pixel */ +#define REG_H_ACTIVE_L 0xE08D /* horizontal half active pixel */ + +//#define REG_H_FP_0P5 0x8678 /* horizontal half front porch pixel */ +//#define REG_H_BP_0P5 0x8676 /* horizontal half back porch pixel */ +//#define REG_H_SW_0P5 0x8672 /* hsync half length pixel */ + +#define REG_V_TOTAL_H 0xE08A /* vertical total lines */ +#define REG_V_TOTAL_L 0xE08B /* vertical total lines */ + +#define REG_V_ACTIVE_H 0xE08E /* vertical active lines */ +#define REG_V_ACTIVE_L 0xE08F /* vertical active lines */ + +//#define REG_V_BP 0x8674 /* vertical back porch lines */ +//#define REG_V_FP 0x8675 /* vertical front porch lines */ +//#define REG_V_SW 0x8671 /* vsync length lines */ +//#define REG_SYNC_POL 0x8670 /* hsync/vsync polarity flags */ +//#define REG_BKB0_A2_REG 0xB0A2 + +#define REG_FM1_FREQ_IN2 0xE092 +#define REG_FM1_FREQ_IN1 0xE093 +#define REG_FM1_FREQ_IN0 0xE094 + +//#define REG_AD_HALF_PCLK 0x8540 + +//#define REG_TMDS_CLK_IN2 0x8750 +//#define REG_TMDS_CLK_IN1 0x8751 +//#define REG_TMDS_CLK_IN0 0x8752 +#define REG_PIX_CLK_IN2 0xE085 +#define REG_PIX_CLK_IN1 0xE086 +#define REG_PIX_CLK_IN0 0xE087 + +/* MIPI-TX */ +#define REG_MIPI_TX_CTRL 0xE0B0 +#define REG_MIPI_LANES 0xE095 +#define REG_MIPI_FORMAT 0xE096 +//#define REG_MIPI_CLK_MODE 0xE096 + +#define RGB_6_BIT 0x00 +#define RGB_8_BIT 0x01 +#define RGB_10_BIT 0x02 +#define YUV444_8_BIT 0x03 +#define YUV444_10_BIT 0x04 +#define YUV422_8_BIT 0x05 +#define YUV422_10_BIT 0x06 +#define YUV422_12_BIT 0x07 +#define YUV420_8_BIT 0x08 +#define YUV420_10_BIT 0x09 + +/* Audio sample rate */ +#define REG_INT_AUDIO 0x86A5 +//#define AUDIO_DISCONNECT 0x88 +#define AUDIO_SR_HIGH 0x55 +#define AUDIO_SR_LOW 0xAA +//#define REG_AUDIO_SR 0xB0AB +#define REG_AUDIO_FS_H 0xE090 +#define REG_AUDIO_FS_L 0xE091 + +/* Interrupts */ +#define REG_INT_HDMI 0xE084 +#define REG_INT_RESPOND 0x86A6 +#define INT_HDMI_DISCONNECT 0x00 +#define INT_HDMI_STABLE 0x01 + +//#define REG_INT_AUDIO 0x86A5 +#define INT_AUDIO_STABLE 0x02 +#define INT_AUDIO_DISCONNECT 0x03 +//#define INT_AUDIO_SR_HIGH 0x55 +//#define INT_AUDIO_SR_LOW 0xAA + +/* FPS registers */ +#define MASK_FMI_FREQ2 0x0F +#define MASK_VSYNC_POL (1 << 1) +#define MASK_HSYNC_POL (1 << 0) + +/* Frame ID registers */ +#define REG_FRAME_ID 0xD40E +#define REG_FRAME_STATUS 0xD414 + +#define LT6911UXE_IRQ_MODE +#define MAX_MIPI_PORT_USE 3 + +static const struct v4l2_dv_timings_cap lt6911uxe_timings_cap_4kp30 = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + /* Pixel clock from REF_01 p. 20. Min/max height/width are unknown */ + V4L2_INIT_BT_TIMINGS( + 160, 3840, /* min/max width */ + 120, 2160, /* min/max height */ + 25000000, 297000000, /* min/max pixelclock */ + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_CUSTOM | + V4L2_DV_BT_CAP_REDUCED_BLANKING) +}; + +struct lt6911uxe_mode { + /* Frame width in pixels */ + u32 width; + + /* Frame height in pixels */ + u32 height; + + /* Horizontal timining size */ + u32 hts; + + /* Default vertical timining size */ + u32 vts_def; + + /* Min vertical timining size */ + u32 vts_min; + + /* Link frequency needed for this resolution */ + u32 link_freq_index; + + /* MEDIA_BUS_FMT */ + u32 code; + + /* REG_MIPI_LANES */ + s32 lanes; + + /* MODE_FPS*/ + u32 fps; + + /* Bit per pixel */ + u32 bpp; + + /* Pixel rate*/ + s64 pixel_clk; + + /* Byte rate*/ + u32 byte_clk; + + /* Audio sample rate*/ + u32 audio_sample_rate; +}; + +struct lt6911uxe_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *audio_sampling_rate_ctrl; + struct v4l2_ctrl *audio_present_ctrl; + struct v4l2_ctrl *csi_port; + struct v4l2_ctrl *i2c_bus; + struct v4l2_ctrl *i2c_id; + struct v4l2_ctrl *i2c_slave_address; + struct v4l2_ctrl *fps; + struct v4l2_ctrl *frame_interval; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *analogue_gain; + struct v4l2_ctrl *digital_gain; + struct v4l2_ctrl *strobe_source; + struct v4l2_ctrl *strobe; + struct v4l2_ctrl *strobe_stop; + struct v4l2_ctrl *timeout; + struct v4l2_ctrl *hblank; + + struct v4l2_dv_timings timings; + struct v4l2_dv_timings detected_timings; + + struct i2c_client *i2c_client; + + struct media_pad pad; + struct mutex mutex; + struct lt6911uxe_platform_data *platform_data; + + /* Current mode */ + struct lt6911uxe_mode *cur_mode; + + bool streaming; + + u8 bank_i2c; + bool enable_i2c; + + u32 mbus_fmt_code; /* current media bus format */ + + u32 thread_run; + struct task_struct *poll_task; + bool auxiliary_port; + + s64 sub_stream; +}; + +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_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct lt6911uxe_state, sd); +} + +static void lt6911uxe_reg_bank(struct v4l2_subdev *sd, u8 bank) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + struct i2c_client *client = lt6911uxe->i2c_client; + int ret; + struct i2c_msg msg; + u8 data[2]; + u8 address; + + if (lt6911uxe->bank_i2c == bank) + return; + dev_dbg(&client->dev, "i2c: change register bank to 0x%02X\n", + bank); + + address = 0xFF; + msg.addr = client->addr; + msg.buf = data; + msg.len = 2; + msg.flags = 0; + + data[0] = address; + data[1] = bank; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret != 1) { + dev_info(&client->dev, "%s: switch to bank 0x%x from 0x%x failed\n", + __func__, bank, client->addr); + return; + } + lt6911uxe->bank_i2c = bank; +} + +static void lt6911uxe_i2c_wr8(struct v4l2_subdev *sd, u16 reg, u8 val) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + struct i2c_client *client = lt6911uxe->i2c_client; + int ret; + struct i2c_msg msg; + u8 data[2]; + u8 address; + + /* write register bank offset */ + u8 bank = (reg >> 8) & 0xFF; + + lt6911uxe_reg_bank(sd, bank); + address = reg & 0xFF; + msg.addr = client->addr; + msg.buf = data; + msg.len = 2; + msg.flags = 0; + + data[0] = address; + data[1] = val; + + ret = i2c_transfer(client->adapter, &msg, 1); + + if (ret != 1) { + dev_info(&client->dev, "%s: write register 0x%x from 0x%x failed\n", + __func__, reg, client->addr); + } + dev_dbg(&client->dev, "i2c: write register: 0x%04X = 0x%02X\n", + reg, val); +} + +static void lt6911uxe_i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + struct i2c_client *client = lt6911uxe->i2c_client; + int ret; + u8 reg_addr[1] = { (u8)(reg & 0xff) }; + u8 bank_addr = (u8)((reg >> 8) & 0xFF); + + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, /* write */ + .len = 1, + .buf = reg_addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, /* read n bytes */ + .len = n, + .buf = values, + }, + }; + + /* write register bank offset */ + lt6911uxe_reg_bank(sd, bank_addr); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_info(&client->dev, "%s: read register 0x%04X from 0x%x failed\n", + __func__, reg, client->addr); + } +} + +static u8 lt6911uxe_i2c_rd8(struct v4l2_subdev *sd, u16 reg) +{ + u8 val = 0; + + lt6911uxe_i2c_rd(sd, reg, &val, 1); + + dev_dbg(sd->dev, "i2c: read 0x%04X = 0x%02X\n", reg, val); + return val; +} + +static void lt6911uxe_ext_control( + struct lt6911uxe_state *lt6911uxe, + bool enable) +{ + if (!lt6911uxe) + return; + + if (lt6911uxe->enable_i2c == enable) + return; + + lt6911uxe->enable_i2c = enable; + if (enable) { + lt6911uxe_i2c_wr8(<6911uxe->sd, REG_ENABLE_I2C, 0x01); +// lt6911uxe_i2c_wr8(<6911uxe->sd, REG_DISABLE_WD, 0x00); + } else + lt6911uxe_i2c_wr8(<6911uxe->sd, REG_ENABLE_I2C, 0x00); +} + +static int lt6911uxe_csi_enable(struct v4l2_subdev *sd, bool enable) +{ + if (enable) + lt6911uxe_i2c_wr8(sd, REG_MIPI_TX_CTRL, 0x01); + else + lt6911uxe_i2c_wr8(sd, REG_MIPI_TX_CTRL, 0x00); + return 0; +} + +static int lt6911uxe_get_audio_sampling_rate(struct lt6911uxe_state *lt6911uxe) +{ + int audio_fs, idx; + static const int eps = 1500; + static const int rates_default[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + audio_fs = lt6911uxe_i2c_rd8(<6911uxe->sd, REG_AUDIO_FS_H) << 8; + audio_fs |= lt6911uxe_i2c_rd8(<6911uxe->sd, REG_AUDIO_FS_L); + + dev_dbg(<6911uxe->i2c_client->dev, "%s: Audio sample rate %d [Hz]\n", + __func__, audio_fs); + + /* 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]; + } + dev_info(<6911uxe->i2c_client->dev, "%s: unhandled sampling rate %d [Hz]", + __func__, audio_fs); + return 0; +} + +static int lt6911uxe_log_status(struct v4l2_subdev *sd) +{ + +#ifdef TIMINGS_ENABLE + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + v4l2_info(sd, "----- Timings -----\n"); + if (!<6911uxe->detected_timings.bt.width) { + v4l2_info(sd, "no video detected\n"); + } else { + v4l2_print_dv_timings(sd->name, "detected format: ", + <6911uxe->detected_timings, true); + } + v4l2_print_dv_timings(sd->name, "configured format: ", + <6911uxe->timings, true); +#endif + return 0; +} + +static int lt6911uxe_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + dev_info(sd->dev, "%s():\n", __func__); + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + default: + return -EINVAL; + } +} + +static const struct v4l2_dv_timings_cap *lt6911uxe_g_timings_cap( + struct lt6911uxe_state *lt6911uxe) +{ + return <6911uxe_timings_cap_4kp30; +} + +static int lt6911uxe_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + *status = 0; + *status |= lt6911uxe->streaming ? V4L2_IN_ST_NO_SIGNAL : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status); + return 0; +} + +static int __maybe_unused lt6911uxe_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + if (!v4l2_valid_dv_timings(timings, lt6911uxe_g_timings_cap(lt6911uxe), + NULL, NULL)) { + v4l2_err(sd, "%s: timings out of range\n", __func__); + return -EINVAL; + } + + v4l2_find_dv_timings_cap(timings, lt6911uxe_g_timings_cap(lt6911uxe), 0, + NULL, NULL); + memset(timings->bt.reserved, 0, sizeof(timings->bt.reserved)); + lt6911uxe->timings = *timings; + return 0; +} + +static int lt6911uxe_g_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s():\n", __func__); + *timings = lt6911uxe->timings; + return 0; +} + +static int __maybe_unused lt6911uxe_query_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *timings) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + v4l2_dbg(3, debug, sd, "%s():\n", __func__); + if (false == lt6911uxe->streaming) { + v4l2_warn(sd, "%s: no valid signal\n", __func__); + return -ENOLINK; + } + + if (!v4l2_valid_dv_timings(<6911uxe->detected_timings, + lt6911uxe_g_timings_cap(lt6911uxe), NULL, NULL)) { + v4l2_warn(sd, "%s: timings out of range\n", __func__); + return -ERANGE; + } + + *timings = lt6911uxe->detected_timings; + return 0; +} + +static const struct v4l2_ctrl_config lt6911uxe_ctrl_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_ctrl_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 __maybe_unused get_hblank(struct lt6911uxe_state *lt6911uxe) +{ + u64 hblank; + u64 pixel_rate; + u64 pixel_clk; + + if (lt6911uxe->cur_mode->lanes == 4) { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } else if (lt6911uxe->cur_mode->lanes == 2) { + pixel_rate = FSERIAL_CLK_2_LANE; + pixel_clk = PIX_CLK_2_LANE; + } else { + pixel_rate = FSERIAL_CLK_4_LANE; + pixel_clk = PIX_CLK_4_LANE; + } + + if (pixel_clk) + hblank = 0x128 * (pixel_rate / pixel_clk); + else + hblank = 1184; + + return hblank; +} + +static int lt6911uxe_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct lt6911uxe_state *lt6911uxe = container_of(ctrl->handler, + struct lt6911uxe_state, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + s64 exposure_max; + int ret = 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 = 1; + __v4l2_ctrl_modify_range(lt6911uxe->exposure, + lt6911uxe->exposure->minimum, + exposure_max, + lt6911uxe->exposure->step, + lt6911uxe->cur_mode->height - 1); + } + + /* 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: + dev_dbg(&client->dev, "set analogue gain.\n"); + break; + + case V4L2_CID_DIGITAL_GAIN: + dev_dbg(&client->dev, "set digital gain.\n"); + break; + + case V4L2_CID_EXPOSURE: + dev_dbg(&client->dev, "set exposure time.\n"); + break; + + case V4L2_CID_VBLANK: + dev_dbg(&client->dev, "set vblank %d\n", + lt6911uxe->cur_mode->height + ctrl->val); + break; + case V4L2_CID_FLASH_STROBE_SOURCE: + dev_dbg(&client->dev, "set led flash source %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_STROBE: + dev_dbg(&client->dev, "set flash strobe.\n"); + break; + + case V4L2_CID_FLASH_STROBE_STOP: + dev_dbg(&client->dev, "turn off led %d\n", ctrl->val); + break; + + case V4L2_CID_FLASH_TIMEOUT: + dev_dbg(&client->dev, "set led delay\n"); + break; + + default: + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops lt6911uxe_ctrl_ops = { + .s_ctrl = lt6911uxe_set_ctrl, +}; + +static struct v4l2_ctrl_config lt6911uxe_csi_port = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_CSI_PORT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "CSI port", + .min = 0, + .max = 5, + .def = 1, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxe_i2c_bus = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_I2C_BUS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C bus", + .min = 0, + .max = MINORMASK, + .def = 0, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxe_i2c_id = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_I2C_ID, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C id", + .min = 0x10, + .max = 0x77, + .def = 0x10, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxe_i2c_slave_address = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_I2C_SLAVE_ADDRESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "I2C slave address", + .min = 0x0, + .max = 0x7f, + .def = 0x2b, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxe_fps = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_FPS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "fps", + .min = 25, + .max = 60, + .def = 30, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static struct v4l2_ctrl_config lt6911uxe_frame_interval = { + .ops = <6911uxe_ctrl_ops, + .id = LT6911UXE_CID_FRAME_INTERVAL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "frame interval", + .min = 16, + .max = 40, + .def = 33, + .step = 1, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static u64 get_pixel_rate(struct lt6911uxe_state *lt6911uxe) +{ + u64 pixel_rate = 995328000ULL; /* default value: 4K@30 */ + + if (lt6911uxe->cur_mode->lanes) { + 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_init_controls(struct lt6911uxe_state *lt6911uxe) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 hblank; + int ret; + + ctrl_hdlr = <6911uxe->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); + if (ret) + return ret; + + ctrl_hdlr->lock = <6911uxe->mutex; + lt6911uxe->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_LINK_FREQ, + sizeof(lt6911uxe->cur_mode->pixel_clk), + 0, <6911uxe->cur_mode->pixel_clk); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set link_freq ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxe->link_freq) + lt6911uxe->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + lt6911uxe->vblank = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_VBLANK, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set vblank ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe->analogue_gain = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set analogue_gain ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe->digital_gain = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set digital_gain ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe->exposure = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_EXPOSURE, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set exposure ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe_csi_port.def = lt6911uxe->platform_data->port; + lt6911uxe->csi_port = + v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxe_csi_port, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + lt6911uxe_i2c_bus.def = i2c_adapter_id(client->adapter); + lt6911uxe->i2c_bus = + v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxe_i2c_bus, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_bus ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe_i2c_id.def = client->addr; + lt6911uxe->i2c_id = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxe_i2c_id, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_id ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe_i2c_slave_address.def = + lt6911uxe->platform_data->i2c_slave_address; + lt6911uxe->i2c_slave_address = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxe_i2c_slave_address, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set i2c_slave_address ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe_fps.def = lt6911uxe->cur_mode->fps; + lt6911uxe->fps = v4l2_ctrl_new_custom(ctrl_hdlr, <6911uxe_fps, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set fps ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + if (lt6911uxe->cur_mode->fps) + lt6911uxe_frame_interval.def = 1000 / lt6911uxe->cur_mode->fps; + else + lt6911uxe_frame_interval.def = 33; + + lt6911uxe->frame_interval = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxe_frame_interval, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set frame_interval ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_PIXEL_RATE, + get_pixel_rate(lt6911uxe), + get_pixel_rate(lt6911uxe), 1, + get_pixel_rate(lt6911uxe)); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set pixel_rate ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxe->pixel_rate) + lt6911uxe->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + hblank = 1; + lt6911uxe->hblank = v4l2_ctrl_new_std(ctrl_hdlr, + <6911uxe_ctrl_ops, + V4L2_CID_HBLANK, 0, 1, 1, 1); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set hblank ctrl_hdlr, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + if (lt6911uxe->hblank) + lt6911uxe->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + /* custom v4l2 audio controls */ + lt6911uxe->audio_sampling_rate_ctrl = v4l2_ctrl_new_custom( + ctrl_hdlr, <6911uxe_ctrl_audio_sampling_rate, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set audio sampling rate ctrl, err=%d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + lt6911uxe->audio_present_ctrl = v4l2_ctrl_new_custom(ctrl_hdlr, + <6911uxe_ctrl_audio_present, NULL); + if (ctrl_hdlr->error) { + dev_dbg(&client->dev, "Set audio present ctrl, error = %d.\n", + ctrl_hdlr->error); + return ctrl_hdlr->error; + } + + lt6911uxe->sd.ctrl_handler = ctrl_hdlr; + return 0; +} + +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_state *lt6911uxe) +{ + int ret = 0; + + if (lt6911uxe->auxiliary_port == true) + return 0; + + lt6911uxe_ext_control(lt6911uxe, true); + lt6911uxe_csi_enable(<6911uxe->sd, true); + lt6911uxe_ext_control(lt6911uxe, false); + + ret = __v4l2_ctrl_handler_setup(lt6911uxe->sd.ctrl_handler); + if (ret) + return ret; + + return 0; +} + +static void lt6911uxe_stop_streaming(struct lt6911uxe_state *lt6911uxe) +{ + if (lt6911uxe->auxiliary_port == true) + return; + + /*The fps of 1080p60fps will be dropped to half when the CSI disabled. */ + lt6911uxe_ext_control(lt6911uxe, true); + lt6911uxe_csi_enable(<6911uxe->sd, false); + lt6911uxe_ext_control(lt6911uxe, false); +} + +static int lt6911uxe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + int ret = 0; + + if (lt6911uxe->streaming == enable) + return 0; + + if (lt6911uxe->auxiliary_port == true) + return 0; + + mutex_lock(<6911uxe->mutex); + if (enable) { + dev_dbg(sd->dev, "[%s()], start streaming.\n", __func__); + ret = lt6911uxe_start_streaming(lt6911uxe); + if (ret) { + enable = 0; + lt6911uxe_stop_streaming(lt6911uxe); + } + } else { + dev_dbg(sd->dev, "[%s()], stop streaming.\n", __func__); + lt6911uxe_stop_streaming(lt6911uxe); + } + mutex_unlock(<6911uxe->mutex); + + lt6911uxe->streaming = enable; + + return ret; +} + +static int lt6911uxe_g_frame_interval(struct v4l2_subdev *sd, + struct v4l2_subdev_frame_interval *fival) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + fival->pad = 0; + fival->interval.numerator = 1; + fival->interval.denominator = lt6911uxe->cur_mode->fps; + + return 0; +} + +static int lt6911uxe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + s32 vblank_def; + s64 hblank; + + mutex_lock(<6911uxe->mutex); + lt6911uxe_update_pad_format(lt6911uxe->cur_mode, &fmt->format); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; + } else { + __v4l2_ctrl_s_ctrl(lt6911uxe->link_freq, + lt6911uxe->cur_mode->link_freq_index); + __v4l2_ctrl_modify_range(lt6911uxe->pixel_rate, + 25000000, 297000000, 1, + get_pixel_rate(lt6911uxe)); + + hblank = get_hblank(lt6911uxe); + __v4l2_ctrl_modify_range(lt6911uxe->hblank, + hblank, + hblank, + 1, + hblank); + + /* Update limits and set FPS to default */ + vblank_def = lt6911uxe->cur_mode->vts_def - lt6911uxe->cur_mode->height; + __v4l2_ctrl_modify_range(lt6911uxe->vblank, + 0, + 0xffff - lt6911uxe->cur_mode->height, 1, + vblank_def); + __v4l2_ctrl_s_ctrl(lt6911uxe->vblank, vblank_def); + + __v4l2_ctrl_s_ctrl(lt6911uxe->fps, lt6911uxe->cur_mode->fps); + + if (lt6911uxe->cur_mode->fps) + __v4l2_ctrl_s_ctrl(lt6911uxe->frame_interval, 1000 / lt6911uxe->cur_mode->fps); + else + __v4l2_ctrl_s_ctrl(lt6911uxe->frame_interval, 33); + } + + mutex_unlock(<6911uxe->mutex); + + return 0; +} + +static int lt6911uxe_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + mutex_lock(<6911uxe->mutex); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + fmt->format = *v4l2_subdev_get_try_format(<6911uxe->sd, sd_state, + fmt->pad); + else + lt6911uxe_update_pad_format(lt6911uxe->cur_mode, &fmt->format); + + mutex_unlock(<6911uxe->mutex); + + 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_state *lt6911uxe = to_state(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_state *lt6911uxe = to_state(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_state *lt6911uxe = to_state(sd); + + fie->interval.numerator = 1; + fie->interval.denominator = lt6911uxe->cur_mode->fps; + + return 0; +} + +static int lt6911uxe_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + lt6911uxe_update_pad_format(lt6911uxe->cur_mode, + v4l2_subdev_get_try_format(sd, fh->state, 0)); + + return 0; +} + +static const struct v4l2_subdev_internal_ops lt6911uxe_subdev_internal_ops = { + .open = lt6911uxe_open, +}; + +static const struct v4l2_subdev_video_ops lt6911uxe_video_ops = { + .s_stream = lt6911uxe_set_stream, + .g_frame_interval = lt6911uxe_g_frame_interval, + .g_input_status = lt6911uxe_g_input_status, +// .s_dv_timings = lt6911uxe_s_dv_timings, + .g_dv_timings = lt6911uxe_g_dv_timings, +// .query_dv_timings = lt6911uxe_query_dv_timings, + .s_stream = lt6911uxe_set_stream, +}; + +static const struct v4l2_subdev_pad_ops lt6911uxe_pad_ops = { + .set_fmt = lt6911uxe_set_format, + .get_fmt = lt6911uxe_get_format, + .enum_mbus_code = lt6911uxe_enum_mbus_code, + .enum_frame_size = lt6911uxe_enum_frame_size, + .enum_frame_interval = lt6911uxe_enum_frame_interval, +}; + +static struct v4l2_subdev_core_ops lt6911uxe_subdev_core_ops = { + .log_status = lt6911uxe_log_status, + .subscribe_event = lt6911uxe_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 = { + .open = lt6911uxe_open, +}; + +static int lt6911uxe_identify_module(struct lt6911uxe_state *lt6911uxe) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + u16 val; + + val = lt6911uxe_i2c_rd8(<6911uxe->sd, REG_CHIP_ID_H) << 8; + val |= lt6911uxe_i2c_rd8(<6911uxe->sd, REG_CHIP_ID_L); + + if (val != LT6911UXE_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x", + LT6911UXE_CHIP_ID, val); + return -ENXIO; + } + dev_info(&client->dev, + "Found lt6911uxe Bridge Chip, ID is 0x%x\n", val); + return 0; +} + +static void lt6911uxe_remove(struct i2c_client *client) +{ +} + +static int lt6911uxe_video_status_update(struct lt6911uxe_state *lt6911uxe) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + u16 val = 0; + u8 int_event = 0; + u32 byte_clock = 0, pixel_clk = 0; + u32 htotal = 0, vtotal = 0; + u32 width = 0, height = 0; + u32 width2 = 0, format = 0; + u32 fps = 0, lanes = 4; + u8 fm0 = 0, fm1 = 0, fm2 = 0; + + /* Read interrupt event */ + int_event = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_INT_HDMI); + switch (int_event) { + case INT_HDMI_STABLE: + dev_info(&client->dev, "Video signal stable\n"); + + /* byte clock / MIPI clock */ + fm2 = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_FM1_FREQ_IN2);// & MASK_FMI_FREQ2; + fm1 = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_FM1_FREQ_IN1); + fm0 = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_FM1_FREQ_IN0); + + byte_clock = (fm2<<16 | fm1<<8 | fm0) * 1000; + + /* Pixel clock */ + val = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_PIX_CLK_IN2); + pixel_clk |= val << 16; + val = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_PIX_CLK_IN1); + pixel_clk |= (val << 8); + val = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_PIX_CLK_IN0); + pixel_clk |= val; + pixel_clk *= 1000; + + htotal = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_H_TOTAL_H) << 8; + htotal |= lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_H_TOTAL_L); + + /* video frame size */ + vtotal = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_V_TOTAL_H) << 8; + vtotal |= lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_V_TOTAL_L); + + width = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_H_ACTIVE_H) << 8; + + width2 = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_H_ACTIVE_L); + + width |= width2; + + width = width << 1; + + height = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_V_ACTIVE_H) << 8; + height |= lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_V_ACTIVE_L); + + lanes = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_MIPI_LANES); + + format = lt6911uxe_i2c_rd8(<6911uxe->sd, + REG_MIPI_FORMAT); + + if (htotal && vtotal) + fps = pixel_clk / (htotal * vtotal); + + lt6911uxe->cur_mode->height = height; + lt6911uxe->cur_mode->fps = fps; + lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; + if (lanes == 8) { + dev_dbg(&client->dev, "Lane number:%u is unexpected.\n", lanes); + /* 4K60fps with 2 MIPI ports*/ + if (width >= 3840) + lt6911uxe->cur_mode->width = width / 2; /* YUV422 */ + else + lt6911uxe->cur_mode->width = width; /* YUV422 */ + lt6911uxe->cur_mode->lanes = lanes / 2; + lt6911uxe->cur_mode->pixel_clk = byte_clock * 4; + lt6911uxe->cur_mode->byte_clk = byte_clock; + } else { + lt6911uxe->cur_mode->width = width; + lt6911uxe->cur_mode->lanes = lanes; + lt6911uxe->cur_mode->pixel_clk = byte_clock * 4; + lt6911uxe->cur_mode->byte_clk = byte_clock; + } + v4l2_subdev_notify_event(<6911uxe->sd, + <6911uxe_ev_source_change); + + dev_dbg(&client->dev, "Pixel Clk:%u, %lld\n", + pixel_clk, lt6911uxe->cur_mode->pixel_clk); + dev_dbg(&client->dev, + "width:%u, height:%u, fps:%u, lanes: %d\n", + lt6911uxe->cur_mode->width, + lt6911uxe->cur_mode->height, + lt6911uxe->cur_mode->fps, + lt6911uxe->cur_mode->lanes); + break; + case INT_HDMI_DISCONNECT: + lt6911uxe_stop_streaming(lt6911uxe); + lt6911uxe->cur_mode->width = 0; + lt6911uxe->cur_mode->height = 0; + lt6911uxe->cur_mode->fps = 30; + lt6911uxe->cur_mode->pixel_clk = 0; + lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16; + lt6911uxe->cur_mode->byte_clk = 0; + lt6911uxe->cur_mode->lanes = 4; + v4l2_subdev_notify_event(<6911uxe->sd, + <6911uxe_ev_stream_end); + + dev_info(&client->dev, "Video signal disconnected\n"); + break; + default: + dev_dbg(&client->dev, "Unhandled video= 0x%02X\n", int_event); + return -ENOLINK; + } + + return 0; +} + +static void lt6911uxe_audio_status_update(struct lt6911uxe_state *lt6911uxe) +{ + struct i2c_client *client = v4l2_get_subdevdata(<6911uxe->sd); + u8 int_event; + int audio_fs = 0; + + /* read interrupt event */ + int_event = lt6911uxe_i2c_rd8(<6911uxe->sd, REG_INT_AUDIO); + dev_dbg(&client->dev, "Audio event:0x%02X\n", int_event); + + switch (int_event) { + case INT_AUDIO_DISCONNECT: + dev_dbg(&client->dev, "Audio signal disconnected\n"); + audio_fs = 0; + lt6911uxe->cur_mode->audio_sample_rate = 0; + break; + + case AUDIO_SR_HIGH: + case AUDIO_SR_LOW: + audio_fs = lt6911uxe_get_audio_sampling_rate(lt6911uxe); + lt6911uxe->cur_mode->audio_sample_rate = audio_fs; + dev_dbg(&client->dev, + "Sampling rate changed: %d\n", audio_fs); + break; + default: + dev_dbg(&client->dev, "Unhandled audio= 0x%02X\n", int_event); + // use the default value for avoiding problem + lt6911uxe->cur_mode->audio_sample_rate = 0; + break; + } + + __v4l2_ctrl_s_ctrl(lt6911uxe->audio_present_ctrl, + (lt6911uxe->cur_mode->audio_sample_rate != 0)); + + if (lt6911uxe->cur_mode->audio_sample_rate) + __v4l2_ctrl_s_ctrl(lt6911uxe->audio_sampling_rate_ctrl, + lt6911uxe->cur_mode->audio_sample_rate); +} + +static void lt6911uxe_check_status(struct lt6911uxe_state *lt6911uxe) +{ + mutex_lock(<6911uxe->mutex); + lt6911uxe_ext_control(lt6911uxe, true); + lt6911uxe_video_status_update(lt6911uxe); + lt6911uxe_audio_status_update(lt6911uxe); + lt6911uxe_i2c_wr8(<6911uxe->sd, REG_INT_RESPOND, 1); + lt6911uxe_ext_control(lt6911uxe, false); + mutex_unlock(<6911uxe->mutex); +} + +static irqreturn_t lt6911uxe_threaded_irq_fn(int irq, void *dev_id) +{ + struct v4l2_subdev *sd = dev_id; + struct lt6911uxe_state *lt6911uxe; + + if (!sd) { + dev_err(NULL, "Invalid dev_id argument!\n"); + return IRQ_NONE; + } + + lt6911uxe = to_state(sd); + if (!lt6911uxe) { + dev_err(sd->dev, "Invalid lt6911uxe state argument!\n"); + return IRQ_NONE; + } + + dev_dbg(sd->dev, "%s in kthread %d\n", __func__, current->pid); + lt6911uxe_check_status(lt6911uxe); + return IRQ_HANDLED; +} + +static int lt6911uxe_probe(struct i2c_client *client) +{ + struct lt6911uxe_state *lt6911uxe; + struct v4l2_subdev *sd; + int ret; + + lt6911uxe = devm_kzalloc(&client->dev, sizeof(struct lt6911uxe_state), + 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; + + memset(lt6911uxe->cur_mode, 0, sizeof(struct lt6911uxe_mode)); + lt6911uxe->cur_mode->width = 3840, + lt6911uxe->cur_mode->height = 2160, + lt6911uxe->cur_mode->code = MEDIA_BUS_FMT_UYVY8_1X16, + lt6911uxe->cur_mode->lanes = 4, + lt6911uxe->cur_mode->fps = 30, + lt6911uxe->cur_mode->bpp = 8, + lt6911uxe->cur_mode->pixel_clk = 297000000, + lt6911uxe->cur_mode->byte_clk = 168000000, + lt6911uxe->cur_mode->audio_sample_rate = 48000, + + lt6911uxe->platform_data = client->dev.platform_data; + if (lt6911uxe->platform_data == NULL) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + + lt6911uxe->i2c_client = client; + sd = <6911uxe->sd; + v4l2_i2c_subdev_init(sd, client, <6911uxe_subdev_ops); + + if (lt6911uxe->platform_data->suffix) + snprintf(lt6911uxe->sd.name, + sizeof(lt6911uxe->sd.name), "lt6911uxe %c", + lt6911uxe->platform_data->suffix); + + mutex_init(<6911uxe->mutex); + if (-1 != lt6911uxe->platform_data->reset_pin) + if (!gpio_get_value(lt6911uxe->platform_data->reset_pin)) + gpio_set_value(lt6911uxe->platform_data->reset_pin, 1); + + msleep(50); + + if (-1 != lt6911uxe->platform_data->irq_pin) { + lt6911uxe->auxiliary_port = false; + dev_info(&client->dev, "Probing lt6911uxe chip...\n"); + + lt6911uxe_ext_control(lt6911uxe, true); + ret = lt6911uxe_identify_module(lt6911uxe); + if (ret) { + dev_err(&client->dev, "failed to find chip: %d", ret); + return ret; + } + lt6911uxe_ext_control(lt6911uxe, false); + + 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.internal_ops = <6911uxe_internal_ops; + lt6911uxe->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + 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; + } + + /* Setting irq */ + ret = devm_gpio_request_one(&client->dev, + lt6911uxe->platform_data->irq_pin, + GPIOF_OUT_INIT_HIGH, "Interrupt signal"); + if (ret) { + dev_err(&client->dev, "IRQ pin %d (name: %s) request failed! ret: %d\n", + lt6911uxe->platform_data->irq_pin, + lt6911uxe->platform_data->irq_pin_name, ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = gpio_direction_input(lt6911uxe->platform_data->irq_pin); + if (ret) { + dev_err(&client->dev, "Set gpio pin %d direction input failed! ret: %d\n", + lt6911uxe->platform_data->irq_pin, ret); + goto probe_error_v4l2_ctrl_handler_free; + } + + ret = devm_request_threaded_irq(&client->dev, + gpio_to_irq(lt6911uxe->platform_data->irq_pin), + NULL, lt6911uxe_threaded_irq_fn, + lt6911uxe->platform_data->irq_pin_flags, + lt6911uxe->platform_data->irq_pin_name, lt6911uxe); + if (ret) { + dev_err(&client->dev, "IRQ request failed! ret: %d\n", ret); + goto probe_error_v4l2_ctrl_handler_free; + } + /* Check the current status */ + usleep_range(200000, 205000); + lt6911uxe_check_status(lt6911uxe); + /* Stop to transmit MIPI data firstly waiting for IPU ready */ + lt6911uxe_stop_streaming(lt6911uxe); + } else { + /* 4K60fps mode, the setting needs to be fixed on 1920x2160@60fps*/ + lt6911uxe->auxiliary_port = true; + lt6911uxe->cur_mode->width = 1920, + lt6911uxe->cur_mode->height = 2160, + lt6911uxe->cur_mode->fps = 60, + + 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.internal_ops = <6911uxe_internal_ops; + lt6911uxe->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + 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; + } + } + + lt6911uxe->sd.dev = &client->dev; + lt6911uxe->sd.internal_ops = <6911uxe_subdev_internal_ops; + lt6911uxe->sd.flags |= + V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + ret = v4l2_async_register_subdev_sensor(<6911uxe->sd); + if (ret < 0) { + dev_err(&client->dev, "failed to register V4L2 subdev: %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); + dev_info(&client->dev, "End to probe lt6911uxe Bridge Chip.\n"); + dev_info(&client->dev, "%s Probe Succeeded", lt6911uxe->sd.name); + return 0; + +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); + mutex_destroy(<6911uxe->mutex); + dev_err(&client->dev, "%s Probe Failed", lt6911uxe->sd.name); + + return ret; +} + +static int lt6911uxe_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct lt6911uxe_state *lt6911uxe = to_state(sd); + + if (-1 != lt6911uxe->platform_data->reset_pin) + if (gpio_get_value(lt6911uxe->platform_data->reset_pin)) + gpio_set_value(lt6911uxe->platform_data->reset_pin, 0); + + mutex_lock(<6911uxe->mutex); + if (lt6911uxe->streaming) + lt6911uxe_stop_streaming(lt6911uxe); + + mutex_unlock(<6911uxe->mutex); + dev_dbg(sd->dev, "suspend streaming...\n"); + return 0; +} + +static int lt6911uxe_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct lt6911uxe_state *lt6911uxe = to_state(sd); + int ret; + + if (-1 != lt6911uxe->platform_data->reset_pin) + if (!gpio_get_value(lt6911uxe->platform_data->reset_pin)) + gpio_set_value(lt6911uxe->platform_data->reset_pin, 1); + + usleep_range(200000, 205000); + //recheck the current HDMI status in case changed + lt6911uxe_check_status(lt6911uxe); + + mutex_lock(<6911uxe->mutex); + if (lt6911uxe->streaming) { + ret = lt6911uxe_start_streaming(lt6911uxe); + if (ret) { + lt6911uxe->streaming = false; + lt6911uxe_stop_streaming(lt6911uxe); + mutex_unlock(<6911uxe->mutex); + return ret; + } + } + mutex_unlock(<6911uxe->mutex); + dev_dbg(sd->dev, "resume streaming...\n"); + return 0; +} + +static const struct dev_pm_ops lt6911uxe_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lt6911uxe_suspend, lt6911uxe_resume) +}; + +static const struct i2c_device_id lt6911uxe_id_table[] = { + { "lt6911uxe", 0 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i2c, lt6911uxe_id_table); + +static struct i2c_driver lt6911uxe_i2c_driver = { + .driver = { + .name = "lt6911uxe", + .pm = <6911uxe_pm_ops, + }, + .probe_new = lt6911uxe_probe, + .remove = lt6911uxe_remove, + .id_table = lt6911uxe_id_table, +}; + +module_i2c_driver(lt6911uxe_i2c_driver); + +MODULE_AUTHOR("Fu Wei "); +MODULE_DESCRIPTION("lt6911uxe HDMI to MIPI Bridge Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/i2c/ti953-ser.c b/drivers/media/i2c/ti953-ser.c new file mode 100644 index 000000000000..b9686b900382 --- /dev/null +++ b/drivers/media/i2c/ti953-ser.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2020 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ti960-reg.h" +#include "ti953.h" + +int ti953_reg_write(struct v4l2_subdev *sd, unsigned short rx_port, + unsigned short ser_alias, unsigned char reg, unsigned char val) +{ + int ret; + int retry, timeout = 10; + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned short addr_backup; + + dev_dbg(sd->dev, "%s port %d, ser_alias %x, reg %x, val %x", + __func__, rx_port, ser_alias, reg, val); + addr_backup = client->addr; + client->addr = ser_alias; + for (retry = 0; retry < timeout; retry++) { + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) + usleep_range(5000, 6000); + else + break; + } + + client->addr = addr_backup; + if (retry >= timeout) { + dev_err(sd->dev, + "%s:write reg failed: port=%2x, addr=%2x, reg=%2x\n", + __func__, rx_port, ser_alias, reg); + return -EREMOTEIO; + } + + return 0; +} + +int ti953_reg_read(struct v4l2_subdev *sd, unsigned short rx_port, + unsigned short ser_alias, unsigned char reg, unsigned char *val) +{ + int ret, retry, timeout = 10; + struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned short addr_backup; + + addr_backup = client->addr; + client->addr = ser_alias; + for (retry = 0; retry < timeout; retry++) { + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + usleep_range(5000, 6000); + else { + *val = ret & 0xFF; + break; + } + } + + client->addr = addr_backup; + if (retry >= timeout) { + dev_err(sd->dev, + "%s:read reg failed: port=%2x, addr=%2x, reg=%2x\n", + __func__, rx_port, ser_alias, reg); + return -EREMOTEIO; + } + + return 0; +} + +bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +{ + bool ret = false; + int i; + int rval; + unsigned char val; + + for (i = 0; i < ARRAY_SIZE(ti953_FPD3_RX_ID); i++) { + rval = ti953_reg_read(sd, rx_port, ser_alias, + ti953_FPD3_RX_ID[i].reg, &val); + if (rval) { + dev_err(sd->dev, "port %d, ti953 write timeout %d\n", rx_port, rval); + break; + } + if (val != ti953_FPD3_RX_ID[i].val_expected) + break; + } + + if (i == ARRAY_SIZE(ti953_FPD3_RX_ID)) + ret = true; + else + dev_err(sd->dev, "TI953 Probe Failed"); + + return ret; +} + +int ti953_init(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +{ + int i, rval; + + for (i = 0; i < ARRAY_SIZE(ti953_init_settings); i++) { + rval = ti953_reg_write(sd, rx_port, ser_alias, + ti953_init_settings[i].reg, + ti953_init_settings[i].val); + if (rval) { + dev_err(sd->dev, "port %d, ti953 write timeout %d\n", 0, rval); + break; + } + } + + ti953_init_clk(sd, rx_port, ser_alias); + + return 0; +} + +int ti953_init_clk(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +{ + int i, rval; + + for (i = 0; i < ARRAY_SIZE(ti953_init_settings_clk); i++) { + rval = ti953_reg_write(sd, rx_port, ser_alias, + ti953_init_settings_clk[i].reg, + ti953_init_settings_clk[i].val); + if (rval) { + dev_err(sd->dev, "port %d, ti953 write timeout %d\n", 0, rval); + break; + } + } + + return 0; +} + +int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_alias, uint8_t i2c_speed) +{ + struct ti953_register_write scl_high_reg; + struct ti953_register_write scl_low_reg; + int32_t ret = 0; + + scl_high_reg.reg = TI953_SCL_HIGH_TIME; + scl_low_reg.reg = TI953_SCL_LOW_TIME; + switch (i2c_speed) { + case TI953_I2C_SPEED_STANDARD: + scl_high_reg.val = TI953_I2C_SCL_HIGH_TIME_STANDARD; + scl_low_reg.val = TI953_I2C_SCL_LOW_TIME_STANDARD; + break; + case TI953_I2C_SPEED_FAST: + scl_high_reg.val = TI953_I2C_SCL_HIGH_TIME_FAST; + scl_low_reg.val = TI953_I2C_SCL_LOW_TIME_FAST; + break; + case TI953_I2C_SPEED_FAST_PLUS: + scl_high_reg.val = TI953_I2C_SCL_HIGH_TIME_FAST_PLUS; + scl_low_reg.val = TI953_I2C_SCL_LOW_TIME_FAST_PLUS; + break; + case TI953_I2C_SPEED_HIGH: + default: + dev_err(sd->dev, "port %u, ti953 unsupported I2C speed mode %u", + rx_port, i2c_speed); + scl_high_reg.val = TI953_I2C_SCL_HIGH_TIME_STANDARD; + scl_low_reg.val = TI953_I2C_SCL_LOW_TIME_STANDARD; + ret = -EINVAL; + break; + } + if (ret != 0) + return ret; + ret = ti953_reg_write(sd, rx_port, ser_alias, + scl_high_reg.reg, scl_high_reg.val); + if (ret != 0) { + dev_err(sd->dev, "port %u, ti953 write SCL_HIGH_TIME failed %d", + rx_port, ret); + return ret; + } + ret = ti953_reg_write(sd, rx_port, ser_alias, + scl_low_reg.reg, scl_low_reg.val); + if (ret != 0) { + dev_err(sd->dev, "port %u, ti953 write SCL_LOW_TIME failed %d", + rx_port, ret); + return ret; + } + + return 0; +} diff --git a/drivers/media/i2c/ti953.h b/drivers/media/i2c/ti953.h new file mode 100644 index 000000000000..da47bfaf23b3 --- /dev/null +++ b/drivers/media/i2c/ti953.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 Intel Corporation */ + +#ifndef TI953_H +#define TI953_H + +struct ti953_register_write { + u8 reg; + u8 val; +}; + +struct ti953_register_devid { + u8 reg; + u8 val_expected; +}; + +/* register definition */ +#define TI953_RESET_CTL 0x1 +#define TI953_GENERAL_CFG 0x2 +#define TI953_LOCAL_GPIO_DATA 0xd +#define TI953_GPIO_INPUT_CTRL 0xe +#define TI953_SCL_HIGH_TIME 0xbU +#define TI953_SCL_LOW_TIME 0xcU + +/* register value definition */ +#define TI953_DIGITAL_RESET_1 0x2 +#define TI953_GPIO0_RMTEN 0x10 +#define TI953_GPIO0_OUT 0x1 +#define TI953_GPIO1_OUT (0x1 << 1) +#define TI953_GPIO_OUT_EN 0xf0 +#define TI953_I2C_SCL_HIGH_TIME_STANDARD 0x7F +#define TI953_I2C_SCL_LOW_TIME_STANDARD 0x7F +#define TI953_I2C_SCL_HIGH_TIME_FAST 0x13 +#define TI953_I2C_SCL_LOW_TIME_FAST 0x26 +#define TI953_I2C_SCL_HIGH_TIME_FAST_PLUS 0x06 +#define TI953_I2C_SCL_LOW_TIME_FAST_PLUS 0x0b + +#define TI953_I2C_SPEED_STANDARD 0x1U +#define TI953_I2C_SPEED_FAST 0x2U +#define TI953_I2C_SPEED_HIGH 0x3U +#define TI953_I2C_SPEED_FAST_PLUS 0x4U + +/* + * TI953_GENERAL_CFG: + * bit 0: I2C Strap Mode: I2C Voltage level + * bit 1: CRC_TX_GEN_ENABLE: Transmitter CRC Generator + * bit 4 - 5: + * CSI-2 Data lane configuration. + * 00: 1-lane configuration + * 01: 2-lane configuration + * 11: 4-lane configuration + * bit 6: + * CONTS_CLK: + * CSI-2 Clock Lane Configuration. + * 0 : Non Continuous Clock + * 1 : Continuous Clock + */ +#define TI953_CONTS_CLK 0x40 +#define TI953_CSI_1LANE 0x00 +#define TI953_CSI_2LANE 0x10 +#define TI953_CSI_4LANE 0x30 +#define TI953_CSI_LANE_MASK ~(0x30) +#define TI953_CRC_TX_GEN_ENABLE 0x2 +#define TI953_I2C_STRAP_MODE 0x1 + +static const struct ti953_register_write ti953_init_settings[] = { + {0x4c, 0x01}, /* ox03a10 init sequence */ + {0xb0, 0x04}, + {0xb1, 0x03}, + {0xb2, 0x25}, + {0xb1, 0x13}, + {0xb2, 0x25}, + {0xb0, 0x04}, + {0xb1, 0x04}, + {0xb2, 0x30}, + {0xb1, 0x14}, + {0xb2, 0x30}, + {0xb0, 0x04}, + {0xb1, 0x06}, + {0xb2, 0x40}, + {0x42, 0x01}, + {0x41, 0x93}, + {0x4c, 0x12}, + {0xb0, 0x08}, + {0xb1, 0x03}, + {0xb2, 0x25}, + {0xb1, 0x13}, + {0xb2, 0x25}, + {0xb0, 0x08}, + {0xb1, 0x04}, + {0xb2, 0x30}, + {0xb1, 0x14}, + {0xb2, 0x30}, + {0xb0, 0x08}, + {0xb1, 0x06}, + {0xb2, 0x40}, + {0x42, 0x01}, + {0x41, 0x93}, + {0x4c, 0x24}, + {0xb0, 0x0c}, + {0xb1, 0x03}, + {0xb2, 0x25}, + {0xb1, 0x13}, + {0xb2, 0x25}, + {0xb0, 0x0c}, + {0xb1, 0x04}, + {0xb2, 0x30}, + {0xb1, 0x14}, + {0xb2, 0x30}, + {0xb0, 0x0c}, + {0xb1, 0x06}, + {0xb2, 0x40}, + {0x42, 0x01}, + {0x41, 0x93}, + {0x4c, 0x38}, + {0xb0, 0x10}, + {0xb1, 0x03}, + {0xb2, 0x25}, + {0xb1, 0x13}, + {0xb2, 0x25}, + {0xb0, 0x10}, + {0xb1, 0x04}, + {0xb2, 0x30}, + {0xb1, 0x14}, + {0xb2, 0x30}, + {0xb0, 0x10}, + {0xb1, 0x06}, + {0xb2, 0x40}, + {0x42, 0x01}, + {0x41, 0x93}, +}; + +static const struct ti953_register_write ti953_init_settings_clk[] = { + {0x06, 0x41}, + /* WA: set N to 0x25 for 30 fps */ + {0x07, 0x25}, +}; + +static const struct ti953_register_devid ti953_FPD3_RX_ID[] = { + {0xf0, 0x5f}, + {0xf1, 0x55}, + {0xf2, 0x42}, + {0xf3, 0x39}, + {0xf4, 0x35}, + {0xf5, 0x33}, +}; + +int ti953_reg_write(struct v4l2_subdev *sd, unsigned short rx_port, + unsigned short ser_alias, unsigned char reg, unsigned char val); + +int ti953_reg_read(struct v4l2_subdev *sd, unsigned short rx_port, + unsigned short ser_alias, unsigned char reg, unsigned char *val); + +bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); + +int ti953_init(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); +int ti953_init_clk(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); +int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_alias, uint8_t i2c_speed); + +#endif diff --git a/drivers/media/i2c/ti960-des.c b/drivers/media/i2c/ti960-des.c new file mode 100644 index 000000000000..384447033323 --- /dev/null +++ b/drivers/media/i2c/ti960-des.c @@ -0,0 +1,1740 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "ti960-reg.h" +#include "ti953.h" + +#define MIPI_CSI2_TYPE_RAW12 0x2c +#define MIPI_CSI2_TYPE_YUV422_8 0x1e + +struct ti960_subdev { + struct v4l2_subdev *sd; + unsigned short rx_port; + unsigned short fsin_gpio; + unsigned short phy_i2c_addr; + unsigned short alias_i2c_addr; + unsigned short ser_i2c_addr; + char sd_name[16]; +}; + +struct ti960 { + struct v4l2_subdev sd; + struct media_pad pad[NR_OF_TI960_PADS]; + struct v4l2_ctrl_handler ctrl_handler; + struct ti960_pdata *pdata; + struct ti960_subdev sub_devs[NR_OF_TI960_SINK_PADS]; + struct ti960_subdev_pdata subdev_pdata[NR_OF_TI960_SINK_PADS]; + const char *name; + + struct mutex mutex; + + struct regmap *regmap8; + struct regmap *regmap16; + + struct v4l2_mbus_framefmt *ffmts[NR_OF_TI960_PADS]; + struct rect *crop; + struct rect *compose; + + unsigned int nsinks; + unsigned int nsources; + unsigned int nstreams; + unsigned int npads; + + struct gpio_chip gc; + + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *test_pattern; +}; + +#define to_ti960(_sd) container_of(_sd, struct ti960, sd) + +static const s64 ti960_op_sys_clock[] = {800000000, + 600000000, + 400000000, + 200000000}; + +/* + * Order matters. + * + * 1. Bits-per-pixel, descending. + * 2. Bits-per-pixel compressed, descending. + * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel + * orders must be defined. + */ +static const struct ti960_csi_data_format va_csi_data_formats[] = { + { MEDIA_BUS_FMT_YUYV8_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x1e }, + { MEDIA_BUS_FMT_UYVY8_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x1e }, + { MEDIA_BUS_FMT_SGRBG16_1X16, 16, 16, PIXEL_ORDER_GRBG, 0x2e }, + { MEDIA_BUS_FMT_SRGGB16_1X16, 16, 16, PIXEL_ORDER_RGGB, 0x2e }, + { MEDIA_BUS_FMT_SBGGR16_1X16, 16, 16, PIXEL_ORDER_BGGR, 0x2e }, + { MEDIA_BUS_FMT_SGBRG16_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x2e }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, PIXEL_ORDER_GRBG, 0x2c }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, PIXEL_ORDER_RGGB, 0x2c }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, PIXEL_ORDER_BGGR, 0x2c }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, PIXEL_ORDER_GBRG, 0x2c }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, PIXEL_ORDER_GRBG, 0x2b }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, PIXEL_ORDER_RGGB, 0x2b }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, PIXEL_ORDER_BGGR, 0x2b }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, PIXEL_ORDER_GBRG, 0x2b }, +}; + +static const uint32_t ti960_supported_codes_pad[] = { + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_SBGGR16_1X16, + MEDIA_BUS_FMT_SGBRG16_1X16, + MEDIA_BUS_FMT_SGRBG16_1X16, + MEDIA_BUS_FMT_SRGGB16_1X16, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + 0, +}; + +static const uint32_t *ti960_supported_codes[] = { + ti960_supported_codes_pad, +}; + +static struct regmap_config ti960_reg_config8 = { + .reg_bits = 8, + .val_bits = 8, +}; + +static struct regmap_config ti960_reg_config16 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, +}; + +static s64 ti960_query_sub_stream[NR_OF_TI960_SINK_PADS] = { + 0, 0, 0, 0 +}; + +static void set_sub_stream_fmt(int index, u32 code) +{ + ti960_query_sub_stream[index] &= 0xFFFFFFFFFFFF0000; + ti960_query_sub_stream[index] |= code; +} + +static void set_sub_stream_h(int index, u32 height) +{ + s64 val = height & 0xFFFF; + + ti960_query_sub_stream[index] &= 0xFFFFFFFF0000FFFF; + ti960_query_sub_stream[index] |= val << 16; +} + +static void set_sub_stream_w(int index, u32 width) +{ + s64 val = width & 0xFFFF; + + ti960_query_sub_stream[index] &= 0xFFFF0000FFFFFFFF; + ti960_query_sub_stream[index] |= val << 32; +} + +static void set_sub_stream_dt(int index, u32 dt) +{ + s64 val = dt & 0xFF; + + ti960_query_sub_stream[index] &= 0xFF00FFFFFFFFFFFF; + ti960_query_sub_stream[index] |= val << 48; +} + +static void set_sub_stream_vc_id(int index, u32 vc_id) +{ + s64 val = vc_id & 0xFF; + + ti960_query_sub_stream[index] &= 0x00FFFFFFFFFFFFFF; + ti960_query_sub_stream[index] |= val << 56; +} + +static u8 ti960_set_sub_stream[] = { + 0, 0, 0, 0 +}; + +static int bus_switch(struct ti960 *va) +{ + int ret; + int retry, timeout = 10; + struct i2c_client *client = v4l2_get_subdevdata(&va->sd); + unsigned short addr_backup; + + dev_dbg(&client->dev, "try to set bus switch\n"); + addr_backup = client->addr; + client->addr = 0x70; + for (retry = 0; retry < timeout; retry++) { + ret = i2c_smbus_write_byte(client, 0x01); + if (ret < 0) + usleep_range(5000, 6000); + else + break; + } + + client->addr = addr_backup; + if (retry >= timeout) { + dev_err(&client->dev, "bus switch failed, maybe no bus switch\n"); + } + + return 0; +} + +static int ti960_reg_read(struct ti960 *va, unsigned char reg, unsigned int *val) +{ + int ret, retry, timeout = 10; + + for (retry = 0; retry < timeout; retry++) { + ret = regmap_read(va->regmap8, reg, val); + if (ret < 0) { + dev_err(va->sd.dev, "960 reg read ret=%x", ret); + usleep_range(5000, 6000); + } else { + break; + } + } + + if (retry >= timeout) { + dev_err(va->sd.dev, + "%s:devid read failed: reg=%2x, ret=%d\n", + __func__, reg, ret); + return -EREMOTEIO; + } + + return 0; +} + +static int ti960_reg_write(struct ti960 *va, unsigned char reg, unsigned int val) +{ + int ret, retry, timeout = 10; + + for (retry = 0; retry < timeout; retry++) { + dev_dbg(va->sd.dev, "write reg %x = %x", reg, val); + ret = regmap_write(va->regmap8, reg, val); + if (ret < 0) { + dev_err(va->sd.dev, "960 reg write ret=%x", ret); + usleep_range(5000, 6000); + } else { + break; + } + } + + if (retry >= timeout) { + dev_err(va->sd.dev, + "%s:devid write failed: reg=%2x, ret=%d\n", + __func__, reg, ret); + return -EREMOTEIO; + } + + return 0; +} + +static int ti960_reg_set_bit(struct ti960 *va, unsigned char reg, + unsigned char bit, unsigned char val) +{ + int ret; + unsigned int reg_val; + + ret = regmap_read(va->regmap8, reg, ®_val); + if (ret) + return ret; + if (val) + reg_val |= 1 << bit; + else + reg_val &= ~(1 << bit); + + return ti960_reg_write(va, reg, reg_val); +} + +static int ti960_map_phy_i2c_addr(struct ti960 *va, unsigned short rx_port, + unsigned short addr) +{ + int rval; + + rval = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + return ti960_reg_write(va, TI960_SLAVE_ID0, addr); +} + +static int ti960_map_alias_i2c_addr(struct ti960 *va, unsigned short rx_port, + unsigned short addr) +{ + int rval; + + rval = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + return ti960_reg_write(va, TI960_SLAVE_ALIAS_ID0, addr); +} + +static int ti960_map_ser_alias_addr(struct ti960 *va, unsigned short rx_port, + unsigned short ser_alias) +{ + int rval; + + dev_dbg(va->sd.dev, "%s port %d, ser_alias %x\n", __func__, rx_port, ser_alias); + rval = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + return ti960_reg_write(va, TI960_SER_ALIAS_ID, ser_alias); +} + +static int ti960_fsin_gpio_init(struct ti960 *va, unsigned short rx_port, + unsigned short ser_alias, unsigned short fsin_gpio) +{ + unsigned char gpio_data; + int rval; + int reg_val; + + dev_dbg(va->sd.dev, "%s\n", __func__); + rval = regmap_read(va->regmap8, TI960_FS_CTL, ®_val); + if (rval) { + dev_dbg(va->sd.dev, "Failed to read gpio status.\n"); + return rval; + } + + if (!reg_val & TI960_FSIN_ENABLE) { + dev_dbg(va->sd.dev, "FSIN not enabled, skip config FSIN GPIO.\n"); + return 0; + } + + rval = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + switch (fsin_gpio) { + case 0: + case 1: + rval = regmap_read(va->regmap8, TI960_BC_GPIO_CTL0, ®_val); + if (rval) { + dev_dbg(va->sd.dev, "Failed to read gpio status.\n"); + return rval; + } + + if (fsin_gpio == 0) { + reg_val &= ~TI960_GPIO0_MASK; + reg_val |= TI960_GPIO0_FSIN; + } else { + reg_val &= ~TI960_GPIO1_MASK; + reg_val |= TI960_GPIO1_FSIN; + } + + rval = ti960_reg_write(va, TI960_BC_GPIO_CTL0, reg_val); + if (rval) + dev_dbg(va->sd.dev, "Failed to set gpio.\n"); + break; + case 2: + case 3: + rval = regmap_read(va->regmap8, TI960_BC_GPIO_CTL1, ®_val); + if (rval) { + dev_dbg(va->sd.dev, "Failed to read gpio status.\n"); + return rval; + } + + if (fsin_gpio == 2) { + reg_val &= ~TI960_GPIO2_MASK; + reg_val |= TI960_GPIO2_FSIN; + } else { + reg_val &= ~TI960_GPIO3_MASK; + reg_val |= TI960_GPIO3_FSIN; + } + + rval = ti960_reg_write(va, TI960_BC_GPIO_CTL1, reg_val); + if (rval) + dev_dbg(va->sd.dev, "Failed to set gpio.\n"); + break; + } + + /* enable output and remote control */ + ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); + rval = ti953_reg_read(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + &gpio_data); + if (rval) + return rval; + ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + gpio_data | TI953_GPIO0_RMTEN << fsin_gpio); + + return rval; +} + +static int ti960_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const uint32_t *supported_code = + ti960_supported_codes[code->pad]; + int i; + + for (i = 0; supported_code[i]; i++) { + if (i == code->index) { + code->code = supported_code[i]; + return 0; + } + } + + return -EINVAL; +} + +static const struct ti960_csi_data_format + *ti960_validate_csi_data_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(va_csi_data_formats); i++) { + if (va_csi_data_formats[i].code == code) + return &va_csi_data_formats[i]; + } + + return &va_csi_data_formats[0]; +} + +static int ti960_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + int sink_pad = pad; + + if (sink_pad >= 0) { + struct media_pad *remote_pad = + media_pad_remote_pad_first(&sd->entity.pads[sink_pad]); + if (remote_pad) { + struct v4l2_subdev *rsd = media_entity_to_v4l2_subdev(remote_pad->entity); + + dev_dbg(sd->dev, "%s remote sd: %s\n", __func__, rsd->name); + v4l2_subdev_call(rsd, pad, get_frame_desc, 0, desc); + } + } else + dev_err(sd->dev, "can't find the frame desc\n"); + + return 0; +} + +static struct v4l2_mbus_framefmt * +__ti960_get_ffmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + unsigned int pad, unsigned int which, + unsigned int stream) +{ + struct ti960 *va = to_ti960(subdev); + + if (pad < 0 || pad >= NR_OF_TI960_PADS || + stream < 0 || stream >= va->nstreams) { + dev_err(subdev->dev, "%s invalid pad %d, or stream %d\n", __func__, pad, stream); + return NULL; + } + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(subdev, sd_state, pad); + else + return &va->ffmts[pad][stream]; +} + +static int ti960_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ti960 *va = to_ti960(subdev); + struct v4l2_mbus_framefmt *ffmt; + + mutex_lock(&va->mutex); + ffmt = __ti960_get_ffmt(subdev, sd_state, fmt->pad, fmt->which, 0); + if (!ffmt) { + mutex_unlock(&va->mutex); + return -EINVAL; + } + fmt->format = *ffmt; + mutex_unlock(&va->mutex); + + dev_dbg(subdev->dev, "subdev_format: which: %s, pad: %d.\n", + fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE ? + "V4L2_SUBDEV_FORMAT_ACTIVE" : "V4L2_SUBDEV_FORMAT_TRY", + fmt->pad); + + dev_dbg(subdev->dev, "framefmt: width: %d, height: %d, code: 0x%x.\n", + fmt->format.width, fmt->format.height, fmt->format.code); + + return 0; +} + +static int ti960_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ti960 *va = to_ti960(subdev); + const struct ti960_csi_data_format *csi_format; + struct v4l2_mbus_framefmt *ffmt; + + csi_format = ti960_validate_csi_data_format( + fmt->format.code); + + mutex_lock(&va->mutex); + ffmt = __ti960_get_ffmt(subdev, sd_state, fmt->pad, fmt->which, 0); + + if (!ffmt) { + mutex_unlock(&va->mutex); + return -EINVAL; + } + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ffmt->width = fmt->format.width; + ffmt->height = fmt->format.height; + ffmt->code = csi_format->code; + } + fmt->format = *ffmt; + + if (fmt->pad < NR_OF_TI960_SINK_PADS) { + set_sub_stream_fmt(fmt->pad, ffmt->code); + set_sub_stream_h(fmt->pad, ffmt->height); + set_sub_stream_w(fmt->pad, ffmt->width); + + /* select correct csi-2 data type id */ + if (ffmt->code >= MEDIA_BUS_FMT_UYVY8_1X16 && + ffmt->code <= MEDIA_BUS_FMT_YVYU8_1X16) + set_sub_stream_dt(fmt->pad, MIPI_CSI2_TYPE_YUV422_8); + else + set_sub_stream_dt(fmt->pad, MIPI_CSI2_TYPE_RAW12); + set_sub_stream_vc_id(fmt->pad, fmt->pad); + dev_dbg(subdev->dev, + "framefmt: width: %d, height: %d, code: 0x%x.\n", + ffmt->width, ffmt->height, ffmt->code); + } + + mutex_unlock(&va->mutex); + return 0; +} + +static int ti960_open(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(subdev, fh->state, 0); + + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = TI960_PAD_SOURCE, + .format = { + .width = TI960_MAX_WIDTH, + .height = TI960_MAX_HEIGHT, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + }, + }; + + *try_fmt = fmt.format; + + return 0; +} + +static int ti960_map_subdevs_addr(struct ti960 *va) +{ + unsigned short rx_port, phy_i2c_addr, alias_i2c_addr; + int i, rval; + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + rx_port = va->sub_devs[i].rx_port; + phy_i2c_addr = va->sub_devs[i].phy_i2c_addr; + alias_i2c_addr = va->sub_devs[i].alias_i2c_addr; + + if (!phy_i2c_addr || !alias_i2c_addr) + continue; + + rval = ti960_map_phy_i2c_addr(va, rx_port, phy_i2c_addr); + if (rval) + return rval; + + /* set 7bit alias i2c addr */ + rval = ti960_map_alias_i2c_addr(va, rx_port, + alias_i2c_addr << 1); + if (rval) + return rval; + } + + return 0; +} + +/* + * FIXME: workaround, reset to avoid block. + */ +static int reset_sensor(struct ti960 *va, unsigned short rx_port, + unsigned short ser_alias, int reset) +{ + int rval; + unsigned char gpio_data; + + rval = ti953_reg_read(&va->sd, rx_port, ser_alias, + TI953_LOCAL_GPIO_DATA, + &gpio_data); + if (rval) + return rval; + + ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_GPIO_INPUT_CTRL, + TI953_GPIO_OUT_EN); + gpio_data &= ~(TI953_GPIO0_RMTEN << reset); + gpio_data &= ~(TI953_GPIO0_OUT << reset); + ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + gpio_data); + msleep(50); + gpio_data |= TI953_GPIO0_OUT << reset; + ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + gpio_data); + + return 0; +} + +static int ti960_config_ser(struct ti960 *va, struct ti960_subdev *subdev, + struct ti960_subdev_pdata *pdata) +{ + unsigned short rx_port, phy_i2c_addr, alias_i2c_addr, ser_i2c_addr; + int i, rval; + unsigned char val; + bool speed_detect_fail; + + rx_port = subdev->rx_port; + phy_i2c_addr = subdev->phy_i2c_addr; + alias_i2c_addr = subdev->alias_i2c_addr; + ser_i2c_addr = subdev->ser_i2c_addr; + + rval = ti960_map_ser_alias_addr(va, rx_port, + ser_i2c_addr << 1); + if (rval) + return rval; + + if (!ti953_detect(&va->sd, rx_port, ser_i2c_addr)) + return -ENODEV; + + ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + TI953_RESET_CTL, TI953_DIGITAL_RESET_1); + msleep(50); + + if (pdata->module_flags & TI960_FL_INIT_SER) { + rval = ti953_init(&va->sd, rx_port, ser_i2c_addr); + if (rval) + return rval; + } + + if (pdata->module_flags & TI960_FL_INIT_SER_CLK) { + rval = ti953_init_clk(&va->sd, rx_port, ser_i2c_addr); + if (rval) + return rval; + } + + if (pdata->module_flags & TI960_FL_POWERUP) { + ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); + + /* boot sequence */ + for (i = 0; i < TI960_MAX_GPIO_POWERUP_SEQ; i++) { + if (pdata->gpio_powerup_seq[i] == (char)-1) + break; + ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + TI953_LOCAL_GPIO_DATA, + pdata->gpio_powerup_seq[i]); + msleep(50); + } + } + + /* Configure ti953 CSI lane */ + rval = ti953_reg_read(&va->sd, rx_port, ser_i2c_addr, + TI953_GENERAL_CFG, &val); + dev_dbg(va->sd.dev, "ti953 read default general CFG (%x)\n", val); + if (va->pdata->ser_nlanes == 2) + val |= TI953_CSI_2LANE; + else if (va->pdata->ser_nlanes == 4) + val |= TI953_CSI_4LANE; + else + dev_err(va->sd.dev, "not expected csi lane\n"); + rval = ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + TI953_GENERAL_CFG, val); + if (rval != 0) { + dev_err(va->sd.dev, "ti953 write failed(%d)\n", rval); + return rval; + } + + ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + TI953_I2C_SPEED_FAST_PLUS); + speed_detect_fail = + ti953_reg_read(&va->sd, rx_port, + alias_i2c_addr, 0, &val); + if (speed_detect_fail) { + ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + TI953_I2C_SPEED_FAST); + speed_detect_fail = + ti953_reg_read(&va->sd, rx_port, + alias_i2c_addr, 0, &val); + } + if (speed_detect_fail) { + ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + TI953_I2C_SPEED_STANDARD); + speed_detect_fail = + ti953_reg_read(&va->sd, rx_port, + alias_i2c_addr, 0, &val); + } + if (speed_detect_fail) + dev_err(va->sd.dev, "i2c bus speed standard failed!"); + + return 0; +} + +static int ti960_registered(struct v4l2_subdev *subdev) +{ + struct ti960 *va = to_ti960(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int i, j, k, l, m, rval; + bool port_registered[NR_OF_TI960_SINK_PADS]; + unsigned char val; + + for (i = 0 ; i < NR_OF_TI960_SINK_PADS; i++) + port_registered[i] = false; + + for (i = 0, k = 0; i < va->pdata->subdev_num; i++) { + struct ti960_subdev_info *info = + &va->pdata->subdev_info[i]; + struct ti960_subdev_pdata *pdata = + (struct ti960_subdev_pdata *) + info->board_info.platform_data; + struct ti960_subdev sensor_subdev; + + sensor_subdev.rx_port = info->rx_port; + sensor_subdev.phy_i2c_addr = info->phy_i2c_addr; + sensor_subdev.alias_i2c_addr = info->board_info.addr; + sensor_subdev.ser_i2c_addr = info->ser_alias; + + if (k >= va->nsinks) + break; + + if (port_registered[info->rx_port]) { + dev_err(va->sd.dev, + "rx port %d registed already\n", + info->rx_port); + continue; + } + + /* + * The sensors should not share the same pdata structure. + * Clone the pdata for each sensor. + */ + memcpy(&va->subdev_pdata[k], pdata, sizeof(*pdata)); + + va->sub_devs[k].fsin_gpio = va->subdev_pdata[k].fsin; + + /* Spin sensor subdev suffix name */ + va->subdev_pdata[k].suffix = info->suffix; + + /* + * Change the gpio value to have xshutdown + * and rx port included, so in gpio_set those + * can be caculated from it. + */ + va->subdev_pdata[k].xshutdown += va->gc.base + + info->rx_port * NR_OF_GPIOS_PER_PORT; + info->board_info.platform_data = &va->subdev_pdata[k]; + + if (!info->phy_i2c_addr || !info->board_info.addr) { + dev_err(va->sd.dev, "can't find the physical and alias addr.\n"); + return -EINVAL; + } + + rval = ti960_config_ser(va, &sensor_subdev, pdata); + if (rval) + continue; + + /* Map PHY I2C address. */ + rval = ti960_map_phy_i2c_addr(va, info->rx_port, + info->phy_i2c_addr); + if (rval) + return rval; + + /* Map 7bit ALIAS I2C address. */ + rval = ti960_map_alias_i2c_addr(va, info->rx_port, + info->board_info.addr << 1); + if (rval) + return rval; + + va->sub_devs[k].sd = v4l2_i2c_new_subdev_board( + va->sd.v4l2_dev, client->adapter, + &info->board_info, 0); + if (!va->sub_devs[k].sd) { + dev_err(va->sd.dev, + "can't create new i2c subdev %c\n", + info->suffix); + continue; + } + va->sub_devs[k].rx_port = info->rx_port; + va->sub_devs[k].phy_i2c_addr = info->phy_i2c_addr; + va->sub_devs[k].alias_i2c_addr = info->board_info.addr; + va->sub_devs[k].ser_i2c_addr = info->ser_alias; + snprintf(va->sub_devs[k].sd->name, sizeof(va->sd.name), "%s %c", + va->subdev_pdata[k].module_name, + va->subdev_pdata[k].suffix); + memcpy(va->sub_devs[k].sd_name, + va->subdev_pdata[k].module_name, + min(sizeof(va->sub_devs[k].sd_name) - 1, + sizeof(va->subdev_pdata[k].module_name) - 1)); + + for (j = 0; j < va->sub_devs[k].sd->entity.num_pads; j++) { + if (va->sub_devs[k].sd->entity.pads[j].flags & + MEDIA_PAD_FL_SOURCE) + break; + } + + if (j == va->sub_devs[k].sd->entity.num_pads) { + dev_warn(va->sd.dev, + "no source pad in subdev %c\n", + info->suffix); + return -ENOENT; + } + + for (l = 0; l < va->nsinks; l++) { + rval = media_create_pad_link( + &va->sub_devs[k].sd->entity, j, + &va->sd.entity, l, MEDIA_LNK_FL_DYNAMIC); + if (rval) { + dev_err(va->sd.dev, + "can't create link to %c\n", + info->suffix); + return -EINVAL; + } + } + port_registered[va->sub_devs[k].rx_port] = true; + k++; + } + rval = ti960_map_subdevs_addr(va); + if (rval) + return rval; + + return 0; +} + +static int ti960_set_power(struct v4l2_subdev *subdev, int on) +{ + struct ti960 *va = to_ti960(subdev); + int ret; + u8 val; + u8 link_freq; + + ret = ti960_reg_write(va, TI960_RESET, + (on) ? TI960_POWER_ON : TI960_POWER_OFF); + if (ret || !on) + return ret; + + /* Select TX port 0 R/W by default */ + ret = ti960_reg_write(va, TI960_CSI_PORT_SEL, 0x01); + /* Configure MIPI clock bsaed on control value. */ + link_freq = v4l2_ctrl_g_ctrl(va->link_freq); + ret = ti960_reg_write(va, TI960_CSI_PLL_CTL, + link_freq); + if (ret) + return ret; + val = TI960_CSI_ENABLE; + + /* Configure ti960 CSI lane */ + if (va->pdata->deser_nlanes == 2) + val |= TI960_CSI_2LANE; + else if (va->pdata->deser_nlanes == 4) + val |= TI960_CSI_4LANE; + else + dev_err(va->sd.dev, "not expected csi lane\n"); + + /* Enable skew calculation when 1.6Gbps output is enabled. */ + if (link_freq == TI960_MIPI_1600MBPS) + val |= TI960_CSI_SKEWCAL; + + return ti960_reg_write(va, TI960_CSI_CTL, val); +} + +static bool ti960_broadcast_mode(struct v4l2_subdev *subdev) +{ + struct ti960 *va = to_ti960(subdev); + struct v4l2_subdev_format fmt = { 0 }; + struct v4l2_subdev *sd; + char *sd_name = NULL; + bool first = true; + unsigned int h = 0, w = 0, code = 0; + bool single_stream = true; + int i, rval; + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + struct media_pad *remote_pad = + media_pad_remote_pad_first(&va->pad[i]); + + if (!remote_pad) + continue; + + if (!ti960_set_sub_stream[i]) + continue; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = remote_pad->index; + + rval = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (rval) + return false; + + if (first) { + sd_name = va->sub_devs[i].sd_name; + h = fmt.format.height; + w = fmt.format.width; + code = fmt.format.code; + first = false; + } else { + if (strncmp(sd_name, va->sub_devs[i].sd_name, 16)) + return false; + + if (h != fmt.format.height || w != fmt.format.width + || code != fmt.format.code) + return false; + + single_stream = false; + } + } + + if (single_stream) + return false; + + return true; +} + +static int ti960_rx_port_config(struct ti960 *va, int sink, int rx_port) +{ + int rval; + unsigned int csi_vc_map; + + /* Select RX port. */ + rval = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) { + dev_err(va->sd.dev, "Failed to select RX port.\n"); + return rval; + } + + rval = ti960_reg_write(va, TI960_PORT_CONFIG, + TI960_FPD3_CSI); + if (rval) { + dev_err(va->sd.dev, "Failed to set port config.\n"); + return rval; + } + + /* + * CSI VC MAPPING. + */ + csi_vc_map = sink * 0x55; + dev_info(va->sd.dev, "%s sink pad %d, rx_port %d, csi_vc_map %x", + __func__, sink, rx_port, csi_vc_map); + rval = ti960_reg_write(va, TI960_CSI_VC_MAP, + csi_vc_map); + if (rval) { + dev_err(va->sd.dev, "Failed to set port config.\n"); + return rval; + } + return 0; +} + +static int ti960_find_subdev_index(struct ti960 *va, struct v4l2_subdev *sd) +{ + int i; + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + if (va->sub_devs[i].sd == sd) + return i; + } + + WARN_ON(1); + + return -EINVAL; +} + +static int ti960_find_subdev_index_by_rx_port(struct ti960 *va, u8 rx_port) +{ + int i; + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + if (va->sub_devs[i].rx_port == rx_port) + return i; + } + WARN_ON(1); + + return -EINVAL; +} + +static int ti960_set_frame_sync(struct ti960 *va, int enable) +{ + int i, rval; + int index = !!enable; + + for (i = 0; i < ARRAY_SIZE(ti960_frame_sync_settings[index]); i++) { + rval = ti960_reg_write(va, + ti960_frame_sync_settings[index][i].reg, + ti960_frame_sync_settings[index][i].val); + if (rval) { + dev_err(va->sd.dev, "Failed to %s frame sync\n", + enable ? "enable" : "disable"); + return rval; + } + } + + return 0; +} + +static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) +{ + struct ti960 *va = to_ti960(subdev); + struct v4l2_subdev *sd; + int i, j, rval; + bool broadcast; + unsigned short rx_port; + unsigned short ser_alias; + int sd_idx = -1; + DECLARE_BITMAP(rx_port_enabled, 32); + + dev_dbg(va->sd.dev, "TI960 set stream, enable %d\n", enable); + + broadcast = ti960_broadcast_mode(subdev); + if (enable) + dev_info(va->sd.dev, "TI960 in %s mode", + broadcast ? "broadcast" : "non broadcast"); + + bitmap_zero(rx_port_enabled, 32); + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + struct media_pad *remote_pad = + media_pad_remote_pad_first(&va->pad[i]); + + if (!remote_pad) + continue; + + if (!ti960_set_sub_stream[i]) + continue; + + /* Find ti960 subdev */ + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + j = ti960_find_subdev_index(va, sd); + if (j < 0) + return -EINVAL; + rx_port = va->sub_devs[j].rx_port; + ser_alias = va->sub_devs[j].ser_i2c_addr; + rval = ti960_rx_port_config(va, i, rx_port); + if (rval < 0) + return rval; + + bitmap_set(rx_port_enabled, rx_port, 1); + + if (broadcast && sd_idx == -1) { + sd_idx = j; + } else if (broadcast) { + rval = ti960_map_alias_i2c_addr(va, rx_port, + va->sub_devs[sd_idx].alias_i2c_addr << 1); + if (rval < 0) + return rval; + } else { + /* Stream on/off sensor */ + dev_err(va->sd.dev, + "set stream for %s, enable %d\n", + sd->name, enable); + rval = v4l2_subdev_call(sd, video, s_stream, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set stream for %s, enable %d\n", + sd->name, enable); + return rval; + } + + /* RX port fordward */ + rval = ti960_reg_set_bit(va, TI960_FWD_CTL1, + rx_port + 4, !enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to forward RX port%d. enable %d\n", + i, enable); + return rval; + } + if (va->subdev_pdata[j].module_flags & TI960_FL_RESET) { + rval = reset_sensor(va, rx_port, ser_alias, + va->subdev_pdata[j].reset); + if (rval) + return rval; + } + } + } + + if (broadcast) { + if (sd_idx < 0) { + dev_err(va->sd.dev, "No sensor connected!\n"); + return -ENODEV; + } + sd = va->sub_devs[sd_idx].sd; + rval = v4l2_subdev_call(sd, video, s_stream, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set stream for %s. enable %d\n", + sd->name, enable); + return rval; + } + + rval = ti960_set_frame_sync(va, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set frame sync.\n"); + return rval; + } + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + if (enable && test_bit(i, rx_port_enabled)) { + rval = ti960_fsin_gpio_init(va, + va->sub_devs[i].rx_port, + va->sub_devs[i].ser_i2c_addr, + va->sub_devs[i].fsin_gpio); + if (rval) { + dev_err(va->sd.dev, + "Failed to enable frame sync gpio init.\n"); + return rval; + } + + if (va->subdev_pdata[i].module_flags & TI960_FL_RESET) { + rx_port = va->sub_devs[i].rx_port; + ser_alias = va->sub_devs[i].ser_i2c_addr; + rval = reset_sensor(va, rx_port, ser_alias, + va->subdev_pdata[i].reset); + if (rval) + return rval; + } + } + } + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + if (!test_bit(i, rx_port_enabled)) + continue; + + /* RX port fordward */ + rval = ti960_reg_set_bit(va, TI960_FWD_CTL1, + i + 4, !enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to forward RX port%d. enable %d\n", + i, enable); + return rval; + } + } + + /* + * Restore each subdev i2c address as we may + * touch it later. + */ + rval = ti960_map_subdevs_addr(va); + if (rval) + return rval; + } + + return 0; +} + +static int ti960_set_stream_vc(struct ti960 *va, u8 vc_id, u8 state) +{ + unsigned short rx_port; + unsigned short ser_alias; + struct v4l2_subdev *sd; + int rval; + int i; + + i = ti960_find_subdev_index_by_rx_port(va, vc_id); + if (i < 0) + return -EINVAL; + rx_port = va->sub_devs[i].rx_port; + ser_alias = va->sub_devs[i].ser_i2c_addr; + sd = va->sub_devs[i].sd; + + rval = ti960_rx_port_config(va, vc_id, rx_port); + if (rval < 0) + return rval; + + rval = v4l2_subdev_call(sd, video, s_stream, state); + if (rval) { + dev_err(va->sd.dev, + "Failed to set stream for %s, enable %d\n", + sd->name, state); + return rval; + } + dev_info(va->sd.dev, "set stream for %s, enable %d\n", + sd->name, state); + + /* RX port fordward */ + rval = ti960_reg_set_bit(va, TI960_FWD_CTL1, + rx_port + 4, !state); + if (rval) { + dev_err(va->sd.dev, + "Failed to forward RX port%d. enable %d\n", + i, state); + return rval; + } + if (va->subdev_pdata[i].module_flags & TI960_FL_RESET) { + rval = reset_sensor(va, rx_port, ser_alias, + va->subdev_pdata[i].reset); + if (rval) + return rval; + } + + return 0; +} + +static struct v4l2_subdev_internal_ops ti960_sd_internal_ops = { + .open = ti960_open, + .registered = ti960_registered, +}; + +static const struct v4l2_subdev_video_ops ti960_sd_video_ops = { + .s_stream = ti960_set_stream, +}; + +static const struct v4l2_subdev_core_ops ti960_core_subdev_ops = { + .s_power = ti960_set_power, +}; + +static u8 ti960_get_nubmer_of_streaming(void) +{ + u8 n = 0; + u8 i = 0; + + for (; i < ARRAY_SIZE(ti960_set_sub_stream); i++) { + if (ti960_set_sub_stream[i]) + n++; + } + + return n; +} + +static int ti960_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ti960 *va = container_of(ctrl->handler, + struct ti960, ctrl_handler); + u32 val; + u8 vc_id; + u8 state; + + switch (ctrl->id) { + case V4L2_CID_IPU_SET_SUB_STREAM: + val = (*ctrl->p_new.p_s64 & 0xFFFF); + dev_info(va->sd.dev, "V4L2_CID_IPU_SET_SUB_STREAM %x\n", val); + vc_id = (val >> 8) & 0x00FF; + state = val & 0x00FF; + if (vc_id > NR_OF_TI960_SINK_PADS - 1) { + dev_err(va->sd.dev, "invalid vc %d\n", vc_id); + break; + } + + ti960_reg_write(va, TI960_CSI_PORT_SEL, 0x01); + ti960_reg_read(va, TI960_CSI_CTL, &val); + if (state) { + if (ti960_get_nubmer_of_streaming() == 0) + val |= TI960_CSI_CONTS_CLOCK; + ti960_set_sub_stream[vc_id] = state; + } else { + ti960_set_sub_stream[vc_id] = state; + if (ti960_get_nubmer_of_streaming() == 0) + val &= ~TI960_CSI_CONTS_CLOCK; + } + + ti960_reg_write(va, TI960_CSI_CTL, val); + ti960_set_stream_vc(va, vc_id, state); + break; + default: + dev_info(va->sd.dev, "unknown control id: 0x%X\n", ctrl->id); + } + + return 0; +} + +static const struct v4l2_ctrl_ops ti960_ctrl_ops = { + .s_ctrl = ti960_s_ctrl, +}; + +static const struct v4l2_ctrl_config ti960_controls[] = { + { + .ops = &ti960_ctrl_ops, + .id = V4L2_CID_LINK_FREQ, + .name = "V4L2_CID_LINK_FREQ", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .min = 0, + .max = ARRAY_SIZE(ti960_op_sys_clock) - 1, + .def = TI960_MIPI_1200MBPS, + .menu_skip_mask = 0, + .qmenu_int = ti960_op_sys_clock, + }, + { + .ops = &ti960_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN, + .name = "V4L2_CID_TEST_PATTERN", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1, + .step = 1, + .def = 0, + }, + { + .ops = &ti960_ctrl_ops, + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, + .name = "query virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(ti960_query_sub_stream) - 1, + .min = 0, + .def = 0, + .menu_skip_mask = 0, + .qmenu_int = ti960_query_sub_stream, + }, + { + .ops = &ti960_ctrl_ops, + .id = V4L2_CID_IPU_SET_SUB_STREAM, + .name = "set virtual channel", + .type = V4L2_CTRL_TYPE_INTEGER64, + .max = 0xFFFF, + .min = 0, + .def = 0, + .step = 1, + }, +}; + +static const struct v4l2_subdev_pad_ops ti960_sd_pad_ops = { + .get_fmt = ti960_get_format, + .set_fmt = ti960_set_format, + .get_frame_desc = ti960_get_frame_desc, + .enum_mbus_code = ti960_enum_mbus_code, +}; + +static struct v4l2_subdev_ops ti960_sd_ops = { + .core = &ti960_core_subdev_ops, + .video = &ti960_sd_video_ops, + .pad = &ti960_sd_pad_ops, +}; + +static int ti960_register_subdev(struct ti960 *va) +{ + int i, rval; + struct i2c_client *client = v4l2_get_subdevdata(&va->sd); + + v4l2_subdev_init(&va->sd, &ti960_sd_ops); + snprintf(va->sd.name, sizeof(va->sd.name), "TI960 %c", + va->pdata->suffix); + + va->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + va->sd.internal_ops = &ti960_sd_internal_ops; + va->sd.entity.function = MEDIA_ENT_F_VID_MUX; + + v4l2_set_subdevdata(&va->sd, client); + + v4l2_ctrl_handler_init(&va->ctrl_handler, + ARRAY_SIZE(ti960_controls)); + + if (va->ctrl_handler.error) { + dev_err(va->sd.dev, + "Failed to init ti960 controls. ERR: %d!\n", + va->ctrl_handler.error); + return va->ctrl_handler.error; + } + + va->sd.ctrl_handler = &va->ctrl_handler; + + for (i = 0; i < ARRAY_SIZE(ti960_controls); i++) { + const struct v4l2_ctrl_config *cfg = + &ti960_controls[i]; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&va->ctrl_handler, cfg, NULL); + if (!ctrl) { + dev_err(va->sd.dev, + "Failed to create ctrl %s!\n", cfg->name); + rval = va->ctrl_handler.error; + goto failed_out; + } + } + + va->link_freq = v4l2_ctrl_find(&va->ctrl_handler, V4L2_CID_LINK_FREQ); + switch (va->pdata->link_freq_mbps) { + case 1600: + __v4l2_ctrl_s_ctrl(va->link_freq, TI960_MIPI_1600MBPS); + break; + case 1200: + __v4l2_ctrl_s_ctrl(va->link_freq, TI960_MIPI_1200MBPS); + break; + case 800: + __v4l2_ctrl_s_ctrl(va->link_freq, TI960_MIPI_800MBPS); + break; + case 400: + __v4l2_ctrl_s_ctrl(va->link_freq, TI960_MIPI_400MBPS); + break; + default: + break; + } + va->test_pattern = v4l2_ctrl_find(&va->ctrl_handler, + V4L2_CID_TEST_PATTERN); + + for (i = 0; i < va->nsinks; i++) + va->pad[i].flags = MEDIA_PAD_FL_SINK; + va->pad[TI960_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + rval = media_entity_pads_init(&va->sd.entity, + NR_OF_TI960_PADS, va->pad); + if (rval) { + dev_err(va->sd.dev, + "Failed to init media entity for ti960!\n"); + goto failed_out; + } + + return 0; + +failed_out: + v4l2_ctrl_handler_free(&va->ctrl_handler); + return rval; +} + +static int ti960_init(struct ti960 *va) +{ +#ifdef TI960_RESET_NEEDED + unsigned int reset_gpio = va->pdata->reset_gpio; +#endif + int i, rval; + unsigned int val; + +#ifdef TI960_RESET_NEEDED + /* TI960 PDB pulled up to high by HW design in some board */ + gpio_set_value(reset_gpio, 1); + usleep_range(2000, 3000); + dev_err(va->sd.dev, "Setting reset gpio %d to 1.\n", reset_gpio); +#endif + + bus_switch(va); + usleep_range(8000, 9000); + + rval = ti960_reg_read(va, TI960_DEVID, &val); + if (rval) { + dev_err(va->sd.dev, "Failed to read device ID of TI960!\n"); + return rval; + } + dev_info(va->sd.dev, "TI960 device ID: 0x%X\n", val); + for (i = 0; i < ARRAY_SIZE(ti960_gpio_settings); i++) { + rval = ti960_reg_write(va, + ti960_gpio_settings[i].reg, + ti960_gpio_settings[i].val); + if (rval) { + dev_err(va->sd.dev, + "Failed to write TI960 gpio setting, reg %2x, val %2x\n", + ti960_gpio_settings[i].reg, ti960_gpio_settings[i].val); + return rval; + } + } + usleep_range(10000, 11000); + + for (i = 0; i < ARRAY_SIZE(ti960_init_settings); i++) { + if (ti960_init_settings[i].reg == 0) { + usleep_range(ti960_init_settings[i].val * 1000, ti960_init_settings[i].val * 1000); + continue; + } + rval = ti960_reg_write(va, + ti960_init_settings[i].reg, + ti960_init_settings[i].val); + if (rval) { + dev_err(va->sd.dev, + "Failed to write TI960 init setting, reg %2x, val %2x\n", + ti960_init_settings[i].reg, ti960_init_settings[i].val); + return rval; + } + } + /* wait for ti953 ready */ + msleep(200); + + rval = ti960_map_subdevs_addr(va); + if (rval) + return rval; + + return 0; +} + +static void ti960_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value) +{ + struct i2c_client *client = to_i2c_client(chip->parent); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti960 *va = to_ti960(subdev); + unsigned int reg_val; + int rx_port, gpio_port; + int ret; + + if (gpio >= NR_OF_TI960_GPIOS) + return; + + rx_port = gpio / NR_OF_GPIOS_PER_PORT; + gpio_port = gpio % NR_OF_GPIOS_PER_PORT; + + ret = ti960_reg_write(va, TI960_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (ret) { + dev_dbg(&client->dev, "Failed to select RX port.\n"); + return; + } + ret = regmap_read(va->regmap8, TI960_BC_GPIO_CTL0, ®_val); + if (ret) { + dev_dbg(&client->dev, "Failed to read gpio status.\n"); + return; + } + + if (gpio_port == 0) { + reg_val &= ~TI960_GPIO0_MASK; + reg_val |= value ? TI960_GPIO0_HIGH : TI960_GPIO0_LOW; + } else { + reg_val &= ~TI960_GPIO1_MASK; + reg_val |= value ? TI960_GPIO1_HIGH : TI960_GPIO1_LOW; + } + + ret = ti960_reg_write(va, TI960_BC_GPIO_CTL0, reg_val); + if (ret) + dev_dbg(&client->dev, "Failed to set gpio.\n"); +} + +static int ti960_gpio_direction_output(struct gpio_chip *chip, + unsigned int gpio, int level) +{ + return 0; +} + +static int ti960_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ti960 *va; + int i, rval = 0; + int gpio_FPD = 0; + + if (client->dev.platform_data == NULL) + return -ENODEV; + + va = devm_kzalloc(&client->dev, sizeof(*va), GFP_KERNEL); + if (!va) + return -ENOMEM; + + va->pdata = client->dev.platform_data; + + va->nsources = NR_OF_TI960_SOURCE_PADS; + va->nsinks = NR_OF_TI960_SINK_PADS; + va->npads = NR_OF_TI960_PADS; + va->nstreams = NR_OF_TI960_STREAMS; + + va->crop = devm_kcalloc(&client->dev, va->npads, + sizeof(struct v4l2_rect), GFP_KERNEL); + + va->compose = devm_kcalloc(&client->dev, va->npads, + sizeof(struct v4l2_rect), GFP_KERNEL); + + if (!va->crop || !va->compose) + return -ENOMEM; + + for (i = 0; i < va->npads; i++) { + va->ffmts[i] = devm_kcalloc(&client->dev, va->nstreams, + sizeof(struct v4l2_mbus_framefmt), + GFP_KERNEL); + if (!va->ffmts[i]) + return -ENOMEM; + } + + va->regmap8 = devm_regmap_init_i2c(client, + &ti960_reg_config8); + if (IS_ERR(va->regmap8)) { + dev_err(&client->dev, "Failed to init regmap8!\n"); + return -EIO; + } + + va->regmap16 = devm_regmap_init_i2c(client, + &ti960_reg_config16); + if (IS_ERR(va->regmap16)) { + dev_err(&client->dev, "Failed to init regmap16!\n"); + return -EIO; + } + + mutex_init(&va->mutex); + v4l2_i2c_subdev_init(&va->sd, client, &ti960_sd_ops); + rval = ti960_register_subdev(va); + if (rval) { + dev_err(&client->dev, "Failed to register va subdevice!\n"); + return rval; + } + +#ifdef TI960_RESET_NEEDED + if (devm_gpio_request_one(va->sd.dev, va->pdata->reset_gpio, 0, + "ti960 reset") != 0) { + dev_err(va->sd.dev, "Unable to acquire gpio %d\n", + va->pdata->reset_gpio); + return -ENODEV; + } +#endif + + if (va->pdata->FPD_gpio != -1) { + rval = devm_gpio_request_one(&client->dev, + va->pdata->FPD_gpio, + GPIOF_OUT_INIT_LOW, "Cam"); + if (rval) { + dev_err(&client->dev, + "camera power GPIO pin request failed!\n"); + return rval; + } + + /* pull up GPPC_B23 to high for FPD link power */ + gpio_FPD = gpio_get_value(va->pdata->FPD_gpio); + if (gpio_FPD == 0) + gpio_set_value(va->pdata->FPD_gpio, 1); + } + + rval = ti960_init(va); + if (rval) { + dev_err(&client->dev, "Failed to init TI960!\n"); + goto free_gpio; + } + + /* + * TI960 has several back channel GPIOs. + * We export GPIO0 and GPIO1 to control reset or fsin. + */ + va->gc.parent = &client->dev; + va->gc.owner = THIS_MODULE; + va->gc.label = "TI960 GPIO"; + va->gc.ngpio = NR_OF_TI960_GPIOS; + va->gc.base = -1; + va->gc.set = ti960_gpio_set; + va->gc.direction_output = ti960_gpio_direction_output; + rval = gpiochip_add(&va->gc); + if (rval) { + dev_err(&client->dev, "Failed to add gpio chip! %d\n", rval); + rval = -EIO; + goto free_gpio; + } + + dev_err(&client->dev, "%s Probe Succeeded", va->sd.name); + dev_err(&client->dev, "%s Link Freq %d Mbps", va->sd.name, va->pdata->link_freq_mbps); + return 0; + +free_gpio: + if (va->pdata->FPD_gpio != -1) { + dev_err(&client->dev, "restore and free FPD gpio!\n"); + /* restore GPPC_B23 */ + if (gpio_FPD == 0) + gpio_set_value(va->pdata->FPD_gpio, 0); + + gpio_free(va->pdata->FPD_gpio); + } + + dev_err(&client->dev, "%s Probe Failed", va->sd.name); + return rval; +} + +static void ti960_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti960 *va = to_ti960(subdev); + int i; + + if (!va) + return; + + mutex_destroy(&va->mutex); + v4l2_ctrl_handler_free(&va->ctrl_handler); + v4l2_device_unregister_subdev(&va->sd); + media_entity_cleanup(&va->sd.entity); + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + if (va->sub_devs[i].sd) { + struct i2c_client *sub_client = + v4l2_get_subdevdata(va->sub_devs[i].sd); + + i2c_unregister_device(sub_client); + } + va->sub_devs[i].sd = NULL; + } + + gpiochip_remove(&va->gc); + +} + +#ifdef CONFIG_PM +static int ti960_suspend(struct device *dev) +{ + return 0; +} + +static int ti960_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti960 *va = to_ti960(subdev); + int i, rval; + struct ti960_subdev *sensor_subdev; + struct ti960_subdev_pdata *pdata; + unsigned char val; + + rval = ti960_init(va); + if (rval) { + dev_err(va->sd.dev, "resume: failed init ti960"); + return rval; + } + + for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { + sensor_subdev = &va->sub_devs[i]; + pdata = &va->subdev_pdata[i]; + if (sensor_subdev->sd == NULL) + break; + + rval = ti960_config_ser(va, sensor_subdev, pdata); + if (rval) + dev_warn(va->sd.dev, "resume: failed config subdev"); + } + + return 0; +} +#else +#define ti960_suspend NULL +#define ti960_resume NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id ti960_id_table[] = { + { TI960_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ti960_id_table); + +static const struct dev_pm_ops ti960_pm_ops = { + .suspend = ti960_suspend, + .resume = ti960_resume, +}; + +static struct i2c_driver ti960_i2c_driver = { + .driver = { + .name = TI960_NAME, + .pm = &ti960_pm_ops, + }, + .probe = ti960_probe, + .remove = ti960_remove, + .id_table = ti960_id_table, +}; +module_i2c_driver(ti960_i2c_driver); + +MODULE_AUTHOR("Chen Meng J "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TI960 CSI2-Aggregator driver"); diff --git a/drivers/media/i2c/ti960-reg.h b/drivers/media/i2c/ti960-reg.h new file mode 100644 index 000000000000..5330c81799d8 --- /dev/null +++ b/drivers/media/i2c/ti960-reg.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2022 Intel Corporation */ + +#ifndef TI960_REG_H +#define TI960_REG_H + +struct ti960_register_write { + u8 reg; + u8 val; +}; + +struct ti960_register_devid { + u8 reg; + u8 val_expected; +}; + +static const struct ti960_register_write ti960_frame_sync_settings[2][5] = { + { + {0x18, 0x00}, /* Disable frame sync. */ + {0x19, 0x00}, + {0x1a, 0x00}, + {0x1b, 0x00}, + {0x1c, 0x00}, + }, + { + {0x19, 0x15}, /* Frame sync high time.*/ + {0x1a, 0xb3}, + {0x1b, 0xc3}, /* Frame sync low time. */ + {0x1c, 0x4f}, + {0x18, 0x01}, /* Enable frame sync. and use high/low mode */ + } +}; + +static const struct ti960_register_write ti960_gpio_settings[] = { + {0x10, 0x81}, + {0x11, 0x85}, + {0x12, 0x89}, + {0x13, 0x8d}, +}; + +static const struct ti960_register_write ti960_init_settings[] = { + {0x0c, 0x0f}, /* RX_PORT_CTL */ + {0x1f, 0x06}, /* CSI_PLL_CTL */ + {0x4c, 0x01}, /* FPD3_PORT_SEL */ + {0x58, 0x5e}, /* BCC_CONFIG */ + {0x5c, 0xb0}, /* SER_ALIAS_ID */ + {0x5d, 0x6c}, /* SlaveID[0] */ + {0x65, 0x60}, /* SlaveAlias[0] */ + {0x6d, 0x7c}, /* PORT_CONFIG */ + {0x7c, 0x01}, /* PORT_CONFIG2 */ + {0x70, 0x2b}, /* RAW10_ID */ + {0x71, 0x2c}, /* RAW12_ID */ + {0x72, 0xe4}, /* CSI_VC_MAP */ + {0x4c, 0x12}, /* FPD3_PORT_SEL */ + {0x58, 0x5e}, + {0x5c, 0xb2}, + {0x5d, 0x6c}, + {0x65, 0x62}, + {0x6d, 0x7c}, + {0x7c, 0x01}, + {0x70, 0x2b}, + {0x71, 0x2c}, + {0x72, 0xee}, /* CSI_VC_MAP */ + {0x4c, 0x24}, /* FPD3_PORT_SEL */ + {0x58, 0x5e}, + {0x5c, 0xb4}, + {0x5d, 0x6c}, + {0x65, 0x64}, + {0x6d, 0x7c}, + {0x7c, 0x01}, + {0x70, 0x2b}, + {0x71, 0x2c}, + {0x72, 0xe4}, + {0x4c, 0x38}, /* FPD3_PORT_SEL */ + {0x58, 0x5e}, + {0x5c, 0xb6}, + {0x5d, 0x6c}, + {0x65, 0x66}, + {0x6d, 0x7c}, + {0x7c, 0x01}, + {0x70, 0x2b}, + {0x71, 0x2c}, + {0x72, 0xe4}, + {0xb0, 0x14}, /* FPD3 RX Shared Reg */ + {0xb1, 0x03}, + {0xb2, 0x04}, + {0xb1, 0x04}, + {0xb2, 0x04}, + {0x32, 0x12}, /* select TX1 R/W */ + {0x33, 0x01}, /* CSI_ENABLE */ + {0x34, 0x08}, /* CSI_PASS_MODE all */ + {0x32, 0x01}, /* select TX0 R/W */ + {0x33, 0x01}, /* CSI_ENABLE */ + {0x34, 0x08}, /* CSI_PASS_MODE all */ + {0x20, 0xf0}, + {0x21, 0x03}, +}; + +/* register definition */ +#define TI960_DEVID 0x0 +#define TI960_RESET 0x1 +#define TI960_CSI_PLL_CTL 0x1f +#define TI960_FS_CTL 0x18 +#define TI960_FWD_CTL1 0x20 +#define TI960_RX_PORT_SEL 0x4c +#define TI960_SER_ALIAS_ID 0x5c +#define TI960_SLAVE_ID0 0x5d +#define TI960_SLAVE_ALIAS_ID0 0x65 +#define TI960_PORT_CONFIG 0x6d +#define TI960_BC_GPIO_CTL0 0x6e +#define TI960_BC_GPIO_CTL1 0x6f +#define TI960_RAW10_ID 0x70 +#define TI960_RAW12_ID 0x71 +#define TI960_CSI_VC_MAP 0x72 +#define TI960_PORT_CONFIG2 0x7c +#define TI960_CSI_PORT_SEL 0x32 +#define TI960_CSI_CTL 0x33 +#define TI960_CSI_CTL2 0x34 + +/* register value definition */ +#define TI960_POWER_ON 0x1 +#define TI960_POWER_OFF 0x20 +#define TI960_FPD3_RAW10_100MHz 0x7f +#define TI960_FPD3_RAW12_50MHz 0x7d +#define TI960_FPD3_RAW12_75MHz 0x7e +#define TI960_FPD3_CSI 0x7c +#define TI960_RAW12 0x41 +#define TI960_RAW10_NORMAL 0x1 +#define TI960_RAW10_8BIT 0x81 +#define TI960_GPIO0_HIGH 0x09 +#define TI960_GPIO0_LOW 0x08 +#define TI960_GPIO1_HIGH 0x90 +#define TI960_GPIO1_LOW 0x80 +#define TI960_GPIO0_FSIN 0x0a +#define TI960_GPIO1_FSIN 0xa0 +#define TI960_GPIO0_MASK 0x0f +#define TI960_GPIO1_MASK 0xf0 +#define TI960_GPIO2_FSIN 0x0a +#define TI960_GPIO3_FSIN 0xa0 +#define TI960_GPIO2_MASK 0x0f +#define TI960_GPIO3_MASK 0xf0 +#define TI960_MIPI_400MBPS 0x3 +#define TI960_MIPI_800MBPS 0x2 +#define TI960_MIPI_1200MBPS 0x1 +#define TI960_MIPI_1600MBPS 0x0 +#define TI960_CSI_ENABLE 0x1 +#define TI960_CSI_CONTS_CLOCK 0x2 +#define TI960_CSI_SKEWCAL 0x40 +#define TI960_FSIN_ENABLE 0x1 +#define TI960_CSI_2LANE 0x20 +#define TI960_CSI_4LANE 0x00 + +#endif diff --git a/drivers/media/i2c/ti964-reg.h b/drivers/media/i2c/ti964-reg.h new file mode 100644 index 000000000000..940f7d85da8b --- /dev/null +++ b/drivers/media/i2c/ti964-reg.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2016 - 2020 Intel Corporation */ + +#ifndef TI964_REG_H +#define TI964_REG_H + +struct ti964_register_write { + u8 reg; + u8 val; +}; + +static const struct ti964_register_write ti964_frame_sync_settings[2][5] = { + { + {0x18, 0x00}, /* Disable frame sync. */ + {0x19, 0x00}, + {0x1a, 0x02}, + {0x1b, 0x0a}, + {0x1c, 0xd3}, + }, + { + {0x19, 0x01}, /* Frame sync high time.*/ + {0x1a, 0x15}, + {0x1b, 0x09}, /* Frame sync low time. */ + {0x1c, 0xC3}, + {0x18, 0x01}, /* Enable frame sync. and use high/low mode */ + } +}; + +static const struct ti964_register_write ti964_init_settings[] = { + {0x8, 0x1c}, + {0xa, 0x79}, + {0xb, 0x79}, + {0xd, 0xb9}, + {0x10, 0x91}, + {0x11, 0x85}, + {0x12, 0x89}, + {0x13, 0xc1}, + {0x17, 0xe1}, + {0x18, 0x0}, /* Disable frame sync. */ + {0x19, 0x0}, /* Frame sync high time. */ + {0x1a, 0x2}, + {0x1b, 0xa}, /* Frame sync low time. */ + {0x1c, 0xd3}, + {0x21, 0x43}, /* Enable best effort mode. */ + {0xb0, 0x10}, + {0xb1, 0x14}, + {0xb2, 0x1f}, + {0xb3, 0x8}, + {0x32, 0x1}, /* Select CSI port 0 */ + {0x4c, 0x1}, /* Select RX port 0 */ + {0x58, 0x58}, + {0x5c, 0x18}, /* TI913 alias addr 0xc */ + {0x6d, 0x7f}, + {0x70, 0x1e}, /* YUV422_8 */ + {0x7c, 0x81}, /* Use RAW10 8bit mode */ + {0xd2, 0x84}, + {0x4c, 0x12}, /* Select RX port 1 */ + {0x58, 0x58}, + {0x5c, 0x1a}, /* TI913 alias addr 0xd */ + {0x6d, 0x7f}, + {0x70, 0x5e}, /* YUV422_8 */ + {0x7c, 0x81}, /* Use RAW10 8bit mode */ + {0xd2, 0x84}, + {0x4c, 0x24}, /* Select RX port 2*/ + {0x58, 0x58}, + {0x5c, 0x1c}, /* TI913 alias addr 0xe */ + {0x6d, 0x7f}, + {0x70, 0x9e}, /* YUV422_8 */ + {0x7c, 0x81}, /* Use RAW10 8bit mode */ + {0xd2, 0x84}, + {0x4c, 0x38}, /* Select RX port3 */ + {0x58, 0x58}, + {0x5c, 0x1e}, /* TI913 alias addr 0xf */ + {0x6d, 0x7f}, + {0x70, 0xde}, /* YUV422_8 */ + {0x7c, 0x81}, /* Use RAW10 8bit mode */ + {0xd2, 0x84}, + {0xbc, 0x00}, +}; + +static const struct ti964_register_write ti964_tp_settings[] = { + {0xb0, 0x0}, + {0xb1, 0x02}, + {0xb2, 0xb3}, + {0xb1, 0x01}, +}; +/*register definition */ +#define TI964_DEVID 0x0 +#define TI964_RESET 0x1 +#define TI964_CSI_PLL_CTL 0x1f +#define TI964_FS_CTL 0x18 +#define TI964_FWD_CTL1 0x20 +#define TI964_RX_PORT_SEL 0x4c +#define TI964_SLAVE_ID0 0x5d +#define TI964_SLAVE_ALIAS_ID0 0x65 +#define TI964_PORT_CONFIG 0x6d +#define TI964_BC_GPIO_CTL0 0x6e +#define TI964_RAW10_ID 0x70 +#define TI964_RAW12_ID 0x71 +#define TI964_PORT_CONFIG2 0x7c + +#define TI964_IND_ACC_DATA 0xb2 +#define TI964_CSI_CTL 0x33 + +/* register value definition */ +#define TI964_POWER_ON 0x1 +#define TI964_POWER_OFF 0x20 +#define TI964_FPD3_RAW10_100MHz 0x7f +#define TI964_FPD3_RAW12_50MHz 0x7d +#define TI964_FPD3_RAW12_75MHz 0x7e +#define TI964_RAW12 0x41 +#define TI964_RAW10_NORMAL 0x1 +#define TI964_RAW10_8BIT 0x81 +#define TI964_GPIO0_HIGH 0x09 +#define TI964_GPIO0_LOW 0x08 +#define TI964_GPIO1_HIGH 0x90 +#define TI964_GPIO1_LOW 0x80 +#define TI964_GPIO0_FSIN 0x0a +#define TI964_GPIO1_FSIN 0xa0 +#define TI964_GPIO0_MASK 0x0f +#define TI964_GPIO1_MASK 0xf0 +#define TI964_MIPI_800MBPS 0x2 +#define TI964_MIPI_1600MBPS 0x0 +#define TI964_CSI_ENABLE 0x1 +#define TI964_CSI_CONTS_CLOCK 0x2 +#define TI964_CSI_SKEWCAL 0x40 +#define TI964_FSIN_ENABLE 0x1 +#endif diff --git a/drivers/media/i2c/ti964.c b/drivers/media/i2c/ti964.c new file mode 100644 index 000000000000..124c55cb9810 --- /dev/null +++ b/drivers/media/i2c/ti964.c @@ -0,0 +1,1342 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2016 - 2023 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ti964-reg.h" + +struct ti964_subdev { + struct v4l2_subdev *sd; + unsigned short rx_port; + unsigned short fsin_gpio; + unsigned short phy_i2c_addr; + unsigned short alias_i2c_addr; + char sd_name[16]; +}; + +struct ti964 { + struct v4l2_subdev sd; + struct media_pad pad[NR_OF_TI964_PADS]; + struct v4l2_ctrl_handler ctrl_handler; + struct ti964_pdata *pdata; + struct ti964_subdev sub_devs[NR_OF_TI964_SINK_PADS]; + struct ti964_platform_data subdev_pdata[NR_OF_TI964_SINK_PADS]; + const char *name; + + struct mutex mutex; + + struct regmap *regmap8; + struct regmap *regmap16; + + struct v4l2_mbus_framefmt *ffmts[NR_OF_TI964_PADS]; + struct rect *crop; + struct rect *compose; + + struct { + unsigned int *stream_id; + } *stream; /* stream enable/disable status, indexed by pad */ + struct { + unsigned int sink; + unsigned int source; + int flags; + } *route; /* pad level info, indexed by stream */ + + unsigned int nsinks; + unsigned int nsources; + unsigned int nstreams; + unsigned int npads; + + struct gpio_chip gc; + + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *test_pattern; +}; + +#define to_ti964(_sd) container_of(_sd, struct ti964, sd) + +static const s64 ti964_op_sys_clock[] = {400000000, 800000000}; +static const u8 ti964_op_sys_clock_reg_val[] = { + TI964_MIPI_800MBPS, + TI964_MIPI_1600MBPS +}; + +/* + * Order matters. + * + * 1. Bits-per-pixel, descending. + * 2. Bits-per-pixel compressed, descending. + * 3. Pixel order, same as in pixel_order_str. Formats for all four pixel + * orders must be defined. + */ +static const struct ti964_csi_data_format va_csi_data_formats[] = { + { MEDIA_BUS_FMT_YUYV8_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x1e }, + { MEDIA_BUS_FMT_UYVY8_1X16, 16, 16, PIXEL_ORDER_GBRG, 0x1e }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12, 12, PIXEL_ORDER_GRBG, 0x2c }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12, 12, PIXEL_ORDER_RGGB, 0x2c }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12, 12, PIXEL_ORDER_BGGR, 0x2c }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12, 12, PIXEL_ORDER_GBRG, 0x2c }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10, 10, PIXEL_ORDER_GRBG, 0x2b }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10, 10, PIXEL_ORDER_RGGB, 0x2b }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10, 10, PIXEL_ORDER_BGGR, 0x2b }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10, 10, PIXEL_ORDER_GBRG, 0x2b }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8, 8, PIXEL_ORDER_GRBG, 0x2a }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8, 8, PIXEL_ORDER_RGGB, 0x2a }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8, 8, PIXEL_ORDER_BGGR, 0x2a }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8, 8, PIXEL_ORDER_GBRG, 0x2a }, +}; + +static const uint32_t ti964_supported_codes_pad[] = { + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + 0, +}; + +static const uint32_t *ti964_supported_codes[] = { + ti964_supported_codes_pad, +}; + +static struct regmap_config ti964_reg_config8 = { + .reg_bits = 8, + .val_bits = 8, +}; + +static struct regmap_config ti964_reg_config16 = { + .reg_bits = 16, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int ti964_reg_set_bit(struct ti964 *va, unsigned char reg, + unsigned char bit, unsigned char val) +{ + int ret; + unsigned int reg_val; + + ret = regmap_read(va->regmap8, reg, ®_val); + if (ret) + return ret; + if (val) + reg_val |= 1 << bit; + else + reg_val &= ~(1 << bit); + + return regmap_write(va->regmap8, reg, reg_val); +} + +static int ti964_map_phy_i2c_addr(struct ti964 *va, unsigned short rx_port, + unsigned short addr) +{ + int rval; + + rval = regmap_write(va->regmap8, TI964_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + return regmap_write(va->regmap8, TI964_SLAVE_ID0, addr); +} + +static int ti964_map_alias_i2c_addr(struct ti964 *va, unsigned short rx_port, + unsigned short addr) +{ + int rval; + + rval = regmap_write(va->regmap8, TI964_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + return regmap_write(va->regmap8, TI964_SLAVE_ALIAS_ID0, addr); +} + +static int ti964_fsin_gpio_init(struct ti964 *va, unsigned short rx_port, + unsigned short fsin_gpio) +{ + int rval; + int reg_val; + + rval = regmap_read(va->regmap8, TI964_FS_CTL, ®_val); + if (rval) { + dev_dbg(va->sd.dev, "Failed to read gpio status.\n"); + return rval; + } + + if (!reg_val & TI964_FSIN_ENABLE) { + dev_dbg(va->sd.dev, "FSIN not enabled, skip config FSIN GPIO.\n"); + return 0; + } + + rval = regmap_write(va->regmap8, TI964_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) + return rval; + + rval = regmap_read(va->regmap8, TI964_BC_GPIO_CTL0, ®_val); + if (rval) { + dev_dbg(va->sd.dev, "Failed to read gpio status.\n"); + return rval; + } + + if (fsin_gpio == 0) { + reg_val &= ~TI964_GPIO0_MASK; + reg_val |= TI964_GPIO0_FSIN; + } else { + reg_val &= ~TI964_GPIO1_MASK; + reg_val |= TI964_GPIO1_FSIN; + } + + rval = regmap_write(va->regmap8, TI964_BC_GPIO_CTL0, reg_val); + if (rval) + dev_dbg(va->sd.dev, "Failed to set gpio.\n"); + + return rval; +} + +static int ti964_get_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_routing *route) +{ + struct ti964 *va = to_ti964(sd); + int i; + + for (i = 0; i < min(va->nstreams, route->num_routes); ++i) { + unsigned int sink = va->route[i].sink; + unsigned int source = va->route[i].source; + + route->routes[i].sink_pad = sink; + route->routes[i].sink_stream = + va->stream[sink].stream_id[0]; + route->routes[i].source_pad = source; + route->routes[i].source_stream = + va->stream[source].stream_id[sink]; + route->routes[i].flags = va->route[i].flags; + } + + route->num_routes = i; + + return 0; +} + +static int ti964_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_routing *route) +{ + struct ti964 *va = to_ti964(sd); + int i, j, ret = 0; + + for (i = 0; i < min(route->num_routes, va->nstreams); ++i) { + struct v4l2_subdev_route *t = &route->routes[i]; + unsigned int sink = t->sink_pad; + unsigned int source = t->source_pad; + + if (t->sink_stream > va->nstreams - 1 || + t->source_stream > va->nstreams - 1) + continue; + + if (t->source_pad != TI964_PAD_SOURCE) + continue; + + for (j = 0; j < va->nstreams; j++) { + if (sink == va->route[j].sink && + source == va->route[j].source) + break; + } + + if (j == va->nstreams) + continue; + + va->stream[sink].stream_id[0] = t->sink_stream; + va->stream[source].stream_id[sink] = t->source_stream; + + if (t->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) + va->route[j].flags |= + V4L2_SUBDEV_ROUTE_FL_ACTIVE; + else if (!(t->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + va->route[j].flags &= + (~V4L2_SUBDEV_ROUTE_FL_ACTIVE); + } + + return ret; +} + +static int ti964_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ti964 *va = to_ti964(sd); + const uint32_t *supported_code = + ti964_supported_codes[code->pad]; + bool next_stream = false; + int i; + + if (code->stream & V4L2_SUBDEV_FLAG_NEXT_STREAM) { + next_stream = true; + code->stream &= ~V4L2_SUBDEV_FLAG_NEXT_STREAM; + } + + if (code->stream > va->nstreams) + return -EINVAL; + + if (next_stream) { + if (!(va->pad[code->pad].flags & MEDIA_PAD_FL_MULTIPLEX)) + return -EINVAL; + if (code->stream < va->nstreams - 1) { + code->stream++; + return 0; + } else { + return -EINVAL; + } + } + + for (i = 0; supported_code[i]; i++) { + if (i == code->index) { + code->code = supported_code[i]; + return 0; + } + } + + return -EINVAL; +} + +static const struct ti964_csi_data_format + *ti964_validate_csi_data_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(va_csi_data_formats); i++) { + if (va_csi_data_formats[i].code == code) + return &va_csi_data_formats[i]; + } + + return &va_csi_data_formats[0]; +} + +static int ti964_get_frame_desc(struct v4l2_subdev *sd, + unsigned int pad, struct v4l2_mbus_frame_desc *desc) +{ + struct ti964 *va = to_ti964(sd); + struct v4l2_mbus_frame_desc_entry *entry = desc->entry; + u8 vc = 0; + int i; + + desc->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + desc->num_entries = min_t(int, va->nstreams, V4L2_FRAME_DESC_ENTRY_MAX); + + for (i = 0; i < desc->num_entries; i++) { + struct v4l2_mbus_framefmt *ffmt = + &va->ffmts[TI964_PAD_SOURCE][i]; + const struct ti964_csi_data_format *csi_format = + ti964_validate_csi_data_format(ffmt->code); + + entry->two_dim.width = ffmt->width; + entry->two_dim.height = ffmt->height; + entry->pixelcode = ffmt->code; + entry->bus.csi2.channel = vc++; + entry->bpp = csi_format->compressed; + entry++; + } + + return 0; +} + +static struct v4l2_mbus_framefmt * +__ti964_get_ffmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + unsigned int pad, unsigned int which, + unsigned int stream) +{ + struct ti964 *va = to_ti964(subdev); + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(subdev, sd_state, pad); + else + return &va->ffmts[pad][stream]; +} + +static int ti964_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ti964 *va = to_ti964(subdev); + + if (fmt->stream > va->nstreams) + return -EINVAL; + + mutex_lock(&va->mutex); + fmt->format = *__ti964_get_ffmt(subdev, sd_state, fmt->pad, + fmt->which, fmt->stream); + mutex_unlock(&va->mutex); + + dev_dbg(subdev->dev, "subdev_format: which: %s, pad: %d, stream: %d.\n", + fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE ? + "V4L2_SUBDEV_FORMAT_ACTIVE" : "V4L2_SUBDEV_FORMAT_TRY", + fmt->pad, fmt->stream); + + dev_dbg(subdev->dev, "framefmt: width: %d, height: %d, code: 0x%x.\n", + fmt->format.width, fmt->format.height, fmt->format.code); + + return 0; +} + +static int ti964_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ti964 *va = to_ti964(subdev); + const struct ti964_csi_data_format *csi_format; + struct v4l2_mbus_framefmt *ffmt; + + if (fmt->stream > va->nstreams) + return -EINVAL; + + csi_format = ti964_validate_csi_data_format( + fmt->format.code); + + mutex_lock(&va->mutex); + ffmt = __ti964_get_ffmt(subdev, sd_state, fmt->pad, fmt->which, + fmt->stream); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + ffmt->width = fmt->format.width; + ffmt->height = fmt->format.height; + ffmt->code = csi_format->code; + } + fmt->format = *ffmt; + mutex_unlock(&va->mutex); + + dev_dbg(subdev->dev, "framefmt: width: %d, height: %d, code: 0x%x.\n", + ffmt->width, ffmt->height, ffmt->code); + + return 0; +} + +static int ti964_open(struct v4l2_subdev *subdev, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(subdev, fh->state, 0); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .pad = TI964_PAD_SOURCE, + .format = { + .width = TI964_MAX_WIDTH, + .height = TI964_MAX_HEIGHT, + .code = MEDIA_BUS_FMT_YUYV8_1X16, + }, + .stream = 0, + }; + + *try_fmt = fmt.format; + + return 0; +} + +static int ti964_registered(struct v4l2_subdev *subdev) +{ + struct ti964 *va = to_ti964(subdev); + struct i2c_client *client = v4l2_get_subdevdata(subdev); + int i, j, k, l, rval; + + for (i = 0, k = 0; i < va->pdata->subdev_num; i++) { + struct ti964_subdev_info *info = + &va->pdata->subdev_info[i]; + struct ti964_platform_data *pdata = + (struct ti964_platform_data *) + info->board_info.platform_data; + + if (k >= va->nsinks) + break; + + /* + * The sensors should not share the same pdata structure. + * Clone the pdata for each sensor. + */ + memcpy(&va->subdev_pdata[k], pdata, sizeof(*pdata)); + if (va->subdev_pdata[k].xshutdown != 0 && + va->subdev_pdata[k].xshutdown != 1) { + dev_err(va->sd.dev, "xshutdown(%d) must be 0 or 1 to connect.\n", + va->subdev_pdata[k].xshutdown); + return -EINVAL; + } + + /* If 0 is xshutdown, then 1 would be FSIN, vice versa. */ + va->sub_devs[k].fsin_gpio = 1 - va->subdev_pdata[k].xshutdown; + + /* Spin sensor subdev suffix name */ + va->subdev_pdata[k].suffix = info->suffix; + + /* + * Change the gpio value to have xshutdown + * and rx port included, so in gpio_set those + * can be caculated from it. + */ + va->subdev_pdata[k].xshutdown += va->gc.base + + info->rx_port * NR_OF_GPIOS_PER_PORT; + info->board_info.platform_data = &va->subdev_pdata[k]; + + if (!info->phy_i2c_addr || !info->board_info.addr) { + dev_err(va->sd.dev, "can't find the physical and alias addr.\n"); + return -EINVAL; + } + + /* Map PHY I2C address. */ + rval = ti964_map_phy_i2c_addr(va, info->rx_port, + info->phy_i2c_addr); + if (rval) + return rval; + + /* Map 7bit ALIAS I2C address. */ + rval = ti964_map_alias_i2c_addr(va, info->rx_port, + info->board_info.addr << 1); + if (rval) + return rval; + + /* aggre and subdves share the same i2c bus */ + va->sub_devs[k].sd = v4l2_i2c_new_subdev_board( + va->sd.v4l2_dev, client->adapter, + &info->board_info, 0); + if (!va->sub_devs[k].sd) { + dev_err(va->sd.dev, + "can't create new i2c subdev %d-%04x\n", + info->i2c_adapter_id, + info->board_info.addr); + continue; + } + va->sub_devs[k].rx_port = info->rx_port; + va->sub_devs[k].phy_i2c_addr = info->phy_i2c_addr; + va->sub_devs[k].alias_i2c_addr = info->board_info.addr; + memcpy(va->sub_devs[k].sd_name, + va->subdev_pdata[k].module_name, + min(sizeof(va->sub_devs[k].sd_name) - 1, + sizeof(va->subdev_pdata[k].module_name) - 1)); + + for (j = 0; j < va->sub_devs[k].sd->entity.num_pads; j++) { + if (va->sub_devs[k].sd->entity.pads[j].flags & + MEDIA_PAD_FL_SOURCE) + break; + } + + if (j == va->sub_devs[k].sd->entity.num_pads) { + dev_warn(va->sd.dev, + "no source pad in subdev %d-%04x\n", + info->i2c_adapter_id, + info->board_info.addr); + return -ENOENT; + } + + for (l = 0; l < va->nsinks; l++) { + rval = media_create_pad_link( + &va->sub_devs[k].sd->entity, j, + &va->sd.entity, l, 0); + if (rval) { + dev_err(va->sd.dev, + "can't create link to %d-%04x\n", + info->i2c_adapter_id, + info->board_info.addr); + return -EINVAL; + } + } + k++; + } + + return 0; +} + +static int ti964_set_power(struct v4l2_subdev *subdev, int on) +{ + struct ti964 *va = to_ti964(subdev); + int ret; + u8 val; + + ret = regmap_write(va->regmap8, TI964_RESET, + (on) ? TI964_POWER_ON : TI964_POWER_OFF); + if (ret || !on) + return ret; + + /* Configure MIPI clock bsaed on control value. */ + ret = regmap_write(va->regmap8, TI964_CSI_PLL_CTL, + ti964_op_sys_clock_reg_val[ + v4l2_ctrl_g_ctrl(va->link_freq)]); + if (ret) + return ret; + val = TI964_CSI_ENABLE; + val |= TI964_CSI_CONTS_CLOCK; + /* Enable skew calculation when 1.6Gbps output is enabled. */ + if (v4l2_ctrl_g_ctrl(va->link_freq)) + val |= TI964_CSI_SKEWCAL; + return regmap_write(va->regmap8, TI964_CSI_CTL, val); +} + +static bool ti964_broadcast_mode(struct v4l2_subdev *subdev) +{ + struct ti964 *va = to_ti964(subdev); + struct v4l2_subdev_format fmt = { 0 }; + struct v4l2_subdev *sd; + char *sd_name = NULL; + bool first = true; + unsigned int h = 0, w = 0, code = 0; + bool single_stream = true; + int i, rval; + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + struct media_pad *remote_pad = + media_entity_remote_pad(&va->pad[i]); + + if (!remote_pad) + continue; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt.pad = remote_pad->index; + fmt.stream = 0; + + rval = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); + if (rval) + return false; + + if (first) { + sd_name = va->sub_devs[i].sd_name; + h = fmt.format.height; + w = fmt.format.width; + code = fmt.format.code; + first = false; + } else { + if (strncmp(sd_name, va->sub_devs[i].sd_name, 16)) + return false; + + if (h != fmt.format.height || w != fmt.format.width + || code != fmt.format.code) + return false; + + single_stream = false; + } + } + + if (single_stream) + return false; + + return true; +} + +static int ti964_tp_set_stream(struct v4l2_subdev *subdev, int enable) +{ + struct ti964 *va = to_ti964(subdev); + int i, rval; + + dev_dbg(va->sd.dev, "TI964 starts to stream test pattern.\n"); + for (i = 0; i < ARRAY_SIZE(ti964_tp_settings); i++) { + rval = regmap_write(va->regmap8, + ti964_tp_settings[i].reg, + ti964_tp_settings[i].val); + if (rval) { + dev_err(va->sd.dev, "Register write error.\n"); + return rval; + } + } + + rval = regmap_write(va->regmap8, TI964_IND_ACC_DATA, enable); + if (rval) { + dev_err(va->sd.dev, "Register write error.\n"); + return rval; + } + + return 0; +} + +static int ti964_rx_port_config(struct ti964 *va, int sink, int rx_port) +{ + int rval; + u8 bpp; + int port_cfg2_val; + int vc_mode_reg_index; + int vc_mode_reg_val; + int mipi_dt_type; + int high_fv_flags = va->subdev_pdata[sink].high_framevalid_flags; + + /* Select RX port. */ + rval = regmap_write(va->regmap8, TI964_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (rval) { + dev_err(va->sd.dev, "Failed to select RX port.\n"); + return rval; + } + + /* Set RX port mode. */ + bpp = ti964_validate_csi_data_format( + va->ffmts[sink][0].code)->width; + rval = regmap_write(va->regmap8, TI964_PORT_CONFIG, + (bpp == 12) ? + TI964_FPD3_RAW12_75MHz : TI964_FPD3_RAW10_100MHz); + if (rval) { + dev_err(va->sd.dev, "Failed to set port config.\n"); + return rval; + } + + mipi_dt_type = ti964_validate_csi_data_format( + va->ffmts[sink][0].code)->mipi_dt_code; + /* + * RAW8 and YUV422 need to enable RAW10 bit mode. + * RAW12 need to set the RAW10_8bit to reserved. + */ + switch (bpp) { + case 8: + case 16: + port_cfg2_val = TI964_RAW10_8BIT & (~high_fv_flags); + vc_mode_reg_index = TI964_RAW10_ID; + break; + case 12: + port_cfg2_val = TI964_RAW12; + vc_mode_reg_index = TI964_RAW12_ID; + break; + default: + port_cfg2_val = TI964_RAW10_NORMAL & (~high_fv_flags); + vc_mode_reg_index = TI964_RAW10_ID; + break; + } + + vc_mode_reg_val = mipi_dt_type | sink << 6; + rval = regmap_write(va->regmap8, vc_mode_reg_index, vc_mode_reg_val); + if (rval) { + dev_err(va->sd.dev, "Failed to set virtual channel & data type.\n"); + return rval; + } + + rval = regmap_write(va->regmap8, TI964_PORT_CONFIG2, port_cfg2_val); + if (rval) { + dev_err(va->sd.dev, "Failed to set port config2.\n"); + return rval; + } + + return 0; +} + +static int ti964_map_subdevs_addr(struct ti964 *va) +{ + unsigned short rx_port, phy_i2c_addr, alias_i2c_addr; + int i, rval; + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + rx_port = va->sub_devs[i].rx_port; + phy_i2c_addr = va->sub_devs[i].phy_i2c_addr; + alias_i2c_addr = va->sub_devs[i].alias_i2c_addr; + + if (!phy_i2c_addr || !alias_i2c_addr) + continue; + + rval = ti964_map_phy_i2c_addr(va, rx_port, phy_i2c_addr); + if (rval) + return rval; + + /* set 7bit alias i2c addr */ + rval = ti964_map_alias_i2c_addr(va, rx_port, + alias_i2c_addr << 1); + if (rval) + return rval; + } + + return 0; +} + +static int ti964_find_subdev_index(struct ti964 *va, struct v4l2_subdev *sd) +{ + int i; + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + if (va->sub_devs[i].sd == sd) + return i; + } + + WARN_ON(1); + + return -EINVAL; +} + +static int ti964_set_frame_sync(struct ti964 *va, int enable) +{ + int i, rval; + int index = !!enable; + + for (i = 0; i < ARRAY_SIZE(ti964_frame_sync_settings[index]); i++) { + rval = regmap_write(va->regmap8, + ti964_frame_sync_settings[index][i].reg, + ti964_frame_sync_settings[index][i].val); + if (rval) { + dev_err(va->sd.dev, "Failed to %s frame sync\n", + enable ? "enable" : "disable"); + return rval; + } + } + + return 0; +} + +static int ti964_set_stream(struct v4l2_subdev *subdev, int enable) +{ + struct ti964 *va = to_ti964(subdev); + struct v4l2_subdev *sd; + int i, j, rval; + bool broadcast; + unsigned int rx_port; + int sd_idx = -1; + DECLARE_BITMAP(rx_port_enabled, 32); + + dev_dbg(va->sd.dev, "TI964 set stream, enable %d\n", enable); + + if (v4l2_ctrl_g_ctrl(va->test_pattern)) + return ti964_tp_set_stream(subdev, enable); + + broadcast = ti964_broadcast_mode(subdev); + if (enable) + dev_info(va->sd.dev, "TI964 in %s mode", + broadcast ? "broadcast" : "non broadcast"); + + bitmap_zero(rx_port_enabled, 32); + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + struct media_pad *remote_pad = + media_entity_remote_pad(&va->pad[i]); + + if (!remote_pad) + continue; + + /* Find ti964 subdev */ + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + j = ti964_find_subdev_index(va, sd); + if (j < 0) + return -EINVAL; + rx_port = va->sub_devs[j].rx_port; + rval = ti964_rx_port_config(va, i, rx_port); + if (rval < 0) + return rval; + + bitmap_set(rx_port_enabled, rx_port, 1); + + if (broadcast && sd_idx == -1) { + sd_idx = j; + } else if (broadcast) { + rval = ti964_map_alias_i2c_addr(va, rx_port, + va->sub_devs[sd_idx].alias_i2c_addr << 1); + if (rval < 0) + return rval; + } else { + /* Stream on/off sensor */ + rval = v4l2_subdev_call(sd, video, s_stream, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set stream for %s, enable %d\n", + sd->name, enable); + return rval; + } + + /* RX port fordward */ + rval = ti964_reg_set_bit(va, TI964_FWD_CTL1, + rx_port + 4, !enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to forward RX port%d. enable %d\n", + i, enable); + return rval; + } + + } + } + + if (broadcast) { + if (sd_idx < 0) { + dev_err(va->sd.dev, "No sensor connected!\n"); + return -ENODEV; + } + sd = va->sub_devs[sd_idx].sd; + rval = v4l2_subdev_call(sd, video, s_stream, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set stream for %s. enable %d\n", + sd->name, enable); + return rval; + } + + rval = ti964_set_frame_sync(va, enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to set frame sync.\n"); + return rval; + } + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + if (enable && test_bit(i, rx_port_enabled)) { + rval = ti964_fsin_gpio_init(va, + va->sub_devs[i].rx_port, + va->sub_devs[i].fsin_gpio); + if (rval) { + dev_err(va->sd.dev, + "Failed to enable frame sync gpio init.\n"); + return rval; + } + } + } + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + if (!test_bit(i, rx_port_enabled)) + continue; + + /* RX port fordward */ + rval = ti964_reg_set_bit(va, TI964_FWD_CTL1, + i + 4, !enable); + if (rval) { + dev_err(va->sd.dev, + "Failed to forward RX port%d. enable %d\n", + i, enable); + return rval; + } + } + + /* + * Restore each subdev i2c address as we may + * touch it later. + */ + rval = ti964_map_subdevs_addr(va); + if (rval) + return rval; + } + + return 0; +} + +static struct v4l2_subdev_internal_ops ti964_sd_internal_ops = { + .open = ti964_open, + .registered = ti964_registered, +}; + +static bool ti964_sd_has_route(struct media_entity *entity, + unsigned int pad0, unsigned int pad1, int *stream) +{ + struct ti964 *va = to_ti964(media_entity_to_v4l2_subdev(entity)); + + if (va == NULL || stream == NULL || + *stream >= va->nstreams || *stream < 0) + return false; + + if ((va->route[*stream].flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) && + ((va->route[*stream].source == pad0 && + va->route[*stream].sink == pad1) || + (va->route[*stream].source == pad1 && + va->route[*stream].sink == pad0))) + return true; + + return false; +} + +static const struct media_entity_operations ti964_sd_entity_ops = { + .has_route = ti964_sd_has_route, +}; + +static const struct v4l2_subdev_video_ops ti964_sd_video_ops = { + .s_stream = ti964_set_stream, +}; + +static const struct v4l2_subdev_core_ops ti964_core_subdev_ops = { + .s_power = ti964_set_power, +}; + +static int ti964_s_ctrl(struct v4l2_ctrl *ctrl) +{ + return 0; +} + +static const struct v4l2_ctrl_ops ti964_ctrl_ops = { + .s_ctrl = ti964_s_ctrl, +}; + +static const struct v4l2_ctrl_config ti964_controls[] = { + { + .ops = &ti964_ctrl_ops, + .id = V4L2_CID_LINK_FREQ, + .name = "V4L2_CID_LINK_FREQ", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .max = ARRAY_SIZE(ti964_op_sys_clock) - 1, + .min = 0, + .step = 0, + .def = 0, + .qmenu_int = ti964_op_sys_clock, + }, + { + .ops = &ti964_ctrl_ops, + .id = V4L2_CID_TEST_PATTERN, + .name = "V4L2_CID_TEST_PATTERN", + .type = V4L2_CTRL_TYPE_INTEGER, + .max = 1, + .min = 0, + .step = 1, + .def = 0, + }, +}; + +static const struct v4l2_subdev_pad_ops ti964_sd_pad_ops = { + .get_fmt = ti964_get_format, + .set_fmt = ti964_set_format, + .get_frame_desc = ti964_get_frame_desc, + .enum_mbus_code = ti964_enum_mbus_code, + .set_routing = ti964_set_routing, + .get_routing = ti964_get_routing, +}; + +static struct v4l2_subdev_ops ti964_sd_ops = { + .core = &ti964_core_subdev_ops, + .video = &ti964_sd_video_ops, + .pad = &ti964_sd_pad_ops, +}; + +static int ti964_register_subdev(struct ti964 *va) +{ + int i, rval; + struct i2c_client *client = v4l2_get_subdevdata(&va->sd); + + v4l2_subdev_init(&va->sd, &ti964_sd_ops); + snprintf(va->sd.name, sizeof(va->sd.name), "TI964 %c", + va->pdata->suffix); + + va->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_SUBSTREAMS; + + va->sd.internal_ops = &ti964_sd_internal_ops; + va->sd.entity.ops = &ti964_sd_entity_ops; + + v4l2_set_subdevdata(&va->sd, client); + + v4l2_ctrl_handler_init(&va->ctrl_handler, + ARRAY_SIZE(ti964_controls)); + + if (va->ctrl_handler.error) { + dev_err(va->sd.dev, + "Failed to init ti964 controls. ERR: %d!\n", + va->ctrl_handler.error); + return va->ctrl_handler.error; + } + + va->sd.ctrl_handler = &va->ctrl_handler; + + for (i = 0; i < ARRAY_SIZE(ti964_controls); i++) { + const struct v4l2_ctrl_config *cfg = + &ti964_controls[i]; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&va->ctrl_handler, cfg, NULL); + if (!ctrl) { + dev_err(va->sd.dev, + "Failed to create ctrl %s!\n", cfg->name); + rval = va->ctrl_handler.error; + goto failed_out; + } + } + + va->link_freq = v4l2_ctrl_find(&va->ctrl_handler, V4L2_CID_LINK_FREQ); + va->test_pattern = v4l2_ctrl_find(&va->ctrl_handler, + V4L2_CID_TEST_PATTERN); + + for (i = 0; i < va->nsinks; i++) + va->pad[i].flags = MEDIA_PAD_FL_SINK; + va->pad[TI964_PAD_SOURCE].flags = + MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MULTIPLEX; + rval = media_entity_pads_init(&va->sd.entity, + NR_OF_TI964_PADS, va->pad); + if (rval) { + dev_err(va->sd.dev, + "Failed to init media entity for ti964!\n"); + goto failed_out; + } + + return 0; + +failed_out: + v4l2_ctrl_handler_free(&va->ctrl_handler); + return rval; +} + +static int ti964_init(struct ti964 *va) +{ + unsigned int reset_gpio = va->pdata->reset_gpio; + int i, rval; + unsigned int val; + + gpio_set_value(reset_gpio, 1); + usleep_range(2000, 3000); + dev_dbg(va->sd.dev, "Setting reset gpio %d to 1.\n", reset_gpio); + + rval = regmap_read(va->regmap8, TI964_DEVID, &val); + if (rval) { + dev_err(va->sd.dev, "Failed to read device ID of TI964!\n"); + return rval; + } + dev_info(va->sd.dev, "TI964 device ID: 0x%X\n", val); + + for (i = 0; i < ARRAY_SIZE(ti964_init_settings); i++) { + rval = regmap_write(va->regmap8, + ti964_init_settings[i].reg, + ti964_init_settings[i].val); + if (rval) + return rval; + } + + rval = ti964_map_subdevs_addr(va); + if (rval) + return rval; + + return 0; +} + +static void ti964_gpio_set(struct gpio_chip *chip, unsigned gpio, int value) +{ + struct i2c_client *client = to_i2c_client(chip->parent); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti964 *va = to_ti964(subdev); + unsigned int reg_val; + int rx_port, gpio_port; + int ret; + + if (gpio >= NR_OF_TI964_GPIOS) + return; + + rx_port = gpio / NR_OF_GPIOS_PER_PORT; + gpio_port = gpio % NR_OF_GPIOS_PER_PORT; + + ret = regmap_write(va->regmap8, TI964_RX_PORT_SEL, + (rx_port << 4) + (1 << rx_port)); + if (ret) { + dev_dbg(&client->dev, "Failed to select RX port.\n"); + return; + } + ret = regmap_read(va->regmap8, TI964_BC_GPIO_CTL0, ®_val); + if (ret) { + dev_dbg(&client->dev, "Failed to read gpio status.\n"); + return; + } + + if (gpio_port == 0) { + reg_val &= ~TI964_GPIO0_MASK; + reg_val |= value ? TI964_GPIO0_HIGH : TI964_GPIO0_LOW; + } else { + reg_val &= ~TI964_GPIO1_MASK; + reg_val |= value ? TI964_GPIO1_HIGH : TI964_GPIO1_LOW; + } + + ret = regmap_write(va->regmap8, TI964_BC_GPIO_CTL0, reg_val); + if (ret) + dev_dbg(&client->dev, "Failed to set gpio.\n"); +} + +static int ti964_gpio_direction_output(struct gpio_chip *chip, + unsigned gpio, int level) +{ + return 0; +} + +static int ti964_probe(struct i2c_client *client, + const struct i2c_device_id *devid) +{ + struct ti964 *va; + int i, rval = 0; + + if (client->dev.platform_data == NULL) + return -ENODEV; + + va = devm_kzalloc(&client->dev, sizeof(*va), GFP_KERNEL); + if (!va) + return -ENOMEM; + + va->pdata = client->dev.platform_data; + + va->nsources = NR_OF_TI964_SOURCE_PADS; + va->nsinks = NR_OF_TI964_SINK_PADS; + va->npads = NR_OF_TI964_PADS; + va->nstreams = NR_OF_TI964_STREAMS; + + va->crop = devm_kcalloc(&client->dev, va->npads, + sizeof(struct v4l2_rect), GFP_KERNEL); + + va->compose = devm_kcalloc(&client->dev, va->npads, + sizeof(struct v4l2_rect), GFP_KERNEL); + + va->route = devm_kcalloc(&client->dev, va->nstreams, + sizeof(*va->route), GFP_KERNEL); + + va->stream = devm_kcalloc(&client->dev, va->npads, + sizeof(*va->stream), GFP_KERNEL); + + if (!va->crop || !va->compose || !va->route || !va->stream) + return -ENOMEM; + + for (i = 0; i < va->npads; i++) { + va->ffmts[i] = devm_kcalloc(&client->dev, va->nstreams, + sizeof(struct v4l2_mbus_framefmt), + GFP_KERNEL); + if (!va->ffmts[i]) + return -ENOMEM; + + va->stream[i].stream_id = + devm_kcalloc(&client->dev, va->nsinks, + sizeof(*va->stream[i].stream_id), GFP_KERNEL); + if (!va->stream[i].stream_id) + return -ENOMEM; + } + + for (i = 0; i < va->nstreams; i++) { + va->route[i].sink = i; + va->route[i].source = TI964_PAD_SOURCE; + va->route[i].flags = 0; + } + + for (i = 0; i < va->nsinks; i++) { + va->stream[i].stream_id[0] = i; + va->stream[TI964_PAD_SOURCE].stream_id[i] = i; + } + + va->regmap8 = devm_regmap_init_i2c(client, + &ti964_reg_config8); + if (IS_ERR(va->regmap8)) { + dev_err(&client->dev, "Failed to init regmap8!\n"); + return -EIO; + } + + va->regmap16 = devm_regmap_init_i2c(client, + &ti964_reg_config16); + if (IS_ERR(va->regmap16)) { + dev_err(&client->dev, "Failed to init regmap16!\n"); + return -EIO; + } + + mutex_init(&va->mutex); + v4l2_i2c_subdev_init(&va->sd, client, &ti964_sd_ops); + rval = ti964_register_subdev(va); + if (rval) { + dev_err(&client->dev, "Failed to register va subdevice!\n"); + return rval; + } + + if (devm_gpio_request_one(va->sd.dev, va->pdata->reset_gpio, 0, + "ti964 reset") != 0) { + dev_err(va->sd.dev, "Unable to acquire gpio %d\n", + va->pdata->reset_gpio); + return -ENODEV; + } + + rval = ti964_init(va); + if (rval) { + dev_err(&client->dev, "Failed to init TI964!\n"); + return rval; + } + + /* + * TI964 has several back channel GPIOs. + * We export GPIO0 and GPIO1 to control reset or fsin. + */ + va->gc.parent = &client->dev; + va->gc.owner = THIS_MODULE; + va->gc.label = "TI964 GPIO"; + va->gc.ngpio = NR_OF_TI964_GPIOS; + va->gc.base = -1; + va->gc.set = ti964_gpio_set; + va->gc.direction_output = ti964_gpio_direction_output; + rval = gpiochip_add(&va->gc); + if (rval) { + dev_err(&client->dev, "Failed to add gpio chip!\n"); + return -EIO; + } + + return 0; +} + +static int ti964_remove(struct i2c_client *client) +{ + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti964 *va = to_ti964(subdev); + int i; + + if (!va) + return 0; + + mutex_destroy(&va->mutex); + v4l2_ctrl_handler_free(&va->ctrl_handler); + v4l2_device_unregister_subdev(&va->sd); + media_entity_cleanup(&va->sd.entity); + + for (i = 0; i < NR_OF_TI964_SINK_PADS; i++) { + if (va->sub_devs[i].sd) { + struct i2c_client *sub_client = + v4l2_get_subdevdata(va->sub_devs[i].sd); + + i2c_unregister_device(sub_client); + } + va->sub_devs[i].sd = NULL; + } + + gpiochip_remove(&va->gc); + + return 0; +} + +#ifdef CONFIG_PM +static int ti964_suspend(struct device *dev) +{ + return 0; +} + +static int ti964_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *subdev = i2c_get_clientdata(client); + struct ti964 *va = to_ti964(subdev); + + return ti964_init(va); +} +#else +#define ti964_suspend NULL +#define ti964_resume NULL +#endif /* CONFIG_PM */ + +static const struct i2c_device_id ti964_id_table[] = { + { TI964_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, ti964_id_table); + +static const struct dev_pm_ops ti964_pm_ops = { + .suspend = ti964_suspend, + .resume = ti964_resume, +}; + +static struct i2c_driver ti964_i2c_driver = { + .driver = { + .name = TI964_NAME, + .pm = &ti964_pm_ops, + }, + .probe = ti964_probe, + .remove = ti964_remove, + .id_table = ti964_id_table, +}; +module_i2c_driver(ti964_i2c_driver); + +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TI964 CSI2-Aggregator driver"); diff --git a/drivers/media/pci/intel/Kconfig b/drivers/media/pci/intel/Kconfig new file mode 100644 index 000000000000..50d70ef17170 --- /dev/null +++ b/drivers/media/pci/intel/Kconfig @@ -0,0 +1,64 @@ +config VIDEO_INTEL_IPU6 + tristate "Intel IPU driver" + depends on ACPI + depends on MEDIA_SUPPORT + depends on MEDIA_PCI_SUPPORT + depends on X86_64 + select IOMMU_API + select IOMMU_IOVA + select X86_DEV_DMA_OPS if X86 + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + select PHYS_ADDR_T_64BIT + select COMMON_CLK + help + This is the Intel imaging processing unit, found in Intel SoCs and + used for capturing images and video from a camera sensor. + + To compile this driver, say Y here! It contains 3 modules - + intel_ipu*, intel_ipu*_isys and intel_ipu*_psys. + +choice + prompt "intel ipu hardware platform type" + depends on VIDEO_INTEL_IPU6 + default VIDEO_INTEL_IPU_SOC + +config VIDEO_INTEL_IPU_SOC + bool "Compile for SOC" + help + Used for real SoC hardware driver development + Select for SOC platform + +endchoice + +config VIDEO_INTEL_IPU_WERROR + tristate "Force GCC to throw an error instead of a warning when compiling" + depends on VIDEO_INTEL_IPU6 + depends on EXPERT + depends on !COMPILE_TEST + default n + help + Add -Werror to the build flags for (and only for) intel ipu module. + Do not enable this unless you are writing code for the ipu module. + + Recommended for driver developers only. + + If in doubt, say "N". + +config VIDEO_INTEL_IPU_USE_PLATFORMDATA + bool "Enable platform data" + default y + help + Enalbe platform data in IPU. + +config VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING + bool "Enable platform data dynamic loading" + depends on VIDEO_INTEL_IPU_USE_PLATFORMDATA + default n + help + Load sensor configuration data from file system. Then if there's hardware + configuration change no need to recompile the kernel. Useful in multiple port + and multiple sensor case + + If in doubt, say "N". + diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile index 19fe3aab1a18..b90ae2a83a57 100644 --- a/drivers/media/pci/intel/Makefile +++ b/drivers/media/pci/intel/Makefile @@ -1,13 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2010 - 2020 Intel Corporation. - -# force check the compile warning to make sure zero warnings -# note we may have build issue when gcc upgraded. -subdir-ccflags-y := -Wall -Wextra -subdir-ccflags-y += $(call cc-disable-warning, unused-parameter) -subdir-ccflags-y += $(call cc-disable-warning, implicit-fallthrough) -subdir-ccflags-y += $(call cc-disable-warning, missing-field-initializers) -subdir-ccflags-y += $(call cc-disable-warning, type-limits) -subdir-ccflags-$(CONFIG_VIDEO_INTEL_IPU_WERROR) += -Werror +# Copyright (c) 2010 - 2024 Intel Corporation. obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ diff --git a/drivers/media/pci/intel/ipu-bus.c b/drivers/media/pci/intel/ipu-bus.c index 0569ccb61969..b4f0268dcf10 100644 --- a/drivers/media/pci/intel/ipu-bus.c +++ b/drivers/media/pci/intel/ipu-bus.c @@ -123,21 +123,13 @@ static int ipu_bus_probe(struct device *dev) return rval; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) -static int ipu_bus_remove(struct device *dev) -#else static void ipu_bus_remove(struct device *dev) -#endif { struct ipu_bus_device *adev = to_ipu_bus_device(dev); struct ipu_bus_driver *adrv = to_ipu_bus_driver(dev->driver); if (adrv->remove) adrv->remove(adev); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) - - return 0; -#endif } static struct bus_type ipu_bus = { @@ -174,11 +166,7 @@ struct ipu_bus_device *ipu_bus_initialize_device(struct pci_dev *pdev, adev->dev.parent = parent; adev->dev.bus = &ipu_bus; adev->dev.release = ipu_bus_release; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 16) adev->dev.dma_ops = &ipu_dma_ops; -#else - adev->dev.archdata.dma_ops = &ipu_dma_ops; -#endif adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU_MMU_ADDRESS_BITS : IPU_MMU_ADDRESS_BITS_NON_SECURE); diff --git a/drivers/media/pci/intel/ipu-buttress.c b/drivers/media/pci/intel/ipu-buttress.c index 938ec3ac9312..d9bc45c68a4f 100644 --- a/drivers/media/pci/intel/ipu-buttress.c +++ b/drivers/media/pci/intel/ipu-buttress.c @@ -736,9 +736,6 @@ int ipu_buttress_map_fw_image(struct ipu_bus_device *sys, const void *addr; unsigned long n_pages; int rval, i; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 8, 0) - int nents; -#endif n_pages = PAGE_ALIGN(fw->size) >> PAGE_SHIFT; @@ -765,17 +762,6 @@ int ipu_buttress_map_fw_image(struct ipu_bus_device *sys, goto out; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 8, 0) - nents = dma_map_sg(&sys->dev, sgt->sgl, sgt->orig_nents, DMA_TO_DEVICE); - if (!nents) { - rval = -ENOMEM; - sg_free_table(sgt); - goto out; - } - sgt->nents = nents; - dma_sync_sg_for_device(&sys->dev, sgt->sgl, sgt->orig_nents, - DMA_TO_DEVICE); -#else rval = dma_map_sgtable(&sys->dev, sgt, DMA_TO_DEVICE, 0); if (rval < 0) { rval = -ENOMEM; @@ -784,7 +770,6 @@ int ipu_buttress_map_fw_image(struct ipu_bus_device *sys, } dma_sync_sgtable_for_device(&sys->dev, sgt, DMA_TO_DEVICE); -#endif out: kfree(pages); @@ -983,6 +968,46 @@ struct clk_ipu_sensor { }; #define to_clk_ipu_sensor(_hw) container_of(_hw, struct clk_ipu_sensor, hw) +/* + * The dev_id was hard code in platform data, as i2c bus number + * may change dynamiclly, we need to update this bus id + * accordingly. + * + * @adapter_id: hardware i2c adapter id, this was fixed in platform data + * return: i2c bus id registered in system + */ +int ipu_get_i2c_bus_id(int adapter_id, char *adapter_bdf, int bdf_len) +{ + struct i2c_adapter *adapter; + char name[32]; + int i = 0; + + if (adapter_bdf) { + while ((adapter = i2c_get_adapter(i)) != NULL) { + struct device *parent = adapter->dev.parent; + struct device *pp = parent->parent; + + if (pp && !strncmp(adapter_bdf, dev_name(pp), bdf_len)) + return i; + i++; + } + } + + i = 0; + snprintf(name, sizeof(name), "i2c_designware.%d", adapter_id); + while ((adapter = i2c_get_adapter(i)) != NULL) { + struct device *parent = adapter->dev.parent; + + if (parent && !strncmp(name, dev_name(parent), sizeof(name))) + return i; + i++; + } + + /* Not found, should never happen! */ + WARN_ON_ONCE(1); + return -1; +} +EXPORT_SYMBOL_GPL(ipu_get_i2c_bus_id); int ipu_buttress_tsc_read(struct ipu_device *isp, u64 *val) { diff --git a/drivers/media/pci/intel/ipu-buttress.h b/drivers/media/pci/intel/ipu-buttress.h index 2c60641f696d..25bff1138f19 100644 --- a/drivers/media/pci/intel/ipu-buttress.h +++ b/drivers/media/pci/intel/ipu-buttress.h @@ -126,4 +126,5 @@ void ipu_buttress_csi_port_config(struct ipu_device *isp, int ipu_buttress_restore(struct ipu_device *isp); int ipu_buttress_isys_freq_set(void *data, u64 val); +int ipu_get_i2c_bus_id(int adapter_id, char *adapter_bdf, int bdf_len); #endif /* IPU_BUTTRESS_H */ diff --git a/drivers/media/pci/intel/ipu-cpd.c b/drivers/media/pci/intel/ipu-cpd.c index b44e0788cb7d..b2d878414a6b 100644 --- a/drivers/media/pci/intel/ipu-cpd.c +++ b/drivers/media/pci/intel/ipu-cpd.c @@ -241,11 +241,7 @@ void *ipu_cpd_create_pkg_dir(struct ipu_bus_device *adev, *pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz; pkg_dir = dma_alloc_attrs(&adev->dev, *pkg_dir_size, dma_addr, GFP_KERNEL, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL); -#else 0); -#endif if (!pkg_dir) return pkg_dir; @@ -271,11 +267,7 @@ void *ipu_cpd_create_pkg_dir(struct ipu_bus_device *adev, "Unable to parse module data section!\n"); dma_free_attrs(&adev->dev, *pkg_dir_size, pkg_dir, *dma_addr, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL); -#else 0); -#endif return NULL; } @@ -298,11 +290,7 @@ void ipu_cpd_free_pkg_dir(struct ipu_bus_device *adev, u64 *pkg_dir, dma_addr_t dma_addr, unsigned int pkg_dir_size) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - dma_free_attrs(&adev->dev, pkg_dir_size, pkg_dir, dma_addr, NULL); -#else dma_free_attrs(&adev->dev, pkg_dir_size, pkg_dir, dma_addr, 0); -#endif } EXPORT_SYMBOL_GPL(ipu_cpd_free_pkg_dir); diff --git a/drivers/media/pci/intel/ipu-dma.c b/drivers/media/pci/intel/ipu-dma.c index 4aa97b8436ee..24ad5fecd884 100644 --- a/drivers/media/pci/intel/ipu-dma.c +++ b/drivers/media/pci/intel/ipu-dma.c @@ -13,9 +13,7 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) #include -#endif #include "ipu-dma.h" #include "ipu-bus.h" @@ -44,11 +42,7 @@ static struct vm_info *get_vm_info(struct ipu_mmu *mmu, dma_addr_t iova) /* Begin of things adapted from arch/arm/mm/dma-mapping.c */ static void __dma_clear_buffer(struct page *page, size_t size, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { /* * Ensure that the allocated pages are zeroed, and that any data @@ -57,22 +51,13 @@ static void __dma_clear_buffer(struct page *page, size_t size, void *ptr = page_address(page); memset(ptr, 0, size); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs)) - clflush_cache_range(ptr, size); -#else if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) clflush_cache_range(ptr, size); -#endif } static struct page **__dma_alloc_buffer(struct device *dev, size_t size, gfp_t gfp, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { struct page **pages; int count = size >> PAGE_SHIFT; @@ -117,11 +102,7 @@ static struct page **__dma_alloc_buffer(struct device *dev, size_t size, static int __dma_free_buffer(struct device *dev, struct page **pages, size_t size, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { int count = size >> PAGE_SHIFT; int i; @@ -173,11 +154,7 @@ static void ipu_dma_sync_sg_for_cpu(struct device *dev, static void *ipu_dma_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { struct ipu_mmu *mmu = to_ipu_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu_bus_device(dev)->isp->pdev; @@ -267,11 +244,7 @@ static void *ipu_dma_alloc(struct device *dev, size_t size, static void ipu_dma_free(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { struct ipu_mmu *mmu = to_ipu_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu_bus_device(dev)->isp->pdev; @@ -322,45 +295,6 @@ static void ipu_dma_free(struct device *dev, size_t size, void *vaddr, kfree(info); } -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) - -static int ipu_dma_mmap(struct device *dev, struct vm_area_struct *vma, - void *addr, dma_addr_t iova, size_t size, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else - unsigned long attrs) -#endif -{ - struct ipu_mmu *mmu = to_ipu_bus_device(dev)->mmu; - struct vm_info *info; - size_t count = PAGE_ALIGN(size) >> PAGE_SHIFT; - size_t i; - int ret; - - info = get_vm_info(mmu, iova); - if (!info) - return -EFAULT; - - if (!info->vaddr) - return -EFAULT; - - if (vma->vm_start & ~PAGE_MASK) - return -EINVAL; - - if (size > info->size) - return -EFAULT; - - for (i = 0; i < count; i++) { - ret = vm_insert_page(vma, vma->vm_start + (i << PAGE_SHIFT), - info->pages[i]); - if (ret < 0) - return ret; - } - - return 0; -} -#else static int ipu_dma_mmap(struct device *dev, struct vm_area_struct *vma, void *addr, dma_addr_t iova, size_t size, @@ -386,16 +320,10 @@ static int ipu_dma_mmap(struct device *dev, struct vm_area_struct *vma, return vm_insert_pages(vma, vma->vm_start, info->pages, &count); } -#endif - static void ipu_dma_unmap_sg(struct device *dev, struct scatterlist *sglist, int nents, enum dma_data_direction dir, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { int i, npages, count; struct scatterlist *sg; @@ -411,11 +339,7 @@ static void ipu_dma_unmap_sg(struct device *dev, if (WARN_ON(!iova)) return; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs)) -#else if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) -#endif ipu_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); /* get the nents as orig_nents given by caller */ @@ -460,11 +384,7 @@ static void ipu_dma_unmap_sg(struct device *dev, static int ipu_dma_map_sg(struct device *dev, struct scatterlist *sglist, int nents, enum dma_data_direction dir, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { struct ipu_mmu *mmu = to_ipu_bus_device(dev)->mmu; struct pci_dev *pdev = to_ipu_bus_device(dev)->isp->pdev; @@ -516,11 +436,7 @@ static int ipu_dma_map_sg(struct device *dev, struct scatterlist *sglist, iova_addr += PAGE_ALIGN(sg_dma_len(sg)) >> PAGE_SHIFT; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs)) -#else if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) -#endif ipu_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); mmu->tlb_invalidate(mmu); @@ -538,11 +454,7 @@ static int ipu_dma_map_sg(struct device *dev, struct scatterlist *sglist, */ static int ipu_dma_get_sgtable(struct device *dev, struct sg_table *sgt, void *cpu_addr, dma_addr_t handle, size_t size, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs *attrs) -#else unsigned long attrs) -#endif { struct ipu_mmu *mmu = to_ipu_bus_device(dev)->mmu; struct vm_info *info; diff --git a/drivers/media/pci/intel/ipu-fw-com.c b/drivers/media/pci/intel/ipu-fw-com.c index 0431ecb3ec4e..78bb63926801 100644 --- a/drivers/media/pci/intel/ipu-fw-com.c +++ b/drivers/media/pci/intel/ipu-fw-com.c @@ -263,12 +263,8 @@ void *ipu_fw_com_prepare(struct ipu_fw_com_cfg *cfg, ctx->dma_buffer = dma_alloc_attrs(&ctx->adev->dev, sizeall, &ctx->dma_addr, GFP_KERNEL, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) attrs); ctx->attrs = attrs; -#else - NULL); -#endif if (!ctx->dma_buffer) { dev_err(&ctx->adev->dev, "failed to allocate dma memory\n"); kfree(ctx); @@ -400,11 +396,7 @@ int ipu_fw_com_release(struct ipu_fw_com_context *ctx, unsigned int force) dma_free_attrs(&ctx->adev->dev, ctx->dma_size, ctx->dma_buffer, ctx->dma_addr, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) ctx->attrs); -#else - NULL); -#endif kfree(ctx); return 0; } diff --git a/drivers/media/pci/intel/ipu-isys-csi2-be-soc.c b/drivers/media/pci/intel/ipu-isys-csi2-be-soc.c index 83040911cf63..a6d18dd9d8c8 100644 --- a/drivers/media/pci/intel/ipu-isys-csi2-be-soc.c +++ b/drivers/media/pci/intel/ipu-isys-csi2-be-soc.c @@ -15,6 +15,8 @@ #include "ipu-isys-subdev.h" #include "ipu-isys-video.h" +extern int vnode_num; + /* * Raw bayer format pixel order MUST BE MAINTAINED in groups of four codes. * Otherwise pixel order calculation below WILL BREAK! @@ -39,6 +41,7 @@ static const u32 csi2_be_soc_supported_codes_pad[] = { MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SRGGB8_1X8, MEDIA_BUS_FMT_Y8_1X8, + MEDIA_BUS_FMT_FIXED, 0, }; @@ -120,11 +123,7 @@ __subdev_link_validate(struct v4l2_subdev *sd, struct media_link *link, static int ipu_isys_csi2_be_soc_set_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); @@ -135,13 +134,8 @@ ipu_isys_csi2_be_soc_set_sel(struct v4l2_subdev *sd, asd->valid_tgts[sel->pad].crop) { enum isys_subdev_prop_tgt tgt = IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *ffmt = - __ipu_isys_get_ffmt(sd, cfg, sel->pad, sel->which); -#else struct v4l2_mbus_framefmt *ffmt = __ipu_isys_get_ffmt(sd, state, sel->pad, sel->which); -#endif if (get_supported_code_index(ffmt->code) < 0) { /* Non-bayer formats can't be odd lines cropped */ @@ -155,17 +149,10 @@ ipu_isys_csi2_be_soc_set_sel(struct v4l2_subdev *sd, sel->r.height = clamp(sel->r.height, IPU_ISYS_MIN_HEIGHT, IPU_ISYS_MAX_HEIGHT); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - *__ipu_isys_get_selection(sd, cfg, sel->target, sel->pad, - sel->which) = sel->r; - ipu_isys_subdev_fmt_propagate(sd, cfg, NULL, &sel->r, - tgt, sel->pad, sel->which); -#else *__ipu_isys_get_selection(sd, state, sel->target, sel->pad, sel->which) = sel->r; ipu_isys_subdev_fmt_propagate(sd, state, NULL, &sel->r, tgt, sel->pad, sel->which); -#endif return 0; } return -EINVAL; @@ -187,63 +174,34 @@ static struct v4l2_subdev_ops csi2_be_soc_sd_ops = { }; static struct media_entity_operations csi2_be_soc_entity_ops = { - .link_validate = v4l2_subdev_link_validate, }; static void csi2_be_soc_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *ffmt = - __ipu_isys_get_ffmt(sd, cfg, fmt->pad, - fmt->which); -#else struct v4l2_mbus_framefmt *ffmt = __ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which); -#endif if (sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SINK) { if (fmt->format.field != V4L2_FIELD_ALTERNATE) fmt->format.field = V4L2_FIELD_NONE; *ffmt = fmt->format; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - ipu_isys_subdev_fmt_propagate(sd, cfg, &fmt->format, - NULL, - IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, - fmt->pad, fmt->which); -#else ipu_isys_subdev_fmt_propagate(sd, state, &fmt->format, NULL, IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which); -#endif } else if (sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SOURCE) { struct v4l2_mbus_framefmt *sink_ffmt; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_rect *r = __ipu_isys_get_selection(sd, cfg, - V4L2_SEL_TGT_CROP, fmt->pad, fmt->which); -#else struct v4l2_rect *r = __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_CROP, fmt->pad, fmt->which); -#endif struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); u32 code; int idx; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - sink_ffmt = __ipu_isys_get_ffmt(sd, cfg, 0, fmt->which); -#else sink_ffmt = __ipu_isys_get_ffmt(sd, state, 0, fmt->which); -#endif code = sink_ffmt->code; idx = get_supported_code_index(code); @@ -259,7 +217,7 @@ static void csi2_be_soc_set_ffmt(struct v4l2_subdev *sd, + (idx & ~CSI2_BE_CROP_MASK)]; } - ffmt->code = code; + ffmt->code = fmt->format.code; ffmt->width = r->width; ffmt->height = r->height; ffmt->field = sink_ffmt->field; @@ -269,10 +227,14 @@ static void csi2_be_soc_set_ffmt(struct v4l2_subdev *sd, void ipu_isys_csi2_be_soc_cleanup(struct ipu_isys_csi2_be_soc *csi2_be_soc) { + int i; + v4l2_device_unregister_subdev(&csi2_be_soc->asd.sd); ipu_isys_subdev_cleanup(&csi2_be_soc->asd); - v4l2_ctrl_handler_free(&csi2_be_soc->av.ctrl_handler); - ipu_isys_video_cleanup(&csi2_be_soc->av); + for (i = 0; i < vnode_num; i++) { + v4l2_ctrl_handler_free(&csi2_be_soc->av[i].ctrl_handler); + ipu_isys_video_cleanup(&csi2_be_soc->av[i]); + } } int ipu_isys_csi2_be_soc_init(struct ipu_isys_csi2_be_soc *csi2_be_soc, @@ -296,12 +258,16 @@ int ipu_isys_csi2_be_soc_init(struct ipu_isys_csi2_be_soc *csi2_be_soc, NR_OF_CSI2_BE_SOC_PADS, NR_OF_CSI2_BE_SOC_SOURCE_PADS, NR_OF_CSI2_BE_SOC_SINK_PADS, 0, - CSI2_BE_SOC_PAD_SOURCE, + CSI2_BE_SOC_PAD_SOURCE(0), CSI2_BE_SOC_PAD_SINK); if (rval) goto fail; - csi2_be_soc->asd.valid_tgts[CSI2_BE_SOC_PAD_SOURCE].crop = true; + for (i = CSI2_BE_SOC_PAD_SOURCE(0); + i < NR_OF_CSI2_BE_SOC_SOURCE_PADS + CSI2_BE_SOC_PAD_SOURCE(0); + i++) { + csi2_be_soc->asd.valid_tgts[i].crop = true; + } for (i = 0; i < NR_OF_CSI2_BE_SOC_PADS; i++) csi2_be_soc_supported_codes[i] = @@ -313,8 +279,6 @@ int ipu_isys_csi2_be_soc_init(struct ipu_isys_csi2_be_soc *csi2_be_soc, fmt.pad = CSI2_BE_SOC_PAD_SINK; ipu_isys_subdev_set_ffmt(&csi2_be_soc->asd.sd, NULL, &fmt); - fmt.pad = CSI2_BE_SOC_PAD_SOURCE; - ipu_isys_subdev_set_ffmt(&csi2_be_soc->asd.sd, NULL, &fmt); csi2_be_soc->asd.sd.internal_ops = &csi2_be_soc_sd_internal_ops; snprintf(csi2_be_soc->asd.sd.name, sizeof(csi2_be_soc->asd.sd.name), @@ -328,55 +292,66 @@ int ipu_isys_csi2_be_soc_init(struct ipu_isys_csi2_be_soc *csi2_be_soc, goto fail; } - snprintf(csi2_be_soc->av.vdev.name, sizeof(csi2_be_soc->av.vdev.name), - IPU_ISYS_ENTITY_PREFIX " BE SOC capture %d", index); - - /* - * Pin type could be overwritten for YUV422 to I420 case, at - * set_format phase - */ - csi2_be_soc->av.aq.css_pin_type = IPU_FW_ISYS_PIN_TYPE_RAW_SOC; - csi2_be_soc->av.isys = isys; - csi2_be_soc->av.pfmts = ipu_isys_pfmts_be_soc; - csi2_be_soc->av.try_fmt_vid_mplane = - ipu_isys_video_try_fmt_vid_mplane_default; - csi2_be_soc->av.prepare_fw_stream = - ipu_isys_prepare_fw_cfg_default; - csi2_be_soc->av.aq.buf_prepare = ipu_isys_buf_prepare; - csi2_be_soc->av.aq.fill_frame_buff_set_pin = - ipu_isys_buffer_to_fw_frame_buff_pin; - csi2_be_soc->av.aq.link_fmt_validate = ipu_isys_link_fmt_validate; - csi2_be_soc->av.aq.vbq.buf_struct_size = - sizeof(struct ipu_isys_video_buffer); - - /* create v4l2 ctrl for be-soc video node */ - rval = v4l2_ctrl_handler_init(&csi2_be_soc->av.ctrl_handler, 0); - if (rval) { - dev_err(&isys->adev->dev, - "failed to init v4l2 ctrl handler for be_soc\n"); - goto fail; - } + for (i = 0; i < vnode_num; i++) { + if (!index) + snprintf(csi2_be_soc->av[i].vdev.name, + sizeof(csi2_be_soc->av[i].vdev.name), + IPU_ISYS_ENTITY_PREFIX " BE SOC capture %d", i); + else + snprintf(csi2_be_soc->av[i].vdev.name, + sizeof(csi2_be_soc->av[i].vdev.name), + IPU_ISYS_ENTITY_PREFIX " BE SOC %d capture %d", index, i); + /* + * Pin type could be overwritten for YUV422 to I420 case, at + * set_format phase + */ + csi2_be_soc->av[i].aq.css_pin_type = + IPU_FW_ISYS_PIN_TYPE_RAW_SOC; + csi2_be_soc->av[i].isys = isys; + csi2_be_soc->av[i].pfmts = ipu_isys_pfmts_be_soc; + + csi2_be_soc->av[i].try_fmt_vid_mplane = + ipu_isys_video_try_fmt_vid_mplane_default; + csi2_be_soc->av[i].prepare_fw_stream = + ipu_isys_prepare_fw_cfg_default; + csi2_be_soc->av[i].aq.buf_prepare = ipu_isys_buf_prepare; + csi2_be_soc->av[i].aq.fill_frame_buff_set_pin = + ipu_isys_buffer_to_fw_frame_buff_pin; + csi2_be_soc->av[i].aq.link_fmt_validate = + ipu_isys_link_fmt_validate; + csi2_be_soc->av[i].aq.vbq.buf_struct_size = + sizeof(struct ipu_isys_video_buffer); + + /* create v4l2 ctrl for be-soc video node */ + rval = + v4l2_ctrl_handler_init(&csi2_be_soc->av[i].ctrl_handler, 0); + if (rval) { + dev_err(&isys->adev->dev, + "failed to init v4l2 ctrl hdl for be_soc\n"); + goto fail; + } - csi2_be_soc->av.compression_ctrl = - v4l2_ctrl_new_custom(&csi2_be_soc->av.ctrl_handler, - &compression_ctrl_cfg, NULL); - if (!csi2_be_soc->av.compression_ctrl) { - dev_err(&isys->adev->dev, - "failed to create BE-SOC cmprs ctrl\n"); - goto fail; - } - csi2_be_soc->av.compression = 0; - csi2_be_soc->av.vdev.ctrl_handler = - &csi2_be_soc->av.ctrl_handler; - - rval = ipu_isys_video_init(&csi2_be_soc->av, - &csi2_be_soc->asd.sd.entity, - CSI2_BE_SOC_PAD_SOURCE, - MEDIA_PAD_FL_SINK, - MEDIA_LNK_FL_DYNAMIC); - if (rval) { - dev_info(&isys->adev->dev, "can't init video node\n"); - goto fail; + csi2_be_soc->av[i].compression_ctrl = + v4l2_ctrl_new_custom(&csi2_be_soc->av[i].ctrl_handler, + &compression_ctrl_cfg, NULL); + if (!csi2_be_soc->av[i].compression_ctrl) { + dev_err(&isys->adev->dev, + "failed to create BE-SOC cmprs ctrl\n"); + goto fail; + } + csi2_be_soc->av[i].compression = 0; + csi2_be_soc->av[i].vdev.ctrl_handler = + &csi2_be_soc->av[i].ctrl_handler; + + rval = ipu_isys_video_init(&csi2_be_soc->av[i], + &csi2_be_soc->asd.sd.entity, + CSI2_BE_SOC_PAD_SOURCE(i), + MEDIA_PAD_FL_SINK, + MEDIA_LNK_FL_DYNAMIC); + if (rval) { + dev_info(&isys->adev->dev, "can't init video node\n"); + goto fail; + } } return 0; diff --git a/drivers/media/pci/intel/ipu-isys-csi2-be.c b/drivers/media/pci/intel/ipu-isys-csi2-be.c index d5d0b33efa7d..d18598493ba3 100644 --- a/drivers/media/pci/intel/ipu-isys-csi2-be.c +++ b/drivers/media/pci/intel/ipu-isys-csi2-be.c @@ -94,11 +94,7 @@ static int get_supported_code_index(u32 code) } static int ipu_isys_csi2_be_set_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); @@ -107,19 +103,11 @@ static int ipu_isys_csi2_be_set_sel(struct v4l2_subdev *sd, if (sel->target == V4L2_SEL_TGT_CROP && pad->flags & MEDIA_PAD_FL_SOURCE && asd->valid_tgts[CSI2_BE_PAD_SOURCE].crop) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *ffmt = - __ipu_isys_get_ffmt(sd, cfg, sel->pad, sel->which); - struct v4l2_rect *r = __ipu_isys_get_selection - (sd, cfg, sel->target, CSI2_BE_PAD_SINK, sel->which); - -#else struct v4l2_mbus_framefmt *ffmt = __ipu_isys_get_ffmt(sd, state, sel->pad, sel->which); struct v4l2_rect *r = __ipu_isys_get_selection (sd, state, sel->target, CSI2_BE_PAD_SINK, sel->which); -#endif if (get_supported_code_index(ffmt->code) < 0) { /* Non-bayer formats can't be single line cropped */ sel->r.left &= ~1; @@ -140,28 +128,15 @@ static int ipu_isys_csi2_be_set_sel(struct v4l2_subdev *sd, */ sel->r.height = clamp(sel->r.height, IPU_ISYS_MIN_HEIGHT, r->height); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - *__ipu_isys_get_selection(sd, cfg, sel->target, sel->pad, - sel->which) = sel->r; - ipu_isys_subdev_fmt_propagate - (sd, cfg, NULL, &sel->r, - IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP, - sel->pad, sel->which); -#else *__ipu_isys_get_selection(sd, state, sel->target, sel->pad, sel->which) = sel->r; ipu_isys_subdev_fmt_propagate (sd, state, NULL, &sel->r, IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP, sel->pad, sel->which); -#endif return 0; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - return ipu_isys_subdev_set_sel(sd, cfg, sel); -#else return ipu_isys_subdev_set_sel(sd, state, sel); -#endif } static const struct v4l2_subdev_pad_ops csi2_be_sd_pad_ops = { @@ -183,56 +158,25 @@ static struct media_entity_operations csi2_be_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) -static void csi2_be_set_ffmt(struct v4l2_subdev *sd, - struct v4l2_subdev_fh *cfg, - struct v4l2_subdev_format *fmt) -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) -static void csi2_be_set_ffmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -#else static void csi2_be_set_ffmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) -#endif { struct ipu_isys_csi2 *csi2 = to_ipu_isys_csi2(sd); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *ffmt = - __ipu_isys_get_ffmt(sd, cfg, fmt->pad, fmt->which); - -#else struct v4l2_mbus_framefmt *ffmt = __ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which); -#endif switch (fmt->pad) { case CSI2_BE_PAD_SINK: if (fmt->format.field != V4L2_FIELD_ALTERNATE) fmt->format.field = V4L2_FIELD_NONE; *ffmt = fmt->format; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - ipu_isys_subdev_fmt_propagate - (sd, cfg, &fmt->format, NULL, - IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which); -#else ipu_isys_subdev_fmt_propagate (sd, state, &fmt->format, NULL, IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which); -#endif return; case CSI2_BE_PAD_SOURCE: { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *sink_ffmt = - __ipu_isys_get_ffmt(sd, cfg, CSI2_BE_PAD_SINK, - fmt->which); - struct v4l2_rect *r = - __ipu_isys_get_selection(sd, cfg, V4L2_SEL_TGT_CROP, - CSI2_BE_PAD_SOURCE, - fmt->which); -#else struct v4l2_mbus_framefmt *sink_ffmt = __ipu_isys_get_ffmt(sd, state, CSI2_BE_PAD_SINK, fmt->which); @@ -240,7 +184,6 @@ static void csi2_be_set_ffmt(struct v4l2_subdev *sd, __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_CROP, CSI2_BE_PAD_SOURCE, fmt->which); -#endif struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); u32 code = sink_ffmt->code; int idx = get_supported_code_index(code); diff --git a/drivers/media/pci/intel/ipu-isys-csi2-be.h b/drivers/media/pci/intel/ipu-isys-csi2-be.h index 94d827e78e9f..8243da98706d 100644 --- a/drivers/media/pci/intel/ipu-isys-csi2-be.h +++ b/drivers/media/pci/intel/ipu-isys-csi2-be.h @@ -22,10 +22,15 @@ struct ipu_isys; #define NR_OF_CSI2_BE_SOURCE_PADS 1 #define NR_OF_CSI2_BE_SINK_PADS 1 -#define NR_OF_CSI2_BE_SOC_SOURCE_PADS 1 +#define INVALIA_VC_ID -1 +#define NR_OF_CSI2_BE_SOC_SOURCE_PADS NR_OF_CSI2_BE_SOC_STREAMS #define NR_OF_CSI2_BE_SOC_SINK_PADS 1 #define CSI2_BE_SOC_PAD_SINK 0 -#define CSI2_BE_SOC_PAD_SOURCE 1 +#define CSI2_BE_SOC_PAD_SOURCE(n) \ + ({ typeof(n) __n = (n); \ + (__n) >= NR_OF_CSI2_BE_SOC_SOURCE_PADS ? \ + (NR_OF_CSI2_BE_SOC_SOURCE_PADS - 1) : \ + ((__n) + NR_OF_CSI2_BE_SOC_SINK_PADS); }) #define NR_OF_CSI2_BE_SOC_PADS \ (NR_OF_CSI2_BE_SOC_SOURCE_PADS + NR_OF_CSI2_BE_SOC_SINK_PADS) @@ -45,7 +50,7 @@ struct ipu_isys_csi2_be { struct ipu_isys_csi2_be_soc { struct ipu_isys_csi2_be_pdata *pdata; struct ipu_isys_subdev asd; - struct ipu_isys_video av; + struct ipu_isys_video av[NR_OF_CSI2_BE_SOC_SOURCE_PADS]; }; #define to_ipu_isys_csi2_be(sd) \ @@ -56,6 +61,9 @@ struct ipu_isys_csi2_be_soc { container_of(to_ipu_isys_subdev(sd), \ struct ipu_isys_csi2_be_soc, asd) +int ipu_isys_csi2_be_init(struct ipu_isys_csi2_be *csi2_be, + struct ipu_isys *isys); +void ipu_isys_csi2_be_cleanup(struct ipu_isys_csi2_be *csi2_be); int ipu_isys_csi2_be_soc_init(struct ipu_isys_csi2_be_soc *csi2_be_soc, struct ipu_isys *isys, int index); void ipu_isys_csi2_be_soc_cleanup(struct ipu_isys_csi2_be_soc *csi2_be); diff --git a/drivers/media/pci/intel/ipu-isys-csi2.c b/drivers/media/pci/intel/ipu-isys-csi2.c index 234cd8b4c5a9..f66a0f0ebd05 100644 --- a/drivers/media/pci/intel/ipu-isys-csi2.c +++ b/drivers/media/pci/intel/ipu-isys-csi2.c @@ -81,7 +81,6 @@ static struct v4l2_subdev_internal_ops csi2_sd_internal_ops = { .close = ipu_isys_subdev_close, }; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 255) int ipu_isys_csi2_get_link_freq(struct ipu_isys_csi2 *csi2, s64 *link_freq) { struct media_pipeline *mp = media_entity_pipeline(&csi2->asd.sd.entity); @@ -113,60 +112,6 @@ int ipu_isys_csi2_get_link_freq(struct ipu_isys_csi2 *csi2, s64 *link_freq) return 0; } -#else -int ipu_isys_csi2_get_link_freq(struct ipu_isys_csi2 *csi2, __s64 *link_freq) -{ - struct media_pipeline *mp = media_entity_pipeline(&csi2->asd.sd.entity); - struct ipu_isys_pipeline *pipe = container_of(mp, - struct ipu_isys_pipeline, - pipe); - struct v4l2_subdev *ext_sd = - media_entity_to_v4l2_subdev(pipe->external->entity); - struct v4l2_ext_control c = {.id = V4L2_CID_LINK_FREQ, }; - struct v4l2_ext_controls cs = {.count = 1, - .controls = &c, - }; - struct v4l2_querymenu qm = {.id = c.id, }; - int rval; - - if (!ext_sd) { - WARN_ON(1); - return -ENODEV; - } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) - rval = v4l2_g_ext_ctrls(ext_sd->ctrl_handler, - ext_sd->devnode, - ext_sd->v4l2_dev->mdev, - &cs); -#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) - rval = v4l2_g_ext_ctrls(ext_sd->ctrl_handler, - ext_sd->v4l2_dev->mdev, - &cs); -#else - rval = v4l2_g_ext_ctrls(ext_sd->ctrl_handler, &cs); -#endif - if (rval) { - dev_info(&csi2->isys->adev->dev, "can't get link frequency\n"); - return rval; - } - - qm.index = c.value; - - rval = v4l2_querymenu(ext_sd->ctrl_handler, &qm); - if (rval) { - dev_info(&csi2->isys->adev->dev, "can't get menu item\n"); - return rval; - } - - dev_dbg(&csi2->isys->adev->dev, "%s: link frequency %lld\n", __func__, - qm.value); - - if (!qm.value) - return -EINVAL; - *link_freq = qm.value; - return 0; -} -#endif static int subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub) @@ -287,12 +232,21 @@ static int set_stream(struct v4l2_subdev *sd, int enable) cfg = v4l2_get_subdev_hostdata(ext_sd); if (!enable) { + csi2->stream_count--; + if (csi2->stream_count) + return 0; + ipu_isys_csi2_set_stream(sd, timing, 0, enable); return 0; } ip->has_sof = true; + if (csi2->stream_count) { + csi2->stream_count++; + return 0; + } + nlanes = cfg->nlanes; dev_dbg(&csi2->isys->adev->dev, "lane nr %d.\n", nlanes); @@ -302,6 +256,7 @@ static int set_stream(struct v4l2_subdev *sd, int enable) return rval; rval = ipu_isys_csi2_set_stream(sd, timing, nlanes, enable); + csi2->stream_count++; return rval; } @@ -329,7 +284,8 @@ static int csi2_link_validate(struct media_link *link) struct ipu_isys_csi2 *csi2; struct ipu_isys_pipeline *ip; struct media_pipeline *mp; - int rval; + struct v4l2_subdev *source_sd; + struct v4l2_subdev *sink_sd; mp = media_entity_pipeline(link->sink->entity); if (!link->sink->entity || !link->source->entity || !mp) @@ -340,25 +296,17 @@ static int csi2_link_validate(struct media_link *link) csi2->receiver_errors = 0; ip->csi2 = csi2; ipu_isys_video_add_capture_done(ip, csi2_capture_done); - rval = v4l2_subdev_link_validate(link); - if (rval) - return rval; - - if (!v4l2_ctrl_g_ctrl(csi2->store_csi2_header)) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) - struct media_pad *remote_pad = - media_entity_remote_pad(&csi2->asd.pad[CSI2_PAD_SOURCE]); -#else - struct media_pad *remote_pad = - media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SOURCE]); -#endif + source_sd = media_entity_to_v4l2_subdev(link->source->entity); + sink_sd = media_entity_to_v4l2_subdev(link->sink->entity); + if (!source_sd) + return -ENODEV; - if (remote_pad && - is_media_entity_v4l2_subdev(remote_pad->entity)) { - dev_err(&csi2->isys->adev->dev, - "CSI2 BE requires CSI2 headers.\n"); - return -EINVAL; - } + if (strncmp(source_sd->name, IPU_ISYS_ENTITY_PREFIX, + strlen(IPU_ISYS_ENTITY_PREFIX)) != 0) { + ip->external = link->source; + ip->source = to_ipu_isys_subdev(sink_sd)->source; + dev_dbg(&csi2->isys->adev->dev, "%s: using source %d\n", + sink_sd->entity.name, ip->source); } return 0; @@ -368,37 +316,19 @@ static const struct v4l2_subdev_video_ops csi2_sd_video_ops = { .s_stream = set_stream, }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) -static int ipu_isys_csi2_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - return ipu_isys_subdev_get_ffmt(sd, cfg, fmt); -} -#else static int ipu_isys_csi2_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { return ipu_isys_subdev_get_ffmt(sd, state, fmt); } -#endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) -static int ipu_isys_csi2_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *fmt) -{ - return ipu_isys_subdev_set_ffmt(sd, cfg, fmt); -} -#else static int ipu_isys_csi2_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { return ipu_isys_subdev_set_ffmt(sd, state, fmt); } -#endif static int __subdev_link_validate(struct v4l2_subdev *sd, struct media_link *link, @@ -434,20 +364,12 @@ static struct media_entity_operations csi2_entity_ops = { }; static void csi2_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { enum isys_subdev_prop_tgt tgt = IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT; struct v4l2_mbus_framefmt *ffmt = -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - __ipu_isys_get_ffmt(sd, cfg, fmt->pad, -#else __ipu_isys_get_ffmt(sd, state, fmt->pad, -#endif fmt->which); if (fmt->format.field != V4L2_FIELD_ALTERNATE) @@ -455,13 +377,8 @@ static void csi2_set_ffmt(struct v4l2_subdev *sd, if (fmt->pad == CSI2_PAD_SINK) { *ffmt = fmt->format; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - ipu_isys_subdev_fmt_propagate(sd, cfg, &fmt->format, NULL, - tgt, fmt->pad, fmt->which); -#else ipu_isys_subdev_fmt_propagate(sd, state, &fmt->format, NULL, tgt, fmt->pad, fmt->which); -#endif return; } @@ -477,6 +394,25 @@ static void csi2_set_ffmt(struct v4l2_subdev *sd, WARN_ON(1); } +static const struct ipu_isys_pixelformat * +csi2_try_fmt(struct ipu_isys_video *av, + struct v4l2_pix_format_mplane *mpix) +{ + struct media_link *link = list_first_entry(&av->vdev.entity.links, + struct media_link, list); + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(link->source->entity); + struct ipu_isys_csi2 *csi2; + + if (!sd) + return NULL; + + csi2 = to_ipu_isys_csi2(sd); + + return ipu_isys_video_try_fmt_vid_mplane(av, mpix, + v4l2_ctrl_g_ctrl(csi2->store_csi2_header)); +} + void ipu_isys_csi2_cleanup(struct ipu_isys_csi2 *csi2) { if (!csi2->isys) @@ -484,6 +420,7 @@ void ipu_isys_csi2_cleanup(struct ipu_isys_csi2 *csi2) v4l2_device_unregister_subdev(&csi2->asd.sd); ipu_isys_subdev_cleanup(&csi2->asd); + ipu_isys_video_cleanup(&csi2->av); csi2->isys = NULL; } @@ -529,6 +466,7 @@ int ipu_isys_csi2_init(struct ipu_isys_csi2 *csi2, csi2->asd.ctrl_init = csi_ctrl_init; csi2->asd.isys = isys; init_completion(&csi2->eof_completion); + csi2->stream_count = 0; rval = ipu_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0, NR_OF_CSI2_PADS, NR_OF_CSI2_SOURCE_PADS, @@ -565,6 +503,36 @@ int ipu_isys_csi2_init(struct ipu_isys_csi2 *csi2, __ipu_isys_subdev_set_ffmt(&csi2->asd.sd, NULL, &fmt); mutex_unlock(&csi2->asd.mutex); + snprintf(csi2->av.vdev.name, sizeof(csi2->av.vdev.name), + IPU_ISYS_ENTITY_PREFIX " CSI-2 %u capture", index); + csi2->av.isys = isys; + csi2->av.aq.css_pin_type = IPU_FW_ISYS_PIN_TYPE_MIPI; + csi2->av.pfmts = ipu_isys_pfmts_packed; + csi2->av.try_fmt_vid_mplane = csi2_try_fmt; + csi2->av.prepare_fw_stream = + ipu_isys_prepare_fw_cfg_default; + csi2->av.packed = true; + csi2->av.line_header_length = + IPU_ISYS_CSI2_LONG_PACKET_HEADER_SIZE; + csi2->av.line_footer_length = + IPU_ISYS_CSI2_LONG_PACKET_FOOTER_SIZE; + csi2->av.aq.buf_prepare = ipu_isys_buf_prepare; + csi2->av.aq.fill_frame_buff_set_pin = + ipu_isys_buffer_to_fw_frame_buff_pin; + csi2->av.aq.link_fmt_validate = + ipu_isys_link_fmt_validate; + csi2->av.aq.vbq.buf_struct_size = + sizeof(struct ipu_isys_video_buffer); + + rval = ipu_isys_video_init(&csi2->av, + &csi2->asd.sd.entity, + CSI2_PAD_SOURCE, + MEDIA_PAD_FL_SINK, 0); + if (rval) { + dev_info(&isys->adev->dev, "can't init video node\n"); + goto fail; + } + return 0; fail: @@ -573,7 +541,7 @@ int ipu_isys_csi2_init(struct ipu_isys_csi2 *csi2, return rval; } -void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2) +void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2, unsigned int vc) { struct ipu_isys_pipeline *ip = NULL; struct v4l2_event ev = { @@ -584,10 +552,11 @@ void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2) unsigned int i; spin_lock_irqsave(&csi2->isys->lock, flags); - csi2->in_frame = true; + csi2->in_frame[vc] = true; for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { if (csi2->isys->pipes[i] && + csi2->isys->pipes[i]->vc == vc && csi2->isys->pipes[i]->csi2 == csi2) { ip = csi2->isys->pipes[i]; break; @@ -601,15 +570,16 @@ void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2) } ev.u.frame_sync.frame_sequence = atomic_inc_return(&ip->sequence) - 1; + ev.id = vc; spin_unlock_irqrestore(&csi2->isys->lock, flags); v4l2_event_queue(vdev, &ev); dev_dbg(&csi2->isys->adev->dev, - "sof_event::csi2-%i sequence: %i\n", - csi2->index, ev.u.frame_sync.frame_sequence); + "sof_event::csi2-%i CPU-timestamp:%lld, sequence:%i, vc:%d\n", + csi2->index, ktime_get_ns(), ev.u.frame_sync.frame_sequence, vc); } -void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2) +void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2, unsigned int vc) { struct ipu_isys_pipeline *ip = NULL; unsigned long flags; @@ -617,12 +587,13 @@ void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2) u32 frame_sequence; spin_lock_irqsave(&csi2->isys->lock, flags); - csi2->in_frame = false; - if (csi2->wait_for_sync) + csi2->in_frame[vc] = false; + if (csi2->wait_for_sync[vc]) complete(&csi2->eof_completion); for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { if (csi2->isys->pipes[i] && + csi2->isys->pipes[i]->vc == vc && csi2->isys->pipes[i]->csi2 == csi2) { ip = csi2->isys->pipes[i]; break; @@ -634,8 +605,8 @@ void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2) spin_unlock_irqrestore(&csi2->isys->lock, flags); dev_dbg(&csi2->isys->adev->dev, - "eof_event::csi2-%i sequence: %i\n", - csi2->index, frame_sequence); + "eof_event: csi2-%i sequence: %i, vc: %d\n", + csi2->index, frame_sequence, vc); return; } @@ -646,24 +617,27 @@ void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2) void ipu_isys_csi2_wait_last_eof(struct ipu_isys_csi2 *csi2) { unsigned long flags, tout; + unsigned int i; - spin_lock_irqsave(&csi2->isys->lock, flags); + for (i = 0; i < NR_OF_CSI2_VC; i++) { + spin_lock_irqsave(&csi2->isys->lock, flags); + + if (!csi2->in_frame[i]) { + spin_unlock_irqrestore(&csi2->isys->lock, flags); + continue; + } - if (!csi2->in_frame) { + reinit_completion(&csi2->eof_completion); + csi2->wait_for_sync[i] = true; spin_unlock_irqrestore(&csi2->isys->lock, flags); - return; + tout = wait_for_completion_timeout(&csi2->eof_completion, + IPU_EOF_TIMEOUT_JIFFIES); + if (!tout) + dev_err(&csi2->isys->adev->dev, + "csi2-%d: timeout at sync to eof of vc %d\n", + csi2->index, i); + csi2->wait_for_sync[i] = false; } - - reinit_completion(&csi2->eof_completion); - csi2->wait_for_sync = true; - spin_unlock_irqrestore(&csi2->isys->lock, flags); - tout = wait_for_completion_timeout(&csi2->eof_completion, - IPU_EOF_TIMEOUT_JIFFIES); - if (!tout) - dev_err(&csi2->isys->adev->dev, - "csi2-%d: timeout at sync to eof\n", - csi2->index); - csi2->wait_for_sync = false; } struct ipu_isys_buffer * diff --git a/drivers/media/pci/intel/ipu-isys-csi2.h b/drivers/media/pci/intel/ipu-isys-csi2.h index 08edfaed7bf7..cf624c2f1503 100644 --- a/drivers/media/pci/intel/ipu-isys-csi2.h +++ b/drivers/media/pci/intel/ipu-isys-csi2.h @@ -89,8 +89,10 @@ struct ipu_isys_csi2 { unsigned int nlanes; unsigned int index; atomic_t sof_sequence; - bool in_frame; - bool wait_for_sync; + bool in_frame[NR_OF_CSI2_VC]; + bool wait_for_sync[NR_OF_CSI2_VC]; + + unsigned int stream_count; struct v4l2_ctrl *store_csi2_header; }; @@ -148,8 +150,8 @@ void ipu_isys_csi2_cleanup(struct ipu_isys_csi2 *csi2); struct ipu_isys_buffer * ipu_isys_csi2_get_short_packet_buffer(struct ipu_isys_pipeline *ip, struct ipu_isys_buffer_list *bl); -void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2); -void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2); +void ipu_isys_csi2_sof_event(struct ipu_isys_csi2 *csi2, unsigned int vc); +void ipu_isys_csi2_eof_event(struct ipu_isys_csi2 *csi2, unsigned int vc); void ipu_isys_csi2_wait_last_eof(struct ipu_isys_csi2 *csi2); /* interface for platform specific */ diff --git a/drivers/media/pci/intel/ipu-isys-media.h b/drivers/media/pci/intel/ipu-isys-media.h index e84a5bc557c9..7b226aa27ead 100644 --- a/drivers/media/pci/intel/ipu-isys-media.h +++ b/drivers/media/pci/intel/ipu-isys-media.h @@ -7,86 +7,6 @@ #include #include -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) -#define is_media_entity_v4l2_subdev(e) \ - (media_entity_type(e) == MEDIA_ENT_T_V4L2_SUBDEV) -#define is_media_entity_v4l2_io(e) \ - (media_entity_type(e) == MEDIA_ENT_T_DEVNODE) -#define media_create_pad_link(a, b, c, d, e) \ - media_entity_create_link(a, b, c, d, e) -#define media_entity_pads_init(a, b, c) \ - media_entity_init(a, b, c, 0) -#define media_entity_id(ent) ((ent)->id) -#define media_entity_graph_walk_init(a, b) 0 -#define media_entity_graph_walk_cleanup(a) do { } while (0) - -#define IPU_COMPAT_MAX_ENTITIES MEDIA_ENTITY_ENUM_MAX_ID - -struct media_entity_enum { - unsigned long *bmap; - int idx_max; -}; - -static inline int media_entity_enum_init(struct media_entity_enum *ent_enum, - struct media_device *mdev) -{ - int idx_max = IPU_COMPAT_MAX_ENTITIES; - - ent_enum->bmap = kcalloc(DIV_ROUND_UP(idx_max, BITS_PER_LONG), - sizeof(long), GFP_KERNEL); - if (!ent_enum->bmap) - return -ENOMEM; - - bitmap_zero(ent_enum->bmap, idx_max); - - ent_enum->idx_max = idx_max; - return 0; -} - -static inline void media_entity_enum_cleanup(struct media_entity_enum *ent_enum) -{ - kfree(ent_enum->bmap); -} - -static inline void media_entity_enum_set(struct media_entity_enum *ent_enum, - struct media_entity *entity) -{ - if (media_entity_id(entity) >= ent_enum->idx_max) { - WARN_ON(1); - return; - } - __set_bit(media_entity_id(entity), ent_enum->bmap); -} - -static inline void media_entity_enum_zero(struct media_entity_enum *ent_enum) -{ - bitmap_zero(ent_enum->bmap, ent_enum->idx_max); -} - -static inline bool media_entity_enum_test(struct media_entity_enum *ent_enum, - struct media_entity *entity) -{ - if (media_entity_id(entity) >= ent_enum->idx_max) { - WARN_ON(1); - return false; - } - - return test_bit(media_entity_id(entity), ent_enum->bmap); -} -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) -#define media_pipeline_start(e, p) media_entity_pipeline_start(e, p) - -#define media_pipeline_stop(e) media_entity_pipeline_stop(e) - -#define media_graph_walk_init(g, d) media_entity_graph_walk_init(g, d) - -#define media_graph_walk_start(g, p) media_entity_graph_walk_start(g, p) - -#define media_graph_walk_next(g) media_entity_graph_walk_next(g) - -#define media_graph_walk_cleanup(g) media_entity_graph_walk_cleanup(g) -#endif - struct __packed media_request_cmd { __u32 cmd; __u32 request; diff --git a/drivers/media/pci/intel/ipu-isys-queue.c b/drivers/media/pci/intel/ipu-isys-queue.c index a9ce27e76855..b0a8689ed734 100644 --- a/drivers/media/pci/intel/ipu-isys-queue.c +++ b/drivers/media/pci/intel/ipu-isys-queue.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -12,67 +13,40 @@ #include "ipu.h" #include "ipu-bus.h" +#include "ipu-cpd.h" +#include "ipu-platform-isys-csi2-reg.h" #include "ipu-buttress.h" #include "ipu-isys.h" #include "ipu-isys-csi2.h" #include "ipu-isys-video.h" +extern int vnode_num; + static bool wall_clock_ts_on; module_param(wall_clock_ts_on, bool, 0660); MODULE_PARM_DESC(wall_clock_ts_on, "Timestamp based on REALTIME clock"); +extern bool enable_hw_sof_irq; static int queue_setup(struct vb2_queue *q, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - const struct v4l2_format *__fmt, -#endif unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - void *alloc_ctxs[]) -#else struct device *alloc_devs[]) -#endif { struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q); struct ipu_isys_video *av = ipu_isys_queue_to_video(aq); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - const struct v4l2_format *fmt = __fmt; - const struct ipu_isys_pixelformat *pfmt; - struct v4l2_pix_format_mplane mpix; -#else bool use_fmt = false; -#endif unsigned int i; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - if (fmt) - mpix = fmt->fmt.pix_mp; - else - mpix = av->mpix; - - pfmt = av->try_fmt_vid_mplane(av, &mpix); - - *num_planes = mpix.num_planes; -#else /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ if (!*num_planes) { use_fmt = true; *num_planes = av->mpix.num_planes; } -#endif for (i = 0; i < *num_planes; i++) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - sizes[i] = mpix.plane_fmt[i].sizeimage; -#else if (use_fmt) sizes[i] = av->mpix.plane_fmt[i].sizeimage; -#endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - alloc_ctxs[i] = aq->ctx; -#else alloc_devs[i] = aq->dev; -#endif dev_dbg(&av->isys->adev->dev, "%s: queue setup: plane %d size %u\n", av->vdev.name, i, sizes[i]); @@ -128,11 +102,7 @@ int ipu_isys_buf_prepare(struct vb2_buffer *vb) vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline * av->mpix.height); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_planes[0].data_offset = av->line_header_length / BITS_PER_BYTE; -#else vb->planes[0].data_offset = av->line_header_length / BITS_PER_BYTE; -#endif return 0; } @@ -271,11 +241,7 @@ static void flush_firmware_streamon_fail(struct ipu_isys_pipeline *ip) dev_dbg(&av->isys->adev->dev, "%s: queue buffer %u back to incoming\n", av->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index); -#else vb->index); -#endif /* Queue already streaming, return to driver. */ list_add(&ib->head, &aq->incoming); continue; @@ -284,11 +250,7 @@ static void flush_firmware_streamon_fail(struct ipu_isys_pipeline *ip) dev_dbg(&av->isys->adev->dev, "%s: return %u back to videobuf2\n", av->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index); -#else vb->index); -#endif vb2_buffer_done(ipu_isys_buffer_to_vb2_buffer(ib), VB2_BUF_STATE_QUEUED); } @@ -333,11 +295,7 @@ static int buffer_list_get(struct ipu_isys_pipeline *ip, dev_dbg(&ip->isys->adev->dev, "buffer: %s: buffer %u\n", ipu_isys_queue_to_video(aq)->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - ipu_isys_buffer_to_vb2_buffer(ib)->v4l2_buf.index -#else ipu_isys_buffer_to_vb2_buffer(ib)->index -#endif ); list_del(&ib->head); list_add(&ib->head, &bl->head); @@ -392,11 +350,7 @@ ipu_isys_buffer_to_fw_frame_buff_pin(struct vb2_buffer *vb, set->output_pins[aq->fw_output].addr = vb2_dma_contig_plane_dma_addr(vb, 0); set->output_pins[aq->fw_output].out_buf_id = -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index + 1; -#else vb->index + 1; -#endif } /* @@ -413,8 +367,8 @@ ipu_isys_buffer_to_fw_frame_buff(struct ipu_fw_isys_frame_buff_set_abi *set, WARN_ON(!bl->nbufs); - set->send_irq_sof = 1; - set->send_resp_sof = 1; + set->send_irq_sof = enable_hw_sof_irq ? 0 : 1; + set->send_resp_sof = enable_hw_sof_irq ? 0 : 1; set->send_irq_eof = 0; set->send_resp_eof = 0; @@ -547,11 +501,7 @@ static void buf_queue(struct vb2_buffer *vb) dev_dbg(&av->isys->adev->dev, "buffer: %s: buf_queue %u\n", av->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index -#else vb->index -#endif ); for (i = 0; i < vb->num_planes; i++) @@ -562,6 +512,22 @@ static void buf_queue(struct vb2_buffer *vb) list_add(&ib->head, &aq->incoming); spin_unlock_irqrestore(&aq->lock, flags); + mutex_unlock(&av->mutex); + mutex_lock(&av->isys->reset_mutex); + while (av->isys->in_reset) { + mutex_unlock(&av->isys->reset_mutex); + dev_dbg(&av->isys->adev->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); + mutex_lock(&av->mutex); + + /* ip may be cleared in ipu reset */ + ip = to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); + pipe_av = container_of(ip, struct ipu_isys_video, ip); if (ib->req) return; @@ -645,12 +611,8 @@ int ipu_isys_link_fmt_validate(struct ipu_isys_queue *aq) { struct ipu_isys_video *av = ipu_isys_queue_to_video(aq); struct v4l2_subdev_format fmt = { 0 }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) - struct media_pad *pad = media_entity_remote_pad(av->vdev.entity.pads); -#else struct media_pad *pad = media_pad_remote_pad_first(av->vdev.entity.pads); -#endif struct v4l2_subdev *sd; int rval; @@ -719,11 +681,7 @@ static void return_buffers(struct ipu_isys_queue *aq, "%s: stop_streaming incoming %u\n", ipu_isys_queue_to_video(vb2_queue_to_ipu_isys_queue (vb->vb2_queue))->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index); -#else vb->index); -#endif spin_lock_irqsave(&aq->lock, flags); } @@ -748,11 +706,7 @@ static void return_buffers(struct ipu_isys_queue *aq, dev_warn(&av->isys->adev->dev, "%s: cleaning active queue %u\n", ipu_isys_queue_to_video(vb2_queue_to_ipu_isys_queue (vb->vb2_queue))->vdev.name, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.index); -#else vb->index); -#endif spin_lock_irqsave(&aq->lock, flags); reset_needed = 1; @@ -767,7 +721,7 @@ static void return_buffers(struct ipu_isys_queue *aq, } } -static int start_streaming(struct vb2_queue *q, unsigned int count) +static int __start_streaming(struct vb2_queue *q, unsigned int count) { struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q); struct ipu_isys_video *av = ipu_isys_queue_to_video(aq); @@ -788,36 +742,40 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) if (first) { rval = ipu_isys_video_prepare_streaming(av, 1); - if (rval) + if (rval) { + dev_err(&av->isys->adev->dev, + "%s: prepare stream: failed (%d)\n", + av->vdev.name, rval); goto out_return_buffers; + } } mutex_unlock(&av->isys->stream_mutex); - rval = aq->link_fmt_validate(aq); - if (rval) { - dev_dbg(&av->isys->adev->dev, - "%s: link format validation failed (%d)\n", - av->vdev.name, rval); - goto out_unprepare_streaming; - } - ip = to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); pipe_av = container_of(ip, struct ipu_isys_video, ip); - mutex_unlock(&av->mutex); + if (pipe_av != av) { + mutex_unlock(&av->mutex); + mutex_lock(&pipe_av->mutex); + } - mutex_lock(&pipe_av->mutex); ip->nr_streaming++; dev_dbg(&av->isys->adev->dev, "queue %u of %u\n", ip->nr_streaming, ip->nr_queues); list_add(&aq->node, &ip->queues); - if (ip->nr_streaming != ip->nr_queues) + if (ip->nr_streaming != ip->nr_queues) { + dev_dbg(&av->isys->adev->dev, + "%s: streaming queue not match (%d)(%d)\n", + av->vdev.name, ip->nr_streaming, ip->nr_queues); goto out; + } if (list_empty(&av->isys->requests)) { bl = &__bl; rval = buffer_list_get(ip, bl); if (rval == -EINVAL) { + dev_err(&av->isys->adev->dev, + "buffer list invalid\n"); goto out_stream_start; } else if (rval < 0) { dev_dbg(&av->isys->adev->dev, @@ -827,22 +785,28 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) } rval = ipu_isys_stream_start(ip, bl, false); - if (rval) + if (rval) { + dev_err(&av->isys->adev->dev, + "isys stream start failed\n"); goto out_stream_start; + } out: - mutex_unlock(&pipe_av->mutex); - mutex_lock(&av->mutex); + if (pipe_av != av) { + mutex_unlock(&pipe_av->mutex); + mutex_lock(&av->mutex); + } return 0; out_stream_start: list_del(&aq->node); ip->nr_streaming--; - mutex_unlock(&pipe_av->mutex); - mutex_lock(&av->mutex); + if (pipe_av != av) { + mutex_unlock(&pipe_av->mutex); + mutex_lock(&av->mutex); + } -out_unprepare_streaming: mutex_lock(&av->isys->stream_mutex); if (first) ipu_isys_video_prepare_streaming(av, 0); @@ -854,6 +818,257 @@ static int start_streaming(struct vb2_queue *q, unsigned int count) return rval; } +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q); + struct ipu_isys_video *av = ipu_isys_queue_to_video(aq); + int rval; + + mutex_unlock(&av->mutex); + mutex_lock(&av->isys->reset_mutex); + while (av->isys->in_stop_streaming) { + mutex_unlock(&av->isys->reset_mutex); + dev_dbg(&av->isys->adev->dev, "buffer: %s: wait for stop streaming\n", + av->vdev.name + ); + usleep_range(10000, 11000); + mutex_lock(&av->isys->reset_mutex); + } + mutex_unlock(&av->isys->reset_mutex); + mutex_lock(&av->mutex); + + rval = __start_streaming(q, count); + if (rval) + av->start_streaming = 0; + else + av->start_streaming = 1; + + return rval; +} + +static void reset_stop_streaming(struct ipu_isys_video *av) +{ + struct ipu_isys_pipeline *ip = + to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); + struct ipu_isys_queue *aq = &av->aq; + + dev_dbg(&av->isys->adev->dev, "%s: stop streaming\n", av->vdev.name); + + mutex_lock(&av->isys->stream_mutex); + if (ip->nr_streaming == ip->nr_queues && ip->streaming) + ipu_isys_video_set_streaming(av, 0, NULL); + if (ip->nr_streaming == 1) + ipu_isys_video_prepare_streaming(av, 0); + else + av->vdev.entity.pads[0].pipe = NULL; + mutex_unlock(&av->isys->stream_mutex); + + ip->nr_streaming--; + list_del(&aq->node); + ip->streaming = 0; + av->start_streaming = 0; +} + +static int reset_start_streaming(struct ipu_isys_video *av) +{ + struct ipu_isys_queue *aq = &av->aq; + unsigned long flags; + int rval; + + dev_dbg(&av->isys->adev->dev, "%s: start streaming\n", av->vdev.name); + + spin_lock_irqsave(&aq->lock, flags); + while (!list_empty(&aq->active)) { + struct ipu_isys_buffer *ib = list_first_entry(&aq->active, + struct + ipu_isys_buffer, + head); + + list_del(&ib->head); + list_add_tail(&ib->head, &aq->incoming); + } + spin_unlock_irqrestore(&aq->lock, flags); + + av->skipframe = 1; + rval = __start_streaming(&aq->vbq, 0); + if (rval) { + dev_dbg(&av->isys->adev->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 rval; +} + +static int ipu_isys_reset(struct ipu_isys_video *self_av, + struct ipu_isys_pipeline *self_ip) +{ + struct ipu_isys *isys = self_av->isys; + struct ipu_bus_device *adev = isys->adev; + struct ipu_device *isp = isys->adev->isp; + struct ipu_isys_video *av = NULL; + struct ipu_isys_pipeline *ip = NULL; + struct ipu_isys_csi2_be_soc *csi2_be_soc = NULL; + int rval, i, j; + int has_streaming = 0; + + dev_dbg(&isys->adev->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(&isys->adev->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); + + av = &isys->csi2->av; + ip = to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); + + if (av != self_av && ip && ip != self_ip) { + mutex_lock(&av->mutex); + if (ip->streaming && !ip->nr_streaming) { + av->reset = true; + has_streaming = true; + reset_stop_streaming(av); + } + mutex_unlock(&av->mutex); + } + + av = &isys->csi2_be.av; + ip = to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); + + if (av != self_av && ip && ip != self_ip) { + mutex_lock(&av->mutex); + if (ip->streaming && !ip->nr_streaming) { + av->reset = true; + has_streaming = true; + reset_stop_streaming(av); + } + mutex_unlock(&av->mutex); + } + + for (i = 0; i < NR_OF_CSI2_BE_SOC_DEV; i++) { + csi2_be_soc = &isys->csi2_be_soc[i]; + for (j = 0; j < vnode_num; j++) { + av = &csi2_be_soc->av[j]; + if (av == self_av) + continue; + + ip = to_ipu_isys_pipeline + (media_entity_pipeline(&av->vdev.entity)); + if (!ip || ip == self_ip) + continue; + + mutex_lock(&av->mutex); + if (!ip->streaming && !ip->nr_streaming) { + mutex_unlock(&av->mutex); + continue; + } + + if (!av->start_streaming) { + mutex_unlock(&av->mutex); + continue; + } + + av->reset = true; + has_streaming = true; + reset_stop_streaming(av); + mutex_unlock(&av->mutex); + } + } + + if (!has_streaming) + goto end_of_reset; + + dev_dbg(&isys->adev->dev, "ipu reset, close fw\n"); + ipu_fw_isys_close(isys); + + dev_dbg(&isys->adev->dev, "ipu reset, power cycle\n"); + + /* bus_pm_runtime_suspend() */ + /* isys_runtime_pm_suspend() */ + adev->dev.bus->pm->runtime_suspend(&adev->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() */ + adev->dev.bus->pm->runtime_resume(&adev->dev); + + ipu_configure_spc(isys->adev->isp, + &isys->pdata->ipdata->hw_variant, + IPU_CPD_PKG_DIR_ISYS_SERVER_IDX, + isys->pdata->base, isys->pkg_dir, + isys->pkg_dir_dma_addr); + + ipu_cleanup_fw_msg_bufs(isys); + if (isys->fwcom) { + dev_err(&isys->adev->dev, "Clearing old context\n"); + ipu_fw_isys_cleanup(isys); + } + + rval = ipu_fw_isys_init(av->isys, + isys->pdata->ipdata->num_parallel_streams); + if (rval < 0) + dev_err(&isys->adev->dev, "ipu fw isys init failed\n"); + + dev_dbg(&isys->adev->dev, "restart streams\n"); + + av = &isys->csi2->av; + if (av->reset) { + av->reset = false; + mutex_lock(&av->mutex); + reset_start_streaming(av); + mutex_unlock(&av->mutex); + } + + av = &isys->csi2_be.av; + if (av->reset) { + av->reset = false; + mutex_lock(&av->mutex); + reset_start_streaming(av); + mutex_unlock(&av->mutex); + } + + for (i = 0; i < NR_OF_CSI2_BE_SOC_DEV; i++) { + csi2_be_soc = &isys->csi2_be_soc[i]; + for (j = 0; j < vnode_num; j++) { + av = &csi2_be_soc->av[j]; + if (!av->reset) + continue; + + av->reset = false; + mutex_lock(&av->mutex); + reset_start_streaming(av); + mutex_unlock(&av->mutex); + } + } + +end_of_reset: + mutex_lock(&isys->reset_mutex); + isys->in_reset = false; + mutex_unlock(&isys->reset_mutex); + dev_dbg(&isys->adev->dev, "reset done\n"); + + return 0; +} + static void stop_streaming(struct vb2_queue *q) { struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q); @@ -862,6 +1077,50 @@ static void stop_streaming(struct vb2_queue *q) struct ipu_isys_pipeline *ip = to_ipu_isys_pipeline(mp); struct ipu_isys_video *pipe_av = container_of(ip, struct ipu_isys_video, ip); + dev_dbg(&av->isys->adev->dev, "stop: %s: enter\n", + av->vdev.name); + + struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); + + if (!source_pad) { + dev_err(&av->isys->adev->dev, "stop stream: no link.\n"); + return; + } + + mutex_unlock(&av->mutex); + mutex_lock(&av->isys->reset_mutex); + while (av->isys->in_reset || av->isys->in_stop_streaming) { + mutex_unlock(&av->isys->reset_mutex); + dev_dbg(&av->isys->adev->dev, "stop: %s: wait for in_reset = %d\n", + av->vdev.name, av->isys->in_reset); + dev_dbg(&av->isys->adev->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); + + ip = to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); + pipe_av = container_of(ip, struct ipu_isys_video, ip); + + mutex_lock(&av->mutex); + + if (!ip) { + dev_err(&av->isys->adev->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; + } if (pipe_av != av) { mutex_unlock(&av->mutex); @@ -873,6 +1132,8 @@ static void stop_streaming(struct vb2_queue *q) ipu_isys_video_set_streaming(av, 0, NULL); if (ip->nr_streaming == 1) ipu_isys_video_prepare_streaming(av, 0); + else + av->vdev.entity.pads[0].pipe = NULL; mutex_unlock(&av->isys->stream_mutex); ip->nr_streaming--; @@ -885,6 +1146,20 @@ static void stop_streaming(struct vb2_queue *q) } return_buffers(aq, VB2_BUF_STATE_ERROR); + 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->reset_needed) { + if (!ip->nr_streaming && (!is_support_vc(source_pad, ip) || is_has_metadata(ip))) + ipu_isys_reset(av, ip); + else + av->isys->reset_needed = 0; + } + + dev_dbg(&av->isys->adev->dev, "stop: %s: exit\n", + av->vdev.name); } static unsigned int @@ -942,12 +1217,7 @@ ipu_isys_buf_calc_sequence_time(struct ipu_isys_buffer *ib, struct ipu_fw_isys_resp_info_abi *info) { struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); -#endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - struct timespec ts_now; -#endif struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue); struct ipu_isys_video *av = ipu_isys_queue_to_video(aq); struct device *dev = &av->isys->adev->dev; @@ -967,23 +1237,6 @@ ipu_isys_buf_calc_sequence_time(struct ipu_isys_buffer *ib, / ip->nr_queues; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.sequence = sequence; - ts_now = ns_to_timespec(ns); - vb->v4l2_buf.timestamp.tv_sec = ts_now.tv_sec; - vb->v4l2_buf.timestamp.tv_usec = ts_now.tv_nsec / NSEC_PER_USEC; - - dev_dbg(dev, "buffer: %s: buffer done %u\n", av->vdev.name, - vb->v4l2_buf.index); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - vbuf->sequence = sequence; - ts_now = ns_to_timespec(ns); - vbuf->timestamp.tv_sec = ts_now.tv_sec; - vbuf->timestamp.tv_usec = ts_now.tv_nsec / NSEC_PER_USEC; - - dev_dbg(dev, "%s: buffer done %u\n", av->vdev.name, - vb->index); -#else vbuf->vb2_buf.timestamp = ns; vbuf->sequence = sequence; @@ -991,20 +1244,22 @@ ipu_isys_buf_calc_sequence_time(struct ipu_isys_buffer *ib, av->vdev.name, ktime_get_ns(), sequence); dev_dbg(dev, "index:%d, vbuf timestamp:%lld, endl\n", vb->index, vbuf->vb2_buf.timestamp); -#endif } void ipu_isys_queue_buf_done(struct ipu_isys_buffer *ib) { struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib); - if (atomic_read(&ib->str2mmio_flag)) { + if (atomic_read(&ib->ib_err_flag)) { vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); /* * Operation on buffer is ended with error and will be reported * to the userspace when it is de-queued */ - atomic_set(&ib->str2mmio_flag, 0); + atomic_set(&ib->ib_err_flag, 0); + } else if (atomic_read(&ib->skipframe_flag)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + atomic_set(&ib->skipframe_flag, 0); } else { vb2_buffer_done(vb, VB2_BUF_STATE_DONE); } @@ -1020,11 +1275,7 @@ void ipu_isys_queue_buf_ready(struct ipu_isys_pipeline *ip, struct vb2_buffer *vb; unsigned long flags; bool first = true; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - struct v4l2_buffer *buf; -#else struct vb2_v4l2_buffer *buf; -#endif dev_dbg(&isys->adev->dev, "buffer: %s: received buffer %8.8x\n", ipu_isys_queue_to_video(aq)->vdev.name, info->pin.addr); @@ -1050,22 +1301,32 @@ void ipu_isys_queue_buf_ready(struct ipu_isys_pipeline *ip, first = false; continue; } + u32 mask = 0; + u32 csi2_status = ip->csi2->receiver_errors; + u32 irq = readl(ip->csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); + + mask = (ipu_ver == IPU_VER_6 || ipu_ver == IPU_VER_6EP || + ipu_ver == IPU_VER_6EP_MTL) ? + IPU6_CSI_RX_ERROR_IRQ_MASK : IPU6SE_CSI_RX_ERROR_IRQ_MASK; + + csi2_status |= irq & mask; if (info->error_info.error == - IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { + IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO || + csi2_status != 0) { /* * Check for error message: - * 'IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' + * 'IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' & + * CSI2 errors */ - atomic_set(&ib->str2mmio_flag, 1); + dev_dbg(&isys->adev->dev, "buffer: csi2_status: 0x%x, fw error: %d\n", + csi2_status, info->error_info.error); + atomic_set(&ib->ib_err_flag, 1); } dev_dbg(&isys->adev->dev, "buffer: found buffer %pad\n", &addr); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - buf = &vb->v4l2_buf; -#else buf = to_vb2_v4l2_buffer(vb); -#endif buf->field = V4L2_FIELD_NONE; list_del(&ib->head); @@ -1073,6 +1334,12 @@ void ipu_isys_queue_buf_ready(struct ipu_isys_pipeline *ip, ipu_isys_buf_calc_sequence_time(ib, info); + struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (atomic_read(&ib->ib_err_flag)) + dev_err(&isys->adev->dev, "csi2-%i error: #%d\n", + ip->csi2->index, vbuf->sequence); /* * For interlaced buffers, the notification to user space * is postponed to capture_done event since the field @@ -1141,16 +1408,8 @@ int ipu_isys_queue_init(struct ipu_isys_queue *aq) if (rval) return rval; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - aq->ctx = vb2_dma_contig_init_ctx(&isys->adev->dev); - if (IS_ERR(aq->ctx)) { - vb2_queue_release(&aq->vbq); - return PTR_ERR(aq->ctx); - } -#else aq->dev = &isys->adev->dev; aq->vbq.dev = &isys->adev->dev; -#endif spin_lock_init(&aq->lock); INIT_LIST_HEAD(&aq->active); INIT_LIST_HEAD(&aq->incoming); @@ -1160,12 +1419,5 @@ int ipu_isys_queue_init(struct ipu_isys_queue *aq) void ipu_isys_queue_cleanup(struct ipu_isys_queue *aq) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - if (IS_ERR_OR_NULL(aq->ctx)) - return; - - vb2_dma_contig_cleanup_ctx(aq->ctx); - aq->ctx = NULL; -#endif vb2_queue_release(&aq->vbq); } diff --git a/drivers/media/pci/intel/ipu-isys-queue.h b/drivers/media/pci/intel/ipu-isys-queue.h index cc1e80d336c1..bb21234f5c98 100644 --- a/drivers/media/pci/intel/ipu-isys-queue.h +++ b/drivers/media/pci/intel/ipu-isys-queue.h @@ -7,11 +7,7 @@ #include #include -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) -#include -#else #include -#endif #include "ipu-isys-media.h" @@ -28,11 +24,7 @@ enum ipu_isys_buffer_type { struct ipu_isys_queue { struct list_head node; /* struct ipu_isys_pipeline.queues */ struct vb2_queue vbq; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct vb2_alloc_ctx *ctx; -#else struct device *dev; -#endif /* * @lock: serialise access to queued and pre_streamon_queued */ @@ -56,15 +48,12 @@ struct ipu_isys_buffer { enum ipu_isys_buffer_type type; struct list_head req_head; struct media_device_request *req; - atomic_t str2mmio_flag; + atomic_t ib_err_flag; + atomic_t skipframe_flag; }; struct ipu_isys_video_buffer { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - struct vb2_buffer vb; -#else struct vb2_v4l2_buffer vb_v4l2; -#endif struct ipu_isys_buffer ib; }; @@ -92,20 +81,12 @@ struct ipu_isys_buffer_list { #define ipu_isys_to_isys_video_buffer(__ib) \ container_of(__ib, struct ipu_isys_video_buffer, ib) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) -#define vb2_buffer_to_ipu_isys_video_buffer(__vb) \ - container_of(__vb, struct ipu_isys_video_buffer, vb) - -#define ipu_isys_buffer_to_vb2_buffer(__ib) \ - (&ipu_isys_to_isys_video_buffer(__ib)->vb) -#else #define vb2_buffer_to_ipu_isys_video_buffer(__vb) \ container_of(to_vb2_v4l2_buffer(__vb), \ struct ipu_isys_video_buffer, vb_v4l2) #define ipu_isys_buffer_to_vb2_buffer(__ib) \ (&ipu_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) -#endif #define vb2_buffer_to_ipu_isys_buffer(__vb) \ (&vb2_buffer_to_ipu_isys_video_buffer(__vb)->ib) diff --git a/drivers/media/pci/intel/ipu-isys-subdev.c b/drivers/media/pci/intel/ipu-isys-subdev.c index f5d869107c13..2f7ef867a61d 100644 --- a/drivers/media/pci/intel/ipu-isys-subdev.c +++ b/drivers/media/pci/intel/ipu-isys-subdev.c @@ -142,14 +142,7 @@ u32 ipu_isys_subdev_code_to_uncompressed(u32 sink_code) } struct v4l2_mbus_framefmt *__ipu_isys_get_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config - *cfg, -#else struct v4l2_subdev_state *state, -#endif unsigned int pad, unsigned int which) { @@ -158,25 +151,11 @@ struct v4l2_mbus_framefmt *__ipu_isys_get_ffmt(struct v4l2_subdev *sd, if (which == V4L2_SUBDEV_FORMAT_ACTIVE) return &asd->ffmt[pad]; else -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - return v4l2_subdev_get_try_format(cfg, pad); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - return v4l2_subdev_get_try_format(sd, cfg, pad); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) return v4l2_subdev_get_try_format(sd, state, pad); -#else - return v4l2_subdev_state_get_format(state, pad); -#endif } struct v4l2_rect *__ipu_isys_get_selection(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif unsigned int target, unsigned int pad, unsigned int which) { @@ -191,27 +170,10 @@ struct v4l2_rect *__ipu_isys_get_selection(struct v4l2_subdev *sd, } } else { switch (target) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - case V4L2_SEL_TGT_CROP: - return v4l2_subdev_get_try_crop(cfg, pad); - case V4L2_SEL_TGT_COMPOSE: - return v4l2_subdev_get_try_compose(cfg, pad); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - case V4L2_SEL_TGT_CROP: - return v4l2_subdev_get_try_crop(sd, cfg, pad); - case V4L2_SEL_TGT_COMPOSE: - return v4l2_subdev_get_try_compose(sd, cfg, pad); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) case V4L2_SEL_TGT_CROP: return v4l2_subdev_get_try_crop(sd, state, pad); case V4L2_SEL_TGT_COMPOSE: return v4l2_subdev_get_try_compose(sd, state, pad); -#else - case V4L2_SEL_TGT_CROP: - return v4l2_subdev_state_get_crop(state, pad); - case V4L2_SEL_TGT_COMPOSE: - return v4l2_subdev_state_get_compose(state, pad); -#endif } } WARN_ON(1); @@ -234,13 +196,7 @@ static int target_valid(struct v4l2_subdev *sd, unsigned int target, } int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_mbus_framefmt *ffmt, struct v4l2_rect *r, enum isys_subdev_prop_tgt tgt, @@ -279,21 +235,12 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, } for (i = 0; i < sd->entity.num_pads; i++) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - ffmts[i] = __ipu_isys_get_ffmt(sd, cfg, i, which); - crops[i] = __ipu_isys_get_selection(sd, cfg, V4L2_SEL_TGT_CROP, - i, which); - compose[i] = __ipu_isys_get_selection(sd, cfg, - V4L2_SEL_TGT_COMPOSE, - i, which); -#else ffmts[i] = __ipu_isys_get_ffmt(sd, state, i, which); crops[i] = __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_CROP, i, which); compose[i] = __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_COMPOSE, i, which); -#endif } switch (tgt) { @@ -302,13 +249,8 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, crops[pad]->top = 0; crops[pad]->width = ffmt->width; crops[pad]->height = ffmt->height; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - rval = ipu_isys_subdev_fmt_propagate(sd, cfg, ffmt, crops[pad], - tgt + 1, pad, which); -#else rval = ipu_isys_subdev_fmt_propagate(sd, state, ffmt, crops[pad], tgt + 1, pad, which); -#endif goto out_subdev_fmt_propagate; case IPU_ISYS_SUBDEV_PROP_TGT_SINK_CROP: if (WARN_ON(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) @@ -318,15 +260,9 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, compose[pad]->top = 0; compose[pad]->width = r->width; compose[pad]->height = r->height; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - rval = ipu_isys_subdev_fmt_propagate(sd, cfg, ffmt, - compose[pad], tgt + 1, - pad, which); -#else rval = ipu_isys_subdev_fmt_propagate(sd, state, ffmt, compose[pad], tgt + 1, pad, which); -#endif goto out_subdev_fmt_propagate; case IPU_ISYS_SUBDEV_PROP_TGT_SINK_COMPOSE: if (WARN_ON(sd->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)) { @@ -343,19 +279,11 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, compose[i]->top = 0; compose[i]->width = r->width; compose[i]->height = r->height; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - rval = ipu_isys_subdev_fmt_propagate(sd, cfg, - ffmt, - compose[i], - tgt + 1, i, - which); -#else rval = ipu_isys_subdev_fmt_propagate(sd, state, ffmt, compose[i], tgt + 1, i, which); -#endif if (rval) goto out_subdev_fmt_propagate; } @@ -370,15 +298,9 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, crops[pad]->top = 0; crops[pad]->width = r->width; crops[pad]->height = r->height; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - rval = ipu_isys_subdev_fmt_propagate(sd, cfg, ffmt, - crops[pad], tgt + 1, - pad, which); -#else rval = ipu_isys_subdev_fmt_propagate(sd, state, ffmt, crops[pad], tgt + 1, pad, which); -#endif goto out_subdev_fmt_propagate; case IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP:{ struct v4l2_subdev_format fmt = { @@ -398,11 +320,7 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, }, }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - asd->set_ffmt(sd, cfg, &fmt); -#else asd->set_ffmt(sd, state, &fmt); -#endif goto out_subdev_fmt_propagate; } } @@ -415,30 +333,16 @@ int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, } int ipu_isys_subdev_set_ffmt_default(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { struct v4l2_mbus_framefmt *ffmt = -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - __ipu_isys_get_ffmt(sd, cfg, fmt->pad, fmt->which); -#else __ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which); -#endif /* No propagation for non-zero pads. */ if (fmt->pad) { struct v4l2_mbus_framefmt *sink_ffmt = -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - __ipu_isys_get_ffmt(sd, cfg, 0, fmt->which); -#else __ipu_isys_get_ffmt(sd, state, 0, fmt->which); -#endif ffmt->width = sink_ffmt->width; ffmt->height = sink_ffmt->height; @@ -453,34 +357,18 @@ int ipu_isys_subdev_set_ffmt_default(struct v4l2_subdev *sd, ffmt->code = fmt->format.code; ffmt->field = fmt->format.field; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - return ipu_isys_subdev_fmt_propagate(sd, cfg, &fmt->format, NULL, - IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, - fmt->pad, fmt->which); -#else return ipu_isys_subdev_fmt_propagate(sd, state, &fmt->format, NULL, IPU_ISYS_SUBDEV_PROP_TGT_SINK_FMT, fmt->pad, fmt->which); -#endif } int __ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); struct v4l2_mbus_framefmt *ffmt = -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - __ipu_isys_get_ffmt(sd, cfg, fmt->pad, fmt->which); -#else __ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which); -#endif u32 code = asd->supported_codes[fmt->pad][0]; unsigned int i; @@ -500,11 +388,7 @@ int __ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, fmt->format.code = code; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - asd->set_ffmt(sd, cfg, fmt); -#else asd->set_ffmt(sd, state, fmt); -#endif fmt->format = *ffmt; @@ -512,62 +396,35 @@ int __ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, } int ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); int rval; mutex_lock(&asd->mutex); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - rval = __ipu_isys_subdev_set_ffmt(sd, cfg, fmt); -#else rval = __ipu_isys_subdev_set_ffmt(sd, state, fmt); -#endif mutex_unlock(&asd->mutex); return rval; } int ipu_isys_subdev_get_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); mutex_lock(&asd->mutex); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - fmt->format = *__ipu_isys_get_ffmt(sd, cfg, fmt->pad, - fmt->which); -#else fmt->format = *__ipu_isys_get_ffmt(sd, state, fmt->pad, fmt->which); -#endif mutex_unlock(&asd->mutex); return 0; } int ipu_isys_subdev_set_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); @@ -582,13 +439,8 @@ int ipu_isys_subdev_set_sel(struct v4l2_subdev *sd, case V4L2_SEL_TGT_CROP: if (pad->flags & MEDIA_PAD_FL_SINK) { struct v4l2_mbus_framefmt *ffmt = -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - __ipu_isys_get_ffmt(sd, cfg, sel->pad, - sel->which); -#else __ipu_isys_get_ffmt(sd, state, sel->pad, sel->which); -#endif __r.width = ffmt->width; __r.height = ffmt->height; @@ -596,37 +448,21 @@ int ipu_isys_subdev_set_sel(struct v4l2_subdev *sd, tgt = IPU_ISYS_SUBDEV_PROP_TGT_SINK_CROP; } else { /* 0 is the sink pad. */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - r = __ipu_isys_get_selection(sd, cfg, sel->target, 0, - sel->which); -#else r = __ipu_isys_get_selection(sd, state, sel->target, 0, sel->which); -#endif tgt = IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_CROP; } break; case V4L2_SEL_TGT_COMPOSE: if (pad->flags & MEDIA_PAD_FL_SINK) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - r = __ipu_isys_get_selection(sd, cfg, V4L2_SEL_TGT_CROP, - sel->pad, sel->which); -#else r = __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_CROP, sel->pad, sel->which); -#endif tgt = IPU_ISYS_SUBDEV_PROP_TGT_SINK_COMPOSE; } else { -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - r = __ipu_isys_get_selection(sd, cfg, - V4L2_SEL_TGT_COMPOSE, 0, - sel->which); -#else r = __ipu_isys_get_selection(sd, state, V4L2_SEL_TGT_COMPOSE, 0, sel->which); -#endif tgt = IPU_ISYS_SUBDEV_PROP_TGT_SOURCE_COMPOSE; } break; @@ -636,51 +472,27 @@ int ipu_isys_subdev_set_sel(struct v4l2_subdev *sd, sel->r.width = clamp(sel->r.width, IPU_ISYS_MIN_WIDTH, r->width); sel->r.height = clamp(sel->r.height, IPU_ISYS_MIN_HEIGHT, r->height); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - *__ipu_isys_get_selection(sd, cfg, sel->target, sel->pad, - sel->which) = sel->r; - return ipu_isys_subdev_fmt_propagate(sd, cfg, NULL, &sel->r, tgt, - sel->pad, sel->which); -#else *__ipu_isys_get_selection(sd, state, sel->target, sel->pad, sel->which) = sel->r; return ipu_isys_subdev_fmt_propagate(sd, state, NULL, &sel->r, tgt, sel->pad, sel->which); -#endif } int ipu_isys_subdev_get_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel) { if (!target_valid(sd, sel->target, sel->pad)) return -EINVAL; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - sel->r = *__ipu_isys_get_selection(sd, cfg, sel->target, - sel->pad, sel->which); -#else sel->r = *__ipu_isys_get_selection(sd, state, sel->target, sel->pad, sel->which); -#endif return 0; } int ipu_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_mbus_code_enum *code) { struct ipu_isys_subdev *asd = to_ipu_isys_subdev(sd); @@ -726,6 +538,11 @@ int ipu_isys_subdev_link_validate(struct v4l2_subdev *sd, ip->source = to_ipu_isys_subdev(sd)->source; dev_dbg(&asd->isys->adev->dev, "%s: using source %d\n", sd->entity.name, ip->source); + /* + * multi streams with different format/resolusion from external, + * without route info, ignore link validate here. + */ + return 0; } else if (source_sd->entity.num_pads == 1) { /* All internal sources have a single pad. */ ip->external = link->source; @@ -750,35 +567,12 @@ int ipu_isys_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) mutex_lock(&asd->mutex); for (i = 0; i < asd->sd.entity.num_pads; i++) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_get_try_format(fh, i); - struct v4l2_rect *try_crop = - v4l2_subdev_get_try_crop(fh, i); - struct v4l2_rect *try_compose = - v4l2_subdev_get_try_compose(fh, i); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_get_try_format(sd, fh->pad, i); - struct v4l2_rect *try_crop = - v4l2_subdev_get_try_crop(sd, fh->pad, i); - struct v4l2_rect *try_compose = - v4l2_subdev_get_try_compose(sd, fh->pad, i); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_get_try_format(sd, fh->state, i); struct v4l2_rect *try_crop = v4l2_subdev_get_try_crop(sd, fh->state, i); struct v4l2_rect *try_compose = v4l2_subdev_get_try_compose(sd, fh->state, i); -#else - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_state_get_format(fh->state, i); - struct v4l2_rect *try_crop = - v4l2_subdev_state_get_crop(fh->state, i); - struct v4l2_rect *try_compose = - v4l2_subdev_state_get_compose(fh->state, i); -#endif *try_fmt = asd->ffmt[i]; *try_crop = asd->crop[i]; diff --git a/drivers/media/pci/intel/ipu-isys-subdev.h b/drivers/media/pci/intel/ipu-isys-subdev.h index 1c510156990d..0bcbc0249856 100644 --- a/drivers/media/pci/intel/ipu-isys-subdev.h +++ b/drivers/media/pci/intel/ipu-isys-subdev.h @@ -75,13 +75,7 @@ struct ipu_isys_subdev { struct v4l2_ctrl_handler ctrl_handler; void (*ctrl_init)(struct v4l2_subdev *sd); void (*set_ffmt)(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt); struct { bool crop; @@ -96,14 +90,7 @@ struct ipu_isys_subdev { container_of(__sd, struct ipu_isys_subdev, sd) struct v4l2_mbus_framefmt *__ipu_isys_get_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config - *cfg, -#else struct v4l2_subdev_state *state, -#endif unsigned int pad, unsigned int which); @@ -114,87 +101,37 @@ u32 ipu_isys_subdev_code_to_uncompressed(u32 sink_code); enum ipu_isys_subdev_pixelorder ipu_isys_subdev_get_pixelorder(u32 code); int ipu_isys_subdev_fmt_propagate(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_mbus_framefmt *ffmt, struct v4l2_rect *r, enum isys_subdev_prop_tgt tgt, unsigned int pad, unsigned int which); int ipu_isys_subdev_set_ffmt_default(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt); int __ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt); struct v4l2_rect *__ipu_isys_get_selection(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif unsigned int target, unsigned int pad, unsigned int which); int ipu_isys_subdev_set_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt); int ipu_isys_subdev_get_ffmt(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_format *fmt); int ipu_isys_subdev_get_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel); int ipu_isys_subdev_set_sel(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_selection *sel); int ipu_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0) - struct v4l2_subdev_fh *cfg, -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - struct v4l2_subdev_pad_config *cfg, -#else struct v4l2_subdev_state *state, -#endif struct v4l2_subdev_mbus_code_enum *code); int ipu_isys_subdev_link_validate(struct v4l2_subdev *sd, diff --git a/drivers/media/pci/intel/ipu-isys-video.c b/drivers/media/pci/intel/ipu-isys-video.c index 92b369d7df9e..b32459a5a211 100644 --- a/drivers/media/pci/intel/ipu-isys-video.c +++ b/drivers/media/pci/intel/ipu-isys-video.c @@ -9,19 +9,14 @@ #include #include #include +#include -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) -#include -#else #include -#endif #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) #include -#endif #include "ipu.h" #include "ipu-bus.h" @@ -35,14 +30,6 @@ #include "ipu-fw-isys.h" #include "ipu-fw-com.h" -#define IPU_IS_DEFAULT_FREQ \ - (IPU_IS_FREQ_RATIO_BASE * IPU_IS_FREQ_CTL_DEFAULT_RATIO) -#define IPU6SE_IS_DEFAULT_FREQ \ - (IPU_IS_FREQ_RATIO_BASE * IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO) - -/* use max resolution pixel rate by default */ -#define DEFAULT_PIXEL_RATE (360000000ULL * 2 * 4 / 10) - #define MAX_VIDEO_DEVICES 8 static int video_nr[MAX_VIDEO_DEVICES] = { [0 ...(MAX_VIDEO_DEVICES - 1)] = -1 }; @@ -100,6 +87,7 @@ const struct ipu_isys_pixelformat ipu_isys_pfmts_be_soc[] = { {V4L2_PIX_FMT_Y210, 20, 20, 0, MEDIA_BUS_FMT_YUYV10_1X20, IPU_FW_ISYS_FRAME_FORMAT_YUYV}, #endif + {V4L2_META_FMT_D4XX, 8, 8, 0, MEDIA_BUS_FMT_FIXED, 0}, {} }; @@ -190,13 +178,7 @@ static int video_open(struct file *file) if (rval) goto out_power_down; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) - rval = ipu_pipeline_pm_use(&av->vdev.entity, 1); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) - rval = v4l2_pipeline_pm_use(&av->vdev.entity, 1); -#else rval = v4l2_pipeline_pm_get(&av->vdev.entity); -#endif if (rval) goto out_v4l2_fh_release; @@ -241,13 +223,7 @@ static int video_open(struct file *file) out_lib_init: isys->video_opened--; mutex_unlock(&isys->mutex); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) - ipu_pipeline_pm_use(&av->vdev.entity, 0); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) - v4l2_pipeline_pm_use(&av->vdev.entity, 0); -#else v4l2_pipeline_pm_put(&av->vdev.entity); -#endif out_v4l2_fh_release: v4l2_fh_release(file); @@ -262,11 +238,26 @@ static int video_release(struct file *file) struct ipu_isys_video *av = video_drvdata(file); int ret = 0; + dev_dbg(&av->isys->adev->dev, "release: %s: enter\n", + av->vdev.name); vb2_fop_release(file); + mutex_lock(&av->isys->reset_mutex); + while (av->isys->in_reset) { + mutex_unlock(&av->isys->reset_mutex); + dev_dbg(&av->isys->adev->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); + mutex_lock(&av->isys->mutex); if (!--av->isys->video_opened) { + dev_dbg(&av->isys->adev->dev, "release: %s: close fw\n", + av->vdev.name); ipu_fw_isys_close(av->isys); if (av->isys->fwcom) { av->isys->reset_needed = true; @@ -276,23 +267,18 @@ static int video_release(struct file *file) mutex_unlock(&av->isys->mutex); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) - ipu_pipeline_pm_use(&av->vdev.entity, 0); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) - v4l2_pipeline_pm_use(&av->vdev.entity, 0); -#else v4l2_pipeline_pm_put(&av->vdev.entity); -#endif if (av->isys->reset_needed) pm_runtime_put_sync(&av->isys->adev->dev); else pm_runtime_put(&av->isys->adev->dev); + dev_dbg(&av->isys->adev->dev, "release: %s: exit\n", + av->vdev.name); return ret; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) static struct media_pad *other_pad(struct media_pad *pad) { struct media_link *link; @@ -308,18 +294,11 @@ static struct media_pad *other_pad(struct media_pad *pad) WARN_ON(1); return NULL; } -#endif const struct ipu_isys_pixelformat * ipu_isys_get_pixelformat(struct ipu_isys_video *av, u32 pixelformat) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - struct media_pad *pad = - av->vdev.entity.pads[0].flags & MEDIA_PAD_FL_SOURCE ? - av->vdev.entity.links[0].sink : av->vdev.entity.links[0].source; -#else struct media_pad *pad = other_pad(&av->vdev.entity.pads[0]); -#endif struct v4l2_subdev *sd; const u32 *supported_codes; const struct ipu_isys_pixelformat *pfmt; @@ -370,13 +349,7 @@ int ipu_isys_vidioc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct ipu_isys_video *av = video_drvdata(file); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - struct media_pad *pad = - av->vdev.entity.pads[0].flags & MEDIA_PAD_FL_SOURCE ? - av->vdev.entity.links[0].sink : av->vdev.entity.links[0].source; -#else struct media_pad *pad = other_pad(&av->vdev.entity.pads[0]); -#endif struct v4l2_subdev *sd; const u32 *supported_codes; const struct ipu_isys_pixelformat *pfmt; @@ -418,6 +391,16 @@ static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh, { struct ipu_isys_video *av = video_drvdata(file); + if (fmt->type == V4L2_BUF_TYPE_META_CAPTURE) { + fmt->fmt.meta.buffersize = av->mpix.plane_fmt[0].sizeimage; + fmt->fmt.meta.bytesperline = av->mpix.plane_fmt[0].bytesperline; + fmt->fmt.meta.width = av->mpix.width; + fmt->fmt.meta.height = av->mpix.height; + fmt->fmt.meta.dataformat = av->mpix.pixelformat; + + return 0; + } + fmt->fmt.pix_mp = av->mpix; return 0; @@ -543,10 +526,29 @@ static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh, struct v4l2_format *f) { struct ipu_isys_video *av = video_drvdata(file); + struct v4l2_pix_format_mplane mpix; if (av->aq.vbq.streaming) return -EBUSY; + if (f->type == V4L2_BUF_TYPE_META_CAPTURE) { + memset(&av->mpix, 0, sizeof(av->mpix)); + memset(&mpix, 0, sizeof(mpix)); + mpix.width = f->fmt.meta.width; + mpix.height = f->fmt.meta.height; + mpix.pixelformat = f->fmt.meta.dataformat; + av->pfmt = av->try_fmt_vid_mplane(av, &mpix); + av->aq.vbq.type = V4L2_BUF_TYPE_META_CAPTURE; + av->aq.vbq.is_multiplanar = false; + av->aq.vbq.is_output = false; + av->mpix = mpix; + f->fmt.meta.width = mpix.width; + f->fmt.meta.height = mpix.height; + f->fmt.meta.dataformat = mpix.pixelformat; + f->fmt.meta.bytesperline = mpix.plane_fmt[0].bytesperline; + f->fmt.meta.buffersize = mpix.plane_fmt[0].sizeimage; + return 0; + } av->pfmt = av->try_fmt_vid_mplane(av, &f->fmt.pix_mp); av->mpix = f->fmt.pix_mp; @@ -632,7 +634,8 @@ static int link_validate(struct media_link *link) { struct ipu_isys_video *av = container_of(link->sink, struct ipu_isys_video, pad); - struct ipu_isys_pipeline *ip = &av->ip; + struct ipu_isys_pipeline *ip = + to_ipu_isys_pipeline(media_entity_pipeline(&av->vdev.entity)); struct v4l2_subdev *sd; if (!link->source->entity) @@ -640,11 +643,7 @@ static int link_validate(struct media_link *link) sd = media_entity_to_v4l2_subdev(link->source->entity); if (is_external(av, link->source->entity)) { -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) - ip->external = media_entity_remote_pad(av->vdev.entity.pads); -#else ip->external = media_pad_remote_pad_first(av->vdev.entity.pads); -#endif ip->source = to_ipu_isys_subdev(sd)->source; } @@ -653,21 +652,6 @@ static int link_validate(struct media_link *link) return 0; } -static void set_buttress_isys_freq(struct ipu_isys_video *av, bool max) -{ - struct ipu_device *isp = av->isys->adev->isp; - - if (max) { - ipu_buttress_isys_freq_set(isp, BUTTRESS_MAX_FORCE_IS_FREQ); - } else { - if (ipu_ver == IPU_VER_6SE) - ipu_buttress_isys_freq_set(isp, IPU6SE_IS_DEFAULT_FREQ); - else - ipu_buttress_isys_freq_set(isp, IPU_IS_DEFAULT_FREQ); - } - -} - static void get_stream_opened(struct ipu_isys_video *av) { unsigned long flags; @@ -676,8 +660,6 @@ static void get_stream_opened(struct ipu_isys_video *av) av->isys->stream_opened++; spin_unlock_irqrestore(&av->isys->lock, flags); - if (av->isys->stream_opened > 1) - set_buttress_isys_freq(av, true); } static void put_stream_opened(struct ipu_isys_video *av) @@ -688,8 +670,6 @@ static void put_stream_opened(struct ipu_isys_video *av) av->isys->stream_opened--; spin_unlock_irqrestore(&av->isys->lock, flags); - if (av->isys->stream_opened <= 1) - set_buttress_isys_freq(av, false); } static int get_stream_handle(struct ipu_isys_video *av) @@ -738,17 +718,10 @@ static int get_external_facing_format(struct ipu_isys_pipeline *ip, return -ENODEV; } sd = media_entity_to_v4l2_subdev(ip->external->entity); -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) - external_facing = (strncmp(sd->name, IPU_ISYS_ENTITY_PREFIX, - strlen(IPU_ISYS_ENTITY_PREFIX)) == 0) ? - ip->external : - media_entity_remote_pad(ip->external); -#else external_facing = (strncmp(sd->name, IPU_ISYS_ENTITY_PREFIX, strlen(IPU_ISYS_ENTITY_PREFIX)) == 0) ? ip->external : media_pad_remote_pad_first(ip->external); -#endif if (WARN_ON(!external_facing)) { dev_warn(&av->isys->adev->dev, "no external facing pad --- driver bug?\n"); @@ -765,43 +738,16 @@ static int get_external_facing_format(struct ipu_isys_pipeline *ip, static void short_packet_queue_destroy(struct ipu_isys_pipeline *ip) { struct ipu_isys_video *av = container_of(ip, struct ipu_isys_video, ip); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs attrs; -#else - unsigned long attrs; -#endif -#endif unsigned int i; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - init_dma_attrs(&attrs); - dma_set_attr(DMA_ATTR_NON_CONSISTENT, &attrs); -#else - attrs = DMA_ATTR_NON_CONSISTENT; -#endif -#endif if (!ip->short_packet_bufs) return; for (i = 0; i < IPU_ISYS_SHORT_PACKET_BUFFER_NUM; i++) { if (ip->short_packet_bufs[i].buffer) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) dma_free_coherent(&av->isys->adev->dev, ip->short_packet_buffer_size, ip->short_packet_bufs[i].buffer, ip->short_packet_bufs[i].dma_addr); -#else - dma_free_attrs(&av->isys->adev->dev, - ip->short_packet_buffer_size, - ip->short_packet_bufs[i].buffer, - ip->short_packet_bufs[i].dma_addr, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - &attrs); -#else - attrs); -#endif -#endif } kfree(ip->short_packet_bufs); ip->short_packet_bufs = NULL; @@ -811,13 +757,6 @@ static int short_packet_queue_setup(struct ipu_isys_pipeline *ip) { struct ipu_isys_video *av = container_of(ip, struct ipu_isys_video, ip); struct v4l2_subdev_format source_fmt = { 0 }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs attrs; -#else - unsigned long attrs; -#endif -#endif unsigned int i; int rval; size_t buf_size; @@ -841,14 +780,6 @@ static int short_packet_queue_setup(struct ipu_isys_pipeline *ip) /* Initialize short packet queue. */ INIT_LIST_HEAD(&ip->short_packet_incoming); INIT_LIST_HEAD(&ip->short_packet_active); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - init_dma_attrs(&attrs); - dma_set_attr(DMA_ATTR_NON_CONSISTENT, &attrs); -#else - attrs = DMA_ATTR_NON_CONSISTENT; -#endif -#endif ip->short_packet_bufs = kzalloc(sizeof(struct ipu_isys_private_buffer) * @@ -863,18 +794,8 @@ static int short_packet_queue_setup(struct ipu_isys_pipeline *ip) buf->ip = ip; buf->ib.type = IPU_ISYS_SHORT_PACKET_BUFFER; buf->bytesused = buf_size; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) buf->buffer = dma_alloc_coherent(&av->isys->adev->dev, buf_size, &buf->dma_addr, GFP_KERNEL); -#else - buf->buffer = dma_alloc_attrs(&av->isys->adev->dev, buf_size, - &buf->dma_addr, GFP_KERNEL, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - &attrs); -#else - attrs); -#endif -#endif if (!buf->buffer) { short_packet_queue_destroy(ip); return -ENOMEM; @@ -928,6 +849,369 @@ csi_short_packet_prepare_fw_cfg(struct ipu_isys_pipeline *ip, output_info->error_handling_enable = false; } +#define MEDIA_ENTITY_MAX_PADS 512 + +bool is_support_vc(struct media_pad *source_pad, + struct ipu_isys_pipeline *ip) +{ + struct media_pad *remote_pad = source_pad; + struct media_pad *extern_pad = NULL; + struct v4l2_subdev *sd = NULL; + struct v4l2_query_ext_ctrl qm_ctrl = { + .id = V4L2_CID_IPU_QUERY_SUB_STREAM, }; + int i; + + while ((remote_pad = + media_pad_remote_pad_first(&remote_pad->entity->pads[0]) + )) { + /* Non-subdev nodes can be safely ignored here. */ + if (!is_media_entity_v4l2_subdev(remote_pad->entity)) + continue; + + /* Don't start truly external devices quite yet. */ + if (strncmp(remote_pad->entity->name, + IPU_ISYS_CSI2_ENTITY_PREFIX, + strlen(IPU_ISYS_CSI2_ENTITY_PREFIX)) != 0) + continue; + + dev_dbg(remote_pad->entity->graph_obj.mdev->dev, + "It finds CSI2 %s\n", remote_pad->entity->name); + extern_pad = + media_pad_remote_pad_first(&remote_pad->entity->pads[0]); + if (!extern_pad) { + dev_dbg(remote_pad->entity->graph_obj.mdev->dev, + "extern_pad is null\n"); + return false; + } + sd = media_entity_to_v4l2_subdev(extern_pad->entity); + break; + } + + if (!sd) { + dev_dbg(source_pad->entity->graph_obj.mdev->dev, + "It doesn't find extern entity\n"); + return false; + } + + if (v4l2_query_ext_ctrl(sd->ctrl_handler, &qm_ctrl)) { + dev_dbg(source_pad->entity->graph_obj.mdev->dev, + "%s, No vc\n", __func__); + for (i = 0; i < CSI2_BE_SOC_SOURCE_PADS_NUM; i++) + ip->asv[i].vc = 0; + + return false; + } + + return true; +} + +bool is_has_metadata(const struct ipu_isys_pipeline *ip) +{ + int i = 0; + + for (i = 0; i < CSI2_BE_SOC_SOURCE_PADS_NUM; i++) { + if (ip->asv[i].dt == IPU_ISYS_MIPI_CSI2_TYPE_EMBEDDED8) + return true; + } + return false; +} + +static int ipu_isys_query_sensor_info(struct media_pad *source_pad, + struct ipu_isys_pipeline *ip) +{ + int i; + int ret = -ENOLINK; + bool flag = false; + unsigned int pad_id = source_pad->index; + struct media_pad *remote_pad = source_pad; + struct media_pad *extern_pad = NULL; + struct v4l2_subdev *sd = NULL; + struct v4l2_querymenu qm = {.id = V4L2_CID_IPU_QUERY_SUB_STREAM, }; + + while ((remote_pad = + media_pad_remote_pad_first(&remote_pad->entity->pads[0]) + )) { + /* Non-subdev nodes can be safely ignored here. */ + if (!is_media_entity_v4l2_subdev(remote_pad->entity)) + continue; + + /* Don't start truly external devices quite yet. */ + if (strncmp(remote_pad->entity->name, + IPU_ISYS_CSI2_ENTITY_PREFIX, + strlen(IPU_ISYS_CSI2_ENTITY_PREFIX)) != 0) + continue; + + dev_dbg(remote_pad->entity->graph_obj.mdev->dev, + "It finds CSI2 %s\n", remote_pad->entity->name); + extern_pad = + media_pad_remote_pad_first(&remote_pad->entity->pads[0]); + if (!extern_pad) { + dev_dbg(remote_pad->entity->graph_obj.mdev->dev, + "extern_pad is null\n"); + return -ENOLINK; + } + sd = media_entity_to_v4l2_subdev(extern_pad->entity); + break; + } + + if (!sd) { + dev_dbg(source_pad->entity->graph_obj.mdev->dev, + "It doesn't find extern entity\n"); + return -ENOLINK; + } + + /* Get the sub stream info and set the current pipe's vc id */ + for (i = 0; i < CSI2_BE_SOC_SOURCE_PADS_NUM; i++) { + /* + * index is sub stream id. sub stream id is + * equalto BE SOC source pad id - sink pad count + */ + qm.index = i; + ret = v4l2_querymenu(sd->ctrl_handler, &qm); + if (ret) + continue; + + /* get sub stream info by sub stream id */ + ip->asv[qm.index].substream = qm.index; + ip->asv[qm.index].code = SUB_STREAM_CODE(qm.value); + ip->asv[qm.index].height = SUB_STREAM_H(qm.value); + ip->asv[qm.index].width = SUB_STREAM_W(qm.value); + ip->asv[qm.index].dt = SUB_STREAM_DT(qm.value); + ip->asv[qm.index].vc = SUB_STREAM_VC_ID(qm.value); + if (ip->asv[qm.index].substream == + (pad_id - NR_OF_CSI2_BE_SOC_SINK_PADS)) { + ip->vc = ip->asv[qm.index].vc; + flag = true; + pr_info("The current entity vc:id:%d\n", ip->vc); + } + dev_dbg(source_pad->entity->graph_obj.mdev->dev, + "dentity vc:%d, dt:%x, substream:%d\n", + ip->vc, ip->asv[qm.index].dt, + ip->asv[qm.index].substream); + } + + if (flag) + return 0; + + return ret; +} + +static void media_pipeline_stop_for_vc(struct ipu_isys_video *av) +{ + struct media_pipeline *pipe = av->pad.pipe; + struct media_entity *entity = &av->vdev.entity; + struct media_device *mdev = entity->graph_obj.mdev; + struct media_graph graph; + int ret; + + /* + * If the following check fails, the driver has performed an + * unbalanced call to media_pipeline_stop() + */ + if (WARN_ON(!pipe)) + return; + + if (--pipe->start_count) + return; + + ret = media_graph_walk_init(&graph, mdev); + if (ret) + return; + + media_graph_walk_start(&graph, entity); + dev_dbg(av->vdev.entity.graph_obj.mdev->dev, + "stream count: %u, av entity name: %s.\n", + av->ip.csi2->stream_count, av->vdev.entity.name); + while ((entity = media_graph_walk_next(&graph))) { + dev_dbg(av->vdev.entity.graph_obj.mdev->dev, + "walk entity name: %s.\n", + entity->name); + if (av->ip.csi2->stream_count == 0 || !strcmp(entity->name, av->vdev.entity.name)) + entity->pads[0].pipe = NULL; + } + + media_graph_walk_cleanup(&graph); +} + +static int media_pipeline_walk_by_vc(struct ipu_isys_video *av, + struct media_pipeline *pipe) +{ + int ret = -ENOLINK; + int i; + int entity_vc = INVALIA_VC_ID; + u32 n; + struct media_entity *entity = &av->vdev.entity; + struct media_device *mdev = entity->graph_obj.mdev; + struct media_graph graph; + struct media_entity *entity_err = entity; + struct media_link *link; + struct ipu_isys_pipeline *ip = to_ipu_isys_pipeline(pipe); + struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); + unsigned int pad_id; + bool is_vc = false; + + if (!source_pad) { + dev_err(entity->graph_obj.mdev->dev, "no remote pad found\n"); + return ret; + } + + if (pipe->start_count) { + pipe->start_count++; + return 0; + } + + is_vc = is_support_vc(source_pad, ip); + if (is_vc) { + ret = ipu_isys_query_sensor_info(source_pad, ip); + if (ret) { + dev_err(entity->graph_obj.mdev->dev, + "query sensor info failed\n"); + return ret; + } + } + + ret = media_graph_walk_init(&graph, mdev); + if (ret) + return ret; + + media_graph_walk_start(&graph, entity); + while ((entity = media_graph_walk_next(&graph))) { + DECLARE_BITMAP(active, MEDIA_ENTITY_MAX_PADS); + DECLARE_BITMAP(has_no_links, MEDIA_ENTITY_MAX_PADS); + + dev_dbg(entity->graph_obj.mdev->dev, "entity name:%s\n", + entity->name); + + if (entity->pads[0].pipe && entity->pads[0].pipe == pipe) { + dev_dbg(entity->graph_obj.mdev->dev, + "Pipe active for %s. when start for %s\n", + entity->name, entity_err->name); + } + /* + * If entity's pipe is not null and it is video device, it has + * be enabled. + */ + if (entity->pads[0].pipe && + is_media_entity_v4l2_video_device(entity)) + continue; + + /* + * If it is video device and its vc id is not equal to curren + * video device's vc id, it should continue. + */ + if (is_vc && is_media_entity_v4l2_video_device(entity)) { + source_pad = + media_pad_remote_pad_first(entity->pads); + + if (!source_pad) { + dev_warn(entity->graph_obj.mdev->dev, + "no remote pad found\n"); + continue; + } + pad_id = source_pad->index; + for (i = 0; i < CSI2_BE_SOC_SOURCE_PADS_NUM; i++) { + if (ip->asv[i].substream == + (pad_id - NR_OF_CSI2_BE_SOC_SINK_PADS)) { + entity_vc = ip->asv[i].vc; + break; + } + } + + if (entity_vc != ip->vc) + continue; + } + + entity->pads[0].pipe = pipe; + + if (!entity->ops || !entity->ops->link_validate) + continue; + + bitmap_zero(active, entity->num_pads); + bitmap_fill(has_no_links, entity->num_pads); + + list_for_each_entry(link, &entity->links, list) { + struct media_pad *pad = link->sink->entity == entity + ? link->sink : link->source; + + /* Mark that a pad is connected by a link. */ + bitmap_clear(has_no_links, pad->index, 1); + + /* + * Pads that either do not need to connect or + * are connected through an enabled link are + * fine. + */ + if (!(pad->flags & MEDIA_PAD_FL_MUST_CONNECT) || + link->flags & MEDIA_LNK_FL_ENABLED) + bitmap_set(active, pad->index, 1); + + /* + * Link validation will only take place for + * sink ends of the link that are enabled. + */ + if (link->sink != pad || + !(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + ret = entity->ops->link_validate(link); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_dbg(entity->graph_obj.mdev->dev, + "link failed for %s:%u->%s:%u,ret:%d\n", + link->source->entity->name, + link->source->index, + entity->name, link->sink->index, ret); + goto error; + } + } + + /* Either no links or validated links are fine. */ + bitmap_or(active, active, has_no_links, entity->num_pads); + + if (!bitmap_full(active, entity->num_pads)) { + ret = -ENOLINK; + n = (u32)find_first_zero_bit(active, entity->num_pads); + dev_dbg(entity->graph_obj.mdev->dev, + "%s:%u must be connected by an enabled link\n", + entity->name, n); + goto error; + } + } + + media_graph_walk_cleanup(&graph); + pipe->start_count++; + + return 0; + +error: + /* + * Link validation on graph failed. We revert what we did and + * return the error. + */ + media_graph_walk_start(&graph, entity_err); + while ((entity_err = media_graph_walk_next(&graph))) { + entity_err->pads[0].pipe = NULL; + if (entity_err == entity) + break; + } + + media_graph_walk_cleanup(&graph); + + return ret; +} + +static int media_pipeline_start_by_vc(struct ipu_isys_video *av, + struct media_pipeline *pipe) +{ + struct media_device *mdev = av->vdev.entity.graph_obj.mdev; + int ret; + + mutex_lock(&mdev->graph_mutex); + ret = media_pipeline_walk_by_vc(av, pipe); + mutex_unlock(&mdev->graph_mutex); + + return ret; +} + void ipu_isys_prepare_fw_cfg_default(struct ipu_isys_video *av, struct ipu_fw_isys_stream_cfg_data_abi *cfg) @@ -939,13 +1223,70 @@ ipu_isys_prepare_fw_cfg_default(struct ipu_isys_video *av, struct ipu_isys *isys = av->isys; unsigned int type_index, type; int pin = cfg->nof_output_pins++; + struct v4l2_subdev_format source_fmt = { 0 }; + int i, ret; + int input_pin = cfg->nof_input_pins++; + struct ipu_fw_isys_input_pin_info_abi *input_pin_info = + &cfg->input_pins[input_pin]; + struct ipu_isys_sub_stream_vc *sv = NULL; + struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); + unsigned int sub_stream_id; + + if (!source_pad) { + dev_err(&av->isys->adev->dev, "no remote pad found\n"); + return; + } + + if (is_support_vc(source_pad, ip)) { + sub_stream_id = source_pad->index - NR_OF_CSI2_BE_SOC_SINK_PADS; + + for (i = 0; i < NR_OF_CSI2_BE_SOC_SOURCE_PADS; i++) { + if (sub_stream_id == ip->asv[i].substream) { + sv = &ip->asv[i]; + break; + } + } + if (!sv) { + dev_err(&av->isys->adev->dev, + "Don't find input pin info for vc:%d\n", + ip->vc); + return; + } + + input_pin_info->input_res.width = sv->width; + input_pin_info->input_res.height = sv->height; + input_pin_info->dt = + (sv->dt != 0 ? sv->dt : IPU_ISYS_MIPI_CSI2_TYPE_RAW12); + } else { + ret = get_external_facing_format(ip, &source_fmt); + if (ret) + return; + + ip->vc = 0; + input_pin_info->input_res.width = source_fmt.format.width; + input_pin_info->input_res.height = source_fmt.format.height; + input_pin_info->dt = + ipu_isys_mbus_code_to_mipi(source_fmt.format.code); + } + + input_pin_info->mapped_dt = N_IPU_FW_ISYS_MIPI_DATA_TYPE; + input_pin_info->mipi_decompression = + IPU_FW_ISYS_MIPI_COMPRESSION_TYPE_NO_COMPRESSION; + input_pin_info->capture_mode = + IPU_FW_ISYS_CAPTURE_MODE_REGULAR; + if (ip->csi2 && !v4l2_ctrl_g_ctrl(ip->csi2->store_csi2_header)) + input_pin_info->mipi_store_mode = + IPU_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER; + else if (input_pin_info->dt == IPU_ISYS_MIPI_CSI2_TYPE_EMBEDDED8) + input_pin_info->mipi_store_mode = + IPU_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER; aq->fw_output = pin; ip->output_pins[pin].pin_ready = ipu_isys_queue_buf_ready; ip->output_pins[pin].aq = aq; pin_info = &cfg->output_pins[pin]; - pin_info->input_pin_id = 0; + pin_info->input_pin_id = input_pin; pin_info->output_res.width = av->mpix.width; pin_info->output_res.height = av->mpix.height; @@ -957,7 +1298,11 @@ ipu_isys_prepare_fw_cfg_default(struct ipu_isys_video *av, BITS_PER_BYTE), av->isys->line_align); - pin_info->pt = aq->css_pin_type; + if (input_pin_info->dt == IPU_ISYS_MIPI_CSI2_TYPE_EMBEDDED8 || + input_pin_info->dt == IPU_ISYS_MIPI_CSI2_TYPE_RGB888) + pin_info->pt = IPU_FW_ISYS_PIN_TYPE_MIPI; + else + pin_info->pt = aq->css_pin_type; pin_info->ft = av->pfmt->css_pixelformat; pin_info->send_irq = 1; memset(pin_info->ts_offsets, 0, sizeof(pin_info->ts_offsets)); @@ -965,7 +1310,7 @@ ipu_isys_prepare_fw_cfg_default(struct ipu_isys_video *av, S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; pin_info->csi_be_soc_pixel_remapping = CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; - cfg->vc = 0; + cfg->vc = ip->vc; switch (pin_info->pt) { /* non-snoopable sensor data to PSYS */ @@ -1100,11 +1445,7 @@ static int start_stream_firmware(struct ipu_isys_video *av, struct ipu_isys_video *isl_av = NULL; struct v4l2_subdev_format source_fmt = { 0 }; struct v4l2_subdev *be_sd = NULL; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0) - struct media_pad *source_pad = media_entity_remote_pad(&av->pad); -#else struct media_pad *source_pad = media_pad_remote_pad_first(&av->pad); -#endif struct ipu_fw_isys_cropping_abi *crop; enum ipu_fw_isys_send_type send_type; int rval, rvalout, tout; @@ -1119,23 +1460,10 @@ static int start_stream_firmware(struct ipu_isys_video *av, stream_cfg = to_stream_cfg_msg_buf(msg); stream_cfg->compfmt = get_comp_format(source_fmt.format.code); - stream_cfg->input_pins[0].input_res.width = source_fmt.format.width; - stream_cfg->input_pins[0].input_res.height = source_fmt.format.height; - stream_cfg->input_pins[0].dt = - ipu_isys_mbus_code_to_mipi(source_fmt.format.code); - stream_cfg->input_pins[0].mapped_dt = N_IPU_FW_ISYS_MIPI_DATA_TYPE; - stream_cfg->input_pins[0].mipi_decompression = - IPU_FW_ISYS_MIPI_COMPRESSION_TYPE_NO_COMPRESSION; - stream_cfg->input_pins[0].capture_mode = - IPU_FW_ISYS_CAPTURE_MODE_REGULAR; - if (ip->csi2 && !v4l2_ctrl_g_ctrl(ip->csi2->store_csi2_header)) - stream_cfg->input_pins[0].mipi_store_mode = - IPU_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER; stream_cfg->src = ip->source; stream_cfg->vc = 0; stream_cfg->isl_use = ip->isl_mode; - stream_cfg->nof_input_pins = 1; stream_cfg->sensor_type = IPU_FW_ISYS_SENSOR_MODE_NORMAL; /* @@ -1257,7 +1585,7 @@ static int start_stream_firmware(struct ipu_isys_video *av, reinit_completion(&ip->stream_start_completion); - if (bl) { + if (bl && source_pad && (is_support_vc(source_pad, ip))) { send_type = IPU_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; ipu_fw_isys_dump_frame_buff_set(dev, buf, stream_cfg->nof_output_pins); @@ -1290,6 +1618,21 @@ static int start_stream_firmware(struct ipu_isys_video *av, rval = -EIO; goto out_stream_close; } + if (source_pad && !is_support_vc(source_pad, ip)) { + if (bl) { + dev_dbg(dev, "start stream: capture\n"); + + ipu_fw_isys_dump_frame_buff_set(dev, buf, stream_cfg->nof_output_pins); + rval = ipu_fw_isys_complex_cmd(av->isys, ip->stream_handle, buf, + to_dma_addr(msg), sizeof(*buf), + IPU_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + + if (rval < 0) { + dev_err(dev, "can't queue buffers (%d)\n", rval); + goto out_stream_close; + } + } + } dev_dbg(dev, "start stream: complete\n"); return 0; @@ -1342,7 +1685,7 @@ static void stop_streaming_firmware(struct ipu_isys_video *av) } tout = wait_for_completion_timeout(&ip->stream_stop_completion, - IPU_LIB_CALL_TIMEOUT_JIFFIES); + IPU_LIB_CALL_TIMEOUT_JIFFIES_RESET); if (!tout) dev_err(dev, "stream stop time out\n"); else if (ip->error) @@ -1368,13 +1711,16 @@ static void close_streaming_firmware(struct ipu_isys_video *av) } tout = wait_for_completion_timeout(&ip->stream_close_completion, - IPU_LIB_CALL_TIMEOUT_JIFFIES); + IPU_LIB_CALL_TIMEOUT_JIFFIES_RESET); if (!tout) dev_err(dev, "stream close time out\n"); else if (ip->error) dev_err(dev, "stream close error: %d\n", ip->error); else dev_dbg(dev, "close stream: complete\n"); + ip->last_sequence = atomic_read(&ip->sequence); + dev_dbg(dev, "IPU_ISYS_RESET: ip->last_sequence = %d\n", + ip->last_sequence); put_stream_opened(av); put_stream_handle(av); @@ -1412,11 +1758,7 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, struct ipu_isys *isys = av->isys; struct device *dev = &isys->adev->dev; struct ipu_isys_pipeline *ip; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) struct media_graph graph; -#else - struct media_entity_graph graph; -#endif struct media_entity *entity; struct media_device *mdev = &av->isys->media_dev; struct media_pipeline *mp; @@ -1432,11 +1774,8 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, if (ip->interlaced && isys->short_packet_source == IPU_ISYS_SHORT_PACKET_FROM_RECEIVER) short_packet_queue_destroy(ip); -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) - media_pipeline_stop(&av->vdev.entity); -#else - media_pipeline_stop(av->vdev.entity.pads); -#endif + media_pipeline_stop_for_vc(av); + av->vdev.entity.pads[0].pipe = NULL; media_entity_enum_cleanup(&ip->entity_enum); return 0; } @@ -1447,7 +1786,13 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, ip->has_sof = false; ip->nr_queues = 0; ip->external = NULL; - atomic_set(&ip->sequence, 0); + if (av->isys->in_reset) { + atomic_set(&ip->sequence, ip->last_sequence); + dev_dbg(dev, "atomic_set : ip->last_sequence = %d\n", + ip->last_sequence); + } else { + atomic_set(&ip->sequence, 0); + } ip->isl_mode = IPU_ISL_OFF; for (i = 0; i < IPU_NUM_CAPTURE_DONE; i++) @@ -1465,11 +1810,7 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, if (rval) return rval; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) - rval = media_pipeline_start(&av->vdev.entity, &ip->pipe); -#else - rval = media_pipeline_start(av->vdev.entity.pads, &ip->pipe); -#endif + rval = media_pipeline_start_by_vc(av, &ip->pipe); if (rval < 0) { dev_dbg(dev, "pipeline start failed\n"); goto out_enum_cleanup; @@ -1510,11 +1851,7 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, return 0; out_pipeline_stop: -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) - media_pipeline_stop(&av->vdev.entity); -#else media_pipeline_stop(av->vdev.entity.pads); -#endif out_enum_cleanup: media_entity_enum_cleanup(&ip->entity_enum); @@ -1522,123 +1859,12 @@ int ipu_isys_video_prepare_streaming(struct ipu_isys_video *av, return rval; } -static void configure_stream_watermark(struct ipu_isys_video *av) -{ - u32 vblank, hblank; - u64 pixel_rate; - int ret = 0; - struct v4l2_subdev *esd; - struct v4l2_ctrl *ctrl; - struct ipu_isys_pipeline *ip; - struct isys_iwake_watermark *iwake_watermark; - struct v4l2_control vb = { .id = V4L2_CID_VBLANK, .value = 0 }; - struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; - struct media_pipeline *mp = media_entity_pipeline(&av->vdev.entity); - - ip = to_ipu_isys_pipeline(mp); - if (!ip->external->entity) { - WARN_ON(1); - return; - } - esd = media_entity_to_v4l2_subdev(ip->external->entity); - - av->watermark->width = av->mpix.width; - av->watermark->height = av->mpix.height; - - ret = v4l2_g_ctrl(esd->ctrl_handler, &vb); - if (!ret && vb.value >= 0) - vblank = vb.value; - else - vblank = 0; - - ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); - if (!ret && hb.value >= 0) - hblank = hb.value; - else - hblank = 0; - - ctrl = v4l2_ctrl_find(esd->ctrl_handler, V4L2_CID_PIXEL_RATE); - - if (!ctrl) - pixel_rate = DEFAULT_PIXEL_RATE; - else - pixel_rate = v4l2_ctrl_g_ctrl_int64(ctrl); - - av->watermark->vblank = vblank; - av->watermark->hblank = hblank; - av->watermark->pixel_rate = pixel_rate; - if (!pixel_rate) { - iwake_watermark = av->isys->iwake_watermark; - mutex_lock(&iwake_watermark->mutex); - iwake_watermark->force_iwake_disable = true; - mutex_unlock(&iwake_watermark->mutex); - WARN(1, "%s Invalid pixel_rate, disable iwake.\n", __func__); - return; - } -} - -static void calculate_stream_datarate(struct video_stream_watermark *watermark) -{ - u64 pixels_per_line, bytes_per_line, line_time_ns; - u64 pages_per_line, pb_bytes_per_line, stream_data_rate; - u16 sram_granulrity_shift = - (ipu_ver == IPU_VER_6 || ipu_ver == IPU_VER_6EP || - ipu_ver == IPU_VER_6EP_MTL) ? - IPU6_SRAM_GRANULRITY_SHIFT : IPU6SE_SRAM_GRANULRITY_SHIFT; - u16 sram_granulrity_size = - (ipu_ver == IPU_VER_6 || ipu_ver == IPU_VER_6EP || - ipu_ver == IPU_VER_6EP_MTL) ? - IPU6_SRAM_GRANULRITY_SIZE : IPU6SE_SRAM_GRANULRITY_SIZE; - - pixels_per_line = watermark->width + watermark->hblank; - line_time_ns = - pixels_per_line * 1000 / (watermark->pixel_rate / 1000000); - /* 2 bytes per Bayer pixel */ - bytes_per_line = watermark->width << 1; - /* bytes to IS pixel buffer pages */ - pages_per_line = bytes_per_line >> sram_granulrity_shift; - - /* pages for each line */ - pages_per_line = DIV_ROUND_UP(bytes_per_line, - sram_granulrity_size); - pb_bytes_per_line = pages_per_line << sram_granulrity_shift; - - /* data rate MB/s */ - stream_data_rate = (pb_bytes_per_line * 1000) / line_time_ns; - watermark->stream_data_rate = stream_data_rate; -} - -static void update_stream_watermark(struct ipu_isys_video *av, bool state) -{ - struct isys_iwake_watermark *iwake_watermark; - - iwake_watermark = av->isys->iwake_watermark; - if (state) { - calculate_stream_datarate(av->watermark); - mutex_lock(&iwake_watermark->mutex); - list_add(&av->watermark->stream_node, - &iwake_watermark->video_list); - mutex_unlock(&iwake_watermark->mutex); - } else { - av->watermark->stream_data_rate = 0; - mutex_lock(&iwake_watermark->mutex); - list_del(&av->watermark->stream_node); - mutex_unlock(&iwake_watermark->mutex); - } - update_watermark_setting(av->isys); -} - int ipu_isys_video_set_streaming(struct ipu_isys_video *av, unsigned int state, struct ipu_isys_buffer_list *bl) { struct device *dev = &av->isys->adev->dev; -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - struct media_device *mdev = av->vdev.entity.parent; - struct media_entity_graph graph; -#else struct media_device *mdev = av->vdev.entity.graph_obj.mdev; -#endif struct media_entity_enum entities; struct media_entity *entity, *entity2; @@ -1646,6 +1872,12 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, struct ipu_isys_pipeline *ip = to_ipu_isys_pipeline(mp); struct v4l2_subdev *sd, *esd; int rval = 0; + struct v4l2_ext_control c = {.id = V4L2_CID_IPU_SET_SUB_STREAM, }; + struct v4l2_ext_controls cs = {.count = 1, + .controls = &c, + }; + struct v4l2_query_ext_ctrl qm_ctrl = { + .id = V4L2_CID_IPU_SET_SUB_STREAM, }; dev_dbg(dev, "set stream: %d\n", state); @@ -1670,23 +1902,23 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, /* stop external sub-device now. */ dev_info(dev, "stream off %s\n", ip->external->entity->name); - v4l2_subdev_call(esd, video, s_stream, state); + if (!v4l2_query_ext_ctrl(esd->ctrl_handler, &qm_ctrl)) { + c.value64 = SUB_STREAM_SET_VALUE(ip->vc, state); + v4l2_s_ext_ctrls(NULL, esd->ctrl_handler, + esd->devnode, + esd->v4l2_dev->mdev, + &cs); + } else { + v4l2_subdev_call(esd, video, s_stream, state); + } } mutex_lock(&mdev->graph_mutex); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) media_graph_walk_start(&ip->graph, -#else - media_graph_walk_start(&graph, -#endif &av->vdev.entity); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) while ((entity = media_graph_walk_next(&ip->graph))) { -#else - while ((entity = media_graph_walk_next(&graph))) { -#endif sd = media_entity_to_v4l2_subdev(entity); /* Non-subdev nodes can be safely ignored here. */ @@ -1714,17 +1946,11 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, mutex_unlock(&mdev->graph_mutex); - if (av->aq.css_pin_type == IPU_FW_ISYS_PIN_TYPE_RAW_SOC) { - if (state) - configure_stream_watermark(av); - update_stream_watermark(av, state); - } - /* Oh crap */ if (state) { rval = start_stream_firmware(av, bl); if (rval) - goto out_update_stream_watermark; + goto out_media_entity_stop_streaming; dev_dbg(dev, "set stream: source %d, stream_handle %d\n", ip->source, ip->stream_handle); @@ -1732,11 +1958,20 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, /* Start external sub-device now. */ dev_info(dev, "stream on %s\n", ip->external->entity->name); - rval = v4l2_subdev_call(esd, video, s_stream, state); + if (!v4l2_query_ext_ctrl(esd->ctrl_handler, &qm_ctrl)) { + c.value64 = SUB_STREAM_SET_VALUE(ip->vc, state); + rval = v4l2_s_ext_ctrls(NULL, esd->ctrl_handler, + esd->devnode, + esd->v4l2_dev->mdev, + &cs); + } else { + rval = v4l2_subdev_call(esd, video, s_stream, state); + } if (rval) goto out_media_entity_stop_streaming_firmware; } else { close_streaming_firmware(av); + av->ip.vc = INVALIA_VC_ID; } if (state) @@ -1750,25 +1985,13 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, out_media_entity_stop_streaming_firmware: stop_streaming_firmware(av); -out_update_stream_watermark: - if (av->aq.css_pin_type == IPU_FW_ISYS_PIN_TYPE_RAW_SOC) - update_stream_watermark(av, 0); - out_media_entity_stop_streaming: mutex_lock(&mdev->graph_mutex); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) media_graph_walk_start(&ip->graph, -#else - media_graph_walk_start(&graph, -#endif &av->vdev.entity); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) while (state && (entity2 = media_graph_walk_next(&ip->graph)) && -#else - while (state && (entity2 = media_graph_walk_next(&graph)) && -#endif entity2 != entity) { sd = media_entity_to_v4l2_subdev(entity2); @@ -1788,33 +2011,16 @@ int ipu_isys_video_set_streaming(struct ipu_isys_video *av, return rval; } -#ifdef CONFIG_COMPAT -static long ipu_isys_compat_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - long ret = -ENOIOCTLCMD; - void __user *up = compat_ptr(arg); - - /* - * at present, there is not any private IOCTL need to compat handle - */ - if (file->f_op->unlocked_ioctl) - ret = file->f_op->unlocked_ioctl(file, cmd, (unsigned long)up); - - return ret; -} -#endif - static const struct v4l2_ioctl_ops ioctl_ops_mplane = { .vidioc_querycap = ipu_isys_vidioc_querycap, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0) .vidioc_enum_fmt_vid_cap = ipu_isys_vidioc_enum_fmt, -#else - .vidioc_enum_fmt_vid_cap_mplane = ipu_isys_vidioc_enum_fmt, -#endif .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, + .vidioc_enum_fmt_meta_cap = ipu_isys_vidioc_enum_fmt, + .vidioc_g_fmt_meta_cap = vidioc_g_fmt_vid_cap_mplane, + .vidioc_s_fmt_meta_cap = vidioc_s_fmt_vid_cap_mplane, + .vidioc_try_fmt_meta_cap = vidioc_try_fmt_vid_cap_mplane, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_prepare_buf = vb2_ioctl_prepare_buf, @@ -1838,9 +2044,6 @@ static const struct v4l2_file_operations isys_fops = { .owner = THIS_MODULE, .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, -#ifdef CONFIG_COMPAT - .compat_ioctl32 = ipu_isys_compat_ioctl, -#endif .mmap = vb2_fop_mmap, .open = video_open, .release = video_release, @@ -1860,6 +2063,7 @@ int ipu_isys_video_init(struct ipu_isys_video *av, static atomic_t video_dev_count = ATOMIC_INIT(0); const struct v4l2_ioctl_ops *ioctl_ops = NULL; int rval, video_dev_nr; + int i; mutex_init(&av->mutex); init_completion(&av->ip.stream_open_completion); @@ -1869,20 +2073,21 @@ int ipu_isys_video_init(struct ipu_isys_video *av, INIT_LIST_HEAD(&av->ip.queues); spin_lock_init(&av->ip.short_packet_queue_lock); av->ip.isys = av->isys; - - if (!av->watermark) { - av->watermark = kzalloc(sizeof(*av->watermark), GFP_KERNEL); - if (!av->watermark) { - rval = -ENOMEM; - goto out_mutex_destroy; - } + av->ip.vc = INVALIA_VC_ID; + for (i = 0; i < NR_OF_CSI2_BE_SOC_SOURCE_PADS; i++) { + memset(&av->ip.asv[i], 0, + sizeof(struct ipu_isys_sub_stream_vc)); + av->ip.asv[i].vc = INVALIA_VC_ID; } + av->reset = false; + av->skipframe = 0; av->vdev.device_caps = V4L2_CAP_STREAMING; if (pad_flags & MEDIA_PAD_FL_SINK) { av->aq.vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ioctl_ops = &ioctl_ops_mplane; av->vdev.device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE; + av->vdev.device_caps |= V4L2_CAP_META_CAPTURE; av->vdev.vfl_dir = VFL_DIR_RX; } else { av->aq.vbq.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; @@ -1917,11 +2122,7 @@ int ipu_isys_video_init(struct ipu_isys_video *av, mutex_lock(&av->mutex); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0) - rval = video_register_device(&av->vdev, VFL_TYPE_GRABBER, video_dev_nr); -#else rval = video_register_device(&av->vdev, VFL_TYPE_VIDEO, video_dev_nr); -#endif if (rval) goto out_media_entity_cleanup; @@ -1952,7 +2153,6 @@ int ipu_isys_video_init(struct ipu_isys_video *av, ipu_isys_queue_cleanup(&av->aq); out_mutex_destroy: - kfree(av->watermark); mutex_destroy(&av->mutex); return rval; @@ -1963,7 +2163,6 @@ void ipu_isys_video_cleanup(struct ipu_isys_video *av) if (!av->initialized) return; - kfree(av->watermark); video_unregister_device(&av->vdev); media_entity_cleanup(&av->vdev.entity); mutex_destroy(&av->mutex); diff --git a/drivers/media/pci/intel/ipu-isys-video.h b/drivers/media/pci/intel/ipu-isys-video.h index 3741ecb083e5..f71ea62662d2 100644 --- a/drivers/media/pci/intel/ipu-isys-video.h +++ b/drivers/media/pci/intel/ipu-isys-video.h @@ -12,10 +12,12 @@ #include #include "ipu-isys-queue.h" +#include "ipu-platform-isys.h" #define IPU_ISYS_OUTPUT_PINS 11 #define IPU_NUM_CAPTURE_DONE 2 #define IPU_ISYS_MAX_PARALLEL_SOF 2 +#define CSI2_BE_SOC_SOURCE_PADS_NUM NR_OF_CSI2_BE_SOC_STREAMS struct ipu_isys; struct ipu_isys_csi2_be_soc; @@ -41,10 +43,31 @@ struct output_pin_data { struct ipu_isys_queue *aq; }; +/* + * struct ipu_isys_sub_stream_vc + */ +struct ipu_isys_sub_stream_vc { + unsigned int substream; /* sub stream id */ + int vc; /* VC number */ + u32 width; + u32 height; + unsigned int dt; + unsigned int code; +}; + +#define SUB_STREAM_CODE(value) ((value) & 0xFFFF) +#define SUB_STREAM_H(value) (((value) >> 16) & 0xFFFF) +#define SUB_STREAM_W(value) (((value) >> 32) & 0xFFFF) +#define SUB_STREAM_DT(value) (((value) >> 48) & 0xFF) +#define SUB_STREAM_VC_ID(value) ((value) >> 56 & 0xFF) +#define SUB_STREAM_SET_VALUE(vc_id, stream_state) \ + ((((vc_id) << 8) & 0xFF00) | (stream_state)) + struct ipu_isys_pipeline { struct media_pipeline pipe; struct media_pad *external; atomic_t sequence; + int last_sequence; unsigned int seq_index; struct sequence_info seq[IPU_ISYS_MAX_PARALLEL_SOF]; int source; /* SSI stream source */ @@ -59,6 +82,7 @@ struct ipu_isys_pipeline { * Number of capture queues, write access serialised using struct * ipu_isys.stream_mutex */ + /* If it supports vc, this is number of links for the same vc. */ int nr_queues; int nr_streaming; /* Number of capture queues streaming */ int streaming; /* Has streaming been really started? */ @@ -87,30 +111,15 @@ struct ipu_isys_pipeline { spinlock_t short_packet_queue_lock; struct list_head pending_interlaced_bufs; unsigned int short_packet_trace_index; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) struct media_graph graph; -#else - struct media_entity_graph graph; -#endif -#endif struct media_entity_enum entity_enum; + unsigned int vc; + struct ipu_isys_sub_stream_vc asv[CSI2_BE_SOC_SOURCE_PADS_NUM]; }; #define to_ipu_isys_pipeline(__pipe) \ container_of((__pipe), struct ipu_isys_pipeline, pipe) -struct video_stream_watermark { - u32 width; - u32 height; - u32 vblank; - u32 hblank; - u32 frame_rate; - u64 pixel_rate; - u64 stream_data_rate; - struct list_head stream_node; -}; - struct ipu_isys_video { /* Serialise access to other fields in the struct. */ struct mutex mutex; @@ -123,6 +132,9 @@ struct ipu_isys_video { struct ipu_isys *isys; struct ipu_isys_pipeline ip; unsigned int streaming; + unsigned int reset; + unsigned int skipframe; + unsigned int start_streaming; bool packed; bool compression; bool initialized; @@ -132,8 +144,6 @@ struct ipu_isys_video { unsigned int line_header_length; /* bits */ unsigned int line_footer_length; /* bits */ - struct video_stream_watermark *watermark; - const struct ipu_isys_pixelformat * (*try_fmt_vid_mplane)(struct ipu_isys_video *av, struct v4l2_pix_format_mplane *mpix); @@ -182,4 +192,9 @@ void ipu_isys_video_add_capture_done(struct ipu_isys_pipeline *ip, (struct ipu_isys_pipeline *ip, struct ipu_fw_isys_resp_info_abi *resp)); +bool is_support_vc(struct media_pad *source_pad, + struct ipu_isys_pipeline *ip); + +bool is_has_metadata(const struct ipu_isys_pipeline *ip); + #endif /* IPU_ISYS_VIDEO_H */ diff --git a/drivers/media/pci/intel/ipu-isys.c b/drivers/media/pci/intel/ipu-isys.c index e5332165228e..dd9cad80afae 100644 --- a/drivers/media/pci/intel/ipu-isys.c +++ b/drivers/media/pci/intel/ipu-isys.c @@ -14,21 +14,17 @@ #include #include -#if IS_ENABLED(CONFIG_IPU_BRIDGE) && \ -LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) -#include -#endif #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) #include -#endif #include +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) #include #include #include #include #include #include +#endif #include "ipu.h" #include "ipu-bus.h" #include "ipu-cpd.h" @@ -42,295 +38,62 @@ LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) #include "ipu-platform.h" #include "ipu-platform-buttress-regs.h" -#define ISYS_PM_QOS_VALUE 300 +int vnode_num = NR_OF_CSI2_BE_SOC_STREAMS; +module_param(vnode_num, int, 0440); +MODULE_PARM_DESC(vnode_num, "override vnode_num default value is 16"); -#define IPU_BUTTRESS_FABIC_CONTROL 0x68 -#define GDA_ENABLE_IWAKE_INDEX 2 -#define GDA_IWAKE_THRESHOLD_INDEX 1 -#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX 0 -#define GDA_MEMOPEN_THRESHOLD_INDEX 3 - -#define DEFAULT_DID_RATIO 90 -#define IPU6EP_LTR_VALUE 200 -#define IPU6EP_MTL_LTR_VALUE 1023 -#define DEFAULT_IWAKE_THRESHOLD 0x42 -#define IPU6EP_MIN_MEMOPEN_TH 0x4 -#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc -#define DEFAULT_MEM_OPEN_TIME 10 -#define ONE_THOUSAND_MICROSECOND 1000 -/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */ -#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE 0x800 - -/* LTR & DID value are 10 bit at most */ -#define LTR_DID_VAL_MAX 1023 -#define LTR_DEFAULT_VALUE 0x70503C19 -#define FILL_TIME_DEFAULT_VALUE 0xFFF0783C -#define LTR_DID_PKGC_2R 20 -#define LTR_DID_PKGC_8 100 -#define LTR_SCALE_DEFAULT 5 -#define LTR_SCALE_1024NS 2 -#define DID_SCALE_1US 2 -#define DID_SCALE_32US 3 -#define REG_PKGC_PMON_CFG 0xB00 - -#define VAL_PKGC_PMON_CFG_RESET 0x38 -#define VAL_PKGC_PMON_CFG_START 0x7 - -#define IS_PIXEL_BUFFER_PAGES 0x80 -/* BIOS provides the driver the LTR and threshold information in IPU, - * IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6. - */ -#define IPU6_MAX_SRAM_SIZE (200 << 10) -/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE. - */ -#define IPU6SE_MAX_SRAM_SIZE (96 << 10) -/* When iwake mode is disabled the critical threshold is statically set to 75% - * of the IS pixel buffer criticalThreshold = (128 * 3) / 4 - */ -#define CRITICAL_THRESHOLD_IWAKE_DISABLE (IS_PIXEL_BUFFER_PAGES * 3 / 4) - -union fabric_ctrl { - struct { - u16 ltr_val : 10; - u16 ltr_scale : 3; - u16 RSVD1 : 3; - u16 did_val : 10; - u16 did_scale : 3; - u16 RSVD2 : 1; - u16 keep_power_in_D0 : 1; - u16 keep_power_override : 1; - } bits; - u32 value; -}; - -enum ltr_did_type { - LTR_IWAKE_ON, - LTR_IWAKE_OFF, - LTR_ISYS_ON, - LTR_ISYS_OFF, - LTR_ENHANNCE_IWAKE, - LTR_TYPE_MAX -}; - -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) -/* - * BEGIN adapted code from drivers/media/platform/omap3isp/isp.c. - * FIXME: This (in terms of functionality if not code) should be most - * likely generalised in the framework, and use made optional for - * drivers. - */ +#define ISYS_PM_QOS_VALUE 300 +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) /* - * ipu_pipeline_pm_use_count - Count the number of users of a pipeline - * @entity: The entity - * - * Return the total number of users of all video device nodes in the pipeline. + * The param was passed from module to indicate if port + * could be optimized. */ -static int ipu_pipeline_pm_use_count(struct media_pad *pad) -{ - struct media_entity_graph graph; - struct media_entity *entity = pad->entity; - int use = 0; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_init(&graph, entity->graph_obj.mdev); -#endif - media_graph_walk_start(&graph, pad); - - while ((entity = media_graph_walk_next(&graph))) { - if (is_media_entity_v4l2_io(entity)) - use += entity->use_count; - } - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_cleanup(&graph); +static bool csi2_port_optimized = true; +module_param(csi2_port_optimized, bool, 0660); +MODULE_PARM_DESC(csi2_port_optimized, "IPU CSI2 port optimization"); #endif - return use; -} -/* - * ipu_pipeline_pm_power_one - Apply power change to an entity - * @entity: The entity - * @change: Use count change - * - * Change the entity use count by @change. If the entity is a subdev update its - * power state by calling the core::s_power operation when the use count goes - * from 0 to != 0 or from != 0 to 0. - * - * Return 0 on success or a negative error code on failure. - */ -static int ipu_pipeline_pm_power_one(struct media_entity *entity, int change) -{ - struct v4l2_subdev *subdev; - int ret; - - subdev = is_media_entity_v4l2_subdev(entity) - ? media_entity_to_v4l2_subdev(entity) : NULL; - - if (entity->use_count == 0 && change > 0 && subdev) { - ret = v4l2_subdev_call(subdev, core, s_power, 1); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - } - - entity->use_count += change; - WARN_ON(entity->use_count < 0); - - if (entity->use_count == 0 && change < 0 && subdev) - v4l2_subdev_call(subdev, core, s_power, 0); - - return 0; -} +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +struct isys_i2c_test { + u8 bus_nr; + u16 addr; + struct i2c_client *client; +}; -/* - * ipu_pipeline_pm_power - Apply power change to all entities - * in a pipeline - * @entity: The entity - * @change: Use count change - * @from_pad: Starting pad - * - * Walk the pipeline to update the use count and the power state of - * all non-node - * entities. - * - * Return 0 on success or a negative error code on failure. - */ -static int ipu_pipeline_pm_power(struct media_entity *entity, - int change, int from_pad) +static int isys_i2c_test(struct device *dev, void *priv) { - struct media_entity_graph graph; - struct media_entity *first = entity; - int ret = 0; + struct i2c_client *client = i2c_verify_client(dev); + struct isys_i2c_test *test = priv; - if (!change) + if (!client) return 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_init(&graph, entity->graph_obj.mdev); -#endif - media_graph_walk_start(&graph, &entity->pads[from_pad]); - - while (!ret && (entity = media_graph_walk_next(&graph))) - if (!is_media_entity_v4l2_io(entity)) - ret = ipu_pipeline_pm_power_one(entity, change); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_cleanup(&graph); -#endif - if (!ret) + if (i2c_adapter_id(client->adapter) != test->bus_nr || + client->addr != test->addr) return 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_init(&graph, entity->graph_obj.mdev); -#endif - media_graph_walk_start(&graph, &first->pads[from_pad]); - - while ((first = media_graph_walk_next(&graph)) && - first != entity) - if (!is_media_entity_v4l2_io(first)) - ipu_pipeline_pm_power_one(first, -change); - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) - media_graph_walk_cleanup(&graph); -#endif - return ret; -} + test->client = client; -/* - * ipu_pipeline_pm_use - Update the use count of an entity - * @entity: The entity - * @use: Use (1) or stop using (0) the entity - * - * Update the use count of all entities in the pipeline and power entities - * on or off accordingly. - * - * Return 0 on success or a negative error code on failure. Powering entities - * off is assumed to never fail. No failure can occur when the use parameter is - * set to 0. - */ -int ipu_pipeline_pm_use(struct media_entity *entity, int use) -{ - int change = use ? 1 : -1; - int ret; - - mutex_lock(&entity-> -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - parent -#else - graph_obj.mdev -#endif - ->graph_mutex); - - /* Apply use count to node. */ - entity->use_count += change; - WARN_ON(entity->use_count < 0); - - /* Apply power change to connected non-nodes. */ - ret = ipu_pipeline_pm_power(entity, change, 0); - if (ret < 0) - entity->use_count -= change; - - mutex_unlock(&entity-> -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) - parent -#else - graph_obj.mdev -#endif - ->graph_mutex); - - return ret; + return 0; } -/* - * ipu_pipeline_link_notify - Link management notification callback - * @link: The link - * @flags: New link flags that will be applied - * @notification: The link's state change notification type - * (MEDIA_DEV_NOTIFY_*) - * - * React to link management on powered pipelines by updating the use count of - * all entities in the source and sink sides of the link. Entities are powered - * on or off accordingly. - * - * Return 0 on success or a negative error code on failure. Powering entities - * off is assumed to never fail. This function will not fail for disconnection - * events. - */ -static int ipu_pipeline_link_notify(struct media_link *link, u32 flags, - unsigned int notification) +static struct +i2c_client *isys_find_i2c_subdev(struct i2c_adapter *adapter, + struct ipu_isys_subdev_info *sd_info) { - struct media_entity *source = link->source->entity; - struct media_entity *sink = link->sink->entity; - int source_use = ipu_pipeline_pm_use_count(link->source); - int sink_use = ipu_pipeline_pm_use_count(link->sink); - int ret; - - if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && - !(flags & MEDIA_LNK_FL_ENABLED)) { - /* Powering off entities is assumed to never fail. */ - ipu_pipeline_pm_power(source, -sink_use, 0); - ipu_pipeline_pm_power(sink, -source_use, 0); - return 0; - } - - if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH && - (flags & MEDIA_LNK_FL_ENABLED)) { - ret = ipu_pipeline_pm_power(source, sink_use, 0); - if (ret < 0) - return ret; - - ret = ipu_pipeline_pm_power(sink, source_use, 0); - if (ret < 0) - ipu_pipeline_pm_power(source, -sink_use, 0); - - return ret; - } + struct i2c_board_info *info = &sd_info->i2c.board_info; + struct isys_i2c_test test = { + .bus_nr = i2c_adapter_id(adapter), + .addr = info->addr, + }; + int rval; - return 0; + rval = i2c_for_each_dev(&test, isys_i2c_test); + if (rval || !test.client) + return NULL; + return test.client; } - -/* END adapted code from drivers/media/platform/omap3isp/isp.c */ -#endif /* < v4.6 */ - +#endif static int isys_complete_ext_device_registration(struct ipu_isys *isys, struct v4l2_subdev *sd, @@ -368,6 +131,152 @@ isys_complete_ext_device_registration(struct ipu_isys *isys, v4l2_device_unregister_subdev(sd); return rval; } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +static int isys_register_ext_subdev(struct ipu_isys *isys, + struct ipu_isys_subdev_info *sd_info) +{ + struct i2c_adapter *adapter; + struct v4l2_subdev *sd; + struct i2c_client *client; + int rval; + int bus; + + bus = ipu_get_i2c_bus_id(sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf, + sizeof(sd_info->i2c.i2c_adapter_bdf)); + if (bus < 0) { + dev_err(&isys->adev->dev, + "getting i2c bus id for adapter %d (bdf %s) failed", + sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf); + return -ENOENT; + } + dev_info(&isys->adev->dev, + "got i2c bus id %d for adapter %d (bdf %s)", bus, + sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf); + adapter = i2c_get_adapter(bus); + if (!adapter) { + dev_warn(&isys->adev->dev, "can't find adapter\n"); + return -ENOENT; + } + + dev_info(&isys->adev->dev, + "creating new i2c subdev for %s (address %2.2x, bus %d)", + sd_info->i2c.board_info.type, sd_info->i2c.board_info.addr, + bus); + + if (sd_info->csi2) { + dev_info(&isys->adev->dev, "sensor device on CSI port: %d\n", + sd_info->csi2->port); + if (sd_info->csi2->port >= isys->pdata->ipdata->csi2.nports || + !isys->csi2[sd_info->csi2->port].isys) { + dev_warn(&isys->adev->dev, "invalid csi2 port %u\n", + sd_info->csi2->port); + rval = -EINVAL; + goto skip_put_adapter; + } + } else { + dev_info(&isys->adev->dev, "non camera subdevice\n"); + } + + client = isys_find_i2c_subdev(adapter, sd_info); + if (client) { + dev_dbg(&isys->adev->dev, "Device exists\n"); + rval = 0; + goto skip_put_adapter; + } + + sd = v4l2_i2c_new_subdev_board(&isys->v4l2_dev, adapter, + &sd_info->i2c.board_info, NULL); + if (!sd) { + dev_warn(&isys->adev->dev, "can't create new i2c subdev\n"); + rval = -EINVAL; + goto skip_put_adapter; + } + + if (!sd_info->csi2) + return 0; + + return isys_complete_ext_device_registration(isys, sd, sd_info->csi2); + +skip_put_adapter: + i2c_put_adapter(adapter); + + return rval; +} + +static int isys_unregister_ext_subdev(struct ipu_isys *isys, + struct ipu_isys_subdev_info *sd_info) +{ + struct i2c_adapter *adapter; + struct i2c_client *client; + int bus; + + bus = ipu_get_i2c_bus_id(sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf, + sizeof(sd_info->i2c.i2c_adapter_bdf)); + if (bus < 0) { + dev_err(&isys->adev->dev, + "getting i2c bus id for adapter %d (bdf %s) failed\n", + sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf); + return -ENOENT; + } + dev_dbg(&isys->adev->dev, + "got i2c bus id %d for adapter %d (bdf %s)\n", bus, + sd_info->i2c.i2c_adapter_id, + sd_info->i2c.i2c_adapter_bdf); + adapter = i2c_get_adapter(bus); + if (!adapter) { + dev_warn(&isys->adev->dev, "can't find adapter\n"); + return -ENOENT; + } + + dev_dbg(&isys->adev->dev, + "unregister i2c subdev for %s (address %2.2x, bus %d)\n", + sd_info->i2c.board_info.type, sd_info->i2c.board_info.addr, + bus); + + client = isys_find_i2c_subdev(adapter, sd_info); + if (!client) { + dev_dbg(&isys->adev->dev, "Device not exists\n"); + goto skip_put_adapter; + } + + i2c_unregister_device(client); + +skip_put_adapter: + i2c_put_adapter(adapter); + + return 0; +} + +static void isys_register_ext_subdevs(struct ipu_isys *isys) +{ + struct ipu_isys_subdev_pdata *spdata = isys->pdata->spdata; + struct ipu_isys_subdev_info **sd_info; + + if (!spdata) { + dev_info(&isys->adev->dev, "no subdevice info provided\n"); + return; + } + for (sd_info = spdata->subdevs; *sd_info; sd_info++) + isys_register_ext_subdev(isys, *sd_info); +} + +static void isys_unregister_ext_subdevs(struct ipu_isys *isys) +{ + struct ipu_isys_subdev_pdata *spdata = isys->pdata->spdata; + struct ipu_isys_subdev_info **sd_info; + + if (!spdata) + return; + + for (sd_info = spdata->subdevs; *sd_info; sd_info++) + isys_unregister_ext_subdev(isys, *sd_info); +} +#endif static void isys_unregister_subdevices(struct ipu_isys *isys) { @@ -375,6 +284,7 @@ static void isys_unregister_subdevices(struct ipu_isys *isys) &isys->pdata->ipdata->csi2; unsigned int i; + ipu_isys_csi2_be_cleanup(&isys->csi2_be); for (i = 0; i < NR_OF_CSI2_BE_SOC_DEV; i++) ipu_isys_csi2_be_soc_cleanup(&isys->csi2_be_soc[i]); @@ -387,9 +297,37 @@ static int isys_register_subdevices(struct ipu_isys *isys) const struct ipu_isys_internal_csi2_pdata *csi2 = &isys->pdata->ipdata->csi2; struct ipu_isys_csi2_be_soc *csi2_be_soc; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + struct ipu_isys_subdev_pdata *spdata = isys->pdata->spdata; + struct ipu_isys_subdev_info **sd_info; + DECLARE_BITMAP(csi2_enable, 32); +#endif unsigned int i, k; int rval; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + /* + * Here is somewhat a workaround, let each platform decide + * if csi2 port can be optimized, which means only registered + * port from pdata would be enabled. + */ + if (csi2_port_optimized && spdata) { + bitmap_zero(csi2_enable, 32); + for (sd_info = spdata->subdevs; *sd_info; sd_info++) { + if ((*sd_info)->csi2) { + i = (*sd_info)->csi2->port; + if (i >= csi2->nports) { + dev_warn(&isys->adev->dev, + "invalid csi2 port %u\n", i); + continue; + } + bitmap_set(csi2_enable, i, 1); + } + } + } else { + bitmap_fill(csi2_enable, 32); + } +#endif isys->csi2 = devm_kcalloc(&isys->adev->dev, csi2->nports, sizeof(*isys->csi2), GFP_KERNEL); if (!isys->csi2) { @@ -398,6 +336,10 @@ static int isys_register_subdevices(struct ipu_isys *isys) } for (i = 0; i < csi2->nports; i++) { +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + if (!test_bit(i, csi2_enable)) + continue; +#endif rval = ipu_isys_csi2_init(&isys->csi2[i], isys, isys->pdata->base + csi2->offsets[i], i); @@ -407,6 +349,10 @@ static int isys_register_subdevices(struct ipu_isys *isys) isys->isr_csi2_bits |= IPU_ISYS_UNISPART_IRQ_CSI2(i); } + if (vnode_num < 1 || vnode_num > NR_OF_CSI2_BE_SOC_STREAMS) { + vnode_num = NR_OF_CSI2_BE_SOC_STREAMS; + dev_warn(&isys->adev->dev, "Invalid video node number %d\n", vnode_num); } + for (k = 0; k < NR_OF_CSI2_BE_SOC_DEV; k++) { rval = ipu_isys_csi2_be_soc_init(&isys->csi2_be_soc[k], isys, k); @@ -417,7 +363,27 @@ static int isys_register_subdevices(struct ipu_isys *isys) } } + rval = ipu_isys_csi2_be_init(&isys->csi2_be, isys); + if (rval) { + dev_info(&isys->adev->dev, + "can't register raw csi2 be device\n"); + goto fail; + } + for (i = 0; i < csi2->nports; i++) { +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + if (!test_bit(i, csi2_enable)) + continue; +#endif + rval = media_create_pad_link(&isys->csi2[i].asd.sd.entity, + CSI2_PAD_SOURCE, + &isys->csi2_be.asd.sd.entity, + CSI2_BE_PAD_SINK, 0); + if (rval) { + dev_info(&isys->adev->dev, + "can't create link csi2 <=> csi2_be\n"); + goto fail; + } for (k = 0; k < NR_OF_CSI2_BE_SOC_DEV; k++) { csi2_be_soc = &isys->csi2_be_soc[k]; rval = @@ -440,296 +406,8 @@ static int isys_register_subdevices(struct ipu_isys *isys) return rval; } -/* read ltrdid threshold values from BIOS or system configuration */ -static void get_lut_ltrdid(struct ipu_isys *isys, struct ltr_did *pltr_did) -{ - struct isys_iwake_watermark *iwake_watermark = isys->iwake_watermark; - /* default values*/ - struct ltr_did ltrdid_default; - - ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE; - ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; - - if (iwake_watermark->ltrdid.lut_ltr.value) - *pltr_did = iwake_watermark->ltrdid; - else - *pltr_did = ltrdid_default; -} - -static int set_iwake_register(struct ipu_isys *isys, u32 index, u32 value) -{ - int ret = 0; - u32 req_id = index; - u32 offset = 0; - - ret = ipu_fw_isys_send_proxy_token(isys, req_id, index, offset, value); - if (ret) - dev_err(&isys->adev->dev, "write %d failed %d", index, ret); - - return ret; -} - -/* - * When input system is powered up and before enabling any new sensor capture, - * or after disabling any sensor capture the following values need to be set: - * LTR_value = LTR(usec) from calculation; - * LTR_scale = 2; - * DID_value = DID(usec) from calculation; - * DID_scale = 2; - * - * When input system is powered down, the LTR and DID values - * must be returned to the default values: - * LTR_value = 1023; - * LTR_scale = 5; - * DID_value = 1023; - * DID_scale = 2; - */ -static void set_iwake_ltrdid(struct ipu_isys *isys, - u16 ltr, - u16 did, - enum ltr_did_type use) -{ - u16 ltr_val, ltr_scale = LTR_SCALE_1024NS; - u16 did_val, did_scale = DID_SCALE_1US; - union fabric_ctrl fc; - struct ipu_device *isp = isys->adev->isp; - - switch (use) { - case LTR_IWAKE_ON: - ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX); - did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX); - ltr_scale = (ltr == LTR_DID_VAL_MAX && - did == LTR_DID_VAL_MAX) ? - LTR_SCALE_DEFAULT : LTR_SCALE_1024NS; - break; - case LTR_ISYS_ON: - case LTR_IWAKE_OFF: - ltr_val = LTR_DID_PKGC_2R; - did_val = LTR_DID_PKGC_2R; - break; - case LTR_ISYS_OFF: - ltr_val = LTR_DID_VAL_MAX; - did_val = LTR_DID_VAL_MAX; - ltr_scale = LTR_SCALE_DEFAULT; - break; - case LTR_ENHANNCE_IWAKE: - if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) { - ltr_val = LTR_DID_VAL_MAX; - did_val = LTR_DID_VAL_MAX; - ltr_scale = LTR_SCALE_DEFAULT; - } else if (did < ONE_THOUSAND_MICROSECOND) { - ltr_val = ltr; - did_val = did; - } else { - ltr_val = ltr; - /* div 90% value by 32 to account for scale change */ - did_val = did / 32; - did_scale = DID_SCALE_32US; - } - break; - default: - return; - } - - fc.value = readl(isp->base + IPU_BUTTRESS_FABIC_CONTROL); - fc.bits.ltr_val = ltr_val; - fc.bits.ltr_scale = ltr_scale; - fc.bits.did_val = did_val; - fc.bits.did_scale = did_scale; - dev_dbg(&isys->adev->dev, - "%s ltr: %d did: %d", __func__, ltr_val, did_val); - writel(fc.value, isp->base + IPU_BUTTRESS_FABIC_CONTROL); -} - -/* SW driver may clear register GDA_ENABLE_IWAKE before the FW configures the - * stream for debug purposes. Otherwise SW should not access this register. - */ -static int enable_iwake(struct ipu_isys *isys, bool enable) -{ - int ret = 0; - struct isys_iwake_watermark *iwake_watermark = isys->iwake_watermark; - - mutex_lock(&iwake_watermark->mutex); - if (iwake_watermark->iwake_enabled == enable) { - mutex_unlock(&iwake_watermark->mutex); - return ret; - } - ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable); - if (!ret) - iwake_watermark->iwake_enabled = enable; - mutex_unlock(&iwake_watermark->mutex); - return ret; -} - -void update_watermark_setting(struct ipu_isys *isys) -{ - struct isys_iwake_watermark *iwake_watermark = isys->iwake_watermark; - struct list_head *stream_node; - struct video_stream_watermark *p_watermark; - struct ltr_did ltrdid; - u16 calc_fill_time_us = 0, ltr = 0, did = 0; - enum ltr_did_type ltr_did_type; - u32 iwake_threshold, iwake_critical_threshold, page_num, mem_threshold; - u32 mem_open_threshold = 0; - u64 threshold_bytes; - u64 isys_pb_datarate_mbs = 0; - u16 sram_granulrity_shift = - (ipu_ver == IPU_VER_6 || ipu_ver == IPU_VER_6EP || - ipu_ver == IPU_VER_6EP_MTL) ? - IPU6_SRAM_GRANULRITY_SHIFT : IPU6SE_SRAM_GRANULRITY_SHIFT; - int max_sram_size = - (ipu_ver == IPU_VER_6 || ipu_ver == IPU_VER_6EP || - ipu_ver == IPU_VER_6EP_MTL) ? - IPU6_MAX_SRAM_SIZE : IPU6SE_MAX_SRAM_SIZE; - - mutex_lock(&iwake_watermark->mutex); - if (iwake_watermark->force_iwake_disable) { - set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); - set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, - CRITICAL_THRESHOLD_IWAKE_DISABLE); - mutex_unlock(&iwake_watermark->mutex); - return; - } - - if (list_empty(&iwake_watermark->video_list)) { - isys_pb_datarate_mbs = 0; - } else { - list_for_each(stream_node, &iwake_watermark->video_list) - { - p_watermark = list_entry(stream_node, - struct video_stream_watermark, - stream_node); - isys_pb_datarate_mbs += p_watermark->stream_data_rate; - } - } - mutex_unlock(&iwake_watermark->mutex); - - if (!isys_pb_datarate_mbs) { - enable_iwake(isys, false); - set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); - mutex_lock(&iwake_watermark->mutex); - set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, - CRITICAL_THRESHOLD_IWAKE_DISABLE); - mutex_unlock(&iwake_watermark->mutex); - return; - } - /* should enable iwake by default according to FW */ - enable_iwake(isys, true); - calc_fill_time_us = (u16)(max_sram_size / isys_pb_datarate_mbs); - - if (ipu_ver == IPU_VER_6EP_MTL || ipu_ver == IPU_VER_6EP) { - ltr = (ipu_ver == IPU_VER_6EP_MTL) ? - IPU6EP_MTL_LTR_VALUE : IPU6EP_LTR_VALUE; - did = calc_fill_time_us * DEFAULT_DID_RATIO / 100; - ltr_did_type = LTR_ENHANNCE_IWAKE; - } else { - get_lut_ltrdid(isys, <rdid); - - if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0) - ltr = 0; - else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1) - ltr = ltrdid.lut_ltr.bits.val0; - else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2) - ltr = ltrdid.lut_ltr.bits.val1; - else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3) - ltr = ltrdid.lut_ltr.bits.val2; - else - ltr = ltrdid.lut_ltr.bits.val3; - - did = calc_fill_time_us - ltr; - ltr_did_type = LTR_IWAKE_ON; - } - - threshold_bytes = did * isys_pb_datarate_mbs; - /* calculate iwake threshold with 2KB granularity pages */ - iwake_threshold = - max_t(u32, 1, threshold_bytes >> sram_granulrity_shift); - - iwake_threshold = min_t(u32, iwake_threshold, max_sram_size); - - set_iwake_ltrdid(isys, ltr, did, ltr_did_type); - mutex_lock(&iwake_watermark->mutex); - if (ipu_ver == IPU_VER_6EP_MTL || ipu_ver == IPU_VER_6EP) - set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, - DEFAULT_IWAKE_THRESHOLD); - else - set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, - iwake_threshold); - - if (ipu_ver == IPU_VER_6EP_MTL || ipu_ver == IPU_VER_6EP) { - /* Calculate number of pages that will be filled in 10 usec */ - page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) / - ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE; - page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) % - ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0; - - mem_threshold = (ipu_ver == IPU_VER_6EP_MTL) ? - IPU6EP_MTL_MIN_MEMOPEN_TH : IPU6EP_MIN_MEMOPEN_TH; - mem_open_threshold = max_t(u32, mem_threshold, page_num); - - dev_dbg(&isys->adev->dev, "%s mem_open_threshold: %u\n", - __func__, mem_open_threshold); - set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX, - mem_open_threshold); - } - - /* set the critical threshold to halfway between - * iwake threshold and the full buffer. - */ - iwake_critical_threshold = iwake_threshold + - (IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2; - - dev_dbg(&isys->adev->dev, "%s threshold: %u critical: %u\n", - __func__, iwake_threshold, iwake_critical_threshold); - - set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, - iwake_critical_threshold); - mutex_unlock(&iwake_watermark->mutex); - - writel(VAL_PKGC_PMON_CFG_RESET, - isys->adev->isp->base + REG_PKGC_PMON_CFG); - writel(VAL_PKGC_PMON_CFG_START, - isys->adev->isp->base + REG_PKGC_PMON_CFG); -} - -static int isys_iwake_watermark_init(struct ipu_isys *isys) -{ - struct isys_iwake_watermark *iwake_watermark; - - if (isys->iwake_watermark) - return 0; - - iwake_watermark = devm_kzalloc(&isys->adev->dev, - sizeof(*iwake_watermark), GFP_KERNEL); - if (!iwake_watermark) - return -ENOMEM; - INIT_LIST_HEAD(&iwake_watermark->video_list); - mutex_init(&iwake_watermark->mutex); - - iwake_watermark->ltrdid.lut_ltr.value = 0; - isys->iwake_watermark = iwake_watermark; - iwake_watermark->isys = isys; - iwake_watermark->iwake_enabled = false; - iwake_watermark->force_iwake_disable = false; - return 0; -} - -static int isys_iwake_watermark_cleanup(struct ipu_isys *isys) -{ - struct isys_iwake_watermark *iwake_watermark = isys->iwake_watermark; - - if (!iwake_watermark) - return -EINVAL; - mutex_lock(&iwake_watermark->mutex); - list_del(&iwake_watermark->video_list); - mutex_unlock(&iwake_watermark->mutex); - mutex_destroy(&iwake_watermark->mutex); - isys->iwake_watermark = NULL; - return 0; -} - +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) /* The .bound() notifier callback when a match is found */ -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) static int isys_notifier_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) @@ -755,44 +433,6 @@ static void isys_notifier_unbind(struct v4l2_async_notifier *notifier, dev_info(&isys->adev->dev, "unbind %s\n", sd->name); } -#else -static int isys_notifier_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *sd, - struct v4l2_async_connection *asc) -{ - struct ipu_isys *isys = container_of(notifier, - struct ipu_isys, notifier); - struct sensor_async_sd *s_asd = container_of(asc, - struct sensor_async_sd, asc); - int ret; -#if IS_ENABLED(CONFIG_IPU_BRIDGE) - - ret = ipu_bridge_instantiate_vcm(sd->dev); - if (ret) { - dev_err(&isys->adev->dev, "instantiate vcm failed\n"); - return ret; - } -#endif - - dev_info(&isys->adev->dev, "bind %s nlanes is %d port is %d\n", - sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); - ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); - if (ret) - return ret; - - return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); -} - -static void isys_notifier_unbind(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *sd, - struct v4l2_async_connection *asc) -{ - struct ipu_isys *isys = container_of(notifier, - struct ipu_isys, notifier); - - dev_info(&isys->adev->dev, "unbind %s\n", sd->name); -} -#endif static int isys_notifier_complete(struct v4l2_async_notifier *notifier) { @@ -810,7 +450,6 @@ static const struct v4l2_async_notifier_operations isys_async_ops = { .complete = isys_notifier_complete, }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) static int isys_fwnode_parse(struct device *dev, struct v4l2_fwnode_endpoint *vep, struct v4l2_async_subdev *asd) @@ -823,44 +462,7 @@ static int isys_fwnode_parse(struct device *dev, return 0; } -#endif - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) && LINUX_VERSION_CODE != KERNEL_VERSION(5, 15, 71) -static int isys_notifier_init(struct ipu_isys *isys) -{ - struct ipu_device *isp = isys->adev->isp; - size_t asd_struct_size = sizeof(struct sensor_async_subdev); - int ret; - - v4l2_async_notifier_init(&isys->notifier); - ret = v4l2_async_notifier_parse_fwnode_endpoints(&isp->pdev->dev, - &isys->notifier, - asd_struct_size, - isys_fwnode_parse); - - if (ret < 0) { - dev_err(&isys->adev->dev, - "v4l2 parse_fwnode_endpoints() failed: %d\n", ret); - return ret; - } - - if (list_empty(&isys->notifier.asd_list)) { - /* isys probe could continue with async subdevs missing */ - dev_warn(&isys->adev->dev, "no subdev found in graph\n"); - return 0; - } - - isys->notifier.ops = &isys_async_ops; - ret = v4l2_async_notifier_register(&isys->v4l2_dev, &isys->notifier); - if (ret) { - dev_err(&isys->adev->dev, - "failed to register async notifier : %d\n", ret); - v4l2_async_notifier_cleanup(&isys->notifier); - } - return ret; -} -#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) static int isys_notifier_init(struct ipu_isys *isys) { struct ipu_device *isp = isys->adev->isp; @@ -894,79 +496,7 @@ static int isys_notifier_init(struct ipu_isys *isys) return ret; } -#else -static int isys_notifier_init(struct ipu_isys *isys) -{ - const struct ipu_isys_internal_csi2_pdata *csi2 = - &isys->pdata->ipdata->csi2; - struct ipu_device *isp = isys->adev->isp; - struct device *dev = &isp->pdev->dev; - unsigned int i; - int ret; - - v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev); - for (i = 0; i < csi2->nports; i++) { - struct v4l2_fwnode_endpoint vep = { - .bus_type = V4L2_MBUS_CSI2_DPHY - }; - struct sensor_async_sd *s_asd; - struct fwnode_handle *ep; - - ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, - FWNODE_GRAPH_ENDPOINT_NEXT); - if (!ep) - continue; - - ret = v4l2_fwnode_endpoint_parse(ep, &vep); - if (ret) - goto err_parse; - - s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep, - struct - sensor_async_sd); - if (IS_ERR(s_asd)) { - ret = PTR_ERR(s_asd); - goto err_parse; - } - - s_asd->csi2.port = vep.base.port; - s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; - - fwnode_handle_put(ep); - - continue; - -err_parse: - fwnode_handle_put(ep); - return ret; - } - - if (list_empty(&isys->notifier.waiting_list)) { - /* isys probe could continue with async subdevs missing */ - dev_warn(&isys->adev->dev, "no subdev found in graph\n"); - return 0; - } - - isys->notifier.ops = &isys_async_ops; - ret = v4l2_async_nf_register(&isys->notifier); - if (ret) { - dev_err(&isys->adev->dev, - "failed to register async notifier : %d\n", ret); - v4l2_async_nf_cleanup(&isys->notifier); - } - - return ret; -} -#endif - -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 16, 0) && LINUX_VERSION_CODE != KERNEL_VERSION(5, 15, 71) -static void isys_notifier_cleanup(struct ipu_isys *isys) -{ - v4l2_async_notifier_unregister(&isys->notifier); - v4l2_async_notifier_cleanup(&isys->notifier); -} -#else static void isys_notifier_cleanup(struct ipu_isys *isys) { v4l2_async_nf_unregister(&isys->notifier); @@ -975,11 +505,7 @@ static void isys_notifier_cleanup(struct ipu_isys *isys) #endif static struct media_device_ops isys_mdev_ops = { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) - .link_notify = ipu_pipeline_link_notify, -#else .link_notify = v4l2_pipeline_link_notify, -#endif }; static int isys_register_devices(struct ipu_isys *isys) @@ -987,26 +513,15 @@ static int isys_register_devices(struct ipu_isys *isys) int rval; isys->media_dev.dev = &isys->adev->dev; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 12) isys->media_dev.ops = &isys_mdev_ops; -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) - isys->media_dev.link_notify = ipu_pipeline_link_notify; -#else - isys->media_dev.link_notify = v4l2_pipeline_link_notify; -#endif strscpy(isys->media_dev.model, IPU_MEDIA_DEV_MODEL_NAME, sizeof(isys->media_dev.model)); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) - isys->media_dev.driver_version = LINUX_VERSION_CODE; -#endif snprintf(isys->media_dev.bus_info, sizeof(isys->media_dev.bus_info), "pci:%s", dev_name(isys->adev->dev.parent->parent)); strscpy(isys->v4l2_dev.name, isys->media_dev.model, sizeof(isys->v4l2_dev.name)); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) media_device_init(&isys->media_dev); -#endif rval = media_device_register(&isys->media_dev); if (rval < 0) { @@ -1026,9 +541,13 @@ static int isys_register_devices(struct ipu_isys *isys) if (rval) goto out_v4l2_device_unregister; +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) rval = isys_notifier_init(isys); if (rval) goto out_isys_unregister_subdevices; +#else + isys_register_ext_subdevs(isys); +#endif rval = v4l2_device_register_subdev_nodes(&isys->v4l2_dev); if (rval) @@ -1037,19 +556,25 @@ static int isys_register_devices(struct ipu_isys *isys) return 0; out_isys_notifier_cleanup: +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) isys_notifier_cleanup(isys); +#else + isys_unregister_ext_subdevs(isys); +#endif +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) out_isys_unregister_subdevices: isys_unregister_subdevices(isys); +#else + isys_unregister_subdevices(isys); +#endif out_v4l2_device_unregister: v4l2_device_unregister(&isys->v4l2_dev); out_media_device_unregister: media_device_unregister(&isys->media_dev); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) media_device_cleanup(&isys->media_dev); -#endif return rval; } @@ -1057,11 +582,12 @@ static int isys_register_devices(struct ipu_isys *isys) static void isys_unregister_devices(struct ipu_isys *isys) { isys_unregister_subdevices(isys); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + isys_unregister_ext_subdevs(isys); +#endif v4l2_device_unregister(&isys->v4l2_dev); media_device_unregister(&isys->media_dev); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) media_device_cleanup(&isys->media_dev); -#endif } #ifdef CONFIG_PM @@ -1082,11 +608,7 @@ static int isys_runtime_pm_resume(struct device *dev) ipu_trace_restore(dev); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); -#else - pm_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); -#endif ret = ipu_buttress_start_tsc_sync(isp); if (ret) @@ -1103,7 +625,6 @@ static int isys_runtime_pm_resume(struct device *dev) } isys_setup_hw(isys); - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); return 0; } @@ -1126,15 +647,10 @@ static int isys_runtime_pm_suspend(struct device *dev) mutex_unlock(&isys->mutex); isys->phy_termcal_val = 0; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); -#else - pm_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); -#endif ipu_mmu_hw_cleanup(adev->mmu); - set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); return 0; } @@ -1182,35 +698,23 @@ static void isys_remove(struct ipu_bus_device *adev) list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head) { dma_free_attrs(&adev->dev, sizeof(struct isys_fw_msgs), fwmsg, fwmsg->dma_addr, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL); -#else 0); -#endif } list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head) { dma_free_attrs(&adev->dev, sizeof(struct isys_fw_msgs), fwmsg, fwmsg->dma_addr, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL -#else 0 -#endif ); } - isys_iwake_watermark_cleanup(isys); - ipu_trace_uninit(&adev->dev); +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) isys_notifier_cleanup(isys); +#endif isys_unregister_devices(isys); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) cpu_latency_qos_remove_request(&isys->pm_qos); -#else - pm_qos_remove_request(&isys->pm_qos); -#endif if (!isp->secure_mode) { ipu_cpd_free_pkg_dir(adev, isys->pkg_dir, @@ -1223,30 +727,13 @@ static void isys_remove(struct ipu_bus_device *adev) mutex_destroy(&isys->stream_mutex); mutex_destroy(&isys->mutex); + mutex_destroy(&isys->reset_mutex); if (isys->short_packet_source == IPU_ISYS_SHORT_PACKET_FROM_TUNIT) { u32 trace_size = IPU_ISYS_SHORT_PACKET_TRACE_BUFFER_SIZE; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) dma_free_coherent(&adev->dev, trace_size, isys->short_packet_trace_buffer, isys->short_packet_trace_buffer_dma_addr); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - struct dma_attrs attrs; - - init_dma_attrs(&attrs); - dma_set_attr(DMA_ATTR_NON_CONSISTENT, &attrs); - dma_free_attrs(&adev->dev, trace_size, - isys->short_packet_trace_buffer, - isys->short_packet_trace_buffer_dma_addr, - &attrs); -#else - unsigned long attrs; - - attrs = DMA_ATTR_NON_CONSISTENT; - dma_free_attrs(&adev->dev, trace_size, - isys->short_packet_trace_buffer, - isys->short_packet_trace_buffer_dma_addr, attrs); -#endif } } @@ -1271,43 +758,10 @@ static int ipu_isys_icache_prefetch_set(void *data, u64 val) return 0; } -static int isys_iwake_control_get(void *data, u64 *val) -{ - struct ipu_isys *isys = data; - struct isys_iwake_watermark *iwake_watermark = isys->iwake_watermark; - - mutex_lock(&iwake_watermark->mutex); - *val = isys->iwake_watermark->force_iwake_disable; - mutex_unlock(&iwake_watermark->mutex); - return 0; -} - -static int isys_iwake_control_set(void *data, u64 val) -{ - struct ipu_isys *isys = data; - struct isys_iwake_watermark *iwake_watermark; - - if (val != !!val) - return -EINVAL; - /* If stream is open, refuse to set iwake */ - if (isys->stream_opened) - return -EBUSY; - - iwake_watermark = isys->iwake_watermark; - mutex_lock(&iwake_watermark->mutex); - isys->iwake_watermark->force_iwake_disable = !!val; - mutex_unlock(&iwake_watermark->mutex); - return 0; -} - DEFINE_SIMPLE_ATTRIBUTE(isys_icache_prefetch_fops, ipu_isys_icache_prefetch_get, ipu_isys_icache_prefetch_set, "%llu\n"); -DEFINE_SIMPLE_ATTRIBUTE(isys_iwake_control_fops, - isys_iwake_control_get, - isys_iwake_control_set, "%llu\n"); - static int ipu_isys_init_debugfs(struct ipu_isys *isys) { struct dentry *file; @@ -1325,11 +779,6 @@ static int ipu_isys_init_debugfs(struct ipu_isys *isys) if (IS_ERR(file)) goto err; - file = debugfs_create_file("iwake_disable", 0600, - dir, isys, &isys_iwake_control_fops); - if (IS_ERR(file)) - goto err; - isys->debugfsdir = dir; #ifdef IPU_ISYS_GPC @@ -1356,11 +805,7 @@ static int alloc_fw_msg_bufs(struct ipu_isys *isys, int amount) addr = dma_alloc_attrs(&isys->adev->dev, sizeof(struct isys_fw_msgs), &dma_addr, GFP_KERNEL, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL); -#else 0); -#endif if (!addr) break; addr->dma_addr = dma_addr; @@ -1380,11 +825,7 @@ static int alloc_fw_msg_bufs(struct ipu_isys *isys, int amount) dma_free_attrs(&isys->adev->dev, sizeof(struct isys_fw_msgs), addr, addr->dma_addr, -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) - NULL); -#else 0); -#endif spin_lock_irqsave(&isys->listlock, flags); } spin_unlock_irqrestore(&isys->listlock, flags); @@ -1528,6 +969,9 @@ static int isys_probe(struct ipu_bus_device *adev) mutex_init(&isys->stream_mutex); mutex_init(&isys->lib_mutex); + mutex_init(&isys->reset_mutex); + isys->in_reset = false; + spin_lock_init(&isys->listlock); INIT_LIST_HEAD(&isys->framebuflist); INIT_LIST_HEAD(&isys->framebuflist_fw); @@ -1567,28 +1011,17 @@ static int isys_probe(struct ipu_bus_device *adev) ipu_trace_init(adev->isp, isys->pdata->base, &adev->dev, isys_trace_blocks); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0) cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); -#else - pm_qos_add_request(&isys->pm_qos, PM_QOS_CPU_DMA_LATENCY, - PM_QOS_DEFAULT_VALUE); -#endif alloc_fw_msg_bufs(isys, 20); rval = isys_register_devices(isys); if (rval) goto out_remove_pkg_dir_shared_buffer; - rval = isys_iwake_watermark_init(isys); - if (rval) - goto out_unregister_devices; ipu_mmu_hw_cleanup(adev->mmu); return 0; -out_unregister_devices: - isys_iwake_watermark_cleanup(isys); - isys_unregister_devices(isys); out_remove_pkg_dir_shared_buffer: cpu_latency_qos_remove_request(&isys->pm_qos); if (!isp->secure_mode) @@ -1606,6 +1039,8 @@ static int isys_probe(struct ipu_bus_device *adev) mutex_destroy(&isys->mutex); mutex_destroy(&isys->stream_mutex); + mutex_destroy(&isys->reset_mutex); + if (isys->short_packet_source == IPU_ISYS_SHORT_PACKET_FROM_TUNIT) mutex_destroy(&isys->short_packet_tracing_mutex); @@ -1779,11 +1214,7 @@ int isys_isr_one(struct ipu_bus_device *adev) struct vb2_buffer *vb; vb = ipu_isys_buffer_to_vb2_buffer(ib); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0) - vb->v4l2_buf.field = pipe->cur_field; -#else to_vb2_v4l2_buffer(vb)->field = pipe->cur_field; -#endif list_del(&ib->head); ipu_isys_queue_buf_done(ib); @@ -1796,7 +1227,7 @@ int isys_isr_one(struct ipu_bus_device *adev) break; case IPU_FW_ISYS_RESP_TYPE_FRAME_SOF: if (pipe->csi2) - ipu_isys_csi2_sof_event(pipe->csi2); + ipu_isys_csi2_sof_event(pipe->csi2, pipe->vc); pipe->seq[pipe->seq_index].sequence = atomic_read(&pipe->sequence) - 1; @@ -1810,7 +1241,7 @@ int isys_isr_one(struct ipu_bus_device *adev) break; case IPU_FW_ISYS_RESP_TYPE_FRAME_EOF: if (pipe->csi2) - ipu_isys_csi2_eof_event(pipe->csi2); + ipu_isys_csi2_eof_event(pipe->csi2, pipe->vc); dev_dbg(&adev->dev, "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", diff --git a/drivers/media/pci/intel/ipu-isys.h b/drivers/media/pci/intel/ipu-isys.h index 11d17c30a055..647bff951faa 100644 --- a/drivers/media/pci/intel/ipu-isys.h +++ b/drivers/media/pci/intel/ipu-isys.h @@ -47,6 +47,8 @@ #define IPU_ISYS_TURNOFF_TIMEOUT 1000 #define IPU_LIB_CALL_TIMEOUT_JIFFIES \ msecs_to_jiffies(IPU_LIB_CALL_TIMEOUT_MS) +#define IPU_LIB_CALL_TIMEOUT_JIFFIES_RESET \ + msecs_to_jiffies(200) #define IPU_ISYS_CSI2_LONG_PACKET_HEADER_SIZE 32 #define IPU_ISYS_CSI2_LONG_PACKET_FOOTER_SIZE 32 @@ -56,52 +58,10 @@ #define IPU_ISYS_MAX_WIDTH 16384U #define IPU_ISYS_MAX_HEIGHT 16384U -#ifdef CONFIG_IPU_SINGLE_BE_SOC_DEVICE -#define NR_OF_CSI2_BE_SOC_DEV 1 -#else #define NR_OF_CSI2_BE_SOC_DEV 8 -#endif - -/* the threshold granularity is 2KB on IPU6 */ -#define IPU6_SRAM_GRANULRITY_SHIFT 11 -#define IPU6_SRAM_GRANULRITY_SIZE 2048 -/* the threshold granularity is 1KB on IPU6SE */ -#define IPU6SE_SRAM_GRANULRITY_SHIFT 10 -#define IPU6SE_SRAM_GRANULRITY_SIZE 1024 struct task_struct; -struct ltr_did { - union { - u32 value; - struct { - u8 val0; - u8 val1; - u8 val2; - u8 val3; - } bits; - } lut_ltr; - union { - u32 value; - struct { - u8 th0; - u8 th1; - u8 th2; - u8 th3; - } bits; - } lut_fill_time; -}; - -struct isys_iwake_watermark { - bool iwake_enabled; - bool force_iwake_disable; - u32 iwake_threshold; - u64 isys_pixelbuffer_datarate; - struct ltr_did ltrdid; - struct mutex mutex; /* protect whole struct */ - struct ipu_isys *isys; - struct list_head video_list; -}; struct ipu_isys_sensor_info { unsigned int vc1_data_start; unsigned int vc1_data_end; @@ -196,13 +156,15 @@ struct ipu_isys { spinlock_t listlock; /* Protect framebuflist */ struct list_head framebuflist; struct list_head framebuflist_fw; +#if !IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) struct v4l2_async_notifier notifier; - struct isys_iwake_watermark *iwake_watermark; +#endif + struct mutex reset_mutex; + bool in_reset; + bool in_stop_streaming; }; -void update_watermark_setting(struct ipu_isys *isys); - struct isys_fw_msgs { union { u64 dummy; @@ -217,9 +179,6 @@ struct isys_fw_msgs { #define to_stream_cfg_msg_buf(a) (&(a)->fw_msg.stream) #define to_dma_addr(a) ((a)->dma_addr) -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) -int ipu_pipeline_pm_use(struct media_entity *entity, int use); -#endif struct isys_fw_msgs *ipu_get_fw_msg_buf(struct ipu_isys_pipeline *ip); void ipu_put_fw_mgs_buf(struct ipu_isys *isys, u64 data); void ipu_cleanup_fw_msg_bufs(struct ipu_isys *isys); diff --git a/drivers/media/pci/intel/ipu-mmu.c b/drivers/media/pci/intel/ipu-mmu.c index ffaa742eb914..dcf1bfa4123b 100644 --- a/drivers/media/pci/intel/ipu-mmu.c +++ b/drivers/media/pci/intel/ipu-mmu.c @@ -616,15 +616,7 @@ static struct ipu_dma_mapping *alloc_dma_mapping(struct ipu_device *isp) kfree(dmap); return NULL; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) - init_iova_domain(&dmap->iovad, - dmap->mmu_info->aperture_end >> PAGE_SHIFT); -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) - init_iova_domain(&dmap->iovad, SZ_4K, 1, - dmap->mmu_info->aperture_end >> PAGE_SHIFT); -#else init_iova_domain(&dmap->iovad, SZ_4K, 1); -#endif dmap->mmu_info->dmap = dmap; kref_init(&dmap->ref); diff --git a/drivers/media/pci/intel/ipu-pdata.h b/drivers/media/pci/intel/ipu-pdata.h index df70bc946ff0..4ead8db07bde 100644 --- a/drivers/media/pci/intel/ipu-pdata.h +++ b/drivers/media/pci/intel/ipu-pdata.h @@ -228,6 +228,9 @@ struct ipu_isys_internal_pdata { struct ipu_isys_pdata { void __iomem *base; const struct ipu_isys_internal_pdata *ipdata; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + struct ipu_isys_subdev_pdata *spdata; +#endif }; struct ipu_psys_internal_pdata { diff --git a/drivers/media/pci/intel/ipu-trace.c b/drivers/media/pci/intel/ipu-trace.c index 282ac659a102..2f52668c24e8 100644 --- a/drivers/media/pci/intel/ipu-trace.c +++ b/drivers/media/pci/intel/ipu-trace.c @@ -121,17 +121,10 @@ static void __ipu_trace_restore(struct device *dev) if (!sys->memory.memory_buffer) { sys->memory.memory_buffer = -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) dma_alloc_coherent(dev, MEMORY_RING_BUFFER_SIZE + MEMORY_RING_BUFFER_GUARD, &sys->memory.dma_handle, GFP_KERNEL); -#else - dma_alloc_attrs(dev, MEMORY_RING_BUFFER_SIZE + - MEMORY_RING_BUFFER_GUARD, - &sys->memory.dma_handle, - GFP_KERNEL, DMA_ATTR_NON_CONSISTENT); -#endif } if (!sys->memory.memory_buffer) { @@ -771,19 +764,11 @@ int ipu_trace_init(struct ipu_device *isp, void __iomem *base, sys->base = base; sys->blocks = blocks; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) sys->memory.memory_buffer = dma_alloc_coherent(dev, MEMORY_RING_BUFFER_SIZE + MEMORY_RING_BUFFER_GUARD, &sys->memory.dma_handle, GFP_KERNEL); -#else - sys->memory.memory_buffer = - dma_alloc_attrs(dev, MEMORY_RING_BUFFER_SIZE + - MEMORY_RING_BUFFER_GUARD, - &sys->memory.dma_handle, - GFP_KERNEL, DMA_ATTR_NON_CONSISTENT); -#endif if (!sys->memory.memory_buffer) dev_err(dev, "failed alloc memory for tracing.\n"); @@ -808,19 +793,11 @@ void ipu_trace_uninit(struct device *dev) mutex_lock(&trace->lock); if (sys->memory.memory_buffer) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) dma_free_coherent(sys->dev, MEMORY_RING_BUFFER_SIZE + MEMORY_RING_BUFFER_GUARD, sys->memory.memory_buffer, sys->memory.dma_handle); -#else - dma_free_attrs(sys->dev, - MEMORY_RING_BUFFER_SIZE + - MEMORY_RING_BUFFER_GUARD, - sys->memory.memory_buffer, - sys->memory.dma_handle, DMA_ATTR_NON_CONSISTENT); -#endif sys->dev = NULL; sys->memory.memory_buffer = NULL; diff --git a/drivers/media/pci/intel/ipu.c b/drivers/media/pci/intel/ipu.c index eb5d46a47920..3abead4479ec 100644 --- a/drivers/media/pci/intel/ipu.c +++ b/drivers/media/pci/intel/ipu.c @@ -27,34 +27,35 @@ #include "ipu-platform-regs.h" #include "ipu-platform-isys-csi2-reg.h" #include "ipu-trace.h" -#if IS_ENABLED(CONFIG_IPU_BRIDGE) && \ -LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) -#include -#elif defined(CONFIG_IPU_ISYS_BRIDGE) -#include "cio2-bridge.h" + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +#include +#endif +#endif + +#if IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) +#include #endif #define IPU_PCI_BAR 0 enum ipu_version ipu_ver; EXPORT_SYMBOL(ipu_ver); -#if IS_ENABLED(CONFIG_IPU_BRIDGE) && \ -LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) || \ -defined(CONFIG_IPU_ISYS_BRIDGE) -static int ipu_isys_check_fwnode_graph(struct fwnode_handle *fwnode) -{ - struct fwnode_handle *endpoint; +static int isys_freq_override = -1; +module_param(isys_freq_override, int, 0660); +MODULE_PARM_DESC(isys_freq_override, "override isys freq default value"); - if (IS_ERR_OR_NULL(fwnode)) - return -EINVAL; +static int psys_freq_override = -1; +module_param(psys_freq_override, int, 0660); +MODULE_PARM_DESC(psys_freq_override, "override psys freq default value"); - endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); - if (endpoint) { - fwnode_handle_put(endpoint); - return 0; - } - - return ipu_isys_check_fwnode_graph(fwnode->secondary); +#if IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) +static int isys_init_acpi_add_device(struct device *dev, void *priv, + struct ipu_isys_csi2_config *csi2, + bool reprobe) +{ + return 0; } #endif @@ -64,41 +65,18 @@ static struct ipu_bus_device *ipu_isys_init(struct pci_dev *pdev, void __iomem *base, const struct ipu_isys_internal_pdata *ipdata, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + struct ipu_isys_subdev_pdata + *spdata, +#endif unsigned int nr) { struct ipu_bus_device *isys; struct ipu_isys_pdata *pdata; - int ret; -#if IS_ENABLED(CONFIG_IPU_BRIDGE) && \ -LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) || \ -defined(CONFIG_IPU_ISYS_BRIDGE) -struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); - - ret = ipu_isys_check_fwnode_graph(fwnode); - if (ret) { - if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { - dev_err(&pdev->dev, - "fwnode graph has no endpoints connection\n"); - return ERR_PTR(-EINVAL); - } - -#if defined(CONFIG_IPU_ISYS_BRIDGE) - ret = cio2_bridge_init(pdev); - if (ret) { - dev_err_probe(&pdev->dev, ret, - "ipu_isys_bridge_init() failed\n"); - return ERR_PTR(ret); - } -#else - ret = ipu_bridge_init(&pdev->dev, ipu_bridge_parse_ssdb); - if (ret) { - dev_err_probe(&pdev->dev, ret, - "IPU bridge init failed\n"); - return ERR_PTR(ret); - } -#endif - } +#if IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) + struct ipu_isys_subdev_pdata *acpi_pdata; #endif + int ret; pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -106,6 +84,9 @@ struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); pdata->base = base; pdata->ipdata = ipdata; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + pdata->spdata = spdata; +#endif /* Use 250MHz for ipu6 se */ if (ipu_ver == IPU_VER_6SE) @@ -119,6 +100,18 @@ struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); kfree(pdata); return isys; } +#if IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) + if (!spdata) { + dev_dbg(&pdev->dev, "No subdevice info provided"); + ipu_get_acpi_devices(isys, &isys->dev, &acpi_pdata, NULL, + isys_init_acpi_add_device); + pdata->spdata = acpi_pdata; + } else { + dev_dbg(&pdev->dev, "Subdevice info found"); + ipu_get_acpi_devices(isys, &isys->dev, &acpi_pdata, &spdata, + isys_init_acpi_add_device); + } +#endif isys->mmu = ipu_mmu_init(&pdev->dev, base, ISYS_MMID, &ipdata->hw_variant); if (IS_ERR(isys->mmu)) { @@ -444,6 +437,59 @@ int request_cpd_fw(const struct firmware **firmware_p, const char *name, } EXPORT_SYMBOL(request_cpd_fw); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +static inline int match_spdata(struct ipu_isys_subdev_info *sd, + const struct ipu_spdata_rep *rep) +{ + if (strcmp(sd->i2c.board_info.type, rep->name)) + return 0; + + if (strcmp(sd->i2c.i2c_adapter_bdf, rep->i2c_adapter_bdf_o)) + return 0; + + if (sd->i2c.board_info.addr != rep->slave_addr_o) + return 0; + + if (sd->csi2->port != rep->port_o) + return 0; + + return 1; +} + +static void fixup_spdata(const void *spdata_rep, + struct ipu_isys_subdev_pdata *spdata) +{ + const struct ipu_spdata_rep *rep = spdata_rep; + struct ipu_isys_subdev_info **subdevs, *sd_info; + + if (!spdata) + return; + + for (; rep->name[0]; rep++) { + for (subdevs = spdata->subdevs; *subdevs; subdevs++) { + sd_info = *subdevs; + + if (!sd_info->csi2) + continue; + + if (match_spdata(sd_info, rep)) { + strcpy(sd_info->i2c.i2c_adapter_bdf, + rep->i2c_adapter_bdf_n); + sd_info->i2c.board_info.addr = + rep->slave_addr_n; + sd_info->csi2->port = rep->port_n; + + if (sd_info->fixup_spdata) + sd_info->fixup_spdata(rep, + sd_info->i2c.board_info.platform_data); + } + } + } +} +#endif +#endif + static int ipu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct ipu_device *isp; @@ -592,6 +638,15 @@ static int ipu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu_bus_del_devices; } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + rval = request_firmware(&isp->spdata_fw, IPU_SPDATA_NAME, &pdev->dev); + if (rval) + dev_warn(&isp->pdev->dev, "no spdata replace, using default\n"); + else + fixup_spdata(isp->spdata_fw->data, pdev->dev.platform_data); +#endif +#endif rval = ipu_trace_add(isp); if (rval) dev_err(&pdev->dev, "Trace support not available\n"); @@ -616,12 +671,27 @@ static int ipu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) isp->isys = ipu_isys_init(pdev, &pdev->dev, isys_ctrl, isys_base, &isys_ipdata, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + pdev->dev.platform_data, +#endif 0); if (IS_ERR(isp->isys)) { rval = PTR_ERR(isp->isys); goto out_ipu_bus_del_devices; } + if (isys_freq_override >= BUTTRESS_MIN_FORCE_IS_FREQ && + isys_freq_override <= BUTTRESS_MAX_FORCE_IS_FREQ) { + u64 val = isys_freq_override; + + do_div(val, BUTTRESS_IS_FREQ_STEP); + isys_ctrl->divisor = val; + dev_info(&isp->pdev->dev, + "set isys freq as (%d), actually set (%d)\n", + isys_freq_override, + isys_ctrl->divisor * BUTTRESS_IS_FREQ_STEP); + } + psys_ctrl = devm_kzalloc(&pdev->dev, sizeof(*psys_ctrl), GFP_KERNEL); if (!psys_ctrl) { rval = -ENOMEM; @@ -639,6 +709,18 @@ static int ipu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_ipu_bus_del_devices; } + if (psys_freq_override >= BUTTRESS_MIN_FORCE_PS_FREQ && + psys_freq_override <= BUTTRESS_MAX_FORCE_PS_FREQ) { + u64 val = psys_freq_override; + + do_div(val, BUTTRESS_PS_FREQ_STEP); + psys_ctrl->divisor = val; + psys_ctrl->qos_floor = val; + dev_info(&isp->pdev->dev, + "adjusted psys freq from input (%d) and set (%d)\n", + psys_freq_override, + psys_ctrl->divisor * BUTTRESS_PS_FREQ_STEP); + } rval = pm_runtime_get_sync(&isp->psys->dev); if (rval < 0) { dev_err(&isp->psys->dev, "Failed to get runtime PM\n"); @@ -721,6 +803,11 @@ static int ipu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pm_runtime_put(&isp->psys->dev); ipu_bus_del_devices(pdev); release_firmware(isp->cpd_fw); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + release_firmware(isp->spdata_fw); +#endif +#endif buttress_exit: ipu_buttress_exit(isp); @@ -758,29 +845,6 @@ static void ipu_pci_remove(struct pci_dev *pdev) release_firmware(isp->cpd_fw); } -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) -static void ipu_pci_reset_notify(struct pci_dev *pdev, bool prepare) -{ - struct ipu_device *isp = pci_get_drvdata(pdev); - - if (prepare) { - dev_err(&pdev->dev, "FLR prepare\n"); - pm_runtime_forbid(&isp->pdev->dev); - isp->flr_done = true; - return; - } - - ipu_buttress_restore(isp); - if (isp->secure_mode) - ipu_buttress_reset_authentication(isp); - - ipu_bus_flr_recovery(); - isp->ipc_reinit = true; - pm_runtime_allow(&isp->pdev->dev); - - dev_err(&pdev->dev, "FLR completed\n"); -} -#else static void ipu_pci_reset_prepare(struct pci_dev *pdev) { struct ipu_device *isp = pci_get_drvdata(pdev); @@ -804,7 +868,6 @@ static void ipu_pci_reset_done(struct pci_dev *pdev) dev_warn(&pdev->dev, "FLR completed\n"); } -#endif #ifdef CONFIG_PM @@ -905,12 +968,8 @@ static const struct pci_device_id ipu_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, ipu_pci_tbl); static const struct pci_error_handlers pci_err_handlers = { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0) - .reset_notify = ipu_pci_reset_notify, -#else .reset_prepare = ipu_pci_reset_prepare, .reset_done = ipu_pci_reset_done, -#endif }; static struct pci_driver ipu_pci_driver = { @@ -956,10 +1015,6 @@ static void __exit ipu_exit(void) module_init(ipu_init); module_exit(ipu_exit); -#if IS_ENABLED(CONFIG_IPU_BRIDGE) && \ -LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) -MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); -#endif MODULE_AUTHOR("Sakari Ailus "); MODULE_AUTHOR("Jouni Högander "); MODULE_AUTHOR("Antti Laakso "); diff --git a/drivers/media/pci/intel/ipu.h b/drivers/media/pci/intel/ipu.h index ca57b8f5d6f5..488fb02dd526 100644 --- a/drivers/media/pci/intel/ipu.h +++ b/drivers/media/pci/intel/ipu.h @@ -82,6 +82,11 @@ struct ipu_device { unsigned int pkg_dir_size; struct sg_table fw_sgt; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + const struct firmware *spdata_fw; +#endif +#endif void __iomem *base; #ifdef CONFIG_DEBUG_FS struct dentry *ipu_dir; @@ -110,17 +115,5 @@ int request_cpd_fw(const struct firmware **firmware_p, const char *name, struct device *device); extern enum ipu_version ipu_ver; void ipu_internal_pdata_init(void); -#if defined(CONFIG_IPU_ISYS_BRIDGE) -int cio2_bridge_init(struct pci_dev *cio2); -#endif - -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) -#include -/* Helpers for building against various kernel versions */ -static inline struct media_pipeline *media_entity_pipeline(struct media_entity *entity) -{ - return entity->pipe; -} -#endif #endif /* IPU_H */ diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index 23f8cdba47ae..e6e582e63d96 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -5,8 +5,9 @@ ifneq ($(EXTERNAL_BUILD), 1) srcpath := $(srctree) endif -ccflags-y += -DIPU_TPG_FRAME_SYNC -DIPU_PSYS_GPC \ - -DIPU_ISYS_GPC +ccflags-y += -DIPU_HAS_S2M -DIPU_TPG_FRAME_SYNC \ + -DIPU_ISYS_GPC -DI2C_DYNAMIC +ccflags-y += -DIPU_ISYS_RESET intel-ipu6-objs += ../ipu.o \ ../ipu-bus.o \ @@ -17,9 +18,6 @@ intel-ipu6-objs += ../ipu.o \ ../ipu-cpd.o \ ipu6.o \ ../ipu-fw-com.o -ifdef CONFIG_IPU_ISYS_BRIDGE -intel-ipu6-objs += ../cio2-bridge.o -endif obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o @@ -35,28 +33,13 @@ intel-ipu6-isys-objs += ../ipu-isys.o \ ../ipu-isys-video.o \ ../ipu-isys-queue.o \ ../ipu-isys-subdev.o +intel-ipu6-isys-objs += ../ipu-isys-csi2-be.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o -intel-ipu6-psys-objs += ../ipu-psys.o \ - ipu6-psys.o \ - ipu-resources.o \ - ipu6-psys-gpc.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 - -ifeq ($(CONFIG_COMPAT),y) -intel-ipu6-psys-objs += ../ipu-psys-compat32.o -endif - -obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-psys.o +obj-$(CONFIG_VIDEO_INTEL_IPU6) += ../psys/ ccflags-y += -I$(srcpath)/$(src)/../../../../../include/ +ccflags-y += -I$(srcpath)/$(src)/../psys/ ccflags-y += -I$(srcpath)/$(src)/../ ccflags-y += -I$(srcpath)/$(src)/ diff --git a/drivers/media/pci/intel/ipu6/ipu-platform-isys-csi2-reg.h b/drivers/media/pci/intel/ipu6/ipu-platform-isys-csi2-reg.h index 0bed87dd05b3..73fa693ad768 100644 --- a/drivers/media/pci/intel/ipu6/ipu-platform-isys-csi2-reg.h +++ b/drivers/media/pci/intel/ipu6/ipu-platform-isys-csi2-reg.h @@ -54,8 +54,8 @@ #define CSI_RX_NUM_ERRORS_IN_IRQ 20 #define CSI_RX_NUM_IRQ 32 -#define IPU_CSI_RX_IRQ_FS_VC 1 -#define IPU_CSI_RX_IRQ_FE_VC 2 +#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2)) +#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2)) /* PPI2CSI */ #define CSI_REG_PPI2CSI_ENABLE 0x200 diff --git a/drivers/media/pci/intel/ipu6/ipu-platform-isys.h b/drivers/media/pci/intel/ipu6/ipu-platform-isys.h index 70cddb263d8d..4765e2424062 100644 --- a/drivers/media/pci/intel/ipu6/ipu-platform-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu-platform-isys.h @@ -10,6 +10,9 @@ * FW support max 16 streams */ #define IPU_ISYS_MAX_STREAMS 16 +#define NR_OF_CSI2_BE_SOC_STREAMS 16 +#define NR_OF_CSI2_VC 16 +#define IPU_ISYS_CSI2_ENTITY_PREFIX "Intel IPU6 CSI-2" #define ISYS_UNISPART_IRQS (IPU_ISYS_UNISPART_IRQ_SW | \ IPU_ISYS_UNISPART_IRQ_CSI0 | \ diff --git a/drivers/media/pci/intel/ipu6/ipu-platform.h b/drivers/media/pci/intel/ipu6/ipu-platform.h index 826fe5febf5e..3db72d3cae2f 100644 --- a/drivers/media/pci/intel/ipu6/ipu-platform.h +++ b/drivers/media/pci/intel/ipu6/ipu-platform.h @@ -22,12 +22,18 @@ #define IPU6EPADLN_FIRMWARE_NAME_NEW "intel/ipu/ipu6epadln_fw.bin" #define IPU6EPMTLES_FIRMWARE_NAME_NEW "intel/ipu/ipu6epmtles_fw.bin" +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +/* array of struct ipu_spdata_rep terminated by NULL */ +#define IPU_SPDATA_NAME "ipu6v1_spdata.bin" +#endif + /* * The following definitions are encoded to the media_device's model field so * that the software components which uses IPU driver can get the hw stepping * information. */ -#define IPU_MEDIA_DEV_MODEL_NAME "ipu6" +#define IPU_MEDIA_DEV_MODEL_NAME "ipu6-downstream" #define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX #define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index 4bb5167859c1..b45f6c2027be 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -15,6 +15,9 @@ #include "ipu6-isys-dwc-phy.h" #include "ipu-isys-csi2.h" +bool enable_hw_sof_irq; +module_param(enable_hw_sof_irq, bool, 0660); +MODULE_PARM_DESC(enable_hw_sof_irq, "enable hw sof for debug!"); struct ipu6_csi2_error { const char *error_string; bool is_info_only; @@ -95,9 +98,14 @@ static int ipu6_csi2_phy_power_set(struct ipu_isys *isys, return ret; ipu6_isys_phy_reset(isys, phy_id, 0); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) + ipu6_isys_phy_common_init(isys, cfg); + ret = ipu6_isys_phy_config(isys, cfg); +#else ipu6_isys_phy_common_init(isys); ret = ipu6_isys_phy_config(isys); +#endif if (ret) return ret; @@ -541,7 +549,10 @@ int ipu_isys_csi2_set_stream(struct v4l2_subdev *sd, /* To save CPU wakeups, disable CSI SOF/EOF irq */ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); - writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + + dev_dbg(&isys->adev->dev, "HW CSI SOF irq enable %d\n", + enable_hw_sof_irq); + writel(enable_hw_sof_irq ? 0x55555555 : 0, + csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); @@ -603,6 +614,7 @@ int ipu_isys_csi2_set_stream(struct v4l2_subdev *sd, void ipu_isys_csi2_isr(struct ipu_isys_csi2 *csi2) { u32 status; + unsigned int i; ipu6_isys_register_errors(csi2); @@ -612,10 +624,13 @@ void ipu_isys_csi2_isr(struct ipu_isys_csi2 *csi2) writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); - if (status & IPU_CSI_RX_IRQ_FS_VC) - ipu_isys_csi2_sof_event(csi2); - if (status & IPU_CSI_RX_IRQ_FE_VC) - ipu_isys_csi2_eof_event(csi2); + for (i = 0; i < NR_OF_CSI2_VC; i++) { + if (status & IPU_CSI_RX_IRQ_FS_VC(i)) + ipu_isys_csi2_sof_event(csi2, i); + + if (status & IPU_CSI_RX_IRQ_FE_VC(i)) + ipu_isys_csi2_eof_event(csi2, i); + } } unsigned int ipu_isys_csi2_get_current_field(struct ipu_isys_pipeline *ip, diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c index ca6ff50b64f7..fcc1ca5c892e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c @@ -205,37 +205,13 @@ static u32 dwc_dphy_ifc_read_mask(struct ipu_isys *isys, u32 phy_id, u32 addr, static int dwc_dphy_pwr_up(struct ipu_isys *isys, u32 phy_id) { u32 fsm_state; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) - ktime_t __timeout = ktime_add_us(ktime_get(), DWC_DPHY_TIMEOUT); -#else int ret; u32 timeout = DWC_DPHY_TIMEOUT; -#endif dwc_dphy_write(isys, phy_id, IPU_DWC_DPHY_RSTZ, 1); usleep_range(10, 20); dwc_dphy_write(isys, phy_id, IPU_DWC_DPHY_SHUTDOWNZ, 1); -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) - for (;;) { - fsm_state = dwc_dphy_ifc_read_mask(isys, phy_id, 0x1e, 0, 4); - if (fsm_state == PHY_FSM_STATE_IDLE || - fsm_state == PHY_FSM_STATE_ULP) - break; - if (ktime_compare(ktime_get(), __timeout) > 0) { - fsm_state = dwc_dphy_ifc_read_mask(isys, phy_id, - 0x1e, 0, 4); - break; - } - usleep_range(50, 100); - } - - if (fsm_state != PHY_FSM_STATE_IDLE && fsm_state != PHY_FSM_STATE_ULP) { - dev_err(&isys->adev->dev, "DPHY%d power up failed, state 0x%x", - phy_id, fsm_state); - return -ETIMEDOUT; - } -#else ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state, (fsm_state == PHY_FSM_STATE_IDLE || fsm_state == PHY_FSM_STATE_ULP), 100, timeout, @@ -246,7 +222,6 @@ static int dwc_dphy_pwr_up(struct ipu_isys *isys, u32 phy_id) phy_id, fsm_state); return ret; } -#endif return 0; } diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-gpc.c b/drivers/media/pci/intel/ipu6/ipu6-isys-gpc.c index 63fbf170f7bf..b21da348eb8a 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-gpc.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-gpc.c @@ -172,15 +172,8 @@ int ipu_isys_gpc_init_debugfs(struct ipu_isys *isys) if (IS_ERR(dir)) goto err; -#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) - file = debugfs_create_bool("enable", 0600, dir, - &isys_gpcs->gpc[i].enable); - if (IS_ERR(file)) - goto err; -#else debugfs_create_bool("enable", 0600, dir, &isys_gpcs->gpc[i].enable); -#endif debugfs_create_u32("source", 0600, dir, &isys_gpcs->gpc[i].source); diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-phy.c index e8834bc523d0..88ebea54f773 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-phy.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-phy.c @@ -497,6 +497,27 @@ int ipu6_isys_phy_ready(struct ipu_isys *isys, unsigned int phy_id) return -ETIMEDOUT; } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +int ipu6_isys_phy_common_init(struct ipu_isys *isys, struct ipu_isys_csi2_config *cfg) +{ + unsigned int phy_id; + void __iomem *phy_base; + struct ipu_bus_device *adev = to_ipu_bus_device(&isys->adev->dev); + struct ipu_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + unsigned int i; + + phy_id = cfg->port / 4; + phy_base = isp_base + IPU6_ISYS_PHY_BASE(phy_id); + + for (i = 0 ; i < ARRAY_SIZE(common_init_regs); i++) { + writel(common_init_regs[i].val, + phy_base + common_init_regs[i].reg); + } + + return 0; +} +#else int ipu6_isys_phy_common_init(struct ipu_isys *isys) { unsigned int phy_id; @@ -504,21 +525,12 @@ int ipu6_isys_phy_common_init(struct ipu_isys *isys) struct ipu_bus_device *adev = to_ipu_bus_device(&isys->adev->dev); struct ipu_device *isp = adev->isp; void __iomem *isp_base = isp->base; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) struct v4l2_async_subdev *asd; struct sensor_async_subdev *s_asd; unsigned int i; list_for_each_entry(asd, &isys->notifier.asd_list, asd_list) { s_asd = container_of(asd, struct sensor_async_subdev, asd); -#else - struct v4l2_async_connection *asc; - struct sensor_async_sd *s_asd; - unsigned int i; - - list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { - s_asd = container_of(asc, struct sensor_async_sd, asc); -#endif phy_id = s_asd->csi2.port / 4; phy_base = isp_base + IPU6_ISYS_PHY_BASE(phy_id); @@ -530,6 +542,7 @@ int ipu6_isys_phy_common_init(struct ipu_isys *isys) return 0; } +#endif static int ipu6_isys_driver_port_to_phy_port(struct ipu_isys_csi2_config *cfg) { @@ -562,6 +575,55 @@ static int ipu6_isys_driver_port_to_phy_port(struct ipu_isys_csi2_config *cfg) return ret; } +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +int ipu6_isys_phy_config(struct ipu_isys *isys, struct ipu_isys_csi2_config *cfg) +{ + unsigned int phy_port, phy_id; + void __iomem *phy_base; + struct ipu_bus_device *adev = to_ipu_bus_device(&isys->adev->dev); + struct ipu_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + const struct phy_reg **phy_config_regs; + struct ipu_isys_subdev_pdata *spdata = isys->pdata->spdata; + struct ipu_isys_subdev_info **subdevs, *sd_info; + int i; + + if (!spdata) { + dev_err(&isys->adev->dev, "no subdevice info provided\n"); + return -EINVAL; + } + + phy_id = cfg->port / 4; + phy_base = isp_base + IPU6_ISYS_PHY_BASE(phy_id); + for (subdevs = spdata->subdevs; *subdevs; subdevs++) { + sd_info = *subdevs; + if (!sd_info->csi2) + continue; + + phy_port = ipu6_isys_driver_port_to_phy_port(sd_info->csi2); + if (phy_port < 0) { + dev_err(&isys->adev->dev, "invalid port %d for lane %d", + cfg->port, cfg->nlanes); + return -ENXIO; + } + + if ((sd_info->csi2->port / 4) != phy_id) + continue; + + dev_dbg(&isys->adev->dev, "port%d PHY%u lanes %u\n", + phy_port, phy_id, cfg->nlanes); + + phy_config_regs = config_regs[sd_info->csi2->nlanes/2]; + + for (i = 0; phy_config_regs[phy_port][i].reg; i++) { + writel(phy_config_regs[phy_port][i].val, + phy_base + phy_config_regs[phy_port][i].reg); + } + } + + return 0; +} +#else int ipu6_isys_phy_config(struct ipu_isys *isys) { int phy_port; @@ -571,7 +633,6 @@ int ipu6_isys_phy_config(struct ipu_isys *isys) struct ipu_device *isp = adev->isp; void __iomem *isp_base = isp->base; const struct phy_reg **phy_config_regs; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) struct v4l2_async_subdev *asd; struct sensor_async_subdev *s_asd; struct ipu_isys_csi2_config cfg; @@ -579,15 +640,6 @@ int ipu6_isys_phy_config(struct ipu_isys *isys) list_for_each_entry(asd, &isys->notifier.asd_list, asd_list) { s_asd = container_of(asd, struct sensor_async_subdev, asd); -#else - struct v4l2_async_connection *asc; - struct sensor_async_sd *s_asd; - struct ipu_isys_csi2_config cfg; - int i; - - list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { - s_asd = container_of(asc, struct sensor_async_sd, asc); -#endif cfg.port = s_asd->csi2.port; cfg.nlanes = s_asd->csi2.nlanes; phy_port = ipu6_isys_driver_port_to_phy_port(&cfg); @@ -612,3 +664,4 @@ int ipu6_isys_phy_config(struct ipu_isys *isys) return 0; } +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-phy.h b/drivers/media/pci/intel/ipu6/ipu6-isys-phy.h index c2c7c44d24ac..d769eaf86fdf 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-phy.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-phy.h @@ -154,6 +154,11 @@ int ipu6_isys_phy_powerdown_ack(struct ipu_isys *isys, unsigned int phy_id); int ipu6_isys_phy_reset(struct ipu_isys *isys, unsigned int phy_id, bool assert); int ipu6_isys_phy_ready(struct ipu_isys *isys, unsigned int phy_id); +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) +int ipu6_isys_phy_common_init(struct ipu_isys *isys, struct ipu_isys_csi2_config *cfg); +int ipu6_isys_phy_config(struct ipu_isys *isys, struct ipu_isys_csi2_config *cfg); +#else int ipu6_isys_phy_common_init(struct ipu_isys *isys); int ipu6_isys_phy_config(struct ipu_isys *isys); #endif +#endif diff --git a/drivers/media/pci/intel/psys/Makefile b/drivers/media/pci/intel/psys/Makefile new file mode 100644 index 000000000000..fbb56b7fc45c --- /dev/null +++ b/drivers/media/pci/intel/psys/Makefile @@ -0,0 +1,35 @@ +# 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 + +ccflags-y += -DIPU_OTF_SUPPORT +ccflags-y += -DIPU_PSYS_GPC + +intel-ipu6-psys-objs += ipu-psys.o \ + ipu6-psys.o \ + ipu-resources.o \ + ipu6-psys-gpc.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 + +ifeq ($(CONFIG_COMPAT),y) +intel-ipu6-psys-objs += ipu-psys-compat32.o +endif + +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/psys/ipu-fw-psys.c b/drivers/media/pci/intel/psys/ipu-fw-psys.c new file mode 100644 index 000000000000..7fd7bc827ce6 --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu-fw-psys.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2016 - 2024 Intel Corporation + +#include + +#include + +#include +#include "ipu-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; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 0); + +out: + return ret; +} + +int ipu_fw_psys_ppg_suspend(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + /* ppg suspend cmd uses QUEUE_DEVICE_ID instead of QUEUE_COMMAND_ID */ + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 1); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 1); + +out: + return ret; +} + +int ipu_fw_psys_ppg_resume(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 0); + +out: + return ret; +} + +int ipu_fw_psys_pg_abort(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_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 = ipu_recv_get_token(psys->fwcom, 0); + if (!rcv) + return 0; + + memcpy(event, rcv, sizeof(*event)); + ipu_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) +{ + 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(&kcmd->fh->psys->adev->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) +{ + u32 type; + u32 buffer_state; + u32 *buffer_ptr; + struct ipu_fw_psys_buffer_set *buf_set = kcmd->kbuf_set->buf_set; + + 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(&kcmd->fh->psys->adev->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 ipu_fw_psys_cmd *psys_cmd; + unsigned int queue_id; + int ret = 0; + unsigned int size; + + if (ipu_ver == IPU_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 = ipu_send_get_token(kcmd->fh->psys->fwcom, queue_id); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + + ipu_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) +{ + int retry = IPU_PSYS_OPEN_RETRY, retval; + + retval = ipu_fw_com_open(psys->fwcom); + if (retval) { + dev_err(&psys->adev->dev, "fw com open failed.\n"); + return retval; + } + + do { + usleep_range(IPU_PSYS_OPEN_TIMEOUT_US, + IPU_PSYS_OPEN_TIMEOUT_US + 10); + retval = ipu_fw_com_ready(psys->fwcom); + if (!retval) { + dev_dbg(&psys->adev->dev, "psys port open ready!\n"); + break; + } + } while (retry-- > 0); + + if (!retry && retval) { + dev_err(&psys->adev->dev, "psys port open ready failed %d\n", + retval); + ipu_fw_com_close(psys->fwcom); + return retval; + } + return 0; +} + +int ipu_fw_psys_close(struct ipu_psys *psys) +{ + int retval; + + retval = ipu_fw_com_close(psys->fwcom); + if (retval) { + dev_err(&psys->adev->dev, "fw com close failed.\n"); + return retval; + } + return retval; +} diff --git a/drivers/media/pci/intel/psys/ipu-fw-psys.h b/drivers/media/pci/intel/psys/ipu-fw-psys.h new file mode 100644 index 000000000000..2d20448140f3 --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu-fw-resources.c b/drivers/media/pci/intel/psys/ipu-fw-resources.c new file mode 100644 index 000000000000..dba859777434 --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu-platform-psys.h b/drivers/media/pci/intel/psys/ipu-platform-psys.h new file mode 100644 index 000000000000..dc2cae37710d --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu-platform-resources.h b/drivers/media/pci/intel/psys/ipu-platform-resources.h new file mode 100644 index 000000000000..e2e908ddfc38 --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu-psys-compat32.c b/drivers/media/pci/intel/psys/ipu-psys-compat32.c new file mode 100644 index 000000000000..32350ca2ae6b --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu-psys.c b/drivers/media/pci/intel/psys/ipu-psys.c new file mode 100644 index 000000000000..9d69a260db11 --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu-psys.c @@ -0,0 +1,1775 @@ +// 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 "ipu.h" +#include "ipu-mmu.h" +#include "ipu-bus.h" +#include "ipu-platform.h" +#include "ipu-buttress.h" +#include "ipu-cpd.h" +#include "ipu-fw-psys.h" +#include "ipu-psys.h" +#include "ipu-platform-psys.h" +#include "ipu-platform-regs.h" +#include "ipu-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 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 ipu_psys_remove(struct ipu_bus_device *adev); + +static struct bus_type ipu_psys_bus = { + .name = IPU_PSYS_NAME, +}; + +/* + * 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 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(&psys->adev->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 nr = 0; + int ret = -ENOMEM; + + 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; + + if (attach->npages != 0) { + pages = attach->pages; + npages = attach->npages; + attach->vma_is_io = 1; + goto skip_pages; + } + + pages = kvzalloc(array_size, GFP_KERNEL); + if (!pages) + goto free_sgt; + + mmap_read_lock(current->mm); + vma = find_vma(current->mm, start); + if (!vma) { + ret = -EFAULT; + goto error_up_read; + } + + /* + * For buffers from Gralloc, VM_PFNMAP is expected, + * but VM_IO is set. Possibly bug in Gralloc. + */ + attach->vma_is_io = vma->vm_flags & (VM_IO | VM_PFNMAP); + + if (attach->vma_is_io) { + unsigned long io_start = start; + + if (vma->vm_end < start + attach->len) { + dev_err(attach->dev, + "vma at %lu is too small for %llu bytes\n", + start, attach->len); + ret = -EFAULT; + goto error_up_read; + } + + for (nr = 0; nr < npages; nr++, io_start += PAGE_SIZE) { + unsigned long pfn; + + ret = follow_pfn(vma, io_start, &pfn); + if (ret) + goto error_up_read; + pages[nr] = pfn_to_page(pfn); + } + } else { + nr = get_user_pages(start & PAGE_MASK, npages, + FOLL_WRITE, + pages, NULL); + if (nr < npages) + goto error_up_read; + } + mmap_read_unlock(current->mm); + + attach->pages = pages; + attach->npages = npages; + +skip_pages: + 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 (!attach->vma_is_io) + while (nr > 0) + put_page(pages[--nr]); + + if (array_size <= PAGE_SIZE) + kfree(pages); + else + vfree(pages); +free_sgt: + kfree(sgt); + + dev_err(attach->dev, "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; + + if (!attach->vma_is_io) { + int i = attach->npages; + + while (--i >= 0) { + set_page_dirty_lock(attach->pages[i]); + put_page(attach->pages[i]); + } + } + + 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); +} + +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_device *isp = psys->adev->isp; + struct ipu_psys_fh *fh; + int rval; + + if (isp->flr_done) + return -EIO; + + 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(kbuf->dbuf, &dmap); + } + if (kbuf->sgt) + dma_buf_unmap_attachment(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 ipu_psys_kbuffer *kbuf; + struct ipu_psys_desc *desc; + + desc = psys_desc_lookup(fh, fd); + if (WARN_ON_ONCE(!desc)) { + dev_err(&psys->adev->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(&psys->adev->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 ipu_psys_desc *desc; + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct dma_buf *dbuf; + int ret; + + if (!buf->base.userptr) { + dev_err(&psys->adev->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(&psys->adev->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 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(&psys->adev->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, &psys->adev->dev); + if (IS_ERR(kbuf->db_attach)) { + dev_dbg(&psys->adev->dev, "dma buf attach failed\n"); + goto kbuf_map_fail; + } + + kbuf->sgt = dma_buf_map_attachment(kbuf->db_attach, DMA_BIDIRECTIONAL); + if (IS_ERR_OR_NULL(kbuf->sgt)) { + kbuf->sgt = NULL; + dev_dbg(&psys->adev->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(kbuf->dbuf, &dmap)) { + dev_dbg(&psys->adev->dev, "dma buf vmap failed\n"); + goto kbuf_map_fail; + } + kbuf->kaddr = dmap.vaddr; + + dev_dbg(&psys->adev->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->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->dev, "IOC_UNMAPBUF\n"); + + return ret; +} + +static unsigned int 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; + unsigned int res = 0; + + dev_dbg(&psys->adev->dev, "ipu psys poll\n"); + + poll_wait(file, &fh->wait, wait); + + if (ipu_get_completed_kcmd(fh)) + res = POLLIN; + + dev_dbg(&psys->adev->dev, "ipu psys poll res %u\n", res); + + return res; +} + +static long ipu_get_manifest(struct ipu_psys_manifest *manifest, + struct ipu_psys_fh *fh) +{ + struct ipu_psys *psys = fh->psys; + struct ipu_device *isp = psys->adev->isp; + struct ipu_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(psys->fw_sgt.sgl); + + entries = ipu_cpd_pkg_dir_get_num_entries(psys->pkg_dir); + if (!manifest || manifest->index > entries - 1) { + dev_err(&psys->adev->dev, "invalid argument\n"); + return -EINVAL; + } + + if (!ipu_cpd_pkg_dir_get_size(psys->pkg_dir, manifest->index) || + ipu_cpd_pkg_dir_get_type(psys->pkg_dir, manifest->index) < + IPU_CPD_PKG_DIR_CLIENT_PG_TYPE) { + dev_dbg(&psys->adev->dev, "invalid pkg dir entry\n"); + return -ENOENT; + } + + client_pkg_offset = ipu_cpd_pkg_dir_get_address(psys->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 ipu_bus_device *adev = to_ipu_bus_device(dev); + struct ipu_psys *psys = ipu_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 = ipu_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 (!ipu_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); + ipu_trace_restore(&psys->adev->dev); + + ipu_configure_spc(adev->isp, + &psys->pdata->ipdata->hw_variant, + IPU_CPD_PKG_DIR_PSYS_SERVER_IDX, + psys->pdata->base, psys->pkg_dir, + psys->pkg_dir_dma_addr); + + retval = ipu_fw_psys_open(psys); + if (retval) { + dev_err(&psys->adev->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 ipu_bus_device *adev = to_ipu_bus_device(dev); + struct ipu_psys *psys = ipu_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); + + ipu_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 cpd_fw_reload(struct ipu_device *isp) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(isp->psys); + int rval; + + if (!isp->secure_mode) { + dev_warn(&isp->pdev->dev, + "CPD firmware reload was only supported for secure mode.\n"); + return -EINVAL; + } + + if (isp->cpd_fw) { + ipu_cpd_free_pkg_dir(isp->psys, psys->pkg_dir, + psys->pkg_dir_dma_addr, + psys->pkg_dir_size); + + ipu_buttress_unmap_fw_image(isp->psys, &psys->fw_sgt); + release_firmware(isp->cpd_fw); + isp->cpd_fw = NULL; + dev_info(&isp->pdev->dev, "Old FW removed\n"); + } + + rval = request_cpd_fw(&isp->cpd_fw, isp->cpd_fw_name, + &isp->pdev->dev); + if (rval) { + dev_err(&isp->pdev->dev, "Requesting firmware(%s) failed\n", + isp->cpd_fw_name); + return rval; + } + + rval = ipu_cpd_validate_cpd_file(isp, isp->cpd_fw->data, + isp->cpd_fw->size); + if (rval) { + dev_err(&isp->pdev->dev, "Failed to validate cpd file\n"); + goto out_release_firmware; + } + + rval = ipu_buttress_map_fw_image(isp->psys, isp->cpd_fw, &psys->fw_sgt); + if (rval) + goto out_release_firmware; + + psys->pkg_dir = ipu_cpd_create_pkg_dir(isp->psys, + isp->cpd_fw->data, + sg_dma_address(psys->fw_sgt.sgl), + &psys->pkg_dir_dma_addr, + &psys->pkg_dir_size); + + if (!psys->pkg_dir) { + rval = -EINVAL; + goto out_unmap_fw_image; + } + + isp->pkg_dir = psys->pkg_dir; + isp->pkg_dir_dma_addr = psys->pkg_dir_dma_addr; + isp->pkg_dir_size = psys->pkg_dir_size; + + if (!isp->secure_mode) + return 0; + + rval = ipu_fw_authenticate(isp, 1); + if (rval) + goto out_free_pkg_dir; + + return 0; + +out_free_pkg_dir: + ipu_cpd_free_pkg_dir(isp->psys, psys->pkg_dir, + psys->pkg_dir_dma_addr, psys->pkg_dir_size); +out_unmap_fw_image: + ipu_buttress_unmap_fw_image(isp->psys, &psys->fw_sgt); +out_release_firmware: + release_firmware(isp->cpd_fw); + isp->cpd_fw = NULL; + + return rval; +} + +#ifdef CONFIG_DEBUG_FS +static int ipu_psys_icache_prefetch_sp_get(void *data, u64 *val) +{ + struct ipu_psys *psys = data; + + *val = psys->icache_prefetch_sp; + return 0; +} + +static int ipu_psys_icache_prefetch_sp_set(void *data, u64 val) +{ + struct ipu_psys *psys = data; + + if (val != !!val) + return -EINVAL; + + psys->icache_prefetch_sp = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(psys_icache_prefetch_sp_fops, + ipu_psys_icache_prefetch_sp_get, + ipu_psys_icache_prefetch_sp_set, "%llu\n"); + +static int ipu_psys_icache_prefetch_isp_get(void *data, u64 *val) +{ + struct ipu_psys *psys = data; + + *val = psys->icache_prefetch_isp; + return 0; +} + +static int ipu_psys_icache_prefetch_isp_set(void *data, u64 val) +{ + struct ipu_psys *psys = data; + + if (val != !!val) + return -EINVAL; + + psys->icache_prefetch_isp = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(psys_icache_prefetch_isp_fops, + ipu_psys_icache_prefetch_isp_get, + ipu_psys_icache_prefetch_isp_set, "%llu\n"); + +static int ipu_psys_init_debugfs(struct ipu_psys *psys) +{ + struct dentry *file; + struct dentry *dir; + + dir = debugfs_create_dir("psys", psys->adev->isp->ipu_dir); + if (IS_ERR(dir)) + return -ENOMEM; + + file = debugfs_create_file("icache_prefetch_sp", 0600, + dir, psys, &psys_icache_prefetch_sp_fops); + if (IS_ERR(file)) + goto err; + + file = debugfs_create_file("icache_prefetch_isp", 0600, + dir, psys, &psys_icache_prefetch_isp_fops); + if (IS_ERR(file)) + goto err; + + psys->debugfsdir = dir; + + return 0; +err: + debugfs_remove_recursive(dir); + return -ENOMEM; +} +#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 ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + void __iomem *spc_regs_base = psys->pdata->base + + psys->pdata->ipdata->hw_variant.spc_offset; + u32 val = 0; + + val |= IPU_PSYS_SPC_STATUS_START | + IPU_PSYS_SPC_STATUS_RUN | + IPU_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; + val |= psys->icache_prefetch_sp ? + IPU_PSYS_SPC_STATUS_ICACHE_PREFETCH : 0; + writel(val, spc_regs_base + IPU_PSYS_REG_SPC_STATUS_CTRL); +} + +static int query_sp(struct ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_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 + IPU_PSYS_REG_SPC_STATUS_CTRL); + + /* return true when READY == 1, START == 0 */ + val &= IPU_PSYS_SPC_STATUS_READY | IPU_PSYS_SPC_STATUS_START; + + return val == IPU_PSYS_SPC_STATUS_READY; +} + +static int ipu_psys_fw_init(struct ipu_psys *psys) +{ + unsigned int size; + struct ipu_fw_syscom_queue_config *queue_cfg; + struct ipu_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 ipu_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 == IPU_VER_6 || ipu_ver == IPU_VER_6EP || ipu_ver == IPU_VER_6EP_MTL) + size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; + + queue_cfg = devm_kzalloc(&psys->adev->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 = ipu_fw_com_prepare(&fwcom, psys->adev, psys->pdata->base); + if (!psys->fwcom) { + dev_err(&psys->adev->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; + int rval; + + rval = ipu_psys_fw_init(psys); + + if (rval) { + dev_err(&psys->adev->dev, "FW init failed(%d)\n", rval); + ipu_psys_remove(psys->adev); + } else { + dev_info(&psys->adev->dev, "FW init done\n"); + } +} + +static int ipu_psys_probe(struct ipu_bus_device *adev) +{ + struct ipu_device *isp = adev->isp; + struct ipu_psys_pg *kpg, *kpg0; + struct ipu_psys *psys; + unsigned int minor; + int i, rval = -E2BIG; + + /* firmware is not ready, so defer the probe */ + if (!isp->pkg_dir) + return -EPROBE_DEFER; + + rval = ipu_mmu_hw_init(adev->mmu); + if (rval) + return rval; + + 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(&adev->dev, "too many devices\n"); + goto out_unlock; + } + + psys = devm_kzalloc(&adev->dev, sizeof(*psys), GFP_KERNEL); + if (!psys) { + rval = -ENOMEM; + goto out_unlock; + } + + psys->adev = adev; + psys->pdata = adev->pdata; + psys->icache_prefetch_sp = 0; + + psys->power_gating = 0; + + ipu_trace_init(adev->isp, psys->pdata->base, &adev->dev, + psys_trace_blocks); + + 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(&adev->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; + } + + ipu_bus_set_drvdata(adev, 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(); + psys->pkg_dir = isp->pkg_dir; + psys->pkg_dir_dma_addr = isp->pkg_dir_dma_addr; + psys->pkg_dir_size = isp->pkg_dir_size; + psys->fw_sgt = isp->fw_sgt; + + /* 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(&adev->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 = ipu_cpd_pkg_dir_get_num_entries(psys->pkg_dir); + + dev_info(&adev->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(&adev->dev, "FW init failed(%d)\n", rval); + goto out_free_pgs; + } + + psys->dev.parent = &adev->dev; + psys->dev.bus = &ipu_psys_bus; + 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(&psys->dev, "psys device_register failed\n"); + goto out_release_fw_com; + } + + /* Add the hw stepping information to caps */ + strscpy(psys->caps.dev_model, IPU_MEDIA_DEV_MODEL_NAME, + sizeof(psys->caps.dev_model)); + + mutex_unlock(&ipu_psys_mutex); + +#ifdef CONFIG_DEBUG_FS + /* Debug fs failure is not fatal. */ + ipu_psys_init_debugfs(psys); +#endif + + adev->isp->cpd_fw_reload = &cpd_fw_reload; + + dev_info(&adev->dev, "psys probe minor: %d\n", minor); + + ipu_mmu_hw_cleanup(adev->mmu); + + return 0; + +out_release_fw_com: + ipu_fw_com_release(psys->fwcom, 1); +out_free_pgs: + list_for_each_entry_safe(kpg, kpg0, &psys->pgs, list) { + dma_free_attrs(&adev->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 */ + ipu_trace_uninit(&adev->dev); + mutex_unlock(&ipu_psys_mutex); + + ipu_mmu_hw_cleanup(adev->mmu); + + return rval; +} + +static void ipu_psys_remove(struct ipu_bus_device *adev) +{ + struct ipu_device *isp = adev->isp; + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + struct ipu_psys_pg *kpg, *kpg0; + +#ifdef CONFIG_DEBUG_FS + if (isp->ipu_dir) + debugfs_remove_recursive(psys->debugfsdir); +#endif + + 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(&adev->dev, kpg->size, kpg->pg, + kpg->pg_dma_addr, 0); + kfree(kpg); + } + + if (psys->fwcom && ipu_fw_com_release(psys->fwcom, 1)) + dev_err(&adev->dev, "fw com release failed.\n"); + + kfree(psys->server_init); + kfree(psys->syscom_config); + + ipu_trace_uninit(&adev->dev); + + 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(&adev->dev, "removed\n"); +} + +static irqreturn_t psys_isr_threaded(struct ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + void __iomem *base = psys->pdata->base; + u32 status; + int r; + + mutex_lock(&psys->mutex); +#ifdef CONFIG_PM + r = pm_runtime_get_if_in_use(&psys->adev->dev); + if (!r || WARN_ON_ONCE(r < 0)) { + mutex_unlock(&psys->mutex); + return IRQ_NONE; + } +#endif + + status = readl(base + IPU_REG_PSYS_GPDEV_IRQ_STATUS); + writel(status, base + IPU_REG_PSYS_GPDEV_IRQ_CLEAR); + + if (status & IPU_PSYS_GPDEV_IRQ_FWIRQ(IPU_PSYS_GPDEV_FWIRQ0)) { + writel(0, base + IPU_REG_PSYS_GPDEV_FWIRQ(0)); + ipu_psys_handle_events(psys); + } + + pm_runtime_put(&psys->adev->dev); + mutex_unlock(&psys->mutex); + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static struct ipu_bus_driver ipu_psys_driver = { + .probe = ipu_psys_probe, + .remove = ipu_psys_remove, + .isr_threaded = psys_isr_threaded, + .wanted = IPU_PSYS_NAME, + .drv = { + .name = IPU_PSYS_NAME, + .owner = THIS_MODULE, + .pm = PSYS_PM_OPS, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ipu_psys_init(void) +{ + int rval = alloc_chrdev_region(&ipu_psys_dev_t, 0, + IPU_PSYS_NUM_DEVICES, IPU_PSYS_NAME); + if (rval) { + pr_err("can't alloc psys chrdev region (%d)\n", rval); + return rval; + } + + rval = bus_register(&ipu_psys_bus); + if (rval) { + pr_warn("can't register psys bus (%d)\n", rval); + goto out_bus_register; + } + + ipu_bus_register_driver(&ipu_psys_driver); + + return rval; + +out_bus_register: + unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); + + return rval; +} + +static void __exit ipu_psys_exit(void) +{ + ipu_bus_unregister_driver(&ipu_psys_driver); + bus_unregister(&ipu_psys_bus); + unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); +} + +static const struct pci_device_id ipu_pci_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6SE_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_P_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_N_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_RPL_P_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_MTL_PCI_ID)}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, ipu_pci_tbl); + +module_init(ipu_psys_init); +module_exit(ipu_psys_exit); + +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); diff --git a/drivers/media/pci/intel/psys/ipu-psys.h b/drivers/media/pci/intel/psys/ipu-psys.h new file mode 100644 index 000000000000..450ed6dd8660 --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu-psys.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2013 - 2024 Intel Corporation */ + +#ifndef IPU_PSYS_H +#define IPU_PSYS_H + +#include +#include + +#include +#include "ipu.h" +#include "ipu-pdata.h" +#include "ipu-fw-psys.h" +#include "ipu-platform-psys.h" + +#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 + +/* 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 ipu_psys_pdata *pdata; + struct ipu_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 */ +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfsdir; +#endif + + /* 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 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 ipu_buttress_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; + bool vma_is_io; + 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/psys/ipu-resources.c b/drivers/media/pci/intel/psys/ipu-resources.c new file mode 100644 index 000000000000..b607b0716f56 --- /dev/null +++ b/drivers/media/pci/intel/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 == IPU_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 == IPU_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 == IPU_VER_6EP || ipu_ver == IPU_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 == IPU_VER_6SE) + return ipu6se_res_defs; + + if (ipu_ver == IPU_VER_6EP || ipu_ver == IPU_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 == IPU_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 == IPU_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/psys/ipu6-fw-resources.c b/drivers/media/pci/intel/psys/ipu6-fw-resources.c new file mode 100644 index 000000000000..fa89a4a7ba7e --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu6-fw-resources.c @@ -0,0 +1,609 @@ +// 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; + 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 == IPU_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 == IPU_VER_6 || ipu_ver == IPU_VER_6EP || + ipu_ver == IPU_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(&psys->adev->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(&psys->adev->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(&psys->adev->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(&psys->adev->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 == IPU_VER_6SE || ipu_ver == IPU_VER_6 || + ipu_ver == IPU_VER_6EP || ipu_ver == IPU_VER_6EP_MTL) + return; + + WARN(1, "%s ipu_ver:[%u] is unsupported!\n", __func__, ipu_ver); +} +#endif diff --git a/drivers/media/pci/intel/psys/ipu6-l-scheduler.c b/drivers/media/pci/intel/psys/ipu6-l-scheduler.c new file mode 100644 index 000000000000..0d5f84a1ee31 --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu6-l-scheduler.c @@ -0,0 +1,615 @@ +// 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; + + mutex_lock(&sc_list->lock); + list_for_each_entry_safe(tmp0, tmp1, &sc_list->list, sched_list) { + if (tmp0 == kppg) { + dev_dbg(&psys->adev->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 ipu_psys_ppg *tmp0, *tmp1; + + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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; + 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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(&psys->adev->dev, + "ppg is suspending/stopping\n"); + } else { + dev_err(&psys->adev->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) +{ + 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(&psys->adev->dev, "ppg %p stopped!\n", kppg); + ipu_psys_kcmd_complete(kppg, kcmd, -EIO); + } + } + + if (old_ppg_state != kppg->state) + dev_dbg(&psys->adev->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) +{ + /* Assume power gating process can be aborted directly during START */ + if (psys->power_gating == PSYS_POWER_GATED) { + dev_dbg(&psys->adev->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 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(&psys->adev->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) +{ + /* 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(&psys->adev->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/psys/ipu6-platform-resources.h b/drivers/media/pci/intel/psys/ipu6-platform-resources.h new file mode 100644 index 000000000000..4e275d32740a --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu6-ppg.c b/drivers/media/pci/intel/psys/ipu6-ppg.c new file mode 100644 index 000000000000..0345d3bc5c2b --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu6-ppg.c @@ -0,0 +1,561 @@ +// 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 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(&fh->psys->adev->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 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(&psys->adev->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 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(&psys->adev->dev, "Unable to set bufset\n"); + goto error; + } + } + + return 0; + +error: + dev_err(&psys->adev->dev, "failed to get buffer set\n"); + return ret; +} + +void ipu_psys_ppg_complete(struct ipu_psys *psys, struct ipu_psys_ppg *kppg) +{ + u8 queue_id; + int old_ppg_state; + + if (!psys || !kppg) + return; + + 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(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg, + KCMD_STATE_PPG_START); + unsigned int i; + int ret; + + if (!kcmd) { + dev_err(&psys->adev->dev, "failed to find start kcmd!\n"); + return -EINVAL; + } + + dev_dbg(&psys->adev->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(&psys->adev->dev, "Unable to set terminal\n"); + return ret; + } + } + + ret = ipu_fw_psys_pg_submit(kcmd); + if (ret) { + dev_err(&psys->adev->dev, "failed to submit kcmd!\n"); + return ret; + } + + ret = ipu_psys_allocate_resources(&psys->adev->dev, + kcmd->kpg->pg, + kcmd->pg_manifest, + &kcmd->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "alloc resources failed!\n"); + return ret; + } + + ret = pm_runtime_get_sync(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->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(&psys->adev->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(&psys->adev->dev); + ipu_psys_reset_process_cell(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd tmp_kcmd = { + .kpg = kppg->kpg, + .fh = kppg->fh, + }; + int ret; + + dev_dbg(&psys->adev->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(&psys->adev->dev, + kppg->kpg->pg, + kppg->manifest, + &kppg->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "failed to allocate res\n"); + return -EIO; + } + + ret = ipu_fw_psys_ppg_resume(&tmp_kcmd); + if (ret) { + dev_err(&psys->adev->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(&psys->adev->dev, "failed to submit kcmd!\n"); + return ret; + } + + ret = ipu_psys_allocate_resources(&psys->adev->dev, + kppg->kpg->pg, + kppg->manifest, + &kppg->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "failed to allocate res\n"); + return ret; + } + + ret = ipu_psys_kcmd_start(psys, &tmp_kcmd); + if (ret) { + dev_err(&psys->adev->dev, "failed to start kcmd!\n"); + goto error; + } + } + dev_dbg(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd kcmd_temp; + int ppg_id, ret = 0; + + if (kcmd) { + list_move_tail(&kcmd->list, &kppg->kcmds_processing_list); + } else { + dev_dbg(&psys->adev->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(&psys->adev->dev, "stop ppg(%d, addr 0x%p)\n", ppg_id, kppg); + + if (kppg->state & PPG_STATE_SUSPENDED) { + if (enable_suspend_resume) { + dev_dbg(&psys->adev->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(&psys->adev->dev, + "ppg(%d) failed to resume\n", ppg_id); + } else if (kcmd != &kcmd_temp) { + ipu_psys_free_cmd_queue_resource( + &psys->resource_pool_running, + ipu_fw_psys_ppg_get_base_queue_id(kcmd)); + ipu_psys_kcmd_complete(kppg, kcmd, 0); + dev_dbg(&psys->adev->dev, + "s_change:%s %p %d -> %d\n", __func__, + kppg, kppg->state, PPG_STATE_STOPPED); + pm_runtime_put(&psys->adev->dev); + kppg->state = PPG_STATE_STOPPED; + return 0; + } else { + return 0; + } + } + dev_dbg(&psys->adev->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(&psys->adev->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 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(&psys->adev->dev, "suspend ppg(%d, addr 0x%p)\n", ppg_id, kppg); + + dev_dbg(&psys->adev->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(&psys->adev->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; + 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(&psys->adev->dev, + "kppg 0x%p fail to qbufset %d", + kppg, ret); + break; + } + list_move_tail(&kcmd->list, + &kppg->kcmds_processing_list); + dev_dbg(&psys->adev->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 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, STOPPED kppgs has already + * power down and new kppgs might come now. + */ + if (kppg->state != PPG_STATE_SUSPENDED) { + mutex_unlock(&kppg->mutex); + continue; + } + + ret = pm_runtime_put(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->dev, + "failed to power gating off\n"); + pm_runtime_get_sync(&psys->adev->dev); + + } + mutex_unlock(&kppg->mutex); + } + mutex_unlock(&fh->mutex); + } +} + +void ipu_psys_exit_power_gating(struct ipu_psys *psys) +{ + 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(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->dev, + "failed to power gating\n"); + pm_runtime_put_noidle(&psys->adev->dev); + } + mutex_unlock(&kppg->mutex); + } + mutex_unlock(&fh->mutex); + } +} diff --git a/drivers/media/pci/intel/psys/ipu6-ppg.h b/drivers/media/pci/intel/psys/ipu6-ppg.h new file mode 100644 index 000000000000..676a4eadc23d --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu6-psys-gpc.c b/drivers/media/pci/intel/psys/ipu6-psys-gpc.c new file mode 100644 index 000000000000..ff1925693558 --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu6-psys.c b/drivers/media/pci/intel/psys/ipu6-psys.c new file mode 100644 index 000000000000..10e8919dde1d --- /dev/null +++ b/drivers/media/pci/intel/psys/ipu6-psys.c @@ -0,0 +1,1020 @@ +// 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 "ipu.h" +#include "ipu-psys.h" +#include "ipu6-ppg.h" +#include "ipu-platform-regs.h" +#include "ipu-trace.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"); + +struct ipu_trace_block psys_trace_blocks[] = { + { + .offset = IPU_TRACE_REG_PS_TRACE_UNIT_BASE, + .type = IPU_TRACE_BLOCK_TUN, + }, + { + .offset = IPU_TRACE_REG_PS_SPC_EVQ_BASE, + .type = IPU_TRACE_BLOCK_TM, + }, + { + .offset = IPU_TRACE_REG_PS_SPP0_EVQ_BASE, + .type = IPU_TRACE_BLOCK_TM, + }, + { + .offset = IPU_TRACE_REG_PS_SPC_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_SPP0_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_MMU_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_GPREG_TRACE_TIMER_RST_N, + .type = IPU_TRACE_TIMER_RST, + }, + { + .type = IPU_TRACE_BLOCK_END, + } +}; + +static void ipu6_set_sp_info_bits(void *base) +{ + int i; + + writel(IPU_INFO_REQUEST_DESTINATION_IOSF, + base + IPU_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); + + for (i = 0; i < 4; i++) + writel(IPU_INFO_REQUEST_DESTINATION_IOSF, + base + IPU_REG_PSYS_INFO_SEG_CMEM_MASTER(i)); + for (i = 0; i < 4; i++) + writel(IPU_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) +{ + unsigned int i; + u32 val; + + /* power domain req */ + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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 *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 + + IPU_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 = IPU_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 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(&psys->adev->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(&psys->adev->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(&psys->adev->dev, + "err: all buffer.flags&DMA_HANDLE must 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(&psys->adev->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(&psys->adev->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(&psys->adev->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(&psys->adev->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; +} + +/* + * 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; + + 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(&psys->adev->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) +{ + int ret; + + if (psys->adev->isp->flr_done) + return -EIO; + + 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->dev, "token not match\n"); + return -EINVAL; + } + + kcmd->kpg = kppg->kpg; + + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd *kcmd; + size_t pg_size; + int ret; + + if (psys->adev->isp->flr_done) + return -EIO; + + 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(&psys->adev->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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->dev, "invalid event\n"); + continue; + } + + if (error || !kppg) { + dev_err(&psys->adev->dev, "event error, command %d\n", + cmd); + break; + } + + dev_dbg(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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(&psys->adev->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 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(&fh->psys->adev->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 ipu_psys_kcmd *kcmd = NULL; + int rval; + + dev_dbg(&psys->adev->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/psys/ipu6ep-fw-resources.c b/drivers/media/pci/intel/psys/ipu6ep-fw-resources.c new file mode 100644 index 000000000000..4a1a86989d2e --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu6ep-platform-resources.h b/drivers/media/pci/intel/psys/ipu6ep-platform-resources.h new file mode 100644 index 000000000000..842014973206 --- /dev/null +++ b/drivers/media/pci/intel/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/psys/ipu6se-fw-resources.c b/drivers/media/pci/intel/psys/ipu6se-fw-resources.c new file mode 100644 index 000000000000..806ef7058930 --- /dev/null +++ b/drivers/media/pci/intel/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 + */ +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 */ +}; + +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, +}; + +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 +}; + +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 +}; + +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/psys/ipu6se-platform-resources.h b/drivers/media/pci/intel/psys/ipu6se-platform-resources.h new file mode 100644 index 000000000000..81c618de0f9a --- /dev/null +++ b/drivers/media/pci/intel/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/drivers/media/platform/intel/Kconfig b/drivers/media/platform/intel/Kconfig new file mode 100644 index 000000000000..c87ba10d14dc --- /dev/null +++ b/drivers/media/platform/intel/Kconfig @@ -0,0 +1,24 @@ +config INTEL_IPU6_TGLRVP_PDATA + bool "Enable built in platform data for TGL RVP" + depends on MEDIA_CAMERA_SUPPORT + help + Pre-ACPI system platform data is compiled inside kernel + +config INTEL_IPU6_ADLRVP_PDATA + bool "Enable built in platform data for ADL RVP" + depends on MEDIA_CAMERA_SUPPORT + help + Pre-ACPI system platform data is compiled inside kernel + + This platform data is useful for software development before + the SOC system not ready. + +config INTEL_IPU6_ACPI + tristate "Enable IPU ACPI driver" + depends on I2C + depends on ACPI + depends on INTEL_SKL_INT3472 + help + Driver to read ACPI data from BIOS + + This driver is used to read ACPI data from BIOS diff --git a/drivers/media/platform/intel/Makefile b/drivers/media/platform/intel/Makefile new file mode 100644 index 000000000000..2417a4b67a15 --- /dev/null +++ b/drivers/media/platform/intel/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2010 - 2022 Intel Corporation. + +ifneq ($(EXTERNAL_BUILD), 1) +srcpath := $(srctree) +endif + +ccflags-y += -I$(srcpath)/$(src)/../../../../include/ +ccflags-y += -I$(srcpath)/$(src)/../../pci/intel/ + +obj-$(CONFIG_INTEL_IPU6_TGLRVP_PDATA) += ipu6-tglrvp-pdata.o +obj-$(CONFIG_INTEL_IPU6_JSLRVP_PDATA) += ipu6-jslrvp-pdata.o +obj-$(CONFIG_INTEL_IPU6_ADLRVP_PDATA) += ipu6-adlrvp-pdata.o +obj-$(CONFIG_INTEL_IPU6_ACPI) += ipu6-acpi.o \ + ipu6-acpi-pdata.o \ + ipu6-acpi-common.o diff --git a/drivers/media/platform/intel/ipu6-acpi-common.c b/drivers/media/platform/intel/ipu6-acpi-common.c new file mode 100644 index 000000000000..368fe630abb4 --- /dev/null +++ b/drivers/media/platform/intel/ipu6-acpi-common.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016--2024 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +static int get_integer_dsdt_data(struct device *dev, const u8 *dsdt, + int func, u64 *out) +{ + struct acpi_handle *dev_handle = ACPI_HANDLE(dev); + union acpi_object *obj; + + obj = acpi_evaluate_dsm(dev_handle, (void *)dsdt, 0, func, NULL); + if (!obj) { + pr_err("IPU6 ACPI: Could not find corresponding DSM\n"); + return -ENODEV; + } + + if (obj->type != ACPI_TYPE_INTEGER) { + ACPI_FREE(obj); + return -ENODEV; + } + *out = obj->integer.value; + ACPI_FREE(obj); + return 0; +} + +static int read_acpi_block(struct device *dev, char *id, void *data, u32 size) +{ + union acpi_object *obj; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_handle *dev_handle = ACPI_HANDLE(dev); + int status; + u32 buffer_length; + + status = acpi_evaluate_object(dev_handle, id, NULL, &buffer); + if (!ACPI_SUCCESS(status)) { + pr_err("IPU6 ACPI: Could not find ACPI block"); + return -ENODEV; + } + + obj = (union acpi_object *)buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_BUFFER) { + pr_err("IPU6 ACPI: Could not read ACPI buffer\n"); + status = -ENODEV; + goto err; + } + + if (obj->buffer.length > size) { + pr_err("IPU6 ACPI: Given buffer is too small\n"); + status = -ENODEV; + goto err; + } + + memcpy(data, obj->buffer.pointer, min(size, obj->buffer.length)); + buffer_length = obj->buffer.length; + kfree(buffer.pointer); + + return buffer_length; +err: + kfree(buffer.pointer); + return status; +} + +static int ipu_acpi_get_gpio_data(struct device *dev, struct ipu_gpio_info *gpio, int size, + u64 *gpio_num) +{ + const u8 dsdt_cam_gpio[] = { + 0x40, 0x46, 0x23, 0x79, 0x10, 0x9e, 0xea, 0x4f, + 0xa5, 0xc1, 0xB5, 0xaa, 0x8b, 0x19, 0x75, 0x6f }; + + int i = 0, j = 0, retries = 0, loop = 0; + u64 num_gpio; + int rval; + + rval = get_integer_dsdt_data(dev, dsdt_cam_gpio, 1, &num_gpio); + + if (rval < 0) { + pr_err("IPU6 ACPI: Failed to get number of GPIO pins\n"); + return rval; + } + + pr_info("IPU6 APCI: Num of gpio found = %lld", num_gpio); + + if (num_gpio == 0) { + *gpio_num = num_gpio; + return rval; + } + + if (num_gpio > size) { + pr_err("IPU6 ACPI: Incorrect number of GPIO pins\n"); + return rval; + } + + /* repeat until all gpio pin is saved */ + while (i < num_gpio && loop <= LOOP_SIZE) { + u64 data; + struct gpio_desc *desc = NULL; + + rval = get_integer_dsdt_data(dev, dsdt_cam_gpio, i + 2, &data); + + if (rval < 0) { + pr_err("IPU6 ACPI: Failed to get GPIO data\n"); + return -ENODEV; + } + + gpio[i].func = (data & 0xff); + gpio[i].valid = FALSE; + + desc = gpiod_get_index(dev, NULL, i + retries, GPIOD_ASIS); + + if (!IS_ERR(desc)) { + unsigned short pin = desc_to_gpio(desc); + bool save = TRUE; + + /* always save first GPIO pin */ + if (i == 0) + save = TRUE; + + /* check subsequent GPIO pin for replicate */ + else { + for (j = 0; j <= i; j++) { + /* retry if same as previous pin */ + if (gpio[j].pin == pin) { + retries++; + save = FALSE; + gpiod_put(desc); + break; + } + } + } + + /* save into array */ + if (save == TRUE) { + pr_info("IPU6 ACPI: DSM: Pin number = %d. Func = %x.", pin, gpio[i].func); + gpio[i].pin = pin; + gpio[i].valid = TRUE; + gpiod_put(desc); + i++; + retries = 0; + } + } + loop++; + } + *gpio_num = num_gpio; + + return rval; +} + +static int ipu_acpi_get_i2c_info(struct device *dev, struct ipu_i2c_info *i2c, int size, u64 *num) +{ + const u8 dsdt_cam_i2c[] = { + 0x49, 0x75, 0x25, 0x26, 0x71, 0x92, 0xA4, 0x4C, + 0xBB, 0x43, 0xC4, 0x89, 0x9D, 0x5A, 0x48, 0x81}; + + u64 num_i2c; + int i; + int rval; + + rval = get_integer_dsdt_data(dev, dsdt_cam_i2c, 1, &num_i2c); + + if (rval < 0) { + pr_err("IPU6 ACPI: Failed to get number of I2C\n"); + return -ENODEV; + } + + for (i = 0; i < num_i2c && i < size; i++) { + u64 data; + + rval = get_integer_dsdt_data(dev, dsdt_cam_i2c, i + 2, + &data); + + if (rval < 0) { + pr_err("IPU6 ACPI: Failed to get I2C data\n"); + return -ENODEV; + } + + i2c[i].bus = ((data >> 24) & 0xff); + i2c[i].addr = (data >> 8) & 0xff; + + pr_info("IPU6 ACPI: DSM: i2c bus %d addr %x", + i2c[i].bus, i2c[i].addr); + } + + *num = num_i2c; + + return 0; +} + +static int match_depend(struct device *dev, const void *data) +{ + return (dev && dev->fwnode == data) ? 1 : 0; +} + +int ipu_acpi_get_control_logic_data(struct device *dev, + struct control_logic_data **ctl_data) +{ + /* CLDB data */ + struct control_logic_data_packed ctl_logic_data; + int ret; + + ret = read_acpi_block(dev, "CLDB", &ctl_logic_data, + sizeof(ctl_logic_data)); + + if (ret < 0) { + pr_err("IPU6 ACPI: Fail to read from CLDB"); + return ret; + } + + (*ctl_data)->type = ctl_logic_data.controllogictype; + (*ctl_data)->id = ctl_logic_data.controllogicid; + (*ctl_data)->sku = ctl_logic_data.sensorcardsku; + + pr_info("IPU6 ACPI: CLDB: version %d clid %d cltype %d sku %d", + ctl_logic_data.version, + ctl_logic_data.controllogicid, + ctl_logic_data.controllogictype, + ctl_logic_data.sensorcardsku); + + /* GPIO data */ + ret = ipu_acpi_get_gpio_data(dev, (*ctl_data)->gpio, ARRAY_SIZE((*ctl_data)->gpio), + &((*ctl_data)->gpio_num)); + + if (ret < 0) { + dev_err(dev, "Failed to get GPIO data"); + return ret; + } + return 0; +} + +int ipu_acpi_get_dep_data(struct device *dev, + struct control_logic_data *ctl_data) +{ + struct acpi_handle *dev_handle = ACPI_HANDLE(dev); + struct acpi_handle_list dep_devices; + acpi_status status; + int i; + int rval; + + ctl_data->completed = false; + + if (!acpi_has_method(dev_handle, "_DEP")) { + pr_err("IPU6 ACPI: %s does not have _DEP method", dev_name(dev)); + return 0; + } + + status = acpi_evaluate_reference(dev_handle, "_DEP", NULL, + &dep_devices); + + if (ACPI_FAILURE(status)) { + pr_err("IPU6 ACPI: %s failed to evaluate _DEP", dev_name(dev)); + return -ENODEV; + } + + for (i = 0; i < dep_devices.count; i++) { + struct acpi_device *device; + struct acpi_device_info *info; + struct device *p_dev; + int match; + + status = acpi_get_object_info(dep_devices.handles[i], &info); + if (ACPI_FAILURE(status)) { + pr_err("IPU6 ACPI: %s error reading _DEP device info", dev_name(dev)); + continue; + } + + match = info->valid & ACPI_VALID_HID && + !strcmp(info->hardware_id.string, "INT3472"); + + kfree(info); + + if (!match) + continue; + + /* Process device IN3472 created by acpi */ + device = acpi_fetch_acpi_dev(dep_devices.handles[i]); + if (!device) { + pr_err("IPU6 ACPI: Failed to get ACPI device"); + return -ENODEV; + } + + pr_info("IPU6 ACPI: Dependent ACPI device found: %s\n", + dev_name(&device->dev)); + + p_dev = bus_find_device(&platform_bus_type, NULL, + &device->fwnode, match_depend); + + if (p_dev) { + pr_info("IPU6 ACPI: Dependent platform device found %s\n", + dev_name(p_dev)); + + /* obtain Control Logic Data from BIOS */ + rval = ipu_acpi_get_control_logic_data(p_dev, &ctl_data); + + if (rval) { + pr_err("IPU6 ACPI: Error getting Control Logic Data"); + return rval; + } + + ctl_data->completed = true; + } else + pr_err("IPU6 ACPI: Dependent platform device not found for %s\n", dev_name(dev)); + } + + if (!ctl_data->completed) { + ctl_data->type = CL_EMPTY; + pr_err("IPU6 APCI: No control logic data available"); + } + + return 0; +} +EXPORT_SYMBOL(ipu_acpi_get_dep_data); + +int ipu_acpi_get_cam_data(struct device *dev, + struct sensor_bios_data *sensor) +{ + /* SSDB */ + struct sensor_bios_data_packed sensor_data; + int ret; + + ret = read_acpi_block(dev, "SSDB", &sensor_data, + sizeof(sensor_data)); + + if (ret < 0) { + pr_err("IPU6 ACPI: Fail to read from SSDB"); + return ret; + } + + /* Xshutdown is not part of the ssdb data */ + sensor->link = sensor_data.link; + sensor->lanes = sensor_data.lanes; + sensor->pprval = sensor_data.pprval; + sensor->pprunit = sensor_data.pprunit; + + pr_info("IPU6 ACPI: SSDB: name %s. link %d. lanes %d. pprval %d. pprunit %x", + dev_name(dev), sensor->link, sensor->lanes, sensor->pprval, sensor->pprunit); + + /* I2C */ + ret = ipu_acpi_get_i2c_info(dev, sensor->i2c, ARRAY_SIZE(sensor->i2c), &sensor->i2c_num); + + if (ret < 0) { + pr_err("IPU6 ACPI: Failed to get I2C info"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(ipu_acpi_get_cam_data); + +MODULE_AUTHOR("Samu Onkalo "); +MODULE_AUTHOR("Khai Wen Ng "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IPU6 ACPI support"); + diff --git a/drivers/media/platform/intel/ipu6-acpi-pdata.c b/drivers/media/platform/intel/ipu6-acpi-pdata.c new file mode 100644 index 000000000000..a891da26c1ab --- /dev/null +++ b/drivers/media/platform/intel/ipu6-acpi-pdata.c @@ -0,0 +1,986 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016--2024 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#define MIN_SENSOR_I2C 1 +#define MIN_SERDES_I2C 3 +#define SENSOR_2X_I2C 5 +#define SUFFIX_BASE 96 +#define MSG_LEN 128 + +static struct ipu_isys_subdev_pdata *ptr_built_in_pdata; + +void set_built_in_pdata(struct ipu_isys_subdev_pdata *pdata) +{ + ptr_built_in_pdata = pdata; +}; +EXPORT_SYMBOL(set_built_in_pdata); + +static struct ipu_isys_clk_mapping clk_mapping[] = { + { CLKDEV_INIT(NULL, NULL, NULL), NULL } +}; + +struct ipu_isys_subdev_pdata acpi_subdev_pdata = { + .subdevs = (struct ipu_isys_subdev_info *[]) { + NULL, + }, + .clk_map = clk_mapping, +}; + +struct serdes_local serdes_info; + +struct ipu_isys_subdev_pdata *get_acpi_subdev_pdata(void) +{ + struct ipu_isys_subdev_pdata *ptr; + ptr = &acpi_subdev_pdata; + return ptr; +} +EXPORT_SYMBOL(get_acpi_subdev_pdata); + +static void print_serdes_sdinfo(struct serdes_subdev_info *sdinfo) +{ + int j; + struct serdes_module_pdata *sd_mpdata = sdinfo->board_info.platform_data; + + if (!sd_mpdata) { + pr_err("Empty serdes module pdata"); + return; + } + + pr_debug("\t\trx_port \t\t= %d", sdinfo->rx_port); + pr_debug("\t\tphy_i2c_addr \t\t= 0x%x", sdinfo->phy_i2c_addr); + pr_debug("\t\tser_alias \t\t= 0x%x", sdinfo->ser_alias); + pr_debug("\t\tsuffix \t\t\t= %c", sdinfo->suffix); + pr_debug("\t\tboard_info.type \t= %s", sdinfo->board_info.type); + pr_debug("\t\tboard_info.addr \t= 0x%x", sdinfo->board_info.addr); + + pr_debug("serdes board_info.platform_data"); + pr_debug("\t\tlanes \t\t\t= %d", sd_mpdata->lanes); + pr_debug("\t\tmodule_name \t\t= %s", sd_mpdata->module_name); + pr_debug("\t\tfsin \t\t\t= %d", sd_mpdata->fsin); + + if (serdes_info.gpio_powerup_seq > 0) + for (j = 0; j < serdes_info.gpio_powerup_seq; j++) + pr_debug("\t\t gpio_powerup_seq[%d] \t= %d", j, + (int)sd_mpdata->gpio_powerup_seq[j]); +} + +static void print_serdes_subdev(struct ipu_isys_subdev_info *sd) +{ + struct serdes_platform_data *sd_pdata = sd->i2c.board_info.platform_data; + int i; + struct serdes_subdev_info *sd_sdinfo; + struct serdes_module_pdata *sd_mpdata; + + if (!sd_pdata) { + pr_err("Empty serdes subdev pdata"); + return; + } + + pr_debug("IPU6 ACPI: %s", __func__); + pr_debug("sd_csi2"); + pr_debug("\t\tnlanes \t\t\t= %d", sd->csi2->nlanes); + pr_debug("\t\tport \t\t\t= %d", sd->csi2->port); + + pr_debug("sd->i2c"); + pr_debug("\t\ti2c_adapter_bdf \t= %s", sd->i2c.i2c_adapter_bdf); + pr_debug("\t\tboard_info.type \t= %s", sd->i2c.board_info.type); + pr_debug("\t\tboard_info.addr \t= 0x%x", sd->i2c.board_info.addr); + + pr_debug("sd->i2c.board_info.platform_data"); + pr_debug("\t\treset_gpio \t\t= %d", sd_pdata->reset_gpio); + pr_debug("\t\tFPD_gpio \t\t= %d", sd_pdata->FPD_gpio); + pr_debug("\t\tsuffix \t\t\t= %c", sd_pdata->suffix); + + for (i = 0; i < serdes_info.rx_port; i++) { + sd_sdinfo = &sd_pdata->subdev_info[i]; + sd_mpdata = sd_sdinfo->board_info.platform_data; + + if (!sd_mpdata) + continue; + + pr_debug("serdes subdev_info[%d]", i); + print_serdes_sdinfo(sd_sdinfo); + } + +} + +static void print_subdev(struct ipu_isys_subdev_info *sd) +{ + struct sensor_platform_data *spdata = sd->i2c.board_info.platform_data; + int i; + + if (!spdata) { + pr_err("IPU6 ACPI: Empty sensor subdev"); + return; + } + + pr_debug("IPU6 ACPI: %s", __func__); + pr_debug("sd->csi2"); + pr_debug("\t\tnlanes \t\t\t= %d", sd->csi2->nlanes); + pr_debug("\t\tport \t\t\t= %d", sd->csi2->port); + + pr_debug("sd->i2c"); + pr_debug("\t\ti2c_adapter_bdf \t= %s", sd->i2c.i2c_adapter_bdf); + pr_debug("\t\tboard_info.type \t= %s", sd->i2c.board_info.type); + pr_debug("\t\tboard_info.addr \t= 0x%x", sd->i2c.board_info.addr); + + pr_debug("sd->i2c.platform_data"); + pr_debug("\t\tport \t\t\t= %d", spdata->port); + pr_debug("\t\tlanes \t\t\t= %d", spdata->lanes); + pr_debug("\t\ti2c_slave_address \t= 0x%x", spdata->i2c_slave_address); + pr_debug("\t\tirq_pin \t\t= %d", spdata->irq_pin); + pr_debug("\t\tirq_pin_name \t\t= %s", spdata->irq_pin_name); + pr_debug("\t\tsuffix \t\t\t= %c", spdata->suffix); + pr_debug("\t\treset_pin \t\t= %d", spdata->reset_pin); + pr_debug("\t\tdetect_pin \t\t= %d", spdata->detect_pin); + + for (i = 0; i < IPU_SPDATA_GPIO_NUM; i++) + pr_debug("\t\tgpios[%d] \t\t= %d", i, spdata->gpios[i]); +} + +static void add_local_subdevs(struct ipu_isys_subdev_info *new_subdev_info) +{ + struct ipu_isys_subdev_pdata *ptr_acpi_subdev_pdata = &acpi_subdev_pdata; + int i = 0; + + while (i <= MAX_ACPI_SENSOR_NUM) { + if (!ptr_acpi_subdev_pdata->subdevs[i]) { + ptr_acpi_subdev_pdata->subdevs[i] = new_subdev_info; + ptr_acpi_subdev_pdata->subdevs[i+1] = NULL; + break; + } + i++; + } +} + +static void update_short(struct device *dev, + char msg[MSG_LEN], + unsigned short *old_short, + unsigned int new_short) +{ + if (*old_short != new_short) { + dev_info(dev, "%s 0x%x -> 0x%x", msg, *old_short, new_short); + *old_short = new_short; + } +} + +static void update_hex(struct device *dev, + char msg[MSG_LEN], + unsigned int *old_hex, + unsigned int new_hex) +{ + if (*old_hex != new_hex) { + dev_info(dev, "%s 0x%x -> 0x%x", msg, *old_hex, new_hex); + *old_hex = new_hex; + } +} + +static void update_int(struct device *dev, + char msg[MSG_LEN], + unsigned int *old_int, + unsigned int new_int) +{ + if (*old_int != new_int) { + dev_info(dev, "%s %d -> %d", msg, *old_int, new_int); + *old_int = new_int; + } +} + +static void update_inta(struct device *dev, + char msg[MSG_LEN], + int old_int[MSG_LEN], + int new_int[MSG_LEN], + size_t size) +{ + int i; + + for (i = 0; i < size; i++) { + if (old_int[i] != new_int[i]) { + dev_info(dev, "%s %d -> %d", msg, old_int[i], new_int[i]); + old_int[i] = new_int[i]; + } + } +} + +static void update_str(struct device *dev, + char msg[MSG_LEN], + char old_str[MSG_LEN], + char new_str[MSG_LEN]) +{ + if (strcmp(old_str, new_str) != 0) { + dev_info(dev, "%s %s -> %s", msg, old_str, new_str); + strscpy(old_str, new_str, strlen(new_str)+1); + } +} + +static void update_subdev(struct device *dev, + struct ipu_isys_subdev_info *new_sd, + struct ipu_isys_subdev_info **old_sd) +{ + struct sensor_platform_data *old_pdata = + (*old_sd)->i2c.board_info.platform_data; + + struct sensor_platform_data *new_pdata = + new_sd->i2c.board_info.platform_data; + + /* csi2 */ + update_int(dev, "CSI2 port", &(*old_sd)->csi2->port, new_sd->csi2->port); + update_int(dev, "CSI2 nlanes", &(*old_sd)->csi2->nlanes, new_sd->csi2->nlanes); + + /* i2c */ + update_short(dev, "I2C board_info addr", &(*old_sd)->i2c.board_info.addr, + new_sd->i2c.board_info.addr); + update_str(dev, "I2C i2c_adapter_bdf", (*old_sd)->i2c.i2c_adapter_bdf, + new_sd->i2c.i2c_adapter_bdf); + + /* platform data */ + update_int(dev, "pdata port", &(old_pdata)->port, new_pdata->port); + update_int(dev, "pdata lanes", &(old_pdata)->lanes, new_pdata->lanes); + update_hex(dev, "pdata I2C slave addr", &(old_pdata)->i2c_slave_address, + new_pdata->i2c_slave_address); + update_int(dev, "pdata irq_pin", &(old_pdata)->irq_pin, new_pdata->irq_pin); + update_str(dev, "pdata irq_pin_name", old_pdata->irq_pin_name, new_pdata->irq_pin_name); + update_int(dev, "pdata reset_pin", &(old_pdata)->reset_pin, new_pdata->reset_pin); + update_int(dev, "pdata detect_pin", &(old_pdata)->detect_pin, new_pdata->detect_pin); + update_inta(dev, "pdata gpios", old_pdata->gpios, new_pdata->gpios, IPU_SPDATA_GPIO_NUM); +} + +static void update_serdes_subdev(struct device *dev, + struct ipu_isys_subdev_info *new_sd, + struct ipu_isys_subdev_info **old_sd) +{ + struct serdes_platform_data *old_pdata = + (*old_sd)->i2c.board_info.platform_data; + + struct serdes_platform_data *new_pdata = + new_sd->i2c.board_info.platform_data; + + int i; + struct serdes_subdev_info *old_sdinfo, *new_sdinfo; + struct serdes_module_pdata *old_mpdata, *new_mpdata; + + /* csi2 */ + update_int(dev, "CSI2 port", &(*old_sd)->csi2->port, new_sd->csi2->port); + update_int(dev, "CSI2 nlanes", &(*old_sd)->csi2->nlanes, new_sd->csi2->nlanes); + + /* i2c */ + update_short(dev, "I2C board_info addr", &(*old_sd)->i2c.board_info.addr, + new_sd->i2c.board_info.addr); + update_str(dev, "I2C i2c_adapter_bdf", (*old_sd)->i2c.i2c_adapter_bdf, + new_sd->i2c.i2c_adapter_bdf); + + update_int(dev, "I2C Pdata reset_gpio", &old_pdata->reset_gpio, + new_pdata->reset_gpio); + update_int(dev, "I2C Pdata FPD_gpio", &old_pdata->FPD_gpio, new_pdata->FPD_gpio); + + /* platform data */ + for (i = 0; i < SERDES_MAX_PORT; i++) { + old_sdinfo = &old_pdata->subdev_info[i]; + old_mpdata = old_sdinfo->board_info.platform_data; + + new_sdinfo = &new_pdata->subdev_info[i]; + new_mpdata = new_sdinfo->board_info.platform_data; + + if (!strcmp(old_sdinfo->board_info.type, new_sdinfo->board_info.type) && + old_sdinfo->suffix == new_sdinfo->suffix) { + update_short(dev, "SdInfo port", &old_sdinfo->rx_port, + new_sdinfo->rx_port); + update_short(dev, "SdInfo ser_alias", &old_sdinfo->ser_alias, + new_sdinfo->ser_alias); + update_short(dev, "SdInfo board_info.addr", &old_sdinfo->board_info.addr, + new_sdinfo->board_info.addr); + + if (!strcmp(old_mpdata->module_name, new_mpdata->module_name)) { + update_int(dev, "mPdata lanes", &old_mpdata->lanes, + new_mpdata->lanes); + update_int(dev, "mPdata fsin", &old_mpdata->fsin, + new_mpdata->fsin); + update_inta(dev, "mPdata gpio_powerup_seq", + (int *)old_mpdata->gpio_powerup_seq, + (int *)new_mpdata->gpio_powerup_seq, + SERDES_MAX_GPIO_POWERUP_SEQ); + } + } + } +} + +static int compare_subdev(struct device *dev, + struct ipu_isys_subdev_info *new_subdev, + struct ipu_isys_subdev_info *old_subdev, + enum connection_type connect) +{ + /* check for ACPI HID in existing pdata */ + if (old_subdev->acpi_hid) { + /* compare with HID for User Custom */ + if (!strcmp(old_subdev->acpi_hid, dev_name(dev))) { + dev_info(dev, "Found matching sensor : %s", dev_name(dev)); + return 0; + } + } + /* compare sensor type */ + if (!strcmp(old_subdev->i2c.board_info.type, + new_subdev->i2c.board_info.type)) { + + if (connect == TYPE_DIRECT) { + struct sensor_platform_data *old_pdata, *new_pdata; + + old_pdata = (struct sensor_platform_data *) + old_subdev->i2c.board_info.platform_data; + + new_pdata = (struct sensor_platform_data *) + new_subdev->i2c.board_info.platform_data; + + if (old_pdata->suffix == new_pdata->suffix) { + dev_info(dev, "Found matching sensor : %s %c", + old_subdev->i2c.board_info.type, + old_pdata->suffix); + return 0; + } + } else if (connect == TYPE_SERDES) { + struct serdes_platform_data *old_pdata, *new_pdata; + + old_pdata = (struct serdes_platform_data *) + old_subdev->i2c.board_info.platform_data; + + new_pdata = (struct serdes_platform_data *) + new_subdev->i2c.board_info.platform_data; + + if (old_pdata->suffix == new_pdata->suffix) { + dev_info(dev, "Found matching sensor : %s %c", + old_subdev->i2c.board_info.type, + old_pdata->suffix); + return 0; + } + } + } + return -1; +} + +static void update_pdata(struct device *dev, + struct ipu_isys_subdev_info *new_subdev, + enum connection_type connect) +{ + struct ipu_isys_subdev_info *acpi_subdev; + bool found = false; + + acpi_subdev = new_subdev; + + /* update local ipu_isys_subdev_pdata */ + add_local_subdevs(acpi_subdev); + + /* found existing pdata */ + if (ptr_built_in_pdata) { + struct ipu_isys_subdev_info **subdevs, *sd_info; + + for (subdevs = ptr_built_in_pdata->subdevs; *subdevs; subdevs++) { + sd_info = *subdevs; + + /* found similar subdev in existing pdata*/ + if (!compare_subdev(dev, acpi_subdev, sd_info, connect)) { + /* print and update old subdev */ + if (connect == TYPE_DIRECT) { + dev_dbg(dev, "Old sensor subdev\n"); + print_subdev(sd_info); + update_subdev(dev, acpi_subdev, &sd_info); + dev_dbg(dev, "Updated subdev\n"); + print_subdev(sd_info); + } else if (connect == TYPE_SERDES) { + dev_dbg(dev, "Old serdes subdev\n"); + print_serdes_subdev(sd_info); + update_serdes_subdev(dev, acpi_subdev, &sd_info); + dev_dbg(dev, "Updated subdev\n"); + print_serdes_subdev(sd_info); + } + + /* stop once similar subdev updated */ + found = true; + break; + } + } + + /* no similar subdev found */ + if (!found) { + if (connect == TYPE_DIRECT) { + struct sensor_platform_data *acpi_pdata; + + acpi_pdata = (struct sensor_platform_data *) + acpi_subdev->i2c.board_info.platform_data; + + dev_err(dev, "Pdata does not contain %s %c\n", + acpi_subdev->i2c.board_info.type, + acpi_pdata->suffix); + + /* print new subdev */ + print_subdev(acpi_subdev); + + } else { + struct serdes_platform_data *acpi_pdata; + + acpi_pdata = (struct serdes_platform_data *) + acpi_subdev->i2c.board_info.platform_data; + + dev_err(dev, "Pdata does not contain %s %c\n", + acpi_subdev->i2c.board_info.type, + acpi_pdata->suffix); + + print_serdes_subdev(acpi_subdev); + } + } + } + /* does not have existing pdata */ + else { + /* print new subdev */ + if (connect == TYPE_DIRECT) { + pr_debug("New sensor subdev\n"); + print_subdev(acpi_subdev); + } else { + pr_debug("New serdes subdev\n"); + print_serdes_subdev(acpi_subdev); + } + } + + /* update total num of sensor connected */ + if (connect == TYPE_SERDES) { + if (!serdes_info.sensor_num) + serdes_info.sensor_num = serdes_info.rx_port; + else + serdes_info.sensor_num += serdes_info.rx_port; + + serdes_info.deser_num++; + } +} + +static void set_ti960_gpio(struct control_logic_data *ctl_data, struct serdes_platform_data **pdata) +{ + int i; + + (*pdata)->reset_gpio = 0; + (*pdata)->FPD_gpio = -1; + + if (ctl_data->completed && ctl_data->gpio_num > 0) { + for (i = 0; i < ctl_data->gpio_num; i++) { + if (ctl_data->gpio[i].func != GPIO_RESET) + dev_err(ctl_data->dev, + "IPU6 ACPI: Invalid GPIO func: %d\n", + ctl_data->gpio[i].func); + + /* check for RESET selection in BIOS */ + if (ctl_data->gpio[i].valid && ctl_data->gpio[i].func == GPIO_RESET) + (*pdata)->FPD_gpio = ctl_data->gpio[i].pin; + } + } +} + +static void set_lt_gpio(struct control_logic_data *ctl_data, struct sensor_platform_data **pdata, + bool is_dummy) +{ + int i; + + (*pdata)->irq_pin = -1; + (*pdata)->reset_pin = -1; + (*pdata)->detect_pin = -1; + + if (ctl_data->completed && ctl_data->gpio_num > 0 && !is_dummy) { + for (i = 0; i < ctl_data->gpio_num; i++) { + /* check for unsupported GPIO function */ + if (ctl_data->gpio[i].func != GPIO_RESET && + ctl_data->gpio[i].func != GPIO_READY_STAT && + ctl_data->gpio[i].func != GPIO_HDMI_DETECT) + dev_err(ctl_data->dev, + "IPU6 ACPI: Invalid GPIO func: %d\n", + ctl_data->gpio[i].func); + + /* check for RESET selection in BIOS */ + if (ctl_data->gpio[i].valid && ctl_data->gpio[i].func == GPIO_RESET) + (*pdata)->reset_pin = ctl_data->gpio[i].pin; + + /* check for READY_STAT selection in BIOS */ + if (ctl_data->gpio[i].valid && ctl_data->gpio[i].func == GPIO_READY_STAT) { + (*pdata)->irq_pin = ctl_data->gpio[i].pin; + (*pdata)->irq_pin_flags = IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT; + strscpy((*pdata)->irq_pin_name, "READY_STAT", sizeof("READY_STAT")); + } + + /* check for HDMI_DETECT selection in BIOS */ + if (ctl_data->gpio[i].valid && ctl_data->gpio[i].func == GPIO_HDMI_DETECT) + (*pdata)->detect_pin = ctl_data->gpio[i].pin; + } + } +} + +static void set_common_gpio(struct control_logic_data *ctl_data, + struct sensor_platform_data **pdata) +{ + int i; + + /* TODO: consider remove specific naming such as irq_pin, and use gpios[] */ + (*pdata)->irq_pin = -1; + (*pdata)->reset_pin = -1; + (*pdata)->detect_pin = -1; + + (*pdata)->gpios[0] = -1; + (*pdata)->gpios[1] = 0; + (*pdata)->gpios[2] = 0; + (*pdata)->gpios[3] = 0; + + /* all sensors should have RESET GPIO */ + if (ctl_data->completed && ctl_data->gpio_num > 0) + for (i = 0; i < ctl_data->gpio_num; i++) + if (ctl_data->gpio[i].func != GPIO_RESET) + dev_err(ctl_data->dev, + "IPU6 ACPI: Invalid GPIO func: %d\n", + ctl_data->gpio[i].func); +} + +static int set_csi2(struct ipu_isys_subdev_info **sensor_sd, + unsigned int lanes, + unsigned int port) +{ + struct ipu_isys_csi2_config *csi2_config; + + csi2_config = kzalloc(sizeof(*csi2_config), GFP_KERNEL); + if (!csi2_config) + return -ENOMEM; + + csi2_config->nlanes = lanes; + csi2_config->port = port; + (*sensor_sd)->csi2 = csi2_config; + + return 0; +} + +static void set_i2c(struct ipu_isys_subdev_info **sensor_sd, + struct device *dev, + const char sensor_name[I2C_NAME_SIZE], + unsigned int addr) +{ + dev_info(dev, "IPU6 ACPI: kernel I2C BDF: %s, kernel I2C bus = %s", + dev_name(dev->parent->parent->parent), dev_name(dev->parent)); + (*sensor_sd)->i2c.board_info.addr = addr; + strscpy((*sensor_sd)->i2c.board_info.type, sensor_name, I2C_NAME_SIZE); + strscpy((*sensor_sd)->i2c.i2c_adapter_bdf, dev_name(dev->parent->parent->parent), + sizeof((*sensor_sd)->i2c.i2c_adapter_bdf)); +} + +static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char sensor_name[I2C_NAME_SIZE], + unsigned int lanes) +{ + /* general */ + (*module_pdata)->lanes = lanes; + strscpy((*module_pdata)->module_name, sensor_name, I2C_NAME_SIZE); + + /* TI960 and IMX390 specific */ + if (!strcmp(sensor_name, IMX390_NAME)) { + (*module_pdata)->gpio_powerup_seq[0] = 0; + (*module_pdata)->gpio_powerup_seq[1] = 0x9; + (*module_pdata)->gpio_powerup_seq[2] = -1; + (*module_pdata)->gpio_powerup_seq[3] = -1; + (*module_pdata)->module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK; + (*module_pdata)->fsin = 3; + } + + /* TI960 and ISX031 specific */ + if (!strcmp(sensor_name, ISX031_NAME)) { + (*module_pdata)->gpio_powerup_seq[0] = 0x0; + (*module_pdata)->gpio_powerup_seq[1] = 0x08; + (*module_pdata)->gpio_powerup_seq[2] = 0X08; + (*module_pdata)->gpio_powerup_seq[3] = -1; + (*module_pdata)->module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK; + (*module_pdata)->fsin = 2; + } +} + +#define PORT_NR 8 + +static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, + struct device *dev, + struct serdes_platform_data **pdata, + char sensor_name[I2C_NAME_SIZE], + unsigned int lanes, + unsigned int addr, + unsigned int subdev_port) +{ + int i; + struct serdes_module_pdata *module_pdata[PORT_NR]; + struct serdes_subdev_info *serdes_sdinfo; + size_t subdev_size = subdev_port * sizeof(*serdes_sdinfo); + + serdes_sdinfo = kzalloc(subdev_size, GFP_KERNEL); + if (!serdes_sdinfo) + return -ENOMEM; + + for (i = 0; i < subdev_port; i++) { + module_pdata[i] = kzalloc(sizeof(*module_pdata[i]), GFP_KERNEL); + if (!module_pdata[i]) { + kfree(serdes_sdinfo); + return -ENOMEM; + } + + set_serdes_sd_pdata(&module_pdata[i], sensor_name, lanes); + + /* board info */ + strscpy(serdes_sdinfo[i].board_info.type, sensor_name, I2C_NAME_SIZE); + if (!strcmp(sensor_name, D457_NAME)) { + if (i == 0) + serdes_sdinfo[i].board_info.addr = serdes_info.sensor_map_addr; + else + serdes_sdinfo[i].board_info.addr = serdes_info.sensor_map_addr_2; + } else + serdes_sdinfo[i].board_info.addr = serdes_info.sensor_map_addr + + serdes_info.sensor_num + i; + + serdes_sdinfo[i].board_info.platform_data = module_pdata[i]; + + /* serdes_subdev_info */ + serdes_sdinfo[i].rx_port = i; + if (!strcmp(sensor_name, D457_NAME)) { + if (i == 0) + serdes_sdinfo[i].ser_alias = serdes_info.ser_map_addr; + else + serdes_sdinfo[i].ser_alias = serdes_info.ser_map_addr_2; + } else + serdes_sdinfo[i].ser_alias = serdes_info.ser_map_addr + + serdes_info.sensor_num + i; + + serdes_sdinfo[i].phy_i2c_addr = serdes_info.phy_i2c_addr; + serdes_sdinfo[i].suffix = SUFFIX_BASE + serdes_info.sensor_num + i + 1; + } + + (*pdata)->subdev_info = serdes_sdinfo; + (*pdata)->subdev_num = subdev_port; + + return 0; +} + +static u8 suffix_offset = 1; +static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, + struct device *dev, + char sensor_name[I2C_NAME_SIZE], + struct control_logic_data *ctl_data, + unsigned int port, + unsigned int lanes, + unsigned int addr, + unsigned int rx_port, + unsigned int deser_lanes, + bool is_dummy, + enum connection_type connect) +{ + if (connect == TYPE_DIRECT) { + struct sensor_platform_data *pdata; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pr_debug("IPU6 ACPI: %s - Direct connection", __func__); + /* use ascii */ + /* port for start from 0 */ + if (port >= 0) { + pdata->suffix = port + SUFFIX_BASE + 1; + pr_info("IPU6 ACPI: create %s %c, on port %d", + sensor_name, pdata->suffix, port); + } else + dev_err(dev, "INVALID MIPI PORT"); + + pdata->port = port; + pdata->lanes = lanes; + pdata->i2c_slave_address = addr; + + /* gpio */ + if (!strcmp(sensor_name, LT6911UXC_NAME) || !strcmp(sensor_name, LT6911UXE_NAME)) + set_lt_gpio(ctl_data, &pdata, is_dummy); + else + set_common_gpio(ctl_data, &pdata); + + (*sensor_sd)->i2c.board_info.platform_data = pdata; + } else if (connect == TYPE_SERDES) { + struct serdes_platform_data *pdata; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pr_debug("IPU6 ACPI: %s - Serdes connection", __func__); + + /* use ascii */ + if (!strcmp(sensor_name, D457_NAME) && port >= 0) { + pdata->suffix = serdes_info.deser_num + SUFFIX_BASE + 1; + pr_info("IPU6 ACPI: create %s %c, on deserializer port %d", + sensor_name, pdata->suffix, serdes_info.deser_num); + } else if (port >= 0) { + pdata->suffix = SUFFIX_BASE + suffix_offset++; + pr_info("IPU6 ACPI: create %s %c, on mipi port %d", + sensor_name, pdata->suffix, port); + } else + pr_err("IPU6 ACPI: Invalid MIPI Port : %d", port); + + if (!strcmp(sensor_name, IMX390_NAME) || !strcmp(sensor_name, ISX031_NAME)) + set_ti960_gpio(ctl_data, &pdata); + if (!strcmp(sensor_name, ISX031_NAME)) { + pdata->link_freq_mbps = 1600; + } else if (!strcmp(sensor_name, IMX390_NAME)) { + pdata->link_freq_mbps = 1200; + } + pdata->deser_nlanes = deser_lanes; + pdata->ser_nlanes = lanes; + set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, lanes, addr, rx_port); + + (*sensor_sd)->i2c.board_info.platform_data = pdata; + pdata->deser_board_info = &(*sensor_sd)->i2c.board_info; + } + + return 0; +} + +static void set_serdes_info(struct device *dev, char *sensor_name, const char *serdes_name, + struct sensor_bios_data *cam_data) +{ + int i; + + /* pprunit as num of sensor connected to deserializer */ + serdes_info.rx_port = cam_data->pprunit; + + /* i2c devices */ + serdes_info.i2c_num = cam_data->i2c_num; + + i = 1; + /* serializer mapped addr */ + serdes_info.ser_map_addr = cam_data->i2c[i++].addr; + /* sensor mapped addr */ + serdes_info.sensor_map_addr = cam_data->i2c[i++].addr; + if (!strcmp(sensor_name, D457_NAME) && serdes_info.i2c_num == SENSOR_2X_I2C) { + /* 2nd group of mapped addr */ + serdes_info.ser_map_addr_2 = cam_data->i2c[i++].addr; + serdes_info.sensor_map_addr_2 = cam_data->i2c[i++].addr; + } + + /* TI960 specific */ + if (!strcmp(serdes_name, TI960_NAME)) + serdes_info.gpio_powerup_seq = TI960_MAX_GPIO_POWERUP_SEQ; + else + serdes_info.gpio_powerup_seq = 0; + + if (!strcmp(sensor_name, IMX390_NAME)) + serdes_info.phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS; + else if (!strcmp(sensor_name, ISX031_NAME)) + serdes_info.phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT; + else + serdes_info.phy_i2c_addr = 0; +} + +static int populate_dummy(struct device *dev, + char sensor_name[I2C_NAME_SIZE], + struct sensor_bios_data *cam_data, + struct control_logic_data *ctl_data, + enum connection_type connect) +{ + struct ipu_isys_subdev_info *dummy; + unsigned short addr_dummy = 0x11; + int ret; + + pr_debug("IPU6 ACPI: %s", __func__); + + dummy = kzalloc(sizeof(*dummy), GFP_KERNEL); + if (!dummy) + return -ENOMEM; + + ret = set_csi2(&dummy, cam_data->lanes, cam_data->pprval); + if (ret) { + kfree(dummy); + return ret; + } + + set_i2c(&dummy, dev, sensor_name, addr_dummy); + + ret = set_pdata(&dummy, dev, sensor_name, ctl_data, cam_data->pprval, + cam_data->lanes, addr_dummy, 0, 0, true, connect); + if (ret) { + kfree(dummy); + return ret; + } + + update_pdata(dev, dummy, connect); + + return 0; +} + +static int populate_sensor_pdata(struct device *dev, + struct ipu_isys_subdev_info **sensor_sd, + char sensor_name[I2C_NAME_SIZE], + struct sensor_bios_data *cam_data, + struct control_logic_data *ctl_data, + enum connection_type connect, + const char *serdes_name) +{ + int ret; + + if (connect == TYPE_DIRECT) { + /* sensor csi2 info */ + ret = set_csi2(sensor_sd, cam_data->lanes, cam_data->link); + if (ret) + return ret; + + /* sensor i2c info */ + if (cam_data->i2c_num == MIN_SENSOR_I2C) { + pr_debug("IPU6 ACPI: num of I2C device for Direct connection: %lld is Correct.", + cam_data->i2c_num); + set_i2c(sensor_sd, dev, sensor_name, cam_data->i2c[0].addr); + } else { + pr_err("IPU6 ACPI: num of I2C device for Direct connection : %lld is Incorrect", + cam_data->i2c_num); + return -1; + } + /* LT use LT Control Logic type */ + if (!strcmp(sensor_name, LT6911UXC_NAME) || + !strcmp(sensor_name, LT6911UXE_NAME)) { + if (ctl_data->type != CL_LT) { + dev_err(dev, "IPU6 ACPI: Control Logic Type\n"); + dev_err(dev, "for %s: %d is Incorrect\n", + sensor_name, ctl_data->type); + return -EINVAL; + } + /* Others use DISCRETE Control Logic */ + } else if (ctl_data->type != CL_DISCRETE) { + dev_err(dev, "IPU6 ACPI: Control Logic Type\n"); + dev_err(dev, "for %s: %d is Incorrect\n", + sensor_name, ctl_data->type); + return -EINVAL; + } + } else if (connect == TYPE_SERDES) { + /* serdes csi2 info. pprval as deserializer lane */ + ret = set_csi2(sensor_sd, cam_data->pprval, cam_data->link); + if (ret) + return ret; + + /* Use DISCRETE Control Logic or No Control Logic for serdes */ + if (ctl_data->type != CL_DISCRETE && ctl_data->type != CL_EMPTY) { + pr_err("IPU6 ACPI: Control Logic Type for serdes: %d is Incorrect", + ctl_data->type); + return -1; + } + + /* serdes i2c info */ + if (cam_data->i2c_num >= MIN_SERDES_I2C) { + pr_debug("IPU6 ACPI: num of I2C device for Serdes connection: %lld is Correct", + cam_data->i2c_num); + set_i2c(sensor_sd, dev, serdes_name, cam_data->i2c[0].addr); + } else { + pr_err("IPU6 ACPI: num of I2C device for Serdes connection: %lld is Incorrect", + cam_data->i2c_num); + return -1; + } + + /* local serdes info */ + set_serdes_info(dev, sensor_name, serdes_name, cam_data); + } + + /* Use last I2C device */ + ret = set_pdata(sensor_sd, dev, sensor_name, ctl_data, cam_data->link, + cam_data->lanes, cam_data->i2c[cam_data->i2c_num - 1].addr, + cam_data->pprunit, cam_data->pprval, false, connect); + + if (ret) + return ret; + + update_pdata(dev, *sensor_sd, connect); + + /* Lontium specific */ + if (!strcmp(sensor_name, LT6911UXC_NAME) || !strcmp(sensor_name, LT6911UXE_NAME)) { + if (cam_data->pprval != cam_data->link) { + ret = populate_dummy(dev, sensor_name, cam_data, ctl_data, connect); + if (ret) + return ret; + } + } + + return 0; +} + +int get_sensor_pdata(struct i2c_client *client, + struct ipu_camera_module_data *data, + struct ipu_i2c_helper *helper, + void *priv, size_t size, + enum connection_type connect, const char *serdes_name) +{ + struct sensor_bios_data *cam_data; + struct control_logic_data *ctl_data; + struct ipu_isys_subdev_info *sensor_sd; + int rval; + + cam_data = kzalloc(sizeof(*cam_data), GFP_KERNEL); + if (!cam_data) + return -ENOMEM; + + cam_data->dev = &client->dev; + + ctl_data = kzalloc(sizeof(*ctl_data), GFP_KERNEL); + if (!ctl_data) { + kfree(cam_data); + return -ENOMEM; + } + + ctl_data->dev = &client->dev; + + sensor_sd = kzalloc(sizeof(*sensor_sd), GFP_KERNEL); + if (!sensor_sd) { + kfree(cam_data); + kfree(ctl_data); + return -ENOMEM; + } + + /* camera info */ + rval = ipu_acpi_get_cam_data(&client->dev, cam_data); + if (rval) { + kfree(sensor_sd); + kfree(cam_data); + kfree(ctl_data); + return rval; + } + + /* control logic info */ + rval = ipu_acpi_get_dep_data(&client->dev, ctl_data); + if (rval) { + kfree(sensor_sd); + kfree(cam_data); + kfree(ctl_data); + return rval; + } + + /* populate pdata */ + rval = populate_sensor_pdata(&client->dev, &sensor_sd, + client->name, cam_data, ctl_data, connect, serdes_name); + if (rval) { + kfree(sensor_sd); + kfree(cam_data); + kfree(ctl_data); + return rval; + } + + client->dev.platform_data = sensor_sd; + + kfree(cam_data); + kfree(ctl_data); + return rval; +} +EXPORT_SYMBOL(get_sensor_pdata); + +MODULE_AUTHOR("Khai Wen, Ng "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IPU6 ACPI support"); diff --git a/drivers/media/platform/intel/ipu6-acpi.c b/drivers/media/platform/intel/ipu6-acpi.c new file mode 100644 index 000000000000..51eb5b00f816 --- /dev/null +++ b/drivers/media/platform/intel/ipu6-acpi.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016--2023 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ipu.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(devices); + +static struct ipu_camera_module_data *add_device_to_list( + struct list_head *devices) +{ + struct ipu_camera_module_data *cam_device; + + cam_device = kzalloc(sizeof(*cam_device), GFP_KERNEL); + if (!cam_device) + return NULL; + + list_add(&cam_device->list, devices); + return cam_device; +} + +static const struct ipu_acpi_devices supported_devices[] = { +/* + * { "ACPI ID", sensor_name, get_sensor_pdata, NULL, 0, TYPE, serdes_name }, // Custom HID + */ + { "INTC10C0", AR0234_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // AR0234 HID + { "INTC10B1", LT6911UXC_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXC HID + { "INTC10C1", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// IMX390 HID + { "INTC1031", ISX031_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// ISX031 HID + { "INTC10C5", LT6911UXE_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXE HID + { "INTC10CD", D457_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, D457_NAME },// D457 HID +}; + +static int get_table_index(struct device *device, const __u8 *acpi_name) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(supported_devices); i++) { + if (!strcmp(acpi_name, supported_devices[i].hid_name)) + return i; + } + + return -ENODEV; +} + +/* List of ACPI devices what we can handle */ +/* Must match with HID in BIOS option. Add new sensor if required */ +static const struct acpi_device_id ipu_acpi_match[] = { +/* + * { "AR0234A", 0 }, // Custom HID + */ + { "INTC10C0", 0 }, // AR0234 HID + { "INTC10B1", 0 }, // LT6911UXC HID + { "INTC10C1", 0 }, // IMX390 HID + { "INTC1031", 0 }, // ISX031 HID + { "INTC10C5", 0 }, // LT6911UXE HID + { "INTC10CD", 0 }, // D457 HID + {}, +}; +static int ipu_acpi_get_pdata(struct i2c_client *client, + const struct acpi_device_id *acpi_id, + struct ipu_i2c_helper *helper) +{ + struct ipu_camera_module_data *camdata; + int index = get_table_index(&client->dev, acpi_id->id); + + if (index < 0) { + pr_err("Device is not in supported devices list\n"); + return -ENODEV; + } + + camdata = add_device_to_list(&devices); + if (!camdata) + return -ENOMEM; + + strscpy(client->name, supported_devices[index].real_driver, + sizeof(client->name)); + + pr_info("IPU6 ACPI: Getting BIOS data for %s (%s)", client->name, dev_name(&client->dev)); + + supported_devices[index].get_platform_data( + client, camdata, helper, + supported_devices[index].priv_data, + supported_devices[index].priv_size, + supported_devices[index].connect, + supported_devices[index].serdes_name); + + return 0; +} + +static int ipu_i2c_test(struct device *dev, void *priv) +{ + struct i2c_client *client = i2c_verify_client(dev); + const struct acpi_device_id *acpi_id; + + /* + * Check that we are handling only I2C devices which really has + * ACPI data and are one of the devices which we want to handle + */ + + if (!ACPI_COMPANION(dev) || !client) + return 0; + + acpi_id = acpi_match_device(ipu_acpi_match, dev); + if (!acpi_id) { + dev_err(dev, "IPU6 ACPI: ACPI device %s NOT supported\n", + dev_name(dev)); + return 0; + } + + /* + * Skip if platform data has already been added. + * Probably ACPI data overruled by kernel platform data + */ + if (client->dev.platform_data) + return 0; + + /* Looks that we got what we are looking for */ + if (ipu_acpi_get_pdata(client, acpi_id, priv)) + pr_err("IPU6 ACPI: Failed to process ACPI data"); + + /* Don't return error since we want to process remaining devices */ + + /* Unregister matching client */ + i2c_unregister_device(client); + + return 0; +} + +/* Scan all i2c devices and pick ones which we can handle */ + +/* Try to get all IPU related devices mentioned in BIOS and all related information + * If there is existing ipu_isys_subdev_pdata, update the existing pdata + * If not, return a new generated existing pdata + */ + +int ipu_get_acpi_devices(void *driver_data, + struct device *dev, + struct ipu_isys_subdev_pdata **spdata, + struct ipu_isys_subdev_pdata **built_in_pdata, + int (*fn) + (struct device *, void *, + struct ipu_isys_csi2_config *csi2, + bool reprobe)) +{ + struct ipu_i2c_helper helper = { + .fn = fn, + .driver_data = driver_data, + }; + int rval; + + if (!built_in_pdata) + dev_dbg(dev, "Built-in pdata not found"); + else { + dev_dbg(dev, "Built-in pdata found"); + set_built_in_pdata(*built_in_pdata); + } + + if ((!fn) || (!driver_data)) + return -ENODEV; + + rval = i2c_for_each_dev(&helper, ipu_i2c_test); + if (rval < 0) + return rval; + + if (!built_in_pdata) { + dev_dbg(dev, "Return ACPI generated pdata"); + *spdata = get_acpi_subdev_pdata(); + } else + dev_dbg(dev, "Return updated built-in pdata"); + + return 0; +} +EXPORT_SYMBOL(ipu_get_acpi_devices); + +static int __init ipu_acpi_init(void) +{ + set_built_in_pdata(NULL); + return 0; +} + +static void __exit ipu_acpi_exit(void) +{ +} + +module_init(ipu_acpi_init); +module_exit(ipu_acpi_exit); + +MODULE_AUTHOR("Samu Onkalo "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IPU6 ACPI support"); + diff --git a/drivers/media/platform/intel/ipu6-adlrvp-pdata.c b/drivers/media/platform/intel/ipu6-adlrvp-pdata.c new file mode 100644 index 000000000000..d6f417f791b5 --- /dev/null +++ b/drivers/media/platform/intel/ipu6-adlrvp-pdata.c @@ -0,0 +1,748 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2022-2024 Intel Corporation +#include +#include +#include +#include +#include + +#include +#if IS_ENABLED(CONFIG_VIDEO_TI960) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_AR0234) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_IMX390) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_ISX031) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXC) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXE) +#include +#endif +#if IS_ENABLED(CONFIG_VIDEO_D4XX) +#include +#endif + +#include "ipu.h" + +#if IS_ENABLED(CONFIG_VIDEO_AR0234) +#define AR0234_LANES 2 +#define AR0234_I2C_ADDRESS 0x10 + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +static void ar0234_fixup_spdata(const void *spdata_rep, void *spdata) +{ + const struct ipu_spdata_rep *rep = spdata_rep; + struct ar0234_platform_data *platform = spdata; + + if (spdata_rep && spdata) { + platform->port = rep->port_n; + platform->lanes = rep->lanes; + platform->i2c_slave_address = rep->slave_addr_n; + platform->gpios[0] = rep->gpios[0]; + platform->irq_pin = rep->irq_pin; + platform->irq_pin_flags = rep->irq_pin_flags; + strcpy(platform->irq_pin_name, rep->irq_pin_name); + platform->suffix = rep->suffix; + } +} +#endif + +static struct ipu_isys_csi2_config ar0234_csi2_cfg_1 = { + .nlanes = AR0234_LANES, + .port = 1, +}; + +static struct ar0234_platform_data ar0234_pdata_1 = { + .port = 1, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'a', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_1 = { + .csi2 = &ar0234_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS), + .platform_data = &ar0234_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; + +static struct ipu_isys_csi2_config ar0234_csi2_cfg_2 = { + .nlanes = AR0234_LANES, + .port = 2, +}; + +static struct ar0234_platform_data ar0234_pdata_2 = { + .port = 2, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'b', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_2 = { + .csi2 = &ar0234_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS), + .platform_data = &ar0234_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_IMX390) +#define IMX390_LANES 4 +#define IMX390_D3RCM_I2C_ADDRESS 0x1a +#define IMX390_D3RCM_I2C_ADDRESS_8BIT (IMX390_D3RCM_I2C_ADDRESS << 1) +#define IMX390_D3CM_I2C_ADDRESS_8BIT (IMX390_D3CM_I2C_ADDRESS << 1) +#define IMX390_I2C_ADDRESS_3 0x1e +#define IMX390_I2C_ADDRESS_8BIT_3 (IMX390_I2C_ADDRESS_3 << 1) +#define IMX390_I2C_ADDRESS_4 0x20 +#define IMX390_I2C_ADDRESS_8BIT_4 (IMX390_I2C_ADDRESS_4 << 1) + +#endif + +#if IS_ENABLED(CONFIG_VIDEO_TI960) +#define TI960_LANES 4 + +#if IS_ENABLED(CONFIG_VIDEO_IMX390) +#define IMX390A_ADDRESS 0x44 +#define IMX390B_ADDRESS 0x45 +#define IMX390C_ADDRESS 0x46 +#define IMX390D_ADDRESS 0x47 + +#define IMX390A_SER_ADDRESS 0x40 +#define IMX390B_SER_ADDRESS 0x41 +#define IMX390C_SER_ADDRESS 0x42 +#define IMX390D_SER_ADDRESS 0x43 + +static struct ti960_subdev_pdata imx390_d3rcm_pdata_stub = { + .lanes = 4, + .gpio_powerup_seq = {0, 0xa, -1, -1}, + .module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK, + .module_name = "imx390", + .fsin = 0, /* gpio 0 used for FSIN */ +}; + +static struct ti960_subdev_pdata imx390_d3cm_pdata_stub = { + .lanes = 4, + .gpio_powerup_seq = {0, 0x9, -1, -1}, + .module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK, + .module_name = "imx390", + .fsin = 3, /* gpio 3 used for FSIN */ +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_ISX031) + +#define ISX031A_ADDRESS 0x44 +#define ISX031B_ADDRESS 0x45 +#define ISX031C_ADDRESS 0x46 +#define ISX031D_ADDRESS 0x47 + +#define ISX031A_SER_ADDRESS 0x40 +#define ISX031B_SER_ADDRESS 0x41 +#define ISX031C_SER_ADDRESS 0x42 +#define ISX031D_SER_ADDRESS 0x43 + +static struct ti960_subdev_pdata isx031_pdata_stub = { + .lanes = 4, + .fsin = 2, + .gpio_powerup_seq = {0x00, 0x08, 0x08, -1}, + .module_flags = TI960_FL_POWERUP, + .module_name = "isx031", +}; +#endif + +static struct ipu_isys_csi2_config ti960_csi2_cfg_1 = { + .nlanes = TI960_LANES, + .port = 1, +}; + +static struct ipu_isys_csi2_config ti960_csi2_cfg_2 = { + .nlanes = TI960_LANES, + .port = 2, +}; + +static struct ti960_subdev_info ti960_subdevs_1[] = { +#if IS_ENABLED(CONFIG_VIDEO_ISX031) + { + .board_info = { + .type = "isx031", + .addr = ISX031A_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031A_SER_ADDRESS, + .suffix = 'a', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031B_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031B_SER_ADDRESS, + .suffix = 'b', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031C_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031C_SER_ADDRESS, + .suffix = 'c', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031D_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031D_SER_ADDRESS, + .suffix = 'd', + }, +#endif +#if IS_ENABLED(CONFIG_VIDEO_IMX390) + /* D3RCM */ + { + .board_info = { + .type = "imx390", + .addr = IMX390A_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390A_SER_ADDRESS, + .suffix = 'a', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390B_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390B_SER_ADDRESS, + .suffix = 'b', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390C_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390C_SER_ADDRESS, + .suffix = 'c', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390D_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390D_SER_ADDRESS, + .suffix = 'd', + }, + /* D3CM */ + { + .board_info = { + .type = "imx390", + .addr = IMX390A_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390A_SER_ADDRESS, + .suffix = 'a', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390B_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390B_SER_ADDRESS, + .suffix = 'b', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390C_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390C_SER_ADDRESS, + .suffix = 'c', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390D_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390D_SER_ADDRESS, + .suffix = 'd', + }, +#endif +}; + +static struct ti960_subdev_info ti960_subdevs_2[] = { +#if IS_ENABLED(CONFIG_VIDEO_ISX031) + { + .board_info = { + .type = "isx031", + .addr = ISX031A_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031A_SER_ADDRESS, + .suffix = 'e', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031B_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031B_SER_ADDRESS, + .suffix = 'f', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031C_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031C_SER_ADDRESS, + .suffix = 'g', + }, + { + .board_info = { + .type = "isx031", + .addr = ISX031D_ADDRESS, + .platform_data = &isx031_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .ser_alias = ISX031D_SER_ADDRESS, + .suffix = 'h', + }, +#endif +}; + +static struct ti960_pdata ti960_pdata_1 = { + .subdev_info = ti960_subdevs_1, + .subdev_num = ARRAY_SIZE(ti960_subdevs_1), + .reset_gpio = 0, + .FPD_gpio = -1, + .suffix = 'a', +}; + +static struct ti960_pdata ti960_pdata_2 = { + .subdev_info = ti960_subdevs_2, + .subdev_num = ARRAY_SIZE(ti960_subdevs_2), + .reset_gpio = 0, + .FPD_gpio = -1, + .suffix = 'b', +}; + +static struct ipu_isys_subdev_info ti960_sd_1 = { + .csi2 = &ti960_csi2_cfg_1, + .i2c = { + .board_info = { + .type = "ti960", + .addr = TI960_I2C_ADDRESS_2, + .platform_data = &ti960_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.1", + } +}; + +static struct ipu_isys_subdev_info ti960_sd_2 = { + .csi2 = &ti960_csi2_cfg_2, + .i2c = { + .board_info = { + .type = "ti960", + .addr = TI960_I2C_ADDRESS_2, + .platform_data = &ti960_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + } +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXC) +#define LT6911UXC_LANES 4 +#define LT6911UXC_I2C_ADDRESS 0x2B + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +static void lt6911uxc_fixup_spdata(const void *spdata_rep, void *spdata) +{ + const struct ipu_spdata_rep *rep = spdata_rep; + struct lt6911uxc_platform_data *platform = spdata; + + if (spdata_rep && spdata) { + platform->port = rep->port_n; + platform->lanes = rep->lanes; + platform->i2c_slave_address = rep->slave_addr_n; + platform->gpios[0] = rep->gpios[0]; + platform->irq_pin = rep->irq_pin; + platform->irq_pin_flags = rep->irq_pin_flags; + strcpy(platform->irq_pin_name, rep->irq_pin_name); + platform->suffix = rep->suffix; + } +} +#endif + +static struct ipu_isys_csi2_config lt6911uxc_csi2_cfg_1 = { + .nlanes = LT6911UXC_LANES, + .port = 1, +}; + +static struct lt6911uxc_platform_data lt6911uxc_pdata_1 = { + .port = 1, + .lanes = LT6911UXC_LANES, + .i2c_slave_address = LT6911UXC_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "READY_STAT", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'a', + .reset_pin = -1, + .detect_pin = -1, + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info lt6911uxc_sd_1 = { + .csi2 = <6911uxc_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxc", LT6911UXC_I2C_ADDRESS), + .platform_data = <6911uxc_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = lt6911uxc_fixup_spdata, +#endif +}; + +static struct ipu_isys_csi2_config lt6911uxc_csi2_cfg_2 = { + .nlanes = LT6911UXC_LANES, + .port = 2, +}; + +static struct lt6911uxc_platform_data lt6911uxc_pdata_2 = { + .port = 2, + .lanes = LT6911UXC_LANES, + .i2c_slave_address = LT6911UXC_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "READY_STAT", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'b', + .reset_pin = -1, + .detect_pin = -1, + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info lt6911uxc_sd_2 = { + .csi2 = <6911uxc_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxc", LT6911UXC_I2C_ADDRESS), + .platform_data = <6911uxc_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = lt6911uxc_fixup_spdata, +#endif +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXE) +#define LT6911UXE_LANES 4 +#define LT6911UXE_I2C_ADDRESS 0x2B + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +static void lt6911uxe_fixup_spdata(const void *spdata_rep, void *spdata) +{ + const struct ipu_spdata_rep *rep = spdata_rep; + struct lt6911uxe_platform_data *platform = spdata; + + if (spdata_rep && spdata) { + platform->port = rep->port_n; + platform->lanes = rep->lanes; + platform->i2c_slave_address = rep->slave_addr_n; + platform->gpios[0] = rep->gpios[0]; + platform->irq_pin = rep->irq_pin; + platform->irq_pin_flags = rep->irq_pin_flags; + strcpy(platform->irq_pin_name, rep->irq_pin_name); + platform->suffix = rep->suffix; + } +} +#endif + +static struct ipu_isys_csi2_config lt6911uxe_csi2_cfg_1 = { + .nlanes = LT6911UXE_LANES, + .port = 1, +}; + +static struct lt6911uxe_platform_data lt6911uxe_pdata_1 = { + .port = 1, + .lanes = LT6911UXE_LANES, + .i2c_slave_address = LT6911UXE_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "READY_STAT", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'a', + .reset_pin = -1, + .detect_pin = -1, + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info lt6911uxe_sd_1 = { + .csi2 = <6911uxe_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxe", LT6911UXE_I2C_ADDRESS), + .platform_data = <6911uxe_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = lt6911uxe_fixup_spdata, +#endif +}; + +static struct ipu_isys_csi2_config lt6911uxe_csi2_cfg_2 = { + .nlanes = LT6911UXE_LANES, + .port = 2, +}; + +static struct lt6911uxe_platform_data lt6911uxe_pdata_2 = { + .port = 2, + .lanes = LT6911UXE_LANES, + .i2c_slave_address = LT6911UXE_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "READY_STAT", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'b', + .reset_pin = -1, + .detect_pin = -1, + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info lt6911uxe_sd_2 = { + .csi2 = <6911uxe_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxe", LT6911UXE_I2C_ADDRESS), + .platform_data = <6911uxe_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = lt6911uxe_fixup_spdata, +#endif +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_D4XX) +#define D4XX_LANES 2 +#define D4XX_I2C_ADDRESS_0 0x10 +#define D4XX_I2C_ADDRESS_1 0x12 +#define D4XX_I2C_ADDRESS_2 0x14 +#define D4XX_PORT0 0 +#define D4XX_PORT1 1 +#define D4XX_PORT2 2 +#define D4XX_PORT3 3 + +static struct ipu_isys_csi2_config d4xx_csi2_cfg_0 = { + .nlanes = D4XX_LANES, + .port = D4XX_PORT0, +}; + +static struct d4xx_pdata d4xx_pdata_0 = { + .suffix = 'a', +}; + +static struct ipu_isys_subdev_info d4xx_sd_0 = { + .csi2 = &d4xx_csi2_cfg_0, + .i2c = { + .board_info = { + I2C_BOARD_INFO("d4xx", D4XX_I2C_ADDRESS_1), + .platform_data = &d4xx_pdata_0, + }, + .i2c_adapter_bdf = "0000:00:15.1", + }, +}; + +static struct ipu_isys_csi2_config d4xx_csi2_cfg_1 = { + .nlanes = D4XX_LANES, + .port = D4XX_PORT1, +}; + +static struct d4xx_pdata d4xx_pdata_1 = { + .suffix = 'b', +}; + +static struct ipu_isys_subdev_info d4xx_sd_1 = { + .csi2 = &d4xx_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("d4xx", D4XX_I2C_ADDRESS_2), + .platform_data = &d4xx_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.1", + }, +}; + +static struct ipu_isys_csi2_config d4xx_csi2_cfg_2 = { + .nlanes = D4XX_LANES, + .port = D4XX_PORT2, +}; + +static struct d4xx_pdata d4xx_pdata_2 = { + .suffix = 'c', +}; + +static struct ipu_isys_subdev_info d4xx_sd_2 = { + .csi2 = &d4xx_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("d4xx", D4XX_I2C_ADDRESS_1), + .platform_data = &d4xx_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +}; + +static struct ipu_isys_csi2_config d4xx_csi2_cfg_3 = { + .nlanes = D4XX_LANES, + .port = D4XX_PORT3, +}; + +static struct d4xx_pdata d4xx_pdata_3 = { + .suffix = 'd', +}; + +static struct ipu_isys_subdev_info d4xx_sd_3 = { + .csi2 = &d4xx_csi2_cfg_3, + .i2c = { + .board_info = { + I2C_BOARD_INFO("d4xx", D4XX_I2C_ADDRESS_2), + .platform_data = &d4xx_pdata_3, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +}; +#endif + +static struct ipu_isys_clk_mapping clk_mapping[] = { + { CLKDEV_INIT(NULL, NULL, NULL), NULL } +}; + +static struct ipu_isys_subdev_pdata pdata = { + .subdevs = (struct ipu_isys_subdev_info *[]) { +#if IS_ENABLED(CONFIG_VIDEO_AR0234) + &ar0234_sd_1, + &ar0234_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_TI960) + &ti960_sd_1, + &ti960_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXC) + <6911uxc_sd_1, + <6911uxc_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXE) + <6911uxe_sd_1, + <6911uxe_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_D4XX) + &d4xx_sd_0, + &d4xx_sd_1, + &d4xx_sd_2, + &d4xx_sd_3, +#endif + NULL, + }, + .clk_map = clk_mapping, +}; + +static void ipu6_quirk(struct pci_dev *pci_dev) +{ + dev_info(&pci_dev->dev, "%s() attach the platform data", __func__); + pci_dev->dev.platform_data = &pdata; +} + +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_P_PCI_ID, ipu6_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_N_PCI_ID, ipu6_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, IPU6EP_RPL_P_PCI_ID, ipu6_quirk); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/intel/ipu6-tglrvp-pdata.c b/drivers/media/platform/intel/ipu6-tglrvp-pdata.c new file mode 100644 index 000000000000..a0f316e3050e --- /dev/null +++ b/drivers/media/platform/intel/ipu6-tglrvp-pdata.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2022-2024 Intel Corporation +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "ipu.h" + +#if defined(CONFIG_VIDEO_OV8856) +#define OV8856_LANES 4 +#define OV8856_I2C_ADDRESS 0x10 +#define DW9714_I2C_ADDRESS 0xc + +static struct ipu_isys_csi2_config ov8856_csi2_cfg_1 = { + .nlanes = OV8856_LANES, + .port = 1, +}; + +static struct ipu_isys_csi2_config ov8856_csi2_cfg_2 = { + .nlanes = OV8856_LANES, + .port = 2, +}; + +static struct ipu_isys_subdev_info ov8856_sd_1 = { + .csi2 = &ov8856_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ov8856", OV8856_I2C_ADDRESS), + }, + .i2c_adapter_id = 13, + .i2c_adapter_bdf = "0000:00:15.3", + } +}; + +static struct ipu_isys_subdev_info dw9714_sd_1 = { + .i2c = { + .board_info = { + I2C_BOARD_INFO("dw9714", DW9714_I2C_ADDRESS), + }, + .i2c_adapter_id = 13, + .i2c_adapter_bdf = "0000:00:15.3", + } +}; + +static struct ipu_isys_subdev_info ov8856_sd_2 = { + .csi2 = &ov8856_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ov8856", OV8856_I2C_ADDRESS), + }, + .i2c_adapter_id = 15, + .i2c_adapter_bdf = "0000:00:19.1", + } +}; + +#endif + +#if IS_ENABLED(CONFIG_VIDEO_AR0234) +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +static void ar0234_fixup_spdata(const void *spdata_rep, void *spdata) +{ + const struct ipu_spdata_rep *rep = spdata_rep; + struct ar0234_platform_data *platform = spdata; + + if (spdata_rep && spdata) { + platform->port = rep->port_n; + platform->lanes = rep->lanes; + platform->i2c_slave_address = rep->slave_addr_n; + platform->gpios[0] = rep->gpios[0]; + platform->irq_pin = rep->irq_pin; + platform->irq_pin_flags = rep->irq_pin_flags; + strcpy(platform->irq_pin_name, rep->irq_pin_name); + platform->suffix = rep->suffix; + } +} +#endif + +#define AR0234_LANES 2 +#define AR0234_I2C_ADDRESS 0x10 +#define AR0234_I2C_ADDRESS_2 0x18 + +static struct ipu_isys_csi2_config ar0234_csi2_cfg_1 = { + .nlanes = AR0234_LANES, + .port = 1, +}; + +static struct ar0234_platform_data ar0234_pdata_1 = { + .port = 1, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "B23", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'a', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_1 = { + .csi2 = &ar0234_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS), + .platform_data = &ar0234_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.3", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; + +static struct ipu_isys_csi2_config ar0234_csi2_cfg_2 = { + .nlanes = AR0234_LANES, + .port = 2, +}; + +static struct ar0234_platform_data ar0234_pdata_2 = { + .port = 2, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "R6", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'b', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_2 = { + .csi2 = &ar0234_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS), + .platform_data = &ar0234_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; + +#if !IS_ENABLED(CONFIG_VIDEO_LT6911UXC) +static struct ipu_isys_csi2_config ar0234_csi2_cfg_3 = { + .nlanes = AR0234_LANES, + .port = 4, +}; + +static struct ar0234_platform_data ar0234_pdata_3 = { + .port = 4, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "IMGCLKOUT_3", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'c', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_3 = { + .csi2 = &ar0234_csi2_cfg_3, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS), + .platform_data = &ar0234_pdata_3, + }, + .i2c_adapter_bdf = "0000:00:15.2", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; +#endif + +static struct ipu_isys_csi2_config ar0234_csi2_cfg_4 = { + .nlanes = AR0234_LANES, + .port = 5, +}; + +static struct ar0234_platform_data ar0234_pdata_4 = { + .port = 5, + .lanes = 2, + .i2c_slave_address = AR0234_I2C_ADDRESS_2, + .irq_pin = -1, + .irq_pin_name = "H15", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'd', + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info ar0234_sd_4 = { + .csi2 = &ar0234_csi2_cfg_4, + .i2c = { + .board_info = { + I2C_BOARD_INFO("ar0234", AR0234_I2C_ADDRESS_2), + .platform_data = &ar0234_pdata_4, + }, + .i2c_adapter_bdf = "0000:00:15.2", + }, +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + .fixup_spdata = ar0234_fixup_spdata, +#endif +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_IMX390) +#define IMX390_LANES 4 +#define IMX390_D3RCM_I2C_ADDRESS 0x1a +#define IMX390_D3RCM_I2C_ADDRESS_8BIT (IMX390_D3RCM_I2C_ADDRESS << 1) +#define IMX390_D3CM_I2C_ADDRESS_8BIT (IMX390_D3CM_I2C_ADDRESS << 1) +#define IMX390_I2C_ADDRESS_3 0x1e +#define IMX390_I2C_ADDRESS_8BIT_3 (IMX390_I2C_ADDRESS_3 << 1) +#define IMX390_I2C_ADDRESS_4 0x20 +#define IMX390_I2C_ADDRESS_8BIT_4 (IMX390_I2C_ADDRESS_4 << 1) + +static struct ipu_isys_csi2_config imx390_csi2_cfg_1 = { + .nlanes = IMX390_LANES, + .port = 1, +}; + +static struct imx390_platform_data imx390_pdata_1 = { + .port = 1, + .lanes = 4, + .i2c_slave_address = IMX390_I2C_ADDRESS_3, + .suffix = 'a', +}; + +static struct ipu_isys_subdev_info imx390_sd_1 = { + .csi2 = &imx390_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("imx390", IMX390_I2C_ADDRESS_3), + .platform_data = &imx390_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.3", + }, +}; + +static struct ipu_isys_csi2_config imx390_csi2_cfg_2 = { + .nlanes = IMX390_LANES, + .port = 2, +}; + +static struct imx390_platform_data imx390_pdata_2 = { + .port = 2, + .lanes = 4, + .i2c_slave_address = IMX390_I2C_ADDRESS_3, + .suffix = 'b', +}; + +static struct ipu_isys_subdev_info imx390_sd_2 = { + .csi2 = &imx390_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("imx390", IMX390_I2C_ADDRESS_3), + .platform_data = &imx390_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +}; + +static struct ipu_isys_subdev_info imx390_sd_3 = { + .csi2 = &imx390_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("imx390", IMX390_I2C_ADDRESS_4), + .platform_data = &imx390_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.3", + }, +}; + +static struct ipu_isys_subdev_info imx390_sd_4 = { + .csi2 = &imx390_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("imx390", IMX390_I2C_ADDRESS_4), + .platform_data = &imx390_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_TI960) +#define TI960_I2C_ADAPTER 2 +#define TI960_I2C_ADAPTER_2 4 +#define TI960_LANES 4 + +#define IMX390A_ADDRESS 0x44 +#define IMX390B_ADDRESS 0x45 +#define IMX390C_ADDRESS 0x46 +#define IMX390D_ADDRESS 0x47 + +#define IMX390A_SER_ADDRESS 0x40 +#define IMX390B_SER_ADDRESS 0x41 +#define IMX390C_SER_ADDRESS 0x42 +#define IMX390D_SER_ADDRESS 0x43 + +static struct ti960_subdev_pdata imx390_d3rcm_pdata_stub = { + .lanes = 4, + .gpio_powerup_seq = {0, 0xa, -1, -1}, + .module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK, + .module_name = "imx390", + .fsin = 0, /* gpio 0 used for FSIN */ +}; + +static struct ti960_subdev_pdata imx390_d3cm_pdata_stub = { + .lanes = 4, + .gpio_powerup_seq = {0, 0x9, -1, -1}, + .module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK, + .module_name = "imx390", + .fsin = 3, /* gpio 3 used for FSIN */ +}; + +static struct ipu_isys_csi2_config ti960_csi2_cfg = { + .nlanes = TI960_LANES, + .port = 1, +}; + +static struct ti960_subdev_info ti960_subdevs[] = { +#if IS_ENABLED(CONFIG_VIDEO_IMX390) + /* D3RCM */ + { + .board_info = { + .type = "imx390", + .addr = IMX390A_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390A_SER_ADDRESS, + .suffix = 'a', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390B_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390B_SER_ADDRESS, + .suffix = 'b', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390C_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390C_SER_ADDRESS, + .suffix = 'c', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390D_ADDRESS, + .platform_data = &imx390_d3rcm_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390D_SER_ADDRESS, + .suffix = 'd', + }, + /* D3CM */ + { + .board_info = { + .type = "imx390", + .addr = IMX390A_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 0, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390A_SER_ADDRESS, + .suffix = 'a', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390B_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 1, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390B_SER_ADDRESS, + .suffix = 'b', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390C_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 2, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390C_SER_ADDRESS, + .suffix = 'c', + }, + { + .board_info = { + .type = "imx390", + .addr = IMX390D_ADDRESS, + .platform_data = &imx390_d3cm_pdata_stub, + }, + .rx_port = 3, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .ser_alias = IMX390D_SER_ADDRESS, + .suffix = 'd', + }, +#endif +}; + +static struct ti960_pdata ti960_pdata = { + .subdev_info = ti960_subdevs, + .subdev_num = ARRAY_SIZE(ti960_subdevs), + .reset_gpio = 0, + .FPD_gpio = 175, + .suffix = 'a', +}; + +static struct ipu_isys_subdev_info ti960_sd = { + .csi2 = &ti960_csi2_cfg, + .i2c = { + .board_info = { + .type = "ti960", + .addr = TI960_I2C_ADDRESS, + .platform_data = &ti960_pdata, + }, + .i2c_adapter_bdf = "0000:00:15.3", + } +}; + +static struct ti960_pdata ti960_pdata_2 = { + .subdev_info = ti960_subdevs, + .subdev_num = ARRAY_SIZE(ti960_subdevs), + .reset_gpio = 0, + .FPD_gpio = -1, + .suffix = 'a', +}; + +static struct ipu_isys_subdev_info ti960_sd_2 = { + .csi2 = &ti960_csi2_cfg, + .i2c = { + .board_info = { + .type = "ti960", + .addr = TI960_I2C_ADDRESS_2, + .platform_data = &ti960_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:15.0", + } +}; +#endif + +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXC) + +#define LT6911UXC_LANES 4 +#define LT6911UXC_I2C_ADDRESS 0x2B + +static struct ipu_isys_csi2_config lt6911uxc_csi2_cfg_0 = { + .nlanes = LT6911UXC_LANES, + .port = 5, +}; + +static struct lt6911uxc_platform_data lt6911uxc_pdata_0 = { + .port = 5, + .lanes = LT6911UXC_LANES, + .i2c_slave_address = LT6911UXC_I2C_ADDRESS, + .irq_pin = -1, // -1 means it is an auxiliary port which has no + .irq_pin_name = "B23", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'e', + .reset_pin = -1, + .detect_pin = -1, + .gpios = {-1, 0, 0, 0}, +}; + +static struct ipu_isys_subdev_info lt6911uxc_sd_0 = { + .csi2 = <6911uxc_csi2_cfg_0, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxc", LT6911UXC_I2C_ADDRESS), + .platform_data = <6911uxc_pdata_0, + }, + .i2c_adapter_bdf = "0000:00:15.2", + }, +}; + +static struct ipu_isys_csi2_config lt6911uxc_csi2_cfg_1 = { + .nlanes = LT6911UXC_LANES, + .port = 1, +}; + +static struct lt6911uxc_platform_data lt6911uxc_pdata_1 = { + .port = 1, + .lanes = LT6911UXC_LANES, + .i2c_slave_address = LT6911UXC_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "C2", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'a', + .reset_pin = -1, + .detect_pin = -1, +}; + +static struct ipu_isys_subdev_info lt6911uxc_sd_1 = { + .csi2 = <6911uxc_csi2_cfg_1, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxc", LT6911UXC_I2C_ADDRESS), + .platform_data = <6911uxc_pdata_1, + }, + .i2c_adapter_bdf = "0000:00:15.3", + }, +}; + +static struct ipu_isys_csi2_config lt6911uxc_csi2_cfg_2 = { + .nlanes = LT6911UXC_LANES, + .port = 2, +}; + +static struct lt6911uxc_platform_data lt6911uxc_pdata_2 = { + .port = 2, + .lanes = LT6911UXC_LANES, + .i2c_slave_address = LT6911UXC_I2C_ADDRESS, + .irq_pin = -1, + .irq_pin_name = "B18", + .irq_pin_flags = IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + .suffix = 'b', + .reset_pin = -1, + .detect_pin = -1, +}; + +static struct ipu_isys_subdev_info lt6911uxc_sd_2 = { + .csi2 = <6911uxc_csi2_cfg_2, + .i2c = { + .board_info = { + I2C_BOARD_INFO("lt6911uxc", LT6911UXC_I2C_ADDRESS), + .platform_data = <6911uxc_pdata_2, + }, + .i2c_adapter_bdf = "0000:00:19.1", + }, +}; +#endif + +static struct ipu_isys_clk_mapping clk_mapping[] = { + { CLKDEV_INIT(NULL, NULL, NULL), NULL } +}; + +static struct ipu_isys_subdev_pdata pdata = { + .subdevs = (struct ipu_isys_subdev_info *[]) { +#if defined(CONFIG_VIDEO_OV8856) + &ov8856_sd_1, + &dw9714_sd_1, + &ov8856_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_AR0234) + &ar0234_sd_1, + &ar0234_sd_2, +#if !IS_ENABLED(CONFIG_VIDEO_LT6911UXC) + &ar0234_sd_3, +#endif + &ar0234_sd_4, +#endif +#if IS_ENABLED(CONFIG_VIDEO_IMX390) + &imx390_sd_1, + &imx390_sd_2, + &imx390_sd_3, + &imx390_sd_4, +#endif +#if IS_ENABLED(CONFIG_VIDEO_TI960) + &ti960_sd, + &ti960_sd_2, +#endif +#if IS_ENABLED(CONFIG_VIDEO_LT6911UXC) + <6911uxc_sd_0, //Auxiliary port for 4k60fps + <6911uxc_sd_1, + <6911uxc_sd_2, +#endif + NULL, + }, + .clk_map = clk_mapping, +}; + +static void ipu6_quirk(struct pci_dev *pci_dev) +{ + pci_dev->dev.platform_data = &pdata; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, IPU6_PCI_ID, ipu6_quirk); + +MODULE_LICENSE("GPL"); diff --git a/include/media/ar0234.h b/include/media/ar0234.h new file mode 100644 index 000000000000..c8a8c95d4380 --- /dev/null +++ b/include/media/ar0234.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2014 - 2022 Intel Corporation */ + +#ifndef __AR0234_H +#define __AR0234_H + +#include + +#define AR0234_NAME "ar0234" + +struct ar0234_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[16]; + int reset_pin; + int detect_pin; + char suffix; + int gpios[4]; +}; + +#endif /* __AR0234_H */ diff --git a/include/media/d4xx_pdata.h b/include/media/d4xx_pdata.h new file mode 100644 index 000000000000..c8009cf9cdc0 --- /dev/null +++ b/include/media/d4xx_pdata.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2022 Intel Corporation */ + +#ifndef D457_H +#define D457_H + +#define D457_NAME "d4xx" +#define MAX9296_NAME "MAX9296" + +struct d4xx_subdev_info { + struct i2c_board_info board_info; + int i2c_adapter_id; + unsigned short rx_port; + unsigned short phy_i2c_addr; + unsigned short ser_alias; + const char suffix; /* suffix for subdevs */ +}; + +struct d4xx_pdata { + unsigned int subdev_num; + struct d4xx_subdev_info *subdev_info; + unsigned int reset_gpio; + int FPD_gpio; + const char suffix; + unsigned int link_freq_mbps; + unsigned int deser_nlanes; + unsigned int ser_nlanes; + struct i2c_board_info *deser_board_info; +}; +#endif diff --git a/include/media/imx390.h b/include/media/imx390.h new file mode 100644 index 000000000000..be95249fbe96 --- /dev/null +++ b/include/media/imx390.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2014 - 2020 Intel Corporation */ + +#ifndef __IMX390_H +#define __IMX390_H + +#include + +#define IMX390_NAME "imx390" + +#define IMX390_D3CM_I2C_ADDRESS (0x21 << 1) + +struct imx390_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[16]; + char suffix; + int gpios[4]; +}; + +#endif /* __IMX390_H */ diff --git a/include/media/ipu-acpi-pdata.h b/include/media/ipu-acpi-pdata.h new file mode 100644 index 000000000000..9b241cf19ae0 --- /dev/null +++ b/include/media/ipu-acpi-pdata.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CL_EMPTY 0 +#define CL_DISCRETE 1 +#define CL_LT 5 +#define SERDES_MAX_PORT 4 +#define SERDES_MAX_GPIO_POWERUP_SEQ 4 +#define LOOP_SIZE 10 + +int get_sensor_pdata(struct i2c_client *client, + struct ipu_camera_module_data *data, + struct ipu_i2c_helper *helper, + void *priv, size_t size, + enum connection_type connect, + const char *serdes_name); + +struct ipu_isys_subdev_pdata *get_acpi_subdev_pdata(void); + +struct sensor_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[IPU_SPDATA_IRQ_PIN_NAME_LEN]; + int reset_pin; + int detect_pin; + char suffix; + int gpios[IPU_SPDATA_GPIO_NUM]; +}; + +struct serdes_platform_data { + unsigned int subdev_num; + struct serdes_subdev_info *subdev_info; + unsigned int reset_gpio; + unsigned int FPD_gpio; + char suffix; + unsigned int link_freq_mbps; + unsigned int deser_nlanes; + unsigned int ser_nlanes; + struct i2c_board_info *deser_board_info; +}; + +struct serdes_subdev_info { + struct i2c_board_info board_info; + int i2c_adapter_id; + unsigned short rx_port; + unsigned short phy_i2c_addr; + unsigned short ser_alias; + char suffix; /* suffix for subdevs */ +}; + +struct serdes_module_pdata { + unsigned short i2c_addr; + unsigned short i2c_adapter; + unsigned int lanes; + int xshutdown; + int fsin; + int reset; + char gpio_powerup_seq[SERDES_MAX_GPIO_POWERUP_SEQ]; + unsigned int module_flags; + char module_name[I2C_NAME_SIZE]; + char suffix; +}; + +struct serdes_local { + /* num of camera sensor connected to current mipi port */ + unsigned int rx_port; + + /* num of i2c addr for current ACPI device */ + unsigned int i2c_num; + + /* current sensor_addr */ + unsigned short sensor_addr; + + /* physical i2c addr */ + unsigned short phy_i2c_addr; + + /* last mapped addr */ + unsigned short sensor_map_addr; + + /* current serializer_addr */ + unsigned short ser_addr; + + /* last mapped addr */ + unsigned short ser_map_addr; + + /* 2nd group of mapped addr for 2x sensors */ + unsigned short sensor_map_addr_2; + unsigned short ser_map_addr_2; + + /* current gpio_powerup_seq */ + unsigned int gpio_powerup_seq; + + /* current module flag */ + unsigned int module_flags; + + /* counter for total camera sensor connected */ + unsigned int sensor_num; + + /* counter for total deser connected */ + unsigned int deser_num; +}; diff --git a/include/media/ipu-acpi.h b/include/media/ipu-acpi.h new file mode 100644 index 000000000000..67953db42715 --- /dev/null +++ b/include/media/ipu-acpi.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016--2022 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MEDIA_INTEL_IPU_ACPI_H +#define MEDIA_INTEL_IPU_ACPI_H + +#include +#include "ipu-isys.h" + +#define MAX_ACPI_SENSOR_NUM 4 +#define MAX_ACPI_I2C_NUM 12 +#define MAX_ACPI_GPIO_NUM 12 + +#define GPIO_RESET 0x0 +#define GPIO_POWER_EN 0xb +#define GPIO_READY_STAT 0x13 +#define GPIO_HDMI_DETECT 0x14 + +void set_built_in_pdata(struct ipu_isys_subdev_pdata *pdata); + +enum connection_type { + TYPE_DIRECT, + TYPE_SERDES +}; + +/* Data representation as it is in ACPI SSDB buffer */ +struct sensor_bios_data_packed { + u8 version; + u8 sku; + u8 guid_csi2[16]; + u8 devfunction; + u8 bus; + u32 dphylinkenfuses; + u32 clockdiv; + u8 link; + u8 lanes; + u32 csiparams[10]; + u32 maxlanespeed; + u8 sensorcalibfileidx; + u8 sensorcalibfileidxInMBZ[3]; + u8 romtype; + u8 vcmtype; + u8 platforminfo; + u8 platformsubinfo; + u8 flash; + u8 privacyled; + u8 degree; + u8 mipilinkdefined; + u32 mclkspeed; + u8 controllogicid; + u8 mipidataformat; + u8 siliconversion; + u8 customerid; + u8 mclkport; + u8 pmicpos; + u8 voltagerail; + u8 pprval; + u8 pprunit; + u8 flashid; + u8 reserved2[8]; +} __attribute__((__packed__)); + +struct ipu_i2c_info { + unsigned short bus; + unsigned short addr; +}; + +/* Fields needed by ipu driver */ +/* Each I2C client can have 12 device */ +struct sensor_bios_data { + struct device *dev; + u8 link; + u8 lanes; + u8 vcmtype; + u8 flash; + u8 degree; + u8 mclkport; + u32 mclkspeed; + u16 xshutdown; + u8 controllogicid; + u8 pprval; + u8 pprunit; + struct ipu_i2c_info i2c[MAX_ACPI_I2C_NUM]; + u64 i2c_num; +}; + +struct control_logic_data_packed { + u8 version; + u8 controllogictype; + u8 controllogicid; + u8 sensorcardsku; + u8 inputclk; + u8 platformid; + u8 subplatformid; + u8 customerid; + u8 wled1_maxflashcurrent; + u8 wled1_maxtorchcurrent; + u8 wled2_maxflashcurrent; + u8 wled2_maxtorchcurrent; + u8 wled1_type; + u8 wled2_type; + u8 pch_clk_src; + u8 reserved2[17]; +} __attribute__((__packed__)); + +struct ipu_gpio_info { + unsigned short init_state; + unsigned short pin; + unsigned short func; + bool valid; +}; + +struct ipu_irq_info { + int irq_pin; + char irq_pin_name[IPU_SPDATA_IRQ_PIN_NAME_LEN]; +}; + +/* Each I2C client linked to 1 set of CTL Logic */ +struct control_logic_data { + struct device *dev; + u8 id; + u8 type; + u8 sku; + u64 gpio_num; + struct ipu_gpio_info gpio[MAX_ACPI_GPIO_NUM]; + bool completed; +}; + +int ipu_get_acpi_devices(void *driver_data, + struct device *dev, + struct ipu_isys_subdev_pdata **spdata, + struct ipu_isys_subdev_pdata **built_in_pdata, + int (*fn) + (struct device *, void *, + struct ipu_isys_csi2_config *csi2, + bool reprobe)); + +struct ipu_isys_subdev_pdata *get_built_in_pdata(void); + +int ipu_acpi_get_cam_data(struct device *dev, + struct sensor_bios_data *sensor); + +int ipu_acpi_get_dep_data(struct device *dev, + struct control_logic_data *ctl_data); + +int ipu_acpi_get_control_logic_data(struct device *dev, + struct control_logic_data **ctl_data); + +struct intel_ipu6_regulator { + char *src_dev_name; + char *src_rail; + char *dest_rail; +}; + +struct ipu_i2c_helper { + int (*fn)(struct device *dev, void *priv, + struct ipu_isys_csi2_config *csi2, + bool reprobe); + void *driver_data; +}; + +struct ipu_i2c_new_dev { + struct list_head list; + struct i2c_board_info info; + unsigned short int bus; +}; + +struct ipu_camera_module_data { + struct list_head list; + struct ipu_isys_subdev_info sd; + struct ipu_isys_csi2_config csi2; + unsigned int ext_clk; + void *pdata; /* Ptr to generated platform data*/ + void *priv; /* Private for specific subdevice */ +}; + +struct ipu_acpi_devices { + const char *hid_name; + const char *real_driver; + int (*get_platform_data)(struct i2c_client *client, + struct ipu_camera_module_data *data, + struct ipu_i2c_helper *helper, + void *priv, + size_t size, + enum connection_type type, + const char *serdes_name); + void *priv_data; + size_t priv_size; + enum connection_type connect; + const char *serdes_name; +}; + +#endif diff --git a/include/media/ipu-isys.h b/include/media/ipu-isys.h index 0788b1b40fb6..57a7ef96e064 100644 --- a/include/media/ipu-isys.h +++ b/include/media/ipu-isys.h @@ -22,9 +22,61 @@ struct ipu_isys_subdev_i2c_info { char i2c_adapter_bdf[32]; }; +#if (IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING)) \ + || IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) +#define IPU_SPDATA_NAME_LEN 20 +#define IPU_SPDATA_BDF_LEN 32 +#define IPU_SPDATA_GPIO_NUM 4 +#define IPU_SPDATA_IRQ_PIN_NAME_LEN 16 +#endif + +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) +/** + * struct ipu_spdata_rep - override subdev platform data + * + * @name: i2c_board_info.type + * @i2c_adapter_bdf_o: old i2c adapter bdf + * @slave_addr_o: old i2c slave address + * @i2c_adapter_bdf_n: new i2c adapter bdf + * @slave_addr_n: new i2c slave address + * + * identify a subdev with @name, @i2c_adapter_bdf_o and @slave_addr_o and + * configure it to use the new @i2c_adapter_bdf_n and @slave_addr_n + */ +struct ipu_spdata_rep { + /* i2c old information */ + char name[IPU_SPDATA_NAME_LEN]; + unsigned int port_o; + char i2c_adapter_bdf_o[IPU_SPDATA_BDF_LEN]; + uint32_t slave_addr_o; + + /* i2c new information */ + unsigned int port_n; + char i2c_adapter_bdf_n[IPU_SPDATA_BDF_LEN]; + uint32_t slave_addr_n; + + /* sensor_platform */ + unsigned int lanes; + int gpios[IPU_SPDATA_GPIO_NUM]; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[IPU_SPDATA_IRQ_PIN_NAME_LEN]; + char suffix; +}; +#endif + struct ipu_isys_subdev_info { struct ipu_isys_csi2_config *csi2; struct ipu_isys_subdev_i2c_info i2c; +#if IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_USE_PLATFORMDATA) \ + && IS_ENABLED(CONFIG_VIDEO_INTEL_IPU_PDATA_DYNAMIC_LOADING) + void (*fixup_spdata)(const void *spdata_rep, void *spdata); +#endif +#if IS_ENABLED(CONFIG_INTEL_IPU6_ACPI) + char *acpi_hid; +#endif }; struct ipu_isys_clk_mapping { @@ -37,16 +89,9 @@ struct ipu_isys_subdev_pdata { struct ipu_isys_clk_mapping *clk_map; }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) struct sensor_async_subdev { struct v4l2_async_subdev asd; struct ipu_isys_csi2_config csi2; }; -#else -struct sensor_async_sd { - struct v4l2_async_connection asc; - struct ipu_isys_csi2_config csi2; -}; -#endif #endif /* MEDIA_IPU_H */ diff --git a/include/media/isx031.h b/include/media/isx031.h new file mode 100644 index 000000000000..36c9f3d330ce --- /dev/null +++ b/include/media/isx031.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2014 - 2022 Intel Corporation */ + +#ifndef __ISX031_H +#define __ISX031_H + +#include + +#define ISX031_NAME "isx031" + +#define ISX031_I2C_ADDRESS 0x1a +#define ISX031_I2C_ADDRESS_8BIT (ISX031_I2C_ADDRESS << 1) + +struct isx031_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[16]; + char suffix; + int gpios[4]; +}; + +#endif /* __ISX031_H */ diff --git a/include/media/lt6911uxc.h b/include/media/lt6911uxc.h new file mode 100644 index 000000000000..e8368c3cdcd5 --- /dev/null +++ b/include/media/lt6911uxc.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2014 - 2022 Intel Corporation */ + +#ifndef __LT6911UXC_H +#define __LT6911UXC_H + +#include + +#define LT6911UXC_NAME "lt6911uxc" + +struct lt6911uxc_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[16]; + int reset_pin; + int detect_pin; + char suffix; + int gpios[4]; +}; + +#endif /* __LT6911UXC_H */ diff --git a/include/media/lt6911uxe.h b/include/media/lt6911uxe.h new file mode 100644 index 000000000000..353dba7a188e --- /dev/null +++ b/include/media/lt6911uxe.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023 Intel Corporation */ + +#ifndef __LT6911UXE_H +#define __LT6911UXE_H + +#include + +#define LT6911UXE_NAME "lt6911uxe" + +struct lt6911uxe_platform_data { + unsigned int port; + unsigned int lanes; + uint32_t i2c_slave_address; + int irq_pin; + unsigned int irq_pin_flags; + char irq_pin_name[16]; + int reset_pin; + int detect_pin; + char suffix; + int gpios[4]; +}; + +#endif /* __LT6911UXE_H */ diff --git a/include/media/ti960.h b/include/media/ti960.h new file mode 100644 index 000000000000..b9aa89cbf6ed --- /dev/null +++ b/include/media/ti960.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 Intel Corporation */ + +#ifndef TI960_H +#define TI960_H + +#include +#include +#include +#include +#include + +#define TI960_NAME "ti960" + +#define TI960_I2C_ADDRESS 0x32 +#define TI960_I2C_ADDRESS_2 0x3d + +#define PIXEL_ORDER_GRBG 0 +#define PIXEL_ORDER_RGGB 1 +#define PIXEL_ORDER_BGGR 2 +#define PIXEL_ORDER_GBRG 3 + +#define NR_OF_TI960_VCS_PER_SINK_PAD 2 +#define NR_OF_TI960_VCS_SOURCE_PAD 4 +#define NR_OF_TI960_SOURCE_PADS 1 +#define NR_OF_TI960_SINK_PADS 4 +#define NR_OF_TI960_PADS \ + (NR_OF_TI960_SOURCE_PADS + NR_OF_TI960_SINK_PADS) +/* 4port * 2vc/port * 8 stream total */ +#define NR_OF_TI960_STREAMS \ + (NR_OF_TI960_SINK_PADS * NR_OF_TI960_VCS_PER_SINK_PAD \ + * NR_OF_TI960_VCS_SOURCE_PAD) +#define NR_OF_GPIOS_PER_PORT 2 +#define NR_OF_TI960_GPIOS \ + (NR_OF_TI960_SINK_PADS * NR_OF_GPIOS_PER_PORT) + +#define TI960_PAD_SOURCE 4 + +#define TI960_MIN_WIDTH 640 +#define TI960_MIN_HEIGHT 480 +#define TI960_MAX_WIDTH 1920 +#define TI960_MAX_HEIGHT 1200 + +struct ti960_csi_data_format { + u32 code; + u8 width; + u8 compressed; + u8 pixel_order; + u8 mipi_dt_code; +}; + +struct ti960_subdev_info { + struct i2c_board_info board_info; + int i2c_adapter_id; + unsigned short rx_port; + unsigned short phy_i2c_addr; + unsigned short ser_alias; + const char suffix; /* suffix for subdevs */ +}; + +struct ti960_pdata { + unsigned int subdev_num; + struct ti960_subdev_info *subdev_info; + unsigned int reset_gpio; + int FPD_gpio; + const char suffix; + unsigned int link_freq_mbps; + unsigned int deser_nlanes; + unsigned int ser_nlanes; +}; + +#define TI960_MAX_GPIO_POWERUP_SEQ 4 + +/* set this flag if this module needs serializer initialization */ +#define TI960_FL_INIT_SER BIT(0) +/* set this flag if this module has extra powerup sequence */ +#define TI960_FL_POWERUP BIT(1) +/* set this flag if this module needs reset signal */ +#define TI960_FL_RESET BIT(2) +/* set this flag if it need to init serial clk only */ +#define TI960_FL_INIT_SER_CLK BIT(4) + +struct ti960_subdev_pdata { + unsigned short i2c_addr; + unsigned short i2c_adapter; + + unsigned int lanes; /* Number of CSI-2 lanes */ + + /* specify gpio pins of Deser for PWDN, FSIN, RESET. */ + int xshutdown; + int fsin; + int reset; + + /* specify gpio pins boot timing. */ + /* Bit 3 write 0/1 on GPIO3 + * Bit 2 write 0/1 on GPIO2 + * Bit 1 write 0/1 on GPIO1 + * Bit 0 write 0/1 on GPIO0 + */ + char gpio_powerup_seq[TI960_MAX_GPIO_POWERUP_SEQ]; + + /* module_flags can be: + * TI960_FL_INIT_SER + * TI960_FL_POWERUP + * TI960_FL_RESET + */ + unsigned int module_flags; + + char module_name[16]; /* module name from ACPI */ + char suffix; /* suffix to identify multi sensors, abcd.. */ +}; + +#endif diff --git a/include/media/ti964.h b/include/media/ti964.h new file mode 100644 index 000000000000..593453c07b73 --- /dev/null +++ b/include/media/ti964.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2016 - 2020 Intel Corporation */ + +#ifndef TI964_H +#define TI964_H + +#include +#include +#include +#include +#include + +#define TI964_NAME "ti964" + +#define PIXEL_ORDER_GRBG 0 +#define PIXEL_ORDER_RGGB 1 +#define PIXEL_ORDER_BGGR 2 +#define PIXEL_ORDER_GBRG 3 + +#define NR_OF_TI964_STREAMS 4 +#define NR_OF_TI964_SOURCE_PADS 1 +#define NR_OF_TI964_SINK_PADS 4 +#define NR_OF_TI964_PADS \ + (NR_OF_TI964_SOURCE_PADS + NR_OF_TI964_SINK_PADS) +#define NR_OF_GPIOS_PER_PORT 2 +#define NR_OF_TI964_GPIOS \ + (NR_OF_TI964_SINK_PADS * NR_OF_GPIOS_PER_PORT) + +#define TI964_PAD_SOURCE 4 + +#define TI964_MIN_WIDTH 640 +#define TI964_MIN_HEIGHT 480 +#define TI964_MAX_WIDTH 1920 +#define TI964_MAX_HEIGHT 1080 + +struct ti964_csi_data_format { + u32 code; + u8 width; + u8 compressed; + u8 pixel_order; + u8 mipi_dt_code; +}; + +struct ti964_subdev_info { + struct i2c_board_info board_info; + int i2c_adapter_id; + unsigned short rx_port; + unsigned short phy_i2c_addr; + const char suffix; /* suffix for subdevs */ +}; + +struct ti964_pdata { + unsigned int subdev_num; + struct ti964_subdev_info *subdev_info; + unsigned int reset_gpio; + const char suffix; /* suffix for multi aggregators, abcd... */ +}; + +struct ti964_platform_data { + unsigned short i2c_addr; + unsigned short i2c_adapter; + + /* specify gpio pins of Deser for PWDN, FSIN, RESET. */ + int xshutdown; + + char module_name[16]; /* module name from ACPI */ + char suffix; /* suffix to identify multi sensors, abcd.. */ + unsigned int high_framevalid_flags; /* high framevaild flags*/ +}; + +#endif diff --git a/include/uapi/linux/ipu-isys.h b/include/uapi/linux/ipu-isys.h index 4aabb14328a5..f5bb12c16991 100644 --- a/include/uapi/linux/ipu-isys.h +++ b/include/uapi/linux/ipu-isys.h @@ -9,6 +9,9 @@ #define V4L2_CID_IPU_STORE_CSI2_HEADER (V4L2_CID_IPU_BASE + 2) #define V4L2_CID_IPU_ISYS_COMPRESSION (V4L2_CID_IPU_BASE + 3) +#define V4L2_CID_IPU_QUERY_SUB_STREAM (V4L2_CID_IPU_BASE + 4) +#define V4L2_CID_IPU_SET_SUB_STREAM (V4L2_CID_IPU_BASE + 5) + #define VIDIOC_IPU_GET_DRIVER_VERSION \ _IOWR('v', BASE_VIDIOC_PRIVATE + 3, uint32_t) From d8a5c9d79075e3cfdf1c9574e37c653ae40b25e5 Mon Sep 17 00:00:00 2001 From: jiabinhe Date: Tue, 24 Sep 2024 14:07:14 +0800 Subject: [PATCH 2/7] update README Signed-off-by: jiabinhe --- README.md | 106 +++++------------------------------------------------- 1 file changed, 8 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 0867b4a59361..8f856f873ec5 100644 --- a/README.md +++ b/README.md @@ -3,115 +3,25 @@ This repository supports MIPI cameras through the IPU6 on Intel Tiger Lake, Alder Lake, Raptor Lake and Meteor Lake platforms. There are 4 repositories that provide the complete setup: -- https://github.com/intel/ipu6-drivers - kernel drivers for the IPU and sensors -- https://github.com/intel/ipu6-camera-bins - IPU firmware and proprietary image processing libraries -- https://github.com/intel/ipu6-camera-hal - HAL for processing of images in userspace +- https://github.com/intel/ipu6-drivers/tree/ia_ipu6 - kernel drivers for the IPU and sensors +- https://github.com/intel/ipu6-camera-bins/ia_ipu6 - IPU firmware and proprietary image processing libraries +- https://github.com/intel/ipu6-camera-hal/ia_ipu6 - HAL for processing of images in userspace - https://github.com/intel/icamerasrc/tree/icamerasrc_slim_api (branch:icamerasrc_slim_api) - Gstreamer src plugin ## Content of this repository: - IPU6 kernel driver - Kernel patches needed -- Drivers for HM11B1, OV01A1S, OV01A10, OV02C10, OV02E10, OV2740, HM2170, HM2172 and HI556 sensors +- Drivers for IMX390 and ISX031 sensors -## Dependencies -- intel-vsc driver and LJCA USB driver (use https://github.com/intel/ivsc-driver.git for kernel version < 6.6) -- intel USB-IO driver (https://github.com/intel/usbio-drivers.git) -- INTEL_SKL_INT3472 should be enabled - -## Build instructions: -Three ways are available: -1. build with kernel source tree -2. build out of kernel source tree -3. and build with dkms - -### 1. Build with kernel source tree -- Tested with kernel v6.8 +### Build with kernel source tree +- Tested with kernel v6.1.95 - Check out kernel -- Apply patches: - ```sh - # For Meteor Lake B stepping only - patch/0002-iommu-Add-passthrough-for-MTL-IPU.patch - - # For v5.15 <= kernel version < v5.17 and using INT3472 - patch/int3472-v5.15/*.patch - - # For v5.17 <= kernel version < v6.1.7 and using INT3472 - patch/int3472-v5.17/*.patch - - # For kernel version >= 6.1.7 and using INT3472 - patch/int3472-v6.1.7/*.patch - - # For kernel version >= 6.3 and using ov13b10 - patch/ov13b10-v6.3/*.patch - - # For kernel version v6.8 - patch/v6.8/*.patch - ``` -- For kernel v6.8. patch/v6.8/0002-media-Add-IPU6-and-supported-sensors-config.patch will change the related Kconfig & Makefile. -- For latest linux-firmware repo, apply patch/linux-firmware/0001-Add-symbolic-link-for-Intel-IPU6-firmwares.patch to it to make driver work. - Copy `drivers` and `include` folders to kernel source **(except Kconfig & Makefile at drivers/media/pci/intel and drivers/media/i2c as they are modified by patches in previous step. You can delete them before you copy folders.)**. - Enable the following settings in .config ```conf CONFIG_VIDEO_INTEL_IPU6=m - CONFIG_IPU_ISYS_BRIDGE=y - # For kernel >= v6.8 please use IPU_BRIDGE instead of IPU_ISYS_BRIDGE - CONFIG_IPU_BRIDGE=m - CONFIG_VIDEO_OV01A1S=m - CONFIG_VIDEO_OV01A10=m - CONFIG_VIDEO_HM11B1=m - CONFIG_VIDEO_OV02C10=m - CONFIG_VIDEO_OV02E10=m - CONFIG_VIDEO_HM2170=m - CONFIG_VIDEO_HM2172=m - # Set this only if you use only 1 camera and don't want too many device nodes in media-ctl - # CONFIG_IPU_SINGLE_BE_SOC_DEVICE=y - # If your kernel < 5.15 or not set CONFIG_INTEL_SKL_INT3472, please set this - # CONFIG_POWER_CTRL_LOGIC=m - ``` -- LJCA and CVF part as below, please check details at https://github.com/intel/ivsc-driver/blob/main/README.md. - ```conf - CONFIG_MFD_LJCA=m - CONFIG_I2C_LJCA=m - CONFIG_SPI_LJCA=m - CONFIG_GPIO_LJCA=m - CONFIG_USB_LJCA=m - CONFIG_INTEL_MEI_VSC=m - CONFIG_INTEL_MEI_VSC_HW=m - CONFIG_INTEL_VSC=m - CONFIG_INTEL_VSC_CSI=m - CONFIG_INTEL_VSC_ACE=m - CONFIG_INTEL_VSC_PSE=m - CONFIG_INTEL_VSC_ACE_DEBUG=m - ``` -### 2. Build outside kernel source tree -- Requires kernel header installed on build machine -- For kernel >= v6.8, still need to patch kernel by patch/v6.8/0004 & 0005 to make upstream iVSC driver work correctly. For kernel <= v6.6, requires iVSC out-of-tree driver be built together. -- To prepare out-of-tree iVSC driver under kernel <= v6.6: - ```sh - cd ipu6-drivers - git clone https://github.com/intel/ivsc-driver.git - cp -r ivsc-driver/backport-include ivsc-driver/drivers ivsc-driver/include . - rm -rf ivsc-driver - ``` - -- To build and install: - ```sh - make -j`nproc` && sudo make modules_install && sudo depmod -a - ``` - -### 3. Build with dkms -- Prepare out-of-tree iVSC driver under kernel <= v6.6: - ```sh - cd ipu6-drivers - git clone https://github.com/intel/ivsc-driver.git - cp -r ivsc-driver/backport-include ivsc-driver/drivers ivsc-driver/include . - rm -rf ivsc-driver - ``` - -- Register, build and auto install: - ```sh - sudo dkms add . - sudo dkms autoinstall ipu6-drivers/0.0.0 + CONFIG_VIDEO_IMX390=m + CONFIG_VIDEO_ISX031=m ``` From f8be2baf4e3c788f3a02d17aa60658f1c56ed091 Mon Sep 17 00:00:00 2001 From: onceforall <34802599+onceforall@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:35:44 +0800 Subject: [PATCH 3/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f856f873ec5..2ecaa1b45aa2 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This repository supports MIPI cameras through the IPU6 on Intel Tiger Lake, Alde There are 4 repositories that provide the complete setup: - https://github.com/intel/ipu6-drivers/tree/ia_ipu6 - kernel drivers for the IPU and sensors -- https://github.com/intel/ipu6-camera-bins/ia_ipu6 - IPU firmware and proprietary image processing libraries -- https://github.com/intel/ipu6-camera-hal/ia_ipu6 - HAL for processing of images in userspace +- https://github.com/intel/ipu6-camera-bins/tree/ia_ipu6 - IPU firmware and proprietary image processing libraries +- https://github.com/intel/ipu6-camera-hal/tree/ia_ipu6 - HAL for processing of images in userspace - https://github.com/intel/icamerasrc/tree/icamerasrc_slim_api (branch:icamerasrc_slim_api) - Gstreamer src plugin From 24ccd146f994ca2da046fb65a3f9f47e4a7d8341 Mon Sep 17 00:00:00 2001 From: onceforall <34802599+onceforall@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:51:34 +0800 Subject: [PATCH 4/7] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ecaa1b45aa2..70fd5c9fff7b 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,5 @@ There are 4 repositories that provide the complete setup: CONFIG_VIDEO_INTEL_IPU6=m CONFIG_VIDEO_IMX390=m CONFIG_VIDEO_ISX031=m + CONFIG_VIDEO_TI960=m ``` From b8666e71285c708b5f2b3246898249badf709067 Mon Sep 17 00:00:00 2001 From: jiabinhe Date: Wed, 9 Oct 2024 16:09:04 +0800 Subject: [PATCH 5/7] support D3CMC68N-106-085 IMX390 module Signed-off-by: jiabinhe --- .../media/platform/intel/ipu6-acpi-pdata.c | 34 +++++++++++++------ drivers/media/platform/intel/ipu6-acpi.c | 5 ++- include/media/ipu-acpi-pdata.h | 3 +- include/media/ipu-acpi.h | 3 +- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/intel/ipu6-acpi-pdata.c b/drivers/media/platform/intel/ipu6-acpi-pdata.c index a891da26c1ab..05bd4d1dbf50 100644 --- a/drivers/media/platform/intel/ipu6-acpi-pdata.c +++ b/drivers/media/platform/intel/ipu6-acpi-pdata.c @@ -580,14 +580,14 @@ static void set_i2c(struct ipu_isys_subdev_info **sensor_sd, } static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char sensor_name[I2C_NAME_SIZE], - unsigned int lanes) + const char *hid_name, unsigned int lanes) { /* general */ (*module_pdata)->lanes = lanes; strscpy((*module_pdata)->module_name, sensor_name, I2C_NAME_SIZE); /* TI960 and IMX390 specific */ - if (!strcmp(sensor_name, IMX390_NAME)) { + if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10C1")) { (*module_pdata)->gpio_powerup_seq[0] = 0; (*module_pdata)->gpio_powerup_seq[1] = 0x9; (*module_pdata)->gpio_powerup_seq[2] = -1; @@ -596,6 +596,16 @@ static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char (*module_pdata)->fsin = 3; } + /* TI960 and IMX390 specific */ + if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10CM")) { + (*module_pdata)->gpio_powerup_seq[0] = 0; + (*module_pdata)->gpio_powerup_seq[1] = 0xa; + (*module_pdata)->gpio_powerup_seq[2] = -1; + (*module_pdata)->gpio_powerup_seq[3] = -1; + (*module_pdata)->module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK; + (*module_pdata)->fsin = 0; + } + /* TI960 and ISX031 specific */ if (!strcmp(sensor_name, ISX031_NAME)) { (*module_pdata)->gpio_powerup_seq[0] = 0x0; @@ -613,6 +623,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, struct device *dev, struct serdes_platform_data **pdata, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, unsigned int lanes, unsigned int addr, unsigned int subdev_port) @@ -633,7 +644,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, return -ENOMEM; } - set_serdes_sd_pdata(&module_pdata[i], sensor_name, lanes); + set_serdes_sd_pdata(&module_pdata[i], sensor_name, hid_name, lanes); /* board info */ strscpy(serdes_sdinfo[i].board_info.type, sensor_name, I2C_NAME_SIZE); @@ -673,6 +684,7 @@ static u8 suffix_offset = 1; static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, struct device *dev, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, struct control_logic_data *ctl_data, unsigned int port, unsigned int lanes, @@ -740,7 +752,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, } pdata->deser_nlanes = deser_lanes; pdata->ser_nlanes = lanes; - set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, lanes, addr, rx_port); + set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, hid_name, lanes, addr, rx_port); (*sensor_sd)->i2c.board_info.platform_data = pdata; pdata->deser_board_info = &(*sensor_sd)->i2c.board_info; @@ -787,6 +799,7 @@ static void set_serdes_info(struct device *dev, char *sensor_name, const char *s static int populate_dummy(struct device *dev, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect) @@ -809,7 +822,7 @@ static int populate_dummy(struct device *dev, set_i2c(&dummy, dev, sensor_name, addr_dummy); - ret = set_pdata(&dummy, dev, sensor_name, ctl_data, cam_data->pprval, + ret = set_pdata(&dummy, dev, sensor_name, hid_name, ctl_data, cam_data->pprval, cam_data->lanes, addr_dummy, 0, 0, true, connect); if (ret) { kfree(dummy); @@ -827,7 +840,8 @@ static int populate_sensor_pdata(struct device *dev, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect, - const char *serdes_name) + const char *serdes_name, + const char *hid_name) { int ret; @@ -892,7 +906,7 @@ static int populate_sensor_pdata(struct device *dev, } /* Use last I2C device */ - ret = set_pdata(sensor_sd, dev, sensor_name, ctl_data, cam_data->link, + ret = set_pdata(sensor_sd, dev, sensor_name, hid_name, ctl_data, cam_data->link, cam_data->lanes, cam_data->i2c[cam_data->i2c_num - 1].addr, cam_data->pprunit, cam_data->pprval, false, connect); @@ -904,7 +918,7 @@ static int populate_sensor_pdata(struct device *dev, /* Lontium specific */ if (!strcmp(sensor_name, LT6911UXC_NAME) || !strcmp(sensor_name, LT6911UXE_NAME)) { if (cam_data->pprval != cam_data->link) { - ret = populate_dummy(dev, sensor_name, cam_data, ctl_data, connect); + ret = populate_dummy(dev, sensor_name, hid_name, cam_data, ctl_data, connect); if (ret) return ret; } @@ -917,7 +931,7 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_camera_module_data *data, struct ipu_i2c_helper *helper, void *priv, size_t size, - enum connection_type connect, const char *serdes_name) + enum connection_type connect, const char *serdes_name, const char *hid_name) { struct sensor_bios_data *cam_data; struct control_logic_data *ctl_data; @@ -965,7 +979,7 @@ int get_sensor_pdata(struct i2c_client *client, /* populate pdata */ rval = populate_sensor_pdata(&client->dev, &sensor_sd, - client->name, cam_data, ctl_data, connect, serdes_name); + client->name, cam_data, ctl_data, connect, serdes_name, hid_name); if (rval) { kfree(sensor_sd); kfree(cam_data); diff --git a/drivers/media/platform/intel/ipu6-acpi.c b/drivers/media/platform/intel/ipu6-acpi.c index 51eb5b00f816..e6dd7c3d708a 100644 --- a/drivers/media/platform/intel/ipu6-acpi.c +++ b/drivers/media/platform/intel/ipu6-acpi.c @@ -66,6 +66,7 @@ static const struct ipu_acpi_devices supported_devices[] = { { "INTC10C0", AR0234_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // AR0234 HID { "INTC10B1", LT6911UXC_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXC HID { "INTC10C1", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// IMX390 HID + { "INTC10CM", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// new D3 IMX390 HID { "INTC1031", ISX031_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// ISX031 HID { "INTC10C5", LT6911UXE_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXE HID { "INTC10CD", D457_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, D457_NAME },// D457 HID @@ -92,6 +93,7 @@ static const struct acpi_device_id ipu_acpi_match[] = { { "INTC10C0", 0 }, // AR0234 HID { "INTC10B1", 0 }, // LT6911UXC HID { "INTC10C1", 0 }, // IMX390 HID + { "INTC10CM", 0 }, // D3CMC68N-106-085 IMX390 HID { "INTC1031", 0 }, // ISX031 HID { "INTC10C5", 0 }, // LT6911UXE HID { "INTC10CD", 0 }, // D457 HID @@ -123,7 +125,8 @@ static int ipu_acpi_get_pdata(struct i2c_client *client, supported_devices[index].priv_data, supported_devices[index].priv_size, supported_devices[index].connect, - supported_devices[index].serdes_name); + supported_devices[index].serdes_name, + supported_devices[index].hid_name); return 0; } diff --git a/include/media/ipu-acpi-pdata.h b/include/media/ipu-acpi-pdata.h index 9b241cf19ae0..e9849078e988 100644 --- a/include/media/ipu-acpi-pdata.h +++ b/include/media/ipu-acpi-pdata.h @@ -22,7 +22,8 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_i2c_helper *helper, void *priv, size_t size, enum connection_type connect, - const char *serdes_name); + const char *serdes_name, + const char *hid_name); struct ipu_isys_subdev_pdata *get_acpi_subdev_pdata(void); diff --git a/include/media/ipu-acpi.h b/include/media/ipu-acpi.h index 67953db42715..8b6a92c47812 100644 --- a/include/media/ipu-acpi.h +++ b/include/media/ipu-acpi.h @@ -195,7 +195,8 @@ struct ipu_acpi_devices { void *priv, size_t size, enum connection_type type, - const char *serdes_name); + const char *serdes_name, + const char *hid_name); void *priv_data; size_t priv_size; enum connection_type connect; From 963c4b759a44e6eecd7ec6a6c330a7925bb9a117 Mon Sep 17 00:00:00 2001 From: jiabinhe Date: Fri, 11 Oct 2024 16:56:02 +0800 Subject: [PATCH 6/7] WW41 release Signed-off-by: jiabinhe --- drivers/media/pci/intel/ipu.c | 5 +- drivers/media/pci/intel/ipu6/Makefile | 4 +- drivers/media/pci/intel/ipu6/psys/Makefile | 36 + .../media/pci/intel/ipu6/psys/ipu-fw-psys.c | 431 ++++ .../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 | 1775 +++++++++++++++++ drivers/media/pci/intel/ipu6/psys/ipu-psys.h | 224 +++ .../media/pci/intel/ipu6/psys/ipu-resources.c | 868 ++++++++ .../pci/intel/ipu6/psys/ipu6-fw-resources.c | 609 ++++++ .../pci/intel/ipu6/psys/ipu6-l-scheduler.c | 615 ++++++ .../intel/ipu6/psys/ipu6-platform-resources.h | 196 ++ drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c | 561 ++++++ 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 | 1020 ++++++++++ .../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 + .../media/platform/intel/ipu6-acpi-pdata.c | 34 +- drivers/media/platform/intel/ipu6-acpi.c | 5 +- include/media/ipu-acpi-pdata.h | 3 +- include/media/ipu-acpi.h | 3 +- 27 files changed, 8220 insertions(+), 36 deletions(-) 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 diff --git a/drivers/media/pci/intel/ipu.c b/drivers/media/pci/intel/ipu.c index 3abead4479ec..c88e1b8d8144 100644 --- a/drivers/media/pci/intel/ipu.c +++ b/drivers/media/pci/intel/ipu.c @@ -367,8 +367,9 @@ static int ipu_pci_config_setup(struct pci_dev *dev) pci_write_config_word(dev, PCI_COMMAND, pci_command); /* disable IPU6 PCI ATS on mtl ES2 */ - if (ipu_ver == IPU_VER_6EP_MTL && boot_cpu_data.x86_stepping == 0x2 && - pci_ats_supported(dev)) + if ((boot_cpu_data.x86_model == 0xac || + boot_cpu_data.x86_model == 0xaa) && + boot_cpu_data.x86_stepping == 0x2 && pci_ats_supported(dev)) pci_disable_ats(dev); /* no msi pci capability for IPU6EP */ diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile index e6e582e63d96..5be6ac707e03 100644 --- a/drivers/media/pci/intel/ipu6/Makefile +++ b/drivers/media/pci/intel/ipu6/Makefile @@ -37,9 +37,9 @@ intel-ipu6-isys-objs += ../ipu-isys-csi2-be.o obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o -obj-$(CONFIG_VIDEO_INTEL_IPU6) += ../psys/ +obj-$(CONFIG_VIDEO_INTEL_IPU6) += psys/ ccflags-y += -I$(srcpath)/$(src)/../../../../../include/ -ccflags-y += -I$(srcpath)/$(src)/../psys/ +ccflags-y += -I$(srcpath)/$(src)/psys/ ccflags-y += -I$(srcpath)/$(src)/../ ccflags-y += -I$(srcpath)/$(src)/ diff --git a/drivers/media/pci/intel/ipu6/psys/Makefile b/drivers/media/pci/intel/ipu6/psys/Makefile new file mode 100644 index 000000000000..d4f5a3c9efa4 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/Makefile @@ -0,0 +1,36 @@ +# 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 + +ccflags-y += -DIPU_OTF_SUPPORT +ccflags-y += -DIPU_PSYS_GPC + +intel-ipu6-psys-objs += ipu-psys.o \ + ipu6-psys.o \ + ipu-resources.o \ + ipu6-psys-gpc.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 + +ifeq ($(CONFIG_COMPAT),y) +intel-ipu6-psys-objs += ipu-psys-compat32.o +endif + +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)/../ +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..7fd7bc827ce6 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu-fw-psys.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2016 - 2024 Intel Corporation + +#include + +#include + +#include +#include "ipu-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; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 0); + +out: + return ret; +} + +int ipu_fw_psys_ppg_suspend(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + /* ppg suspend cmd uses QUEUE_DEVICE_ID instead of QUEUE_COMMAND_ID */ + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 1); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 1); + +out: + return ret; +} + +int ipu_fw_psys_ppg_resume(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_send_put_token(kcmd->fh->psys->fwcom, 0); + +out: + return ret; +} + +int ipu_fw_psys_pg_abort(struct ipu_psys_kcmd *kcmd) +{ + struct ipu_fw_psys_cmd *psys_cmd; + int ret = 0; + + psys_cmd = ipu_send_get_token(kcmd->fh->psys->fwcom, 0); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + ipu_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 = ipu_recv_get_token(psys->fwcom, 0); + if (!rcv) + return 0; + + memcpy(event, rcv, sizeof(*event)); + ipu_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) +{ + 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(&kcmd->fh->psys->adev->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) +{ + u32 type; + u32 buffer_state; + u32 *buffer_ptr; + struct ipu_fw_psys_buffer_set *buf_set = kcmd->kbuf_set->buf_set; + + 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(&kcmd->fh->psys->adev->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 ipu_fw_psys_cmd *psys_cmd; + unsigned int queue_id; + int ret = 0; + unsigned int size; + + if (ipu_ver == IPU_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 = ipu_send_get_token(kcmd->fh->psys->fwcom, queue_id); + if (!psys_cmd) { + dev_err(&kcmd->fh->psys->adev->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; + + ipu_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) +{ + int retry = IPU_PSYS_OPEN_RETRY, retval; + + retval = ipu_fw_com_open(psys->fwcom); + if (retval) { + dev_err(&psys->adev->dev, "fw com open failed.\n"); + return retval; + } + + do { + usleep_range(IPU_PSYS_OPEN_TIMEOUT_US, + IPU_PSYS_OPEN_TIMEOUT_US + 10); + retval = ipu_fw_com_ready(psys->fwcom); + if (!retval) { + dev_dbg(&psys->adev->dev, "psys port open ready!\n"); + break; + } + } while (retry-- > 0); + + if (!retry && retval) { + dev_err(&psys->adev->dev, "psys port open ready failed %d\n", + retval); + ipu_fw_com_close(psys->fwcom); + return retval; + } + return 0; +} + +int ipu_fw_psys_close(struct ipu_psys *psys) +{ + int retval; + + retval = ipu_fw_com_close(psys->fwcom); + if (retval) { + dev_err(&psys->adev->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..9d69a260db11 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu-psys.c @@ -0,0 +1,1775 @@ +// 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 "ipu.h" +#include "ipu-mmu.h" +#include "ipu-bus.h" +#include "ipu-platform.h" +#include "ipu-buttress.h" +#include "ipu-cpd.h" +#include "ipu-fw-psys.h" +#include "ipu-psys.h" +#include "ipu-platform-psys.h" +#include "ipu-platform-regs.h" +#include "ipu-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 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 ipu_psys_remove(struct ipu_bus_device *adev); + +static struct bus_type ipu_psys_bus = { + .name = IPU_PSYS_NAME, +}; + +/* + * 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 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(&psys->adev->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 nr = 0; + int ret = -ENOMEM; + + 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; + + if (attach->npages != 0) { + pages = attach->pages; + npages = attach->npages; + attach->vma_is_io = 1; + goto skip_pages; + } + + pages = kvzalloc(array_size, GFP_KERNEL); + if (!pages) + goto free_sgt; + + mmap_read_lock(current->mm); + vma = find_vma(current->mm, start); + if (!vma) { + ret = -EFAULT; + goto error_up_read; + } + + /* + * For buffers from Gralloc, VM_PFNMAP is expected, + * but VM_IO is set. Possibly bug in Gralloc. + */ + attach->vma_is_io = vma->vm_flags & (VM_IO | VM_PFNMAP); + + if (attach->vma_is_io) { + unsigned long io_start = start; + + if (vma->vm_end < start + attach->len) { + dev_err(attach->dev, + "vma at %lu is too small for %llu bytes\n", + start, attach->len); + ret = -EFAULT; + goto error_up_read; + } + + for (nr = 0; nr < npages; nr++, io_start += PAGE_SIZE) { + unsigned long pfn; + + ret = follow_pfn(vma, io_start, &pfn); + if (ret) + goto error_up_read; + pages[nr] = pfn_to_page(pfn); + } + } else { + nr = get_user_pages(start & PAGE_MASK, npages, + FOLL_WRITE, + pages, NULL); + if (nr < npages) + goto error_up_read; + } + mmap_read_unlock(current->mm); + + attach->pages = pages; + attach->npages = npages; + +skip_pages: + 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 (!attach->vma_is_io) + while (nr > 0) + put_page(pages[--nr]); + + if (array_size <= PAGE_SIZE) + kfree(pages); + else + vfree(pages); +free_sgt: + kfree(sgt); + + dev_err(attach->dev, "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; + + if (!attach->vma_is_io) { + int i = attach->npages; + + while (--i >= 0) { + set_page_dirty_lock(attach->pages[i]); + put_page(attach->pages[i]); + } + } + + 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); +} + +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_device *isp = psys->adev->isp; + struct ipu_psys_fh *fh; + int rval; + + if (isp->flr_done) + return -EIO; + + 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(kbuf->dbuf, &dmap); + } + if (kbuf->sgt) + dma_buf_unmap_attachment(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 ipu_psys_kbuffer *kbuf; + struct ipu_psys_desc *desc; + + desc = psys_desc_lookup(fh, fd); + if (WARN_ON_ONCE(!desc)) { + dev_err(&psys->adev->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(&psys->adev->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 ipu_psys_desc *desc; + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct dma_buf *dbuf; + int ret; + + if (!buf->base.userptr) { + dev_err(&psys->adev->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(&psys->adev->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 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(&psys->adev->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, &psys->adev->dev); + if (IS_ERR(kbuf->db_attach)) { + dev_dbg(&psys->adev->dev, "dma buf attach failed\n"); + goto kbuf_map_fail; + } + + kbuf->sgt = dma_buf_map_attachment(kbuf->db_attach, DMA_BIDIRECTIONAL); + if (IS_ERR_OR_NULL(kbuf->sgt)) { + kbuf->sgt = NULL; + dev_dbg(&psys->adev->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(kbuf->dbuf, &dmap)) { + dev_dbg(&psys->adev->dev, "dma buf vmap failed\n"); + goto kbuf_map_fail; + } + kbuf->kaddr = dmap.vaddr; + + dev_dbg(&psys->adev->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->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->dev, "IOC_UNMAPBUF\n"); + + return ret; +} + +static unsigned int 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; + unsigned int res = 0; + + dev_dbg(&psys->adev->dev, "ipu psys poll\n"); + + poll_wait(file, &fh->wait, wait); + + if (ipu_get_completed_kcmd(fh)) + res = POLLIN; + + dev_dbg(&psys->adev->dev, "ipu psys poll res %u\n", res); + + return res; +} + +static long ipu_get_manifest(struct ipu_psys_manifest *manifest, + struct ipu_psys_fh *fh) +{ + struct ipu_psys *psys = fh->psys; + struct ipu_device *isp = psys->adev->isp; + struct ipu_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(psys->fw_sgt.sgl); + + entries = ipu_cpd_pkg_dir_get_num_entries(psys->pkg_dir); + if (!manifest || manifest->index > entries - 1) { + dev_err(&psys->adev->dev, "invalid argument\n"); + return -EINVAL; + } + + if (!ipu_cpd_pkg_dir_get_size(psys->pkg_dir, manifest->index) || + ipu_cpd_pkg_dir_get_type(psys->pkg_dir, manifest->index) < + IPU_CPD_PKG_DIR_CLIENT_PG_TYPE) { + dev_dbg(&psys->adev->dev, "invalid pkg dir entry\n"); + return -ENOENT; + } + + client_pkg_offset = ipu_cpd_pkg_dir_get_address(psys->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 ipu_bus_device *adev = to_ipu_bus_device(dev); + struct ipu_psys *psys = ipu_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 = ipu_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 (!ipu_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); + ipu_trace_restore(&psys->adev->dev); + + ipu_configure_spc(adev->isp, + &psys->pdata->ipdata->hw_variant, + IPU_CPD_PKG_DIR_PSYS_SERVER_IDX, + psys->pdata->base, psys->pkg_dir, + psys->pkg_dir_dma_addr); + + retval = ipu_fw_psys_open(psys); + if (retval) { + dev_err(&psys->adev->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 ipu_bus_device *adev = to_ipu_bus_device(dev); + struct ipu_psys *psys = ipu_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); + + ipu_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 cpd_fw_reload(struct ipu_device *isp) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(isp->psys); + int rval; + + if (!isp->secure_mode) { + dev_warn(&isp->pdev->dev, + "CPD firmware reload was only supported for secure mode.\n"); + return -EINVAL; + } + + if (isp->cpd_fw) { + ipu_cpd_free_pkg_dir(isp->psys, psys->pkg_dir, + psys->pkg_dir_dma_addr, + psys->pkg_dir_size); + + ipu_buttress_unmap_fw_image(isp->psys, &psys->fw_sgt); + release_firmware(isp->cpd_fw); + isp->cpd_fw = NULL; + dev_info(&isp->pdev->dev, "Old FW removed\n"); + } + + rval = request_cpd_fw(&isp->cpd_fw, isp->cpd_fw_name, + &isp->pdev->dev); + if (rval) { + dev_err(&isp->pdev->dev, "Requesting firmware(%s) failed\n", + isp->cpd_fw_name); + return rval; + } + + rval = ipu_cpd_validate_cpd_file(isp, isp->cpd_fw->data, + isp->cpd_fw->size); + if (rval) { + dev_err(&isp->pdev->dev, "Failed to validate cpd file\n"); + goto out_release_firmware; + } + + rval = ipu_buttress_map_fw_image(isp->psys, isp->cpd_fw, &psys->fw_sgt); + if (rval) + goto out_release_firmware; + + psys->pkg_dir = ipu_cpd_create_pkg_dir(isp->psys, + isp->cpd_fw->data, + sg_dma_address(psys->fw_sgt.sgl), + &psys->pkg_dir_dma_addr, + &psys->pkg_dir_size); + + if (!psys->pkg_dir) { + rval = -EINVAL; + goto out_unmap_fw_image; + } + + isp->pkg_dir = psys->pkg_dir; + isp->pkg_dir_dma_addr = psys->pkg_dir_dma_addr; + isp->pkg_dir_size = psys->pkg_dir_size; + + if (!isp->secure_mode) + return 0; + + rval = ipu_fw_authenticate(isp, 1); + if (rval) + goto out_free_pkg_dir; + + return 0; + +out_free_pkg_dir: + ipu_cpd_free_pkg_dir(isp->psys, psys->pkg_dir, + psys->pkg_dir_dma_addr, psys->pkg_dir_size); +out_unmap_fw_image: + ipu_buttress_unmap_fw_image(isp->psys, &psys->fw_sgt); +out_release_firmware: + release_firmware(isp->cpd_fw); + isp->cpd_fw = NULL; + + return rval; +} + +#ifdef CONFIG_DEBUG_FS +static int ipu_psys_icache_prefetch_sp_get(void *data, u64 *val) +{ + struct ipu_psys *psys = data; + + *val = psys->icache_prefetch_sp; + return 0; +} + +static int ipu_psys_icache_prefetch_sp_set(void *data, u64 val) +{ + struct ipu_psys *psys = data; + + if (val != !!val) + return -EINVAL; + + psys->icache_prefetch_sp = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(psys_icache_prefetch_sp_fops, + ipu_psys_icache_prefetch_sp_get, + ipu_psys_icache_prefetch_sp_set, "%llu\n"); + +static int ipu_psys_icache_prefetch_isp_get(void *data, u64 *val) +{ + struct ipu_psys *psys = data; + + *val = psys->icache_prefetch_isp; + return 0; +} + +static int ipu_psys_icache_prefetch_isp_set(void *data, u64 val) +{ + struct ipu_psys *psys = data; + + if (val != !!val) + return -EINVAL; + + psys->icache_prefetch_isp = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(psys_icache_prefetch_isp_fops, + ipu_psys_icache_prefetch_isp_get, + ipu_psys_icache_prefetch_isp_set, "%llu\n"); + +static int ipu_psys_init_debugfs(struct ipu_psys *psys) +{ + struct dentry *file; + struct dentry *dir; + + dir = debugfs_create_dir("psys", psys->adev->isp->ipu_dir); + if (IS_ERR(dir)) + return -ENOMEM; + + file = debugfs_create_file("icache_prefetch_sp", 0600, + dir, psys, &psys_icache_prefetch_sp_fops); + if (IS_ERR(file)) + goto err; + + file = debugfs_create_file("icache_prefetch_isp", 0600, + dir, psys, &psys_icache_prefetch_isp_fops); + if (IS_ERR(file)) + goto err; + + psys->debugfsdir = dir; + + return 0; +err: + debugfs_remove_recursive(dir); + return -ENOMEM; +} +#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 ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + void __iomem *spc_regs_base = psys->pdata->base + + psys->pdata->ipdata->hw_variant.spc_offset; + u32 val = 0; + + val |= IPU_PSYS_SPC_STATUS_START | + IPU_PSYS_SPC_STATUS_RUN | + IPU_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; + val |= psys->icache_prefetch_sp ? + IPU_PSYS_SPC_STATUS_ICACHE_PREFETCH : 0; + writel(val, spc_regs_base + IPU_PSYS_REG_SPC_STATUS_CTRL); +} + +static int query_sp(struct ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_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 + IPU_PSYS_REG_SPC_STATUS_CTRL); + + /* return true when READY == 1, START == 0 */ + val &= IPU_PSYS_SPC_STATUS_READY | IPU_PSYS_SPC_STATUS_START; + + return val == IPU_PSYS_SPC_STATUS_READY; +} + +static int ipu_psys_fw_init(struct ipu_psys *psys) +{ + unsigned int size; + struct ipu_fw_syscom_queue_config *queue_cfg; + struct ipu_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 ipu_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 == IPU_VER_6 || ipu_ver == IPU_VER_6EP || ipu_ver == IPU_VER_6EP_MTL) + size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID; + + queue_cfg = devm_kzalloc(&psys->adev->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 = ipu_fw_com_prepare(&fwcom, psys->adev, psys->pdata->base); + if (!psys->fwcom) { + dev_err(&psys->adev->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; + int rval; + + rval = ipu_psys_fw_init(psys); + + if (rval) { + dev_err(&psys->adev->dev, "FW init failed(%d)\n", rval); + ipu_psys_remove(psys->adev); + } else { + dev_info(&psys->adev->dev, "FW init done\n"); + } +} + +static int ipu_psys_probe(struct ipu_bus_device *adev) +{ + struct ipu_device *isp = adev->isp; + struct ipu_psys_pg *kpg, *kpg0; + struct ipu_psys *psys; + unsigned int minor; + int i, rval = -E2BIG; + + /* firmware is not ready, so defer the probe */ + if (!isp->pkg_dir) + return -EPROBE_DEFER; + + rval = ipu_mmu_hw_init(adev->mmu); + if (rval) + return rval; + + 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(&adev->dev, "too many devices\n"); + goto out_unlock; + } + + psys = devm_kzalloc(&adev->dev, sizeof(*psys), GFP_KERNEL); + if (!psys) { + rval = -ENOMEM; + goto out_unlock; + } + + psys->adev = adev; + psys->pdata = adev->pdata; + psys->icache_prefetch_sp = 0; + + psys->power_gating = 0; + + ipu_trace_init(adev->isp, psys->pdata->base, &adev->dev, + psys_trace_blocks); + + 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(&adev->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; + } + + ipu_bus_set_drvdata(adev, 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(); + psys->pkg_dir = isp->pkg_dir; + psys->pkg_dir_dma_addr = isp->pkg_dir_dma_addr; + psys->pkg_dir_size = isp->pkg_dir_size; + psys->fw_sgt = isp->fw_sgt; + + /* 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(&adev->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 = ipu_cpd_pkg_dir_get_num_entries(psys->pkg_dir); + + dev_info(&adev->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(&adev->dev, "FW init failed(%d)\n", rval); + goto out_free_pgs; + } + + psys->dev.parent = &adev->dev; + psys->dev.bus = &ipu_psys_bus; + 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(&psys->dev, "psys device_register failed\n"); + goto out_release_fw_com; + } + + /* Add the hw stepping information to caps */ + strscpy(psys->caps.dev_model, IPU_MEDIA_DEV_MODEL_NAME, + sizeof(psys->caps.dev_model)); + + mutex_unlock(&ipu_psys_mutex); + +#ifdef CONFIG_DEBUG_FS + /* Debug fs failure is not fatal. */ + ipu_psys_init_debugfs(psys); +#endif + + adev->isp->cpd_fw_reload = &cpd_fw_reload; + + dev_info(&adev->dev, "psys probe minor: %d\n", minor); + + ipu_mmu_hw_cleanup(adev->mmu); + + return 0; + +out_release_fw_com: + ipu_fw_com_release(psys->fwcom, 1); +out_free_pgs: + list_for_each_entry_safe(kpg, kpg0, &psys->pgs, list) { + dma_free_attrs(&adev->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 */ + ipu_trace_uninit(&adev->dev); + mutex_unlock(&ipu_psys_mutex); + + ipu_mmu_hw_cleanup(adev->mmu); + + return rval; +} + +static void ipu_psys_remove(struct ipu_bus_device *adev) +{ + struct ipu_device *isp = adev->isp; + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + struct ipu_psys_pg *kpg, *kpg0; + +#ifdef CONFIG_DEBUG_FS + if (isp->ipu_dir) + debugfs_remove_recursive(psys->debugfsdir); +#endif + + 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(&adev->dev, kpg->size, kpg->pg, + kpg->pg_dma_addr, 0); + kfree(kpg); + } + + if (psys->fwcom && ipu_fw_com_release(psys->fwcom, 1)) + dev_err(&adev->dev, "fw com release failed.\n"); + + kfree(psys->server_init); + kfree(psys->syscom_config); + + ipu_trace_uninit(&adev->dev); + + 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(&adev->dev, "removed\n"); +} + +static irqreturn_t psys_isr_threaded(struct ipu_bus_device *adev) +{ + struct ipu_psys *psys = ipu_bus_get_drvdata(adev); + void __iomem *base = psys->pdata->base; + u32 status; + int r; + + mutex_lock(&psys->mutex); +#ifdef CONFIG_PM + r = pm_runtime_get_if_in_use(&psys->adev->dev); + if (!r || WARN_ON_ONCE(r < 0)) { + mutex_unlock(&psys->mutex); + return IRQ_NONE; + } +#endif + + status = readl(base + IPU_REG_PSYS_GPDEV_IRQ_STATUS); + writel(status, base + IPU_REG_PSYS_GPDEV_IRQ_CLEAR); + + if (status & IPU_PSYS_GPDEV_IRQ_FWIRQ(IPU_PSYS_GPDEV_FWIRQ0)) { + writel(0, base + IPU_REG_PSYS_GPDEV_FWIRQ(0)); + ipu_psys_handle_events(psys); + } + + pm_runtime_put(&psys->adev->dev); + mutex_unlock(&psys->mutex); + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static struct ipu_bus_driver ipu_psys_driver = { + .probe = ipu_psys_probe, + .remove = ipu_psys_remove, + .isr_threaded = psys_isr_threaded, + .wanted = IPU_PSYS_NAME, + .drv = { + .name = IPU_PSYS_NAME, + .owner = THIS_MODULE, + .pm = PSYS_PM_OPS, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ipu_psys_init(void) +{ + int rval = alloc_chrdev_region(&ipu_psys_dev_t, 0, + IPU_PSYS_NUM_DEVICES, IPU_PSYS_NAME); + if (rval) { + pr_err("can't alloc psys chrdev region (%d)\n", rval); + return rval; + } + + rval = bus_register(&ipu_psys_bus); + if (rval) { + pr_warn("can't register psys bus (%d)\n", rval); + goto out_bus_register; + } + + ipu_bus_register_driver(&ipu_psys_driver); + + return rval; + +out_bus_register: + unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); + + return rval; +} + +static void __exit ipu_psys_exit(void) +{ + ipu_bus_unregister_driver(&ipu_psys_driver); + bus_unregister(&ipu_psys_bus); + unregister_chrdev_region(ipu_psys_dev_t, IPU_PSYS_NUM_DEVICES); +} + +static const struct pci_device_id ipu_pci_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6SE_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_P_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_ADL_N_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_RPL_P_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU6EP_MTL_PCI_ID)}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, ipu_pci_tbl); + +module_init(ipu_psys_init); +module_exit(ipu_psys_exit); + +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); 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..450ed6dd8660 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu-psys.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2013 - 2024 Intel Corporation */ + +#ifndef IPU_PSYS_H +#define IPU_PSYS_H + +#include +#include + +#include +#include "ipu.h" +#include "ipu-pdata.h" +#include "ipu-fw-psys.h" +#include "ipu-platform-psys.h" + +#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 + +/* 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 ipu_psys_pdata *pdata; + struct ipu_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 */ +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfsdir; +#endif + + /* 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 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 ipu_buttress_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; + bool vma_is_io; + 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..b607b0716f56 --- /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 == IPU_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 == IPU_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 == IPU_VER_6EP || ipu_ver == IPU_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 == IPU_VER_6SE) + return ipu6se_res_defs; + + if (ipu_ver == IPU_VER_6EP || ipu_ver == IPU_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 == IPU_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 == IPU_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..fa89a4a7ba7e --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu6-fw-resources.c @@ -0,0 +1,609 @@ +// 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; + 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 == IPU_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 == IPU_VER_6 || ipu_ver == IPU_VER_6EP || + ipu_ver == IPU_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(&psys->adev->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(&psys->adev->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(&psys->adev->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(&psys->adev->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 == IPU_VER_6SE || ipu_ver == IPU_VER_6 || + ipu_ver == IPU_VER_6EP || ipu_ver == IPU_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..0d5f84a1ee31 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu6-l-scheduler.c @@ -0,0 +1,615 @@ +// 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; + + mutex_lock(&sc_list->lock); + list_for_each_entry_safe(tmp0, tmp1, &sc_list->list, sched_list) { + if (tmp0 == kppg) { + dev_dbg(&psys->adev->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 ipu_psys_ppg *tmp0, *tmp1; + + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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; + 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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(&psys->adev->dev, + "ppg is suspending/stopping\n"); + } else { + dev_err(&psys->adev->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) +{ + 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(&psys->adev->dev, "ppg %p stopped!\n", kppg); + ipu_psys_kcmd_complete(kppg, kcmd, -EIO); + } + } + + if (old_ppg_state != kppg->state) + dev_dbg(&psys->adev->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) +{ + /* Assume power gating process can be aborted directly during START */ + if (psys->power_gating == PSYS_POWER_GATED) { + dev_dbg(&psys->adev->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 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(&psys->adev->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) +{ + /* 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(&psys->adev->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..0345d3bc5c2b --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu6-ppg.c @@ -0,0 +1,561 @@ +// 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 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(&fh->psys->adev->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 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(&psys->adev->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 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(&psys->adev->dev, "Unable to set bufset\n"); + goto error; + } + } + + return 0; + +error: + dev_err(&psys->adev->dev, "failed to get buffer set\n"); + return ret; +} + +void ipu_psys_ppg_complete(struct ipu_psys *psys, struct ipu_psys_ppg *kppg) +{ + u8 queue_id; + int old_ppg_state; + + if (!psys || !kppg) + return; + + 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(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd *kcmd = ipu_psys_ppg_get_kcmd(kppg, + KCMD_STATE_PPG_START); + unsigned int i; + int ret; + + if (!kcmd) { + dev_err(&psys->adev->dev, "failed to find start kcmd!\n"); + return -EINVAL; + } + + dev_dbg(&psys->adev->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(&psys->adev->dev, "Unable to set terminal\n"); + return ret; + } + } + + ret = ipu_fw_psys_pg_submit(kcmd); + if (ret) { + dev_err(&psys->adev->dev, "failed to submit kcmd!\n"); + return ret; + } + + ret = ipu_psys_allocate_resources(&psys->adev->dev, + kcmd->kpg->pg, + kcmd->pg_manifest, + &kcmd->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "alloc resources failed!\n"); + return ret; + } + + ret = pm_runtime_get_sync(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->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(&psys->adev->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(&psys->adev->dev); + ipu_psys_reset_process_cell(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd tmp_kcmd = { + .kpg = kppg->kpg, + .fh = kppg->fh, + }; + int ret; + + dev_dbg(&psys->adev->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(&psys->adev->dev, + kppg->kpg->pg, + kppg->manifest, + &kppg->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "failed to allocate res\n"); + return -EIO; + } + + ret = ipu_fw_psys_ppg_resume(&tmp_kcmd); + if (ret) { + dev_err(&psys->adev->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(&psys->adev->dev, "failed to submit kcmd!\n"); + return ret; + } + + ret = ipu_psys_allocate_resources(&psys->adev->dev, + kppg->kpg->pg, + kppg->manifest, + &kppg->kpg->resource_alloc, + &psys->resource_pool_running); + if (ret) { + dev_err(&psys->adev->dev, "failed to allocate res\n"); + return ret; + } + + ret = ipu_psys_kcmd_start(psys, &tmp_kcmd); + if (ret) { + dev_err(&psys->adev->dev, "failed to start kcmd!\n"); + goto error; + } + } + dev_dbg(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd kcmd_temp; + int ppg_id, ret = 0; + + if (kcmd) { + list_move_tail(&kcmd->list, &kppg->kcmds_processing_list); + } else { + dev_dbg(&psys->adev->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(&psys->adev->dev, "stop ppg(%d, addr 0x%p)\n", ppg_id, kppg); + + if (kppg->state & PPG_STATE_SUSPENDED) { + if (enable_suspend_resume) { + dev_dbg(&psys->adev->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(&psys->adev->dev, + "ppg(%d) failed to resume\n", ppg_id); + } else if (kcmd != &kcmd_temp) { + ipu_psys_free_cmd_queue_resource( + &psys->resource_pool_running, + ipu_fw_psys_ppg_get_base_queue_id(kcmd)); + ipu_psys_kcmd_complete(kppg, kcmd, 0); + dev_dbg(&psys->adev->dev, + "s_change:%s %p %d -> %d\n", __func__, + kppg, kppg->state, PPG_STATE_STOPPED); + pm_runtime_put(&psys->adev->dev); + kppg->state = PPG_STATE_STOPPED; + return 0; + } else { + return 0; + } + } + dev_dbg(&psys->adev->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(&psys->adev->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 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(&psys->adev->dev, "suspend ppg(%d, addr 0x%p)\n", ppg_id, kppg); + + dev_dbg(&psys->adev->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(&psys->adev->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; + 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(&psys->adev->dev, + "kppg 0x%p fail to qbufset %d", + kppg, ret); + break; + } + list_move_tail(&kcmd->list, + &kppg->kcmds_processing_list); + dev_dbg(&psys->adev->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 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, STOPPED kppgs has already + * power down and new kppgs might come now. + */ + if (kppg->state != PPG_STATE_SUSPENDED) { + mutex_unlock(&kppg->mutex); + continue; + } + + ret = pm_runtime_put(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->dev, + "failed to power gating off\n"); + pm_runtime_get_sync(&psys->adev->dev); + + } + mutex_unlock(&kppg->mutex); + } + mutex_unlock(&fh->mutex); + } +} + +void ipu_psys_exit_power_gating(struct ipu_psys *psys) +{ + 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(&psys->adev->dev); + if (ret < 0) { + dev_err(&psys->adev->dev, + "failed to power gating\n"); + pm_runtime_put_noidle(&psys->adev->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..10e8919dde1d --- /dev/null +++ b/drivers/media/pci/intel/ipu6/psys/ipu6-psys.c @@ -0,0 +1,1020 @@ +// 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 "ipu.h" +#include "ipu-psys.h" +#include "ipu6-ppg.h" +#include "ipu-platform-regs.h" +#include "ipu-trace.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"); + +struct ipu_trace_block psys_trace_blocks[] = { + { + .offset = IPU_TRACE_REG_PS_TRACE_UNIT_BASE, + .type = IPU_TRACE_BLOCK_TUN, + }, + { + .offset = IPU_TRACE_REG_PS_SPC_EVQ_BASE, + .type = IPU_TRACE_BLOCK_TM, + }, + { + .offset = IPU_TRACE_REG_PS_SPP0_EVQ_BASE, + .type = IPU_TRACE_BLOCK_TM, + }, + { + .offset = IPU_TRACE_REG_PS_SPC_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_SPP0_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_MMU_GPC_BASE, + .type = IPU_TRACE_BLOCK_GPC, + }, + { + .offset = IPU_TRACE_REG_PS_GPREG_TRACE_TIMER_RST_N, + .type = IPU_TRACE_TIMER_RST, + }, + { + .type = IPU_TRACE_BLOCK_END, + } +}; + +static void ipu6_set_sp_info_bits(void *base) +{ + int i; + + writel(IPU_INFO_REQUEST_DESTINATION_IOSF, + base + IPU_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); + + for (i = 0; i < 4; i++) + writel(IPU_INFO_REQUEST_DESTINATION_IOSF, + base + IPU_REG_PSYS_INFO_SEG_CMEM_MASTER(i)); + for (i = 0; i < 4; i++) + writel(IPU_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) +{ + unsigned int i; + u32 val; + + /* power domain req */ + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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 *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 + + IPU_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 = IPU_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 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(&psys->adev->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(&psys->adev->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(&psys->adev->dev, + "err: all buffer.flags&DMA_HANDLE must 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(&psys->adev->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(&psys->adev->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(&psys->adev->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(&psys->adev->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; +} + +/* + * 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; + + 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(&psys->adev->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) +{ + int ret; + + if (psys->adev->isp->flr_done) + return -EIO; + + 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->dev, "token not match\n"); + return -EINVAL; + } + + kcmd->kpg = kppg->kpg; + + dev_dbg(&psys->adev->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(&psys->adev->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(&psys->adev->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 ipu_psys_kcmd *kcmd; + size_t pg_size; + int ret; + + if (psys->adev->isp->flr_done) + return -EIO; + + 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(&psys->adev->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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->dev, "invalid event\n"); + continue; + } + + if (error || !kppg) { + dev_err(&psys->adev->dev, "event error, command %d\n", + cmd); + break; + } + + dev_dbg(&psys->adev->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 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(&psys->adev->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(&psys->adev->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 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(&psys->adev->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(&psys->adev->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(&psys->adev->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 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(&fh->psys->adev->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 ipu_psys_kcmd *kcmd = NULL; + int rval; + + dev_dbg(&psys->adev->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..806ef7058930 --- /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 + */ +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 */ +}; + +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, +}; + +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 +}; + +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 +}; + +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/drivers/media/platform/intel/ipu6-acpi-pdata.c b/drivers/media/platform/intel/ipu6-acpi-pdata.c index 05bd4d1dbf50..a891da26c1ab 100644 --- a/drivers/media/platform/intel/ipu6-acpi-pdata.c +++ b/drivers/media/platform/intel/ipu6-acpi-pdata.c @@ -580,14 +580,14 @@ static void set_i2c(struct ipu_isys_subdev_info **sensor_sd, } static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char sensor_name[I2C_NAME_SIZE], - const char *hid_name, unsigned int lanes) + unsigned int lanes) { /* general */ (*module_pdata)->lanes = lanes; strscpy((*module_pdata)->module_name, sensor_name, I2C_NAME_SIZE); /* TI960 and IMX390 specific */ - if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10C1")) { + if (!strcmp(sensor_name, IMX390_NAME)) { (*module_pdata)->gpio_powerup_seq[0] = 0; (*module_pdata)->gpio_powerup_seq[1] = 0x9; (*module_pdata)->gpio_powerup_seq[2] = -1; @@ -596,16 +596,6 @@ static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char (*module_pdata)->fsin = 3; } - /* TI960 and IMX390 specific */ - if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10CM")) { - (*module_pdata)->gpio_powerup_seq[0] = 0; - (*module_pdata)->gpio_powerup_seq[1] = 0xa; - (*module_pdata)->gpio_powerup_seq[2] = -1; - (*module_pdata)->gpio_powerup_seq[3] = -1; - (*module_pdata)->module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK; - (*module_pdata)->fsin = 0; - } - /* TI960 and ISX031 specific */ if (!strcmp(sensor_name, ISX031_NAME)) { (*module_pdata)->gpio_powerup_seq[0] = 0x0; @@ -623,7 +613,6 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, struct device *dev, struct serdes_platform_data **pdata, char sensor_name[I2C_NAME_SIZE], - const char *hid_name, unsigned int lanes, unsigned int addr, unsigned int subdev_port) @@ -644,7 +633,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, return -ENOMEM; } - set_serdes_sd_pdata(&module_pdata[i], sensor_name, hid_name, lanes); + set_serdes_sd_pdata(&module_pdata[i], sensor_name, lanes); /* board info */ strscpy(serdes_sdinfo[i].board_info.type, sensor_name, I2C_NAME_SIZE); @@ -684,7 +673,6 @@ static u8 suffix_offset = 1; static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, struct device *dev, char sensor_name[I2C_NAME_SIZE], - const char *hid_name, struct control_logic_data *ctl_data, unsigned int port, unsigned int lanes, @@ -752,7 +740,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, } pdata->deser_nlanes = deser_lanes; pdata->ser_nlanes = lanes; - set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, hid_name, lanes, addr, rx_port); + set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, lanes, addr, rx_port); (*sensor_sd)->i2c.board_info.platform_data = pdata; pdata->deser_board_info = &(*sensor_sd)->i2c.board_info; @@ -799,7 +787,6 @@ static void set_serdes_info(struct device *dev, char *sensor_name, const char *s static int populate_dummy(struct device *dev, char sensor_name[I2C_NAME_SIZE], - const char *hid_name, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect) @@ -822,7 +809,7 @@ static int populate_dummy(struct device *dev, set_i2c(&dummy, dev, sensor_name, addr_dummy); - ret = set_pdata(&dummy, dev, sensor_name, hid_name, ctl_data, cam_data->pprval, + ret = set_pdata(&dummy, dev, sensor_name, ctl_data, cam_data->pprval, cam_data->lanes, addr_dummy, 0, 0, true, connect); if (ret) { kfree(dummy); @@ -840,8 +827,7 @@ static int populate_sensor_pdata(struct device *dev, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect, - const char *serdes_name, - const char *hid_name) + const char *serdes_name) { int ret; @@ -906,7 +892,7 @@ static int populate_sensor_pdata(struct device *dev, } /* Use last I2C device */ - ret = set_pdata(sensor_sd, dev, sensor_name, hid_name, ctl_data, cam_data->link, + ret = set_pdata(sensor_sd, dev, sensor_name, ctl_data, cam_data->link, cam_data->lanes, cam_data->i2c[cam_data->i2c_num - 1].addr, cam_data->pprunit, cam_data->pprval, false, connect); @@ -918,7 +904,7 @@ static int populate_sensor_pdata(struct device *dev, /* Lontium specific */ if (!strcmp(sensor_name, LT6911UXC_NAME) || !strcmp(sensor_name, LT6911UXE_NAME)) { if (cam_data->pprval != cam_data->link) { - ret = populate_dummy(dev, sensor_name, hid_name, cam_data, ctl_data, connect); + ret = populate_dummy(dev, sensor_name, cam_data, ctl_data, connect); if (ret) return ret; } @@ -931,7 +917,7 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_camera_module_data *data, struct ipu_i2c_helper *helper, void *priv, size_t size, - enum connection_type connect, const char *serdes_name, const char *hid_name) + enum connection_type connect, const char *serdes_name) { struct sensor_bios_data *cam_data; struct control_logic_data *ctl_data; @@ -979,7 +965,7 @@ int get_sensor_pdata(struct i2c_client *client, /* populate pdata */ rval = populate_sensor_pdata(&client->dev, &sensor_sd, - client->name, cam_data, ctl_data, connect, serdes_name, hid_name); + client->name, cam_data, ctl_data, connect, serdes_name); if (rval) { kfree(sensor_sd); kfree(cam_data); diff --git a/drivers/media/platform/intel/ipu6-acpi.c b/drivers/media/platform/intel/ipu6-acpi.c index e6dd7c3d708a..51eb5b00f816 100644 --- a/drivers/media/platform/intel/ipu6-acpi.c +++ b/drivers/media/platform/intel/ipu6-acpi.c @@ -66,7 +66,6 @@ static const struct ipu_acpi_devices supported_devices[] = { { "INTC10C0", AR0234_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // AR0234 HID { "INTC10B1", LT6911UXC_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXC HID { "INTC10C1", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// IMX390 HID - { "INTC10CM", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// new D3 IMX390 HID { "INTC1031", ISX031_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// ISX031 HID { "INTC10C5", LT6911UXE_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXE HID { "INTC10CD", D457_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, D457_NAME },// D457 HID @@ -93,7 +92,6 @@ static const struct acpi_device_id ipu_acpi_match[] = { { "INTC10C0", 0 }, // AR0234 HID { "INTC10B1", 0 }, // LT6911UXC HID { "INTC10C1", 0 }, // IMX390 HID - { "INTC10CM", 0 }, // D3CMC68N-106-085 IMX390 HID { "INTC1031", 0 }, // ISX031 HID { "INTC10C5", 0 }, // LT6911UXE HID { "INTC10CD", 0 }, // D457 HID @@ -125,8 +123,7 @@ static int ipu_acpi_get_pdata(struct i2c_client *client, supported_devices[index].priv_data, supported_devices[index].priv_size, supported_devices[index].connect, - supported_devices[index].serdes_name, - supported_devices[index].hid_name); + supported_devices[index].serdes_name); return 0; } diff --git a/include/media/ipu-acpi-pdata.h b/include/media/ipu-acpi-pdata.h index e9849078e988..9b241cf19ae0 100644 --- a/include/media/ipu-acpi-pdata.h +++ b/include/media/ipu-acpi-pdata.h @@ -22,8 +22,7 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_i2c_helper *helper, void *priv, size_t size, enum connection_type connect, - const char *serdes_name, - const char *hid_name); + const char *serdes_name); struct ipu_isys_subdev_pdata *get_acpi_subdev_pdata(void); diff --git a/include/media/ipu-acpi.h b/include/media/ipu-acpi.h index 8b6a92c47812..67953db42715 100644 --- a/include/media/ipu-acpi.h +++ b/include/media/ipu-acpi.h @@ -195,8 +195,7 @@ struct ipu_acpi_devices { void *priv, size_t size, enum connection_type type, - const char *serdes_name, - const char *hid_name); + const char *serdes_name); void *priv_data; size_t priv_size; enum connection_type connect; From d1357f5d8d1e080d960cea496f1c5468454a8e22 Mon Sep 17 00:00:00 2001 From: jiabinhe Date: Fri, 25 Oct 2024 10:53:14 +0800 Subject: [PATCH 7/7] ww45 release Signed-off-by: jiabinhe --- drivers/media/i2c/ar0234.c | 2 +- drivers/media/i2c/imx390.c | 29 +- drivers/media/i2c/isx031.c | 3 +- drivers/media/i2c/lt6911uxc.c | 4 +- drivers/media/i2c/lt6911uxe.c | 4 +- drivers/media/i2c/ti953-ser.c | 72 ++-- drivers/media/i2c/ti953.h | 16 +- drivers/media/i2c/ti960-des.c | 318 +++++++++++++----- .../media/platform/intel/ipu6-acpi-pdata.c | 36 +- drivers/media/platform/intel/ipu6-acpi.c | 5 +- .../media/platform/intel/ipu6-adlrvp-pdata.c | 36 +- .../media/platform/intel/ipu6-tglrvp-pdata.c | 20 +- include/media/imx390.h | 2 +- include/media/ipu-acpi-pdata.h | 3 +- include/media/ipu-acpi.h | 3 +- include/media/isx031.h | 1 - 16 files changed, 341 insertions(+), 213 deletions(-) diff --git a/drivers/media/i2c/ar0234.c b/drivers/media/i2c/ar0234.c index 37dfcb17b63f..0d0e3a4b36ce 100644 --- a/drivers/media/i2c/ar0234.c +++ b/drivers/media/i2c/ar0234.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2019-2024 Intel Corporation. -#include #include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/media/i2c/imx390.c b/drivers/media/i2c/imx390.c index bb68227b93ad..7bb4c5a19a7c 100644 --- a/drivers/media/i2c/imx390.c +++ b/drivers/media/i2c/imx390.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2021-2024 Intel Corporation. -#include #include #include #include @@ -9,10 +8,11 @@ #include #include #include +#include +#include #include #include #include -#include #include #define IMX390_LINK_FREQ_360MHZ 360000000ULL @@ -26,7 +26,6 @@ #define IMX390_REG_VALUE_16BIT 2 #define IMX390_REG_CHIP_ID 0x0330 -#define IMX390_CHIP_ID 0x0 /* vertical-timings from sensor */ #define IMX390_REG_VTS 0x300A @@ -1366,6 +1365,10 @@ static int imx390_set_ctrl(struct v4l2_ctrl *ctrl) struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); int ret = 0; + /* 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 IMX390_CID_EXPOSURE_SHS1: case IMX390_CID_EXPOSURE_SHS2: @@ -1895,21 +1898,21 @@ static int imx390_identify_module(struct imx390 *imx390) { struct i2c_client *client = v4l2_get_subdevdata(&imx390->sd); int ret; + int retry = 50; u32 val; - ret = imx390_read_reg(imx390, IMX390_REG_CHIP_ID, - IMX390_REG_VALUE_08BIT, &val); + while (retry--) { + ret = imx390_read_reg(imx390, IMX390_REG_CHIP_ID, + IMX390_REG_VALUE_16BIT, &val); + if (ret == 0) + break; + usleep_range(10000, 10500); + } + if (ret) return ret; - return 0; - - /* chip id not known yet */ - if (val != IMX390_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%x", - IMX390_CHIP_ID, val); - return -ENXIO; - } + dev_info(&client->dev, "chip id: %04x", val); return 0; } diff --git a/drivers/media/i2c/isx031.c b/drivers/media/i2c/isx031.c index d6359d2c6aa6..a84d33e1c1ef 100644 --- a/drivers/media/i2c/isx031.c +++ b/drivers/media/i2c/isx031.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2022 Intel Corporation. -#include #include #include #include @@ -9,6 +8,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/drivers/media/i2c/lt6911uxc.c b/drivers/media/i2c/lt6911uxc.c index c08174f4d2cf..71599a97412d 100644 --- a/drivers/media/i2c/lt6911uxc.c +++ b/drivers/media/i2c/lt6911uxc.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2022-2023 Intel Corporation. -#include #include #include #include @@ -12,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -21,7 +22,6 @@ #include #include -#include #include /* v4l2 debug level */ diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c index 0102a652ff4f..d218075b7232 100644 --- a/drivers/media/i2c/lt6911uxe.c +++ b/drivers/media/i2c/lt6911uxe.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2023 Intel Corporation. -#include #include #include #include @@ -12,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -21,7 +22,6 @@ #include #include -#include #include /* v4l2 debug level */ diff --git a/drivers/media/i2c/ti953-ser.c b/drivers/media/i2c/ti953-ser.c index b9686b900382..c26d7099e15f 100644 --- a/drivers/media/i2c/ti953-ser.c +++ b/drivers/media/i2c/ti953-ser.c @@ -19,18 +19,11 @@ #include "ti960-reg.h" #include "ti953.h" -int ti953_reg_write(struct v4l2_subdev *sd, unsigned short rx_port, - unsigned short ser_alias, unsigned char reg, unsigned char val) +int ti953_reg_write(struct i2c_client *client, unsigned char reg, unsigned char val) { int ret; int retry, timeout = 10; - struct i2c_client *client = v4l2_get_subdevdata(sd); - unsigned short addr_backup; - - dev_dbg(sd->dev, "%s port %d, ser_alias %x, reg %x, val %x", - __func__, rx_port, ser_alias, reg, val); - addr_backup = client->addr; - client->addr = ser_alias; + dev_dbg(&client->dev, "reg %x, val %x", reg, val); for (retry = 0; retry < timeout; retry++) { ret = i2c_smbus_write_byte_data(client, reg, val); if (ret < 0) @@ -39,26 +32,19 @@ int ti953_reg_write(struct v4l2_subdev *sd, unsigned short rx_port, break; } - client->addr = addr_backup; if (retry >= timeout) { - dev_err(sd->dev, - "%s:write reg failed: port=%2x, addr=%2x, reg=%2x\n", - __func__, rx_port, ser_alias, reg); + dev_err(&client->dev, "%s:failed: reg=%2x\n", __func__, reg); return -EREMOTEIO; } return 0; } -int ti953_reg_read(struct v4l2_subdev *sd, unsigned short rx_port, - unsigned short ser_alias, unsigned char reg, unsigned char *val) +int ti953_reg_read(struct i2c_client *client, unsigned char reg, unsigned char *val) { int ret, retry, timeout = 10; - struct i2c_client *client = v4l2_get_subdevdata(sd); unsigned short addr_backup; - addr_backup = client->addr; - client->addr = ser_alias; for (retry = 0; retry < timeout; retry++) { ret = i2c_smbus_read_byte_data(client, reg); if (ret < 0) @@ -69,18 +55,15 @@ int ti953_reg_read(struct v4l2_subdev *sd, unsigned short rx_port, } } - client->addr = addr_backup; if (retry >= timeout) { - dev_err(sd->dev, - "%s:read reg failed: port=%2x, addr=%2x, reg=%2x\n", - __func__, rx_port, ser_alias, reg); + dev_err(&client->dev, "%s:failed: reg=%2x\n", __func__, reg); return -EREMOTEIO; } return 0; } -bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +bool ti953_detect(struct i2c_client *client) { bool ret = false; int i; @@ -88,10 +71,9 @@ bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short unsigned char val; for (i = 0; i < ARRAY_SIZE(ti953_FPD3_RX_ID); i++) { - rval = ti953_reg_read(sd, rx_port, ser_alias, - ti953_FPD3_RX_ID[i].reg, &val); + rval = ti953_reg_read(client, ti953_FPD3_RX_ID[i].reg, &val); if (rval) { - dev_err(sd->dev, "port %d, ti953 write timeout %d\n", rx_port, rval); + dev_err(&client->dev, "ti953 read timeout %d\n", rval); break; } if (val != ti953_FPD3_RX_ID[i].val_expected) @@ -101,40 +83,38 @@ bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short if (i == ARRAY_SIZE(ti953_FPD3_RX_ID)) ret = true; else - dev_err(sd->dev, "TI953 Probe Failed"); + dev_err(&client->dev, "TI953 Probe Failed"); return ret; } -int ti953_init(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +int ti953_init(struct i2c_client *client) { int i, rval; for (i = 0; i < ARRAY_SIZE(ti953_init_settings); i++) { - rval = ti953_reg_write(sd, rx_port, ser_alias, - ti953_init_settings[i].reg, - ti953_init_settings[i].val); + rval = ti953_reg_write(client, ti953_init_settings[i].reg, + ti953_init_settings[i].val); if (rval) { - dev_err(sd->dev, "port %d, ti953 write timeout %d\n", 0, rval); + dev_err(&client->dev, "ti953 write timeout %d\n", rval); break; } } - ti953_init_clk(sd, rx_port, ser_alias); + ti953_init_clk(client); return 0; } -int ti953_init_clk(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias) +int ti953_init_clk(struct i2c_client *client) { int i, rval; for (i = 0; i < ARRAY_SIZE(ti953_init_settings_clk); i++) { - rval = ti953_reg_write(sd, rx_port, ser_alias, - ti953_init_settings_clk[i].reg, - ti953_init_settings_clk[i].val); + rval = ti953_reg_write(client, ti953_init_settings_clk[i].reg, + ti953_init_settings_clk[i].val); if (rval) { - dev_err(sd->dev, "port %d, ti953 write timeout %d\n", 0, rval); + dev_err(&client->dev, "ti953 write timeout %d\n", rval); break; } } @@ -142,7 +122,7 @@ int ti953_init_clk(struct v4l2_subdev *sd, unsigned short rx_port, unsigned shor return 0; } -int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_alias, uint8_t i2c_speed) +int32_t ti953_bus_speed(struct i2c_client *client, uint8_t i2c_speed) { struct ti953_register_write scl_high_reg; struct ti953_register_write scl_low_reg; @@ -165,8 +145,7 @@ int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_a break; case TI953_I2C_SPEED_HIGH: default: - dev_err(sd->dev, "port %u, ti953 unsupported I2C speed mode %u", - rx_port, i2c_speed); + dev_err(&client->dev, "ti953 unsupported I2C speed mode %u", i2c_speed); scl_high_reg.val = TI953_I2C_SCL_HIGH_TIME_STANDARD; scl_low_reg.val = TI953_I2C_SCL_LOW_TIME_STANDARD; ret = -EINVAL; @@ -174,18 +153,15 @@ int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_a } if (ret != 0) return ret; - ret = ti953_reg_write(sd, rx_port, ser_alias, - scl_high_reg.reg, scl_high_reg.val); + ret = ti953_reg_write(client, scl_high_reg.reg, scl_high_reg.val); if (ret != 0) { - dev_err(sd->dev, "port %u, ti953 write SCL_HIGH_TIME failed %d", - rx_port, ret); + dev_err(&client->dev, "ti953 write SCL_HIGH_TIME failed %d", ret); return ret; } - ret = ti953_reg_write(sd, rx_port, ser_alias, + ret = ti953_reg_write(client, scl_low_reg.reg, scl_low_reg.val); if (ret != 0) { - dev_err(sd->dev, "port %u, ti953 write SCL_LOW_TIME failed %d", - rx_port, ret); + dev_err(&client->dev, " ti953 write SCL_LOW_TIME failed %d", ret); return ret; } diff --git a/drivers/media/i2c/ti953.h b/drivers/media/i2c/ti953.h index da47bfaf23b3..f06f899251ab 100644 --- a/drivers/media/i2c/ti953.h +++ b/drivers/media/i2c/ti953.h @@ -145,16 +145,12 @@ static const struct ti953_register_devid ti953_FPD3_RX_ID[] = { {0xf5, 0x33}, }; -int ti953_reg_write(struct v4l2_subdev *sd, unsigned short rx_port, - unsigned short ser_alias, unsigned char reg, unsigned char val); +int ti953_reg_write(struct i2c_client *client, unsigned char reg, unsigned char val); +int ti953_reg_read(struct i2c_client *client, unsigned char reg, unsigned char *val); -int ti953_reg_read(struct v4l2_subdev *sd, unsigned short rx_port, - unsigned short ser_alias, unsigned char reg, unsigned char *val); - -bool ti953_detect(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); - -int ti953_init(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); -int ti953_init_clk(struct v4l2_subdev *sd, unsigned short rx_port, unsigned short ser_alias); -int32_t ti953_bus_speed(struct v4l2_subdev *sd, uint16_t rx_port, uint16_t ser_alias, uint8_t i2c_speed); +bool ti953_detect(struct i2c_client *client); +int ti953_init(struct i2c_client *client); +int ti953_init_clk(struct i2c_client *client); +int32_t ti953_bus_speed(struct i2c_client *client, uint8_t i2c_speed); #endif diff --git a/drivers/media/i2c/ti960-des.c b/drivers/media/i2c/ti960-des.c index 384447033323..380d000822c6 100644 --- a/drivers/media/i2c/ti960-des.c +++ b/drivers/media/i2c/ti960-des.c @@ -22,9 +22,22 @@ #include "ti960-reg.h" #include "ti953.h" +// FIXME: Move these to platform data +#define D3_BUS_SWITCH_ADDR 0x70 +#define D3_GPIO_EXP_ADDR 0x70 +#define D3_GPIO_EXP_ALIAS(rx_port) (0x60+rx_port) + +#define NUM_ALIASES 8 + #define MIPI_CSI2_TYPE_RAW12 0x2c #define MIPI_CSI2_TYPE_YUV422_8 0x1e +struct ti960_slave { + unsigned short addr; + unsigned short alias; + bool auto_ack; +}; + struct ti960_subdev { struct v4l2_subdev *sd; unsigned short rx_port; @@ -32,6 +45,9 @@ struct ti960_subdev { unsigned short phy_i2c_addr; unsigned short alias_i2c_addr; unsigned short ser_i2c_addr; + struct i2c_client *serializer; + struct i2c_client *gpio_exp; + struct ti960_slave slaves[NUM_ALIASES]; char sd_name[16]; }; @@ -42,6 +58,7 @@ struct ti960 { struct ti960_pdata *pdata; struct ti960_subdev sub_devs[NR_OF_TI960_SINK_PADS]; struct ti960_subdev_pdata subdev_pdata[NR_OF_TI960_SINK_PADS]; + struct i2c_client *bus_switch; const char *name; struct mutex mutex; @@ -179,12 +196,13 @@ static int bus_switch(struct ti960 *va) { int ret; int retry, timeout = 10; - struct i2c_client *client = v4l2_get_subdevdata(&va->sd); - unsigned short addr_backup; + struct i2c_client *client = va->bus_switch; + + if (!va->bus_switch) + return 0; dev_dbg(&client->dev, "try to set bus switch\n"); - addr_backup = client->addr; - client->addr = 0x70; + for (retry = 0; retry < timeout; retry++) { ret = i2c_smbus_write_byte(client, 0x01); if (ret < 0) @@ -193,7 +211,6 @@ static int bus_switch(struct ti960 *va) break; } - client->addr = addr_backup; if (retry >= timeout) { dev_err(&client->dev, "bus switch failed, maybe no bus switch\n"); } @@ -267,30 +284,77 @@ static int ti960_reg_set_bit(struct ti960 *va, unsigned char reg, return ti960_reg_write(va, reg, reg_val); } -static int ti960_map_phy_i2c_addr(struct ti960 *va, unsigned short rx_port, - unsigned short addr) +static int ti960_map_i2c_slave(struct ti960 *va, struct ti960_subdev *sd, + unsigned short addr, unsigned int alias_addr) { int rval; + int index; + struct ti960_slave *slaves = sd->slaves; + + for (index = 0; index < NUM_ALIASES; index++) + if (slaves[index].addr == 0) + break; + + if (index >= NUM_ALIASES) + return -ENOMEM; + if (addr != 0) + slaves[index].addr = addr; + + if (alias_addr != 0) + slaves[index].alias = alias_addr; + + if (addr != 0 && alias_addr != 0) + dev_dbg(va->sd.dev, "%s rx port %d: map %02x to alias %02x\n", + __func__, sd->rx_port, addr, alias_addr); rval = ti960_reg_write(va, TI960_RX_PORT_SEL, - (rx_port << 4) + (1 << rx_port)); + (sd->rx_port << 4) + (1 << sd->rx_port)); if (rval) return rval; - return ti960_reg_write(va, TI960_SLAVE_ID0, addr); + if (addr != 0) { + rval = ti960_reg_write(va, TI960_SLAVE_ID0 + index, addr << 1); + if (rval) + return rval; + } + + if (alias_addr != 0) { + rval = ti960_reg_write(va, TI960_SLAVE_ALIAS_ID0 + index, alias_addr << 1); + if (rval) + return rval; + } + + return 0; } -static int ti960_map_alias_i2c_addr(struct ti960 *va, unsigned short rx_port, - unsigned short addr) +static int ti960_unmap_i2c_slave(struct ti960 *va, struct ti960_subdev *sd, + unsigned short addr) { int rval; + int index; + struct ti960_slave *slaves = sd->slaves; - rval = ti960_reg_write(va, TI960_RX_PORT_SEL, - (rx_port << 4) + (1 << rx_port)); + for (index = 0; index < NUM_ALIASES; index++) + if (slaves[index].addr == addr) + break; + + if (index >= NUM_ALIASES) + return 0; + + dev_dbg(va->sd.dev, "rx port %d: unmap %02x from alias %02x\n", + sd->rx_port, addr, slaves[index].alias); + slaves[index].addr = 0; + slaves[index].alias = 0; + + rval = ti960_reg_write(va, TI960_SLAVE_ID0 + index, 0); + if (rval) + return rval; + + rval = ti960_reg_write(va, TI960_SLAVE_ALIAS_ID0 + index, 0); if (rval) return rval; - return ti960_reg_write(va, TI960_SLAVE_ALIAS_ID0, addr); + return 0; } static int ti960_map_ser_alias_addr(struct ti960 *va, unsigned short rx_port, @@ -298,21 +362,24 @@ static int ti960_map_ser_alias_addr(struct ti960 *va, unsigned short rx_port, { int rval; - dev_dbg(va->sd.dev, "%s port %d, ser_alias %x\n", __func__, rx_port, ser_alias); + dev_err(va->sd.dev, "%s port %d, ser_alias %x\n", __func__, rx_port, ser_alias); rval = ti960_reg_write(va, TI960_RX_PORT_SEL, (rx_port << 4) + (1 << rx_port)); if (rval) return rval; - return ti960_reg_write(va, TI960_SER_ALIAS_ID, ser_alias); + return ti960_reg_write(va, TI960_SER_ALIAS_ID, ser_alias << 1); } -static int ti960_fsin_gpio_init(struct ti960 *va, unsigned short rx_port, - unsigned short ser_alias, unsigned short fsin_gpio) +static int ti960_fsin_gpio_init(struct ti960 *va, struct ti960_subdev *subdev) { unsigned char gpio_data; int rval; int reg_val; + unsigned short rx_port = subdev->rx_port; + unsigned short ser_alias = subdev->ser_i2c_addr; + unsigned short fsin_gpio = subdev->fsin_gpio; + struct i2c_client *serializer = subdev->serializer; dev_dbg(va->sd.dev, "%s\n", __func__); rval = regmap_read(va->regmap8, TI960_FS_CTL, ®_val); @@ -375,12 +442,12 @@ static int ti960_fsin_gpio_init(struct ti960 *va, unsigned short rx_port, } /* enable output and remote control */ - ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); - rval = ti953_reg_read(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + ti953_reg_write(serializer, TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); + rval = ti953_reg_read(serializer, TI953_LOCAL_GPIO_DATA, &gpio_data); if (rval) return rval; - ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + ti953_reg_write(serializer, TI953_LOCAL_GPIO_DATA, gpio_data | TI953_GPIO0_RMTEN << fsin_gpio); return rval; @@ -564,13 +631,8 @@ static int ti960_map_subdevs_addr(struct ti960 *va) if (!phy_i2c_addr || !alias_i2c_addr) continue; - rval = ti960_map_phy_i2c_addr(va, rx_port, phy_i2c_addr); - if (rval) - return rval; - - /* set 7bit alias i2c addr */ - rval = ti960_map_alias_i2c_addr(va, rx_port, - alias_i2c_addr << 1); + rval = ti960_map_i2c_slave(va, &va->sub_devs[i], + phy_i2c_addr, alias_i2c_addr); if (rval) return rval; } @@ -578,36 +640,81 @@ static int ti960_map_subdevs_addr(struct ti960 *va) return 0; } +static int gpio_exp_reset_sensor(struct i2c_client *client, int reset) +{ + s32 gpio_cfg; + s32 gpio_out; + + dev_dbg(&client->dev, "reset gpio %d", reset); + + gpio_cfg = i2c_smbus_read_byte_data(client, 0x03); + if (gpio_cfg < 0) + return gpio_cfg; + gpio_cfg &= ~(1u << reset); + i2c_smbus_write_byte_data(client, 0x03, gpio_cfg); + + gpio_out = i2c_smbus_read_byte_data(client, 0x01); + gpio_out &= ~(1u << reset); + i2c_smbus_write_byte_data(client, 0x01, gpio_out); + msleep(500); + gpio_out |= (1u << reset); + i2c_smbus_write_byte_data(client, 0x01, gpio_out); + + return 0; +} + /* * FIXME: workaround, reset to avoid block. */ -static int reset_sensor(struct ti960 *va, unsigned short rx_port, - unsigned short ser_alias, int reset) +static int ti953_reset_sensor(struct i2c_client *client, int reset) { int rval; unsigned char gpio_data; - rval = ti953_reg_read(&va->sd, rx_port, ser_alias, - TI953_LOCAL_GPIO_DATA, + rval = ti953_reg_read(client, TI953_LOCAL_GPIO_DATA, &gpio_data); if (rval) return rval; - ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_GPIO_INPUT_CTRL, + ti953_reg_write(client, TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); gpio_data &= ~(TI953_GPIO0_RMTEN << reset); gpio_data &= ~(TI953_GPIO0_OUT << reset); - ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + ti953_reg_write(client, TI953_LOCAL_GPIO_DATA, gpio_data); msleep(50); gpio_data |= TI953_GPIO0_OUT << reset; - ti953_reg_write(&va->sd, rx_port, ser_alias, TI953_LOCAL_GPIO_DATA, + ti953_reg_write(client, TI953_LOCAL_GPIO_DATA, gpio_data); - + msleep(500); return 0; } -static int ti960_config_ser(struct ti960 *va, struct ti960_subdev *subdev, +static int reset_sensor(struct ti960 *va, struct ti960_subdev *sd, + struct ti960_subdev_pdata *pdata) +{ + if (sd->gpio_exp) + return gpio_exp_reset_sensor(sd->gpio_exp, pdata->reset); + + if (sd->serializer) + return ti953_reset_sensor(sd->serializer, pdata->reset); + + return -ENODEV; +} + +static bool gpio_exp_detect(struct i2c_client *client) +{ + int rval; + + rval = i2c_smbus_read_byte_data(client, 0x03); + if (rval < 0) + return false; + + return (rval == 0xFF); +} + +static int ti960_config_ser(struct ti960 *va, struct i2c_client *client, int k, + struct ti960_subdev *subdev, struct ti960_subdev_pdata *pdata) { unsigned short rx_port, phy_i2c_addr, alias_i2c_addr, ser_i2c_addr; @@ -621,38 +728,72 @@ static int ti960_config_ser(struct ti960 *va, struct ti960_subdev *subdev, ser_i2c_addr = subdev->ser_i2c_addr; rval = ti960_map_ser_alias_addr(va, rx_port, - ser_i2c_addr << 1); + ser_i2c_addr); if (rval) return rval; - if (!ti953_detect(&va->sd, rx_port, ser_i2c_addr)) + subdev->serializer = i2c_new_dummy_device(client->adapter, ser_i2c_addr); + if (IS_ERR(subdev->serializer)) { + dev_err(va->sd.dev, "rx port %d: Failed to allocate serializer client: %ld", + rx_port, PTR_ERR(subdev->serializer)); + return -ENODEV; + } + + if (!ti953_detect(subdev->serializer)) { + dev_warn(va->sd.dev, "rx port %d: No link detected", + rx_port); + i2c_unregister_device(subdev->serializer); + subdev->serializer = NULL; return -ENODEV; + } + dev_info(va->sd.dev, "rx port: %d: Found serializer", rx_port); + va->sub_devs[k].serializer = subdev->serializer; - ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + rval = ti960_map_i2c_slave(va, &va->sub_devs[k], + D3_GPIO_EXP_ADDR, D3_GPIO_EXP_ALIAS(rx_port)); + if (rval) + return rval; + + subdev->gpio_exp = i2c_new_dummy_device(client->adapter, D3_GPIO_EXP_ALIAS(rx_port)); + if (IS_ERR(subdev->gpio_exp)) { + dev_err(va->sd.dev, "rx port %d: Failed to allocate gpio-expander client: %ld", + rx_port, PTR_ERR(subdev->gpio_exp)); + } else { + if (!gpio_exp_detect(subdev->gpio_exp)) { + dev_err(va->sd.dev, "rx port: %d: Missing gpio-expander", rx_port); + i2c_unregister_device(subdev->gpio_exp); + subdev->gpio_exp = NULL; + } else { + dev_info(va->sd.dev, "rx port: %d: Found gpio-expander", rx_port); + va->sub_devs[k].gpio_exp = subdev->gpio_exp; + } + } + + ti953_reg_write(subdev->serializer, TI953_RESET_CTL, TI953_DIGITAL_RESET_1); msleep(50); if (pdata->module_flags & TI960_FL_INIT_SER) { - rval = ti953_init(&va->sd, rx_port, ser_i2c_addr); + rval = ti953_init(subdev->serializer); if (rval) return rval; } if (pdata->module_flags & TI960_FL_INIT_SER_CLK) { - rval = ti953_init_clk(&va->sd, rx_port, ser_i2c_addr); + rval = ti953_init_clk(subdev->serializer); if (rval) return rval; } if (pdata->module_flags & TI960_FL_POWERUP) { - ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + ti953_reg_write(subdev->serializer, TI953_GPIO_INPUT_CTRL, TI953_GPIO_OUT_EN); /* boot sequence */ for (i = 0; i < TI960_MAX_GPIO_POWERUP_SEQ; i++) { if (pdata->gpio_powerup_seq[i] == (char)-1) break; - ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + ti953_reg_write(subdev->serializer, TI953_LOCAL_GPIO_DATA, pdata->gpio_powerup_seq[i]); msleep(50); @@ -660,7 +801,7 @@ static int ti960_config_ser(struct ti960 *va, struct ti960_subdev *subdev, } /* Configure ti953 CSI lane */ - rval = ti953_reg_read(&va->sd, rx_port, ser_i2c_addr, + rval = ti953_reg_read(subdev->serializer, TI953_GENERAL_CFG, &val); dev_dbg(va->sd.dev, "ti953 read default general CFG (%x)\n", val); if (va->pdata->ser_nlanes == 2) @@ -669,35 +810,31 @@ static int ti960_config_ser(struct ti960 *va, struct ti960_subdev *subdev, val |= TI953_CSI_4LANE; else dev_err(va->sd.dev, "not expected csi lane\n"); - rval = ti953_reg_write(&va->sd, rx_port, ser_i2c_addr, + rval = ti953_reg_write(subdev->serializer, TI953_GENERAL_CFG, val); if (rval != 0) { dev_err(va->sd.dev, "ti953 write failed(%d)\n", rval); return rval; } - ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + ti953_bus_speed(subdev->serializer, TI953_I2C_SPEED_FAST_PLUS); speed_detect_fail = - ti953_reg_read(&va->sd, rx_port, - alias_i2c_addr, 0, &val); + ti953_reg_read(subdev->serializer, 0, &val); if (speed_detect_fail) { - ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + ti953_bus_speed(subdev->serializer, TI953_I2C_SPEED_FAST); speed_detect_fail = - ti953_reg_read(&va->sd, rx_port, - alias_i2c_addr, 0, &val); + ti953_reg_read(subdev->serializer, 0, &val); } if (speed_detect_fail) { - ti953_bus_speed(&va->sd, rx_port, ser_i2c_addr, + ti953_bus_speed(subdev->serializer, TI953_I2C_SPEED_STANDARD); speed_detect_fail = - ti953_reg_read(&va->sd, rx_port, - alias_i2c_addr, 0, &val); + ti953_reg_read(subdev->serializer, 0, &val); } if (speed_detect_fail) dev_err(va->sd.dev, "i2c bus speed standard failed!"); - return 0; } @@ -711,7 +848,6 @@ static int ti960_registered(struct v4l2_subdev *subdev) for (i = 0 ; i < NR_OF_TI960_SINK_PADS; i++) port_registered[i] = false; - for (i = 0, k = 0; i < va->pdata->subdev_num; i++) { struct ti960_subdev_info *info = &va->pdata->subdev_info[i]; @@ -719,12 +855,10 @@ static int ti960_registered(struct v4l2_subdev *subdev) (struct ti960_subdev_pdata *) info->board_info.platform_data; struct ti960_subdev sensor_subdev; - sensor_subdev.rx_port = info->rx_port; sensor_subdev.phy_i2c_addr = info->phy_i2c_addr; sensor_subdev.alias_i2c_addr = info->board_info.addr; sensor_subdev.ser_i2c_addr = info->ser_alias; - if (k >= va->nsinks) break; @@ -742,7 +876,7 @@ static int ti960_registered(struct v4l2_subdev *subdev) memcpy(&va->subdev_pdata[k], pdata, sizeof(*pdata)); va->sub_devs[k].fsin_gpio = va->subdev_pdata[k].fsin; - + va->sub_devs[k].rx_port = info->rx_port; /* Spin sensor subdev suffix name */ va->subdev_pdata[k].suffix = info->suffix; @@ -759,22 +893,17 @@ static int ti960_registered(struct v4l2_subdev *subdev) dev_err(va->sd.dev, "can't find the physical and alias addr.\n"); return -EINVAL; } - - rval = ti960_config_ser(va, &sensor_subdev, pdata); + rval = ti960_config_ser(va, client, k, &sensor_subdev, pdata); if (rval) continue; - /* Map PHY I2C address. */ - rval = ti960_map_phy_i2c_addr(va, info->rx_port, - info->phy_i2c_addr); - if (rval) - return rval; + reset_sensor(va, &va->sub_devs[k], &va->subdev_pdata[k]); - /* Map 7bit ALIAS I2C address. */ - rval = ti960_map_alias_i2c_addr(va, info->rx_port, - info->board_info.addr << 1); + /* Map slave I2C address. */ + rval = ti960_map_i2c_slave(va, &va->sub_devs[k], + info->phy_i2c_addr, info->board_info.addr); if (rval) - return rval; + continue; va->sub_devs[k].sd = v4l2_i2c_new_subdev_board( va->sd.v4l2_dev, client->adapter, @@ -785,7 +914,6 @@ static int ti960_registered(struct v4l2_subdev *subdev) info->suffix); continue; } - va->sub_devs[k].rx_port = info->rx_port; va->sub_devs[k].phy_i2c_addr = info->phy_i2c_addr; va->sub_devs[k].alias_i2c_addr = info->board_info.addr; va->sub_devs[k].ser_i2c_addr = info->ser_alias; @@ -812,8 +940,8 @@ static int ti960_registered(struct v4l2_subdev *subdev) for (l = 0; l < va->nsinks; l++) { rval = media_create_pad_link( - &va->sub_devs[k].sd->entity, j, - &va->sd.entity, l, MEDIA_LNK_FL_DYNAMIC); + &va->sub_devs[k].sd->entity, j, + &va->sd.entity, l, MEDIA_LNK_FL_DYNAMIC); if (rval) { dev_err(va->sd.dev, "can't create link to %c\n", @@ -1047,8 +1175,8 @@ static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) if (broadcast && sd_idx == -1) { sd_idx = j; } else if (broadcast) { - rval = ti960_map_alias_i2c_addr(va, rx_port, - va->sub_devs[sd_idx].alias_i2c_addr << 1); + rval = ti960_map_i2c_slave(va, rx_port, 0, + va->sub_devs[sd_idx].alias_i2c_addr); if (rval < 0) return rval; } else { @@ -1074,8 +1202,7 @@ static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) return rval; } if (va->subdev_pdata[j].module_flags & TI960_FL_RESET) { - rval = reset_sensor(va, rx_port, ser_alias, - va->subdev_pdata[j].reset); + rval = reset_sensor(va, &va->sub_devs[j], &va->subdev_pdata[j]); if (rval) return rval; } @@ -1105,10 +1232,7 @@ static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) for (i = 0; i < NR_OF_TI960_SINK_PADS; i++) { if (enable && test_bit(i, rx_port_enabled)) { - rval = ti960_fsin_gpio_init(va, - va->sub_devs[i].rx_port, - va->sub_devs[i].ser_i2c_addr, - va->sub_devs[i].fsin_gpio); + rval = ti960_fsin_gpio_init(va, &va->sub_devs[i]); if (rval) { dev_err(va->sd.dev, "Failed to enable frame sync gpio init.\n"); @@ -1118,8 +1242,8 @@ static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) if (va->subdev_pdata[i].module_flags & TI960_FL_RESET) { rx_port = va->sub_devs[i].rx_port; ser_alias = va->sub_devs[i].ser_i2c_addr; - rval = reset_sensor(va, rx_port, ser_alias, - va->subdev_pdata[i].reset); + rval = reset_sensor(va, &va->sub_devs[i], + &va->subdev_pdata[i]); if (rval) return rval; } @@ -1140,7 +1264,6 @@ static int ti960_set_stream(struct v4l2_subdev *subdev, int enable) return rval; } } - /* * Restore each subdev i2c address as we may * touch it later. @@ -1192,8 +1315,7 @@ static int ti960_set_stream_vc(struct ti960 *va, u8 vc_id, u8 state) return rval; } if (va->subdev_pdata[i].module_flags & TI960_FL_RESET) { - rval = reset_sensor(va, rx_port, ser_alias, - va->subdev_pdata[i].reset); + rval = reset_sensor(va, &va->sub_devs[i], &va->subdev_pdata[i]); if (rval) return rval; } @@ -1603,6 +1725,12 @@ static int ti960_probe(struct i2c_client *client, gpio_set_value(va->pdata->FPD_gpio, 1); } + va->bus_switch = i2c_new_dummy_device(client->adapter, D3_BUS_SWITCH_ADDR); + if (IS_ERR(va->bus_switch)) { + dev_warn(&client->dev, "Failed to create client for bus-switch: %ld", + PTR_ERR(va->bus_switch)); + va->bus_switch = 0; + } rval = ti960_init(va); if (rval) { dev_err(&client->dev, "Failed to init TI960!\n"); @@ -1667,6 +1795,20 @@ static void ti960_remove(struct i2c_client *client) i2c_unregister_device(sub_client); } va->sub_devs[i].sd = NULL; + if (va->sub_devs[i].serializer) { + i2c_unregister_device(va->sub_devs[i].serializer); + va->sub_devs[i].serializer = NULL; + } + + if (va->sub_devs[i].gpio_exp) { + i2c_unregister_device(va->sub_devs[i].gpio_exp); + va->sub_devs[i].gpio_exp = NULL; + } + } + + if (va->bus_switch) { + i2c_unregister_device(va->bus_switch); + va->bus_switch = NULL; } gpiochip_remove(&va->gc); @@ -1701,7 +1843,7 @@ static int ti960_resume(struct device *dev) if (sensor_subdev->sd == NULL) break; - rval = ti960_config_ser(va, sensor_subdev, pdata); + rval = ti960_config_ser(va, client, i, sensor_subdev, pdata); if (rval) dev_warn(va->sd.dev, "resume: failed config subdev"); } diff --git a/drivers/media/platform/intel/ipu6-acpi-pdata.c b/drivers/media/platform/intel/ipu6-acpi-pdata.c index a891da26c1ab..92d53c5d7504 100644 --- a/drivers/media/platform/intel/ipu6-acpi-pdata.c +++ b/drivers/media/platform/intel/ipu6-acpi-pdata.c @@ -580,14 +580,14 @@ static void set_i2c(struct ipu_isys_subdev_info **sensor_sd, } static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char sensor_name[I2C_NAME_SIZE], - unsigned int lanes) + const char *hid_name, unsigned int lanes) { /* general */ (*module_pdata)->lanes = lanes; strscpy((*module_pdata)->module_name, sensor_name, I2C_NAME_SIZE); /* TI960 and IMX390 specific */ - if (!strcmp(sensor_name, IMX390_NAME)) { + if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10C1")) { (*module_pdata)->gpio_powerup_seq[0] = 0; (*module_pdata)->gpio_powerup_seq[1] = 0x9; (*module_pdata)->gpio_powerup_seq[2] = -1; @@ -596,6 +596,16 @@ static void set_serdes_sd_pdata(struct serdes_module_pdata **module_pdata, char (*module_pdata)->fsin = 3; } + /* TI960 and IMX390 specific */ + if (!strcmp(sensor_name, IMX390_NAME) && !strcmp(hid_name, "INTC10CM")) { + (*module_pdata)->gpio_powerup_seq[0] = 0; + (*module_pdata)->gpio_powerup_seq[1] = 0xa; + (*module_pdata)->gpio_powerup_seq[2] = -1; + (*module_pdata)->gpio_powerup_seq[3] = -1; + (*module_pdata)->module_flags = TI960_FL_POWERUP | TI960_FL_INIT_SER_CLK; + (*module_pdata)->fsin = 0; + } + /* TI960 and ISX031 specific */ if (!strcmp(sensor_name, ISX031_NAME)) { (*module_pdata)->gpio_powerup_seq[0] = 0x0; @@ -613,6 +623,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, struct device *dev, struct serdes_platform_data **pdata, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, unsigned int lanes, unsigned int addr, unsigned int subdev_port) @@ -633,7 +644,7 @@ static int set_serdes_subdev(struct ipu_isys_subdev_info **serdes_sd, return -ENOMEM; } - set_serdes_sd_pdata(&module_pdata[i], sensor_name, lanes); + set_serdes_sd_pdata(&module_pdata[i], sensor_name, hid_name, lanes); /* board info */ strscpy(serdes_sdinfo[i].board_info.type, sensor_name, I2C_NAME_SIZE); @@ -673,6 +684,7 @@ static u8 suffix_offset = 1; static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, struct device *dev, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, struct control_logic_data *ctl_data, unsigned int port, unsigned int lanes, @@ -740,7 +752,7 @@ static int set_pdata(struct ipu_isys_subdev_info **sensor_sd, } pdata->deser_nlanes = deser_lanes; pdata->ser_nlanes = lanes; - set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, lanes, addr, rx_port); + set_serdes_subdev(sensor_sd, dev, &pdata, sensor_name, hid_name, lanes, addr, rx_port); (*sensor_sd)->i2c.board_info.platform_data = pdata; pdata->deser_board_info = &(*sensor_sd)->i2c.board_info; @@ -780,13 +792,14 @@ static void set_serdes_info(struct device *dev, char *sensor_name, const char *s if (!strcmp(sensor_name, IMX390_NAME)) serdes_info.phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS; else if (!strcmp(sensor_name, ISX031_NAME)) - serdes_info.phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT; + serdes_info.phy_i2c_addr = ISX031_I2C_ADDRESS; else serdes_info.phy_i2c_addr = 0; } static int populate_dummy(struct device *dev, char sensor_name[I2C_NAME_SIZE], + const char *hid_name, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect) @@ -809,7 +822,7 @@ static int populate_dummy(struct device *dev, set_i2c(&dummy, dev, sensor_name, addr_dummy); - ret = set_pdata(&dummy, dev, sensor_name, ctl_data, cam_data->pprval, + ret = set_pdata(&dummy, dev, sensor_name, hid_name, ctl_data, cam_data->pprval, cam_data->lanes, addr_dummy, 0, 0, true, connect); if (ret) { kfree(dummy); @@ -827,7 +840,8 @@ static int populate_sensor_pdata(struct device *dev, struct sensor_bios_data *cam_data, struct control_logic_data *ctl_data, enum connection_type connect, - const char *serdes_name) + const char *serdes_name, + const char *hid_name) { int ret; @@ -892,7 +906,7 @@ static int populate_sensor_pdata(struct device *dev, } /* Use last I2C device */ - ret = set_pdata(sensor_sd, dev, sensor_name, ctl_data, cam_data->link, + ret = set_pdata(sensor_sd, dev, sensor_name, hid_name, ctl_data, cam_data->link, cam_data->lanes, cam_data->i2c[cam_data->i2c_num - 1].addr, cam_data->pprunit, cam_data->pprval, false, connect); @@ -904,7 +918,7 @@ static int populate_sensor_pdata(struct device *dev, /* Lontium specific */ if (!strcmp(sensor_name, LT6911UXC_NAME) || !strcmp(sensor_name, LT6911UXE_NAME)) { if (cam_data->pprval != cam_data->link) { - ret = populate_dummy(dev, sensor_name, cam_data, ctl_data, connect); + ret = populate_dummy(dev, sensor_name, hid_name, cam_data, ctl_data, connect); if (ret) return ret; } @@ -917,7 +931,7 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_camera_module_data *data, struct ipu_i2c_helper *helper, void *priv, size_t size, - enum connection_type connect, const char *serdes_name) + enum connection_type connect, const char *serdes_name, const char *hid_name) { struct sensor_bios_data *cam_data; struct control_logic_data *ctl_data; @@ -965,7 +979,7 @@ int get_sensor_pdata(struct i2c_client *client, /* populate pdata */ rval = populate_sensor_pdata(&client->dev, &sensor_sd, - client->name, cam_data, ctl_data, connect, serdes_name); + client->name, cam_data, ctl_data, connect, serdes_name, hid_name); if (rval) { kfree(sensor_sd); kfree(cam_data); diff --git a/drivers/media/platform/intel/ipu6-acpi.c b/drivers/media/platform/intel/ipu6-acpi.c index 51eb5b00f816..e6dd7c3d708a 100644 --- a/drivers/media/platform/intel/ipu6-acpi.c +++ b/drivers/media/platform/intel/ipu6-acpi.c @@ -66,6 +66,7 @@ static const struct ipu_acpi_devices supported_devices[] = { { "INTC10C0", AR0234_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // AR0234 HID { "INTC10B1", LT6911UXC_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXC HID { "INTC10C1", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// IMX390 HID + { "INTC10CM", IMX390_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// new D3 IMX390 HID { "INTC1031", ISX031_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, TI960_NAME },// ISX031 HID { "INTC10C5", LT6911UXE_NAME, get_sensor_pdata, NULL, 0, TYPE_DIRECT, NULL }, // LT6911UXE HID { "INTC10CD", D457_NAME, get_sensor_pdata, NULL, 0, TYPE_SERDES, D457_NAME },// D457 HID @@ -92,6 +93,7 @@ static const struct acpi_device_id ipu_acpi_match[] = { { "INTC10C0", 0 }, // AR0234 HID { "INTC10B1", 0 }, // LT6911UXC HID { "INTC10C1", 0 }, // IMX390 HID + { "INTC10CM", 0 }, // D3CMC68N-106-085 IMX390 HID { "INTC1031", 0 }, // ISX031 HID { "INTC10C5", 0 }, // LT6911UXE HID { "INTC10CD", 0 }, // D457 HID @@ -123,7 +125,8 @@ static int ipu_acpi_get_pdata(struct i2c_client *client, supported_devices[index].priv_data, supported_devices[index].priv_size, supported_devices[index].connect, - supported_devices[index].serdes_name); + supported_devices[index].serdes_name, + supported_devices[index].hid_name); return 0; } diff --git a/drivers/media/platform/intel/ipu6-adlrvp-pdata.c b/drivers/media/platform/intel/ipu6-adlrvp-pdata.c index d6f417f791b5..cc5857ec99bb 100644 --- a/drivers/media/platform/intel/ipu6-adlrvp-pdata.c +++ b/drivers/media/platform/intel/ipu6-adlrvp-pdata.c @@ -123,12 +123,8 @@ static struct ipu_isys_subdev_info ar0234_sd_2 = { #if IS_ENABLED(CONFIG_VIDEO_IMX390) #define IMX390_LANES 4 #define IMX390_D3RCM_I2C_ADDRESS 0x1a -#define IMX390_D3RCM_I2C_ADDRESS_8BIT (IMX390_D3RCM_I2C_ADDRESS << 1) -#define IMX390_D3CM_I2C_ADDRESS_8BIT (IMX390_D3CM_I2C_ADDRESS << 1) #define IMX390_I2C_ADDRESS_3 0x1e -#define IMX390_I2C_ADDRESS_8BIT_3 (IMX390_I2C_ADDRESS_3 << 1) #define IMX390_I2C_ADDRESS_4 0x20 -#define IMX390_I2C_ADDRESS_8BIT_4 (IMX390_I2C_ADDRESS_4 << 1) #endif @@ -203,7 +199,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031A_SER_ADDRESS, .suffix = 'a', }, @@ -214,7 +210,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031B_SER_ADDRESS, .suffix = 'b', }, @@ -225,7 +221,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031C_SER_ADDRESS, .suffix = 'c', }, @@ -236,7 +232,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031D_SER_ADDRESS, .suffix = 'd', }, @@ -250,7 +246,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390A_SER_ADDRESS, .suffix = 'a', }, @@ -261,7 +257,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390B_SER_ADDRESS, .suffix = 'b', }, @@ -272,7 +268,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390C_SER_ADDRESS, .suffix = 'c', }, @@ -283,7 +279,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390D_SER_ADDRESS, .suffix = 'd', }, @@ -295,7 +291,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390A_SER_ADDRESS, .suffix = 'a', }, @@ -306,7 +302,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390B_SER_ADDRESS, .suffix = 'b', }, @@ -317,7 +313,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390C_SER_ADDRESS, .suffix = 'c', }, @@ -328,7 +324,7 @@ static struct ti960_subdev_info ti960_subdevs_1[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390D_SER_ADDRESS, .suffix = 'd', }, @@ -344,7 +340,7 @@ static struct ti960_subdev_info ti960_subdevs_2[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031A_SER_ADDRESS, .suffix = 'e', }, @@ -355,7 +351,7 @@ static struct ti960_subdev_info ti960_subdevs_2[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031B_SER_ADDRESS, .suffix = 'f', }, @@ -366,7 +362,7 @@ static struct ti960_subdev_info ti960_subdevs_2[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031C_SER_ADDRESS, .suffix = 'g', }, @@ -377,7 +373,7 @@ static struct ti960_subdev_info ti960_subdevs_2[] = { .platform_data = &isx031_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = ISX031_I2C_ADDRESS_8BIT, + .phy_i2c_addr = ISX031_I2C_ADDRESS, .ser_alias = ISX031D_SER_ADDRESS, .suffix = 'h', }, diff --git a/drivers/media/platform/intel/ipu6-tglrvp-pdata.c b/drivers/media/platform/intel/ipu6-tglrvp-pdata.c index a0f316e3050e..d4a9ede8c5da 100644 --- a/drivers/media/platform/intel/ipu6-tglrvp-pdata.c +++ b/drivers/media/platform/intel/ipu6-tglrvp-pdata.c @@ -223,12 +223,8 @@ static struct ipu_isys_subdev_info ar0234_sd_4 = { #if IS_ENABLED(CONFIG_VIDEO_IMX390) #define IMX390_LANES 4 #define IMX390_D3RCM_I2C_ADDRESS 0x1a -#define IMX390_D3RCM_I2C_ADDRESS_8BIT (IMX390_D3RCM_I2C_ADDRESS << 1) -#define IMX390_D3CM_I2C_ADDRESS_8BIT (IMX390_D3CM_I2C_ADDRESS << 1) #define IMX390_I2C_ADDRESS_3 0x1e -#define IMX390_I2C_ADDRESS_8BIT_3 (IMX390_I2C_ADDRESS_3 << 1) #define IMX390_I2C_ADDRESS_4 0x20 -#define IMX390_I2C_ADDRESS_8BIT_4 (IMX390_I2C_ADDRESS_4 << 1) static struct ipu_isys_csi2_config imx390_csi2_cfg_1 = { .nlanes = IMX390_LANES, @@ -345,7 +341,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390A_SER_ADDRESS, .suffix = 'a', }, @@ -356,7 +352,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390B_SER_ADDRESS, .suffix = 'b', }, @@ -367,7 +363,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390C_SER_ADDRESS, .suffix = 'c', }, @@ -378,7 +374,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3rcm_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3RCM_I2C_ADDRESS, .ser_alias = IMX390D_SER_ADDRESS, .suffix = 'd', }, @@ -390,7 +386,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 0, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390A_SER_ADDRESS, .suffix = 'a', }, @@ -401,7 +397,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 1, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390B_SER_ADDRESS, .suffix = 'b', }, @@ -412,7 +408,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 2, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390C_SER_ADDRESS, .suffix = 'c', }, @@ -423,7 +419,7 @@ static struct ti960_subdev_info ti960_subdevs[] = { .platform_data = &imx390_d3cm_pdata_stub, }, .rx_port = 3, - .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS_8BIT, + .phy_i2c_addr = IMX390_D3CM_I2C_ADDRESS, .ser_alias = IMX390D_SER_ADDRESS, .suffix = 'd', }, diff --git a/include/media/imx390.h b/include/media/imx390.h index be95249fbe96..41e7d6fccfeb 100644 --- a/include/media/imx390.h +++ b/include/media/imx390.h @@ -8,7 +8,7 @@ #define IMX390_NAME "imx390" -#define IMX390_D3CM_I2C_ADDRESS (0x21 << 1) +#define IMX390_D3CM_I2C_ADDRESS 0x21 struct imx390_platform_data { unsigned int port; diff --git a/include/media/ipu-acpi-pdata.h b/include/media/ipu-acpi-pdata.h index 9b241cf19ae0..e9849078e988 100644 --- a/include/media/ipu-acpi-pdata.h +++ b/include/media/ipu-acpi-pdata.h @@ -22,7 +22,8 @@ int get_sensor_pdata(struct i2c_client *client, struct ipu_i2c_helper *helper, void *priv, size_t size, enum connection_type connect, - const char *serdes_name); + const char *serdes_name, + const char *hid_name); struct ipu_isys_subdev_pdata *get_acpi_subdev_pdata(void); diff --git a/include/media/ipu-acpi.h b/include/media/ipu-acpi.h index 67953db42715..8b6a92c47812 100644 --- a/include/media/ipu-acpi.h +++ b/include/media/ipu-acpi.h @@ -195,7 +195,8 @@ struct ipu_acpi_devices { void *priv, size_t size, enum connection_type type, - const char *serdes_name); + const char *serdes_name, + const char *hid_name); void *priv_data; size_t priv_size; enum connection_type connect; diff --git a/include/media/isx031.h b/include/media/isx031.h index 36c9f3d330ce..e703e001db6b 100644 --- a/include/media/isx031.h +++ b/include/media/isx031.h @@ -9,7 +9,6 @@ #define ISX031_NAME "isx031" #define ISX031_I2C_ADDRESS 0x1a -#define ISX031_I2C_ADDRESS_8BIT (ISX031_I2C_ADDRESS << 1) struct isx031_platform_data { unsigned int port;