* [PATCH v1 0/2] Add LT9611C DRM bridge driver and device tree binding
@ 2025-09-03 12:38 syyang
2025-09-03 12:38 ` [PATCH v1 1/2] This patch adds a new device tree binding documentation syyang
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
0 siblings, 2 replies; 26+ messages in thread
From: syyang @ 2025-09-03 12:38 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong, rfoss
Cc: Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993, syyang
This series adds support for the Lontium LT9611C chip:
- Adds a new device tree binding documentation for the LT9611C bridge.
- Adds a new DRM bridge driver for the LT9611C chip.
syyang (2):
This patch adds a new device tree binding documentation.
This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
.../display/bridge/lontium,lt9611c.yaml | 123 ++
drivers/gpu/drm/bridge/Kconfig | 16 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 +++++++++++++++++
4 files changed, 1636 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
--
2.25.1
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v1 1/2] This patch adds a new device tree binding documentation.
2025-09-03 12:38 [PATCH v1 0/2] Add LT9611C DRM bridge driver and device tree binding syyang
@ 2025-09-03 12:38 ` syyang
2025-09-03 16:33 ` Rob Herring (Arm)
2025-09-04 2:21 ` [PATCH v1 " Dmitry Baryshkov
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
1 sibling, 2 replies; 26+ messages in thread
From: syyang @ 2025-09-03 12:38 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong, rfoss
Cc: Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993, syyang
- New device tree binding documentation at
Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
Signed-off-by: syyang <syyang@lontium.com>
---
.../display/bridge/lontium,lt9611c.yaml | 123 ++++++++++++++++++
1 file changed, 123 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
new file mode 100644
index 000000000000..e8f204c71a95
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Lontium LT9611C 2 Port MIPI to HDMI Bridge
+
+maintainers:
+ - Rob Herring <robh@kernel.org>
+
+description: |
+ The LT9611C are bridge devices which convert DSI to HDMI
+
+properties:
+ compatible:
+ enum:
+ - lontium,lt9611c
+ - lontium,lt9611uxd
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+ description: GPIO connected to active high RESET 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:
+ Primary MIPI port-1 for MIPI input
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Additional MIPI port-2 for MIPI input, used in combination
+ with primary MIPI port-1 to drive higher resolution displays
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ HDMI port for HDMI output
+
+ required:
+ - port@0
+ - port@2
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - vdd-supply
+ - vcc-supply
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c10 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ hdmi-bridge@41 {
+ compatible = "lontium,lt9611c";
+ reg = <0x41>;
+ #sound-dai-cells = <0>;
+ interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
+ vdd-supply = <<9611_1v2>;
+ vcc-supply = <<9611_3v3>;
+ dsi-lanes = <4>;
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ lt9611_a: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ lt9611_b: endpoint {
+ remote-endpoint = <&dsi1_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ lt9611_out: endpoint {
+ remote-endpoint = <&hdmi_con>;
+ };
+ };
+ };
+ };
+ };
+
+...
+
--
2.25.1
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-03 12:38 [PATCH v1 0/2] Add LT9611C DRM bridge driver and device tree binding syyang
2025-09-03 12:38 ` [PATCH v1 1/2] This patch adds a new device tree binding documentation syyang
@ 2025-09-03 12:38 ` syyang
2025-09-04 2:52 ` Dmitry Baryshkov
` (2 more replies)
1 sibling, 3 replies; 26+ messages in thread
From: syyang @ 2025-09-03 12:38 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong, rfoss
Cc: Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993, syyang
The following changes are included:
- Updated Kconfig and Makefile to include the new driver
- Implementation of the bridge driver at
drivers/gpu/drm/bridge/lontium-lt9611c.c
Signed-off-by: syyang <syyang@lontium.com>
---
drivers/gpu/drm/bridge/Kconfig | 16 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 ++++++++++++++++++++++
3 files changed, 1513 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index b9e0ca85226a..f0df146ed2ce 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -170,6 +170,22 @@ config DRM_LONTIUM_LT9611
HDMI signals
Please say Y if you have such hardware.
+config DRM_LONTIUM_LT9611C
+ tristate "Lontium LT9611C DSI/HDMI bridge"
+ select SND_SOC_HDMI_CODEC if SND_SOC
+ depends on OF
+ select DRM_PANEL_BRIDGE
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_DISPLAY_HELPER
+ select DRM_DISPLAY_HDMI_STATE_HELPER
+ select REGMAP_I2C
+ help
+ Driver for Lontium LT9611C DSI to HDMI bridge
+ chip driver that converts dual DSI and I2S to
+ HDMI signals
+ Please say Y if you have such hardware.
+
config DRM_LONTIUM_LT9611UXC
tristate "Lontium LT9611UXC DSI/HDMI bridge"
select SND_SOC_HDMI_CODEC if SND_SOC
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 245e8a27e3fc..ccfa9987aa4f 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
+obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
obj-$(CONFIG_DRM_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-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
new file mode 100644
index 000000000000..c4d680362583
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
@@ -0,0 +1,1496 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 LONTIUM
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <drm/drm_modes.h>
+#include <sound/hdmi-codec.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <linux/pm_runtime.h>
+
+#define FW_SIZE (64 * 1024)
+#define LT_PAGE_SIZE 256
+#define FW_FILE "LT9611C.bin"
+#define NOT_UPGRADE 0
+#define UPGRADE 1
+
+struct lt9611c {
+ 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 work_struct work;
+ struct device_node *dsi0_node;
+ struct device_node *dsi1_node;
+ struct mipi_dsi_device *dsi0;
+ struct mipi_dsi_device *dsi1;
+ struct platform_device *audio_pdev;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ struct device *codec_dev;
+ struct task_struct *kthread;
+ struct task_struct *test_kthread;
+ hdmi_codec_plugged_cb plugged_cb;
+ const struct firmware *fw;
+ u64 fw_version;
+ u8 *edid_buf;
+ int edid_len;
+ bool edid_valid;
+ u8 fw_crc;
+
+ bool work_inited;
+ bool bridge_added;
+ bool regulators_enabled;
+ bool hdmi_connected;
+ enum drm_connector_status audio_status;
+};
+
+static const struct regmap_range chip_ranges[] = {
+ { .range_min = 0, .range_max = 0xff },
+};
+
+static const struct regmap_access_table chip_table = {
+ .yes_ranges = chip_ranges,
+ .n_yes_ranges = ARRAY_SIZE(chip_ranges),
+};
+
+static const struct regmap_config lt9611c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &chip_table,
+ .cache_type = REGCACHE_NONE,
+};
+
+struct crc_info {
+ u8 width;
+ u32 poly;
+ u32 crc_init;
+ u32 xor_out;
+ bool refin;
+ bool refout;
+};
+
+static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c);
+
+static unsigned int bits_reverse(u32 in_val, u8 bits)
+{
+ u32 out_val = 0;
+ u8 i;
+
+ for (i = 0; i < bits; i++) {
+ if (in_val & (1 << i))
+ out_val |= 1 << (bits - 1 - i);
+ }
+
+ return out_val;
+}
+
+static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
+{
+ u8 width = type.width;
+ u32 poly = type.poly;
+ u32 crc = type.crc_init;
+ u32 xorout = type.xor_out;
+ bool refin = type.refin;
+ bool refout = type.refout;
+ u8 n;
+ u32 bits;
+ u32 data;
+ u8 i;
+
+ n = (width < 8) ? 0 : (width - 8);
+ crc = (width < 8) ? (crc << (8 - width)) : crc;
+ bits = (width < 8) ? 0x80 : (1 << (width - 1));
+ poly = (width < 8) ? (poly << (8 - width)) : poly;
+ while (buf_len--) {
+ data = *(buf++);
+ if (refin)
+ data = bits_reverse(data, 8);
+ crc ^= (data << n);
+ for (i = 0; i < 8; i++) {
+ if (crc & bits)
+ crc = (crc << 1) ^ poly;
+ else
+ crc = crc << 1;
+ }
+ }
+ crc = (width < 8) ? (crc >> (8 - width)) : crc;
+ if (refout)
+ crc = bits_reverse(crc, width);
+ crc ^= xorout;
+
+ return (crc & ((2 << (width - 1)) - 1));
+}
+
+static u8 calculate_crc(struct lt9611c *lt9611c)
+{
+ struct crc_info type = {
+ .width = 8,
+ .poly = 0x31,
+ .crc_init = 0,
+ .xor_out = 0,
+ .refout = false,
+ .refin = false,
+ };
+ const u8 *upgrade_data;
+ u64 len;
+ u64 crc_size = FW_SIZE - 1;
+ u8 default_val = 0xFF;
+
+ if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
+ dev_err(lt9611c->dev, "firmware data not available for CRC\n");
+ return 0;
+ }
+
+ upgrade_data = lt9611c->fw->data;
+ len = lt9611c->fw->size;
+
+ type.crc_init = get_crc(type, upgrade_data, len);
+
+ crc_size -= len;
+ while (crc_size--)
+ type.crc_init = get_crc(type, &default_val, 1);
+
+ return type.crc_init;
+}
+
+static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
+{
+ struct device *dev = lt9611c->dev;
+ int ret = 0;
+
+ ret = regmap_write(lt9611c->regmap, reg, val);
+ if (ret < 0) {
+ dev_err(dev,
+ "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
+ lt9611c->client->addr, reg, ret);
+ }
+
+ return ret;
+}
+
+static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
+{
+ struct device *dev = lt9611c->dev;
+ int ret = 0;
+ unsigned int tmp;
+
+ if (!val)
+ return -EINVAL;
+
+ ret = regmap_read(lt9611c->regmap, reg, &tmp);
+ if (ret < 0) {
+ dev_err(dev,
+ "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
+ lt9611c->client->addr, reg, ret);
+
+ return ret;
+ }
+
+ *val = (u8)tmp;
+
+ return 0;
+}
+
+static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
+ unsigned int param_count, u8 *return_buffer,
+ unsigned int return_count)
+{
+ int count, i;
+ u8 temp;
+
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0xDE, 0x01);
+
+ count = 0;
+ do {
+ i2c_read_byte(lt9611c, 0xAE, &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;
+
+ i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
+ }
+
+ i2c_write_byte(lt9611c, 0xDE, 0x02);
+
+ count = 0;
+ do {
+ i2c_read_byte(lt9611c, 0xAE, &temp);
+ usleep_range(1000, 2000);
+ count++;
+ } while (count < 100 && temp != 0x02);
+
+ if (temp != 0x02)
+ return -2;
+
+ for (i = 0; i < return_count; i++)
+ i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
+
+ return 0;
+}
+
+static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ /* ensure filesystem ready */
+ msleep(3000);
+ ret = request_firmware(<9611c->fw, FW_FILE, dev);
+ if (ret) {
+ dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
+ return ret;
+ }
+
+ if (lt9611c->fw->size > FW_SIZE - 1) {
+ dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
+ lt9611c->fw = NULL;
+ return -EINVAL;
+ }
+
+ dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
+
+ lt9611c->fw_crc = calculate_crc(lt9611c);
+
+ dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
+
+ return 0;
+}
+
+static void lt9611c_config_parameters(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0xEE, 0x01);
+ //fifo_rst_n
+ i2c_write_byte(lt9611c, 0xFF, 0xE1);
+ i2c_write_byte(lt9611c, 0x03, 0x3F);
+ i2c_write_byte(lt9611c, 0x03, 0xFF);
+
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0x5E, 0xC1);
+ i2c_write_byte(lt9611c, 0x58, 0x00);
+ i2c_write_byte(lt9611c, 0x59, 0x50);
+ i2c_write_byte(lt9611c, 0x5A, 0x10);
+ i2c_write_byte(lt9611c, 0x5A, 0x00);
+ i2c_write_byte(lt9611c, 0x58, 0x21);
+}
+
+static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0x5e, 0x5f);
+ i2c_write_byte(lt9611c, 0x5a, 0x20);
+ i2c_write_byte(lt9611c, 0x5a, 0x00);
+ i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
+ i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
+ i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
+ i2c_write_byte(lt9611c, 0x5a, 0x10);
+ i2c_write_byte(lt9611c, 0x5a, 0x00);
+}
+
+static void lt9611c_wren(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0x5a, 0x04);
+ i2c_write_byte(lt9611c, 0x5a, 0x00);
+}
+
+static void lt9611c_wrdi(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0x5A, 0x08);
+ i2c_write_byte(lt9611c, 0x5A, 0x00);
+}
+
+static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u8 flash_crc;
+
+ if (!lt9611c)
+ return -EINVAL;
+
+ lt9611c_config_parameters(lt9611c);
+ lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
+ i2c_write_byte(lt9611c, 0x58, 0x21);
+
+ ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
+ if (ret < 0) {
+ dev_err(dev, "failed to read flash crc\n");
+ return ret;
+ }
+
+ dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
+ flash_crc, lt9611c->fw_crc);
+
+ lt9611c_wrdi(lt9611c);
+
+ return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
+}
+
+static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ //fifo_rst_n
+ i2c_write_byte(lt9611c, 0xFF, 0xE1);
+ i2c_write_byte(lt9611c, 0x03, 0x3F);
+ i2c_write_byte(lt9611c, 0x03, 0xFF);
+
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0x5e, 0x40);
+ i2c_write_byte(lt9611c, 0x56, 0x05);
+ i2c_write_byte(lt9611c, 0x55, 0x25);
+ i2c_write_byte(lt9611c, 0x55, 0x01);
+ i2c_write_byte(lt9611c, 0x58, 0x21);
+
+ ret = i2c_read_byte(lt9611c, 0x5f, status);
+ if (ret < 0)
+ dev_err(dev, "failed to read flash register status\n");
+
+ return ret;
+}
+
+static void lt9611c_block_erase(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ u32 i = 0;
+ u8 flash_status = 0;
+ u8 block_num = 0x00;
+ u32 flash_addr = 0x00;
+
+ for (block_num = 0; block_num < 2; block_num++) {
+ flash_addr = (block_num * 0x008000);
+ i2c_write_byte(lt9611c, 0xFF, 0xE0);
+ i2c_write_byte(lt9611c, 0xEE, 0x01);
+ i2c_write_byte(lt9611c, 0x5A, 0x04);
+ i2c_write_byte(lt9611c, 0x5A, 0x00);
+ i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
+ i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
+ i2c_write_byte(lt9611c, 0x5D, flash_addr);
+ i2c_write_byte(lt9611c, 0x5A, 0x01);
+ i2c_write_byte(lt9611c, 0x5A, 0x00);
+ msleep(100);
+ i = 0;
+ while (1) {
+ read_flash_reg_status(lt9611c, &flash_status);
+ if ((flash_status & 0x01) == 0)
+ break;
+
+ if (i > 50)
+ break;
+
+ i++;
+ msleep(50);
+ }
+ }
+
+ dev_info(dev, "erase flash done.\n");
+}
+
+static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0x51, 0x00);
+ i2c_write_byte(lt9611c, 0x55, 0xc0);
+ i2c_write_byte(lt9611c, 0x55, 0x80);
+ i2c_write_byte(lt9611c, 0x5e, 0xc0);
+ i2c_write_byte(lt9611c, 0x58, 0x21);
+}
+
+static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0x51, 0xff);
+ i2c_write_byte(lt9611c, 0x55, 0x80);
+ i2c_write_byte(lt9611c, 0x5e, 0xc0);
+ i2c_write_byte(lt9611c, 0x58, 0x21);
+}
+
+static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
+ i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
+ i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
+ i2c_write_byte(lt9611c, 0x5A, 0x30);
+ i2c_write_byte(lt9611c, 0x5A, 0x00);
+}
+
+static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ int page = 0, num = 0, i = 0;
+ const u8 *data;
+ u64 size, index;
+ u8 value;
+
+ data = lt9611c->fw->data;
+ size = lt9611c->fw->size;
+
+ page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
+
+ if (page * LT_PAGE_SIZE > 64 * 1024) {
+ 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++) {
+ lt9611c_data_to_sram(lt9611c);
+
+ for (i = 0; i < LT_PAGE_SIZE; i++) {
+ index = num * LT_PAGE_SIZE + i;
+ value = (index < size) ? data[index] : 0xFF;
+
+ ret = i2c_write_byte(lt9611c, 0x59, value);
+ if (ret < 0) {
+ dev_err(dev, "write error at page %u, index %u\n", num, i);
+ return ret;
+ }
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+
+ addr += LT_PAGE_SIZE;
+ }
+
+ lt9611c_wrdi(lt9611c);
+
+ return 0;
+}
+
+static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u8 crc;
+
+ crc = lt9611c->fw_crc;
+ lt9611c_crc_to_sram(lt9611c);
+ ret = i2c_write_byte(lt9611c, 0x59, crc);
+ if (ret < 0) {
+ dev_err(dev, "failed to write CRC\n");
+ return -1;
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+ lt9611c_wrdi(lt9611c);
+
+ dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
+
+ return 0;
+}
+
+static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
+
+ lt9611c_config_parameters(lt9611c);
+ lt9611c_block_erase(lt9611c);
+
+ ret = lt9611c_write_data(lt9611c, 0);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write firmware data\n");
+ return ret;
+ }
+
+ ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write firmware CRC\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ u8 crc_result;
+
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0xee, 0x01);
+ i2c_read_byte(lt9611c, 0x21, &crc_result);
+
+ if (crc_result == lt9611c->fw_crc) {
+ dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
+ return 0;
+ }
+
+ dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
+ lt9611c->fw_crc, crc_result);
+ return -EIO;
+}
+
+static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct lt9611c, bridge);
+}
+
+static void lt9611c_lock(struct lt9611c *lt9611c)
+{
+ mutex_lock(<9611c->ocm_lock);
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0xee, 0x01);
+}
+
+static void lt9611c_unlock(struct lt9611c *lt9611c)
+{
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0xee, 0x00);
+ mutex_unlock(<9611c->ocm_lock);
+}
+
+static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
+{
+ struct lt9611c *lt9611c = dev_id;
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u8 irq_status;
+ u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
+ u8 data[5];
+
+ mutex_lock(<9611c->ocm_lock);
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_read_byte(lt9611c, 0x84, &irq_status);
+
+ if (!(irq_status & BIT(0))) {
+ mutex_unlock(<9611c->ocm_lock);
+ return IRQ_HANDLED;
+ }
+ dev_info(dev, "HPD interrupt triggered.\n");
+
+ i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
+ usleep_range(10000, 12000);
+ i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
+
+ ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
+ if (ret) {
+ dev_err(dev, "failed to read HPD status\n");
+ } else {
+ lt9611c->hdmi_connected = (data[4] == 0x02);
+ dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
+ }
+
+ lt9611c->audio_status = lt9611c->hdmi_connected ?
+ connector_status_connected :
+ connector_status_disconnected;
+
+ schedule_work(<9611c->work);
+
+ mutex_unlock(<9611c->ocm_lock);
+
+ return IRQ_HANDLED;
+}
+
+static void lt9611c_hpd_work(struct work_struct *work)
+{
+ struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
+ bool connected;
+
+ mutex_lock(<9611c->ocm_lock);
+ connected = lt9611c->hdmi_connected;
+ mutex_unlock(<9611c->ocm_lock);
+
+ drm_bridge_hpd_notify(<9611c->bridge,
+ connected ?
+ connector_status_connected :
+ connector_status_disconnected);
+
+ lt9611c_audio_update_connector_status(lt9611c);
+}
+
+static void lt9611c_reset(struct lt9611c *lt9611c)
+{
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
+ msleep(20);
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
+ msleep(20);
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
+ msleep(400);
+}
+
+static int lt9611c_regulator_init(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ lt9611c->supplies[0].supply = "vcc";
+ lt9611c->supplies[1].supply = "vdd";
+
+ ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
+
+ return ret;
+}
+
+static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
+{
+ int ret;
+
+ ret = regulator_enable(lt9611c->supplies[0].consumer);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5000, 10000);
+
+ ret = regulator_enable(lt9611c->supplies[1].consumer);
+ if (ret < 0) {
+ regulator_disable(lt9611c->supplies[0].consumer);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
+{
+ int ret;
+
+ ret = regulator_disable(lt9611c->supplies[0].consumer);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5000, 10000);
+
+ ret = regulator_disable(lt9611c->supplies[1].consumer);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
+ struct device_node *dsi_node)
+{
+ const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ host = of_find_mipi_dsi_host_by_node(dsi_node);
+ if (!host)
+ return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
+
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi)) {
+ 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+
+ return drm_bridge_attach(encoder, lt9611c->next_bridge,
+ bridge, flags);
+}
+
+static enum drm_mode_status lt9611c_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 lt9611c_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj_mode)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ struct device *dev = lt9611c->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, "Out video info:\n");
+ dev_info(dev,
+ "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
+ h_total, hactive, hsync_len, hfront_porch, hback_porch);
+ dev_info(dev,
+ "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
+ v_total, vactive, vsync_len, vfront_porch, vback_porch);
+
+ 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(<9611c->ocm_lock);
+ ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
+ if (ret)
+ dev_err(dev, "video set failed\n");
+ mutex_unlock(<9611c->ocm_lock);
+}
+
+static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ struct device *dev = lt9611c->dev;
+ int ret;
+ bool connected = false;
+ u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
+ u8 data[5];
+
+ mutex_lock(<9611c->ocm_lock);
+ ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
+ if (ret) {
+ dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
+ ret);
+ } else {
+ connected = (data[4] == 0x02);
+ }
+
+ lt9611c->hdmi_connected = connected;
+
+ if (lt9611c->hdmi_connected)
+ lt9611c->audio_status = connector_status_connected;
+ else
+ lt9611c->audio_status = connector_status_disconnected;
+
+ mutex_unlock(<9611c->ocm_lock);
+
+ return connected ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int lt9611c_read_edid(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret, i, bytes_to_copy, offset = 0;
+ u8 packets_num;
+ u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
+ u8 return_edid_data[37];
+ u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
+ u8 return_edid_byte_num[6];
+
+ ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
+ if (ret) {
+ dev_err(dev, "Failed to read EDID byte number\n");
+ lt9611c->edid_valid = false;
+ return ret;
+ }
+
+ lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
+
+ if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
+ lt9611c->edid_len : 0)) {
+ kfree(lt9611c->edid_buf);
+ lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
+ if (!lt9611c->edid_buf) {
+ dev_err(dev, "Failed to allocate EDID buffer\n");
+ lt9611c->edid_len = 0;
+ lt9611c->edid_valid = false;
+ return -ENOMEM;
+ }
+ }
+
+ packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
+ (lt9611c->edid_len / 32);
+ for (i = 0; i < packets_num; i++) {
+ read_edid_data_cmd[4] = (u8)i;
+ ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
+ if (ret) {
+ dev_err(dev, "Failed to read EDID packet %d\n", i);
+ lt9611c->edid_valid = false;
+ return -EIO;
+ }
+ offset = i * 32;
+ bytes_to_copy = min(32, lt9611c->edid_len - offset);
+ memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
+ }
+
+ lt9611c->edid_valid = true;
+
+ return ret;
+}
+
+static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
+{
+ struct lt9611c *lt9611c = data;
+ struct device *dev = lt9611c->dev;
+ unsigned int total_blocks;
+ int ret;
+
+ if (len > 128)
+ return -EINVAL;
+
+ guard(mutex)(<9611c->ocm_lock);
+ if (block == 0 || !lt9611c->edid_valid) {
+ ret = lt9611c_read_edid(lt9611c);
+ if (ret) {
+ dev_err(dev, "EDID read failed\n");
+ return ret;
+ }
+ }
+
+ total_blocks = lt9611c->edid_len / 128;
+ if (!total_blocks) {
+ dev_err(dev, "No valid EDID blocks\n");
+ return -EIO;
+ }
+
+ if (block >= total_blocks) {
+ dev_err(dev, "Requested block %u exceeds total blocks %u\n",
+ block, total_blocks);
+ return -EINVAL;
+ }
+
+ memcpy(buf, lt9611c->edid_buf + block * 128, len);
+
+ return 0;
+}
+
+static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+
+ usleep_range(10000, 20000);
+ return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
+}
+
+static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
+ .attach = lt9611c_bridge_attach,
+ .mode_valid = lt9611c_bridge_mode_valid,
+ .mode_set = lt9611c_bridge_mode_set,
+ .detect = lt9611c_bridge_detect,
+ .edid_read = lt9611c_bridge_edid_read,
+};
+
+static int lt9611c_parse_dt(struct device *dev,
+ struct lt9611c *lt9611c)
+{
+ lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
+ if (!lt9611c->dsi0_node) {
+ dev_err(dev, "failed to get remote node for primary dsi\n");
+ return -ENODEV;
+ }
+
+ lt9611c->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, <9611c->next_bridge);
+}
+
+static int lt9611c_gpio_init(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+
+ lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(lt9611c->reset_gpio)) {
+ dev_err(dev, "failed to acquire reset gpio\n");
+ return PTR_ERR(lt9611c->reset_gpio);
+ }
+
+ return 0;
+}
+
+static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
+{
+ u8 val;
+ u64 ver = 0;
+
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0xee, 0x01);
+
+ i2c_read_byte(lt9611c, 0x80, &val);
+ ver = val;
+
+ i2c_read_byte(lt9611c, 0x81, &val);
+ ver = (ver << 8) | val;
+
+ *version = ver;
+}
+
+static int lt9611c_read_chipid(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ u8 val = 0;
+
+ i2c_write_byte(lt9611c, 0xff, 0xe0);
+ i2c_write_byte(lt9611c, 0xee, 0x01);
+ i2c_write_byte(lt9611c, 0xff, 0xe1);
+
+ i2c_read_byte(lt9611c, 0x00, &val);
+ if (val != 0x23)
+ return -ENODEV;
+
+ i2c_read_byte(lt9611c, 0x01, &val);
+ if (val != 0x06)
+ return -ENODEV;
+
+ dev_info(dev, "ChipId = 0x2306\n");
+
+ return 0;
+}
+
+static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+
+ dev_info(lt9611c->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 lt9611c_audio_shutdown(struct device *dev, void *data)
+{
+}
+
+static int lt9611c_audio_startup(struct device *dev, void *data)
+{
+ return 0;
+}
+
+static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
+{
+ enum drm_connector_status status;
+
+ status = lt9611c->audio_status;
+ if (lt9611c->plugged_cb && lt9611c->codec_dev)
+ lt9611c->plugged_cb(lt9611c->codec_dev,
+ status == connector_status_connected);
+}
+
+static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
+ void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct lt9611c *lt9611c = data;
+
+ lt9611c->plugged_cb = fn;
+ lt9611c->codec_dev = codec_dev;
+ lt9611c_audio_update_connector_status(lt9611c);
+
+ return 0;
+}
+
+static const struct hdmi_codec_ops lt9611c_codec_ops = {
+ .hw_params = lt9611c_hdmi_hw_params,
+ .audio_shutdown = lt9611c_audio_shutdown,
+ .audio_startup = lt9611c_audio_startup,
+ .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
+};
+
+static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .ops = <9611c_codec_ops,
+ .max_i2s_channels = 2,
+ .i2s = 1,
+ .data = lt9611c,
+ };
+
+ lt9611c->audio_pdev =
+ platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
+ PLATFORM_DEVID_AUTO,
+ &codec_data, sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
+}
+
+static void lt9611c_audio_exit(struct lt9611c *lt9611c)
+{
+ if (lt9611c->audio_pdev) {
+ platform_device_unregister(lt9611c->audio_pdev);
+ lt9611c->audio_pdev = NULL;
+ }
+}
+
+static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ lt9611c_lock(lt9611c);
+ ret = lt9611c_prepare_firmware_data(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "Failed prepare firmware data: %d\n", ret);
+ goto out;
+ }
+
+ ret = lt9611c_firmware_upgrade(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "upgrade failure\n");
+ goto out;
+ }
+ lt9611c_reset(lt9611c);
+ ret = lt9611c_upgrade_result(lt9611c);
+ if (ret < 0)
+ goto out;
+
+out:
+ lt9611c_unlock(lt9611c);
+ lt9611c_reset(lt9611c);
+ if (lt9611c->fw) {
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ }
+
+ return ret;
+}
+
+static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ ret = lt9611c_firmware_update_store(lt9611c);
+ if (ret < 0)
+ return ret;
+ return len;
+}
+
+static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
+}
+
+static DEVICE_ATTR_RW(lt9611c_firmware);
+
+static struct attribute *lt9611c_attrs[] = {
+ &dev_attr_lt9611c_firmware.attr,
+ NULL,
+};
+
+static const struct attribute_group lt9611c_attr_group = {
+ .attrs = lt9611c_attrs,
+};
+
+static const struct attribute_group *lt9611c_attr_groups[] = {
+ <9611c_attr_group,
+ NULL,
+};
+
+static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+
+ if (lt9611c->work_inited) {
+ cancel_work_sync(<9611c->work);
+ lt9611c->work_inited = false;
+ dev_err(dev, "work cancelled\n");
+ }
+
+ if (lt9611c->bridge_added) {
+ drm_bridge_remove(<9611c->bridge);
+ lt9611c->bridge_added = false;
+ dev_err(dev, "DRM bridge removed\n");
+ }
+
+ if (lt9611c->regulators_enabled) {
+ regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ lt9611c->regulators_enabled = false;
+ dev_err(dev, "regulators disabled\n");
+ }
+
+ if (lt9611c->audio_pdev)
+ lt9611c_audio_exit(lt9611c);
+
+ if (lt9611c->fw) {
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ dev_err(dev, "firmware released\n");
+ }
+
+ if (lt9611c->dsi0_node) {
+ of_node_put(lt9611c->dsi0_node);
+ lt9611c->dsi0_node = NULL;
+ dev_err(dev, "dsi0 node released\n");
+ }
+
+ if (lt9611c->dsi1_node) {
+ of_node_put(lt9611c->dsi1_node);
+ lt9611c->dsi1_node = NULL;
+ dev_err(dev, "dsi1 node released\n");
+ }
+}
+
+static int lt9611c_main(void *data)
+{
+ struct lt9611c *lt9611c = data;
+ struct device *dev = lt9611c->dev;
+ struct i2c_client *client = lt9611c->client;
+ int ret;
+
+ lt9611c->work_inited = false;
+ lt9611c->bridge_added = false;
+ lt9611c->regulators_enabled = false;
+
+ ret = lt9611c_parse_dt(dev, lt9611c);
+ if (ret) {
+ dev_err(dev, "failed to parse device tree\n");
+ return ret;
+ }
+
+ ret = lt9611c_gpio_init(lt9611c);
+ if (ret < 0)
+ goto err_cleanup;
+
+ ret = lt9611c_regulator_init(lt9611c);
+ if (ret < 0)
+ goto err_cleanup;
+
+ ret = lt9611c_regulator_enable(lt9611c);
+ if (ret)
+ goto err_cleanup;
+
+ lt9611c->regulators_enabled = true;
+
+ lt9611c_reset(lt9611c);
+
+ ret = lt9611c_read_chipid(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "failed to read chip id.\n");
+ goto err_cleanup;
+ }
+
+ lt9611c_lock(lt9611c);
+ lt9611c_read_version(lt9611c, <9611c->fw_version);
+
+ ret = lt9611c_prepare_firmware_data(lt9611c);
+ if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
+ dev_info(dev, "firmware upgrade needed\n");
+
+ ret = lt9611c_firmware_upgrade(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "firmware upgrade failed\n");
+ lt9611c_unlock(lt9611c);
+ goto err_cleanup;
+ }
+
+ lt9611c_reset(lt9611c);
+ ret = lt9611c_upgrade_result(lt9611c);
+ if (ret < 0) {
+ lt9611c_unlock(lt9611c);
+ goto err_cleanup;
+ }
+
+ lt9611c_read_version(lt9611c, <9611c->fw_version);
+ lt9611c_unlock(lt9611c);
+
+ } else {
+ dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
+ lt9611c_unlock(lt9611c);
+ }
+
+ if (lt9611c->fw) {
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ }
+ dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
+
+ INIT_WORK(<9611c->work, lt9611c_hpd_work);
+ lt9611c->work_inited = true;
+
+ if (!client->irq) {
+ dev_err(dev, "failed to get INTP IRQ\n");
+ ret = -ENODEV;
+ goto err_cleanup;
+ }
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ lt9611c_irq_thread_handler,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
+ IRQF_NO_AUTOEN,
+ "lt9611c", lt9611c);
+ if (ret) {
+ dev_err(dev, "failed to request irq\n");
+ goto err_cleanup;
+ }
+
+ lt9611c->bridge.funcs = <9611c_bridge_funcs;
+ lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
+ lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
+ lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ drm_bridge_add(<9611c->bridge);
+ lt9611c->bridge_added = true;
+
+ /* Attach primary DSI */
+ lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
+ if (IS_ERR(lt9611c->dsi0)) {
+ ret = PTR_ERR(lt9611c->dsi0);
+ dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
+ goto err_cleanup;
+ }
+
+ /* Attach secondary DSI, if specified */
+ if (lt9611c->dsi1_node) {
+ lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
+ if (IS_ERR(lt9611c->dsi1)) {
+ ret = PTR_ERR(lt9611c->dsi1);
+ dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
+ goto err_cleanup;
+ }
+ }
+
+ lt9611c->audio_status = connector_status_disconnected;
+
+ ret = lt9611c_audio_init(dev, lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "audio init failed\n");
+ goto err_cleanup;
+ }
+
+ lt9611c_reset(lt9611c);
+ enable_irq(lt9611c->client->irq);
+
+ return 0;
+
+err_cleanup:
+ lt9611c_cleanup_resources(lt9611c);
+ return ret;
+}
+
+static int lt9611c_probe(struct i2c_client *client)
+{
+ struct lt9611c *lt9611c;
+ struct device *dev = &client->dev;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "device doesn't support I2C\n");
+ return -ENODEV;
+ }
+
+ lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
+ if (!lt9611c)
+ return -ENOMEM;
+
+ lt9611c->dev = dev;
+ lt9611c->client = client;
+ mutex_init(<9611c->ocm_lock);
+
+ lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
+ if (IS_ERR(lt9611c->regmap)) {
+ dev_err(dev, "regmap i2c init failed\n");
+ return PTR_ERR(lt9611c->regmap);
+ }
+
+ i2c_set_clientdata(client, lt9611c);
+
+ lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
+ if (IS_ERR(lt9611c->kthread)) {
+ dev_err(dev, "Failed to create kernel thread\n");
+ return PTR_ERR(lt9611c->kthread);
+ }
+
+ return 0;
+}
+
+static void lt9611c_remove(struct i2c_client *client)
+{
+ struct lt9611c *lt9611c = i2c_get_clientdata(client);
+ struct device *dev = lt9611c->dev;
+
+ kfree(lt9611c->edid_buf);
+ disable_irq(client->irq);
+ lt9611c_cleanup_resources(lt9611c);
+ mutex_destroy(<9611c->ocm_lock);
+ dev_info(dev, "remove driver\n");
+}
+
+static int lt9611c_bridge_suspend(struct device *dev)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ dev_info(lt9611c->dev, "suspend\n");
+ disable_irq(lt9611c->client->irq);
+ ret = lt9611c_regulator_disable(lt9611c);
+ gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
+
+ return ret;
+}
+
+static int lt9611c_bridge_resume(struct device *dev)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ ret = lt9611c_regulator_enable(lt9611c);
+ lt9611c_reset(lt9611c);
+ enable_irq(lt9611c->client->irq);
+ dev_info(lt9611c->dev, "resume\n");
+
+ return ret;
+}
+
+static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
+ lt9611c_bridge_resume)
+};
+
+static struct i2c_device_id lt9611c_id[] = {
+ { "lontium,lt9611c", 0 },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id lt9611c_match_table[] = {
+ { .compatible = "lontium,lt9611c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, lt9611c_match_table);
+
+static struct i2c_driver lt9611c_driver = {
+ .driver = {
+ .name = "lt9611c",
+ .of_match_table = lt9611c_match_table,
+ .pm = <9611c_bridge_pm_ops,
+ .dev_groups = lt9611c_attr_groups,
+ },
+ .probe = lt9611c_probe,
+ .remove = lt9611c_remove,
+ .id_table = lt9611c_id,
+};
+module_i2c_driver(lt9611c_driver);
+
+MODULE_AUTHOR("syyang <syyang@lontium.com>");
+MODULE_LICENSE("GPL v2");
+
+MODULE_FIRMWARE(FW_FILE);
--
2.25.1
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH v1 1/2] This patch adds a new device tree binding documentation.
2025-09-03 12:38 ` [PATCH v1 1/2] This patch adds a new device tree binding documentation syyang
@ 2025-09-03 16:33 ` Rob Herring (Arm)
2025-09-04 2:25 ` [PATCH v2 " syyang
2025-09-04 2:21 ` [PATCH v1 " Dmitry Baryshkov
1 sibling, 1 reply; 26+ messages in thread
From: Rob Herring (Arm) @ 2025-09-03 16:33 UTC (permalink / raw)
To: syyang
Cc: neil.armstrong, devicetree, Laurent.pinchart, yangsunyun1993,
andrzej.hajda, jonas, dri-devel, rfoss, jernej.skrabec, krzk+dt,
conor+dt, linux-kernel
On Wed, 03 Sep 2025 05:38:24 -0700, syyang wrote:
> - New device tree binding documentation at
> Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
> Signed-off-by: syyang <syyang@lontium.com>
> ---
> .../display/bridge/lontium,lt9611c.yaml | 123 ++++++++++++++++++
> 1 file changed, 123 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml: warning: ignoring duplicate '$id' value 'http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#'
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml: $id: Cannot determine base path from $id, relative path/filename doesn't match actual path or filename
$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml
file: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.example.dtb: /example-0/i2c10/hdmi-bridge@41: failed to match any schema with compatible: ['lontium,lt9611c']
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250903123825.1721443-2-syyang@lontium.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 1/2] This patch adds a new device tree binding documentation.
2025-09-03 12:38 ` [PATCH v1 1/2] This patch adds a new device tree binding documentation syyang
2025-09-03 16:33 ` Rob Herring (Arm)
@ 2025-09-04 2:21 ` Dmitry Baryshkov
[not found] ` <CAFQXuNYKcGHyWLD5hjj24CrbaXzkaKsLU4R2vmhYaryQArA_yQ@mail.gmail.com>
1 sibling, 1 reply; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-04 2:21 UTC (permalink / raw)
To: syyang
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong, rfoss,
Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993
On Wed, Sep 03, 2025 at 05:38:24AM -0700, syyang wrote:
> - New device tree binding documentation at
> Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
> Signed-off-by: syyang <syyang@lontium.com>
Please fix your Git setup and use your full name in SoB tag and author
metadata.
> ---
> .../display/bridge/lontium,lt9611c.yaml | 123 ++++++++++++++++++
> 1 file changed, 123 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> new file mode 100644
> index 000000000000..e8f204c71a95
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> @@ -0,0 +1,123 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Lontium LT9611C 2 Port MIPI to HDMI Bridge
> +
> +maintainers:
> + - Rob Herring <robh@kernel.org>
Are you sure?
> +
> +description: |
> + The LT9611C are bridge devices which convert DSI to HDMI
Can't you extend the existing lontium,lt9611.yaml?
> +
> +properties:
> + compatible:
> + enum:
> + - lontium,lt9611c
> + - lontium,lt9611uxd
> +
> + reg:
> + maxItems: 1
> +
> + "#sound-dai-cells":
> + const: 0
> +
> + interrupts:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> + description: GPIO connected to active high RESET 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:
> + Primary MIPI port-1 for MIPI input
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Additional MIPI port-2 for MIPI input, used in combination
> + with primary MIPI port-1 to drive higher resolution displays
> +
> + port@2:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + HDMI port for HDMI output
> +
> + required:
> + - port@0
> + - port@2
All of this totally looks like lontium,lt9611.yaml, except the
vdd-supply voltage difference.
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - vdd-supply
> + - vcc-supply
> + - ports
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c10 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + hdmi-bridge@41 {
> + compatible = "lontium,lt9611c";
> + reg = <0x41>;
> + #sound-dai-cells = <0>;
> + interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
> + reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
> + vdd-supply = <<9611_1v2>;
> + vcc-supply = <<9611_3v3>;
> + dsi-lanes = <4>;
> + status = "okay";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + lt9611_a: endpoint {
> + remote-endpoint = <&dsi0_out>;
> + };
> + };
> +
> + port@1 {
> + reg = <1>;
> + lt9611_b: endpoint {
> + remote-endpoint = <&dsi1_out>;
> + };
> + };
> +
> + port@2 {
> + reg = <2>;
> + lt9611_out: endpoint {
> + remote-endpoint = <&hdmi_con>;
> + };
> + };
> + };
> + };
> + };
> +
> +...
> +
> --
> 2.25.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-03 16:33 ` Rob Herring (Arm)
@ 2025-09-04 2:25 ` syyang
2025-09-04 2:53 ` Dmitry Baryshkov
2025-09-04 5:49 ` Krzysztof Kozlowski
0 siblings, 2 replies; 26+ messages in thread
From: syyang @ 2025-09-04 2:25 UTC (permalink / raw)
To: robh
Cc: Laurent.pinchart, andrzej.hajda, conor+dt, devicetree, dri-devel,
jernej.skrabec, jonas, krzk+dt, linux-kernel, neil.armstrong,
rfoss, syyang, yangsunyun1993
Fix device tree binding validation errors reported by Rob Herring.
v2:
- Fixed $id field to match actual filename (lontium,lt9611c.yaml)
- build pass
Thanks to Rob Herring for the review and feedback.
Signed-off-by: syyang <syyang@lontium.com>
---
.../display/bridge/lontium,lt9611c.yaml | 121 ++++++++++++++++++
1 file changed, 121 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
new file mode 100644
index 000000000000..712644da4f1d
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Lontium LT9611C 2 Port MIPI to HDMI Bridge
+
+maintainers:
+ - Rob Herring <robh@kernel.org>
+
+description: |
+ The LT9611C are bridge devices which convert DSI to HDMI
+
+properties:
+ compatible:
+ enum:
+ - lontium,lt9611c
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+ description: GPIO connected to active high RESET 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:
+ Primary MIPI port-1 for MIPI input
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Additional MIPI port-2 for MIPI input, used in combination
+ with primary MIPI port-1 to drive higher resolution displays
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ HDMI port for HDMI output
+
+ required:
+ - port@0
+ - port@2
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - vdd-supply
+ - vcc-supply
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c10 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ hdmi-bridge@41 {
+ compatible = "lontium,lt9611c";
+ reg = <0x41>;
+ #sound-dai-cells = <0>;
+ interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
+ vdd-supply = <<9611_1v2>;
+ vcc-supply = <<9611_3v3>;
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ lt9611_a: endpoint {
+ remote-endpoint = <&dsi0_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ lt9611_b: endpoint {
+ remote-endpoint = <&dsi1_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ lt9611_out: endpoint {
+ remote-endpoint = <&hdmi_con>;
+ };
+ };
+ };
+ };
+ };
+
+...
+
--
2.25.1
^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
@ 2025-09-04 2:52 ` Dmitry Baryshkov
2025-09-04 10:48 ` 杨孙运
2025-09-08 11:03 ` Jani Nikula
2025-09-08 11:07 ` Jani Nikula
2 siblings, 1 reply; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-04 2:52 UTC (permalink / raw)
To: syyang
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong, rfoss,
Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993
On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> The following changes are included:
>
> - Updated Kconfig and Makefile to include the new driver
> - Implementation of the bridge driver at
> drivers/gpu/drm/bridge/lontium-lt9611c.c
This is really not interesting, it can be seen from the patch itself.
Please read Documentation/process/submitting-patches.rst.
Is it possible to toggle infoframes?
Is there YUV 444 / 422 output support? YUV420? Interlaced?
>
> Signed-off-by: syyang <syyang@lontium.com>
> ---
> drivers/gpu/drm/bridge/Kconfig | 16 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 ++++++++++++++++++++++
> 3 files changed, 1513 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b9e0ca85226a..f0df146ed2ce 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -170,6 +170,22 @@ config DRM_LONTIUM_LT9611
> HDMI signals
> Please say Y if you have such hardware.
>
> +config DRM_LONTIUM_LT9611C
> + tristate "Lontium LT9611C DSI/HDMI bridge"
> + select SND_SOC_HDMI_CODEC if SND_SOC
> + depends on OF
> + select DRM_PANEL_BRIDGE
> + select DRM_KMS_HELPER
> + select DRM_MIPI_DSI
> + select DRM_DISPLAY_HELPER
> + select DRM_DISPLAY_HDMI_STATE_HELPER
You are not using the HDMI_STATE_HELPER, are you?
> + select REGMAP_I2C
> + help
> + Driver for Lontium LT9611C DSI to HDMI bridge
> + chip driver that converts dual DSI and I2S to
> + HDMI signals
> + Please say Y if you have such hardware.
> +
> config DRM_LONTIUM_LT9611UXC
> tristate "Lontium LT9611UXC DSI/HDMI bridge"
> select SND_SOC_HDMI_CODEC if SND_SOC
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 245e8a27e3fc..ccfa9987aa4f 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -15,6 +15,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_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-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> new file mode 100644
> index 000000000000..c4d680362583
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> @@ -0,0 +1,1496 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2025 LONTIUM
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> + * for more details.
Drop the licence text, you have SPDX header for it.
> + */
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <drm/drm_modes.h>
> +#include <sound/hdmi-codec.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +#include <linux/pm_runtime.h>
Sort the headers
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 256
> +#define FW_FILE "LT9611C.bin"
Please land this firmware to the linux-firmware repository.
> +#define NOT_UPGRADE 0
> +#define UPGRADE 1
You know, C99 has 'bool' type.
> +
> +struct lt9611c {
> + 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 work_struct work;
> + struct device_node *dsi0_node;
> + struct device_node *dsi1_node;
> + struct mipi_dsi_device *dsi0;
> + struct mipi_dsi_device *dsi1;
> + struct platform_device *audio_pdev;
> + struct gpio_desc *reset_gpio;
> + struct regulator_bulk_data supplies[2];
> + struct device *codec_dev;
> + struct task_struct *kthread;
> + struct task_struct *test_kthread;
> + hdmi_codec_plugged_cb plugged_cb;
> + const struct firmware *fw;
> + u64 fw_version;
> + u8 *edid_buf;
> + int edid_len;
> + bool edid_valid;
> + u8 fw_crc;
> +
> + bool work_inited;
> + bool bridge_added;
> + bool regulators_enabled;
> + bool hdmi_connected;
> + enum drm_connector_status audio_status;
> +};
> +
> +static const struct regmap_range chip_ranges[] = {
> + { .range_min = 0, .range_max = 0xff },
> +};
> +
> +static const struct regmap_access_table chip_table = {
> + .yes_ranges = chip_ranges,
> + .n_yes_ranges = ARRAY_SIZE(chip_ranges),
> +};
> +
> +static const struct regmap_config lt9611c_regmap_config = {
> + .reg_bits = 8,
It's 16
> + .val_bits = 8,
> + .volatile_table = &chip_table,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +struct crc_info {
> + u8 width;
> + u32 poly;
> + u32 crc_init;
> + u32 xor_out;
> + bool refin;
> + bool refout;
> +};
> +
> +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c);
> +
> +static unsigned int bits_reverse(u32 in_val, u8 bits)
> +{
> + u32 out_val = 0;
> + u8 i;
> +
> + for (i = 0; i < bits; i++) {
> + if (in_val & (1 << i))
> + out_val |= 1 << (bits - 1 - i);
> + }
> +
> + return out_val;
> +}
> +
> +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
Use library functions for that.
> +{
> + u8 width = type.width;
> + u32 poly = type.poly;
> + u32 crc = type.crc_init;
> + u32 xorout = type.xor_out;
> + bool refin = type.refin;
> + bool refout = type.refout;
> + u8 n;
> + u32 bits;
> + u32 data;
> + u8 i;
> +
> + n = (width < 8) ? 0 : (width - 8);
> + crc = (width < 8) ? (crc << (8 - width)) : crc;
> + bits = (width < 8) ? 0x80 : (1 << (width - 1));
> + poly = (width < 8) ? (poly << (8 - width)) : poly;
> + while (buf_len--) {
> + data = *(buf++);
> + if (refin)
> + data = bits_reverse(data, 8);
> + crc ^= (data << n);
> + for (i = 0; i < 8; i++) {
> + if (crc & bits)
> + crc = (crc << 1) ^ poly;
> + else
> + crc = crc << 1;
> + }
> + }
> + crc = (width < 8) ? (crc >> (8 - width)) : crc;
> + if (refout)
> + crc = bits_reverse(crc, width);
> + crc ^= xorout;
> +
> + return (crc & ((2 << (width - 1)) - 1));
> +}
> +
> +static u8 calculate_crc(struct lt9611c *lt9611c)
> +{
> + struct crc_info type = {
> + .width = 8,
> + .poly = 0x31,
> + .crc_init = 0,
> + .xor_out = 0,
> + .refout = false,
> + .refin = false,
> + };
> + const u8 *upgrade_data;
> + u64 len;
> + u64 crc_size = FW_SIZE - 1;
> + u8 default_val = 0xFF;
> +
> + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
> + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
> + return 0;
> + }
> +
> + upgrade_data = lt9611c->fw->data;
> + len = lt9611c->fw->size;
> +
> + type.crc_init = get_crc(type, upgrade_data, len);
> +
> + crc_size -= len;
> + while (crc_size--)
> + type.crc_init = get_crc(type, &default_val, 1);
> +
> + return type.crc_init;
> +}
> +
> +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret = 0;
> +
> + ret = regmap_write(lt9611c->regmap, reg, val);
> + if (ret < 0) {
> + dev_err(dev,
> + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> + lt9611c->client->addr, reg, ret);
> + }
> +
> + return ret;
> +}
> +
> +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
Drop these two wrappers, they provide no extra functionality.
> +{
> + struct device *dev = lt9611c->dev;
> + int ret = 0;
> + unsigned int tmp;
> +
> + if (!val)
> + return -EINVAL;
> +
> + ret = regmap_read(lt9611c->regmap, reg, &tmp);
> + if (ret < 0) {
> + dev_err(dev,
> + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> + lt9611c->client->addr, reg, ret);
> +
> + return ret;
> + }
> +
> + *val = (u8)tmp;
> +
> + return 0;
> +}
> +
> +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> + unsigned int param_count, u8 *return_buffer,
> + unsigned int return_count)
> +{
> + int count, i;
> + u8 temp;
> +
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0xDE, 0x01);
- lowercase all hex values
- use paged writes as implemented for LT9611 and LT9611UXC
> +
> + count = 0;
> + do {
> + i2c_read_byte(lt9611c, 0xAE, &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;
> +
> + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
> + }
> +
> + i2c_write_byte(lt9611c, 0xDE, 0x02);
> +
> + count = 0;
> + do {
> + i2c_read_byte(lt9611c, 0xAE, &temp);
> + usleep_range(1000, 2000);
> + count++;
> + } while (count < 100 && temp != 0x02);
> +
> + if (temp != 0x02)
> + return -2;
> +
> + for (i = 0; i < return_count; i++)
> + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
> +
> + return 0;
> +}
> +
> +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + /* ensure filesystem ready */
> + msleep(3000);
No. If the firmware is necessary and it's not ready, return
-EPROBE_DEFER.
> + ret = request_firmware(<9611c->fw, FW_FILE, dev);
> + if (ret) {
> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> + return ret;
> + }
> +
> + if (lt9611c->fw->size > FW_SIZE - 1) {
> + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
> + lt9611c->fw = NULL;
> + return -EINVAL;
> + }
> +
> + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
> +
> + lt9611c->fw_crc = calculate_crc(lt9611c);
> +
> + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
No spamming with the unnecessary info. If you want, print the version
of the firmware.
> +
> + return 0;
> +}
> +
> +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0xEE, 0x01);
> + //fifo_rst_n
> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> + i2c_write_byte(lt9611c, 0x03, 0x3F);
> + i2c_write_byte(lt9611c, 0x03, 0xFF);
> +
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0x5E, 0xC1);
> + i2c_write_byte(lt9611c, 0x58, 0x00);
> + i2c_write_byte(lt9611c, 0x59, 0x50);
> + i2c_write_byte(lt9611c, 0x5A, 0x10);
> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> + i2c_write_byte(lt9611c, 0x58, 0x21);
> +}
> +
> +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0x5e, 0x5f);
> + i2c_write_byte(lt9611c, 0x5a, 0x20);
> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> + i2c_write_byte(lt9611c, 0x5a, 0x10);
> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> +}
> +
> +static void lt9611c_wren(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0x5a, 0x04);
> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> +}
> +
> +static void lt9611c_wrdi(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0x5A, 0x08);
> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> +}
> +
> +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> + u8 flash_crc;
> +
> + if (!lt9611c)
> + return -EINVAL;
How can it be NULL here?
> +
> + lt9611c_config_parameters(lt9611c);
> + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
> + i2c_write_byte(lt9611c, 0x58, 0x21);
> +
> + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
> + if (ret < 0) {
> + dev_err(dev, "failed to read flash crc\n");
> + return ret;
> + }
> +
> + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
> + flash_crc, lt9611c->fw_crc);
dev_dbg()
> +
> + lt9611c_wrdi(lt9611c);
> +
> + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
> +}
> +
> +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + //fifo_rst_n
> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> + i2c_write_byte(lt9611c, 0x03, 0x3F);
> + i2c_write_byte(lt9611c, 0x03, 0xFF);
> +
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0x5e, 0x40);
> + i2c_write_byte(lt9611c, 0x56, 0x05);
> + i2c_write_byte(lt9611c, 0x55, 0x25);
> + i2c_write_byte(lt9611c, 0x55, 0x01);
> + i2c_write_byte(lt9611c, 0x58, 0x21);
> +
> + ret = i2c_read_byte(lt9611c, 0x5f, status);
> + if (ret < 0)
> + dev_err(dev, "failed to read flash register status\n");
> +
> + return ret;
> +}
> +
> +static void lt9611c_block_erase(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + u32 i = 0;
> + u8 flash_status = 0;
> + u8 block_num = 0x00;
> + u32 flash_addr = 0x00;
> +
> + for (block_num = 0; block_num < 2; block_num++) {
> + flash_addr = (block_num * 0x008000);
> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> + i2c_write_byte(lt9611c, 0xEE, 0x01);
> + i2c_write_byte(lt9611c, 0x5A, 0x04);
> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
> + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
> + i2c_write_byte(lt9611c, 0x5D, flash_addr);
> + i2c_write_byte(lt9611c, 0x5A, 0x01);
> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> + msleep(100);
> + i = 0;
> + while (1) {
> + read_flash_reg_status(lt9611c, &flash_status);
> + if ((flash_status & 0x01) == 0)
> + break;
> +
> + if (i > 50)
> + break;
> +
> + i++;
> + msleep(50);
> + }
> + }
> +
> + dev_info(dev, "erase flash done.\n");
> +}
> +
> +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0x51, 0x00);
> + i2c_write_byte(lt9611c, 0x55, 0xc0);
> + i2c_write_byte(lt9611c, 0x55, 0x80);
> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> + i2c_write_byte(lt9611c, 0x58, 0x21);
> +}
> +
> +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0x51, 0xff);
> + i2c_write_byte(lt9611c, 0x55, 0x80);
> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> + i2c_write_byte(lt9611c, 0x58, 0x21);
> +}
> +
> +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> + i2c_write_byte(lt9611c, 0x5A, 0x30);
> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> +}
> +
> +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> + int page = 0, num = 0, i = 0;
> + const u8 *data;
> + u64 size, index;
> + u8 value;
> +
> + data = lt9611c->fw->data;
> + size = lt9611c->fw->size;
> +
> + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> +
> + if (page * LT_PAGE_SIZE > 64 * 1024) {
> + dev_err(dev, "firmware size out of range\n");
> + return -EINVAL;
> + }
> +
> + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
dev_dbg()
> +
> + for (num = 0; num < page; num++) {
> + lt9611c_data_to_sram(lt9611c);
> +
> + for (i = 0; i < LT_PAGE_SIZE; i++) {
> + index = num * LT_PAGE_SIZE + i;
> + value = (index < size) ? data[index] : 0xFF;
> +
> + ret = i2c_write_byte(lt9611c, 0x59, value);
> + if (ret < 0) {
> + dev_err(dev, "write error at page %u, index %u\n", num, i);
> + return ret;
> + }
> + }
> +
> + lt9611c_wren(lt9611c);
> + lt9611c_sram_to_flash(lt9611c, addr);
> +
> + addr += LT_PAGE_SIZE;
> + }
> +
> + lt9611c_wrdi(lt9611c);
> +
> + return 0;
> +}
> +
> +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> + u8 crc;
> +
> + crc = lt9611c->fw_crc;
> + lt9611c_crc_to_sram(lt9611c);
> + ret = i2c_write_byte(lt9611c, 0x59, crc);
> + if (ret < 0) {
> + dev_err(dev, "failed to write CRC\n");
> + return -1;
> + }
> +
> + lt9611c_wren(lt9611c);
> + lt9611c_sram_to_flash(lt9611c, addr);
> + lt9611c_wrdi(lt9611c);
> +
> + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
dev_dbg
> +
> + return 0;
> +}
> +
> +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
dev_dbg
> +
> + lt9611c_config_parameters(lt9611c);
> + lt9611c_block_erase(lt9611c);
> +
> + ret = lt9611c_write_data(lt9611c, 0);
> + if (ret < 0) {
> + dev_err(dev, "Failed to write firmware data\n");
> + return ret;
> + }
> +
> + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
> + if (ret < 0) {
> + dev_err(dev, "Failed to write firmware CRC\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + u8 crc_result;
> +
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0xee, 0x01);
> + i2c_read_byte(lt9611c, 0x21, &crc_result);
> +
> + if (crc_result == lt9611c->fw_crc) {
> + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
dev_dbg
> + return 0;
> + }
> +
> + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
> + lt9611c->fw_crc, crc_result);
> + return -EIO;
> +}
> +
> +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct lt9611c, bridge);
> +}
> +
> +static void lt9611c_lock(struct lt9611c *lt9611c)
> +{
> + mutex_lock(<9611c->ocm_lock);
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0xee, 0x01);
> +}
> +
> +static void lt9611c_unlock(struct lt9611c *lt9611c)
> +{
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0xee, 0x00);
> + mutex_unlock(<9611c->ocm_lock);
> +}
> +
> +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
> +{
> + struct lt9611c *lt9611c = dev_id;
> + struct device *dev = lt9611c->dev;
> + int ret;
> + u8 irq_status;
> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> + u8 data[5];
> +
> + mutex_lock(<9611c->ocm_lock);
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_read_byte(lt9611c, 0x84, &irq_status);
> +
> + if (!(irq_status & BIT(0))) {
> + mutex_unlock(<9611c->ocm_lock);
> + return IRQ_HANDLED;
> + }
> + dev_info(dev, "HPD interrupt triggered.\n");
Nice joke. dev_dbg().
> +
> + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
> + usleep_range(10000, 12000);
Why?
> + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
> +
> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> + if (ret) {
> + dev_err(dev, "failed to read HPD status\n");
> + } else {
> + lt9611c->hdmi_connected = (data[4] == 0x02);
> + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
dev_dbg()
> + }
> +
> + lt9611c->audio_status = lt9611c->hdmi_connected ?
> + connector_status_connected :
> + connector_status_disconnected;
What is it being used for? Why do you need separate status for audio?
> +
> + schedule_work(<9611c->work);
> +
> + mutex_unlock(<9611c->ocm_lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void lt9611c_hpd_work(struct work_struct *work)
> +{
> + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
> + bool connected;
> +
> + mutex_lock(<9611c->ocm_lock);
> + connected = lt9611c->hdmi_connected;
> + mutex_unlock(<9611c->ocm_lock);
> +
> + drm_bridge_hpd_notify(<9611c->bridge,
> + connected ?
> + connector_status_connected :
> + connector_status_disconnected);
Incorrect indentation.
> +
> + lt9611c_audio_update_connector_status(lt9611c);
> +}
> +
> +static void lt9611c_reset(struct lt9611c *lt9611c)
> +{
> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> + msleep(20);
> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> + msleep(20);
> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> + msleep(400);
> +}
> +
> +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + lt9611c->supplies[0].supply = "vcc";
> + lt9611c->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
> +
> + return ret;
> +}
> +
> +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
> +{
> + int ret;
> +
> + ret = regulator_enable(lt9611c->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_enable(lt9611c->supplies[1].consumer);
> + if (ret < 0) {
> + regulator_disable(lt9611c->supplies[0].consumer);
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
> +{
> + int ret;
> +
> + ret = regulator_disable(lt9611c->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_disable(lt9611c->supplies[1].consumer);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
> + struct device_node *dsi_node)
> +{
> + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
> + struct mipi_dsi_device *dsi;
> + struct mipi_dsi_host *host;
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + host = of_find_mipi_dsi_host_by_node(dsi_node);
> + if (!host)
> + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
> +
> + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> + if (IS_ERR(dsi)) {
> + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> +
> + return drm_bridge_attach(encoder, lt9611c->next_bridge,
> + bridge, flags);
> +}
> +
> +static enum drm_mode_status lt9611c_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)
Use .hdmi_tmds_char_rate_valid() for that.
> + return MODE_OK;
> + else
> + return MODE_BAD;
> +}
> +
> +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
> + const struct drm_display_mode *mode,
> + const struct drm_display_mode *adj_mode)
- Wrong indentation
- mode_set callback is deprecated and should not be used for new
drivers.
> +{
> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> + struct device *dev = lt9611c->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;
> +
> + 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, "Out video info:\n");
> + dev_info(dev,
> + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
> + h_total, hactive, hsync_len, hfront_porch, hback_porch);
> + dev_info(dev,
> + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
> + v_total, vactive, vsync_len, vfront_porch, vback_porch);
Fix indentation
Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
spammy.
> +
> + 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(<9611c->ocm_lock);
> + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
> + if (ret)
> + dev_err(dev, "video set failed\n");
> + mutex_unlock(<9611c->ocm_lock);
> +}
> +
> +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
> + struct drm_connector *connector)
> +{
> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> + struct device *dev = lt9611c->dev;
> + int ret;
> + bool connected = false;
> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> + u8 data[5];
> +
> + mutex_lock(<9611c->ocm_lock);
> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> + if (ret) {
> + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
> + ret);
> + } else {
> + connected = (data[4] == 0x02);
> + }
THere is no need to put single-line statements in brackets. Drop those.
> +
> + lt9611c->hdmi_connected = connected;
> +
> + if (lt9611c->hdmi_connected)
> + lt9611c->audio_status = connector_status_connected;
> + else
> + lt9611c->audio_status = connector_status_disconnected;
> +
> + mutex_unlock(<9611c->ocm_lock);
> +
> + return connected ? connector_status_connected :
> + connector_status_disconnected;
> +}
> +
> +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret, i, bytes_to_copy, offset = 0;
> + u8 packets_num;
> + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> + u8 return_edid_data[37];
> + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> + u8 return_edid_byte_num[6];
> +
> + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> + if (ret) {
> + dev_err(dev, "Failed to read EDID byte number\n");
> + lt9611c->edid_valid = false;
> + return ret;
> + }
> +
> + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> +
> + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> + lt9611c->edid_len : 0)) {
> + kfree(lt9611c->edid_buf);
> + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> + if (!lt9611c->edid_buf) {
> + dev_err(dev, "Failed to allocate EDID buffer\n");
> + lt9611c->edid_len = 0;
> + lt9611c->edid_valid = false;
> + return -ENOMEM;
> + }
> + }
> +
> + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> + (lt9611c->edid_len / 32);
> + for (i = 0; i < packets_num; i++) {
> + read_edid_data_cmd[4] = (u8)i;
> + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> + if (ret) {
> + dev_err(dev, "Failed to read EDID packet %d\n", i);
> + lt9611c->edid_valid = false;
> + return -EIO;
> + }
> + offset = i * 32;
> + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
Don't store EDID in the long-term structures. Read it on demand.
> + }
> +
> + lt9611c->edid_valid = true;
> +
> + return ret;
> +}
> +
> +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> +{
> + struct lt9611c *lt9611c = data;
> + struct device *dev = lt9611c->dev;
> + unsigned int total_blocks;
> + int ret;
> +
> + if (len > 128)
> + return -EINVAL;
> +
> + guard(mutex)(<9611c->ocm_lock);
> + if (block == 0 || !lt9611c->edid_valid) {
> + ret = lt9611c_read_edid(lt9611c);
> + if (ret) {
> + dev_err(dev, "EDID read failed\n");
> + return ret;
> + }
> + }
> +
> + total_blocks = lt9611c->edid_len / 128;
> + if (!total_blocks) {
> + dev_err(dev, "No valid EDID blocks\n");
> + return -EIO;
> + }
> +
> + if (block >= total_blocks) {
> + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> + block, total_blocks);
> + return -EINVAL;
> + }
> +
> + memcpy(buf, lt9611c->edid_buf + block * 128, len);
> +
> + return 0;
> +}
> +
> +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> + struct drm_connector *connector)
> +{
> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> +
> + usleep_range(10000, 20000);
Why?
> + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> +}
> +
> +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> + .attach = lt9611c_bridge_attach,
> + .mode_valid = lt9611c_bridge_mode_valid,
> + .mode_set = lt9611c_bridge_mode_set,
> + .detect = lt9611c_bridge_detect,
> + .edid_read = lt9611c_bridge_edid_read,
> +};
> +
> +static int lt9611c_parse_dt(struct device *dev,
> + struct lt9611c *lt9611c)
> +{
> + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
> + if (!lt9611c->dsi0_node) {
> + dev_err(dev, "failed to get remote node for primary dsi\n");
> + return -ENODEV;
> + }
> +
> + lt9611c->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, <9611c->next_bridge);
> +}
> +
> +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> +
> + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(lt9611c->reset_gpio)) {
> + dev_err(dev, "failed to acquire reset gpio\n");
> + return PTR_ERR(lt9611c->reset_gpio);
> + }
> +
> + return 0;
> +}
> +
> +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
> +{
> + u8 val;
> + u64 ver = 0;
> +
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0xee, 0x01);
> +
> + i2c_read_byte(lt9611c, 0x80, &val);
> + ver = val;
> +
> + i2c_read_byte(lt9611c, 0x81, &val);
> + ver = (ver << 8) | val;
> +
> + *version = ver;
> +}
> +
> +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + u8 val = 0;
> +
> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> + i2c_write_byte(lt9611c, 0xee, 0x01);
> + i2c_write_byte(lt9611c, 0xff, 0xe1);
> +
> + i2c_read_byte(lt9611c, 0x00, &val);
> + if (val != 0x23)
> + return -ENODEV;
> +
> + i2c_read_byte(lt9611c, 0x01, &val);
> + if (val != 0x06)
> + return -ENODEV;
> +
> + dev_info(dev, "ChipId = 0x2306\n");
> +
> + return 0;
> +}
> +
> +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> + struct hdmi_codec_daifmt *fmt,
> + struct hdmi_codec_params *hparms)
> +{
> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> +
> + dev_info(lt9611c->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;
> + }
Does that add anything on top of the limitations of hdmi-codec.c?
> +
> + return 0;
> +}
> +
> +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> +{
> +}
> +
> +static int lt9611c_audio_startup(struct device *dev, void *data)
> +{
> + return 0;
> +}
> +
> +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> +{
> + enum drm_connector_status status;
> +
> + status = lt9611c->audio_status;
> + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> + lt9611c->plugged_cb(lt9611c->codec_dev,
> + status == connector_status_connected);
> +}
> +
> +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> + void *data,
> + hdmi_codec_plugged_cb fn,
> + struct device *codec_dev)
> +{
> + struct lt9611c *lt9611c = data;
> +
> + lt9611c->plugged_cb = fn;
> + lt9611c->codec_dev = codec_dev;
> + lt9611c_audio_update_connector_status(lt9611c);
> +
> + return 0;
> +}
> +
> +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> + .hw_params = lt9611c_hdmi_hw_params,
> + .audio_shutdown = lt9611c_audio_shutdown,
> + .audio_startup = lt9611c_audio_startup,
> + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> +};
No, we have HDMI audio helpers for that. Drop this and use the helpers
instead.
> +
> +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
> +{
> + struct hdmi_codec_pdata codec_data = {
> + .ops = <9611c_codec_ops,
> + .max_i2s_channels = 2,
> + .i2s = 1,
> + .data = lt9611c,
> + };
> +
> + lt9611c->audio_pdev =
> + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
> + PLATFORM_DEVID_AUTO,
> + &codec_data, sizeof(codec_data));
> +
> + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
> +}
> +
> +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
> +{
> + if (lt9611c->audio_pdev) {
> + platform_device_unregister(lt9611c->audio_pdev);
> + lt9611c->audio_pdev = NULL;
> + }
> +}
> +
> +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret;
> +
> + lt9611c_lock(lt9611c);
> + ret = lt9611c_prepare_firmware_data(lt9611c);
> + if (ret < 0) {
> + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
> + goto out;
> + }
> +
> + ret = lt9611c_firmware_upgrade(lt9611c);
> + if (ret < 0) {
> + dev_err(dev, "upgrade failure\n");
> + goto out;
> + }
> + lt9611c_reset(lt9611c);
> + ret = lt9611c_upgrade_result(lt9611c);
> + if (ret < 0)
> + goto out;
> +
> +out:
> + lt9611c_unlock(lt9611c);
> + lt9611c_reset(lt9611c);
> + if (lt9611c->fw) {
> + release_firmware(lt9611c->fw);
> + lt9611c->fw = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = lt9611c_firmware_update_store(lt9611c);
Inline
> + if (ret < 0)
> + return ret;
> + return len;
> +}
> +
> +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
> +}
> +
> +static DEVICE_ATTR_RW(lt9611c_firmware);
> +
> +static struct attribute *lt9611c_attrs[] = {
> + &dev_attr_lt9611c_firmware.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group lt9611c_attr_group = {
> + .attrs = lt9611c_attrs,
> +};
> +
> +static const struct attribute_group *lt9611c_attr_groups[] = {
> + <9611c_attr_group,
> + NULL,
> +};
> +
> +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> +
> + if (lt9611c->work_inited) {
> + cancel_work_sync(<9611c->work);
> + lt9611c->work_inited = false;
> + dev_err(dev, "work cancelled\n");
Why???
> + }
> +
> + if (lt9611c->bridge_added) {
> + drm_bridge_remove(<9611c->bridge);
> + lt9611c->bridge_added = false;
> + dev_err(dev, "DRM bridge removed\n");
> + }
> +
> + if (lt9611c->regulators_enabled) {
> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> + lt9611c->regulators_enabled = false;
> + dev_err(dev, "regulators disabled\n");
> + }
> +
> + if (lt9611c->audio_pdev)
> + lt9611c_audio_exit(lt9611c);
> +
> + if (lt9611c->fw) {
You definitely don't need firmware when the bridge is up and running.
> + release_firmware(lt9611c->fw);
> + lt9611c->fw = NULL;
> + dev_err(dev, "firmware released\n");
> + }
> +
> + if (lt9611c->dsi0_node) {
> + of_node_put(lt9611c->dsi0_node);
> + lt9611c->dsi0_node = NULL;
> + dev_err(dev, "dsi0 node released\n");
> + }
> +
> + if (lt9611c->dsi1_node) {
> + of_node_put(lt9611c->dsi1_node);
> + lt9611c->dsi1_node = NULL;
> + dev_err(dev, "dsi1 node released\n");
> + }
> +}
> +
> +static int lt9611c_main(void *data)
> +{
> + struct lt9611c *lt9611c = data;
> + struct device *dev = lt9611c->dev;
> + struct i2c_client *client = lt9611c->client;
> + int ret;
> +
> + lt9611c->work_inited = false;
> + lt9611c->bridge_added = false;
> + lt9611c->regulators_enabled = false;
> +
> + ret = lt9611c_parse_dt(dev, lt9611c);
> + if (ret) {
> + dev_err(dev, "failed to parse device tree\n");
> + return ret;
> + }
> +
> + ret = lt9611c_gpio_init(lt9611c);
> + if (ret < 0)
> + goto err_cleanup;
> +
> + ret = lt9611c_regulator_init(lt9611c);
> + if (ret < 0)
> + goto err_cleanup;
> +
> + ret = lt9611c_regulator_enable(lt9611c);
> + if (ret)
> + goto err_cleanup;
> +
> + lt9611c->regulators_enabled = true;
> +
> + lt9611c_reset(lt9611c);
> +
> + ret = lt9611c_read_chipid(lt9611c);
> + if (ret < 0) {
> + dev_err(dev, "failed to read chip id.\n");
> + goto err_cleanup;
> + }
> +
> + lt9611c_lock(lt9611c);
> + lt9611c_read_version(lt9611c, <9611c->fw_version);
> +
> + ret = lt9611c_prepare_firmware_data(lt9611c);
> + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
> + dev_info(dev, "firmware upgrade needed\n");
> +
> + ret = lt9611c_firmware_upgrade(lt9611c);
> + if (ret < 0) {
> + dev_err(dev, "firmware upgrade failed\n");
> + lt9611c_unlock(lt9611c);
> + goto err_cleanup;
> + }
> +
> + lt9611c_reset(lt9611c);
> + ret = lt9611c_upgrade_result(lt9611c);
> + if (ret < 0) {
> + lt9611c_unlock(lt9611c);
> + goto err_cleanup;
> + }
> +
> + lt9611c_read_version(lt9611c, <9611c->fw_version);
> + lt9611c_unlock(lt9611c);
> +
> + } else {
> + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
> + lt9611c_unlock(lt9611c);
> + }
> +
> + if (lt9611c->fw) {
> + release_firmware(lt9611c->fw);
> + lt9611c->fw = NULL;
> + }
> + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
> +
> + INIT_WORK(<9611c->work, lt9611c_hpd_work);
> + lt9611c->work_inited = true;
> +
> + if (!client->irq) {
> + dev_err(dev, "failed to get INTP IRQ\n");
> + ret = -ENODEV;
> + goto err_cleanup;
> + }
> +
> + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> + lt9611c_irq_thread_handler,
> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
> + IRQF_NO_AUTOEN,
> + "lt9611c", lt9611c);
> + if (ret) {
> + dev_err(dev, "failed to request irq\n");
> + goto err_cleanup;
> + }
> +
> + lt9611c->bridge.funcs = <9611c_bridge_funcs;
> + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
> + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> +
> + drm_bridge_add(<9611c->bridge);
> + lt9611c->bridge_added = true;
No unnecessary flags, please. Implement proper cleanup path, unwinding
resources one by one.
> +
> + /* Attach primary DSI */
> + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
> + if (IS_ERR(lt9611c->dsi0)) {
> + ret = PTR_ERR(lt9611c->dsi0);
> + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
> + goto err_cleanup;
> + }
> +
> + /* Attach secondary DSI, if specified */
> + if (lt9611c->dsi1_node) {
> + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
> + if (IS_ERR(lt9611c->dsi1)) {
> + ret = PTR_ERR(lt9611c->dsi1);
> + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
> + goto err_cleanup;
> + }
> + }
> +
> + lt9611c->audio_status = connector_status_disconnected;
> +
> + ret = lt9611c_audio_init(dev, lt9611c);
> + if (ret < 0) {
> + dev_err(dev, "audio init failed\n");
> + goto err_cleanup;
> + }
> +
> + lt9611c_reset(lt9611c);
> + enable_irq(lt9611c->client->irq);
> +
> + return 0;
> +
> +err_cleanup:
> + lt9611c_cleanup_resources(lt9611c);
> + return ret;
> +}
> +
> +static int lt9611c_probe(struct i2c_client *client)
> +{
> + struct lt9611c *lt9611c;
> + struct device *dev = &client->dev;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + dev_err(dev, "device doesn't support I2C\n");
> + return -ENODEV;
> + }
> +
> + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
devm_drm_bridge_alloc()
> + if (!lt9611c)
> + return -ENOMEM;
> +
> + lt9611c->dev = dev;
> + lt9611c->client = client;
> + mutex_init(<9611c->ocm_lock);
> +
> + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
> + if (IS_ERR(lt9611c->regmap)) {
> + dev_err(dev, "regmap i2c init failed\n");
> + return PTR_ERR(lt9611c->regmap);
> + }
> +
> + i2c_set_clientdata(client, lt9611c);
> +
> + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
Why do you need extra kthread for that???
> + if (IS_ERR(lt9611c->kthread)) {
> + dev_err(dev, "Failed to create kernel thread\n");
> + return PTR_ERR(lt9611c->kthread);
> + }
> +
> + return 0;
> +}
> +
> +static void lt9611c_remove(struct i2c_client *client)
> +{
> + struct lt9611c *lt9611c = i2c_get_clientdata(client);
> + struct device *dev = lt9611c->dev;
> +
> + kfree(lt9611c->edid_buf);
> + disable_irq(client->irq);
> + lt9611c_cleanup_resources(lt9611c);
> + mutex_destroy(<9611c->ocm_lock);
> + dev_info(dev, "remove driver\n");
> +}
> +
> +static int lt9611c_bridge_suspend(struct device *dev)
> +{
> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> + int ret;
> +
> + dev_info(lt9611c->dev, "suspend\n");
> + disable_irq(lt9611c->client->irq);
> + ret = lt9611c_regulator_disable(lt9611c);
> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> +
> + return ret;
> +}
> +
> +static int lt9611c_bridge_resume(struct device *dev)
> +{
> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = lt9611c_regulator_enable(lt9611c);
> + lt9611c_reset(lt9611c);
> + enable_irq(lt9611c->client->irq);
> + dev_info(lt9611c->dev, "resume\n");
> +
> + return ret;
> +}
> +
> +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
> + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
> + lt9611c_bridge_resume)
> +};
> +
> +static struct i2c_device_id lt9611c_id[] = {
> + { "lontium,lt9611c", 0 },
> + { /* sentinel */ }
> +};
> +
> +static const struct of_device_id lt9611c_match_table[] = {
> + { .compatible = "lontium,lt9611c" },
Your schema also had lt9611uxd
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
> +
> +static struct i2c_driver lt9611c_driver = {
> + .driver = {
> + .name = "lt9611c",
> + .of_match_table = lt9611c_match_table,
> + .pm = <9611c_bridge_pm_ops,
> + .dev_groups = lt9611c_attr_groups,
> + },
> + .probe = lt9611c_probe,
> + .remove = lt9611c_remove,
> + .id_table = lt9611c_id,
> +};
> +module_i2c_driver(lt9611c_driver);
> +
> +MODULE_AUTHOR("syyang <syyang@lontium.com>");
> +MODULE_LICENSE("GPL v2");
> +
> +MODULE_FIRMWARE(FW_FILE);
> --
> 2.25.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-04 2:25 ` [PATCH v2 " syyang
@ 2025-09-04 2:53 ` Dmitry Baryshkov
2025-09-04 5:49 ` Krzysztof Kozlowski
1 sibling, 0 replies; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-04 2:53 UTC (permalink / raw)
To: syyang
Cc: robh, Laurent.pinchart, andrzej.hajda, conor+dt, devicetree,
dri-devel, jernej.skrabec, jonas, krzk+dt, linux-kernel,
neil.armstrong, rfoss, yangsunyun1993
On Wed, Sep 03, 2025 at 07:25:24PM -0700, syyang wrote:
> Fix device tree binding validation errors reported by Rob Herring.
>
> v2:
> - Fixed $id field to match actual filename (lontium,lt9611c.yaml)
> - build pass
>
> Thanks to Rob Herring for the review and feedback.
Read Documentation/process/submitting-patches.rst
>
> Signed-off-by: syyang <syyang@lontium.com>
> ---
> .../display/bridge/lontium,lt9611c.yaml | 121 ++++++++++++++++++
> 1 file changed, 121 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 1/2] This patch adds a new device tree binding documentation.
[not found] ` <CAFQXuNYKcGHyWLD5hjj24CrbaXzkaKsLU4R2vmhYaryQArA_yQ@mail.gmail.com>
@ 2025-09-04 2:56 ` Dmitry Baryshkov
0 siblings, 0 replies; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-04 2:56 UTC (permalink / raw)
To: 杨孙运
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Andrzej Hajda,
Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
Jernej Skrabec, devicetree, DRI Development List, open list
On Thu, 4 Sept 2025 at 05:39, 杨孙运 <yangsunyun1993@gmail.com> wrote:
>
> thanks Dmitry baryshkov:
>
> 1. Please fix your Git setup and use your full name in SoB tag and author metadata.
> -> i will fix.
>
> 2. +maintainers:
> + - Rob Herring <robh@kernel.org>
> Are you sure?
>
> -> I'm not sure. I need to do some research.
>
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:22写道:
>>
>> On Wed, Sep 03, 2025 at 05:38:24AM -0700, syyang wrote:
>> > - New device tree binding documentation at
>> > Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>> >
>> > Signed-off-by: syyang <syyang@lontium.com>
Please:
- Don't use HTML email
- Don't reply off-list
- Don't top-post
>>
>> Please fix your Git setup and use your full name in SoB tag and author
>> metadata.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-04 2:25 ` [PATCH v2 " syyang
2025-09-04 2:53 ` Dmitry Baryshkov
@ 2025-09-04 5:49 ` Krzysztof Kozlowski
2025-09-04 8:08 ` 杨孙运
1 sibling, 1 reply; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-09-04 5:49 UTC (permalink / raw)
To: syyang, robh
Cc: Laurent.pinchart, andrzej.hajda, conor+dt, devicetree, dri-devel,
jernej.skrabec, jonas, krzk+dt, linux-kernel, neil.armstrong,
rfoss, yangsunyun1993
On 04/09/2025 04:25, syyang wrote:
> Fix device tree binding validation errors reported by Rob Herring.
>
> v2:
That's not the place where you put changelog.
> - Fixed $id field to match actual filename (lontium,lt9611c.yaml)
> - build pass
>
> Thanks to Rob Herring for the review and feedback.
Please carefully read submitting patches.
Do not attach (thread) your patchsets to some other threads (unrelated
or older versions). This buries them deep in the mailbox and might
interfere with applying entire sets. See also:
https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
Please use subject prefixes matching the subsystem. You can get them for
example with `git log --oneline -- DIRECTORY_OR_FILE` on the directory
your patch is touching. For bindings, the preferred subjects are
explained here:
https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters
>
> Signed-off-by: syyang <syyang@lontium.com>
> ---
> .../display/bridge/lontium,lt9611c.yaml | 121 ++++++++++++++++++
> 1 file changed, 121 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> new file mode 100644
> index 000000000000..712644da4f1d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> @@ -0,0 +1,121 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611c.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Lontium LT9611C 2 Port MIPI to HDMI Bridge
> +
> +maintainers:
> + - Rob Herring <robh@kernel.org>
No, how so?
> +
> +description: |
Do not need '|' unless you need to preserve formatting.
> + The LT9611C are bridge devices which convert DSI to HDMI
Why this cannot be added to lt9611 binding? Commit msg should clearly
answer that.
> +
> +properties:
> + compatible:
> + enum:
> + - lontium,lt9611c
> +
> + reg:
> + maxItems: 1
> +
> + "#sound-dai-cells":
Missing dai-common ref.
> + const: 0
> +
> + interrupts:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> + description: GPIO connected to active high RESET 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:
> + Primary MIPI port-1 for MIPI input
> +
> + port@1:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + Additional MIPI port-2 for MIPI input, used in combination
> + with primary MIPI port-1 to drive higher resolution displays
> +
> + port@2:
> + $ref: /schemas/graph.yaml#/properties/port
> + description:
> + HDMI port for HDMI output
> +
> + required:
> + - port@0
> + - port@2
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - vdd-supply
> + - vcc-supply
> + - ports
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c10 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + hdmi-bridge@41 {
> + compatible = "lontium,lt9611c";
> + reg = <0x41>;
> + #sound-dai-cells = <0>;
> + interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
> + reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
> + vdd-supply = <<9611_1v2>;
> + vcc-supply = <<9611_3v3>;
> + status = "okay";
Nope, drop.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-04 5:49 ` Krzysztof Kozlowski
@ 2025-09-04 8:08 ` 杨孙运
2025-09-04 8:26 ` Krzysztof Kozlowski
0 siblings, 1 reply; 26+ messages in thread
From: 杨孙运 @ 2025-09-04 8:08 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: syyang, robh, Laurent.pinchart, andrzej.hajda, conor+dt,
devicetree, dri-devel, jernej.skrabec, jonas, krzk+dt,
linux-kernel, neil.armstrong, rfoss
Krzysztof Kozlowski <krzk@kernel.org> 于2025年9月4日周四 13:49写道:
>
> On 04/09/2025 04:25, syyang wrote:
> > Fix device tree binding validation errors reported by Rob Herring.
> >
> > v2:
>
> That's not the place where you put changelog.
>
I'm currently learning what to do. Thank you
> > - Fixed $id field to match actual filename (lontium,lt9611c.yaml)
> > - build pass
> >
> > Thanks to Rob Herring for the review and feedback.
>
> Please carefully read submitting patches.
>
I'm currently learning what to do. Thank you
> Do not attach (thread) your patchsets to some other threads (unrelated
> or older versions). This buries them deep in the mailbox and might
> interfere with applying entire sets. See also:
> https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
>
> Please use subject prefixes matching the subsystem. You can get them for
> example with `git log --oneline -- DIRECTORY_OR_FILE` on the directory
> your patch is touching. For bindings, the preferred subjects are
> explained here:
> https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters
>
I'm currently learning what to do. Thank you.
> >
> > Signed-off-by: syyang <syyang@lontium.com>
> > ---
> > .../display/bridge/lontium,lt9611c.yaml | 121 ++++++++++++++++++
> > 1 file changed, 121 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> > new file mode 100644
> > index 000000000000..712644da4f1d
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611c.yaml
> > @@ -0,0 +1,121 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611c.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Lontium LT9611C 2 Port MIPI to HDMI Bridge
> > +
> > +maintainers:
> > + - Rob Herring <robh@kernel.org>
>
> No, how so?
>
I will change it to the email address I use when working at the
company. (syyang@lontium.com)
> > +
> > +description: |
>
> Do not need '|' unless you need to preserve formatting.
>
Both lontium,lt9211.yaml and lontium,lt9611.yaml use "|", so please
confirm if they need to be removed.
> > + The LT9611C are bridge devices which convert DSI to HDMI
>
> Why this cannot be added to lt9611 binding? Commit msg should clearly
> answer that.
>
The lt9611 and lt9611c are chips of different specifications, and
their related parameters are different.
The VDD-supply of lt9611c is 1.2V; the vdd-supply of lt9611uxc is also
1.2V, while the vdd-supply of lt9611 is 1.8V.
Now lt9611 and lt9611uxc are connected together. I'm not sure if this
is a problem.
If this lt9611c can also be bound to lt9611, and if you think it's
okay, then I have no problem.
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - lontium,lt9611c
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + "#sound-dai-cells":
>
> Missing dai-common ref.
>
I don't understand . I referred to:
Documentation/devicetree/bindings/display/bridge/ite,it6505.yaml
Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> > + const: 0
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + reset-gpios:
> > + maxItems: 1
> > + description: GPIO connected to active high RESET 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:
> > + Primary MIPI port-1 for MIPI input
> > +
> > + port@1:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description:
> > + Additional MIPI port-2 for MIPI input, used in combination
> > + with primary MIPI port-1 to drive higher resolution displays
> > +
> > + port@2:
> > + $ref: /schemas/graph.yaml#/properties/port
> > + description:
> > + HDMI port for HDMI output
> > +
> > + required:
> > + - port@0
> > + - port@2
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - interrupts
> > + - vdd-supply
> > + - vcc-supply
> > + - ports
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + #include <dt-bindings/gpio/gpio.h>
> > + #include <dt-bindings/interrupt-controller/irq.h>
> > +
> > + i2c10 {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + hdmi-bridge@41 {
> > + compatible = "lontium,lt9611c";
> > + reg = <0x41>;
> > + #sound-dai-cells = <0>;
> > + interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
> > + reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
> > + vdd-supply = <<9611_1v2>;
> > + vcc-supply = <<9611_3v3>;
> > + status = "okay";
>
> Nope, drop.
>
remove status = "okay" ?
>
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-04 8:08 ` 杨孙运
@ 2025-09-04 8:26 ` Krzysztof Kozlowski
2025-09-04 11:27 ` 杨孙运
0 siblings, 1 reply; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-09-04 8:26 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, Laurent.pinchart, andrzej.hajda, conor+dt,
devicetree, dri-devel, jernej.skrabec, jonas, krzk+dt,
linux-kernel, neil.armstrong, rfoss
On Thu, Sep 04, 2025 at 04:08:30PM +0800, 杨孙运 wrote:
> > > +
> > > +description: |
> >
> > Do not need '|' unless you need to preserve formatting.
> >
> Both lontium,lt9211.yaml and lontium,lt9611.yaml use "|", so please
> confirm if they need to be removed.
Add code which is needed, not code which you found somewhere. Why '|' is
needed here?
>
> > > + The LT9611C are bridge devices which convert DSI to HDMI
> >
> > Why this cannot be added to lt9611 binding? Commit msg should clearly
> > answer that.
> >
> The lt9611 and lt9611c are chips of different specifications, and
> their related parameters are different.
> The VDD-supply of lt9611c is 1.2V; the vdd-supply of lt9611uxc is also
> 1.2V, while the vdd-supply of lt9611 is 1.8V.
> Now lt9611 and lt9611uxc are connected together. I'm not sure if this
> is a problem.
> If this lt9611c can also be bound to lt9611, and if you think it's
> okay, then I have no problem.
It is not a problem.
>
> > > +
> > > +properties:
> > > + compatible:
> > > + enum:
> > > + - lontium,lt9611c
> > > +
> > > + reg:
> > > + maxItems: 1
> > > +
> > > + "#sound-dai-cells":
> >
> > Missing dai-common ref.
> >
> I don't understand . I referred to:
> Documentation/devicetree/bindings/display/bridge/ite,it6505.yaml
> Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
>
You call this device a DAI, so your binding should reference dai-common
schema, like every other one. You can check simple codecs for examples.
> > > + const: 0
> > > +
> > > + interrupts:
> > > + maxItems: 1
> > > +
> > > + reset-gpios:
> > > + maxItems: 1
> > > + description: GPIO connected to active high RESET 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:
> > > + Primary MIPI port-1 for MIPI input
> > > +
> > > + port@1:
> > > + $ref: /schemas/graph.yaml#/properties/port
> > > + description:
> > > + Additional MIPI port-2 for MIPI input, used in combination
> > > + with primary MIPI port-1 to drive higher resolution displays
> > > +
> > > + port@2:
> > > + $ref: /schemas/graph.yaml#/properties/port
> > > + description:
> > > + HDMI port for HDMI output
> > > +
> > > + required:
> > > + - port@0
> > > + - port@2
> > > +
> > > +required:
> > > + - compatible
> > > + - reg
> > > + - interrupts
> > > + - vdd-supply
> > > + - vcc-supply
> > > + - ports
> > > +
> > > +additionalProperties: false
> > > +
> > > +examples:
> > > + - |
> > > + #include <dt-bindings/gpio/gpio.h>
> > > + #include <dt-bindings/interrupt-controller/irq.h>
> > > +
> > > + i2c10 {
> > > + #address-cells = <1>;
> > > + #size-cells = <0>;
> > > +
> > > + hdmi-bridge@41 {
> > > + compatible = "lontium,lt9611c";
> > > + reg = <0x41>;
> > > + #sound-dai-cells = <0>;
> > > + interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
> > > + reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
> > > + vdd-supply = <<9611_1v2>;
> > > + vcc-supply = <<9611_3v3>;
> > > + status = "okay";
> >
> > Nope, drop.
> >
> remove status = "okay" ?
Yes. Instead of asking me, you can try to think about possibilities.
Ask yourself yourself - why do you need it here? What changes if you
have it? What changes if you drop it? Why reviewer asks for it - maybe
there is something behind. That way you will learn more about this.
I suggest to go through the slides of my OSSE25 talk about DT for
beginners.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-04 2:52 ` Dmitry Baryshkov
@ 2025-09-04 10:48 ` 杨孙运
2025-09-04 11:04 ` Krzysztof Kozlowski
2025-09-04 14:39 ` Dmitry Baryshkov
0 siblings, 2 replies; 26+ messages in thread
From: 杨孙运 @ 2025-09-04 10:48 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
>
> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> > The following changes are included:
> >
> > - Updated Kconfig and Makefile to include the new driver
> > - Implementation of the bridge driver at
> > drivers/gpu/drm/bridge/lontium-lt9611c.c
>
> This is really not interesting, it can be seen from the patch itself.
> Please read Documentation/process/submitting-patches.rst.
>
Sorry, I will study submitting-patches.rst.
> Is it possible to toggle infoframes?
sorry, I don't understand the meaning of this sentence. Please explain
it in detail.
> Is there YUV 444 / 422 output support? YUV420? Interlaced?
>
HDMI output support YUV 444, YUV422 , YUV420 and Interlaced.
> >
> > Signed-off-by: syyang <syyang@lontium.com>
> > ---
> > drivers/gpu/drm/bridge/Kconfig | 16 +
> > drivers/gpu/drm/bridge/Makefile | 1 +
> > drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 ++++++++++++++++++++++
> > 3 files changed, 1513 insertions(+)
> > create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
> >
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index b9e0ca85226a..f0df146ed2ce 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -170,6 +170,22 @@ config DRM_LONTIUM_LT9611
> > HDMI signals
> > Please say Y if you have such hardware.
> >
> > +config DRM_LONTIUM_LT9611C
> > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > + select SND_SOC_HDMI_CODEC if SND_SOC
> > + depends on OF
> > + select DRM_PANEL_BRIDGE
> > + select DRM_KMS_HELPER
> > + select DRM_MIPI_DSI
> > + select DRM_DISPLAY_HELPER
> > + select DRM_DISPLAY_HDMI_STATE_HELPER
>
> You are not using the HDMI_STATE_HELPER, are you?
>
I will fix,thanks.
> > + select REGMAP_I2C
> > + help
> > + Driver for Lontium LT9611C DSI to HDMI bridge
> > + chip driver that converts dual DSI and I2S to
> > + HDMI signals
> > + Please say Y if you have such hardware.
> > +
> > config DRM_LONTIUM_LT9611UXC
> > tristate "Lontium LT9611UXC DSI/HDMI bridge"
> > select SND_SOC_HDMI_CODEC if SND_SOC
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index 245e8a27e3fc..ccfa9987aa4f 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -15,6 +15,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> > obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
> > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > obj-$(CONFIG_DRM_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-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > new file mode 100644
> > index 000000000000..c4d680362583
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > @@ -0,0 +1,1496 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2025 LONTIUM
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of the GNU General Public License as published by the
> > + * Free Software Foundation.
> > + *
> > + * This program is distributed in the hope that it will be useful, but
> > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> > + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> > + * for more details.
>
> Drop the licence text, you have SPDX header for it.
>
I will fix,thanks.
> > + */
> > +#include <linux/firmware.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/i2c.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regmap.h>
> > +#include <linux/regulator/consumer.h>
> > +#include <drm/drm_modes.h>
> > +#include <sound/hdmi-codec.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_edid.h>
> > +#include <drm/drm_mipi_dsi.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_print.h>
> > +#include <drm/drm_probe_helper.h>
> > +#include <linux/pm_runtime.h>
>
> Sort the headers
>
I will fix,thanks.
> > +
> > +#define FW_SIZE (64 * 1024)
> > +#define LT_PAGE_SIZE 256
> > +#define FW_FILE "LT9611C.bin"
>
> Please land this firmware to the linux-firmware repository.
>
The LT9611C has built-in firmware, and when the chip is running, it
uses the internal firmware.
The firmware needs to be updated only when the customer encounters
issues during the debugging phase due to changes in hardware design or
parameters.
When the customer needs to update the firmware, they will apply to our
company for a customized firmware.
They will place this firmware under /lib/firmware, and then reboot the
platform. After that, the driver will update the firmware.
So I think there is no need to upload the firmware.
> > +#define NOT_UPGRADE 0
> > +#define UPGRADE 1
>
> You know, C99 has 'bool' type.
>
I will fix,thanks.
> > +
> > +struct lt9611c {
> > + 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 work_struct work;
> > + struct device_node *dsi0_node;
> > + struct device_node *dsi1_node;
> > + struct mipi_dsi_device *dsi0;
> > + struct mipi_dsi_device *dsi1;
> > + struct platform_device *audio_pdev;
> > + struct gpio_desc *reset_gpio;
> > + struct regulator_bulk_data supplies[2];
> > + struct device *codec_dev;
> > + struct task_struct *kthread;
> > + struct task_struct *test_kthread;
> > + hdmi_codec_plugged_cb plugged_cb;
> > + const struct firmware *fw;
> > + u64 fw_version;
> > + u8 *edid_buf;
> > + int edid_len;
> > + bool edid_valid;
> > + u8 fw_crc;
> > +
> > + bool work_inited;
> > + bool bridge_added;
> > + bool regulators_enabled;
> > + bool hdmi_connected;
> > + enum drm_connector_status audio_status;
> > +};
> > +
> > +static const struct regmap_range chip_ranges[] = {
> > + { .range_min = 0, .range_max = 0xff },
> > +};
> > +
> > +static const struct regmap_access_table chip_table = {
> > + .yes_ranges = chip_ranges,
> > + .n_yes_ranges = ARRAY_SIZE(chip_ranges),
> > +};
> > +
> > +static const struct regmap_config lt9611c_regmap_config = {
> > + .reg_bits = 8,
>
> It's 16
>
I will check this issue. thanks
> > + .val_bits = 8,
> > + .volatile_table = &chip_table,
> > + .cache_type = REGCACHE_NONE,
> > +};
> > +
> > +struct crc_info {
> > + u8 width;
> > + u32 poly;
> > + u32 crc_init;
> > + u32 xor_out;
> > + bool refin;
> > + bool refout;
> > +};
> > +
> > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c);
> > +
> > +static unsigned int bits_reverse(u32 in_val, u8 bits)
> > +{
> > + u32 out_val = 0;
> > + u8 i;
> > +
> > + for (i = 0; i < bits; i++) {
> > + if (in_val & (1 << i))
> > + out_val |= 1 << (bits - 1 - i);
> > + }
> > +
> > + return out_val;
> > +}
> > +
> > +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
>
> Use library functions for that.
>
I'm not sure whether the algorithms in the llibrary functions are
consistent with those designed in our chip.
If either of them changes, it will cause the firmware updated to the
chip to fail to run.
I think it's safer to implement it using our own code.
I'll check it.
> > +{
> > + u8 width = type.width;
> > + u32 poly = type.poly;
> > + u32 crc = type.crc_init;
> > + u32 xorout = type.xor_out;
> > + bool refin = type.refin;
> > + bool refout = type.refout;
> > + u8 n;
> > + u32 bits;
> > + u32 data;
> > + u8 i;
> > +
> > + n = (width < 8) ? 0 : (width - 8);
> > + crc = (width < 8) ? (crc << (8 - width)) : crc;
> > + bits = (width < 8) ? 0x80 : (1 << (width - 1));
> > + poly = (width < 8) ? (poly << (8 - width)) : poly;
> > + while (buf_len--) {
> > + data = *(buf++);
> > + if (refin)
> > + data = bits_reverse(data, 8);
> > + crc ^= (data << n);
> > + for (i = 0; i < 8; i++) {
> > + if (crc & bits)
> > + crc = (crc << 1) ^ poly;
> > + else
> > + crc = crc << 1;
> > + }
> > + }
> > + crc = (width < 8) ? (crc >> (8 - width)) : crc;
> > + if (refout)
> > + crc = bits_reverse(crc, width);
> > + crc ^= xorout;
> > +
> > + return (crc & ((2 << (width - 1)) - 1));
> > +}
> > +
> > +static u8 calculate_crc(struct lt9611c *lt9611c)
> > +{
> > + struct crc_info type = {
> > + .width = 8,
> > + .poly = 0x31,
> > + .crc_init = 0,
> > + .xor_out = 0,
> > + .refout = false,
> > + .refin = false,
> > + };
> > + const u8 *upgrade_data;
> > + u64 len;
> > + u64 crc_size = FW_SIZE - 1;
> > + u8 default_val = 0xFF;
> > +
> > + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
> > + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
> > + return 0;
> > + }
> > +
> > + upgrade_data = lt9611c->fw->data;
> > + len = lt9611c->fw->size;
> > +
> > + type.crc_init = get_crc(type, upgrade_data, len);
> > +
> > + crc_size -= len;
> > + while (crc_size--)
> > + type.crc_init = get_crc(type, &default_val, 1);
> > +
> > + return type.crc_init;
> > +}
> > +
> > +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret = 0;
> > +
> > + ret = regmap_write(lt9611c->regmap, reg, val);
> > + if (ret < 0) {
> > + dev_err(dev,
> > + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > + lt9611c->client->addr, reg, ret);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
>
> Drop these two wrappers, they provide no extra functionality.
>
I will consider fixing this issue. thanks.
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret = 0;
> > + unsigned int tmp;
> > +
> > + if (!val)
> > + return -EINVAL;
> > +
> > + ret = regmap_read(lt9611c->regmap, reg, &tmp);
> > + if (ret < 0) {
> > + dev_err(dev,
> > + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > + lt9611c->client->addr, reg, ret);
> > +
> > + return ret;
> > + }
> > +
> > + *val = (u8)tmp;
> > +
> > + return 0;
> > +}
> > +
> > +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> > + unsigned int param_count, u8 *return_buffer,
> > + unsigned int return_count)
> > +{
> > + int count, i;
> > + u8 temp;
> > +
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0xDE, 0x01);
>
> - lowercase all hex values
i will fix , thanks.
> - use paged writes as implemented for LT9611 and LT9611UXC
>
Don't understand.
> > +
> > + count = 0;
> > + do {
> > + i2c_read_byte(lt9611c, 0xAE, &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;
> > +
> > + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
> > + }
> > +
> > + i2c_write_byte(lt9611c, 0xDE, 0x02);
> > +
> > + count = 0;
> > + do {
> > + i2c_read_byte(lt9611c, 0xAE, &temp);
> > + usleep_range(1000, 2000);
> > + count++;
> > + } while (count < 100 && temp != 0x02);
> > +
> > + if (temp != 0x02)
> > + return -2;
> > +
> > + for (i = 0; i < return_count; i++)
> > + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
> > +
> > + return 0;
> > +}
> > +
> > +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + /* ensure filesystem ready */
> > + msleep(3000);
>
> No. If the firmware is necessary and it's not ready, return
> -EPROBE_DEFER.
>
The firmware is unnecessary . This part of the code is for customers
who need to upgrade the chip firmware.
Due to the different designs of the platform, the firmware used by
each customer may be different.
Therefore, when they need to update the firmware, they only need to
compile the firmware into the /lib/firmware directory during the
compilation
process, and then burn the image into the platform.
Once reboot platform, the firmware upgrade can be automatically completed.
When there is no need to upgrade the firmware, this part of the code
will not affect the operation of the driver.
> > + ret = request_firmware(<9611c->fw, FW_FILE, dev);
> > + if (ret) {
> > + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> > + return ret;
> > + }
> > +
> > + if (lt9611c->fw->size > FW_SIZE - 1) {
> > + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
> > + lt9611c->fw = NULL;
> > + return -EINVAL;
> > + }
> > +
> > + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
> > +
> > + lt9611c->fw_crc = calculate_crc(lt9611c);
> > +
> > + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
>
> No spamming with the unnecessary info. If you want, print the version
> of the firmware.
>
i will fix, thanks
> > +
> > + return 0;
> > +}
> > +
> > +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > + //fifo_rst_n
> > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > +
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0x5E, 0xC1);
> > + i2c_write_byte(lt9611c, 0x58, 0x00);
> > + i2c_write_byte(lt9611c, 0x59, 0x50);
> > + i2c_write_byte(lt9611c, 0x5A, 0x10);
> > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > +}
> > +
> > +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0x5e, 0x5f);
> > + i2c_write_byte(lt9611c, 0x5a, 0x20);
> > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > + i2c_write_byte(lt9611c, 0x5a, 0x10);
> > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > +}
> > +
> > +static void lt9611c_wren(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0x5a, 0x04);
> > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > +}
> > +
> > +static void lt9611c_wrdi(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0x5A, 0x08);
> > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > +}
> > +
> > +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > + u8 flash_crc;
> > +
> > + if (!lt9611c)
> > + return -EINVAL;
>
> How can it be NULL here?
>
i will fix, thanks
> > +
> > + lt9611c_config_parameters(lt9611c);
> > + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
> > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > +
> > + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to read flash crc\n");
> > + return ret;
> > + }
> > +
> > + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
> > + flash_crc, lt9611c->fw_crc);
>
> dev_dbg()
>
i will fix, thanks
> > +
> > + lt9611c_wrdi(lt9611c);
> > +
> > + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
> > +}
> > +
> > +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + //fifo_rst_n
> > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > +
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0x5e, 0x40);
> > + i2c_write_byte(lt9611c, 0x56, 0x05);
> > + i2c_write_byte(lt9611c, 0x55, 0x25);
> > + i2c_write_byte(lt9611c, 0x55, 0x01);
> > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > +
> > + ret = i2c_read_byte(lt9611c, 0x5f, status);
> > + if (ret < 0)
> > + dev_err(dev, "failed to read flash register status\n");
> > +
> > + return ret;
> > +}
> > +
> > +static void lt9611c_block_erase(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + u32 i = 0;
> > + u8 flash_status = 0;
> > + u8 block_num = 0x00;
> > + u32 flash_addr = 0x00;
> > +
> > + for (block_num = 0; block_num < 2; block_num++) {
> > + flash_addr = (block_num * 0x008000);
> > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > + i2c_write_byte(lt9611c, 0x5A, 0x04);
> > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
> > + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
> > + i2c_write_byte(lt9611c, 0x5D, flash_addr);
> > + i2c_write_byte(lt9611c, 0x5A, 0x01);
> > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > + msleep(100);
> > + i = 0;
> > + while (1) {
> > + read_flash_reg_status(lt9611c, &flash_status);
> > + if ((flash_status & 0x01) == 0)
> > + break;
> > +
> > + if (i > 50)
> > + break;
> > +
> > + i++;
> > + msleep(50);
> > + }
> > + }
> > +
> > + dev_info(dev, "erase flash done.\n");
> > +}
> > +
> > +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0x51, 0x00);
> > + i2c_write_byte(lt9611c, 0x55, 0xc0);
> > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > +}
> > +
> > +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0x51, 0xff);
> > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > +}
> > +
> > +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > + i2c_write_byte(lt9611c, 0x5A, 0x30);
> > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > +}
> > +
> > +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > + int page = 0, num = 0, i = 0;
> > + const u8 *data;
> > + u64 size, index;
> > + u8 value;
> > +
> > + data = lt9611c->fw->data;
> > + size = lt9611c->fw->size;
> > +
> > + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> > +
> > + if (page * LT_PAGE_SIZE > 64 * 1024) {
> > + dev_err(dev, "firmware size out of range\n");
> > + return -EINVAL;
> > + }
> > +
> > + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
>
>
> dev_dbg()
>
i will fix, thanks
> > +
> > + for (num = 0; num < page; num++) {
> > + lt9611c_data_to_sram(lt9611c);
> > +
> > + for (i = 0; i < LT_PAGE_SIZE; i++) {
> > + index = num * LT_PAGE_SIZE + i;
> > + value = (index < size) ? data[index] : 0xFF;
> > +
> > + ret = i2c_write_byte(lt9611c, 0x59, value);
> > + if (ret < 0) {
> > + dev_err(dev, "write error at page %u, index %u\n", num, i);
> > + return ret;
> > + }
> > + }
> > +
> > + lt9611c_wren(lt9611c);
> > + lt9611c_sram_to_flash(lt9611c, addr);
> > +
> > + addr += LT_PAGE_SIZE;
> > + }
> > +
> > + lt9611c_wrdi(lt9611c);
> > +
> > + return 0;
> > +}
> > +
> > +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > + u8 crc;
> > +
> > + crc = lt9611c->fw_crc;
> > + lt9611c_crc_to_sram(lt9611c);
> > + ret = i2c_write_byte(lt9611c, 0x59, crc);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to write CRC\n");
> > + return -1;
> > + }
> > +
> > + lt9611c_wren(lt9611c);
> > + lt9611c_sram_to_flash(lt9611c, addr);
> > + lt9611c_wrdi(lt9611c);
> > +
> > + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
>
> dev_dbg
>
i will fix, thanks
> > +
> > + return 0;
> > +}
> > +
> > +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
>
> dev_dbg
>
i will fix, thanks
> > +
> > + lt9611c_config_parameters(lt9611c);
> > + lt9611c_block_erase(lt9611c);
> > +
> > + ret = lt9611c_write_data(lt9611c, 0);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to write firmware data\n");
> > + return ret;
> > + }
> > +
> > + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to write firmware CRC\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + u8 crc_result;
> > +
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > + i2c_read_byte(lt9611c, 0x21, &crc_result);
> > +
> > + if (crc_result == lt9611c->fw_crc) {
> > + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
>
> dev_dbg
>
i will fix, thanks
> > + return 0;
> > + }
> > +
> > + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
> > + lt9611c->fw_crc, crc_result);
> > + return -EIO;
> > +}
> > +
> > +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
> > +{
> > + return container_of(bridge, struct lt9611c, bridge);
> > +}
> > +
> > +static void lt9611c_lock(struct lt9611c *lt9611c)
> > +{
> > + mutex_lock(<9611c->ocm_lock);
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > +}
> > +
> > +static void lt9611c_unlock(struct lt9611c *lt9611c)
> > +{
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0xee, 0x00);
> > + mutex_unlock(<9611c->ocm_lock);
> > +}
> > +
> > +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
> > +{
> > + struct lt9611c *lt9611c = dev_id;
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > + u8 irq_status;
> > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > + u8 data[5];
> > +
> > + mutex_lock(<9611c->ocm_lock);
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_read_byte(lt9611c, 0x84, &irq_status);
> > +
> > + if (!(irq_status & BIT(0))) {
> > + mutex_unlock(<9611c->ocm_lock);
> > + return IRQ_HANDLED;
> > + }
> > + dev_info(dev, "HPD interrupt triggered.\n");
>
> Nice joke. dev_dbg().
>
i will fix, thanks
> > +
> > + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
> > + usleep_range(10000, 12000);
>
> Why?
>
Our chip design specification requires that this be done when clearing
the interrupt.
> > + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
> > +
> > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > + if (ret) {
> > + dev_err(dev, "failed to read HPD status\n");
> > + } else {
> > + lt9611c->hdmi_connected = (data[4] == 0x02);
> > + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
>
> dev_dbg()
>
i will fix, thanks
> > + }
> > +
> > + lt9611c->audio_status = lt9611c->hdmi_connected ?
> > + connector_status_connected :
> > + connector_status_disconnected;
>
> What is it being used for? Why do you need separate status for audio?
>
Used to update the connection status of the audio.
The separate status indicators make it clearer for the readers.
> > +
> > + schedule_work(<9611c->work);
> > +
> > + mutex_unlock(<9611c->ocm_lock);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static void lt9611c_hpd_work(struct work_struct *work)
> > +{
> > + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
> > + bool connected;
> > +
> > + mutex_lock(<9611c->ocm_lock);
> > + connected = lt9611c->hdmi_connected;
> > + mutex_unlock(<9611c->ocm_lock);
> > +
> > + drm_bridge_hpd_notify(<9611c->bridge,
> > + connected ?
> > + connector_status_connected :
> > + connector_status_disconnected);
>
> Incorrect indentation.
>
? The checkpatch.pl did not detect it.
> > +
> > + lt9611c_audio_update_connector_status(lt9611c);
> > +}
> > +
> > +static void lt9611c_reset(struct lt9611c *lt9611c)
> > +{
> > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > + msleep(20);
> > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > + msleep(20);
> > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > + msleep(400);
> > +}
> > +
> > +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + lt9611c->supplies[0].supply = "vcc";
> > + lt9611c->supplies[1].supply = "vdd";
> > +
> > + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
> > +
> > + return ret;
> > +}
> > +
> > +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
> > +{
> > + int ret;
> > +
> > + ret = regulator_enable(lt9611c->supplies[0].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + usleep_range(5000, 10000);
> > +
> > + ret = regulator_enable(lt9611c->supplies[1].consumer);
> > + if (ret < 0) {
> > + regulator_disable(lt9611c->supplies[0].consumer);
> > + return ret;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
> > +{
> > + int ret;
> > +
> > + ret = regulator_disable(lt9611c->supplies[0].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + usleep_range(5000, 10000);
> > +
> > + ret = regulator_disable(lt9611c->supplies[1].consumer);
> > + if (ret < 0)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
> > + struct device_node *dsi_node)
> > +{
> > + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
> > + struct mipi_dsi_device *dsi;
> > + struct mipi_dsi_host *host;
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + host = of_find_mipi_dsi_host_by_node(dsi_node);
> > + if (!host)
> > + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
> > +
> > + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> > + if (IS_ERR(dsi)) {
> > + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
> > + struct drm_encoder *encoder,
> > + enum drm_bridge_attach_flags flags)
> > +{
> > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > +
> > + return drm_bridge_attach(encoder, lt9611c->next_bridge,
> > + bridge, flags);
> > +}
> > +
> > +static enum drm_mode_status lt9611c_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)
>
> Use .hdmi_tmds_char_rate_valid() for that.
>
I will check and test, thanks
> > + return MODE_OK;
> > + else
> > + return MODE_BAD;
> > +}
> > +
> > +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
> > + const struct drm_display_mode *mode,
> > + const struct drm_display_mode *adj_mode)
>
> - Wrong indentation
will fix, thanks
> - mode_set callback is deprecated and should not be used for new
> drivers.
>
I found that kernel 6.17 is still in use mode_set callback.
> > +{
> > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > + struct device *dev = lt9611c->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;
> > +
>
> > + 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, "Out video info:\n");
> > + dev_info(dev,
> > + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
> > + h_total, hactive, hsync_len, hfront_porch, hback_porch);
> > + dev_info(dev,
> > + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
> > + v_total, vactive, vsync_len, vfront_porch, vback_porch);
>
>
> Fix indentation
The indentation issue was not detected by checkpatch.pl.
> Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
> spammy.
>
i will fix, thanks
> > +
> > + 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(<9611c->ocm_lock);
> > + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
> > + if (ret)
> > + dev_err(dev, "video set failed\n");
> > + mutex_unlock(<9611c->ocm_lock);
> > +}
> > +
> > +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
> > + struct drm_connector *connector)
> > +{
> > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > + bool connected = false;
> > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > + u8 data[5];
> > +
> > + mutex_lock(<9611c->ocm_lock);
> > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > + if (ret) {
> > + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
> > + ret);
> > + } else {
> > + connected = (data[4] == 0x02);
> > + }
>
> THere is no need to put single-line statements in brackets. Drop those.
>
yes, i will fix, thks
> > +
> > + lt9611c->hdmi_connected = connected;
> > +
> > + if (lt9611c->hdmi_connected)
> > + lt9611c->audio_status = connector_status_connected;
> > + else
> > + lt9611c->audio_status = connector_status_disconnected;
> > +
> > + mutex_unlock(<9611c->ocm_lock);
> > +
> > + return connected ? connector_status_connected :
> > + connector_status_disconnected;
> > +}
> > +
> > +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret, i, bytes_to_copy, offset = 0;
> > + u8 packets_num;
> > + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> > + u8 return_edid_data[37];
> > + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> > + u8 return_edid_byte_num[6];
> > +
> > + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> > + if (ret) {
> > + dev_err(dev, "Failed to read EDID byte number\n");
> > + lt9611c->edid_valid = false;
> > + return ret;
> > + }
> > +
> > + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> > +
> > + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> > + lt9611c->edid_len : 0)) {
> > + kfree(lt9611c->edid_buf);
> > + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> > + if (!lt9611c->edid_buf) {
> > + dev_err(dev, "Failed to allocate EDID buffer\n");
> > + lt9611c->edid_len = 0;
> > + lt9611c->edid_valid = false;
> > + return -ENOMEM;
> > + }
> > + }
> > +
> > + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> > + (lt9611c->edid_len / 32);
> > + for (i = 0; i < packets_num; i++) {
> > + read_edid_data_cmd[4] = (u8)i;
> > + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> > + if (ret) {
> > + dev_err(dev, "Failed to read EDID packet %d\n", i);
> > + lt9611c->edid_valid = false;
> > + return -EIO;
> > + }
> > + offset = i * 32;
> > + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> > + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
>
> Don't store EDID in the long-term structures. Read it on demand.
>
I will think about this issue.
> > + }
> > +
> > + lt9611c->edid_valid = true;
> > +
> > + return ret;
> > +}
> > +
> > +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> > +{
> > + struct lt9611c *lt9611c = data;
> > + struct device *dev = lt9611c->dev;
> > + unsigned int total_blocks;
> > + int ret;
> > +
> > + if (len > 128)
> > + return -EINVAL;
> > +
> > + guard(mutex)(<9611c->ocm_lock);
> > + if (block == 0 || !lt9611c->edid_valid) {
> > + ret = lt9611c_read_edid(lt9611c);
> > + if (ret) {
> > + dev_err(dev, "EDID read failed\n");
> > + return ret;
> > + }
> > + }
> > +
> > + total_blocks = lt9611c->edid_len / 128;
> > + if (!total_blocks) {
> > + dev_err(dev, "No valid EDID blocks\n");
> > + return -EIO;
> > + }
> > +
> > + if (block >= total_blocks) {
> > + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> > + block, total_blocks);
> > + return -EINVAL;
> > + }
> > +
> > + memcpy(buf, lt9611c->edid_buf + block * 128, len);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> > + struct drm_connector *connector)
> > +{
> > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > +
> > + usleep_range(10000, 20000);
>
> Why?
>
Delay for a while to ensure that EDID is ready.
> > + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> > +}
> > +
> > +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> > + .attach = lt9611c_bridge_attach,
> > + .mode_valid = lt9611c_bridge_mode_valid,
> > + .mode_set = lt9611c_bridge_mode_set,
> > + .detect = lt9611c_bridge_detect,
> > + .edid_read = lt9611c_bridge_edid_read,
> > +};
> > +
> > +static int lt9611c_parse_dt(struct device *dev,
> > + struct lt9611c *lt9611c)
> > +{
> > + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
> > + if (!lt9611c->dsi0_node) {
> > + dev_err(dev, "failed to get remote node for primary dsi\n");
> > + return -ENODEV;
> > + }
> > +
> > + lt9611c->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, <9611c->next_bridge);
> > +}
> > +
> > +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > +
> > + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > + if (IS_ERR(lt9611c->reset_gpio)) {
> > + dev_err(dev, "failed to acquire reset gpio\n");
> > + return PTR_ERR(lt9611c->reset_gpio);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
> > +{
> > + u8 val;
> > + u64 ver = 0;
> > +
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > +
> > + i2c_read_byte(lt9611c, 0x80, &val);
> > + ver = val;
> > +
> > + i2c_read_byte(lt9611c, 0x81, &val);
> > + ver = (ver << 8) | val;
> > +
> > + *version = ver;
> > +}
> > +
> > +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + u8 val = 0;
> > +
> > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > + i2c_write_byte(lt9611c, 0xff, 0xe1);
> > +
> > + i2c_read_byte(lt9611c, 0x00, &val);
> > + if (val != 0x23)
> > + return -ENODEV;
> > +
> > + i2c_read_byte(lt9611c, 0x01, &val);
> > + if (val != 0x06)
> > + return -ENODEV;
> > +
> > + dev_info(dev, "ChipId = 0x2306\n");
> > +
> > + return 0;
> > +}
> > +
> > +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> > + struct hdmi_codec_daifmt *fmt,
> > + struct hdmi_codec_params *hparms)
> > +{
> > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > +
> > + dev_info(lt9611c->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;
> > + }
>
> Does that add anything on top of the limitations of hdmi-codec.c?
>
The parameters supported in the hdmi-codec.c may not be supported by
my chip. Therefore, we can exclude the parameters that are not
supported by the chip.
> > +
> > + return 0;
> > +}
> > +
> > +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> > +{
> > +}
> > +
> > +static int lt9611c_audio_startup(struct device *dev, void *data)
> > +{
> > + return 0;
> > +}
> > +
> > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> > +{
> > + enum drm_connector_status status;
> > +
> > + status = lt9611c->audio_status;
> > + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> > + lt9611c->plugged_cb(lt9611c->codec_dev,
> > + status == connector_status_connected);
> > +}
> > +
> > +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> > + void *data,
> > + hdmi_codec_plugged_cb fn,
> > + struct device *codec_dev)
> > +{
> > + struct lt9611c *lt9611c = data;
> > +
> > + lt9611c->plugged_cb = fn;
> > + lt9611c->codec_dev = codec_dev;
> > + lt9611c_audio_update_connector_status(lt9611c);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> > + .hw_params = lt9611c_hdmi_hw_params,
> > + .audio_shutdown = lt9611c_audio_shutdown,
> > + .audio_startup = lt9611c_audio_startup,
> > + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> > +};
>
> No, we have HDMI audio helpers for that. Drop this and use the helpers
> instead.
>
??? I don't understand.
> > +
> > +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
> > +{
> > + struct hdmi_codec_pdata codec_data = {
> > + .ops = <9611c_codec_ops,
> > + .max_i2s_channels = 2,
> > + .i2s = 1,
> > + .data = lt9611c,
> > + };
> > +
> > + lt9611c->audio_pdev =
> > + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
> > + PLATFORM_DEVID_AUTO,
> > + &codec_data, sizeof(codec_data));
> > +
> > + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
> > +}
> > +
> > +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
> > +{
> > + if (lt9611c->audio_pdev) {
> > + platform_device_unregister(lt9611c->audio_pdev);
> > + lt9611c->audio_pdev = NULL;
> > + }
> > +}
> > +
> > +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > + int ret;
> > +
> > + lt9611c_lock(lt9611c);
> > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
> > + goto out;
> > + }
> > +
> > + ret = lt9611c_firmware_upgrade(lt9611c);
> > + if (ret < 0) {
> > + dev_err(dev, "upgrade failure\n");
> > + goto out;
> > + }
> > + lt9611c_reset(lt9611c);
> > + ret = lt9611c_upgrade_result(lt9611c);
> > + if (ret < 0)
> > + goto out;
> > +
> > +out:
> > + lt9611c_unlock(lt9611c);
> > + lt9611c_reset(lt9611c);
> > + if (lt9611c->fw) {
> > + release_firmware(lt9611c->fw);
> > + lt9611c->fw = NULL;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = lt9611c_firmware_update_store(lt9611c);
>
> Inline
>
i will fix, thks
> > + if (ret < 0)
> > + return ret;
> > + return len;
> > +}
> > +
> > +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
> > +{
> > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > +
> > + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
> > +}
> > +
> > +static DEVICE_ATTR_RW(lt9611c_firmware);
> > +
> > +static struct attribute *lt9611c_attrs[] = {
> > + &dev_attr_lt9611c_firmware.attr,
> > + NULL,
> > +};
> > +
> > +static const struct attribute_group lt9611c_attr_group = {
> > + .attrs = lt9611c_attrs,
> > +};
> > +
> > +static const struct attribute_group *lt9611c_attr_groups[] = {
> > + <9611c_attr_group,
> > + NULL,
> > +};
> > +
> > +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> > +{
> > + struct device *dev = lt9611c->dev;
> > +
> > + if (lt9611c->work_inited) {
> > + cancel_work_sync(<9611c->work);
> > + lt9611c->work_inited = false;
> > + dev_err(dev, "work cancelled\n");
>
> Why???
>
?? I don't understand.
> > + }
> > +
> > + if (lt9611c->bridge_added) {
> > + drm_bridge_remove(<9611c->bridge);
> > + lt9611c->bridge_added = false;
> > + dev_err(dev, "DRM bridge removed\n");
> > + }
> > +
> > + if (lt9611c->regulators_enabled) {
> > + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > + lt9611c->regulators_enabled = false;
> > + dev_err(dev, "regulators disabled\n");
> > + }
> > +
> > + if (lt9611c->audio_pdev)
> > + lt9611c_audio_exit(lt9611c);
> > +
> > + if (lt9611c->fw) {
>
> You definitely don't need firmware when the bridge is up and running.
>
The previous text has already described the working logic of the firmware.
> > + release_firmware(lt9611c->fw);
> > + lt9611c->fw = NULL;
> > + dev_err(dev, "firmware released\n");
> > + }
> > +
> > + if (lt9611c->dsi0_node) {
> > + of_node_put(lt9611c->dsi0_node);
> > + lt9611c->dsi0_node = NULL;
> > + dev_err(dev, "dsi0 node released\n");
> > + }
> > +
> > + if (lt9611c->dsi1_node) {
> > + of_node_put(lt9611c->dsi1_node);
> > + lt9611c->dsi1_node = NULL;
> > + dev_err(dev, "dsi1 node released\n");
> > + }
> > +}
> > +
> > +static int lt9611c_main(void *data)
> > +{
> > + struct lt9611c *lt9611c = data;
> > + struct device *dev = lt9611c->dev;
> > + struct i2c_client *client = lt9611c->client;
> > + int ret;
> > +
> > + lt9611c->work_inited = false;
> > + lt9611c->bridge_added = false;
> > + lt9611c->regulators_enabled = false;
> > +
> > + ret = lt9611c_parse_dt(dev, lt9611c);
> > + if (ret) {
> > + dev_err(dev, "failed to parse device tree\n");
> > + return ret;
> > + }
> > +
> > + ret = lt9611c_gpio_init(lt9611c);
> > + if (ret < 0)
> > + goto err_cleanup;
> > +
> > + ret = lt9611c_regulator_init(lt9611c);
> > + if (ret < 0)
> > + goto err_cleanup;
> > +
> > + ret = lt9611c_regulator_enable(lt9611c);
> > + if (ret)
> > + goto err_cleanup;
> > +
> > + lt9611c->regulators_enabled = true;
> > +
> > + lt9611c_reset(lt9611c);
> > +
> > + ret = lt9611c_read_chipid(lt9611c);
> > + if (ret < 0) {
> > + dev_err(dev, "failed to read chip id.\n");
> > + goto err_cleanup;
> > + }
> > +
> > + lt9611c_lock(lt9611c);
> > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > +
> > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
> > + dev_info(dev, "firmware upgrade needed\n");
> > +
> > + ret = lt9611c_firmware_upgrade(lt9611c);
> > + if (ret < 0) {
> > + dev_err(dev, "firmware upgrade failed\n");
> > + lt9611c_unlock(lt9611c);
> > + goto err_cleanup;
> > + }
> > +
> > + lt9611c_reset(lt9611c);
> > + ret = lt9611c_upgrade_result(lt9611c);
> > + if (ret < 0) {
> > + lt9611c_unlock(lt9611c);
> > + goto err_cleanup;
> > + }
> > +
> > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > + lt9611c_unlock(lt9611c);
> > +
> > + } else {
> > + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
> > + lt9611c_unlock(lt9611c);
> > + }
> > +
> > + if (lt9611c->fw) {
> > + release_firmware(lt9611c->fw);
> > + lt9611c->fw = NULL;
> > + }
> > + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
> > +
> > + INIT_WORK(<9611c->work, lt9611c_hpd_work);
> > + lt9611c->work_inited = true;
> > +
> > + if (!client->irq) {
> > + dev_err(dev, "failed to get INTP IRQ\n");
> > + ret = -ENODEV;
> > + goto err_cleanup;
> > + }
> > +
> > + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> > + lt9611c_irq_thread_handler,
> > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
> > + IRQF_NO_AUTOEN,
> > + "lt9611c", lt9611c);
> > + if (ret) {
> > + dev_err(dev, "failed to request irq\n");
> > + goto err_cleanup;
> > + }
> > +
> > + lt9611c->bridge.funcs = <9611c_bridge_funcs;
> > + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
> > + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> > + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> > +
> > + drm_bridge_add(<9611c->bridge);
> > + lt9611c->bridge_added = true;
>
> No unnecessary flags, please. Implement proper cleanup path, unwinding
> resources one by one.
>
I will consider this issue. thks
> > +
> > + /* Attach primary DSI */
> > + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
> > + if (IS_ERR(lt9611c->dsi0)) {
> > + ret = PTR_ERR(lt9611c->dsi0);
> > + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
> > + goto err_cleanup;
> > + }
> > +
> > + /* Attach secondary DSI, if specified */
> > + if (lt9611c->dsi1_node) {
> > + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
> > + if (IS_ERR(lt9611c->dsi1)) {
> > + ret = PTR_ERR(lt9611c->dsi1);
> > + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
> > + goto err_cleanup;
> > + }
> > + }
> > +
> > + lt9611c->audio_status = connector_status_disconnected;
> > +
> > + ret = lt9611c_audio_init(dev, lt9611c);
> > + if (ret < 0) {
> > + dev_err(dev, "audio init failed\n");
> > + goto err_cleanup;
> > + }
> > +
> > + lt9611c_reset(lt9611c);
> > + enable_irq(lt9611c->client->irq);
> > +
> > + return 0;
> > +
> > +err_cleanup:
> > + lt9611c_cleanup_resources(lt9611c);
> > + return ret;
> > +}
> > +
> > +static int lt9611c_probe(struct i2c_client *client)
> > +{
> > + struct lt9611c *lt9611c;
> > + struct device *dev = &client->dev;
> > +
> > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> > + dev_err(dev, "device doesn't support I2C\n");
> > + return -ENODEV;
> > + }
> > +
> > + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
>
> devm_drm_bridge_alloc()
>
i will fix, thks
> > + if (!lt9611c)
> > + return -ENOMEM;
> > +
> > + lt9611c->dev = dev;
> > + lt9611c->client = client;
> > + mutex_init(<9611c->ocm_lock);
> > +
> > + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
> > + if (IS_ERR(lt9611c->regmap)) {
> > + dev_err(dev, "regmap i2c init failed\n");
> > + return PTR_ERR(lt9611c->regmap);
> > + }
> > +
> > + i2c_set_clientdata(client, lt9611c);
> > +
> > + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
>
> Why do you need extra kthread for that???
>
> > + if (IS_ERR(lt9611c->kthread)) {
> > + dev_err(dev, "Failed to create kernel thread\n");
> > + return PTR_ERR(lt9611c->kthread);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void lt9611c_remove(struct i2c_client *client)
> > +{
> > + struct lt9611c *lt9611c = i2c_get_clientdata(client);
> > + struct device *dev = lt9611c->dev;
> > +
> > + kfree(lt9611c->edid_buf);
> > + disable_irq(client->irq);
> > + lt9611c_cleanup_resources(lt9611c);
> > + mutex_destroy(<9611c->ocm_lock);
> > + dev_info(dev, "remove driver\n");
> > +}
> > +
> > +static int lt9611c_bridge_suspend(struct device *dev)
> > +{
> > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + dev_info(lt9611c->dev, "suspend\n");
> > + disable_irq(lt9611c->client->irq);
> > + ret = lt9611c_regulator_disable(lt9611c);
> > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > +
> > + return ret;
> > +}
> > +
> > +static int lt9611c_bridge_resume(struct device *dev)
> > +{
> > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = lt9611c_regulator_enable(lt9611c);
> > + lt9611c_reset(lt9611c);
> > + enable_irq(lt9611c->client->irq);
> > + dev_info(lt9611c->dev, "resume\n");
> > +
> > + return ret;
> > +}
> > +
> > +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
> > + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
> > + lt9611c_bridge_resume)
> > +};
> > +
> > +static struct i2c_device_id lt9611c_id[] = {
> > + { "lontium,lt9611c", 0 },
> > + { /* sentinel */ }
> > +};
> > +
> > +static const struct of_device_id lt9611c_match_table[] = {
> > + { .compatible = "lontium,lt9611c" },
>
> Your schema also had lt9611uxd
>
i will fix, thks
> > + { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
> > +
> > +static struct i2c_driver lt9611c_driver = {
> > + .driver = {
> > + .name = "lt9611c",
> > + .of_match_table = lt9611c_match_table,
> > + .pm = <9611c_bridge_pm_ops,
> > + .dev_groups = lt9611c_attr_groups,
> > + },
> > + .probe = lt9611c_probe,
> > + .remove = lt9611c_remove,
> > + .id_table = lt9611c_id,
> > +};
> > +module_i2c_driver(lt9611c_driver);
> > +
> > +MODULE_AUTHOR("syyang <syyang@lontium.com>");
> > +MODULE_LICENSE("GPL v2");
> > +
> > +MODULE_FIRMWARE(FW_FILE);
> > --
> > 2.25.1
> >
>
> --
> With best wishes
> Dmitry
Dmitry, thank you very much
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-04 10:48 ` 杨孙运
@ 2025-09-04 11:04 ` Krzysztof Kozlowski
2025-09-04 11:17 ` 杨孙运
2025-09-04 14:39 ` Dmitry Baryshkov
1 sibling, 1 reply; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-09-04 11:04 UTC (permalink / raw)
To: 杨孙运, Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel
On 04/09/2025 12:48, 杨孙运 wrote:
>>> +
>>> +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
>>> +{
>>> + struct device *dev = lt9611c->dev;
>>> +
>>> + if (lt9611c->work_inited) {
>>> + cancel_work_sync(<9611c->work);
>>> + lt9611c->work_inited = false;
>>> + dev_err(dev, "work cancelled\n");
>>
>> Why???
>>
> ?? I don't understand.
You need to explain why that line - printing error - should be here. And
focus on "WHY" part.
>
>>> + }
>>> +
>>> + if (lt9611c->bridge_added) {
>>> + drm_bridge_remove(<9611c->bridge);
>>> + lt9611c->bridge_added = false;
>>> + dev_err(dev, "DRM bridge removed\n");
>>> + }
>>> +
>>> + if (lt9611c->regulators_enabled) {
>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
>>> + lt9611c->regulators_enabled = false;
>>> + dev_err(dev, "regulators disabled\n");
>>> + }
>>> +
>>> + if (lt9611c->audio_pdev)
>>> + lt9611c_audio_exit(lt9611c);
>>> +
>>> + if (lt9611c->fw) {
>>
>> You definitely don't need firmware when the bridge is up and running.
>>
> The previous text has already described the working logic of the firmware.
>
>>> + release_firmware(lt9611c->fw);
>>> + lt9611c->fw = NULL;
>>> + dev_err(dev, "firmware released\n");
>>> + }
>>> +
>>> + if (lt9611c->dsi0_node) {
>>> + of_node_put(lt9611c->dsi0_node);
>>> + lt9611c->dsi0_node = NULL;
>>> + dev_err(dev, "dsi0 node released\n");
Your driver is way, way to noisy.
Please read coding style - what does it say about driver being silent?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-04 11:04 ` Krzysztof Kozlowski
@ 2025-09-04 11:17 ` 杨孙运
0 siblings, 0 replies; 26+ messages in thread
From: 杨孙运 @ 2025-09-04 11:17 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Dmitry Baryshkov, syyang, robh, krzk+dt, conor+dt, andrzej.hajda,
neil.armstrong, rfoss, Laurent.pinchart, jonas, jernej.skrabec,
devicetree, dri-devel, linux-kernel
Krzysztof Kozlowski <krzk@kernel.org> 于2025年9月4日周四 19:04写道:
>
> On 04/09/2025 12:48, 杨孙运 wrote:
> >>> +
> >>> +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> >>> +{
> >>> + struct device *dev = lt9611c->dev;
> >>> +
> >>> + if (lt9611c->work_inited) {
> >>> + cancel_work_sync(<9611c->work);
> >>> + lt9611c->work_inited = false;
> >>> + dev_err(dev, "work cancelled\n");
> >>
> >> Why???
> >>
> > ?? I don't understand.
>
> You need to explain why that line - printing error - should be here. And
> focus on "WHY" part.
>
I understand. Thks.
> >
> >>> + }
> >>> +
> >>> + if (lt9611c->bridge_added) {
> >>> + drm_bridge_remove(<9611c->bridge);
> >>> + lt9611c->bridge_added = false;
> >>> + dev_err(dev, "DRM bridge removed\n");
> >>> + }
> >>> +
> >>> + if (lt9611c->regulators_enabled) {
> >>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> >>> + lt9611c->regulators_enabled = false;
> >>> + dev_err(dev, "regulators disabled\n");
> >>> + }
> >>> +
> >>> + if (lt9611c->audio_pdev)
> >>> + lt9611c_audio_exit(lt9611c);
> >>> +
> >>> + if (lt9611c->fw) {
> >>
> >> You definitely don't need firmware when the bridge is up and running.
> >>
> > The previous text has already described the working logic of the firmware.
> >
> >>> + release_firmware(lt9611c->fw);
> >>> + lt9611c->fw = NULL;
> >>> + dev_err(dev, "firmware released\n");
> >>> + }
> >>> +
> >>> + if (lt9611c->dsi0_node) {
> >>> + of_node_put(lt9611c->dsi0_node);
> >>> + lt9611c->dsi0_node = NULL;
> >>> + dev_err(dev, "dsi0 node released\n");
>
> Your driver is way, way to noisy.
>
> Please read coding style - what does it say about driver being silent?
>
Thank you for your guidance. I am learning how to make the driver silent.
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v2 1/2] This patch adds a new device tree binding documentation.
2025-09-04 8:26 ` Krzysztof Kozlowski
@ 2025-09-04 11:27 ` 杨孙运
0 siblings, 0 replies; 26+ messages in thread
From: 杨孙运 @ 2025-09-04 11:27 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: syyang, robh, Laurent.pinchart, andrzej.hajda, conor+dt,
devicetree, dri-devel, jernej.skrabec, jonas, krzk+dt,
linux-kernel, neil.armstrong, rfoss
Krzysztof Kozlowski <krzk@kernel.org> 于2025年9月4日周四 16:26写道:
>
> On Thu, Sep 04, 2025 at 04:08:30PM +0800, 杨孙运 wrote:
> > > > +
> > > > +description: |
> > >
> > > Do not need '|' unless you need to preserve formatting.
> > >
> > Both lontium,lt9211.yaml and lontium,lt9611.yaml use "|", so please
> > confirm if they need to be removed.
>
> Add code which is needed, not code which you found somewhere. Why '|' is
> needed here?
>
'|' It feels more comfortable to me to write the description text
after a line break. '|' When there's only one line of description
text, it can be omitted.
> >
> > > > + The LT9611C are bridge devices which convert DSI to HDMI
> > >
> > > Why this cannot be added to lt9611 binding? Commit msg should clearly
> > > answer that.
> > >
> > The lt9611 and lt9611c are chips of different specifications, and
> > their related parameters are different.
> > The VDD-supply of lt9611c is 1.2V; the vdd-supply of lt9611uxc is also
> > 1.2V, while the vdd-supply of lt9611 is 1.8V.
> > Now lt9611 and lt9611uxc are connected together. I'm not sure if this
> > is a problem.
> > If this lt9611c can also be bound to lt9611, and if you think it's
> > okay, then I have no problem.
>
> It is not a problem.
>
Do you think it can be merged with lt9611 and lt9611uxc?
> >
> > > > +
> > > > +properties:
> > > > + compatible:
> > > > + enum:
> > > > + - lontium,lt9611c
> > > > +
> > > > + reg:
> > > > + maxItems: 1
> > > > +
> > > > + "#sound-dai-cells":
> > >
> > > Missing dai-common ref.
> > >
> > I don't understand . I referred to:
> > Documentation/devicetree/bindings/display/bridge/ite,it6505.yaml
> > Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml
> >
>
>
> You call this device a DAI, so your binding should reference dai-common
> schema, like every other one. You can check simple codecs for examples.
>
i will fix , thks
> > > > + const: 0
> > > > +
> > > > + interrupts:
> > > > + maxItems: 1
> > > > +
> > > > + reset-gpios:
> > > > + maxItems: 1
> > > > + description: GPIO connected to active high RESET 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:
> > > > + Primary MIPI port-1 for MIPI input
> > > > +
> > > > + port@1:
> > > > + $ref: /schemas/graph.yaml#/properties/port
> > > > + description:
> > > > + Additional MIPI port-2 for MIPI input, used in combination
> > > > + with primary MIPI port-1 to drive higher resolution displays
> > > > +
> > > > + port@2:
> > > > + $ref: /schemas/graph.yaml#/properties/port
> > > > + description:
> > > > + HDMI port for HDMI output
> > > > +
> > > > + required:
> > > > + - port@0
> > > > + - port@2
> > > > +
> > > > +required:
> > > > + - compatible
> > > > + - reg
> > > > + - interrupts
> > > > + - vdd-supply
> > > > + - vcc-supply
> > > > + - ports
> > > > +
> > > > +additionalProperties: false
> > > > +
> > > > +examples:
> > > > + - |
> > > > + #include <dt-bindings/gpio/gpio.h>
> > > > + #include <dt-bindings/interrupt-controller/irq.h>
> > > > +
> > > > + i2c10 {
> > > > + #address-cells = <1>;
> > > > + #size-cells = <0>;
> > > > +
> > > > + hdmi-bridge@41 {
> > > > + compatible = "lontium,lt9611c";
> > > > + reg = <0x41>;
> > > > + #sound-dai-cells = <0>;
> > > > + interrupts-extended = <&pio 128 GPIO_ACTIVE_HIGH>;
> > > > + reset-gpios = <&pio 127 GPIO_ACTIVE_HIGH>;
> > > > + vdd-supply = <<9611_1v2>;
> > > > + vcc-supply = <<9611_3v3>;
> > > > + status = "okay";
> > >
> > > Nope, drop.
> > >
> > remove status = "okay" ?
>
> Yes. Instead of asking me, you can try to think about possibilities.
> Ask yourself yourself - why do you need it here? What changes if you
> have it? What changes if you drop it? Why reviewer asks for it - maybe
> there is something behind. That way you will learn more about this.
>
> I suggest to go through the slides of my OSSE25 talk about DT for
> beginners.
>
thanks,
> Best regards,
> Krzysztof
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-04 10:48 ` 杨孙运
2025-09-04 11:04 ` Krzysztof Kozlowski
@ 2025-09-04 14:39 ` Dmitry Baryshkov
2025-09-05 2:55 ` 杨孙运
1 sibling, 1 reply; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-04 14:39 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel
On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> >
> > On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> > > The following changes are included:
> > >
> > > - Updated Kconfig and Makefile to include the new driver
> > > - Implementation of the bridge driver at
> > > drivers/gpu/drm/bridge/lontium-lt9611c.c
> >
> > This is really not interesting, it can be seen from the patch itself.
> > Please read Documentation/process/submitting-patches.rst.
> >
> Sorry, I will study submitting-patches.rst.
>
> > Is it possible to toggle infoframes?
>
> sorry, I don't understand the meaning of this sentence. Please explain
> it in detail.
Is it possible to control InfoFrames being sent over the HDMI cable?
Both contents and enabling/disabling.
>
>
> > Is there YUV 444 / 422 output support? YUV420? Interlaced?
> >
>
> HDMI output support YUV 444, YUV422 , YUV420 and Interlaced.
See corresponding properties of the DRM bridge to be set if that's
supported.
>
>
> > >
> > > Signed-off-by: syyang <syyang@lontium.com>
> > > ---
> > > drivers/gpu/drm/bridge/Kconfig | 16 +
> > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 ++++++++++++++++++++++
> > > 3 files changed, 1513 insertions(+)
> > > create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
> > >
> > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > > index b9e0ca85226a..f0df146ed2ce 100644
> > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > @@ -170,6 +170,22 @@ config DRM_LONTIUM_LT9611
> > > HDMI signals
> > > Please say Y if you have such hardware.
> > >
> > > +config DRM_LONTIUM_LT9611C
> > > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > > + select SND_SOC_HDMI_CODEC if SND_SOC
> > > + depends on OF
> > > + select DRM_PANEL_BRIDGE
> > > + select DRM_KMS_HELPER
> > > + select DRM_MIPI_DSI
> > > + select DRM_DISPLAY_HELPER
> > > + select DRM_DISPLAY_HDMI_STATE_HELPER
> >
> > You are not using the HDMI_STATE_HELPER, are you?
> >
> I will fix,thanks.
>
> > > + select REGMAP_I2C
> > > + help
> > > + Driver for Lontium LT9611C DSI to HDMI bridge
> > > + chip driver that converts dual DSI and I2S to
> > > + HDMI signals
> > > + Please say Y if you have such hardware.
> > > +
> > > config DRM_LONTIUM_LT9611UXC
> > > tristate "Lontium LT9611UXC DSI/HDMI bridge"
> > > select SND_SOC_HDMI_CODEC if SND_SOC
> > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > > index 245e8a27e3fc..ccfa9987aa4f 100644
> > > --- a/drivers/gpu/drm/bridge/Makefile
> > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > @@ -15,6 +15,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > > +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
> > > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > > obj-$(CONFIG_DRM_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-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > new file mode 100644
> > > index 000000000000..c4d680362583
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > @@ -0,0 +1,1496 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright 2025 LONTIUM
> > > + * This program is free software; you can redistribute it and/or modify it
> > > + * under the terms of the GNU General Public License as published by the
> > > + * Free Software Foundation.
> > > + *
> > > + * This program is distributed in the hope that it will be useful, but
> > > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> > > + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> > > + * for more details.
> >
> > Drop the licence text, you have SPDX header for it.
> >
> I will fix,thanks.
>
> > > + */
> > > +#include <linux/firmware.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/module.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/of_graph.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/regmap.h>
> > > +#include <linux/regulator/consumer.h>
> > > +#include <drm/drm_modes.h>
> > > +#include <sound/hdmi-codec.h>
> > > +#include <drm/drm_atomic_helper.h>
> > > +#include <drm/drm_bridge.h>
> > > +#include <drm/drm_edid.h>
> > > +#include <drm/drm_mipi_dsi.h>
> > > +#include <drm/drm_of.h>
> > > +#include <drm/drm_print.h>
> > > +#include <drm/drm_probe_helper.h>
> > > +#include <linux/pm_runtime.h>
> >
> > Sort the headers
> >
> I will fix,thanks.
>
> > > +
> > > +#define FW_SIZE (64 * 1024)
> > > +#define LT_PAGE_SIZE 256
> > > +#define FW_FILE "LT9611C.bin"
> >
> > Please land this firmware to the linux-firmware repository.
> >
>
> The LT9611C has built-in firmware, and when the chip is running, it
> uses the internal firmware.
> The firmware needs to be updated only when the customer encounters
> issues during the debugging phase due to changes in hardware design or
> parameters.
> When the customer needs to update the firmware, they will apply to our
> company for a customized firmware.
> They will place this firmware under /lib/firmware, and then reboot the
> platform. After that, the driver will update the firmware.
> So I think there is no need to upload the firmware.
Then please make firmware updates opt-in, requiring some user action.
I'd suggest first getting the simplfified version of the driver in
(without fw update capabilities) and then adding FW upgrades in as a
separate patch.
>
> > > +#define NOT_UPGRADE 0
> > > +#define UPGRADE 1
> >
> > You know, C99 has 'bool' type.
> >
> I will fix,thanks.
>
> > > +
> > > +struct lt9611c {
> > > + 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 work_struct work;
> > > + struct device_node *dsi0_node;
> > > + struct device_node *dsi1_node;
> > > + struct mipi_dsi_device *dsi0;
> > > + struct mipi_dsi_device *dsi1;
> > > + struct platform_device *audio_pdev;
> > > + struct gpio_desc *reset_gpio;
> > > + struct regulator_bulk_data supplies[2];
> > > + struct device *codec_dev;
> > > + struct task_struct *kthread;
> > > + struct task_struct *test_kthread;
> > > + hdmi_codec_plugged_cb plugged_cb;
> > > + const struct firmware *fw;
> > > + u64 fw_version;
> > > + u8 *edid_buf;
> > > + int edid_len;
> > > + bool edid_valid;
> > > + u8 fw_crc;
> > > +
> > > + bool work_inited;
> > > + bool bridge_added;
> > > + bool regulators_enabled;
> > > + bool hdmi_connected;
> > > + enum drm_connector_status audio_status;
> > > +};
> > > +
> > > +static const struct regmap_range chip_ranges[] = {
> > > + { .range_min = 0, .range_max = 0xff },
> > > +};
> > > +
> > > +static const struct regmap_access_table chip_table = {
> > > + .yes_ranges = chip_ranges,
> > > + .n_yes_ranges = ARRAY_SIZE(chip_ranges),
> > > +};
> > > +
> > > +static const struct regmap_config lt9611c_regmap_config = {
> > > + .reg_bits = 8,
> >
> > It's 16
> >
> I will check this issue. thanks
>
> > > + .val_bits = 8,
> > > + .volatile_table = &chip_table,
> > > + .cache_type = REGCACHE_NONE,
> > > +};
> > > +
> > > +struct crc_info {
> > > + u8 width;
> > > + u32 poly;
> > > + u32 crc_init;
> > > + u32 xor_out;
> > > + bool refin;
> > > + bool refout;
> > > +};
> > > +
> > > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c);
> > > +
> > > +static unsigned int bits_reverse(u32 in_val, u8 bits)
> > > +{
> > > + u32 out_val = 0;
> > > + u8 i;
> > > +
> > > + for (i = 0; i < bits; i++) {
> > > + if (in_val & (1 << i))
> > > + out_val |= 1 << (bits - 1 - i);
> > > + }
> > > +
> > > + return out_val;
> > > +}
> > > +
> > > +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
> >
> > Use library functions for that.
> >
>
> I'm not sure whether the algorithms in the llibrary functions are
> consistent with those designed in our chip.
> If either of them changes, it will cause the firmware updated to the
> chip to fail to run.
CRC polynoms don't change that easily
> I think it's safer to implement it using our own code.
No, it's not.
> I'll check it.
>
> > > +{
> > > + u8 width = type.width;
> > > + u32 poly = type.poly;
> > > + u32 crc = type.crc_init;
> > > + u32 xorout = type.xor_out;
> > > + bool refin = type.refin;
> > > + bool refout = type.refout;
> > > + u8 n;
> > > + u32 bits;
> > > + u32 data;
> > > + u8 i;
> > > +
> > > + n = (width < 8) ? 0 : (width - 8);
> > > + crc = (width < 8) ? (crc << (8 - width)) : crc;
> > > + bits = (width < 8) ? 0x80 : (1 << (width - 1));
> > > + poly = (width < 8) ? (poly << (8 - width)) : poly;
> > > + while (buf_len--) {
> > > + data = *(buf++);
> > > + if (refin)
> > > + data = bits_reverse(data, 8);
> > > + crc ^= (data << n);
> > > + for (i = 0; i < 8; i++) {
> > > + if (crc & bits)
> > > + crc = (crc << 1) ^ poly;
> > > + else
> > > + crc = crc << 1;
> > > + }
> > > + }
> > > + crc = (width < 8) ? (crc >> (8 - width)) : crc;
> > > + if (refout)
> > > + crc = bits_reverse(crc, width);
> > > + crc ^= xorout;
> > > +
> > > + return (crc & ((2 << (width - 1)) - 1));
> > > +}
> > > +
> > > +static u8 calculate_crc(struct lt9611c *lt9611c)
> > > +{
> > > + struct crc_info type = {
> > > + .width = 8,
> > > + .poly = 0x31,
> > > + .crc_init = 0,
> > > + .xor_out = 0,
> > > + .refout = false,
> > > + .refin = false,
> > > + };
> > > + const u8 *upgrade_data;
> > > + u64 len;
> > > + u64 crc_size = FW_SIZE - 1;
> > > + u8 default_val = 0xFF;
> > > +
> > > + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
> > > + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
> > > + return 0;
> > > + }
> > > +
> > > + upgrade_data = lt9611c->fw->data;
> > > + len = lt9611c->fw->size;
> > > +
> > > + type.crc_init = get_crc(type, upgrade_data, len);
> > > +
> > > + crc_size -= len;
> > > + while (crc_size--)
> > > + type.crc_init = get_crc(type, &default_val, 1);
> > > +
> > > + return type.crc_init;
> > > +}
> > > +
> > > +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret = 0;
> > > +
> > > + ret = regmap_write(lt9611c->regmap, reg, val);
> > > + if (ret < 0) {
> > > + dev_err(dev,
> > > + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > > + lt9611c->client->addr, reg, ret);
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
> >
> > Drop these two wrappers, they provide no extra functionality.
> >
>
> I will consider fixing this issue. thanks.
>
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret = 0;
> > > + unsigned int tmp;
> > > +
> > > + if (!val)
> > > + return -EINVAL;
> > > +
> > > + ret = regmap_read(lt9611c->regmap, reg, &tmp);
> > > + if (ret < 0) {
> > > + dev_err(dev,
> > > + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > > + lt9611c->client->addr, reg, ret);
> > > +
> > > + return ret;
> > > + }
> > > +
> > > + *val = (u8)tmp;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> > > + unsigned int param_count, u8 *return_buffer,
> > > + unsigned int return_count)
> > > +{
> > > + int count, i;
> > > + u8 temp;
> > > +
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0xDE, 0x01);
> >
> > - lowercase all hex values
>
> i will fix , thanks.
>
> > - use paged writes as implemented for LT9611 and LT9611UXC
> >
> Don't understand.
Use 16-bit addressing as done by those two drivers. This way 0xff
becomes a page switch.
>
> > > +
> > > + count = 0;
> > > + do {
> > > + i2c_read_byte(lt9611c, 0xAE, &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;
> > > +
> > > + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
> > > + }
> > > +
> > > + i2c_write_byte(lt9611c, 0xDE, 0x02);
> > > +
> > > + count = 0;
> > > + do {
> > > + i2c_read_byte(lt9611c, 0xAE, &temp);
> > > + usleep_range(1000, 2000);
> > > + count++;
> > > + } while (count < 100 && temp != 0x02);
> > > +
> > > + if (temp != 0x02)
> > > + return -2;
> > > +
> > > + for (i = 0; i < return_count; i++)
> > > + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + /* ensure filesystem ready */
> > > + msleep(3000);
> >
> > No. If the firmware is necessary and it's not ready, return
> > -EPROBE_DEFER.
> >
> The firmware is unnecessary . This part of the code is for customers
> who need to upgrade the chip firmware.
>
> Due to the different designs of the platform, the firmware used by
> each customer may be different.
Well... That's a very bad way to go. We have had this issue with
LT9611UXC at one of my previous jobs. Our customers have had various
kinds of issues because of the wrong firmware.
Please provide some reference, which works in a DSI-to-HDMI case and
make it _tunable_ rather than requiring to replace the firmware
completely.
>
> Therefore, when they need to update the firmware, they only need to
> compile the firmware into the /lib/firmware directory during the
> compilation
> process, and then burn the image into the platform.
>
> Once reboot platform, the firmware upgrade can be automatically completed.
The firmware upgrade must be triggered by user, unless the FW is
completely empty.
>
> When there is no need to upgrade the firmware, this part of the code
> will not affect the operation of the driver.
>
> > > + ret = request_firmware(<9611c->fw, FW_FILE, dev);
> > > + if (ret) {
> > > + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> > > + return ret;
> > > + }
> > > +
> > > + if (lt9611c->fw->size > FW_SIZE - 1) {
> > > + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
> > > + lt9611c->fw = NULL;
> > > + return -EINVAL;
> > > + }
> > > +
> > > + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
> > > +
> > > + lt9611c->fw_crc = calculate_crc(lt9611c);
> > > +
> > > + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
> >
> > No spamming with the unnecessary info. If you want, print the version
> > of the firmware.
> >
> i will fix, thanks
>
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > > + //fifo_rst_n
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > > +
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0x5E, 0xC1);
> > > + i2c_write_byte(lt9611c, 0x58, 0x00);
> > > + i2c_write_byte(lt9611c, 0x59, 0x50);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x10);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > +}
> > > +
> > > +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0x5e, 0x5f);
> > > + i2c_write_byte(lt9611c, 0x5a, 0x20);
> > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > > + i2c_write_byte(lt9611c, 0x5a, 0x10);
> > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > +}
> > > +
> > > +static void lt9611c_wren(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0x5a, 0x04);
> > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > +}
> > > +
> > > +static void lt9611c_wrdi(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x08);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > +}
> > > +
> > > +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > + u8 flash_crc;
> > > +
> > > + if (!lt9611c)
> > > + return -EINVAL;
> >
> > How can it be NULL here?
> >
> i will fix, thanks
>
> > > +
> > > + lt9611c_config_parameters(lt9611c);
> > > + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
> > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > +
> > > + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
> > > + if (ret < 0) {
> > > + dev_err(dev, "failed to read flash crc\n");
> > > + return ret;
> > > + }
> > > +
> > > + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
> > > + flash_crc, lt9611c->fw_crc);
> >
> > dev_dbg()
> >
> i will fix, thanks
>
> > > +
> > > + lt9611c_wrdi(lt9611c);
> > > +
> > > + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
> > > +}
> > > +
> > > +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + //fifo_rst_n
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > > +
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0x5e, 0x40);
> > > + i2c_write_byte(lt9611c, 0x56, 0x05);
> > > + i2c_write_byte(lt9611c, 0x55, 0x25);
> > > + i2c_write_byte(lt9611c, 0x55, 0x01);
> > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > +
> > > + ret = i2c_read_byte(lt9611c, 0x5f, status);
> > > + if (ret < 0)
> > > + dev_err(dev, "failed to read flash register status\n");
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static void lt9611c_block_erase(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + u32 i = 0;
> > > + u8 flash_status = 0;
> > > + u8 block_num = 0x00;
> > > + u32 flash_addr = 0x00;
> > > +
> > > + for (block_num = 0; block_num < 2; block_num++) {
> > > + flash_addr = (block_num * 0x008000);
> > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x04);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
> > > + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
> > > + i2c_write_byte(lt9611c, 0x5D, flash_addr);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x01);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > + msleep(100);
> > > + i = 0;
> > > + while (1) {
> > > + read_flash_reg_status(lt9611c, &flash_status);
> > > + if ((flash_status & 0x01) == 0)
> > > + break;
> > > +
> > > + if (i > 50)
> > > + break;
> > > +
> > > + i++;
> > > + msleep(50);
> > > + }
> > > + }
> > > +
> > > + dev_info(dev, "erase flash done.\n");
> > > +}
> > > +
> > > +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0x51, 0x00);
> > > + i2c_write_byte(lt9611c, 0x55, 0xc0);
> > > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > +}
> > > +
> > > +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0x51, 0xff);
> > > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > +}
> > > +
> > > +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > > + i2c_write_byte(lt9611c, 0x5A, 0x30);
> > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > +}
> > > +
> > > +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > + int page = 0, num = 0, i = 0;
> > > + const u8 *data;
> > > + u64 size, index;
> > > + u8 value;
> > > +
> > > + data = lt9611c->fw->data;
> > > + size = lt9611c->fw->size;
> > > +
> > > + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> > > +
> > > + if (page * LT_PAGE_SIZE > 64 * 1024) {
> > > + dev_err(dev, "firmware size out of range\n");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
> >
> >
> > dev_dbg()
> >
> i will fix, thanks
>
> > > +
> > > + for (num = 0; num < page; num++) {
> > > + lt9611c_data_to_sram(lt9611c);
> > > +
> > > + for (i = 0; i < LT_PAGE_SIZE; i++) {
> > > + index = num * LT_PAGE_SIZE + i;
> > > + value = (index < size) ? data[index] : 0xFF;
> > > +
> > > + ret = i2c_write_byte(lt9611c, 0x59, value);
> > > + if (ret < 0) {
> > > + dev_err(dev, "write error at page %u, index %u\n", num, i);
> > > + return ret;
> > > + }
> > > + }
> > > +
> > > + lt9611c_wren(lt9611c);
> > > + lt9611c_sram_to_flash(lt9611c, addr);
> > > +
> > > + addr += LT_PAGE_SIZE;
> > > + }
> > > +
> > > + lt9611c_wrdi(lt9611c);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > + u8 crc;
> > > +
> > > + crc = lt9611c->fw_crc;
> > > + lt9611c_crc_to_sram(lt9611c);
> > > + ret = i2c_write_byte(lt9611c, 0x59, crc);
> > > + if (ret < 0) {
> > > + dev_err(dev, "failed to write CRC\n");
> > > + return -1;
> > > + }
> > > +
> > > + lt9611c_wren(lt9611c);
> > > + lt9611c_sram_to_flash(lt9611c, addr);
> > > + lt9611c_wrdi(lt9611c);
> > > +
> > > + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
> >
> > dev_dbg
> >
> i will fix, thanks
>
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
> >
> > dev_dbg
> >
> i will fix, thanks
>
> > > +
> > > + lt9611c_config_parameters(lt9611c);
> > > + lt9611c_block_erase(lt9611c);
> > > +
> > > + ret = lt9611c_write_data(lt9611c, 0);
> > > + if (ret < 0) {
> > > + dev_err(dev, "Failed to write firmware data\n");
> > > + return ret;
> > > + }
> > > +
> > > + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
> > > + if (ret < 0) {
> > > + dev_err(dev, "Failed to write firmware CRC\n");
> > > + return ret;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + u8 crc_result;
> > > +
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > + i2c_read_byte(lt9611c, 0x21, &crc_result);
> > > +
> > > + if (crc_result == lt9611c->fw_crc) {
> > > + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
> >
> > dev_dbg
> >
> i will fix, thanks
>
> > > + return 0;
> > > + }
> > > +
> > > + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
> > > + lt9611c->fw_crc, crc_result);
> > > + return -EIO;
> > > +}
> > > +
> > > +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
> > > +{
> > > + return container_of(bridge, struct lt9611c, bridge);
> > > +}
> > > +
> > > +static void lt9611c_lock(struct lt9611c *lt9611c)
> > > +{
> > > + mutex_lock(<9611c->ocm_lock);
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > +}
> > > +
> > > +static void lt9611c_unlock(struct lt9611c *lt9611c)
> > > +{
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0xee, 0x00);
> > > + mutex_unlock(<9611c->ocm_lock);
> > > +}
> > > +
> > > +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
> > > +{
> > > + struct lt9611c *lt9611c = dev_id;
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > + u8 irq_status;
> > > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > > + u8 data[5];
> > > +
> > > + mutex_lock(<9611c->ocm_lock);
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_read_byte(lt9611c, 0x84, &irq_status);
> > > +
> > > + if (!(irq_status & BIT(0))) {
> > > + mutex_unlock(<9611c->ocm_lock);
> > > + return IRQ_HANDLED;
> > > + }
> > > + dev_info(dev, "HPD interrupt triggered.\n");
> >
> > Nice joke. dev_dbg().
> >
> i will fix, thanks
>
> > > +
> > > + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
> > > + usleep_range(10000, 12000);
> >
> > Why?
> >
> Our chip design specification requires that this be done when clearing
> the interrupt.
Add a comment.
>
> > > + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
> > > +
> > > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > > + if (ret) {
> > > + dev_err(dev, "failed to read HPD status\n");
> > > + } else {
> > > + lt9611c->hdmi_connected = (data[4] == 0x02);
> > > + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
> >
> > dev_dbg()
> >
> i will fix, thanks
>
> > > + }
> > > +
> > > + lt9611c->audio_status = lt9611c->hdmi_connected ?
> > > + connector_status_connected :
> > > + connector_status_disconnected;
> >
> > What is it being used for? Why do you need separate status for audio?
> >
> Used to update the connection status of the audio.
> The separate status indicators make it clearer for the readers.
>
> > > +
> > > + schedule_work(<9611c->work);
> > > +
> > > + mutex_unlock(<9611c->ocm_lock);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > > +
> > > +static void lt9611c_hpd_work(struct work_struct *work)
> > > +{
> > > + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
> > > + bool connected;
> > > +
> > > + mutex_lock(<9611c->ocm_lock);
> > > + connected = lt9611c->hdmi_connected;
> > > + mutex_unlock(<9611c->ocm_lock);
> > > +
> > > + drm_bridge_hpd_notify(<9611c->bridge,
> > > + connected ?
> > > + connector_status_connected :
> > > + connector_status_disconnected);
> >
> > Incorrect indentation.
> >
> ? The checkpatch.pl did not detect it.
use --strict.
>
> > > +
> > > + lt9611c_audio_update_connector_status(lt9611c);
> > > +}
> > > +
> > > +static void lt9611c_reset(struct lt9611c *lt9611c)
> > > +{
> > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > + msleep(20);
> > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > + msleep(20);
> > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > + msleep(400);
> > > +}
> > > +
> > > +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + lt9611c->supplies[0].supply = "vcc";
> > > + lt9611c->supplies[1].supply = "vdd";
> > > +
> > > + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
> > > +{
> > > + int ret;
> > > +
> > > + ret = regulator_enable(lt9611c->supplies[0].consumer);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + usleep_range(5000, 10000);
> > > +
> > > + ret = regulator_enable(lt9611c->supplies[1].consumer);
> > > + if (ret < 0) {
> > > + regulator_disable(lt9611c->supplies[0].consumer);
> > > + return ret;
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
> > > +{
> > > + int ret;
> > > +
> > > + ret = regulator_disable(lt9611c->supplies[0].consumer);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + usleep_range(5000, 10000);
> > > +
> > > + ret = regulator_disable(lt9611c->supplies[1].consumer);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
> > > + struct device_node *dsi_node)
> > > +{
> > > + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
> > > + struct mipi_dsi_device *dsi;
> > > + struct mipi_dsi_host *host;
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + host = of_find_mipi_dsi_host_by_node(dsi_node);
> > > + if (!host)
> > > + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
> > > +
> > > + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> > > + if (IS_ERR(dsi)) {
> > > + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
> > > + struct drm_encoder *encoder,
> > > + enum drm_bridge_attach_flags flags)
> > > +{
> > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > +
> > > + return drm_bridge_attach(encoder, lt9611c->next_bridge,
> > > + bridge, flags);
> > > +}
> > > +
> > > +static enum drm_mode_status lt9611c_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)
> >
> > Use .hdmi_tmds_char_rate_valid() for that.
> >
> I will check and test, thanks
>
> > > + return MODE_OK;
> > > + else
> > > + return MODE_BAD;
> > > +}
> > > +
> > > +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
> > > + const struct drm_display_mode *mode,
> > > + const struct drm_display_mode *adj_mode)
> >
> > - Wrong indentation
> will fix, thanks
>
> > - mode_set callback is deprecated and should not be used for new
> > drivers.
> >
> I found that kernel 6.17 is still in use mode_set callback.
Check the documentation in drm_bridge_funcs.
>
> > > +{
> > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > + struct device *dev = lt9611c->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;
> > > +
> >
> > > + 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, "Out video info:\n");
> > > + dev_info(dev,
> > > + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
> > > + h_total, hactive, hsync_len, hfront_porch, hback_porch);
> > > + dev_info(dev,
> > > + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
> > > + v_total, vactive, vsync_len, vfront_porch, vback_porch);
> >
> >
> > Fix indentation
> The indentation issue was not detected by checkpatch.pl.
The indentation issue is detected by the brain and eye.
>
> > Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
> > spammy.
> >
> i will fix, thanks
>
> > > +
> > > + 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(<9611c->ocm_lock);
> > > + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
> > > + if (ret)
> > > + dev_err(dev, "video set failed\n");
> > > + mutex_unlock(<9611c->ocm_lock);
> > > +}
> > > +
> > > +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
> > > + struct drm_connector *connector)
> > > +{
> > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > + bool connected = false;
> > > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > > + u8 data[5];
> > > +
> > > + mutex_lock(<9611c->ocm_lock);
> > > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > > + if (ret) {
> > > + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
> > > + ret);
> > > + } else {
> > > + connected = (data[4] == 0x02);
> > > + }
> >
> > THere is no need to put single-line statements in brackets. Drop those.
> >
> yes, i will fix, thks
>
> > > +
> > > + lt9611c->hdmi_connected = connected;
> > > +
> > > + if (lt9611c->hdmi_connected)
> > > + lt9611c->audio_status = connector_status_connected;
> > > + else
> > > + lt9611c->audio_status = connector_status_disconnected;
> > > +
> > > + mutex_unlock(<9611c->ocm_lock);
> > > +
> > > + return connected ? connector_status_connected :
> > > + connector_status_disconnected;
> > > +}
> > > +
> > > +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret, i, bytes_to_copy, offset = 0;
> > > + u8 packets_num;
> > > + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> > > + u8 return_edid_data[37];
> > > + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> > > + u8 return_edid_byte_num[6];
> > > +
> > > + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> > > + if (ret) {
> > > + dev_err(dev, "Failed to read EDID byte number\n");
> > > + lt9611c->edid_valid = false;
> > > + return ret;
> > > + }
> > > +
> > > + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> > > +
> > > + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> > > + lt9611c->edid_len : 0)) {
> > > + kfree(lt9611c->edid_buf);
> > > + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> > > + if (!lt9611c->edid_buf) {
> > > + dev_err(dev, "Failed to allocate EDID buffer\n");
> > > + lt9611c->edid_len = 0;
> > > + lt9611c->edid_valid = false;
> > > + return -ENOMEM;
> > > + }
> > > + }
> > > +
> > > + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> > > + (lt9611c->edid_len / 32);
> > > + for (i = 0; i < packets_num; i++) {
> > > + read_edid_data_cmd[4] = (u8)i;
> > > + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> > > + if (ret) {
> > > + dev_err(dev, "Failed to read EDID packet %d\n", i);
> > > + lt9611c->edid_valid = false;
> > > + return -EIO;
> > > + }
> > > + offset = i * 32;
> > > + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> > > + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
> >
> > Don't store EDID in the long-term structures. Read it on demand.
> >
> I will think about this issue.
>
> > > + }
> > > +
> > > + lt9611c->edid_valid = true;
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> > > +{
> > > + struct lt9611c *lt9611c = data;
> > > + struct device *dev = lt9611c->dev;
> > > + unsigned int total_blocks;
> > > + int ret;
> > > +
> > > + if (len > 128)
> > > + return -EINVAL;
> > > +
> > > + guard(mutex)(<9611c->ocm_lock);
> > > + if (block == 0 || !lt9611c->edid_valid) {
> > > + ret = lt9611c_read_edid(lt9611c);
> > > + if (ret) {
> > > + dev_err(dev, "EDID read failed\n");
> > > + return ret;
> > > + }
> > > + }
> > > +
> > > + total_blocks = lt9611c->edid_len / 128;
> > > + if (!total_blocks) {
> > > + dev_err(dev, "No valid EDID blocks\n");
> > > + return -EIO;
> > > + }
> > > +
> > > + if (block >= total_blocks) {
> > > + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> > > + block, total_blocks);
> > > + return -EINVAL;
> > > + }
> > > +
> > > + memcpy(buf, lt9611c->edid_buf + block * 128, len);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> > > + struct drm_connector *connector)
> > > +{
> > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > +
> > > + usleep_range(10000, 20000);
> >
> > Why?
> >
> Delay for a while to ensure that EDID is ready.
Your other chip had interrupt status to note that EDID is ready. I hope
you have that one too. Blindly calling usleep_range() is a bad idea.
>
> > > + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> > > +}
> > > +
> > > +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> > > + .attach = lt9611c_bridge_attach,
> > > + .mode_valid = lt9611c_bridge_mode_valid,
> > > + .mode_set = lt9611c_bridge_mode_set,
> > > + .detect = lt9611c_bridge_detect,
> > > + .edid_read = lt9611c_bridge_edid_read,
> > > +};
> > > +
> > > +static int lt9611c_parse_dt(struct device *dev,
> > > + struct lt9611c *lt9611c)
> > > +{
> > > + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
> > > + if (!lt9611c->dsi0_node) {
> > > + dev_err(dev, "failed to get remote node for primary dsi\n");
> > > + return -ENODEV;
> > > + }
> > > +
> > > + lt9611c->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, <9611c->next_bridge);
> > > +}
> > > +
> > > +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > +
> > > + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > > + if (IS_ERR(lt9611c->reset_gpio)) {
> > > + dev_err(dev, "failed to acquire reset gpio\n");
> > > + return PTR_ERR(lt9611c->reset_gpio);
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
> > > +{
> > > + u8 val;
> > > + u64 ver = 0;
> > > +
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > +
> > > + i2c_read_byte(lt9611c, 0x80, &val);
> > > + ver = val;
> > > +
> > > + i2c_read_byte(lt9611c, 0x81, &val);
> > > + ver = (ver << 8) | val;
> > > +
> > > + *version = ver;
> > > +}
> > > +
> > > +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + u8 val = 0;
> > > +
> > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > + i2c_write_byte(lt9611c, 0xff, 0xe1);
> > > +
> > > + i2c_read_byte(lt9611c, 0x00, &val);
> > > + if (val != 0x23)
> > > + return -ENODEV;
> > > +
> > > + i2c_read_byte(lt9611c, 0x01, &val);
> > > + if (val != 0x06)
> > > + return -ENODEV;
> > > +
> > > + dev_info(dev, "ChipId = 0x2306\n");
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> > > + struct hdmi_codec_daifmt *fmt,
> > > + struct hdmi_codec_params *hparms)
> > > +{
> > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > +
> > > + dev_info(lt9611c->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;
> > > + }
> >
> > Does that add anything on top of the limitations of hdmi-codec.c?
> >
> The parameters supported in the hdmi-codec.c may not be supported by
> my chip. Therefore, we can exclude the parameters that are not
> supported by the chip.
Are they?
>
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> > > +{
> > > +}
> > > +
> > > +static int lt9611c_audio_startup(struct device *dev, void *data)
> > > +{
> > > + return 0;
> > > +}
> > > +
> > > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> > > +{
> > > + enum drm_connector_status status;
> > > +
> > > + status = lt9611c->audio_status;
> > > + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> > > + lt9611c->plugged_cb(lt9611c->codec_dev,
> > > + status == connector_status_connected);
> > > +}
> > > +
> > > +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> > > + void *data,
> > > + hdmi_codec_plugged_cb fn,
> > > + struct device *codec_dev)
> > > +{
> > > + struct lt9611c *lt9611c = data;
> > > +
> > > + lt9611c->plugged_cb = fn;
> > > + lt9611c->codec_dev = codec_dev;
> > > + lt9611c_audio_update_connector_status(lt9611c);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> > > + .hw_params = lt9611c_hdmi_hw_params,
> > > + .audio_shutdown = lt9611c_audio_shutdown,
> > > + .audio_startup = lt9611c_audio_startup,
> > > + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> > > +};
> >
> > No, we have HDMI audio helpers for that. Drop this and use the helpers
> > instead.
> >
> ??? I don't understand.
See <drm/display/drm_hdmi_audio_helper.h> and
https://lore.kernel.org/dri-devel/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com/
>
> > > +
> > > +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
> > > +{
> > > + struct hdmi_codec_pdata codec_data = {
> > > + .ops = <9611c_codec_ops,
> > > + .max_i2s_channels = 2,
> > > + .i2s = 1,
> > > + .data = lt9611c,
> > > + };
> > > +
> > > + lt9611c->audio_pdev =
> > > + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
> > > + PLATFORM_DEVID_AUTO,
> > > + &codec_data, sizeof(codec_data));
> > > +
> > > + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
> > > +}
> > > +
> > > +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
> > > +{
> > > + if (lt9611c->audio_pdev) {
> > > + platform_device_unregister(lt9611c->audio_pdev);
> > > + lt9611c->audio_pdev = NULL;
> > > + }
> > > +}
> > > +
> > > +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > + int ret;
> > > +
> > > + lt9611c_lock(lt9611c);
> > > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > > + if (ret < 0) {
> > > + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
> > > + goto out;
> > > + }
> > > +
> > > + ret = lt9611c_firmware_upgrade(lt9611c);
> > > + if (ret < 0) {
> > > + dev_err(dev, "upgrade failure\n");
> > > + goto out;
> > > + }
> > > + lt9611c_reset(lt9611c);
> > > + ret = lt9611c_upgrade_result(lt9611c);
> > > + if (ret < 0)
> > > + goto out;
> > > +
> > > +out:
> > > + lt9611c_unlock(lt9611c);
> > > + lt9611c_reset(lt9611c);
> > > + if (lt9611c->fw) {
> > > + release_firmware(lt9611c->fw);
> > > + lt9611c->fw = NULL;
> > > + }
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > + int ret;
> > > +
> > > + ret = lt9611c_firmware_update_store(lt9611c);
> >
> > Inline
> >
> i will fix, thks
>
> > > + if (ret < 0)
> > > + return ret;
> > > + return len;
> > > +}
> > > +
> > > +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
> > > +{
> > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > +
> > > + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
> > > +}
> > > +
> > > +static DEVICE_ATTR_RW(lt9611c_firmware);
> > > +
> > > +static struct attribute *lt9611c_attrs[] = {
> > > + &dev_attr_lt9611c_firmware.attr,
> > > + NULL,
> > > +};
> > > +
> > > +static const struct attribute_group lt9611c_attr_group = {
> > > + .attrs = lt9611c_attrs,
> > > +};
> > > +
> > > +static const struct attribute_group *lt9611c_attr_groups[] = {
> > > + <9611c_attr_group,
> > > + NULL,
> > > +};
> > > +
> > > +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> > > +{
> > > + struct device *dev = lt9611c->dev;
> > > +
> > > + if (lt9611c->work_inited) {
> > > + cancel_work_sync(<9611c->work);
> > > + lt9611c->work_inited = false;
> > > + dev_err(dev, "work cancelled\n");
> >
> > Why???
> >
> ?? I don't understand.
Why do you need to be so spammy?
>
> > > + }
> > > +
> > > + if (lt9611c->bridge_added) {
> > > + drm_bridge_remove(<9611c->bridge);
> > > + lt9611c->bridge_added = false;
> > > + dev_err(dev, "DRM bridge removed\n");
> > > + }
> > > +
> > > + if (lt9611c->regulators_enabled) {
> > > + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > > + lt9611c->regulators_enabled = false;
> > > + dev_err(dev, "regulators disabled\n");
> > > + }
> > > +
> > > + if (lt9611c->audio_pdev)
> > > + lt9611c_audio_exit(lt9611c);
> > > +
> > > + if (lt9611c->fw) {
> >
> > You definitely don't need firmware when the bridge is up and running.
> >
> The previous text has already described the working logic of the firmware.
It's another topic: you are storing the firmware in memory while the
driver is bound. It's not necessary. You can release it after updating
it on the chip.
>
> > > + release_firmware(lt9611c->fw);
> > > + lt9611c->fw = NULL;
> > > + dev_err(dev, "firmware released\n");
> > > + }
> > > +
> > > + if (lt9611c->dsi0_node) {
> > > + of_node_put(lt9611c->dsi0_node);
> > > + lt9611c->dsi0_node = NULL;
> > > + dev_err(dev, "dsi0 node released\n");
> > > + }
> > > +
> > > + if (lt9611c->dsi1_node) {
> > > + of_node_put(lt9611c->dsi1_node);
> > > + lt9611c->dsi1_node = NULL;
> > > + dev_err(dev, "dsi1 node released\n");
> > > + }
> > > +}
> > > +
> > > +static int lt9611c_main(void *data)
> > > +{
> > > + struct lt9611c *lt9611c = data;
> > > + struct device *dev = lt9611c->dev;
> > > + struct i2c_client *client = lt9611c->client;
> > > + int ret;
> > > +
> > > + lt9611c->work_inited = false;
> > > + lt9611c->bridge_added = false;
> > > + lt9611c->regulators_enabled = false;
> > > +
> > > + ret = lt9611c_parse_dt(dev, lt9611c);
> > > + if (ret) {
> > > + dev_err(dev, "failed to parse device tree\n");
> > > + return ret;
> > > + }
> > > +
> > > + ret = lt9611c_gpio_init(lt9611c);
> > > + if (ret < 0)
> > > + goto err_cleanup;
> > > +
> > > + ret = lt9611c_regulator_init(lt9611c);
> > > + if (ret < 0)
> > > + goto err_cleanup;
> > > +
> > > + ret = lt9611c_regulator_enable(lt9611c);
> > > + if (ret)
> > > + goto err_cleanup;
> > > +
> > > + lt9611c->regulators_enabled = true;
> > > +
> > > + lt9611c_reset(lt9611c);
> > > +
> > > + ret = lt9611c_read_chipid(lt9611c);
> > > + if (ret < 0) {
> > > + dev_err(dev, "failed to read chip id.\n");
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + lt9611c_lock(lt9611c);
> > > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > > +
> > > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > > + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
> > > + dev_info(dev, "firmware upgrade needed\n");
> > > +
> > > + ret = lt9611c_firmware_upgrade(lt9611c);
> > > + if (ret < 0) {
> > > + dev_err(dev, "firmware upgrade failed\n");
> > > + lt9611c_unlock(lt9611c);
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + lt9611c_reset(lt9611c);
> > > + ret = lt9611c_upgrade_result(lt9611c);
> > > + if (ret < 0) {
> > > + lt9611c_unlock(lt9611c);
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > > + lt9611c_unlock(lt9611c);
> > > +
> > > + } else {
> > > + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
> > > + lt9611c_unlock(lt9611c);
> > > + }
> > > +
> > > + if (lt9611c->fw) {
> > > + release_firmware(lt9611c->fw);
> > > + lt9611c->fw = NULL;
> > > + }
> > > + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
> > > +
> > > + INIT_WORK(<9611c->work, lt9611c_hpd_work);
> > > + lt9611c->work_inited = true;
> > > +
> > > + if (!client->irq) {
> > > + dev_err(dev, "failed to get INTP IRQ\n");
> > > + ret = -ENODEV;
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> > > + lt9611c_irq_thread_handler,
> > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
> > > + IRQF_NO_AUTOEN,
> > > + "lt9611c", lt9611c);
> > > + if (ret) {
> > > + dev_err(dev, "failed to request irq\n");
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + lt9611c->bridge.funcs = <9611c_bridge_funcs;
> > > + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
> > > + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> > > + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> > > +
> > > + drm_bridge_add(<9611c->bridge);
> > > + lt9611c->bridge_added = true;
> >
> > No unnecessary flags, please. Implement proper cleanup path, unwinding
> > resources one by one.
> >
> I will consider this issue. thks
>
> > > +
> > > + /* Attach primary DSI */
> > > + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
> > > + if (IS_ERR(lt9611c->dsi0)) {
> > > + ret = PTR_ERR(lt9611c->dsi0);
> > > + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + /* Attach secondary DSI, if specified */
> > > + if (lt9611c->dsi1_node) {
> > > + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
> > > + if (IS_ERR(lt9611c->dsi1)) {
> > > + ret = PTR_ERR(lt9611c->dsi1);
> > > + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
> > > + goto err_cleanup;
> > > + }
> > > + }
> > > +
> > > + lt9611c->audio_status = connector_status_disconnected;
> > > +
> > > + ret = lt9611c_audio_init(dev, lt9611c);
> > > + if (ret < 0) {
> > > + dev_err(dev, "audio init failed\n");
> > > + goto err_cleanup;
> > > + }
> > > +
> > > + lt9611c_reset(lt9611c);
> > > + enable_irq(lt9611c->client->irq);
> > > +
> > > + return 0;
> > > +
> > > +err_cleanup:
> > > + lt9611c_cleanup_resources(lt9611c);
> > > + return ret;
> > > +}
> > > +
> > > +static int lt9611c_probe(struct i2c_client *client)
> > > +{
> > > + struct lt9611c *lt9611c;
> > > + struct device *dev = &client->dev;
> > > +
> > > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> > > + dev_err(dev, "device doesn't support I2C\n");
> > > + return -ENODEV;
> > > + }
> > > +
> > > + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
> >
> > devm_drm_bridge_alloc()
> >
> i will fix, thks
>
> > > + if (!lt9611c)
> > > + return -ENOMEM;
> > > +
> > > + lt9611c->dev = dev;
> > > + lt9611c->client = client;
> > > + mutex_init(<9611c->ocm_lock);
> > > +
> > > + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
> > > + if (IS_ERR(lt9611c->regmap)) {
> > > + dev_err(dev, "regmap i2c init failed\n");
> > > + return PTR_ERR(lt9611c->regmap);
> > > + }
> > > +
> > > + i2c_set_clientdata(client, lt9611c);
> > > +
> > > + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> >
> > Why do you need extra kthread for that???
> >
> > > + if (IS_ERR(lt9611c->kthread)) {
> > > + dev_err(dev, "Failed to create kernel thread\n");
> > > + return PTR_ERR(lt9611c->kthread);
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void lt9611c_remove(struct i2c_client *client)
> > > +{
> > > + struct lt9611c *lt9611c = i2c_get_clientdata(client);
> > > + struct device *dev = lt9611c->dev;
> > > +
> > > + kfree(lt9611c->edid_buf);
> > > + disable_irq(client->irq);
> > > + lt9611c_cleanup_resources(lt9611c);
> > > + mutex_destroy(<9611c->ocm_lock);
> > > + dev_info(dev, "remove driver\n");
> > > +}
> > > +
> > > +static int lt9611c_bridge_suspend(struct device *dev)
> > > +{
> > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > + int ret;
> > > +
> > > + dev_info(lt9611c->dev, "suspend\n");
> > > + disable_irq(lt9611c->client->irq);
> > > + ret = lt9611c_regulator_disable(lt9611c);
> > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int lt9611c_bridge_resume(struct device *dev)
> > > +{
> > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > + int ret;
> > > +
> > > + ret = lt9611c_regulator_enable(lt9611c);
> > > + lt9611c_reset(lt9611c);
> > > + enable_irq(lt9611c->client->irq);
> > > + dev_info(lt9611c->dev, "resume\n");
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
> > > + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
> > > + lt9611c_bridge_resume)
> > > +};
> > > +
> > > +static struct i2c_device_id lt9611c_id[] = {
> > > + { "lontium,lt9611c", 0 },
> > > + { /* sentinel */ }
> > > +};
> > > +
> > > +static const struct of_device_id lt9611c_match_table[] = {
> > > + { .compatible = "lontium,lt9611c" },
> >
> > Your schema also had lt9611uxd
> >
> i will fix, thks
>
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
> > > +
> > > +static struct i2c_driver lt9611c_driver = {
> > > + .driver = {
> > > + .name = "lt9611c",
> > > + .of_match_table = lt9611c_match_table,
> > > + .pm = <9611c_bridge_pm_ops,
> > > + .dev_groups = lt9611c_attr_groups,
> > > + },
> > > + .probe = lt9611c_probe,
> > > + .remove = lt9611c_remove,
> > > + .id_table = lt9611c_id,
> > > +};
> > > +module_i2c_driver(lt9611c_driver);
> > > +
> > > +MODULE_AUTHOR("syyang <syyang@lontium.com>");
> > > +MODULE_LICENSE("GPL v2");
> > > +
> > > +MODULE_FIRMWARE(FW_FILE);
> > > --
> > > 2.25.1
> > >
> >
> > --
> > With best wishes
> > Dmitry
>
> Dmitry, thank you very much
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-04 14:39 ` Dmitry Baryshkov
@ 2025-09-05 2:55 ` 杨孙运
2025-09-05 8:10 ` neil.armstrong
2025-09-05 14:10 ` Dmitry Baryshkov
0 siblings, 2 replies; 26+ messages in thread
From: 杨孙运 @ 2025-09-05 2:55 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rly
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
>
> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> > >
> > > On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> > > > The following changes are included:
> > > >
> > > > - Updated Kconfig and Makefile to include the new driver
> > > > - Implementation of the bridge driver at
> > > > drivers/gpu/drm/bridge/lontium-lt9611c.c
> > >
> > > This is really not interesting, it can be seen from the patch itself.
> > > Please read Documentation/process/submitting-patches.rst.
> > >
> > Sorry, I will study submitting-patches.rst.
> >
> > > Is it possible to toggle infoframes?
> >
> > sorry, I don't understand the meaning of this sentence. Please explain
> > it in detail.
>
> Is it possible to control InfoFrames being sent over the HDMI cable?
> Both contents and enabling/disabling.
>
Can be controlled via I2C
> >
> >
> > > Is there YUV 444 / 422 output support? YUV420? Interlaced?
> > >
> >
> > HDMI output support YUV 444, YUV422 , YUV420 and Interlaced.
>
> See corresponding properties of the DRM bridge to be set if that's
> supported.
>
You are right, I will research it.
> >
> >
> > > >
> > > > Signed-off-by: syyang <syyang@lontium.com>
> > > > ---
> > > > drivers/gpu/drm/bridge/Kconfig | 16 +
> > > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > > drivers/gpu/drm/bridge/lontium-lt9611c.c | 1496 ++++++++++++++++++++++
> > > > 3 files changed, 1513 insertions(+)
> > > > create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > >
> > > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > > > index b9e0ca85226a..f0df146ed2ce 100644
> > > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > > @@ -170,6 +170,22 @@ config DRM_LONTIUM_LT9611
> > > > HDMI signals
> > > > Please say Y if you have such hardware.
> > > >
> > > > +config DRM_LONTIUM_LT9611C
> > > > + tristate "Lontium LT9611C DSI/HDMI bridge"
> > > > + select SND_SOC_HDMI_CODEC if SND_SOC
> > > > + depends on OF
> > > > + select DRM_PANEL_BRIDGE
> > > > + select DRM_KMS_HELPER
> > > > + select DRM_MIPI_DSI
> > > > + select DRM_DISPLAY_HELPER
> > > > + select DRM_DISPLAY_HDMI_STATE_HELPER
> > >
> > > You are not using the HDMI_STATE_HELPER, are you?
> > >
> > I will fix,thanks.
> >
> > > > + select REGMAP_I2C
> > > > + help
> > > > + Driver for Lontium LT9611C DSI to HDMI bridge
> > > > + chip driver that converts dual DSI and I2S to
> > > > + HDMI signals
> > > > + Please say Y if you have such hardware.
> > > > +
> > > > config DRM_LONTIUM_LT9611UXC
> > > > tristate "Lontium LT9611UXC DSI/HDMI bridge"
> > > > select SND_SOC_HDMI_CODEC if SND_SOC
> > > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > > > index 245e8a27e3fc..ccfa9987aa4f 100644
> > > > --- a/drivers/gpu/drm/bridge/Makefile
> > > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > > @@ -15,6 +15,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> > > > +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o
> > > > obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> > > > obj-$(CONFIG_DRM_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-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > > new file mode 100644
> > > > index 000000000000..c4d680362583
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > > @@ -0,0 +1,1496 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Copyright 2025 LONTIUM
> > > > + * This program is free software; you can redistribute it and/or modify it
> > > > + * under the terms of the GNU General Public License as published by the
> > > > + * Free Software Foundation.
> > > > + *
> > > > + * This program is distributed in the hope that it will be useful, but
> > > > + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
> > > > + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> > > > + * for more details.
> > >
> > > Drop the licence text, you have SPDX header for it.
> > >
> > I will fix,thanks.
> >
> > > > + */
> > > > +#include <linux/firmware.h>
> > > > +#include <linux/gpio/consumer.h>
> > > > +#include <linux/i2c.h>
> > > > +#include <linux/interrupt.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/mutex.h>
> > > > +#include <linux/of_graph.h>
> > > > +#include <linux/platform_device.h>
> > > > +#include <linux/regmap.h>
> > > > +#include <linux/regulator/consumer.h>
> > > > +#include <drm/drm_modes.h>
> > > > +#include <sound/hdmi-codec.h>
> > > > +#include <drm/drm_atomic_helper.h>
> > > > +#include <drm/drm_bridge.h>
> > > > +#include <drm/drm_edid.h>
> > > > +#include <drm/drm_mipi_dsi.h>
> > > > +#include <drm/drm_of.h>
> > > > +#include <drm/drm_print.h>
> > > > +#include <drm/drm_probe_helper.h>
> > > > +#include <linux/pm_runtime.h>
> > >
> > > Sort the headers
> > >
> > I will fix,thanks.
> >
> > > > +
> > > > +#define FW_SIZE (64 * 1024)
> > > > +#define LT_PAGE_SIZE 256
> > > > +#define FW_FILE "LT9611C.bin"
> > >
> > > Please land this firmware to the linux-firmware repository.
> > >
> >
> > The LT9611C has built-in firmware, and when the chip is running, it
> > uses the internal firmware.
> > The firmware needs to be updated only when the customer encounters
> > issues during the debugging phase due to changes in hardware design or
> > parameters.
> > When the customer needs to update the firmware, they will apply to our
> > company for a customized firmware.
> > They will place this firmware under /lib/firmware, and then reboot the
> > platform. After that, the driver will update the firmware.
> > So I think there is no need to upload the firmware.
>
> Then please make firmware updates opt-in, requiring some user action.
> I'd suggest first getting the simplfified version of the driver in
> (without fw update capabilities) and then adding FW upgrades in as a
> separate patch.
>
I will research it.
> >
> > > > +#define NOT_UPGRADE 0
> > > > +#define UPGRADE 1
> > >
> > > You know, C99 has 'bool' type.
> > >
> > I will fix,thanks.
> >
> > > > +
> > > > +struct lt9611c {
> > > > + 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 work_struct work;
> > > > + struct device_node *dsi0_node;
> > > > + struct device_node *dsi1_node;
> > > > + struct mipi_dsi_device *dsi0;
> > > > + struct mipi_dsi_device *dsi1;
> > > > + struct platform_device *audio_pdev;
> > > > + struct gpio_desc *reset_gpio;
> > > > + struct regulator_bulk_data supplies[2];
> > > > + struct device *codec_dev;
> > > > + struct task_struct *kthread;
> > > > + struct task_struct *test_kthread;
> > > > + hdmi_codec_plugged_cb plugged_cb;
> > > > + const struct firmware *fw;
> > > > + u64 fw_version;
> > > > + u8 *edid_buf;
> > > > + int edid_len;
> > > > + bool edid_valid;
> > > > + u8 fw_crc;
> > > > +
> > > > + bool work_inited;
> > > > + bool bridge_added;
> > > > + bool regulators_enabled;
> > > > + bool hdmi_connected;
> > > > + enum drm_connector_status audio_status;
> > > > +};
> > > > +
> > > > +static const struct regmap_range chip_ranges[] = {
> > > > + { .range_min = 0, .range_max = 0xff },
> > > > +};
> > > > +
> > > > +static const struct regmap_access_table chip_table = {
> > > > + .yes_ranges = chip_ranges,
> > > > + .n_yes_ranges = ARRAY_SIZE(chip_ranges),
> > > > +};
> > > > +
> > > > +static const struct regmap_config lt9611c_regmap_config = {
> > > > + .reg_bits = 8,
> > >
> > > It's 16
> > >
> > I will check this issue. thanks
> >
> > > > + .val_bits = 8,
> > > > + .volatile_table = &chip_table,
> > > > + .cache_type = REGCACHE_NONE,
> > > > +};
> > > > +
> > > > +struct crc_info {
> > > > + u8 width;
> > > > + u32 poly;
> > > > + u32 crc_init;
> > > > + u32 xor_out;
> > > > + bool refin;
> > > > + bool refout;
> > > > +};
> > > > +
> > > > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c);
> > > > +
> > > > +static unsigned int bits_reverse(u32 in_val, u8 bits)
> > > > +{
> > > > + u32 out_val = 0;
> > > > + u8 i;
> > > > +
> > > > + for (i = 0; i < bits; i++) {
> > > > + if (in_val & (1 << i))
> > > > + out_val |= 1 << (bits - 1 - i);
> > > > + }
> > > > +
> > > > + return out_val;
> > > > +}
> > > > +
> > > > +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
> > >
> > > Use library functions for that.
> > >
> >
> > I'm not sure whether the algorithms in the llibrary functions are
> > consistent with those designed in our chip.
> > If either of them changes, it will cause the firmware updated to the
> > chip to fail to run.
>
> CRC polynoms don't change that easily
>
> > I think it's safer to implement it using our own code.
>
> No, it's not.
>
If we calculate CRC_1 using the library function and then burn it
together with the firmware into the chip, when the chip boot, it will
use the internal hardware to calculate the firmware CRC_2.
If CRC_1 is not equal to CRC_2, the chip will fail to boot. The
library function will not be changed. I'm worried that the algorithm
in our chip's hardware is different from the library function. I'll
research it.
> > I'll check it.
> >
> > > > +{
> > > > + u8 width = type.width;
> > > > + u32 poly = type.poly;
> > > > + u32 crc = type.crc_init;
> > > > + u32 xorout = type.xor_out;
> > > > + bool refin = type.refin;
> > > > + bool refout = type.refout;
> > > > + u8 n;
> > > > + u32 bits;
> > > > + u32 data;
> > > > + u8 i;
> > > > +
> > > > + n = (width < 8) ? 0 : (width - 8);
> > > > + crc = (width < 8) ? (crc << (8 - width)) : crc;
> > > > + bits = (width < 8) ? 0x80 : (1 << (width - 1));
> > > > + poly = (width < 8) ? (poly << (8 - width)) : poly;
> > > > + while (buf_len--) {
> > > > + data = *(buf++);
> > > > + if (refin)
> > > > + data = bits_reverse(data, 8);
> > > > + crc ^= (data << n);
> > > > + for (i = 0; i < 8; i++) {
> > > > + if (crc & bits)
> > > > + crc = (crc << 1) ^ poly;
> > > > + else
> > > > + crc = crc << 1;
> > > > + }
> > > > + }
> > > > + crc = (width < 8) ? (crc >> (8 - width)) : crc;
> > > > + if (refout)
> > > > + crc = bits_reverse(crc, width);
> > > > + crc ^= xorout;
> > > > +
> > > > + return (crc & ((2 << (width - 1)) - 1));
> > > > +}
> > > > +
> > > > +static u8 calculate_crc(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct crc_info type = {
> > > > + .width = 8,
> > > > + .poly = 0x31,
> > > > + .crc_init = 0,
> > > > + .xor_out = 0,
> > > > + .refout = false,
> > > > + .refin = false,
> > > > + };
> > > > + const u8 *upgrade_data;
> > > > + u64 len;
> > > > + u64 crc_size = FW_SIZE - 1;
> > > > + u8 default_val = 0xFF;
> > > > +
> > > > + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
> > > > + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
> > > > + return 0;
> > > > + }
> > > > +
> > > > + upgrade_data = lt9611c->fw->data;
> > > > + len = lt9611c->fw->size;
> > > > +
> > > > + type.crc_init = get_crc(type, upgrade_data, len);
> > > > +
> > > > + crc_size -= len;
> > > > + while (crc_size--)
> > > > + type.crc_init = get_crc(type, &default_val, 1);
> > > > +
> > > > + return type.crc_init;
> > > > +}
> > > > +
> > > > +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret = 0;
> > > > +
> > > > + ret = regmap_write(lt9611c->regmap, reg, val);
> > > > + if (ret < 0) {
> > > > + dev_err(dev,
> > > > + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > > > + lt9611c->client->addr, reg, ret);
> > > > + }
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
> > >
> > > Drop these two wrappers, they provide no extra functionality.
> > >
> >
> > I will consider fixing this issue. thanks.
> >
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret = 0;
> > > > + unsigned int tmp;
> > > > +
> > > > + if (!val)
> > > > + return -EINVAL;
> > > > +
> > > > + ret = regmap_read(lt9611c->regmap, reg, &tmp);
> > > > + if (ret < 0) {
> > > > + dev_err(dev,
> > > > + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> > > > + lt9611c->client->addr, reg, ret);
> > > > +
> > > > + return ret;
> > > > + }
> > > > +
> > > > + *val = (u8)tmp;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> > > > + unsigned int param_count, u8 *return_buffer,
> > > > + unsigned int return_count)
> > > > +{
> > > > + int count, i;
> > > > + u8 temp;
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0xDE, 0x01);
> > >
> > > - lowercase all hex values
> >
> > i will fix , thanks.
> >
> > > - use paged writes as implemented for LT9611 and LT9611UXC
> > >
> > Don't understand.
>
> Use 16-bit addressing as done by those two drivers. This way 0xff
> becomes a page switch.
>
i will research it.
> >
> > > > +
> > > > + count = 0;
> > > > + do {
> > > > + i2c_read_byte(lt9611c, 0xAE, &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;
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
> > > > + }
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xDE, 0x02);
> > > > +
> > > > + count = 0;
> > > > + do {
> > > > + i2c_read_byte(lt9611c, 0xAE, &temp);
> > > > + usleep_range(1000, 2000);
> > > > + count++;
> > > > + } while (count < 100 && temp != 0x02);
> > > > +
> > > > + if (temp != 0x02)
> > > > + return -2;
> > > > +
> > > > + for (i = 0; i < return_count; i++)
> > > > + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + /* ensure filesystem ready */
> > > > + msleep(3000);
> > >
> > > No. If the firmware is necessary and it's not ready, return
> > > -EPROBE_DEFER.
> > >
> > The firmware is unnecessary . This part of the code is for customers
> > who need to upgrade the chip firmware.
> >
> > Due to the different designs of the platform, the firmware used by
> > each customer may be different.
>
> Well... That's a very bad way to go. We have had this issue with
> LT9611UXC at one of my previous jobs. Our customers have had various
> kinds of issues because of the wrong firmware.
>
> Please provide some reference, which works in a DSI-to-HDMI case and
> make it _tunable_ rather than requiring to replace the firmware
> completely.
>
i will research it.
Yes, you worked together with my colleagues to handle the issue of
LT9611UXC. (At that time, you used dmitry.baryshkov@linaro.org)
> >
> > Therefore, when they need to update the firmware, they only need to
> > compile the firmware into the /lib/firmware directory during the
> > compilation
> > process, and then burn the image into the platform.
> >
> > Once reboot platform, the firmware upgrade can be automatically completed.
>
> The firmware upgrade must be triggered by user, unless the FW is
> completely empty.
>
Is it necessary for the authorities to insist on doing so?
> >
> > When there is no need to upgrade the firmware, this part of the code
> > will not affect the operation of the driver.
> >
> > > > + ret = request_firmware(<9611c->fw, FW_FILE, dev);
> > > > + if (ret) {
> > > > + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> > > > + return ret;
> > > > + }
> > > > +
> > > > + if (lt9611c->fw->size > FW_SIZE - 1) {
> > > > + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
> > > > + lt9611c->fw = NULL;
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
> > > > +
> > > > + lt9611c->fw_crc = calculate_crc(lt9611c);
> > > > +
> > > > + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
> > >
> > > No spamming with the unnecessary info. If you want, print the version
> > > of the firmware.
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > > > + //fifo_rst_n
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > > > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > > > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0x5E, 0xC1);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x00);
> > > > + i2c_write_byte(lt9611c, 0x59, 0x50);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x10);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > > +}
> > > > +
> > > > +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0x5e, 0x5f);
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x20);
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > > > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > > > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x10);
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > > +}
> > > > +
> > > > +static void lt9611c_wren(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x04);
> > > > + i2c_write_byte(lt9611c, 0x5a, 0x00);
> > > > +}
> > > > +
> > > > +static void lt9611c_wrdi(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x08);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > > +}
> > > > +
> > > > +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > + u8 flash_crc;
> > > > +
> > > > + if (!lt9611c)
> > > > + return -EINVAL;
> > >
> > > How can it be NULL here?
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + lt9611c_config_parameters(lt9611c);
> > > > + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > > +
> > > > + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "failed to read flash crc\n");
> > > > + return ret;
> > > > + }
> > > > +
> > > > + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
> > > > + flash_crc, lt9611c->fw_crc);
> > >
> > > dev_dbg()
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + lt9611c_wrdi(lt9611c);
> > > > +
> > > > + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
> > > > +}
> > > > +
> > > > +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + //fifo_rst_n
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> > > > + i2c_write_byte(lt9611c, 0x03, 0x3F);
> > > > + i2c_write_byte(lt9611c, 0x03, 0xFF);
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0x5e, 0x40);
> > > > + i2c_write_byte(lt9611c, 0x56, 0x05);
> > > > + i2c_write_byte(lt9611c, 0x55, 0x25);
> > > > + i2c_write_byte(lt9611c, 0x55, 0x01);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > > +
> > > > + ret = i2c_read_byte(lt9611c, 0x5f, status);
> > > > + if (ret < 0)
> > > > + dev_err(dev, "failed to read flash register status\n");
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static void lt9611c_block_erase(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + u32 i = 0;
> > > > + u8 flash_status = 0;
> > > > + u8 block_num = 0x00;
> > > > + u32 flash_addr = 0x00;
> > > > +
> > > > + for (block_num = 0; block_num < 2; block_num++) {
> > > > + flash_addr = (block_num * 0x008000);
> > > > + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> > > > + i2c_write_byte(lt9611c, 0xEE, 0x01);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x04);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > > + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
> > > > + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
> > > > + i2c_write_byte(lt9611c, 0x5D, flash_addr);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x01);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > > + msleep(100);
> > > > + i = 0;
> > > > + while (1) {
> > > > + read_flash_reg_status(lt9611c, &flash_status);
> > > > + if ((flash_status & 0x01) == 0)
> > > > + break;
> > > > +
> > > > + if (i > 50)
> > > > + break;
> > > > +
> > > > + i++;
> > > > + msleep(50);
> > > > + }
> > > > + }
> > > > +
> > > > + dev_info(dev, "erase flash done.\n");
> > > > +}
> > > > +
> > > > +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0x51, 0x00);
> > > > + i2c_write_byte(lt9611c, 0x55, 0xc0);
> > > > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > > > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > > +}
> > > > +
> > > > +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0x51, 0xff);
> > > > + i2c_write_byte(lt9611c, 0x55, 0x80);
> > > > + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> > > > + i2c_write_byte(lt9611c, 0x58, 0x21);
> > > > +}
> > > > +
> > > > +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> > > > + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> > > > + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x30);
> > > > + i2c_write_byte(lt9611c, 0x5A, 0x00);
> > > > +}
> > > > +
> > > > +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > + int page = 0, num = 0, i = 0;
> > > > + const u8 *data;
> > > > + u64 size, index;
> > > > + u8 value;
> > > > +
> > > > + data = lt9611c->fw->data;
> > > > + size = lt9611c->fw->size;
> > > > +
> > > > + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> > > > +
> > > > + if (page * LT_PAGE_SIZE > 64 * 1024) {
> > > > + dev_err(dev, "firmware size out of range\n");
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
> > >
> > >
> > > dev_dbg()
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + for (num = 0; num < page; num++) {
> > > > + lt9611c_data_to_sram(lt9611c);
> > > > +
> > > > + for (i = 0; i < LT_PAGE_SIZE; i++) {
> > > > + index = num * LT_PAGE_SIZE + i;
> > > > + value = (index < size) ? data[index] : 0xFF;
> > > > +
> > > > + ret = i2c_write_byte(lt9611c, 0x59, value);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "write error at page %u, index %u\n", num, i);
> > > > + return ret;
> > > > + }
> > > > + }
> > > > +
> > > > + lt9611c_wren(lt9611c);
> > > > + lt9611c_sram_to_flash(lt9611c, addr);
> > > > +
> > > > + addr += LT_PAGE_SIZE;
> > > > + }
> > > > +
> > > > + lt9611c_wrdi(lt9611c);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > + u8 crc;
> > > > +
> > > > + crc = lt9611c->fw_crc;
> > > > + lt9611c_crc_to_sram(lt9611c);
> > > > + ret = i2c_write_byte(lt9611c, 0x59, crc);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "failed to write CRC\n");
> > > > + return -1;
> > > > + }
> > > > +
> > > > + lt9611c_wren(lt9611c);
> > > > + lt9611c_sram_to_flash(lt9611c, addr);
> > > > + lt9611c_wrdi(lt9611c);
> > > > +
> > > > + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
> > >
> > > dev_dbg
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
> > >
> > > dev_dbg
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + lt9611c_config_parameters(lt9611c);
> > > > + lt9611c_block_erase(lt9611c);
> > > > +
> > > > + ret = lt9611c_write_data(lt9611c, 0);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "Failed to write firmware data\n");
> > > > + return ret;
> > > > + }
> > > > +
> > > > + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "Failed to write firmware CRC\n");
> > > > + return ret;
> > > > + }
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + u8 crc_result;
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > > + i2c_read_byte(lt9611c, 0x21, &crc_result);
> > > > +
> > > > + if (crc_result == lt9611c->fw_crc) {
> > > > + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
> > >
> > > dev_dbg
> > >
> > i will fix, thanks
> >
> > > > + return 0;
> > > > + }
> > > > +
> > > > + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
> > > > + lt9611c->fw_crc, crc_result);
> > > > + return -EIO;
> > > > +}
> > > > +
> > > > +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
> > > > +{
> > > > + return container_of(bridge, struct lt9611c, bridge);
> > > > +}
> > > > +
> > > > +static void lt9611c_lock(struct lt9611c *lt9611c)
> > > > +{
> > > > + mutex_lock(<9611c->ocm_lock);
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > > +}
> > > > +
> > > > +static void lt9611c_unlock(struct lt9611c *lt9611c)
> > > > +{
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0xee, 0x00);
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > +}
> > > > +
> > > > +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_id;
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > + u8 irq_status;
> > > > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > > > + u8 data[5];
> > > > +
> > > > + mutex_lock(<9611c->ocm_lock);
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_read_byte(lt9611c, 0x84, &irq_status);
> > > > +
> > > > + if (!(irq_status & BIT(0))) {
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > + return IRQ_HANDLED;
> > > > + }
> > > > + dev_info(dev, "HPD interrupt triggered.\n");
> > >
> > > Nice joke. dev_dbg().
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
> > > > + usleep_range(10000, 12000);
> > >
> > > Why?
> > >
> > Our chip design specification requires that this be done when clearing
> > the interrupt.
>
> Add a comment.
>
i will add comment, thks
> >
> > > > + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
> > > > +
> > > > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > > > + if (ret) {
> > > > + dev_err(dev, "failed to read HPD status\n");
> > > > + } else {
> > > > + lt9611c->hdmi_connected = (data[4] == 0x02);
> > > > + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
> > >
> > > dev_dbg()
> > >
> > i will fix, thanks
> >
> > > > + }
> > > > +
> > > > + lt9611c->audio_status = lt9611c->hdmi_connected ?
> > > > + connector_status_connected :
> > > > + connector_status_disconnected;
> > >
> > > What is it being used for? Why do you need separate status for audio?
> > >
> > Used to update the connection status of the audio.
> > The separate status indicators make it clearer for the readers.
> >
> > > > +
> > > > + schedule_work(<9611c->work);
> > > > +
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > +
> > > > + return IRQ_HANDLED;
> > > > +}
> > > > +
> > > > +static void lt9611c_hpd_work(struct work_struct *work)
> > > > +{
> > > > + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
> > > > + bool connected;
> > > > +
> > > > + mutex_lock(<9611c->ocm_lock);
> > > > + connected = lt9611c->hdmi_connected;
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > +
> > > > + drm_bridge_hpd_notify(<9611c->bridge,
> > > > + connected ?
> > > > + connector_status_connected :
> > > > + connector_status_disconnected);
> > >
> > > Incorrect indentation.
> > >
> > ? The checkpatch.pl did not detect it.
>
> use --strict.
>
i will , thks
> >
> > > > +
> > > > + lt9611c_audio_update_connector_status(lt9611c);
> > > > +}
> > > > +
> > > > +static void lt9611c_reset(struct lt9611c *lt9611c)
> > > > +{
> > > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > > + msleep(20);
> > > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > > + msleep(20);
> > > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> > > > + msleep(400);
> > > > +}
> > > > +
> > > > +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + lt9611c->supplies[0].supply = "vcc";
> > > > + lt9611c->supplies[1].supply = "vdd";
> > > > +
> > > > + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
> > > > +{
> > > > + int ret;
> > > > +
> > > > + ret = regulator_enable(lt9611c->supplies[0].consumer);
> > > > + if (ret < 0)
> > > > + return ret;
> > > > +
> > > > + usleep_range(5000, 10000);
> > > > +
> > > > + ret = regulator_enable(lt9611c->supplies[1].consumer);
> > > > + if (ret < 0) {
> > > > + regulator_disable(lt9611c->supplies[0].consumer);
> > > > + return ret;
> > > > + }
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
> > > > +{
> > > > + int ret;
> > > > +
> > > > + ret = regulator_disable(lt9611c->supplies[0].consumer);
> > > > + if (ret < 0)
> > > > + return ret;
> > > > +
> > > > + usleep_range(5000, 10000);
> > > > +
> > > > + ret = regulator_disable(lt9611c->supplies[1].consumer);
> > > > + if (ret < 0)
> > > > + return ret;
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
> > > > + struct device_node *dsi_node)
> > > > +{
> > > > + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
> > > > + struct mipi_dsi_device *dsi;
> > > > + struct mipi_dsi_host *host;
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + host = of_find_mipi_dsi_host_by_node(dsi_node);
> > > > + if (!host)
> > > > + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
> > > > +
> > > > + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> > > > + if (IS_ERR(dsi)) {
> > > > + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
> > > > + struct drm_encoder *encoder,
> > > > + enum drm_bridge_attach_flags flags)
> > > > +{
> > > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > > +
> > > > + return drm_bridge_attach(encoder, lt9611c->next_bridge,
> > > > + bridge, flags);
> > > > +}
> > > > +
> > > > +static enum drm_mode_status lt9611c_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)
> > >
> > > Use .hdmi_tmds_char_rate_valid() for that.
> > >
> > I will check and test, thanks
> >
> > > > + return MODE_OK;
> > > > + else
> > > > + return MODE_BAD;
> > > > +}
> > > > +
> > > > +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
> > > > + const struct drm_display_mode *mode,
> > > > + const struct drm_display_mode *adj_mode)
> > >
> > > - Wrong indentation
> > will fix, thanks
> >
> > > - mode_set callback is deprecated and should not be used for new
> > > drivers.
> > >
> > I found that kernel 6.17 is still in use mode_set callback.
>
> Check the documentation in drm_bridge_funcs.
>
i will research.
> >
> > > > +{
> > > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > > + struct device *dev = lt9611c->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;
> > > > +
> > >
> > > > + 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, "Out video info:\n");
> > > > + dev_info(dev,
> > > > + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
> > > > + h_total, hactive, hsync_len, hfront_porch, hback_porch);
> > > > + dev_info(dev,
> > > > + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
> > > > + v_total, vactive, vsync_len, vfront_porch, vback_porch);
> > >
> > >
> > > Fix indentation
> > The indentation issue was not detected by checkpatch.pl.
>
> The indentation issue is detected by the brain and eye.
>
sorry, i will research.
> >
> > > Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
> > > spammy.
> > >
> > i will fix, thanks
> >
> > > > +
> > > > + 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(<9611c->ocm_lock);
> > > > + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
> > > > + if (ret)
> > > > + dev_err(dev, "video set failed\n");
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > +}
> > > > +
> > > > +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
> > > > + struct drm_connector *connector)
> > > > +{
> > > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > + bool connected = false;
> > > > + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> > > > + u8 data[5];
> > > > +
> > > > + mutex_lock(<9611c->ocm_lock);
> > > > + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> > > > + if (ret) {
> > > > + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
> > > > + ret);
> > > > + } else {
> > > > + connected = (data[4] == 0x02);
> > > > + }
> > >
> > > THere is no need to put single-line statements in brackets. Drop those.
> > >
> > yes, i will fix, thks
> >
> > > > +
> > > > + lt9611c->hdmi_connected = connected;
> > > > +
> > > > + if (lt9611c->hdmi_connected)
> > > > + lt9611c->audio_status = connector_status_connected;
> > > > + else
> > > > + lt9611c->audio_status = connector_status_disconnected;
> > > > +
> > > > + mutex_unlock(<9611c->ocm_lock);
> > > > +
> > > > + return connected ? connector_status_connected :
> > > > + connector_status_disconnected;
> > > > +}
> > > > +
> > > > +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret, i, bytes_to_copy, offset = 0;
> > > > + u8 packets_num;
> > > > + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> > > > + u8 return_edid_data[37];
> > > > + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> > > > + u8 return_edid_byte_num[6];
> > > > +
> > > > + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> > > > + if (ret) {
> > > > + dev_err(dev, "Failed to read EDID byte number\n");
> > > > + lt9611c->edid_valid = false;
> > > > + return ret;
> > > > + }
> > > > +
> > > > + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> > > > +
> > > > + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> > > > + lt9611c->edid_len : 0)) {
> > > > + kfree(lt9611c->edid_buf);
> > > > + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> > > > + if (!lt9611c->edid_buf) {
> > > > + dev_err(dev, "Failed to allocate EDID buffer\n");
> > > > + lt9611c->edid_len = 0;
> > > > + lt9611c->edid_valid = false;
> > > > + return -ENOMEM;
> > > > + }
> > > > + }
> > > > +
> > > > + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> > > > + (lt9611c->edid_len / 32);
> > > > + for (i = 0; i < packets_num; i++) {
> > > > + read_edid_data_cmd[4] = (u8)i;
> > > > + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> > > > + if (ret) {
> > > > + dev_err(dev, "Failed to read EDID packet %d\n", i);
> > > > + lt9611c->edid_valid = false;
> > > > + return -EIO;
> > > > + }
> > > > + offset = i * 32;
> > > > + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> > > > + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
> > >
> > > Don't store EDID in the long-term structures. Read it on demand.
> > >
> > I will think about this issue.
> >
> > > > + }
> > > > +
> > > > + lt9611c->edid_valid = true;
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> > > > +{
> > > > + struct lt9611c *lt9611c = data;
> > > > + struct device *dev = lt9611c->dev;
> > > > + unsigned int total_blocks;
> > > > + int ret;
> > > > +
> > > > + if (len > 128)
> > > > + return -EINVAL;
> > > > +
> > > > + guard(mutex)(<9611c->ocm_lock);
> > > > + if (block == 0 || !lt9611c->edid_valid) {
> > > > + ret = lt9611c_read_edid(lt9611c);
> > > > + if (ret) {
> > > > + dev_err(dev, "EDID read failed\n");
> > > > + return ret;
> > > > + }
> > > > + }
> > > > +
> > > > + total_blocks = lt9611c->edid_len / 128;
> > > > + if (!total_blocks) {
> > > > + dev_err(dev, "No valid EDID blocks\n");
> > > > + return -EIO;
> > > > + }
> > > > +
> > > > + if (block >= total_blocks) {
> > > > + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> > > > + block, total_blocks);
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + memcpy(buf, lt9611c->edid_buf + block * 128, len);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> > > > + struct drm_connector *connector)
> > > > +{
> > > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > > +
> > > > + usleep_range(10000, 20000);
> > >
> > > Why?
> > >
> > Delay for a while to ensure that EDID is ready.
>
> Your other chip had interrupt status to note that EDID is ready. I hope
> you have that one too. Blindly calling usleep_range() is a bad idea.
>
Different chips have different logic. i will research it.
> >
> > > > + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> > > > +}
> > > > +
> > > > +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> > > > + .attach = lt9611c_bridge_attach,
> > > > + .mode_valid = lt9611c_bridge_mode_valid,
> > > > + .mode_set = lt9611c_bridge_mode_set,
> > > > + .detect = lt9611c_bridge_detect,
> > > > + .edid_read = lt9611c_bridge_edid_read,
> > > > +};
> > > > +
> > > > +static int lt9611c_parse_dt(struct device *dev,
> > > > + struct lt9611c *lt9611c)
> > > > +{
> > > > + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
> > > > + if (!lt9611c->dsi0_node) {
> > > > + dev_err(dev, "failed to get remote node for primary dsi\n");
> > > > + return -ENODEV;
> > > > + }
> > > > +
> > > > + lt9611c->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, <9611c->next_bridge);
> > > > +}
> > > > +
> > > > +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > +
> > > > + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> > > > + if (IS_ERR(lt9611c->reset_gpio)) {
> > > > + dev_err(dev, "failed to acquire reset gpio\n");
> > > > + return PTR_ERR(lt9611c->reset_gpio);
> > > > + }
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
> > > > +{
> > > > + u8 val;
> > > > + u64 ver = 0;
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > > +
> > > > + i2c_read_byte(lt9611c, 0x80, &val);
> > > > + ver = val;
> > > > +
> > > > + i2c_read_byte(lt9611c, 0x81, &val);
> > > > + ver = (ver << 8) | val;
> > > > +
> > > > + *version = ver;
> > > > +}
> > > > +
> > > > +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + u8 val = 0;
> > > > +
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe0);
> > > > + i2c_write_byte(lt9611c, 0xee, 0x01);
> > > > + i2c_write_byte(lt9611c, 0xff, 0xe1);
> > > > +
> > > > + i2c_read_byte(lt9611c, 0x00, &val);
> > > > + if (val != 0x23)
> > > > + return -ENODEV;
> > > > +
> > > > + i2c_read_byte(lt9611c, 0x01, &val);
> > > > + if (val != 0x06)
> > > > + return -ENODEV;
> > > > +
> > > > + dev_info(dev, "ChipId = 0x2306\n");
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> > > > + struct hdmi_codec_daifmt *fmt,
> > > > + struct hdmi_codec_params *hparms)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > +
> > > > + dev_info(lt9611c->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;
> > > > + }
> > >
> > > Does that add anything on top of the limitations of hdmi-codec.c?
> > >
> > The parameters supported in the hdmi-codec.c may not be supported by
> > my chip. Therefore, we can exclude the parameters that are not
> > supported by the chip.
>
> Are they?
>
The firmware handles all parameter adaptation autonomously. This code
merely needs to expose the chip's capabilities to hdmi-codec.c.
> >
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> > > > +{
> > > > +}
> > > > +
> > > > +static int lt9611c_audio_startup(struct device *dev, void *data)
> > > > +{
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> > > > +{
> > > > + enum drm_connector_status status;
> > > > +
> > > > + status = lt9611c->audio_status;
> > > > + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> > > > + lt9611c->plugged_cb(lt9611c->codec_dev,
> > > > + status == connector_status_connected);
> > > > +}
> > > > +
> > > > +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> > > > + void *data,
> > > > + hdmi_codec_plugged_cb fn,
> > > > + struct device *codec_dev)
> > > > +{
> > > > + struct lt9611c *lt9611c = data;
> > > > +
> > > > + lt9611c->plugged_cb = fn;
> > > > + lt9611c->codec_dev = codec_dev;
> > > > + lt9611c_audio_update_connector_status(lt9611c);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> > > > + .hw_params = lt9611c_hdmi_hw_params,
> > > > + .audio_shutdown = lt9611c_audio_shutdown,
> > > > + .audio_startup = lt9611c_audio_startup,
> > > > + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> > > > +};
> > >
> > > No, we have HDMI audio helpers for that. Drop this and use the helpers
> > > instead.
> > >
> > ??? I don't understand.
>
> See <drm/display/drm_hdmi_audio_helper.h> and
> https://lore.kernel.org/dri-devel/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com/
>
i will research, thks.
Could you please share the latest driver file for lt9611uxc.c that you
have written? (to this email: syyang@lontium.com)
> >
> > > > +
> > > > +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
> > > > +{
> > > > + struct hdmi_codec_pdata codec_data = {
> > > > + .ops = <9611c_codec_ops,
> > > > + .max_i2s_channels = 2,
> > > > + .i2s = 1,
> > > > + .data = lt9611c,
> > > > + };
> > > > +
> > > > + lt9611c->audio_pdev =
> > > > + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
> > > > + PLATFORM_DEVID_AUTO,
> > > > + &codec_data, sizeof(codec_data));
> > > > +
> > > > + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
> > > > +}
> > > > +
> > > > +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
> > > > +{
> > > > + if (lt9611c->audio_pdev) {
> > > > + platform_device_unregister(lt9611c->audio_pdev);
> > > > + lt9611c->audio_pdev = NULL;
> > > > + }
> > > > +}
> > > > +
> > > > +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > + int ret;
> > > > +
> > > > + lt9611c_lock(lt9611c);
> > > > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
> > > > + goto out;
> > > > + }
> > > > +
> > > > + ret = lt9611c_firmware_upgrade(lt9611c);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "upgrade failure\n");
> > > > + goto out;
> > > > + }
> > > > + lt9611c_reset(lt9611c);
> > > > + ret = lt9611c_upgrade_result(lt9611c);
> > > > + if (ret < 0)
> > > > + goto out;
> > > > +
> > > > +out:
> > > > + lt9611c_unlock(lt9611c);
> > > > + lt9611c_reset(lt9611c);
> > > > + if (lt9611c->fw) {
> > > > + release_firmware(lt9611c->fw);
> > > > + lt9611c->fw = NULL;
> > > > + }
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
> > > > + const char *buf, size_t len)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > + int ret;
> > > > +
> > > > + ret = lt9611c_firmware_update_store(lt9611c);
> > >
> > > Inline
> > >
> > i will fix, thks
> >
> > > > + if (ret < 0)
> > > > + return ret;
> > > > + return len;
> > > > +}
> > > > +
> > > > +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > +
> > > > + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
> > > > +}
> > > > +
> > > > +static DEVICE_ATTR_RW(lt9611c_firmware);
> > > > +
> > > > +static struct attribute *lt9611c_attrs[] = {
> > > > + &dev_attr_lt9611c_firmware.attr,
> > > > + NULL,
> > > > +};
> > > > +
> > > > +static const struct attribute_group lt9611c_attr_group = {
> > > > + .attrs = lt9611c_attrs,
> > > > +};
> > > > +
> > > > +static const struct attribute_group *lt9611c_attr_groups[] = {
> > > > + <9611c_attr_group,
> > > > + NULL,
> > > > +};
> > > > +
> > > > +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> > > > +{
> > > > + struct device *dev = lt9611c->dev;
> > > > +
> > > > + if (lt9611c->work_inited) {
> > > > + cancel_work_sync(<9611c->work);
> > > > + lt9611c->work_inited = false;
> > > > + dev_err(dev, "work cancelled\n");
> > >
> > > Why???
> > >
> > ?? I don't understand.
>
> Why do you need to be so spammy?
>
i will fix, thks
> >
> > > > + }
> > > > +
> > > > + if (lt9611c->bridge_added) {
> > > > + drm_bridge_remove(<9611c->bridge);
> > > > + lt9611c->bridge_added = false;
> > > > + dev_err(dev, "DRM bridge removed\n");
> > > > + }
> > > > +
> > > > + if (lt9611c->regulators_enabled) {
> > > > + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > > > + lt9611c->regulators_enabled = false;
> > > > + dev_err(dev, "regulators disabled\n");
> > > > + }
> > > > +
> > > > + if (lt9611c->audio_pdev)
> > > > + lt9611c_audio_exit(lt9611c);
> > > > +
> > > > + if (lt9611c->fw) {
> > >
> > > You definitely don't need firmware when the bridge is up and running.
> > >
> > The previous text has already described the working logic of the firmware.
>
> It's another topic: you are storing the firmware in memory while the
> driver is bound. It's not necessary. You can release it after updating
> it on the chip.
>
I understand what you mean.
Based on the above conversation, your intention is that when the
customer needs to upgrade the firmware, they should modify the
comparison conditions of the version, then compile and burn the
kernel, and then perform the firmware upgrade, just like the LT9611UXC
driver. Instead of loading the firmware every time.
My design intention is to avoid the need for recompiling the driver
when upgrading. Instead, a file named "LT9611C.bin" can be directly
sent to the "/lib/firmware" directory via scp. Then you can either
perform a reboot for the upgrade or execute the command manually for
the upgrade.
Perhaps you are suggesting that we could follow the design approach of
the LT9611UXC driver?
> >
> > > > + release_firmware(lt9611c->fw);
> > > > + lt9611c->fw = NULL;
> > > > + dev_err(dev, "firmware released\n");
> > > > + }
> > > > +
> > > > + if (lt9611c->dsi0_node) {
> > > > + of_node_put(lt9611c->dsi0_node);
> > > > + lt9611c->dsi0_node = NULL;
> > > > + dev_err(dev, "dsi0 node released\n");
> > > > + }
> > > > +
> > > > + if (lt9611c->dsi1_node) {
> > > > + of_node_put(lt9611c->dsi1_node);
> > > > + lt9611c->dsi1_node = NULL;
> > > > + dev_err(dev, "dsi1 node released\n");
> > > > + }
> > > > +}
> > > > +
> > > > +static int lt9611c_main(void *data)
> > > > +{
> > > > + struct lt9611c *lt9611c = data;
> > > > + struct device *dev = lt9611c->dev;
> > > > + struct i2c_client *client = lt9611c->client;
> > > > + int ret;
> > > > +
> > > > + lt9611c->work_inited = false;
> > > > + lt9611c->bridge_added = false;
> > > > + lt9611c->regulators_enabled = false;
> > > > +
> > > > + ret = lt9611c_parse_dt(dev, lt9611c);
> > > > + if (ret) {
> > > > + dev_err(dev, "failed to parse device tree\n");
> > > > + return ret;
> > > > + }
> > > > +
> > > > + ret = lt9611c_gpio_init(lt9611c);
> > > > + if (ret < 0)
> > > > + goto err_cleanup;
> > > > +
> > > > + ret = lt9611c_regulator_init(lt9611c);
> > > > + if (ret < 0)
> > > > + goto err_cleanup;
> > > > +
> > > > + ret = lt9611c_regulator_enable(lt9611c);
> > > > + if (ret)
> > > > + goto err_cleanup;
> > > > +
> > > > + lt9611c->regulators_enabled = true;
> > > > +
> > > > + lt9611c_reset(lt9611c);
> > > > +
> > > > + ret = lt9611c_read_chipid(lt9611c);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "failed to read chip id.\n");
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + lt9611c_lock(lt9611c);
> > > > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > > > +
> > > > + ret = lt9611c_prepare_firmware_data(lt9611c);
> > > > + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
> > > > + dev_info(dev, "firmware upgrade needed\n");
> > > > +
> > > > + ret = lt9611c_firmware_upgrade(lt9611c);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "firmware upgrade failed\n");
> > > > + lt9611c_unlock(lt9611c);
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + lt9611c_reset(lt9611c);
> > > > + ret = lt9611c_upgrade_result(lt9611c);
> > > > + if (ret < 0) {
> > > > + lt9611c_unlock(lt9611c);
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + lt9611c_read_version(lt9611c, <9611c->fw_version);
> > > > + lt9611c_unlock(lt9611c);
> > > > +
> > > > + } else {
> > > > + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
> > > > + lt9611c_unlock(lt9611c);
> > > > + }
> > > > +
> > > > + if (lt9611c->fw) {
> > > > + release_firmware(lt9611c->fw);
> > > > + lt9611c->fw = NULL;
> > > > + }
> > > > + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
> > > > +
> > > > + INIT_WORK(<9611c->work, lt9611c_hpd_work);
> > > > + lt9611c->work_inited = true;
> > > > +
> > > > + if (!client->irq) {
> > > > + dev_err(dev, "failed to get INTP IRQ\n");
> > > > + ret = -ENODEV;
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> > > > + lt9611c_irq_thread_handler,
> > > > + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
> > > > + IRQF_NO_AUTOEN,
> > > > + "lt9611c", lt9611c);
> > > > + if (ret) {
> > > > + dev_err(dev, "failed to request irq\n");
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + lt9611c->bridge.funcs = <9611c_bridge_funcs;
> > > > + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
> > > > + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> > > > + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> > > > +
> > > > + drm_bridge_add(<9611c->bridge);
> > > > + lt9611c->bridge_added = true;
> > >
> > > No unnecessary flags, please. Implement proper cleanup path, unwinding
> > > resources one by one.
> > >
> > I will consider this issue. thks
> >
> > > > +
> > > > + /* Attach primary DSI */
> > > > + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
> > > > + if (IS_ERR(lt9611c->dsi0)) {
> > > > + ret = PTR_ERR(lt9611c->dsi0);
> > > > + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + /* Attach secondary DSI, if specified */
> > > > + if (lt9611c->dsi1_node) {
> > > > + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
> > > > + if (IS_ERR(lt9611c->dsi1)) {
> > > > + ret = PTR_ERR(lt9611c->dsi1);
> > > > + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
> > > > + goto err_cleanup;
> > > > + }
> > > > + }
> > > > +
> > > > + lt9611c->audio_status = connector_status_disconnected;
> > > > +
> > > > + ret = lt9611c_audio_init(dev, lt9611c);
> > > > + if (ret < 0) {
> > > > + dev_err(dev, "audio init failed\n");
> > > > + goto err_cleanup;
> > > > + }
> > > > +
> > > > + lt9611c_reset(lt9611c);
> > > > + enable_irq(lt9611c->client->irq);
> > > > +
> > > > + return 0;
> > > > +
> > > > +err_cleanup:
> > > > + lt9611c_cleanup_resources(lt9611c);
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int lt9611c_probe(struct i2c_client *client)
> > > > +{
> > > > + struct lt9611c *lt9611c;
> > > > + struct device *dev = &client->dev;
> > > > +
> > > > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> > > > + dev_err(dev, "device doesn't support I2C\n");
> > > > + return -ENODEV;
> > > > + }
> > > > +
> > > > + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
> > >
> > > devm_drm_bridge_alloc()
> > >
> > i will fix, thks
> >
> > > > + if (!lt9611c)
> > > > + return -ENOMEM;
> > > > +
> > > > + lt9611c->dev = dev;
> > > > + lt9611c->client = client;
> > > > + mutex_init(<9611c->ocm_lock);
> > > > +
> > > > + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
> > > > + if (IS_ERR(lt9611c->regmap)) {
> > > > + dev_err(dev, "regmap i2c init failed\n");
> > > > + return PTR_ERR(lt9611c->regmap);
> > > > + }
> > > > +
> > > > + i2c_set_clientdata(client, lt9611c);
> > > > +
> > > > + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> > >
> > > Why do you need extra kthread for that???
Upgrading the firmware takes time. execute it sequentially in the
probe function, it will block the system boot.
Using the kthread method will not block the system boot.
> > >
> > > > + if (IS_ERR(lt9611c->kthread)) {
> > > > + dev_err(dev, "Failed to create kernel thread\n");
> > > > + return PTR_ERR(lt9611c->kthread);
> > > > + }
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static void lt9611c_remove(struct i2c_client *client)
> > > > +{
> > > > + struct lt9611c *lt9611c = i2c_get_clientdata(client);
> > > > + struct device *dev = lt9611c->dev;
> > > > +
> > > > + kfree(lt9611c->edid_buf);
> > > > + disable_irq(client->irq);
> > > > + lt9611c_cleanup_resources(lt9611c);
> > > > + mutex_destroy(<9611c->ocm_lock);
> > > > + dev_info(dev, "remove driver\n");
> > > > +}
> > > > +
> > > > +static int lt9611c_bridge_suspend(struct device *dev)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > + int ret;
> > > > +
> > > > + dev_info(lt9611c->dev, "suspend\n");
> > > > + disable_irq(lt9611c->client->irq);
> > > > + ret = lt9611c_regulator_disable(lt9611c);
> > > > + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static int lt9611c_bridge_resume(struct device *dev)
> > > > +{
> > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > + int ret;
> > > > +
> > > > + ret = lt9611c_regulator_enable(lt9611c);
> > > > + lt9611c_reset(lt9611c);
> > > > + enable_irq(lt9611c->client->irq);
> > > > + dev_info(lt9611c->dev, "resume\n");
> > > > +
> > > > + return ret;
> > > > +}
> > > > +
> > > > +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
> > > > + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
> > > > + lt9611c_bridge_resume)
> > > > +};
> > > > +
> > > > +static struct i2c_device_id lt9611c_id[] = {
> > > > + { "lontium,lt9611c", 0 },
> > > > + { /* sentinel */ }
> > > > +};
> > > > +
> > > > +static const struct of_device_id lt9611c_match_table[] = {
> > > > + { .compatible = "lontium,lt9611c" },
> > >
> > > Your schema also had lt9611uxd
> > >
> > i will fix, thks
> >
> > > > + { /* sentinel */ }
> > > > +};
> > > > +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
> > > > +
> > > > +static struct i2c_driver lt9611c_driver = {
> > > > + .driver = {
> > > > + .name = "lt9611c",
> > > > + .of_match_table = lt9611c_match_table,
> > > > + .pm = <9611c_bridge_pm_ops,
> > > > + .dev_groups = lt9611c_attr_groups,
> > > > + },
> > > > + .probe = lt9611c_probe,
> > > > + .remove = lt9611c_remove,
> > > > + .id_table = lt9611c_id,
> > > > +};
> > > > +module_i2c_driver(lt9611c_driver);
> > > > +
> > > > +MODULE_AUTHOR("syyang <syyang@lontium.com>");
> > > > +MODULE_LICENSE("GPL v2");
> > > > +
> > > > +MODULE_FIRMWARE(FW_FILE);
> > > > --
> > > > 2.25.1
> > > >
> > >
> > > --
> > > With best wishes
> > > Dmitry
> >
> > Dmitry, thank you very much
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-05 2:55 ` 杨孙运
@ 2025-09-05 8:10 ` neil.armstrong
2025-09-05 8:58 ` 杨孙运
2025-09-05 14:10 ` Dmitry Baryshkov
1 sibling, 1 reply; 26+ messages in thread
From: neil.armstrong @ 2025-09-05 8:10 UTC (permalink / raw)
To: 杨孙运, Dmitry Baryshkov
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, rfoss,
Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, xmzhu, llzhang, rly
Hi,
First, thanks for submitting a driver for this bridge, it's highly appreciated
vendors makes this effort.
On 05/09/2025 04:55, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
>
>>
>> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
>>> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
>>>>
>>>> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
>>>>> The following changes are included:
>>>>>
>>>>> - Updated Kconfig and Makefile to include the new driver
>>>>> - Implementation of the bridge driver at
>>>>> drivers/gpu/drm/bridge/lontium-lt9611c.c
>>>>
>>>> This is really not interesting, it can be seen from the patch itself.
>>>> Please read Documentation/process/submitting-patches.rst.
>>>>
>>> Sorry, I will study submitting-patches.rst.
>>>
If you're unsure about the quality and acceptability of your patch,
please submit them as RFC.
If you have questions, you can discuss on IRC with linux developers
on OFTC #dri-devel for example.
<snip>
>>>>> +
>>>>> +static unsigned int bits_reverse(u32 in_val, u8 bits)
>>>>> +{
>>>>> + u32 out_val = 0;
>>>>> + u8 i;
>>>>> +
>>>>> + for (i = 0; i < bits; i++) {
>>>>> + if (in_val & (1 << i))
>>>>> + out_val |= 1 << (bits - 1 - i);
>>>>> + }
>>>>> +
>>>>> + return out_val;
>>>>> +}
>>>>> +
>>>>> +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
>>>>
>>>> Use library functions for that.
>>>>
>>>
>>> I'm not sure whether the algorithms in the llibrary functions are
>>> consistent with those designed in our chip.
>>> If either of them changes, it will cause the firmware updated to the
>>> chip to fail to run.
>>
>> CRC polynoms don't change that easily
>>
>>> I think it's safer to implement it using our own code.
>>
>> No, it's not.
>>
> If we calculate CRC_1 using the library function and then burn it
> together with the firmware into the chip, when the chip boot, it will
> use the internal hardware to calculate the firmware CRC_2.
> If CRC_1 is not equal to CRC_2, the chip will fail to boot. The
> library function will not be changed. I'm worried that the algorithm
> in our chip's hardware is different from the library function. I'll
> research it.
We very well know how checksum checking works, Linux has pretty solid
CRC library functions that can accomodate any polynomial & init values.
Please look at source/include/linux/crc8.h and use them accordingly.
If some changes would still be needed to generate the required CRC
then the library functions should be updated.
>
>>> I'll check it.
>>>
>>>>> +{
>>>>> + u8 width = type.width;
>>>>> + u32 poly = type.poly;
>>>>> + u32 crc = type.crc_init;
>>>>> + u32 xorout = type.xor_out;
>>>>> + bool refin = type.refin;
>>>>> + bool refout = type.refout;
>>>>> + u8 n;
>>>>> + u32 bits;
>>>>> + u32 data;
>>>>> + u8 i;
>>>>> +
>>>>> + n = (width < 8) ? 0 : (width - 8);
>>>>> + crc = (width < 8) ? (crc << (8 - width)) : crc;
>>>>> + bits = (width < 8) ? 0x80 : (1 << (width - 1));
>>>>> + poly = (width < 8) ? (poly << (8 - width)) : poly;
>>>>> + while (buf_len--) {
>>>>> + data = *(buf++);
>>>>> + if (refin)
>>>>> + data = bits_reverse(data, 8);
>>>>> + crc ^= (data << n);
>>>>> + for (i = 0; i < 8; i++) {
>>>>> + if (crc & bits)
>>>>> + crc = (crc << 1) ^ poly;
>>>>> + else
>>>>> + crc = crc << 1;
>>>>> + }
>>>>> + }
>>>>> + crc = (width < 8) ? (crc >> (8 - width)) : crc;
>>>>> + if (refout)
>>>>> + crc = bits_reverse(crc, width);
>>>>> + crc ^= xorout;
>>>>> +
>>>>> + return (crc & ((2 << (width - 1)) - 1));
>>>>> +}
>>>>> +
>>>>> +static u8 calculate_crc(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct crc_info type = {
>>>>> + .width = 8,
>>>>> + .poly = 0x31,
>>>>> + .crc_init = 0,
>>>>> + .xor_out = 0,
>>>>> + .refout = false,
>>>>> + .refin = false,
>>>>> + };
>>>>> + const u8 *upgrade_data;
>>>>> + u64 len;
>>>>> + u64 crc_size = FW_SIZE - 1;
>>>>> + u8 default_val = 0xFF;
>>>>> +
>>>>> + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
>>>>> + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
>>>>> + return 0;
>>>>> + }
>>>>> +
>>>>> + upgrade_data = lt9611c->fw->data;
>>>>> + len = lt9611c->fw->size;
>>>>> +
>>>>> + type.crc_init = get_crc(type, upgrade_data, len);
>>>>> +
>>>>> + crc_size -= len;
>>>>> + while (crc_size--)
>>>>> + type.crc_init = get_crc(type, &default_val, 1);
>>>>> +
>>>>> + return type.crc_init;
>>>>> +}
>>>>> +
>>>>> +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret = 0;
>>>>> +
>>>>> + ret = regmap_write(lt9611c->regmap, reg, val);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev,
>>>>> + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
>>>>> + lt9611c->client->addr, reg, ret);
>>>>> + }
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
>>>>
>>>> Drop these two wrappers, they provide no extra functionality.
>>>>
>>>
>>> I will consider fixing this issue. thanks.
Please avoid dead code and useless wrappers, and make debug code as minimal
as possible and print only when strictly needed, and consider using _dbg
prints.
>>>
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret = 0;
>>>>> + unsigned int tmp;
>>>>> +
>>>>> + if (!val)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + ret = regmap_read(lt9611c->regmap, reg, &tmp);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev,
>>>>> + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
>>>>> + lt9611c->client->addr, reg, ret);
>>>>> +
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + *val = (u8)tmp;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
>>>>> + unsigned int param_count, u8 *return_buffer,
>>>>> + unsigned int return_count)
>>>>> +{
>>>>> + int count, i;
>>>>> + u8 temp;
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0xDE, 0x01);
>>>>
>>>> - lowercase all hex values
>>>
>>> i will fix , thanks.
>>>
>>>> - use paged writes as implemented for LT9611 and LT9611UXC
>>>>
>>> Don't understand.
>>
>> Use 16-bit addressing as done by those two drivers. This way 0xff
>> becomes a page switch.
>>
> i will research it.
Regmap supports page switching internally, check out how the other lontium drivers are designed.
>
>>>
>>>>> +
>>>>> + count = 0;
>>>>> + do {
>>>>> + i2c_read_byte(lt9611c, 0xAE, &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;
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
>>>>> + }
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xDE, 0x02);
>>>>> +
>>>>> + count = 0;
>>>>> + do {
>>>>> + i2c_read_byte(lt9611c, 0xAE, &temp);
>>>>> + usleep_range(1000, 2000);
>>>>> + count++;
>>>>> + } while (count < 100 && temp != 0x02);
>>>>> +
>>>>> + if (temp != 0x02)
>>>>> + return -2;
>>>>> +
>>>>> + for (i = 0; i < return_count; i++)
>>>>> + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + /* ensure filesystem ready */
>>>>> + msleep(3000);
>>>>
>>>> No. If the firmware is necessary and it's not ready, return
>>>> -EPROBE_DEFER.
>>>>
>>> The firmware is unnecessary . This part of the code is for customers
>>> who need to upgrade the chip firmware.
>>>
>>> Due to the different designs of the platform, the firmware used by
>>> each customer may be different.
>>
>> Well... That's a very bad way to go. We have had this issue with
>> LT9611UXC at one of my previous jobs. Our customers have had various
>> kinds of issues because of the wrong firmware.
>>
>> Please provide some reference, which works in a DSI-to-HDMI case and
>> make it _tunable_ rather than requiring to replace the firmware
>> completely.
>>
> i will research it.
> Yes, you worked together with my colleagues to handle the issue of
> LT9611UXC. (At that time, you used dmitry.baryshkov@linaro.org)
>
>>>
>>> Therefore, when they need to update the firmware, they only need to
>>> compile the firmware into the /lib/firmware directory during the
>>> compilation
>>> process, and then burn the image into the platform.
>>>
>>> Once reboot platform, the firmware upgrade can be automatically completed.
>>
>> The firmware upgrade must be triggered by user, unless the FW is
>> completely empty.
>>
> Is it necessary for the authorities to insist on doing so?
If by authorities you mean the DRM Bridge Maintainers, then since I'm one
of the maintainers yes I insist you follow this scheme.
But as Dmitry said, if the bridge can work nominally without a firmware upgrade
then it's simpler to add the firmware update in a second time.
>
>>>
>>> When there is no need to upgrade the firmware, this part of the code
>>> will not affect the operation of the driver.
>>>
>>>>> + ret = request_firmware(<9611c->fw, FW_FILE, dev);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->fw->size > FW_SIZE - 1) {
>>>>> + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
>>>>> + lt9611c->fw = NULL;
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
>>>>> +
>>>>> + lt9611c->fw_crc = calculate_crc(lt9611c);
>>>>> +
>>>>> + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
>>>>
>>>> No spamming with the unnecessary info. If you want, print the version
>>>> of the firmware.
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0xEE, 0x01);
>>>>> + //fifo_rst_n
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
>>>>> + i2c_write_byte(lt9611c, 0x03, 0x3F);
>>>>> + i2c_write_byte(lt9611c, 0x03, 0xFF);
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0x5E, 0xC1);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x00);
>>>>> + i2c_write_byte(lt9611c, 0x59, 0x50);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x10);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0x5e, 0x5f);
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x20);
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
>>>>> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
>>>>> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
>>>>> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x10);
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_wren(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x04);
>>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_wrdi(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x08);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> + u8 flash_crc;
>>>>> +
>>>>> + if (!lt9611c)
>>>>> + return -EINVAL;
>>>>
>>>> How can it be NULL here?
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + lt9611c_config_parameters(lt9611c);
>>>>> + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
>>>>> +
>>>>> + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "failed to read flash crc\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
>>>>> + flash_crc, lt9611c->fw_crc);
>>>>
>>>> dev_dbg()
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + lt9611c_wrdi(lt9611c);
>>>>> +
>>>>> + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
>>>>> +}
>>>>> +
>>>>> +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + //fifo_rst_n
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
>>>>> + i2c_write_byte(lt9611c, 0x03, 0x3F);
>>>>> + i2c_write_byte(lt9611c, 0x03, 0xFF);
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0x5e, 0x40);
>>>>> + i2c_write_byte(lt9611c, 0x56, 0x05);
>>>>> + i2c_write_byte(lt9611c, 0x55, 0x25);
>>>>> + i2c_write_byte(lt9611c, 0x55, 0x01);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
>>>>> +
>>>>> + ret = i2c_read_byte(lt9611c, 0x5f, status);
>>>>> + if (ret < 0)
>>>>> + dev_err(dev, "failed to read flash register status\n");
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_block_erase(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + u32 i = 0;
>>>>> + u8 flash_status = 0;
>>>>> + u8 block_num = 0x00;
>>>>> + u32 flash_addr = 0x00;
>>>>> +
>>>>> + for (block_num = 0; block_num < 2; block_num++) {
>>>>> + flash_addr = (block_num * 0x008000);
>>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
>>>>> + i2c_write_byte(lt9611c, 0xEE, 0x01);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x04);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
>>>>> + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
>>>>> + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
>>>>> + i2c_write_byte(lt9611c, 0x5D, flash_addr);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x01);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
>>>>> + msleep(100);
>>>>> + i = 0;
>>>>> + while (1) {
>>>>> + read_flash_reg_status(lt9611c, &flash_status);
>>>>> + if ((flash_status & 0x01) == 0)
>>>>> + break;
>>>>> +
>>>>> + if (i > 50)
>>>>> + break;
>>>>> +
>>>>> + i++;
>>>>> + msleep(50);
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + dev_info(dev, "erase flash done.\n");
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0x51, 0x00);
>>>>> + i2c_write_byte(lt9611c, 0x55, 0xc0);
>>>>> + i2c_write_byte(lt9611c, 0x55, 0x80);
>>>>> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0x51, 0xff);
>>>>> + i2c_write_byte(lt9611c, 0x55, 0x80);
>>>>> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
>>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
>>>>> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
>>>>> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x30);
>>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> + int page = 0, num = 0, i = 0;
>>>>> + const u8 *data;
>>>>> + u64 size, index;
>>>>> + u8 value;
>>>>> +
>>>>> + data = lt9611c->fw->data;
>>>>> + size = lt9611c->fw->size;
>>>>> +
>>>>> + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
>>>>> +
>>>>> + if (page * LT_PAGE_SIZE > 64 * 1024) {
>>>>> + dev_err(dev, "firmware size out of range\n");
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
>>>>
>>>>
>>>> dev_dbg()
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + for (num = 0; num < page; num++) {
>>>>> + lt9611c_data_to_sram(lt9611c);
>>>>> +
>>>>> + for (i = 0; i < LT_PAGE_SIZE; i++) {
>>>>> + index = num * LT_PAGE_SIZE + i;
>>>>> + value = (index < size) ? data[index] : 0xFF;
>>>>> +
>>>>> + ret = i2c_write_byte(lt9611c, 0x59, value);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "write error at page %u, index %u\n", num, i);
>>>>> + return ret;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + lt9611c_wren(lt9611c);
>>>>> + lt9611c_sram_to_flash(lt9611c, addr);
>>>>> +
>>>>> + addr += LT_PAGE_SIZE;
>>>>> + }
>>>>> +
>>>>> + lt9611c_wrdi(lt9611c);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> + u8 crc;
>>>>> +
>>>>> + crc = lt9611c->fw_crc;
>>>>> + lt9611c_crc_to_sram(lt9611c);
>>>>> + ret = i2c_write_byte(lt9611c, 0x59, crc);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "failed to write CRC\n");
>>>>> + return -1;
>>>>> + }
>>>>> +
>>>>> + lt9611c_wren(lt9611c);
>>>>> + lt9611c_sram_to_flash(lt9611c, addr);
>>>>> + lt9611c_wrdi(lt9611c);
>>>>> +
>>>>> + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
>>>>
>>>> dev_dbg
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
>>>>
>>>> dev_dbg
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + lt9611c_config_parameters(lt9611c);
>>>>> + lt9611c_block_erase(lt9611c);
>>>>> +
>>>>> + ret = lt9611c_write_data(lt9611c, 0);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "Failed to write firmware data\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "Failed to write firmware CRC\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + u8 crc_result;
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
>>>>> + i2c_read_byte(lt9611c, 0x21, &crc_result);
>>>>> +
>>>>> + if (crc_result == lt9611c->fw_crc) {
>>>>> + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
>>>>
>>>> dev_dbg
>>>>
>>> i will fix, thanks
>>>
>>>>> + return 0;
>>>>> + }
>>>>> +
>>>>> + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
>>>>> + lt9611c->fw_crc, crc_result);
>>>>> + return -EIO;
>>>>> +}
>>>>> +
>>>>> +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
>>>>> +{
>>>>> + return container_of(bridge, struct lt9611c, bridge);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_lock(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + mutex_lock(<9611c->ocm_lock);
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_unlock(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0xee, 0x00);
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> +}
>>>>> +
>>>>> +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_id;
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> + u8 irq_status;
>>>>> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
>>>>> + u8 data[5];
>>>>> +
>>>>> + mutex_lock(<9611c->ocm_lock);
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_read_byte(lt9611c, 0x84, &irq_status);
>>>>> +
>>>>> + if (!(irq_status & BIT(0))) {
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> + return IRQ_HANDLED;
>>>>> + }
>>>>> + dev_info(dev, "HPD interrupt triggered.\n");
>>>>
>>>> Nice joke. dev_dbg().
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
>>>>> + usleep_range(10000, 12000);
>>>>
>>>> Why?
>>>>
>>> Our chip design specification requires that this be done when clearing
>>> the interrupt.
>>
>> Add a comment.
>>
> i will add comment, thks
>
>>>
>>>>> + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
>>>>> +
>>>>> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "failed to read HPD status\n");
>>>>> + } else {
>>>>> + lt9611c->hdmi_connected = (data[4] == 0x02);
>>>>> + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
>>>>
>>>> dev_dbg()
>>>>
>>> i will fix, thanks
>>>
>>>>> + }
>>>>> +
>>>>> + lt9611c->audio_status = lt9611c->hdmi_connected ?
>>>>> + connector_status_connected :
>>>>> + connector_status_disconnected;
>>>>
>>>> What is it being used for? Why do you need separate status for audio?
>>>>
>>> Used to update the connection status of the audio.
>>> The separate status indicators make it clearer for the readers.
>>>
>>>>> +
>>>>> + schedule_work(<9611c->work);
>>>>> +
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> +
>>>>> + return IRQ_HANDLED;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_hpd_work(struct work_struct *work)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
>>>>> + bool connected;
>>>>> +
>>>>> + mutex_lock(<9611c->ocm_lock);
>>>>> + connected = lt9611c->hdmi_connected;
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> +
>>>>> + drm_bridge_hpd_notify(<9611c->bridge,
>>>>> + connected ?
>>>>> + connector_status_connected :
>>>>> + connector_status_disconnected);
>>>>
>>>> Incorrect indentation.
>>>>
>>> ? The checkpatch.pl did not detect it.
>>
>> use --strict.
>>
> i will , thks
>
>>>
>>>>> +
>>>>> + lt9611c_audio_update_connector_status(lt9611c);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>> + msleep(20);
>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>>> + msleep(20);
>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
>>>>> + msleep(400);
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + lt9611c->supplies[0].supply = "vcc";
>>>>> + lt9611c->supplies[1].supply = "vdd";
>>>>> +
>>>>> + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + int ret;
>>>>> +
>>>>> + ret = regulator_enable(lt9611c->supplies[0].consumer);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> +
>>>>> + usleep_range(5000, 10000);
>>>>> +
>>>>> + ret = regulator_enable(lt9611c->supplies[1].consumer);
>>>>> + if (ret < 0) {
>>>>> + regulator_disable(lt9611c->supplies[0].consumer);
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + int ret;
>>>>> +
>>>>> + ret = regulator_disable(lt9611c->supplies[0].consumer);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> +
>>>>> + usleep_range(5000, 10000);
>>>>> +
>>>>> + ret = regulator_disable(lt9611c->supplies[1].consumer);
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
>>>>> + struct device_node *dsi_node)
>>>>> +{
>>>>> + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
>>>>> + struct mipi_dsi_device *dsi;
>>>>> + struct mipi_dsi_host *host;
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + host = of_find_mipi_dsi_host_by_node(dsi_node);
>>>>> + if (!host)
>>>>> + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
>>>>> +
>>>>> + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
>>>>> + if (IS_ERR(dsi)) {
>>>>> + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
>>>>> + struct drm_encoder *encoder,
>>>>> + enum drm_bridge_attach_flags flags)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
>>>>> +
>>>>> + return drm_bridge_attach(encoder, lt9611c->next_bridge,
>>>>> + bridge, flags);
>>>>> +}
>>>>> +
>>>>> +static enum drm_mode_status lt9611c_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)
>>>>
>>>> Use .hdmi_tmds_char_rate_valid() for that.
>>>>
>>> I will check and test, thanks
>>>
>>>>> + return MODE_OK;
>>>>> + else
>>>>> + return MODE_BAD;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
>>>>> + const struct drm_display_mode *mode,
>>>>> + const struct drm_display_mode *adj_mode)
>>>>
>>>> - Wrong indentation
>>> will fix, thanks
>>>
>>>> - mode_set callback is deprecated and should not be used for new
>>>> drivers.
>>>>
>>> I found that kernel 6.17 is still in use mode_set callback.
>>
>> Check the documentation in drm_bridge_funcs.
>>
> i will research.
>
>>>
>>>>> +{
>>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
>>>>> + struct device *dev = lt9611c->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;
>>>>> +
>>>>
>>>>> + 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, "Out video info:\n");
>>>>> + dev_info(dev,
>>>>> + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
>>>>> + h_total, hactive, hsync_len, hfront_porch, hback_porch);
>>>>> + dev_info(dev,
>>>>> + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
>>>>> + v_total, vactive, vsync_len, vfront_porch, vback_porch);
>>>>
>>>>
>>>> Fix indentation
>>> The indentation issue was not detected by checkpatch.pl.
>>
>> The indentation issue is detected by the brain and eye.
>>
> sorry, i will research.
>
>>>
>>>> Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
>>>> spammy.
>>>>
>>> i will fix, thanks
>>>
>>>>> +
>>>>> + 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(<9611c->ocm_lock);
>>>>> + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
>>>>> + if (ret)
>>>>> + dev_err(dev, "video set failed\n");
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> +}
>>>>> +
>>>>> +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
>>>>> + struct drm_connector *connector)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> + bool connected = false;
>>>>> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
>>>>> + u8 data[5];
>>>>> +
>>>>> + mutex_lock(<9611c->ocm_lock);
>>>>> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
>>>>> + ret);
>>>>> + } else {
>>>>> + connected = (data[4] == 0x02);
>>>>> + }
>>>>
>>>> THere is no need to put single-line statements in brackets. Drop those.
>>>>
>>> yes, i will fix, thks
>>>
>>>>> +
>>>>> + lt9611c->hdmi_connected = connected;
>>>>> +
>>>>> + if (lt9611c->hdmi_connected)
>>>>> + lt9611c->audio_status = connector_status_connected;
>>>>> + else
>>>>> + lt9611c->audio_status = connector_status_disconnected;
>>>>> +
>>>>> + mutex_unlock(<9611c->ocm_lock);
>>>>> +
>>>>> + return connected ? connector_status_connected :
>>>>> + connector_status_disconnected;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_read_edid(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret, i, bytes_to_copy, offset = 0;
>>>>> + u8 packets_num;
>>>>> + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
>>>>> + u8 return_edid_data[37];
>>>>> + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
>>>>> + u8 return_edid_byte_num[6];
>>>>> +
>>>>> + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "Failed to read EDID byte number\n");
>>>>> + lt9611c->edid_valid = false;
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
>>>>> +
>>>>> + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
>>>>> + lt9611c->edid_len : 0)) {
>>>>> + kfree(lt9611c->edid_buf);
>>>>> + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
>>>>> + if (!lt9611c->edid_buf) {
>>>>> + dev_err(dev, "Failed to allocate EDID buffer\n");
>>>>> + lt9611c->edid_len = 0;
>>>>> + lt9611c->edid_valid = false;
>>>>> + return -ENOMEM;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
>>>>> + (lt9611c->edid_len / 32);
>>>>> + for (i = 0; i < packets_num; i++) {
>>>>> + read_edid_data_cmd[4] = (u8)i;
>>>>> + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "Failed to read EDID packet %d\n", i);
>>>>> + lt9611c->edid_valid = false;
>>>>> + return -EIO;
>>>>> + }
>>>>> + offset = i * 32;
>>>>> + bytes_to_copy = min(32, lt9611c->edid_len - offset);
>>>>> + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
>>>>
>>>> Don't store EDID in the long-term structures. Read it on demand.
>>>>
>>> I will think about this issue.
>>>
>>>>> + }
>>>>> +
>>>>> + lt9611c->edid_valid = true;
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = data;
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + unsigned int total_blocks;
>>>>> + int ret;
>>>>> +
>>>>> + if (len > 128)
>>>>> + return -EINVAL;
>>>>> +
>>>>> + guard(mutex)(<9611c->ocm_lock);
>>>>> + if (block == 0 || !lt9611c->edid_valid) {
>>>>> + ret = lt9611c_read_edid(lt9611c);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "EDID read failed\n");
>>>>> + return ret;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + total_blocks = lt9611c->edid_len / 128;
>>>>> + if (!total_blocks) {
>>>>> + dev_err(dev, "No valid EDID blocks\n");
>>>>> + return -EIO;
>>>>> + }
>>>>> +
>>>>> + if (block >= total_blocks) {
>>>>> + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
>>>>> + block, total_blocks);
>>>>> + return -EINVAL;
>>>>> + }
>>>>> +
>>>>> + memcpy(buf, lt9611c->edid_buf + block * 128, len);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
>>>>> + struct drm_connector *connector)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
>>>>> +
>>>>> + usleep_range(10000, 20000);
>>>>
>>>> Why?
>>>>
>>> Delay for a while to ensure that EDID is ready.
>>
>> Your other chip had interrupt status to note that EDID is ready. I hope
>> you have that one too. Blindly calling usleep_range() is a bad idea.
>>
> Different chips have different logic. i will research it.
>
>>>
>>>>> + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
>>>>> +}
>>>>> +
>>>>> +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
>>>>> + .attach = lt9611c_bridge_attach,
>>>>> + .mode_valid = lt9611c_bridge_mode_valid,
>>>>> + .mode_set = lt9611c_bridge_mode_set,
>>>>> + .detect = lt9611c_bridge_detect,
>>>>> + .edid_read = lt9611c_bridge_edid_read,
>>>>> +};
>>>>> +
>>>>> +static int lt9611c_parse_dt(struct device *dev,
>>>>> + struct lt9611c *lt9611c)
>>>>> +{
>>>>> + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
>>>>> + if (!lt9611c->dsi0_node) {
>>>>> + dev_err(dev, "failed to get remote node for primary dsi\n");
>>>>> + return -ENODEV;
>>>>> + }
>>>>> +
>>>>> + lt9611c->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, <9611c->next_bridge);
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> +
>>>>> + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
>>>>> + if (IS_ERR(lt9611c->reset_gpio)) {
>>>>> + dev_err(dev, "failed to acquire reset gpio\n");
>>>>> + return PTR_ERR(lt9611c->reset_gpio);
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
>>>>> +{
>>>>> + u8 val;
>>>>> + u64 ver = 0;
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
>>>>> +
>>>>> + i2c_read_byte(lt9611c, 0x80, &val);
>>>>> + ver = val;
>>>>> +
>>>>> + i2c_read_byte(lt9611c, 0x81, &val);
>>>>> + ver = (ver << 8) | val;
>>>>> +
>>>>> + *version = ver;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + u8 val = 0;
>>>>> +
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
>>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
>>>>> + i2c_write_byte(lt9611c, 0xff, 0xe1);
>>>>> +
>>>>> + i2c_read_byte(lt9611c, 0x00, &val);
>>>>> + if (val != 0x23)
>>>>> + return -ENODEV;
>>>>> +
>>>>> + i2c_read_byte(lt9611c, 0x01, &val);
>>>>> + if (val != 0x06)
>>>>> + return -ENODEV;
>>>>> +
>>>>> + dev_info(dev, "ChipId = 0x2306\n");
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
>>>>> + struct hdmi_codec_daifmt *fmt,
>>>>> + struct hdmi_codec_params *hparms)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
>>>>> +
>>>>> + dev_info(lt9611c->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;
>>>>> + }
>>>>
>>>> Does that add anything on top of the limitations of hdmi-codec.c?
>>>>
>>> The parameters supported in the hdmi-codec.c may not be supported by
>>> my chip. Therefore, we can exclude the parameters that are not
>>> supported by the chip.
>>
>> Are they?
>>
> The firmware handles all parameter adaptation autonomously. This code
> merely needs to expose the chip's capabilities to hdmi-codec.c.
>
>>>
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_audio_shutdown(struct device *dev, void *data)
>>>>> +{
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_audio_startup(struct device *dev, void *data)
>>>>> +{
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + enum drm_connector_status status;
>>>>> +
>>>>> + status = lt9611c->audio_status;
>>>>> + if (lt9611c->plugged_cb && lt9611c->codec_dev)
>>>>> + lt9611c->plugged_cb(lt9611c->codec_dev,
>>>>> + status == connector_status_connected);
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
>>>>> + void *data,
>>>>> + hdmi_codec_plugged_cb fn,
>>>>> + struct device *codec_dev)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = data;
>>>>> +
>>>>> + lt9611c->plugged_cb = fn;
>>>>> + lt9611c->codec_dev = codec_dev;
>>>>> + lt9611c_audio_update_connector_status(lt9611c);
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct hdmi_codec_ops lt9611c_codec_ops = {
>>>>> + .hw_params = lt9611c_hdmi_hw_params,
>>>>> + .audio_shutdown = lt9611c_audio_shutdown,
>>>>> + .audio_startup = lt9611c_audio_startup,
>>>>> + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
>>>>> +};
>>>>
>>>> No, we have HDMI audio helpers for that. Drop this and use the helpers
>>>> instead.
>>>>
>>> ??? I don't understand.
>>
>> See <drm/display/drm_hdmi_audio_helper.h> and
>> https://lore.kernel.org/dri-devel/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com/
>>
> i will research, thks.
> Could you please share the latest driver file for lt9611uxc.c that you
> have written? (to this email: syyang@lontium.com)
https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/bridge/lontium-lt9611uxc.c
>
>>>
>>>>> +
>>>>> +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct hdmi_codec_pdata codec_data = {
>>>>> + .ops = <9611c_codec_ops,
>>>>> + .max_i2s_channels = 2,
>>>>> + .i2s = 1,
>>>>> + .data = lt9611c,
>>>>> + };
>>>>> +
>>>>> + lt9611c->audio_pdev =
>>>>> + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
>>>>> + PLATFORM_DEVID_AUTO,
>>>>> + &codec_data, sizeof(codec_data));
>>>>> +
>>>>> + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + if (lt9611c->audio_pdev) {
>>>>> + platform_device_unregister(lt9611c->audio_pdev);
>>>>> + lt9611c->audio_pdev = NULL;
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + int ret;
>>>>> +
>>>>> + lt9611c_lock(lt9611c);
>>>>> + ret = lt9611c_prepare_firmware_data(lt9611c);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
>>>>> + goto out;
>>>>> + }
>>>>> +
>>>>> + ret = lt9611c_firmware_upgrade(lt9611c);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "upgrade failure\n");
>>>>> + goto out;
>>>>> + }
>>>>> + lt9611c_reset(lt9611c);
>>>>> + ret = lt9611c_upgrade_result(lt9611c);
>>>>> + if (ret < 0)
>>>>> + goto out;
>>>>> +
>>>>> +out:
>>>>> + lt9611c_unlock(lt9611c);
>>>>> + lt9611c_reset(lt9611c);
>>>>> + if (lt9611c->fw) {
>>>>> + release_firmware(lt9611c->fw);
>>>>> + lt9611c->fw = NULL;
>>>>> + }
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
>>>>> + const char *buf, size_t len)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
>>>>> + int ret;
>>>>> +
>>>>> + ret = lt9611c_firmware_update_store(lt9611c);
>>>>
>>>> Inline
>>>>
>>> i will fix, thks
>>>
>>>>> + if (ret < 0)
>>>>> + return ret;
>>>>> + return len;
>>>>> +}
>>>>> +
>>>>> +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
>>>>> +
>>>>> + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
>>>>> +}
>>>>> +
>>>>> +static DEVICE_ATTR_RW(lt9611c_firmware);
>>>>> +
>>>>> +static struct attribute *lt9611c_attrs[] = {
>>>>> + &dev_attr_lt9611c_firmware.attr,
>>>>> + NULL,
>>>>> +};
>>>>> +
>>>>> +static const struct attribute_group lt9611c_attr_group = {
>>>>> + .attrs = lt9611c_attrs,
>>>>> +};
>>>>> +
>>>>> +static const struct attribute_group *lt9611c_attr_groups[] = {
>>>>> + <9611c_attr_group,
>>>>> + NULL,
>>>>> +};
>>>>> +
>>>>> +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
>>>>> +{
>>>>> + struct device *dev = lt9611c->dev;
>>>>> +
>>>>> + if (lt9611c->work_inited) {
>>>>> + cancel_work_sync(<9611c->work);
>>>>> + lt9611c->work_inited = false;
>>>>> + dev_err(dev, "work cancelled\n");
>>>>
>>>> Why???
>>>>
>>> ?? I don't understand.
>>
>> Why do you need to be so spammy?
>>
> i will fix, thks
>
>>>
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->bridge_added) {
>>>>> + drm_bridge_remove(<9611c->bridge);
>>>>> + lt9611c->bridge_added = false;
>>>>> + dev_err(dev, "DRM bridge removed\n");
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->regulators_enabled) {
>>>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
>>>>> + lt9611c->regulators_enabled = false;
>>>>> + dev_err(dev, "regulators disabled\n");
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->audio_pdev)
>>>>> + lt9611c_audio_exit(lt9611c);
>>>>> +
>>>>> + if (lt9611c->fw) {
>>>>
>>>> You definitely don't need firmware when the bridge is up and running.
>>>>
>>> The previous text has already described the working logic of the firmware.
>>
>> It's another topic: you are storing the firmware in memory while the
>> driver is bound. It's not necessary. You can release it after updating
>> it on the chip.
>>
> I understand what you mean.
> Based on the above conversation, your intention is that when the
> customer needs to upgrade the firmware, they should modify the
> comparison conditions of the version, then compile and burn the
> kernel, and then perform the firmware upgrade, just like the LT9611UXC
> driver. Instead of loading the firmware every time.
> My design intention is to avoid the need for recompiling the driver
> when upgrading. Instead, a file named "LT9611C.bin" can be directly
> sent to the "/lib/firmware" directory via scp. Then you can either
> perform a reboot for the upgrade or execute the command manually for
> the upgrade.
> Perhaps you are suggesting that we could follow the design approach of
> the LT9611UXC driver?
Yes no need to rebuild, just use sysfs to trigger an update.
>
>>>
>>>>> + release_firmware(lt9611c->fw);
>>>>> + lt9611c->fw = NULL;
>>>>> + dev_err(dev, "firmware released\n");
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->dsi0_node) {
>>>>> + of_node_put(lt9611c->dsi0_node);
>>>>> + lt9611c->dsi0_node = NULL;
>>>>> + dev_err(dev, "dsi0 node released\n");
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->dsi1_node) {
>>>>> + of_node_put(lt9611c->dsi1_node);
>>>>> + lt9611c->dsi1_node = NULL;
>>>>> + dev_err(dev, "dsi1 node released\n");
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_main(void *data)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = data;
>>>>> + struct device *dev = lt9611c->dev;
>>>>> + struct i2c_client *client = lt9611c->client;
>>>>> + int ret;
>>>>> +
>>>>> + lt9611c->work_inited = false;
>>>>> + lt9611c->bridge_added = false;
>>>>> + lt9611c->regulators_enabled = false;
>>>>> +
>>>>> + ret = lt9611c_parse_dt(dev, lt9611c);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "failed to parse device tree\n");
>>>>> + return ret;
>>>>> + }
>>>>> +
>>>>> + ret = lt9611c_gpio_init(lt9611c);
>>>>> + if (ret < 0)
>>>>> + goto err_cleanup;
>>>>> +
>>>>> + ret = lt9611c_regulator_init(lt9611c);
>>>>> + if (ret < 0)
>>>>> + goto err_cleanup;
>>>>> +
>>>>> + ret = lt9611c_regulator_enable(lt9611c);
>>>>> + if (ret)
>>>>> + goto err_cleanup;
>>>>> +
>>>>> + lt9611c->regulators_enabled = true;
>>>>> +
>>>>> + lt9611c_reset(lt9611c);
>>>>> +
>>>>> + ret = lt9611c_read_chipid(lt9611c);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "failed to read chip id.\n");
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + lt9611c_lock(lt9611c);
>>>>> + lt9611c_read_version(lt9611c, <9611c->fw_version);
>>>>> +
>>>>> + ret = lt9611c_prepare_firmware_data(lt9611c);
>>>>> + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
>>>>> + dev_info(dev, "firmware upgrade needed\n");
>>>>> +
>>>>> + ret = lt9611c_firmware_upgrade(lt9611c);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "firmware upgrade failed\n");
>>>>> + lt9611c_unlock(lt9611c);
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + lt9611c_reset(lt9611c);
>>>>> + ret = lt9611c_upgrade_result(lt9611c);
>>>>> + if (ret < 0) {
>>>>> + lt9611c_unlock(lt9611c);
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + lt9611c_read_version(lt9611c, <9611c->fw_version);
>>>>> + lt9611c_unlock(lt9611c);
>>>>> +
>>>>> + } else {
>>>>> + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
>>>>> + lt9611c_unlock(lt9611c);
>>>>> + }
>>>>> +
>>>>> + if (lt9611c->fw) {
>>>>> + release_firmware(lt9611c->fw);
>>>>> + lt9611c->fw = NULL;
>>>>> + }
>>>>> + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
>>>>> +
>>>>> + INIT_WORK(<9611c->work, lt9611c_hpd_work);
>>>>> + lt9611c->work_inited = true;
>>>>> +
>>>>> + if (!client->irq) {
>>>>> + dev_err(dev, "failed to get INTP IRQ\n");
>>>>> + ret = -ENODEV;
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
>>>>> + lt9611c_irq_thread_handler,
>>>>> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
>>>>> + IRQF_NO_AUTOEN,
>>>>> + "lt9611c", lt9611c);
>>>>> + if (ret) {
>>>>> + dev_err(dev, "failed to request irq\n");
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + lt9611c->bridge.funcs = <9611c_bridge_funcs;
>>>>> + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
>>>>> + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
>>>>> + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
>>>>> +
>>>>> + drm_bridge_add(<9611c->bridge);
>>>>> + lt9611c->bridge_added = true;
>>>>
>>>> No unnecessary flags, please. Implement proper cleanup path, unwinding
>>>> resources one by one.
>>>>
>>> I will consider this issue. thks
>>>
>>>>> +
>>>>> + /* Attach primary DSI */
>>>>> + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
>>>>> + if (IS_ERR(lt9611c->dsi0)) {
>>>>> + ret = PTR_ERR(lt9611c->dsi0);
>>>>> + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + /* Attach secondary DSI, if specified */
>>>>> + if (lt9611c->dsi1_node) {
>>>>> + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
>>>>> + if (IS_ERR(lt9611c->dsi1)) {
>>>>> + ret = PTR_ERR(lt9611c->dsi1);
>>>>> + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + lt9611c->audio_status = connector_status_disconnected;
>>>>> +
>>>>> + ret = lt9611c_audio_init(dev, lt9611c);
>>>>> + if (ret < 0) {
>>>>> + dev_err(dev, "audio init failed\n");
>>>>> + goto err_cleanup;
>>>>> + }
>>>>> +
>>>>> + lt9611c_reset(lt9611c);
>>>>> + enable_irq(lt9611c->client->irq);
>>>>> +
>>>>> + return 0;
>>>>> +
>>>>> +err_cleanup:
>>>>> + lt9611c_cleanup_resources(lt9611c);
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_probe(struct i2c_client *client)
>>>>> +{
>>>>> + struct lt9611c *lt9611c;
>>>>> + struct device *dev = &client->dev;
>>>>> +
>>>>> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
>>>>> + dev_err(dev, "device doesn't support I2C\n");
>>>>> + return -ENODEV;
>>>>> + }
>>>>> +
>>>>> + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
>>>>
>>>> devm_drm_bridge_alloc()
>>>>
>>> i will fix, thks
>>>
>>>>> + if (!lt9611c)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + lt9611c->dev = dev;
>>>>> + lt9611c->client = client;
>>>>> + mutex_init(<9611c->ocm_lock);
>>>>> +
>>>>> + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
>>>>> + if (IS_ERR(lt9611c->regmap)) {
>>>>> + dev_err(dev, "regmap i2c init failed\n");
>>>>> + return PTR_ERR(lt9611c->regmap);
>>>>> + }
>>>>> +
>>>>> + i2c_set_clientdata(client, lt9611c);
>>>>> +
>>>>> + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
>>>>
>>>> Why do you need extra kthread for that???
>
> Upgrading the firmware takes time. execute it sequentially in the
> probe function, it will block the system boot.
> Using the kthread method will not block the system boot.
Just follow the drivers/gpu/drm/bridge/lontium-lt9611uxc.c way to do this.
>
>>>>
>>>>> + if (IS_ERR(lt9611c->kthread)) {
>>>>> + dev_err(dev, "Failed to create kernel thread\n");
>>>>> + return PTR_ERR(lt9611c->kthread);
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static void lt9611c_remove(struct i2c_client *client)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = i2c_get_clientdata(client);
>>>>> + struct device *dev = lt9611c->dev;
>>>>> +
>>>>> + kfree(lt9611c->edid_buf);
>>>>> + disable_irq(client->irq);
>>>>> + lt9611c_cleanup_resources(lt9611c);
>>>>> + mutex_destroy(<9611c->ocm_lock);
>>>>> + dev_info(dev, "remove driver\n");
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_bridge_suspend(struct device *dev)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
>>>>> + int ret;
>>>>> +
>>>>> + dev_info(lt9611c->dev, "suspend\n");
>>>>> + disable_irq(lt9611c->client->irq);
>>>>> + ret = lt9611c_regulator_disable(lt9611c);
>>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static int lt9611c_bridge_resume(struct device *dev)
>>>>> +{
>>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
>>>>> + int ret;
>>>>> +
>>>>> + ret = lt9611c_regulator_enable(lt9611c);
>>>>> + lt9611c_reset(lt9611c);
>>>>> + enable_irq(lt9611c->client->irq);
>>>>> + dev_info(lt9611c->dev, "resume\n");
>>>>> +
>>>>> + return ret;
>>>>> +}
>>>>> +
>>>>> +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
>>>>> + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
>>>>> + lt9611c_bridge_resume)
>>>>> +};
>>>>> +
>>>>> +static struct i2c_device_id lt9611c_id[] = {
>>>>> + { "lontium,lt9611c", 0 },
>>>>> + { /* sentinel */ }
>>>>> +};
>>>>> +
>>>>> +static const struct of_device_id lt9611c_match_table[] = {
>>>>> + { .compatible = "lontium,lt9611c" },
>>>>
>>>> Your schema also had lt9611uxd
>>>>
>>> i will fix, thks
>>>
>>>>> + { /* sentinel */ }
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
>>>>> +
>>>>> +static struct i2c_driver lt9611c_driver = {
>>>>> + .driver = {
>>>>> + .name = "lt9611c",
>>>>> + .of_match_table = lt9611c_match_table,
>>>>> + .pm = <9611c_bridge_pm_ops,
>>>>> + .dev_groups = lt9611c_attr_groups,
>>>>> + },
>>>>> + .probe = lt9611c_probe,
>>>>> + .remove = lt9611c_remove,
>>>>> + .id_table = lt9611c_id,
>>>>> +};
>>>>> +module_i2c_driver(lt9611c_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("syyang <syyang@lontium.com>");
>>>>> +MODULE_LICENSE("GPL v2");
>>>>> +
>>>>> +MODULE_FIRMWARE(FW_FILE);
>>>>> --
>>>>> 2.25.1
>>>>>
>>>>
>>>> --
>>>> With best wishes
>>>> Dmitry
>>>
>>> Dmitry, thank you very much
>>
>> --
>> With best wishes
>> Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-05 8:10 ` neil.armstrong
@ 2025-09-05 8:58 ` 杨孙运
2025-09-05 14:24 ` Dmitry Baryshkov
0 siblings, 1 reply; 26+ messages in thread
From: 杨孙运 @ 2025-09-05 8:58 UTC (permalink / raw)
To: Neil Armstrong
Cc: Dmitry Baryshkov, syyang, robh, krzk+dt, conor+dt, andrzej.hajda,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rlyu, xbpeng
HI,
As a vendors , we have begun to attempt to contribute to the Linux,
and we are very willing to do so.
there are still many rules that we don't understand and need to learn.
<neil.armstrong@linaro.org> 于2025年9月5日周五 16:10写道:
>
> Hi,
>
> First, thanks for submitting a driver for this bridge, it's highly appreciated
> vendors makes this effort.
>
> On 05/09/2025 04:55, 杨孙运 wrote:
> > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
> >
> >>
> >> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> >>> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> >>>>
> >>>> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> >>>>> The following changes are included:
> >>>>>
> >>>>> - Updated Kconfig and Makefile to include the new driver
> >>>>> - Implementation of the bridge driver at
> >>>>> drivers/gpu/drm/bridge/lontium-lt9611c.c
> >>>>
> >>>> This is really not interesting, it can be seen from the patch itself.
> >>>> Please read Documentation/process/submitting-patches.rst.
> >>>>
> >>> Sorry, I will study submitting-patches.rst.
> >>>
>
> If you're unsure about the quality and acceptability of your patch,
> please submit them as RFC.
>
> If you have questions, you can discuss on IRC with linux developers
> on OFTC #dri-devel for example.
>
we will study it, thks.
> <snip>
>
> >>>>> +
> >>>>> +static unsigned int bits_reverse(u32 in_val, u8 bits)
> >>>>> +{
> >>>>> + u32 out_val = 0;
> >>>>> + u8 i;
> >>>>> +
> >>>>> + for (i = 0; i < bits; i++) {
> >>>>> + if (in_val & (1 << i))
> >>>>> + out_val |= 1 << (bits - 1 - i);
> >>>>> + }
> >>>>> +
> >>>>> + return out_val;
> >>>>> +}
> >>>>> +
> >>>>> +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
> >>>>
> >>>> Use library functions for that.
> >>>>
> >>>
> >>> I'm not sure whether the algorithms in the llibrary functions are
> >>> consistent with those designed in our chip.
> >>> If either of them changes, it will cause the firmware updated to the
> >>> chip to fail to run.
> >>
> >> CRC polynoms don't change that easily
> >>
> >>> I think it's safer to implement it using our own code.
> >>
> >> No, it's not.
> >>
> > If we calculate CRC_1 using the library function and then burn it
> > together with the firmware into the chip, when the chip boot, it will
> > use the internal hardware to calculate the firmware CRC_2.
> > If CRC_1 is not equal to CRC_2, the chip will fail to boot. The
> > library function will not be changed. I'm worried that the algorithm
> > in our chip's hardware is different from the library function. I'll
> > research it.
>
> We very well know how checksum checking works, Linux has pretty solid
> CRC library functions that can accomodate any polynomial & init values.
>
> Please look at source/include/linux/crc8.h and use them accordingly.
>
> If some changes would still be needed to generate the required CRC
> then the library functions should be updated.
>
> >
> >>> I'll check it.
> >>>
> >>>>> +{
> >>>>> + u8 width = type.width;
> >>>>> + u32 poly = type.poly;
> >>>>> + u32 crc = type.crc_init;
> >>>>> + u32 xorout = type.xor_out;
> >>>>> + bool refin = type.refin;
> >>>>> + bool refout = type.refout;
> >>>>> + u8 n;
> >>>>> + u32 bits;
> >>>>> + u32 data;
> >>>>> + u8 i;
> >>>>> +
> >>>>> + n = (width < 8) ? 0 : (width - 8);
> >>>>> + crc = (width < 8) ? (crc << (8 - width)) : crc;
> >>>>> + bits = (width < 8) ? 0x80 : (1 << (width - 1));
> >>>>> + poly = (width < 8) ? (poly << (8 - width)) : poly;
> >>>>> + while (buf_len--) {
> >>>>> + data = *(buf++);
> >>>>> + if (refin)
> >>>>> + data = bits_reverse(data, 8);
> >>>>> + crc ^= (data << n);
> >>>>> + for (i = 0; i < 8; i++) {
> >>>>> + if (crc & bits)
> >>>>> + crc = (crc << 1) ^ poly;
> >>>>> + else
> >>>>> + crc = crc << 1;
> >>>>> + }
> >>>>> + }
> >>>>> + crc = (width < 8) ? (crc >> (8 - width)) : crc;
> >>>>> + if (refout)
> >>>>> + crc = bits_reverse(crc, width);
> >>>>> + crc ^= xorout;
> >>>>> +
> >>>>> + return (crc & ((2 << (width - 1)) - 1));
> >>>>> +}
> >>>>> +
> >>>>> +static u8 calculate_crc(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct crc_info type = {
> >>>>> + .width = 8,
> >>>>> + .poly = 0x31,
> >>>>> + .crc_init = 0,
> >>>>> + .xor_out = 0,
> >>>>> + .refout = false,
> >>>>> + .refin = false,
> >>>>> + };
> >>>>> + const u8 *upgrade_data;
> >>>>> + u64 len;
> >>>>> + u64 crc_size = FW_SIZE - 1;
> >>>>> + u8 default_val = 0xFF;
> >>>>> +
> >>>>> + if (!lt9611c->fw || !lt9611c->fw->data || lt9611c->fw->size == 0) {
> >>>>> + dev_err(lt9611c->dev, "firmware data not available for CRC\n");
> >>>>> + return 0;
> >>>>> + }
> >>>>> +
> >>>>> + upgrade_data = lt9611c->fw->data;
> >>>>> + len = lt9611c->fw->size;
> >>>>> +
> >>>>> + type.crc_init = get_crc(type, upgrade_data, len);
> >>>>> +
> >>>>> + crc_size -= len;
> >>>>> + while (crc_size--)
> >>>>> + type.crc_init = get_crc(type, &default_val, 1);
> >>>>> +
> >>>>> + return type.crc_init;
> >>>>> +}
> >>>>> +
> >>>>> +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret = 0;
> >>>>> +
> >>>>> + ret = regmap_write(lt9611c->regmap, reg, val);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev,
> >>>>> + "regmap_write error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> >>>>> + lt9611c->client->addr, reg, ret);
> >>>>> + }
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
> >>>>
> >>>> Drop these two wrappers, they provide no extra functionality.
> >>>>
> >>>
> >>> I will consider fixing this issue. thanks.
>
> Please avoid dead code and useless wrappers, and make debug code as minimal
> as possible and print only when strictly needed, and consider using _dbg
> prints.
>
> >>>
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret = 0;
> >>>>> + unsigned int tmp;
> >>>>> +
> >>>>> + if (!val)
> >>>>> + return -EINVAL;
> >>>>> +
> >>>>> + ret = regmap_read(lt9611c->regmap, reg, &tmp);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev,
> >>>>> + "regmap_read error: i2c addr=0x%02x, reg addr=0x%02x, error=%d",
> >>>>> + lt9611c->client->addr, reg, ret);
> >>>>> +
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + *val = (u8)tmp;
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> >>>>> + unsigned int param_count, u8 *return_buffer,
> >>>>> + unsigned int return_count)
> >>>>> +{
> >>>>> + int count, i;
> >>>>> + u8 temp;
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0xDE, 0x01);
> >>>>
> >>>> - lowercase all hex values
> >>>
> >>> i will fix , thanks.
> >>>
> >>>> - use paged writes as implemented for LT9611 and LT9611UXC
> >>>>
> >>> Don't understand.
> >>
> >> Use 16-bit addressing as done by those two drivers. This way 0xff
> >> becomes a page switch.
> >>
> > i will research it.
>
> Regmap supports page switching internally, check out how the other lontium drivers are designed.
>
> >
> >>>
> >>>>> +
> >>>>> + count = 0;
> >>>>> + do {
> >>>>> + i2c_read_byte(lt9611c, 0xAE, &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;
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xB0 + i, params[i]);
> >>>>> + }
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xDE, 0x02);
> >>>>> +
> >>>>> + count = 0;
> >>>>> + do {
> >>>>> + i2c_read_byte(lt9611c, 0xAE, &temp);
> >>>>> + usleep_range(1000, 2000);
> >>>>> + count++;
> >>>>> + } while (count < 100 && temp != 0x02);
> >>>>> +
> >>>>> + if (temp != 0x02)
> >>>>> + return -2;
> >>>>> +
> >>>>> + for (i = 0; i < return_count; i++)
> >>>>> + i2c_read_byte(lt9611c, 0x85 + i, &return_buffer[i]);
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + /* ensure filesystem ready */
> >>>>> + msleep(3000);
> >>>>
> >>>> No. If the firmware is necessary and it's not ready, return
> >>>> -EPROBE_DEFER.
> >>>>
> >>> The firmware is unnecessary . This part of the code is for customers
> >>> who need to upgrade the chip firmware.
> >>>
> >>> Due to the different designs of the platform, the firmware used by
> >>> each customer may be different.
> >>
> >> Well... That's a very bad way to go. We have had this issue with
> >> LT9611UXC at one of my previous jobs. Our customers have had various
> >> kinds of issues because of the wrong firmware.
> >>
> >> Please provide some reference, which works in a DSI-to-HDMI case and
> >> make it _tunable_ rather than requiring to replace the firmware
> >> completely.
> >>
> > i will research it.
> > Yes, you worked together with my colleagues to handle the issue of
> > LT9611UXC. (At that time, you used dmitry.baryshkov@linaro.org)
> >
> >>>
> >>> Therefore, when they need to update the firmware, they only need to
> >>> compile the firmware into the /lib/firmware directory during the
> >>> compilation
> >>> process, and then burn the image into the platform.
> >>>
> >>> Once reboot platform, the firmware upgrade can be automatically completed.
> >>
> >> The firmware upgrade must be triggered by user, unless the FW is
> >> completely empty.
> >>
> > Is it necessary for the authorities to insist on doing so?
>
> If by authorities you mean the DRM Bridge Maintainers, then since I'm one
> of the maintainers yes I insist you follow this scheme.
>
> But as Dmitry said, if the bridge can work nominally without a firmware upgrade
> then it's simpler to add the firmware update in a second time.
>
> >
> >>>
> >>> When there is no need to upgrade the firmware, this part of the code
> >>> will not affect the operation of the driver.
> >>>
> >>>>> + ret = request_firmware(<9611c->fw, FW_FILE, dev);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->fw->size > FW_SIZE - 1) {
> >>>>> + dev_err(dev, "firmware too large (%zu > %d)\n", lt9611c->fw->size, FW_SIZE - 1);
> >>>>> + lt9611c->fw = NULL;
> >>>>> + return -EINVAL;
> >>>>> + }
> >>>>> +
> >>>>> + dev_info(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
> >>>>> +
> >>>>> + lt9611c->fw_crc = calculate_crc(lt9611c);
> >>>>> +
> >>>>> + dev_info(dev, "LT9611C.bin crc: 0x%02X\n", lt9611c->fw_crc);
> >>>>
> >>>> No spamming with the unnecessary info. If you want, print the version
> >>>> of the firmware.
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_config_parameters(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0xEE, 0x01);
> >>>>> + //fifo_rst_n
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> >>>>> + i2c_write_byte(lt9611c, 0x03, 0x3F);
> >>>>> + i2c_write_byte(lt9611c, 0x03, 0xFF);
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0x5E, 0xC1);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x00);
> >>>>> + i2c_write_byte(lt9611c, 0x59, 0x50);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x10);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_flash_to_fifo(struct lt9611c *lt9611c, u64 addr)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0x5e, 0x5f);
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x20);
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> >>>>> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> >>>>> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> >>>>> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x10);
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_wren(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x04);
> >>>>> + i2c_write_byte(lt9611c, 0x5a, 0x00);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_wrdi(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x08);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_upgrade_judgment(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> + u8 flash_crc;
> >>>>> +
> >>>>> + if (!lt9611c)
> >>>>> + return -EINVAL;
> >>>>
> >>>> How can it be NULL here?
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + lt9611c_config_parameters(lt9611c);
> >>>>> + lt9611c_flash_to_fifo(lt9611c, FW_SIZE - 1);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
> >>>>> +
> >>>>> + ret = i2c_read_byte(lt9611c, 0x5f, &flash_crc);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "failed to read flash crc\n");
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + dev_info(dev, "flash firmware crc=0x%02X, expected crc=0x%02X",
> >>>>> + flash_crc, lt9611c->fw_crc);
> >>>>
> >>>> dev_dbg()
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + lt9611c_wrdi(lt9611c);
> >>>>> +
> >>>>> + return (flash_crc == lt9611c->fw_crc) ? NOT_UPGRADE : UPGRADE;
> >>>>> +}
> >>>>> +
> >>>>> +static int read_flash_reg_status(struct lt9611c *lt9611c, u8 *status)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + //fifo_rst_n
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE1);
> >>>>> + i2c_write_byte(lt9611c, 0x03, 0x3F);
> >>>>> + i2c_write_byte(lt9611c, 0x03, 0xFF);
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0x5e, 0x40);
> >>>>> + i2c_write_byte(lt9611c, 0x56, 0x05);
> >>>>> + i2c_write_byte(lt9611c, 0x55, 0x25);
> >>>>> + i2c_write_byte(lt9611c, 0x55, 0x01);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
> >>>>> +
> >>>>> + ret = i2c_read_byte(lt9611c, 0x5f, status);
> >>>>> + if (ret < 0)
> >>>>> + dev_err(dev, "failed to read flash register status\n");
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_block_erase(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + u32 i = 0;
> >>>>> + u8 flash_status = 0;
> >>>>> + u8 block_num = 0x00;
> >>>>> + u32 flash_addr = 0x00;
> >>>>> +
> >>>>> + for (block_num = 0; block_num < 2; block_num++) {
> >>>>> + flash_addr = (block_num * 0x008000);
> >>>>> + i2c_write_byte(lt9611c, 0xFF, 0xE0);
> >>>>> + i2c_write_byte(lt9611c, 0xEE, 0x01);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x04);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> >>>>> + i2c_write_byte(lt9611c, 0x5B, flash_addr >> 16);
> >>>>> + i2c_write_byte(lt9611c, 0x5C, flash_addr >> 8);
> >>>>> + i2c_write_byte(lt9611c, 0x5D, flash_addr);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x01);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> >>>>> + msleep(100);
> >>>>> + i = 0;
> >>>>> + while (1) {
> >>>>> + read_flash_reg_status(lt9611c, &flash_status);
> >>>>> + if ((flash_status & 0x01) == 0)
> >>>>> + break;
> >>>>> +
> >>>>> + if (i > 50)
> >>>>> + break;
> >>>>> +
> >>>>> + i++;
> >>>>> + msleep(50);
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + dev_info(dev, "erase flash done.\n");
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0x51, 0x00);
> >>>>> + i2c_write_byte(lt9611c, 0x55, 0xc0);
> >>>>> + i2c_write_byte(lt9611c, 0x55, 0x80);
> >>>>> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0x51, 0xff);
> >>>>> + i2c_write_byte(lt9611c, 0x55, 0x80);
> >>>>> + i2c_write_byte(lt9611c, 0x5e, 0xc0);
> >>>>> + i2c_write_byte(lt9611c, 0x58, 0x21);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, u64 addr)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0x5b, ((addr & 0xFF0000) >> 16));
> >>>>> + i2c_write_byte(lt9611c, 0x5c, ((addr & 0xFF00) >> 8));
> >>>>> + i2c_write_byte(lt9611c, 0x5d, (addr & 0xFF));
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x30);
> >>>>> + i2c_write_byte(lt9611c, 0x5A, 0x00);
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_write_data(struct lt9611c *lt9611c, u64 addr)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> + int page = 0, num = 0, i = 0;
> >>>>> + const u8 *data;
> >>>>> + u64 size, index;
> >>>>> + u8 value;
> >>>>> +
> >>>>> + data = lt9611c->fw->data;
> >>>>> + size = lt9611c->fw->size;
> >>>>> +
> >>>>> + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> >>>>> +
> >>>>> + if (page * LT_PAGE_SIZE > 64 * 1024) {
> >>>>> + dev_err(dev, "firmware size out of range\n");
> >>>>> + return -EINVAL;
> >>>>> + }
> >>>>> +
> >>>>> + dev_info(dev, "%u pages, total size %llu byte\n", page, size);
> >>>>
> >>>>
> >>>> dev_dbg()
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + for (num = 0; num < page; num++) {
> >>>>> + lt9611c_data_to_sram(lt9611c);
> >>>>> +
> >>>>> + for (i = 0; i < LT_PAGE_SIZE; i++) {
> >>>>> + index = num * LT_PAGE_SIZE + i;
> >>>>> + value = (index < size) ? data[index] : 0xFF;
> >>>>> +
> >>>>> + ret = i2c_write_byte(lt9611c, 0x59, value);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "write error at page %u, index %u\n", num, i);
> >>>>> + return ret;
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_wren(lt9611c);
> >>>>> + lt9611c_sram_to_flash(lt9611c, addr);
> >>>>> +
> >>>>> + addr += LT_PAGE_SIZE;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_wrdi(lt9611c);
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_write_crc(struct lt9611c *lt9611c, u64 addr)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> + u8 crc;
> >>>>> +
> >>>>> + crc = lt9611c->fw_crc;
> >>>>> + lt9611c_crc_to_sram(lt9611c);
> >>>>> + ret = i2c_write_byte(lt9611c, 0x59, crc);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "failed to write CRC\n");
> >>>>> + return -1;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_wren(lt9611c);
> >>>>> + lt9611c_sram_to_flash(lt9611c, addr);
> >>>>> + lt9611c_wrdi(lt9611c);
> >>>>> +
> >>>>> + dev_info(dev, "CRC 0x%02X written to flash at addr 0x%llX\n", crc, addr);
> >>>>
> >>>> dev_dbg
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + dev_info(dev, "starting firmware upgrade, size: %zu bytes\n", lt9611c->fw->size);
> >>>>
> >>>> dev_dbg
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + lt9611c_config_parameters(lt9611c);
> >>>>> + lt9611c_block_erase(lt9611c);
> >>>>> +
> >>>>> + ret = lt9611c_write_data(lt9611c, 0);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "Failed to write firmware data\n");
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + ret = lt9611c_write_crc(lt9611c, FW_SIZE - 1);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "Failed to write firmware CRC\n");
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_upgrade_result(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + u8 crc_result;
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
> >>>>> + i2c_read_byte(lt9611c, 0x21, &crc_result);
> >>>>> +
> >>>>> + if (crc_result == lt9611c->fw_crc) {
> >>>>> + dev_info(dev, "LT9611C firmware upgrade success, CRC=0x%02X\n", crc_result);
> >>>>
> >>>> dev_dbg
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> + return 0;
> >>>>> + }
> >>>>> +
> >>>>> + dev_err(dev, "LT9611C firmware upgrade failed, expected CRC=0x%02X, read CRC=0x%02X\n",
> >>>>> + lt9611c->fw_crc, crc_result);
> >>>>> + return -EIO;
> >>>>> +}
> >>>>> +
> >>>>> +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
> >>>>> +{
> >>>>> + return container_of(bridge, struct lt9611c, bridge);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_lock(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + mutex_lock(<9611c->ocm_lock);
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_unlock(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0xee, 0x00);
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> +}
> >>>>> +
> >>>>> +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_id;
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> + u8 irq_status;
> >>>>> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> >>>>> + u8 data[5];
> >>>>> +
> >>>>> + mutex_lock(<9611c->ocm_lock);
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_read_byte(lt9611c, 0x84, &irq_status);
> >>>>> +
> >>>>> + if (!(irq_status & BIT(0))) {
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> + return IRQ_HANDLED;
> >>>>> + }
> >>>>> + dev_info(dev, "HPD interrupt triggered.\n");
> >>>>
> >>>> Nice joke. dev_dbg().
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xdf, irq_status & BIT(0));
> >>>>> + usleep_range(10000, 12000);
> >>>>
> >>>> Why?
> >>>>
> >>> Our chip design specification requires that this be done when clearing
> >>> the interrupt.
> >>
> >> Add a comment.
> >>
> > i will add comment, thks
> >
> >>>
> >>>>> + i2c_write_byte(lt9611c, 0xdf, irq_status & (~BIT(0)));
> >>>>> +
> >>>>> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "failed to read HPD status\n");
> >>>>> + } else {
> >>>>> + lt9611c->hdmi_connected = (data[4] == 0x02);
> >>>>> + dev_info(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
> >>>>
> >>>> dev_dbg()
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->audio_status = lt9611c->hdmi_connected ?
> >>>>> + connector_status_connected :
> >>>>> + connector_status_disconnected;
> >>>>
> >>>> What is it being used for? Why do you need separate status for audio?
> >>>>
> >>> Used to update the connection status of the audio.
> >>> The separate status indicators make it clearer for the readers.
> >>>
> >>>>> +
> >>>>> + schedule_work(<9611c->work);
> >>>>> +
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> +
> >>>>> + return IRQ_HANDLED;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_hpd_work(struct work_struct *work)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work);
> >>>>> + bool connected;
> >>>>> +
> >>>>> + mutex_lock(<9611c->ocm_lock);
> >>>>> + connected = lt9611c->hdmi_connected;
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> +
> >>>>> + drm_bridge_hpd_notify(<9611c->bridge,
> >>>>> + connected ?
> >>>>> + connector_status_connected :
> >>>>> + connector_status_disconnected);
> >>>>
> >>>> Incorrect indentation.
> >>>>
> >>> ? The checkpatch.pl did not detect it.
> >>
> >> use --strict.
> >>
> > i will , thks
> >
> >>>
> >>>>> +
> >>>>> + lt9611c_audio_update_connector_status(lt9611c);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_reset(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>>>> + msleep(20);
> >>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >>>>> + msleep(20);
> >>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1);
> >>>>> + msleep(400);
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_regulator_init(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + lt9611c->supplies[0].supply = "vcc";
> >>>>> + lt9611c->supplies[1].supply = "vdd";
> >>>>> +
> >>>>> + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies);
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_regulator_enable(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + int ret;
> >>>>> +
> >>>>> + ret = regulator_enable(lt9611c->supplies[0].consumer);
> >>>>> + if (ret < 0)
> >>>>> + return ret;
> >>>>> +
> >>>>> + usleep_range(5000, 10000);
> >>>>> +
> >>>>> + ret = regulator_enable(lt9611c->supplies[1].consumer);
> >>>>> + if (ret < 0) {
> >>>>> + regulator_disable(lt9611c->supplies[0].consumer);
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + int ret;
> >>>>> +
> >>>>> + ret = regulator_disable(lt9611c->supplies[0].consumer);
> >>>>> + if (ret < 0)
> >>>>> + return ret;
> >>>>> +
> >>>>> + usleep_range(5000, 10000);
> >>>>> +
> >>>>> + ret = regulator_disable(lt9611c->supplies[1].consumer);
> >>>>> + if (ret < 0)
> >>>>> + return ret;
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c,
> >>>>> + struct device_node *dsi_node)
> >>>>> +{
> >>>>> + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL };
> >>>>> + struct mipi_dsi_device *dsi;
> >>>>> + struct mipi_dsi_host *host;
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + host = of_find_mipi_dsi_host_by_node(dsi_node);
> >>>>> + if (!host)
> >>>>> + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n"));
> >>>>> +
> >>>>> + dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
> >>>>> + if (IS_ERR(dsi)) {
> >>>>> + 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 lt9611c_bridge_attach(struct drm_bridge *bridge,
> >>>>> + struct drm_encoder *encoder,
> >>>>> + enum drm_bridge_attach_flags flags)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> >>>>> +
> >>>>> + return drm_bridge_attach(encoder, lt9611c->next_bridge,
> >>>>> + bridge, flags);
> >>>>> +}
> >>>>> +
> >>>>> +static enum drm_mode_status lt9611c_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)
> >>>>
> >>>> Use .hdmi_tmds_char_rate_valid() for that.
> >>>>
> >>> I will check and test, thanks
> >>>
> >>>>> + return MODE_OK;
> >>>>> + else
> >>>>> + return MODE_BAD;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_bridge_mode_set(struct drm_bridge *bridge,
> >>>>> + const struct drm_display_mode *mode,
> >>>>> + const struct drm_display_mode *adj_mode)
> >>>>
> >>>> - Wrong indentation
> >>> will fix, thanks
> >>>
> >>>> - mode_set callback is deprecated and should not be used for new
> >>>> drivers.
> >>>>
> >>> I found that kernel 6.17 is still in use mode_set callback.
> >>
> >> Check the documentation in drm_bridge_funcs.
> >>
> > i will research.
> >
> >>>
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> >>>>> + struct device *dev = lt9611c->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;
> >>>>> +
> >>>>
> >>>>> + 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, "Out video info:\n");
> >>>>> + dev_info(dev,
> >>>>> + "h_total=%d, hactive=%d, hsync_len=%d, hfront_porch=%d, hback_porch=%d\n",
> >>>>> + h_total, hactive, hsync_len, hfront_porch, hback_porch);
> >>>>> + dev_info(dev,
> >>>>> + "v_total=%d, vactive=%d, vsync_len=%d, vfront_porch=%d, vback_porch=%d\n",
> >>>>> + v_total, vactive, vsync_len, vfront_porch, vback_porch);
> >>>>
> >>>>
> >>>> Fix indentation
> >>> The indentation issue was not detected by checkpatch.pl.
> >>
> >> The indentation issue is detected by the brain and eye.
> >>
> > sorry, i will research.
> >
> >>>
> >>>> Use dev_dbg / drm_dbg_kms() all over the driver. Your code is too
> >>>> spammy.
> >>>>
> >>> i will fix, thanks
> >>>
> >>>>> +
> >>>>> + 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(<9611c->ocm_lock);
> >>>>> + ret = i2c_read_write_flow(lt9611c, video_timing_set_cmd, 26, return_timing_set_param, 3);
> >>>>> + if (ret)
> >>>>> + dev_err(dev, "video set failed\n");
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> +}
> >>>>> +
> >>>>> +static enum drm_connector_status lt9611c_bridge_detect(struct drm_bridge *bridge,
> >>>>> + struct drm_connector *connector)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> + bool connected = false;
> >>>>> + u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
> >>>>> + u8 data[5];
> >>>>> +
> >>>>> + mutex_lock(<9611c->ocm_lock);
> >>>>> + ret = i2c_read_write_flow(lt9611c, cmd, 5, data, 5);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "Failed to read HPD status, cannot determine HDMI connection (err=%d)\n",
> >>>>> + ret);
> >>>>> + } else {
> >>>>> + connected = (data[4] == 0x02);
> >>>>> + }
> >>>>
> >>>> THere is no need to put single-line statements in brackets. Drop those.
> >>>>
> >>> yes, i will fix, thks
> >>>
> >>>>> +
> >>>>> + lt9611c->hdmi_connected = connected;
> >>>>> +
> >>>>> + if (lt9611c->hdmi_connected)
> >>>>> + lt9611c->audio_status = connector_status_connected;
> >>>>> + else
> >>>>> + lt9611c->audio_status = connector_status_disconnected;
> >>>>> +
> >>>>> + mutex_unlock(<9611c->ocm_lock);
> >>>>> +
> >>>>> + return connected ? connector_status_connected :
> >>>>> + connector_status_disconnected;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret, i, bytes_to_copy, offset = 0;
> >>>>> + u8 packets_num;
> >>>>> + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> >>>>> + u8 return_edid_data[37];
> >>>>> + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> >>>>> + u8 return_edid_byte_num[6];
> >>>>> +
> >>>>> + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "Failed to read EDID byte number\n");
> >>>>> + lt9611c->edid_valid = false;
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> >>>>> +
> >>>>> + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> >>>>> + lt9611c->edid_len : 0)) {
> >>>>> + kfree(lt9611c->edid_buf);
> >>>>> + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> >>>>> + if (!lt9611c->edid_buf) {
> >>>>> + dev_err(dev, "Failed to allocate EDID buffer\n");
> >>>>> + lt9611c->edid_len = 0;
> >>>>> + lt9611c->edid_valid = false;
> >>>>> + return -ENOMEM;
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> >>>>> + (lt9611c->edid_len / 32);
> >>>>> + for (i = 0; i < packets_num; i++) {
> >>>>> + read_edid_data_cmd[4] = (u8)i;
> >>>>> + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "Failed to read EDID packet %d\n", i);
> >>>>> + lt9611c->edid_valid = false;
> >>>>> + return -EIO;
> >>>>> + }
> >>>>> + offset = i * 32;
> >>>>> + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> >>>>> + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
> >>>>
> >>>> Don't store EDID in the long-term structures. Read it on demand.
> >>>>
> >>> I will think about this issue.
> >>>
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->edid_valid = true;
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = data;
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + unsigned int total_blocks;
> >>>>> + int ret;
> >>>>> +
> >>>>> + if (len > 128)
> >>>>> + return -EINVAL;
> >>>>> +
> >>>>> + guard(mutex)(<9611c->ocm_lock);
> >>>>> + if (block == 0 || !lt9611c->edid_valid) {
> >>>>> + ret = lt9611c_read_edid(lt9611c);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "EDID read failed\n");
> >>>>> + return ret;
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + total_blocks = lt9611c->edid_len / 128;
> >>>>> + if (!total_blocks) {
> >>>>> + dev_err(dev, "No valid EDID blocks\n");
> >>>>> + return -EIO;
> >>>>> + }
> >>>>> +
> >>>>> + if (block >= total_blocks) {
> >>>>> + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> >>>>> + block, total_blocks);
> >>>>> + return -EINVAL;
> >>>>> + }
> >>>>> +
> >>>>> + memcpy(buf, lt9611c->edid_buf + block * 128, len);
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> >>>>> + struct drm_connector *connector)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> >>>>> +
> >>>>> + usleep_range(10000, 20000);
> >>>>
> >>>> Why?
> >>>>
> >>> Delay for a while to ensure that EDID is ready.
> >>
> >> Your other chip had interrupt status to note that EDID is ready. I hope
> >> you have that one too. Blindly calling usleep_range() is a bad idea.
> >>
> > Different chips have different logic. i will research it.
> >
> >>>
> >>>>> + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> >>>>> +}
> >>>>> +
> >>>>> +static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
> >>>>> + .attach = lt9611c_bridge_attach,
> >>>>> + .mode_valid = lt9611c_bridge_mode_valid,
> >>>>> + .mode_set = lt9611c_bridge_mode_set,
> >>>>> + .detect = lt9611c_bridge_detect,
> >>>>> + .edid_read = lt9611c_bridge_edid_read,
> >>>>> +};
> >>>>> +
> >>>>> +static int lt9611c_parse_dt(struct device *dev,
> >>>>> + struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
> >>>>> + if (!lt9611c->dsi0_node) {
> >>>>> + dev_err(dev, "failed to get remote node for primary dsi\n");
> >>>>> + return -ENODEV;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->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, <9611c->next_bridge);
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_gpio_init(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> +
> >>>>> + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> >>>>> + if (IS_ERR(lt9611c->reset_gpio)) {
> >>>>> + dev_err(dev, "failed to acquire reset gpio\n");
> >>>>> + return PTR_ERR(lt9611c->reset_gpio);
> >>>>> + }
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_read_version(struct lt9611c *lt9611c, u64 *version)
> >>>>> +{
> >>>>> + u8 val;
> >>>>> + u64 ver = 0;
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
> >>>>> +
> >>>>> + i2c_read_byte(lt9611c, 0x80, &val);
> >>>>> + ver = val;
> >>>>> +
> >>>>> + i2c_read_byte(lt9611c, 0x81, &val);
> >>>>> + ver = (ver << 8) | val;
> >>>>> +
> >>>>> + *version = ver;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_read_chipid(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + u8 val = 0;
> >>>>> +
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe0);
> >>>>> + i2c_write_byte(lt9611c, 0xee, 0x01);
> >>>>> + i2c_write_byte(lt9611c, 0xff, 0xe1);
> >>>>> +
> >>>>> + i2c_read_byte(lt9611c, 0x00, &val);
> >>>>> + if (val != 0x23)
> >>>>> + return -ENODEV;
> >>>>> +
> >>>>> + i2c_read_byte(lt9611c, 0x01, &val);
> >>>>> + if (val != 0x06)
> >>>>> + return -ENODEV;
> >>>>> +
> >>>>> + dev_info(dev, "ChipId = 0x2306\n");
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> >>>>> + struct hdmi_codec_daifmt *fmt,
> >>>>> + struct hdmi_codec_params *hparms)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> >>>>> +
> >>>>> + dev_info(lt9611c->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;
> >>>>> + }
> >>>>
> >>>> Does that add anything on top of the limitations of hdmi-codec.c?
> >>>>
> >>> The parameters supported in the hdmi-codec.c may not be supported by
> >>> my chip. Therefore, we can exclude the parameters that are not
> >>> supported by the chip.
> >>
> >> Are they?
> >>
> > The firmware handles all parameter adaptation autonomously. This code
> > merely needs to expose the chip's capabilities to hdmi-codec.c.
> >
> >>>
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> >>>>> +{
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_audio_startup(struct device *dev, void *data)
> >>>>> +{
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + enum drm_connector_status status;
> >>>>> +
> >>>>> + status = lt9611c->audio_status;
> >>>>> + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> >>>>> + lt9611c->plugged_cb(lt9611c->codec_dev,
> >>>>> + status == connector_status_connected);
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> >>>>> + void *data,
> >>>>> + hdmi_codec_plugged_cb fn,
> >>>>> + struct device *codec_dev)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = data;
> >>>>> +
> >>>>> + lt9611c->plugged_cb = fn;
> >>>>> + lt9611c->codec_dev = codec_dev;
> >>>>> + lt9611c_audio_update_connector_status(lt9611c);
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> >>>>> + .hw_params = lt9611c_hdmi_hw_params,
> >>>>> + .audio_shutdown = lt9611c_audio_shutdown,
> >>>>> + .audio_startup = lt9611c_audio_startup,
> >>>>> + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> >>>>> +};
> >>>>
> >>>> No, we have HDMI audio helpers for that. Drop this and use the helpers
> >>>> instead.
> >>>>
> >>> ??? I don't understand.
> >>
> >> See <drm/display/drm_hdmi_audio_helper.h> and
> >> https://lore.kernel.org/dri-devel/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com/
> >>
> > i will research, thks.
> > Could you please share the latest driver file for lt9611uxc.c that you
> > have written? (to this email: syyang@lontium.com)
>
> https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/bridge/lontium-lt9611uxc.c
>
> >
> >>>
> >>>>> +
> >>>>> +static int lt9611c_audio_init(struct device *dev, struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct hdmi_codec_pdata codec_data = {
> >>>>> + .ops = <9611c_codec_ops,
> >>>>> + .max_i2s_channels = 2,
> >>>>> + .i2s = 1,
> >>>>> + .data = lt9611c,
> >>>>> + };
> >>>>> +
> >>>>> + lt9611c->audio_pdev =
> >>>>> + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
> >>>>> + PLATFORM_DEVID_AUTO,
> >>>>> + &codec_data, sizeof(codec_data));
> >>>>> +
> >>>>> + return PTR_ERR_OR_ZERO(lt9611c->audio_pdev);
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_audio_exit(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + if (lt9611c->audio_pdev) {
> >>>>> + platform_device_unregister(lt9611c->audio_pdev);
> >>>>> + lt9611c->audio_pdev = NULL;
> >>>>> + }
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_firmware_update_store(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + int ret;
> >>>>> +
> >>>>> + lt9611c_lock(lt9611c);
> >>>>> + ret = lt9611c_prepare_firmware_data(lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "Failed prepare firmware data: %d\n", ret);
> >>>>> + goto out;
> >>>>> + }
> >>>>> +
> >>>>> + ret = lt9611c_firmware_upgrade(lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "upgrade failure\n");
> >>>>> + goto out;
> >>>>> + }
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> + ret = lt9611c_upgrade_result(lt9611c);
> >>>>> + if (ret < 0)
> >>>>> + goto out;
> >>>>> +
> >>>>> +out:
> >>>>> + lt9611c_unlock(lt9611c);
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> + if (lt9611c->fw) {
> >>>>> + release_firmware(lt9611c->fw);
> >>>>> + lt9611c->fw = NULL;
> >>>>> + }
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static ssize_t lt9611c_firmware_store(struct device *dev, struct device_attribute *attr,
> >>>>> + const char *buf, size_t len)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> >>>>> + int ret;
> >>>>> +
> >>>>> + ret = lt9611c_firmware_update_store(lt9611c);
> >>>>
> >>>> Inline
> >>>>
> >>> i will fix, thks
> >>>
> >>>>> + if (ret < 0)
> >>>>> + return ret;
> >>>>> + return len;
> >>>>> +}
> >>>>> +
> >>>>> +static ssize_t lt9611c_firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> >>>>> +
> >>>>> + return sysfs_emit(buf, "0x%04llx\n", lt9611c->fw_version);
> >>>>> +}
> >>>>> +
> >>>>> +static DEVICE_ATTR_RW(lt9611c_firmware);
> >>>>> +
> >>>>> +static struct attribute *lt9611c_attrs[] = {
> >>>>> + &dev_attr_lt9611c_firmware.attr,
> >>>>> + NULL,
> >>>>> +};
> >>>>> +
> >>>>> +static const struct attribute_group lt9611c_attr_group = {
> >>>>> + .attrs = lt9611c_attrs,
> >>>>> +};
> >>>>> +
> >>>>> +static const struct attribute_group *lt9611c_attr_groups[] = {
> >>>>> + <9611c_attr_group,
> >>>>> + NULL,
> >>>>> +};
> >>>>> +
> >>>>> +static void lt9611c_cleanup_resources(struct lt9611c *lt9611c)
> >>>>> +{
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> +
> >>>>> + if (lt9611c->work_inited) {
> >>>>> + cancel_work_sync(<9611c->work);
> >>>>> + lt9611c->work_inited = false;
> >>>>> + dev_err(dev, "work cancelled\n");
> >>>>
> >>>> Why???
> >>>>
> >>> ?? I don't understand.
> >>
> >> Why do you need to be so spammy?
> >>
> > i will fix, thks
> >
> >>>
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->bridge_added) {
> >>>>> + drm_bridge_remove(<9611c->bridge);
> >>>>> + lt9611c->bridge_added = false;
> >>>>> + dev_err(dev, "DRM bridge removed\n");
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->regulators_enabled) {
> >>>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> >>>>> + lt9611c->regulators_enabled = false;
> >>>>> + dev_err(dev, "regulators disabled\n");
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->audio_pdev)
> >>>>> + lt9611c_audio_exit(lt9611c);
> >>>>> +
> >>>>> + if (lt9611c->fw) {
> >>>>
> >>>> You definitely don't need firmware when the bridge is up and running.
> >>>>
> >>> The previous text has already described the working logic of the firmware.
> >>
> >> It's another topic: you are storing the firmware in memory while the
> >> driver is bound. It's not necessary. You can release it after updating
> >> it on the chip.
> >>
> > I understand what you mean.
> > Based on the above conversation, your intention is that when the
> > customer needs to upgrade the firmware, they should modify the
> > comparison conditions of the version, then compile and burn the
> > kernel, and then perform the firmware upgrade, just like the LT9611UXC
> > driver. Instead of loading the firmware every time.
> > My design intention is to avoid the need for recompiling the driver
> > when upgrading. Instead, a file named "LT9611C.bin" can be directly
> > sent to the "/lib/firmware" directory via scp. Then you can either
> > perform a reboot for the upgrade or execute the command manually for
> > the upgrade.
> > Perhaps you are suggesting that we could follow the design approach of
> > the LT9611UXC driver?
>
> Yes no need to rebuild, just use sysfs to trigger an update.
>
I think you haven't attempted to understand the intention behind my design.
If during the debugging process, the customer discovers that a certain
parameter in the chip's firmware is not suitable for the current
situation, then he requests a perfect firmware from our company to be
updated onto the chip.
When there are hundreds or tens of thousands of devices that need to
be updated, simply use sysfs to trigger the update. It is a very bad
thing.
If you want to use version number comparison as the upgrade condition
like in lt9611uxc.c, then the customer will need to modify the version
number comparison condition and rebuild the driver. This method is not
as simple as the one I have designed.
> >
> >>>
> >>>>> + release_firmware(lt9611c->fw);
> >>>>> + lt9611c->fw = NULL;
> >>>>> + dev_err(dev, "firmware released\n");
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->dsi0_node) {
> >>>>> + of_node_put(lt9611c->dsi0_node);
> >>>>> + lt9611c->dsi0_node = NULL;
> >>>>> + dev_err(dev, "dsi0 node released\n");
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->dsi1_node) {
> >>>>> + of_node_put(lt9611c->dsi1_node);
> >>>>> + lt9611c->dsi1_node = NULL;
> >>>>> + dev_err(dev, "dsi1 node released\n");
> >>>>> + }
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_main(void *data)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = data;
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> + struct i2c_client *client = lt9611c->client;
> >>>>> + int ret;
> >>>>> +
> >>>>> + lt9611c->work_inited = false;
> >>>>> + lt9611c->bridge_added = false;
> >>>>> + lt9611c->regulators_enabled = false;
> >>>>> +
> >>>>> + ret = lt9611c_parse_dt(dev, lt9611c);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "failed to parse device tree\n");
> >>>>> + return ret;
> >>>>> + }
> >>>>> +
> >>>>> + ret = lt9611c_gpio_init(lt9611c);
> >>>>> + if (ret < 0)
> >>>>> + goto err_cleanup;
> >>>>> +
> >>>>> + ret = lt9611c_regulator_init(lt9611c);
> >>>>> + if (ret < 0)
> >>>>> + goto err_cleanup;
> >>>>> +
> >>>>> + ret = lt9611c_regulator_enable(lt9611c);
> >>>>> + if (ret)
> >>>>> + goto err_cleanup;
> >>>>> +
> >>>>> + lt9611c->regulators_enabled = true;
> >>>>> +
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> +
> >>>>> + ret = lt9611c_read_chipid(lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "failed to read chip id.\n");
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_lock(lt9611c);
> >>>>> + lt9611c_read_version(lt9611c, <9611c->fw_version);
> >>>>> +
> >>>>> + ret = lt9611c_prepare_firmware_data(lt9611c);
> >>>>> + if (ret == 0 && lt9611c_upgrade_judgment(lt9611c) == UPGRADE) {
> >>>>> + dev_info(dev, "firmware upgrade needed\n");
> >>>>> +
> >>>>> + ret = lt9611c_firmware_upgrade(lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "firmware upgrade failed\n");
> >>>>> + lt9611c_unlock(lt9611c);
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> + ret = lt9611c_upgrade_result(lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + lt9611c_unlock(lt9611c);
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_read_version(lt9611c, <9611c->fw_version);
> >>>>> + lt9611c_unlock(lt9611c);
> >>>>> +
> >>>>> + } else {
> >>>>> + dev_info(dev, "skip firmware upgrade, using chip internal firmware\n");
> >>>>> + lt9611c_unlock(lt9611c);
> >>>>> + }
> >>>>> +
> >>>>> + if (lt9611c->fw) {
> >>>>> + release_firmware(lt9611c->fw);
> >>>>> + lt9611c->fw = NULL;
> >>>>> + }
> >>>>> + dev_info(dev, "current version:0x%04llx", lt9611c->fw_version);
> >>>>> +
> >>>>> + INIT_WORK(<9611c->work, lt9611c_hpd_work);
> >>>>> + lt9611c->work_inited = true;
> >>>>> +
> >>>>> + if (!client->irq) {
> >>>>> + dev_err(dev, "failed to get INTP IRQ\n");
> >>>>> + ret = -ENODEV;
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
> >>>>> + lt9611c_irq_thread_handler,
> >>>>> + IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
> >>>>> + IRQF_NO_AUTOEN,
> >>>>> + "lt9611c", lt9611c);
> >>>>> + if (ret) {
> >>>>> + dev_err(dev, "failed to request irq\n");
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->bridge.funcs = <9611c_bridge_funcs;
> >>>>> + lt9611c->bridge.of_node = lt9611c->client->dev.of_node;
> >>>>> + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
> >>>>> + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> >>>>> +
> >>>>> + drm_bridge_add(<9611c->bridge);
> >>>>> + lt9611c->bridge_added = true;
> >>>>
> >>>> No unnecessary flags, please. Implement proper cleanup path, unwinding
> >>>> resources one by one.
> >>>>
> >>> I will consider this issue. thks
> >>>
> >>>>> +
> >>>>> + /* Attach primary DSI */
> >>>>> + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
> >>>>> + if (IS_ERR(lt9611c->dsi0)) {
> >>>>> + ret = PTR_ERR(lt9611c->dsi0);
> >>>>> + dev_err(dev, "Failed to attach primary DSI, error=%d\n", ret);
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + /* Attach secondary DSI, if specified */
> >>>>> + if (lt9611c->dsi1_node) {
> >>>>> + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node);
> >>>>> + if (IS_ERR(lt9611c->dsi1)) {
> >>>>> + ret = PTR_ERR(lt9611c->dsi1);
> >>>>> + dev_err(dev, "Failed to attach secondary DSI, error=%d\n", ret);
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c->audio_status = connector_status_disconnected;
> >>>>> +
> >>>>> + ret = lt9611c_audio_init(dev, lt9611c);
> >>>>> + if (ret < 0) {
> >>>>> + dev_err(dev, "audio init failed\n");
> >>>>> + goto err_cleanup;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> + enable_irq(lt9611c->client->irq);
> >>>>> +
> >>>>> + return 0;
> >>>>> +
> >>>>> +err_cleanup:
> >>>>> + lt9611c_cleanup_resources(lt9611c);
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_probe(struct i2c_client *client)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c;
> >>>>> + struct device *dev = &client->dev;
> >>>>> +
> >>>>> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> >>>>> + dev_err(dev, "device doesn't support I2C\n");
> >>>>> + return -ENODEV;
> >>>>> + }
> >>>>> +
> >>>>> + lt9611c = devm_kzalloc(dev, sizeof(*lt9611c), GFP_KERNEL);
> >>>>
> >>>> devm_drm_bridge_alloc()
> >>>>
> >>> i will fix, thks
> >>>
> >>>>> + if (!lt9611c)
> >>>>> + return -ENOMEM;
> >>>>> +
> >>>>> + lt9611c->dev = dev;
> >>>>> + lt9611c->client = client;
> >>>>> + mutex_init(<9611c->ocm_lock);
> >>>>> +
> >>>>> + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config);
> >>>>> + if (IS_ERR(lt9611c->regmap)) {
> >>>>> + dev_err(dev, "regmap i2c init failed\n");
> >>>>> + return PTR_ERR(lt9611c->regmap);
> >>>>> + }
> >>>>> +
> >>>>> + i2c_set_clientdata(client, lt9611c);
> >>>>> +
> >>>>> + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> >>>>
> >>>> Why do you need extra kthread for that???
> >
> > Upgrading the firmware takes time. execute it sequentially in the
> > probe function, it will block the system boot.
> > Using the kthread method will not block the system boot.
>
> Just follow the drivers/gpu/drm/bridge/lontium-lt9611uxc.c way to do this.
>
In fact, I think the method in lontium-lt9611uxc.c is a very bad one.
My clients often encounter situations where the system gets blocked
during the firmware upgrade process, and they have no idea what has
happened.
> >
> >>>>
> >>>>> + if (IS_ERR(lt9611c->kthread)) {
> >>>>> + dev_err(dev, "Failed to create kernel thread\n");
> >>>>> + return PTR_ERR(lt9611c->kthread);
> >>>>> + }
> >>>>> +
> >>>>> + return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static void lt9611c_remove(struct i2c_client *client)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = i2c_get_clientdata(client);
> >>>>> + struct device *dev = lt9611c->dev;
> >>>>> +
> >>>>> + kfree(lt9611c->edid_buf);
> >>>>> + disable_irq(client->irq);
> >>>>> + lt9611c_cleanup_resources(lt9611c);
> >>>>> + mutex_destroy(<9611c->ocm_lock);
> >>>>> + dev_info(dev, "remove driver\n");
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_bridge_suspend(struct device *dev)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> >>>>> + int ret;
> >>>>> +
> >>>>> + dev_info(lt9611c->dev, "suspend\n");
> >>>>> + disable_irq(lt9611c->client->irq);
> >>>>> + ret = lt9611c_regulator_disable(lt9611c);
> >>>>> + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0);
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static int lt9611c_bridge_resume(struct device *dev)
> >>>>> +{
> >>>>> + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> >>>>> + int ret;
> >>>>> +
> >>>>> + ret = lt9611c_regulator_enable(lt9611c);
> >>>>> + lt9611c_reset(lt9611c);
> >>>>> + enable_irq(lt9611c->client->irq);
> >>>>> + dev_info(lt9611c->dev, "resume\n");
> >>>>> +
> >>>>> + return ret;
> >>>>> +}
> >>>>> +
> >>>>> +static const struct dev_pm_ops lt9611c_bridge_pm_ops = {
> >>>>> + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend,
> >>>>> + lt9611c_bridge_resume)
> >>>>> +};
> >>>>> +
> >>>>> +static struct i2c_device_id lt9611c_id[] = {
> >>>>> + { "lontium,lt9611c", 0 },
> >>>>> + { /* sentinel */ }
> >>>>> +};
> >>>>> +
> >>>>> +static const struct of_device_id lt9611c_match_table[] = {
> >>>>> + { .compatible = "lontium,lt9611c" },
> >>>>
> >>>> Your schema also had lt9611uxd
> >>>>
> >>> i will fix, thks
> >>>
> >>>>> + { /* sentinel */ }
> >>>>> +};
> >>>>> +MODULE_DEVICE_TABLE(of, lt9611c_match_table);
> >>>>> +
> >>>>> +static struct i2c_driver lt9611c_driver = {
> >>>>> + .driver = {
> >>>>> + .name = "lt9611c",
> >>>>> + .of_match_table = lt9611c_match_table,
> >>>>> + .pm = <9611c_bridge_pm_ops,
> >>>>> + .dev_groups = lt9611c_attr_groups,
> >>>>> + },
> >>>>> + .probe = lt9611c_probe,
> >>>>> + .remove = lt9611c_remove,
> >>>>> + .id_table = lt9611c_id,
> >>>>> +};
> >>>>> +module_i2c_driver(lt9611c_driver);
> >>>>> +
> >>>>> +MODULE_AUTHOR("syyang <syyang@lontium.com>");
> >>>>> +MODULE_LICENSE("GPL v2");
> >>>>> +
> >>>>> +MODULE_FIRMWARE(FW_FILE);
> >>>>> --
> >>>>> 2.25.1
> >>>>>
> >>>>
> >>>> --
> >>>> With best wishes
> >>>> Dmitry
> >>>
> >>> Dmitry, thank you very much
> >>
> >> --
> >> With best wishes
> >> Dmitry
>
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-05 2:55 ` 杨孙运
2025-09-05 8:10 ` neil.armstrong
@ 2025-09-05 14:10 ` Dmitry Baryshkov
1 sibling, 0 replies; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-05 14:10 UTC (permalink / raw)
To: 杨孙运
Cc: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rly
On Fri, Sep 05, 2025 at 10:55:51AM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
>
> >
> > On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> > > >
> > > > On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> > > > > The following changes are included:
> > > > >
> > > > > - Updated Kconfig and Makefile to include the new driver
> > > > > - Implementation of the bridge driver at
> > > > > drivers/gpu/drm/bridge/lontium-lt9611c.c
> > > >
> > > > This is really not interesting, it can be seen from the patch itself.
> > > > Please read Documentation/process/submitting-patches.rst.
> > > >
> > > Sorry, I will study submitting-patches.rst.
> > >
> > > > Is it possible to toggle infoframes?
> > >
> > > sorry, I don't understand the meaning of this sentence. Please explain
> > > it in detail.
> >
> > Is it possible to control InfoFrames being sent over the HDMI cable?
> > Both contents and enabling/disabling.
> >
> Can be controlled via I2C
Then please implement DRM_BRIDGE_OP_HDMI and corresponding InfoFrame
callbacks.
> > >
> > > > > +
> > > > > +#define FW_SIZE (64 * 1024)
> > > > > +#define LT_PAGE_SIZE 256
> > > > > +#define FW_FILE "LT9611C.bin"
> > > >
> > > > Please land this firmware to the linux-firmware repository.
> > > >
> > >
> > > The LT9611C has built-in firmware, and when the chip is running, it
> > > uses the internal firmware.
> > > The firmware needs to be updated only when the customer encounters
> > > issues during the debugging phase due to changes in hardware design or
> > > parameters.
> > > When the customer needs to update the firmware, they will apply to our
> > > company for a customized firmware.
> > > They will place this firmware under /lib/firmware, and then reboot the
> > > platform. After that, the driver will update the firmware.
> > > So I think there is no need to upload the firmware.
> >
> > Then please make firmware updates opt-in, requiring some user action.
> > I'd suggest first getting the simplfified version of the driver in
> > (without fw update capabilities) and then adding FW upgrades in as a
> > separate patch.
> >
> I will research it.
Research... what?
> > > > > +static unsigned int get_crc(struct crc_info type, const u8 *buf, u64 buf_len)
> > > >
> > > > Use library functions for that.
> > > >
> > >
> > > I'm not sure whether the algorithms in the llibrary functions are
> > > consistent with those designed in our chip.
> > > If either of them changes, it will cause the firmware updated to the
> > > chip to fail to run.
> >
> > CRC polynoms don't change that easily
> >
> > > I think it's safer to implement it using our own code.
> >
> > No, it's not.
> >
> If we calculate CRC_1 using the library function and then burn it
> together with the firmware into the chip, when the chip boot, it will
> use the internal hardware to calculate the firmware CRC_2.
> If CRC_1 is not equal to CRC_2, the chip will fail to boot. The
> library function will not be changed. I'm worried that the algorithm
> in our chip's hardware is different from the library function. I'll
> research it.
Please take a look first, before having fears or implenting yet another
CRC function.
>
> > > I'll check it.
> > > > > +static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
> > > > > +{
> > > > > + struct device *dev = lt9611c->dev;
> > > > > + int ret;
> > > > > +
> > > > > + /* ensure filesystem ready */
> > > > > + msleep(3000);
> > > >
> > > > No. If the firmware is necessary and it's not ready, return
> > > > -EPROBE_DEFER.
> > > >
> > > The firmware is unnecessary . This part of the code is for customers
> > > who need to upgrade the chip firmware.
> > >
> > > Due to the different designs of the platform, the firmware used by
> > > each customer may be different.
> >
> > Well... That's a very bad way to go. We have had this issue with
> > LT9611UXC at one of my previous jobs. Our customers have had various
> > kinds of issues because of the wrong firmware.
> >
> > Please provide some reference, which works in a DSI-to-HDMI case and
> > make it _tunable_ rather than requiring to replace the firmware
> > completely.
> >
> i will research it.
> Yes, you worked together with my colleagues to handle the issue of
> LT9611UXC. (At that time, you used dmitry.baryshkov@linaro.org)
>
> > >
> > > Therefore, when they need to update the firmware, they only need to
> > > compile the firmware into the /lib/firmware directory during the
> > > compilation
> > > process, and then burn the image into the platform.
> > >
> > > Once reboot platform, the firmware upgrade can be automatically completed.
> >
> > The firmware upgrade must be triggered by user, unless the FW is
> > completely empty.
> >
> Is it necessary for the authorities to insist on doing so?
I think so. The rootfs might be the same for different devices,
different use cases, etc. So your 'load and program firmware if it's
present in rootfs' doesn't work in a general case.
> > > > > +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> > > > > + struct drm_connector *connector)
> > > > > +{
> > > > > + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> > > > > +
> > > > > + usleep_range(10000, 20000);
> > > >
> > > > Why?
> > > >
> > > Delay for a while to ensure that EDID is ready.
> >
> > Your other chip had interrupt status to note that EDID is ready. I hope
> > you have that one too. Blindly calling usleep_range() is a bad idea.
> >
> Different chips have different logic. i will research it.
Thanks.
> > > > > +static int lt9611c_hdmi_hw_params(struct device *dev, void *data,
> > > > > + struct hdmi_codec_daifmt *fmt,
> > > > > + struct hdmi_codec_params *hparms)
> > > > > +{
> > > > > + struct lt9611c *lt9611c = dev_get_drvdata(dev);
> > > > > +
> > > > > + dev_info(lt9611c->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;
> > > > > + }
> > > >
> > > > Does that add anything on top of the limitations of hdmi-codec.c?
> > > >
> > > The parameters supported in the hdmi-codec.c may not be supported by
> > > my chip. Therefore, we can exclude the parameters that are not
> > > supported by the chip.
> >
> > Are they?
> >
> The firmware handles all parameter adaptation autonomously. This code
> merely needs to expose the chip's capabilities to hdmi-codec.c.
Which params are supported by hdmi-codec but not suppored by your chip?
>
> > >
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static void lt9611c_audio_shutdown(struct device *dev, void *data)
> > > > > +{
> > > > > +}
> > > > > +
> > > > > +static int lt9611c_audio_startup(struct device *dev, void *data)
> > > > > +{
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static void lt9611c_audio_update_connector_status(struct lt9611c *lt9611c)
> > > > > +{
> > > > > + enum drm_connector_status status;
> > > > > +
> > > > > + status = lt9611c->audio_status;
> > > > > + if (lt9611c->plugged_cb && lt9611c->codec_dev)
> > > > > + lt9611c->plugged_cb(lt9611c->codec_dev,
> > > > > + status == connector_status_connected);
> > > > > +}
> > > > > +
> > > > > +static int lt9611c_hdmi_audio_hook_plugged_cb(struct device *dev,
> > > > > + void *data,
> > > > > + hdmi_codec_plugged_cb fn,
> > > > > + struct device *codec_dev)
> > > > > +{
> > > > > + struct lt9611c *lt9611c = data;
> > > > > +
> > > > > + lt9611c->plugged_cb = fn;
> > > > > + lt9611c->codec_dev = codec_dev;
> > > > > + lt9611c_audio_update_connector_status(lt9611c);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +static const struct hdmi_codec_ops lt9611c_codec_ops = {
> > > > > + .hw_params = lt9611c_hdmi_hw_params,
> > > > > + .audio_shutdown = lt9611c_audio_shutdown,
> > > > > + .audio_startup = lt9611c_audio_startup,
> > > > > + .hook_plugged_cb = lt9611c_hdmi_audio_hook_plugged_cb,
> > > > > +};
> > > >
> > > > No, we have HDMI audio helpers for that. Drop this and use the helpers
> > > > instead.
> > > >
> > > ??? I don't understand.
> >
> > See <drm/display/drm_hdmi_audio_helper.h> and
> > https://lore.kernel.org/dri-devel/20250803-lt9611uxc-hdmi-v1-2-cb9ce1793acf@oss.qualcomm.com/
> >
> i will research, thks.
> Could you please share the latest driver file for lt9611uxc.c that you
> have written? (to this email: syyang@lontium.com)
No, I will not. It's called upstream, because everything is either in
the tree or on the mailing list.
> > > > > + if (lt9611c->audio_pdev)
> > > > > + lt9611c_audio_exit(lt9611c);
> > > > > +
> > > > > + if (lt9611c->fw) {
> > > >
> > > > You definitely don't need firmware when the bridge is up and running.
> > > >
> > > The previous text has already described the working logic of the firmware.
> >
> > It's another topic: you are storing the firmware in memory while the
> > driver is bound. It's not necessary. You can release it after updating
> > it on the chip.
> >
> I understand what you mean.
No, you don't. The driver can call release_firmware() after flashing the
chip. That's it at this point. Nothing more.
> Based on the above conversation, your intention is that when the
> customer needs to upgrade the firmware, they should modify the
> comparison conditions of the version, then compile and burn the
> kernel, and then perform the firmware upgrade, just like the LT9611UXC
> driver. Instead of loading the firmware every time.
Of course not. I've asked to add a way for the user to trigger firmware
update. It might be a sysfs file (like I did for lt9611uxc), it can be
modparam or anything else. But again, this is not relevant _here_.
I simply ask you to release the memory after it is no longer needed.
> My design intention is to avoid the need for recompiling the driver
> when upgrading. Instead, a file named "LT9611C.bin" can be directly
> sent to the "/lib/firmware" directory via scp. Then you can either
> perform a reboot for the upgrade or execute the command manually for
> the upgrade.
Rootfs might be read-only, it might be shared via NFS, it might be
coming from the cloud via Docker container, etc.
> Perhaps you are suggesting that we could follow the design approach of
> the LT9611UXC driver?
>
> > > > > +
> > > > > + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> > > >
> > > > Why do you need extra kthread for that???
>
> Upgrading the firmware takes time. execute it sequentially in the
> probe function, it will block the system boot.
> Using the kthread method will not block the system boot.
Using async probing. Or program firmware after checking all resources
that are required for the device.
Also, as you've written, firmware flashing is an exception, so delaying
one exceptional boot is not a real issue.
And anyway, this requires a comment in the source file and a comment in
the commit message.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-05 8:58 ` 杨孙运
@ 2025-09-05 14:24 ` Dmitry Baryshkov
2025-09-08 1:14 ` 杨孙运
0 siblings, 1 reply; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-05 14:24 UTC (permalink / raw)
To: 杨孙运
Cc: Neil Armstrong, syyang, robh, krzk+dt, conor+dt, andrzej.hajda,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rlyu, xbpeng
On Fri, Sep 05, 2025 at 04:58:59PM +0800, 杨孙运 wrote:
> HI,
>
> As a vendors , we have begun to attempt to contribute to the Linux,
> and we are very willing to do so.
> there are still many rules that we don't understand and need to learn.
Not top-posting and trimming your emails would be nice things to learn
too.
> <neil.armstrong@linaro.org> 于2025年9月5日周五 16:10写道:
> > On 05/09/2025 04:55, 杨孙运 wrote:
> > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
> > >> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> > >>> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> > >>>> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> > >>>>> + }
> > >>>>> +
> > >>>>> + if (lt9611c->bridge_added) {
> > >>>>> + drm_bridge_remove(<9611c->bridge);
> > >>>>> + lt9611c->bridge_added = false;
> > >>>>> + dev_err(dev, "DRM bridge removed\n");
> > >>>>> + }
> > >>>>> +
> > >>>>> + if (lt9611c->regulators_enabled) {
> > >>>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > >>>>> + lt9611c->regulators_enabled = false;
> > >>>>> + dev_err(dev, "regulators disabled\n");
> > >>>>> + }
> > >>>>> +
> > >>>>> + if (lt9611c->audio_pdev)
> > >>>>> + lt9611c_audio_exit(lt9611c);
> > >>>>> +
> > >>>>> + if (lt9611c->fw) {
> > >>>>
> > >>>> You definitely don't need firmware when the bridge is up and running.
> > >>>>
> > >>> The previous text has already described the working logic of the firmware.
> > >>
> > >> It's another topic: you are storing the firmware in memory while the
> > >> driver is bound. It's not necessary. You can release it after updating
> > >> it on the chip.
> > >>
> > > I understand what you mean.
> > > Based on the above conversation, your intention is that when the
> > > customer needs to upgrade the firmware, they should modify the
> > > comparison conditions of the version, then compile and burn the
> > > kernel, and then perform the firmware upgrade, just like the LT9611UXC
> > > driver. Instead of loading the firmware every time.
> > > My design intention is to avoid the need for recompiling the driver
> > > when upgrading. Instead, a file named "LT9611C.bin" can be directly
> > > sent to the "/lib/firmware" directory via scp. Then you can either
> > > perform a reboot for the upgrade or execute the command manually for
> > > the upgrade.
> > > Perhaps you are suggesting that we could follow the design approach of
> > > the LT9611UXC driver?
> >
> > Yes no need to rebuild, just use sysfs to trigger an update.
> >
> I think you haven't attempted to understand the intention behind my design.
>
> If during the debugging process, the customer discovers that a certain
> parameter in the chip's firmware is not suitable for the current
> situation, then he requests a perfect firmware from our company to be
> updated onto the chip.
That's fine.
>
> When there are hundreds or tens of thousands of devices that need to
> be updated, simply use sysfs to trigger the update. It is a very bad
> thing.
Delivering updates to devices it off-topic here. You can use SWUpdate,
Mender or any other system to deliver updates and to trigger the
firmware reflash afterwards.
> If you want to use version number comparison as the upgrade condition
> like in lt9611uxc.c, then the customer will need to modify the version
> number comparison condition and rebuild the driver. This method is not
> as simple as the one I have designed.
Well, no. If there is a firmware update, it should be shared to
linux-firmware and then everybody can benefit from it.
Think about one company using your chip in their SoM or compute module
and then another company integrating that module into their design?
Who will contact you? Or a company selling devkits with your chip.
Having per-customer firmware is a nightmare for developers and for
integrators.
> > >>>>> +
> > >>>>> + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> > >>>>
> > >>>> Why do you need extra kthread for that???
> > >
> > > Upgrading the firmware takes time. execute it sequentially in the
> > > probe function, it will block the system boot.
> > > Using the kthread method will not block the system boot.
> >
> > Just follow the drivers/gpu/drm/bridge/lontium-lt9611uxc.c way to do this.
> >
> In fact, I think the method in lontium-lt9611uxc.c is a very bad one.
> My clients often encounter situations where the system gets blocked
> during the firmware upgrade process, and they have no idea what has
> happened.
Patches are welcome, please help us by improving LT9611UXC support :-)
E.g. it would be nice to control InfoFrames or YUV output capabilities.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-05 14:24 ` Dmitry Baryshkov
@ 2025-09-08 1:14 ` 杨孙运
2025-09-08 10:17 ` Dmitry Baryshkov
0 siblings, 1 reply; 26+ messages in thread
From: 杨孙运 @ 2025-09-08 1:14 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Neil Armstrong, syyang, robh, krzk+dt, conor+dt, andrzej.hajda,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rlyu, xbpeng
Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月5日周五 22:24写道:
>
> On Fri, Sep 05, 2025 at 04:58:59PM +0800, 杨孙运 wrote:
> > HI,
> >
> > As a vendors , we have begun to attempt to contribute to the Linux,
> > and we are very willing to do so.
> > there are still many rules that we don't understand and need to learn.
>
> Not top-posting and trimming your emails would be nice things to learn
> too.
>
> > <neil.armstrong@linaro.org> 于2025年9月5日周五 16:10写道:
> > > On 05/09/2025 04:55, 杨孙运 wrote:
> > > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
> > > >> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> > > >>> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> > > >>>> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
>
> > > >>>>> + }
> > > >>>>> +
> > > >>>>> + if (lt9611c->bridge_added) {
> > > >>>>> + drm_bridge_remove(<9611c->bridge);
> > > >>>>> + lt9611c->bridge_added = false;
> > > >>>>> + dev_err(dev, "DRM bridge removed\n");
> > > >>>>> + }
> > > >>>>> +
> > > >>>>> + if (lt9611c->regulators_enabled) {
> > > >>>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > > >>>>> + lt9611c->regulators_enabled = false;
> > > >>>>> + dev_err(dev, "regulators disabled\n");
> > > >>>>> + }
> > > >>>>> +
> > > >>>>> + if (lt9611c->audio_pdev)
> > > >>>>> + lt9611c_audio_exit(lt9611c);
> > > >>>>> +
> > > >>>>> + if (lt9611c->fw) {
> > > >>>>
> > > >>>> You definitely don't need firmware when the bridge is up and running.
> > > >>>>
> > > >>> The previous text has already described the working logic of the firmware.
> > > >>
> > > >> It's another topic: you are storing the firmware in memory while the
> > > >> driver is bound. It's not necessary. You can release it after updating
> > > >> it on the chip.
> > > >>
> > > > I understand what you mean.
> > > > Based on the above conversation, your intention is that when the
> > > > customer needs to upgrade the firmware, they should modify the
> > > > comparison conditions of the version, then compile and burn the
> > > > kernel, and then perform the firmware upgrade, just like the LT9611UXC
> > > > driver. Instead of loading the firmware every time.
> > > > My design intention is to avoid the need for recompiling the driver
> > > > when upgrading. Instead, a file named "LT9611C.bin" can be directly
> > > > sent to the "/lib/firmware" directory via scp. Then you can either
> > > > perform a reboot for the upgrade or execute the command manually for
> > > > the upgrade.
> > > > Perhaps you are suggesting that we could follow the design approach of
> > > > the LT9611UXC driver?
> > >
> > > Yes no need to rebuild, just use sysfs to trigger an update.
> > >
> > I think you haven't attempted to understand the intention behind my design.
> >
> > If during the debugging process, the customer discovers that a certain
> > parameter in the chip's firmware is not suitable for the current
> > situation, then he requests a perfect firmware from our company to be
> > updated onto the chip.
>
> That's fine.
>
> >
> > When there are hundreds or tens of thousands of devices that need to
> > be updated, simply use sysfs to trigger the update. It is a very bad
> > thing.
>
> Delivering updates to devices it off-topic here. You can use SWUpdate,
> Mender or any other system to deliver updates and to trigger the
> firmware reflash afterwards.
>
> > If you want to use version number comparison as the upgrade condition
> > like in lt9611uxc.c, then the customer will need to modify the version
> > number comparison condition and rebuild the driver. This method is not
> > as simple as the one I have designed.
>
> Well, no. If there is a firmware update, it should be shared to
> linux-firmware and then everybody can benefit from it.
>
> Think about one company using your chip in their SoM or compute module
> and then another company integrating that module into their design?
> Who will contact you? Or a company selling devkits with your chip.
>
> Having per-customer firmware is a nightmare for developers and for
> integrators.
>
We are a company that sells our own developed chips. After other
platform design companies purchase our chips, they will design their
platforms based on the hardware schematic of our chips. During this
process, they must contact us. We will communicate about the design
opinions of the platform, the configuration of parameters, and the use
of custom firmware. We cannot provide a common firmware. This is
determined by the characteristics of the chips.
> > > >>>>> +
> > > >>>>> + lt9611c->kthread = kthread_run(lt9611c_main, lt9611c, "lt9611c");
> > > >>>>
> > > >>>> Why do you need extra kthread for that???
> > > >
> > > > Upgrading the firmware takes time. execute it sequentially in the
> > > > probe function, it will block the system boot.
> > > > Using the kthread method will not block the system boot.
> > >
> > > Just follow the drivers/gpu/drm/bridge/lontium-lt9611uxc.c way to do this.
> > >
> > In fact, I think the method in lontium-lt9611uxc.c is a very bad one.
> > My clients often encounter situations where the system gets blocked
> > during the firmware upgrade process, and they have no idea what has
> > happened.
>
> Patches are welcome, please help us by improving LT9611UXC support :-)
>
> E.g. it would be nice to control InfoFrames or YUV output capabilities.
>
> --
> With best wishes
> Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-08 1:14 ` 杨孙运
@ 2025-09-08 10:17 ` Dmitry Baryshkov
0 siblings, 0 replies; 26+ messages in thread
From: Dmitry Baryshkov @ 2025-09-08 10:17 UTC (permalink / raw)
To: 杨孙运
Cc: Neil Armstrong, syyang, robh, krzk+dt, conor+dt, andrzej.hajda,
rfoss, Laurent.pinchart, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, xmzhu, llzhang, rlyu, xbpeng
On Mon, Sep 08, 2025 at 09:14:51AM +0800, 杨孙运 wrote:
> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月5日周五 22:24写道:
> >
> > On Fri, Sep 05, 2025 at 04:58:59PM +0800, 杨孙运 wrote:
> > > HI,
> > >
> > > As a vendors , we have begun to attempt to contribute to the Linux,
> > > and we are very willing to do so.
> > > there are still many rules that we don't understand and need to learn.
> >
> > Not top-posting and trimming your emails would be nice things to learn
> > too.
> >
> > > <neil.armstrong@linaro.org> 于2025年9月5日周五 16:10写道:
> > > > On 05/09/2025 04:55, 杨孙运 wrote:
> > > > > Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 22:39写道:
> > > > >> On Thu, Sep 04, 2025 at 06:48:13PM +0800, 杨孙运 wrote:
> > > > >>> Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> 于2025年9月4日周四 10:52写道:
> > > > >>>> On Wed, Sep 03, 2025 at 05:38:25AM -0700, syyang wrote:
> >
> > > > >>>>> + }
> > > > >>>>> +
> > > > >>>>> + if (lt9611c->bridge_added) {
> > > > >>>>> + drm_bridge_remove(<9611c->bridge);
> > > > >>>>> + lt9611c->bridge_added = false;
> > > > >>>>> + dev_err(dev, "DRM bridge removed\n");
> > > > >>>>> + }
> > > > >>>>> +
> > > > >>>>> + if (lt9611c->regulators_enabled) {
> > > > >>>>> + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
> > > > >>>>> + lt9611c->regulators_enabled = false;
> > > > >>>>> + dev_err(dev, "regulators disabled\n");
> > > > >>>>> + }
> > > > >>>>> +
> > > > >>>>> + if (lt9611c->audio_pdev)
> > > > >>>>> + lt9611c_audio_exit(lt9611c);
> > > > >>>>> +
> > > > >>>>> + if (lt9611c->fw) {
> > > > >>>>
> > > > >>>> You definitely don't need firmware when the bridge is up and running.
> > > > >>>>
> > > > >>> The previous text has already described the working logic of the firmware.
> > > > >>
> > > > >> It's another topic: you are storing the firmware in memory while the
> > > > >> driver is bound. It's not necessary. You can release it after updating
> > > > >> it on the chip.
> > > > >>
> > > > > I understand what you mean.
> > > > > Based on the above conversation, your intention is that when the
> > > > > customer needs to upgrade the firmware, they should modify the
> > > > > comparison conditions of the version, then compile and burn the
> > > > > kernel, and then perform the firmware upgrade, just like the LT9611UXC
> > > > > driver. Instead of loading the firmware every time.
> > > > > My design intention is to avoid the need for recompiling the driver
> > > > > when upgrading. Instead, a file named "LT9611C.bin" can be directly
> > > > > sent to the "/lib/firmware" directory via scp. Then you can either
> > > > > perform a reboot for the upgrade or execute the command manually for
> > > > > the upgrade.
> > > > > Perhaps you are suggesting that we could follow the design approach of
> > > > > the LT9611UXC driver?
> > > >
> > > > Yes no need to rebuild, just use sysfs to trigger an update.
> > > >
> > > I think you haven't attempted to understand the intention behind my design.
> > >
> > > If during the debugging process, the customer discovers that a certain
> > > parameter in the chip's firmware is not suitable for the current
> > > situation, then he requests a perfect firmware from our company to be
> > > updated onto the chip.
> >
> > That's fine.
> >
> > >
> > > When there are hundreds or tens of thousands of devices that need to
> > > be updated, simply use sysfs to trigger the update. It is a very bad
> > > thing.
> >
> > Delivering updates to devices it off-topic here. You can use SWUpdate,
> > Mender or any other system to deliver updates and to trigger the
> > firmware reflash afterwards.
> >
> > > If you want to use version number comparison as the upgrade condition
> > > like in lt9611uxc.c, then the customer will need to modify the version
> > > number comparison condition and rebuild the driver. This method is not
> > > as simple as the one I have designed.
> >
> > Well, no. If there is a firmware update, it should be shared to
> > linux-firmware and then everybody can benefit from it.
> >
> > Think about one company using your chip in their SoM or compute module
> > and then another company integrating that module into their design?
> > Who will contact you? Or a company selling devkits with your chip.
> >
> > Having per-customer firmware is a nightmare for developers and for
> > integrators.
> >
>
> We are a company that sells our own developed chips. After other
> platform design companies purchase our chips, they will design their
> platforms based on the hardware schematic of our chips. During this
> process, they must contact us. We will communicate about the design
> opinions of the platform, the configuration of parameters, and the use
> of custom firmware. We cannot provide a common firmware. This is
> determined by the characteristics of the chips.
We are getting off-topic here, but this model has issues as the platform
design company might be completely different from the company that ends
up (re)using the platform in their products.
Anyway, I'm looking forward to reviewing the next iteration of the
driver. It is really appreciated when chip vendors work on the drivers
for their chips.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
2025-09-04 2:52 ` Dmitry Baryshkov
@ 2025-09-08 11:03 ` Jani Nikula
2025-09-08 11:07 ` Jani Nikula
2 siblings, 0 replies; 26+ messages in thread
From: Jani Nikula @ 2025-09-08 11:03 UTC (permalink / raw)
To: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss
Cc: Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993, syyang
On Wed, 03 Sep 2025, syyang <syyang@lontium.com> wrote:
> +static int lt9611c_read_edid(struct lt9611c *lt9611c)
> +{
> + struct device *dev = lt9611c->dev;
> + int ret, i, bytes_to_copy, offset = 0;
> + u8 packets_num;
> + u8 read_edid_data_cmd[5] = {0x52, 0x48, 0x33, 0x3A, 0x00};
> + u8 return_edid_data[37];
> + u8 read_edid_byte_num_cmd[5] = {0x52, 0x48, 0x32, 0x3A, 0x00};
> + u8 return_edid_byte_num[6];
> +
> + ret = i2c_read_write_flow(lt9611c, read_edid_byte_num_cmd, 5, return_edid_byte_num, 6);
> + if (ret) {
> + dev_err(dev, "Failed to read EDID byte number\n");
> + lt9611c->edid_valid = false;
> + return ret;
> + }
> +
> + lt9611c->edid_len = (return_edid_byte_num[4] << 8) | return_edid_byte_num[5];
> +
> + if (!lt9611c->edid_buf || lt9611c->edid_len > (lt9611c->edid_valid ?
> + lt9611c->edid_len : 0)) {
> + kfree(lt9611c->edid_buf);
> + lt9611c->edid_buf = kzalloc(lt9611c->edid_len, GFP_KERNEL);
> + if (!lt9611c->edid_buf) {
> + dev_err(dev, "Failed to allocate EDID buffer\n");
> + lt9611c->edid_len = 0;
> + lt9611c->edid_valid = false;
> + return -ENOMEM;
> + }
> + }
If you want to do caching, store a struct drm_edid pointer at a higher
level, not dumb buffers at the low level. Might be easier to start off
without any caching.
> +
> + packets_num = (lt9611c->edid_len % 32) ? (lt9611c->edid_len / 32 + 1) :
> + (lt9611c->edid_len / 32);
> + for (i = 0; i < packets_num; i++) {
> + read_edid_data_cmd[4] = (u8)i;
> + ret = i2c_read_write_flow(lt9611c, read_edid_data_cmd, 5, return_edid_data, 37);
> + if (ret) {
> + dev_err(dev, "Failed to read EDID packet %d\n", i);
> + lt9611c->edid_valid = false;
> + return -EIO;
> + }
> + offset = i * 32;
> + bytes_to_copy = min(32, lt9611c->edid_len - offset);
> + memcpy(lt9611c->edid_buf + offset, &return_edid_data[5], bytes_to_copy);
> + }
And really, you wouldn't have to implement the custom get edid block at
all, if you added a proper i2c adapter implementation and passed that to
drm_edid_read_ddc().
> +
> + lt9611c->edid_valid = true;
> +
> + return ret;
> +}
> +
> +static int lt9611c_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
> +{
> + struct lt9611c *lt9611c = data;
> + struct device *dev = lt9611c->dev;
> + unsigned int total_blocks;
> + int ret;
> +
> + if (len > 128)
> + return -EINVAL;
> +
> + guard(mutex)(<9611c->ocm_lock);
> + if (block == 0 || !lt9611c->edid_valid) {
> + ret = lt9611c_read_edid(lt9611c);
> + if (ret) {
> + dev_err(dev, "EDID read failed\n");
> + return ret;
> + }
> + }
> +
> + total_blocks = lt9611c->edid_len / 128;
> + if (!total_blocks) {
> + dev_err(dev, "No valid EDID blocks\n");
> + return -EIO;
> + }
> +
> + if (block >= total_blocks) {
> + dev_err(dev, "Requested block %u exceeds total blocks %u\n",
> + block, total_blocks);
> + return -EINVAL;
> + }
> +
> + memcpy(buf, lt9611c->edid_buf + block * 128, len);
The get edid block hook is supposed to read *one* block. Can't you
implement reading one block? This now reads the entire edid for every
block.
Again, better yet, i2c adapter implementation.
> +
> + return 0;
> +}
> +
> +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge,
> + struct drm_connector *connector)
> +{
> + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
> +
> + usleep_range(10000, 20000);
> + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
> +}
--
Jani Nikula, Intel
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip.
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
2025-09-04 2:52 ` Dmitry Baryshkov
2025-09-08 11:03 ` Jani Nikula
@ 2025-09-08 11:07 ` Jani Nikula
2 siblings, 0 replies; 26+ messages in thread
From: Jani Nikula @ 2025-09-08 11:07 UTC (permalink / raw)
To: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
rfoss
Cc: Laurent.pinchart, jonas, jernej.skrabec, devicetree, dri-devel,
linux-kernel, yangsunyun1993, syyang
On Wed, 03 Sep 2025, syyang <syyang@lontium.com> wrote:
> +static int i2c_write_byte(struct lt9611c *lt9611c, u8 reg, u8 val)
> +static int i2c_read_byte(struct lt9611c *lt9611c, u8 reg, u8 *val)
> +static int i2c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
> + unsigned int param_count, u8 *return_buffer,
> + unsigned int return_count)
include/linux/i2c.h provides a plethora of i2c_ prefixed functions and
types. I think it's bad practice to reuse the prefix of a very common
kernel interface, even if in static functions.
You might get collisions later, but the reader of the code is mislead to
believe this is something in i2c.h.
BR,
Jani.
--
Jani Nikula, Intel
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2025-09-08 11:07 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-03 12:38 [PATCH v1 0/2] Add LT9611C DRM bridge driver and device tree binding syyang
2025-09-03 12:38 ` [PATCH v1 1/2] This patch adds a new device tree binding documentation syyang
2025-09-03 16:33 ` Rob Herring (Arm)
2025-09-04 2:25 ` [PATCH v2 " syyang
2025-09-04 2:53 ` Dmitry Baryshkov
2025-09-04 5:49 ` Krzysztof Kozlowski
2025-09-04 8:08 ` 杨孙运
2025-09-04 8:26 ` Krzysztof Kozlowski
2025-09-04 11:27 ` 杨孙运
2025-09-04 2:21 ` [PATCH v1 " Dmitry Baryshkov
[not found] ` <CAFQXuNYKcGHyWLD5hjj24CrbaXzkaKsLU4R2vmhYaryQArA_yQ@mail.gmail.com>
2025-09-04 2:56 ` Dmitry Baryshkov
2025-09-03 12:38 ` [PATCH v1 2/2] This patch adds a new DRM bridge driver for the Lontium LT9611C chip syyang
2025-09-04 2:52 ` Dmitry Baryshkov
2025-09-04 10:48 ` 杨孙运
2025-09-04 11:04 ` Krzysztof Kozlowski
2025-09-04 11:17 ` 杨孙运
2025-09-04 14:39 ` Dmitry Baryshkov
2025-09-05 2:55 ` 杨孙运
2025-09-05 8:10 ` neil.armstrong
2025-09-05 8:58 ` 杨孙运
2025-09-05 14:24 ` Dmitry Baryshkov
2025-09-08 1:14 ` 杨孙运
2025-09-08 10:17 ` Dmitry Baryshkov
2025-09-05 14:10 ` Dmitry Baryshkov
2025-09-08 11:03 ` Jani Nikula
2025-09-08 11:07 ` Jani Nikula
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).