From nobody Fri Sep 25 07:22:59 2026 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6F6B13D6491 for ; Tue, 15 Sep 2026 12:34:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789475656; cv=none; b=PhaAWs5n2flBo96OCL24fQnKp/z3Nz0TdOvVBuLmcFhI5uOehItnEOo/Mp2fundbgXCoWWvy7Y9x3DBF2FphsKfipo6As7rhKx96f5sHZKosJBv8AzwN15k3GBoh0h8NgzQTlmxE51/OrJCujae/IMH0SDoM4mJccy6iHDwVl0w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789475656; c=relaxed/simple; bh=/wP/ifaUlX4YnTsryPY8ZpP52sJzH4xKyqmqoOC+tAg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ZwK/4jLanMUB8pX7Uk3OtSS0fx6phu7HLBBLUBii3FkHkgUstTB/DYnIbevNZJ1sO4tQxVlutu9ksoHazoRwLMQdjjNHKBdJ4rzls897c4sbVZBLeUOXy0UqyfQL9MS+wtyzJBC3YPfOLlpkqJP2Nv4wkE8sJnMonu+vNqhV6Yc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=DXR+c/Nk; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=WyiTdpat; arc=none smtp.client-ip=205.220.180.131 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) header.d=qualcomm.com header.i=@qualcomm.com header.b="DXR+c/Nk"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="WyiTdpat" Received: from pps.filterd (m0279871.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68F9hGnI3912064 for ; Tue, 15 Sep 2026 12:34:14 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= UUSdb7GhbdJGsEth6SJ34W7vHVCnsU2vlRUxl4txdnY=; b=DXR+c/NkTVTzGySv K85gvrkKea5T+0Rsz0qACMz0ple9+iYwbNAb0O/h+UnNmoUf0C3OSqyAxcMJYF1J L+hCZpJvC2Et8rdpSBm9zZ8/jVysadIv6pLi8vcEwqo+XBoSYmIq/ndoXVXngn1b ACh8rCD8r6dhkrI6vY2oLIDCJwhbuHnjxeSYTehmfsDMhdprvb/nNQndZvOYaUeD fUOmdXrYYQStdrs0S59cpereVClilrb3PzNzlrLH/n42SzlY+hSZs9FeYhdEmFFa 8LRPQiPzANq9+XhnzomR9dQqOkzmtVfv8RJlnZ1YCy4PyxsZNlLw4QVGE1xXES/C Kz0SCA== Received: from mail-pf1-f200.google.com (mail-pf1-f200.google.com [209.85.210.200]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4gq0jeshv9-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Tue, 15 Sep 2026 12:34:14 +0000 (GMT) Received: by mail-pf1-f200.google.com with SMTP id d2e1a72fcca58-8710450f731so848424b3a.0 for ; Tue, 15 Sep 2026 05:34:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1789475653; x=1790080453; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=UUSdb7GhbdJGsEth6SJ34W7vHVCnsU2vlRUxl4txdnY=; b=WyiTdpatqEkrpbzGFlvetHEBkDcAXJW07EIPBdY5vKa53iE1DxgaMfm+9EhfvgjnVT 8wcfv+R02in5SXUu715H5R2Xj4t57lMdtivU3NeIYNOSkvTLC7KDwmwVI4YxD2KlBdGK 6JVuDlm9uTW4XNEocG8+nDmPaxEVPkbgFqp59N/Mx6xVPfSXl4LDNkBO4mbChCteNo7V ZBCLzaoRmTwft6uprksCkSYqu9f4b3bFmkuoCGgvAsLwIUW6CPs08BHeLpeMyPQnYFqZ uHcflaZ1Fe2atlvQYuaSbj3ux0Cq+kDrX0YErY/7km/fl2e8c/o01GQPEUvNBQuoMHgD mB0Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789475653; x=1790080453; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=UUSdb7GhbdJGsEth6SJ34W7vHVCnsU2vlRUxl4txdnY=; b=2V8u2XgnN1Q0mFJbw2YzjbL4iX/HrV/QDMwTiURcIX5mMn+v5DIZRjMyLfVtkPteIg Q3pEcXeNosR7s8fvwxHC+bXhIExqtkODbkkKHa5U00NirBCFFUnlf6cKfWP3d4ZKlV6N HqN90i7Hk2+WNQvbh9wjr9HkQJWEMncjIPAZnIr3Nyar/ZsM3sQfyCEgftY11XjdOWFU nVinMd+cVVvlptxI7y9vrci/jXjV3rCh37ZlM5xR2ff00I5mgfZy9UfGOggtVRjrOsM1 +FrbVHilmbvzjc647PBkDBJM8hJCkdNRjdbv+NJ9nluTy7v3levEjuTEEEJYZRkFOcru 8aEA== X-Forwarded-Encrypted: i=1; AKwUvBzteteM2jvCATKZHrfEui/ujxzRDMrtsGIYNGZTycROEnh8tjtFdURSwE0HVphMIG1815o9+T9dRV8DtnE=@vger.kernel.org X-Gm-Message-State: AFuF++kc6K6kKkOt5xgqgWpXA1JN3q3g4alZaO4mUAl6xC447bv/mKDi a22xb2D+mB3z/+WdF+TjVYbg6oINzujOONMYrHcPVizakp1oTlNoxmCb0U7qxn/T29VqtUDLq8l HMgywtlCFXwpOitLLVHO2aLJufRBlcZnk8LwZqJrY2vZ6+PNaP+ZIqKb3/H4uc8dEfuA= X-Gm-Gg: AYBFou3GjNuxRE2TBUml8AQit5Y2oQEyGFcM+idebnjSe7Md5xfGbkcbfHnnR2aNUFF BxuUyu6ezwgGtV+oNkkjC7yYqgiSccfQtl1LNErewrVWwSKqafHd0VoDGnXl4Q9qz8XJ3TL5CvI 4LMazq6UbBaddjbRflzQkxb7pd6AvoZxQsMbHWDSk4/AsJIvi5SXwGNgXY/pOGNAGdTvW7yVHgH a5PduAt4xCzQ78CVfITg3Ehjbow4x23WAMbsLrmiXafaDAw+QAiMw9zvyOBKcuYaz5DJxFV7zUU oYEWJDANtv8V99umsR8TpqQOXXxaVUbeF21rAFmiQvnq5TsHw3+xmmp0CCLBfWCDqbPgfaEBhR3 BXtpiNjJimhOxVcnenSZY8rvDhg== X-Received: by 2002:a05:6a00:2e9a:b0:857:727c:a1f1 with SMTP id d2e1a72fcca58-87180de6625mr1343356b3a.19.1789475653278; Tue, 15 Sep 2026 05:34:13 -0700 (PDT) X-Received: by 2002:a05:6a00:2e9a:b0:857:727c:a1f1 with SMTP id d2e1a72fcca58-87180de6625mr1343249b3a.19.1789475652642; Tue, 15 Sep 2026 05:34:12 -0700 (PDT) Received: from hu-mdsor-hyd.qualcomm.com ([202.46.22.19]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-86b287c481asm6509975b3a.11.2026.09.15.05.34.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2026 05:34:12 -0700 (PDT) From: mohit.dsor@oss.qualcomm.com Date: Tue, 15 Sep 2026 18:03:50 +0530 Subject: [PATCH v15 1/2] dt-bindings: bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260915-lt9611c-v7-v15-1-88f9e9387eab@oss.qualcomm.com> References: <20260915-lt9611c-v7-v15-0-88f9e9387eab@oss.qualcomm.com> In-Reply-To: <20260915-lt9611c-v7-v15-0-88f9e9387eab@oss.qualcomm.com> To: Sunyun Yang , 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 Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Mohit Dsor , Krzysztof Kozlowski X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1789475639; l=2049; i=mohit.dsor@oss.qualcomm.com; s=20260808; h=from:subject:message-id; bh=ut+CndQz/jrWa/IvwRM0rWOCvN47R5Wgsw+GSTdue28=; b=IAPO8LsTqDXbz6623zZAU4eY+YP8m1qcI6DLsM0OMc2Fy0L17AW7/y2t6XnOGW6s1jOqrjQRE MQI6XXwG9DuC5xHPZi98Luh5eDhpPazd1zQVhr1qqaExOIWwOTGilKS X-Developer-Key: i=mohit.dsor@oss.qualcomm.com; a=ed25519; pk=outHVBN53JhozDw1xkj3G1qrqqE5scMBP7M4WL3aHQQ= X-Proofpoint-Spam-Info: AW1haW4tMjYwOTE1MDE4MiBTYWx0ZWRfXxSUJmwsY6KJ9 TrLK1M5GUOFDMzJgwIblvO81Zl8LkyiVUE9mp07M1Y7KxIJSYCXcaUgT7NUn7BCwxBSoiM1qDh8 p3m1DW0bnOu8dUE10arHzLMMbKAAQHc= X-Authority-Analysis: v=2.4 cv=Kr/YSmWN c=1 sm=1 tr=0 ts=6aa93b46 cx=c_pps a=mDZGXZTwRPZaeRUbqKGCBw==:117 a=fChuTYTh2wq5r3m49p7fHw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=3WHJM1ZQz_JShphwDgj5:22 a=gEfo2CItAAAA:8 a=Kz8-B0t5AAAA:8 a=EUspDBNiAAAA:8 a=VwQbUJbxAAAA:8 a=SdC2vBNBQt3qShdCGKMA:9 a=QEXdDO2ut3YA:10 a=zc0IvFSfCIW2DFIPzwfm:22 a=sptkURWiP4Gy88Gu7hUp:22 a=RuZk68QooNbwfxovefhk:22 X-Proofpoint-GUID: CpRGGpIazuBm34ddfXLjqNJtv_5xOmYA X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTE1MDE4MiBTYWx0ZWRfXw3b8jivjzIdi u18+eN3Of6lhDv2yQF8S3uIlhtLxmHGBV/RRE136xsThJ+iy2ElfMItont4n0J832sXV82nF/bX KNQ9oWMH57i/9sMlH7hZ5Xu0P7VLAxIhUBejHcxO4CEizSNF7hR9mA6/hL8bslyjlik64YIDDwt kzl/CYRyDFJFje73Cq7NYAbZPXntxCI+Tr1etHGu3uTO1Fv8/3Xv0lZ5zmbo3dhJ89eolWLxJdh 1vjMMulPkiac67zwhk9IY1BwlMZ+H5fdA7Tmy8Nw9oECIplgJhVChqls7u3bXyrzJKKf06eOyk+ gnUPf2eIfp+njKDdDynuXXa1l23SgNgggtUhkjFLz4YdbADzk8c/nlmAAzXranGTFUeAmgTs2wa vwy0nCEv5XumsXIHWd0Fnx02wh9tiE35u84RpS/O1rk3pMlWiVNRP7zFKI0w13SaSPlC2+zrUrf 00SVsDAjXXAWhcTuldQ== X-Proofpoint-ORIG-GUID: CpRGGpIazuBm34ddfXLjqNJtv_5xOmYA 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-09-15_03,2026-09-14_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 phishscore=0 bulkscore=0 suspectscore=0 priorityscore=1501 impostorscore=0 adultscore=0 lowpriorityscore=0 spamscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2609040000 definitions=main-2609150182 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: - LT9611C: supports 1-port mipi dsi to hdmi 1.4 - LT9611EX: supports 2-port mipi dsi to hdmi 1.4 - LT9611UXD: supports 2-port mipi dsi to hdmi 1.4/2.0 Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sunyun Yang Signed-off-by: Mohit Dsor --- .../bindings/display/bridge/lontium,lt9611.yaml | 18 ++++++++++++++= ++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt961= 1.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.ya= ml index 429a06057ae8..194aeaac601f 100644 --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml @@ -4,19 +4,23 @@ $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# =20 -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge =20 maintainers: - Vinod Koul =20 description: | - The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI + The LT9611, LT9611UXC, LT9611C, LT9611EX, and LT9611UXD + are bridge devices which convert DSI to HDMI =20 properties: compatible: enum: - lontium,lt9611 + - lontium,lt9611c + - lontium,lt9611ex - lontium,lt9611uxc + - lontium,lt9611uxd =20 reg: maxItems: 1 @@ -78,6 +82,16 @@ required: =20 allOf: - $ref: /schemas/sound/dai-common.yaml# + - if: + properties: + compatible: + contains: + const: lontium,lt9611c + then: + properties: + ports: + properties: + port@1: false =20 unevaluatedProperties: false =20 --=20 2.34.1 From nobody Fri Sep 25 07:22:59 2026 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3C4A83B6344 for ; Tue, 15 Sep 2026 12:34:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789475665; cv=none; b=mhGzICZOZv2/75+KwbTN1o48fMW/afRfSL6dv53zlzSDDAYIkXmuhj62aFGa1vxnShpqsyOq54CPEUAF6CIhDja7DzzH8nQxVpStQbIwNLPT6BrvpFoQfuNn/DPgGOiPGSDgeY3r/HKaXJkKgdVl4u2F3/BE2cQ2mx8vGhw9VRU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789475665; c=relaxed/simple; bh=OWwUPJa9AeXxf5P1yP2VjoiGZuVThFz/hyJ+UASJ0Ig=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Zvtd+rfHtLHi432QEBxMTnbw98KxY26/p7HB7dLGq0CPn9Xumid2rTdFPCZGCybHg2e6QNzK6a3/yC/GXArJD98qPnFobYzhYwQH4B4LrI+jd0g8caZd7UT5lvFj54AxONv4UIwOGWFz89b21jLPn4vU4741f/3vpbTG8flfmRU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=Qp25qyoH; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=B0Bfr6AU; arc=none smtp.client-ip=205.220.180.131 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) header.d=qualcomm.com header.i=@qualcomm.com header.b="Qp25qyoH"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="B0Bfr6AU" Received: from pps.filterd (m0279871.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 68F9hEZa3911947 for ; Tue, 15 Sep 2026 12:34:22 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= NzvypJC1FDK1gCep5xJhO122dUw/3uzttIatcE8+pSM=; b=Qp25qyoHmtvIetSV 2lnJN6Nc54IYWhe7uRY6O7pPK6HrHUK16QwyHgkPBJDCisvN49uA2MHg5cD8ds8H sHKENjSv4bdiLS6SAocDlucVi4UMRkgjEyAdUCK5JNHV4D4lxeFtW2lsxolQkXW2 j3oGDxWwSCezDItLUHeeoTQXJE5TmZkvg1q9ysqJAHy9e1eJs8IUr3yyMd1REwqu DYR2+/dq3uSexZoOYo8voiH2Kw7vT0OHo50whXUMuv4FSTURls0nSqGN3YSgTTkb 2q9xJf2l161DU/oyz7uYheBp6gtu4yS74WENnmyRbJiyxRdns/gKMSO/wHNb98sp ZP1KFA== Received: from mail-pf1-f200.google.com (mail-pf1-f200.google.com [209.85.210.200]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4gq0jeshvs-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Tue, 15 Sep 2026 12:34:21 +0000 (GMT) Received: by mail-pf1-f200.google.com with SMTP id d2e1a72fcca58-8633c0d7353so5693176b3a.3 for ; Tue, 15 Sep 2026 05:34:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1789475661; x=1790080461; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=NzvypJC1FDK1gCep5xJhO122dUw/3uzttIatcE8+pSM=; b=B0Bfr6AUnwqSCJ01oCEI9gGA4mfDH7RC8m4EIiplxRHk0v5FTfln895CNi1Z5csbvh Nx0YAq8L4fxwpppw+jCG0rYVhQdb6bUG3w+YVDgosgCxCQ/XfH65b3sNy+Z/RCDi/HXX HHYwrhCNweZQttdla37gIK+0XeKCsxBIwDaI8oJIIYz3t44hPyF9+qfa9TIuCA93kIZT 9/Shr60pwLiFh0z+jKCeZx9G4Ggyq6dZpTEKabeH0dVcdLhgmopnL2Te/eph7TXUCH88 hqA43aH6NVJaKJIm9Lcmfs7v41Zs/wvyPfd3HPe81NJPoXSp0ojHvTFbfQhNEjU4eg+t rNLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789475661; x=1790080461; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=NzvypJC1FDK1gCep5xJhO122dUw/3uzttIatcE8+pSM=; b=Pk6SGfYW1iV91Ca5aARjzC0DG5Y4qL11oFzFpvkTt4QQ2Jcs3Z/UdFRnlwTw2Xp5dT xgkzUi7wYy6OYW87MOFr8cnc6ZukbcUAHK5VXYbdK8zyk9BB0ne8cwIn/daLD97dkq/B JgrEPri1A282cv3sv9nk+WQjLWelSwL08HJKyL+txYu2OLLSr7j0aR1o5eQKodioIJOR 7W6BS0D10hQOff/rrTCP+RtkoKh5kWvnc1uFDZSqeVV3QZ1DJ4o7fDOs90dYxK9LPJqw 6+pYsWmiwBEAUwAgs2GhlGSJIdshbhTnE6UtgMvv2O7NIutFyeXRRpJ+4iAic1stCcNB tJ0A== X-Forwarded-Encrypted: i=1; AKwUvBxWFvT5vT9BpIr4lZ5DM45zoalgY3rC8hlYc8XCr0/7Ur2yQhNNdL+3T3xXqwHM6MKdfIsCfeLwHmSBK6A=@vger.kernel.org X-Gm-Message-State: AFuF++m5YEbPlwwaWJn3Wh8qZNDsYTUdglgReo1WTWUeh7KRWUkAsGb4 IqYHE38KIaO01FIFJUi4L198ILYV2emsD5jE4iC2h2iM/gYerqEQ7Es3+dS0ObrtQ5YIRXIt1ur H//2Br1+KqT2uGjsVmOgUfeM7fIemccla7HrM5kVY1bXdgrFVDXo4ocarhc8RlRvwK5I= X-Gm-Gg: AYBFou3EBCzzJhjFWIPWkkSzT3RRrWDoP1zDgLitqYlPQyLzx+Q1sMzB+rg5FINEvVv hHbKWFN0oB8IGPT6U5wXn7e7/37CSW4jHVi7j25XXmhLoQUw0yuEb3YPa2EZn1TAVgeyPRsHicC dER7B6EEdSGWW/f1hrTZxQXV686W2dx02w8z1S2DqTc8o9QcsBY+V6kEAxPlmhN2on4g17NqIGj 7dln4EOBpycUBdMOGk9RAmdyQltadWMB3B0HQdlRH/uZ7CVVckyAOph1lGYUfJnC6Z2MD3MpJgz ana2lNyGEtmlxLbm8g0wIp+QQ8zu8StuL+jtmLeOWnBGnwhxwa4RsDny3qrG1yfXMiRrNIwrTtO AExEAjP/7RtwBddh5gsaHmoNmOw== X-Received: by 2002:a05:6a00:98f:b0:84f:77cc:63cd with SMTP id d2e1a72fcca58-8717f5bafcamr1268192b3a.17.1789475660025; Tue, 15 Sep 2026 05:34:20 -0700 (PDT) X-Received: by 2002:a05:6a00:98f:b0:84f:77cc:63cd with SMTP id d2e1a72fcca58-8717f5bafcamr1268098b3a.17.1789475659282; Tue, 15 Sep 2026 05:34:19 -0700 (PDT) Received: from hu-mdsor-hyd.qualcomm.com ([202.46.22.19]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-86b287c481asm6509975b3a.11.2026.09.15.05.34.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 15 Sep 2026 05:34:18 -0700 (PDT) From: mohit.dsor@oss.qualcomm.com Date: Tue, 15 Sep 2026 18:03:51 +0530 Subject: [PATCH v15 2/2] drm/bridge: Add Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260915-lt9611c-v7-v15-2-88f9e9387eab@oss.qualcomm.com> References: <20260915-lt9611c-v7-v15-0-88f9e9387eab@oss.qualcomm.com> In-Reply-To: <20260915-lt9611c-v7-v15-0-88f9e9387eab@oss.qualcomm.com> To: Sunyun Yang , 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 Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Mohit Dsor , Dmitry Baryshkov X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1789475639; l=40585; i=mohit.dsor@oss.qualcomm.com; s=20260808; h=from:subject:message-id; bh=PtBEjsRF8qGeIPEASPUOowzRoOvGkeoJWuJBDyUpoKc=; b=mjTcH5LtVxthXzvRAlRRwjX3BhwLL4oIA2jkn9BvGRhHlUE6et3VE0ErOqWFeQ9nacAfQPqcT oIt8djDxrnzBHjyw9jPlbIse0N0dtmj4XNAPpEHObHbjfQ+6p0lH/do X-Developer-Key: i=mohit.dsor@oss.qualcomm.com; a=ed25519; pk=outHVBN53JhozDw1xkj3G1qrqqE5scMBP7M4WL3aHQQ= X-Proofpoint-Spam-Info: AW1haW4tMjYwOTE1MDE4MiBTYWx0ZWRfX+tLZDB3ulAln gkuT6QFi66xIvHOZOa01cUDv5QsSkrHsOO+f/TOkBwOGsurdbmsc17k2O/oQ/HFUtbRgEcchP03 079W9DT7SLlXdq7vc63dizmiJrpwqfc= X-Authority-Analysis: v=2.4 cv=Kr/YSmWN c=1 sm=1 tr=0 ts=6aa93b4d cx=c_pps a=mDZGXZTwRPZaeRUbqKGCBw==:117 a=fChuTYTh2wq5r3m49p7fHw==:17 a=IkcTkHD0fZMA:10 a=VdqzKS8jKosA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=3WHJM1ZQz_JShphwDgj5:22 a=Kz8-B0t5AAAA:8 a=EUspDBNiAAAA:8 a=pZRRnxfUaOvZTz4fmkIA:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=zc0IvFSfCIW2DFIPzwfm:22 a=RuZk68QooNbwfxovefhk:22 X-Proofpoint-GUID: 7eSWDNJenh0YD6hjSbhHj1Hc3CXU1_q8 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwOTE1MDE4MiBTYWx0ZWRfX4zMOEgYxePeR z01kGLPPV+pt9OufITflkP0nTrdzvP9vBKbMWAEb2H1IPE/OuQQOfJ4fHIxEEInNrD1VlQAo+2F hM6tt07Osw3YPHTbrXnBeB3TwrJcgYaIAs2wXlSrqHKrxluR9ebw5Oq0JOU2fK/8d/iolSm6Cvi 8lJ0LnJJK/yqoajrYTM9h4DC63t0+NiewEREUarUyqYmYC2G6lEn4ueAzvzQT954Mlbkh9+gXPg sijCG+u1ZKuEABLf+NHmrt7VoXctnnKtOtRtJ8DHX7ApVgEsaulM1c3DKsazyG/Ri8xhtABdklc tEGOjTBDTvkGc54/BcPQxfeHq3Xa4VlIZE3mrs17OmL30GnFCVnUIiGIG7KRQaRkahJ2loq558I lM+Cisz+3kFERXv/eyE3zx6rmSBWfTUX8nF/9NHe63y6FFxt4l9Bwiv4z/Z4MGoqxTKYsa0Li8X AAxQaT7CBS6GAaQ5cHQ== X-Proofpoint-ORIG-GUID: 7eSWDNJenh0YD6hjSbhHj1Hc3CXU1_q8 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-09-15_03,2026-09-14_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 phishscore=0 bulkscore=0 suspectscore=0 priorityscore=1501 impostorscore=0 adultscore=0 lowpriorityscore=0 spamscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2609040000 definitions=main-2609150182 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: Reviewed-by: Dmitry Baryshkov 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 | 1319 ++++++++++++++++++= ++++ 4 files changed, 1347 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-lontium-firmware b/Documentati= on/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. =20 +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/Makef= ile 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) +=3D ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) +=3D lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) +=3D lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) +=3D lontium-lt9611.o +obj-$(CONFIG_DRM_LONTIUM_LT9611C) +=3D lontium-lt9611c.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) +=3D lontium-lt9611uxc.o obj-$(CONFIG_DRM_LONTIUM_LT8713SX) +=3D lontium-lt8713sx.o obj-$(CONFIG_DRM_LVDS_CODEC) +=3D lvds-codec.o diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bri= dge/lontium-lt9611c.c new file mode 100644 index 000000000000..2f80afa7c630 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c @@ -0,0 +1,1319 @@ +// 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 =3D 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[] =3D { + [CHIP_LT9611C] =3D { CHIP_LT9611C, 340000000 }, + [CHIP_LT9611EX] =3D { CHIP_LT9611EX, 340000000 }, + [CHIP_LT9611UXD] =3D { 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; +}; + +DECLARE_CRC8_TABLE(lt9611c_crc8_table); + +static const struct regmap_range_cfg lt9611c_ranges[] =3D { + { + .name =3D "register_range", + .range_min =3D 0, + .range_max =3D 0xfe9c, + .selector_reg =3D LT9611C_PAGE_CONTROL, + .selector_mask =3D 0xff, + .selector_shift =3D 0, + .window_start =3D 0, + .window_len =3D 0x100, + }, +}; + +static const struct regmap_config lt9611c_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D 0xfe9c, + .ranges =3D lt9611c_ranges, + .num_ranges =3D 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 =3D 0xe0dd - 0xe0b0 + 1; + + regmap_write(lt9611c->regmap, 0xe0de, 0x01); + + ret =3D regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp =3D=3D 0x01, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret =3D regmap_bulk_write(lt9611c->regmap, 0xe0b0, &cmd->hdr, + LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + for (i =3D 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 =3D regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp =3D=3D 0x02, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret =3D regmap_bulk_read(lt9611c->regmap, 0xe085, + &rsp->hdr, LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + + if (rsp->data && rsp->data_len) + ret =3D regmap_bulk_read(lt9611c->regmap, + 0xe085 + LT9611C_CMD_HDR_SIZE, + rsp->data, rsp->data_len); + + 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[] =3D { + 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_w= rite_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); +} + +static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr) +{ + const struct reg_sequence seq_write[] =3D { + 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[] =3D { + 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 =3D 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[] =3D { + 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[] =3D { + 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[] =3D { + 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 =3D lt9611c->dev; + unsigned int block_num; + unsigned int flash_status =3D 0; + u32 flash_addr =3D 0; + int ret; + + for (block_num =3D 0; block_num < 2; block_num++) { + flash_addr =3D block_num * 0x008000; + lt9611c_erase_op(lt9611c, flash_addr); + msleep(100); + ret =3D 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 firmwa= re *fw, size_t addr) +{ + struct device *dev =3D lt9611c->dev; + unsigned int npages; + unsigned int flash_status; + int ret; + size_t size =3D fw->size; + const u8 *data =3D fw->data; + + npages =3D 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 =3D 0; num < npages; num++) { + lt9611c_data_to_sram(lt9611c); + + for (unsigned int i =3D 0; i < LT_PAGE_SIZE; i++) { + size_t index =3D num * LT_PAGE_SIZE + i; + u8 value =3D (index < size) ? data[index] : 0xff; + + ret =3D 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); + lt9611c_sram_to_flash(lt9611c, addr); + + ret =3D 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 +=3D LT_PAGE_SIZE; + } + + lt9611c_wrdi(lt9611c); + + return 0; +} + +static int lt9611c_write_crc(struct lt9611c *lt9611c, u8 fw_crc, size_t ad= dr) +{ + struct device *dev =3D lt9611c->dev; + unsigned int flash_status; + int ret; + + lt9611c_crc_to_sram(lt9611c); + ret =3D 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 =3D 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 =3D lt9611c->dev; + unsigned int crc_result; + int ret; + + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + ret =3D regmap_read(lt9611c->regmap, 0xe021, &crc_result); + if (ret) + return dev_err_probe(dev, ret, "failed to read firmware crc\n"); + + if (crc_result !=3D fw_crc) { + dev_err(dev, "lt9611c fw upgrade failed, expected crc=3D0x%02x, read crc= =3D0x%02x\n", + fw_crc, crc_result); + return -EIO; + } + + dev_info(dev, "lt9611c firmware upgrade success, crc=3D0x%02x\n", crc_res= ult); + return 0; +} + +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c) +{ + struct device *dev =3D lt9611c->dev; + const struct firmware *fw; + u8 *buffer; + size_t total_size =3D FW_SIZE - 1; + u8 fw_crc; + int ret; + + ret =3D 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 =3D -EINVAL; + goto out_release_fw; + } + dev_dbg(dev, "firmware size: %zu bytes\n", fw->size); + + buffer =3D kzalloc(total_size, GFP_KERNEL); + if (!buffer) { + ret =3D -ENOMEM; + goto out_release_fw; + } + + memcpy(buffer, fw->data, fw->size); + memset(buffer + fw->size, 0xff, total_size - fw->size); + + fw_crc =3D crc8(lt9611c_crc8_table, buffer, total_size, 0); + kfree(buffer); + + 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 =3D lt9611c_block_erase(lt9611c); + if (ret < 0) + goto out_unlock; + + ret =3D lt9611c_write_data(lt9611c, fw, 0); + if (ret < 0) { + dev_err(dev, "failed to write firmware data\n"); + goto out_unlock; + } + + ret =3D lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1); + if (ret < 0) { + dev_err(dev, "failed to write firmware crc\n"); + goto out_unlock; + } + + lt9611c_reset(lt9611c); + ret =3D 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_brid= ge *bridge) +{ + return container_of_const(bridge, struct lt9611c, bridge); +} + +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611c *lt9611c =3D dev_id; + struct device *dev =3D lt9611c->dev; + int ret; + unsigned int irq_status; + + guard(mutex)(<9611c->mcu_lock); + + ret =3D 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, irq_status & BIT(0)); + usleep_range(10000, 12000); + regmap_write(lt9611c->regmap, 0xe0df, irq_status & (~BIT(0))); + + schedule_work(<9611c->work); + + return IRQ_HANDLED; +} + +static void lt9611c_hpd_work(struct work_struct *work) +{ + struct lt9611c *lt9611c =3D container_of(work, struct lt9611c, work); + struct device *dev =3D lt9611c->dev; + static const u8 hpd_data[] =3D { 0x00 }; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data =3D hpd_data, + .data_len =3D 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp =3D { .data =3D &hpd_status, .data_len =3D 1 }; + bool connected; + int ret; + + /* Added delay as need time to reflect hpd after interrupt*/ + msleep(200); + + mutex_lock(<9611c->mcu_lock); + ret =3D lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status\n"); + else + lt9611c->hdmi_connected =3D (hpd_status =3D=3D 0x02); + + connected =3D 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 =3D lt9611c->dev; + int ret; + + lt9611c->supplies[0].supply =3D "vcc"; + lt9611c->supplies[1].supply =3D "vdd"; + + ret =3D devm_regulator_bulk_get(dev, 2, lt9611c->supplies); + if (ret) + return ret; + + return regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supp= lies); +} + +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info =3D { "lt9611c", 0, NULL }; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct device *dev =3D lt9611c->dev; + int ret; + + host =3D of_find_mipi_dsi_host_by_node(dsi_node); + if (!host) + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi hos= t\n")); + + dsi =3D 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 de= vice\n")); + + dsi->lanes =3D 4; + dsi->format =3D MIPI_DSI_FMT_RGB888; + dsi->mode_flags =3D MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret =3D 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 =3D bridge_to_lt9611c(bridge); + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + return drm_bridge_attach(encoder, lt9611c->bridge.next_bridge, bridge, fl= ags); +} + +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 =3D 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 =3D 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 =3D {}; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_WRITE, LT9611C_TYPE_MIPI, 0x33, LT9611C_CMD_SEP = }, + .data =3D (u8 *)&timing_data, + .data_len =3D sizeof(timing_data), + }; + + guard(mutex)(<9611c->mcu_lock); + + timing_data.htotal =3D cpu_to_be16(mode->htotal); + timing_data.hactive =3D cpu_to_be16(mode->hdisplay); + timing_data.hfront_porch =3D cpu_to_be16(mode->hsync_start - mode->hdispl= ay); + timing_data.hsync_len =3D cpu_to_be16(mode->hsync_end - mode->hsync_sta= rt); + timing_data.hback_porch =3D cpu_to_be16(mode->htotal - mode->hsync_end); + timing_data.vtotal =3D cpu_to_be16(mode->vtotal); + timing_data.vactive =3D cpu_to_be16(mode->vdisplay); + timing_data.vfront_porch =3D cpu_to_be16(mode->vsync_start - mode->vdispl= ay); + timing_data.vsync_len =3D cpu_to_be16(mode->vsync_end - mode->vsync_sta= rt); + timing_data.vback_porch =3D cpu_to_be16(mode->vtotal - mode->vsync_end); + timing_data.framerate =3D drm_mode_vrefresh(mode); + timing_data.vic =3D drm_match_cea_mode(mode); + + dev_dbg(dev, "hactive=3D%d, vactive=3D%d\n", mode->hdisplay, mode->vdispl= ay); + dev_dbg(dev, "framerate=3D%d\n", timing_data.framerate); + dev_dbg(dev, "vic =3D 0x%02x\n", timing_data.vic); + + ret =3D lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "video set failed\n"); +} + +static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct lt9611c *lt9611c =3D 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; + + connector =3D drm_atomic_get_new_connector_for_encoder(state, bridge->enc= oder); + if (WARN_ON(!connector)) + return; + + conn_state =3D drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state =3D drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + mode =3D &crtc_state->adjusted_mode; + + lt9611c_video_setup(lt9611c, mode); + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); +} + +static enum drm_connector_status +lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *con= nector) +{ + struct lt9611c *lt9611c =3D bridge_to_lt9611c(bridge); + struct device *dev =3D lt9611c->dev; + int ret; + bool connected =3D false; + static const u8 hpd_data[] =3D { 0x00 }; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data =3D hpd_data, + .data_len =3D 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp =3D { .data =3D &hpd_status, .data_len =3D 1 }; + + guard(mutex)(<9611c->mcu_lock); + + ret =3D lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status (err=3D%d)\n", ret); + else + connected =3D (hpd_status =3D=3D 0x02); + + lt9611c->hdmi_connected =3D 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 =3D data; + struct device *dev =3D lt9611c->dev; + u8 edid_raw[LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE]; + u8 y0; + int ret, i, offset =3D 0; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x33, LT9611C_CMD_SEP }, + }; + struct lt9611c_rsp rsp =3D { + .data =3D edid_raw, + .data_len =3D LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE, + }; + + if (len !=3D 128) + return -EINVAL; + guard(mutex)(<9611c->mcu_lock); + + for (i =3D 0; i < 4; i++) { + y0 =3D block * 4 + i; + cmd.data =3D &y0; + cmd.data_len =3D 1; + ret =3D 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_SI= ZE); + offset +=3D 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 =3D 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 =3D {}; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x35, LT9611C_CMD_SEP = }, + }; + + if (WARN_ON(len > LT9611C_INFOFRAME_MAX_SIZE)) + return -EINVAL; + + extra[0] =3D type; + memcpy(&extra[1], buffer, len); + cmd.data =3D extra; + cmd.data_len =3D 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 =3D type; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x42, LT9611C_CMD_SEP = }, + .data =3D &clear_data, + .data_len =3D 1, + }; + struct lt9611c_rsp rsp =3D {}; + + 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D bridge_to_lt9611c(bridge); + u8 audio_extra[2]; + struct lt9611c_rsp rsp =3D {}; + int ret; + struct lt9611c_cmd cmd =3D { + .hdr =3D { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x36, LT9611C_CMD_SEP = }, + .data =3D audio_extra, + .data_len =3D ARRAY_SIZE(audio_extra) }; + + if (hparms->sample_width =3D=3D 32) + return -EINVAL; + + switch (fmt->fmt) { + case HDMI_I2S: + audio_extra[0] =3D 0x01; + break; + case HDMI_SPDIF: + audio_extra[0] =3D 0x02; + break; + default: + return -EINVAL; + } + + audio_extra[1] =3D hparms->channels; + + mutex_lock(<9611c->mcu_lock); + ret =3D 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 =3D { + .attach =3D lt9611c_bridge_attach, + .detect =3D lt9611c_bridge_detect, + .edid_read =3D lt9611c_bridge_edid_read, + .atomic_enable =3D lt9611c_bridge_atomic_enable, + .atomic_duplicate_state =3D drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state =3D drm_atomic_helper_bridge_destroy_state, + .atomic_create_state =3D drm_atomic_helper_bridge_create_state, + + .hdmi_tmds_char_rate_valid =3D lt9611c_hdmi_tmds_char_rate_valid, + .hdmi_write_avi_infoframe =3D lt9611c_hdmi_write_avi_infoframe, + .hdmi_clear_avi_infoframe =3D lt9611c_hdmi_clear_avi_infoframe, + .hdmi_write_hdmi_infoframe =3D lt9611c_hdmi_write_hdmi_infoframe, + .hdmi_clear_hdmi_infoframe =3D lt9611c_hdmi_clear_hdmi_infoframe, + .hdmi_write_audio_infoframe =3D lt9611c_hdmi_write_audio_infoframe, + .hdmi_clear_audio_infoframe =3D lt9611c_hdmi_clear_audio_infoframe, + + .hdmi_audio_prepare =3D lt9611c_hdmi_audio_prepare, + .hdmi_audio_shutdown =3D 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 =3D 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 primar= y dsi\n"); + + *dsi1_node =3D of_graph_get_remote_node(dev->of_node, 1, -1); + + if (*dsi1_node && lt9611c->cdata->chip_type =3D=3D CHIP_LT9611C) { + ret =3D dev_err_probe(dev, -EINVAL, + "LT9611C does not support dual DSI\n"); + goto err_put_dsi1; + } + + lt9611c->bridge.next_bridge =3D of_drm_get_bridge_by_endpoint(dev->of_nod= e, 2, -1); + if (IS_ERR(lt9611c->bridge.next_bridge)) { + ret =3D 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 =3D regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret =3D 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 =3D lt9611c->dev; + u8 chipid[2]; + int ret; + + ret =3D regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret =3D regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2); + if (ret) + return ret; + + if (chipid[0] !=3D 0x23 || chipid[1] !=3D 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 =3D dev_get_drvdata(dev); + int ret; + + dev_warn(dev, "starting firmware upgrade =E2=80=94 display will be disrup= ted\n"); + + ret =3D 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 =3D 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[] =3D { + &dev_attr_firmware.attr, + NULL, +}; + +static const struct attribute_group lt9611c_attr_group =3D { + .attrs =3D lt9611c_attrs, +}; + +static const struct attribute_group *lt9611c_attr_groups[] =3D { + <9611c_attr_group, + NULL, +}; + +static int lt9611c_probe(struct i2c_client *client) +{ + struct lt9611c *lt9611c; + struct device *dev =3D &client->dev; + struct device_node *dsi0_node =3D NULL; + struct device_node *dsi1_node =3D NULL; + struct mipi_dsi_device *dsi; + bool fw_updated =3D 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 =3D devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_b= ridge_funcs); + if (IS_ERR(lt9611c)) + return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n"= ); + + lt9611c->dev =3D dev; + lt9611c->client =3D client; + i2c_set_clientdata(client, lt9611c); + + const struct lt9611c_chip_data *cdata =3D i2c_get_match_data(client); + + if (!cdata) + return dev_err_probe(dev, -EINVAL, "no match data for device\n"); + + lt9611c->cdata =3D cdata; + + ret =3D devm_mutex_init(dev, <9611c->mcu_lock); + if (ret) + return dev_err_probe(dev, ret, "failed to init mutex\n"); + + lt9611c->regmap =3D 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 fai= led\n"); + + ret =3D 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 =3D devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611c->reset_gpio)) { + ret =3D PTR_ERR(lt9611c->reset_gpio); + goto err_put_nodes; + } + + ret =3D lt9611c_regulator_init(lt9611c); + if (ret < 0) + goto err_put_nodes; + + lt9611c_reset(lt9611c); + + lt9611c_lock(lt9611c); + + ret =3D 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 =3D lt9611c_read_version(lt9611c); + if (lt9611c->fw_version < 0) { + dev_err(dev, "failed to read fw version\n"); + ret =3D -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } else if (lt9611c->fw_version =3D=3D 0) { + if (!fw_updated) { + fw_updated =3D true; + lt9611c_unlock(lt9611c); + ret =3D 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 =3D -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + } + + lt9611c_unlock(lt9611c); + dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version); + + INIT_WORK(<9611c->work, lt9611c_hpd_work); + + ret =3D 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 =3D client->dev.of_node; + lt9611c->bridge.ops =3D DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HDMI_AUDIO; + lt9611c->bridge.type =3D DRM_MODE_CONNECTOR_HDMIA; + + lt9611c->bridge.vendor =3D "Lontium"; + lt9611c->bridge.product =3D "LT9611C"; + + lt9611c->bridge.hdmi_audio_dev =3D dev; + lt9611c->bridge.hdmi_audio_max_i2s_playback_channels =3D 8; + lt9611c->bridge.hdmi_audio_dai_port =3D 2; + + drm_bridge_add(<9611c->bridge); + + /* Attach primary DSI */ + dsi =3D lt9611c_attach_dsi(lt9611c, dsi0_node); + if (IS_ERR(dsi)) { + ret =3D PTR_ERR(dsi); + goto err_remove_bridge; + } + + /* Attach secondary DSI, if specified */ + if (dsi1_node) { + dsi =3D lt9611c_attach_dsi(lt9611c, dsi1_node); + if (IS_ERR(dsi)) { + ret =3D PTR_ERR(dsi); + goto err_remove_bridge; + } + } + + lt9611c->hdmi_connected =3D 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 =3D 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 =3D 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 =3D regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->su= pplies); + if (ret) { + dev_err(lt9611c->dev, "regulator bulk disable failed.\n"); + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); + enable_irq(lt9611c->client->irq); + return ret; + } + + return 0; +} + +static int lt9611c_bridge_resume(struct device *dev) +{ + struct lt9611c *lt9611c =3D dev_get_drvdata(dev); + int ret; + + ret =3D regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->sup= plies); + 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 =3D { + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend, + lt9611c_bridge_resume) +}; + +static const struct i2c_device_id lt9611c_id[] =3D { + { .name =3D "lt9611c", .driver_data =3D (kernel_ulong_t)<9611c_chip_d= ata[CHIP_LT9611C] }, + { .name =3D "lt9611ex", .driver_data =3D (kernel_ulong_t)<9611c_chip_d= ata[CHIP_LT9611EX] }, + { .name =3D "lt9611uxd", .driver_data =3D (kernel_ulong_t)<9611c_chip_d= ata[CHIP_LT9611UXD] }, + { /* sentinel */ } +}; + +static const struct of_device_id lt9611c_match_table[] =3D { + { .compatible =3D "lontium,lt9611c", .data =3D <9611c_chip_data[CHIP_= LT9611C] }, + { .compatible =3D "lontium,lt9611ex", .data =3D <9611c_chip_data[CHIP_= LT9611EX] }, + { .compatible =3D "lontium,lt9611uxd", .data =3D <9611c_chip_data[CHIP_= LT9611UXD] }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lt9611c_match_table); + +static struct i2c_driver lt9611c_driver =3D { + .driver =3D { + .name =3D "lt9611c", + .of_match_table =3D lt9611c_match_table, + .pm =3D <9611c_bridge_pm_ops, + .dev_groups =3D lt9611c_attr_groups, + }, + .probe =3D lt9611c_probe, + .remove =3D lt9611c_remove, + .id_table =3D 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); --=20 2.34.1