From e31bd1eb17f927d067e40916abb17c4ce5f95ece Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 21 Aug 2026 08:38:12 +0200 Subject: media: uapi: Add controls for ST DCMIPP Driver Add definitions and documentation for the custom control provided by the DCMIPP driver in order to control the pixelproc gamma correction. Signed-off-by: Alain Volmat Signed-off-by: Hans Verkuil --- include/uapi/linux/media/st/dcmipp_config.h | 16 ++++++++++++++++ include/uapi/linux/v4l2-controls.h | 6 ++++++ 2 files changed, 22 insertions(+) create mode 100644 include/uapi/linux/media/st/dcmipp_config.h (limited to 'include/uapi') diff --git a/include/uapi/linux/media/st/dcmipp_config.h b/include/uapi/linux/media/st/dcmipp_config.h new file mode 100644 index 000000000000..ad8d146820ea --- /dev/null +++ b/include/uapi/linux/media/st/dcmipp_config.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ST DCMIPP Driver - Userspace API + * + * Copyright (C) STMicroelectronics SA 2026 + */ + +#ifndef __UAPI_DCMIPP_CONFIG_H +#define __UAPI_DCMIPP_CONFIG_H + +#include +#include + +#define V4L2_CID_DCMIPP_PIXELPROC_GAMMA_CORRECTION_ENABLE (V4L2_CID_USER_DCMIPP_BASE + 0x0) + +#endif /* __UAPI_DCMIPP_CONFIG_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index affec0ab4781..42b4f82afeb8 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -234,6 +234,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_MALI_C55_BASE (V4L2_CID_USER_BASE + 0x1230) +/* + * The base for the ST DCMIPP driver controls. + * We reserve 16 controls for this driver + */ +#define V4L2_CID_USER_DCMIPP_BASE (V4L2_CID_USER_BASE + 0x1240) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ -- cgit v1.2.3 From 142e5f00bd57ffdc03fcec2699f95f0cc6b7e9c6 Mon Sep 17 00:00:00 2001 From: Hermes Wu Date: Tue, 11 Aug 2026 18:31:26 +0800 Subject: media: i2c: add driver for ITE IT6625/IT6626 Add a V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI-to-MIPI CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and IT6626 an HDMI 2.1 input, converting it to a D-PHY (or C/D-PHY on IT6626) MIPI CSI-2 output. The bridge is configured over I2C, exposes an HDMI CEC adapter, and supports EDID read/write, DV timings detection/configuration, and HPD control via the standard V4L2 subdevice pad and video ops. Signed-off-by: Hermes Wu Signed-off-by: Hans Verkuil [hverkuil: change it6625_v4l2_sd_ctrl_update to a void function] --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 18 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/it6625.c | 2390 ++++++++++++++++++++++++++++++++++++ include/uapi/linux/it6625.h | 25 + include/uapi/linux/v4l2-controls.h | 6 + 6 files changed, 2448 insertions(+) create mode 100644 drivers/media/i2c/it6625.c create mode 100644 include/uapi/linux/it6625.h (limited to 'include/uapi') diff --git a/MAINTAINERS b/MAINTAINERS index f0bb3f1b5ebb..f515e1473af9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13965,6 +13965,14 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: Documentation/devicetree/bindings/display/bridge/ite,it66121.yaml F: drivers/gpu/drm/bridge/ite-it66121.c +ITE IT6625 HDMI to MIPI MEDIA DRIVER +M: Hermes Wu +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ite,it6625.yaml +F: drivers/media/i2c/it6625.c +F: include/uapi/linux/it6625.h + IVTV VIDEO4LINUX DRIVER M: Andy Walls L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index df6a73731803..3370b66ab4ee 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1322,6 +1322,24 @@ config VIDEO_ISL7998X Support for Intersil ISL7998x analog to MIPI-CSI2 or BT.656 decoder. +config VIDEO_IT6625 + tristate "IT6625 HDMI to MIPI CSI bridge" + depends on VIDEO_DEV && I2C + depends on OF + select CEC_CORE + select MEDIA_CONTROLLER + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + V4L2 subdevice driver for the ITE IT6625/IT6626 HDMI to MIPI + CSI-2 bridge chips. IT6625 accepts an HDMI 2.0 input and + IT6626 an HDMI 2.1 input, converting it to a MIPI CSI-2 + output. The driver also exposes an HDMI CEC adapter. + + To compile this driver as a module, choose M here: the + module will be called it6625. + config VIDEO_LT6911UXE tristate "Lontium LT6911UXE decoder" depends on ACPI && VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 9c7f990a3e66..fd1cb25718c0 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_IMX678) += imx678.o obj-$(CONFIG_VIDEO_IMX471) += imx471.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o +obj-$(CONFIG_VIDEO_IT6625) += it6625.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o obj-$(CONFIG_VIDEO_LM3560) += lm3560.o obj-$(CONFIG_VIDEO_LM3646) += lm3646.o diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c new file mode 100644 index 000000000000..da17e5d5ce9e --- /dev/null +++ b/drivers/media/i2c/it6625.c @@ -0,0 +1,2390 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * it6625 - ite HDMI to MIPI bridge + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int debug = 3; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-3)"); + +#define REG_CHIP_ID_0 0x00 +#define REG_CHIP_ID_1 0x01 +#define REG_FW_VER_MAJOR 0x03 +#define REG_FW_VER_MINOR 0x04 +#define REG_PROTOCOL_VERSION 0x05 +#define B_PVER_MINOR BIT(0) +#define B_PVER_MAJOR BIT(4) + +#define REG_CMD_SET 0x10 +#define CMD_SET_CEC_LA 0xC0 +#define CMD_SET_CEC_ENABLE 0xC1 + +#define REG_EDID_START 0x20 +#define REG_CEC_RX_DATA 0x20 +#define REG_CEC_TX_DATA 0x30 + +#define REG_H_ACTIVE_1 0x50 +#define REG_H_ACTIVE_0 0x51 +#define REG_V_ACTIVE_1 0x52 +#define REG_V_ACTIVE_0 0x53 +#define REG_H_TOTAL_1 0x54 +#define REG_H_TOTAL_0 0x55 +#define REG_V_TOTAL_1 0x56 +#define REG_V_TOTAL_0 0x57 +#define REG_VID_PCLK 0x58 +#define REG_H_FP_1 0x5C +#define REG_H_FP_0 0x5D +#define REG_H_SW_1 0x5E +#define REG_H_SW_0 0x5F +#define REG_H_BP_1 0x60 +#define REG_H_BP_0 0x61 +#define REG_V_FP_1 0x62 +#define REG_V_FP_0 0x63 +#define REG_V_SW_1 0x64 +#define REG_V_SW_0 0x65 +#define REG_V_BP_1 0x66 +#define REG_V_BP_0 0x67 +#define REG_VID_INFO 0x68 +#define B_INTERLACE BIT(0) +#define B_HSWPOL BIT(1) +#define B_VSWPOL BIT(2) +#define B_PIXEL_REP BIT(4) + +#define REG_VIC 0x69 +#define REG_HDMI_VIDEO_INFO 0x6A +#define B_COLOR_MODE BIT(0) +#define B_COLOR_DEPTH BIT(4) +#define REG_HDMI_AUDIO_INFO1 0x6B +#define B_AUD_FS BIT(0) + +#define REG_HDMI_AUDIO_INFO2 0x6C +#define B_AUD_CH BIT(0) +#define B_AUD_WL BIT(4) + +#define REG_HDMI_AUDIO_INFO3 0x6D +#define B_AUD_TYPE BIT(0) +#define B_AUD_MS BIT(2) +#define B_AUD_3D BIT(3) + +#define REG_AUDIO_FMT 0x6E +#define B_I2S_WL BIT(0) +#define B_I2S_ALN BIT(2) +#define B_I2S_DLY BIT(3) +#define B_I2S_LR BIT(4) +#define B_I2S_SFT BIT(5) +#define B_AUD_OUT_IF BIT(6) + +#define REG_IF_LATCH_HB 0x6F +#define REG_IF_DATA 0x70 +#define REG_EMP_DATA 0xA0 +#define REG_AVI_DATA 0xAE + +#define REG_TX_STATUS 0xE0 + +#define REG_RX_STATUS 0xE2 +#define B_RX_5V BIT(0) +#define B_RX_HPD BIT(1) +#define B_RX_STABLE BIT(2) +#define B_RX_HDMI BIT(3) +#define B_RX_AVMUTE BIT(4) +#define B_RX_AUD_ON BIT(5) + +#define REG_RX_HDCP_STS 0xE3 +#define B_HDCP1_AUTH_START BIT(0) +#define B_HDCP2_AUTH_START BIT(1) +#define B_HDCP_AUTH_DONE BIT(2) +#define B_HDCP_ENC BIT(3) + +#define REG_CEC_STATUS 0xE4 +#define B_CEC_TX_DONE BIT(0) +#define B_CEC_TX_NACK BIT(1) + +#define REG_CEC_RX_DATA_LEN 0xE5 +#define REG_CEC_TX_DATA_LEN 0xE6 + +#define REG_SYS_MIPI_INT 0xEB +#define B_MIPI_OUTPUT_ENABLE BIT(0) +#define B_MIPI_VIDEO_UNSTABLE BIT(1) + +#define REG_RX_INT_STATUS1 0xEC +#define B_HDMI_5V_CHG BIT(0) +#define B_HDMI_VID_CHG BIT(1) +#define B_HDMI_AUD_CHG BIT(2) +#define B_HDMI_CP_CHG BIT(3) +#define B_HDMI_IF_LATCH BIT(4) +#define B_HDMI_NO_IF_LATCH BIT(5) +#define B_HDMI_EMP BIT(6) +#define B_HDMI_NO_EMP BIT(7) + +#define REG_RX_INT_STATUS2 0xED +#define B_HDMI_AVI BIT(0) +#define B_HDMI_NO_AVI BIT(1) +#define B_HDMI_VSIF BIT(2) +#define B_HDMI_NO_VSIF BIT(3) +#define B_HDMI_AVMUTE_CHG BIT(4) + +#define REG_CHIP_CONTROL 0xF0 +#define B_HDMI_RESET BIT(5) +#define B_FW_START BIT(6) + +#define REG_MIPI_CFG 0xF1 +#define M_MIPI_LANE 0x03 +#define B_MIPI_SPLIT BIT(2) +#define B_MIPI_SPLIT_CFG BIT(3) +#define B_MIPI_DPHY BIT(4) +#define B_MIPI_USE_DSI BIT(5) +#define B_MIPI_CONTINU_CLK BIT(6) + +#define REG_MIPI_DATA_TYPE 0xF2 +#define CSI_RGB444 0x20 +#define CSI_RGB555 0x21 +#define CSI_RGB565 0x22 +#define CSI_RGB666 0x23 +#define CSI_RGB888 0x24 +#define CSI_YUV420_8b_L 0x1A +#define CSI_YUV420_8b 0x1C +#define CSI_YUV420_10b 0x1D +#define CSI_YUV422_8b 0x1E +#define CSI_YUV422_10b 0x1F +#define CSI_RGB_10b 0x30 +#define CSI_RGB_12b 0x31 +#define CSI_YUV422_12b 0x32 +#define CSI_YUV420_10b_L 0x33 +#define CSI_YUV420_12b 0x34 +#define CSI_YUV444_8b 0x35 +#define CSI_YUV444_10b 0x36 +#define CSI_YUV444_12b 0x37 + +#define REG_MIPI_CONTROL 0xF3 +#define B_MIPI_OUTPUT BIT(0) + +#define REG_RX_CFG 0xF4 +#define B_MANUAL_HPD BIT(0) +#define B_HPD_HIGH BIT(1) +#define B_HPD_TOGGLE BIT(3) + +#define REG_CSC_CFG 0xF5 +#define B_DYNAMIC_RANGE BIT(0) + +#define REG_MISC_CFG 0xF6 +#define B_BAUD_RATE BIT(0) +#define B_DEBUG_MSG BIT(1) + +#define REG_HPD_DELAY 0xF7 +#define B_DELAY_COUNT BIT(0) +#define B_DELAY_UNIT BIT(7) + +#define REG_INFO_BANK_SEL 0xFD +#define CTL_BANK_EDID_READ 1 +#define CTL_BANK_EDID_WRITE 5 + +#define REG_HOST_CTRL_INT 0xFE +#define B_CMD_SET BIT(4) +#define B_CEC_SEND_DATA BIT(5) +#define B_CONFIG_UPDATE BIT(6) +#define B_IF_BANK BIT(7) + +#define REG_MCU_INTERRUPT 0xFF +#define B_SYS_INT_ACTIVE BIT(0) +#define B_CEC_RX_RECEIVED BIT(1) +#define B_CEC_TX_UPDATE BIT(2) + +#define EDID_NUM_BLOCKS_MAX 4 +#define EDID_BLOCK_SIZE 128 + +#define I2C_MAX_XFER_SIZE 8 +#define POLL_INTERVAL_CEC_MS 10 +#define POLL_INTERVAL_MS 40 + +#define AUD32K 0x03 +#define AUD44K 0x00 +#define AUD48K 0x02 +#define AUD64K 0x0B +#define AUD88K 0x08 +#define AUD96K 0x0A +#define AUD128K 0x2B +#define AUD176K 0x0C +#define AUD192K 0x0E +#define AUD256K 0x1B +#define AUD352K 0x0D +#define AUD384K 0x05 +#define AUD512K 0x3B +#define AUD705K 0x2D +#define AUD768K 0x09 +#define AUD1024K 0x35 +#define AUD1411K 0x1D +#define AUD1536K 0x15 + +enum it6625_chip_type { + IT6625_CHIP = 0, + IT6626_CHIP = 1, +}; + +struct it6625 { + struct device *dev; + struct i2c_client *i2c_client; + struct regmap *it6625_regmap; + enum it6625_chip_type chip_type; + + /* protects concurrent access to the chip's registers and state */ + struct mutex it6625_lock; + /* serializes the complete VIDIOC_S_EDID sequence against itself */ + struct mutex edid_lock; + /* serializes a full InfoFrame debugfs read transaction against itself */ + struct mutex if_read_lock; + /* + * protects if_active/if_type/if_snapshot/if_snapshot_err/ + * if_snapshot_done and the REG_MCU_INTERRUPT/REG_RX_INT_STATUS1/2/ + * REG_IF_LATCH_HB/REG_IF_DATA register group between the interrupt + * path and the InfoFrame debugfs callback. Never held across + * wait_for_completion_timeout(). Nests outside it6625_lock. + */ + struct mutex if_state_lock; + + struct v4l2_subdev sd; + struct v4l2_mbus_config_mipi_csi2 bus; + struct video_device *vdev; + struct media_pad pad; + struct v4l2_ctrl_handler hdl; + + /* controls */ + struct v4l2_ctrl *ctrl_5v_detect; + struct v4l2_ctrl *ctrl_audio_sampling_rate; + struct v4l2_ctrl *ctrl_audio_present; + struct v4l2_ctrl *ctrl_link_freq; + + struct delayed_work hpd_delayed_work; + + struct timer_list timer; + struct work_struct polling_work; + + struct v4l2_dv_timings timings; + + u8 csi_lanes; + u8 port_num; + enum v4l2_mbus_type bus_type; + u8 csi_format; + u32 mbus_fmt_code; + /* number of EDID blocks currently loaded, protected by edid_lock */ + u8 edid_blocks; + + struct gpio_desc *reset_gpio; + + struct cec_adapter *cec_adap; + + struct dentry *debugfs_dir; + + struct v4l2_debugfs_if *infoframes; + struct completion if_latched; + /* true while an InfoFrame debugfs request is outstanding */ + bool if_active; + /* HDMI packet-type byte currently armed in REG_IF_LATCH_HB */ + u32 if_type; + /* driver-private copy of REG_IF_DATA, captured at the genuine latch */ + u8 if_snapshot[31]; + /* it6625_read_bytes() result for the if_snapshot capture */ + int if_snapshot_err; + /* set just before complete(), to disambiguate timeout vs. capture */ + bool if_snapshot_done; +}; + +/* + * Index 0: D-PHY (4-lane). Index 1: C-PHY (3-trio) -- the confirmed + * hardware max C-PHY capability, tested single-port/three-trio. + */ +static const s64 it6625_link_freq[] = { + 445500000, + 2500000000LL, +}; + +/* + * Shared cap for every topology except the reference exception below: + * IT6625 (no C-PHY support at all), IT6626 running D-PHY, and IT6626 + * running C-PHY with fewer than three trios. This is conservative + * scoping, not a claim that one-/two-trio C-PHY can't also support a + * higher rate -- they're simply unvalidated. + */ +static const struct v4l2_dv_timings_cap it6625_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 300000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +/* + * IT6626 C-PHY, three trios: raised pixel-clock ceiling (594 MHz vs + * the shared 300 MHz cap) for this topology's higher C-PHY capability. + */ +static const struct v4l2_dv_timings_cap it6626_cphy_3trio_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .reserved = { 0 }, + + V4L2_INIT_BT_TIMINGS(640, 3840, 480, 2160, 27000000, 594000000, + V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT, + V4L2_DV_BT_CAP_PROGRESSIVE | V4L2_DV_BT_CAP_INTERLACED | + V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM) +}; + +static const struct v4l2_dv_timings_cap * +it6625_get_timings_cap(struct it6625 *it6625) +{ + if (it6625->chip_type == IT6626_CHIP && + it6625->bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->csi_lanes == 3) + return &it6626_cphy_3trio_timings_cap; + + return &it6625_timings_cap; +} + +static const struct it6625_format_info { + u8 csi_format; + u32 mbus_fmt_code; +} it6625_formats[] = { + { CSI_YUV422_8b, MEDIA_BUS_FMT_UYVY8_1X16 }, + { CSI_RGB888, MEDIA_BUS_FMT_RGB888_1X24 }, + { CSI_YUV444_8b, MEDIA_BUS_FMT_YUV8_1X24 }, +}; + +static inline int it6625_csi_format_idx(u8 csi_format) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].csi_format == csi_format) + return i; + } + + return -EINVAL; +} + +static inline int it6625_csi_mbus_code_idx(u32 mbus_fmt_code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(it6625_formats); i++) { + if (it6625_formats[i].mbus_fmt_code == mbus_fmt_code) + return i; + } + + return -EINVAL; +} + +static inline struct it6625 *sd_to_6625(struct v4l2_subdev *sd) +{ + return container_of(sd, struct it6625, sd); +} + +static const struct regmap_config it6625_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, + .max_raw_read = I2C_MAX_XFER_SIZE, + .max_raw_write = I2C_MAX_XFER_SIZE, +}; + +static int it6625_regmap_i2c_init(struct i2c_client *client, + struct it6625 *it6625) +{ + it6625->i2c_client = client; + it6625->dev = &client->dev; + + it6625->it6625_regmap = devm_regmap_init_i2c(it6625->i2c_client, + &it6625_regmap_config); + if (IS_ERR(it6625->it6625_regmap)) + return PTR_ERR(it6625->it6625_regmap); + + return 0; +} + +static int it6625_read_byte(struct it6625 *it6625, u8 reg) +{ + unsigned int val; + int err; + struct device *dev = it6625->dev; + + err = regmap_read(it6625->it6625_regmap, reg, &val); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return val; +} + +static int it6625_write_byte(struct it6625 *it6625, u8 reg, u8 val) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_write(it6625->it6625_regmap, reg, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_set_bits(struct it6625 *it6625, u8 reg, u8 mask, u8 val) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_update_bits(it6625->it6625_regmap, reg, mask, val); + if (err < 0) { + dev_err(dev, "reg[0x%x] set bits failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_read_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_bulk_read(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] read failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_write_bytes(struct it6625 *it6625, u8 reg, u8 *buf, int len) +{ + int err; + struct device *dev = it6625->dev; + + err = regmap_bulk_write(it6625->it6625_regmap, reg, buf, len); + if (err < 0) { + dev_err(dev, "reg[0x%x] write failed err: %d", reg, err); + return err; + } + + return 0; +} + +static int it6625_wait_for_status(struct it6625 *it6625, u8 reg, u8 val, + int timeout_ms) +{ + struct device *dev = it6625->dev; + int status; + int rval; + int sleep_ms = 10; + int timeout_round_ms = DIV_ROUND_UP(timeout_ms, sleep_ms) * sleep_ms; + + status = read_poll_timeout(it6625_read_byte, rval, rval == val, + sleep_ms * 1000, + timeout_round_ms * 1000, + false, it6625, reg); + + dev_info(dev, "%s status = %d %d", __func__, status, (int)rval); + if (status < 0) { + dev_err(dev, "%s err status = %d", __func__, status); + return -ETIMEDOUT; + } + + return 0; +} + +static void it6625_write_command(struct it6625 *it6625, u8 *cmds, int cmd_len) +{ + it6625_write_bytes(it6625, REG_CMD_SET, cmds, cmd_len); + it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_CMD_SET); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_update_config(struct it6625 *it6625) +{ + int err; + + err = it6625_set_bits(it6625, REG_HOST_CTRL_INT, + B_CONFIG_UPDATE, B_CONFIG_UPDATE); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_set_bank(struct it6625 *it6625, u8 bank) +{ + int err; + + err = it6625_write_byte(it6625, REG_INFO_BANK_SEL, bank); + if (err < 0) + return err; + + err = it6625_write_byte(it6625, REG_HOST_CTRL_INT, B_IF_BANK); + if (err < 0) + return err; + + return it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static inline bool is_hdmi(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_HDMI); +} + +static inline bool hdmi_5v_power_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_5V); +} + +static inline bool no_signal(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? true : !(val & B_RX_STABLE); +} + +static inline bool audio_present(struct it6625 *it6625) +{ + int val; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + return (val < 0) ? false : (val & B_RX_AUD_ON); +} + +static int get_audio_sampling_rate(struct it6625 *it6625) +{ + int fs_id; + int i, freq = 0; + const struct fs_id_map { + u8 fs_id; + u32 freq; + } s_fsid_map[] = { + { AUD32K, 32000 }, + { AUD44K, 44100 }, + { AUD48K, 48000 }, + { AUD64K, 64000 }, + { AUD88K, 88200 }, + { AUD96K, 96000 }, + + { AUD128K, 128000 }, + { AUD176K, 176400 }, + { AUD192K, 192000 }, + { AUD256K, 256000 }, + { AUD352K, 352800 }, + { AUD384K, 384000 }, + + { AUD512K, 512000 }, + { AUD705K, 705600 }, + { AUD768K, 768000 }, + { AUD1024K, 1024000 }, + { AUD1411K, 1411200 }, + { AUD1536K, 1536000 }, + }; + + if (no_signal(it6625) || !audio_present(it6625)) + return 0; + + guard(mutex)(&it6625->it6625_lock); + + fs_id = it6625_read_byte(it6625, REG_HDMI_AUDIO_INFO1); + if (fs_id < 0) + return 0; + + for (i = 0; i < ARRAY_SIZE(s_fsid_map); i++) { + if (s_fsid_map[i].fs_id == fs_id) { + freq = s_fsid_map[i].freq; + break; + } + } + + return freq; +} + +static u64 it6625_get_pclk(struct it6625 *it6625) +{ + u32 pclk; + u8 ck[4]; + int ret; + + ret = it6625_read_bytes(it6625, REG_VID_PCLK, ck, 4); + if (ret < 0) { + dev_err(it6625->dev, "failed to read pixel clock"); + return 0; + } + + pclk = ck[0]; + pclk <<= 8; + pclk |= ck[1]; + pclk <<= 8; + pclk |= ck[2]; + pclk <<= 8; + pclk |= ck[3]; + + v4l2_dbg(1, debug, &it6625->sd, "%s: pclk=%u (%08x)", + __func__, pclk, pclk); + + return (u64)pclk * 1000; +} + +static int it6625_read_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + if (!edid) { + dev_err(dev, "edid buffer is NULL"); + return -EINVAL; + } + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_READ + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_read_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static int it6625_write_edid(struct it6625 *it6625, u8 *edid, int start_block, + int num_blocks) +{ + int i, bank_ctrl, err = 0; + struct device *dev = it6625->dev; + + if (start_block < 0 || num_blocks <= 0 || + start_block > EDID_NUM_BLOCKS_MAX || + num_blocks > EDID_NUM_BLOCKS_MAX || + start_block + num_blocks > EDID_NUM_BLOCKS_MAX) { + dev_err(dev, + "invalid block range: start_block=%d, num_blocks=%d", + start_block, num_blocks); + return -EINVAL; + } + + guard(mutex)(&it6625->it6625_lock); + for (i = 0; i < num_blocks; i++) { + bank_ctrl = CTL_BANK_EDID_WRITE + start_block + i; + err = it6625_set_bank(it6625, bank_ctrl); + if (err < 0) + break; + + err = it6625_write_bytes(it6625, REG_EDID_START, + edid + (i * 128), 128); + if (err < 0) + break; + + err = it6625_update_config(it6625); + if (err < 0) + break; + } + + it6625_set_bank(it6625, 0); + + return err < 0 ? err : num_blocks; +} + +static void it6625_enable_auto_hpd(struct it6625 *it6625) +{ + dev_dbg(it6625->dev, "%s: auto HPD", __func__); + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x00); + it6625_update_config(it6625); +} + +static void it6625_disable_hpd(struct it6625 *it6625) +{ + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, 0x01); + it6625_update_config(it6625); +} + +static void it6625_enable_hpd(struct it6625 *it6625) +{ + schedule_delayed_work(&it6625->hpd_delayed_work, HZ / 7); +} + +static void it6625_hpd_delayed_work(struct work_struct *work) +{ + struct it6625 *it6625 = container_of(work, + struct it6625, hpd_delayed_work.work); + int val = 0; + + guard(mutex)(&it6625->it6625_lock); + it6625_set_bits(it6625, REG_RX_CFG, 0x03, val); + it6625_update_config(it6625); +} + +static int it6625_get_detected_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + struct v4l2_bt_timings *bt = &timings->bt; + int val; + unsigned int width, height; + u8 buffer[4]; + u8 buffer2[12]; + + if (no_signal(it6625)) { + dev_err(it6625->dev, "no signal detected"); + return -ENOLINK; + } + + guard(mutex)(&it6625->it6625_lock); + + memset(timings, 0, sizeof(struct v4l2_dv_timings)); + timings->type = V4L2_DV_BT_656_1120; + val = it6625_read_byte(it6625, REG_VID_INFO); + if (val < 0) { + dev_err(it6625->dev, "failed to read video info"); + return -EIO; + } + + bt->interlaced = val & B_INTERLACE ? + V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE; + + if (it6625_read_bytes(it6625, REG_H_ACTIVE_1, buffer, 4) < 0) + return -EIO; + + width = ((buffer[0] & 0xff) << 8) + buffer[1]; + height = ((buffer[2] & 0xff) << 8) + buffer[3]; + + bt->width = width; + bt->height = height; + + if (it6625_read_bytes(it6625, REG_H_FP_1, buffer2, 12) < 0) + return -EIO; + + bt->hfrontporch = ((buffer2[0] & 0xff) << 8) + buffer2[1]; + bt->hsync = ((buffer2[2] & 0xff) << 8) + buffer2[3]; + bt->hbackporch = ((buffer2[4] & 0xff) << 8) + buffer2[5]; + bt->vfrontporch = ((buffer2[6] & 0xff) << 8) + buffer2[7]; + bt->vsync = ((buffer2[8] & 0xff) << 8) + buffer2[9]; + bt->vbackporch = ((buffer2[10] & 0xff) << 8) + buffer2[11]; + + bt->pixelclock = it6625_get_pclk(it6625); + if (bt->interlaced == V4L2_DV_INTERLACED) { + bt->height *= 2; + bt->il_vsync = bt->vsync + 1; + } + + return 0; +} + +static void it6625_show_avi_infoframe(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + union hdmi_infoframe frame; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + u8 ver, len; + int ret; + + if (!is_hdmi(it6625)) { + dev_err(dev, "not HDMI signal, skip AVI infoframe log"); + return; + } + + ret = it6625_read_bytes(it6625, REG_AVI_DATA, buffer + 1, + HDMI_INFOFRAME_SIZE(AVI) - 1); + if (ret < 0) { + dev_err(dev, "failed to read AVI infoframe data"); + return; + } + + len = buffer[1]; + ver = buffer[2]; + + buffer[0] = HDMI_INFOFRAME_TYPE_AVI; + buffer[1] = ver; + buffer[2] = len; + + ret = hdmi_infoframe_unpack(&frame, buffer, sizeof(buffer)); + if (ret < 0) { + dev_err(dev, "unpack of AVI infoframe failed"); + return; + } + + hdmi_infoframe_log(KERN_INFO, dev, &frame); +} + +static int it6625_s_ctrl_detect_hdmi_5v(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_5v_detect, + hdmi_5v_power_present(it6625)); +} + +static int it6625_s_ctrl_audio_sampling_rate(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_sampling_rate, + get_audio_sampling_rate(it6625)); +} + +static int it6625_s_ctrl_audio_present(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + return v4l2_ctrl_s_ctrl(it6625->ctrl_audio_present, + audio_present(it6625)); +} + +static void it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd) +{ + it6625_s_ctrl_detect_hdmi_5v(sd); + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_enable_stream_locked(struct it6625 *it6625, bool enable) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + + lockdep_assert_held(&it6625->it6625_lock); + + v4l2_dbg(3, debug, sd, "%s: %sable", + __func__, enable ? "en" : "dis"); + + val = enable ? B_MIPI_OUTPUT : 0; + it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val); + it6625_update_config(it6625); +} + +static void it6625_enable_stream(struct it6625 *it6625, bool enable) +{ + guard(mutex)(&it6625->it6625_lock); + it6625_enable_stream_locked(it6625, enable); +} + +static void it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val) +{ + u8 mipi_data_type; + + lockdep_assert_held(&it6625->it6625_lock); + + dev_dbg(it6625->dev, "mipi_data_type = 0x%x", cfg_val); + + mipi_data_type = cfg_val & 0xFF; + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type); + it6625_update_config(it6625); +} + +static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timings *t) +{ + if (!V4L2_DV_BT_FRAME_HEIGHT(t) || !V4L2_DV_BT_FRAME_WIDTH(t)) + return 0; + + return DIV_ROUND_CLOSEST((unsigned int)t->pixelclock, + V4L2_DV_BT_FRAME_HEIGHT(t) * + V4L2_DV_BT_FRAME_WIDTH(t)); +} + +static void it6625_initial_setup(struct it6625 *it6625) +{ + int val = 0; + + guard(mutex)(&it6625->it6625_lock); + + /* + * REG_MIPI_CFG[0:2] lane count field: 1 lane -> 0, 2 lanes -> 1, + * 3 lanes (C-PHY only) -> 3, 4 lanes (D-PHY only) -> 3. + */ + switch (it6625->csi_lanes) { + case 1: + val = FIELD_PREP(M_MIPI_LANE, 0); + break; + case 2: + val = FIELD_PREP(M_MIPI_LANE, 1); + break; + default: + val = FIELD_PREP(M_MIPI_LANE, 3); + break; + } + + if (it6625->bus_type == V4L2_MBUS_CSI2_DPHY) + val |= FIELD_PREP(B_MIPI_DPHY, 1); + + if (it6625->port_num == 2) + val |= FIELD_PREP(B_MIPI_SPLIT, 1); + + it6625_write_byte(it6625, REG_MIPI_CFG, val); + it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625->csi_format); + it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00); + it6625_write_byte(it6625, REG_RX_CFG, 0x00); + + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CONFIG_UPDATE, B_CONFIG_UPDATE); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); +} + +static int it6625_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2]; + + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = enable ? 1 : 0; + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static void it6625_cec_reset_la(struct it6625 *it6625, bool keep_enabled) +{ + u8 cmds[2]; + + if (keep_enabled) { + cmds[0] = CMD_SET_CEC_LA; + cmds[1] = CEC_LOG_ADDR_UNREGISTERED; + } else { + cmds[0] = CMD_SET_CEC_ENABLE; + cmds[1] = 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); +} + +static int it6625_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) +{ + struct it6625 *it6625 = adap->priv; + u8 cmds[2] = {CMD_SET_CEC_LA, log_addr}; + + dev_dbg(it6625->dev, "%s: la=%d", __func__, log_addr); + + if (log_addr == CEC_LOG_ADDR_INVALID) { + it6625_cec_reset_la(it6625, adap->is_enabled); + return 0; + } + + guard(mutex)(&it6625->it6625_lock); + it6625_write_command(it6625, cmds, sizeof(cmds)); + + return 0; +} + +static int it6625_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct it6625 *it6625 = adap->priv; + + guard(mutex)(&it6625->it6625_lock); + it6625_write_bytes(it6625, REG_CEC_TX_DATA, msg->msg, msg->len); + it6625_write_byte(it6625, REG_CEC_TX_DATA_LEN, msg->len); + it6625_set_bits(it6625, REG_HOST_CTRL_INT, B_CEC_SEND_DATA, B_CEC_SEND_DATA); + it6625_wait_for_status(it6625, REG_HOST_CTRL_INT, 0x00, 25); + + return 0; +} + +static const struct cec_adap_ops it6625_cec_adap_ops = { + .adap_enable = it6625_cec_adap_enable, + .adap_log_addr = it6625_cec_adap_log_addr, + .adap_transmit = it6625_cec_adap_transmit, +}; + +static void it6625_cec_handler(struct it6625 *it6625, u8 intstatus) +{ + struct cec_msg rxmsg = {}; + int val = 0; + + if (intstatus & B_CEC_RX_RECEIVED) { + scoped_guard(mutex, &it6625->it6625_lock) { + val = it6625_read_byte(it6625, REG_CEC_RX_DATA_LEN); + if (val > 0 && val <= CEC_MAX_MSG_SIZE) + it6625_read_bytes(it6625, REG_CEC_RX_DATA, &rxmsg.msg[0], val); + it6625_write_byte(it6625, REG_CEC_RX_DATA_LEN, 0); + } + + if (val > 0 && val <= CEC_MAX_MSG_SIZE) { + rxmsg.len = val; + cec_received_msg(it6625->cec_adap, &rxmsg); + } else { + dev_err(it6625->dev, "invalid CEC RX length %d", val); + } + } + + if (intstatus & B_CEC_TX_UPDATE) { + val = it6625_read_byte(it6625, REG_CEC_STATUS); + if (val < 0) { + dev_err(it6625->dev, "read CEC status failed"); + return; + } + + if (val & BIT(0)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_OK); + } else if (val & BIT(1)) { + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } else { + dev_info(it6625->dev, "unknown CEC status %02X", val); + cec_transmit_attempt_done(it6625->cec_adap, + CEC_TX_STATUS_NACK); + } + } +} + +static void it6625_irq_format_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + struct v4l2_dv_timings timings; + const struct v4l2_event it6625_ev_fmt = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + int ret; + + if (no_signal(it6625)) { + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); + return; + } + + ret = it6625_get_detected_timings(it6625, &timings); + if (ret < 0) { + v4l2_dbg(1, debug, sd, "Failed to get detected timings"); + return; + } + + if (sd->devnode) + v4l2_subdev_notify_event(sd, &it6625_ev_fmt); +} + +static void it6625_irq_hdmi_audio_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_s_ctrl_audio_sampling_rate(sd); + it6625_s_ctrl_audio_present(sd); +} + +static void it6625_get_timings(struct it6625 *it6625, + struct v4l2_dv_timings *timings) +{ + guard(mutex)(&it6625->it6625_lock); + *timings = it6625->timings; +} + +static void it6625_clear_timings(struct it6625 *it6625) +{ + guard(mutex)(&it6625->it6625_lock); + memset(&it6625->timings, 0, sizeof(it6625->timings)); +} + +static void it6625_irq_hdmi_5v_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + + it6625_clear_timings(it6625); + it6625_v4l2_sd_ctrl_update(sd); +} + +static void it6625_irq_hdcp_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + u8 cp_sts; + + cp_sts = it6625_read_byte(it6625, REG_RX_HDCP_STS); + v4l2_info(sd, "HDCP change to %02X", cp_sts); +} + +static void it6625_irq_infoframe_latch(struct it6625 *it6625) +{ + if (!it6625->if_active) + return; + + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->if_snapshot_err = + it6625_read_bytes(it6625, REG_IF_DATA, + it6625->if_snapshot, 31); + } + it6625->if_snapshot_done = true; + complete(&it6625->if_latched); +} + +static void it6625_irq_emata_packet_latch(struct it6625 *it6625) +{ + u8 extend_packet[14]; + + it6625_read_bytes(it6625, REG_EMP_DATA, extend_packet, 13); +} + +static void it6625_irq_avmute_change(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val; + u8 avmute; + + val = it6625_read_byte(it6625, REG_RX_STATUS); + if (val < 0) + return; + + avmute = (val & B_RX_AVMUTE) ? 1 : 0; + v4l2_info(sd, "AVMute change to %d", avmute); +} + +static void it6625_dispatch_int_status1(struct it6625 *it6625, int int_sts1) +{ + if (int_sts1 & B_HDMI_5V_CHG) + it6625_irq_hdmi_5v_change(it6625); + + if (int_sts1 & B_HDMI_VID_CHG) + it6625_irq_format_change(it6625); + + if (int_sts1 & B_HDMI_AUD_CHG) + it6625_irq_hdmi_audio_change(it6625); + + if (int_sts1 & B_HDMI_CP_CHG) + it6625_irq_hdcp_change(it6625); + + if (int_sts1 & B_HDMI_IF_LATCH) + it6625_irq_infoframe_latch(it6625); + + if (int_sts1 & B_HDMI_EMP) + it6625_irq_emata_packet_latch(it6625); +} + +static void it6625_dispatch_int_status2(struct it6625 *it6625, int int_sts2) +{ + struct v4l2_subdev *sd = &it6625->sd; + + if (int_sts2 & B_HDMI_AVI) + it6625_show_avi_infoframe(it6625); + + if (int_sts2 & B_HDMI_NO_AVI) + v4l2_info(sd, "AVI-info stopped"); + + if (int_sts2 & B_HDMI_AVMUTE_CHG) + it6625_irq_avmute_change(it6625); +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_rx_int_status(struct it6625 *it6625) +{ + int val1, val2, err; + + val1 = it6625_read_byte(it6625, REG_RX_INT_STATUS1); + val2 = it6625_read_byte(it6625, REG_RX_INT_STATUS2); + + err = 0; + if (val1 < 0) + err = val1; + else if (val2 < 0) + err = val2; + + if (val1 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS1, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status1(it6625, val1); + } + + if (val2 >= 0) { + int werr = it6625_write_byte(it6625, REG_RX_INT_STATUS2, 0x00); + + if (werr && !err) + err = werr; + it6625_dispatch_int_status2(it6625, val2); + } + + return err; +} + +/* caller must already hold if_state_lock */ +static int it6625_drain_interrupts(struct it6625 *it6625, bool *had_event) +{ + struct v4l2_subdev *sd = &it6625->sd; + int val, err; + + val = it6625_read_byte(it6625, REG_MCU_INTERRUPT); + if (val < 0) { + if (had_event) + *had_event = false; + return val; + } + if (had_event) + *had_event = val > 0; + if (val == 0) + return 0; + + err = it6625_write_byte(it6625, REG_MCU_INTERRUPT, val); + + v4l2_dbg(1, debug, sd, "%s: INT = 0x%02X", __func__, val); + + if (val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE) && it6625->cec_adap) + it6625_cec_handler(it6625, val & (B_CEC_RX_RECEIVED | B_CEC_TX_UPDATE)); + + if (val & B_SYS_INT_ACTIVE) { + int child_err = it6625_drain_rx_int_status(it6625); + + if (child_err && !err) + err = child_err; + } + + return err; +} + +static bool it6625_interrupt_handler(struct it6625 *it6625) +{ + bool had_event = false; + + guard(mutex)(&it6625->if_state_lock); + it6625_drain_interrupts(it6625, &had_event); + return had_event; +} + +static irqreturn_t it6625_irq_handler(int unused, void *data) +{ + struct it6625 *it6625 = data; + + return it6625_interrupt_handler(it6625) ? IRQ_HANDLED : IRQ_NONE; +} + +static void it6625_irq_poll_timer(struct timer_list *t) +{ + struct it6625 *it6625 = timer_container_of(it6625, t, timer); + unsigned int msecs; + + schedule_work(&it6625->polling_work); + /* + * If CEC is present, then we need to poll more frequently, + * otherwise we will miss CEC messages. + */ + msecs = it6625->cec_adap ? POLL_INTERVAL_CEC_MS : POLL_INTERVAL_MS; + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(msecs)); +} + +static void it6625_polling_work(struct work_struct *work) +{ + struct it6625 *it6625 = container_of(work, struct it6625, + polling_work); + + it6625_interrupt_handler(it6625); +} + +static const char *it6625_csi_format_name(u8 csi_format) +{ + switch (csi_format) { + case CSI_YUV422_8b: + return "YUV422 8bit"; + case CSI_RGB888: + return "RGB888 8bit"; + case CSI_YUV444_8b: + return "YUV444 8bit"; + default: + return "unknown"; + } +} + +static int it6625_log_status(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings, configured_timings; + struct v4l2_bt_timings bt; + u8 csi_format; + + if (it6625_get_detected_timings(it6625, &timings)) + v4l2_info(sd, "No video detected"); + else + v4l2_print_dv_timings(sd->name, "Detected format: ", &timings, + true); + + it6625_get_timings(it6625, &configured_timings); + v4l2_print_dv_timings(sd->name, "Configured format: ", + &configured_timings, true); + + /* snapshot together so the reported pair was actually configured together */ + scoped_guard(mutex, &it6625->it6625_lock) { + csi_format = it6625->csi_format; + bt = it6625->timings.bt; + } + + v4l2_info(sd, "CSI format: %s @ %uHz", + it6625_csi_format_name(csi_format), + fps_from_bt_timings(&bt)); + + it6625_show_avi_infoframe(it6625); + + return 0; +} + +static int it6625_isr(struct v4l2_subdev *sd, u32 status, bool *handled) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + schedule_work(&it6625->polling_work); + *handled = true; + + return 0; +} + +static int it6625_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub); + default: + return -EINVAL; + } +} + +static int it6625_g_input_status(struct v4l2_subdev *sd, u32 *status) +{ + struct it6625 *it6625 = sd_to_6625(sd); + bool val; + + val = no_signal(it6625); + *status = 0; + *status |= val ? V4L2_IN_ST_NO_SIGNAL : 0; + *status |= val ? V4L2_IN_ST_NO_SYNC : 0; + + v4l2_dbg(1, debug, sd, "%s: status = 0x%x", __func__, *status); + + return 0; +} + +static int +it6625_update_timings_if_changed(struct it6625 *it6625, + const struct v4l2_dv_timings *timings) +{ + int ret; + + guard(mutex)(&it6625->it6625_lock); + if (v4l2_match_dv_timings(&it6625->timings, timings, 0, false)) { + ret = 0; + } else if (!v4l2_valid_dv_timings(timings, it6625_get_timings_cap(it6625), + NULL, NULL)) { + ret = -ERANGE; + } else { + it6625->timings = *timings; + ret = 1; + } + + return ret; +} + +static int it6625_enum_dv_timings(struct v4l2_subdev *sd, + struct v4l2_enum_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (timings->pad != 0) + return -EINVAL; + + return v4l2_enum_dv_timings_cap(timings, + it6625_get_timings_cap(it6625), NULL, NULL); +} + +static int it6625_dv_timings_cap(struct v4l2_subdev *sd, + struct v4l2_dv_timings_cap *cap) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (cap->pad != 0) + return -EINVAL; + + *cap = *it6625_get_timings_cap(it6625); + + return 0; +} + +static int it6625_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + it6625_enable_stream(it6625, enable); + return 0; +} + +static int it6625_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + dev_dbg(sd->dev, "%s: index=%d", __func__, code->index); + + if (code->index >= ARRAY_SIZE(it6625_formats)) + return -EINVAL; + + dev_dbg(sd->dev, "%s: code=0x%08x", __func__, + it6625_formats[code->index].mbus_fmt_code); + code->code = it6625_formats[code->index].mbus_fmt_code; + + return 0; +} + +static int it6625_get_mbus_config(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_mbus_config *cfg) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + cfg->type = it6625->bus_type; + cfg->bus.mipi_csi2.flags = 0; + cfg->bus.mipi_csi2.num_data_lanes = it6625->csi_lanes; + + return 0; +} + +static int it6625_pad_s_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + if (!timings) + return -EINVAL; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + ret = it6625_update_timings_if_changed(it6625, timings); + if (ret == -ERANGE) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return ret; + } + + if (ret == 0) + v4l2_dbg(1, debug, sd, "%s: no change", __func__); + + return 0; +} + +static int it6625_pad_g_dv_timings(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + + if (pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, timings); + + return 0; +} + +static int it6625_pad_query_dv_timings(struct v4l2_subdev *sd, + unsigned int pad, + struct v4l2_dv_timings *timings) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + + if (pad != 0) + return -EINVAL; + + ret = it6625_get_detected_timings(it6625, timings); + if (ret) + return ret; + + if (debug) + v4l2_print_dv_timings(sd->name, __func__, timings, false); + + if (!v4l2_valid_dv_timings(timings, it6625_get_timings_cap(it6625), NULL, NULL)) { + v4l2_dbg(1, debug, sd, "%s: timings out of range", __func__); + return -ERANGE; + } + + return 0; +} + +static inline u32 format_to_colorspace(u8 csi_format) +{ + switch (csi_format) { + case CSI_RGB444: + case CSI_RGB555: + case CSI_RGB565: + case CSI_RGB666: + case CSI_RGB888: + case CSI_RGB_10b: + case CSI_RGB_12b: + return V4L2_COLORSPACE_SRGB; + case CSI_YUV420_8b_L: + case CSI_YUV420_8b: + case CSI_YUV420_10b: + case CSI_YUV422_8b: + case CSI_YUV422_10b: + case CSI_YUV422_12b: + case CSI_YUV420_10b_L: + case CSI_YUV420_12b: + case CSI_YUV444_8b: + case CSI_YUV444_10b: + case CSI_YUV444_12b: + return V4L2_COLORSPACE_REC709; + default: + return 0; + } +} + +static int it6625_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_dv_timings timings; + + if (format->pad != 0) + return -EINVAL; + + it6625_get_timings(it6625, &timings); + format->format.width = timings.bt.width; + format->format.height = timings.bt.height; + format->format.field = timings.bt.interlaced == V4L2_DV_INTERLACED ? + V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + format->format.code = fmt->code; + format->format.colorspace = fmt->colorspace; + } else { + scoped_guard(mutex, &it6625->it6625_lock) { + format->format.colorspace = + format_to_colorspace(it6625->csi_format); + format->format.code = it6625->mbus_fmt_code; + } + } + + return 0; +} + +static int it6625_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int ret; + u32 mbus_fmt_code = format->format.code; + + ret = it6625_get_fmt(sd, sd_state, format); + format->format.code = mbus_fmt_code; + + if (ret) + return ret; + + ret = it6625_csi_mbus_code_idx(mbus_fmt_code); + + if (ret < 0) { + v4l2_dbg(1, debug, sd, + "%s: unsupported format code 0x%x, falling back to default", + __func__, mbus_fmt_code); + ret = 0; + mbus_fmt_code = it6625_formats[ret].mbus_fmt_code; + format->format.code = mbus_fmt_code; + } + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_format(sd_state, format->pad); + fmt->code = format->format.code; + fmt->colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + format->format.colorspace = fmt->colorspace; + v4l2_dbg(1, debug, sd, "%s: try format code = 0x%x", + __func__, format->format.code); + return 0; + } + + scoped_guard(mutex, &it6625->it6625_lock) { + it6625->csi_format = it6625_formats[ret].csi_format; + it6625->mbus_fmt_code = format->format.code; + it6625_enable_stream_locked(it6625, false); + it6625_set_mipi_config_locked(it6625, it6625->csi_format); + } + + format->format.colorspace = format_to_colorspace(it6625_formats[ret].csi_format); + + return 0; +} + +static int it6625_g_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int err; + + if (edid->pad != 0) + return -EINVAL; + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + guard(mutex)(&it6625->edid_lock); + + if (edid->start_block == 0 && edid->blocks == 0) { + edid->blocks = it6625->edid_blocks; + return 0; + } + + if (it6625->edid_blocks == 0) + return -ENODATA; + + if (edid->start_block >= it6625->edid_blocks || edid->blocks == 0) + return -EINVAL; + + if (edid->blocks > it6625->edid_blocks - edid->start_block) + edid->blocks = it6625->edid_blocks - edid->start_block; + + err = it6625_read_edid(it6625, edid->edid, edid->start_block, + edid->blocks); + + return (err < 0) ? err : 0; +} + +static int it6625_s_edid(struct v4l2_subdev *sd, + struct v4l2_subdev_edid *edid) +{ + struct it6625 *it6625 = sd_to_6625(sd); + int err; + u16 parent_pa = CEC_PHYS_ADDR_INVALID; + + if (edid->pad != 0) { + v4l2_err(sd, "invalid pad %d", edid->pad); + return -EINVAL; + } + + memset(edid->reserved, 0, sizeof(edid->reserved)); + + if (edid->start_block != 0) { + v4l2_err(sd, "start_block must be 0 for set edid"); + return -EINVAL; + } + + if (edid->blocks > EDID_NUM_BLOCKS_MAX) { + v4l2_err(sd, "too many edid blocks: %d", edid->blocks); + edid->blocks = EDID_NUM_BLOCKS_MAX; + return -E2BIG; + } + + if (edid->blocks != 0) { + u16 pa = v4l2_get_edid_phys_addr(edid->edid, + edid->blocks * 128, NULL); + err = v4l2_phys_addr_validate(pa, &parent_pa, NULL); + if (err) { + v4l2_err(sd, "invalid CEC physical address in EDID"); + return err; + } + } + + guard(mutex)(&it6625->edid_lock); + + it6625_disable_hpd(it6625); + cec_phys_addr_invalidate(it6625->cec_adap); + it6625->edid_blocks = 0; + + if (edid->blocks == 0) + return 0; + + err = it6625_write_edid(it6625, edid->edid, + edid->start_block, edid->blocks); + if (err < 0) { + v4l2_err(sd, "write edid failed"); + return err; + } + + it6625->edid_blocks = edid->blocks; + cec_s_phys_addr(it6625->cec_adap, parent_pa, false); + + if (hdmi_5v_power_present(it6625)) { + it6625_enable_hpd(it6625); + it6625_s_ctrl_detect_hdmi_5v(sd); + } else { + it6625_enable_auto_hpd(it6625); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops it6625_core_ops = { + .log_status = it6625_log_status, + .interrupt_service_routine = it6625_isr, + .subscribe_event = it6625_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops it6625_video_ops = { + .g_input_status = it6625_g_input_status, + .s_stream = it6625_s_stream, +}; + +static const struct v4l2_subdev_pad_ops it6625_pad_ops = { + .enum_mbus_code = it6625_enum_mbus_code, + .set_fmt = it6625_set_fmt, + .get_fmt = it6625_get_fmt, + .get_edid = it6625_g_edid, + .set_edid = it6625_s_edid, + .enum_dv_timings = it6625_enum_dv_timings, + .dv_timings_cap = it6625_dv_timings_cap, + .get_mbus_config = it6625_get_mbus_config, + .s_dv_timings = it6625_pad_s_dv_timings, + .g_dv_timings = it6625_pad_g_dv_timings, + .query_dv_timings = it6625_pad_query_dv_timings, +}; + +static const struct v4l2_subdev_ops it6625_ops = { + .core = &it6625_core_ops, + .video = &it6625_video_ops, + .pad = &it6625_pad_ops, +}; + +static int it6625_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(sd_state, 0); + + fmt->code = it6625_formats[0].mbus_fmt_code; + fmt->colorspace = format_to_colorspace(it6625_formats[0].csi_format); + + return 0; +} + +static const struct v4l2_subdev_internal_ops it6625_internal_ops = { + .init_state = it6625_init_state, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_sampling_rate = { + .id = V4L2_CID_IT6625_AUDIO_SAMPLING_RATE, + .name = "Audio Sampling Rate", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 1536000, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config it6625_ctrl_audio_present = { + .id = V4L2_CID_IT6625_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 int it6625_v4l2_init_controls(struct v4l2_subdev *sd) +{ + struct it6625 *it6625 = sd_to_6625(sd); + struct v4l2_ctrl_handler *hdl = &it6625->hdl; + + v4l2_ctrl_handler_init(hdl, 4); + it6625->ctrl_5v_detect = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT, + 0, 1, 0, 0); + + it6625->ctrl_audio_sampling_rate = + v4l2_ctrl_new_custom(hdl, + &it6625_ctrl_audio_sampling_rate, + NULL); + it6625->ctrl_audio_present = + v4l2_ctrl_new_custom(hdl, &it6625_ctrl_audio_present, NULL); + it6625->ctrl_link_freq = + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(it6625_link_freq) - 1, + it6625->bus_type == V4L2_MBUS_CSI2_CPHY ? 1 : 0, + it6625_link_freq); + if (hdl->error) { + v4l2_err(sd, "Failed to initialize controls"); + v4l2_ctrl_handler_free(hdl); + return hdl->error; + } + + sd->ctrl_handler = hdl; + + return 0; +} + +static void it6625_regdump_print(struct seq_file *s, const u8 *reg_buf) +{ + int i; + + seq_puts(s, " 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F\n"); + + for (i = 0; i < 256; i++) { + if (i % 16 == 0) + seq_printf(s, "[%02X] ", i & 0xF0); + seq_printf(s, "0x%02X ", reg_buf[i]); + if (i % 16 == 15) + seq_putc(s, '\n'); + } +} + +static int it6625_mipi_reg_show(struct seq_file *s, void *data) +{ + struct it6625 *it6625 = s->private; + u8 reg_buf[256]; + int ret; + + scoped_guard(mutex, &it6625->it6625_lock) + ret = it6625_read_bytes(it6625, 0x00, reg_buf, sizeof(reg_buf)); + if (ret < 0) + return ret; + it6625_regdump_print(s, reg_buf); + + return 0; +} + +static int it6625_mipi_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, it6625_mipi_reg_show, inode->i_private); +} + +static ssize_t it6625_mipi_reg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct it6625 *it6625 = file_inode(file)->i_private; + char buf[32] = {}; + unsigned int addr, val; + ssize_t len; + loff_t pos = 0; + + len = simple_write_to_buffer(buf, sizeof(buf) - 1, &pos, user_buf, count); + if (len < 0) + return len; + buf[len] = '\0'; + + if (sscanf(buf, "%X %X", &addr, &val) != 2) + return -EINVAL; + + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, addr, val); + + return count; +} + +static const struct file_operations it6625_mipi_reg_fops = { + .owner = THIS_MODULE, + .open = it6625_mipi_reg_open, + .read = seq_read, + .write = it6625_mipi_reg_write, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Maps a V4L2_DEBUGFS_IF_* framework flag to the actual HDMI packet-type + * header byte REG_IF_LATCH_HB expects to arm that InfoFrame type. + */ +static int it6625_if_packet_type(u32 type) +{ + switch (type) { + case V4L2_DEBUGFS_IF_AVI: + return 0x82; + case V4L2_DEBUGFS_IF_AUDIO: + return 0x84; + case V4L2_DEBUGFS_IF_SPD: + return 0x83; + case V4L2_DEBUGFS_IF_HDMI: + return 0x81; + default: + return -EINVAL; + } +} + +static ssize_t it6625_debugfs_if_read(u32 type, void *priv, struct file *filp, + char __user *ubuf, size_t count, + loff_t *ppos) +{ + struct v4l2_subdev *sd = priv; + struct it6625 *it6625 = sd_to_6625(sd); + u8 buf[32] = {}; + int packet_type; + int err, err_reset; + bool captured; + int len; + + packet_type = it6625_if_packet_type(type); + if (packet_type < 0) + return 0; + + guard(mutex)(&it6625->if_read_lock); + + scoped_guard(mutex, &it6625->if_state_lock) { + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + if (err) + return err; + + err = it6625_drain_interrupts(it6625, NULL); + if (err) + return err; + + reinit_completion(&it6625->if_latched); + + it6625->if_active = true; + it6625->if_type = packet_type; + it6625->if_snapshot_done = false; + it6625->if_snapshot_err = 0; + + scoped_guard(mutex, &it6625->it6625_lock) + err = it6625_write_byte(it6625, REG_IF_LATCH_HB, + packet_type); + if (err) { + scoped_guard(mutex, &it6625->it6625_lock) + it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + it6625->if_active = false; + return err; + } + } + + wait_for_completion_timeout(&it6625->if_latched, msecs_to_jiffies(100)); + + scoped_guard(mutex, &it6625->if_state_lock) { + captured = it6625->if_snapshot_done; + it6625->if_active = false; + + scoped_guard(mutex, &it6625->it6625_lock) + err_reset = it6625_write_byte(it6625, REG_IF_LATCH_HB, 0); + + if (!captured) { + if (err_reset) + return err_reset; + return 0; + } + + if (!it6625->if_snapshot_err) { + buf[0] = packet_type; + memcpy(&buf[1], it6625->if_snapshot, 31); + } + + err = it6625->if_snapshot_err ? it6625->if_snapshot_err : err_reset; + if (err) + return err; + } + + len = buf[2] ? buf[2] + 4 : -ENOENT; + if (len > (int)sizeof(buf)) + len = -ENOENT; + if (len < 0) + return 0; + return simple_read_from_buffer(ubuf, count, ppos, buf, len); +} + +static void it6625_debugfs_init(struct it6625 *it6625, struct i2c_client *client) +{ + it6625->debugfs_dir = debugfs_create_dir(dev_name(&client->dev), NULL); + + debugfs_create_file("mipi_reg", 0600, it6625->debugfs_dir, it6625, + &it6625_mipi_reg_fops); + + it6625->infoframes = v4l2_debugfs_if_alloc(it6625->debugfs_dir, + V4L2_DEBUGFS_IF_AVI | V4L2_DEBUGFS_IF_AUDIO | + V4L2_DEBUGFS_IF_SPD | V4L2_DEBUGFS_IF_HDMI, + &it6625->sd, it6625_debugfs_if_read); +} + +static void it6625_init_data(struct it6625 *it6625) +{ + static struct v4l2_dv_timings default_timing = + V4L2_DV_BT_CEA_1920X1080P60; + + it6625->csi_lanes = 4; + it6625->port_num = 1; + it6625->bus_type = V4L2_MBUS_CSI2_DPHY; + it6625->csi_format = it6625_formats[0].csi_format; + it6625->mbus_fmt_code = it6625_formats[0].mbus_fmt_code; + it6625->timings = default_timing; + /* firmware ships with a verified 2-block default EDID in EDID RAM */ + it6625->edid_blocks = 2; +} + +static int it6625_parse_endpoint(struct it6625 *it6625) +{ + struct device *dev = it6625->dev; + /* + * Pre-setting bus_type here makes v4l2_fwnode_endpoint_alloc_parse() + * treat it as a hard requirement and reject any endpoint whose DT + * bus-type disagrees, so this must stay V4L2_MBUS_UNKNOWN to let it + * autodetect C-PHY vs D-PHY from the endpoint itself. + */ + struct v4l2_fwnode_endpoint endpoint = { .bus_type = V4L2_MBUS_UNKNOWN }; + struct device_node *ep = NULL; + unsigned int max_lanes; + unsigned int port; + int ret; + + /* + * port@0 and port@1 are the two CSI-2 output ports MIPI0/MIPI1 + * (port@2 is the HDMI input). This chip series can drive both + * simultaneously in split or mirror mode, so port_num counts how + * many of MIPI0/MIPI1 have an endpoint wired up. This driver only + * wires up a single source pad, so lane/bus-type configuration is + * parsed from whichever of the two is found first. + */ + it6625->port_num = 0; + for (port = 0; port < 2; port++) { + struct device_node *port_ep = + of_graph_get_endpoint_by_regs(dev->of_node, port, -1); + + if (!port_ep) + continue; + + it6625->port_num++; + if (!ep) + ep = port_ep; + else + of_node_put(port_ep); + } + + if (!ep) { + it6625->port_num = 1; + dev_dbg(dev, "no CSI-2 endpoint node found, using default %u CSI lanes", + it6625->csi_lanes); + return 0; + } + + ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint); + of_node_put(ep); + if (ret) { + dev_err(dev, "failed to parse endpoint: %d", ret); + return ret; + } + + if (endpoint.bus_type != V4L2_MBUS_CSI2_DPHY && + endpoint.bus_type != V4L2_MBUS_CSI2_CPHY) { + dev_err(dev, "unsupported bus type %d, expected CSI-2 D-PHY or C-PHY", + endpoint.bus_type); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + if (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY && + it6625->chip_type != IT6626_CHIP) { + dev_err(dev, "IT6625 does not support C-PHY, only IT6626 does"); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + max_lanes = (endpoint.bus_type == V4L2_MBUS_CSI2_CPHY) ? 3 : 4; + + if (endpoint.bus.mipi_csi2.num_data_lanes == 0 || + endpoint.bus.mipi_csi2.num_data_lanes > max_lanes) { + dev_err(dev, + "invalid number of CSI data lanes: %u (max %u for this bus type)", + endpoint.bus.mipi_csi2.num_data_lanes, max_lanes); + v4l2_fwnode_endpoint_free(&endpoint); + return -EINVAL; + } + + it6625->csi_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + it6625->bus_type = endpoint.bus_type; + v4l2_fwnode_endpoint_free(&endpoint); + + return 0; +} + +static int it6625_parse_dt(struct it6625 *it6625) +{ + return it6625_parse_endpoint(it6625); +} + +static int it6625_init_v4l2_subdev(struct it6625 *it6625) +{ + struct v4l2_subdev *sd = &it6625->sd; + int err; + + sd->dev = it6625->dev; + + v4l2_i2c_subdev_init(sd, it6625->i2c_client, &it6625_ops); + sd->internal_ops = &it6625_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + if (it6625_v4l2_init_controls(sd)) { + dev_err(it6625->dev, "Failed to initialize v4l2 controls"); + return -ENOMEM; + } + + it6625->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + err = media_entity_pads_init(&sd->entity, 1, &it6625->pad); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + v4l2_ctrl_handler_free(sd->ctrl_handler); + return err; + } + + return 0; +} + +static int it6625_check_device(struct it6625 *it6625) +{ + static const u8 chip_ids[][2] = { + { 0x66, 0x25 }, + { 0x66, 0x26 }, + }; + int chip_id0, chip_id1; + + chip_id0 = it6625_read_byte(it6625, REG_CHIP_ID_0); + chip_id1 = it6625_read_byte(it6625, REG_CHIP_ID_1); + if (chip_id0 != chip_ids[it6625->chip_type][0] || + chip_id1 != chip_ids[it6625->chip_type][1]) { + dev_err(it6625->dev, + "chip ID mismatch: got 0x%02x%02x, expected 0x%02x%02x", + chip_id0, chip_id1, + chip_ids[it6625->chip_type][0], + chip_ids[it6625->chip_type][1]); + return -ENODEV; + } + + return 0; +} + +static int it6625_probe(struct i2c_client *client) +{ + struct it6625 *it6625; + struct v4l2_subdev *sd; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + it6625 = devm_kzalloc(&client->dev, sizeof(struct it6625), GFP_KERNEL); + if (!it6625) + return -ENOMEM; + + it6625->chip_type = (uintptr_t)i2c_get_match_data(client); + + it6625->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(it6625->reset_gpio)) + return PTR_ERR(it6625->reset_gpio); + + if (it6625->reset_gpio) { + usleep_range(1000, 2000); + gpiod_set_value_cansleep(it6625->reset_gpio, 0); + usleep_range(10000, 11000); + } + + err = it6625_regmap_i2c_init(client, it6625); + if (err) + return err; + + err = it6625_check_device(it6625); + if (err) + return err; + + it6625_init_data(it6625); + + err = it6625_parse_dt(it6625); + if (err) + return err; + + mutex_init(&it6625->it6625_lock); + mutex_init(&it6625->edid_lock); + mutex_init(&it6625->if_read_lock); + mutex_init(&it6625->if_state_lock); + init_completion(&it6625->if_latched); + INIT_DELAYED_WORK(&it6625->hpd_delayed_work, it6625_hpd_delayed_work); + INIT_WORK(&it6625->polling_work, it6625_polling_work); + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, it6625_irq_handler, + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "it6625", it6625); + if (err) + goto err_clean_work_queues; + } else { + dev_info(it6625->dev, "no IRQ, falling back to polling"); + timer_setup(&it6625->timer, it6625_irq_poll_timer, 0); + } + + sd = &it6625->sd; + err = it6625_init_v4l2_subdev(it6625); + if (err) + goto err_clean_work_queues; + + err = v4l2_ctrl_handler_setup(sd->ctrl_handler); + if (err) + goto err_clean_hdl; + + it6625->cec_adap = cec_allocate_adapter(&it6625_cec_adap_ops, + it6625, dev_name(it6625->dev), + CEC_CAP_DEFAULTS | + CEC_CAP_MONITOR_ALL | + CEC_CAP_PHYS_ADDR, + 1); + if (IS_ERR(it6625->cec_adap)) { + err = PTR_ERR(it6625->cec_adap); + dev_err(it6625->dev, "%s %d", __func__, __LINE__); + goto err_clean_hdl; + } + + err = cec_register_adapter(it6625->cec_adap, &client->dev); + if (err < 0) { + dev_err(it6625->dev, "%s: failed to register the cec device", __func__); + cec_delete_adapter(it6625->cec_adap); + it6625->cec_adap = NULL; + goto err_clean_hdl; + } + + it6625_debugfs_init(it6625, client); + + it6625_initial_setup(it6625); + it6625_v4l2_sd_ctrl_update(sd); + + err = v4l2_async_register_subdev(sd); + if (err < 0) { + dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err); + goto err_clean_debugfs; + } + + if (client->irq) + enable_irq(client->irq); + else + mod_timer(&it6625->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS)); + + return 0; + +err_clean_debugfs: + v4l2_debugfs_if_free(it6625->infoframes); + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); +err_clean_hdl: + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); + +err_clean_work_queues: + if (!client->irq) + timer_shutdown_sync(&it6625->timer); + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + return err; +} + +static void it6625_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct it6625 *it6625 = sd_to_6625(sd); + + v4l2_debugfs_if_free(it6625->infoframes); + + if (client->irq) + disable_irq(client->irq); + else + timer_shutdown_sync(&it6625->timer); + + cancel_work_sync(&it6625->polling_work); + cancel_delayed_work_sync(&it6625->hpd_delayed_work); + + v4l2_async_unregister_subdev(sd); + v4l2_device_unregister_subdev(sd); + + debugfs_remove_recursive(it6625->debugfs_dir); + cec_unregister_adapter(it6625->cec_adap); + mutex_destroy(&it6625->it6625_lock); + mutex_destroy(&it6625->edid_lock); + mutex_destroy(&it6625->if_read_lock); + mutex_destroy(&it6625->if_state_lock); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&it6625->hdl); +} + +static const struct i2c_device_id it6625_id[] = { + { .name = "it6625", .driver_data = IT6625_CHIP }, + { .name = "it6626", .driver_data = IT6626_CHIP }, + {} +}; +MODULE_DEVICE_TABLE(i2c, it6625_id); + +static const struct of_device_id it6625_of_match[] = { + { .compatible = "ite,it6625", .data = (void *)IT6625_CHIP }, + { .compatible = "ite,it6626", .data = (void *)IT6626_CHIP }, + {}, +}; +MODULE_DEVICE_TABLE(of, it6625_of_match); + +static struct i2c_driver it6625_driver = { + .driver = { + .name = "it6625", + .of_match_table = it6625_of_match, + }, + .probe = it6625_probe, + .remove = it6625_remove, + .id_table = it6625_id, +}; +module_i2c_driver(it6625_driver); + +MODULE_DESCRIPTION("iTE it6625/it6626 HDMI to MIPI CSI bridge driver"); +MODULE_AUTHOR("Hermes Wu "); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/it6625.h b/include/uapi/linux/it6625.h new file mode 100644 index 000000000000..a895658aaf6a --- /dev/null +++ b/include/uapi/linux/it6625.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Controls header for IT6625/IT6626 driver + */ + +#ifndef _UAPI_LINUX_IT6625_H +#define _UAPI_LINUX_IT6625_H + +#include + +/* + * Currently detected HDMI audio sampling rate, in Hz. Read-only. + * 0 means the rate is unavailable/unknown: no audio is currently + * present on the input, the hardware reported a sample-rate id this + * driver doesn't recognize, or the status read itself failed. Never a + * literal 0 Hz sample rate. + */ +#define V4L2_CID_IT6625_AUDIO_SAMPLING_RATE (V4L2_CID_USER_IT6625_BASE + 0) + +/* + * Whether HDMI audio is currently present on the input. Read-only. + */ +#define V4L2_CID_IT6625_AUDIO_PRESENT (V4L2_CID_USER_IT6625_BASE + 1) + +#endif /* _UAPI_LINUX_IT6625_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 42b4f82afeb8..3a2c0a7ba5e2 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -240,6 +240,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_DCMIPP_BASE (V4L2_CID_USER_BASE + 0x1240) +/* + * The base for IT6625/IT6626 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_IT6625_BASE (V4L2_CID_USER_BASE + 0x1250) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ -- cgit v1.2.3 From 48f7e8e50ca495bd8a2ad233462a1e7db95ea0fe Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 8 Sep 2026 12:10:39 +0200 Subject: media: vim2m: give vim2m a proper control ID range The vim2m driver has two driver-specific controls, but the control IDs were never reserved in v4l2-control.h. While these control IDs clash with the range reserved for the old meye driver, since that meye driver has been removed from the kernel quite a long time ago we just officially claim that range for the vim2m driver. Signed-off-by: Hans Verkuil Reported-by: Nicolas Dufresne Closes: https://lore.kernel.org/linux-media/71a3fa9710cf1afbb80ebe850878557c3e95a1b2.camel@ndufresne.ca/ Reviewed-by: Nicolas Dufresne --- drivers/media/test-drivers/vim2m.c | 4 ++-- include/uapi/linux/v4l2-controls.h | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'include/uapi') diff --git a/drivers/media/test-drivers/vim2m.c b/drivers/media/test-drivers/vim2m.c index f4a2c4083829..459fd4aedf30 100644 --- a/drivers/media/test-drivers/vim2m.c +++ b/drivers/media/test-drivers/vim2m.c @@ -150,8 +150,8 @@ enum { V4L2_M2M_DST = 1, }; -#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_BASE + 0x1000) -#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_BASE + 0x1001) +#define V4L2_CID_TRANS_TIME_MSEC (V4L2_CID_USER_VIM2M_BASE + 0) +#define V4L2_CID_TRANS_NUM_BUFS (V4L2_CID_USER_VIM2M_BASE + 1) static struct vim2m_fmt *find_format(u32 fourcc) { diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 3a2c0a7ba5e2..d17e41d51d2e 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -123,6 +123,15 @@ enum v4l2_colorfx { #define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000) #endif +/* + * The base for the vim2m driver controls. + * We reserve 16 controls for this driver. + * The vim2m control range clashed with the meye control range, which was not + * intended, but since the meye driver has been removed, we can just keep the + * vim2m control range. + */ +#define V4L2_CID_USER_VIM2M_BASE (V4L2_CID_USER_BASE + 0x1000) + /* The base for the bttv driver controls. * We reserve 32 controls for this driver. */ #define V4L2_CID_USER_BTTV_BASE (V4L2_CID_USER_BASE + 0x1010) -- cgit v1.2.3 From 2d3873cadda8537d8991abb24484bc590001fcbe Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:24:48 +0200 Subject: media: mali-c55: add padding to mali_c55_params_ccm structure The newly added structure has extra padding on the on some architectures, which triggers a pedantic uapi check: ./usr/include/linux/media/arm/mali-c55-config.h:807:1: error: padding struct size to alignment boundary with 2 bytes [-Werror=padded] Add explicit padding here to avoid risking information leaks and incompatibilities between architectures. Fixes: bb401df68c06 ("media: mali-c55: Add support for CCM") Cc: stable@vger.kernel.org Signed-off-by: Arnd Bergmann Reviewed-by: Vincenzo Frascino Reviewed-by: Linus Walleij Reviewed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- include/uapi/linux/media/arm/mali-c55-config.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/uapi') diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index ef0fe17df96e..3ba416d81cd7 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -794,12 +794,14 @@ struct mali_c55_params_mesh_shading_selection { * Q4.8 format * @gains: Gains for red, green and blue channels in unsigned Q4.8 format * @offs: Offsets for red, green and blue channels + * @__pad: Reserved bytes */ struct mali_c55_params_ccm { struct v4l2_isp_params_block_header header; __u16 coeffs[3][3]; __u16 gains[3]; __u16 offs[3]; + __u16 __pad; }; /** -- cgit v1.2.3 From 49cdf56876d3fd4ff9eecd40cc6b9a3efa095473 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 30 Sep 2026 01:41:35 +0300 Subject: media: mc: Add INTERNAL pad flag Internal sink pads will be used as routing endpoints in V4L2 [GS]_ROUTING IOCTLs, to indicate that the stream begins in the entity. Internal sink pads are pads that have both SINK and INTERNAL flags set. Also prevent creating links to pads that have been flagged as internal and initialising SOURCE pads with INTERNAL flag set. Reviewed-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Tested-by: Martin Hecht Reviewed-by: Mirela Rabulea Signed-off-by: Sakari Ailus --- .../userspace-api/media/mediactl/media-types.rst | 9 +++++++++ drivers/media/mc/mc-entity.c | 15 ++++++++++++--- include/uapi/linux/media.h | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) (limited to 'include/uapi') diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst index 6332e8395263..200c37a1da26 100644 --- a/Documentation/userspace-api/media/mediactl/media-types.rst +++ b/Documentation/userspace-api/media/mediactl/media-types.rst @@ -361,6 +361,7 @@ Types and flags used to represent the media graph elements .. _MEDIA-PAD-FL-SINK: .. _MEDIA-PAD-FL-SOURCE: .. _MEDIA-PAD-FL-MUST-CONNECT: +.. _MEDIA-PAD-FL-INTERNAL: .. flat-table:: Media pad flags :header-rows: 0 @@ -381,6 +382,14 @@ Types and flags used to represent the media graph elements enabled links even when this flag isn't set; the absence of the flag doesn't imply there is none. + * - ``MEDIA_PAD_FL_INTERNAL`` + - The internal flag indicates an internal pad that has no external + connections. As they are internal to entities, internal pads shall not + be connected with links. + + The internal flag may currently be present only in a sink pad where it + indicates that the :ref:``stream `` originates + from within the entity. One and only one of ``MEDIA_PAD_FL_SINK`` and ``MEDIA_PAD_FL_SOURCE`` must be set for every pad. diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c index 3fa0bc687851..7938f785e2e3 100644 --- a/drivers/media/mc/mc-entity.c +++ b/drivers/media/mc/mc-entity.c @@ -209,11 +209,16 @@ int media_entity_pads_init(struct media_entity *entity, u16 num_pads, mutex_lock(&mdev->graph_mutex); media_entity_for_each_pad(entity, iter) { + const u32 pad_flags = iter->flags & (MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_INTERNAL); + iter->entity = entity; iter->index = i++; - if (hweight32(iter->flags & (MEDIA_PAD_FL_SINK | - MEDIA_PAD_FL_SOURCE)) != 1) { + if (pad_flags != MEDIA_PAD_FL_SINK && + pad_flags != (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL) && + pad_flags != MEDIA_PAD_FL_SOURCE) { ret = -EINVAL; break; } @@ -1118,7 +1123,8 @@ int media_get_pad_index(struct media_entity *entity, u32 pad_type, for (i = 0; i < entity->num_pads; i++) { if ((entity->pads[i].flags & - (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type) + (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_INTERNAL)) != pad_type) continue; if (entity->pads[i].sig_type == sig_type) @@ -1148,6 +1154,9 @@ media_create_pad_link(struct media_entity *source, u16 source_pad, return -EINVAL; if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK))) return -EINVAL; + if (WARN_ON(source->pads[source_pad].flags & MEDIA_PAD_FL_INTERNAL) || + WARN_ON(sink->pads[sink_pad].flags & MEDIA_PAD_FL_INTERNAL)) + return -EINVAL; link = media_add_link(&source->links); if (link == NULL) diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 1c80b1d6bbaf..80cfd12a43fc 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -208,6 +208,7 @@ struct media_entity_desc { #define MEDIA_PAD_FL_SINK (1U << 0) #define MEDIA_PAD_FL_SOURCE (1U << 1) #define MEDIA_PAD_FL_MUST_CONNECT (1U << 2) +#define MEDIA_PAD_FL_INTERNAL (1U << 3) struct media_pad_desc { __u32 entity; /* entity ID */ -- cgit v1.2.3 From 77a10651b2f23b6a56d27570fa9a870f8e977125 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 7 Oct 2026 14:43:45 +0300 Subject: media: mc: Keep INTERNAL pad flags just for the kernel, for now Do not expose INTERNAL pads on UAPI as neither the documentation nor v4l2-compliance changes to support MEDIA_PAD_FL_INTERNAL pads have been merged yet. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- include/uapi/linux/media.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/uapi') diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 80cfd12a43fc..2da6ae4da003 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -208,7 +208,13 @@ struct media_entity_desc { #define MEDIA_PAD_FL_SINK (1U << 0) #define MEDIA_PAD_FL_SOURCE (1U << 1) #define MEDIA_PAD_FL_MUST_CONNECT (1U << 2) +#if defined(__KERNEL__) +/* + * Forgo INTERNAL pads until proper documentation and v4l2-compliance testing + * are merged. + */ #define MEDIA_PAD_FL_INTERNAL (1U << 3) +#endif /* __KERNEL__ */ struct media_pad_desc { __u32 entity; /* entity ID */ -- cgit v1.2.3