From mboxrd@z Thu Jan 1 00:00:00 1970 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 D90EB334681 for ; Sat, 27 Jun 2026 20:41:34 +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=1782592897; cv=none; b=ZeO4rQO8ZO0Bcu9YiMxWMfnvE1Wp/qd9xRss73oSbwbp80GUx6rQg+X5K2U6jt9w7pKcyfMsmwH7/ZbCrZRECYLGQkZR+i+cGaW6p5klWw/Ya+yK2NHhlUbB9162pMVvFCVFoLDpQuXN7eoaicyfZQNmYxlcDGI9CIFChHW6ALc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782592897; c=relaxed/simple; bh=HtXDBhNJ6iDaLKHdfmt0ciip0sHkruR5d9jr96i/XwM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=SfmyfdN7mONLJQgLTHh/d7xoFDLV0ci3Vv4JVzm7TUN+x1Jkm0famPwdJY0aklWH8E5ZOf6eJTfaJDrFgIeS8QIDyuiknVyNfBqJPI9PImrjqY3LoWIiby5/5lk/19CTtUItPhWZ1EbGz4I/5tF2RU1StYWYVQ47tY+HV0sCESU= 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=kffWjEn9; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=ZNWYvWWG; 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="kffWjEn9"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="ZNWYvWWG" Received: from pps.filterd (m0279873.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65RKaG3Q2041559 for ; Sat, 27 Jun 2026 20:41:34 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=qcppdkim1; bh=CrKyGl+ALPI V+8lc/Z+qyUaTN+TwsBKYRuz+GUivLz0=; b=kffWjEn9axKaxgcdItKaRZX80U2 diEum9QqXegwPwke5yAmM8gFIIAUdYj14nQipcAMT2zl+0+uaIZrmn54CiEGyL/b SMaOGUHn1LYsrkpK6jKh1a9dboMor11EnHY4ZYcX+x+oV2wUXSNWBTQzyzxDugvw zcp9POmSW+i18Ma+6ZeAPUTzCu0TMWpp6l/hq3yqijfG/7hKOhQAlW+JbuzZ+9l5 lxvPulWfjkI02k8KqnmMA1uTzOicQCtAqlOmLf+99E2Io3sxMhNZmi8sbmySkGpn gccS3rVwrs7Sb4lkyMeWjwfnVwV8hfXh4FyN1VWtsPcHUI72vXyRiBmZrcQ== Received: from mail-dl1-f72.google.com (mail-dl1-f72.google.com [74.125.82.72]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4f27349n16-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Sat, 27 Jun 2026 20:41:33 +0000 (GMT) Received: by mail-dl1-f72.google.com with SMTP id a92af1059eb24-137fe4a3640so3594195c88.0 for ; Sat, 27 Jun 2026 13:41:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1782592893; x=1783197693; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=CrKyGl+ALPIV+8lc/Z+qyUaTN+TwsBKYRuz+GUivLz0=; b=ZNWYvWWGWJPvBB/ZrmjoJ9ZFW+L9euDDVK8vThvKGKBJvrdV+/Xl4clPmeb90xUfQG cMacBOOJP4DdG6nTJgJ5OJ1vUyOM2PIDRj+BIU5zQwEEtXeXBZSs9PPUxepL572GOV6y fHZAJTR/2kj1+t1Cj4OJD6i/rBfJq5IOU2xIHMHRtAy7rb/MAZp8oa0dUcbD4EjRy9E4 vXrYRl3/HFRmGLhg7FD1bot+Sr9RFO0AoUjkrn3xs9RJfh92VGwH79ZG+x+8fMNnAbGz xCL53+U7hvDwngbdpfCl6sA2JRCObWut05NcuOBJLrSVeWRA7h8aYik0uVOWe6cW/ZCS CzCA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782592893; x=1783197693; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=CrKyGl+ALPIV+8lc/Z+qyUaTN+TwsBKYRuz+GUivLz0=; b=JPy+1SBBtcK8CAiPjW8OUIVjPI5HXiYYqsmPJuj1+Qm9fS70uTehcDOplcXkTtSgjT 5xiR0uRlMOwxGl724yWwZYRNOfPQFLR6ii4hLm9VsSYDcdLEP63lnAHjIhO2Z3uJDnUV RTwcmJ7kdlqECh7HLaTF8/9YefdGUtMzITJ4dnlXWofpLYvkOqJcKyrMrYbMbZmrQueh OIiMsMBlU/DxkXm5S5C2WJPkhu03sF6SYvQ0BN/Zwa3fQfN3HEX7AGf5u3RBfZblTD9h DVvn2cDo/3Y9s7svit0FQVd/vG0TzMg/GsFAbT7cqs5PheHPTmGiNiI0nyJRlFvDeu36 xSug== X-Gm-Message-State: AOJu0Yy39rKCgp/RTsjzoIxuvViEm1PxguHa5rNatSh1C1KP15UFEtJb zie9PVrYh8d8AHXcn79vGyoFtZFSThF+fngT8HbAggGE5vkRchOuFq3KOCaVcO4C3CTVqquXmW0 1VLu08pzqrWE1uiLUG+Ln9dmntyceed5rIA8NnC4Mz0s3/qSWp0bcpiAIYJxqU/qqF4w= X-Gm-Gg: AfdE7ck5Kt11SM95K8oqmyqVTsHCvi+6ZubE+B359Eec3URkdr6ocnMEz72O7QJIwyG K3CQjHS43DgRaPBw/nwv/WKXQZ211W0BMgOscl36lwMgnXbmz1PMQWmVjtHkwYOZoLVjprzVE0u 6CFE7eFJ2LhIYA3JkBRRQTl6BambD1UxZ9VIkTUgRAOW55ZlEfnHZm/OjOpBq63q0uWDQPHKt// JgjeSw+2uwWdQ7TLEjWIrA9iBI/bq/D//69l3rqlX8IYmQDYui7KURtXCOB13T5ZM3Baome9CQp Cr+bdbUHD0oex+hXdhyw7csNSbi8NWPpW1nIy4AUrHKPUaQC60V+4Hjjk1JHQrouYzb17jKEzXQ kJTPJYQ2BY+c2pyDAG4iKLOZiWY3YvrIlNqAk X-Received: by 2002:a05:7022:690:b0:138:4e1:4701 with SMTP id a92af1059eb24-139db9e612dmr9757108c88.7.1782592892373; Sat, 27 Jun 2026 13:41:32 -0700 (PDT) X-Received: by 2002:a05:7022:690:b0:138:4e1:4701 with SMTP id a92af1059eb24-139db9e612dmr9757091c88.7.1782592891754; Sat, 27 Jun 2026 13:41:31 -0700 (PDT) Received: from hu-mdsor-hyd.qualcomm.com ([202.46.22.19]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-139d8f318f3sm33653900c88.2.2026.06.27.13.41.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 27 Jun 2026 13:41:31 -0700 (PDT) From: mohit.dsor@oss.qualcomm.com To: dri-devel@lists.freedesktop.org Cc: linux-kernel@vger.kernel.org, boss@oss.qualcomm.com, qc-display-maintainer@oss.qualcomm.com Subject: [PATCH] drm: bridge: add support for lontium LT9611UXD bridge Date: Sun, 28 Jun 2026 02:11:16 +0530 Message-Id: <20260627204125.3334834-2-mohit.dsor@oss.qualcomm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260627204125.3334834-1-mohit.dsor@oss.qualcomm.com> References: <20260627204125.3334834-1-mohit.dsor@oss.qualcomm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Proofpoint-GUID: _WAv8JjbbwrOP5A4_l_1ad7VGswvG3ms X-Proofpoint-ORIG-GUID: _WAv8JjbbwrOP5A4_l_1ad7VGswvG3ms X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI3MDE4MyBTYWx0ZWRfX1h8aF70hMzNI XQKkra82wVMtiwOO1qrEs3wkKGVMu8qtgIwVaRtX/bq1EB3aqyVCXj7HzWDHR0dNMPkqzlLjo0M gvfk28sPeCye0/hg4i1PDB215NC7eOg= X-Authority-Analysis: v=2.4 cv=HYokiCE8 c=1 sm=1 tr=0 ts=6a40357d cx=c_pps a=bS7HVuBVfinNPG3f6cIo3Q==:117 a=fChuTYTh2wq5r3m49p7fHw==:17 a=FelO9ux0wxsA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=rJkE3RaqiGZ5pbrm-msn:22 a=EUspDBNiAAAA:8 a=sGNGgX2c9Exm6_61A6cA:9 a=vBUdepa8ALXHeOFLBtFW:22 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI3MDE4MyBTYWx0ZWRfX7qAwhnF7eiCw F+MVCP/b1y+Y9YkZ+pvw2Ercmcz6gr0MK9F+DE1zTwLNoiAlQw9C/NOnHsiWzKuDgsiHeeCYWms 5CnGfXOixryd2XNJic1v3Nja/k/yz26ZyoRbVcwN2WzSAK9LNwWARjqljWv+ExkotqgeKevvSLM 4/QWggxgZqngeaffXi4gwXVkYV468r8f8JqL4/MEw+W7gEC5OWnDbAOXWgghKGpKhDorbWYZbxK Um7x5iAxzRXpNFM3G7I1iC2kQmM8N4yCg1VV589WDeoChGNuFRTT0FRumJpTE7OZvAY8QfHtoWw vPQBvyADpw+/2719EAah4WhGTa6SquAOH9Hj7mui/jAThy3hDee6/nYXcCScLQFX9dILHQihgGs 6hKuKEHBTChxBUKEkyZmzM/JSl0nfYWqD+43AMBbxP+h+/Ob+jhJxGcBUI16wU0ce15KSIn/aVw z7RBM0B+4CaPXnxgJlg== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-27_03,2026-06-26_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 clxscore=1015 suspectscore=0 priorityscore=1501 lowpriorityscore=0 impostorscore=0 spamscore=0 malwarescore=0 bulkscore=0 phishscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606270183 From: Mohit Dsor Add support for Lontium LT9611UXD HDMI bridge. Lontium LT9611UXD is a DSI to HDMI bridge which supports DSI ports and I2S port as an input and HDMI port as output. Despite name being similar to LT9611 and LT9611UXD, these devices are different enough to warrant separate driver. Signed-off-by: Mohit Dsor --- drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt9611uxd.c | 1140 ++++++++++++++++++++ 2 files changed, 1141 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611uxd.c diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7..8ea14c6404f3 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -17,6 +17,7 @@ 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_LT9611UXC) += lontium-lt9611uxc.o +obj-$(CONFIG_DRM_LONTIUM_LT9611UXD) += lontium-lt9611uxd.o obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxd.c b/drivers/gpu/drm/bridge/lontium-lt9611uxd.c new file mode 100644 index 000000000000..879ea4bc91c4 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxd.c @@ -0,0 +1,1140 @@ +// 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 +#include +#include +#include +#include +#include + +#include +#include + +#define FW_SIZE (64 * 1024) +#define LT_PAGE_SIZE 256 +#define FW_FILE "LT9611UXD.bin" + +#define LT9611UXD_CHIP_ID0 0x23 +#define LT9611UXD_CHIP_ID1 0x06 + +DECLARE_CRC8_TABLE(lt9611uxd_crc_table); + +struct lt9611uxd { + struct device *dev; + struct i2c_client *client; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct regmap *regmap; + /* Protects all accesses to registers by stopping the on-chip MCU */ + struct mutex ocm_lock; + struct wait_queue_head wq; + struct work_struct work; + struct device_node *dsi0_node; + struct device_node *dsi1_node; + struct mipi_dsi_device *dsi0; + struct mipi_dsi_device *dsi1; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + const struct firmware *fw; + int fw_version; + u8 fw_crc; + + bool hdmi_connected; + bool edid_read; +}; + +#define LT9611D_PAGE_CONTROL 0xff + +static const struct regmap_range_cfg lt9611uxd_ranges[] = { + { + .name = "register_range", + .range_min = 0, + .range_max = 0xffff, + .selector_reg = LT9611D_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config lt9611uxd_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xffff, + .ranges = lt9611uxd_ranges, + .num_ranges = ARRAY_SIZE(lt9611uxd_ranges), +}; + +static int lt9611uxd_i2c_read_write_flow(struct lt9611uxd *lt9611uxd, u8 *params, + unsigned int param_count, u8 *return_buffer, + unsigned int return_count) +{ + int count, i; + unsigned int temp; + + regmap_write(lt9611uxd->regmap, 0xe0de, 0x01); + + count = 0; + do { + regmap_read(lt9611uxd->regmap, 0xe0ae, &temp); + usleep_range(1000, 2000); + count++; + } while (count < 100 && temp != 0x01); + + if (temp != 0x01) + return -1; + + for (i = 0; i < param_count; i++) { + if (i > 0xdd - 0xb0) + break; + + regmap_write(lt9611uxd->regmap, 0xe0b0 + i, params[i]); + } + + regmap_write(lt9611uxd->regmap, 0xe0de, 0x02); + + count = 0; + do { + regmap_read(lt9611uxd->regmap, 0xe0ae, &temp); + usleep_range(1000, 2000); + count++; + } while (count < 100 && temp != 0x02); + + if (temp != 0x02) + return -2; + + regmap_bulk_read(lt9611uxd->regmap, 0xe085, return_buffer, return_count); + + return 0; +} + +static int lt9611uxd_prepare_firmware_data(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + u64 crc_size = FW_SIZE - 1; + u8 default_val = 0xff; + int ret; + + /* ensure filesystem ready */ + msleep(3000); + ret = request_firmware(<9611uxd->fw, FW_FILE, dev); + if (ret) { + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret); + return ret; + } + + if (lt9611uxd->fw->size > FW_SIZE - 1) { + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611uxd->fw->size, FW_SIZE - 1); + lt9611uxd->fw = NULL; + return -EINVAL; + } + + dev_info(dev, "firmware size: %zu bytes\n", lt9611uxd->fw->size); + + lt9611uxd->fw_crc = crc8(lt9611uxd_crc_table, lt9611uxd->fw->data, + lt9611uxd->fw->size, 0); + + crc_size -= lt9611uxd->fw->size; + while (crc_size--) + lt9611uxd->fw_crc = crc8(lt9611uxd_crc_table, &default_val, 1, + lt9611uxd->fw_crc); + + dev_info(dev, "LT9611C.bin crc: 0x%02x\n", lt9611uxd->fw_crc); + + return 0; +} + +static void lt9611uxd_config_parameters(struct lt9611uxd *lt9611uxd) +{ + struct reg_sequence seq_write_paras[] = { + REG_SEQ0(0xe0ee, 0x01), + //fifo rst + REG_SEQ0(0xe103, 0x3f), + 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(lt9611uxd->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras)); +} + +static void lt9611uxd_wren(struct lt9611uxd *lt9611uxd) +{ + regmap_write(lt9611uxd->regmap, 0xe05a, 0x04); + regmap_write(lt9611uxd->regmap, 0xe05a, 0x00); +} + +static void lt9611uxd_wrdi(struct lt9611uxd *lt9611uxd) +{ + regmap_write(lt9611uxd->regmap, 0xe05a, 0x08); + regmap_write(lt9611uxd->regmap, 0xe05a, 0x00); +} + +static void lt9611uxd_erase_op(struct lt9611uxd *lt9611uxd, u32 addr) +{ + 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(lt9611uxd->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611uxd_read_flash_reg_status(struct lt9611uxd *lt9611uxd, unsigned int *status) +{ + struct reg_sequence seq_write[] = { + REG_SEQ0(0xe103, 0x3f), + REG_SEQ0(0xe103, 0xff), //fifo rst + + REG_SEQ0(0xe05e, 0x40), + REG_SEQ0(0xe056, 0x05), + REG_SEQ0(0xe055, 0x25), + REG_SEQ0(0xe055, 0x01), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611uxd->regmap, seq_write, ARRAY_SIZE(seq_write)); + + regmap_read(lt9611uxd->regmap, 0xe05f, status); +} + +static void lt9611uxd_crc_to_sram(struct lt9611uxd *lt9611uxd) +{ + 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(lt9611uxd->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611uxd_data_to_sram(struct lt9611uxd *lt9611uxd) +{ + 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(lt9611uxd->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611uxd_sram_to_flash(struct lt9611uxd *lt9611uxd, u64 addr) +{ + 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(lt9611uxd->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611uxd_block_erase(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + u32 i = 0; + unsigned int flash_status = 0; + unsigned int block_num = 0x00; + u32 flash_addr = 0x00; + + for (block_num = 0; block_num < 2; block_num++) { + flash_addr = (block_num * 0x008000); + lt9611uxd_erase_op(lt9611uxd, flash_addr); + msleep(100); + i = 0; + while (1) { + lt9611uxd_read_flash_reg_status(lt9611uxd, &flash_status); + if ((flash_status & 0x01) == 0) + break; + + if (i > 50) + break; + + i++; + msleep(50); + } + } + + dev_info(dev, "erase flash done.\n"); +} + +static int lt9611uxd_write_data(struct lt9611uxd *lt9611uxd, u64 addr) +{ + struct device *dev = lt9611uxd->dev; + int ret; + int page = 0, num = 0, i = 0; + u64 size, index; + const u8 *data; + unsigned int value; + + data = lt9611uxd->fw->data; + size = lt9611uxd->fw->size; + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE; + if (page * LT_PAGE_SIZE > FW_SIZE) { + dev_err(dev, "firmware size out of range\n"); + return -EINVAL; + } + + dev_info(dev, "%u pages, total size %llu byte\n", page, size); + + for (num = 0; num < page; num++) { + lt9611uxd_data_to_sram(lt9611uxd); + + for (i = 0; i < LT_PAGE_SIZE; i++) { + index = num * LT_PAGE_SIZE + i; + value = (index < size) ? data[index] : 0xff; + + ret = regmap_write(lt9611uxd->regmap, 0xe059, value); + if (ret < 0) { + dev_err(dev, "write error at page %u, index %u\n", num, i); + return ret; + } + } + + lt9611uxd_wren(lt9611uxd); + lt9611uxd_sram_to_flash(lt9611uxd, addr); + + addr += LT_PAGE_SIZE; + } + + lt9611uxd_wrdi(lt9611uxd); + + return 0; +} + +static int lt9611uxd_write_crc(struct lt9611uxd *lt9611uxd, u64 addr) +{ + struct device *dev = lt9611uxd->dev; + int ret; + u8 crc; + + crc = lt9611uxd->fw_crc; + lt9611uxd_crc_to_sram(lt9611uxd); + ret = regmap_write(lt9611uxd->regmap, 0xe059, crc); + if (ret < 0) { + dev_err(dev, "failed to write CRC\n"); + return -1; + } + + lt9611uxd_wren(lt9611uxd); + lt9611uxd_sram_to_flash(lt9611uxd, addr); + lt9611uxd_wrdi(lt9611uxd); + + dev_info(dev, "CRC 0x%02x written to flash at addr 0x%llx\n", crc, addr); + + return 0; +} + +static int lt9611uxd_firmware_upgrade(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + int ret; + + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611uxd->fw->size); + + lt9611uxd_config_parameters(lt9611uxd); + lt9611uxd_block_erase(lt9611uxd); + + ret = lt9611uxd_write_data(lt9611uxd, 0); + if (ret < 0) { + dev_err(dev, "Failed to write firmware data\n"); + return ret; + } + + ret = lt9611uxd_write_crc(lt9611uxd, FW_SIZE - 1); + if (ret < 0) { + dev_err(dev, "Failed to write firmware CRC\n"); + return ret; + } + + return 0; +} + +static int lt9611uxd_upgrade_result(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + unsigned int crc_result; + + regmap_write(lt9611uxd->regmap, 0xe0ee, 0x01); + regmap_read(lt9611uxd->regmap, 0xe021, &crc_result); + + if (crc_result != lt9611uxd->fw_crc) { + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n", + lt9611uxd->fw_crc, crc_result); + return -EIO; + } + + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02x\n", crc_result); + return 0; +} + +static struct lt9611uxd *bridge_to_lt9611uxd(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9611uxd, bridge); +} + +static void lt9611uxd_lock(struct lt9611uxd *lt9611uxd) +{ + mutex_lock(<9611uxd->ocm_lock); + regmap_write(lt9611uxd->regmap, 0xe0ee, 0x01); +} + +static void lt9611uxd_unlock(struct lt9611uxd *lt9611uxd) +{ + regmap_write(lt9611uxd->regmap, 0xe0ee, 0x00); + msleep(50); + mutex_unlock(<9611uxd->ocm_lock); +} + +static int lt9611uxd_wait_for_edid(struct lt9611uxd *lt9611uxd) +{ + return wait_event_interruptible_timeout(lt9611uxd->wq, lt9611uxd->edid_read, + msecs_to_jiffies(500)); +} + +static irqreturn_t lt9611uxd_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611uxd *lt9611uxd = dev_id; + struct device *dev = lt9611uxd->dev; + int ret; + unsigned int irq_status; + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00}; + u8 data[5]; + + regmap_read(lt9611uxd->regmap, 0xe084, &irq_status); + if (!(irq_status & BIT(0))) + return IRQ_HANDLED; + + lt9611uxd_lock(lt9611uxd); + + /* Check for EDID ready */ + if (irq_status & BIT(0)) { + lt9611uxd->edid_read = true; + wake_up_all(<9611uxd->wq); + } + + ret = lt9611uxd_i2c_read_write_flow(lt9611uxd, cmd, 5, data, 5); + if (ret) { + dev_err(dev, "failed to read HPD status\n"); + } else { + lt9611uxd->hdmi_connected = (data[4] == 0x02); + dev_info(dev, "HDMI %s\n", lt9611uxd->hdmi_connected ? "connected" : "disconnected"); + } + + schedule_work(<9611uxd->work); + + /*clear interrupt*/ + regmap_write(lt9611uxd->regmap, 0xe0df, irq_status & BIT(0)); + usleep_range(10000, 12000); + regmap_write(lt9611uxd->regmap, 0xe0df, irq_status & (~BIT(0))); + + lt9611uxd_unlock(lt9611uxd); + + return IRQ_HANDLED; +} + +static void lt9611uxd_hpd_work(struct work_struct *work) +{ + struct lt9611uxd *lt9611uxd = container_of(work, struct lt9611uxd, work); + bool connected; + + mutex_lock(<9611uxd->ocm_lock); + connected = lt9611uxd->hdmi_connected; + mutex_unlock(<9611uxd->ocm_lock); + + drm_bridge_hpd_notify(<9611uxd->bridge, connected ? + connector_status_connected : + connector_status_disconnected); +} + +static void lt9611uxd_reset(struct lt9611uxd *lt9611uxd) +{ + gpiod_set_value_cansleep(lt9611uxd->reset_gpio, 1); + msleep(20); + gpiod_set_value_cansleep(lt9611uxd->reset_gpio, 0); + msleep(20); + gpiod_set_value_cansleep(lt9611uxd->reset_gpio, 1); + msleep(400); +} + +static int lt9611uxd_regulator_init(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + int ret; + + lt9611uxd->supplies[0].supply = "vcc"; + lt9611uxd->supplies[1].supply = "vdd"; + + ret = devm_regulator_bulk_get(dev, 2, lt9611uxd->supplies); + if (ret < 0) + return ret; + + return regulator_set_load(lt9611uxd->supplies[0].consumer, 200000); +} + +static int lt9611uxd_regulator_enable(struct lt9611uxd *lt9611uxd) +{ + int ret; + + ret = regulator_enable(lt9611uxd->supplies[0].consumer); + if (ret < 0) + return ret; + + usleep_range(5000, 10000); + + ret = regulator_enable(lt9611uxd->supplies[1].consumer); + if (ret < 0) { + regulator_disable(lt9611uxd->supplies[0].consumer); + return ret; + } + + return 0; +} + +static struct mipi_dsi_device *lt9611uxd_attach_dsi(struct lt9611uxd *lt9611uxd, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info = { "lt9611uxd", 0, NULL }; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct device *dev = lt9611uxd->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)) { + dev_err(dev, "failed to create dsi device\n"); + return dsi; + } + + 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) { + dev_err(dev, "failed to attach dsi to host\n"); + return ERR_PTR(ret); + } + + return dsi; +} + +static int lt9611uxd_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt9611uxd *lt9611uxd = bridge_to_lt9611uxd(bridge); + + return drm_bridge_attach(encoder, lt9611uxd->next_bridge, + bridge, flags); +} + +static enum drm_mode_status lt9611uxd_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + u32 pixclk; + + pixclk = (mode->htotal * mode->vtotal * drm_mode_vrefresh(mode)) / 1000000; + + if (pixclk >= 25 && pixclk <= 340) + return MODE_OK; + else + return MODE_BAD; +} + +static void lt9611uxd_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + struct lt9611uxd *lt9611uxd = bridge_to_lt9611uxd(bridge); + struct device *dev = lt9611uxd->dev; + int ret; + u32 h_total, hactive, hsync_len, hfront_porch, hback_porch; + u32 v_total, vactive, vsync_len, vfront_porch, vback_porch; + u8 video_timing_set_cmd[26] = {0x57, 0x4d, 0x33, 0x3a}; + u8 return_timing_set_param[3]; + u8 framerate; + u8 vic = 0x00; + + h_total = mode->htotal; + hactive = mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + hfront_porch = mode->hsync_start - mode->hdisplay; + hback_porch = mode->htotal - mode->hsync_end; + + v_total = mode->vtotal; + vactive = mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + vfront_porch = mode->vsync_start - mode->vdisplay; + vback_porch = mode->vtotal - mode->vsync_end; + framerate = drm_mode_vrefresh(mode); + vic = drm_match_cea_mode(mode); + + dev_info(dev, "hactive=%d, vactive=%d\n", hactive, vactive); + dev_info(dev, "framerate=%d\n", framerate); + dev_info(dev, "vic = 0x%02x\n", vic); + + video_timing_set_cmd[4] = (h_total >> 8) & 0xff; + video_timing_set_cmd[5] = h_total & 0xff; + video_timing_set_cmd[6] = (hactive >> 8) & 0xff; + video_timing_set_cmd[7] = hactive & 0xff; + video_timing_set_cmd[8] = (hfront_porch >> 8) & 0xff; + video_timing_set_cmd[9] = hfront_porch & 0xff; + video_timing_set_cmd[10] = (hsync_len >> 8) & 0xff; + video_timing_set_cmd[11] = hsync_len & 0xff; + video_timing_set_cmd[12] = (hback_porch >> 8) & 0xff; + video_timing_set_cmd[13] = hback_porch & 0xff; + video_timing_set_cmd[14] = (v_total >> 8) & 0xff; + video_timing_set_cmd[15] = v_total & 0xff; + video_timing_set_cmd[16] = (vactive >> 8) & 0xff; + video_timing_set_cmd[17] = vactive & 0xFF; + video_timing_set_cmd[18] = (vfront_porch >> 8) & 0xff; + video_timing_set_cmd[19] = vfront_porch & 0xff; + video_timing_set_cmd[20] = (vsync_len >> 8) & 0xff; + video_timing_set_cmd[21] = vsync_len & 0xff; + video_timing_set_cmd[22] = (vback_porch >> 8) & 0xff; + video_timing_set_cmd[23] = vback_porch & 0xff; + video_timing_set_cmd[24] = framerate; + video_timing_set_cmd[25] = vic; + + mutex_lock(<9611uxd->ocm_lock); + ret = lt9611uxd_i2c_read_write_flow(lt9611uxd, video_timing_set_cmd, 26, return_timing_set_param, 3); + if (ret) + dev_err(dev, "video set failed\n"); + mutex_unlock(<9611uxd->ocm_lock); +} + +static enum drm_connector_status lt9611uxd_bridge_detect(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611uxd *lt9611uxd = bridge_to_lt9611uxd(bridge); + struct device *dev = lt9611uxd->dev; + int ret; + bool connected = false; + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00}; + u8 data[5]; + + mutex_lock(<9611uxd->ocm_lock); + ret = lt9611uxd_i2c_read_write_flow(lt9611uxd, cmd, 5, data, 5); + if (ret) + dev_err(dev, "Failed to read HPD status (err=%d)\n", ret); + else + connected = (data[4] == 0x02); + + lt9611uxd->hdmi_connected = connected; + + mutex_unlock(<9611uxd->ocm_lock); + + return connected ? connector_status_connected : + connector_status_disconnected; +} + +static int lt9611uxd_get_edid_block(void *data, u8 *buf, + unsigned int block, size_t len) +{ + struct lt9611uxd *lt9611uxd = data; + struct device *dev = lt9611uxd->dev; + u8 cmd[5] = {0x52, 0x48, 0x33, 0x3a, 0x00}; + u8 packet[37]; + int ret, i, offset = 0; + + if (len != 128) + return -EINVAL; + + for (i = 0; i < 4; i++) { + cmd[4] = block * 4 + i; + ret = lt9611uxd_i2c_read_write_flow(lt9611uxd, cmd, sizeof(cmd), + packet, sizeof(packet)); + if (ret) { + dev_err(dev, "Failed to read EDID block %u packet %d\n", + block, i); + return ret; + } + + memcpy(buf + offset, &packet[5], 32); + offset += 32; + } + + return 0; +} + +static const struct drm_edid *lt9611uxd_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611uxd *lt9611uxd = bridge_to_lt9611uxd(bridge); + int ret; + + ret = lt9611uxd_wait_for_edid(lt9611uxd); + if (ret < 0) { + dev_err(lt9611uxd->dev, "wait for EDID failed: %d\n", ret); + return NULL; + } else if (ret == 0) { + dev_err(lt9611uxd->dev, "wait for EDID timeout\n"); + return NULL; + } + + return drm_edid_read_custom(connector, lt9611uxd_get_edid_block, lt9611uxd); +} + +static void lt9611uxd_bridge_hpd_notify(struct drm_bridge *bridge, + struct drm_connector *connector, + enum drm_connector_status status) +{ + const struct drm_edid *drm_edid; + + if (status == connector_status_disconnected) { + drm_connector_hdmi_audio_plugged_notify(connector, false); + drm_edid_connector_update(connector, NULL); + return; + } + + drm_edid = lt9611uxd_bridge_edid_read(bridge, connector); + drm_edid_connector_update(connector, drm_edid); + drm_edid_free(drm_edid); + + if (status == connector_status_connected) + drm_connector_hdmi_audio_plugged_notify(connector, true); +} + +static int lt9611uxd_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct lt9611uxd *lt9611uxd = bridge_to_lt9611uxd(bridge); + + dev_info(lt9611uxd->dev, "SOC sample_rate: %d, sample_width: %d, fmt: %d\n", + hparms->sample_rate, hparms->sample_width, fmt->fmt); + + switch (hparms->sample_rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + case 18: + case 20: + case 24: + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + case HDMI_SPDIF: + break; + default: + return -EINVAL; + } + + return 0; +} + +static void lt9611uxd_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ +} + +static const struct drm_bridge_funcs lt9611uxd_bridge_funcs = { + .attach = lt9611uxd_bridge_attach, + .mode_valid = lt9611uxd_bridge_mode_valid, + .mode_set = lt9611uxd_bridge_mode_set, + .detect = lt9611uxd_bridge_detect, + .edid_read = lt9611uxd_bridge_edid_read, + .hpd_notify = lt9611uxd_bridge_hpd_notify, + .hdmi_audio_prepare = lt9611uxd_hdmi_audio_prepare, + .hdmi_audio_shutdown = lt9611uxd_hdmi_audio_shutdown, +}; + +static int lt9611uxd_parse_dt(struct device *dev, + struct lt9611uxd *lt9611uxd) +{ + lt9611uxd->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1); + if (!lt9611uxd->dsi0_node) { + dev_err(dev, "failed to get remote node for primary dsi\n"); + return -ENODEV; + } + + lt9611uxd->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); + + return drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, <9611uxd->next_bridge); +} + +static int lt9611uxd_gpio_init(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + + /* + * Initialise reset GPIO low (active-low reset: LOW = chip held in reset). + * lt9611uxd_reset() will immediately toggle HIGH->LOW->HIGH to release. + */ + lt9611uxd->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lt9611uxd->reset_gpio)) { + dev_err(dev, "failed to acquire reset gpio\n"); + return PTR_ERR(lt9611uxd->reset_gpio); + } + + return 0; +} + +static int lt9611uxd_read_version(struct lt9611uxd *lt9611uxd) +{ + u8 buf[2]; + int ret; + + ret = regmap_write(lt9611uxd->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret = regmap_bulk_read(lt9611uxd->regmap, 0xe080, buf, 2); + if (ret) + return ret; + + return (buf[0] << 8) | buf[1]; +} + +static int lt9611uxd_read_chipid(struct lt9611uxd *lt9611uxd) +{ + struct device *dev = lt9611uxd->dev; + u8 chipid[2]; + int ret; + + ret = regmap_write(lt9611uxd->regmap, 0xe0ee, 0x01); + if (ret) { + dev_err(dev, "Failed to write unlock register: %d\n", ret); + return ret; + } + + ret = regmap_bulk_read(lt9611uxd->regmap, 0xe100, chipid, 2); + if (ret) { + dev_err(dev, "Failed to read chip ID: %d\n", ret); + return ret; + } + + if (chipid[0] != LT9611UXD_CHIP_ID0 || chipid[1] != LT9611UXD_CHIP_ID1) { + dev_err(dev, "unexpected ChipID: 0x%02x 0x%02x, expected: 0x%02x 0x%02x\n", + chipid[0], chipid[1], LT9611UXD_CHIP_ID0, LT9611UXD_CHIP_ID1); + return -ENODEV; + } + return 0; +} + +static ssize_t lt9611uxd_firmware_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lt9611uxd *lt9611uxd = dev_get_drvdata(dev); + int ret; + + lt9611uxd_lock(lt9611uxd); + ret = lt9611uxd_prepare_firmware_data(lt9611uxd); + if (ret < 0) { + dev_err(dev, "Failed prepare firmware data: %d\n", ret); + goto out; + } + + ret = lt9611uxd_firmware_upgrade(lt9611uxd); + if (ret < 0) { + dev_err(dev, "upgrade failure\n"); + goto out; + } + lt9611uxd_reset(lt9611uxd); + ret = lt9611uxd_upgrade_result(lt9611uxd); + if (ret < 0) + goto out; + +out: + lt9611uxd_unlock(lt9611uxd); + lt9611uxd_reset(lt9611uxd); + if (lt9611uxd->fw) { + release_firmware(lt9611uxd->fw); + lt9611uxd->fw = NULL; + } + + return ret < 0 ? ret : len; +} + +static ssize_t lt9611uxd_firmware_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lt9611uxd *lt9611uxd = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%04x\n", lt9611uxd->fw_version); +} + +static DEVICE_ATTR_RW(lt9611uxd_firmware); + +static struct attribute *lt9611uxd_attrs[] = { + &dev_attr_lt9611uxd_firmware.attr, + NULL, +}; + +static const struct attribute_group lt9611uxd_attr_group = { + .attrs = lt9611uxd_attrs, +}; + +static const struct attribute_group *lt9611uxd_attr_groups[] = { + <9611uxd_attr_group, + NULL, +}; + +static int lt9611uxd_probe(struct i2c_client *client) +{ + struct lt9611uxd *lt9611uxd; + struct device *dev = &client->dev; + int ret; + bool fw_updated = false; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "device doesn't support I2C\n"); + return -ENODEV; + } + + lt9611uxd = devm_drm_bridge_alloc(dev, struct lt9611uxd, bridge, <9611uxd_bridge_funcs); + if (IS_ERR(lt9611uxd)) + return PTR_ERR(lt9611uxd); + + lt9611uxd->dev = dev; + lt9611uxd->client = client; + mutex_init(<9611uxd->ocm_lock); + + lt9611uxd->regmap = devm_regmap_init_i2c(client, <9611uxd_regmap_config); + if (IS_ERR(lt9611uxd->regmap)) { + dev_err(dev, "regmap i2c init failed\n"); + return PTR_ERR(lt9611uxd->regmap); + } + + ret = lt9611uxd_parse_dt(dev, lt9611uxd); + if (ret) { + dev_err(dev, "failed to parse device tree\n"); + return ret; + } + + ret = lt9611uxd_gpio_init(lt9611uxd); + if (ret < 0) + goto err_of_put; + + ret = lt9611uxd_regulator_init(lt9611uxd); + if (ret < 0) + goto err_of_put; + + ret = lt9611uxd_regulator_enable(lt9611uxd); + if (ret) + goto err_of_put; + + lt9611uxd_reset(lt9611uxd); + + ret = lt9611uxd_read_chipid(lt9611uxd); + if (ret < 0) { + dev_err(dev, "failed to read chip id.\n"); + goto err_disable_regulators; + } + + lt9611uxd_lock(lt9611uxd); +retry: + ret = lt9611uxd_read_version(lt9611uxd); + if (ret < 0) { + dev_err(dev, "failed to read FW version\n"); + lt9611uxd_unlock(lt9611uxd); + goto err_disable_regulators; + + } else if (ret == 0) { /*Upgrade conditions*/ + if (!fw_updated) { + fw_updated = true; + ret = lt9611uxd_prepare_firmware_data(lt9611uxd); + if (ret < 0) { + lt9611uxd_unlock(lt9611uxd); + goto err_disable_regulators; + } + + ret = lt9611uxd_firmware_upgrade(lt9611uxd); + if (ret < 0) { + lt9611uxd_unlock(lt9611uxd); + goto err_disable_regulators; + } + + lt9611uxd_reset(lt9611uxd); + + ret = lt9611uxd_upgrade_result(lt9611uxd); + if (ret < 0) { + lt9611uxd_unlock(lt9611uxd); + goto err_disable_regulators; + } else { + goto retry; + } + } else { + dev_err(dev, "FW version 0x%04x, update failed\n", ret); + ret = -EOPNOTSUPP; + lt9611uxd_unlock(lt9611uxd); + goto err_disable_regulators; + } + } + + if (lt9611uxd->fw) { + release_firmware(lt9611uxd->fw); + lt9611uxd->fw = NULL; + } + lt9611uxd_unlock(lt9611uxd); + lt9611uxd->fw_version = ret; + + dev_info(dev, "current version:0x%04x", lt9611uxd->fw_version); + + init_waitqueue_head(<9611uxd->wq); + INIT_WORK(<9611uxd->work, lt9611uxd_hpd_work); + + ret = request_threaded_irq(client->irq, NULL, + lt9611uxd_irq_thread_handler, + IRQF_ONESHOT, "lt9611uxd", lt9611uxd); + + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto err_disable_regulators; + } + + i2c_set_clientdata(client, lt9611uxd); + + lt9611uxd->bridge.of_node = client->dev.of_node; + lt9611uxd->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_HDMI_AUDIO; + lt9611uxd->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + lt9611uxd->bridge.hdmi_audio_dev = dev; + lt9611uxd->bridge.hdmi_audio_max_i2s_playback_channels = 8; + lt9611uxd->bridge.hdmi_audio_dai_port = 2; + + drm_bridge_add(<9611uxd->bridge); + + crc8_populate_msb(lt9611uxd_crc_table, 0x31); + + /* Attach primary DSI */ + lt9611uxd->dsi0 = lt9611uxd_attach_dsi(lt9611uxd, lt9611uxd->dsi0_node); + if (IS_ERR(lt9611uxd->dsi0)) { + ret = PTR_ERR(lt9611uxd->dsi0); + goto err_remove_bridge; + } + + /* Attach secondary DSI, if specified */ + if (lt9611uxd->dsi1_node) { + lt9611uxd->dsi1 = lt9611uxd_attach_dsi(lt9611uxd, lt9611uxd->dsi1_node); + if (IS_ERR(lt9611uxd->dsi1)) { + ret = PTR_ERR(lt9611uxd->dsi1); + goto err_remove_bridge; + } + } + + return 0; + +err_remove_bridge: + free_irq(client->irq, lt9611uxd); + cancel_work_sync(<9611uxd->work); + drm_bridge_remove(<9611uxd->bridge); + +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(lt9611uxd->supplies), lt9611uxd->supplies); + +err_of_put: + of_node_put(lt9611uxd->dsi1_node); + of_node_put(lt9611uxd->dsi0_node); + + if (lt9611uxd->fw) { + release_firmware(lt9611uxd->fw); + lt9611uxd->fw = NULL; + } + + return ret; +} + +static void lt9611uxd_remove(struct i2c_client *client) +{ + struct lt9611uxd *lt9611uxd = i2c_get_clientdata(client); + + free_irq(client->irq, lt9611uxd); + cancel_work_sync(<9611uxd->work); + drm_bridge_remove(<9611uxd->bridge); + mutex_destroy(<9611uxd->ocm_lock); + regulator_bulk_disable(ARRAY_SIZE(lt9611uxd->supplies), lt9611uxd->supplies); + of_node_put(lt9611uxd->dsi1_node); + of_node_put(lt9611uxd->dsi0_node); +} + +static const struct i2c_device_id lt9611uxd_id[] = { + { "lontium,lt9611uxd" }, + { /* sentinel */ } +}; + +static const struct of_device_id lt9611uxd_match_table[] = { + { .compatible = "lontium,lt9611uxd" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lt9611uxd_match_table); + +static struct i2c_driver lt9611uxd_driver = { + .driver = { + .name = "lt9611uxd", + .of_match_table = lt9611uxd_match_table, + .dev_groups = lt9611uxd_attr_groups, + }, + .probe = lt9611uxd_probe, + .remove = lt9611uxd_remove, + .id_table = lt9611uxd_id, +}; +module_i2c_driver(lt9611uxd_driver); + +MODULE_AUTHOR("mohit dsor "); +MODULE_DESCRIPTION("Lontium LT9611UXD MIPI DSI to HDMI bridge driver"); +MODULE_LICENSE("GPL v2"); + +MODULE_FIRMWARE(FW_FILE); -- 2.34.1