From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=205.220.180.131 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792516611; b=fKy0VepQ/g2bo9KQNI6zPGUec/xrCqCkgygnD5hM3XWemXFcbuR0pyf609nAfJJo2+Mr NDL3OCp97xxzJqfvONVvdC0eBJXDpra906ABqEv3ygRFU+A1e897+IgrFzknL7dec4Swv K40H8MwkLCPdE7DTkYOO6chjG+jHCUd4A0yPFH3/2v9DsDbMjJsxA30NKn1JdK+lvZ2Bz TwyQ25cNIP7GD2Is+wj4FhNkfYmThgb03oX5tGT+6gAA1S514qXny6PA2wDUUhq/QoJuP BWTI4B4l2cXjIakVay9d4Jz6ntCax1JA60oucZfsTbS+tHeZllOE1MldRa51aeLctag== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792516611; h=DMARC-Filter:Received:DKIM-Signature:Received:Received: DKIM-Signature:X-Google-DKIM-Signature:X-Forwarded-Encrypted: X-Gm-Message-State:X-Gm-Gg:X-Received:X-Received:Received:Date:From: To:Cc:Subject:Message-ID:References:MIME-Version:Content-Type: Content-Disposition:Content-Transfer-Encoding:In-Reply-To: X-Proofpoint-ORIG-GUID:X-Proofpoint-Spam-Details-Enc: X-Proofpoint-Spam-Info:X-Authority-Analysis:X-Proofpoint-GUID: X-Proofpoint-Virus-Version:X-Proofpoint-Spam-Details; bh=62cxzO9qm5lRKsvE5y8ZLifx4JO5puPHI46VC/bI214=; b=AF5AiGvDkA5RaZY0nsNTzA9xOdy5Rl8bUjOdM3rhlFUDarU80HsIL/V7IIHadpimdL/x p8HudOvuZB31wh7/7PXaSzLppBJU48lji/4mYU9flBG4wbSswZiN/nn0B+yc8B6FhJHhU vs1MDVPWz1i+bOZ1kfq1jrkE24Hqzl+vzDQSOK4b+/g+tAsY0fvnpwkaDvSm5t5Ag8yaF bWaNJow6y22Gk7qUXSPm9e18bch7DqRlwyQEPqF2ScYqKhmyaenjDh94EYVsoTK/cM3XF bsmmGQE/0W4yUFSG1kMelmyWpaLkiy4EYbxNnC9dTs7UuQ4Va2KQ22sZqBNs0PtRBEA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass header.d=qualcomm.com header.i=@qualcomm.com header.a=rsa-sha256 header.s=qcppdkim1 header.b=TrtBWEwr; dkim=pass header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.a=rsa-sha256 header.s=google header.b=LiuIgr4J; arc=none smtp.remote-ip=205.220.180.131 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C759A606A326 for ; Tue, 20 Oct 2026 17:16:49 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org C759A606A326 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=qualcomm.com header.i=@qualcomm.com header.a=rsa-sha256 header.s=qcppdkim1 header.b=TrtBWEwr; dkim=pass (2048-bit key, unprotected) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.a=rsa-sha256 header.s=google header.b=LiuIgr4J Received: from pps.filterd (m0279868.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 69KGu64A1926052 for ; Tue, 20 Oct 2026 17:16:48 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=qcppdkim1; bh= 62cxzO9qm5lRKsvE5y8ZLifx4JO5puPHI46VC/bI214=; b=TrtBWEwrMUk54OSH YE3Jwr08R1SZIOX+hKO4/w8/fdMH/Z9q9VJoFeuUhz4H5pv5q0gyf8FyAS7DY0At /JdVHfFFXwYa+yVBGwzNX+0Ak4Ey5G3rSr+cUoN4qvYZ5eKq4yDswyN/TjHGDmP6 fUDS46XGP0rxc4YBXd2odoV0LNF/dEkTJ/Gu6S2PnyZsydrzz/sqYbMHIIghcCnC EuZqd21lxNqOUnt+uhbj+QcWlmnvCOZIKnAwTzNL2RsCQiUQh0PSTNMCK7kAr0iL yXUR04ooc0dyfOQfbmw2n3bHwDM97UqUWHYq/oj7g70k7zZSzpYXIBaW+5xJox0Y bXqoUw== Received: from mail-dy1-f197.google.com (mail-dy1-f197.google.com [74.125.82.197]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4hdw5qbm3e-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Tue, 20 Oct 2026 17:16:48 +0000 (GMT) Received: by mail-dy1-f197.google.com with SMTP id 5a478bee46e88-3552883bd19so9761998eec.1 for ; Tue, 20 Oct 2026 10:16:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1792516607; x=1793121407; darn=vger.kernel.org; h=in-reply-to:content-transfer-encoding:content-disposition :content-type:mime-version:references:message-id:subject:cc:to:from :date:from:to:cc:subject:date:message-id:reply-to:content-type; bh=62cxzO9qm5lRKsvE5y8ZLifx4JO5puPHI46VC/bI214=; b=LiuIgr4JCdnbIZR2ZmG2yg7RwHSVKScSdoTsKe9htdNCirKiLu7YUL2xG9ISJ3nOTl BEBQV9O86aDuLbPxKn01NzMOdR07J3M0HBT5ZlSH2zmP5B/BFVB7zQ93+yTAKPBZGKyt 3Cw9KE7Sx0Yv6z5jTBm1yIiDu06LewMa6doUEGHQ5RM/HM0kKFVHwEjm9nFA2m8RLvYe 6FXtqutvWsalZekKC+bBa/VznR1fGjATAVfnQ2hfgP3Mhrr6HlQJ+f5oFMiRKg3/2ODX 3AHtJxI1f0txWk35HueYPxyi+wyikU3pvuh4cy3z0YvakeECpviHkgYoiQgM0GHC1UcZ 7zCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792516607; x=1793121407; h=in-reply-to:content-transfer-encoding:content-disposition :content-type:mime-version:references:message-id:subject:cc:to:from :date:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=62cxzO9qm5lRKsvE5y8ZLifx4JO5puPHI46VC/bI214=; b=qcmnHLh7uq/Onf1uVVdLHdNl5uqKKez6L9lrEFv+vuMFt3CuKd5fJF+k/BY6aONEUX 87rgysDs3gjkRMeoGIcxVcKc8xGd21z1MGmaF0QMVB8CLn4lMMJHlpRkA96YvLCqHgxh YaXL5LqhitE18I2sT2AVUmPwgoERmEFKpP65GpM40SJX05qCm2rEo8s8+0LAnLAtIP0f UEPVBJVs+u/9a+ritVBlmfQDYy5dD05354+vJbI5h4WKrKYis8InnRCjgoZ9wjKuGupt kOX5C5hNoOiu/dIY5a5VQXJ37N/rbWJOoGz3/5jzdZYdljwn5J6FQbuQRRf8VNplV+fv VS4g== X-Forwarded-Encrypted: i=1; AKwUvBzmnQzIG8YLTr0EcOd5mFCymCSTOCHeNYAZSozSgA2GJDBbmVKW+beQczIbjZ4KPC9LrFUN2bmLbC3I@vger.kernel.org X-Gm-Message-State: AFq9FYLSdm+9xlxeJuWRQFDlCyN6ke9EXTizgs8UlS64Xl9EaNzZDPXS toUQ6acpT3ID0JukkdJ/y1bh73uNgX/C1zak6tlfe2veI84JWmLJfSHChDNIbpW7LlNPsKSgWOZ IVkkhTalfYu21LbzsQFpFuuF46bp0N4jAw9b7sHTJ7mmx58QCnclWwEciOy7SpG6+ X-Gm-Gg: AYBFou3Bd2v0DccJTQde3TByAniFGE+fPVTrTZWOjFD0nuoav5Z5PztzTzGlwXINjX+ QRyerU0nmjwOA3zBFhfg6a5Mk91x0Xbcx2ZS+tP2FPRqoI3lVg6pVeixLzrB+Av8ZIKLgHYtSCn 7TnfqFsKtUyi4aIP8WatkFQ6+bHCiRRIeM/NAmvbI04ez1H9SzwO62lq+MdHrwdBz490hWo44kG iEyoBlY7V9xZOH97cnRkn92oPrdJYZPxE53LtDgfxOu5tYAfKbxesEinazOsFa8BVybW9YTm9/o 9zGedPlMZDfM4Zn8UVSel4xxI08Ea5rJPpVY9+wgPH7WfbgkwcnUzurmrKPRO9zAbijBIkPC1pF 1QPdDaAGioEqq6/xLhDRSYdB+ X-Received: by 2002:a05:693c:4015:b0:366:6ada:a052 with SMTP id 5a478bee46e88-3666adaa33amr2698313eec.8.1792516606907; Tue, 20 Oct 2026 10:16:46 -0700 (PDT) X-Received: by 2002:a05:693c:4015:b0:366:6ada:a052 with SMTP id 5a478bee46e88-3666adaa33amr2698230eec.8.1792516605644; Tue, 20 Oct 2026 10:16:45 -0700 (PDT) Received: from hu-mdsor-hyd.qualcomm.com ([202.46.22.19]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-36740cc848fsm32767eec.13.2026.10.20.10.16.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Oct 2026 10:16:45 -0700 (PDT) Date: Tue, 20 Oct 2026 22:46:34 +0530 From: Mohit Dsor To: Chaoyi Chen Cc: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Luca Ceresoli , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Vinod Koul , dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, venkata.valluru@oss.qualcomm.com, Jessica Zhang , Sunyun Yang Subject: Re: [PATCH v17 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver Message-ID: References: <20261008-lt9611c-v7-v17-0-1e8f5f2b2f19@oss.qualcomm.com> <20261008-lt9611c-v7-v17-2-1e8f5f2b2f19@oss.qualcomm.com> <8cfd2274-2b80-4895-8da8-031c42fdb107@rock-chips.com> X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <8cfd2274-2b80-4895-8da8-031c42fdb107@rock-chips.com> X-Proofpoint-ORIG-GUID: KnUOxaL0eVWqqs98hJy5u7ZG7pbMJSQG X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYxMDIwMDA2OCBTYWx0ZWRfX9Axp2Sjut2K3 5BMuIEM1tEuZud8O+ES1O+gWT8nQS3Lo/Q54WWCrp6gAUTsTzH8VWm9ngNYUG0kWmDGctWv+3fv +0PkRBDl7MtfRTSeN6rzTGpkpb0xJKME/Y+k2VGcnKZhQtjNrkjsYjgM6SoL6YigOC7ElTxsbo6 Mp0v6E7Jr9lP+PNGRuKpfhWwYH85ib3YjsPPkbO9zpDenjb4Zf6skxUNkm2VpjYig2FVslWgOCy jxqIC6dw9t+vw7NdkgzOZKMvfr/y9ssQWicQ6liv2xN9N6LnuirnRRQcJ7LDqbITLpsvRYmKSs/ 0MI915AA8Lbee25Ltf51McK8QvMMifmuDs/CysNvlfDmhXUdvBLo0okpXSooqGzzmxZJ9eHLD1M wwRZOq+fxZMnB/vGtZUpRigdKB53KVUbjBJmqYJEBgEpBEbkmtrays34SssojL5U0iseRDofCh2 3wr/p42nqidydUVjkrw== X-Proofpoint-Spam-Info: AW1haW4tMjYxMDIwMDA2OCBTYWx0ZWRfXxK0rZ/0aOPus Soqx+JvK80hloWjRO4lssIgXFEtdHbO6VsWDumQxf/1DjJ91G3emr0kcsMtsmJILrEerxTdnB4/ GjdpCyJKzVc9GHmxaDFeSyCOB6gosaM= X-Authority-Analysis: v=2.4 cv=LpcHWXdc c=1 sm=1 tr=0 ts=6ad7a200 cx=c_pps a=Uww141gWH0fZj/3QKPojxA==:117 a=fChuTYTh2wq5r3m49p7fHw==:17 a=IkcTkHD0fZMA:10 a=660iZSQnnn4A:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=ZpdpYltYx_vBUK5n70dp:22 a=EUspDBNiAAAA:8 a=Kz8-B0t5AAAA:8 a=IBHtUT0IppJqG8UD_bsA:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=PxkB5W3o20Ba91AHUih5:22 a=RuZk68QooNbwfxovefhk:22 X-Proofpoint-GUID: KnUOxaL0eVWqqs98hJy5u7ZG7pbMJSQG X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-10-20_03,2026-10-19_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 adultscore=0 phishscore=0 clxscore=1015 impostorscore=0 malwarescore=0 suspectscore=0 lowpriorityscore=0 bulkscore=0 priorityscore=1501 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2610190000 definitions=main-2610200068 On Fri, Oct 09, 2026 at 06:31:40PM +0800, Chaoyi Chen wrote: > Hi, > > On 10/8/2026 4:15 AM, mohit.dsor@oss.qualcomm.com wrote: > > From: Sunyun Yang > > > > LT9611C(EX/UXD) is an I2C-controlled chip that Receiver signal/dual port > > mipi dsi and output hdmi, differences in hardware features: > > > > Description missing here. Will fix it next version. > > > Signed-off-by: Sunyun Yang > > Co-developed-by: Mohit Dsor > > Signed-off-by: Mohit Dsor > > --- > > Documentation/ABI/testing/sysfs-lontium-firmware | 10 + > > drivers/gpu/drm/bridge/Kconfig | 17 + > > drivers/gpu/drm/bridge/Makefile | 1 + > > drivers/gpu/drm/bridge/lontium-lt9611c.c | 1360 ++++++++++++++++++++++ > > 4 files changed, 1388 insertions(+) > > > > diff --git a/Documentation/ABI/testing/sysfs-lontium-firmware b/Documentation/ABI/testing/sysfs-lontium-firmware > > new file mode 100644 > > index 000000000000..8700933ba0f0 > > --- /dev/null > > +++ b/Documentation/ABI/testing/sysfs-lontium-firmware > > @@ -0,0 +1,10 @@ > > +What: /sys/bus/i2c/drivers//.../firmware > > +Date: July 2026 > > +KernelVersion: 7.3 > > +Contact: Mohit Dsor > > +Description: > > + Read: Returns the current firmware version loaded on the > > + Lontium bridge chip in hex format (e.g. "0x0105"). > > + > > + Write: Triggers a firmware upgrade of the chip using the > > + firmware file loaded via the firmware loader. > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig > > index 4a57d49b4c6d..707ccdcffbd5 100644 > > --- a/drivers/gpu/drm/bridge/Kconfig > > +++ b/drivers/gpu/drm/bridge/Kconfig > > @@ -177,6 +177,23 @@ config DRM_LONTIUM_LT9611 > > HDMI signals > > Please say Y if you have such hardware. > > > > +config DRM_LONTIUM_LT9611C > > + tristate "Lontium LT9611C DSI/HDMI bridge" > > + select SND_SOC_HDMI_CODEC if SND_SOC > > + depends on OF && I2C > > + select CRC8 > > + select FW_LOADER > > + select DRM_KMS_HELPER > > + select DRM_MIPI_DSI > > + select DRM_DISPLAY_HELPER > > + select DRM_DISPLAY_HDMI_STATE_HELPER > > + select REGMAP_I2C > > + help > > + Driver for the Lontium LT9611C(EX/UXD) DSI to HDMI bridge chip. > > + It converts single or dual MIPI DSI and I2S signals to HDMI > > + output. Supports HDMI 1.4 (LT9611C/EX) and HDMI 2.0 (LT9611UXD). > > + Say Y if you have hardware using this chip. > > + > > config DRM_LONTIUM_LT9611UXC > > tristate "Lontium LT9611UXC DSI/HDMI bridge" > > select SND_SOC_HDMI_CODEC if SND_SOC > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile > > index 15cc821d85b7..f322f3f89f6b 100644 > > --- a/drivers/gpu/drm/bridge/Makefile > > +++ b/drivers/gpu/drm/bridge/Makefile > > @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o > > obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o > > obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o > > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o > > +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o > > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o > > obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o > > obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o > > diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c > > new file mode 100644 > > index 000000000000..6082c89b388b > > --- /dev/null > > +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c > > @@ -0,0 +1,1360 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright (C) 2026 Lontium Semiconductor, Inc. > > + * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries. > > + */ > > + > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +#define FW_SIZE (64 * 1024) > > +#define LT_PAGE_SIZE 256 > > +#define FW_FILE "Lontium/lt9611c_fw.bin" > > +#define LT9611C_CRC_POLYNOMIAL 0x31 > > +#define LT9611C_PAGE_CONTROL 0xff > > +#define LT9611C_INFOFRAME_MAX_SIZE 32 > > +#define LT9611C_CMD_HDR_SIZE 4 > > +#define LT9611C_CMD_Y0_SIZE 1 /* Y0 echo byte in ACK response */ > > +#define LT9611C_EDID_BUF_SIZE 32 > > + > > +struct lt9611c_cmd_hdr { > > + u8 func; > > + u8 type; > > + u8 seq; > > + u8 sep; > > +}; > > + > > +/* lt9611c_cmd_hdr.func values */ > > +#define LT9611C_FUNC_WRITE 0x57 /* 'W' */ > > +#define LT9611C_FUNC_READ 0x52 /* 'R' */ > > +#define LT9611C_FUNC_ACK 0x41 /* 'A' */ > > + > > +/* lt9611c_cmd_hdr.type values */ > > +#define LT9611C_TYPE_MIPI 0x4d /* 'M' */ > > +#define LT9611C_TYPE_LVDS 0x4c /* 'L' */ > > +#define LT9611C_TYPE_HDMI 0x48 /* 'H' */ > > +#define LT9611C_TYPE_AUDIO 0x41 /* 'A' */ > > +#define LT9611C_TYPE_CUSTOM 0x43 /* 'C' */ > > + > > +/* lt9611c_cmd_hdr.sep is always ':' */ > > +#define LT9611C_CMD_SEP 0x3a /* ':' */ > > + > > +struct lt9611c_cmd { > > + struct lt9611c_cmd_hdr hdr; > > + const u8 *data; > > + size_t data_len; > > +}; > > + > > +struct lt9611c_rsp { > > + struct lt9611c_cmd_hdr hdr; > > + u8 *data; > > + unsigned int data_len; > > +}; > > + > > +enum lt9611_chip_type { > > + CHIP_LT9611C = 0, > > + CHIP_LT9611EX, > > + CHIP_LT9611UXD, > > +}; > > + > > +struct lt9611c_chip_data { > > + enum lt9611_chip_type chip_type; > > + unsigned long long max_tmds_rate; > > +}; > > + > > +static const struct lt9611c_chip_data lt9611c_chip_data[] = { > > + [CHIP_LT9611C] = { CHIP_LT9611C, 340000000 }, > > + [CHIP_LT9611EX] = { CHIP_LT9611EX, 340000000 }, > > + [CHIP_LT9611UXD] = { CHIP_LT9611UXD, 600000000 }, > > +}; > > + > > +struct lt9611c { > > + struct device *dev; > > + struct i2c_client *client; > > + struct drm_bridge bridge; > > + struct regmap *regmap; > > + struct mutex mcu_lock; > > + struct work_struct work; > > + struct gpio_desc *reset_gpio; > > + struct regulator_bulk_data supplies[2]; > > + int fw_version; > > + const struct lt9611c_chip_data *cdata; > > + bool hdmi_connected; > > + bool dual_dsi; > > +}; > > + > > +DECLARE_CRC8_TABLE(lt9611c_crc8_table); > > + > > +static const struct regmap_range_cfg lt9611c_ranges[] = { > > + { > > + .name = "register_range", > > + .range_min = 0, > > + .range_max = 0xfe9c, > > + .selector_reg = LT9611C_PAGE_CONTROL, > > + .selector_mask = 0xff, > > + .selector_shift = 0, > > + .window_start = 0, > > + .window_len = 0x100, > > + }, > > +}; > > + > > +static const struct regmap_config lt9611c_regmap_config = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + .max_register = 0xfe9c, > > + .ranges = lt9611c_ranges, > > + .num_ranges = ARRAY_SIZE(lt9611c_ranges), > > +}; > > + > > +static int lt9611c_read_write_flow(struct lt9611c *lt9611c, > > + const struct lt9611c_cmd *cmd, > > + struct lt9611c_rsp *rsp) > > +{ > > + int ret; > > + unsigned int i; > > + unsigned int temp; > > + unsigned int max_params = 0xe0dd - 0xe0b0 + 1; > > + > > + regmap_write(lt9611c->regmap, 0xe0de, 0x01); > > + > > + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, > > + temp == 0x01, 1000, 200 * 1000); > > + if (ret) > > + return -ETIMEDOUT; > > + > > + ret = regmap_bulk_write(lt9611c->regmap, 0xe0b0, &cmd->hdr, > > + LT9611C_CMD_HDR_SIZE); > > + if (ret) > > + return ret; > > + > > + for (i = 0; cmd->data && i < cmd->data_len && > > + (LT9611C_CMD_HDR_SIZE + i) < max_params; i++) > > + regmap_write(lt9611c->regmap, > > + 0xe0b0 + LT9611C_CMD_HDR_SIZE + i, cmd->data[i]); > > + > > + regmap_write(lt9611c->regmap, 0xe0de, 0x02); > > + > > + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, > > + temp == 0x02, 1000, 200 * 1000); > > + if (ret) > > + return -ETIMEDOUT; > > + > > + ret = regmap_bulk_read(lt9611c->regmap, 0xe085, > > + &rsp->hdr, LT9611C_CMD_HDR_SIZE); > > + if (ret) > > + return ret; > > + > > + > > + if (rsp->data && rsp->data_len) > > + ret = regmap_bulk_read(lt9611c->regmap, > > + 0xe085 + LT9611C_CMD_HDR_SIZE, > > + rsp->data, rsp->data_len); > > + > > Could the offset address here be represented by a macro? Added inline comments (`/* MCU response buffer */` and `/* data follows header */`) to document the `0xe085` address and the `0xe085 + LT9611C_CMD_HDR_SIZE` offset. Adding separate macros for addresses used only once in a single function would add noise without improving readability. Also fixed the extra blank line. > > > + return ret; > > +} > > + > > +static void lt9611c_lock(struct lt9611c *lt9611c) > > +{ > > + mutex_lock(<9611c->mcu_lock); > > + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); > > +} > > + > > +static void lt9611c_unlock(struct lt9611c *lt9611c) > > +{ > > + regmap_write(lt9611c->regmap, 0xe0ee, 0x00); > > + mutex_unlock(<9611c->mcu_lock); > > +} > > + > > +static void lt9611c_config_parameters(struct lt9611c *lt9611c) > > +{ > > + const struct reg_sequence seq_write_paras[] = { > > + REG_SEQ0(0xe0ee, 0x01), > > + REG_SEQ0(0xe103, 0x3f), /* fifo rst */ > > + REG_SEQ0(0xe103, 0xff), > > + REG_SEQ0(0xe05e, 0xc1), > > + REG_SEQ0(0xe058, 0x00), > > + REG_SEQ0(0xe059, 0x50), > > + REG_SEQ0(0xe05a, 0x10), > > + REG_SEQ0(0xe05a, 0x00), > > + REG_SEQ0(0xe058, 0x21), > > + }; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras)); > > +} > > + > > +static void lt9611c_wren(struct lt9611c *lt9611c) > > +{ > > + regmap_write(lt9611c->regmap, 0xe05a, 0x04); > > + regmap_write(lt9611c->regmap, 0xe05a, 0x00); > > +} > > + > > +static void lt9611c_wrdi(struct lt9611c *lt9611c) > > +{ > > + regmap_write(lt9611c->regmap, 0xe05a, 0x08); > > + regmap_write(lt9611c->regmap, 0xe05a, 0x00); > > +} > > + > > Use the full spelling? For example, wr_enable/disable? Will fix it in next version. > > > +static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr) > > +{ > > + const struct reg_sequence seq_write[] = { > > + REG_SEQ0(0xe0ee, 0x01), > > + REG_SEQ0(0xe05a, 0x04), > > + REG_SEQ0(0xe05a, 0x00), > > + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), > > + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), > > + REG_SEQ0(0xe05d, addr & 0xff), > > + REG_SEQ0(0xe05a, 0x01), > > + REG_SEQ0(0xe05a, 0x00), > > + }; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); > > +} > > + > > +static unsigned int read_flash_reg_status(struct lt9611c *lt9611c) > > +{ > > + const struct reg_sequence seq_write[] = { > > + REG_SEQ0(0xe103, 0x3f), > > + REG_SEQ0(0xe103, 0xff), > > + REG_SEQ0(0xe05e, 0x40), > > + REG_SEQ0(0xe056, 0x05), > > + REG_SEQ0(0xe055, 0x25), > > + REG_SEQ0(0xe055, 0x01), > > + REG_SEQ0(0xe058, 0x21), > > + }; > > + unsigned int status; > > + int ret; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); > > + ret = regmap_read(lt9611c->regmap, 0xe05f, &status); > > + if (ret) > > + return 0xff; > > + > > + return status; > > +} > > + > > +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c) > > +{ > > + const struct reg_sequence seq_write[] = { > > + REG_SEQ0(0xe051, 0x00), > > + REG_SEQ0(0xe055, 0xc0), > > + REG_SEQ0(0xe055, 0x80), > > + REG_SEQ0(0xe05e, 0xc0), > > + REG_SEQ0(0xe058, 0x21), > > + }; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); > > +} > > + > > +static void lt9611c_data_to_sram(struct lt9611c *lt9611c) > > +{ > > + const struct reg_sequence seq_write[] = { > > + REG_SEQ0(0xe051, 0xff), > > + REG_SEQ0(0xe055, 0x80), > > + REG_SEQ0(0xe05e, 0xc0), > > + REG_SEQ0(0xe058, 0x21), > > + }; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); > > +} > > + > > +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, size_t addr) > > +{ > > + const struct reg_sequence seq_write[] = { > > + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), > > + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), > > + REG_SEQ0(0xe05d, addr & 0xff), > > + REG_SEQ0(0xe05a, 0x30), > > + REG_SEQ0(0xe05a, 0x00), > > + }; > > + > > + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); > > +} > > + > > +static int lt9611c_block_erase(struct lt9611c *lt9611c) > > +{ > > + struct device *dev = lt9611c->dev; > > + unsigned int block_num; > > + unsigned int flash_status = 0; > > + u32 flash_addr = 0; > > + int ret; > > + > > + for (block_num = 0; block_num < 2; block_num++) { > > + flash_addr = block_num * 0x008000; > > + lt9611c_erase_op(lt9611c, flash_addr); > > + msleep(100); > > + ret = read_poll_timeout(read_flash_reg_status, flash_status, > > + !(flash_status & 0x01), > > + 50 * USEC_PER_MSEC, 2500 * USEC_PER_MSEC, > > + false, lt9611c); > > + if (ret) { > > + dev_err(dev, "flash erase timeout for block %u\n", block_num); > > + return ret; > > + } > > + } > > + > > + return 0; > > +} > > + > > +static int lt9611c_write_data(struct lt9611c *lt9611c, const struct firmware *fw, size_t addr) > > +{ > > + struct device *dev = lt9611c->dev; > > + unsigned int npages; > > + unsigned int flash_status; > > + int ret; > > + size_t size = fw->size; > > + const u8 *data = fw->data; > > + > > + npages = DIV_ROUND_UP(size, LT_PAGE_SIZE); > > + if (npages * LT_PAGE_SIZE > FW_SIZE) { > > + dev_err(dev, "firmware size out of range\n"); > > + return -EINVAL; > > + } > > + > > + dev_dbg(dev, "%u pages, total size %zu byte\n", npages, size); > > + > > + for (unsigned int num = 0; num < npages; num++) { > > + lt9611c_data_to_sram(lt9611c); > > + > > + for (unsigned int i = 0; i < LT_PAGE_SIZE; i++) { > > + size_t index = num * LT_PAGE_SIZE + i; > > + u8 value = (index < size) ? data[index] : 0xff; > > + > > + ret = regmap_write(lt9611c->regmap, 0xe059, value); > > + if (ret < 0) { > > + dev_err(dev, "write error at page %u, index %u\n", num, i); > > + return ret; > > + } > > + } > > + > > + lt9611c_wren(lt9611c); > > Could this item be placed before the loop? lt9611c_wr_enable() cannot be moved before the outer loop. SPI flash controllers (which this chip's MCU uses internally) automatically clear the Write Enable Latch (WEL) after each program operation completes. Therefore, wr_enable must be called before each sram_to_flash() invocation to re-assert WEN for the next page. Moving it outside the loop would enable WEN only once; it would be cleared after the first page write, causing subsequent page programming operations to fail silently. > > > + lt9611c_sram_to_flash(lt9611c, addr); > > + > > + ret = read_poll_timeout(read_flash_reg_status, flash_status, > > + !(flash_status & 0x01), > > + 50 * USEC_PER_MSEC, 2500 * USEC_PER_MSEC, > > + false, lt9611c); > > + if (ret) { > > + dev_err(dev, "flash write timeout at page %u\n", num); > > + return ret; > > + } > > + > > + addr += LT_PAGE_SIZE; > > + } > > + > > + lt9611c_wrdi(lt9611c); > > + > > + return 0; > > +} > > + > > +static int lt9611c_write_crc(struct lt9611c *lt9611c, u8 fw_crc, size_t addr) > > +{ > > + struct device *dev = lt9611c->dev; > > + unsigned int flash_status; > > + int ret; > > + > > + lt9611c_crc_to_sram(lt9611c); > > + ret = regmap_write(lt9611c->regmap, 0xe059, fw_crc); > > + if (ret < 0) { > > + dev_err(dev, "failed to write crc\n"); > > + return ret; > > + } > > + > > + lt9611c_wren(lt9611c); > > + lt9611c_sram_to_flash(lt9611c, addr); > > + > > + ret = read_poll_timeout(read_flash_reg_status, flash_status, > > + !(flash_status & 0x01), > > + 50 * USEC_PER_MSEC, 2500 * USEC_PER_MSEC, > > + false, lt9611c); > > + if (ret) { > > + dev_err(dev, "flash crc write timeout\n"); > > + return ret; > > + } > > + > > + lt9611c_wrdi(lt9611c); > > + > > + dev_dbg(dev, "crc 0x%02x written to flash at addr 0x%zx\n", fw_crc, addr); > > + > > + return 0; > > +} > > + > > +static void lt9611c_reset(struct lt9611c *lt9611c) > > +{ > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); > > + usleep_range(10000, 12000); > > + > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); > > + msleep(400); > > +} > > + > > +static int lt9611c_upgrade_result(struct lt9611c *lt9611c, u8 fw_crc) > > +{ > > + struct device *dev = lt9611c->dev; > > + unsigned int crc_result; > > + int ret; > > + > > + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); > > + ret = regmap_read(lt9611c->regmap, 0xe021, &crc_result); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to read firmware crc\n"); > > + > > + if (crc_result != fw_crc) { > > + dev_err(dev, "lt9611c fw upgrade failed, expected crc=0x%02x, read crc=0x%02x\n", > > + fw_crc, crc_result); > > + return -EIO; > > + } > > + > > + dev_info(dev, "lt9611c firmware upgrade success, crc=0x%02x\n", crc_result); > > + return 0; > > +} > > + > > +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c) > > +{ > > + struct device *dev = lt9611c->dev; > > + const struct firmware *fw; > > + u8 *buffer; > > + size_t total_size = FW_SIZE - 1; > > use const? Will fix it in next version. > > > + u8 fw_crc; > > + int ret; > > + > > + ret = request_firmware(&fw, FW_FILE, dev); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE); > > + > > + if (fw->size > total_size) { > > + dev_err(dev, "firmware too large (%zu > %zu)\n", fw->size, total_size); > > + ret = -EINVAL; > > + goto out_release_fw; > > + } > > + dev_dbg(dev, "firmware size: %zu bytes\n", fw->size); > > + > > + buffer = kzalloc(total_size, GFP_KERNEL); > > + if (!buffer) { > > + ret = -ENOMEM; > > + goto out_release_fw; > > + } > > + > > + memcpy(buffer, fw->data, fw->size); > > + memset(buffer + fw->size, 0xff, total_size - fw->size); > > + > > + fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0); > > + kfree(buffer); > > + > > Actually, there's no need to allocate extra memory for CRC calculation: > > fw_crc = crc8(lt9611c_crc8_table, fw->data, fw->size, 0); > for (unsigned int i = 0; i < total_size - fw->size; i++) { > fw_crc = lt9611c_crc8_table[(fw_crc ^ 0xff) & 0xff]; > } Will fix it in next version. > > > > + dev_info(dev, "starting firmware upgrade, size: %zu bytes, crc: 0x%02x\n", > > + fw->size, fw_crc); > > + > > + /* hardware access requires mcu_lock */ > > + lt9611c_lock(lt9611c); > > + > > + lt9611c_config_parameters(lt9611c); > > + ret = lt9611c_block_erase(lt9611c); > > + if (ret < 0) > > + goto out_unlock; > > + > > + ret = lt9611c_write_data(lt9611c, fw, 0); > > + if (ret < 0) { > > + dev_err(dev, "failed to write firmware data\n"); > > + goto out_unlock; > > + } > > + > > + ret = lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1); > > Just use total_size? Will fix it in next versiob. > > > + if (ret < 0) { > > + dev_err(dev, "failed to write firmware crc\n"); > > + goto out_unlock; > > + } > > + > > + lt9611c_reset(lt9611c); > > + ret = lt9611c_upgrade_result(lt9611c, fw_crc); > > + > > +out_unlock: > > + lt9611c_unlock(lt9611c); > > +out_release_fw: > > + release_firmware(fw); > > + return ret; > > +} > > + > > +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge) > > +{ > > + return container_of(bridge, struct lt9611c, bridge); > > +} > > + > > +static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge) > > +{ > > + return container_of_const(bridge, struct lt9611c, bridge); > > +} > > + > > +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id) > > +{ > > + struct lt9611c *lt9611c = dev_id; > > + struct device *dev = lt9611c->dev; > > + int ret; > > + unsigned int irq_status; > > + > > + guard(mutex)(<9611c->mcu_lock); > > + > > + ret = regmap_read(lt9611c->regmap, 0xe084, &irq_status); > > + if (ret) { > > + dev_err(dev, "failed to read irq status: %d\n", ret); > > + return IRQ_HANDLED; > > + } > > + > > + if (!(irq_status & BIT(0))) > > + return IRQ_NONE; > > + > > + /* Clear interrupt: hardware requires two writes with delay */ > > + regmap_write(lt9611c->regmap, 0xe0df, BIT(0)); > > + usleep_range(10000, 12000); > > + regmap_write(lt9611c->regmap, 0xe0df, 0x00); > > + > > + schedule_work(<9611c->work); > > + > > + return IRQ_HANDLED; > > +} > > + > > +static void lt9611c_hpd_work(struct work_struct *work) > > +{ > > + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work); > > + struct device *dev = lt9611c->dev; > > + static const u8 hpd_data[] = { 0x00 }; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, > > + .data = hpd_data, > > + .data_len = 1, > > + }; > > + u8 hpd_status; > > + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; > > + bool connected; > > + int ret; > > + > > + /* Added delay as need time to reflect hpd after interrupt*/ > > + msleep(200); > > + > > Why is it 200 ms? Updated comment, it is needed as per test. > > > + mutex_lock(<9611c->mcu_lock); > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + if (ret) > > + dev_err(dev, "failed to read HPD status\n"); > > + else > > + lt9611c->hdmi_connected = (hpd_status == 0x02); > > + > > + connected = lt9611c->hdmi_connected; > > + mutex_unlock(<9611c->mcu_lock); > > + > > + drm_bridge_hpd_notify(<9611c->bridge, > > + connected ? connector_status_connected : > > + connector_status_disconnected); > > +} > > + > > +static int lt9611c_regulator_init(struct lt9611c *lt9611c) > > +{ > > + struct device *dev = lt9611c->dev; > > + int ret; > > + > > + lt9611c->supplies[0].supply = "vcc"; > > + lt9611c->supplies[1].supply = "vdd"; > > + > > + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies); > > ARRAY_SIZE(lt9611c->supplies) Will fix it in next version. > > > + if (ret) > > + return ret; > > + > > + return regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); > > +} > > + > > +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c, > > + struct device_node *dsi_node) > > +{ > > + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL }; > > + struct mipi_dsi_device *dsi; > > + struct mipi_dsi_host *host; > > + struct device *dev = lt9611c->dev; > > + int ret; > > + > > + host = of_find_mipi_dsi_host_by_node(dsi_node); > > + if (!host) > > + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n")); > > + > > + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); > > + if (IS_ERR(dsi)) > > + return ERR_PTR(dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi device\n")); > > + > > + dsi->lanes = 4; > > + dsi->format = MIPI_DSI_FMT_RGB888; > > + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | > > + MIPI_DSI_MODE_VIDEO_HSE; > > + > > + ret = devm_mipi_dsi_attach(dev, dsi); > > + if (ret < 0) > > + return ERR_PTR(dev_err_probe(dev, ret, "failed to attach dsi to host\n")); > > + > > + return dsi; > > +} > > + > > +static int lt9611c_bridge_attach(struct drm_bridge *bridge, > > + struct drm_encoder *encoder, > > + enum drm_bridge_attach_flags flags) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) > > + return -EINVAL; > > + > > + return drm_bridge_attach(encoder, lt9611c->bridge.next_bridge, bridge, flags); > > +} > > + > > +static enum drm_mode_status > > +lt9611c_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, > > + const struct drm_display_mode *mode, > > + unsigned long long tmds_rate) > > +{ > > + const struct lt9611c *lt9611c = bridge_to_lt9611c_const(bridge); > > + > > + if (tmds_rate > lt9611c->cdata->max_tmds_rate) > > + return MODE_CLOCK_HIGH; > > + > > + if (tmds_rate < 25000000) > > + return MODE_CLOCK_LOW; > > + > > + return MODE_OK; > > +} > > + > > +static void lt9611c_video_setup(struct lt9611c *lt9611c, > > + const struct drm_display_mode *mode) > > +{ > > + struct device *dev = lt9611c->dev; > > + int ret; > > + struct { > > + __be16 htotal; > > + __be16 hactive; > > + __be16 hfront_porch; > > + __be16 hsync_len; > > + __be16 hback_porch; > > + __be16 vtotal; > > + __be16 vactive; > > + __be16 vfront_porch; > > + __be16 vsync_len; > > + __be16 vback_porch; > > + u8 framerate; > > + u8 vic; > > + } timing_data; > > + struct lt9611c_rsp rsp = {}; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_MIPI, 0x33, LT9611C_CMD_SEP }, > > + .data = (u8 *)&timing_data, > > + .data_len = sizeof(timing_data), > > + }; > > + > > + guard(mutex)(<9611c->mcu_lock); > > + > > + timing_data.htotal = cpu_to_be16(mode->htotal); > > + timing_data.hactive = cpu_to_be16(mode->hdisplay); > > + timing_data.hfront_porch = cpu_to_be16(mode->hsync_start - mode->hdisplay); > > + timing_data.hsync_len = cpu_to_be16(mode->hsync_end - mode->hsync_start); > > + timing_data.hback_porch = cpu_to_be16(mode->htotal - mode->hsync_end); > > + timing_data.vtotal = cpu_to_be16(mode->vtotal); > > + timing_data.vactive = cpu_to_be16(mode->vdisplay); > > + timing_data.vfront_porch = cpu_to_be16(mode->vsync_start - mode->vdisplay); > > + timing_data.vsync_len = cpu_to_be16(mode->vsync_end - mode->vsync_start); > > + timing_data.vback_porch = cpu_to_be16(mode->vtotal - mode->vsync_end); > > + timing_data.framerate = drm_mode_vrefresh(mode); > > + timing_data.vic = drm_match_cea_mode(mode); > > + > > + dev_dbg(dev, "hactive=%d, vactive=%d\n", mode->hdisplay, mode->vdisplay); > > + dev_dbg(dev, "framerate=%d\n", timing_data.framerate); > > + dev_dbg(dev, "vic = 0x%02x\n", timing_data.vic); > > + > > Move mutex to here? Will fix it in next version. > > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + if (ret) > > + dev_err(dev, "video set failed\n"); > > +} > > + > > +/* > > + * Enables dual DSI port input on the LT9611UXD/EX. Must be called > > + * after video_setup when two DSI hosts are connected. > > + */ > > +static int lt9611c_dual_port_enable(struct lt9611c *lt9611c) > > +{ > > + static const u8 dual_port_data[] = { 0x02, 0x70 }; > > + struct device *dev = lt9611c->dev; > > + u8 ack = 0; > > + int ret; > > + struct lt9611c_rsp rsp = { .data = &ack, .data_len = 1 }; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_MIPI, 0x31, LT9611C_CMD_SEP }, > > + .data = dual_port_data, > > + .data_len = ARRAY_SIZE(dual_port_data), > > + }; > > + > > + guard(mutex)(<9611c->mcu_lock); > > + > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + if (ret || !ack) { > > + dev_err(dev, "failed to enable dual DSI port (err=%d)\n", ret); > > + return ret ? ret : -EIO; > > + } > > + > > + return 0; > > +} > > + > > +static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge, > > + struct drm_atomic_commit *state) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + struct drm_connector *connector; > > + struct drm_connector_state *conn_state; > > + struct drm_crtc_state *crtc_state; > > + struct drm_display_mode *mode; > > + int ret; > > + > > + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); > > + if (WARN_ON(!connector)) > > + return; > > + > > + conn_state = drm_atomic_get_new_connector_state(state, connector); > > + if (WARN_ON(!conn_state)) > > + return; > > + > > + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); > > + if (WARN_ON(!crtc_state)) > > + return; > > + > > + mode = &crtc_state->adjusted_mode; > > + > > + lt9611c_video_setup(lt9611c, mode); > > + > > + if (lt9611c->dual_dsi) { > > + ret = lt9611c_dual_port_enable(lt9611c); > > + if (ret) > > + dev_err(lt9611c->dev, "failed to enable dual DSI port (err=%d)\n", ret); > > + } > > + > > + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); > > +} > > + > > +static enum drm_connector_status > > +lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + struct device *dev = lt9611c->dev; > > + int ret; > > + bool connected = false; > > + static const u8 hpd_data[] = { 0x00 }; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, > > + .data = hpd_data, > > + .data_len = 1, > > + }; > > + u8 hpd_status; > > + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; > > + > > + guard(mutex)(<9611c->mcu_lock); > > + > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + if (ret) > > + dev_err(dev, "failed to read HPD status (err=%d)\n", ret); > > + else > > + connected = (hpd_status == 0x02); > > + > > + lt9611c->hdmi_connected = connected; > > + > > + return connected ? connector_status_connected : > > + connector_status_disconnected; > > +} > > + > > +static int lt9611c_get_edid_block(void *data, u8 *buf, > > + unsigned int block, size_t len) > > +{ > > + struct lt9611c *lt9611c = data; > > + struct device *dev = lt9611c->dev; > > + u8 edid_raw[LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE]; > > + u8 y0; > > + int ret, i, offset = 0; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x33, LT9611C_CMD_SEP }, > > + }; > > + struct lt9611c_rsp rsp = { > > + .data = edid_raw, > > + .data_len = LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE, > > + }; > > + > > + if (len != 128) > > + return -EINVAL; > > + guard(mutex)(<9611c->mcu_lock); > > + > > + for (i = 0; i < 4; i++) { > > + y0 = block * 4 + i; > > + cmd.data = &y0; > > + cmd.data_len = 1; > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + if (ret) { > > + dev_err(dev, "Failed to read EDID block %u packet %d\n", > > + block, i); > > + return ret; > > + } > > + memcpy(buf + offset, &edid_raw[LT9611C_CMD_Y0_SIZE], LT9611C_EDID_BUF_SIZE); > > + offset += LT9611C_EDID_BUF_SIZE; > > + } > > + > > + return 0; > > +} > > + > > +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge, > > + struct drm_connector *connector) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c); > > +} > > + > > +static int lt9611c_write_infoframe(struct lt9611c *lt9611c, u8 type, > > + const u8 *buffer, size_t len) > > +{ > > + u8 extra[1 + LT9611C_INFOFRAME_MAX_SIZE]; > > + struct lt9611c_rsp rsp = {}; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x35, LT9611C_CMD_SEP }, > > + }; > > + > > + if (WARN_ON(len > LT9611C_INFOFRAME_MAX_SIZE)) > > + return -EINVAL; > > + > > + extra[0] = type; > > + memcpy(&extra[1], buffer, len); > > + cmd.data = extra; > > + cmd.data_len = 1 + len; > > + > > + lockdep_assert_held(<9611c->mcu_lock); > > + > > + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > +} > > + > > +static int lt9611c_clear_infoframe(struct lt9611c *lt9611c, u8 type) > > +{ > > + u8 clear_data = type; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x42, LT9611C_CMD_SEP }, > > + .data = &clear_data, > > + .data_len = 1, > > + }; > > + struct lt9611c_rsp rsp = {}; > > + > > + lockdep_assert_held(<9611c->mcu_lock); > > + > > + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > +} > > + > > +static int lt9611c_hdmi_write_avi_infoframe(struct drm_bridge *bridge, > > + const u8 *buffer, size_t len) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_write_infoframe(lt9611c, 0x01, buffer, len); > > +} > > + > > +static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_clear_infoframe(lt9611c, 0x01); > > +} > > + > > +static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, > > + const u8 *buffer, size_t len) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_write_infoframe(lt9611c, 0x04, buffer, len); > > +} > > + > > +static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_clear_infoframe(lt9611c, 0x04); > > +} > > + > > +static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge, > > + const u8 *buffer, size_t len) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_write_infoframe(lt9611c, 0x02, buffer, len); > > +} > > + > > +static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + > > + guard(mutex)(<9611c->mcu_lock); > > + return lt9611c_clear_infoframe(lt9611c, 0x02); > > +} > > + > > +static int lt9611c_hdmi_audio_prepare(struct drm_bridge *bridge, > > + struct drm_connector *connector, > > + struct hdmi_codec_daifmt *fmt, > > + struct hdmi_codec_params *hparms) > > +{ > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); > > + u8 audio_extra[2]; > > + struct lt9611c_rsp rsp = {}; > > + int ret; > > + struct lt9611c_cmd cmd = { > > + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x36, LT9611C_CMD_SEP }, > > + .data = audio_extra, > > + .data_len = ARRAY_SIZE(audio_extra) }; > > + > > + if (hparms->sample_width == 32) > > + return -EINVAL; > > + > > + switch (fmt->fmt) { > > + case HDMI_I2S: > > + audio_extra[0] = 0x01; > > + break; > > + case HDMI_SPDIF: > > + audio_extra[0] = 0x02; > > + break; > > + default: > > + return -EINVAL; > > + } > > + > > + audio_extra[1] = hparms->channels; > > + > > + mutex_lock(<9611c->mcu_lock); > > Why not use guard(mutex)? drm_atomic_helper_connector_hdmi_update_audio_infoframe will also call a interactive cmd where lock is needed so it be a deadlock if lock not removed before it. > > > > + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); > > + mutex_unlock(<9611c->mcu_lock); > > + if (ret < 0) { > > + dev_err(lt9611c->dev, "set audio info failed!\n"); > > + return ret; > > + } > > + > > + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, > > + &hparms->cea); > > +} > > + > > +static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge, > > + struct drm_connector *connector) > > +{ > > + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); > > +} > > + > > +static const struct drm_bridge_funcs lt9611c_bridge_funcs = { > > + .attach = lt9611c_bridge_attach, > > + .detect = lt9611c_bridge_detect, > > + .edid_read = lt9611c_bridge_edid_read, > > + .atomic_enable = lt9611c_bridge_atomic_enable, > > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > > + .atomic_create_state = drm_atomic_helper_bridge_create_state, > > + > > + .hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid, > > + .hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe, > > + .hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe, > > + .hdmi_write_hdmi_infoframe = lt9611c_hdmi_write_hdmi_infoframe, > > + .hdmi_clear_hdmi_infoframe = lt9611c_hdmi_clear_hdmi_infoframe, > > + .hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe, > > + .hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe, > > + > > + .hdmi_audio_prepare = lt9611c_hdmi_audio_prepare, > > + .hdmi_audio_shutdown = lt9611c_hdmi_audio_shutdown, > > +}; > > + > > +static int lt9611c_parse_dt(struct device *dev, > > + struct lt9611c *lt9611c, > > + struct device_node **dsi0_node, > > + struct device_node **dsi1_node) > > +{ > > + int ret; > > + > > + *dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1); > > + if (!*dsi0_node) > > + return dev_err_probe(dev, -ENODEV, "failed to get remote node for primary dsi\n"); > > + > > + *dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); > > + > > + if (*dsi1_node && lt9611c->cdata->chip_type == CHIP_LT9611C) { > > + ret = dev_err_probe(dev, -EINVAL, > > + "LT9611C does not support dual DSI\n"); > > + goto err_put_dsi1; > > + } > > + > > + if (*dsi1_node) > > + lt9611c->dual_dsi = true; > > + > > + lt9611c->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 2, -1); > > + if (IS_ERR(lt9611c->bridge.next_bridge)) { > > + ret = PTR_ERR(lt9611c->bridge.next_bridge); > > + goto err_put_dsi1; > > + } > > + > > + return 0; > > + > > +err_put_dsi1: > > + of_node_put(*dsi1_node); > > + of_node_put(*dsi0_node); > > + return ret; > > +} > > + > > +static int lt9611c_read_version(struct lt9611c *lt9611c) > > +{ > > + u8 buf[2]; > > + int ret; > > + > > + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); > > + if (ret) > > + return ret; > > + > > + ret = regmap_bulk_read(lt9611c->regmap, 0xe080, buf, ARRAY_SIZE(buf)); > > + if (ret) > > + return ret; > > + > > + return (buf[0] << 8) | buf[1]; > > +} > > + > > +static int lt9611c_read_chipid(struct lt9611c *lt9611c) > > +{ > > + struct device *dev = lt9611c->dev; > > + u8 chipid[2]; > > + int ret; > > + > > + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); > > + if (ret) > > + return ret; > > + > > + ret = regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2); > > + if (ret) > > + return ret; > > + > > + if (chipid[0] != 0x23 || chipid[1] != 0x06) { > > + dev_err(dev, "ChipID: 0x%02x 0x%02x\n", chipid[0], chipid[1]); > > + return -ENODEV; > > + } > > + > > + return 0; > > +} > > + > > +static ssize_t firmware_store(struct device *dev, struct device_attribute *attr, > > + const char *buf, size_t len) > > +{ > > + struct lt9611c *lt9611c = dev_get_drvdata(dev); > > + int ret; > > + > > + dev_warn(dev, "starting firmware upgrade — display will be disrupted\n"); > > + > > + ret = lt9611c_firmware_upgrade(lt9611c); > > + if (ret < 0) { > > + dev_err(dev, "upgrade failure\n"); > > + return ret; > > + } > > + > > + lt9611c_lock(lt9611c); > > + WRITE_ONCE(lt9611c->fw_version, lt9611c_read_version(lt9611c)); > > + lt9611c_unlock(lt9611c); > > + > > + if (READ_ONCE(lt9611c->fw_version) < 0) > > + dev_warn(dev, "upgrade succeeded but failed to read new fw version\n"); > > + else > > + dev_info(dev, "firmware upgrade succeeded, version: 0x%04x\n", > > + READ_ONCE(lt9611c->fw_version)); > > + > > + return len; > > +} > > + > > +static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, char *buf) > > +{ > > + struct lt9611c *lt9611c = dev_get_drvdata(dev); > > + > > + return sysfs_emit(buf, "0x%04x\n", READ_ONCE(lt9611c->fw_version)); > > +} > > + > > +static DEVICE_ATTR_RW(firmware); > > + > > +static struct attribute *lt9611c_attrs[] = { > > + &dev_attr_firmware.attr, > > + NULL, > > +}; > > + > > +static const struct attribute_group lt9611c_attr_group = { > > + .attrs = lt9611c_attrs, > > +}; > > + > > +static const struct attribute_group *lt9611c_attr_groups[] = { > > + <9611c_attr_group, > > + NULL, > > +}; > > + > > +static int lt9611c_probe(struct i2c_client *client) > > +{ > > + struct lt9611c *lt9611c; > > + struct device *dev = &client->dev; > > + struct device_node *dsi0_node = NULL; > > + struct device_node *dsi1_node = NULL; > > + struct mipi_dsi_device *dsi; > > + bool fw_updated = false; > > + int ret; > > + > > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) > > + return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n"); > > + > > + lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_bridge_funcs); > > + if (IS_ERR(lt9611c)) > > + return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n"); > > + > > + lt9611c->dev = dev; > > + lt9611c->client = client; > > + i2c_set_clientdata(client, lt9611c); > > + > > + const struct lt9611c_chip_data *cdata = i2c_get_match_data(client); > > + > > + if (!cdata) > > + return dev_err_probe(dev, -EINVAL, "no match data for device\n"); > > + > > + lt9611c->cdata = cdata; > > + > > + ret = devm_mutex_init(dev, <9611c->mcu_lock); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to init mutex\n"); > > + > > + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config); > > + if (IS_ERR(lt9611c->regmap)) > > + return dev_err_probe(dev, PTR_ERR(lt9611c->regmap), "regmap i2c init failed\n"); > > + > > + ret = lt9611c_parse_dt(dev, lt9611c, &dsi0_node, &dsi1_node); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to parse device tree\n"); > > + > > + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); > > + if (IS_ERR(lt9611c->reset_gpio)) { > > + ret = PTR_ERR(lt9611c->reset_gpio); > > + goto err_put_nodes; > > + } > > + > > + ret = lt9611c_regulator_init(lt9611c); > > + if (ret < 0) > > + goto err_put_nodes; > > + > > + lt9611c_reset(lt9611c); > > + > > + lt9611c_lock(lt9611c); > > + > > + ret = lt9611c_read_chipid(lt9611c); > > + if (ret < 0) { > > + dev_err(dev, "failed to read chip id.\n"); > > + lt9611c_unlock(lt9611c); > > + goto err_disable_regulators; > > + } > > + > > +retry: > > + lt9611c->fw_version = lt9611c_read_version(lt9611c); > > + if (lt9611c->fw_version < 0) { > > + dev_err(dev, "failed to read fw version\n"); > > + ret = -EOPNOTSUPP; > > + lt9611c_unlock(lt9611c); > > + goto err_disable_regulators; > > + } else if (lt9611c->fw_version == 0) { > > + if (!fw_updated) { > > + fw_updated = true; > > + lt9611c_unlock(lt9611c); > > + ret = lt9611c_firmware_upgrade(lt9611c); > > + if (ret < 0) > > + goto err_disable_regulators; > > + lt9611c_lock(lt9611c); > > + goto retry; > > + > > + } else { > > + dev_err(dev, "fw version 0x%04x, update failed\n", lt9611c->fw_version); > > + ret = -EOPNOTSUPP; > > + lt9611c_unlock(lt9611c); > > + goto err_disable_regulators; > > + } > > + } > > + > > It might be worth extracting these logic into its own function. The firmware version check and upgrade path are already self-contained within the retry block. Extracting them into a separate function would introduce additional function-call overhead and a new function interface for logic that is used only once. Keeping the code inline avoids unnecessary indirection and keeps the control flow easy to follow. We can revisit refactoring if the probe path grows significantly in the future. > > > + lt9611c_unlock(lt9611c); > > + dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version); > > + > > + INIT_WORK(<9611c->work, lt9611c_hpd_work); > > + > > + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, > > + lt9611c_irq_thread_handler, > > + IRQF_TRIGGER_FALLING | > > + IRQF_ONESHOT | > > + IRQF_NO_AUTOEN, > > + "lt9611c", lt9611c); > > + if (ret) { > > + dev_err(dev, "failed to request irq\n"); > > + goto err_disable_regulators; > > + } > > + > > + lt9611c->bridge.of_node = client->dev.of_node; > > + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | > > + DRM_BRIDGE_OP_EDID | > > + DRM_BRIDGE_OP_HPD | > > + DRM_BRIDGE_OP_HDMI | > > + DRM_BRIDGE_OP_HDMI_AUDIO; > > + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA; > > + > > + lt9611c->bridge.vendor = "Lontium"; > > + lt9611c->bridge.product = "LT9611C"; > > + > > + lt9611c->bridge.hdmi_audio_dev = dev; > > + lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8; > > + lt9611c->bridge.hdmi_audio_dai_port = 2; > > + > > + drm_bridge_add(<9611c->bridge); > > + > > + /* Attach primary DSI */ > > + dsi = lt9611c_attach_dsi(lt9611c, dsi0_node); > > + if (IS_ERR(dsi)) { > > + ret = PTR_ERR(dsi); > > + goto err_remove_bridge; > > + } > > + > > + /* Attach secondary DSI, if specified */ > > + if (dsi1_node) { > > + dsi = lt9611c_attach_dsi(lt9611c, dsi1_node); > > + if (IS_ERR(dsi)) { > > + ret = PTR_ERR(dsi); > > + goto err_remove_bridge; > > + } > > + } > > + > > + lt9611c->hdmi_connected = false; > > + enable_irq(client->irq); > > + > > + of_node_put(dsi1_node); > > + of_node_put(dsi0_node); > > + > > + return 0; > > + > > +err_remove_bridge: > > + drm_bridge_remove(<9611c->bridge); > > + cancel_work_sync(<9611c->work); > > + > > +err_disable_regulators: > > + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); > > + > > +err_put_nodes: > > + of_node_put(dsi1_node); > > + of_node_put(dsi0_node); > > + > > + return ret; > > +} > > + > > +static void lt9611c_remove(struct i2c_client *client) > > +{ > > + struct lt9611c *lt9611c = i2c_get_clientdata(client); > > + > > + disable_irq(client->irq); > > + cancel_work_sync(<9611c->work); > > + drm_bridge_remove(<9611c->bridge); > > + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); > > +} > > + > > +static int lt9611c_bridge_suspend(struct device *dev) > > +{ > > + struct lt9611c *lt9611c = dev_get_drvdata(dev); > > + int ret; > > + > > + disable_irq(lt9611c->client->irq); > > + cancel_work_sync(<9611c->work); > > + > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); > > + > > + ret = regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); > > + if (ret) { > > + dev_err(lt9611c->dev, "regulator bulk disable failed.\n"); > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); > > + msleep(400); > > + enable_irq(lt9611c->client->irq); > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int lt9611c_bridge_resume(struct device *dev) > > +{ > > + struct lt9611c *lt9611c = dev_get_drvdata(dev); > > + int ret; > > + > > + ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); > > + if (ret) { > > + dev_err(lt9611c->dev, "regulator bulk enable failed.\n"); > > + return ret; > > + } > > + lt9611c_reset(lt9611c); > > + enable_irq(lt9611c->client->irq); > > + > > + return ret; > > +} > > + > > +static const struct dev_pm_ops lt9611c_bridge_pm_ops = { > > + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend, > > + lt9611c_bridge_resume) > > +}; > > + > > +static const struct i2c_device_id lt9611c_id[] = { > > + { .name = "lt9611c", .driver_data = (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611C] }, > > + { .name = "lt9611ex", .driver_data = (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611EX] }, > > + { .name = "lt9611uxd", .driver_data = (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611UXD] }, > > + { /* sentinel */ } > > +}; > > + > > +static const struct of_device_id lt9611c_match_table[] = { > > + { .compatible = "lontium,lt9611c", .data = <9611c_chip_data[CHIP_LT9611C] }, > > + { .compatible = "lontium,lt9611ex", .data = <9611c_chip_data[CHIP_LT9611EX] }, > > + { .compatible = "lontium,lt9611uxd", .data = <9611c_chip_data[CHIP_LT9611UXD] }, > > + { /* sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, lt9611c_match_table); > > + > > +static struct i2c_driver lt9611c_driver = { > > + .driver = { > > + .name = "lt9611c", > > + .of_match_table = lt9611c_match_table, > > + .pm = <9611c_bridge_pm_ops, > > + .dev_groups = lt9611c_attr_groups, > > + }, > > + .probe = lt9611c_probe, > > + .remove = lt9611c_remove, > > + .id_table = lt9611c_id, > > +}; > > + > > +static int __init lt9611c_init(void) > > +{ > > + crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL); > > + return i2c_add_driver(<9611c_driver); > > +} > > +module_init(lt9611c_init); > > + > > +static void __exit lt9611c_exit(void) > > +{ > > + i2c_del_driver(<9611c_driver); > > +} > > +module_exit(lt9611c_exit); > > + > > +MODULE_AUTHOR("SunYun Yang "); > > +MODULE_DESCRIPTION("Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver"); > > +MODULE_LICENSE("GPL"); > > +MODULE_FIRMWARE(FW_FILE); > > > > -- > Best, > Chaoyi