* [PATCH 1/2] dt-bindings: mailbox: qcom: Document Nord CPUCP mailbox controller
From: Shawn Guo @ 2026-04-20 3:49 UTC (permalink / raw)
To: Jassi Brar
Cc: Sibi Sankar, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, linux-arm-msm, linux-kernel, devicetree, Shawn Guo
In-Reply-To: <20260420034932.1247344-1-shengchao.guo@oss.qualcomm.com>
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Document CPUSS Control Processor (CPUCP) mailbox controller for Qualcomm
Nord SoC. It has 16 IPC channels, compared to 3 on X1E80100 CPUCP.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Signed-off-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
---
Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml b/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml
index 90bfde66cc4a..2dd66a88c186 100644
--- a/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml
+++ b/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml
@@ -23,6 +23,7 @@ properties:
- qcom,sm8750-cpucp-mbox
- const: qcom,x1e80100-cpucp-mbox
- enum:
+ - qcom,nord-cpucp-mbox
- qcom,x1e80100-cpucp-mbox
reg:
--
2.43.0
^ permalink raw reply related
* [PATCH 2/2] mailbox: qcom-cpucp: Add support for Nord CPUCP mailbox controller
From: Shawn Guo @ 2026-04-20 3:49 UTC (permalink / raw)
To: Jassi Brar
Cc: Sibi Sankar, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, linux-arm-msm, linux-kernel, devicetree, Shawn Guo
In-Reply-To: <20260420034932.1247344-1-shengchao.guo@oss.qualcomm.com>
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
The Nord SoC CPUCP mailbox supports 16 IPC channels, compared to 3 on
x1e80100. The existing driver hardcodes the channel count via a
compile-time constant (APSS_CPUCP_IPC_CHAN_SUPPORTED), making it
impossible to support hardware with a different number of channels.
Introduce a qcom_cpucp_mbox_data per-hardware configuration struct that
carries the channel count, and retrieve it via of_device_get_match_data()
at probe time. Switch the channel array from a fixed-size member to a
dynamically allocated buffer sized from the hardware data. Update the
x1e80100 entry to supply its own data struct, and add a new Nord entry
with num_chans = 16.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Signed-off-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
---
drivers/mailbox/qcom-cpucp-mbox.c | 37 ++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 6 deletions(-)
diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c
index 44f4ed15f818..624a4e9eb6c6 100644
--- a/drivers/mailbox/qcom-cpucp-mbox.c
+++ b/drivers/mailbox/qcom-cpucp-mbox.c
@@ -12,7 +12,6 @@
#include <linux/module.h>
#include <linux/platform_device.h>
-#define APSS_CPUCP_IPC_CHAN_SUPPORTED 3
#define APSS_CPUCP_MBOX_CMD_OFF 0x4
/* Tx Registers */
@@ -26,15 +25,23 @@
#define APSS_CPUCP_RX_MBOX_EN 0x4c00
#define APSS_CPUCP_RX_MBOX_CMD_MASK GENMASK_ULL(63, 0)
+/**
+ * struct qcom_cpucp_mbox_data - Per-hardware mailbox configuration data
+ * @num_chans: Number of IPC channels supported by this hardware
+ */
+struct qcom_cpucp_mbox_data {
+ int num_chans;
+};
+
/**
* struct qcom_cpucp_mbox - Holder for the mailbox driver
- * @chans: The mailbox channel
+ * @chans: The mailbox channels (dynamically allocated)
* @mbox: The mailbox controller
* @tx_base: Base address of the CPUCP tx registers
* @rx_base: Base address of the CPUCP rx registers
*/
struct qcom_cpucp_mbox {
- struct mbox_chan chans[APSS_CPUCP_IPC_CHAN_SUPPORTED];
+ struct mbox_chan *chans;
struct mbox_controller mbox;
void __iomem *tx_base;
void __iomem *rx_base;
@@ -53,7 +60,7 @@ static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data)
status = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_STAT);
- for_each_set_bit(i, (unsigned long *)&status, APSS_CPUCP_IPC_CHAN_SUPPORTED) {
+ for_each_set_bit(i, (unsigned long *)&status, cpucp->mbox.num_chans) {
u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF);
struct mbox_chan *chan = &cpucp->chans[i];
unsigned long flags;
@@ -112,15 +119,24 @@ static const struct mbox_chan_ops qcom_cpucp_mbox_chan_ops = {
static int qcom_cpucp_mbox_probe(struct platform_device *pdev)
{
+ const struct qcom_cpucp_mbox_data *data;
struct device *dev = &pdev->dev;
struct qcom_cpucp_mbox *cpucp;
struct mbox_controller *mbox;
int irq, ret;
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return dev_err_probe(dev, -EINVAL, "No match data found\n");
+
cpucp = devm_kzalloc(dev, sizeof(*cpucp), GFP_KERNEL);
if (!cpucp)
return -ENOMEM;
+ cpucp->chans = devm_kcalloc(dev, data->num_chans, sizeof(*cpucp->chans), GFP_KERNEL);
+ if (!cpucp->chans)
+ return -ENOMEM;
+
cpucp->rx_base = devm_of_iomap(dev, dev->of_node, 0, NULL);
if (IS_ERR(cpucp->rx_base))
return PTR_ERR(cpucp->rx_base);
@@ -146,7 +162,7 @@ static int qcom_cpucp_mbox_probe(struct platform_device *pdev)
mbox = &cpucp->mbox;
mbox->dev = dev;
- mbox->num_chans = APSS_CPUCP_IPC_CHAN_SUPPORTED;
+ mbox->num_chans = data->num_chans;
mbox->chans = cpucp->chans;
mbox->ops = &qcom_cpucp_mbox_chan_ops;
@@ -157,8 +173,17 @@ static int qcom_cpucp_mbox_probe(struct platform_device *pdev)
return 0;
}
+static const struct qcom_cpucp_mbox_data qcom_x1e80100_mbox_data = {
+ .num_chans = 3,
+};
+
+static const struct qcom_cpucp_mbox_data qcom_nord_mbox_data = {
+ .num_chans = 16,
+};
+
static const struct of_device_id qcom_cpucp_mbox_of_match[] = {
- { .compatible = "qcom,x1e80100-cpucp-mbox" },
+ { .compatible = "qcom,nord-cpucp-mbox", .data = &qcom_nord_mbox_data },
+ { .compatible = "qcom,x1e80100-cpucp-mbox", .data = &qcom_x1e80100_mbox_data },
{}
};
MODULE_DEVICE_TABLE(of, qcom_cpucp_mbox_of_match);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
From: Dmitry Baryshkov @ 2026-04-20 3:57 UTC (permalink / raw)
To: syyang
Cc: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
maarten.lankhorst, rfoss, mripard, Laurent.pinchart, tzimmermann,
jonas, jernej.skrabec, devicetree, dri-devel, linux-kernel,
yangsunyun1993, xmzhu
In-Reply-To: <20260420023354.1192642-3-syyang@lontium.com>
On Mon, 20 Apr 2026 at 05:34, <syyang@lontium.com> wrote:
>
> From: Sunyun Yang <syyang@lontium.com>
>
> LT7911EXC is a high performance eDP1.4 to MIPI chip for
MIPI what?
> VR/Display application.
>
> -eDP1.4Receiver
> 1.Support SSC
> 2.Support 1/2/4 lanes
> 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> 4.Support lane swap and PN swap
>
> -MIPI Transmitter
> 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> 1/2/3 configurable data trio;2.5Gsps perdatatrio
> 3.Support1/2configurable ports
> 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
>
> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> ---
> drivers/gpu/drm/bridge/Kconfig | 18 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> 3 files changed, 590 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/lontium-lt7911exc.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index c3209b0f4678..bae8cdaea666 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> Please say Y if you have such hardware.
>
> +config DRM_LONTIUM_LT9611C
I thought the patch is for LT7911EXC
> + tristate "Lontium LT9611C DSI/HDMI bridge"
> + select SND_SOC_HDMI_CODEC if SND_SOC
> + depends on OF
> + select CRC8
> + select FW_LOADER
> + 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 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_ITE_IT66121
> tristate "ITE IT66121 HDMI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index beab5b695a6e..54b293d1663e 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
Keep the list sorted, please.
> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o
> diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> new file mode 100644
> index 000000000000..d1c1d9e073ef
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> @@ -0,0 +1,571 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> + */
> +
> +#include <linux/crc32.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
I think you use more than that.
> +#include <drm/drm_of.h>
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 32
> +#define FW_FILE "LT7911EXC.bin"
> +#define LT7911EXC_PAGE_CONTROL 0xff
> +
> +struct lt7911exc {
> + struct device *dev;
> + struct i2c_client *client;
> + struct drm_bridge bridge;
> + struct drm_bridge *panel_bridge;
Use next_bridge from struct drm_bridge instead.
> + struct regmap *regmap;
> + /* Protects all accesses to registers by stopping the on-chip MCU */
> + struct mutex ocm_lock;
> + struct regulator_bulk_data supplies[2];
> +
> + struct gpio_desc *reset_gpio;
> + const struct firmware *fw;
Do you need to store it during the runtime? If not, please remove from
the data struct.
> + int fw_version;
> + u32 fw_crc;
> +
> + bool enabled;
What for?
> +};
> +
> +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> + {
> + .name = "register_range",
> + .range_min = 0,
> + .range_max = 0xffff,
Is it an actual range?
> + .selector_reg = LT7911EXC_PAGE_CONTROL,
> + .selector_mask = 0xff,
> + .selector_shift = 0,
> + .window_start = 0,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config lt7911exc_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0xffff,
> + .ranges = lt7911exc_ranges,
> + .num_ranges = ARRAY_SIZE(lt7911exc_ranges),
> +};
> +
> +static u32 cal_crc32_custom(const u8 *data, u64 length)
> +{
> + u32 crc = 0xffffffff;
> + u8 buf[4];
> + u64 i;
> +
> + for (i = 0; i < length; i += 4) {
> + buf[0] = data[i + 3];
> + buf[1] = data[i + 2];
> + buf[2] = data[i + 1];
> + buf[3] = data[i + 0];
> + crc = crc32_be(crc, buf, 4);
How is it different from crc32_le()?
> + }
Wrong alignment.
> +
> + return crc;
> +}
> +
> +static inline struct lt7911exc *
> + bridge_to_lt7911exc(struct drm_bridge *bridge)
One line, please.
> +{
> + return container_of(bridge, struct lt7911exc, bridge);
> +}
> +
> +static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_enable(lt7911exc->supplies[0].consumer);
If you are not using bulk interface here, why did you declare supplies as bulks?
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> + if (ret < 0) {
> + regulator_disable(lt7911exc->supplies[0].consumer);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> + if (ret < 0)
> + return ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> +{
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
Yep, it's active low. Invert polarities here.
> + msleep(400);
> +
> + dev_dbg(lt7911exc->dev, "lt7911exc reset");
> +}
> +
> +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + lt7911exc->supplies[0].supply = "vcc";
> + lt7911exc->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> + if (ret) {
> + dev_err(lt7911exc->dev, "failed get regulator\n");
> + return ret;
return dev_err_probe();
> + }
> +
> + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(lt7911exc->reset_gpio)) {
> + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
return dev_err_probe();
> + return PTR_ERR(lt7911exc->reset_gpio);
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> +{
> + u8 buf[2];
> + int ret;
> +
> + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
Do you see a buffer overflow here?
> + if (ret)
> + return ret;
> +
> + return (buf[0] << 16) | (buf[1] << 8) | buf[2];
> +}
> +
> +static void lt7911exc_lock(struct lt7911exc *lt7911exc)
> +{
> + mutex_lock(<7911exc->ocm_lock);
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
> +}
> +
> +static void lt7911exc_unlock(struct lt7911exc *lt7911exc)
> +{
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x00);
> + mutex_unlock(<7911exc->ocm_lock);
> +}
> +
> +static int lt7911exc_prepare_firmware_data(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + u8 *buffer;
> + size_t total_size = FW_SIZE - 4;
> +
> + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> + if (ret) {
> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> + return ret;
> + }
> +
> + if (lt7911exc->fw->size > total_size) {
> + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> +
> + buffer = kzalloc(total_size, GFP_KERNEL);
> + if (!buffer) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -ENOMEM;
> + }
> +
> + memset(buffer, 0xff, total_size);
> + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> +
> + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> +
> + kfree(buffer);
> + return 0;
> +}
> +
> +static void lt7911exc_block_erase(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + const u32 addr = 0x00;
> +
> + const struct reg_sequence seq_write[] = {
> + REG_SEQ0(0xe0ee, 0x01),
> + REG_SEQ0(0xe054, 0x01),
> + REG_SEQ0(0xe055, 0x06),
> + REG_SEQ0(0xe051, 0x01),
> + REG_SEQ0(0xe051, 0x00),
> + REG_SEQ0(0xe054, 0x05),
> + REG_SEQ0(0xe055, 0xd8),
> + REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
> + REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
> + REG_SEQ0(0xe05c, addr & 0xff),
> + REG_SEQ0(0xe051, 0x01),
> + REG_SEQ0(0xe050, 0x00),
> + };
> +
> + regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> +
> + msleep(200);
> + dev_dbg(dev, "erase flash done.\n");
> +}
> +
> +static void lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + const struct reg_sequence seq_write[] = {
> + REG_SEQ0(0xe0ee, 0x01),
> + REG_SEQ0(0xe05f, 0x01),
> + REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
> + REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
> + REG_SEQ0(0xe05c, addr & 0xff),
> + };
> +
> + regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> +}
> +
> +static int lt7911exc_write_data(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + int page = 0, num = 0, page_len = 0;
> + u64 size, offset;
> + const u8 *data;
> +
> + data = lt7911exc->fw->data;
> + size = lt7911exc->fw->size;
> + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> + if (page * LT_PAGE_SIZE > FW_SIZE) {
> + dev_err(dev, "firmware size out of range\n");
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "%u pages, total size %llu byte\n", page, size);
> +
> + for (num = 0; num < page; num++) {
> + offset = num * LT_PAGE_SIZE;
> + page_len = (offset + LT_PAGE_SIZE <= size) ? LT_PAGE_SIZE : (size - offset);
> + lt7911exc_prog_init(lt7911exc, addr);
> +
> + ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, &data[offset], page_len);
> + if (ret) {
> + dev_err(dev, "write error at page %d\n", num);
> + return ret;
> + }
> +
> + if (page_len < LT_PAGE_SIZE) {
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + //hardware requires delay
> + usleep_range(1000, 2000);
> + }
> +
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
> + addr += LT_PAGE_SIZE;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_write_crc(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + u8 crc[4];
> + int ret;
> +
> + crc[0] = lt7911exc->fw_crc & 0xff;
> + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> + crc[3] = (lt7911exc->fw_crc >> 24) & 0xff;
> +
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + regmap_write(lt7911exc->regmap, 0xe05a, (addr >> 16) & 0xff);
> + regmap_write(lt7911exc->regmap, 0xe05b, (addr >> 8) & 0xff);
> + regmap_write(lt7911exc->regmap, 0xe05c, addr & 0xff);
> +
> + ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, crc, 4);
> + if (ret)
> + return ret;
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + usleep_range(1000, 2000);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
> +
> + return 0;
> +}
> +
> +static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> +
> + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> + if (ret < 0)
> + return ret;
> +
> + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> +
> + lt7911exc_block_erase(lt7911exc);
> +
> + ret = lt7911exc_write_data(lt7911exc, 0);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware data\n");
> + return ret;
> + }
> +
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> +
> + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware crc\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + u32 read_hw_crc = 0;
> + u8 crc_tmp[4];
> + int ret;
> +
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
> + regmap_write(lt7911exc->regmap, 0xe07b, 0x60);
> + regmap_write(lt7911exc->regmap, 0xe07b, 0x40);
> + msleep(150);
> + ret = regmap_bulk_read(lt7911exc->regmap, 0x22, crc_tmp, 4);
> + if (ret) {
> + dev_err(lt7911exc->dev, "Failed to read CRC: %d\n", ret);
> + return ret;
> + }
> +
> + read_hw_crc = crc_tmp[0] << 24 | crc_tmp[1] << 16 |
> + crc_tmp[2] << 8 | crc_tmp[3];
> +
> + if (read_hw_crc != lt7911exc->fw_crc) {
> + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> + lt7911exc->fw_crc, read_hw_crc);
> + return -EIO;
> + }
> +
> + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> + return 0;
> +}
> +
> +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (lt7911exc->enabled)
> + return;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return;
> +
> + lt7911exc_reset(lt7911exc);
> +
> + lt7911exc->enabled = true;
> +}
> +
> +static void lt7911exc_disable(struct drm_bridge *bridge)
> +{
> + /* Delay after panel is disabled */
> + msleep(20);
> +}
> +
> +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (!lt7911exc->enabled)
> + return;
> +
> + lt7911exc->enabled = false;
> +
> + ret = lt7911exc_regulator_disable(lt7911exc);
> + if (ret)
> + return;
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> +}
> +
> +static int lt7911exc_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> +
> + return drm_bridge_attach(lt7911exc->bridge.encoder, lt7911exc->panel_bridge,
> + <7911exc->bridge, flags);
> +}
> +
> +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> + .pre_enable = lt7911exc_pre_enable,
> + .disable = lt7911exc_disable,
> + .post_disable = lt7911exc_post_disable,
> + .attach = lt7911exc_attach,
> +};
> +
> +static int lt7911exc_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct lt7911exc *lt7911exc;
> + struct drm_bridge *panel_bridge;
> + bool fw_updated = false;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + dev_err(dev, "device doesn't support I2C\n");
> + return -ENODEV;
> + }
> +
> + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> + <7911exc_bridge_funcs);
> + if (IS_ERR(lt7911exc))
> + return PTR_ERR(lt7911exc);
> +
> + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> + if (IS_ERR(panel_bridge))
> + return PTR_ERR(panel_bridge);
> +
> + lt7911exc->panel_bridge = panel_bridge;
> + lt7911exc->client = client;
> + lt7911exc->dev = dev;
> + i2c_set_clientdata(client, lt7911exc);
> + mutex_init(<7911exc->ocm_lock);
devm_mutex_init()
> +
> + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> + if (IS_ERR(lt7911exc->regmap)) {
> + dev_err(dev, "regmap i2c init failed\n");
> + return PTR_ERR(lt7911exc->regmap);
> + }
> +
> + ret = lt7911exc_parse_dt(lt7911exc);
> + if (ret)
> + return ret;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return ret;
> +
> + lt7911exc_reset(lt7911exc);
> + lt7911exc->enabled = true;
> + lt7911exc_lock(lt7911exc);
> +
> +retry:
> + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> + if (lt7911exc->fw_version < 0) {
> + dev_err(dev, "failed to read FW version\n");
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> +
> + } else if (lt7911exc->fw_version == 0) {
> + if (!fw_updated) {
> + fw_updated = true;
> + ret = lt7911exc_firmware_upgrade(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + lt7911exc_reset(lt7911exc);
> +
> + ret = lt7911exc_upgrade_result(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + goto retry;
> +
> + } else {
> + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> + ret = -EOPNOTSUPP;
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> + }
> +
> + lt7911exc_unlock(lt7911exc);
> +
> + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
So, this is the DSI host. Where do you register one? Where do you
populate the DT entries (if there is a panel attached to this bridge
it will be a child node).
> + lt7911exc->bridge.of_node = dev->of_node;
> + drm_bridge_add(<7911exc->bridge);
devm_drm_bridge_add().
> +
> + return 0;
> +
> +err_disable_regulators:
> + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> + if (lt7911exc->fw) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static void lt7911exc_remove(struct i2c_client *client)
> +{
> + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> +
> + drm_bridge_remove(<7911exc->bridge);
> + mutex_destroy(<7911exc->ocm_lock);
> +}
> +
> +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> + {"lontium, lt7911exc", 0},
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> +
> +static const struct of_device_id lt7911exc_devices[] = {
> + {.compatible = "lontium,lt7911exc",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, lt7911exc_devices);
> +
> +static struct i2c_driver lt7911exc_driver = {
> + .id_table = lt7911exc_i2c_table,
> + .probe = lt7911exc_probe,
> + .remove = lt7911exc_remove,
> + .driver = {
> + .name = "lt7911exc",
> + .of_match_table = lt7911exc_devices,
> + },
> +};
> +module_i2c_driver(lt7911exc_driver);
> +
> +MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
> +MODULE_DESCRIPTION("Lontium lt7911exc edp to mipi dsi bridge driver");
MIPI, DSI. It LT7911EXC or lt7911exc?
> +MODULE_LICENSE("GPL v2");
> --
> 2.34.1
>
--
With best wishes
Dmitry
^ permalink raw reply
* [PATCH] dt-bindings: mailbox: qcom-ipcc: Document Nord IPCC
From: Shawn Guo @ 2026-04-20 4:01 UTC (permalink / raw)
To: Jassi Brar
Cc: Manivannan Sadhasivam, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Konrad Dybcio, Dmitry Baryshkov,
Bartosz Golaszewski, Deepti Jaggi, linux-arm-msm, linux-kernel,
devicetree, Shawn Guo
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Document Inter-Processor Communication Controller on Qualcomm Nord SoC
with a fallback on qcom,ipcc.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Signed-off-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
---
Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
index f5c584cf2146..0a86230a2b18 100644
--- a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
+++ b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
@@ -28,6 +28,7 @@ properties:
- qcom,glymur-ipcc
- qcom,kaanapali-ipcc
- qcom,milos-ipcc
+ - qcom,nord-ipcc
- qcom,qcs8300-ipcc
- qcom,qdu1000-ipcc
- qcom,sa8255p-ipcc
--
2.43.0
^ permalink raw reply related
* [PATCH] dt-bindings: mfd: qcom,tcsr: Add compatible for Nord
From: Shawn Guo @ 2026-04-20 4:13 UTC (permalink / raw)
To: Lee Jones
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, devicetree, linux-arm-msm, linux-kernel
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Document Top Control and Status Register controller for Qualcomm Nord
SoC with a fallback on syscon.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
---
Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
index 14ae3f00ef7e..23317d1b381c 100644
--- a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
@@ -19,6 +19,7 @@ properties:
- enum:
- qcom,msm8976-tcsr
- qcom,msm8998-tcsr
+ - qcom,nord-tcsr
- qcom,qcm2290-tcsr
- qcom,qcs404-tcsr
- qcom,qcs615-tcsr
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] dt-bindings: mfd: qcom,tcsr: Add compatible for Nord
From: Shawn Guo @ 2026-04-20 4:19 UTC (permalink / raw)
To: Lee Jones
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, devicetree, linux-arm-msm, linux-kernel
In-Reply-To: <20260420041318.1247875-1-shengchao.guo@oss.qualcomm.com>
On Mon, Apr 20, 2026 at 12:13:18PM +0800, Shawn Guo wrote:
> From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
>
> Document Top Control and Status Register controller for Qualcomm Nord
> SoC with a fallback on syscon.
>
> Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Sorry, but please ignore this one. I forgot to add my SoB.
Shawn
^ permalink raw reply
* [PATCH RESEND] dt-bindings: mfd: qcom,tcsr: Add compatible for Nord
From: Shawn Guo @ 2026-04-20 4:21 UTC (permalink / raw)
To: Lee Jones
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, devicetree, linux-arm-msm, linux-kernel, Shawn Guo
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Document Top Control and Status Register controller for Qualcomm Nord
SoC with a fallback on syscon.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Signed-off-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
---
Resend to add my SoB.
Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
index 14ae3f00ef7e..23317d1b381c 100644
--- a/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom,tcsr.yaml
@@ -19,6 +19,7 @@ properties:
- enum:
- qcom,msm8976-tcsr
- qcom,msm8998-tcsr
+ - qcom,nord-tcsr
- qcom,qcm2290-tcsr
- qcom,qcs404-tcsr
- qcom,qcs615-tcsr
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v5 4/4] Input: charlieplex_keypad: add GPIO charlieplex keypad
From: Dmitry Torokhov @ 2026-04-20 4:47 UTC (permalink / raw)
To: Hugo Villeneuve
Cc: robin, andy, geert, robh, krzk+dt, conor+dt, hvilleneuve,
mkorpershoek, matthias.bgg, angelogioacchino.delregno, lee,
alexander.sverdlin, marek.vasut, akurz, devicetree, linux-kernel,
linux-input, linux-arm-kernel, linux-mediatek
In-Reply-To: <20260312180304.3865850-5-hugo@hugovil.com>
Hi Hugo,
On Thu, Mar 12, 2026 at 02:00:58PM -0400, Hugo Villeneuve wrote:
> +
> +static void charlieplex_keypad_report_key(struct input_dev *input)
> +{
> + struct charlieplex_keypad *keypad = input_get_drvdata(input);
> + const unsigned short *keycodes = input->keycode;
> +
> + if (keypad->current_code > 0) {
> + input_event(input, EV_MSC, MSC_SCAN, keypad->current_code);
> + input_report_key(input, keycodes[keypad->current_code], 0);
This needs input_sync() as otherwise userspace is free to only recognize
the last MSC_SCAN event.
> + }
> +
> + if (keypad->debounce_code) {
> + input_event(input, EV_MSC, MSC_SCAN, keypad->debounce_code);
> + input_report_key(input, keycodes[keypad->debounce_code], 1);
> + }
> +
> + input_sync(input);
> + keypad->current_code = keypad->debounce_code;
> +}
> +
> +static void charlieplex_keypad_check_switch_change(struct input_dev *input,
> + int code)
> +{
> + struct charlieplex_keypad *keypad = input_get_drvdata(input);
> +
> + if (code != keypad->debounce_code) {
> + keypad->debounce_count = 0;
> + keypad->debounce_code = code;
> + } else if (keypad->debounce_count < keypad->debounce_threshold) {
This does not work if debouncing is disabled (debounce threshold is 0).
> + keypad->debounce_count++;
> +
> + if (keypad->debounce_count >= keypad->debounce_threshold &&
> + keypad->debounce_code != keypad->current_code)
> + charlieplex_keypad_report_key(input);
> + }
> +}
> +
> +static void charlieplex_keypad_poll(struct input_dev *input)
> +{
> + struct charlieplex_keypad *keypad = input_get_drvdata(input);
> + int code;
> +
> + code = 0;
> + for (unsigned int oline = 0; oline < keypad->nlines; oline++) {
> + DECLARE_BITMAP(values, MATRIX_MAX_ROWS);
> + int err;
> +
> + /* Activate only one line as output at a time. */
> + gpiod_direction_output(keypad->line_gpios->desc[oline], 1);
> +
> + if (keypad->settling_time_us)
> + fsleep(keypad->settling_time_us);
> +
> + /* Read input on all other lines. */
> + err = gpiod_get_array_value_cansleep(keypad->line_gpios->ndescs,
> + keypad->line_gpios->desc,
> + keypad->line_gpios->info, values);
> + if (err)
> + return;
We need to deactivate the line on error too.
> +
> + for (unsigned int iline = 0; iline < keypad->nlines; iline++) {
> + if (iline == oline)
> + continue; /* Do not read active output line. */
> +
> + /* Check if GPIO is asserted. */
> + if (test_bit(iline, values)) {
> + code = MATRIX_SCAN_CODE(oline, iline,
> + get_count_order(keypad->nlines));
> + /*
> + * Exit loop immediately since we cannot detect
> + * more than one key press at a time.
> + */
> + break;
> + }
> + }
> +
> + gpiod_direction_input(keypad->line_gpios->desc[oline]);
> +
> + if (code)
> + break;
> + }
> +
> + charlieplex_keypad_check_switch_change(input, code);
> +}
> +
> +static int charlieplex_keypad_init_gpio(struct platform_device *pdev,
> + struct charlieplex_keypad *keypad)
> +{
> + char **pin_names;
> + char label[32];
> +
> + snprintf(label, sizeof(label), "%s-pin", pdev->name);
> +
> + keypad->line_gpios = devm_gpiod_get_array(&pdev->dev, "line", GPIOD_IN);
> + if (IS_ERR(keypad->line_gpios))
> + return PTR_ERR(keypad->line_gpios);
> +
> + keypad->nlines = keypad->line_gpios->ndescs;
> +
> + if (keypad->nlines > MATRIX_MAX_ROWS)
> + return -EINVAL;
> +
> + pin_names = devm_kasprintf_strarray(&pdev->dev, label, keypad->nlines);
> + if (IS_ERR(pin_names))
> + return PTR_ERR(pin_names);
> +
> + for (unsigned int i = 0; i < keypad->line_gpios->ndescs; i++)
> + gpiod_set_consumer_name(keypad->line_gpios->desc[i], pin_names[i]);
> +
> + return 0;
> +}
> +
> +static int charlieplex_keypad_probe(struct platform_device *pdev)
> +{
> + struct charlieplex_keypad *keypad;
> + unsigned int debounce_interval_ms;
> + unsigned int poll_interval_ms;
> + struct input_dev *input_dev;
> + int err;
> +
> + keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
> + if (!keypad)
> + return -ENOMEM;
> +
> + input_dev = devm_input_allocate_device(&pdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + keypad->input_dev = input_dev;
> +
> + device_property_read_u32(&pdev->dev, "poll-interval", &poll_interval_ms);
> + device_property_read_u32(&pdev->dev, "debounce-delay-ms", &debounce_interval_ms);
> + device_property_read_u32(&pdev->dev, "settling-time-us", &keypad->settling_time_us);
Not all of these are required properties. If they are missing the driver
will operate on garbage values.
> +
> + keypad->current_code = -1;
> + keypad->debounce_code = -1;
> + keypad->debounce_threshold = DIV_ROUND_UP(debounce_interval_ms, poll_interval_ms);
This will bomb if poll interval is 0.
> +
> + err = charlieplex_keypad_init_gpio(pdev, keypad);
> + if (err)
> + return err;
> +
> + input_dev->name = pdev->name;
> + input_dev->id.bustype = BUS_HOST;
> +
> + err = matrix_keypad_build_keymap(NULL, NULL, keypad->nlines,
> + keypad->nlines, NULL, input_dev);
> + if (err)
> + dev_err_probe(&pdev->dev, -ENOMEM, "failed to build keymap\n");
Missing "return".
> +
> + if (device_property_read_bool(&pdev->dev, "autorepeat"))
> + __set_bit(EV_REP, input_dev->evbit);
> +
> + input_set_capability(input_dev, EV_MSC, MSC_SCAN);
> +
> + err = input_setup_polling(input_dev, charlieplex_keypad_poll);
> + if (err)
> + dev_err_probe(&pdev->dev, err, "unable to set up polling\n");
Missing "return".
I fixed it up and applied, please take a look in my 'next' branch and
tell me if I messed up.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 2/2] drm/bridge: Add LT7911EXC edp to mipi bridge driver
From: Quentin Freimanis @ 2026-04-20 5:05 UTC (permalink / raw)
To: syyang, robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, yangsunyun1993, xmzhu
In-Reply-To: <20260420023354.1192642-3-syyang@lontium.com>
On 2026-04-19 7:33 p.m., syyang@lontium.com wrote:
> From: Sunyun Yang <syyang@lontium.com>
>
> LT7911EXC is a high performance eDP1.4 to MIPI chip for
> VR/Display application.
>
> -eDP1.4Receiver
> 1.Support SSC
s/1.Support/1. Supports/
> 2.Support 1/2/4 lanes
> 3.Support up to 4K@60HzRGB/YCbCr4:4:48bpc
> 4.Support lane swap and PN swap
Same for these
>
> -MIPI Transmitter
> 1.CompliantwithD-PHY1.2&DSI1.1&CSI-22.0;1 clock lane,
> and1/2/3/4 configurable data lanes:2.5Gbpsperdatalane
> 2.CompliantwithC-PHY1.0&DSI-21.0&CSI-22.0;
> 1/2/3 configurable data trio;2.5Gsps perdatatrio
> 3.Support1/2configurable ports
> 4.DSISupport16/20/24-bit YCbCr4:2:2,16/18/24/30-bit RGB
Missing spaces, this is hard to read and needs to be cleaned up
>
> Signed-off-by: Sunyun Yang <syyang@lontium.com>
> ---
> drivers/gpu/drm/bridge/Kconfig | 18 +
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/lontium-lt7911exc.c | 571 +++++++++++++++++++++
> 3 files changed, 590 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/lontium-lt7911exc.c
>
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index c3209b0f4678..bae8cdaea666 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -202,6 +202,24 @@ config DRM_LONTIUM_LT8713SX
> to 3 configurable Type-C/DP1.4/HDMI2.0 outputs
> 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 CRC8
> + select FW_LOADER
> + 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 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_ITE_IT66121
> tristate "ITE IT66121 HDMI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index beab5b695a6e..54b293d1663e 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
> obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
> obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o
> +obj-$(CONFIG_DRM_LONTIUM_LT7911EXC) += lontium-lt7911exc.o
> obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
> obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o
> diff --git a/drivers/gpu/drm/bridge/lontium-lt7911exc.c b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> new file mode 100644
> index 000000000000..d1c1d9e073ef
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/lontium-lt7911exc.c
> @@ -0,0 +1,571 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2026 Lontium Semiconductor, Inc.
> + */
> +
> +#include <linux/crc32.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/regmap.h>
> +#include <drm/drm_of.h>
> +
> +#define FW_SIZE (64 * 1024)
> +#define LT_PAGE_SIZE 32
> +#define FW_FILE "LT7911EXC.bin"
Other lontium bridge chips use lowercase and a _fw.bin suffix, such as
"lt9611uxc_fw.bin". Rename to be consistent
> +#define LT7911EXC_PAGE_CONTROL 0xff
> +
> +struct lt7911exc {
> + struct device *dev;
> + struct i2c_client *client;
> + struct drm_bridge bridge;
> + struct drm_bridge *panel_bridge;
> + struct regmap *regmap;
> + /* Protects all accesses to registers by stopping the on-chip MCU */
> + struct mutex ocm_lock;
> + struct regulator_bulk_data supplies[2];
> +
> + struct gpio_desc *reset_gpio;
> + const struct firmware *fw;
> + int fw_version;
> + u32 fw_crc;
> +
> + bool enabled;
> +};
> +
> +static const struct regmap_range_cfg lt7911exc_ranges[] = {
> + {
> + .name = "register_range",
> + .range_min = 0,
> + .range_max = 0xffff,
> + .selector_reg = LT7911EXC_PAGE_CONTROL,
> + .selector_mask = 0xff,
> + .selector_shift = 0,
> + .window_start = 0,
> + .window_len = 0x100,
> + },
> +};
> +
> +static const struct regmap_config lt7911exc_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0xffff,
> + .ranges = lt7911exc_ranges,
> + .num_ranges = ARRAY_SIZE(lt7911exc_ranges),
> +};
> +
> +static u32 cal_crc32_custom(const u8 *data, u64 length)
> +{
> + u32 crc = 0xffffffff;
> + u8 buf[4];
> + u64 i;
> +
> + for (i = 0; i < length; i += 4) {
> + buf[0] = data[i + 3];
> + buf[1] = data[i + 2];
> + buf[2] = data[i + 1];
> + buf[3] = data[i + 0];
> + crc = crc32_be(crc, buf, 4);
> + }
> +
> + return crc;
> +}
> +
> +static inline struct lt7911exc *
> + bridge_to_lt7911exc(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct lt7911exc, bridge);
> +}
> +
> +static int lt7911exc_regulator_enable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_enable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + usleep_range(5000, 10000);
> +
> + ret = regulator_enable(lt7911exc->supplies[1].consumer);
> + if (ret < 0) {
> + regulator_disable(lt7911exc->supplies[0].consumer);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_regulator_disable(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[1].consumer);
> + if (ret < 0)
> + return ret;
> +
> + ret = regulator_disable(lt7911exc->supplies[0].consumer);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static void lt7911exc_reset(struct lt7911exc *lt7911exc)
> +{
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> + msleep(20);
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 1);
> + msleep(400);
> +
> + dev_dbg(lt7911exc->dev, "lt7911exc reset");
missing newline in dev_dbg(), all other calls have it.
> +}
> +
> +static int lt7911exc_parse_dt(struct lt7911exc *lt7911exc)
> +{
> + int ret;
> +
> + lt7911exc->supplies[0].supply = "vcc";
> + lt7911exc->supplies[1].supply = "vdd";
> +
> + ret = devm_regulator_bulk_get(lt7911exc->dev, 2, lt7911exc->supplies);
> + if (ret) {
> + dev_err(lt7911exc->dev, "failed get regulator\n");
> + return ret;
> + }
> +
> + lt7911exc->reset_gpio = devm_gpiod_get(lt7911exc->dev, "reset", GPIOD_OUT_LOW);
> + if (IS_ERR(lt7911exc->reset_gpio)) {
> + dev_err(lt7911exc->dev, "failed to acquire reset gpio\n");
> + return PTR_ERR(lt7911exc->reset_gpio);
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_read_version(struct lt7911exc *lt7911exc)
> +{
> + u8 buf[2];
> + int ret;
> +
> + ret = regmap_bulk_read(lt7911exc->regmap, 0xe081, buf, 3);
> + if (ret)
> + return ret;
> +
> + return (buf[0] << 16) | (buf[1] << 8) | buf[2];
> +}
> +
> +static void lt7911exc_lock(struct lt7911exc *lt7911exc)
> +{
> + mutex_lock(<7911exc->ocm_lock);
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
> +}
> +
> +static void lt7911exc_unlock(struct lt7911exc *lt7911exc)
> +{
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x00);
> + mutex_unlock(<7911exc->ocm_lock);
> +}
> +
> +static int lt7911exc_prepare_firmware_data(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + u8 *buffer;
> + size_t total_size = FW_SIZE - 4;
> +
> + ret = request_firmware(<7911exc->fw, FW_FILE, dev);
> + if (ret) {
> + dev_err(dev, "failed load file '%s', error type %d\n", FW_FILE, ret);
> + return ret;
> + }
> +
> + if (lt7911exc->fw->size > total_size) {
> + dev_err(dev, "firmware too large (%zu > %zu)\n", lt7911exc->fw->size, total_size);
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "firmware size: %zu bytes\n", lt7911exc->fw->size);
> +
> + buffer = kzalloc(total_size, GFP_KERNEL);
> + if (!buffer) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + return -ENOMEM;
> + }
> +
> + memset(buffer, 0xff, total_size);
> + memcpy(buffer, lt7911exc->fw->data, lt7911exc->fw->size);
> +
> + lt7911exc->fw_crc = cal_crc32_custom(buffer, total_size);
> + dev_dbg(dev, "firmware crc: 0x%08x\n", lt7911exc->fw_crc);
> +
> + kfree(buffer);
> + return 0;
> +}
> +
> +static void lt7911exc_block_erase(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + const u32 addr = 0x00;
> +
> + const struct reg_sequence seq_write[] = {
> + REG_SEQ0(0xe0ee, 0x01),
> + REG_SEQ0(0xe054, 0x01),
> + REG_SEQ0(0xe055, 0x06),
> + REG_SEQ0(0xe051, 0x01),
> + REG_SEQ0(0xe051, 0x00),
> + REG_SEQ0(0xe054, 0x05),
> + REG_SEQ0(0xe055, 0xd8),
> + REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
> + REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
> + REG_SEQ0(0xe05c, addr & 0xff),
> + REG_SEQ0(0xe051, 0x01),
> + REG_SEQ0(0xe050, 0x00),
> + };
> +
> + regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> +
> + msleep(200);
> + dev_dbg(dev, "erase flash done.\n");
> +}
> +
> +static void lt7911exc_prog_init(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + const struct reg_sequence seq_write[] = {
> + REG_SEQ0(0xe0ee, 0x01),
> + REG_SEQ0(0xe05f, 0x01),
> + REG_SEQ0(0xe05a, (addr >> 16) & 0xff),
> + REG_SEQ0(0xe05b, (addr >> 8) & 0xff),
> + REG_SEQ0(0xe05c, addr & 0xff),
> + };
> +
> + regmap_multi_reg_write(lt7911exc->regmap, seq_write, ARRAY_SIZE(seq_write));
> +}
> +
> +static int lt7911exc_write_data(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> + int page = 0, num = 0, page_len = 0;
> + u64 size, offset;
> + const u8 *data;
> +
> + data = lt7911exc->fw->data;
> + size = lt7911exc->fw->size;
> + page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
> + if (page * LT_PAGE_SIZE > FW_SIZE) {
> + dev_err(dev, "firmware size out of range\n");
> + return -EINVAL;
> + }
> +
> + dev_dbg(dev, "%u pages, total size %llu byte\n", page, size);
> +
> + for (num = 0; num < page; num++) {
> + offset = num * LT_PAGE_SIZE;
> + page_len = (offset + LT_PAGE_SIZE <= size) ? LT_PAGE_SIZE : (size - offset);
> + lt7911exc_prog_init(lt7911exc, addr);
> +
> + ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, &data[offset], page_len);
> + if (ret) {
> + dev_err(dev, "write error at page %d\n", num);
> + return ret;
> + }
> +
> + if (page_len < LT_PAGE_SIZE) {
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + //hardware requires delay
> + usleep_range(1000, 2000);
> + }
> +
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
> + addr += LT_PAGE_SIZE;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_write_crc(struct lt7911exc *lt7911exc, u64 addr)
> +{
> + u8 crc[4];
> + int ret;
> +
> + crc[0] = lt7911exc->fw_crc & 0xff;
> + crc[1] = (lt7911exc->fw_crc >> 8) & 0xff;
> + crc[2] = (lt7911exc->fw_crc >> 16) & 0xff;
> + crc[3] = (lt7911exc->fw_crc >> 24) & 0xff;
> +
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + regmap_write(lt7911exc->regmap, 0xe05a, (addr >> 16) & 0xff);
> + regmap_write(lt7911exc->regmap, 0xe05b, (addr >> 8) & 0xff);
> + regmap_write(lt7911exc->regmap, 0xe05c, addr & 0xff);
> +
> + ret = regmap_raw_write(lt7911exc->regmap, 0xe05d, crc, 4);
> + if (ret)
> + return ret;
nit: Newline here makes it more readable
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x05);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x01);
> + usleep_range(1000, 2000);
> + regmap_write(lt7911exc->regmap, 0xe05f, 0x00);
> +
> + return 0;
> +}
> +
> +static int lt7911exc_firmware_upgrade(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + int ret;
> +
> + ret = lt7911exc_prepare_firmware_data(lt7911exc);
> + if (ret < 0)
> + return ret;
> +
> + dev_dbg(dev, "starting firmware upgrade, size: %zu bytes\n", lt7911exc->fw->size);
> +
> + lt7911exc_block_erase(lt7911exc);
> +
> + ret = lt7911exc_write_data(lt7911exc, 0);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware data\n");
> + return ret;
> + }
> +
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> +
> + ret = lt7911exc_write_crc(lt7911exc, FW_SIZE - 4);
> + if (ret < 0) {
> + dev_err(dev, "failed to write firmware crc\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lt7911exc_upgrade_result(struct lt7911exc *lt7911exc)
> +{
> + struct device *dev = lt7911exc->dev;
> + u32 read_hw_crc = 0;
> + u8 crc_tmp[4];
> + int ret;
> +
> + regmap_write(lt7911exc->regmap, 0xe0ee, 0x01);
> + regmap_write(lt7911exc->regmap, 0xe07b, 0x60);
> + regmap_write(lt7911exc->regmap, 0xe07b, 0x40);
> + msleep(150);
> + ret = regmap_bulk_read(lt7911exc->regmap, 0x22, crc_tmp, 4);
> + if (ret) {
> + dev_err(lt7911exc->dev, "Failed to read CRC: %d\n", ret);
> + return ret;
> + }
> +
> + read_hw_crc = crc_tmp[0] << 24 | crc_tmp[1] << 16 |
> + crc_tmp[2] << 8 | crc_tmp[3];
> +
> + if (read_hw_crc != lt7911exc->fw_crc) {
> + dev_err(dev, "lt7911exc firmware upgrade failed, expected CRC=0x%08x, read CRC=0x%08x\n",
> + lt7911exc->fw_crc, read_hw_crc);
> + return -EIO;
> + }
> +
> + dev_dbg(dev, "lt7911exc firmware upgrade success, CRC=0x%08x\n", read_hw_crc);
> + return 0;
> +}
> +
> +static void lt7911exc_pre_enable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (lt7911exc->enabled)
> + return;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return;
> +
> + lt7911exc_reset(lt7911exc);
> +
> + lt7911exc->enabled = true;
> +}
> +
> +static void lt7911exc_disable(struct drm_bridge *bridge)
> +{
> + /* Delay after panel is disabled */
> + msleep(20);
> +}
> +
> +static void lt7911exc_post_disable(struct drm_bridge *bridge)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> + int ret;
> +
> + if (!lt7911exc->enabled)
> + return;
> +
> + lt7911exc->enabled = false;
> +
> + ret = lt7911exc_regulator_disable(lt7911exc);
> + if (ret)
> + return;
> +
> + gpiod_set_value_cansleep(lt7911exc->reset_gpio, 0);
> +}
> +
> +static int lt7911exc_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct lt7911exc *lt7911exc = bridge_to_lt7911exc(bridge);
> +
> + return drm_bridge_attach(lt7911exc->bridge.encoder, lt7911exc->panel_bridge,
> + <7911exc->bridge, flags);
> +}
> +
> +static const struct drm_bridge_funcs lt7911exc_bridge_funcs = {
> + .pre_enable = lt7911exc_pre_enable,
> + .disable = lt7911exc_disable,
> + .post_disable = lt7911exc_post_disable,
These are all deprecated, is there any reason to use them and not the
atomic_* callbacks?
> + .attach = lt7911exc_attach,
> +};
> +
> +static int lt7911exc_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct lt7911exc *lt7911exc;
> + struct drm_bridge *panel_bridge;
> + bool fw_updated = false;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
> + dev_err(dev, "device doesn't support I2C\n");
> + return -ENODEV;
> + }
> +
> + lt7911exc = devm_drm_bridge_alloc(dev, struct lt7911exc, bridge,
> + <7911exc_bridge_funcs);
> + if (IS_ERR(lt7911exc))
> + return PTR_ERR(lt7911exc);
> +
> + panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
> + if (IS_ERR(panel_bridge))
> + return PTR_ERR(panel_bridge);
> +
> + lt7911exc->panel_bridge = panel_bridge;
> + lt7911exc->client = client;
> + lt7911exc->dev = dev;
> + i2c_set_clientdata(client, lt7911exc);
> + mutex_init(<7911exc->ocm_lock);
> +
> + lt7911exc->regmap = devm_regmap_init_i2c(client, <7911exc_regmap_config);
> + if (IS_ERR(lt7911exc->regmap)) {
> + dev_err(dev, "regmap i2c init failed\n");
> + return PTR_ERR(lt7911exc->regmap);
> + }
> +
> + ret = lt7911exc_parse_dt(lt7911exc);
> + if (ret)
> + return ret;
> +
> + ret = lt7911exc_regulator_enable(lt7911exc);
> + if (ret)
> + return ret;
> +
> + lt7911exc_reset(lt7911exc);
> + lt7911exc->enabled = true;
> + lt7911exc_lock(lt7911exc);
> +
> +retry:
> + lt7911exc->fw_version = lt7911exc_read_version(lt7911exc);
> + if (lt7911exc->fw_version < 0) {
> + dev_err(dev, "failed to read FW version\n");
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> +
> + } else if (lt7911exc->fw_version == 0) {
> + if (!fw_updated) {
> + fw_updated = true;
> + ret = lt7911exc_firmware_upgrade(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + lt7911exc_reset(lt7911exc);
> +
> + ret = lt7911exc_upgrade_result(lt7911exc);
> + if (ret < 0) {
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> +
> + goto retry;
> +
> + } else {
> + dev_err(dev, "fw version 0x%04x, update failed\n", lt7911exc->fw_version);
> + ret = -EOPNOTSUPP;
> + lt7911exc_unlock(lt7911exc);
> + goto err_disable_regulators;
> + }
> + }
> +
> + lt7911exc_unlock(lt7911exc);
> +
> + lt7911exc->bridge.type = DRM_MODE_CONNECTOR_DSI;
> + lt7911exc->bridge.of_node = dev->of_node;
> + drm_bridge_add(<7911exc->bridge);
> +
> + return 0;
> +
> +err_disable_regulators:
> + regulator_bulk_disable(ARRAY_SIZE(lt7911exc->supplies), lt7911exc->supplies);
> + if (lt7911exc->fw) {
> + release_firmware(lt7911exc->fw);
> + lt7911exc->fw = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static void lt7911exc_remove(struct i2c_client *client)
> +{
> + struct lt7911exc *lt7911exc = i2c_get_clientdata(client);
> +
> + drm_bridge_remove(<7911exc->bridge);
> + mutex_destroy(<7911exc->ocm_lock);
> +}
> +
> +static const struct i2c_device_id lt7911exc_i2c_table[] = {
> + {"lontium, lt7911exc", 0},
Extra space here in the device id
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, lt7911exc_i2c_table);
> +
> +static const struct of_device_id lt7911exc_devices[] = {
> + {.compatible = "lontium,lt7911exc",},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, lt7911exc_devices);
> +
> +static struct i2c_driver lt7911exc_driver = {
> + .id_table = lt7911exc_i2c_table,
> + .probe = lt7911exc_probe,
> + .remove = lt7911exc_remove,
> + .driver = {
> + .name = "lt7911exc",
> + .of_match_table = lt7911exc_devices,
> + },
> +};
> +module_i2c_driver(lt7911exc_driver);
> +
> +MODULE_AUTHOR("SunYun Yang <syyang@lontium.com>");
> +MODULE_DESCRIPTION("Lontium lt7911exc edp to mipi dsi bridge driver");
> +MODULE_LICENSE("GPL v2");
^ permalink raw reply
* Re: [PATCH v2 2/2] riscv: ultrarisc: 8250_dw: support DP1000 uart
From: Jia Wang @ 2026-04-20 5:27 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jia Wang, Ilpo Järvinen, Greg Kroah-Hartman, Jiri Slaby,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-kernel,
linux-serial, linux-riscv, devicetree, Zhang Xincheng
In-Reply-To: <aeHlaTIKm1wl0J0_@ashevche-desk.local>
On 2026-04-17 10:46 +0300, Andy Shevchenko wrote:
> On Fri, Apr 17, 2026 at 03:32:17PM +0800, Jia Wang wrote:
> > On 2026-03-16 13:35 +0200, Andy Shevchenko wrote:
> > > On Mon, Mar 16, 2026 at 02:33:23PM +0800, Jia Wang via B4 Relay wrote:
>
> ...
>
> > > > +#define DW_UART_QUIRK_FIXED_TYPE BIT(6)
> > >
> > > Seems unrequired.
> > >
> > > But to make sure, can you elaborate what's going on here?
> > > What is the reads from UCV and CPR registers?
> >
> > Apologies for the delayed response.
> >
> > Our DW UART implementation on DP1000 does not provide the CPR/UCV capability
> > registers, and reads from both registers always return 0. As a result, the
> > autodetection logic in 8250_dw cannot obtain meaningful capability
> > information.
> >
> > To handle this, the current approach is to skip autodetection and rely on
> > fixed configuration via a quirk.
> >
> > If there is a preferred or more appropriate way to support DW UART instances
> > without CPR/UCV, I would be happy to adjust the implementation based on your
> > suggestions.
>
> Why can't you provide a CPR value via the existing quirk?
>
Thanks for the feedback.
I will switch to using DW_UART_QUIRK_CPR_VALUE in v3.
> --
> With Best Regards,
> Andy Shevchenko
>
>
>
Best Regards,
Jia Wang
^ permalink raw reply
* Re: [PATCH v2 1/3] dt-bindings: arm: aspeed: add Anacapa EVT1 EVT2 board
From: Colin Huang @ 2026-04-20 5:41 UTC (permalink / raw)
To: Conor Dooley
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, devicetree, linux-arm-kernel, linux-aspeed,
linux-kernel, colin.huang2
In-Reply-To: <20260409-foster-stability-f77b38c6f7a0@spud>
Conor Dooley <conor@kernel.org> 於 2026年4月9日週四 下午11:36寫道:
>
> On Thu, Apr 09, 2026 at 07:40:26PM +0800, Colin Huang wrote:
> > Document Anacapa BMC EVT1 and EVT2 compatibles.
> >
> > Signed-off-by: Colin Huang <u8813345@gmail.com>
>
> Acked-by: Conor Dooley <conor.dooley@microchip.com>
> pw-bot: not-applicable
Hi
Could anyone let me know, what is my next step which I need to do?
I can't find the changed in for-next branch of
https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux.git .
Thanks.
Regard,
Colin Huang
^ permalink raw reply
* Re: [PATCH 2/5] media: synopsys: Add support for multiple streams
From: Frank Li @ 2026-04-20 5:42 UTC (permalink / raw)
To: Guoniu Zhou
Cc: Michael Riesch, Mauro Carvalho Chehab, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Laurent Pinchart, linux-media, linux-kernel, devicetree, imx,
linux-arm-kernel, linux-rockchip
In-Reply-To: <20260415-csi2_imx95-v1-2-7d63f3508719@oss.nxp.com>
On Wed, Apr 15, 2026 at 11:46:53AM +0800, Guoniu Zhou wrote:
> The current driver only supports single stream operation. Add support
> for multiple concurrent streams by tracking enabled streams with a
> bitmask and only initializing the hardware once for the first stream.
>
> This enables use cases such as surround view systems where multiple
> camera streams need to be processed simultaneously through the same
> CSI-2 receiver interface.
>
> Signed-off-by: Guoniu Zhou <guoniu.zhou@oss.nxp.com>
> ---
> drivers/media/platform/synopsys/dw-mipi-csi2rx.c | 45 ++++++++++++++----------
> 1 file changed, 27 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> index 46e2a4315ac2..85a2a95bf080 100644
> --- a/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> +++ b/drivers/media/platform/synopsys/dw-mipi-csi2rx.c
> @@ -113,6 +113,7 @@ struct dw_mipi_csi2rx_device {
>
> enum v4l2_mbus_type bus_type;
> u32 lanes_num;
> + u64 enabled_streams;
>
> const struct dw_mipi_csi2rx_drvdata *drvdata;
> };
> @@ -528,28 +529,31 @@ static int dw_mipi_csi2rx_enable_streams(struct v4l2_subdev *sd,
> DW_MIPI_CSI2RX_PAD_SRC,
> &streams_mask);
>
It maybe simpler
u64 enabled_streams = csi2->enabled_streams;
csi2->enabled_streams |= streams_mask;
if (!enabled_stream)
return 0;
....
err:
si2->enabled_streams &= ~streams_mask;
Frank
> - ret = pm_runtime_resume_and_get(dev);
> - if (ret)
> - goto err;
> + if (!csi2->enabled_streams) {
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret)
> + return ret;
>
> - ret = dw_mipi_csi2rx_start(csi2);
> - if (ret) {
> - dev_err(dev, "failed to enable CSI hardware\n");
> - goto err_pm_runtime_put;
> + ret = dw_mipi_csi2rx_start(csi2);
> + if (ret) {
> + pm_runtime_put(dev);
> + dev_err(dev, "failed to enable CSI hardware\n");
> + return ret;
> + }
> }
>
> ret = v4l2_subdev_enable_streams(remote_sd, remote_pad->index, mask);
> - if (ret)
> - goto err_csi_stop;
> + if (ret) {
> + if (!csi2->enabled_streams) {
> + dw_mipi_csi2rx_stop(csi2);
> + pm_runtime_put(dev);
> + }
> + return ret;
> + }
>
> - return 0;
> + csi2->enabled_streams |= streams_mask;
>
> -err_csi_stop:
> - dw_mipi_csi2rx_stop(csi2);
> -err_pm_runtime_put:
> - pm_runtime_put(dev);
> -err:
> - return ret;
> + return 0;
> }
>
> static int dw_mipi_csi2rx_disable_streams(struct v4l2_subdev *sd,
> @@ -572,10 +576,15 @@ static int dw_mipi_csi2rx_disable_streams(struct v4l2_subdev *sd,
> &streams_mask);
>
> ret = v4l2_subdev_disable_streams(remote_sd, remote_pad->index, mask);
> + if (ret)
> + dev_err(dev, "failed to disable streams on remote subdev: %d\n", ret);
>
> - dw_mipi_csi2rx_stop(csi2);
> + csi2->enabled_streams &= ~streams_mask;
>
> - pm_runtime_put(dev);
> + if (!csi2->enabled_streams) {
> + dw_mipi_csi2rx_stop(csi2);
> + pm_runtime_put(dev);
> + }
>
> return ret;
> }
>
> --
> 2.34.1
>
^ permalink raw reply
* Re: [PATCH V13 02/12] PCI: host-generic: Add common helpers for parsing Root Port properties
From: mani @ 2026-04-20 5:59 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: Sherry Sun, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, Frank Li, s.hauer@pengutronix.de,
kernel@pengutronix.de, festevam@gmail.com, lpieralisi@kernel.org,
kwilczynski@kernel.org, bhelgaas@google.com, Hongxing Zhu,
l.stach@pengutronix.de, imx@lists.linux.dev,
linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260417195533.GA92707@bhelgaas>
On Fri, Apr 17, 2026 at 02:55:33PM -0500, Bjorn Helgaas wrote:
> On Fri, Apr 17, 2026 at 03:17:16AM +0000, Sherry Sun wrote:
> > > On Thu, Apr 16, 2026 at 07:14:12PM +0800, Sherry Sun wrote:
> > > > Introduce generic helper functions to parse Root Port device
> > > > tree nodes and extract common properties like reset GPIOs. This
> > > > allows multiple PCI host controller drivers to share the same
> > > > parsing logic.
> > > >
> > > > Define struct pci_host_port to hold common Root Port properties
> > > > (currently only reset GPIO descriptor) and add
> > > > pci_host_common_parse_ports() to parse Root Port nodes from
> > > > device tree.
> > >
> > > Are the Root Port and the RC the only possible places for 'reset'
> > > GPIO descriptions in DT? I think PERST# routing is outside the
> > > PCIe spec, so it seems like a system could provide a PERST# GPIO
> > > routed to any Switch Upstream Port or Endpoint (I assume a PERST#
> > > connected to a switch would apply to both the upstream port and
> > > the downstream ports).
> >
> > Thanks for the feedback. You're right that PERST# routing could
> > theoretically be connected to any device in the hierarchy. However,
> > for this patch series, I've focused on the most common use case in
> > practice: use Root Port level PERST# instead of the legacy Root
> > Complex level PERST#.
> >
> > Root Port level PERST# - This is the primary target, where each Root
> > Port has individual control over devices connected to it. RC level
> > PERST# - Legacy binding support, where a single GPIO controls all
> > ports.
> >
> > We can extend this framework later if real hardware emerges that
> > needs Switch or EP-level PERST# control. I can add a comment
> > documenting this limitation if needed.
> >
> > BTW, Mani and Rob had some great discussions in dt-schema about
> > PERST# and WAKE# sideband signals settings.
>
> > You can check here:
> > https://github.com/devicetree-org/dt-schema/issues/168
> > https://github.com/devicetree-org/dt-schema/pull/126
> > https://github.com/devicetree-org/dt-schema/pull/170
>
> The upshot of all those conversations is that WAKE# and PERST# can be
> routed to arbitrary devices independent of the PCI topology.
>
> I think extending host-generic to look for 'reset' in Root Port nodes
> is the right thing. My concern is more about where we store it. This
> patch saves it in a new "pci_host_port" struct, but someday we'll want
> a place to save the PERST# GPIOs for several slots behind a switch.
> Then we'll have two different ways to save the same information.
>
Even if there are PERST# GPIOs from the host, connected to downstream ports of a
PCIe switch, they could be stored in the Root Port's (pci_host_port) struct as a
list of PERST#. This is what pcie-qcom driver does.
It is too clumsy to handle PERST# individually for each device. We tried it
before with pwrctrl, but it always ended up biting us on who gets to control the
PERST#. We can't let pwrctrl handle PERST# for a switch port and host controller
driver handle it for RP. And we cannot let pwrctrl handle PERST# for all ports,
because, host controller drivers also need to control them for RC
initialization.
That's why it was decided to handle PERST# for all ports in the host controller
drivers. So following that pattern, this helper could also be extended to parse
the PERST# from all ports defined in DT and store them in the same Root Port
struct.
It should be trivial to implement this logic in the current helper. @Sherry:
Could you please implement this logic?
> WAKE# signals might be more pertinent -- we definitely need to support
> multiple WAKE# signals below a single Root Port, and it seems like
> PERST# and WAKE# GPIOs should be saved the same place.
>
> I'm wondering if both should go in the pci_dev itself. I guess the
> implication is that a pci_dev->reset GPIO would describe a PERST#
> connected to the device *below* the pci_dev, at least for Downstream
> Ports.
>
Problem is, PERST# needs to be controlled even before 'pci_dev' gets created. We
create 'pci_dev' only when a device get's detected. But the PERST# assertion and
deassertion happens even before the pci_host_probe() call, which is the starting
point for enumeration. That's why storing it as a list in 'pci_host_bridge'
makes it accessible by the host controller drivers.
> I don't know about WAKE# signals. When it's in a connector, there's
> probably only a single possible WAKE# per Downstream Port. But is it
> possible have multiple WAKE# signals from a multi-function device
> that's on the motherboard? Saving the WAKE# GPIO in the Downstream
> Port wouldn't accommodate that case.
AFAIK, a single device can have only one WAKE# irrespective of how many function
it exposes. PCIe base spec doesn't indicate whether it is per-device or
per-function, but the form factor specfications like CEM, Mini-CEM define it as
a per-device signal.
Moreover, unlike PERST#, WAKE# is not driven by the host system. Host just needs
to register an IRQ handler to service the interrupts raised by the device. So it
can be parsed *after* enumerating a device and stored in 'pci_dev' as done in
Krishna's series:
https://lore.kernel.org/linux-pci/20260403-wakeirq_support-v9-0-1cbecf3b58d7@oss.qualcomm.com/
- Mani
--
மணிவண்ணன் சதாசிவம்
^ permalink raw reply
* [PATCH] dt-bindings: serial: Add compatible for Qualcomm Nord SoC
From: Shawn Guo @ 2026-04-20 6:05 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Praveen Talari,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, linux-serial, devicetree, linux-arm-msm,
linux-kernel, Shawn Guo
From: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Document compatibles for QUP GENI UART controller on Nord SoC with
fallback on SA8255P compatibles.
Signed-off-by: Deepti Jaggi <deepti.jaggi@oss.qualcomm.com>
Signed-off-by: Shawn Guo <shengchao.guo@oss.qualcomm.com>
---
.../bindings/serial/qcom,sa8255p-geni-uart.yaml | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/serial/qcom,sa8255p-geni-uart.yaml b/Documentation/devicetree/bindings/serial/qcom,sa8255p-geni-uart.yaml
index c8f01923cb25..55e73b359f04 100644
--- a/Documentation/devicetree/bindings/serial/qcom,sa8255p-geni-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/qcom,sa8255p-geni-uart.yaml
@@ -14,9 +14,16 @@ allOf:
properties:
compatible:
- enum:
- - qcom,sa8255p-geni-uart
- - qcom,sa8255p-geni-debug-uart
+ oneOf:
+ - enum:
+ - qcom,sa8255p-geni-uart
+ - qcom,sa8255p-geni-debug-uart
+ - items:
+ - const: qcom,nord-auto-geni-uart
+ - const: qcom,sa8255p-geni-uart
+ - items:
+ - const: qcom,nord-auto-geni-debug-uart
+ - const: qcom,sa8255p-geni-debug-uart
reg:
maxItems: 1
--
2.43.0
^ permalink raw reply related
* [PATCH v3 0/7] Add QSPI support for QCS615 and improve interconnect handling
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya, Dmitry Baryshkov, Konrad Dybcio
Add QSPI controller support for the QCS615 (Talos) platform and improve
interconnect bandwidth management for QSPI controllers across multiple
Qualcomm SoCs.
The series consists of:
1. Add QCS615 compatible string to device tree bindings.
2. Add qspi-memory interconnect path support to the driver for proper DMA
bandwidth allocation.
3. Add QSPI support to QCS615 platform including OPP table, pinmux, and
controller node.
4. Enable QSPI controller and SPI-NOR flash on QCS615-RIDE board.
5. Add QSPI memory interconnect paths to existing SC7180 and Kodiak
platforms.
The key improvement in this series is adding the qspi-memory interconnect
path. Previously, the QSPI driver only managed the CPU-to-QSPI
configuration path. Add support for the QSPI-to-memory path, which is
essential for proper bandwidth allocation during DMA operations when the
QSPI controller transfers data to/from system memory.
Set the memory path bandwidth equal to the transfer speed, matching the
existing pattern used for the CPU path. Enable and disable both paths
properly during runtime PM transitions to ensure efficient power
management.
Apply this change to existing platforms (SC7180/Kodiak) as well as the
newly added QCS615 platform to ensure consistent interconnect handling
across all QSPI-enabled SoCs.
Testing:
- Verified QSPI functionality on QCS615-RIDE with SPI-NOR flash
- Confirmed proper interconnect bandwidth voting during transfers
- Validated runtime PM transitions with both interconnect paths
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
Changes in v3:
- Added missing interconnect-names constraint for qcom,qcs615-qspi.
- Changed interconnect tags for qspi-memory path to QCOM_ICC_TAG_ALWAYS
- Fixed suspend sequence: now disables clocks before dropping performance
state to avoid brownout risk
- Link to v2: https://patch.msgid.link/20260414-spi-nor-v2-0-bcca40de4b5f@oss.qualcomm.com
Changes in v2:
- Moved allOf section to bottom of binding schema
- Added if:then constraint requiring minimum 2 interconnects for qcs615
- Fixed runtime PM error handling with complete goto-based cleanup
- Added proper error paths in suspend/resume functions
- Changed interconnect tags from raw 0 to QCOM_ICC_TAG_ACTIVE_ONLY
- Link to v1: https://patch.msgid.link/20260324-spi-nor-v1-0-3efe59c1c119@oss.qualcomm.com
---
Viken Dadhaniya (7):
dt-bindings: spi: qcom,spi-qcom-qspi: Add qcom,qcs615-qspi compatible
spi: qcom-qspi: Fix incomplete error handling in runtime PM
spi: qcom-qspi: Add interconnect support for memory path
arm64: dts: qcom: talos: Add QSPI support
arm64: dts: qcom: qcs615-ride: enable QSPI and NOR flash
arm64: dts: qcom: kodiak: Add QSPI memory interconnect path
arm64: dts: qcom: sc7180: Add QSPI memory interconnect path
.../bindings/spi/qcom,spi-qcom-qspi.yaml | 21 +++++-
arch/arm64/boot/dts/qcom/kodiak.dtsi | 9 ++-
arch/arm64/boot/dts/qcom/qcs615-ride.dts | 12 ++++
arch/arm64/boot/dts/qcom/sc7180.dtsi | 9 ++-
arch/arm64/boot/dts/qcom/talos.dtsi | 80 ++++++++++++++++++++++
drivers/spi/spi-qcom-qspi.c | 76 +++++++++++++++++---
6 files changed, 188 insertions(+), 19 deletions(-)
---
base-commit: c369299895a591d96745d6492d4888259b004a9e
change-id: 20260324-spi-nor-09c6d9e0de05
Best regards,
--
Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
^ permalink raw reply
* [PATCH v3 1/7] dt-bindings: spi: qcom,spi-qcom-qspi: Add qcom,qcs615-qspi compatible
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
Add support for the QSPI controller on QCS615 SoC.
Move allOf section after required properties and add if:then constraint
to require minimum 2 interconnects for qcs615 variant.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
.../devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml b/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml
index 1696ac46a660..ee2199027e89 100644
--- a/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml
+++ b/Documentation/devicetree/bindings/spi/qcom,spi-qcom-qspi.yaml
@@ -13,13 +13,11 @@ description: The QSPI controller allows SPI protocol communication in single,
dual, or quad wire transmission modes for read/write access to slaves such
as NOR flash.
-allOf:
- - $ref: /schemas/spi/spi-controller.yaml#
-
properties:
compatible:
items:
- enum:
+ - qcom,qcs615-qspi
- qcom,sc7180-qspi
- qcom,sc7280-qspi
- qcom,sdm845-qspi
@@ -67,6 +65,23 @@ required:
- clock-names
- clocks
+allOf:
+ - $ref: /schemas/spi/spi-controller.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,qcs615-qspi
+ then:
+ properties:
+ interconnects:
+ minItems: 2
+ interconnect-names:
+ minItems: 2
+ required:
+ - interconnects
+ - interconnect-names
+
unevaluatedProperties: false
examples:
--
2.34.1
^ permalink raw reply related
* [PATCH v3 2/7] spi: qcom-qspi: Fix incomplete error handling in runtime PM
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
The runtime PM functions had incomplete error handling that could leave the
system in an inconsistent state. If any operation failed midway through
suspend or resume, some resources would be left in the wrong state while
others were already changed, leading to potential clock/power imbalances.
Reorder the suspend/resume sequences to avoid brownout risk by ensuring the
performance state is set appropriately before clocks are enabled and clocks
are disabled before dropping the performance state.
Fix by adding proper error checking for all operations and using goto-based
cleanup to ensure all successfully acquired resources are properly released
on any error.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
drivers/spi/spi-qcom-qspi.c | 43 +++++++++++++++++++++++++++++++++++--------
1 file changed, 35 insertions(+), 8 deletions(-)
diff --git a/drivers/spi/spi-qcom-qspi.c b/drivers/spi/spi-qcom-qspi.c
index 7e39038160e0..8496c4a9f642 100644
--- a/drivers/spi/spi-qcom-qspi.c
+++ b/drivers/spi/spi-qcom-qspi.c
@@ -818,20 +818,33 @@ static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev)
struct qcom_qspi *ctrl = spi_controller_get_devdata(host);
int ret;
- /* Drop the performance state vote */
- dev_pm_opp_set_rate(dev, 0);
clk_bulk_disable_unprepare(QSPI_NUM_CLKS, ctrl->clks);
ret = icc_disable(ctrl->icc_path_cpu_to_qspi);
if (ret) {
dev_err_ratelimited(ctrl->dev, "%s: ICC disable failed for cpu: %d\n",
__func__, ret);
- return ret;
+ goto err_enable_clk;
}
- pinctrl_pm_select_sleep_state(dev);
+ ret = pinctrl_pm_select_sleep_state(dev);
+ if (ret)
+ goto err_enable_icc;
+
+ /* Drop the performance state vote */
+ ret = dev_pm_opp_set_rate(dev, 0);
+ if (ret)
+ goto err_select_default_state;
return 0;
+
+err_select_default_state:
+ pinctrl_pm_select_default_state(dev);
+err_enable_icc:
+ icc_enable(ctrl->icc_path_cpu_to_qspi);
+err_enable_clk:
+ clk_bulk_prepare_enable(QSPI_NUM_CLKS, ctrl->clks);
+ return ret;
}
static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev)
@@ -840,20 +853,34 @@ static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev)
struct qcom_qspi *ctrl = spi_controller_get_devdata(host);
int ret;
- pinctrl_pm_select_default_state(dev);
+ ret = dev_pm_opp_set_rate(dev, ctrl->last_speed * 4);
+ if (ret)
+ return ret;
+
+ ret = pinctrl_pm_select_default_state(dev);
+ if (ret)
+ goto err_opp_set_rate_zero;
ret = icc_enable(ctrl->icc_path_cpu_to_qspi);
if (ret) {
dev_err_ratelimited(ctrl->dev, "%s: ICC enable failed for cpu: %d\n",
__func__, ret);
- return ret;
+ goto err_select_sleep_state;
}
ret = clk_bulk_prepare_enable(QSPI_NUM_CLKS, ctrl->clks);
if (ret)
- return ret;
+ goto err_disable_icc;
- return dev_pm_opp_set_rate(dev, ctrl->last_speed * 4);
+ return 0;
+
+err_disable_icc:
+ icc_disable(ctrl->icc_path_cpu_to_qspi);
+err_select_sleep_state:
+ pinctrl_pm_select_sleep_state(dev);
+err_opp_set_rate_zero:
+ dev_pm_opp_set_rate(dev, 0);
+ return ret;
}
static int __maybe_unused qcom_qspi_suspend(struct device *dev)
--
2.34.1
^ permalink raw reply related
* [PATCH v3 3/7] spi: qcom-qspi: Add interconnect support for memory path
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
The QSPI controller has two interconnect paths:
1. qspi-config: CPU to QSPI controller for register access
2. qspi-memory: QSPI controller to memory for DMA operations
Currently, the driver only manages the qspi-config path. Add support for
the qspi-memory path to ensure proper bandwidth allocation for QSPI data
transfers to/from memory. Enable and disable both paths during runtime PM
transitions.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
drivers/spi/spi-qcom-qspi.c | 41 +++++++++++++++++++++++++++++++++++------
1 file changed, 35 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-qcom-qspi.c b/drivers/spi/spi-qcom-qspi.c
index 8496c4a9f642..af1098f21146 100644
--- a/drivers/spi/spi-qcom-qspi.c
+++ b/drivers/spi/spi-qcom-qspi.c
@@ -174,6 +174,7 @@ struct qcom_qspi {
void *virt_cmd_desc[QSPI_MAX_SG];
unsigned int n_cmd_desc;
struct icc_path *icc_path_cpu_to_qspi;
+ struct icc_path *icc_path_mem;
unsigned long last_speed;
/* Lock to protect data accessed by IRQs */
spinlock_t lock;
@@ -272,7 +273,7 @@ static void qcom_qspi_handle_err(struct spi_controller *host,
static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
{
int ret;
- unsigned int avg_bw_cpu;
+ unsigned int avg_bw_cpu, avg_bw_mem;
if (speed_hz == ctrl->last_speed)
return 0;
@@ -285,7 +286,7 @@ static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
}
/*
- * Set BW quota for CPU.
+ * Set BW quota for CPU and memory paths.
* We don't have explicit peak requirement so keep it equal to avg_bw.
*/
avg_bw_cpu = Bps_to_icc(speed_hz);
@@ -296,6 +297,13 @@ static int qcom_qspi_set_speed(struct qcom_qspi *ctrl, unsigned long speed_hz)
return ret;
}
+ avg_bw_mem = Bps_to_icc(speed_hz);
+ ret = icc_set_bw(ctrl->icc_path_mem, avg_bw_mem, avg_bw_mem);
+ if (ret) {
+ dev_err(ctrl->dev, "ICC BW voting failed for memory: %d\n", ret);
+ return ret;
+ }
+
ctrl->last_speed = speed_hz;
return 0;
@@ -729,6 +737,11 @@ static int qcom_qspi_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(ctrl->icc_path_cpu_to_qspi),
"Failed to get cpu path\n");
+ ctrl->icc_path_mem = devm_of_icc_get(dev, "qspi-memory");
+ if (IS_ERR(ctrl->icc_path_mem))
+ return dev_err_probe(dev, PTR_ERR(ctrl->icc_path_mem),
+ "Failed to get memory path\n");
+
/* Set BW vote for register access */
ret = icc_set_bw(ctrl->icc_path_cpu_to_qspi, Bps_to_icc(1000),
Bps_to_icc(1000));
@@ -827,9 +840,15 @@ static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev)
goto err_enable_clk;
}
+ ret = icc_disable(ctrl->icc_path_mem);
+ if (ret) {
+ dev_err_ratelimited(ctrl->dev, "ICC disable failed for memory: %d\n", ret);
+ goto err_enable_icc_cpu;
+ }
+
ret = pinctrl_pm_select_sleep_state(dev);
if (ret)
- goto err_enable_icc;
+ goto err_enable_icc_mem;
/* Drop the performance state vote */
ret = dev_pm_opp_set_rate(dev, 0);
@@ -840,7 +859,9 @@ static int __maybe_unused qcom_qspi_runtime_suspend(struct device *dev)
err_select_default_state:
pinctrl_pm_select_default_state(dev);
-err_enable_icc:
+err_enable_icc_mem:
+ icc_enable(ctrl->icc_path_mem);
+err_enable_icc_cpu:
icc_enable(ctrl->icc_path_cpu_to_qspi);
err_enable_clk:
clk_bulk_prepare_enable(QSPI_NUM_CLKS, ctrl->clks);
@@ -868,13 +889,21 @@ static int __maybe_unused qcom_qspi_runtime_resume(struct device *dev)
goto err_select_sleep_state;
}
+ ret = icc_enable(ctrl->icc_path_mem);
+ if (ret) {
+ dev_err_ratelimited(ctrl->dev, "ICC enable failed for memory: %d\n", ret);
+ goto err_disable_icc_cpu;
+ }
+
ret = clk_bulk_prepare_enable(QSPI_NUM_CLKS, ctrl->clks);
if (ret)
- goto err_disable_icc;
+ goto err_disable_icc_mem;
return 0;
-err_disable_icc:
+err_disable_icc_mem:
+ icc_disable(ctrl->icc_path_mem);
+err_disable_icc_cpu:
icc_disable(ctrl->icc_path_cpu_to_qspi);
err_select_sleep_state:
pinctrl_pm_select_sleep_state(dev);
--
2.34.1
^ permalink raw reply related
* [PATCH v3 4/7] arm64: dts: qcom: talos: Add QSPI support
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
The Talos (QCS615) platform includes a QSPI controller used for accessing
external flash storage. Add the QSPI OPP table, TLMM pinmux entries, and
the QSPI controller node to enable support for this hardware.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/talos.dtsi | 80 +++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/talos.dtsi b/arch/arm64/boot/dts/qcom/talos.dtsi
index 75716b4a58d6..4b67b3401615 100644
--- a/arch/arm64/boot/dts/qcom/talos.dtsi
+++ b/arch/arm64/boot/dts/qcom/talos.dtsi
@@ -530,6 +530,25 @@ cdsp_smp2p_in: slave-kernel {
};
+ qspi_opp_table: opp-table-qspi {
+ compatible = "operating-points-v2";
+
+ opp-60000000 {
+ opp-hz = /bits/ 64 <60000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-133250000 {
+ opp-hz = /bits/ 64 <133250000>;
+ required-opps = <&rpmhpd_opp_svs>;
+ };
+
+ opp-266500000 {
+ opp-hz = /bits/ 64 <266500000>;
+ required-opps = <&rpmhpd_opp_nom>;
+ };
+ };
+
qup_opp_table: opp-table-qup {
compatible = "operating-points-v2";
@@ -1553,6 +1572,34 @@ tlmm: pinctrl@3100000 {
#interrupt-cells = <2>;
wakeup-parent = <&pdc>;
+ qspi_cs0: qspi-cs0-state {
+ pins = "gpio44";
+ function = "qspi";
+ bias-disable;
+ drive-strength = <6>;
+ };
+
+ qspi_data0123: qspi-data0123-state {
+ pins = "gpio45", "gpio46", "gpio47", "gpio49";
+ function = "qspi";
+ bias-pull-down;
+ drive-strength = <6>;
+ };
+
+ qspi_clk: qspi-clk-state {
+ pins = "gpio48";
+ function = "qspi";
+ bias-pull-down;
+ drive-strength = <6>;
+ };
+
+ qspi_cs1: qspi-cs1-state {
+ pins = "gpio50";
+ function = "qspi";
+ bias-pull-down;
+ drive-strength = <6>;
+ };
+
qup_i2c1_data_clk: qup-i2c1-data-clk-state {
pins = "gpio4", "gpio5";
function = "qup0";
@@ -3682,6 +3729,39 @@ opp-202000000 {
};
};
+ qspi: spi@88df000 {
+ compatible = "qcom,qcs615-qspi",
+ "qcom,qspi-v1";
+ reg = <0x0 0x088df000 0x0 0x1000>;
+
+ interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH 0>;
+
+ clocks = <&gcc GCC_QSPI_CNOC_PERIPH_AHB_CLK>,
+ <&gcc GCC_QSPI_CORE_CLK>;
+ clock-names = "iface",
+ "core";
+
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_QSPI QCOM_ICC_TAG_ACTIVE_ONLY>,
+ <&aggre1_noc MASTER_QSPI QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "qspi-config",
+ "qspi-memory";
+
+ power-domains = <&rpmhpd RPMHPD_CX>;
+ operating-points-v2 = <&qspi_opp_table>;
+
+ iommus = <&apps_smmu 0x160 0x0>;
+
+ pinctrl-0 = <&qspi_clk>, <&qspi_cs0>, <&qspi_data0123>;
+ pinctrl-names = "default";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ status = "disabled";
+ };
+
dc_noc: interconnect@9160000 {
reg = <0x0 0x09160000 0x0 0x3200>;
compatible = "qcom,qcs615-dc-noc";
--
2.34.1
^ permalink raw reply related
* [PATCH v3 5/7] arm64: dts: qcom: qcs615-ride: enable QSPI and NOR flash
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya, Dmitry Baryshkov, Konrad Dybcio
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
The QCS615 Ride board has a SPI-NOR flash connected to the QSPI controller
on CS0. Enable the QSPI controller and add the corresponding SPI-NOR flash
node to allow the system to access it.
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/qcs615-ride.dts | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/qcs615-ride.dts b/arch/arm64/boot/dts/qcom/qcs615-ride.dts
index 5a24c19c415e..b096d96ab220 100644
--- a/arch/arm64/boot/dts/qcom/qcs615-ride.dts
+++ b/arch/arm64/boot/dts/qcom/qcs615-ride.dts
@@ -516,6 +516,18 @@ &pon_resin {
status = "okay";
};
+&qspi {
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+ spi-tx-bus-width = <2>;
+ spi-rx-bus-width = <2>;
+ };
+};
+
&qupv3_id_0 {
status = "okay";
};
--
2.34.1
^ permalink raw reply related
* [PATCH v3 6/7] arm64: dts: qcom: kodiak: Add QSPI memory interconnect path
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
Add the missing QSPI-to-memory interconnect path alongside the existing
configuration path. Without it, the interconnect framework cannot vote for
the bandwidth required by QSPI DMA data transfers.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/kodiak.dtsi | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi
index 6079e67ea829..01e35e34ef51 100644
--- a/arch/arm64/boot/dts/qcom/kodiak.dtsi
+++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi
@@ -4312,9 +4312,12 @@ qspi: spi@88dc000 {
clocks = <&gcc GCC_QSPI_CNOC_PERIPH_AHB_CLK>,
<&gcc GCC_QSPI_CORE_CLK>;
clock-names = "iface", "core";
- interconnects = <&gem_noc MASTER_APPSS_PROC 0
- &cnoc2 SLAVE_QSPI_0 0>;
- interconnect-names = "qspi-config";
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &cnoc2 SLAVE_QSPI_0 QCOM_ICC_TAG_ACTIVE_ONLY>,
+ <&aggre1_noc MASTER_QSPI_0 QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "qspi-config",
+ "qspi-memory";
power-domains = <&rpmhpd SC7280_CX>;
operating-points-v2 = <&qspi_opp_table>;
status = "disabled";
--
2.34.1
^ permalink raw reply related
* [PATCH v3 7/7] arm64: dts: qcom: sc7180: Add QSPI memory interconnect path
From: Viken Dadhaniya @ 2026-04-20 6:12 UTC (permalink / raw)
To: Mark Brown, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, cros-qcom-dts-watchers
Cc: linux-arm-msm, linux-spi, devicetree, linux-kernel,
Viken Dadhaniya
In-Reply-To: <20260420-spi-nor-v3-0-7de325a29010@oss.qualcomm.com>
Add the missing QSPI-to-memory interconnect path alongside the existing
configuration path. Without this path, the interconnect framework cannot
correctly vote for the bandwidth required by QSPI DMA data transfers.
Signed-off-by: Viken Dadhaniya <viken.dadhaniya@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/sc7180.dtsi | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/sc7180.dtsi b/arch/arm64/boot/dts/qcom/sc7180.dtsi
index 45b9864e3304..7515d982b38e 100644
--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
+++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
@@ -2864,9 +2864,12 @@ qspi: spi@88dc000 {
clocks = <&gcc GCC_QSPI_CNOC_PERIPH_AHB_CLK>,
<&gcc GCC_QSPI_CORE_CLK>;
clock-names = "iface", "core";
- interconnects = <&gem_noc MASTER_APPSS_PROC 0
- &config_noc SLAVE_QSPI_0 0>;
- interconnect-names = "qspi-config";
+ interconnects = <&gem_noc MASTER_APPSS_PROC QCOM_ICC_TAG_ACTIVE_ONLY
+ &config_noc SLAVE_QSPI_0 QCOM_ICC_TAG_ACTIVE_ONLY>,
+ <&aggre1_noc MASTER_QSPI QCOM_ICC_TAG_ALWAYS
+ &mc_virt SLAVE_EBI1 QCOM_ICC_TAG_ALWAYS>;
+ interconnect-names = "qspi-config",
+ "qspi-memory";
power-domains = <&rpmhpd SC7180_CX>;
operating-points-v2 = <&qspi_opp_table>;
status = "disabled";
--
2.34.1
^ permalink raw reply related
* [PATCH v3 2/2] drm/bridge: This patch add new DRM bridge driver for LT9611C(EX/UXD) chip
From: syyang @ 2026-04-20 6:16 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, yangsunyun1993, xmzhu, Sunyun Yang
In-Reply-To: <20260420061644.1251070-1-syyang@lontium.com>
From: Sunyun Yang <syyang@lontium.com>
LT9611C(EX/UXD) is a high performance Single/Dual-Port MIPI to
HDMI 1.4/2.0 converter:
-Single/Dual-port MIPI DSI Receiver
1. Compliantwith D-PHY1.2&DSI-2 1.0
2. 1/2configurable ports
3. 1 clock lane and 1/2/3/4 configurable data lanes per port
4. 80Mbps~2.5Gbps per data lane
5. Support RGB666, loosely RGB666, RGB888, RGB565,16-bit YCbCr4:2:2
-HDMI 1.4/2.0 Transmitter
1.Data rate up to 6Gbps
2.Support HDCP1.4/2.3
3.Support CEC,HDR10
4.Support lane swap
-audio
1.sample rates of 32~192 KHz and sample sizes
of 16~24 bits
2.SPDIF interface supports PCM, Dolbydigital, DTS digital audio
at up to 192KHz frame rate
-Miscellaneous
1.CSC:RGB<->YUV444<->YUV422
Signed-off-by: Sunyun Yang <syyang@lontium.com>
---
drivers/gpu/drm/bridge/Kconfig | 18 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt9611c.c | 1365 ++++++++++++++++++++++
3 files changed, 1384 insertions(+)
create mode 100755 drivers/gpu/drm/bridge/lontium-lt9611c.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index c3209b0f4678..32b85a2a65d9 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -177,6 +177,24 @@ 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 CRC8
+ select FW_LOADER
+ 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 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 beab5b695a6e..92688be9692f 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -16,6 +16,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_LONTIUM_LT8713SX) += lontium-lt8713sx.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c
new file mode 100755
index 000000000000..a6d11d0bddf5
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c
@@ -0,0 +1,1365 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Lontium Semiconductor, Inc.
+ */
+
+#include <linux/crc8.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/media-bus-format.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_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <drm/display/drm_hdmi_audio_helper.h>
+#include <drm/display/drm_hdmi_state_helper.h>
+#include <sound/hdmi-codec.h>
+
+#define FW_SIZE (64 * 1024)
+#define LT_PAGE_SIZE 256
+#define FW_FILE "LT9611C.bin"
+#define LT9611C_CRC_POLYNOMIAL 0x31
+#define LT9611C_PAGE_CONTROL 0xff
+
+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 gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ u32 chip_type;
+ const struct firmware *fw;
+ int fw_version;
+ u8 fw_crc;
+ bool hdmi_connected;
+};
+
+DECLARE_CRC8_TABLE(lt9611c_crc8_table);
+
+static const struct regmap_range_cfg lt9611c_ranges[] = {
+ {
+ .name = "register_range",
+ .range_min = 0,
+ .range_max = 0xffff,
+ .selector_reg = LT9611C_PAGE_CONTROL,
+ .selector_mask = 0xff,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 0x100,
+ },
+};
+
+static const struct regmap_config lt9611c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xffff,
+ .ranges = lt9611c_ranges,
+ .num_ranges = ARRAY_SIZE(lt9611c_ranges),
+};
+
+static int lt9611c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
+ unsigned int param_count, u8 *return_buffer,
+ unsigned int return_count)
+{
+ int count, i;
+ unsigned int temp;
+
+ regmap_write(lt9611c->regmap, 0xe0de, 0x01);
+
+ count = 0;
+ do {
+ regmap_read(lt9611c->regmap, 0xe0ae, &temp);
+ usleep_range(1000, 2000);
+ count++;
+ } while (count < 100 && temp != 0x01);
+
+ if (temp != 0x01)
+ return -1;
+
+ for (i = 0; i < param_count; i++) {
+ if (i > 0xdd - 0xb0)
+ break;
+
+ regmap_write(lt9611c->regmap, 0xe0b0 + i, params[i]);
+ }
+
+ regmap_write(lt9611c->regmap, 0xe0de, 0x02);
+
+ count = 0;
+ do {
+ regmap_read(lt9611c->regmap, 0xe0ae, &temp);
+ usleep_range(1000, 2000);
+ count++;
+ } while (count < 100 && temp != 0x02);
+
+ if (temp != 0x02)
+ return -2;
+
+ regmap_bulk_read(lt9611c->regmap, 0xe085, return_buffer, return_count);
+
+ return 0;
+}
+
+static int lt9611c_prepare_firmware_data(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u8 *buffer;
+ size_t total_size = FW_SIZE - 1;
+
+ 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 -EPROBE_DEFER;
+ }
+
+ if (lt9611c->fw->size > total_size) {
+ dev_err(dev, "firmware too large (%zu > %zu)\n", lt9611c->fw->size, total_size);
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "firmware size: %zu bytes\n", lt9611c->fw->size);
+
+ buffer = kzalloc(total_size, GFP_KERNEL);
+ if (!buffer) {
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ return -ENOMEM;
+ }
+
+ memset(buffer, 0xff, total_size);
+ memcpy(buffer, lt9611c->fw->data, lt9611c->fw->size);
+
+ lt9611c->fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0);
+
+ dev_dbg(dev, "firmware crc: 0x%02x\n", lt9611c->fw_crc);
+
+ kfree(buffer);
+ return 0;
+}
+
+static void lt9611c_config_parameters(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write_paras[] = {
+ REG_SEQ0(0xe0ee, 0x01),
+ REG_SEQ0(0xe103, 0x3f), //fifo rst
+ REG_SEQ0(0xe103, 0xff),
+ REG_SEQ0(0xe05e, 0xc1),
+ REG_SEQ0(0xe058, 0x00),
+ REG_SEQ0(0xe059, 0x50),
+ REG_SEQ0(0xe05a, 0x10),
+ REG_SEQ0(0xe05a, 0x00),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras));
+}
+
+static void lt9611c_wren(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe05a, 0x04);
+ regmap_write(lt9611c->regmap, 0xe05a, 0x00);
+}
+
+static void lt9611c_wrdi(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe05a, 0x08);
+ regmap_write(lt9611c->regmap, 0xe05a, 0x00);
+}
+
+static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe0ee, 0x01),
+ REG_SEQ0(0xe05a, 0x04),
+ REG_SEQ0(0xe05a, 0x00),
+ REG_SEQ0(0xe05b, (addr >> 16) & 0xff),
+ REG_SEQ0(0xe05c, (addr >> 8) & 0xff),
+ REG_SEQ0(0xe05d, addr & 0xff),
+ REG_SEQ0(0xe05a, 0x01),
+ REG_SEQ0(0xe05a, 0x00),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void read_flash_reg_status(struct lt9611c *lt9611c, unsigned int *status)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe103, 0x3f),
+ REG_SEQ0(0xe103, 0xff),
+ REG_SEQ0(0xe05e, 0x40),
+ REG_SEQ0(0xe056, 0x05),
+ REG_SEQ0(0xe055, 0x25),
+ REG_SEQ0(0xe055, 0x01),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+
+ regmap_read(lt9611c->regmap, 0xe05f, status);
+}
+
+static void lt9611c_crc_to_sram(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe051, 0x00),
+ REG_SEQ0(0xe055, 0xc0),
+ REG_SEQ0(0xe055, 0x80),
+ REG_SEQ0(0xe05e, 0xc0),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_data_to_sram(struct lt9611c *lt9611c)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe051, 0xff),
+ REG_SEQ0(0xe055, 0x80),
+ REG_SEQ0(0xe05e, 0xc0),
+ REG_SEQ0(0xe058, 0x21),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, size_t addr)
+{
+ const struct reg_sequence seq_write[] = {
+ REG_SEQ0(0xe05b, (addr >> 16) & 0xff),
+ REG_SEQ0(0xe05c, (addr >> 8) & 0xff),
+ REG_SEQ0(0xe05d, addr & 0xff),
+ REG_SEQ0(0xe05a, 0x30),
+ REG_SEQ0(0xe05a, 0x00),
+ };
+
+ regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write));
+}
+
+static void lt9611c_block_erase(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int i;
+ unsigned int block_num;
+ unsigned int flash_status = 0;
+ u32 flash_addr = 0;
+
+ for (block_num = 0; block_num < 2; block_num++) {
+ flash_addr = (block_num * 0x008000);
+ lt9611c_erase_op(lt9611c, flash_addr);
+ 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_dbg(dev, "erase flash done.\n");
+}
+
+static int lt9611c_write_data(struct lt9611c *lt9611c, size_t addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ unsigned int page = 0, num = 0, i = 0;
+ size_t size, index;
+ const u8 *data;
+ u8 value;
+
+ data = lt9611c->fw->data;
+ size = lt9611c->fw->size;
+ page = (size + LT_PAGE_SIZE - 1) / LT_PAGE_SIZE;
+ if (page * LT_PAGE_SIZE > FW_SIZE) {
+ dev_err(dev, "firmware size out of range\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "%u pages, total size %zu 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 = regmap_write(lt9611c->regmap, 0xe059, value);
+ if (ret < 0) {
+ dev_err(dev, "write error at page %u, index %u\n", num, i);
+ return ret;
+ }
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+
+ addr += LT_PAGE_SIZE;
+ }
+
+ lt9611c_wrdi(lt9611c);
+
+ return 0;
+}
+
+static int lt9611c_write_crc(struct lt9611c *lt9611c, size_t addr)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+ u8 crc;
+
+ crc = lt9611c->fw_crc;
+ lt9611c_crc_to_sram(lt9611c);
+ ret = regmap_write(lt9611c->regmap, 0xe059, crc);
+ if (ret < 0) {
+ dev_err(dev, "failed to write crc\n");
+ return ret;
+ }
+
+ lt9611c_wren(lt9611c);
+ lt9611c_sram_to_flash(lt9611c, addr);
+ lt9611c_wrdi(lt9611c);
+
+ dev_dbg(dev, "crc 0x%02x written to flash at addr 0x%zx\n", crc, addr);
+
+ return 0;
+}
+
+static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ int ret;
+
+ ret = lt9611c_prepare_firmware_data(lt9611c);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(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;
+ }
+
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+
+ 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;
+ unsigned int crc_result;
+
+ regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ regmap_read(lt9611c->regmap, 0xe021, &crc_result);
+
+ if (crc_result != lt9611c->fw_crc) {
+ dev_err(dev, "lt9611c fw upgrade failed, expected crc=0x%02x, read crc=0x%02x\n",
+ lt9611c->fw_crc, crc_result);
+ return -1;
+ }
+
+ dev_dbg(dev, "lt9611c firmware upgrade success, crc=0x%02x\n", crc_result);
+ return 0;
+}
+
+static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct lt9611c, bridge);
+}
+
+/*read only*/
+static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge)
+{
+ return container_of(bridge, const struct lt9611c, bridge);
+}
+
+static void lt9611c_lock(struct lt9611c *lt9611c)
+{
+ mutex_lock(<9611c->ocm_lock);
+ regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+}
+
+static void lt9611c_unlock(struct lt9611c *lt9611c)
+{
+ regmap_write(lt9611c->regmap, 0xe0ee, 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;
+ unsigned int irq_status;
+ u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
+ u8 data[5];
+
+ mutex_lock(<9611c->ocm_lock);
+
+ regmap_read(lt9611c->regmap, 0xe084, &irq_status);
+ if (!(irq_status & BIT(0))) {
+ mutex_unlock(<9611c->ocm_lock);
+ return IRQ_HANDLED;
+ }
+
+ ret = lt9611c_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_dbg(dev, "HDMI %s\n", lt9611c->hdmi_connected ? "connected" : "disconnected");
+ }
+
+ schedule_work(<9611c->work);
+
+ /*clear interrupt*/
+ regmap_write(lt9611c->regmap, 0xe0df, irq_status & BIT(0));
+ //hardware need delay
+ usleep_range(10000, 12000);
+ regmap_write(lt9611c->regmap, 0xe0df, irq_status & (~BIT(0)));
+
+ 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);
+}
+
+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);
+
+ dev_dbg(lt9611c->dev, "lt9611c reset");
+}
+
+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 0;
+}
+
+static int lt9611c_regulator_disable(struct lt9611c *lt9611c)
+{
+ int ret;
+
+ ret = regulator_disable(lt9611c->supplies[0].consumer);
+ if (ret < 0)
+ return ret;
+
+ 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) {
+ dev_err(dev, "failed to find dsi host\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ 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_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ unsigned long long tmds_rate)
+{
+ const struct lt9611c *lt9611c = bridge_to_lt9611c_const(bridge);
+
+ if (lt9611c->chip_type == 2) {
+ /*lt9611uxd use*/
+ if (tmds_rate > 600000000)
+ return MODE_CLOCK_HIGH;
+
+ } else {
+ if (tmds_rate > 340000000)
+ return MODE_CLOCK_HIGH;
+ }
+
+ if (tmds_rate < 25000000)
+ return MODE_CLOCK_LOW;
+
+ return MODE_OK;
+}
+
+static void lt9611c_video_setup(struct lt9611c *lt9611c,
+ const struct drm_display_mode *mode)
+{
+ struct device *dev = 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;
+
+ mutex_lock(<9611c->ocm_lock);
+
+ 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_dbg(dev, "hactive=%d, vactive=%d\n", hactive, vactive);
+ dev_dbg(dev, "framerate=%d\n", framerate);
+ dev_dbg(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;
+
+ ret = lt9611c_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 void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_state *state)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+ struct drm_crtc_state *crtc_state;
+ struct drm_display_mode *mode;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
+ if (WARN_ON(!connector))
+ return;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (WARN_ON(!conn_state))
+ return;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+ if (WARN_ON(!crtc_state))
+ return;
+
+ mode = &crtc_state->adjusted_mode;
+
+ lt9611c_video_setup(lt9611c, mode);
+}
+
+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 = lt9611c_read_write_flow(lt9611c, cmd, 5, data, 5);
+ if (ret)
+ dev_err(dev, "failed to read HPD status (err=%d)\n", ret);
+ else
+ connected = (data[4] == 0x02);
+
+ lt9611c->hdmi_connected = connected;
+
+ mutex_unlock(<9611c->ocm_lock);
+
+ return connected ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int lt9611c_get_edid_block(void *data, u8 *buf,
+ unsigned int block, size_t len)
+{
+ struct lt9611c *lt9611c = data;
+ struct device *dev = lt9611c->dev;
+ u8 cmd[5] = {0x52, 0x48, 0x33, 0x3a, 0x00};
+ u8 packet[37];
+ int ret, i, offset = 0;
+
+ if (len != 128)
+ return -EINVAL;
+ mutex_lock(<9611c->ocm_lock);
+
+ for (i = 0; i < 4; i++) {
+ cmd[4] = block * 4 + i;
+ ret = lt9611c_read_write_flow(lt9611c, cmd, sizeof(cmd),
+ packet, sizeof(packet));
+ if (ret) {
+ dev_err(dev, "Failed to read EDID block %u packet %d\n",
+ block, i);
+ mutex_unlock(<9611c->ocm_lock);
+ return ret;
+ }
+
+ memcpy(buf + offset, &packet[5], 32);
+ offset += 32;
+ }
+
+ mutex_unlock(<9611c->ocm_lock);
+
+ 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);
+
+ return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c);
+}
+
+static void lt9611c_bridge_hpd_notify(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ enum drm_connector_status status)
+{
+ const struct drm_edid *drm_edid;
+
+ if (status == connector_status_disconnected) {
+ drm_connector_hdmi_audio_plugged_notify(connector, false);
+ drm_edid_connector_update(connector, NULL);
+ return;
+ }
+
+ drm_edid = lt9611c_bridge_edid_read(bridge, connector);
+ drm_edid_connector_update(connector, drm_edid);
+ drm_edid_free(drm_edid);
+
+ if (status == connector_status_connected)
+ drm_connector_hdmi_audio_plugged_notify(connector, true);
+}
+
+static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 audio_infoframe_cmd[16] = {0x57, 0x48, 0x35, 0x3a, 0x02};
+ u8 data[5];
+ int i, ret;
+
+ for (i = 0; i < len; i++)
+ audio_infoframe_cmd[i + 5] = buffer[i];
+
+ mutex_lock(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, audio_infoframe_cmd, sizeof(audio_infoframe_cmd),
+ data, sizeof(data));
+ mutex_unlock(<9611c->ocm_lock);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write audio infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge)
+{
+ return 0;
+}
+
+static int lt9611c_hdmi_write_avi_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 avi_infoframe_cmd[16] = {0x57, 0x48, 0x35, 0x3a, 0x01};
+ u8 data[5];
+ int i, ret;
+
+ for (i = 0; i < len; i++)
+ avi_infoframe_cmd[i + 5] = buffer[i];
+
+ mutex_lock(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, avi_infoframe_cmd, sizeof(avi_infoframe_cmd),
+ data, sizeof(data));
+ mutex_unlock(<9611c->ocm_lock);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write avi infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge)
+{
+ return 0;
+}
+
+static int lt9611c_hdmi_write_spd_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 spd_infoframe_cmd[16] = {0x57, 0x48, 0x35, 0x3a, 0x04};
+ u8 data[5];
+ int i, ret;
+
+ for (i = 0; i < len; i++)
+ spd_infoframe_cmd[i + 5] = buffer[i];
+
+ mutex_lock(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, spd_infoframe_cmd, sizeof(spd_infoframe_cmd),
+ data, sizeof(data));
+ mutex_unlock(<9611c->ocm_lock);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write spd infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_spd_infoframe(struct drm_bridge *bridge)
+{
+ return 0;
+}
+
+static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge,
+ const u8 *buffer, size_t len)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 spd_infoframe_cmd[16] = {0x57, 0x48, 0x35, 0x3a, 0x05};
+ u8 data[5];
+ int i, ret;
+
+ for (i = 0; i < len; i++)
+ spd_infoframe_cmd[i + 5] = buffer[i];
+
+ mutex_lock(<9611c->ocm_lock);
+
+ ret = lt9611c_read_write_flow(lt9611c, spd_infoframe_cmd, sizeof(spd_infoframe_cmd),
+ data, sizeof(data));
+ mutex_unlock(<9611c->ocm_lock);
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "write hdmi infoframe failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge)
+{
+ return 0;
+}
+
+static int lt9611c_hdmi_audio_prepare(struct drm_bridge *bridge,
+ struct drm_connector *connector,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
+ u8 audio_cmd[6] = {0x57, 0x48, 0x36, 0x3a};
+ u8 data[5];
+ int ret;
+
+ /* Validate sample rate and width (LT9611C auto-detects but we still check) */
+ 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:
+ audio_cmd[4] = 0x01;
+ break;
+ case HDMI_SPDIF:
+ audio_cmd[4] = 0x02;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio_cmd[5] = hparms->channels;
+
+ mutex_lock(<9611c->ocm_lock);
+ ret = lt9611c_read_write_flow(lt9611c, audio_cmd, sizeof(audio_cmd),
+ data, sizeof(data));
+ mutex_unlock(<9611c->ocm_lock);
+
+ if (ret < 0) {
+ dev_err(lt9611c->dev, "set audio info failed!\n");
+ return ret;
+ }
+
+ return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
+ &hparms->cea);
+}
+
+static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
+}
+
+static int lt9611c_hdmi_audio_startup(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ return 0;
+}
+
+static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
+ .attach = lt9611c_bridge_attach,
+ .detect = lt9611c_bridge_detect,
+ .edid_read = lt9611c_bridge_edid_read,
+ .hpd_notify = lt9611c_bridge_hpd_notify,
+ .atomic_enable = lt9611c_bridge_atomic_enable,
+
+ .hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid,
+ .hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe,
+ .hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe,
+ .hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe,
+ .hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe,
+ .hdmi_write_spd_infoframe = lt9611c_hdmi_write_spd_infoframe,
+ .hdmi_clear_spd_infoframe = lt9611c_hdmi_clear_spd_infoframe,
+ .hdmi_write_hdmi_infoframe = lt9611c_hdmi_write_hdmi_infoframe,
+ .hdmi_clear_hdmi_infoframe = lt9611c_hdmi_clear_hdmi_infoframe,
+ .hdmi_audio_startup = lt9611c_hdmi_audio_startup,
+ .hdmi_audio_prepare = lt9611c_hdmi_audio_prepare,
+ .hdmi_audio_shutdown = lt9611c_hdmi_audio_shutdown,
+};
+
+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 int lt9611c_read_version(struct lt9611c *lt9611c)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(lt9611c->regmap, 0xe080, buf, 2);
+ if (ret)
+ return ret;
+
+ return (buf[0] << 8) | buf[1];
+}
+
+static int lt9611c_read_chipid(struct lt9611c *lt9611c)
+{
+ struct device *dev = lt9611c->dev;
+ u8 chipid[2];
+ int ret;
+
+ ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2);
+ if (ret)
+ return ret;
+
+ if (chipid[0] != 0x23 || chipid[1] != 0x06) {
+ dev_err(dev, "ChipID: 0x%02x 0x%02x\n", chipid[0], chipid[1]);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+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;
+
+ lt9611c_lock(lt9611c);
+
+ 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 < 0 ? ret : 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%04x\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 int lt9611c_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct lt9611c *lt9611c;
+ struct device *dev = &client->dev;
+ bool fw_updated = false;
+ int ret;
+
+ crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "device doesn't support I2C\n");
+ return -ENODEV;
+ }
+
+ lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_bridge_funcs);
+ if (IS_ERR(lt9611c))
+ return PTR_ERR(lt9611c);
+
+ lt9611c->dev = dev;
+ lt9611c->client = client;
+ lt9611c->chip_type = id->driver_data;
+ 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);
+ }
+
+ 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_of_put;
+
+ ret = lt9611c_regulator_init(lt9611c);
+ if (ret < 0)
+ goto err_of_put;
+
+ ret = lt9611c_regulator_enable(lt9611c);
+ if (ret)
+ goto err_of_put;
+
+ lt9611c_reset(lt9611c);
+
+ lt9611c_lock(lt9611c);
+
+ ret = lt9611c_read_chipid(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "failed to read chip id.\n");
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+
+retry:
+ ret = lt9611c_read_version(lt9611c);
+ if (ret < 0) {
+ dev_err(dev, "failed to read fw version\n");
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+
+ } else if (ret == 0) {
+ if (!fw_updated) {
+ fw_updated = true;
+ ret = lt9611c_firmware_upgrade(lt9611c);
+ if (ret < 0) {
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+
+ lt9611c_reset(lt9611c);
+
+ ret = lt9611c_upgrade_result(lt9611c);
+ if (ret < 0) {
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+
+ goto retry;
+
+ } else {
+ dev_err(dev, "fw version 0x%04x, update failed\n", ret);
+ ret = -EOPNOTSUPP;
+ lt9611c_unlock(lt9611c);
+ goto err_disable_regulators;
+ }
+ }
+
+ lt9611c_unlock(lt9611c);
+ lt9611c->fw_version = ret;
+
+ dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version);
+
+ INIT_WORK(<9611c->work, lt9611c_hpd_work);
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ lt9611c_irq_thread_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT |
+ IRQF_NO_AUTOEN,
+ "lt9611c", lt9611c);
+ if (ret) {
+ dev_err(dev, "failed to request irq\n");
+ goto err_disable_regulators;
+ }
+
+ lt9611c->bridge.of_node = client->dev.of_node;
+ lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT |
+ DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD |
+ DRM_BRIDGE_OP_HDMI_AUDIO;
+ lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ lt9611c->bridge.hdmi_audio_dev = dev;
+ lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8;
+ lt9611c->bridge.hdmi_audio_dai_port = 2;
+
+ drm_bridge_add(<9611c->bridge);
+
+ /* Attach primary DSI */
+ lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node);
+ if (IS_ERR(lt9611c->dsi0)) {
+ ret = PTR_ERR(lt9611c->dsi0);
+ goto err_remove_bridge;
+ }
+
+ /* 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);
+ goto err_remove_bridge;
+ }
+ }
+
+ lt9611c->hdmi_connected = false;
+ i2c_set_clientdata(client, lt9611c);
+ enable_irq(client->irq);
+ lt9611c_reset(lt9611c);
+
+ return 0;
+
+err_remove_bridge:
+ free_irq(client->irq, lt9611c);
+ cancel_work_sync(<9611c->work);
+ drm_bridge_remove(<9611c->bridge);
+
+err_disable_regulators:
+ regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+
+err_of_put:
+ of_node_put(lt9611c->dsi1_node);
+ of_node_put(lt9611c->dsi0_node);
+ if (lt9611c->fw) {
+ release_firmware(lt9611c->fw);
+ lt9611c->fw = NULL;
+ }
+
+ return ret;
+}
+
+static void lt9611c_remove(struct i2c_client *client)
+{
+ struct lt9611c *lt9611c = i2c_get_clientdata(client);
+
+ free_irq(client->irq, lt9611c);
+ cancel_work_sync(<9611c->work);
+ drm_bridge_remove(<9611c->bridge);
+ mutex_destroy(<9611c->ocm_lock);
+ regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
+ of_node_put(lt9611c->dsi1_node);
+ of_node_put(lt9611c->dsi0_node);
+}
+
+static int lt9611c_bridge_suspend(struct device *dev)
+{
+ struct lt9611c *lt9611c = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(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);
+ enable_irq(lt9611c->client->irq);
+ lt9611c_reset(lt9611c);
+ dev_dbg(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[] = {
+ /* chip_type */
+ { "lontium,lt9611c", 0 },
+ { "lontium,lt9611ex", 1 },
+ { "lontium,lt9611uxd", 2 },
+ { /* sentinel */ }
+};
+
+static const struct of_device_id lt9611c_match_table[] = {
+ { .compatible = "lontium,lt9611c" },
+ { .compatible = "lontium,lt9611ex" },
+ { .compatible = "lontium,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("SunYun Yang <syyang@lontium.com>");
+MODULE_DESCRIPTION("Lontium lt9611c mipi-dsi to hdmi driver");
+MODULE_LICENSE("GPL v2");
+
--
2.34.1
^ permalink raw reply related
* [PATCH v3 0/2] Add LT9611C(EX/UXD) DRM bridge driver and device tree
From: syyang @ 2026-04-20 6:16 UTC (permalink / raw)
To: robh, krzk+dt, conor+dt, andrzej.hajda, neil.armstrong,
dmitry.baryshkov, maarten.lankhorst, rfoss, mripard
Cc: Laurent.pinchart, tzimmermann, jonas, jernej.skrabec, devicetree,
dri-devel, linux-kernel, yangsunyun1993, xmzhu, Sunyun Yang
From: Sunyun Yang <syyang@lontium.com>
This series adds support for the Lontium LT9611C(EX/UXD) MIPI DSI to HDMI
chip:
-dt-bindings: bridge: This patch adds new content to the lontium,lt9611.yaml
binding file.
-drm/bridge: This patch add new DRM bridge driver for LT9611C(EX/UXD) chip.
Signed-off-by: Sunyun Yang<syyang@lontium.com>
---
Changes in v3:
-dt-binding:
1. lt9611c(ex/uxd) content merged into lontium,lt9611.yaml
-drm/bridge:
1. Drop the licence text, only use SPDX header
2. Sort the headers
3. Use library functions for crc8
4. Drop i2c_read_byte and i2c_write_byte
5. Lowercase all hex values
6. Use paged writes as implemented for LT9611C(EX/UXD)
7. Drop dev_info, use dev_dbg
8. Modify lt9611c_get_edid_block, don't store EDID in the long-term structures
9. Use HDMI audio helpers.
10.Remove unnecessary flags,Implement proper cleanup path, unwinding resources
one by one.
11.Replace devm_kzalloc with devm_drm_bridge_alloc.
12.Remove extra kthread.
-Link to v1: https://lore.kernel.org/lkml/20250903123825.1721443-1-syyang@lontium.com/
Changes in v2:
1. Forget modify code, operation error, Please disregard this submit.
Changes in v1:
-dt-binding:
1. Submit the first version of the code.
-drm/bridge:
1. Submit the first version of the code.
---
Sunyun Yang (2):
dt-bindings: bridge: This patch adds new content to the
lontium,lt9611.yaml binding file
drm/bridge: This patch add new DRM bridge driver for LT9611C chip
.../display/bridge/lontium,lt9611.yaml | 8 +-
drivers/gpu/drm/bridge/Kconfig | 18 +
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/lontium-lt9611c.c | 1365 +++++++++++++++++
4 files changed, 1390 insertions(+), 2 deletions(-)
create mode 100755 drivers/gpu/drm/bridge/lontium-lt9611c.c
--
2.34.1
^ permalink raw reply
* [PATCH] dt-bindings: timer: Remove sifive,fine-ctr-bits property
From: Nick Hu @ 2026-04-20 6:18 UTC (permalink / raw)
To: Daniel Lezcano, Thomas Gleixner, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Paul Walmsley, Samuel Holland, Palmer Dabbelt,
Albert Ou, Alexandre Ghiti, Anup Patel
Cc: Conor Dooley, linux-kernel, devicetree, linux-riscv, Nick Hu
The counter width can be inferred from the compatible string, making the
explicit "sifive,fine-ctr-bits" property redundant. Remove the property
to simplify the bindings.
Fixes: 0f920690a82c ("dt-bindings: timer: Add SiFive CLINT2")
Suggested-by: Conor Dooley <conor+dt@kernel.org>
Link: https://lore.kernel.org/linux-riscv/20260330-relative-hardened-5ce35fe1ef57@spud/
Signed-off-by: Nick Hu <nick.hu@sifive.com>
---
.../devicetree/bindings/timer/sifive,clint.yaml | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/Documentation/devicetree/bindings/timer/sifive,clint.yaml b/Documentation/devicetree/bindings/timer/sifive,clint.yaml
index 3c16b260db04..051edb1da0d7 100644
--- a/Documentation/devicetree/bindings/timer/sifive,clint.yaml
+++ b/Documentation/devicetree/bindings/timer/sifive,clint.yaml
@@ -72,22 +72,6 @@ properties:
minItems: 1
maxItems: 4095
- sifive,fine-ctr-bits:
- maximum: 15
- description: The width in bits of the fine counter.
-
-if:
- properties:
- compatible:
- contains:
- const: sifive,clint2
-then:
- required:
- - sifive,fine-ctr-bits
-else:
- properties:
- sifive,fine-ctr-bits: false
-
additionalProperties: false
required:
---
base-commit: c1f49dea2b8f335813d3b348fd39117fb8efb428
change-id: 20260409-clintv2-remove-fine-ctr-d5caeda27863
Best regards,
--
Nick Hu <nick.hu@sifive.com>
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox