From nobody Mon Feb 9 22:18:42 2026 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.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 A5E182C21F5 for ; Sun, 28 Dec 2025 11:41:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766922097; cv=none; b=Kg8Nx1oAtb84IT22c6R2APjR59sbrmfmPinRO7gQsMxlveLEzuIfkiG50/hVZcicV5PWY7eLHK45nXvcQgKcJipDKumEDIV0+XhJYyt19N6PRAuUS0l0nJYkJFfrYFc2vlbzj7ODInmTO6aJXdfyftkMWy4BaNUaSZyYw2EvgXA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1766922097; c=relaxed/simple; bh=YGUOA/mG99rfEU/eoERsbhNRUe7s7u1n+QTtZ/X6mlM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=G6i25lGRZzDHBZ+bf+rPI2P5MX4sBOOLmXWiPgb/Jptqc0jEwf8woEiuhlKriaHoLKXeOkYgha8wkqVg9kk8VTHfqqbe9sH/Rx9dRyqkliwz5T7r51Xkq2mgvyruxN6/zYeKqE1qwDqRGX5olkGsw1MTJaW8MfFGgat+Z7IAdcI= 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=htWF5g+q; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=f2+ZBF6r; arc=none smtp.client-ip=205.220.168.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="htWF5g+q"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="f2+ZBF6r" Received: from pps.filterd (m0279862.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 5BS4pQjx1940873 for ; Sun, 28 Dec 2025 11:41:35 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= z0rsyMfnSMVjG7eYM5sSuBfEla5IEk3vdg7bZNHRL4Y=; b=htWF5g+q2lsfVpbs m9xJtGN0TgPhBY5TuVkg3/nNTSJy0vGvudfBqvHpfrK+CIflD3xZWFhFkaUKxT1O Sj6m110/6Xzur4hjivpxchFls1M32ILwQk5uakzzeYOMw5qWeMZYMCK5izWJnmZb HeT9M7HTI0xwKiQTLwiDqxGv9v0FtI9GLoDEsZNc1Y4YClf4LkB6kCyHO1GvEq6v NObjFhP6yCe7zSEkhyqmvwiS/b5Ptn80blbZBUfr/Fhaf6wLVDl4W72fLwSZNxfE nz79p1qf1LDUm5JGZTPFwkwF3KfIR6xFfrQ7md9OzvsOGH+jge4Y1xsiA0h/DZDw Ebl/Bw== Received: from mail-pg1-f200.google.com (mail-pg1-f200.google.com [209.85.215.200]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4ba87bt37h-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Sun, 28 Dec 2025 11:41:34 +0000 (GMT) Received: by mail-pg1-f200.google.com with SMTP id 41be03b00d2f7-bdd6dfa6f85so586757a12.1 for ; Sun, 28 Dec 2025 03:41:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1766922094; x=1767526894; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=z0rsyMfnSMVjG7eYM5sSuBfEla5IEk3vdg7bZNHRL4Y=; b=f2+ZBF6rQAB9OFWl4cTyTAPijK60to/AOQXzNZIHILBcu8ImseS0Jp02EdAVbQ5wBp mIU9k8ffXiqNQ7bNCmrCRSCzKYKLdb+4vAc8IXEXJpDjHIL4pZlY+5yKWO1kLSx6jTE/ ZrQzMEkzaI3oQdrMNtU/OljjXCkdAi2TY69/BzNchqmdFOeXIPxbRtnh8YrxTwxDW4KS nIvkUY/R2p8aR6DLBnEz0rJWY9+YsPxSTv6lo6HZ4Ezjrws9ivtzJ3m9H4Yt9ajVZjHw vT841REUY5HcwYtd2Sopq4LLuHVS8A+9VIqlQtTSkJkcxC+5/LXwKh3Vo6Z/A3FiWOtk 0FgQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766922094; x=1767526894; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=z0rsyMfnSMVjG7eYM5sSuBfEla5IEk3vdg7bZNHRL4Y=; b=T6283Ff/iF6WJ+IBtKSX13q57UrRzZpz/jOyq77CY6zu8cFdu6JACS1+x4sooJyjYn gCHcbs3HSRy/gzFxWO4dlR1MYYSa5DcB9Hc/rCVyJu8uEcofQujh7hxnAxgqfxaow9yE TB/2fAyt+MU90PFbCC4avP6gYlgTZF0ob52b7kuS42obRZtntvaU63vla+fMN2pLanSf LcHIrxzay01Si5lobaFEpcRGa0d53eDWc0SXNSLDCQb85CN7VHRYnKHYclRUYw+rLAgq +1GJe4fbgEwx62FCXe2+PPvp17CmRwBFIetaiyKwcXcRd7nel5C+n/mo10FVEaSZ4QRq 8xAA== X-Forwarded-Encrypted: i=1; AJvYcCWXA4EhoY2O/5GyL3j5R8yzuDcPLewETJXCwFDTQVqlpHZpLcLWL1LqANYudVqHkzkCcq3rJOQgH3tQxgY=@vger.kernel.org X-Gm-Message-State: AOJu0YwpLNwe/sjoKLXtZdDdRFf/+DLQlc0KAp2p5RYIEnDrmC+FQzM3 SFXjJ5E/dasaCsFzXe/hm7sl0b//WGlPpGyNgei2R0jfd9JkKRpdcTwHeQqNQirHqNEHjWmb2Dt OqFcnZ8IX8ui4ruqWgDJqP0GxFa/POJn6o+ro2UwKVgZoG9b2vURIBnKW2qWqt4PPa6Y= X-Gm-Gg: AY/fxX6oGKxjc+DyG7XweviB/6yufeoLIbZV4kQ6mvM5MkoKfRvssKDMw3c9Va1QTw2 CbKx6dY6sz7LJzz55aWOmM5yVmV/wXZL7H/LvxuogWoPIsYjfXVwJn7B2GfYB4DqamkX9+hUPkc jGIpXUpOauy+jN+Idw2T5kqYBQ6rii3FxelnpOyabDVK+dFp0Fw3Zh2geV65YjT69gpNDlpNOpZ CBqh4+O3RWGBxu6QYUXk33XD+hz0IQpFq9lc3VnauSKc431N8SY3KxCvosKkHFaw8ZaWnyM17Ui CrgQPwYG8JOhGO30I8kH7B0m1ujTSdBg/pkmSFlcao0QFF2Pt2MWdUj3O6rdVriS9MzUiFUPV5K c0MM9LN1PtGjcPxmDIGtcdk4GjAeUxy9bPH+PQEGivU99yRTIuR8VWVLqZ2JobUHz5MiXBqum6D 3lS3Oedyzo7PMPUu7W7Mmmp4MLWS/kcg== X-Received: by 2002:a05:6a00:17a1:b0:7b2:b20:e8d9 with SMTP id d2e1a72fcca58-7ff682460c9mr16272782b3a.6.1766922093826; Sun, 28 Dec 2025 03:41:33 -0800 (PST) X-Google-Smtp-Source: AGHT+IFbzuZjej7dofVQz+LVzaPa4pI3i4/MrbeAYBDogcsludpnEXNFnzNmKk+D5ibY8b1M1Ob4Lg== X-Received: by 2002:a05:6a00:17a1:b0:7b2:b20:e8d9 with SMTP id d2e1a72fcca58-7ff682460c9mr16272764b3a.6.1766922093223; Sun, 28 Dec 2025 03:41:33 -0800 (PST) Received: from hu-vishsain-blr.qualcomm.com (blr-bdr-fw-01_GlobalNAT_AllZones-Outside.qualcomm.com. [103.229.18.19]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-7ff7e197f9asm26214067b3a.43.2025.12.28.03.41.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 03:41:32 -0800 (PST) From: Vishnu Saini Date: Sun, 28 Dec 2025 17:10:40 +0530 Subject: [PATCH v3 2/2] drm/bridge: add support for lontium lt8713sx bridge 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: <20251228-lt8713sx-bridge-driver-v3-2-9169fbef0e5b@oss.qualcomm.com> References: <20251228-lt8713sx-bridge-driver-v3-0-9169fbef0e5b@oss.qualcomm.com> In-Reply-To: <20251228-lt8713sx-bridge-driver-v3-0-9169fbef0e5b@oss.qualcomm.com> To: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Tony Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Vishnu Saini , prahlad.valluru@oss.qualcomm.com, Prahlad Valluru X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1766922077; l=21368; i=vishnu.saini@oss.qualcomm.com; s=20251113; h=from:subject:message-id; bh=YGUOA/mG99rfEU/eoERsbhNRUe7s7u1n+QTtZ/X6mlM=; b=slwxm0toGxl3ud50AGJhdfYa3RzVhhhy24wRu4D2OsP7EIqtUL/DPe9EBhDh2Er2bN0FkVP2L llbjeURvLyjA+iCCggY6JYKI7qq2rlfSU6YNfa8VPQ04vqKX7fuPpNS X-Developer-Key: i=vishnu.saini@oss.qualcomm.com; a=ed25519; pk=8hlXlF8j/3GeOaDK3w2LYhkv9FanCQru0c7kRH/It7k= X-Proofpoint-ORIG-GUID: 0IJGukPNMjqZgQMEitYoa55rFA1qJUzE X-Proofpoint-GUID: 0IJGukPNMjqZgQMEitYoa55rFA1qJUzE X-Proofpoint-Spam-Details-Enc: AW1haW4tMjUxMjI4MDEwOCBTYWx0ZWRfXy8caJiBZPOQ+ DowPzetgWPr8AlOIYWWr234Up2YLS38Xux1Mpt+Gj/FMJ5lGqvrcKVNPsFPUXS+9dCZ6q3b1msT sXRWFk79rNQPi+4npYXkww8w/bs9nCB/xeC4uwpO4w6t4opNMFPGRwMFRHFdHk5LsA5OsvHS7Jo uLmWeXaup1yh989Q8kej4OVocT/7HCT3Q36cL2B15Bwyp8VkFPk1wGKM/HzY/LPOncYKqPaMFgK X7nHtwxTMUbMyaRc/DiInvqxAZ3uDNM6ppSUsPxFPvdnlFPjrBSEIXRsHSYdBzxVnKvcIHaWnip FpVKEF2YaqWF6uUxxS3ulrX7fXLPlrytPln6jWrwmLIsQKpmHZy8KT6J451tVEHIBRUwhhXvZt+ JuKXuLDtT1Qcuykd9oBrzBFGbxyMOfBR5DOQrsAJQx++flp6dVyjiwiOrYS8SFCLUsLPFPr8RI3 Pgh5yr3S3XT38BRTUuA== X-Authority-Analysis: v=2.4 cv=do7Wylg4 c=1 sm=1 tr=0 ts=6951176e cx=c_pps a=oF/VQ+ItUULfLr/lQ2/icg==:117 a=Ou0eQOY4+eZoSc0qltEV5Q==:17 a=IkcTkHD0fZMA:10 a=wP3pNCr1ah4A:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=EUspDBNiAAAA:8 a=Kz8-B0t5AAAA:8 a=Los8Fsgh6qdBVcIiYUAA:9 a=QEXdDO2ut3YA:10 a=3WC7DwWrALyhR5TkjVHa:22 a=RuZk68QooNbwfxovefhk:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.9,FMLib:17.12.100.49 definitions=2025-12-28_04,2025-12-26_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 impostorscore=0 malwarescore=0 bulkscore=0 clxscore=1015 lowpriorityscore=0 adultscore=0 spamscore=0 suspectscore=0 phishscore=0 priorityscore=1501 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2512120000 definitions=main-2512280108 The lt8713sx is a Type-C/DP1.4 to Type-C/DP1.4/HDMI2.0 converter, with three configurable DP1.4/HDMI2.0/DP++ output interfaces and audio output interface. Driver is required for firmware upgrade in the bridge chip. Co-developed-by: Prahlad Valluru Signed-off-by: Prahlad Valluru Signed-off-by: Vishnu Saini --- drivers/gpu/drm/bridge/Kconfig | 10 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt8713sx.c | 682 ++++++++++++++++++++++++++= ++++ 3 files changed, 693 insertions(+) diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index a250afd8d662..7fef383ed7cb 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -184,6 +184,16 @@ config DRM_LONTIUM_LT9611UXC HDMI signals Please say Y if you have such hardware. =20 +config DRM_LONTIUM_LT8713SX + tristate "Lontium LT8713SX DP MST bridge" + depends on OF + select REGMAP_I2C + help + Driver for Lontium LT8713SX DP MST bridge + chip firmware upgrade, which converts Type-C/DP1.4 + to 3 configurable Type-C/DP1.4/HDMI2.0 outputs + Please say Y if you have such hardware. + config DRM_ITE_IT66121 tristate "ITE IT66121 HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makef= ile index c7dc03182e59..07eeb13fa497 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,6 +16,7 @@ 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_LT9611UXC) +=3D lontium-lt9611uxc.o +obj-$(CONFIG_DRM_LONTIUM_LT8713SX) +=3D lontium-lt8713sx.o obj-$(CONFIG_DRM_LVDS_CODEC) +=3D lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) +=3D megachips-stdpxxxx-= ge-b850v3-fw.o obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) +=3D microchip-lvds.o diff --git a/drivers/gpu/drm/bridge/lontium-lt8713sx.c b/drivers/gpu/drm/br= idge/lontium-lt8713sx.c new file mode 100644 index 000000000000..6ea54ff3733d --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt8713sx.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FW_FILE "lt8713sx_fw.bin" + +#define REG_PAGE_CONTROL 0xff + +#define MAX_OUTPUT_PORTS 3 +#define LT8713SX_PAGE_SIZE 256 + +DECLARE_CRC8_TABLE(lt8713sx_crc_table); + +struct lt8713sx { + struct device *dev; + struct drm_bridge bridge; + struct drm_bridge *next_bridge[MAX_OUTPUT_PORTS]; + int num_outputs; + + struct regmap *regmap; + /* Protects all accesses to registers by stopping the on-chip MCU */ + struct mutex ocm_lock; + + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + + struct i2c_client *client; + const struct firmware *fw; + + u8 *fw_buffer; + + u32 main_crc_value; + u32 bank_crc_value[17]; + + int bank_num; +}; + +static void lt8713sx_reset(struct lt8713sx *lt8713sx); + +static const struct regmap_range lt8713sx_ranges[] =3D { + { + .range_min =3D 0x0000, + .range_max =3D 0xffff + }, +}; + +static const struct regmap_access_table lt8713sx_table =3D { + .yes_ranges =3D lt8713sx_ranges, + .n_yes_ranges =3D ARRAY_SIZE(lt8713sx_ranges), +}; + +static const struct regmap_range_cfg lt8713sx_range_cfg =3D { + .name =3D "lt8713sx", + .range_min =3D 0x0000, + .range_max =3D 0xffff, + .selector_reg =3D REG_PAGE_CONTROL, + .selector_mask =3D 0xff, + .selector_shift =3D 0, + .window_start =3D 0, + .window_len =3D 0x100, +}; + +static const struct regmap_config lt8713sx_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .volatile_table =3D <8713sx_table, + .ranges =3D <8713sx_range_cfg, + .num_ranges =3D 1, + .cache_type =3D REGCACHE_NONE, + .max_register =3D 0xffff, +}; + +static void lt8713sx_i2c_enable(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe0ee, 0x01); +} + +static void lt8713sx_i2c_disable(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe0ee, 0x00); +} + +static u32 calculate_crc(const u8 *upgrade_data, u64 len, u64 crc_size) +{ + u8 crc =3D 0x00; + u8 pad =3D 0xff; + + crc =3D crc8(lt8713sx_crc_table, upgrade_data, len, crc); + + /* pad remaining bytes */ + crc_size -=3D len; + while (crc_size--) + crc =3D crc8(lt8713sx_crc_table, &pad, 1, crc); + + return crc; +} + +static int lt8713sx_prepare_firmware_data(struct lt8713sx *lt8713sx) +{ + int ret =3D 0; + u64 sz_12k =3D 12 * SZ_1K; + + ret =3D request_firmware(<8713sx->fw, FW_FILE, lt8713sx->dev); + if (ret < 0) { + dev_err(lt8713sx->dev, "request firmware failed\n"); + return ret; + } + + dev_dbg(lt8713sx->dev, "Firmware size: %zu bytes\n", lt8713sx->fw->size); + + if (lt8713sx->fw->size > SZ_256K - 1) { + dev_err(lt8713sx->dev, "Firmware size exceeds 256KB limit\n"); + release_firmware(lt8713sx->fw); + return -EINVAL; + } + + lt8713sx->fw_buffer =3D kvmalloc(SZ_256K, GFP_KERNEL); + if (!lt8713sx->fw_buffer) { + release_firmware(lt8713sx->fw); + return -ENOMEM; + } + + memset(lt8713sx->fw_buffer, 0xff, SZ_256K); + + if (lt8713sx->fw->size < SZ_64K) { + memcpy(lt8713sx->fw_buffer, lt8713sx->fw->data, lt8713sx->fw->size); + lt8713sx->fw_buffer[SZ_64K - 1] =3D + calculate_crc(lt8713sx->fw->data, lt8713sx->fw->size, SZ_64K - 1); + lt8713sx->main_crc_value =3D lt8713sx->fw_buffer[SZ_64K - 1]; + dev_dbg(lt8713sx->dev, + "Main Firmware Data Crc=3D0x%02X\n", lt8713sx->main_crc_value); + + } else { + /* main firmware */ + memcpy(lt8713sx->fw_buffer, lt8713sx->fw->data, SZ_64K - 1); + lt8713sx->fw_buffer[SZ_64K - 1] =3D + calculate_crc(lt8713sx->fw_buffer, SZ_64K - 1, SZ_64K - 1); + lt8713sx->main_crc_value =3D lt8713sx->fw_buffer[SZ_64K - 1]; + dev_dbg(lt8713sx->dev, + "Main Firmware Data Crc=3D0x%02X\n", lt8713sx->main_crc_value); + + /* bank firmware */ + memcpy(lt8713sx->fw_buffer + SZ_64K, + lt8713sx->fw->data + SZ_64K, + lt8713sx->fw->size - SZ_64K); + + lt8713sx->bank_num =3D (lt8713sx->fw->size - SZ_64K + sz_12k - 1) / sz_1= 2k; + dev_dbg(lt8713sx->dev, "Bank Number Total is %d.\n", lt8713sx->bank_num); + + for (int i =3D 0; i < lt8713sx->bank_num; i++) { + lt8713sx->bank_crc_value[i] =3D + calculate_crc(lt8713sx->fw_buffer + SZ_64K + i * sz_12k, + sz_12k, sz_12k); + dev_dbg(lt8713sx->dev, "Bank number:%d; Firmware Data Crc:0x%02X\n", + i, lt8713sx->bank_crc_value[i]); + } + } + return 0; +} + +static void lt8713sx_config_parameters(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe0ee, 0x01); + regmap_write(lt8713sx->regmap, 0xe05e, 0xc1); + regmap_write(lt8713sx->regmap, 0xe058, 0x00); + regmap_write(lt8713sx->regmap, 0xe059, 0x50); + regmap_write(lt8713sx->regmap, 0xe05a, 0x10); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); + regmap_write(lt8713sx->regmap, 0xe058, 0x21); +} + +static void lt8713sx_wren(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe103, 0xbf); + regmap_write(lt8713sx->regmap, 0xe103, 0xff); + regmap_write(lt8713sx->regmap, 0xe05a, 0x04); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); +} + +static void lt8713sx_wrdi(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe05a, 0x08); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); +} + +static void lt8713sx_fifo_reset(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe103, 0xbf); + regmap_write(lt8713sx->regmap, 0xe103, 0xff); +} + +static void lt8713sx_disable_sram_write(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe055, 0x00); +} + +static void lt8713sx_sram_to_flash(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe05a, 0x30); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); +} + +static void lt8713sx_i2c_to_sram(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe055, 0x80); + regmap_write(lt8713sx->regmap, 0xe05e, 0xc0); + regmap_write(lt8713sx->regmap, 0xe058, 0x21); +} + +static u8 lt8713sx_read_flash_status(struct lt8713sx *lt8713sx) +{ + u32 flash_status =3D 0; + + regmap_write(lt8713sx->regmap, 0xe103, 0x3f); + regmap_write(lt8713sx->regmap, 0xe103, 0xff); + + regmap_write(lt8713sx->regmap, 0xe05e, 0x40); + regmap_write(lt8713sx->regmap, 0xe056, 0x05); /* opcode=3Dread status re= gister */ + regmap_write(lt8713sx->regmap, 0xe055, 0x25); + regmap_write(lt8713sx->regmap, 0xe055, 0x01); + regmap_write(lt8713sx->regmap, 0xe058, 0x21); + + regmap_read(lt8713sx->regmap, 0xe05f, &flash_status); + dev_dbg(lt8713sx->dev, "flash_status:%x\n", flash_status); + + return flash_status; +} + +static void lt8713sx_block_erase(struct lt8713sx *lt8713sx) +{ + u32 i =3D 0; + u8 flash_status =3D 0; + u8 blocknum =3D 0x00; + u32 flashaddr =3D 0x00; + + for (blocknum =3D 0; blocknum < 8; blocknum++) { + flashaddr =3D blocknum * SZ_32K; + regmap_write(lt8713sx->regmap, 0xe0ee, 0x01); + regmap_write(lt8713sx->regmap, 0xe05a, 0x04); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); + regmap_write(lt8713sx->regmap, 0xe05b, flashaddr >> 16); + regmap_write(lt8713sx->regmap, 0xe05c, flashaddr >> 8); + regmap_write(lt8713sx->regmap, 0xe05d, flashaddr); + regmap_write(lt8713sx->regmap, 0xe05a, 0x01); + regmap_write(lt8713sx->regmap, 0xe05a, 0x00); + msleep(100); + i =3D 0; + while (1) { + flash_status =3D lt8713sx_read_flash_status(lt8713sx); + if ((flash_status & 0x01) =3D=3D 0) + break; + + if (i > 50) + break; + + i++; + msleep(50); + } + } + dev_dbg(lt8713sx->dev, "erase flash done.\n"); +} + +static void lt8713sx_load_main_fw_to_sram(struct lt8713sx *lt8713sx) +{ + regmap_write(lt8713sx->regmap, 0xe0ee, 0x01); + regmap_write(lt8713sx->regmap, 0xe068, 0x00); + regmap_write(lt8713sx->regmap, 0xe069, 0x00); + regmap_write(lt8713sx->regmap, 0xe06a, 0x00); + regmap_write(lt8713sx->regmap, 0xe065, 0x00); + regmap_write(lt8713sx->regmap, 0xe066, 0xff); + regmap_write(lt8713sx->regmap, 0xe067, 0xff); + regmap_write(lt8713sx->regmap, 0xe06b, 0x00); + regmap_write(lt8713sx->regmap, 0xe06c, 0x00); + regmap_write(lt8713sx->regmap, 0xe060, 0x01); + msleep(200); + regmap_write(lt8713sx->regmap, 0xe060, 0x00); +} + +static void lt8713sx_load_bank_fw_to_sram(struct lt8713sx *lt8713sx, u64 a= ddr) +{ + regmap_write(lt8713sx->regmap, 0xe0ee, 0x01); + regmap_write(lt8713sx->regmap, 0xe068, ((addr & 0xff0000) >> 16)); + regmap_write(lt8713sx->regmap, 0xe069, ((addr & 0x00ff00) >> 8)); + regmap_write(lt8713sx->regmap, 0xe06a, (addr & 0x0000ff)); + regmap_write(lt8713sx->regmap, 0xe065, 0x00); + regmap_write(lt8713sx->regmap, 0xe066, 0x30); + regmap_write(lt8713sx->regmap, 0xe067, 0x00); + regmap_write(lt8713sx->regmap, 0xe06b, 0x00); + regmap_write(lt8713sx->regmap, 0xe06c, 0x00); + regmap_write(lt8713sx->regmap, 0xe060, 0x01); + msleep(50); + regmap_write(lt8713sx->regmap, 0xe060, 0x00); +} + +static int lt8713sx_write_data(struct lt8713sx *lt8713sx, const u8 *data, = u64 filesize) +{ + int page =3D 0, num =3D 0, i =3D 0, val; + + page =3D (filesize % LT8713SX_PAGE_SIZE) ? + ((filesize / LT8713SX_PAGE_SIZE) + 1) : (filesize / LT8713SX_PAGE_SIZE); + + dev_dbg(lt8713sx->dev, + "Writing to Sram=3D%u pages, total size =3D %llu bytes\n", page, filesiz= e); + + for (num =3D 0; num < page; num++) { + dev_dbg(lt8713sx->dev, "page[%d]\n", num); + lt8713sx_i2c_to_sram(lt8713sx); + + for (i =3D 0; i < LT8713SX_PAGE_SIZE; i++) { + if ((num * LT8713SX_PAGE_SIZE + i) < filesize) + val =3D *(data + (num * LT8713SX_PAGE_SIZE + i)); + else + val =3D 0xff; + regmap_write(lt8713sx->regmap, 0xe059, val); + } + + lt8713sx_wren(lt8713sx); + lt8713sx_sram_to_flash(lt8713sx); + } + + lt8713sx_wrdi(lt8713sx); + lt8713sx_disable_sram_write(lt8713sx); + + return 0; +} + +static void lt8713sx_main_upgrade_result(struct lt8713sx *lt8713sx) +{ + u32 main_crc_result; + + regmap_read(lt8713sx->regmap, 0xe023, &main_crc_result); + + dev_dbg(lt8713sx->dev, "Main CRC HW: 0x%02X\n", main_crc_result); + dev_dbg(lt8713sx->dev, "Main CRC FW: 0x%02X\n", lt8713sx->main_crc_value); + + if (main_crc_result =3D=3D lt8713sx->main_crc_value) + dev_dbg(lt8713sx->dev, "Main Firmware Upgrade Success.\n"); + else + dev_err(lt8713sx->dev, "Main Firmware Upgrade Failed.\n"); +} + +static void lt8713sx_bank_upgrade_result(struct lt8713sx *lt8713sx, u8 ban= knum) +{ + u32 bank_crc_result; + + regmap_read(lt8713sx->regmap, 0xe023, &bank_crc_result); + + dev_dbg(lt8713sx->dev, "Bank %d CRC Result: 0x%02X\n", banknum, bank_crc_= result); + + if (bank_crc_result =3D=3D lt8713sx->bank_crc_value[banknum]) + dev_dbg(lt8713sx->dev, "Bank %d Firmware Upgrade Success.\n", banknum); + else + dev_err(lt8713sx->dev, "Bank %d Firmware Upgrade Failed.\n", banknum); +} + +static void lt8713sx_bank_result_check(struct lt8713sx *lt8713sx) +{ + int i; + u64 addr =3D 0x010000; + + for (i =3D 0; i < lt8713sx->bank_num; i++) { + lt8713sx_load_bank_fw_to_sram(lt8713sx, addr); + lt8713sx_bank_upgrade_result(lt8713sx, i); + addr +=3D 0x3000; + } +} + +static int lt8713sx_firmware_upgrade(struct lt8713sx *lt8713sx) +{ + int ret; + + lt8713sx_config_parameters(lt8713sx); + + lt8713sx_block_erase(lt8713sx); + + if (lt8713sx->fw->size < SZ_64K) { + ret =3D lt8713sx_write_data(lt8713sx, lt8713sx->fw_buffer, SZ_64K); + if (ret < 0) { + dev_err(lt8713sx->dev, "Failed to write firmware data: %d\n", ret); + return ret; + } + } else { + ret =3D lt8713sx_write_data(lt8713sx, lt8713sx->fw_buffer, lt8713sx->fw-= >size); + if (ret < 0) { + dev_err(lt8713sx->dev, "Failed to write firmware data: %d\n", ret); + return ret; + } + } + dev_dbg(lt8713sx->dev, "Write Data done.\n"); + + return 0; +} + +static int lt8713sx_firmware_update(struct lt8713sx *lt8713sx) +{ + int ret =3D 0; + + mutex_lock(<8713sx->ocm_lock); + lt8713sx_i2c_enable(lt8713sx); + + ret =3D lt8713sx_prepare_firmware_data(lt8713sx); + if (ret < 0) { + dev_err(lt8713sx->dev, "Failed to prepare firmware data: %d\n", ret); + goto error; + } + + ret =3D lt8713sx_firmware_upgrade(lt8713sx); + if (ret < 0) { + dev_err(lt8713sx->dev, "Upgrade failure.\n"); + goto error; + } else { + /* Validate CRC */ + lt8713sx_load_main_fw_to_sram(lt8713sx); + lt8713sx_main_upgrade_result(lt8713sx); + lt8713sx_wrdi(lt8713sx); + lt8713sx_fifo_reset(lt8713sx); + lt8713sx_bank_result_check(lt8713sx); + lt8713sx_wrdi(lt8713sx); + } + +error: + lt8713sx_i2c_disable(lt8713sx); + if (!ret) + lt8713sx_reset(lt8713sx); + + kvfree(lt8713sx->fw_buffer); + lt8713sx->fw_buffer =3D NULL; + + if (lt8713sx->fw) { + release_firmware(lt8713sx->fw); + lt8713sx->fw =3D NULL; + } + mutex_unlock(<8713sx->ocm_lock); + + return ret; +} + +static void lt8713sx_reset(struct lt8713sx *lt8713sx) +{ + dev_dbg(lt8713sx->dev, "reset bridge.\n"); + gpiod_set_value_cansleep(lt8713sx->reset_gpio, 1); + msleep(20); + + gpiod_set_value_cansleep(lt8713sx->reset_gpio, 0); + msleep(20); + + dev_dbg(lt8713sx->dev, "reset done.\n"); +} + +static int lt8713sx_regulator_enable(struct lt8713sx *lt8713sx) +{ + int ret; + + ret =3D devm_regulator_get_enable(lt8713sx->dev, "vdd"); + if (ret < 0) + return dev_err_probe(lt8713sx->dev, ret, "failed to enable vdd regulator= \n"); + + usleep_range(1000, 10000); + + ret =3D devm_regulator_get_enable(lt8713sx->dev, "vcc"); + if (ret < 0) + return dev_err_probe(lt8713sx->dev, ret, "failed to enable vcc regulator= \n"); + return 0; +} + +static int lt8713sx_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt8713sx *lt8713sx =3D container_of(bridge, struct lt8713sx, bridg= e); + int i, ret; + + for (i =3D 0; i < lt8713sx->num_outputs; i++) { + if (!lt8713sx->next_bridge[i]) + continue; + + ret =3D drm_bridge_attach(encoder, + lt8713sx->next_bridge[i], + bridge, flags); + if (ret) + return ret; + } + + return 0; +} + +static int lt8713sx_get_ports(struct lt8713sx *lt8713sx) +{ + struct device *dev =3D lt8713sx->dev; + struct device_node *port, *ports, *ep, *remote; + int i =3D 0; + u32 reg; + + ports =3D of_get_child_by_name(dev->of_node, "ports"); + if (!ports) + return -ENODEV; + + for_each_child_of_node(ports, port) { + if (of_property_read_u32(port, "reg", ®)) + continue; + + if (reg =3D=3D 0) + continue; + + if (i >=3D ARRAY_SIZE(lt8713sx->next_bridge)) { + of_node_put(port); + break; + } + + ep =3D of_graph_get_next_endpoint(port, NULL); + if (!ep) + continue; + + remote =3D of_graph_get_remote_port_parent(ep); + of_node_put(ep); + + if (!remote) + continue; + + lt8713sx->next_bridge[i] =3D of_drm_find_bridge(remote); + of_node_put(remote); + if (lt8713sx->next_bridge[i]) + i++; + } + lt8713sx->num_outputs =3D i; + dev_dbg(dev, "Enabled %d output ports", i); + + of_node_put(ports); + return 0; +}; + +static int lt8713sx_gpio_init(struct lt8713sx *lt8713sx) +{ + struct device *dev =3D lt8713sx->dev; + + lt8713sx->reset_gpio =3D devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt8713sx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8713sx->reset_gpio), + "failed to acquire reset gpio\n"); + + /* power enable gpio */ + lt8713sx->enable_gpio =3D devm_gpiod_get_optional(dev, "enable", GPIOD_OU= T_HIGH); + if (IS_ERR(lt8713sx->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(lt8713sx->enable_gpio), + "failed to acquire enable gpio\n"); + return 0; +} + +static ssize_t lt8713sx_firmware_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lt8713sx *lt8713sx =3D dev_get_drvdata(dev); + int ret; + + ret =3D lt8713sx_firmware_update(lt8713sx); + if (ret < 0) + return ret; + return len; +} + +static DEVICE_ATTR_WO(lt8713sx_firmware); + +static struct attribute *lt8713sx_attrs[] =3D { + &dev_attr_lt8713sx_firmware.attr, + NULL, +}; + +static const struct attribute_group lt8713sx_attr_group =3D { + .attrs =3D lt8713sx_attrs, +}; + +static const struct attribute_group *lt8713sx_attr_groups[] =3D { + <8713sx_attr_group, + NULL, +}; + +static const struct drm_bridge_funcs lt8713sx_bridge_funcs =3D { + .attach =3D lt8713sx_bridge_attach, +}; + +static int lt8713sx_probe(struct i2c_client *client) +{ + struct lt8713sx *lt8713sx; + struct device *dev =3D &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n"); + + lt8713sx =3D devm_drm_bridge_alloc(dev, struct lt8713sx, bridge, <8713s= x_bridge_funcs); + if (IS_ERR(lt8713sx)) + return PTR_ERR(lt8713sx); + + lt8713sx->dev =3D dev; + lt8713sx->client =3D client; + i2c_set_clientdata(client, lt8713sx); + + ret =3D devm_mutex_init(lt8713sx->dev, <8713sx->ocm_lock); + if (ret) + return ret; + + lt8713sx->regmap =3D devm_regmap_init_i2c(client, <8713sx_regmap_config= ); + if (IS_ERR(lt8713sx->regmap)) + return dev_err_probe(dev, PTR_ERR(lt8713sx->regmap), "regmap i2c init fa= iled\n"); + + ret =3D lt8713sx_get_ports(lt8713sx); + if (ret < 0) + return ret; + + ret =3D lt8713sx_gpio_init(lt8713sx); + if (ret < 0) + return ret; + + ret =3D lt8713sx_regulator_enable(lt8713sx); + if (ret) + return ret; + + lt8713sx_reset(lt8713sx); + + lt8713sx->bridge.funcs =3D <8713sx_bridge_funcs; + lt8713sx->bridge.of_node =3D dev->of_node; + lt8713sx->bridge.type =3D DRM_MODE_CONNECTOR_DisplayPort; + drm_bridge_add(<8713sx->bridge); + + crc8_populate_msb(lt8713sx_crc_table, 0x31); + + return 0; +} + +static void lt8713sx_remove(struct i2c_client *client) +{ + struct lt8713sx *lt8713sx =3D i2c_get_clientdata(client); + + drm_bridge_remove(<8713sx->bridge); +} + +static struct i2c_device_id lt8713sx_id[] =3D { + { "lontium,lt8713sx", 0 }, + { /* sentinel */ } +}; + +static const struct of_device_id lt8713sx_match_table[] =3D { + { .compatible =3D "lontium,lt8713sx" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lt8713sx_match_table); + +static struct i2c_driver lt8713sx_driver =3D { + .driver =3D { + .name =3D "lt8713sx", + .of_match_table =3D lt8713sx_match_table, + .dev_groups =3D lt8713sx_attr_groups, + }, + .probe =3D lt8713sx_probe, + .remove =3D lt8713sx_remove, + .id_table =3D lt8713sx_id, +}; + +module_i2c_driver(lt8713sx_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("lt8713sx drm bridge driver"); +MODULE_AUTHOR("Tony "); +MODULE_FIRMWARE(FW_FILE); --=20 2.34.1