From nobody Fri Jun 12 12:45:07 2026 Received: from out28-123.mail.aliyun.com (out28-123.mail.aliyun.com [115.124.28.123]) (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 CA2613FA5CF; Fri, 15 May 2026 08:09:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778832594; cv=none; b=pwlWHtKXqWpcO4RzCmpXz9LYW8g+GWd22xQDZcOGQBOyjTYE0fD3q52ZKiWWabCU6r4WSdWrgMdk+Wo1V7G4TwSmOtBgb1R4sMKVLYfg9I8GuF1XZeiM9e7+OubQVGMKbvGH852I8M0dw0sOL45dpkOOQZIDYa87NQDejXs76BY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778832594; c=relaxed/simple; bh=FwZilHJvlu5BcKUF1QM9XkgeYWA0R8eDrcBtZ+1ty8U=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=GO0xarJ1snOZW4IY61Qg0sWB7J/vDyN2TCdNPte+DGtspWP3ZglHnoqmmXF077I6gtbFsKuCl6hn3Q8Cm5glGU/gZRiAP3D9Z18KgEYRLNVkQbjBRukd2O+tjY60xrxM2qabz3q+E8UdoFfIZMS8PvOcjfRvrYk89LfXt9apdoM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=lontium.com; spf=pass smtp.mailfrom=lontium.com; dkim=pass (2048-bit key) header.d=lontium.com header.i=@lontium.com header.b=SNWLUwev; arc=none smtp.client-ip=115.124.28.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=lontium.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lontium.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lontium.com header.i=@lontium.com header.b="SNWLUwev" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lontium.com; s=default; t=1778832583; h=From:To:Subject:Date:Message-Id:MIME-Version; bh=eVZn7MAONGMHowrlbF8Q4henpwp8Ya3lVGmR/icgdNU=; b=SNWLUwevHG6s5CUTaImoY2XU/qJMhXTOuA/COFvj6FPtHwZC1obLhRtzfj8gxIC53hlS8zySAVBMfoMuvNNPT2Ro/MHHsVkuTziFp0zgc0a6U1GAj8piPByfUi7vTKnpiruxVFNitH6Z5UGXqPsCw9uTvPUoMnRFcejGG58WhanAi3R0S3qCI2BMtxmiiSIqvlvfnwGJS3MUMokj5mxhbZ2NmOmYT8Y4QIWv6ixPpgm1DtNPjVY4OfZUPFq8QFIySRbn2KVEq9qUXpwZTVqVJO+zRtKXuSdXyLHeK+ELW/emkQDtStwITozt5dq9/j8JHPcYz3pkjt/cWw6QcSkH0Q== X-Alimail-AntiSpam: AC=CONTINUE;BC=0.08642446|-1;BR=01201311R211ec;CH=green;DM=|CONTINUE|false|;DS=SPAM|spam_ad|0.867174-0.00121929-0.131607;FP=12538499638350542130|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033040074035;MF=syyang@lontium.com;NM=1;PH=DS;RN=22;RT=22;SR=0;TI=SMTPD_---.hXlHgsQ_1778832578; Received: from DESKTOP-V2MKAT2.localdomain(mailfrom:syyang@lontium.com fp:SMTPD_---.hXlHgsQ_1778832578 cluster:ay29) by smtp.aliyun-inc.com; Fri, 15 May 2026 16:09:41 +0800 From: syyang@lontium.com To: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, andrzej.hajda@intel.com, neil.armstrong@linaro.org, dmitry.baryshkov@oss.qualcomm.com, maarten.lankhorst@linux.intel.com, rfoss@kernel.org, mripard@kernel.org Cc: Laurent.pinchart@ideasonboard.com, tzimmermann@suse.de, jonas@kwiboo.se, jernej.skrabec@gmail.com, devicetree@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, xmzhu@lontium.corp-partner.google.com, xmzhu@lontium.com, rlyu@lontium.com, xbpeng@lontium.com, Sunyun Yang , Krzysztof Kozlowski Subject: [PATCH v8 1/2] dt-bindings: bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge Date: Fri, 15 May 2026 16:09:33 +0800 Message-Id: <20260515080934.9870-2-syyang@lontium.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260515080934.9870-1-syyang@lontium.com> References: <20260515080934.9870-1-syyang@lontium.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Sunyun Yang This commit adds the device tree binding schema for the Lontium LT7911EXC. This device is an I2C-controlled bridge that converts eDP 1.4 input to MIPI DSI output. Signed-off-by: Sunyun Yang Reviewed-by: Krzysztof Kozlowski --- .../display/bridge/lontium,lt7911exc.yaml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/lontiu= m,lt7911exc.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt791= 1exc.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911= exc.yaml new file mode 100644 index 000000000000..3290b10ce883 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt7911exc.ya= ml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/lontium,lt7911exc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lontium LT7911EXC eDP to MIPI DSI Bridge + +maintainers: + - Sunyun Yang + +properties: + compatible: + enum: + - lontium,lt7911exc + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + description: GPIO connected to RST_ pin. + + vdd-supply: + description: Regulator for 1.2V MIPI phy power. + + vcc-supply: + description: Regulator for 3.3V IO power. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for eDP input. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for MIPI DSI output. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reset-gpios + - vdd-supply + - vcc-supply + - ports + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + mipi-bridge@41 { + compatible =3D "lontium,lt7911exc"; + reg =3D <0x41>; + reset-gpios =3D <&gpy8 8 GPIO_ACTIVE_LOW>; + vdd-supply =3D <<7911exc_1v2>; + vcc-supply =3D <<7911exc_3v3>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + bridge_in: endpoint { + remote-endpoint =3D <&edp_out>; + }; + }; + + port@1 { + reg =3D <1>; + bridge_out: endpoint { + remote-endpoint =3D <&panel_in>; + }; + }; + }; + }; + }; --=20 2.34.1 From nobody Fri Jun 12 12:45:07 2026 Received: from out28-50.mail.aliyun.com (out28-50.mail.aliyun.com [115.124.28.50]) (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 B750A3B8D48; Fri, 15 May 2026 08:09:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=115.124.28.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778832596; cv=none; b=LhI8ibme5iDGe1s4UxNVviMkXz/wJoWYnlbJQ1W2QRdbpLJmGut3seuGDALHGQBdTTOPRw/qZtEb0vW62iOIYUKAjmNAFoICzc4lolO5T8K5ErT0+PkHAP2ZweNe6DJd3yd+k542yCUfGj9qvU9F4L2lXKvC81mVksFABvgxs6c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778832596; c=relaxed/simple; bh=l59wES/Oiv9+xtI/9GIHvndSMZ105Nga2WepAuuENCI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=hAU+Sg3zfUPOJFoxHD4PvIivaJDbceqPFUu1F+Y/iE5l6mbqdh58mm7HPjcMjaWQybATBnaJJ4MDakWuFLmoH5lBlhxz25sNPZUzEtWf5y3wLpVNsieVpk+3rCO+/k7iAcjPafuwKNteGB6PtocFu8N6dCLSF4YRcYmkgBhmwmQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=lontium.com; spf=pass smtp.mailfrom=lontium.com; dkim=pass (2048-bit key) header.d=lontium.com header.i=@lontium.com header.b=K90u2ueu; arc=none smtp.client-ip=115.124.28.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=lontium.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lontium.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lontium.com header.i=@lontium.com header.b="K90u2ueu" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lontium.com; s=default; t=1778832585; h=From:To:Subject:Date:Message-Id:MIME-Version; bh=iK0098Zya8DH+n/nB6lXel0+PQSdL0ZUrJGIafZh92c=; b=K90u2ueuuF/rbjWE/5MxAqPWWXiG2HwbUUIl71WO7pKX/t5Wgbm8if3C11ytMKWZOBRYcgb96dCX7gcJl0dfIBoRiwlH7VsRcHUcHZhWV14ZjYpJkCDG+ThEW8xs5UVe37pLb+tgOa8U9PMaw6u9gTj5jR14P09TXIjU4Hl6NhiATrIcf8Wq0f1em8Uq5lXTOy7jHSW2GPa1SmHZoAgfTqd1nJSwgB6xcg6jFboyYU86ZutT6Xb2JunPmHBKQyReQpkpN1TvpP8/OTJteuHTfFfuAmqsHVsMLu6Em22y4wsjGDzeQUkdhA7PZ+1Erv+h8jF0N8sc7MWUCrVaJAxMEQ== X-Alimail-AntiSpam: AC=CONTINUE;BC=0.07436259|-1;CH=green;DM=|CONTINUE|false|;DS=CONTINUE|ham_alarm|0.00446885-0.000350693-0.99518;FP=18400369264585959163|0|0|0|0|-1|-1|-1;HT=maildocker-contentspam033037028158;MF=syyang@lontium.com;NM=1;PH=DS;RN=21;RT=21;SR=0;TI=SMTPD_---.hXlHgwP_1778832581; Received: from DESKTOP-V2MKAT2.localdomain(mailfrom:syyang@lontium.com fp:SMTPD_---.hXlHgwP_1778832581 cluster:ay29) by smtp.aliyun-inc.com; Fri, 15 May 2026 16:09:43 +0800 From: syyang@lontium.com To: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, andrzej.hajda@intel.com, neil.armstrong@linaro.org, dmitry.baryshkov@oss.qualcomm.com, maarten.lankhorst@linux.intel.com, rfoss@kernel.org, mripard@kernel.org Cc: Laurent.pinchart@ideasonboard.com, tzimmermann@suse.de, jonas@kwiboo.se, jernej.skrabec@gmail.com, devicetree@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, xmzhu@lontium.corp-partner.google.com, xmzhu@lontium.com, rlyu@lontium.com, xbpeng@lontium.com, Sunyun Yang Subject: [PATCH v8 2/2] drm/bridge: Add Lontium LT7911EXC eDP to MIPI DSI bridge Date: Fri, 15 May 2026 16:09:34 +0800 Message-Id: <20260515080934.9870-3-syyang@lontium.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260515080934.9870-1-syyang@lontium.com> References: <20260515080934.9870-1-syyang@lontium.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Sunyun Yang Add support for the Lontium LT7911EXC bridge chip, which converts eDP input to MIPI DSI output using an internal firmware-controlled pipeline. The driver provides: - DRM bridge integration for eDP-to-DSI routing - MIPI DSI host interface for downstream panel attachment - Firmware upgrade mechanism over I2C (erase/program/verify) - GPIO-based reset and regulator management - Basic register access protection using a mutex-controlled OCM lock Display timing and DSI packet generation are handled by the chip firmware and are not configured by the driver. Signed-off-by: Sunyun Yang Reviewed-by: Dmitry Baryshkov --- drivers/gpu/drm/bridge/Kconfig | 15 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt7911exc.c | 642 +++++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt7911exc.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index c3209b0f4678..cb74730c6ef4 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -132,6 +132,21 @@ config DRM_ITE_IT6505 help ITE IT6505 DisplayPort bridge chip driver. =20 +config DRM_LONTIUM_LT7911EXC + tristate "Lontium eDP/MIPI DSI bridge" + depends on OF + depends on I2C + select CRC32 + select FW_LOADER + select DRM_PANEL + select DRM_KMS_HELPER + select REGMAP_I2C + help + DRM driver for the Lontium LT7911EXC bridge + chip.The LT7911EXC converts eDP input to MIPI + DSI output. + Please say Y if you have such hardware. + config DRM_LONTIUM_LT8912B tristate "Lontium LT8912B DSI/HDMI bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makef= ile index beab5b695a6e..70ddca75dd3a 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_DRM_I2C_NXP_TDA998X) +=3D tda998x.o obj-$(CONFIG_DRM_INNO_HDMI) +=3D inno-hdmi.o obj-$(CONFIG_DRM_ITE_IT6263) +=3D ite-it6263.o obj-$(CONFIG_DRM_ITE_IT6505) +=3D ite-it6505.o +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) +=3D lontium-lt7911exc.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 diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/b= ridge/lontium-lt7911exc.c new file mode 100644 index 000000000000..e7b8a22b2e29 --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Lontium Semiconductor, Inc. + */ + +#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 32 +#define FW_FILE "Lontium/lt7911exc_fw.bin" +#define LT7911EXC_PAGE_CONTROL 0xff + +struct lt7911exc_dsi_output { + struct mipi_dsi_device *dev; + struct drm_panel *panel; + struct drm_bridge *bridge; + }; + +struct lt7911exc { + struct device *dev; + struct i2c_client *client; + struct drm_bridge bridge; + + struct mipi_dsi_host dsi_host; + struct lt7911exc_dsi_output output; + + struct regmap *regmap; + /* Protects all accesses to registers by stopping the on-chip MCU */ + struct mutex ocm_lock; + struct gpio_desc *reset_gpio; + int fw_version; +}; + +static const struct regmap_range_cfg lt7911exc_ranges[] =3D { + { + .name =3D "register_range", + .range_min =3D 0, + .range_max =3D 0xe8ff, + .selector_reg =3D LT7911EXC_PAGE_CONTROL, + .selector_mask =3D 0xff, + .selector_shift =3D 0, + .window_start =3D 0, + .window_len =3D 0x100, + }, +}; + +static const struct regmap_config lt7911exc_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D 0xe8ff, + .ranges =3D lt7911exc_ranges, + .num_ranges =3D ARRAY_SIZE(lt7911exc_ranges), +}; + +static u32 cal_crc32_custom(const u8 *data, u64 length) +{ + u32 crc =3D 0xffffffff; + u8 buf[4]; + u64 i; + + for (i =3D 0; i < length; i +=3D 4) { + buf[0] =3D data[i + 3]; + buf[1] =3D data[i + 2]; + buf[2] =3D data[i + 1]; + buf[3] =3D data[i + 0]; + crc =3D crc32_be(crc, buf, 4); + } + + return crc; +} + +static inline struct lt7911exc *bridge_to_lt7911exc(struct drm_bridge *bri= dge) +{ + return container_of(bridge, struct lt7911exc, bridge); +} + +static inline struct lt7911exc *dsi_host_to_lt7911exc(struct mipi_dsi_host= *host) +{ + return container_of(host, struct lt7911exc, dsi_host); +} + +/* + * Purpose of this function is rest gpio: high -> low -> high + * This clears the previous configuration in the chip, + * and finally remains high to allow the firmware to run again. + */ +static void lt7911exc_reset(struct lt7911exc *lt7911exc) +{ + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0); + msleep(20); + + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1); + msleep(20); + + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0); + msleep(400); + + dev_dbg(lt7911exc->dev, "lt7911exc reset.\n"); +} + +static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc) +{ + int ret; + + ret =3D devm_regulator_get_enable(lt7911exc->dev, "vcc"); + if (ret < 0) + return dev_err_probe(lt7911exc->dev, ret, "failed to enable vcc regulato= r\n"); + + usleep_range(5000, 10000); + + ret =3D devm_regulator_get_enable(lt7911exc->dev, "vdd"); + if (ret < 0) + return dev_err_probe(lt7911exc->dev, ret, "failed to enable vdd regulato= r\n"); + + return 0; +} + +static int lt7911exc_read_version(struct lt7911exc *lt7911exc) +{ + u8 buf[3]; + int ret; + + ret =3D regmap_write(lt7911exc->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + ret =3D regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, ARRAY_SIZE(buf)); + if (ret) + return ret; + + return (buf[0] << 16) | (buf[1] << 8) | buf[2]; +} + +static void lt7911exc_lock(struct lt7911exc *lt7911exc) +{ + mutex_lock(<7911exc->ocm_lock); + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01); +} + +static void lt7911exc_unlock(struct lt7911exc *lt7911exc) +{ + regmap_write(lt7911exc->regmap, 0xe0ee, 0x00); + mutex_unlock(<7911exc->ocm_lock); +} + +static int lt7911exc_block_erase(struct lt7911exc *lt7911exc) +{ + struct device *dev =3D lt7911exc->dev; + const u32 addr =3D 0x00; + int ret; + + const struct reg_sequence seq_write[] =3D { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe054, 0x01), + REG_SEQ0(0xe055, 0x06), + REG_SEQ0(0xe051, 0x01), + REG_SEQ0(0xe051, 0x00), + REG_SEQ0(0xe054, 0x05), + REG_SEQ0(0xe055, 0xd8), + REG_SEQ0(0xe05a, (addr >> 16) & 0xff), + REG_SEQ0(0xe05b, (addr >> 8) & 0xff), + REG_SEQ0(0xe05c, addr & 0xff), + REG_SEQ0(0xe051, 0x01), + REG_SEQ0(0xe050, 0x00), + }; + + ret =3D regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(s= eq_write)); + if (ret) + return ret; + + msleep(200); + dev_dbg(dev, "erase flash done.\n"); + + return 0; +} + +static int lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr) +{ + int ret; + + const struct reg_sequence seq_write[] =3D { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe05f, 0x01), + REG_SEQ0(0xe05a, (addr >> 16) & 0xff), + REG_SEQ0(0xe05b, (addr >> 8) & 0xff), + REG_SEQ0(0xe05c, addr & 0xff), + }; + + ret =3D regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(s= eq_write)); + if (ret) + return ret; + + return 0; +} + +static int lt7911exc_write_data(struct lt7911exc *lt7911exc, const struct = firmware *fw, u64 addr) +{ + struct device *dev =3D lt7911exc->dev; + int ret; + int page =3D 0, num =3D 0, page_len =3D 0; + u64 size, offset; + const u8 *data; + + data =3D fw->data; + size =3D fw->size; + page =3D (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_dbg(dev, "%u pages, total size %llu byte\n", page, size); + + for (num =3D 0; num < page; num++) { + offset =3D num * LT_PAGE_SIZE; + page_len =3D (offset + LT_PAGE_SIZE <=3D size) ? LT_PAGE_SIZE : (size - = offset); + ret =3D lt7911exc_prog_init(lt7911exc, addr); + if (ret) + return ret; + + ret =3D regmap_raw_write(lt7911exc->regmap, 0xe05d, &data[offset], page_= len); + if (ret) { + dev_err(dev, "write error at page %d\n", num); + return ret; + } + + if (page_len < LT_PAGE_SIZE) { + regmap_write(lt7911exc->regmap, 0xe05f, 0x05); + regmap_write(lt7911exc->regmap, 0xe05f, 0x01); + //hardware requires delay + usleep_range(1000, 2000); + } + + regmap_write(lt7911exc->regmap, 0xe05f, 0x00); + addr +=3D LT_PAGE_SIZE; + } + + return 0; +} + +static int lt7911exc_write_crc(struct lt7911exc *lt7911exc, u32 crc32, u64= addr) +{ + u8 crc[4]; + int ret; + + crc[0] =3D crc32 & 0xff; + crc[1] =3D (crc32 >> 8) & 0xff; + crc[2] =3D (crc32 >> 16) & 0xff; + crc[3] =3D (crc32 >> 24) & 0xff; + + regmap_write(lt7911exc->regmap, 0xe05f, 0x01); + regmap_write(lt7911exc->regmap, 0xe05a, (addr >> 16) & 0xff); + regmap_write(lt7911exc->regmap, 0xe05b, (addr >> 8) & 0xff); + regmap_write(lt7911exc->regmap, 0xe05c, addr & 0xff); + + ret =3D regmap_raw_write(lt7911exc->regmap, 0xe05d, crc, 4); + if (ret) + return ret; + + regmap_write(lt7911exc->regmap, 0xe05f, 0x05); + regmap_write(lt7911exc->regmap, 0xe05f, 0x01); + usleep_range(1000, 2000); + regmap_write(lt7911exc->regmap, 0xe05f, 0x00); + + return 0; +} + +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc, u32 crc32) +{ + struct device *dev =3D lt7911exc->dev; + u32 read_hw_crc =3D 0; + u8 crc_tmp[4]; + int ret; + + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01); + regmap_write(lt7911exc->regmap, 0xe07b, 0x60); + regmap_write(lt7911exc->regmap, 0xe07b, 0x40); + msleep(150); + ret =3D regmap_bulk_read(lt7911exc->regmap, 0x22, crc_tmp, ARRAY_SIZE(crc= _tmp)); + if (ret) { + dev_err(lt7911exc->dev, "Failed to read CRC: %d\n", ret); + return ret; + } + + read_hw_crc =3D ((u32)crc_tmp[0] << 24) | ((u32)crc_tmp[1] << 16) | + ((u32)crc_tmp[2] << 8) | ((u32)crc_tmp[3]); + + if (read_hw_crc !=3D crc32) { + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=3D0x%08x, = read CRC=3D0x%08x\n", + crc32, read_hw_crc); + return -EIO; + } + + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=3D0x%08x\n", read_h= w_crc); + return 0; +} + +static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc) +{ + struct device *dev =3D lt7911exc->dev; + const struct firmware *fw; + u8 *buffer; + size_t total_size =3D FW_SIZE - 4; + u32 crc32; + int ret; + + /* 1. load firmware */ + ret =3D request_firmware(&fw, FW_FILE, dev); + if (ret) { + dev_err(dev, "failed to load '%s'\n", FW_FILE); + return ret; + } + + /* 2. check size */ + 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; + } + + /*3. calculate crc32 */ + buffer =3D kzalloc(total_size, GFP_KERNEL); + if (!buffer) { + ret =3D -ENOMEM; + goto out_release_fw; + } + memset(buffer, 0xff, total_size); + memcpy(buffer, fw->data, fw->size); + + crc32 =3D cal_crc32_custom(buffer, total_size); + kfree(buffer); + + /*4. firmware upgrade */ + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", fw->size); + + ret =3D lt7911exc_block_erase(lt7911exc); + if (ret) { + dev_err(dev, "failed to block erase.\n"); + goto out_release_fw; + } + + ret =3D lt7911exc_write_data(lt7911exc, fw, 0); + if (ret < 0) { + dev_err(dev, "failed to write firmware data\n"); + goto out_release_fw; + } + + ret =3D lt7911exc_write_crc(lt7911exc, crc32, FW_SIZE - 4); + if (ret < 0) { + dev_err(dev, "failed to write firmware crc\n"); + goto out_release_fw; + } + + /*5. check upgrade of result */ + lt7911exc_reset(lt7911exc); + + ret =3D lt7911exc_upgrade_result(lt7911exc, crc32); + +out_release_fw: + release_firmware(fw); + return ret; +} + +static void lt7911exc_atomic_pre_enable(struct drm_bridge *bridge, struct = drm_atomic_state *state) +{ + struct lt7911exc *lt7911exc =3D bridge_to_lt7911exc(bridge); + + guard(mutex)(<7911exc->ocm_lock); + + lt7911exc_reset(lt7911exc); +} + +static void lt7911exc_atomic_disable(struct drm_bridge *bridge, struct drm= _atomic_state *state) +{ + /* Delay after panel is disabled */ + msleep(20); +} + +static void lt7911exc_atomic_post_disable(struct drm_bridge *bridge, struc= t drm_atomic_state *state) +{ + struct lt7911exc *lt7911exc =3D bridge_to_lt7911exc(bridge); + + guard(mutex)(<7911exc->ocm_lock); + + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1); +} + +static int lt7911exc_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt7911exc *lt7911exc =3D bridge_to_lt7911exc(bridge); + + return drm_bridge_attach(encoder, lt7911exc->output.bridge, bridge, flags= ); +} + +static const struct drm_bridge_funcs lt7911exc_bridge_funcs =3D { + .attach =3D lt7911exc_bridge_attach, + .atomic_pre_enable =3D lt7911exc_atomic_pre_enable, + .atomic_disable =3D lt7911exc_atomic_disable, + .atomic_post_disable =3D lt7911exc_atomic_post_disable, + .atomic_reset =3D drm_atomic_helper_bridge_reset, + .atomic_duplicate_state =3D drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state =3D drm_atomic_helper_bridge_destroy_state, +}; + +static int lt7911exc_dsi_host_attach(struct mipi_dsi_host *host, struct mi= pi_dsi_device *dev) +{ + struct lt7911exc *lt7911exc =3D dsi_host_to_lt7911exc(host); + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret =3D drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, &br= idge); + if (ret) + return ret; + + if (panel) { + bridge =3D drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + lt7911exc->output.dev =3D dev; + lt7911exc->output.bridge =3D bridge; + lt7911exc->output.panel =3D panel; + + drm_bridge_add(<7911exc->bridge); + return 0; +} + +static int lt7911exc_dsi_host_detach(struct mipi_dsi_host *host, struct mi= pi_dsi_device *dev) +{ + struct lt7911exc *lt7911exc =3D dsi_host_to_lt7911exc(host); + + guard(mutex)(<7911exc->ocm_lock); + + drm_bridge_remove(<7911exc->bridge); + if (lt7911exc->output.panel) + drm_panel_bridge_remove(lt7911exc->output.bridge); + + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1); + + return 0; +} + +/* + * The firmware running inside the LT7911EXC chip + * sends display commands configuration and cannot + * be configured by an external driver. + */ +static ssize_t lt7911exc_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct lt7911exc *lt7911exc =3D dsi_host_to_lt7911exc(host); + unsigned int val; + int ret; + + guard(mutex)(<7911exc->ocm_lock); + + /* ensure eDP input video ready */ + ret =3D regmap_read_poll_timeout(lt7911exc->regmap, 0xe084, val, (val & 0= x01), 5000, 50000); + if (ret) { + dev_warn(lt7911exc->dev, "Video source not ready within 50ms\n"); + return -EBUSY; + } + + /* enable mipi dsi output stream and firmware will send dcs */ + ret =3D regmap_write(lt7911exc->regmap, 0xe0b0, 0x01); + if (ret) + return ret; + + return msg->tx_len; +} + +static const struct mipi_dsi_host_ops lt7911exc_dsi_host_ops =3D { + .attach =3D lt7911exc_dsi_host_attach, + .detach =3D lt7911exc_dsi_host_detach, + .transfer =3D lt7911exc_dsi_host_transfer, +}; + +static ssize_t lt7911exc_firmware_store(struct device *dev, struct device_= attribute *attr, + const char *buf, size_t len) +{ + struct lt7911exc *lt7911exc =3D dev_get_drvdata(dev); + int ret; + + lt7911exc_lock(lt7911exc); + + /* 0 - ensure chip run */ + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0); + msleep(400); + + ret =3D lt7911exc_firmware_upgrade(lt7911exc); + if (ret < 0) + dev_err(dev, "upgrade failure\n"); + + lt7911exc->fw_version =3D lt7911exc_read_version(lt7911exc); + + lt7911exc_unlock(lt7911exc); + + return ret < 0 ? ret : len; +} + +static ssize_t lt7911exc_firmware_show(struct device *dev, struct device_a= ttribute *attr, char *buf) +{ + struct lt7911exc *lt7911exc =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%04x\n", lt7911exc->fw_version); +} + +static DEVICE_ATTR_RW(lt7911exc_firmware); + +static struct attribute *lt7911exc_attrs[] =3D { + &dev_attr_lt7911exc_firmware.attr, + NULL, +}; + +static const struct attribute_group lt7911exc_attr_group =3D { + .attrs =3D lt7911exc_attrs, +}; + +static const struct attribute_group *lt7911exc_attr_groups[] =3D { + <7911exc_attr_group, + NULL, +}; + +static int lt7911exc_probe(struct i2c_client *client) +{ + struct lt7911exc *lt7911exc; + struct device *dev =3D &client->dev; + struct device_node *np =3D dev->of_node; + int ret; + + if (!np) + return -ENODEV; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n"); + + lt7911exc =3D devm_drm_bridge_alloc(dev, struct lt7911exc, bridge, <791= 1exc_bridge_funcs); + if (IS_ERR(lt7911exc)) + return dev_err_probe(dev, PTR_ERR(lt7911exc), "drm bridge alloc failed.\= n"); + + dev_set_drvdata(dev, lt7911exc); + + lt7911exc->client =3D client; + lt7911exc->dev =3D dev; + + ret =3D devm_mutex_init(dev, <7911exc->ocm_lock); + if (ret) + return dev_err_probe(dev, ret, "failed to init mutex\n"); + + lt7911exc->regmap =3D devm_regmap_init_i2c(client, <7911exc_regmap_conf= ig); + if (IS_ERR(lt7911exc->regmap)) + return dev_err_probe(dev, PTR_ERR(lt7911exc->regmap), "regmap i2c init f= ailed\n"); + + /* + * reset GPIO is defined as active low in device tree. + * gpiod_set_value_cansleep() uses logical value: + * 1 -> asserted (active) -> physical low -> reset enabled -> chip stop + * 0 -> deasserted (inactive) -> physical high -> reset released -> chip = run + */ + lt7911exc->reset_gpio =3D devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(lt7911exc->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(lt7911exc->reset_gpio), + "failed to acquire reset gpio\n"); + + ret =3D lt7911exc_regulator_enable(lt7911exc); + if (ret) + return ret; + + lt7911exc_reset(lt7911exc); + + lt7911exc_lock(lt7911exc); + + lt7911exc->fw_version =3D lt7911exc_read_version(lt7911exc); + + lt7911exc_unlock(lt7911exc); + + if (lt7911exc->fw_version < 0) + return dev_err_probe(dev, lt7911exc->fw_version, "failed read version of= chip\n"); + + lt7911exc->dsi_host.dev =3D dev; + lt7911exc->dsi_host.ops =3D <7911exc_dsi_host_ops; + lt7911exc->bridge.of_node =3D np; + + i2c_set_clientdata(client, lt7911exc); + + return mipi_dsi_host_register(<7911exc->dsi_host); +} + +static void lt7911exc_remove(struct i2c_client *client) +{ + struct lt7911exc *lt7911exc =3D i2c_get_clientdata(client); + + mipi_dsi_host_unregister(<7911exc->dsi_host); +} + +static const struct i2c_device_id lt7911exc_i2c_table[] =3D { + {"lt7911exc"}, + {/* sentinel */} +}; + +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table); + +static const struct of_device_id lt7911exc_devices[] =3D { + {.compatible =3D "lontium,lt7911exc"}, + {/* sentinel */} +}; +MODULE_DEVICE_TABLE(of, lt7911exc_devices); + +static struct i2c_driver lt7911exc_driver =3D { + .id_table =3D lt7911exc_i2c_table, + .probe =3D lt7911exc_probe, + .remove =3D lt7911exc_remove, + .driver =3D { + .name =3D "lt7911exc", + .of_match_table =3D lt7911exc_devices, + .dev_groups =3D lt7911exc_attr_groups, + }, +}; +module_i2c_driver(lt7911exc_driver); + +MODULE_AUTHOR("SunYun Yang "); +MODULE_DESCRIPTION("Lontium LT7911EXC EDP to MIPI DSI bridge driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FW_FILE); --=20 2.34.1