2023-10-29 19:46:27

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 0/8] drm/loongson: Submit a mini VBIOS support and a display bridge driver

Loongson boards using various external display bridges to extend output
display interface. To complete the support, we have to introduce VBIOS(
or DT/ACPI support) and display bridge drivers.

Sui Jingfeng (8):
drm/loongson: Introduce a minimal support for Loongson VBIOS
drm/loongson: Introduce a drm bridge driver for it66121 HDMI
transmitter
drm/loongson: Allow attach drm bridge driver by calling
lsdc_output_init()
drm/loongson: Started to attach display bridge driver for LS7A1000
drm/loongson: Using vbios for the LS7A2000 output initialization
drm/loongson: Clean up the output part of LS7A2000
drm/loongson: Support to infer DC reversion from CPU's PRID value
drm/loongson: Add support for the display subsystem in LS2K2000

drivers/gpu/drm/loongson/Kconfig | 1 +
drivers/gpu/drm/loongson/Makefile | 7 +-
drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++
drivers/gpu/drm/loongson/ite_it66121.h | 19 +
drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
drivers/gpu/drm/loongson/loongson_device.c | 50 ++
drivers/gpu/drm/loongson/loongson_vbios.c | 420 ++++++++++
drivers/gpu/drm/loongson/loongson_vbios.h | 59 ++
drivers/gpu/drm/loongson/lsdc_drv.c | 4 +
drivers/gpu/drm/loongson/lsdc_drv.h | 27 +-
drivers/gpu/drm/loongson/lsdc_output.c | 643 +++++++++++++++
drivers/gpu/drm/loongson/lsdc_output.h | 57 +-
drivers/gpu/drm/loongson/lsdc_output_2k2000.c | 84 ++
drivers/gpu/drm/loongson/lsdc_output_7a1000.c | 144 ++--
drivers/gpu/drm/loongson/lsdc_output_7a2000.c | 575 +++-----------
drivers/gpu/drm/loongson/lsdc_probe.c | 35 +
drivers/gpu/drm/loongson/lsdc_probe.h | 2 +
17 files changed, 2560 insertions(+), 584 deletions(-)
create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h
create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.c
create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.h
create mode 100644 drivers/gpu/drm/loongson/lsdc_output.c
create mode 100644 drivers/gpu/drm/loongson/lsdc_output_2k2000.c

--
2.34.1


2023-10-29 19:47:03

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 1/8] drm/loongson: Introduce a minimal support for Loongson VBIOS

Because some boards are equipped with non-transparent display bridges,
which need the VBIOS to provided parameters.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/Makefile | 3 +-
drivers/gpu/drm/loongson/loongson_device.c | 4 +
drivers/gpu/drm/loongson/loongson_vbios.c | 420 +++++++++++++++++++++
drivers/gpu/drm/loongson/loongson_vbios.h | 59 +++
drivers/gpu/drm/loongson/lsdc_drv.c | 4 +
drivers/gpu/drm/loongson/lsdc_drv.h | 8 +
6 files changed, 497 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.c
create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.h

diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
index 91e72bd900c1..bef00b2c5569 100644
--- a/drivers/gpu/drm/loongson/Makefile
+++ b/drivers/gpu/drm/loongson/Makefile
@@ -17,6 +17,7 @@ loongson-y := \
lsdc_ttm.o

loongson-y += loongson_device.o \
- loongson_module.o
+ loongson_module.o \
+ loongson_vbios.o

obj-$(CONFIG_DRM_LOONGSON) += loongson.o
diff --git a/drivers/gpu/drm/loongson/loongson_device.c b/drivers/gpu/drm/loongson/loongson_device.c
index 9986c8a2a255..64096ad5466e 100644
--- a/drivers/gpu/drm/loongson/loongson_device.c
+++ b/drivers/gpu/drm/loongson/loongson_device.c
@@ -7,6 +7,8 @@

#include "lsdc_drv.h"

+extern struct loongson_vbios __loongson_vbios;
+
static const struct lsdc_kms_funcs ls7a1000_kms_funcs = {
.create_i2c = lsdc_create_i2c_chan,
.irq_handler = ls7a1000_dc_irq_handler,
@@ -53,6 +55,7 @@ static const struct loongson_gfx_desc ls7a1000_gfx = {
.reg_size = 8,
},
},
+ .vbios = &__loongson_vbios,
.chip_id = CHIP_LS7A1000,
.model = "LS7A1000 bridge chipset",
};
@@ -85,6 +88,7 @@ static const struct loongson_gfx_desc ls7a2000_gfx = {
.reg_size = 8,
},
},
+ .vbios = &__loongson_vbios,
.chip_id = CHIP_LS7A2000,
.model = "LS7A2000 bridge chipset",
};
diff --git a/drivers/gpu/drm/loongson/loongson_vbios.c b/drivers/gpu/drm/loongson/loongson_vbios.c
new file mode 100644
index 000000000000..dc304018779e
--- /dev/null
+++ b/drivers/gpu/drm/loongson/loongson_vbios.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#include <drm/drm_device.h>
+#include <drm/drm_managed.h>
+
+#include "loongson_vbios.h"
+#include "lsdc_drv.h"
+
+#define LOONGSON_VBIOS_HEADER_STR "Loongson-VBIOS"
+/* Legacy VBIOS is stored at offset 0 */
+#define LOONGSON_VBIOS_LEGACY_OFFSET 0
+/* The size of legacy VBIOS is 1 KiB */
+#define LOONGSON_VBIOS_LEGACY_SIZE 0x000400
+
+/* Data Control Block of Newer version of the VBIOS started at here */
+#define LOONGSON_VBIOS_DCB_OFFSET 0x006000
+/* The last 1 MiB of the VRAM contains the raw VBIOS data */
+#define LOONGSON_VBIOS_BLOCK_OFFSET 0x100000
+/* Only 256KB of the 1 MiB are used for now */
+#define LOONGSON_VBIOS_BLOCK_SIZE 0x040000
+
+struct loongson_vbios __loongson_vbios;
+
+/*
+ * vbios data control block is a kind of metadata, which is used to index
+ * real hardware device data block.
+ */
+struct loongson_vbios_dcb {
+ u16 type; /* what is it */
+ u8 version; /* version of it, useless */
+ u8 id; /* index (usually same with the display pipe) of the hardware */
+ u32 offset; /* the offset of the real data */
+ u32 size; /* the size of the real data */
+ u64 ext0; /* for extension purpose */
+ u64 ext1; /* extra space reserved for future use */
+} __packed;
+
+/*
+ * Loongson-VBIOS Data Block Layout
+ *
+ *
+ * _____________________ 0x00000
+ * |_____________________|
+ * | | [0x0000, 0x0400) : legacy vbios storage
+ * | Not Used Yet |
+ * | |
+ * |---------------------|<------- 0x6000
+ * +----| DCB 0 |
+ * | |---------------------|
+ * | | DCB 1 |
+ * | |---------------------| Format of Data Control Blocks
+ * | | One by one, packed | +------------+
+ * | |---------------------| | u16 type |
+ * | | DCB N |----+ | |
+ * | |---------------------| | +------------+
+ * | | . | | | u8 version |
+ * | | . | | | u8 index |
+ * | | . | | +------------+
+ * | |---------------------| | | |
+ * | | DCB end | | | u32 offset |
+ * | |---------------------| | +------- |
+ * | | | | | | |
+ * | |_____________________| | | +------------+
+ * | |_____________________| | | | |
+ * | | | | | | u32 size |
+ * +--->| vbios header info | | | | -------+
+ * |_____________________| | | | | |
+ * | . | | | +------------+ |
+ * | . | | | | useless | |
+ * | . | | | | members | |
+ * |_____________________| | | +------------+ |
+ * | | | | |
+ * | encoders info |<---+ | |
+ * |_____________________| | |
+ * | | ___| |
+ * |_____________________|____/ |
+ * | | |
+ * | Something | |
+ * |_____________________|_________________ |
+ * | | | |
+ * | EDID | |<--------------+
+ * |_____________________|_____________|___
+ * | |
+ * | | Contents of those device specific data
+ * | GPU specific info | block are implement-defined and version
+ * | | dependent :0
+ * |_____________________|
+ * / . /
+ * / . /
+ * / . /
+ * |_____________________| 0x040000
+ *
+ */
+
+enum loongson_vbios_dcb_type {
+ LV_DCB_HEADER = 0,
+ LV_DCB_CRTC = 1,
+ LV_DCB_ENCODER = 2,
+ LV_DCB_CONNECTOR = 3,
+ LV_DCB_I2C = 4,
+ LV_DCB_PWM = 5,
+ LV_DCB_GPIO = 6,
+ LV_DCB_BACKLIGHT = 7,
+ LV_DCB_FAN = 8,
+ LV_DCB_IRQ = 9,
+ LV_DCB_ENCODER_CFG = 10,
+ LV_DCB_ENCODER_RES = 11,
+ LV_DCB_GPU = 12,
+ LV_DCB_UNKNOWN = 13,
+ LV_DCB_END = 0xffff,
+};
+
+struct loongson_vbios_header {
+ char header[16];
+ u32 version_major;
+ u32 version_minor;
+ char information[20];
+ u32 num_crtc;
+ u32 crtc_offset;
+ u32 num_connector;
+ u32 connector_offset;
+ u32 num_encoder;
+ u32 encoder_offset;
+} __packed;
+
+struct loongson_vbios_encoder {
+ u32 feature;
+ u32 i2c_id;
+ u32 connector_id;
+ u32 type;
+ u32 config_method;
+ u32 chip_id;
+ u8 chip_addr;
+} __packed;
+
+struct loongson_vbios_connector {
+ u32 feature;
+ u32 i2c_id;
+ u8 edid[256];
+ u32 type;
+ u32 hotplug_method;
+ u32 edid_method;
+ u32 hpd_int_gpio;
+ u32 gpio_place;
+} __packed;
+
+/*
+ * A list node which contains the information about the device specific data
+ * block, the device here refer to the property or topology of hardware
+ * configuration, such as external display bridges, HDP GPIO, connectors etc.
+ */
+struct loongson_vbios_node {
+ struct list_head head;
+
+ /* @type: the type of the data. For search */
+ u32 type;
+ /* @id: the index(display pipe) of the data belong to. For search */
+ u32 id;
+ /*
+ * @data: point to the device specific data block, such as external
+ * encoders name and it's i2c device address, hpd gpio resource etc.
+ */
+ const void *data;
+ /*
+ * The size of the data.
+ */
+ u32 size;
+};
+
+/*
+ * The returned pointer is actually point to &__loongson_vbios, but this
+ * function is only intended to provide READ-ONLY access. As our vbios is
+ * only be able to pass(provide) parameters, it is not executable and outside
+ * should not modify it.
+ */
+const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev)
+{
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp);
+
+ return gfx->vbios;
+}
+
+static bool loongson_vbios_is_valid(const struct loongson_vbios *vbios)
+{
+ char header[32];
+
+ memcpy(header, vbios->raw_data, sizeof(header));
+
+ if (strcmp(header, LOONGSON_VBIOS_HEADER_STR))
+ return false;
+
+ return true;
+}
+
+/*
+ * The VBIOS blob is stored at the last 1 MiB of the VRAM, no SPI flush or
+ * EEPROM is needed. Platform BIOS is responsible for store this VBIOS blob
+ * data at right position on per boot time.
+ */
+static int loongson_vbios_construct(struct drm_device *ddev,
+ struct loongson_vbios *vbios)
+{
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u64 vram_end = ldev->vram_base + ldev->vram_size;
+ u64 vbios_start = vram_end - LOONGSON_VBIOS_BLOCK_OFFSET;
+ void __iomem *ptr;
+
+ vbios->raw_data = kzalloc(LOONGSON_VBIOS_BLOCK_SIZE, GFP_KERNEL);
+ if (!vbios->raw_data)
+ return -ENOMEM;
+
+ ptr = ioremap(vbios_start, LOONGSON_VBIOS_BLOCK_SIZE);
+ if (!ptr) {
+ drm_err(ddev, "Map VBIOS region failed\n");
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(vbios->raw_data, ptr, LOONGSON_VBIOS_BLOCK_SIZE);
+
+ iounmap(ptr);
+
+ INIT_LIST_HEAD(&vbios->list);
+ vbios->ddev = ddev;
+
+ return 0;
+}
+
+static void loongson_vbios_destruct(struct drm_device *ddev, void *data)
+{
+ struct loongson_vbios *vbios = (struct loongson_vbios *)data;
+ struct loongson_vbios_node *node;
+ struct loongson_vbios_node *tmp;
+
+ list_for_each_entry_safe(node, tmp, &vbios->list, head) {
+ list_del(&node->head);
+ kfree(node);
+ }
+
+ kfree(vbios->raw_data);
+ vbios->raw_data = NULL;
+}
+
+static void loongson_vbios_print_dcb(struct drm_device *ddev,
+ struct loongson_vbios_dcb *dcb)
+{
+ drm_info(ddev, "type: %u, Offset: %u, Size: %u, version: %u, ID: %u\n",
+ dcb->type, dcb->offset, dcb->size, dcb->version, dcb->id);
+}
+
+/*
+ * Process the data control block, establish a list for later searching.
+ * returns the number of data control block. Generally, loongson vbios
+ * has only 10 DCB or so.
+ */
+static int loongson_vbios_process_dcb(struct loongson_vbios *vbios,
+ bool verbose)
+{
+ struct drm_device *ddev = vbios->ddev;
+ void *base = vbios->raw_data;
+ int count = 0;
+ struct loongson_vbios_dcb *dcb;
+
+ dcb = (struct loongson_vbios_dcb *)(base + LOONGSON_VBIOS_DCB_OFFSET);
+
+ while (dcb->type != LV_DCB_END) {
+ struct loongson_vbios_node *node;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return -ENOMEM;
+
+ node->type = dcb->type;
+ node->id = dcb->id;
+ node->data = base + dcb->offset;
+ node->size = dcb->size;
+
+ list_add_tail(&node->head, &vbios->list);
+
+ if (verbose)
+ loongson_vbios_print_dcb(ddev, dcb);
+
+ ++dcb;
+
+ if (++count > 1024) {
+ drm_err(ddev, "Unlikely, DCB is too much\n");
+ break;
+ }
+ }
+
+ return count;
+}
+
+static const struct loongson_vbios_node *
+loongson_vbios_get_node(struct drm_device *ddev, u32 type, u32 id)
+{
+ const struct loongson_vbios *vbios = to_loongson_vbios(ddev);
+ struct loongson_vbios_node *np;
+
+ if (!vbios)
+ return NULL;
+
+ list_for_each_entry(np, &vbios->list, head) {
+ if (np->type == type && np->id == id)
+ return np;
+ }
+
+ return NULL;
+}
+
+bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
+ u32 pipe,
+ u32 *type,
+ enum loongson_vbios_encoder_name *name,
+ u8 *i2c_addr)
+{
+ const struct loongson_vbios_encoder *vencoder;
+ const struct loongson_vbios_node *np;
+
+ np = loongson_vbios_get_node(ddev, LV_DCB_ENCODER, pipe);
+ if (!np)
+ return false;
+
+ if (np->size != sizeof(*vencoder))
+ WARN_ON(1);
+
+ vencoder = (const struct loongson_vbios_encoder *)np->data;
+
+ if (type)
+ *type = vencoder->type;
+
+ if (name)
+ *name = vencoder->chip_id;
+
+ /* i2c address, as a slave device */
+ if (i2c_addr)
+ *i2c_addr = vencoder->chip_addr;
+
+ return true;
+}
+
+bool loongson_vbios_query_connector_info(struct drm_device *ddev,
+ u32 pipe,
+ u32 *connector_type,
+ u32 *hpd_method,
+ u32 *int_gpio,
+ u8 *edid_blob)
+{
+ const struct loongson_vbios_connector *vconnector;
+ const struct loongson_vbios_node *np;
+
+ np = loongson_vbios_get_node(ddev, LV_DCB_CONNECTOR, pipe);
+ if (!np)
+ return false;
+
+ if (np->size != sizeof(*vconnector))
+ WARN_ON(1);
+
+ vconnector = (const struct loongson_vbios_connector *)np->data;
+
+ if (connector_type)
+ *connector_type = vconnector->type;
+
+ if (edid_blob)
+ memcpy(edid_blob, vconnector->edid, 256);
+
+ if (int_gpio)
+ *int_gpio = vconnector->hpd_int_gpio;
+
+ return true;
+}
+
+static void loongson_vbios_acquire_version(struct drm_device *ddev,
+ struct loongson_vbios *vbios)
+{
+ struct loongson_vbios_header *vh;
+
+ vh = (struct loongson_vbios_header *)vbios->raw_data;
+
+ vbios->version_major = vh->version_major;
+ vbios->version_minor = vh->version_minor;
+
+ drm_info(ddev, "Loongson VBIOS version: %u.%u\n",
+ vh->version_major, vh->version_minor);
+}
+
+int loongson_vbios_init(struct drm_device *ddev)
+{
+ struct loongson_vbios *vbios = &__loongson_vbios;
+ int ret;
+ int num;
+
+ ret = loongson_vbios_construct(ddev, vbios);
+ if (ret)
+ return ret;
+
+ ret = drmm_add_action_or_reset(ddev, loongson_vbios_destruct, vbios);
+ if (ret)
+ return ret;
+
+ if (!loongson_vbios_is_valid(vbios)) {
+ drm_err(ddev, "Loongson VBIOS: header is invalid\n");
+ return -EINVAL;
+ }
+
+ loongson_vbios_acquire_version(ddev, vbios);
+
+ num = loongson_vbios_process_dcb(vbios, false);
+ if (num <= 0) {
+ drm_err(ddev, "Loongson VBIOS: Process DCB failed\n");
+ return -EINVAL;
+ }
+
+ drm_info(ddev, "Loongson VBIOS: has %d DCBs\n", num);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/loongson/loongson_vbios.h b/drivers/gpu/drm/loongson/loongson_vbios.h
new file mode 100644
index 000000000000..66fb43b3609e
--- /dev/null
+++ b/drivers/gpu/drm/loongson/loongson_vbios.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#ifndef __LOONGSON_VBIOS_H__
+#define __LOONGSON_VBIOS_H__
+
+#include <drm/drm_device.h>
+
+struct loongson_vbios {
+ struct list_head list;
+ void *raw_data;
+ struct drm_device *ddev;
+ u32 version_major;
+ u32 version_minor;
+};
+
+enum loongson_vbios_encoder_name {
+ ENCODER_CHIP_UNKNOWN = 0x00,
+ ENCODER_CHIP_INTERNAL_VGA = 0x01,
+ ENCODER_CHIP_INTERNAL_HDMI = 0x02,
+ ENCODER_CHIP_CH7055 = 0x10,
+ ENCODER_CHIP_ADV7125 = 0x11,
+ ENCODER_CHIP_TFP410 = 0x20,
+ ENCODER_CHIP_IT66121 = 0x30,
+ ENCODER_CHIP_SIL9022 = 0x31,
+ ENCODER_CHIP_LT8618 = 0x32,
+ ENCODER_CHIP_MS7210 = 0x33,
+ ENCODER_CHIP_NCS8805 = 0x40,
+ ENCODER_CHIP_LT9721 = 0x42,
+ ENCODER_CHIP_LT6711 = 0x43,
+ ENCODER_CHIP_LT8619 = 0x50,
+};
+
+enum loongson_vbios_hotplug_method {
+ LV_HPD_DISABLED = 0,
+ LV_HPD_POLLING = 1,
+ LV_HPD_IRQ = 2,
+};
+
+const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev);
+
+bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
+ u32 pipe,
+ u32 *type,
+ enum loongson_vbios_encoder_name *name,
+ u8 *i2c_addr);
+
+bool loongson_vbios_query_connector_info(struct drm_device *ddev,
+ u32 pipe,
+ u32 *connector_type,
+ u32 *hpd_method,
+ u32 *int_gpio,
+ u8 *edid_blob);
+
+int loongson_vbios_init(struct drm_device *ddev);
+
+#endif
diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c
index 89ccc0c43169..aebb200fa567 100644
--- a/drivers/gpu/drm/loongson/lsdc_drv.c
+++ b/drivers/gpu/drm/loongson/lsdc_drv.c
@@ -213,6 +213,10 @@ lsdc_create_device(struct pci_dev *pdev,
return ERR_PTR(ret);
}

+ ret = loongson_vbios_init(ddev);
+ if (ret)
+ drm_info(ddev, "No VBIOS support\n");
+
ret = drm_aperture_remove_conflicting_framebuffers(ldev->vram_base,
ldev->vram_size,
driver);
diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
index fbf2d760ef27..335953c988d1 100644
--- a/drivers/gpu/drm/loongson/lsdc_drv.h
+++ b/drivers/gpu/drm/loongson/lsdc_drv.h
@@ -16,6 +16,7 @@
#include <drm/drm_plane.h>
#include <drm/ttm/ttm_device.h>

+#include "loongson_vbios.h"
#include "lsdc_i2c.h"
#include "lsdc_irq.h"
#include "lsdc_gfxpll.h"
@@ -85,6 +86,13 @@ struct loongson_gfx_desc {
u32 reg_size;
} pixpll[LSDC_NUM_CRTC];

+ /*
+ * @vbios: Provide information about the output configuration,
+ * and provide information about dynamic features which cannot
+ * be detected(determined) with the chip_id.
+ */
+ const struct loongson_vbios *vbios;
+
enum loongson_chip_id chip_id;
char model[64];
};
--
2.34.1

2023-10-29 19:47:10

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization

For LS7A2000, the built-in VGA encoder is transparent. Connect another
external transmitter with this internal VGA encoder is not sane, thus is
not allowed. Because there are two internal encoders hardware resource on
the first display pipe, call loongson_vbios_query_encoder_info() to know
what exatly the output configutaion is. Either VGA or HDMI display output
interface, but not both. And formal products should not export three
display connector interfaces. As the hardware has two-way I2Cs and two
CRTCs. So with this observation, we can untangle more.

If there a need to extend(transform) the output interface type, then the
internal HDMI phy MUST be enabled and initialized. External transmitters
must take the HDMI signal as input, this is the only choices. Such as
lt6711(HDMI to eDP), lt8619(HDMI to LVDS) etc.

Before apply this patch, ls7a2000_output_init() is simplified function
which assumed that there is no external display bridge attached. This
naive abstraction no longer suit the needs in the long run. Hence, switch
to call the newly implemented lsdc_output_init() function, which allow us
model the external encoder as a drm display bridge. The driver of this drm
display bridge should reside in the same kernel module with drm/loongson.
We will attach it by ourself, and rely on the VBIOS tell us which display
pipe has what display bridge connected.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/lsdc_output_7a2000.c | 154 ++++++++++++++----
1 file changed, 124 insertions(+), 30 deletions(-)

diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
index ce3dabec887e..bf558b61802b 100644
--- a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
+++ b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
@@ -501,6 +501,126 @@ static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = {
.atomic_mode_set = ls7a2000_hdmi_atomic_mode_set,
};

+/* The built-in tranparent VGA encoder is only available on display pipe 0 */
+static void ls7a2000_pipe0_vga_encoder_reset(struct drm_encoder *encoder)
+{
+ struct lsdc_device *ldev = to_lsdc(encoder->dev);
+ u32 val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
+
+ lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
+
+ /*
+ * The firmware set LSDC_HDMIx_CTRL_REG blindly to use hardware I2C,
+ * which is may not works because of hardware bug. We using built-in
+ * GPIO emulated I2C instead of the hardware I2C here.
+ */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HW_I2C_EN);
+
+ mdelay(20);
+}
+
+static const struct drm_encoder_funcs ls7a2000_pipe0_vga_encoder_funcs = {
+ .reset = ls7a2000_pipe0_vga_encoder_reset,
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct lsdc_output_desc ls7a2000_vga_pipe0 = {
+ .pipe = 0,
+ .encoder_type = DRM_MODE_ENCODER_DAC,
+ .connector_type = DRM_MODE_CONNECTOR_VGA,
+ .encoder_funcs = &ls7a2000_pipe0_vga_encoder_funcs,
+ .encoder_helper_funcs = &lsdc_pipe0_hdmi_encoder_helper_funcs,
+ .connector_funcs = &lsdc_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "VGA-0",
+};
+
+static const struct lsdc_output_desc ls7a2000_hdmi_pipe0 = {
+ .pipe = 0,
+ .encoder_type = DRM_MODE_ENCODER_TMDS,
+ .connector_type = DRM_MODE_CONNECTOR_HDMIA,
+ .encoder_funcs = &lsdc_pipe0_hdmi_encoder_funcs,
+ .encoder_helper_funcs = &lsdc_pipe0_hdmi_encoder_helper_funcs,
+ .connector_funcs = &lsdc_pipe0_hdmi_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "HDMI-0",
+};
+
+static const struct lsdc_output_desc ls7a2000_hdmi_pipe1 = {
+ .pipe = 1,
+ .encoder_type = DRM_MODE_ENCODER_TMDS,
+ .connector_type = DRM_MODE_CONNECTOR_HDMIA,
+ .encoder_funcs = &lsdc_pipe1_hdmi_encoder_funcs,
+ .encoder_helper_funcs = &lsdc_pipe1_hdmi_encoder_helper_funcs,
+ .connector_funcs = &lsdc_pipe1_hdmi_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "HDMI-1",
+};
+
+/*
+ * For LS7A2000, the built-in VGA encoder is transparent. If there are
+ * external encoder exist, then the internal HDMI encoder MUST be enabled
+ * and initialized. As the internal HDMI encoder is always connected, so
+ * only the transmitters which take HDMI signal (such as HDMI to eDP, HDMI
+ * to LVDS, etc) are usable with.
+ */
+const struct lsdc_output_desc *
+ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
+{
+ enum loongson_vbios_encoder_name encoder_name = 0;
+ bool ret;
+
+ ret = loongson_vbios_query_encoder_info(ddev, pipe, NULL,
+ &encoder_name, NULL);
+ if (!ret)
+ goto bailout;
+
+ if (pipe == 0) {
+ switch (encoder_name) {
+ case ENCODER_CHIP_INTERNAL_HDMI:
+ return &ls7a2000_hdmi_pipe0;
+
+ /*
+ * For LS7A2000, the built-in VGA encoder is transparent.
+ */
+ case ENCODER_CHIP_INTERNAL_VGA:
+ return &ls7a2000_vga_pipe0;
+
+ /*
+ * External display bridge exists, the internal HDMI encoder
+ * MUST be enabled and initialized. Please add a drm bridge
+ * driver, and attach to this encoder.
+ */
+ default:
+ return &ls7a2000_hdmi_pipe0;
+ }
+ }
+
+ if (pipe == 1) {
+ switch (encoder_name) {
+ case ENCODER_CHIP_INTERNAL_HDMI:
+ return &ls7a2000_hdmi_pipe1;
+
+ /*
+ * External display bridge exists, the internal HDMI encoder
+ * MUST be enabled and initialized. Please add a drm bridge
+ * driver, and attach it to this encoder.
+ */
+ default:
+ return &ls7a2000_hdmi_pipe1;
+ }
+ }
+
+bailout:
+ if (pipe == 0)
+ return &ls7a2000_vga_pipe0;
+
+ if (pipe == 1)
+ return &ls7a2000_hdmi_pipe1;
+
+ return NULL;
+}
+
/*
* For LS7A2000:
*
@@ -517,36 +637,10 @@ int ls7a2000_output_init(struct drm_device *ddev,
unsigned int pipe)
{
struct lsdc_output *output = &dispipe->output;
- struct drm_encoder *encoder = &output->encoder;
- struct drm_connector *connector = &output->connector;
- int ret;
-
- ret = drm_encoder_init(ddev, encoder, &ls7a2000_encoder_funcs[pipe],
- DRM_MODE_ENCODER_TMDS, "encoder-%u", pipe);
- if (ret)
- return ret;
-
- encoder->possible_crtcs = BIT(pipe);
-
- drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs);
-
- ret = drm_connector_init_with_ddc(ddev, connector,
- &ls7a2000_hdmi_connector_funcs[pipe],
- DRM_MODE_CONNECTOR_HDMIA, ddc);
- if (ret)
- return ret;

- drm_info(ddev, "display pipe-%u has HDMI %s\n", pipe, pipe ? "" : "and/or VGA");
+ output->descp = ls7a2000_query_output_configuration(ddev, pipe);
+ if (!output->descp)
+ return -EINVAL;

- drm_connector_helper_add(connector, &ls7a2000_connector_helpers);
-
- drm_connector_attach_encoder(connector, encoder);
-
- connector->polled = DRM_CONNECTOR_POLL_CONNECT |
- DRM_CONNECTOR_POLL_DISCONNECT;
-
- connector->interlace_allowed = 0;
- connector->doublescan_allowed = 0;
-
- return 0;
+ return lsdc_output_init(ddev, dispipe, ddc, pipe);
}
--
2.34.1

2023-10-29 19:47:10

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 4/8] drm/loongson: Started to attach display bridge driver for LS7A1000

Loongson ML5B_MA board using ITE IT66121 HDMI transmitter to support HDMI
display output, with the vbios provided the necessary information, we are
able to create a minimal drm bridge driver for it. After apply this patch
we are able to change mode freely.

Tested on LS3A5000+LS7A1000 ML5B_MA board.

$ dmesg | grep drm

[drm] dc: 264MHz, gmc: 529MHz, gpu: 529MHz
[drm] Dedicated vram start: 0xe0030000000, size: 64MiB
[drm] Loongson VBIOS version: 0.3
[drm] Loongson VBIOS: has 8 DCBs
[drm] VRAM: 4096 pages ready
[drm] GTT: 32768 pages ready
[drm] lsdc-i2c0(sda pin mask=1, scl pin mask=2) created
[drm] lsdc-i2c1(sda pin mask=4, scl pin mask=8) created
[drm] DisplayPipe-0 has DVO-0
[drm] device address(0x4d) is not correct
[drm] i2c client IT66121@0x4c created
[drm] IT66121 attached, Vendor ID: 0x4954, Device ID: 0x612
[drm] Total 2 outputs
[drm] registered irq: 40
[drm] Initialized loongson 1.0.0 20220701 for 0000:00:06.1 on minor 0
loongson 0000:00:06.1: [drm] fb0: loongsondrmfb frame buffer device

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/lsdc_output_7a1000.c | 144 +++++++-----------
1 file changed, 55 insertions(+), 89 deletions(-)

diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a1000.c b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
index 6fc8dd1c7d9a..e12f9a0157d0 100644
--- a/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
+++ b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
@@ -10,6 +10,7 @@
#include "lsdc_drv.h"
#include "lsdc_output.h"

+#include "ite_it66121.h"
/*
* The display controller in the LS7A1000 exports two DVO interfaces, thus
* external encoder is required, except connected to the DPI panel directly.
@@ -38,68 +39,6 @@
* TODO: Add support for non-transparent encoders
*/

-static int ls7a1000_dpi_connector_get_modes(struct drm_connector *conn)
-{
- unsigned int num = 0;
- struct edid *edid;
-
- if (conn->ddc) {
- edid = drm_get_edid(conn, conn->ddc);
- if (edid) {
- drm_connector_update_edid_property(conn, edid);
- num = drm_add_edid_modes(conn, edid);
- kfree(edid);
- }
-
- return num;
- }
-
- num = drm_add_modes_noedid(conn, 1920, 1200);
-
- drm_set_preferred_mode(conn, 1024, 768);
-
- return num;
-}
-
-static struct drm_encoder *
-ls7a1000_dpi_connector_get_best_encoder(struct drm_connector *connector,
- struct drm_atomic_state *state)
-{
- struct lsdc_output *output = connector_to_lsdc_output(connector);
-
- return &output->encoder;
-}
-
-static const struct drm_connector_helper_funcs
-ls7a1000_dpi_connector_helpers = {
- .atomic_best_encoder = ls7a1000_dpi_connector_get_best_encoder,
- .get_modes = ls7a1000_dpi_connector_get_modes,
-};
-
-static enum drm_connector_status
-ls7a1000_dpi_connector_detect(struct drm_connector *connector, bool force)
-{
- struct i2c_adapter *ddc = connector->ddc;
-
- if (ddc) {
- if (drm_probe_ddc(ddc))
- return connector_status_connected;
-
- return connector_status_disconnected;
- }
-
- return connector_status_unknown;
-}
-
-static const struct drm_connector_funcs ls7a1000_dpi_connector_funcs = {
- .detect = ls7a1000_dpi_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = drm_connector_cleanup,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state
-};
-
static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder)
{
struct drm_device *ddev = encoder->dev;
@@ -139,40 +78,67 @@ static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = {
},
};

+/*
+ * This is a default output description for LS7A1000/LS2K1000, this is always
+ * true from the hardware perspective. It is just that when there are external
+ * display bridge connected, this description no longer complete. As it cannot
+ * describe the topology about the external encoders.
+ */
+static const struct lsdc_output_desc ls7a1000_output_desc[2] = {
+ {
+ .pipe = 0,
+ .encoder_type = DRM_MODE_ENCODER_DPI,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+ .encoder_funcs = &ls7a1000_encoder_funcs[0],
+ .encoder_helper_funcs = &lsdc_encoder_helper_funcs,
+ .connector_funcs = &lsdc_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "DVO-0",
+ },
+ {
+ .pipe = 1,
+ .encoder_type = DRM_MODE_ENCODER_DPI,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+ .encoder_funcs = &ls7a1000_encoder_funcs[1],
+ .encoder_helper_funcs = &lsdc_encoder_helper_funcs,
+ .connector_funcs = &lsdc_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "DVO-1",
+ },
+};
+
int ls7a1000_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
struct i2c_adapter *ddc,
unsigned int index)
{
struct lsdc_output *output = &dispipe->output;
- struct drm_encoder *encoder = &output->encoder;
- struct drm_connector *connector = &output->connector;
- int ret;
-
- ret = drm_encoder_init(ddev, encoder, &ls7a1000_encoder_funcs[index],
- DRM_MODE_ENCODER_TMDS, "encoder-%u", index);
- if (ret)
- return ret;
-
- encoder->possible_crtcs = BIT(index);
-
- ret = drm_connector_init_with_ddc(ddev, connector,
- &ls7a1000_dpi_connector_funcs,
- DRM_MODE_CONNECTOR_DPI, ddc);
- if (ret)
- return ret;
-
- drm_info(ddev, "display pipe-%u has a DVO\n", index);
-
- drm_connector_helper_add(connector, &ls7a1000_dpi_connector_helpers);
-
- drm_connector_attach_encoder(connector, encoder);
+ enum loongson_vbios_encoder_name encoder_name = 0;
+ struct drm_bridge *bridge = NULL;
+ u8 slave_addr;
+ bool ret;
+
+ output->descp = &ls7a1000_output_desc[index];
+
+ ret = loongson_vbios_query_encoder_info(ddev, index, NULL,
+ &encoder_name, &slave_addr);
+ if (!ret)
+ goto skip;
+
+ switch (encoder_name) {
+ case ENCODER_CHIP_IT66121:
+ bridge = it66121_bridge_create(ddev, ddc, slave_addr, false,
+ 0, index);
+ break;
+ default:
+ break;
+ }

- connector->polled = DRM_CONNECTOR_POLL_CONNECT |
- DRM_CONNECTOR_POLL_DISCONNECT;
+ if (IS_ERR(bridge))
+ goto skip;

- connector->interlace_allowed = 0;
- connector->doublescan_allowed = 0;
+ output->bridge = bridge;

- return 0;
+skip:
+ return lsdc_output_init(ddev, dispipe, ddc, index);
}
--
2.34.1

2023-10-29 19:47:12

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 6/8] drm/loongson: Clean up the output part of LS7A2000

Since the majority of sharable subroutines have been move to lsdc_output.c,
and functional changes are done with previous patch. We finally see the
light to cleanup, no functional change.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/lsdc_output_7a2000.c | 469 ------------------
1 file changed, 469 deletions(-)

diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
index bf558b61802b..981ab2045e91 100644
--- a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
+++ b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
@@ -42,465 +42,6 @@
* |______________________|
*/

-static int ls7a2000_connector_get_modes(struct drm_connector *connector)
-{
- unsigned int num = 0;
- struct edid *edid;
-
- if (connector->ddc) {
- edid = drm_get_edid(connector, connector->ddc);
- if (edid) {
- drm_connector_update_edid_property(connector, edid);
- num = drm_add_edid_modes(connector, edid);
- kfree(edid);
- }
-
- return num;
- }
-
- num = drm_add_modes_noedid(connector, 1920, 1200);
-
- drm_set_preferred_mode(connector, 1024, 768);
-
- return num;
-}
-
-static struct drm_encoder *
-ls7a2000_connector_get_best_encoder(struct drm_connector *connector,
- struct drm_atomic_state *state)
-{
- struct lsdc_output *output = connector_to_lsdc_output(connector);
-
- return &output->encoder;
-}
-
-static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = {
- .atomic_best_encoder = ls7a2000_connector_get_best_encoder,
- .get_modes = ls7a2000_connector_get_modes,
-};
-
-/* debugfs */
-
-#define LSDC_HDMI_REG(i, reg) { \
- .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \
- .offset = LSDC_HDMI##i##_##reg##_REG, \
-}
-
-static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = {
- LSDC_HDMI_REG(0, ZONE),
- LSDC_HDMI_REG(0, INTF_CTRL),
- LSDC_HDMI_REG(0, PHY_CTRL),
- LSDC_HDMI_REG(0, PHY_PLL),
- LSDC_HDMI_REG(0, AVI_INFO_CRTL),
- LSDC_HDMI_REG(0, PHY_CAL),
- LSDC_HDMI_REG(0, AUDIO_PLL_LO),
- LSDC_HDMI_REG(0, AUDIO_PLL_HI),
- {NULL, 0}, /* MUST be {NULL, 0} terminated */
-};
-
-static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = {
- LSDC_HDMI_REG(1, ZONE),
- LSDC_HDMI_REG(1, INTF_CTRL),
- LSDC_HDMI_REG(1, PHY_CTRL),
- LSDC_HDMI_REG(1, PHY_PLL),
- LSDC_HDMI_REG(1, AVI_INFO_CRTL),
- LSDC_HDMI_REG(1, PHY_CAL),
- LSDC_HDMI_REG(1, AUDIO_PLL_LO),
- LSDC_HDMI_REG(1, AUDIO_PLL_HI),
- {NULL, 0}, /* MUST be {NULL, 0} terminated */
-};
-
-static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data)
-{
- struct drm_info_node *node = (struct drm_info_node *)m->private;
- struct drm_device *ddev = node->minor->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- const struct lsdc_reg32 *preg;
-
- preg = (const struct lsdc_reg32 *)node->info_ent->data;
-
- while (preg->name) {
- u32 offset = preg->offset;
-
- seq_printf(m, "%s (0x%04x): 0x%08x\n",
- preg->name, offset, lsdc_rreg32(ldev, offset));
- ++preg;
- }
-
- return 0;
-}
-
-static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = {
- { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs },
-};
-
-static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = {
- { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs },
-};
-
-static void ls7a2000_hdmi0_late_register(struct drm_connector *connector,
- struct dentry *root)
-{
- struct drm_device *ddev = connector->dev;
- struct drm_minor *minor = ddev->primary;
-
- drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files,
- ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files),
- root, minor);
-}
-
-static void ls7a2000_hdmi1_late_register(struct drm_connector *connector,
- struct dentry *root)
-{
- struct drm_device *ddev = connector->dev;
- struct drm_minor *minor = ddev->primary;
-
- drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files,
- ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files),
- root, minor);
-}
-
-/* monitor present detection */
-
-static enum drm_connector_status
-ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force)
-{
- struct drm_device *ddev = connector->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- u32 val;
-
- val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
-
- if (val & HDMI0_HPD_FLAG)
- return connector_status_connected;
-
- if (connector->ddc) {
- if (drm_probe_ddc(connector->ddc))
- return connector_status_connected;
-
- return connector_status_disconnected;
- }
-
- return connector_status_unknown;
-}
-
-static enum drm_connector_status
-ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force)
-{
- struct lsdc_device *ldev = to_lsdc(connector->dev);
- u32 val;
-
- val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
-
- if (val & HDMI1_HPD_FLAG)
- return connector_status_connected;
-
- return connector_status_disconnected;
-}
-
-static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = {
- {
- .detect = ls7a2000_hdmi0_vga_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = drm_connector_cleanup,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
- .debugfs_init = ls7a2000_hdmi0_late_register,
- },
- {
- .detect = ls7a2000_hdmi1_connector_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = drm_connector_cleanup,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
- .debugfs_init = ls7a2000_hdmi1_late_register,
- },
-};
-
-/* Even though some board has only one hdmi on display pipe 1,
- * We still need hook lsdc_encoder_funcs up on display pipe 0,
- * This is because we need its reset() callback get called, to
- * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c.
- * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly.
- */
-static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder)
-{
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- u32 val;
-
- val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
- lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
-
- /* using software gpio emulated i2c */
- val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG);
- val &= ~HW_I2C_EN;
- lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val);
-
- /* help the hdmi phy to get out of reset state */
- lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
-
- mdelay(20);
-
- drm_dbg(ddev, "HDMI-0 Reset\n");
-}
-
-static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder)
-{
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- u32 val;
-
- val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
- lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
-
- /* using software gpio emulated i2c */
- val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG);
- val &= ~HW_I2C_EN;
- lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val);
-
- /* help the hdmi phy to get out of reset state */
- lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
-
- mdelay(20);
-
- drm_dbg(ddev, "HDMI-1 Reset\n");
-}
-
-static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = {
- {
- .reset = ls7a2000_hdmi0_encoder_reset,
- .destroy = drm_encoder_cleanup,
- },
- {
- .reset = ls7a2000_hdmi1_encoder_reset,
- .destroy = drm_encoder_cleanup,
- },
-};
-
-static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder,
- struct drm_display_mode *mode)
-{
- struct lsdc_output *output = encoder_to_lsdc_output(encoder);
- struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
- unsigned int index = dispipe->index;
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- struct hdmi_avi_infoframe infoframe;
- u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
- unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE];
- unsigned int content0, content1, content2, content3;
- int err;
-
- err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe,
- &output->connector,
- mode);
- if (err < 0) {
- drm_err(ddev, "failed to setup AVI infoframe: %d\n", err);
- return err;
- }
-
- /* Fixed infoframe configuration not linked to the mode */
- infoframe.colorspace = HDMI_COLORSPACE_RGB;
- infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
- infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
-
- err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
- if (err < 0) {
- drm_err(ddev, "failed to pack AVI infoframe: %d\n", err);
- return err;
- }
-
- content0 = *(unsigned int *)ptr;
- content1 = *(ptr + 4);
- content2 = *(unsigned int *)(ptr + 5);
- content3 = *(unsigned int *)(ptr + 9);
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0);
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1);
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2);
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3);
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index,
- AVI_PKT_ENABLE | AVI_PKT_UPDATE);
-
- drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index);
-
- return 0;
-}
-
-static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder,
- struct drm_atomic_state *state)
-{
- struct lsdc_output *output = encoder_to_lsdc_output(encoder);
- struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
- unsigned int index = dispipe->index;
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- u32 val;
-
- /* Disable the hdmi phy */
- val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index);
- val &= ~HDMI_PHY_EN;
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val);
-
- /* Disable the hdmi interface */
- val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index);
- val &= ~HDMI_INTERFACE_EN;
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val);
-
- drm_dbg(ddev, "HDMI-%u disabled\n", index);
-}
-
-static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder,
- struct drm_atomic_state *state)
-{
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- struct lsdc_output *output = encoder_to_lsdc_output(encoder);
- struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
- unsigned int index = dispipe->index;
- u32 val;
-
- /* datasheet say it should larger than 48 */
- val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val);
-
- val = HDMI_PHY_TERM_STATUS |
- HDMI_PHY_TERM_DET_EN |
- HDMI_PHY_TERM_H_EN |
- HDMI_PHY_TERM_L_EN |
- HDMI_PHY_RESET_N |
- HDMI_PHY_EN;
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val);
-
- udelay(2);
-
- val = HDMI_CTL_PERIOD_MODE |
- HDMI_AUDIO_EN |
- HDMI_PACKET_EN |
- HDMI_INTERFACE_EN |
- (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val);
-
- drm_dbg(ddev, "HDMI-%u enabled\n", index);
-}
-
-/*
- * Fout = M * Fin
- *
- * M = (4 * LF) / (IDF * ODF)
- *
- * IDF: Input Division Factor
- * ODF: Output Division Factor
- * LF: Loop Factor
- * M: Required Mult
- *
- * +--------------------------------------------------------+
- * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) |
- * |-------------------+----+-----+----+-----+--------------|
- * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 |
- * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 |
- * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 |
- * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 |
- * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 |
- * +--------------------------------------------------------+
- */
-static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev,
- int fin,
- unsigned int index)
-{
- struct drm_device *ddev = &ldev->base;
- int count = 0;
- u32 val;
-
- /* Firstly, disable phy pll */
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0);
-
- /*
- * Most of time, loongson HDMI require M = 10
- * for example, 10 = (4 * 40) / (8 * 2)
- * here, write "1" to the ODF will get "2"
- */
-
- if (fin >= 170000)
- val = (16 << HDMI_PLL_IDF_SHIFT) |
- (40 << HDMI_PLL_LF_SHIFT) |
- (0 << HDMI_PLL_ODF_SHIFT);
- else if (fin >= 85000)
- val = (8 << HDMI_PLL_IDF_SHIFT) |
- (40 << HDMI_PLL_LF_SHIFT) |
- (1 << HDMI_PLL_ODF_SHIFT);
- else if (fin >= 42500)
- val = (4 << HDMI_PLL_IDF_SHIFT) |
- (40 << HDMI_PLL_LF_SHIFT) |
- (2 << HDMI_PLL_ODF_SHIFT);
- else if (fin >= 21250)
- val = (2 << HDMI_PLL_IDF_SHIFT) |
- (40 << HDMI_PLL_LF_SHIFT) |
- (3 << HDMI_PLL_ODF_SHIFT);
- else
- val = (1 << HDMI_PLL_IDF_SHIFT) |
- (40 << HDMI_PLL_LF_SHIFT) |
- (4 << HDMI_PLL_ODF_SHIFT);
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val);
-
- val |= HDMI_PLL_ENABLE;
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val);
-
- udelay(2);
-
- drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin);
-
- /* Wait hdmi phy pll lock */
- do {
- val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index);
-
- if (val & HDMI_PLL_LOCKED) {
- drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n",
- index, count);
- break;
- }
- ++count;
- } while (count < 1000);
-
- lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0);
-
- if (count >= 1000)
- drm_err(ddev, "Setting HDMI-%u PLL failed\n", index);
-}
-
-static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder,
- struct drm_crtc_state *crtc_state,
- struct drm_connector_state *conn_state)
-{
- struct lsdc_output *output = encoder_to_lsdc_output(encoder);
- struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
- unsigned int index = dispipe->index;
- struct drm_device *ddev = encoder->dev;
- struct lsdc_device *ldev = to_lsdc(ddev);
- struct drm_display_mode *mode = &crtc_state->mode;
-
- ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, index);
-
- ls7a2000_hdmi_set_avi_infoframe(encoder, mode);
-
- drm_dbg(ddev, "%s modeset finished\n", encoder->name);
-}
-
-static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = {
- .atomic_disable = ls7a2000_hdmi_atomic_disable,
- .atomic_enable = ls7a2000_hdmi_atomic_enable,
- .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set,
-};
-
/* The built-in tranparent VGA encoder is only available on display pipe 0 */
static void ls7a2000_pipe0_vga_encoder_reset(struct drm_encoder *encoder)
{
@@ -621,16 +162,6 @@ ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
return NULL;
}

-/*
- * For LS7A2000:
- *
- * 1) Most of board export one vga + hdmi output interface.
- * 2) Yet, Some boards export double hdmi output interface.
- * 3) Still have boards export three output(2 hdmi + 1 vga).
- *
- * So let's hook hdmi helper funcs to all display pipe, don't miss.
- * writing hdmi register do no harms.
- */
int ls7a2000_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
struct i2c_adapter *ddc,
--
2.34.1

2023-10-29 19:47:15

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
chip to support HDMI output. Thus add a drm bridge based driver for it.
This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/Kconfig | 1 +
drivers/gpu/drm/loongson/Makefile | 2 +
drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
drivers/gpu/drm/loongson/ite_it66121.h | 19 +
drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
5 files changed, 1039 insertions(+)
create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h

diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig
index df6946d505fa..a96f5699099e 100644
--- a/drivers/gpu/drm/loongson/Kconfig
+++ b/drivers/gpu/drm/loongson/Kconfig
@@ -7,6 +7,7 @@ config DRM_LOONGSON
select DRM_TTM
select I2C
select I2C_ALGOBIT
+ select REGMAP_I2C
help
This is a DRM driver for Loongson Graphics, it may including
LS7A2000, LS7A1000, LS2K2000 and LS2K1000 etc. Loongson LS7A
diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
index bef00b2c5569..1459d19b2c90 100644
--- a/drivers/gpu/drm/loongson/Makefile
+++ b/drivers/gpu/drm/loongson/Makefile
@@ -20,4 +20,6 @@ loongson-y += loongson_device.o \
loongson_module.o \
loongson_vbios.o

+loongson-y += ite_it66121.o
+
obj-$(CONFIG_DRM_LOONGSON) += loongson.o
diff --git a/drivers/gpu/drm/loongson/ite_it66121.c b/drivers/gpu/drm/loongson/ite_it66121.c
new file mode 100644
index 000000000000..7b085575f864
--- /dev/null
+++ b/drivers/gpu/drm/loongson/ite_it66121.c
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 BayLibre, SAS
+ * Author: Phong LE <[email protected]>
+ * Copyright (C) 2018-2019, Artem Mygaiev
+ * Copyright (C) 2017, Fresco Logic, Incorporated.
+ *
+ * IT66121 HDMI transmitter driver
+ */
+
+#include <linux/media-bus-format.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/bitfield.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "ite_it66121.h"
+#include "ite_it66121_regs.h"
+
+#define IT66121_CHIP_NAME "IT66121"
+
+struct it66121_bridge {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct regmap *regmap;
+ struct i2c_client *client;
+ /* Protects fields below and device registers */
+ struct mutex lock;
+ u16 vendor_id;
+ u16 device_id;
+ u32 revision;
+};
+
+static inline struct it66121_bridge *
+bridge_to_it66121(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct it66121_bridge, bridge);
+}
+
+static inline struct it66121_bridge *
+connector_to_it66121(struct drm_connector *connector)
+{
+ return container_of(connector, struct it66121_bridge, connector);
+}
+
+static const struct regmap_range_cfg it66121_regmap_banks[] = {
+ {
+ .name = IT66121_CHIP_NAME,
+ .range_min = 0x00,
+ .range_max = 0x1FF,
+ .selector_reg = IT66121_CLK_BANK_REG,
+ .selector_mask = 0x1,
+ .selector_shift = 0,
+ .window_start = 0x00,
+ .window_len = 0x100,
+ },
+};
+
+static const struct regmap_config it66121_regmap_config = {
+ .val_bits = 8,
+ .reg_bits = 8,
+ .max_register = 0x1FF,
+ .ranges = it66121_regmap_banks,
+ .num_ranges = ARRAY_SIZE(it66121_regmap_banks),
+};
+
+static inline int it66121_preamble_ddc(struct it66121_bridge *itb)
+{
+ return regmap_write(itb->regmap, IT66121_MASTER_SEL_REG,
+ IT66121_MASTER_SEL_HOST);
+}
+
+static inline int it66121_fire_afe(struct it66121_bridge *itb)
+{
+ return regmap_write(itb->regmap, IT66121_AFE_DRV_REG, 0);
+}
+
+static int it66121_configure_input(struct it66121_bridge *itb)
+{
+ int ret;
+
+ ret = regmap_write(itb->regmap, IT66121_INPUT_MODE_REG,
+ IT66121_INPUT_MODE_RGB888);
+ if (ret)
+ return ret;
+
+ return regmap_write(itb->regmap, IT66121_INPUT_CSC_REG,
+ IT66121_INPUT_CSC_NO_CONV);
+}
+
+/*
+ * it66121_configure_afe() - Configure the analog front end
+ * @ctx: it66121_ctx object
+ * @mode: mode to configure
+ *
+ * RETURNS:
+ * zero if success, a negative error code otherwise.
+ */
+static int it66121_configure_afe(struct it66121_bridge *itb,
+ const struct drm_display_mode *mode)
+{
+ int ret;
+
+ ret = regmap_write(itb->regmap, IT66121_AFE_DRV_REG,
+ IT66121_AFE_DRV_RST);
+ if (ret)
+ return ret;
+
+ if (mode->clock > IT66121_AFE_CLK_HIGH) {
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_REG,
+ IT66121_AFE_XP_GAINBIT |
+ IT66121_AFE_XP_ENO,
+ IT66121_AFE_XP_GAINBIT);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_GAINBIT |
+ IT66121_AFE_IP_ER0,
+ IT66121_AFE_IP_GAINBIT);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_EC1, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_EC1_REG,
+ IT66121_AFE_XP_EC1_LOWCLK, 0x80);
+ if (ret)
+ return ret;
+ } else {
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_REG,
+ IT66121_AFE_XP_GAINBIT |
+ IT66121_AFE_XP_ENO,
+ IT66121_AFE_XP_ENO);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_GAINBIT |
+ IT66121_AFE_IP_ER0,
+ IT66121_AFE_IP_ER0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_EC1,
+ IT66121_AFE_IP_EC1);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_EC1_REG,
+ IT66121_AFE_XP_EC1_LOWCLK,
+ IT66121_AFE_XP_EC1_LOWCLK);
+ if (ret)
+ return ret;
+ }
+
+ /* Clear reset flags */
+ ret = regmap_write_bits(itb->regmap, IT66121_SW_RST_REG,
+ IT66121_SW_RST_REF | IT66121_SW_RST_VID, 0);
+ if (ret)
+ return ret;
+
+ return it66121_fire_afe(itb);
+}
+
+static inline int it66121_wait_ddc_ready(struct it66121_bridge *itb)
+{
+ u32 error = IT66121_DDC_STATUS_NOACK |
+ IT66121_DDC_STATUS_WAIT_BUS |
+ IT66121_DDC_STATUS_ARBI_LOSE;
+ u32 done = IT66121_DDC_STATUS_TX_DONE;
+ int ret, val;
+
+ ret = regmap_read_poll_timeout(itb->regmap, IT66121_DDC_STATUS_REG,
+ val, val & (error | done),
+ IT66121_EDID_SLEEP_US,
+ IT66121_EDID_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ if (val & error)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static int it66121_abort_ddc_ops(struct it66121_bridge *itb)
+{
+ unsigned int swreset, cpdesire;
+ int ret;
+
+ ret = regmap_read(itb->regmap, IT66121_SW_RST_REG, &swreset);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(itb->regmap, IT66121_HDCP_REG, &cpdesire);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_HDCP_REG,
+ cpdesire & (~IT66121_HDCP_CPDESIRED & 0xFF));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_SW_RST_REG,
+ (swreset | IT66121_SW_RST_HDCP));
+ if (ret)
+ return ret;
+
+ ret = it66121_preamble_ddc(itb);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_COMMAND_REG,
+ IT66121_DDC_COMMAND_ABORT);
+ if (ret)
+ return ret;
+
+ return it66121_wait_ddc_ready(itb);
+}
+
+static int it66121_get_edid_block(void *context,
+ u8 *buf,
+ unsigned int block,
+ size_t len)
+{
+ struct it66121_bridge *itb = (struct it66121_bridge *)context;
+ int remain = len;
+ int offset = 0;
+ int ret, cnt;
+
+ offset = (block % 2) * len;
+ block = block / 2;
+
+ while (remain > 0) {
+ cnt = (remain > IT66121_EDID_FIFO_SIZE) ?
+ IT66121_EDID_FIFO_SIZE : remain;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_COMMAND_REG,
+ IT66121_DDC_COMMAND_FIFO_CLR);
+ if (ret)
+ return ret;
+
+ ret = it66121_wait_ddc_ready(itb);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_OFFSET_REG, offset);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_BYTE_REG, cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_SEGMENT_REG, block);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_COMMAND_REG,
+ IT66121_DDC_COMMAND_EDID_READ);
+ if (ret)
+ return ret;
+
+ offset += cnt;
+ remain -= cnt;
+
+ ret = it66121_wait_ddc_ready(itb);
+ if (ret) {
+ it66121_abort_ddc_ops(itb);
+ return ret;
+ }
+
+ ret = regmap_noinc_read(itb->regmap, IT66121_DDC_RD_FIFO_REG,
+ buf, cnt);
+ if (ret)
+ return ret;
+
+ buf += cnt;
+ }
+
+ return 0;
+}
+
+static bool it66121_is_hpd_detect(struct it66121_bridge *itb)
+{
+ int val;
+
+ if (regmap_read(itb->regmap, IT66121_SYS_STATUS_REG, &val))
+ return false;
+
+ return val & IT66121_SYS_STATUS_HPDETECT;
+}
+
+static int it66121_connector_get_modes(struct drm_connector *connector)
+{
+ struct it66121_bridge *itb = connector_to_it66121(connector);
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ int num_modes = 0;
+ struct edid *edid;
+ int ret;
+
+ edid = drm_bridge_get_edid(&itb->bridge, connector);
+ if (!edid) {
+ drm_err(connector->dev, "Failed to read EDID\n");
+ goto failed;
+ }
+
+ if (drm_connector_update_edid_property(connector, edid)) {
+ drm_err(connector->dev, "Failed to update EDID\n");
+ goto failed;
+ }
+
+ ret = drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+ if (ret)
+ goto failed;
+
+ num_modes = drm_add_edid_modes(connector, edid);
+
+failed:
+ return num_modes;
+}
+
+static int it66121_connector_detect_ctx(struct drm_connector *connector,
+ struct drm_modeset_acquire_ctx *ctx,
+ bool force)
+{
+ struct it66121_bridge *itb = connector_to_it66121(connector);
+
+ return it66121_is_hpd_detect(itb) ? connector_status_connected
+ : connector_status_disconnected;
+}
+
+static struct drm_connector_helper_funcs it66121_connector_helper_funcs = {
+ .get_modes = it66121_connector_get_modes,
+ .detect_ctx = it66121_connector_detect_ctx,
+};
+
+static const struct drm_connector_funcs it66121_connector_funcs = {
+ .reset = drm_atomic_helper_connector_reset,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int it66121_bridge_connector_init(struct drm_bridge *bridge)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ struct drm_connector *connector = &itb->connector;
+ int ret;
+
+ if (bridge->ops & DRM_BRIDGE_OP_HPD) {
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ } else {
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ }
+
+ ret = drm_connector_init(bridge->dev,
+ connector,
+ &it66121_connector_funcs,
+ bridge->type);
+ if (ret)
+ return ret;
+
+ drm_connector_helper_add(connector, &it66121_connector_helper_funcs);
+
+ drm_connector_attach_encoder(connector, bridge->encoder);
+
+ return 0;
+}
+
+static int it66121_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ int ret;
+
+ ret = it66121_bridge_connector_init(bridge);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_RCLK, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_INT_REG,
+ IT66121_INT_TX_CLK_OFF, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_DRV_REG,
+ IT66121_AFE_DRV_PWD, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_REG,
+ IT66121_AFE_XP_PWDI | IT66121_AFE_XP_PWDPLL, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_PWDPLL, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_DRV_REG,
+ IT66121_AFE_DRV_RST, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_XP_REG,
+ IT66121_AFE_XP_RESETB, IT66121_AFE_XP_RESETB);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_AFE_IP_REG,
+ IT66121_AFE_IP_RESETB, IT66121_AFE_IP_RESETB);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(itb->regmap, IT66121_SW_RST_REG,
+ IT66121_SW_RST_REF,
+ IT66121_SW_RST_REF);
+ if (ret)
+ return ret;
+
+ drm_info(bridge->dev,
+ "IT66121 attached, Vendor ID: 0x%x, Device ID: 0x%x\n",
+ itb->vendor_id, itb->device_id);
+
+ /* Per programming manual, sleep here for bridge to settle */
+ msleep(50);
+
+ return 0;
+}
+
+static void it66121_bridge_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *state)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ struct regmap *regmap = itb->regmap;
+ int ret;
+
+ ret = regmap_clear_bits(regmap, IT66121_AVMUTE_REG, IT66121_AVMUTE_BIT);
+ if (ret)
+ drm_err(bridge->dev, "Enable it66121 bridge failed");
+
+ regmap_write(regmap, IT66121_PKT_GEN_CTRL_REG,
+ IT66121_PKT_GEN_CTRL_ON | IT66121_PKT_GEN_CTRL_RPT);
+}
+
+static void it66121_bridge_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ struct regmap *regmap = itb->regmap;
+ int ret;
+
+ ret = regmap_set_bits(regmap, IT66121_AVMUTE_REG, IT66121_AVMUTE_BIT);
+ if (ret)
+ drm_err(bridge->dev, "Disable it66121 bridge failed");
+
+ regmap_write(regmap, IT66121_PKT_GEN_CTRL_REG,
+ IT66121_PKT_GEN_CTRL_ON | IT66121_PKT_GEN_CTRL_RPT);
+}
+
+static void it66121_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj_mode)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ struct hdmi_avi_infoframe avi_infoframe;
+ u8 av_buf[HDMI_INFOFRAME_SIZE(AVI)];
+ int ret;
+
+ mutex_lock(&itb->lock);
+
+ hdmi_avi_infoframe_init(&avi_infoframe);
+
+ ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_infoframe,
+ &itb->connector,
+ adj_mode);
+ if (ret) {
+ drm_err(bridge->dev, "Failed to setup AVI infoframe\n");
+ goto unlock;
+ }
+
+ ret = hdmi_avi_infoframe_pack(&avi_infoframe, av_buf, sizeof(av_buf));
+ if (ret < 0) {
+ drm_err(bridge->dev, "Failed to pack infoframe\n");
+ goto unlock;
+ }
+
+ /* Write new AVI infoframe packet */
+ ret = regmap_bulk_write(itb->regmap, IT66121_AVIINFO_DB1_REG,
+ &av_buf[HDMI_INFOFRAME_HEADER_SIZE],
+ HDMI_AVI_INFOFRAME_SIZE);
+ if (ret)
+ goto unlock;
+
+ if (regmap_write(itb->regmap, IT66121_AVIINFO_CSUM_REG, av_buf[3]))
+ goto unlock;
+
+ /* Enable AVI infoframe */
+ if (regmap_write(itb->regmap, IT66121_AVI_INFO_PKT_REG,
+ IT66121_AVI_INFO_PKT_ON | IT66121_AVI_INFO_PKT_RPT))
+ goto unlock;
+
+ /* Set TX mode to HDMI */
+ if (regmap_write(itb->regmap, IT66121_HDMI_MODE_REG, IT66121_HDMI_MODE_HDMI))
+ goto unlock;
+
+ if (regmap_write_bits(itb->regmap, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_TXCLK,
+ IT66121_CLK_BANK_PWROFF_TXCLK))
+ goto unlock;
+
+ if (it66121_configure_input(itb))
+ goto unlock;
+
+ if (it66121_configure_afe(itb, adj_mode))
+ goto unlock;
+
+ if (regmap_write_bits(itb->regmap, IT66121_CLK_BANK_REG,
+ IT66121_CLK_BANK_PWROFF_TXCLK, 0))
+ goto unlock;
+
+unlock:
+ mutex_unlock(&itb->lock);
+}
+
+static enum drm_mode_status
+it66121_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock > 148500)
+ return MODE_CLOCK_HIGH;
+
+ if (mode->clock < 25000)
+ return MODE_CLOCK_LOW;
+
+ return MODE_OK;
+}
+
+static struct edid *it66121_bridge_get_edid(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+ struct edid *edid;
+ int ret;
+
+ mutex_lock(&itb->lock);
+ ret = it66121_preamble_ddc(itb);
+ if (ret) {
+ edid = NULL;
+ goto unlock;
+ }
+
+ ret = regmap_write(itb->regmap, IT66121_DDC_HEADER_REG,
+ IT66121_DDC_HEADER_EDID);
+ if (ret) {
+ edid = NULL;
+ goto unlock;
+ }
+
+ edid = drm_do_get_edid(connector, it66121_get_edid_block, itb);
+
+unlock:
+ mutex_unlock(&itb->lock);
+
+ return edid;
+}
+
+static void it66121_bridge_detach(struct drm_bridge *bridge)
+{
+ struct it66121_bridge *itb = bridge_to_it66121(bridge);
+
+ mutex_destroy(&itb->lock);
+
+ i2c_unregister_device(itb->client);
+
+ drm_bridge_remove(bridge);
+}
+
+static const struct drm_bridge_funcs it66121_bridge_funcs = {
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .attach = it66121_bridge_attach,
+ .detach = it66121_bridge_detach,
+ .atomic_enable = it66121_bridge_enable,
+ .atomic_disable = it66121_bridge_disable,
+ .mode_set = it66121_bridge_mode_set,
+ .mode_valid = it66121_bridge_mode_valid,
+ .get_edid = it66121_bridge_get_edid,
+};
+
+static void it66121_bridge_get_version(struct it66121_bridge *itb)
+{
+ u32 vendor_ids[2] = { 0 };
+ u32 device_ids[2] = { 0 };
+
+ regmap_read(itb->regmap, IT66121_VENDOR_ID0_REG, &vendor_ids[0]);
+ regmap_read(itb->regmap, IT66121_VENDOR_ID1_REG, &vendor_ids[1]);
+ regmap_read(itb->regmap, IT66121_DEVICE_ID0_REG, &device_ids[0]);
+ regmap_read(itb->regmap, IT66121_DEVICE_ID1_REG, &device_ids[1]);
+
+ /* Revision is shared with DEVICE_ID1 */
+ itb->revision = FIELD_GET(IT66121_REVISION_MASK, device_ids[1]);
+ device_ids[1] &= IT66121_DEVICE_ID1_MASK;
+
+ itb->vendor_id = vendor_ids[1] << 8 | vendor_ids[0];
+ itb->device_id = device_ids[1] << 8 | device_ids[0];
+}
+
+static void it66121_bridge_init_base(struct it66121_bridge *itb, bool hpd)
+{
+ struct drm_bridge *bridge = &itb->bridge;
+
+ bridge->funcs = &it66121_bridge_funcs;
+ bridge->type = DRM_MODE_CONNECTOR_HDMIA;
+ bridge->ops = DRM_BRIDGE_OP_EDID;
+
+ if (hpd)
+ bridge->ops |= DRM_BRIDGE_OP_HPD;
+
+ drm_bridge_add(bridge);
+}
+
+/*
+ * The device address is 0x98 if PCADR pin is pulled low, 0x98 >> 1 = 0x4c
+ * The device address is 0x9A if PCADR pin is pulled high, 0x9A >> 1 = 0x4d
+ */
+static bool it66121_probe_slave(struct drm_device *ddev,
+ struct i2c_adapter *adapter,
+ u8 *addr)
+{
+ struct i2c_msg msg = {
+ .len = 0,
+ };
+ int num = 3;
+ int count;
+ int i;
+
+ /* Try slave address 0x4c */
+ msg.addr = 0x4c;
+ count = 0;
+ for (i = 0; i < num; i++) {
+ count += i2c_transfer(adapter, &msg, 1);
+ udelay(9);
+ }
+
+ if (count == num) {
+ *addr = 0x4c;
+ return true;
+ }
+
+ /* Try slave address 0x4d */
+ msg.addr = 0x4d;
+ count = 0;
+ for (i = 0; i < num; i++) {
+ count += i2c_transfer(adapter, &msg, 1);
+ udelay(9);
+ }
+
+ if (count == num) {
+ *addr = 0x4d;
+ return true;
+ }
+
+ drm_err(ddev, "No reliable slave i2c device found\n");
+
+ /*
+ * If no reliable slave i2c device found, we would like drop the
+ * support.
+ */
+ return false;
+}
+
+struct drm_bridge *it66121_bridge_create(struct drm_device *ddev,
+ struct i2c_adapter *i2c,
+ u8 addr,
+ bool enable_hpd,
+ u32 int_gpio,
+ unsigned int pipe)
+{
+ struct i2c_board_info it66121_board_info = {
+ .type = IT66121_CHIP_NAME,
+ };
+ struct it66121_bridge *itb;
+ struct i2c_client *client;
+ u8 addr_probed;
+
+ if (!it66121_probe_slave(ddev, i2c, &addr_probed))
+ return NULL;
+
+ if (addr != addr_probed) {
+ drm_warn(ddev, "device address(0x%x) is not correct\n", addr);
+ addr = addr_probed;
+ }
+
+ it66121_board_info.addr = addr;
+
+ itb = devm_kzalloc(ddev->dev, sizeof(*itb), GFP_KERNEL);
+ if (!itb)
+ return NULL;
+
+ client = i2c_new_client_device(i2c, &it66121_board_info);
+ if (IS_ERR(client))
+ return NULL;
+
+ drm_info(ddev, "i2c client %s@0x%02x created\n",
+ it66121_board_info.type, it66121_board_info.addr);
+
+ itb->client = client;
+
+ i2c_set_clientdata(client, itb);
+
+ mutex_init(&itb->lock);
+
+ itb->regmap = devm_regmap_init_i2c(client, &it66121_regmap_config);
+ if (IS_ERR(itb->regmap)) {
+ drm_err(ddev, "Failed to map registers\n");
+ return NULL;
+ }
+
+ it66121_bridge_get_version(itb);
+
+ it66121_bridge_init_base(itb, enable_hpd);
+
+ return &itb->bridge;
+}
diff --git a/drivers/gpu/drm/loongson/ite_it66121.h b/drivers/gpu/drm/loongson/ite_it66121.h
new file mode 100644
index 000000000000..c3e26cce1b02
--- /dev/null
+++ b/drivers/gpu/drm/loongson/ite_it66121.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#ifndef __ITE_IT66121_H__
+#define __ITE_IT66121_H__
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+
+struct drm_bridge *it66121_bridge_create(struct drm_device *ddev,
+ struct i2c_adapter *i2c,
+ u8 addr,
+ bool enable_hpd,
+ u32 int_gpio,
+ unsigned int pipe);
+
+#endif
diff --git a/drivers/gpu/drm/loongson/ite_it66121_regs.h b/drivers/gpu/drm/loongson/ite_it66121_regs.h
new file mode 100644
index 000000000000..57118a4501c1
--- /dev/null
+++ b/drivers/gpu/drm/loongson/ite_it66121_regs.h
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+/*
+ * Copyright (C) 2020 BayLibre, SAS
+ * Author: Phong LE <[email protected]>
+ * Copyright (C) 2018-2019, Artem Mygaiev
+ * Copyright (C) 2017, Fresco Logic, Incorporated.
+ */
+
+#ifndef __ITE_IT66121_h__
+#define __ITE_IT66121_h__
+
+#define IT66121_VENDOR_ID0_REG 0x00
+#define IT66121_VENDOR_ID1_REG 0x01
+#define IT66121_DEVICE_ID0_REG 0x02
+#define IT66121_DEVICE_ID1_REG 0x03
+
+#define IT66121_REVISION_MASK GENMASK(7, 4)
+#define IT66121_DEVICE_ID1_MASK GENMASK(3, 0)
+
+#define IT66121_MASTER_SEL_REG 0x10
+#define IT66121_MASTER_SEL_HOST BIT(0)
+
+#define IT66121_AFE_DRV_REG 0x61
+#define IT66121_AFE_DRV_RST BIT(4)
+#define IT66121_AFE_DRV_PWD BIT(5)
+
+#define IT66121_INPUT_MODE_REG 0x70
+#define IT66121_INPUT_MODE_RGB888 (0 << 6)
+#define IT66121_INPUT_MODE_YUV422 BIT(6)
+#define IT66121_INPUT_MODE_YUV444 (2 << 6)
+#define IT66121_INPUT_MODE_CCIR656 BIT(4)
+#define IT66121_INPUT_MODE_SYNCEMB BIT(3)
+#define IT66121_INPUT_MODE_DDR BIT(2)
+
+#define IT66121_INPUT_CSC_REG 0x72
+#define IT66121_INPUT_CSC_ENDITHER BIT(7)
+#define IT66121_INPUT_CSC_ENUDFILTER BIT(6)
+#define IT66121_INPUT_CSC_DNFREE_GO BIT(5)
+#define IT66121_INPUT_CSC_RGB_TO_YUV 0x02
+#define IT66121_INPUT_CSC_YUV_TO_RGB 0x03
+#define IT66121_INPUT_CSC_NO_CONV 0x00
+
+#define IT66121_AFE_XP_REG 0x62
+#define IT66121_AFE_XP_GAINBIT BIT(7)
+#define IT66121_AFE_XP_PWDPLL BIT(6)
+#define IT66121_AFE_XP_ENI BIT(5)
+#define IT66121_AFE_XP_ENO BIT(4)
+#define IT66121_AFE_XP_RESETB BIT(3)
+#define IT66121_AFE_XP_PWDI BIT(2)
+#define IT6610_AFE_XP_BYPASS BIT(0)
+
+#define IT66121_AFE_IP_REG 0x64
+#define IT66121_AFE_IP_GAINBIT BIT(7)
+#define IT66121_AFE_IP_PWDPLL BIT(6)
+#define IT66121_AFE_IP_CKSEL_05 (0 << 4)
+#define IT66121_AFE_IP_CKSEL_1 BIT(4)
+#define IT66121_AFE_IP_CKSEL_2 (2 << 4)
+#define IT66121_AFE_IP_CKSEL_2OR4 (3 << 4)
+#define IT66121_AFE_IP_ER0 BIT(3)
+#define IT66121_AFE_IP_RESETB BIT(2)
+#define IT66121_AFE_IP_ENC BIT(1)
+#define IT66121_AFE_IP_EC1 BIT(0)
+
+#define IT66121_AFE_XP_EC1_REG 0x68
+#define IT66121_AFE_XP_EC1_LOWCLK BIT(4)
+
+#define IT66121_SW_RST_REG 0x04
+#define IT66121_SW_RST_REF BIT(5)
+#define IT66121_SW_RST_AREF BIT(4)
+#define IT66121_SW_RST_VID BIT(3)
+#define IT66121_SW_RST_AUD BIT(2)
+#define IT66121_SW_RST_HDCP BIT(0)
+
+#define IT66121_DDC_COMMAND_REG 0x15
+#define IT66121_DDC_COMMAND_BURST_READ 0x0
+#define IT66121_DDC_COMMAND_EDID_READ 0x3
+#define IT66121_DDC_COMMAND_FIFO_CLR 0x9
+#define IT66121_DDC_COMMAND_SCL_PULSE 0xA
+#define IT66121_DDC_COMMAND_ABORT 0xF
+
+#define IT66121_HDCP_REG 0x20
+#define IT66121_HDCP_CPDESIRED BIT(0)
+#define IT66121_HDCP_EN1P1FEAT BIT(1)
+
+#define IT66121_INT_STATUS1_REG 0x06
+#define IT66121_INT_STATUS1_AUD_OVF BIT(7)
+#define IT66121_INT_STATUS1_DDC_NOACK BIT(5)
+#define IT66121_INT_STATUS1_DDC_FIFOERR BIT(4)
+#define IT66121_INT_STATUS1_DDC_BUSHANG BIT(2)
+#define IT66121_INT_STATUS1_RX_SENS_STATUS BIT(1)
+#define IT66121_INT_STATUS1_HPD_STATUS BIT(0)
+
+#define IT66121_DDC_HEADER_REG 0x11
+#define IT66121_DDC_HEADER_HDCP 0x74
+#define IT66121_DDC_HEADER_EDID 0xA0
+
+#define IT66121_DDC_OFFSET_REG 0x12
+#define IT66121_DDC_BYTE_REG 0x13
+#define IT66121_DDC_SEGMENT_REG 0x14
+#define IT66121_DDC_RD_FIFO_REG 0x17
+
+#define IT66121_CLK_BANK_REG 0x0F
+#define IT66121_CLK_BANK_PWROFF_RCLK BIT(6)
+#define IT66121_CLK_BANK_PWROFF_ACLK BIT(5)
+#define IT66121_CLK_BANK_PWROFF_TXCLK BIT(4)
+#define IT66121_CLK_BANK_PWROFF_CRCLK BIT(3)
+#define IT66121_CLK_BANK_0 0
+#define IT66121_CLK_BANK_1 1
+
+#define IT66121_INT_REG 0x05
+#define IT66121_INT_ACTIVE_HIGH BIT(7)
+#define IT66121_INT_OPEN_DRAIN BIT(6)
+#define IT66121_INT_TX_CLK_OFF BIT(0)
+
+#define IT66121_INT_MASK1_REG 0x09
+#define IT66121_INT_MASK1_AUD_OVF BIT(7)
+#define IT66121_INT_MASK1_DDC_NOACK BIT(5)
+#define IT66121_INT_MASK1_DDC_FIFOERR BIT(4)
+#define IT66121_INT_MASK1_DDC_BUSHANG BIT(2)
+#define IT66121_INT_MASK1_RX_SENS BIT(1)
+#define IT66121_INT_MASK1_HPD BIT(0)
+
+#define IT66121_INT_CLR1_REG 0x0C
+#define IT66121_INT_CLR1_PKTACP BIT(7)
+#define IT66121_INT_CLR1_PKTNULL BIT(6)
+#define IT66121_INT_CLR1_PKTGEN BIT(5)
+#define IT66121_INT_CLR1_KSVLISTCHK BIT(4)
+#define IT66121_INT_CLR1_AUTHDONE BIT(3)
+#define IT66121_INT_CLR1_AUTHFAIL BIT(2)
+#define IT66121_INT_CLR1_RX_SENS BIT(1)
+#define IT66121_INT_CLR1_HPD BIT(0)
+
+#define IT66121_AVMUTE_REG 0xC1
+#define IT66121_AVMUTE_BIT BIT(0)
+#define IT66121_AVMUTE_BLUESCR BIT(1)
+
+#define IT66121_PKT_CTS_CTRL_REG 0xC5
+#define IT66121_PKT_CTS_CTRL_SEL BIT(1)
+
+#define IT66121_PKT_GEN_CTRL_REG 0xC6
+#define IT66121_PKT_GEN_CTRL_ON BIT(0)
+#define IT66121_PKT_GEN_CTRL_RPT BIT(1)
+
+#define IT66121_AVIINFO_DB1_REG 0x158
+#define IT66121_AVIINFO_DB2_REG 0x159
+#define IT66121_AVIINFO_DB3_REG 0x15A
+#define IT66121_AVIINFO_DB4_REG 0x15B
+#define IT66121_AVIINFO_DB5_REG 0x15C
+#define IT66121_AVIINFO_CSUM_REG 0x15D
+#define IT66121_AVIINFO_DB6_REG 0x15E
+#define IT66121_AVIINFO_DB7_REG 0x15F
+#define IT66121_AVIINFO_DB8_REG 0x160
+#define IT66121_AVIINFO_DB9_REG 0x161
+#define IT66121_AVIINFO_DB10_REG 0x162
+#define IT66121_AVIINFO_DB11_REG 0x163
+#define IT66121_AVIINFO_DB12_REG 0x164
+#define IT66121_AVIINFO_DB13_REG 0x165
+
+#define IT66121_AVI_INFO_PKT_REG 0xCD
+#define IT66121_AVI_INFO_PKT_ON BIT(0)
+#define IT66121_AVI_INFO_PKT_RPT BIT(1)
+
+#define IT66121_HDMI_MODE_REG 0xC0
+#define IT66121_HDMI_MODE_HDMI BIT(0)
+
+#define IT66121_SYS_STATUS_REG 0x0E
+#define IT66121_SYS_STATUS_ACTIVE_IRQ BIT(7)
+#define IT66121_SYS_STATUS_HPDETECT BIT(6)
+#define IT66121_SYS_STATUS_SENDECTECT BIT(5)
+#define IT66121_SYS_STATUS_VID_STABLE BIT(4)
+#define IT66121_SYS_STATUS_AUD_CTS_CLR BIT(1)
+#define IT66121_SYS_STATUS_CLEAR_IRQ BIT(0)
+
+#define IT66121_DDC_STATUS_REG 0x16
+#define IT66121_DDC_STATUS_TX_DONE BIT(7)
+#define IT66121_DDC_STATUS_ACTIVE BIT(6)
+#define IT66121_DDC_STATUS_NOACK BIT(5)
+#define IT66121_DDC_STATUS_WAIT_BUS BIT(4)
+#define IT66121_DDC_STATUS_ARBI_LOSE BIT(3)
+#define IT66121_DDC_STATUS_FIFO_FULL BIT(2)
+#define IT66121_DDC_STATUS_FIFO_EMPTY BIT(1)
+#define IT66121_DDC_STATUS_FIFO_VALID BIT(0)
+
+#define IT66121_EDID_SLEEP_US 20000
+#define IT66121_EDID_TIMEOUT_US 200000
+#define IT66121_EDID_FIFO_SIZE 32
+
+#define IT66121_CLK_CTRL0_REG 0x58
+#define IT66121_CLK_CTRL0_AUTO_OVER_SAMPLING BIT(4)
+#define IT66121_CLK_CTRL0_EXT_MCLK_MASK GENMASK(3, 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_128FS (0 << 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_256FS BIT(2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_512FS (2 << 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_1024FS (3 << 2)
+#define IT66121_CLK_CTRL0_AUTO_IPCLK BIT(0)
+#define IT66121_CLK_STATUS1_REG 0x5E
+#define IT66121_CLK_STATUS2_REG 0x5F
+
+#define IT66121_AUD_CTRL0_REG 0xE0
+#define IT66121_AUD_SWL (3 << 6)
+#define IT66121_AUD_16BIT (0 << 6)
+#define IT66121_AUD_18BIT BIT(6)
+#define IT66121_AUD_20BIT (2 << 6)
+#define IT66121_AUD_24BIT (3 << 6)
+#define IT66121_AUD_SPDIFTC BIT(5)
+#define IT66121_AUD_SPDIF BIT(4)
+#define IT66121_AUD_I2S (0 << 4)
+#define IT66121_AUD_EN_I2S3 BIT(3)
+#define IT66121_AUD_EN_I2S2 BIT(2)
+#define IT66121_AUD_EN_I2S1 BIT(1)
+#define IT66121_AUD_EN_I2S0 BIT(0)
+#define IT66121_AUD_CTRL0_AUD_SEL BIT(4)
+
+#define IT66121_AUD_CTRL1_REG 0xE1
+#define IT66121_AUD_FIFOMAP_REG 0xE2
+#define IT66121_AUD_CTRL3_REG 0xE3
+#define IT66121_AUD_SRCVALID_FLAT_REG 0xE4
+#define IT66121_AUD_FLAT_SRC0 BIT(4)
+#define IT66121_AUD_FLAT_SRC1 BIT(5)
+#define IT66121_AUD_FLAT_SRC2 BIT(6)
+#define IT66121_AUD_FLAT_SRC3 BIT(7)
+#define IT66121_AUD_HDAUDIO_REG 0xE5
+
+#define IT66121_AUD_PKT_CTS0_REG 0x130
+#define IT66121_AUD_PKT_CTS1_REG 0x131
+#define IT66121_AUD_PKT_CTS2_REG 0x132
+#define IT66121_AUD_PKT_N0_REG 0x133
+#define IT66121_AUD_PKT_N1_REG 0x134
+#define IT66121_AUD_PKT_N2_REG 0x135
+
+#define IT66121_AUD_CHST_MODE_REG 0x191
+#define IT66121_AUD_CHST_CAT_REG 0x192
+#define IT66121_AUD_CHST_SRCNUM_REG 0x193
+#define IT66121_AUD_CHST_CHTNUM_REG 0x194
+#define IT66121_AUD_CHST_CA_FS_REG 0x198
+#define IT66121_AUD_CHST_OFS_WL_REG 0x199
+
+#define IT66121_AUD_PKT_CTS_CNT0_REG 0x1A0
+#define IT66121_AUD_PKT_CTS_CNT1_REG 0x1A1
+#define IT66121_AUD_PKT_CTS_CNT2_REG 0x1A2
+
+#define IT66121_AUD_FS_22P05K 0x4
+#define IT66121_AUD_FS_44P1K 0x0
+#define IT66121_AUD_FS_88P2K 0x8
+#define IT66121_AUD_FS_176P4K 0xC
+#define IT66121_AUD_FS_24K 0x6
+#define IT66121_AUD_FS_48K 0x2
+#define IT66121_AUD_FS_96K 0xA
+#define IT66121_AUD_FS_192K 0xE
+#define IT66121_AUD_FS_768K 0x9
+#define IT66121_AUD_FS_32K 0x3
+#define IT66121_AUD_FS_OTHER 0x1
+
+#define IT66121_AUD_SWL_21BIT 0xD
+#define IT66121_AUD_SWL_24BIT 0xB
+#define IT66121_AUD_SWL_23BIT 0x9
+#define IT66121_AUD_SWL_22BIT 0x5
+#define IT66121_AUD_SWL_20BIT 0x3
+#define IT66121_AUD_SWL_17BIT 0xC
+#define IT66121_AUD_SWL_19BIT 0x8
+#define IT66121_AUD_SWL_18BIT 0x4
+#define IT66121_AUD_SWL_16BIT 0x2
+#define IT66121_AUD_SWL_NOT_INDICATED 0x0
+
+#define IT66121_AFE_CLK_HIGH 80000 /* Khz */
+
+#endif
--
2.34.1

2023-10-29 19:47:16

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 8/8] drm/loongson: Add support for the display subsystem in LS2K2000

Before apply this patch, drm/loongson is basically works on LS2K2000.
Because majority of hardware features of the DC are same with LS7A2000's
counterpart. Despite LS2K2000 is a SoC, it don't has a dedicated VRAM.
But the firmware will carve out part of system RAM as VRAM, and write the
base address and size of this reserved RAM to the PCI Bar 2 of the GPU.
So this kind of reserved RAM is nearly same with the dedicated video RAM.

In short, the display subsystem in LS2K2000 are nearly compatible with the
display subsystem in LS7A2000. But LS2K2000 has only one built-in HDMI
encoder, which is connected with the CRTC-0 (display pipe 0). Display pipe
1 exports a generic DVO interface. So there still need a trivial fix.

Before apply this patch:

$ dmesg | grep 0000:00:06.1

pci 0000:00:06.1: [0014:7a36] type 00 class 0x030000
pci 0000:00:06.1: reg 0x10: [mem 0x51250000-0x5125ffff 64bit]
pci 0000:00:06.1: reg 0x18: [mem 0x512b6000-0x512b60ff]
pci 0000:00:06.1: BAR 0: assigned [mem 0x51250000-0x5125ffff 64bit]
pci 0000:00:06.1: BAR 2: assigned [mem 0x512b7f00-0x512b7fff]
pci 0000:00:06.1: vgaarb: setting as boot VGA device
loongson 0000:00:06.1: Found LS7A2000 bridge chipset, revision: 16
loongson 0000:00:06.1: [drm] dc: 400MHz, gmc: 800MHz, gpu: 533MHz
loongson 0000:00:06.1: [drm] Dedicated vram start: 0x40000000, size: 256MiB
loongson 0000:00:06.1: [drm] Loongson VBIOS version: 2.1
loongson 0000:00:06.1: [drm] Loongson VBIOS: has 8 DCBs
loongson 0000:00:06.1: [drm] VRAM: 16384 pages ready
loongson 0000:00:06.1: [drm] GTT: 32768 pages ready
loongson 0000:00:06.1: [drm] lsdc-i2c0(sda pin mask=1, scl pin mask=2) created
loongson 0000:00:06.1: [drm] lsdc-i2c1(sda pin mask=4, scl pin mask=8) created
loongson 0000:00:06.1: [drm] DisplayPipe-0 has HDMI-0
loongson 0000:00:06.1: [drm] DisplayPipe-1 has HDMI-1
loongson 0000:00:06.1: [drm] Total 2 outputs
loongson 0000:00:06.1: [drm] registered irq: 42
[drm] Initialized loongson 1.0.0 20220701 for 0000:00:06.1 on minor 0
loongson 0000:00:06.1: [drm] *ERROR* Setting HDMI-1 PLL failed
loongson 0000:00:06.1: [drm] fb0: loongsondrmfb frame buffer device

After apply this patch, the error "*ERROR* Setting HDMI-1 PLL failed" got
fixed.

$ dmesg | grep 0000:00:06.1

pci 0000:00:06.1: [0014:7a36] type 00 class 0x030000
pci 0000:00:06.1: reg 0x10: [mem 0x51250000-0x5125ffff 64bit]
pci 0000:00:06.1: reg 0x18: [mem 0x512b6000-0x512b60ff]
pci 0000:00:06.1: BAR 0: assigned [mem 0x51250000-0x5125ffff 64bit]
pci 0000:00:06.1: BAR 2: assigned [mem 0x512b7f00-0x512b7fff]
pci 0000:00:06.1: vgaarb: setting as boot VGA device
loongson 0000:00:06.1: Found LS2K2000 SoC, revision: 16
loongson 0000:00:06.1: [drm] dc: 400MHz, gmc: 800MHz, gpu: 533MHz
loongson 0000:00:06.1: [drm] Dedicated vram start: 0x40000000, size: 256MiB
loongson 0000:00:06.1: [drm] Loongson VBIOS version: 2.1
loongson 0000:00:06.1: [drm] Loongson VBIOS: has 8 DCBs
loongson 0000:00:06.1: [drm] VRAM: 16384 pages ready
loongson 0000:00:06.1: [drm] GTT: 32768 pages ready
loongson 0000:00:06.1: [drm] lsdc-i2c0(sda pin mask=1, scl pin mask=2) created
loongson 0000:00:06.1: [drm] lsdc-i2c1(sda pin mask=4, scl pin mask=8) created
loongson 0000:00:06.1: [drm] DisplayPipe-0 has HDMI-0
loongson 0000:00:06.1: [drm] DisplayPipe-1 has DVO-1
loongson 0000:00:06.1: [drm] Total 2 outputs
loongson 0000:00:06.1: [drm] registered irq: 42
[drm] Initialized loongson 1.0.0 20220701 for 0000:00:06.1 on minor 0
loongson 0000:00:06.1: [drm] fb0: loongsondrmfb frame buffer device

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/Makefile | 1 +
drivers/gpu/drm/loongson/loongson_device.c | 46 ++++++++++
drivers/gpu/drm/loongson/lsdc_output.h | 5 ++
drivers/gpu/drm/loongson/lsdc_output_2k2000.c | 84 +++++++++++++++++++
4 files changed, 136 insertions(+)
create mode 100644 drivers/gpu/drm/loongson/lsdc_output_2k2000.c

diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
index 393709e686aa..7d3d82ddd5ff 100644
--- a/drivers/gpu/drm/loongson/Makefile
+++ b/drivers/gpu/drm/loongson/Makefile
@@ -10,6 +10,7 @@ loongson-y := \
lsdc_i2c.o \
lsdc_irq.o \
lsdc_output.o \
+ lsdc_output_2k2000.o \
lsdc_output_7a1000.o \
lsdc_output_7a2000.o \
lsdc_plane.o \
diff --git a/drivers/gpu/drm/loongson/loongson_device.c b/drivers/gpu/drm/loongson/loongson_device.c
index 64096ad5466e..33aae403f0b0 100644
--- a/drivers/gpu/drm/loongson/loongson_device.c
+++ b/drivers/gpu/drm/loongson/loongson_device.c
@@ -6,6 +6,7 @@
#include <linux/pci.h>

#include "lsdc_drv.h"
+#include "lsdc_probe.h"

extern struct loongson_vbios __loongson_vbios;

@@ -27,6 +28,15 @@ static const struct lsdc_kms_funcs ls7a2000_kms_funcs = {
.crtc_init = ls7a2000_crtc_init,
};

+static const struct lsdc_kms_funcs ls2k2000_kms_funcs = {
+ .create_i2c = lsdc_create_i2c_chan,
+ .irq_handler = ls7a2000_dc_irq_handler,
+ .output_init = ls2k2000_output_init,
+ .cursor_plane_init = ls7a2000_cursor_plane_init,
+ .primary_plane_init = lsdc_primary_plane_init,
+ .crtc_init = ls7a2000_crtc_init,
+};
+
static const struct loongson_gfx_desc ls7a1000_gfx = {
.dc = {
.num_of_crtc = 2,
@@ -93,14 +103,50 @@ static const struct loongson_gfx_desc ls7a2000_gfx = {
.model = "LS7A2000 bridge chipset",
};

+static const struct loongson_gfx_desc ls2k2000_gfx = {
+ .dc = {
+ .num_of_crtc = 2,
+ .max_pixel_clk = 350000,
+ .max_width = 4096,
+ .max_height = 4096,
+ .num_of_hw_cursor = 2,
+ .hw_cursor_w = 64,
+ .hw_cursor_h = 64,
+ .pitch_align = 64,
+ .has_vblank_counter = true,
+ .funcs = &ls2k2000_kms_funcs,
+ },
+ .conf_reg_base = LS7A2000_CONF_REG_BASE,
+ .gfxpll = {
+ .reg_offset = LS7A2000_PLL_GFX_REG,
+ .reg_size = 8,
+ },
+ .pixpll = {
+ [0] = {
+ .reg_offset = LS7A2000_PIXPLL0_REG,
+ .reg_size = 8,
+ },
+ [1] = {
+ .reg_offset = LS7A2000_PIXPLL1_REG,
+ .reg_size = 8,
+ },
+ },
+ .vbios = &__loongson_vbios,
+ .chip_id = CHIP_LS2K2000,
+ .model = "LS2K2000 SoC",
+};
+
static const struct lsdc_desc *__chip_id_desc_table[] = {
[CHIP_LS7A1000] = &ls7a1000_gfx.dc,
[CHIP_LS7A2000] = &ls7a2000_gfx.dc,
+ [CHIP_LS2K2000] = &ls2k2000_gfx.dc,
[CHIP_LS_LAST] = NULL,
};

const struct lsdc_desc *
lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip_id)
{
+ chip_id = loongson_chip_id_fixup(chip_id);
+
return __chip_id_desc_table[chip_id];
}
diff --git a/drivers/gpu/drm/loongson/lsdc_output.h b/drivers/gpu/drm/loongson/lsdc_output.h
index a37a72687bdf..463d59d680c2 100644
--- a/drivers/gpu/drm/loongson/lsdc_output.h
+++ b/drivers/gpu/drm/loongson/lsdc_output.h
@@ -61,6 +61,11 @@ int ls7a2000_output_init(struct drm_device *ddev,
struct i2c_adapter *ddc,
unsigned int index);

+int ls2k2000_output_init(struct drm_device *ddev,
+ struct lsdc_display_pipe *dispipe,
+ struct i2c_adapter *ddc,
+ unsigned int pipe);
+
int lsdc_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
struct i2c_adapter *ddc,
diff --git a/drivers/gpu/drm/loongson/lsdc_output_2k2000.c b/drivers/gpu/drm/loongson/lsdc_output_2k2000.c
new file mode 100644
index 000000000000..350af51da541
--- /dev/null
+++ b/drivers/gpu/drm/loongson/lsdc_output_2k2000.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#include "lsdc_drv.h"
+#include "lsdc_output.h"
+
+/*
+ * The DC in LS2K2000 is nearly same with the DC in LS7A2000, except that
+ * LS2K2000 has only one built-in HDMI encoder which is connected with the
+ * display pipe 0. Display pipe 1 is a DVO output interface.
+ * ________________________
+ * | | ______________
+ * | +----------| | |
+ * | CRTC-0 ---> | HDMI phy ---> HDMI Connector --> | HDMI Monitor |<--+
+ * | +----------| |______________| |
+ * | +-------+ | |
+ * | | i2c-x | <------------------------------------------+
+ * | +-------+ |
+ * | |
+ * | DC in LS2K2000 |
+ * | |
+ * | +-------+ |
+ * | | i2c-y | <------------------------------------+
+ * | +-------+ | |
+ * | | ____|____
+ * | +-------| | |
+ * | CRTC-1 ------> | DVO --> Encoder1 --> Connector1 --> | Display |
+ * | +-------| |_________|
+ * |________________________|
+ */
+
+static void ls2k2000_pipe1_dvo_encoder_reset(struct drm_encoder *encoder)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u32 val;
+
+ val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
+ lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
+}
+
+const struct drm_encoder_funcs ls2k2000_pipe1_dvo_encoder_funcs = {
+ .reset = ls2k2000_pipe1_dvo_encoder_reset,
+ .destroy = drm_encoder_cleanup,
+};
+
+static const struct lsdc_output_desc ls2k2000_output_desc[2] = {
+ {
+ .pipe = 0,
+ .encoder_type = DRM_MODE_ENCODER_TMDS,
+ .connector_type = DRM_MODE_CONNECTOR_HDMIA,
+ .encoder_funcs = &lsdc_pipe0_hdmi_encoder_funcs,
+ .encoder_helper_funcs = &lsdc_pipe0_hdmi_encoder_helper_funcs,
+ .connector_funcs = &lsdc_pipe0_hdmi_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "HDMI-0",
+ },
+ {
+ .pipe = 1,
+ .encoder_type = DRM_MODE_ENCODER_DPI,
+ .connector_type = DRM_MODE_CONNECTOR_DPI,
+ .encoder_funcs = &ls2k2000_pipe1_dvo_encoder_funcs,
+ .encoder_helper_funcs = &lsdc_encoder_helper_funcs,
+ .connector_funcs = &lsdc_connector_funcs,
+ .connector_helper_funcs = &lsdc_connector_helper_funcs,
+ .name = "DVO-1",
+ },
+};
+
+int ls2k2000_output_init(struct drm_device *ddev,
+ struct lsdc_display_pipe *dispipe,
+ struct i2c_adapter *ddc,
+ unsigned int pipe)
+{
+ struct lsdc_output *output = &dispipe->output;
+
+ output->descp = &ls2k2000_output_desc[pipe];
+
+ lsdc_output_init(ddev, dispipe, ddc, pipe);
+
+ return 0;
+}
--
2.34.1

2023-10-29 19:47:20

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 7/8] drm/loongson: Support to infer DC reversion from CPU's PRID value

Due to the fact that the same display IP core has been integrated into
different platform, there is a need to differentiate them on the runtime.
The DC in LS7A1000/LS2K1000 has the PCI vendor & device ID of 0x0014:0x7A06
The DC in LS7A2000/LS2K2000 has the PCI vendor & device ID of 0x0014:0x7A36

Because the output ports and host platform of the DC IP varies, without a
revision information we can't achieve fine-grained controls. The canonical
approach to do such a thing is to read reversion register from the PCIe
device. But LS2K1000 SoC was taped out at 2017, it is rather old. Our BIOS
engineer don't assign a different revision ID to it, probably because of
ignorance.

LS2K2000 SoC was newly taped on 2023, we strictly force the BIOS engineer
assign a different revision ID(0x10) to it. But the problem is that it is
too casual, there is no formal convention or public documented rule
established. For Loongson LS2K series SoC, the display controller IP is
taped togather with the CPU core. For Loongson LS7A series bridge chips,
the display controller IP is taped togather with the bridge chips itself.
Consider the fact the all Loongson CPU has a unique PRID, this patch choose
to infer DC reversion from CPU's PRID value.

- LS3A4000/LS3A5000 has 0xC0 as its processor ID.
- LS2K2000 has 0xB0 as its processor ID.
- LS2K2000LA has 0xA0 as its processor ID.

The provided approach has no dependency on DT or ACPI, thus is preferfed.
Besides, this approach can be used to acquire more addtional HW features.
So the provided method has the potential to bring more benifits.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/lsdc_drv.h | 2 ++
drivers/gpu/drm/loongson/lsdc_probe.c | 35 +++++++++++++++++++++++++++
drivers/gpu/drm/loongson/lsdc_probe.h | 2 ++
3 files changed, 39 insertions(+)

diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
index 46ba9b88a30d..e1f4a2db2a0a 100644
--- a/drivers/gpu/drm/loongson/lsdc_drv.h
+++ b/drivers/gpu/drm/loongson/lsdc_drv.h
@@ -42,6 +42,8 @@
enum loongson_chip_id {
CHIP_LS7A1000 = 0,
CHIP_LS7A2000 = 1,
+ CHIP_LS2K1000 = 2,
+ CHIP_LS2K2000 = 3,
CHIP_LS_LAST,
};

diff --git a/drivers/gpu/drm/loongson/lsdc_probe.c b/drivers/gpu/drm/loongson/lsdc_probe.c
index 48ba69bb8a98..f49b642d8f65 100644
--- a/drivers/gpu/drm/loongson/lsdc_probe.c
+++ b/drivers/gpu/drm/loongson/lsdc_probe.c
@@ -54,3 +54,38 @@ unsigned int loongson_cpu_get_prid(u8 *imp, u8 *rev)

return prid;
}
+
+enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id)
+{
+ u8 impl;
+
+ if (loongson_cpu_get_prid(&impl, NULL)) {
+ /*
+ * LS2K2000 only has the LoongArch edition.
+ */
+ if (chip_id == CHIP_LS7A2000) {
+ if (impl == LOONGARCH_CPU_IMP_LS2K2000)
+ return CHIP_LS2K2000;
+ }
+
+ /*
+ * LS2K1000 has the LoongArch edition(with two LA264 CPU core)
+ * and the Mips edition(with two mips64r2 CPU core), Only the
+ * instruction set of the CPU are changed, the peripheral
+ * devices are basically same.
+ */
+ if (chip_id == CHIP_LS7A1000) {
+#if defined(__loongarch__)
+ if (impl == LOONGARCH_CPU_IMP_LS2K1000)
+ return CHIP_LS2K1000;
+#endif
+
+#if defined(__mips__)
+ if (impl == LOONGSON_CPU_MIPS_IMP_LS2K)
+ return CHIP_LS2K1000;
+#endif
+ }
+ }
+
+ return chip_id;
+}
diff --git a/drivers/gpu/drm/loongson/lsdc_probe.h b/drivers/gpu/drm/loongson/lsdc_probe.h
index 8bb6de2e3c64..8c630c5c90ce 100644
--- a/drivers/gpu/drm/loongson/lsdc_probe.h
+++ b/drivers/gpu/drm/loongson/lsdc_probe.h
@@ -9,4 +9,6 @@
/* Helpers for chip detection */
unsigned int loongson_cpu_get_prid(u8 *impl, u8 *rev);

+enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id);
+
#endif
--
2.34.1

2023-10-29 19:47:32

by Sui Jingfeng

[permalink] [raw]
Subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Move the sharable subroutine into lsdc_output.c and refactor.

Signed-off-by: Sui Jingfeng <[email protected]>
---
drivers/gpu/drm/loongson/Makefile | 1 +
drivers/gpu/drm/loongson/lsdc_drv.h | 17 -
drivers/gpu/drm/loongson/lsdc_output.c | 640 +++++++++++++++++++++++++
drivers/gpu/drm/loongson/lsdc_output.h | 52 +-
4 files changed, 691 insertions(+), 19 deletions(-)
create mode 100644 drivers/gpu/drm/loongson/lsdc_output.c

diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
index 1459d19b2c90..393709e686aa 100644
--- a/drivers/gpu/drm/loongson/Makefile
+++ b/drivers/gpu/drm/loongson/Makefile
@@ -9,6 +9,7 @@ loongson-y := \
lsdc_gfxpll.o \
lsdc_i2c.o \
lsdc_irq.o \
+ lsdc_output.o \
lsdc_output_7a1000.o \
lsdc_output_7a2000.o \
lsdc_plane.o \
diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
index 335953c988d1..46ba9b88a30d 100644
--- a/drivers/gpu/drm/loongson/lsdc_drv.h
+++ b/drivers/gpu/drm/loongson/lsdc_drv.h
@@ -175,23 +175,6 @@ struct lsdc_cursor {
struct lsdc_device *ldev;
};

-struct lsdc_output {
- struct drm_encoder encoder;
- struct drm_connector connector;
-};
-
-static inline struct lsdc_output *
-connector_to_lsdc_output(struct drm_connector *connector)
-{
- return container_of(connector, struct lsdc_output, connector);
-}
-
-static inline struct lsdc_output *
-encoder_to_lsdc_output(struct drm_encoder *encoder)
-{
- return container_of(encoder, struct lsdc_output, encoder);
-}
-
struct lsdc_display_pipe {
struct lsdc_crtc crtc;
struct lsdc_primary primary;
diff --git a/drivers/gpu/drm/loongson/lsdc_output.c b/drivers/gpu/drm/loongson/lsdc_output.c
new file mode 100644
index 000000000000..8262c3f91ebe
--- /dev/null
+++ b/drivers/gpu/drm/loongson/lsdc_output.c
@@ -0,0 +1,640 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Loongson Technology Corporation Limited
+ */
+
+#include <linux/delay.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_debugfs.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_probe_helper.h>
+
+#include "lsdc_drv.h"
+#include "lsdc_output.h"
+
+/* This file contain shared subroutines for the output part */
+
+/* Usable for generic DVO, VGA and buitl-in HDMI connector */
+
+static int lsdc_connector_get_modes(struct drm_connector *connector)
+{
+ unsigned int num = 0;
+ struct edid *edid;
+
+ if (connector->ddc) {
+ edid = drm_get_edid(connector, connector->ddc);
+ if (edid) {
+ drm_connector_update_edid_property(connector, edid);
+ num = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return num;
+ }
+
+ num = drm_add_modes_noedid(connector, 1920, 1200);
+
+ drm_set_preferred_mode(connector, 1024, 768);
+
+ return num;
+}
+
+static struct drm_encoder *
+lsdc_connector_get_best_encoder(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_output *output = connector_to_lsdc_output(connector);
+
+ return &output->encoder;
+}
+
+const struct drm_connector_helper_funcs lsdc_connector_helper_funcs = {
+ .atomic_best_encoder = lsdc_connector_get_best_encoder,
+ .get_modes = lsdc_connector_get_modes,
+};
+
+static enum drm_connector_status
+lsdc_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct i2c_adapter *ddc = connector->ddc;
+
+ if (ddc) {
+ if (drm_probe_ddc(ddc))
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+ }
+
+ return connector_status_unknown;
+}
+
+const struct drm_connector_funcs lsdc_connector_funcs = {
+ .detect = lsdc_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state
+};
+
+/* debugfs */
+
+#define LSDC_HDMI_REG(i, reg) { \
+ .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \
+ .offset = LSDC_HDMI##i##_##reg##_REG, \
+}
+
+static int lsdc_hdmi_regs_show(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *)m->private;
+ struct drm_device *ddev = node->minor->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ const struct lsdc_reg32 *preg;
+
+ preg = (const struct lsdc_reg32 *)node->info_ent->data;
+
+ while (preg->name) {
+ u32 offset = preg->offset;
+
+ seq_printf(m, "%s (0x%04x): 0x%08x\n",
+ preg->name, offset, lsdc_rreg32(ldev, offset));
+ ++preg;
+ }
+
+ return 0;
+}
+
+/* LSDC built-in HDMI encoder, connected with display pipe 0 */
+
+static const struct lsdc_reg32 lsdc_hdmi_regs_pipe0[] = {
+ LSDC_HDMI_REG(0, ZONE),
+ LSDC_HDMI_REG(0, INTF_CTRL),
+ LSDC_HDMI_REG(0, PHY_CTRL),
+ LSDC_HDMI_REG(0, PHY_PLL),
+ LSDC_HDMI_REG(0, AVI_INFO_CRTL),
+ LSDC_HDMI_REG(0, PHY_CAL),
+ LSDC_HDMI_REG(0, AUDIO_PLL_LO),
+ LSDC_HDMI_REG(0, AUDIO_PLL_HI),
+ {NULL, 0}, /* MUST be {NULL, 0} terminated */
+};
+
+static const struct drm_info_list lsdc_pipe0_hdmi_debugfs_files[] = {
+ { "regs", lsdc_hdmi_regs_show, 0, (void *)lsdc_hdmi_regs_pipe0 },
+};
+
+static enum drm_connector_status
+lsdc_pipe0_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct lsdc_device *ldev = to_lsdc(connector->dev);
+ u32 val;
+
+ val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
+
+ if (val & HDMI0_HPD_FLAG)
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+}
+
+static void lsdc_pipe0_hdmi_late_register(struct drm_connector *connector,
+ struct dentry *root)
+{
+ struct drm_device *ddev = connector->dev;
+ struct drm_minor *minor = ddev->primary;
+
+ drm_debugfs_create_files(lsdc_pipe0_hdmi_debugfs_files,
+ ARRAY_SIZE(lsdc_pipe0_hdmi_debugfs_files),
+ root, minor);
+}
+
+const struct drm_connector_funcs lsdc_pipe0_hdmi_connector_funcs = {
+ .detect = lsdc_pipe0_hdmi_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .debugfs_init = lsdc_pipe0_hdmi_late_register,
+};
+
+/* LSDC built-in HDMI connector, connected with display pipe 1 */
+
+static enum drm_connector_status
+lsdc_pipe1_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct lsdc_device *ldev = to_lsdc(connector->dev);
+ u32 val;
+
+ val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
+
+ if (val & HDMI1_HPD_FLAG)
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+}
+
+static const struct lsdc_reg32 lsdc_pipe1_hdmi_encoder_regs[] = {
+ LSDC_HDMI_REG(1, ZONE),
+ LSDC_HDMI_REG(1, INTF_CTRL),
+ LSDC_HDMI_REG(1, PHY_CTRL),
+ LSDC_HDMI_REG(1, PHY_PLL),
+ LSDC_HDMI_REG(1, AVI_INFO_CRTL),
+ LSDC_HDMI_REG(1, PHY_CAL),
+ LSDC_HDMI_REG(1, AUDIO_PLL_LO),
+ LSDC_HDMI_REG(1, AUDIO_PLL_HI),
+ {NULL, 0}, /* MUST be {NULL, 0} terminated */
+};
+
+static const struct drm_info_list lsdc_pipe1_hdmi_debugfs_files[] = {
+ { "regs", lsdc_hdmi_regs_show, 0, (void *)lsdc_pipe1_hdmi_encoder_regs },
+};
+
+static void lsdc_pipe1_hdmi_late_register(struct drm_connector *connector,
+ struct dentry *root)
+{
+ struct drm_device *ddev = connector->dev;
+ struct drm_minor *minor = ddev->primary;
+
+ drm_debugfs_create_files(lsdc_pipe1_hdmi_debugfs_files,
+ ARRAY_SIZE(lsdc_pipe1_hdmi_debugfs_files),
+ root, minor);
+}
+
+const struct drm_connector_funcs lsdc_pipe1_hdmi_connector_funcs = {
+ .detect = lsdc_pipe1_hdmi_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .debugfs_init = lsdc_pipe1_hdmi_late_register,
+};
+
+/*
+ * Fout = M * in_khz
+ *
+ * M = (4 * LF) / (IDF * ODF)
+ *
+ * IDF: Input Division Factor
+ * ODF: Output Division Factor
+ * LF: Loop Factor
+ * M: Required Mult
+ *
+ * +--------------------------------------------------------+
+ * | in_khz | M | IDF | LF | ODF | Fout(Mhz) |
+ * |-------------------+----+-----+----+-----+--------------|
+ * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 |
+ * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 |
+ * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 |
+ * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 |
+ * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 |
+ * +--------------------------------------------------------+
+ */
+static void lsdc_hdmi_phy_pll_config(struct drm_device *ddev,
+ int in_khz,
+ unsigned int pipe)
+{
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ int count = 0;
+ u32 val;
+
+ /* Firstly, disable phy pll */
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, 0x0);
+
+ /*
+ * Most of time, loongson HDMI require M = 10
+ * for example, 10 = (4 * 40) / (8 * 2)
+ * here, write "1" to the ODF will get "2"
+ */
+
+ if (in_khz >= 170000)
+ val = (16 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (0 << HDMI_PLL_ODF_SHIFT);
+ else if (in_khz >= 85000)
+ val = (8 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (1 << HDMI_PLL_ODF_SHIFT);
+ else if (in_khz >= 42500)
+ val = (4 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (2 << HDMI_PLL_ODF_SHIFT);
+ else if (in_khz >= 21250)
+ val = (2 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (3 << HDMI_PLL_ODF_SHIFT);
+ else
+ val = (1 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (4 << HDMI_PLL_ODF_SHIFT);
+
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, val);
+
+ val |= HDMI_PLL_ENABLE;
+
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, val);
+
+ udelay(2);
+
+ drm_dbg(ddev, "Input frequency of HDMI-%u: %d kHz\n", pipe, in_khz);
+
+ /* Wait hdmi phy pll lock */
+ do {
+ val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe);
+
+ if (val & HDMI_PLL_LOCKED) {
+ drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n",
+ pipe, count);
+ break;
+ }
+ ++count;
+ } while (count < 1000);
+
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, pipe, 0x0f000ff0);
+
+ if (count >= 1000)
+ drm_err(ddev, "Setting HDMI-%u PLL failed\n", pipe);
+}
+
+static int lsdc_hdmi_phy_set_avi_infoframe(struct drm_encoder *encoder,
+ struct drm_connector *connector,
+ struct drm_display_mode *mode,
+ unsigned int index)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct hdmi_avi_infoframe infoframe;
+ u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
+ unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE];
+ unsigned int content0, content1, content2, content3;
+ int err;
+
+ err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe,
+ connector,
+ mode);
+ if (err < 0) {
+ drm_err(ddev, "failed to setup AVI infoframe: %d\n", err);
+ return err;
+ }
+
+ /* Fixed infoframe configuration not linked to the mode */
+ infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
+ infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
+
+ err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
+ if (err < 0) {
+ drm_err(ddev, "failed to pack AVI infoframe: %d\n", err);
+ return err;
+ }
+
+ content0 = *(unsigned int *)ptr;
+ content1 = *(ptr + 4);
+ content2 = *(unsigned int *)(ptr + 5);
+ content3 = *(unsigned int *)(ptr + 9);
+
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0);
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1);
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2);
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3);
+
+ lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index,
+ AVI_PKT_ENABLE | AVI_PKT_UPDATE);
+
+ drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index);
+
+ return 0;
+}
+
+/* Built-in HDMI encoder funcs on display pipe 0 */
+
+static void lsdc_pipe0_hdmi_encoder_reset(struct drm_encoder *encoder)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u32 val;
+
+ val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
+ lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
+
+ /* Using built-in GPIO emulated I2C instead of the hardware I2C */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HW_I2C_EN);
+
+ /* Help the HDMI phy get out of reset state */
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
+
+ drm_dbg(ddev, "%s reset\n", encoder->name);
+
+ mdelay(20);
+}
+
+const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs = {
+ .reset = lsdc_pipe0_hdmi_encoder_reset,
+ .destroy = drm_encoder_cleanup,
+};
+
+/* Built-in HDMI encoder funcs on display pipe 1 */
+
+static void lsdc_pipe1_hdmi_encoder_reset(struct drm_encoder *encoder)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u32 val;
+
+ val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
+ lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
+
+ /* Using built-in GPIO emulated I2C instead of the hardware I2C */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HW_I2C_EN);
+
+ /* Help the HDMI phy get out of reset state */
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
+
+ drm_dbg(ddev, "%s reset\n", encoder->name);
+
+ mdelay(20);
+}
+
+const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs = {
+ .reset = lsdc_pipe1_hdmi_encoder_reset,
+ .destroy = drm_encoder_cleanup,
+};
+
+/* Built-in DVO encoder helper funcs */
+
+static void lsdc_dvo_atomic_disable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+}
+
+static void lsdc_dvo_atomic_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+}
+
+static void lsdc_dvo_atomic_modeset(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+}
+
+const struct drm_encoder_helper_funcs lsdc_encoder_helper_funcs = {
+ .atomic_disable = lsdc_dvo_atomic_disable,
+ .atomic_enable = lsdc_dvo_atomic_enable,
+ .atomic_mode_set = lsdc_dvo_atomic_modeset,
+};
+
+/* Built-in HDMI encoder helper funcs on display pipe 0 */
+
+static void lsdc_pipe0_hdmi_atomic_disable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_device *ldev = to_lsdc(encoder->dev);
+
+ /* Disable the HDMI PHY */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_EN);
+
+ /* Disable the HDMI interface */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HDMI_INTERFACE_EN);
+}
+
+static void lsdc_pipe0_hdmi_atomic_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_device *ldev = to_lsdc(encoder->dev);
+ u32 val;
+
+ /* datasheet say it should larger than 48 */
+ val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
+ lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, val);
+
+ val = HDMI_PHY_TERM_STATUS |
+ HDMI_PHY_TERM_DET_EN |
+ HDMI_PHY_TERM_H_EN |
+ HDMI_PHY_TERM_L_EN |
+ HDMI_PHY_RESET_N |
+ HDMI_PHY_EN;
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val);
+
+ udelay(2);
+
+ val = HDMI_CTL_PERIOD_MODE |
+ HDMI_AUDIO_EN |
+ HDMI_PACKET_EN |
+ HDMI_INTERFACE_EN |
+ (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
+ lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val);
+}
+
+static void lsdc_pipe0_hdmi_atomic_modeset(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct lsdc_output *output = encoder_to_lsdc_output(encoder);
+ struct drm_device *ddev = encoder->dev;
+ struct drm_display_mode *mode = &crtc_state->mode;
+
+ lsdc_hdmi_phy_pll_config(ddev, mode->clock, 0);
+
+ lsdc_hdmi_phy_set_avi_infoframe(encoder, &output->connector, mode, 0);
+
+ drm_dbg(ddev, "%s modeset finished\n", encoder->name);
+}
+
+const struct drm_encoder_helper_funcs lsdc_pipe0_hdmi_encoder_helper_funcs = {
+ .atomic_disable = lsdc_pipe0_hdmi_atomic_disable,
+ .atomic_enable = lsdc_pipe0_hdmi_atomic_enable,
+ .atomic_mode_set = lsdc_pipe0_hdmi_atomic_modeset,
+};
+
+/* Built-in HDMI encoder helper funcs on display pipe 1 */
+
+static void lsdc_pipe1_hdmi_atomic_disable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_device *ldev = to_lsdc(encoder->dev);
+
+ /* Disable the HDMI PHY */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_EN);
+
+ /* Disable the HDMI interface */
+ lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HDMI_INTERFACE_EN);
+}
+
+static void lsdc_pipe1_hdmi_atomic_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_device *ldev = to_lsdc(encoder->dev);
+ u32 val;
+
+ /* datasheet say it should larger than 48 */
+ val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
+ lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, val);
+
+ val = HDMI_PHY_TERM_STATUS |
+ HDMI_PHY_TERM_DET_EN |
+ HDMI_PHY_TERM_H_EN |
+ HDMI_PHY_TERM_L_EN |
+ HDMI_PHY_RESET_N |
+ HDMI_PHY_EN;
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val);
+
+ udelay(2);
+
+ val = HDMI_CTL_PERIOD_MODE |
+ HDMI_AUDIO_EN |
+ HDMI_PACKET_EN |
+ HDMI_INTERFACE_EN |
+ (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
+ lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val);
+}
+
+static void lsdc_pipe1_hdmi_atomic_modeset(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct lsdc_output *output = encoder_to_lsdc_output(encoder);
+ struct drm_device *ddev = encoder->dev;
+ struct drm_display_mode *mode = &crtc_state->mode;
+
+ lsdc_hdmi_phy_pll_config(ddev, mode->clock, 1);
+
+ lsdc_hdmi_phy_set_avi_infoframe(encoder, &output->connector, mode, 1);
+
+ drm_dbg(ddev, "%s modeset finished\n", encoder->name);
+}
+
+const struct drm_encoder_helper_funcs lsdc_pipe1_hdmi_encoder_helper_funcs = {
+ .atomic_disable = lsdc_pipe1_hdmi_atomic_disable,
+ .atomic_enable = lsdc_pipe1_hdmi_atomic_enable,
+ .atomic_mode_set = lsdc_pipe1_hdmi_atomic_modeset,
+};
+
+int lsdc_encoder_init(struct drm_device *ddev,
+ struct lsdc_output *output,
+ unsigned int pipe)
+{
+ const struct lsdc_output_desc *descp = output->descp;
+ struct drm_encoder *encoder = &output->encoder;
+ int ret;
+
+ ret = drm_encoder_init(ddev,
+ encoder,
+ descp->encoder_funcs,
+ descp->encoder_type,
+ descp->name);
+ if (ret)
+ return ret;
+
+ encoder->possible_crtcs = BIT(pipe);
+
+ drm_encoder_helper_add(encoder, descp->encoder_helper_funcs);
+
+ return 0;
+}
+
+int lsdc_connector_init(struct drm_device *ddev,
+ struct lsdc_output *output,
+ struct i2c_adapter *ddc,
+ unsigned int pipe)
+{
+ const struct lsdc_output_desc *descp = output->descp;
+ struct drm_connector *connector = &output->connector;
+ int ret;
+
+ ret = drm_connector_init_with_ddc(ddev,
+ connector,
+ descp->connector_funcs,
+ descp->connector_type,
+ ddc);
+ if (ret)
+ return ret;
+
+ drm_connector_helper_add(connector, descp->connector_helper_funcs);
+
+ drm_connector_attach_encoder(connector, &output->encoder);
+
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ drm_info(ddev, "DisplayPipe-%u has %s\n", pipe, descp->name);
+
+ return 0;
+}
+
+/*
+ * A common, sharable subroutine for the initialization of output part.
+ * If there is external non-transparent display bridge chip on the display
+ * pipe, we will attach it. Otherwise, the output is simple, we will just
+ * initial a connector for it.
+ */
+int lsdc_output_init(struct drm_device *ddev,
+ struct lsdc_display_pipe *dispipe,
+ struct i2c_adapter *ddc,
+ unsigned int pipe)
+{
+ struct lsdc_output *output = &dispipe->output;
+ int ret;
+
+ ret = lsdc_encoder_init(ddev, output, pipe);
+ if (ret)
+ return ret;
+
+ if (output->bridge) {
+ ret = drm_bridge_attach(&output->encoder, output->bridge,
+ NULL, 0);
+ if (ret) {
+ drm_err(ddev, "Attach display bridge failed\n");
+ ret = lsdc_connector_init(ddev, output, ddc, pipe);
+ }
+ } else {
+ ret = lsdc_connector_init(ddev, output, ddc, pipe);
+ }
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/loongson/lsdc_output.h b/drivers/gpu/drm/loongson/lsdc_output.h
index 097789051a1d..a37a72687bdf 100644
--- a/drivers/gpu/drm/loongson/lsdc_output.h
+++ b/drivers/gpu/drm/loongson/lsdc_output.h
@@ -6,16 +6,64 @@
#ifndef __LSDC_OUTPUT_H__
#define __LSDC_OUTPUT_H__

-#include "lsdc_drv.h"
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+struct lsdc_output_desc {
+ u32 pipe;
+ u32 encoder_type;
+ u32 connector_type;
+ const struct drm_encoder_funcs *encoder_funcs;
+ const struct drm_encoder_helper_funcs *encoder_helper_funcs;
+ const struct drm_connector_funcs *connector_funcs;
+ const struct drm_connector_helper_funcs *connector_helper_funcs;
+ const char name[32];
+};
+
+struct lsdc_output {
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct drm_bridge *bridge;
+ const struct lsdc_output_desc *descp;
+};
+
+static inline struct lsdc_output *
+connector_to_lsdc_output(struct drm_connector *connector)
+{
+ return container_of(connector, struct lsdc_output, connector);
+}
+
+static inline struct lsdc_output *
+encoder_to_lsdc_output(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct lsdc_output, encoder);
+}
+
+extern const struct drm_connector_funcs lsdc_connector_funcs;
+extern const struct drm_connector_funcs lsdc_pipe0_hdmi_connector_funcs;
+extern const struct drm_connector_funcs lsdc_pipe1_hdmi_connector_funcs;
+extern const struct drm_connector_helper_funcs lsdc_connector_helper_funcs;
+
+extern const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs;
+extern const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs;
+extern const struct drm_encoder_helper_funcs lsdc_encoder_helper_funcs;
+extern const struct drm_encoder_helper_funcs lsdc_pipe0_hdmi_encoder_helper_funcs;
+extern const struct drm_encoder_helper_funcs lsdc_pipe1_hdmi_encoder_helper_funcs;

int ls7a1000_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
struct i2c_adapter *ddc,
unsigned int index);

-int ls7a2000_output_init(struct drm_device *ldev,
+int ls7a2000_output_init(struct drm_device *ddev,
struct lsdc_display_pipe *dispipe,
struct i2c_adapter *ddc,
unsigned int index);

+int lsdc_output_init(struct drm_device *ddev,
+ struct lsdc_display_pipe *dispipe,
+ struct i2c_adapter *ddc,
+ unsigned int pipe);
+
#endif
--
2.34.1

2023-10-29 22:53:31

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
> chip to support HDMI output. Thus add a drm bridge based driver for it.
> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.

Please use the original bridge driver instead of adding a new one. If
it needs to be changed in any way, please help everyone else by
improving it instead of introducing new driver.

>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/Kconfig | 1 +
> drivers/gpu/drm/loongson/Makefile | 2 +
> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
> 5 files changed, 1039 insertions(+)
> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h


--
With best wishes
Dmitry

2023-10-29 23:06:54

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 1/8] drm/loongson: Introduce a minimal support for Loongson VBIOS

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> Because some boards are equipped with non-transparent display bridges,
> which need the VBIOS to provided parameters.
>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/Makefile | 3 +-
> drivers/gpu/drm/loongson/loongson_device.c | 4 +
> drivers/gpu/drm/loongson/loongson_vbios.c | 420 +++++++++++++++++++++
> drivers/gpu/drm/loongson/loongson_vbios.h | 59 +++
> drivers/gpu/drm/loongson/lsdc_drv.c | 4 +
> drivers/gpu/drm/loongson/lsdc_drv.h | 8 +
> 6 files changed, 497 insertions(+), 1 deletion(-)
> create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.c
> create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.h
>
> diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
> index 91e72bd900c1..bef00b2c5569 100644
> --- a/drivers/gpu/drm/loongson/Makefile
> +++ b/drivers/gpu/drm/loongson/Makefile
> @@ -17,6 +17,7 @@ loongson-y := \
> lsdc_ttm.o
>
> loongson-y += loongson_device.o \
> - loongson_module.o
> + loongson_module.o \
> + loongson_vbios.o
>
> obj-$(CONFIG_DRM_LOONGSON) += loongson.o
> diff --git a/drivers/gpu/drm/loongson/loongson_device.c b/drivers/gpu/drm/loongson/loongson_device.c
> index 9986c8a2a255..64096ad5466e 100644
> --- a/drivers/gpu/drm/loongson/loongson_device.c
> +++ b/drivers/gpu/drm/loongson/loongson_device.c
> @@ -7,6 +7,8 @@
>
> #include "lsdc_drv.h"
>
> +extern struct loongson_vbios __loongson_vbios;

Usually names with two underscores in front of them are reserved for
the compiler internals or low level stuff.

> +
> static const struct lsdc_kms_funcs ls7a1000_kms_funcs = {
> .create_i2c = lsdc_create_i2c_chan,
> .irq_handler = ls7a1000_dc_irq_handler,
> @@ -53,6 +55,7 @@ static const struct loongson_gfx_desc ls7a1000_gfx = {
> .reg_size = 8,
> },
> },
> + .vbios = &__loongson_vbios,
> .chip_id = CHIP_LS7A1000,
> .model = "LS7A1000 bridge chipset",
> };
> @@ -85,6 +88,7 @@ static const struct loongson_gfx_desc ls7a2000_gfx = {
> .reg_size = 8,
> },
> },
> + .vbios = &__loongson_vbios,
> .chip_id = CHIP_LS7A2000,
> .model = "LS7A2000 bridge chipset",
> };
> diff --git a/drivers/gpu/drm/loongson/loongson_vbios.c b/drivers/gpu/drm/loongson/loongson_vbios.c
> new file mode 100644
> index 000000000000..dc304018779e
> --- /dev/null
> +++ b/drivers/gpu/drm/loongson/loongson_vbios.c
> @@ -0,0 +1,420 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2023 Loongson Technology Corporation Limited
> + */
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_managed.h>
> +
> +#include "loongson_vbios.h"
> +#include "lsdc_drv.h"
> +
> +#define LOONGSON_VBIOS_HEADER_STR "Loongson-VBIOS"
> +/* Legacy VBIOS is stored at offset 0 */
> +#define LOONGSON_VBIOS_LEGACY_OFFSET 0
> +/* The size of legacy VBIOS is 1 KiB */
> +#define LOONGSON_VBIOS_LEGACY_SIZE 0x000400
> +
> +/* Data Control Block of Newer version of the VBIOS started at here */
> +#define LOONGSON_VBIOS_DCB_OFFSET 0x006000
> +/* The last 1 MiB of the VRAM contains the raw VBIOS data */
> +#define LOONGSON_VBIOS_BLOCK_OFFSET 0x100000
> +/* Only 256KB of the 1 MiB are used for now */
> +#define LOONGSON_VBIOS_BLOCK_SIZE 0x040000
> +
> +struct loongson_vbios __loongson_vbios;
> +
> +/*
> + * vbios data control block is a kind of metadata, which is used to index
> + * real hardware device data block.
> + */
> +struct loongson_vbios_dcb {
> + u16 type; /* what is it */
> + u8 version; /* version of it, useless */
> + u8 id; /* index (usually same with the display pipe) of the hardware */
> + u32 offset; /* the offset of the real data */
> + u32 size; /* the size of the real data */
> + u64 ext0; /* for extension purpose */
> + u64 ext1; /* extra space reserved for future use */
> +} __packed;
> +
> +/*
> + * Loongson-VBIOS Data Block Layout
> + *
> + *
> + * _____________________ 0x00000
> + * |_____________________|
> + * | | [0x0000, 0x0400) : legacy vbios storage
> + * | Not Used Yet |
> + * | |
> + * |---------------------|<------- 0x6000
> + * +----| DCB 0 |
> + * | |---------------------|
> + * | | DCB 1 |
> + * | |---------------------| Format of Data Control Blocks
> + * | | One by one, packed | +------------+
> + * | |---------------------| | u16 type |
> + * | | DCB N |----+ | |
> + * | |---------------------| | +------------+
> + * | | . | | | u8 version |
> + * | | . | | | u8 index |
> + * | | . | | +------------+
> + * | |---------------------| | | |
> + * | | DCB end | | | u32 offset |
> + * | |---------------------| | +------- |
> + * | | | | | | |
> + * | |_____________________| | | +------------+
> + * | |_____________________| | | | |
> + * | | | | | | u32 size |
> + * +--->| vbios header info | | | | -------+
> + * |_____________________| | | | | |
> + * | . | | | +------------+ |
> + * | . | | | | useless | |
> + * | . | | | | members | |
> + * |_____________________| | | +------------+ |
> + * | | | | |
> + * | encoders info |<---+ | |
> + * |_____________________| | |
> + * | | ___| |
> + * |_____________________|____/ |
> + * | | |
> + * | Something | |
> + * |_____________________|_________________ |
> + * | | | |
> + * | EDID | |<--------------+
> + * |_____________________|_____________|___
> + * | |
> + * | | Contents of those device specific data
> + * | GPU specific info | block are implement-defined and version
> + * | | dependent :0
> + * |_____________________|
> + * / . /
> + * / . /
> + * / . /
> + * |_____________________| 0x040000
> + *
> + */
> +
> +enum loongson_vbios_dcb_type {
> + LV_DCB_HEADER = 0,
> + LV_DCB_CRTC = 1,
> + LV_DCB_ENCODER = 2,
> + LV_DCB_CONNECTOR = 3,
> + LV_DCB_I2C = 4,
> + LV_DCB_PWM = 5,
> + LV_DCB_GPIO = 6,
> + LV_DCB_BACKLIGHT = 7,
> + LV_DCB_FAN = 8,
> + LV_DCB_IRQ = 9,
> + LV_DCB_ENCODER_CFG = 10,
> + LV_DCB_ENCODER_RES = 11,
> + LV_DCB_GPU = 12,
> + LV_DCB_UNKNOWN = 13,
> + LV_DCB_END = 0xffff,
> +};
> +
> +struct loongson_vbios_header {
> + char header[16];
> + u32 version_major;

Please specify whether this is BE or LE. (__be32 or __le32). Usually
we use le32_to_cpu / be32_to_cpu helpers to access external data.

> + u32 version_minor;
> + char information[20];
> + u32 num_crtc;
> + u32 crtc_offset;
> + u32 num_connector;
> + u32 connector_offset;
> + u32 num_encoder;
> + u32 encoder_offset;
> +} __packed;
> +
> +struct loongson_vbios_encoder {
> + u32 feature;
> + u32 i2c_id;
> + u32 connector_id;
> + u32 type;
> + u32 config_method;
> + u32 chip_id;
> + u8 chip_addr;
> +} __packed;
> +
> +struct loongson_vbios_connector {
> + u32 feature;
> + u32 i2c_id;
> + u8 edid[256];
> + u32 type;
> + u32 hotplug_method;
> + u32 edid_method;
> + u32 hpd_int_gpio;
> + u32 gpio_place;
> +} __packed;
> +
> +/*
> + * A list node which contains the information about the device specific data
> + * block, the device here refer to the property or topology of hardware
> + * configuration, such as external display bridges, HDP GPIO, connectors etc.
> + */
> +struct loongson_vbios_node {
> + struct list_head head;
> +
> + /* @type: the type of the data. For search */
> + u32 type;
> + /* @id: the index(display pipe) of the data belong to. For search */
> + u32 id;
> + /*
> + * @data: point to the device specific data block, such as external
> + * encoders name and it's i2c device address, hpd gpio resource etc.
> + */
> + const void *data;
> + /*
> + * The size of the data.
> + */
> + u32 size;
> +};
> +
> +/*
> + * The returned pointer is actually point to &__loongson_vbios, but this
> + * function is only intended to provide READ-ONLY access. As our vbios is
> + * only be able to pass(provide) parameters, it is not executable and outside
> + * should not modify it.
> + */
> +const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev)
> +{
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp);
> +
> + return gfx->vbios;
> +}
> +
> +static bool loongson_vbios_is_valid(const struct loongson_vbios *vbios)
> +{
> + char header[32];
> +
> + memcpy(header, vbios->raw_data, sizeof(header));
> +
> + if (strcmp(header, LOONGSON_VBIOS_HEADER_STR))
> + return false;
> +
> + return true;
> +}
> +
> +/*
> + * The VBIOS blob is stored at the last 1 MiB of the VRAM, no SPI flush or

flash?

> + * EEPROM is needed. Platform BIOS is responsible for store this VBIOS blob

storing

> + * data at right position on per boot time.
> + */
> +static int loongson_vbios_construct(struct drm_device *ddev,
> + struct loongson_vbios *vbios)
> +{
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + u64 vram_end = ldev->vram_base + ldev->vram_size;
> + u64 vbios_start = vram_end - LOONGSON_VBIOS_BLOCK_OFFSET;
> + void __iomem *ptr;
> +
> + vbios->raw_data = kzalloc(LOONGSON_VBIOS_BLOCK_SIZE, GFP_KERNEL);
> + if (!vbios->raw_data)
> + return -ENOMEM;
> +
> + ptr = ioremap(vbios_start, LOONGSON_VBIOS_BLOCK_SIZE);
> + if (!ptr) {
> + drm_err(ddev, "Map VBIOS region failed\n");
> + return -ENOMEM;
> + }
> +
> + memcpy_fromio(vbios->raw_data, ptr, LOONGSON_VBIOS_BLOCK_SIZE);
> +
> + iounmap(ptr);
> +
> + INIT_LIST_HEAD(&vbios->list);
> + vbios->ddev = ddev;
> +
> + return 0;
> +}
> +
> +static void loongson_vbios_destruct(struct drm_device *ddev, void *data)

destroy or free. Also you can use drmm_ functions to make destructor
unnecessary.

> +{
> + struct loongson_vbios *vbios = (struct loongson_vbios *)data;
> + struct loongson_vbios_node *node;
> + struct loongson_vbios_node *tmp;
> +
> + list_for_each_entry_safe(node, tmp, &vbios->list, head) {
> + list_del(&node->head);
> + kfree(node);
> + }
> +
> + kfree(vbios->raw_data);
> + vbios->raw_data = NULL;
> +}
> +
> +static void loongson_vbios_print_dcb(struct drm_device *ddev,
> + struct loongson_vbios_dcb *dcb)
> +{
> + drm_info(ddev, "type: %u, Offset: %u, Size: %u, version: %u, ID: %u\n",
> + dcb->type, dcb->offset, dcb->size, dcb->version, dcb->id);
> +}
> +
> +/*
> + * Process the data control block, establish a list for later searching.
> + * returns the number of data control block. Generally, loongson vbios
> + * has only 10 DCB or so.
> + */
> +static int loongson_vbios_process_dcb(struct loongson_vbios *vbios,
> + bool verbose)
> +{
> + struct drm_device *ddev = vbios->ddev;
> + void *base = vbios->raw_data;
> + int count = 0;
> + struct loongson_vbios_dcb *dcb;
> +
> + dcb = (struct loongson_vbios_dcb *)(base + LOONGSON_VBIOS_DCB_OFFSET);
> +
> + while (dcb->type != LV_DCB_END) {
> + struct loongson_vbios_node *node;
> +
> + node = kzalloc(sizeof(*node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + node->type = dcb->type;
> + node->id = dcb->id;
> + node->data = base + dcb->offset;
> + node->size = dcb->size;
> +
> + list_add_tail(&node->head, &vbios->list);
> +
> + if (verbose)
> + loongson_vbios_print_dcb(ddev, dcb);
> +
> + ++dcb;
> +
> + if (++count > 1024) {
> + drm_err(ddev, "Unlikely, DCB is too much\n");
> + break;
> + }
> + }
> +
> + return count;
> +}
> +
> +static const struct loongson_vbios_node *
> +loongson_vbios_get_node(struct drm_device *ddev, u32 type, u32 id)
> +{
> + const struct loongson_vbios *vbios = to_loongson_vbios(ddev);
> + struct loongson_vbios_node *np;
> +
> + if (!vbios)
> + return NULL;
> +
> + list_for_each_entry(np, &vbios->list, head) {
> + if (np->type == type && np->id == id)
> + return np;
> + }
> +
> + return NULL;
> +}
> +
> +bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
> + u32 pipe,
> + u32 *type,
> + enum loongson_vbios_encoder_name *name,
> + u8 *i2c_addr)
> +{
> + const struct loongson_vbios_encoder *vencoder;
> + const struct loongson_vbios_node *np;
> +
> + np = loongson_vbios_get_node(ddev, LV_DCB_ENCODER, pipe);
> + if (!np)
> + return false;
> +
> + if (np->size != sizeof(*vencoder))
> + WARN_ON(1);
> +
> + vencoder = (const struct loongson_vbios_encoder *)np->data;
> +
> + if (type)
> + *type = vencoder->type;
> +
> + if (name)
> + *name = vencoder->chip_id;
> +
> + /* i2c address, as a slave device */
> + if (i2c_addr)
> + *i2c_addr = vencoder->chip_addr;
> +
> + return true;
> +}
> +
> +bool loongson_vbios_query_connector_info(struct drm_device *ddev,
> + u32 pipe,
> + u32 *connector_type,
> + u32 *hpd_method,
> + u32 *int_gpio,
> + u8 *edid_blob)
> +{
> + const struct loongson_vbios_connector *vconnector;
> + const struct loongson_vbios_node *np;
> +
> + np = loongson_vbios_get_node(ddev, LV_DCB_CONNECTOR, pipe);
> + if (!np)
> + return false;
> +
> + if (np->size != sizeof(*vconnector))
> + WARN_ON(1);
> +
> + vconnector = (const struct loongson_vbios_connector *)np->data;
> +
> + if (connector_type)
> + *connector_type = vconnector->type;
> +
> + if (edid_blob)
> + memcpy(edid_blob, vconnector->edid, 256);
> +
> + if (int_gpio)
> + *int_gpio = vconnector->hpd_int_gpio;
> +
> + return true;
> +}
> +
> +static void loongson_vbios_acquire_version(struct drm_device *ddev,
> + struct loongson_vbios *vbios)
> +{
> + struct loongson_vbios_header *vh;
> +
> + vh = (struct loongson_vbios_header *)vbios->raw_data;
> +
> + vbios->version_major = vh->version_major;
> + vbios->version_minor = vh->version_minor;
> +
> + drm_info(ddev, "Loongson VBIOS version: %u.%u\n",
> + vh->version_major, vh->version_minor);
> +}
> +
> +int loongson_vbios_init(struct drm_device *ddev)
> +{
> + struct loongson_vbios *vbios = &__loongson_vbios;
> + int ret;
> + int num;
> +
> + ret = loongson_vbios_construct(ddev, vbios);
> + if (ret)
> + return ret;
> +
> + ret = drmm_add_action_or_reset(ddev, loongson_vbios_destruct, vbios);
> + if (ret)
> + return ret;
> +
> + if (!loongson_vbios_is_valid(vbios)) {
> + drm_err(ddev, "Loongson VBIOS: header is invalid\n");
> + return -EINVAL;
> + }
> +
> + loongson_vbios_acquire_version(ddev, vbios);

get_version.

> +
> + num = loongson_vbios_process_dcb(vbios, false);
> + if (num <= 0) {
> + drm_err(ddev, "Loongson VBIOS: Process DCB failed\n");
> + return -EINVAL;
> + }
> +
> + drm_info(ddev, "Loongson VBIOS: has %d DCBs\n", num);
> +
> + return 0;
> +}
> diff --git a/drivers/gpu/drm/loongson/loongson_vbios.h b/drivers/gpu/drm/loongson/loongson_vbios.h
> new file mode 100644
> index 000000000000..66fb43b3609e
> --- /dev/null
> +++ b/drivers/gpu/drm/loongson/loongson_vbios.h
> @@ -0,0 +1,59 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2023 Loongson Technology Corporation Limited
> + */
> +
> +#ifndef __LOONGSON_VBIOS_H__
> +#define __LOONGSON_VBIOS_H__
> +
> +#include <drm/drm_device.h>
> +
> +struct loongson_vbios {
> + struct list_head list;
> + void *raw_data;
> + struct drm_device *ddev;
> + u32 version_major;
> + u32 version_minor;
> +};
> +
> +enum loongson_vbios_encoder_name {
> + ENCODER_CHIP_UNKNOWN = 0x00,
> + ENCODER_CHIP_INTERNAL_VGA = 0x01,
> + ENCODER_CHIP_INTERNAL_HDMI = 0x02,
> + ENCODER_CHIP_CH7055 = 0x10,
> + ENCODER_CHIP_ADV7125 = 0x11,
> + ENCODER_CHIP_TFP410 = 0x20,
> + ENCODER_CHIP_IT66121 = 0x30,
> + ENCODER_CHIP_SIL9022 = 0x31,
> + ENCODER_CHIP_LT8618 = 0x32,
> + ENCODER_CHIP_MS7210 = 0x33,
> + ENCODER_CHIP_NCS8805 = 0x40,
> + ENCODER_CHIP_LT9721 = 0x42,
> + ENCODER_CHIP_LT6711 = 0x43,
> + ENCODER_CHIP_LT8619 = 0x50,
> +};
> +
> +enum loongson_vbios_hotplug_method {
> + LV_HPD_DISABLED = 0,
> + LV_HPD_POLLING = 1,
> + LV_HPD_IRQ = 2,
> +};
> +
> +const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev);
> +
> +bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
> + u32 pipe,
> + u32 *type,
> + enum loongson_vbios_encoder_name *name,
> + u8 *i2c_addr);
> +
> +bool loongson_vbios_query_connector_info(struct drm_device *ddev,
> + u32 pipe,
> + u32 *connector_type,
> + u32 *hpd_method,
> + u32 *int_gpio,
> + u8 *edid_blob);
> +
> +int loongson_vbios_init(struct drm_device *ddev);
> +
> +#endif
> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c
> index 89ccc0c43169..aebb200fa567 100644
> --- a/drivers/gpu/drm/loongson/lsdc_drv.c
> +++ b/drivers/gpu/drm/loongson/lsdc_drv.c
> @@ -213,6 +213,10 @@ lsdc_create_device(struct pci_dev *pdev,
> return ERR_PTR(ret);
> }
>
> + ret = loongson_vbios_init(ddev);
> + if (ret)
> + drm_info(ddev, "No VBIOS support\n");
> +
> ret = drm_aperture_remove_conflicting_framebuffers(ldev->vram_base,
> ldev->vram_size,
> driver);
> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
> index fbf2d760ef27..335953c988d1 100644
> --- a/drivers/gpu/drm/loongson/lsdc_drv.h
> +++ b/drivers/gpu/drm/loongson/lsdc_drv.h
> @@ -16,6 +16,7 @@
> #include <drm/drm_plane.h>
> #include <drm/ttm/ttm_device.h>
>
> +#include "loongson_vbios.h"
> #include "lsdc_i2c.h"
> #include "lsdc_irq.h"
> #include "lsdc_gfxpll.h"
> @@ -85,6 +86,13 @@ struct loongson_gfx_desc {
> u32 reg_size;
> } pixpll[LSDC_NUM_CRTC];
>
> + /*
> + * @vbios: Provide information about the output configuration,
> + * and provide information about dynamic features which cannot
> + * be detected(determined) with the chip_id.
> + */
> + const struct loongson_vbios *vbios;
> +
> enum loongson_chip_id chip_id;
> char model[64];
> };
> --
> 2.34.1
>


--
With best wishes
Dmitry

2023-10-29 23:19:33

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> Move the sharable subroutine into lsdc_output.c and refactor.
>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/Makefile | 1 +
> drivers/gpu/drm/loongson/lsdc_drv.h | 17 -
> drivers/gpu/drm/loongson/lsdc_output.c | 640 +++++++++++++++++++++++++
> drivers/gpu/drm/loongson/lsdc_output.h | 52 +-

This diffstat doesn't match patch description.

> 4 files changed, 691 insertions(+), 19 deletions(-)
> create mode 100644 drivers/gpu/drm/loongson/lsdc_output.c
>
> diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
> index 1459d19b2c90..393709e686aa 100644
> --- a/drivers/gpu/drm/loongson/Makefile
> +++ b/drivers/gpu/drm/loongson/Makefile
> @@ -9,6 +9,7 @@ loongson-y := \
> lsdc_gfxpll.o \
> lsdc_i2c.o \
> lsdc_irq.o \
> + lsdc_output.o \
> lsdc_output_7a1000.o \
> lsdc_output_7a2000.o \
> lsdc_plane.o \
> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
> index 335953c988d1..46ba9b88a30d 100644
> --- a/drivers/gpu/drm/loongson/lsdc_drv.h
> +++ b/drivers/gpu/drm/loongson/lsdc_drv.h
> @@ -175,23 +175,6 @@ struct lsdc_cursor {
> struct lsdc_device *ldev;
> };
>
> -struct lsdc_output {
> - struct drm_encoder encoder;
> - struct drm_connector connector;
> -};
> -
> -static inline struct lsdc_output *
> -connector_to_lsdc_output(struct drm_connector *connector)
> -{
> - return container_of(connector, struct lsdc_output, connector);
> -}
> -
> -static inline struct lsdc_output *
> -encoder_to_lsdc_output(struct drm_encoder *encoder)
> -{
> - return container_of(encoder, struct lsdc_output, encoder);
> -}
> -
> struct lsdc_display_pipe {
> struct lsdc_crtc crtc;
> struct lsdc_primary primary;
> diff --git a/drivers/gpu/drm/loongson/lsdc_output.c b/drivers/gpu/drm/loongson/lsdc_output.c
> new file mode 100644
> index 000000000000..8262c3f91ebe
> --- /dev/null
> +++ b/drivers/gpu/drm/loongson/lsdc_output.c
> @@ -0,0 +1,640 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2023 Loongson Technology Corporation Limited
> + */
> +
> +#include <linux/delay.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_debugfs.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_output.h"
> +
> +/* This file contain shared subroutines for the output part */
> +
> +/* Usable for generic DVO, VGA and buitl-in HDMI connector */
> +
> +static int lsdc_connector_get_modes(struct drm_connector *connector)
> +{
> + unsigned int num = 0;
> + struct edid *edid;
> +
> + if (connector->ddc) {
> + edid = drm_get_edid(connector, connector->ddc);
> + if (edid) {
> + drm_connector_update_edid_property(connector, edid);
> + num = drm_add_edid_modes(connector, edid);
> + kfree(edid);
> + }
> +
> + return num;

drm_connector_helper_get_modes_from_ddc()

> + }
> +
> + num = drm_add_modes_noedid(connector, 1920, 1200);
> +
> + drm_set_preferred_mode(connector, 1024, 768);
> +
> + return num;
> +}
> +
> +static struct drm_encoder *
> +lsdc_connector_get_best_encoder(struct drm_connector *connector,
> + struct drm_atomic_state *state)
> +{
> + struct lsdc_output *output = connector_to_lsdc_output(connector);
> +
> + return &output->encoder;

Do you really need it? Does drm_connector_get_single_encoder() work
for your case?

> +}
> +
> +const struct drm_connector_helper_funcs lsdc_connector_helper_funcs = {
> + .atomic_best_encoder = lsdc_connector_get_best_encoder,
> + .get_modes = lsdc_connector_get_modes,
> +};
> +
> +static enum drm_connector_status
> +lsdc_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct i2c_adapter *ddc = connector->ddc;
> +
> + if (ddc) {
> + if (drm_probe_ddc(ddc))
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> + }
> +
> + return connector_status_unknown;
> +}
> +
> +const struct drm_connector_funcs lsdc_connector_funcs = {
> + .detect = lsdc_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state
> +};
> +
> +/* debugfs */
> +
> +#define LSDC_HDMI_REG(i, reg) { \
> + .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \
> + .offset = LSDC_HDMI##i##_##reg##_REG, \
> +}
> +
> +static int lsdc_hdmi_regs_show(struct seq_file *m, void *data)
> +{
> + struct drm_info_node *node = (struct drm_info_node *)m->private;
> + struct drm_device *ddev = node->minor->dev;
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + const struct lsdc_reg32 *preg;
> +
> + preg = (const struct lsdc_reg32 *)node->info_ent->data;
> +
> + while (preg->name) {
> + u32 offset = preg->offset;
> +
> + seq_printf(m, "%s (0x%04x): 0x%08x\n",
> + preg->name, offset, lsdc_rreg32(ldev, offset));
> + ++preg;
> + }
> +
> + return 0;
> +}
> +
> +/* LSDC built-in HDMI encoder, connected with display pipe 0 */
> +
> +static const struct lsdc_reg32 lsdc_hdmi_regs_pipe0[] = {
> + LSDC_HDMI_REG(0, ZONE),
> + LSDC_HDMI_REG(0, INTF_CTRL),
> + LSDC_HDMI_REG(0, PHY_CTRL),
> + LSDC_HDMI_REG(0, PHY_PLL),
> + LSDC_HDMI_REG(0, AVI_INFO_CRTL),
> + LSDC_HDMI_REG(0, PHY_CAL),
> + LSDC_HDMI_REG(0, AUDIO_PLL_LO),
> + LSDC_HDMI_REG(0, AUDIO_PLL_HI),
> + {NULL, 0}, /* MUST be {NULL, 0} terminated */
> +};
> +
> +static const struct drm_info_list lsdc_pipe0_hdmi_debugfs_files[] = {
> + { "regs", lsdc_hdmi_regs_show, 0, (void *)lsdc_hdmi_regs_pipe0 },
> +};
> +
> +static enum drm_connector_status
> +lsdc_pipe0_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct lsdc_device *ldev = to_lsdc(connector->dev);
> + u32 val;
> +
> + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
> +
> + if (val & HDMI0_HPD_FLAG)
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> +}
> +
> +static void lsdc_pipe0_hdmi_late_register(struct drm_connector *connector,
> + struct dentry *root)
> +{
> + struct drm_device *ddev = connector->dev;
> + struct drm_minor *minor = ddev->primary;
> +
> + drm_debugfs_create_files(lsdc_pipe0_hdmi_debugfs_files,
> + ARRAY_SIZE(lsdc_pipe0_hdmi_debugfs_files),
> + root, minor);
> +}
> +
> +const struct drm_connector_funcs lsdc_pipe0_hdmi_connector_funcs = {
> + .detect = lsdc_pipe0_hdmi_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> + .debugfs_init = lsdc_pipe0_hdmi_late_register,
> +};
> +
> +/* LSDC built-in HDMI connector, connected with display pipe 1 */
> +
> +static enum drm_connector_status
> +lsdc_pipe1_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> + struct lsdc_device *ldev = to_lsdc(connector->dev);
> + u32 val;
> +
> + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
> +
> + if (val & HDMI1_HPD_FLAG)
> + return connector_status_connected;
> +
> + return connector_status_disconnected;
> +}
> +
> +static const struct lsdc_reg32 lsdc_pipe1_hdmi_encoder_regs[] = {
> + LSDC_HDMI_REG(1, ZONE),
> + LSDC_HDMI_REG(1, INTF_CTRL),
> + LSDC_HDMI_REG(1, PHY_CTRL),
> + LSDC_HDMI_REG(1, PHY_PLL),
> + LSDC_HDMI_REG(1, AVI_INFO_CRTL),
> + LSDC_HDMI_REG(1, PHY_CAL),
> + LSDC_HDMI_REG(1, AUDIO_PLL_LO),
> + LSDC_HDMI_REG(1, AUDIO_PLL_HI),
> + {NULL, 0}, /* MUST be {NULL, 0} terminated */
> +};
> +
> +static const struct drm_info_list lsdc_pipe1_hdmi_debugfs_files[] = {
> + { "regs", lsdc_hdmi_regs_show, 0, (void *)lsdc_pipe1_hdmi_encoder_regs },
> +};
> +
> +static void lsdc_pipe1_hdmi_late_register(struct drm_connector *connector,
> + struct dentry *root)
> +{
> + struct drm_device *ddev = connector->dev;
> + struct drm_minor *minor = ddev->primary;
> +
> + drm_debugfs_create_files(lsdc_pipe1_hdmi_debugfs_files,
> + ARRAY_SIZE(lsdc_pipe1_hdmi_debugfs_files),
> + root, minor);
> +}
> +
> +const struct drm_connector_funcs lsdc_pipe1_hdmi_connector_funcs = {
> + .detect = lsdc_pipe1_hdmi_connector_detect,
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> + .debugfs_init = lsdc_pipe1_hdmi_late_register,
> +};
> +
> +/*
> + * Fout = M * in_khz
> + *
> + * M = (4 * LF) / (IDF * ODF)
> + *
> + * IDF: Input Division Factor
> + * ODF: Output Division Factor
> + * LF: Loop Factor
> + * M: Required Mult
> + *
> + * +--------------------------------------------------------+
> + * | in_khz | M | IDF | LF | ODF | Fout(Mhz) |
> + * |-------------------+----+-----+----+-----+--------------|
> + * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 |
> + * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 |
> + * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 |
> + * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 |
> + * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 |
> + * +--------------------------------------------------------+
> + */
> +static void lsdc_hdmi_phy_pll_config(struct drm_device *ddev,
> + int in_khz,
> + unsigned int pipe)
> +{
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + int count = 0;
> + u32 val;
> +
> + /* Firstly, disable phy pll */
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, 0x0);
> +
> + /*
> + * Most of time, loongson HDMI require M = 10
> + * for example, 10 = (4 * 40) / (8 * 2)
> + * here, write "1" to the ODF will get "2"
> + */
> +
> + if (in_khz >= 170000)
> + val = (16 << HDMI_PLL_IDF_SHIFT) |
> + (40 << HDMI_PLL_LF_SHIFT) |
> + (0 << HDMI_PLL_ODF_SHIFT);
> + else if (in_khz >= 85000)
> + val = (8 << HDMI_PLL_IDF_SHIFT) |
> + (40 << HDMI_PLL_LF_SHIFT) |
> + (1 << HDMI_PLL_ODF_SHIFT);
> + else if (in_khz >= 42500)
> + val = (4 << HDMI_PLL_IDF_SHIFT) |
> + (40 << HDMI_PLL_LF_SHIFT) |
> + (2 << HDMI_PLL_ODF_SHIFT);
> + else if (in_khz >= 21250)
> + val = (2 << HDMI_PLL_IDF_SHIFT) |
> + (40 << HDMI_PLL_LF_SHIFT) |
> + (3 << HDMI_PLL_ODF_SHIFT);
> + else
> + val = (1 << HDMI_PLL_IDF_SHIFT) |
> + (40 << HDMI_PLL_LF_SHIFT) |
> + (4 << HDMI_PLL_ODF_SHIFT);
> +
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, val);
> +
> + val |= HDMI_PLL_ENABLE;
> +
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe, val);
> +
> + udelay(2);
> +
> + drm_dbg(ddev, "Input frequency of HDMI-%u: %d kHz\n", pipe, in_khz);
> +
> + /* Wait hdmi phy pll lock */
> + do {
> + val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, pipe);
> +
> + if (val & HDMI_PLL_LOCKED) {
> + drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n",
> + pipe, count);
> + break;
> + }
> + ++count;
> + } while (count < 1000);
> +
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, pipe, 0x0f000ff0);
> +
> + if (count >= 1000)
> + drm_err(ddev, "Setting HDMI-%u PLL failed\n", pipe);
> +}
> +
> +static int lsdc_hdmi_phy_set_avi_infoframe(struct drm_encoder *encoder,
> + struct drm_connector *connector,
> + struct drm_display_mode *mode,
> + unsigned int index)
> +{
> + struct drm_device *ddev = encoder->dev;
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + struct hdmi_avi_infoframe infoframe;
> + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
> + unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE];
> + unsigned int content0, content1, content2, content3;
> + int err;
> +
> + err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe,
> + connector,
> + mode);
> + if (err < 0) {
> + drm_err(ddev, "failed to setup AVI infoframe: %d\n", err);
> + return err;
> + }
> +
> + /* Fixed infoframe configuration not linked to the mode */
> + infoframe.colorspace = HDMI_COLORSPACE_RGB;
> + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
> + infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
> +
> + err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
> + if (err < 0) {
> + drm_err(ddev, "failed to pack AVI infoframe: %d\n", err);
> + return err;
> + }
> +
> + content0 = *(unsigned int *)ptr;
> + content1 = *(ptr + 4);
> + content2 = *(unsigned int *)(ptr + 5);
> + content3 = *(unsigned int *)(ptr + 9);
> +
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0);
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1);
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2);
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3);
> +
> + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index,
> + AVI_PKT_ENABLE | AVI_PKT_UPDATE);
> +
> + drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index);
> +
> + return 0;
> +}
> +
> +/* Built-in HDMI encoder funcs on display pipe 0 */
> +
> +static void lsdc_pipe0_hdmi_encoder_reset(struct drm_encoder *encoder)
> +{
> + struct drm_device *ddev = encoder->dev;
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + u32 val;
> +
> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> + lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
> +
> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HW_I2C_EN);
> +
> + /* Help the HDMI phy get out of reset state */
> + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> +
> + drm_dbg(ddev, "%s reset\n", encoder->name);
> +
> + mdelay(20);
> +}
> +
> +const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs = {
> + .reset = lsdc_pipe0_hdmi_encoder_reset,
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +/* Built-in HDMI encoder funcs on display pipe 1 */

All pipe 1 code looks like a pipe0, just the registers were changed.
Could you please refactor that to use a single instance of all
functions and pass pipe id through the data struct?
Then you can use macros to determine whether to use pipe 0 or pipe 1 register.

> +
> +static void lsdc_pipe1_hdmi_encoder_reset(struct drm_encoder *encoder)
> +{
> + struct drm_device *ddev = encoder->dev;
> + struct lsdc_device *ldev = to_lsdc(ddev);
> + u32 val;
> +
> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
> +
> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HW_I2C_EN);
> +
> + /* Help the HDMI phy get out of reset state */
> + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> +
> + drm_dbg(ddev, "%s reset\n", encoder->name);
> +
> + mdelay(20);
> +}
> +
> +const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs = {
> + .reset = lsdc_pipe1_hdmi_encoder_reset,
> + .destroy = drm_encoder_cleanup,
> +};
> +
> +/* Built-in DVO encoder helper funcs */
> +
> +static void lsdc_dvo_atomic_disable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> +}
> +
> +static void lsdc_dvo_atomic_enable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> +}
> +
> +static void lsdc_dvo_atomic_modeset(struct drm_encoder *encoder,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> +}

You do not need these empty callbacks, do you?

> +
> +const struct drm_encoder_helper_funcs lsdc_encoder_helper_funcs = {
> + .atomic_disable = lsdc_dvo_atomic_disable,
> + .atomic_enable = lsdc_dvo_atomic_enable,
> + .atomic_mode_set = lsdc_dvo_atomic_modeset,
> +};
> +
> +/* Built-in HDMI encoder helper funcs on display pipe 0 */
> +
> +static void lsdc_pipe0_hdmi_atomic_disable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> + struct lsdc_device *ldev = to_lsdc(encoder->dev);
> +
> + /* Disable the HDMI PHY */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_EN);
> +
> + /* Disable the HDMI interface */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HDMI_INTERFACE_EN);
> +}
> +
> +static void lsdc_pipe0_hdmi_atomic_enable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> + struct lsdc_device *ldev = to_lsdc(encoder->dev);
> + u32 val;
> +
> + /* datasheet say it should larger than 48 */
> + val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
> + lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, val);
> +
> + val = HDMI_PHY_TERM_STATUS |
> + HDMI_PHY_TERM_DET_EN |
> + HDMI_PHY_TERM_H_EN |
> + HDMI_PHY_TERM_L_EN |
> + HDMI_PHY_RESET_N |
> + HDMI_PHY_EN;
> + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val);
> +
> + udelay(2);
> +
> + val = HDMI_CTL_PERIOD_MODE |
> + HDMI_AUDIO_EN |
> + HDMI_PACKET_EN |
> + HDMI_INTERFACE_EN |
> + (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
> + lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val);
> +}
> +
> +static void lsdc_pipe0_hdmi_atomic_modeset(struct drm_encoder *encoder,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> + struct drm_device *ddev = encoder->dev;
> + struct drm_display_mode *mode = &crtc_state->mode;
> +
> + lsdc_hdmi_phy_pll_config(ddev, mode->clock, 0);
> +
> + lsdc_hdmi_phy_set_avi_infoframe(encoder, &output->connector, mode, 0);
> +
> + drm_dbg(ddev, "%s modeset finished\n", encoder->name);
> +}
> +
> +const struct drm_encoder_helper_funcs lsdc_pipe0_hdmi_encoder_helper_funcs = {
> + .atomic_disable = lsdc_pipe0_hdmi_atomic_disable,
> + .atomic_enable = lsdc_pipe0_hdmi_atomic_enable,
> + .atomic_mode_set = lsdc_pipe0_hdmi_atomic_modeset,
> +};
> +
> +/* Built-in HDMI encoder helper funcs on display pipe 1 */
> +
> +static void lsdc_pipe1_hdmi_atomic_disable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> + struct lsdc_device *ldev = to_lsdc(encoder->dev);
> +
> + /* Disable the HDMI PHY */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_EN);
> +
> + /* Disable the HDMI interface */
> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HDMI_INTERFACE_EN);
> +}
> +
> +static void lsdc_pipe1_hdmi_atomic_enable(struct drm_encoder *encoder,
> + struct drm_atomic_state *state)
> +{
> + struct lsdc_device *ldev = to_lsdc(encoder->dev);
> + u32 val;
> +
> + /* datasheet say it should larger than 48 */
> + val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
> + lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, val);
> +
> + val = HDMI_PHY_TERM_STATUS |
> + HDMI_PHY_TERM_DET_EN |
> + HDMI_PHY_TERM_H_EN |
> + HDMI_PHY_TERM_L_EN |
> + HDMI_PHY_RESET_N |
> + HDMI_PHY_EN;
> + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val);
> +
> + udelay(2);
> +
> + val = HDMI_CTL_PERIOD_MODE |
> + HDMI_AUDIO_EN |
> + HDMI_PACKET_EN |
> + HDMI_INTERFACE_EN |
> + (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
> + lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val);
> +}
> +
> +static void lsdc_pipe1_hdmi_atomic_modeset(struct drm_encoder *encoder,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state)
> +{
> + struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> + struct drm_device *ddev = encoder->dev;
> + struct drm_display_mode *mode = &crtc_state->mode;
> +
> + lsdc_hdmi_phy_pll_config(ddev, mode->clock, 1);
> +
> + lsdc_hdmi_phy_set_avi_infoframe(encoder, &output->connector, mode, 1);
> +
> + drm_dbg(ddev, "%s modeset finished\n", encoder->name);
> +}
> +
> +const struct drm_encoder_helper_funcs lsdc_pipe1_hdmi_encoder_helper_funcs = {
> + .atomic_disable = lsdc_pipe1_hdmi_atomic_disable,
> + .atomic_enable = lsdc_pipe1_hdmi_atomic_enable,
> + .atomic_mode_set = lsdc_pipe1_hdmi_atomic_modeset,
> +};
> +
> +int lsdc_encoder_init(struct drm_device *ddev,
> + struct lsdc_output *output,
> + unsigned int pipe)
> +{
> + const struct lsdc_output_desc *descp = output->descp;
> + struct drm_encoder *encoder = &output->encoder;
> + int ret;
> +
> + ret = drm_encoder_init(ddev,
> + encoder,
> + descp->encoder_funcs,
> + descp->encoder_type,
> + descp->name);
> + if (ret)
> + return ret;
> +
> + encoder->possible_crtcs = BIT(pipe);
> +
> + drm_encoder_helper_add(encoder, descp->encoder_helper_funcs);
> +
> + return 0;
> +}
> +
> +int lsdc_connector_init(struct drm_device *ddev,
> + struct lsdc_output *output,
> + struct i2c_adapter *ddc,
> + unsigned int pipe)
> +{
> + const struct lsdc_output_desc *descp = output->descp;
> + struct drm_connector *connector = &output->connector;
> + int ret;
> +
> + ret = drm_connector_init_with_ddc(ddev,
> + connector,
> + descp->connector_funcs,
> + descp->connector_type,
> + ddc);
> + if (ret)
> + return ret;
> +
> + drm_connector_helper_add(connector, descp->connector_helper_funcs);
> +
> + drm_connector_attach_encoder(connector, &output->encoder);
> +
> + connector->polled = DRM_CONNECTOR_POLL_CONNECT |
> + DRM_CONNECTOR_POLL_DISCONNECT;
> +
> + connector->interlace_allowed = 0;
> + connector->doublescan_allowed = 0;
> +
> + drm_info(ddev, "DisplayPipe-%u has %s\n", pipe, descp->name);
> +
> + return 0;
> +}
> +
> +/*
> + * A common, sharable subroutine for the initialization of output part.
> + * If there is external non-transparent display bridge chip on the display
> + * pipe, we will attach it. Otherwise, the output is simple, we will just
> + * initial a connector for it.
> + */
> +int lsdc_output_init(struct drm_device *ddev,
> + struct lsdc_display_pipe *dispipe,
> + struct i2c_adapter *ddc,
> + unsigned int pipe)
> +{
> + struct lsdc_output *output = &dispipe->output;
> + int ret;
> +
> + ret = lsdc_encoder_init(ddev, output, pipe);
> + if (ret)
> + return ret;
> +
> + if (output->bridge) {
> + ret = drm_bridge_attach(&output->encoder, output->bridge,
> + NULL, 0);

Since you are creating the connector manually, you should pass
DRM_BRIDGE_ATTACH_NO_CONNECTOR here.

> + if (ret) {
> + drm_err(ddev, "Attach display bridge failed\n");
> + ret = lsdc_connector_init(ddev, output, ddc, pipe);
> + }
> + } else {
> + ret = lsdc_connector_init(ddev, output, ddc, pipe);
> + }
> +
> + return ret;
> +}
> diff --git a/drivers/gpu/drm/loongson/lsdc_output.h b/drivers/gpu/drm/loongson/lsdc_output.h
> index 097789051a1d..a37a72687bdf 100644
> --- a/drivers/gpu/drm/loongson/lsdc_output.h
> +++ b/drivers/gpu/drm/loongson/lsdc_output.h
> @@ -6,16 +6,64 @@
> #ifndef __LSDC_OUTPUT_H__
> #define __LSDC_OUTPUT_H__
>
> -#include "lsdc_drv.h"
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +struct lsdc_output_desc {
> + u32 pipe;
> + u32 encoder_type;
> + u32 connector_type;
> + const struct drm_encoder_funcs *encoder_funcs;
> + const struct drm_encoder_helper_funcs *encoder_helper_funcs;
> + const struct drm_connector_funcs *connector_funcs;
> + const struct drm_connector_helper_funcs *connector_helper_funcs;
> + const char name[32];
> +};
> +
> +struct lsdc_output {
> + struct drm_encoder encoder;
> + struct drm_connector connector;
> + struct drm_bridge *bridge;
> + const struct lsdc_output_desc *descp;
> +};
> +
> +static inline struct lsdc_output *
> +connector_to_lsdc_output(struct drm_connector *connector)
> +{
> + return container_of(connector, struct lsdc_output, connector);
> +}
> +
> +static inline struct lsdc_output *
> +encoder_to_lsdc_output(struct drm_encoder *encoder)
> +{
> + return container_of(encoder, struct lsdc_output, encoder);
> +}
> +
> +extern const struct drm_connector_funcs lsdc_connector_funcs;
> +extern const struct drm_connector_funcs lsdc_pipe0_hdmi_connector_funcs;
> +extern const struct drm_connector_funcs lsdc_pipe1_hdmi_connector_funcs;
> +extern const struct drm_connector_helper_funcs lsdc_connector_helper_funcs;
> +
> +extern const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs;
> +extern const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs;
> +extern const struct drm_encoder_helper_funcs lsdc_encoder_helper_funcs;
> +extern const struct drm_encoder_helper_funcs lsdc_pipe0_hdmi_encoder_helper_funcs;
> +extern const struct drm_encoder_helper_funcs lsdc_pipe1_hdmi_encoder_helper_funcs;
>
> int ls7a1000_output_init(struct drm_device *ddev,
> struct lsdc_display_pipe *dispipe,
> struct i2c_adapter *ddc,
> unsigned int index);
>
> -int ls7a2000_output_init(struct drm_device *ldev,
> +int ls7a2000_output_init(struct drm_device *ddev,
> struct lsdc_display_pipe *dispipe,
> struct i2c_adapter *ddc,
> unsigned int index);
>
> +int lsdc_output_init(struct drm_device *ddev,
> + struct lsdc_display_pipe *dispipe,
> + struct i2c_adapter *ddc,
> + unsigned int pipe);
> +
> #endif
> --
> 2.34.1
>


--
With best wishes
Dmitry

2023-10-29 23:22:18

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 4/8] drm/loongson: Started to attach display bridge driver for LS7A1000

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> Loongson ML5B_MA board using ITE IT66121 HDMI transmitter to support HDMI
> display output, with the vbios provided the necessary information, we are
> able to create a minimal drm bridge driver for it. After apply this patch
> we are able to change mode freely.

Please see the Documentation/process/submitting-patches.rst:

Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
to do frotz", as if you are giving orders to the codebase to change
its behaviour.

>
> Tested on LS3A5000+LS7A1000 ML5B_MA board.
>
> $ dmesg | grep drm
>
> [drm] dc: 264MHz, gmc: 529MHz, gpu: 529MHz
> [drm] Dedicated vram start: 0xe0030000000, size: 64MiB
> [drm] Loongson VBIOS version: 0.3
> [drm] Loongson VBIOS: has 8 DCBs
> [drm] VRAM: 4096 pages ready
> [drm] GTT: 32768 pages ready
> [drm] lsdc-i2c0(sda pin mask=1, scl pin mask=2) created
> [drm] lsdc-i2c1(sda pin mask=4, scl pin mask=8) created
> [drm] DisplayPipe-0 has DVO-0
> [drm] device address(0x4d) is not correct
> [drm] i2c client IT66121@0x4c created
> [drm] IT66121 attached, Vendor ID: 0x4954, Device ID: 0x612
> [drm] Total 2 outputs
> [drm] registered irq: 40
> [drm] Initialized loongson 1.0.0 20220701 for 0000:00:06.1 on minor 0
> loongson 0000:00:06.1: [drm] fb0: loongsondrmfb frame buffer device
>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/lsdc_output_7a1000.c | 144 +++++++-----------
> 1 file changed, 55 insertions(+), 89 deletions(-)
>
> diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a1000.c b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
> index 6fc8dd1c7d9a..e12f9a0157d0 100644
> --- a/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
> +++ b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c
> @@ -10,6 +10,7 @@
> #include "lsdc_drv.h"
> #include "lsdc_output.h"
>
> +#include "ite_it66121.h"
> /*
> * The display controller in the LS7A1000 exports two DVO interfaces, thus
> * external encoder is required, except connected to the DPI panel directly.
> @@ -38,68 +39,6 @@
> * TODO: Add support for non-transparent encoders
> */
>
> -static int ls7a1000_dpi_connector_get_modes(struct drm_connector *conn)
> -{
> - unsigned int num = 0;
> - struct edid *edid;
> -
> - if (conn->ddc) {
> - edid = drm_get_edid(conn, conn->ddc);
> - if (edid) {
> - drm_connector_update_edid_property(conn, edid);
> - num = drm_add_edid_modes(conn, edid);
> - kfree(edid);
> - }
> -
> - return num;
> - }
> -
> - num = drm_add_modes_noedid(conn, 1920, 1200);
> -
> - drm_set_preferred_mode(conn, 1024, 768);
> -
> - return num;
> -}
> -
> -static struct drm_encoder *
> -ls7a1000_dpi_connector_get_best_encoder(struct drm_connector *connector,
> - struct drm_atomic_state *state)
> -{
> - struct lsdc_output *output = connector_to_lsdc_output(connector);
> -
> - return &output->encoder;
> -}
> -
> -static const struct drm_connector_helper_funcs
> -ls7a1000_dpi_connector_helpers = {
> - .atomic_best_encoder = ls7a1000_dpi_connector_get_best_encoder,
> - .get_modes = ls7a1000_dpi_connector_get_modes,
> -};
> -
> -static enum drm_connector_status
> -ls7a1000_dpi_connector_detect(struct drm_connector *connector, bool force)
> -{
> - struct i2c_adapter *ddc = connector->ddc;
> -
> - if (ddc) {
> - if (drm_probe_ddc(ddc))
> - return connector_status_connected;
> -
> - return connector_status_disconnected;
> - }
> -
> - return connector_status_unknown;
> -}
> -
> -static const struct drm_connector_funcs ls7a1000_dpi_connector_funcs = {
> - .detect = ls7a1000_dpi_connector_detect,
> - .fill_modes = drm_helper_probe_single_connector_modes,
> - .destroy = drm_connector_cleanup,
> - .reset = drm_atomic_helper_connector_reset,
> - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state
> -};
> -
> static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder)
> {
> struct drm_device *ddev = encoder->dev;
> @@ -139,40 +78,67 @@ static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = {
> },
> };
>
> +/*
> + * This is a default output description for LS7A1000/LS2K1000, this is always
> + * true from the hardware perspective. It is just that when there are external
> + * display bridge connected, this description no longer complete. As it cannot
> + * describe the topology about the external encoders.
> + */
> +static const struct lsdc_output_desc ls7a1000_output_desc[2] = {
> + {
> + .pipe = 0,
> + .encoder_type = DRM_MODE_ENCODER_DPI,
> + .connector_type = DRM_MODE_CONNECTOR_DPI,
> + .encoder_funcs = &ls7a1000_encoder_funcs[0],
> + .encoder_helper_funcs = &lsdc_encoder_helper_funcs,
> + .connector_funcs = &lsdc_connector_funcs,
> + .connector_helper_funcs = &lsdc_connector_helper_funcs,
> + .name = "DVO-0",
> + },
> + {
> + .pipe = 1,
> + .encoder_type = DRM_MODE_ENCODER_DPI,
> + .connector_type = DRM_MODE_CONNECTOR_DPI,
> + .encoder_funcs = &ls7a1000_encoder_funcs[1],
> + .encoder_helper_funcs = &lsdc_encoder_helper_funcs,
> + .connector_funcs = &lsdc_connector_funcs,
> + .connector_helper_funcs = &lsdc_connector_helper_funcs,
> + .name = "DVO-1",
> + },
> +};
> +
> int ls7a1000_output_init(struct drm_device *ddev,
> struct lsdc_display_pipe *dispipe,
> struct i2c_adapter *ddc,
> unsigned int index)
> {
> struct lsdc_output *output = &dispipe->output;
> - struct drm_encoder *encoder = &output->encoder;
> - struct drm_connector *connector = &output->connector;
> - int ret;
> -
> - ret = drm_encoder_init(ddev, encoder, &ls7a1000_encoder_funcs[index],
> - DRM_MODE_ENCODER_TMDS, "encoder-%u", index);
> - if (ret)
> - return ret;
> -
> - encoder->possible_crtcs = BIT(index);
> -
> - ret = drm_connector_init_with_ddc(ddev, connector,
> - &ls7a1000_dpi_connector_funcs,
> - DRM_MODE_CONNECTOR_DPI, ddc);
> - if (ret)
> - return ret;
> -
> - drm_info(ddev, "display pipe-%u has a DVO\n", index);
> -
> - drm_connector_helper_add(connector, &ls7a1000_dpi_connector_helpers);
> -
> - drm_connector_attach_encoder(connector, encoder);
> + enum loongson_vbios_encoder_name encoder_name = 0;
> + struct drm_bridge *bridge = NULL;
> + u8 slave_addr;
> + bool ret;
> +
> + output->descp = &ls7a1000_output_desc[index];
> +
> + ret = loongson_vbios_query_encoder_info(ddev, index, NULL,
> + &encoder_name, &slave_addr);
> + if (!ret)
> + goto skip;
> +
> + switch (encoder_name) {
> + case ENCODER_CHIP_IT66121:
> + bridge = it66121_bridge_create(ddev, ddc, slave_addr, false,
> + 0, index);

No. You have a fancy interface describing connected devices. Could you
please use the infrastructure that already exists for you. For i2c
devices you have to create an i2c device on a corresponding i2c bus.
See drivers/i2c/i2c-core-of.c for inspiration.

> + break;
> + default:
> + break;
> + }
>
> - connector->polled = DRM_CONNECTOR_POLL_CONNECT |
> - DRM_CONNECTOR_POLL_DISCONNECT;
> + if (IS_ERR(bridge))
> + goto skip;
>
> - connector->interlace_allowed = 0;
> - connector->doublescan_allowed = 0;
> + output->bridge = bridge;
>
> - return 0;
> +skip:
> + return lsdc_output_init(ddev, dispipe, ddc, index);
> }
> --
> 2.34.1
>


--
With best wishes
Dmitry

2023-10-29 23:26:51

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 6/8] drm/loongson: Clean up the output part of LS7A2000

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> Since the majority of sharable subroutines have been move to lsdc_output.c,
> and functional changes are done with previous patch. We finally see the
> light to cleanup, no functional change.

Please refactor your patches so that code moves can be reviewed as
code being moved, instead of add + remove patches.

>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/lsdc_output_7a2000.c | 469 ------------------
> 1 file changed, 469 deletions(-)
>
> diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
> index bf558b61802b..981ab2045e91 100644
> --- a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
> +++ b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c
> @@ -42,465 +42,6 @@
> * |______________________|
> */
>
> -static int ls7a2000_connector_get_modes(struct drm_connector *connector)
> -{
> - unsigned int num = 0;
> - struct edid *edid;
> -
> - if (connector->ddc) {
> - edid = drm_get_edid(connector, connector->ddc);
> - if (edid) {
> - drm_connector_update_edid_property(connector, edid);
> - num = drm_add_edid_modes(connector, edid);
> - kfree(edid);
> - }
> -
> - return num;
> - }
> -
> - num = drm_add_modes_noedid(connector, 1920, 1200);
> -
> - drm_set_preferred_mode(connector, 1024, 768);
> -
> - return num;
> -}
> -
> -static struct drm_encoder *
> -ls7a2000_connector_get_best_encoder(struct drm_connector *connector,
> - struct drm_atomic_state *state)
> -{
> - struct lsdc_output *output = connector_to_lsdc_output(connector);
> -
> - return &output->encoder;
> -}
> -
> -static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = {
> - .atomic_best_encoder = ls7a2000_connector_get_best_encoder,
> - .get_modes = ls7a2000_connector_get_modes,
> -};
> -
> -/* debugfs */
> -
> -#define LSDC_HDMI_REG(i, reg) { \
> - .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \
> - .offset = LSDC_HDMI##i##_##reg##_REG, \
> -}
> -
> -static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = {
> - LSDC_HDMI_REG(0, ZONE),
> - LSDC_HDMI_REG(0, INTF_CTRL),
> - LSDC_HDMI_REG(0, PHY_CTRL),
> - LSDC_HDMI_REG(0, PHY_PLL),
> - LSDC_HDMI_REG(0, AVI_INFO_CRTL),
> - LSDC_HDMI_REG(0, PHY_CAL),
> - LSDC_HDMI_REG(0, AUDIO_PLL_LO),
> - LSDC_HDMI_REG(0, AUDIO_PLL_HI),
> - {NULL, 0}, /* MUST be {NULL, 0} terminated */
> -};
> -
> -static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = {
> - LSDC_HDMI_REG(1, ZONE),
> - LSDC_HDMI_REG(1, INTF_CTRL),
> - LSDC_HDMI_REG(1, PHY_CTRL),
> - LSDC_HDMI_REG(1, PHY_PLL),
> - LSDC_HDMI_REG(1, AVI_INFO_CRTL),
> - LSDC_HDMI_REG(1, PHY_CAL),
> - LSDC_HDMI_REG(1, AUDIO_PLL_LO),
> - LSDC_HDMI_REG(1, AUDIO_PLL_HI),
> - {NULL, 0}, /* MUST be {NULL, 0} terminated */
> -};
> -
> -static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data)
> -{
> - struct drm_info_node *node = (struct drm_info_node *)m->private;
> - struct drm_device *ddev = node->minor->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - const struct lsdc_reg32 *preg;
> -
> - preg = (const struct lsdc_reg32 *)node->info_ent->data;
> -
> - while (preg->name) {
> - u32 offset = preg->offset;
> -
> - seq_printf(m, "%s (0x%04x): 0x%08x\n",
> - preg->name, offset, lsdc_rreg32(ldev, offset));
> - ++preg;
> - }
> -
> - return 0;
> -}
> -
> -static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = {
> - { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs },
> -};
> -
> -static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = {
> - { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs },
> -};
> -
> -static void ls7a2000_hdmi0_late_register(struct drm_connector *connector,
> - struct dentry *root)
> -{
> - struct drm_device *ddev = connector->dev;
> - struct drm_minor *minor = ddev->primary;
> -
> - drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files,
> - ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files),
> - root, minor);
> -}
> -
> -static void ls7a2000_hdmi1_late_register(struct drm_connector *connector,
> - struct dentry *root)
> -{
> - struct drm_device *ddev = connector->dev;
> - struct drm_minor *minor = ddev->primary;
> -
> - drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files,
> - ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files),
> - root, minor);
> -}
> -
> -/* monitor present detection */
> -
> -static enum drm_connector_status
> -ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force)
> -{
> - struct drm_device *ddev = connector->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - u32 val;
> -
> - val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
> -
> - if (val & HDMI0_HPD_FLAG)
> - return connector_status_connected;
> -
> - if (connector->ddc) {
> - if (drm_probe_ddc(connector->ddc))
> - return connector_status_connected;
> -
> - return connector_status_disconnected;
> - }
> -
> - return connector_status_unknown;
> -}
> -
> -static enum drm_connector_status
> -ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force)
> -{
> - struct lsdc_device *ldev = to_lsdc(connector->dev);
> - u32 val;
> -
> - val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
> -
> - if (val & HDMI1_HPD_FLAG)
> - return connector_status_connected;
> -
> - return connector_status_disconnected;
> -}
> -
> -static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = {
> - {
> - .detect = ls7a2000_hdmi0_vga_connector_detect,
> - .fill_modes = drm_helper_probe_single_connector_modes,
> - .destroy = drm_connector_cleanup,
> - .reset = drm_atomic_helper_connector_reset,
> - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> - .debugfs_init = ls7a2000_hdmi0_late_register,
> - },
> - {
> - .detect = ls7a2000_hdmi1_connector_detect,
> - .fill_modes = drm_helper_probe_single_connector_modes,
> - .destroy = drm_connector_cleanup,
> - .reset = drm_atomic_helper_connector_reset,
> - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> - .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> - .debugfs_init = ls7a2000_hdmi1_late_register,
> - },
> -};
> -
> -/* Even though some board has only one hdmi on display pipe 1,
> - * We still need hook lsdc_encoder_funcs up on display pipe 0,
> - * This is because we need its reset() callback get called, to
> - * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c.
> - * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly.
> - */
> -static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder)
> -{
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - u32 val;
> -
> - val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> - lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
> -
> - /* using software gpio emulated i2c */
> - val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG);
> - val &= ~HW_I2C_EN;
> - lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val);
> -
> - /* help the hdmi phy to get out of reset state */
> - lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> -
> - mdelay(20);
> -
> - drm_dbg(ddev, "HDMI-0 Reset\n");
> -}
> -
> -static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder)
> -{
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - u32 val;
> -
> - val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> - lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
> -
> - /* using software gpio emulated i2c */
> - val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG);
> - val &= ~HW_I2C_EN;
> - lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val);
> -
> - /* help the hdmi phy to get out of reset state */
> - lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> -
> - mdelay(20);
> -
> - drm_dbg(ddev, "HDMI-1 Reset\n");
> -}
> -
> -static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = {
> - {
> - .reset = ls7a2000_hdmi0_encoder_reset,
> - .destroy = drm_encoder_cleanup,
> - },
> - {
> - .reset = ls7a2000_hdmi1_encoder_reset,
> - .destroy = drm_encoder_cleanup,
> - },
> -};
> -
> -static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder,
> - struct drm_display_mode *mode)
> -{
> - struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> - struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
> - unsigned int index = dispipe->index;
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - struct hdmi_avi_infoframe infoframe;
> - u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
> - unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE];
> - unsigned int content0, content1, content2, content3;
> - int err;
> -
> - err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe,
> - &output->connector,
> - mode);
> - if (err < 0) {
> - drm_err(ddev, "failed to setup AVI infoframe: %d\n", err);
> - return err;
> - }
> -
> - /* Fixed infoframe configuration not linked to the mode */
> - infoframe.colorspace = HDMI_COLORSPACE_RGB;
> - infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
> - infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
> -
> - err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
> - if (err < 0) {
> - drm_err(ddev, "failed to pack AVI infoframe: %d\n", err);
> - return err;
> - }
> -
> - content0 = *(unsigned int *)ptr;
> - content1 = *(ptr + 4);
> - content2 = *(unsigned int *)(ptr + 5);
> - content3 = *(unsigned int *)(ptr + 9);
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0);
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1);
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2);
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3);
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index,
> - AVI_PKT_ENABLE | AVI_PKT_UPDATE);
> -
> - drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index);
> -
> - return 0;
> -}
> -
> -static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder,
> - struct drm_atomic_state *state)
> -{
> - struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> - struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
> - unsigned int index = dispipe->index;
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - u32 val;
> -
> - /* Disable the hdmi phy */
> - val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index);
> - val &= ~HDMI_PHY_EN;
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val);
> -
> - /* Disable the hdmi interface */
> - val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index);
> - val &= ~HDMI_INTERFACE_EN;
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val);
> -
> - drm_dbg(ddev, "HDMI-%u disabled\n", index);
> -}
> -
> -static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder,
> - struct drm_atomic_state *state)
> -{
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> - struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
> - unsigned int index = dispipe->index;
> - u32 val;
> -
> - /* datasheet say it should larger than 48 */
> - val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT;
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val);
> -
> - val = HDMI_PHY_TERM_STATUS |
> - HDMI_PHY_TERM_DET_EN |
> - HDMI_PHY_TERM_H_EN |
> - HDMI_PHY_TERM_L_EN |
> - HDMI_PHY_RESET_N |
> - HDMI_PHY_EN;
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val);
> -
> - udelay(2);
> -
> - val = HDMI_CTL_PERIOD_MODE |
> - HDMI_AUDIO_EN |
> - HDMI_PACKET_EN |
> - HDMI_INTERFACE_EN |
> - (8 << HDMI_VIDEO_PREAMBLE_SHIFT);
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val);
> -
> - drm_dbg(ddev, "HDMI-%u enabled\n", index);
> -}
> -
> -/*
> - * Fout = M * Fin
> - *
> - * M = (4 * LF) / (IDF * ODF)
> - *
> - * IDF: Input Division Factor
> - * ODF: Output Division Factor
> - * LF: Loop Factor
> - * M: Required Mult
> - *
> - * +--------------------------------------------------------+
> - * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) |
> - * |-------------------+----+-----+----+-----+--------------|
> - * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 |
> - * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 |
> - * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 |
> - * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 |
> - * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 |
> - * +--------------------------------------------------------+
> - */
> -static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev,
> - int fin,
> - unsigned int index)
> -{
> - struct drm_device *ddev = &ldev->base;
> - int count = 0;
> - u32 val;
> -
> - /* Firstly, disable phy pll */
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0);
> -
> - /*
> - * Most of time, loongson HDMI require M = 10
> - * for example, 10 = (4 * 40) / (8 * 2)
> - * here, write "1" to the ODF will get "2"
> - */
> -
> - if (fin >= 170000)
> - val = (16 << HDMI_PLL_IDF_SHIFT) |
> - (40 << HDMI_PLL_LF_SHIFT) |
> - (0 << HDMI_PLL_ODF_SHIFT);
> - else if (fin >= 85000)
> - val = (8 << HDMI_PLL_IDF_SHIFT) |
> - (40 << HDMI_PLL_LF_SHIFT) |
> - (1 << HDMI_PLL_ODF_SHIFT);
> - else if (fin >= 42500)
> - val = (4 << HDMI_PLL_IDF_SHIFT) |
> - (40 << HDMI_PLL_LF_SHIFT) |
> - (2 << HDMI_PLL_ODF_SHIFT);
> - else if (fin >= 21250)
> - val = (2 << HDMI_PLL_IDF_SHIFT) |
> - (40 << HDMI_PLL_LF_SHIFT) |
> - (3 << HDMI_PLL_ODF_SHIFT);
> - else
> - val = (1 << HDMI_PLL_IDF_SHIFT) |
> - (40 << HDMI_PLL_LF_SHIFT) |
> - (4 << HDMI_PLL_ODF_SHIFT);
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val);
> -
> - val |= HDMI_PLL_ENABLE;
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val);
> -
> - udelay(2);
> -
> - drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin);
> -
> - /* Wait hdmi phy pll lock */
> - do {
> - val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index);
> -
> - if (val & HDMI_PLL_LOCKED) {
> - drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n",
> - index, count);
> - break;
> - }
> - ++count;
> - } while (count < 1000);
> -
> - lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0);
> -
> - if (count >= 1000)
> - drm_err(ddev, "Setting HDMI-%u PLL failed\n", index);
> -}
> -
> -static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder,
> - struct drm_crtc_state *crtc_state,
> - struct drm_connector_state *conn_state)
> -{
> - struct lsdc_output *output = encoder_to_lsdc_output(encoder);
> - struct lsdc_display_pipe *dispipe = output_to_display_pipe(output);
> - unsigned int index = dispipe->index;
> - struct drm_device *ddev = encoder->dev;
> - struct lsdc_device *ldev = to_lsdc(ddev);
> - struct drm_display_mode *mode = &crtc_state->mode;
> -
> - ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, index);
> -
> - ls7a2000_hdmi_set_avi_infoframe(encoder, mode);
> -
> - drm_dbg(ddev, "%s modeset finished\n", encoder->name);
> -}
> -
> -static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = {

If there are no references to this data piece, it means that it could
have been dropped in one of the earlier patches.

> - .atomic_disable = ls7a2000_hdmi_atomic_disable,
> - .atomic_enable = ls7a2000_hdmi_atomic_enable,
> - .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set,
> -};
> -
> /* The built-in tranparent VGA encoder is only available on display pipe 0 */
> static void ls7a2000_pipe0_vga_encoder_reset(struct drm_encoder *encoder)
> {
> @@ -621,16 +162,6 @@ ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
> return NULL;
> }
>
> -/*
> - * For LS7A2000:
> - *
> - * 1) Most of board export one vga + hdmi output interface.
> - * 2) Yet, Some boards export double hdmi output interface.
> - * 3) Still have boards export three output(2 hdmi + 1 vga).
> - *
> - * So let's hook hdmi helper funcs to all display pipe, don't miss.
> - * writing hdmi register do no harms.
> - */
> int ls7a2000_output_init(struct drm_device *ddev,
> struct lsdc_display_pipe *dispipe,
> struct i2c_adapter *ddc,
> --
> 2.34.1
>


--
With best wishes
Dmitry

2023-10-29 23:27:37

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 7/8] drm/loongson: Support to infer DC reversion from CPU's PRID value

On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>
> Due to the fact that the same display IP core has been integrated into
> different platform, there is a need to differentiate them on the runtime.
> The DC in LS7A1000/LS2K1000 has the PCI vendor & device ID of 0x0014:0x7A06
> The DC in LS7A2000/LS2K2000 has the PCI vendor & device ID of 0x0014:0x7A36
>
> Because the output ports and host platform of the DC IP varies, without a
> revision information we can't achieve fine-grained controls. The canonical
> approach to do such a thing is to read reversion register from the PCIe
> device. But LS2K1000 SoC was taped out at 2017, it is rather old. Our BIOS
> engineer don't assign a different revision ID to it, probably because of
> ignorance.
>
> LS2K2000 SoC was newly taped on 2023, we strictly force the BIOS engineer
> assign a different revision ID(0x10) to it. But the problem is that it is
> too casual, there is no formal convention or public documented rule
> established. For Loongson LS2K series SoC, the display controller IP is
> taped togather with the CPU core. For Loongson LS7A series bridge chips,
> the display controller IP is taped togather with the bridge chips itself.
> Consider the fact the all Loongson CPU has a unique PRID, this patch choose
> to infer DC reversion from CPU's PRID value.
>
> - LS3A4000/LS3A5000 has 0xC0 as its processor ID.
> - LS2K2000 has 0xB0 as its processor ID.
> - LS2K2000LA has 0xA0 as its processor ID.
>
> The provided approach has no dependency on DT or ACPI, thus is preferfed.
> Besides, this approach can be used to acquire more addtional HW features.
> So the provided method has the potential to bring more benifits.
>
> Signed-off-by: Sui Jingfeng <[email protected]>
> ---
> drivers/gpu/drm/loongson/lsdc_drv.h | 2 ++
> drivers/gpu/drm/loongson/lsdc_probe.c | 35 +++++++++++++++++++++++++++
> drivers/gpu/drm/loongson/lsdc_probe.h | 2 ++
> 3 files changed, 39 insertions(+)
>
> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
> index 46ba9b88a30d..e1f4a2db2a0a 100644
> --- a/drivers/gpu/drm/loongson/lsdc_drv.h
> +++ b/drivers/gpu/drm/loongson/lsdc_drv.h
> @@ -42,6 +42,8 @@
> enum loongson_chip_id {
> CHIP_LS7A1000 = 0,
> CHIP_LS7A2000 = 1,
> + CHIP_LS2K1000 = 2,
> + CHIP_LS2K2000 = 3,
> CHIP_LS_LAST,
> };
>
> diff --git a/drivers/gpu/drm/loongson/lsdc_probe.c b/drivers/gpu/drm/loongson/lsdc_probe.c
> index 48ba69bb8a98..f49b642d8f65 100644
> --- a/drivers/gpu/drm/loongson/lsdc_probe.c
> +++ b/drivers/gpu/drm/loongson/lsdc_probe.c
> @@ -54,3 +54,38 @@ unsigned int loongson_cpu_get_prid(u8 *imp, u8 *rev)
>
> return prid;
> }
> +
> +enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id)
> +{
> + u8 impl;
> +
> + if (loongson_cpu_get_prid(&impl, NULL)) {
> + /*
> + * LS2K2000 only has the LoongArch edition.
> + */
> + if (chip_id == CHIP_LS7A2000) {
> + if (impl == LOONGARCH_CPU_IMP_LS2K2000)
> + return CHIP_LS2K2000;
> + }
> +
> + /*
> + * LS2K1000 has the LoongArch edition(with two LA264 CPU core)
> + * and the Mips edition(with two mips64r2 CPU core), Only the
> + * instruction set of the CPU are changed, the peripheral
> + * devices are basically same.
> + */
> + if (chip_id == CHIP_LS7A1000) {
> +#if defined(__loongarch__)
> + if (impl == LOONGARCH_CPU_IMP_LS2K1000)
> + return CHIP_LS2K1000;
> +#endif
> +
> +#if defined(__mips__)
> + if (impl == LOONGSON_CPU_MIPS_IMP_LS2K)
> + return CHIP_LS2K1000;
> +#endif

Can you drop the ifdefs here? The code blocks do not seem to conflict
with each other.

> + }
> + }
> +
> + return chip_id;
> +}
> diff --git a/drivers/gpu/drm/loongson/lsdc_probe.h b/drivers/gpu/drm/loongson/lsdc_probe.h
> index 8bb6de2e3c64..8c630c5c90ce 100644
> --- a/drivers/gpu/drm/loongson/lsdc_probe.h
> +++ b/drivers/gpu/drm/loongson/lsdc_probe.h
> @@ -9,4 +9,6 @@
> /* Helpers for chip detection */
> unsigned int loongson_cpu_get_prid(u8 *impl, u8 *rev);
>
> +enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id);
> +
> #endif
> --
> 2.34.1
>


--
With best wishes
Dmitry

2023-10-30 00:13:23

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6-rc7 next-20231027]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-4-suijingfeng%40loongson.cn
patch subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()
config: loongarch-randconfig-002-20231030 (https://download.01.org/0day-ci/archive/20231030/[email protected]/config)
compiler: loongarch64-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231030/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/loongson/lsdc_output.c:555:5: warning: no previous prototype for 'lsdc_encoder_init' [-Wmissing-prototypes]
555 | int lsdc_encoder_init(struct drm_device *ddev,
| ^~~~~~~~~~~~~~~~~
>> drivers/gpu/drm/loongson/lsdc_output.c:578:5: warning: no previous prototype for 'lsdc_connector_init' [-Wmissing-prototypes]
578 | int lsdc_connector_init(struct drm_device *ddev,
| ^~~~~~~~~~~~~~~~~~~


vim +/lsdc_encoder_init +555 drivers/gpu/drm/loongson/lsdc_output.c

554
> 555 int lsdc_encoder_init(struct drm_device *ddev,
556 struct lsdc_output *output,
557 unsigned int pipe)
558 {
559 const struct lsdc_output_desc *descp = output->descp;
560 struct drm_encoder *encoder = &output->encoder;
561 int ret;
562
563 ret = drm_encoder_init(ddev,
564 encoder,
565 descp->encoder_funcs,
566 descp->encoder_type,
567 descp->name);
568 if (ret)
569 return ret;
570
571 encoder->possible_crtcs = BIT(pipe);
572
573 drm_encoder_helper_add(encoder, descp->encoder_helper_funcs);
574
575 return 0;
576 }
577
> 578 int lsdc_connector_init(struct drm_device *ddev,
579 struct lsdc_output *output,
580 struct i2c_adapter *ddc,
581 unsigned int pipe)
582 {
583 const struct lsdc_output_desc *descp = output->descp;
584 struct drm_connector *connector = &output->connector;
585 int ret;
586
587 ret = drm_connector_init_with_ddc(ddev,
588 connector,
589 descp->connector_funcs,
590 descp->connector_type,
591 ddc);
592 if (ret)
593 return ret;
594
595 drm_connector_helper_add(connector, descp->connector_helper_funcs);
596
597 drm_connector_attach_encoder(connector, &output->encoder);
598
599 connector->polled = DRM_CONNECTOR_POLL_CONNECT |
600 DRM_CONNECTOR_POLL_DISCONNECT;
601
602 connector->interlace_allowed = 0;
603 connector->doublescan_allowed = 0;
604
605 drm_info(ddev, "DisplayPipe-%u has %s\n", pipe, descp->name);
606
607 return 0;
608 }
609

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-10-30 02:35:13

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6-rc7 next-20231027]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-6-suijingfeng%40loongson.cn
patch subject: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization
config: loongarch-randconfig-002-20231030 (https://download.01.org/0day-ci/archive/20231030/[email protected]/config)
compiler: loongarch64-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231030/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/loongson/lsdc_output_7a2000.c:568:1: warning: no previous prototype for 'ls7a2000_query_output_configuration' [-Wmissing-prototypes]
568 | ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/loongson/lsdc_output_7a2000.c:498:46: warning: 'ls7a2000_encoder_helper_funcs' defined but not used [-Wunused-const-variable=]
498 | static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/loongson/lsdc_output_7a2000.c:272:39: warning: 'ls7a2000_encoder_funcs' defined but not used [-Wunused-const-variable=]
272 | static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = {
| ^~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/loongson/lsdc_output_7a2000.c:201:41: warning: 'ls7a2000_hdmi_connector_funcs' defined but not used [-Wunused-const-variable=]
201 | static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/gpu/drm/loongson/lsdc_output_7a2000.c:77:48: warning: 'ls7a2000_connector_helpers' defined but not used [-Wunused-const-variable=]
77 | static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = {
| ^~~~~~~~~~~~~~~~~~~~~~~~~~


vim +/ls7a2000_query_output_configuration +568 drivers/gpu/drm/loongson/lsdc_output_7a2000.c

559
560 /*
561 * For LS7A2000, the built-in VGA encoder is transparent. If there are
562 * external encoder exist, then the internal HDMI encoder MUST be enabled
563 * and initialized. As the internal HDMI encoder is always connected, so
564 * only the transmitters which take HDMI signal (such as HDMI to eDP, HDMI
565 * to LVDS, etc) are usable with.
566 */
567 const struct lsdc_output_desc *
> 568 ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
569 {
570 enum loongson_vbios_encoder_name encoder_name = 0;
571 bool ret;
572
573 ret = loongson_vbios_query_encoder_info(ddev, pipe, NULL,
574 &encoder_name, NULL);
575 if (!ret)
576 goto bailout;
577
578 if (pipe == 0) {
579 switch (encoder_name) {
580 case ENCODER_CHIP_INTERNAL_HDMI:
581 return &ls7a2000_hdmi_pipe0;
582
583 /*
584 * For LS7A2000, the built-in VGA encoder is transparent.
585 */
586 case ENCODER_CHIP_INTERNAL_VGA:
587 return &ls7a2000_vga_pipe0;
588
589 /*
590 * External display bridge exists, the internal HDMI encoder
591 * MUST be enabled and initialized. Please add a drm bridge
592 * driver, and attach to this encoder.
593 */
594 default:
595 return &ls7a2000_hdmi_pipe0;
596 }
597 }
598
599 if (pipe == 1) {
600 switch (encoder_name) {
601 case ENCODER_CHIP_INTERNAL_HDMI:
602 return &ls7a2000_hdmi_pipe1;
603
604 /*
605 * External display bridge exists, the internal HDMI encoder
606 * MUST be enabled and initialized. Please add a drm bridge
607 * driver, and attach it to this encoder.
608 */
609 default:
610 return &ls7a2000_hdmi_pipe1;
611 }
612 }
613
614 bailout:
615 if (pipe == 0)
616 return &ls7a2000_vga_pipe0;
617
618 if (pipe == 1)
619 return &ls7a2000_hdmi_pipe1;
620
621 return NULL;
622 }
623

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-10-30 03:21:17

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 1/8] drm/loongson: Introduce a minimal support for Loongson VBIOS

Hi,


Thanks a lot for reviewing!


On 2023/10/30 06:59, Dmitry Baryshkov wrote:
> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>> Because some boards are equipped with non-transparent display bridges,
>> which need the VBIOS to provided parameters.
>>
>> Signed-off-by: Sui Jingfeng <[email protected]>
>> ---
>> drivers/gpu/drm/loongson/Makefile | 3 +-
>> drivers/gpu/drm/loongson/loongson_device.c | 4 +
>> drivers/gpu/drm/loongson/loongson_vbios.c | 420 +++++++++++++++++++++
>> drivers/gpu/drm/loongson/loongson_vbios.h | 59 +++
>> drivers/gpu/drm/loongson/lsdc_drv.c | 4 +
>> drivers/gpu/drm/loongson/lsdc_drv.h | 8 +
>> 6 files changed, 497 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.c
>> create mode 100644 drivers/gpu/drm/loongson/loongson_vbios.h
>>
>> diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile
>> index 91e72bd900c1..bef00b2c5569 100644
>> --- a/drivers/gpu/drm/loongson/Makefile
>> +++ b/drivers/gpu/drm/loongson/Makefile
>> @@ -17,6 +17,7 @@ loongson-y := \
>> lsdc_ttm.o
>>
>> loongson-y += loongson_device.o \
>> - loongson_module.o
>> + loongson_module.o \
>> + loongson_vbios.o
>>
>> obj-$(CONFIG_DRM_LOONGSON) += loongson.o
>> diff --git a/drivers/gpu/drm/loongson/loongson_device.c b/drivers/gpu/drm/loongson/loongson_device.c
>> index 9986c8a2a255..64096ad5466e 100644
>> --- a/drivers/gpu/drm/loongson/loongson_device.c
>> +++ b/drivers/gpu/drm/loongson/loongson_device.c
>> @@ -7,6 +7,8 @@
>>
>> #include "lsdc_drv.h"
>>
>> +extern struct loongson_vbios __loongson_vbios;
> Usually names with two underscores in front of them are reserved for
> the compiler internals or low level stuff.


Then, is singleunderscore(_loongson_vbios) OK ?

I'm using underscores because I want to tell that the __loongson_vbios is opaque handle,
I want to tell that this is nearly a internals stuff, outside program should not poke into it.
Outside program can only reference it and should use helpers created in loongson_vbios.c to access.

Despite "extern-ed", but it just to take a reference of it. not de-reference.
If a specific SoC don't has VBIOS support, we can use .vbios = NULL; instead.
which is known at compile-time.

If singleunderscore(_loongson_vbios) is OK?
has a underscore denote that it is a variable, probably helpful to understand.
If OK, I will update it to singleunderscore at the next version.



>> +
>> static const struct lsdc_kms_funcs ls7a1000_kms_funcs = {
>> .create_i2c = lsdc_create_i2c_chan,
>> .irq_handler = ls7a1000_dc_irq_handler,
>> @@ -53,6 +55,7 @@ static const struct loongson_gfx_desc ls7a1000_gfx = {
>> .reg_size = 8,
>> },
>> },
>> + .vbios = &__loongson_vbios,
>> .chip_id = CHIP_LS7A1000,
>> .model = "LS7A1000 bridge chipset",
>> };
>> @@ -85,6 +88,7 @@ static const struct loongson_gfx_desc ls7a2000_gfx = {
>> .reg_size = 8,
>> },
>> },
>> + .vbios = &__loongson_vbios,
>> .chip_id = CHIP_LS7A2000,
>> .model = "LS7A2000 bridge chipset",
>> };
>> diff --git a/drivers/gpu/drm/loongson/loongson_vbios.c b/drivers/gpu/drm/loongson/loongson_vbios.c
>> new file mode 100644
>> index 000000000000..dc304018779e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/loongson/loongson_vbios.c
>> @@ -0,0 +1,420 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>> + */
>> +
>> +#include <drm/drm_device.h>
>> +#include <drm/drm_managed.h>
>> +
>> +#include "loongson_vbios.h"
>> +#include "lsdc_drv.h"
>> +
>> +#define LOONGSON_VBIOS_HEADER_STR "Loongson-VBIOS"
>> +/* Legacy VBIOS is stored at offset 0 */
>> +#define LOONGSON_VBIOS_LEGACY_OFFSET 0
>> +/* The size of legacy VBIOS is 1 KiB */
>> +#define LOONGSON_VBIOS_LEGACY_SIZE 0x000400
>> +
>> +/* Data Control Block of Newer version of the VBIOS started at here */
>> +#define LOONGSON_VBIOS_DCB_OFFSET 0x006000
>> +/* The last 1 MiB of the VRAM contains the raw VBIOS data */
>> +#define LOONGSON_VBIOS_BLOCK_OFFSET 0x100000
>> +/* Only 256KB of the 1 MiB are used for now */
>> +#define LOONGSON_VBIOS_BLOCK_SIZE 0x040000
>> +
>> +struct loongson_vbios __loongson_vbios;
>> +
>> +/*
>> + * vbios data control block is a kind of metadata, which is used to index
>> + * real hardware device data block.
>> + */
>> +struct loongson_vbios_dcb {
>> + u16 type; /* what is it */
>> + u8 version; /* version of it, useless */
>> + u8 id; /* index (usually same with the display pipe) of the hardware */
>> + u32 offset; /* the offset of the real data */
>> + u32 size; /* the size of the real data */
>> + u64 ext0; /* for extension purpose */
>> + u64 ext1; /* extra space reserved for future use */
>> +} __packed;
>> +
>> +/*
>> + * Loongson-VBIOS Data Block Layout
>> + *
>> + *
>> + * _____________________ 0x00000
>> + * |_____________________|
>> + * | | [0x0000, 0x0400) : legacy vbios storage
>> + * | Not Used Yet |
>> + * | |
>> + * |---------------------|<------- 0x6000
>> + * +----| DCB 0 |
>> + * | |---------------------|
>> + * | | DCB 1 |
>> + * | |---------------------| Format of Data Control Blocks
>> + * | | One by one, packed | +------------+
>> + * | |---------------------| | u16 type |
>> + * | | DCB N |----+ | |
>> + * | |---------------------| | +------------+
>> + * | | . | | | u8 version |
>> + * | | . | | | u8 index |
>> + * | | . | | +------------+
>> + * | |---------------------| | | |
>> + * | | DCB end | | | u32 offset |
>> + * | |---------------------| | +------- |
>> + * | | | | | | |
>> + * | |_____________________| | | +------------+
>> + * | |_____________________| | | | |
>> + * | | | | | | u32 size |
>> + * +--->| vbios header info | | | | -------+
>> + * |_____________________| | | | | |
>> + * | . | | | +------------+ |
>> + * | . | | | | useless | |
>> + * | . | | | | members | |
>> + * |_____________________| | | +------------+ |
>> + * | | | | |
>> + * | encoders info |<---+ | |
>> + * |_____________________| | |
>> + * | | ___| |
>> + * |_____________________|____/ |
>> + * | | |
>> + * | Something | |
>> + * |_____________________|_________________ |
>> + * | | | |
>> + * | EDID | |<--------------+
>> + * |_____________________|_____________|___
>> + * | |
>> + * | | Contents of those device specific data
>> + * | GPU specific info | block are implement-defined and version
>> + * | | dependent :0
>> + * |_____________________|
>> + * / . /
>> + * / . /
>> + * / . /
>> + * |_____________________| 0x040000
>> + *
>> + */
>> +
>> +enum loongson_vbios_dcb_type {
>> + LV_DCB_HEADER = 0,
>> + LV_DCB_CRTC = 1,
>> + LV_DCB_ENCODER = 2,
>> + LV_DCB_CONNECTOR = 3,
>> + LV_DCB_I2C = 4,
>> + LV_DCB_PWM = 5,
>> + LV_DCB_GPIO = 6,
>> + LV_DCB_BACKLIGHT = 7,
>> + LV_DCB_FAN = 8,
>> + LV_DCB_IRQ = 9,
>> + LV_DCB_ENCODER_CFG = 10,
>> + LV_DCB_ENCODER_RES = 11,
>> + LV_DCB_GPU = 12,
>> + LV_DCB_UNKNOWN = 13,
>> + LV_DCB_END = 0xffff,
>> +};
>> +
>> +struct loongson_vbios_header {
>> + char header[16];
>> + u32 version_major;
> Please specify whether this is BE or LE. (__be32 or __le32). Usually
> we use le32_to_cpu / be32_to_cpu helpers to access external data.
>
Yes, you are right.

But all Loongson CPU are Little Endian, we take it for granted by default.
Use le32_to_cpu() seems unnecessary?

In principle, you are right.
But when I started to do it, I found the actual difficult is that
I don't have a environment(BIG Endian machine) to test. I means that
it is probably better wait the bug happen. Otherwise, I don't know
where to add le32_to_cpu().



>> + u32 version_minor;
>> + char information[20];
>> + u32 num_crtc;
>> + u32 crtc_offset;
>> + u32 num_connector;
>> + u32 connector_offset;
>> + u32 num_encoder;
>> + u32 encoder_offset;
>> +} __packed;
>> +
>> +struct loongson_vbios_encoder {
>> + u32 feature;
>> + u32 i2c_id;
>> + u32 connector_id;
>> + u32 type;
>> + u32 config_method;
>> + u32 chip_id;
>> + u8 chip_addr;
>> +} __packed;
>> +
>> +struct loongson_vbios_connector {
>> + u32 feature;
>> + u32 i2c_id;
>> + u8 edid[256];
>> + u32 type;
>> + u32 hotplug_method;
>> + u32 edid_method;
>> + u32 hpd_int_gpio;
>> + u32 gpio_place;
>> +} __packed;
>> +
>> +/*
>> + * A list node which contains the information about the device specific data
>> + * block, the device here refer to the property or topology of hardware
>> + * configuration, such as external display bridges, HDP GPIO, connectors etc.
>> + */
>> +struct loongson_vbios_node {
>> + struct list_head head;
>> +
>> + /* @type: the type of the data. For search */
>> + u32 type;
>> + /* @id: the index(display pipe) of the data belong to. For search */
>> + u32 id;
>> + /*
>> + * @data: point to the device specific data block, such as external
>> + * encoders name and it's i2c device address, hpd gpio resource etc.
>> + */
>> + const void *data;
>> + /*
>> + * The size of the data.
>> + */
>> + u32 size;
>> +};
>> +
>> +/*
>> + * The returned pointer is actually point to &__loongson_vbios, but this
>> + * function is only intended to provide READ-ONLY access. As our vbios is
>> + * only be able to pass(provide) parameters, it is not executable and outside
>> + * should not modify it.
>> + */
>> +const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev)
>> +{
>> + struct lsdc_device *ldev = to_lsdc(ddev);
>> + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp);
>> +
>> + return gfx->vbios;
>> +}
>> +
>> +static bool loongson_vbios_is_valid(const struct loongson_vbios *vbios)
>> +{
>> + char header[32];
>> +
>> + memcpy(header, vbios->raw_data, sizeof(header));
>> +
>> + if (strcmp(header, LOONGSON_VBIOS_HEADER_STR))
>> + return false;
>> +
>> + return true;
>> +}
>> +
>> +/*
>> + * The VBIOS blob is stored at the last 1 MiB of the VRAM, no SPI flush or
> flash?


Yes, thanks a lot. Will be fixed at the next version.


>> + * EEPROM is needed. Platform BIOS is responsible for store this VBIOS blob
> storing


Thanks a lot,  :-)


>> + * data at right position on per boot time.
>> + */
>> +static int loongson_vbios_construct(struct drm_device *ddev,
>> + struct loongson_vbios *vbios)
>> +{
>> + struct lsdc_device *ldev = to_lsdc(ddev);
>> + u64 vram_end = ldev->vram_base + ldev->vram_size;
>> + u64 vbios_start = vram_end - LOONGSON_VBIOS_BLOCK_OFFSET;
>> + void __iomem *ptr;
>> +
>> + vbios->raw_data = kzalloc(LOONGSON_VBIOS_BLOCK_SIZE, GFP_KERNEL);
>> + if (!vbios->raw_data)
>> + return -ENOMEM;
>> +
>> + ptr = ioremap(vbios_start, LOONGSON_VBIOS_BLOCK_SIZE);
>> + if (!ptr) {
>> + drm_err(ddev, "Map VBIOS region failed\n");
>> + return -ENOMEM;
>> + }
>> +
>> + memcpy_fromio(vbios->raw_data, ptr, LOONGSON_VBIOS_BLOCK_SIZE);
>> +
>> + iounmap(ptr);
>> +
>> + INIT_LIST_HEAD(&vbios->list);
>> + vbios->ddev = ddev;
>> +
>> + return 0;
>> +}
>> +
>> +static void loongson_vbios_destruct(struct drm_device *ddev, void *data)
> destroy or free. Also you can use drmm_ functions to make destructor
> unnecessary.


Originally, I not sure if drmm_ functions suffer from order problem.
I means some data should to be freed first, other data need to be freed after.
free it by myself is more safe and can explicit control.
OK, I will try you advice.


>> +{
>> + struct loongson_vbios *vbios = (struct loongson_vbios *)data;
>> + struct loongson_vbios_node *node;
>> + struct loongson_vbios_node *tmp;
>> +
>> + list_for_each_entry_safe(node, tmp, &vbios->list, head) {
>> + list_del(&node->head);
>> + kfree(node);
>> + }
>> +
>> + kfree(vbios->raw_data);
>> + vbios->raw_data = NULL;
>> +}
>> +
>> +static void loongson_vbios_print_dcb(struct drm_device *ddev,
>> + struct loongson_vbios_dcb *dcb)
>> +{
>> + drm_info(ddev, "type: %u, Offset: %u, Size: %u, version: %u, ID: %u\n",
>> + dcb->type, dcb->offset, dcb->size, dcb->version, dcb->id);
>> +}
>> +
>> +/*
>> + * Process the data control block, establish a list for later searching.
>> + * returns the number of data control block. Generally, loongson vbios
>> + * has only 10 DCB or so.
>> + */
>> +static int loongson_vbios_process_dcb(struct loongson_vbios *vbios,
>> + bool verbose)
>> +{
>> + struct drm_device *ddev = vbios->ddev;
>> + void *base = vbios->raw_data;
>> + int count = 0;
>> + struct loongson_vbios_dcb *dcb;
>> +
>> + dcb = (struct loongson_vbios_dcb *)(base + LOONGSON_VBIOS_DCB_OFFSET);
>> +
>> + while (dcb->type != LV_DCB_END) {
>> + struct loongson_vbios_node *node;
>> +
>> + node = kzalloc(sizeof(*node), GFP_KERNEL);
>> + if (!node)
>> + return -ENOMEM;
>> +
>> + node->type = dcb->type;
>> + node->id = dcb->id;
>> + node->data = base + dcb->offset;
>> + node->size = dcb->size;
>> +
>> + list_add_tail(&node->head, &vbios->list);
>> +
>> + if (verbose)
>> + loongson_vbios_print_dcb(ddev, dcb);
>> +
>> + ++dcb;
>> +
>> + if (++count > 1024) {
>> + drm_err(ddev, "Unlikely, DCB is too much\n");
>> + break;
>> + }
>> + }
>> +
>> + return count;
>> +}
>> +
>> +static const struct loongson_vbios_node *
>> +loongson_vbios_get_node(struct drm_device *ddev, u32 type, u32 id)
>> +{
>> + const struct loongson_vbios *vbios = to_loongson_vbios(ddev);
>> + struct loongson_vbios_node *np;
>> +
>> + if (!vbios)
>> + return NULL;
>> +
>> + list_for_each_entry(np, &vbios->list, head) {
>> + if (np->type == type && np->id == id)
>> + return np;
>> + }
>> +
>> + return NULL;
>> +}
>> +
>> +bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
>> + u32 pipe,
>> + u32 *type,
>> + enum loongson_vbios_encoder_name *name,
>> + u8 *i2c_addr)
>> +{
>> + const struct loongson_vbios_encoder *vencoder;
>> + const struct loongson_vbios_node *np;
>> +
>> + np = loongson_vbios_get_node(ddev, LV_DCB_ENCODER, pipe);
>> + if (!np)
>> + return false;
>> +
>> + if (np->size != sizeof(*vencoder))
>> + WARN_ON(1);
>> +
>> + vencoder = (const struct loongson_vbios_encoder *)np->data;
>> +
>> + if (type)
>> + *type = vencoder->type;
>> +
>> + if (name)
>> + *name = vencoder->chip_id;
>> +
>> + /* i2c address, as a slave device */
>> + if (i2c_addr)
>> + *i2c_addr = vencoder->chip_addr;
>> +
>> + return true;
>> +}
>> +
>> +bool loongson_vbios_query_connector_info(struct drm_device *ddev,
>> + u32 pipe,
>> + u32 *connector_type,
>> + u32 *hpd_method,
>> + u32 *int_gpio,
>> + u8 *edid_blob)
>> +{
>> + const struct loongson_vbios_connector *vconnector;
>> + const struct loongson_vbios_node *np;
>> +
>> + np = loongson_vbios_get_node(ddev, LV_DCB_CONNECTOR, pipe);
>> + if (!np)
>> + return false;
>> +
>> + if (np->size != sizeof(*vconnector))
>> + WARN_ON(1);
>> +
>> + vconnector = (const struct loongson_vbios_connector *)np->data;
>> +
>> + if (connector_type)
>> + *connector_type = vconnector->type;
>> +
>> + if (edid_blob)
>> + memcpy(edid_blob, vconnector->edid, 256);
>> +
>> + if (int_gpio)
>> + *int_gpio = vconnector->hpd_int_gpio;
>> +
>> + return true;
>> +}
>> +
>> +static void loongson_vbios_acquire_version(struct drm_device *ddev,
>> + struct loongson_vbios *vbios)
>> +{
>> + struct loongson_vbios_header *vh;
>> +
>> + vh = (struct loongson_vbios_header *)vbios->raw_data;
>> +
>> + vbios->version_major = vh->version_major;
>> + vbios->version_minor = vh->version_minor;
>> +
>> + drm_info(ddev, "Loongson VBIOS version: %u.%u\n",
>> + vh->version_major, vh->version_minor);
>> +}
>> +
>> +int loongson_vbios_init(struct drm_device *ddev)
>> +{
>> + struct loongson_vbios *vbios = &__loongson_vbios;
>> + int ret;
>> + int num;
>> +
>> + ret = loongson_vbios_construct(ddev, vbios);
>> + if (ret)
>> + return ret;
>> +
>> + ret = drmm_add_action_or_reset(ddev, loongson_vbios_destruct, vbios);
>> + if (ret)
>> + return ret;
>> +
>> + if (!loongson_vbios_is_valid(vbios)) {
>> + drm_err(ddev, "Loongson VBIOS: header is invalid\n");
>> + return -EINVAL;
>> + }
>> +
>> + loongson_vbios_acquire_version(ddev, vbios);
> get_version.
OK, no problem.
>> +
>> + num = loongson_vbios_process_dcb(vbios, false);
>> + if (num <= 0) {
>> + drm_err(ddev, "Loongson VBIOS: Process DCB failed\n");
>> + return -EINVAL;
>> + }
>> +
>> + drm_info(ddev, "Loongson VBIOS: has %d DCBs\n", num);
>> +
>> + return 0;
>> +}
>> diff --git a/drivers/gpu/drm/loongson/loongson_vbios.h b/drivers/gpu/drm/loongson/loongson_vbios.h
>> new file mode 100644
>> index 000000000000..66fb43b3609e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/loongson/loongson_vbios.h
>> @@ -0,0 +1,59 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright (C) 2023 Loongson Technology Corporation Limited
>> + */
>> +
>> +#ifndef __LOONGSON_VBIOS_H__
>> +#define __LOONGSON_VBIOS_H__
>> +
>> +#include <drm/drm_device.h>
>> +
>> +struct loongson_vbios {
>> + struct list_head list;
>> + void *raw_data;
>> + struct drm_device *ddev;
>> + u32 version_major;
>> + u32 version_minor;
>> +};
>> +
>> +enum loongson_vbios_encoder_name {
>> + ENCODER_CHIP_UNKNOWN = 0x00,
>> + ENCODER_CHIP_INTERNAL_VGA = 0x01,
>> + ENCODER_CHIP_INTERNAL_HDMI = 0x02,
>> + ENCODER_CHIP_CH7055 = 0x10,
>> + ENCODER_CHIP_ADV7125 = 0x11,
>> + ENCODER_CHIP_TFP410 = 0x20,
>> + ENCODER_CHIP_IT66121 = 0x30,
>> + ENCODER_CHIP_SIL9022 = 0x31,
>> + ENCODER_CHIP_LT8618 = 0x32,
>> + ENCODER_CHIP_MS7210 = 0x33,
>> + ENCODER_CHIP_NCS8805 = 0x40,
>> + ENCODER_CHIP_LT9721 = 0x42,
>> + ENCODER_CHIP_LT6711 = 0x43,
>> + ENCODER_CHIP_LT8619 = 0x50,
>> +};
>> +
>> +enum loongson_vbios_hotplug_method {
>> + LV_HPD_DISABLED = 0,
>> + LV_HPD_POLLING = 1,
>> + LV_HPD_IRQ = 2,
>> +};
>> +
>> +const struct loongson_vbios *to_loongson_vbios(struct drm_device *ddev);
>> +
>> +bool loongson_vbios_query_encoder_info(struct drm_device *ddev,
>> + u32 pipe,
>> + u32 *type,
>> + enum loongson_vbios_encoder_name *name,
>> + u8 *i2c_addr);
>> +
>> +bool loongson_vbios_query_connector_info(struct drm_device *ddev,
>> + u32 pipe,
>> + u32 *connector_type,
>> + u32 *hpd_method,
>> + u32 *int_gpio,
>> + u8 *edid_blob);
>> +
>> +int loongson_vbios_init(struct drm_device *ddev);
>> +
>> +#endif
>> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c
>> index 89ccc0c43169..aebb200fa567 100644
>> --- a/drivers/gpu/drm/loongson/lsdc_drv.c
>> +++ b/drivers/gpu/drm/loongson/lsdc_drv.c
>> @@ -213,6 +213,10 @@ lsdc_create_device(struct pci_dev *pdev,
>> return ERR_PTR(ret);
>> }
>>
>> + ret = loongson_vbios_init(ddev);
>> + if (ret)
>> + drm_info(ddev, "No VBIOS support\n");
>> +
>> ret = drm_aperture_remove_conflicting_framebuffers(ldev->vram_base,
>> ldev->vram_size,
>> driver);
>> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
>> index fbf2d760ef27..335953c988d1 100644
>> --- a/drivers/gpu/drm/loongson/lsdc_drv.h
>> +++ b/drivers/gpu/drm/loongson/lsdc_drv.h
>> @@ -16,6 +16,7 @@
>> #include <drm/drm_plane.h>
>> #include <drm/ttm/ttm_device.h>
>>
>> +#include "loongson_vbios.h"
>> #include "lsdc_i2c.h"
>> #include "lsdc_irq.h"
>> #include "lsdc_gfxpll.h"
>> @@ -85,6 +86,13 @@ struct loongson_gfx_desc {
>> u32 reg_size;
>> } pixpll[LSDC_NUM_CRTC];
>>
>> + /*
>> + * @vbios: Provide information about the output configuration,
>> + * and provide information about dynamic features which cannot
>> + * be detected(determined) with the chip_id.
>> + */
>> + const struct loongson_vbios *vbios;
>> +
>> enum loongson_chip_id chip_id;
>> char model[64];
>> };
>> --
>> 2.34.1
>>
>

2023-10-30 04:13:54

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi,


On 2023/10/30 07:10, Dmitry Baryshkov wrote:
>> +
>> +/* Built-in HDMI encoder funcs on display pipe 0 */
>> +
>> +static void lsdc_pipe0_hdmi_encoder_reset(struct drm_encoder *encoder)
>> +{
>> + struct drm_device *ddev = encoder->dev;
>> + struct lsdc_device *ldev = to_lsdc(ddev);
>> + u32 val;
>> +
>> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
>> + lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
>> +
>> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
>> + lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HW_I2C_EN);
>> +
>> + /* Help the HDMI phy get out of reset state */
>> + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
>> +
>> + drm_dbg(ddev, "%s reset\n", encoder->name);
>> +
>> + mdelay(20);
>> +}
>> +
>> +const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs = {
>> + .reset = lsdc_pipe0_hdmi_encoder_reset,
>> + .destroy = drm_encoder_cleanup,
>> +};
>> +
>> +/* Built-in HDMI encoder funcs on display pipe 1 */
> All pipe 1 code looks like a pipe0, just the registers were changed.
> Could you please refactor that to use a single instance of all
> functions and pass pipe id through the data struct?
> Then you can use macros to determine whether to use pipe 0 or pipe 1 register.
>

Yes, you are right. But please allow me to explain something.

In the past, Thomas told me to untangle it, despite this idea lead to duplicated code(or pattern).
but at the long run, this pay off.

Because the method of passing pipe id will introduce the "if and else" side effects.
But my functions have no if and else.


```
if (pipe == 0) {
...
} else if (pipe == 1) {
...
}
```

Using the C program language's Macro(#define XXX) to generate code is not fun to me.
Because every time you want to change it, It needs my brains to thinking it twice. Maybe
more than twice.

1) It needs my brains to replace the macros manually each time I saw the code.

2) When I want to change(alter) the prototype, I need to worry about all of the instances.
sometimes it is not symmetry. The DVO port and HDMI phy itself is symmetry, but the
external display bridge connected with them are not symmetry. So, there are some registers
located at the domain of this display controller side should change according to the
different type of external display bridge.

3) Code duplication is actually less harmful than unmaintainable.
macros are abstract, as noob level programmer, we completely drop the idea of abstract.
Bad abstract means design failure, this is what we are most afraid of.
Generally, we would like divide the whole into small cases, handle them one by one.
It is actually to review and understand.

4) From the viewpoint of the hardware, the display output hardware suffer from changes.
Because users always want *new* display interface. The need of the users are also varies.
Personally, I think macros are best for the symmetry case, while the output part of a
display pipe always suffer from change.

>> +
>> +static void lsdc_pipe1_hdmi_encoder_reset(struct drm_encoder *encoder)
>> +{
>> + struct drm_device *ddev = encoder->dev;
>> + struct lsdc_device *ldev = to_lsdc(ddev);
>> + u32 val;
>> +
>> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
>> + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
>> +
>> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
>> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HW_I2C_EN);
>> +
>> + /* Help the HDMI phy get out of reset state */
>> + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
>> +
>> + drm_dbg(ddev, "%s reset\n", encoder->name);
>> +
>> + mdelay(20);
>> +}
>> +
>> +const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs = {
>> + .reset = lsdc_pipe1_hdmi_encoder_reset,
>> + .destroy = drm_encoder_cleanup,
>> +};

2023-10-30 08:51:37

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 7/8] drm/loongson: Support to infer DC reversion from CPU's PRID value

Hi,

On 2023/10/30 07:19, Dmitry Baryshkov wrote:
> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>> Due to the fact that the same display IP core has been integrated into
>> different platform, there is a need to differentiate them on the runtime.
>> The DC in LS7A1000/LS2K1000 has the PCI vendor & device ID of 0x0014:0x7A06
>> The DC in LS7A2000/LS2K2000 has the PCI vendor & device ID of 0x0014:0x7A36
>>
>> Because the output ports and host platform of the DC IP varies, without a
>> revision information we can't achieve fine-grained controls. The canonical
>> approach to do such a thing is to read reversion register from the PCIe
>> device. But LS2K1000 SoC was taped out at 2017, it is rather old. Our BIOS
>> engineer don't assign a different revision ID to it, probably because of
>> ignorance.
>>
>> LS2K2000 SoC was newly taped on 2023, we strictly force the BIOS engineer
>> assign a different revision ID(0x10) to it. But the problem is that it is
>> too casual, there is no formal convention or public documented rule
>> established. For Loongson LS2K series SoC, the display controller IP is
>> taped togather with the CPU core. For Loongson LS7A series bridge chips,
>> the display controller IP is taped togather with the bridge chips itself.
>> Consider the fact the all Loongson CPU has a unique PRID, this patch choose
>> to infer DC reversion from CPU's PRID value.
>>
>> - LS3A4000/LS3A5000 has 0xC0 as its processor ID.
>> - LS2K2000 has 0xB0 as its processor ID.
>> - LS2K2000LA has 0xA0 as its processor ID.
>>
>> The provided approach has no dependency on DT or ACPI, thus is preferfed.
>> Besides, this approach can be used to acquire more addtional HW features.
>> So the provided method has the potential to bring more benifits.
>>
>> Signed-off-by: Sui Jingfeng <[email protected]>
>> ---
>> drivers/gpu/drm/loongson/lsdc_drv.h | 2 ++
>> drivers/gpu/drm/loongson/lsdc_probe.c | 35 +++++++++++++++++++++++++++
>> drivers/gpu/drm/loongson/lsdc_probe.h | 2 ++
>> 3 files changed, 39 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h
>> index 46ba9b88a30d..e1f4a2db2a0a 100644
>> --- a/drivers/gpu/drm/loongson/lsdc_drv.h
>> +++ b/drivers/gpu/drm/loongson/lsdc_drv.h
>> @@ -42,6 +42,8 @@
>> enum loongson_chip_id {
>> CHIP_LS7A1000 = 0,
>> CHIP_LS7A2000 = 1,
>> + CHIP_LS2K1000 = 2,
>> + CHIP_LS2K2000 = 3,
>> CHIP_LS_LAST,
>> };
>>
>> diff --git a/drivers/gpu/drm/loongson/lsdc_probe.c b/drivers/gpu/drm/loongson/lsdc_probe.c
>> index 48ba69bb8a98..f49b642d8f65 100644
>> --- a/drivers/gpu/drm/loongson/lsdc_probe.c
>> +++ b/drivers/gpu/drm/loongson/lsdc_probe.c
>> @@ -54,3 +54,38 @@ unsigned int loongson_cpu_get_prid(u8 *imp, u8 *rev)
>>
>> return prid;
>> }
>> +
>> +enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id)
>> +{
>> + u8 impl;
>> +
>> + if (loongson_cpu_get_prid(&impl, NULL)) {
>> + /*
>> + * LS2K2000 only has the LoongArch edition.
>> + */
>> + if (chip_id == CHIP_LS7A2000) {
>> + if (impl == LOONGARCH_CPU_IMP_LS2K2000)
>> + return CHIP_LS2K2000;
>> + }
>> +
>> + /*
>> + * LS2K1000 has the LoongArch edition(with two LA264 CPU core)
>> + * and the Mips edition(with two mips64r2 CPU core), Only the
>> + * instruction set of the CPU are changed, the peripheral
>> + * devices are basically same.
>> + */
>> + if (chip_id == CHIP_LS7A1000) {
>> +#if defined(__loongarch__)
>> + if (impl == LOONGARCH_CPU_IMP_LS2K1000)
>> + return CHIP_LS2K1000;
>> +#endif
>> +
>> +#if defined(__mips__)
>> + if (impl == LOONGSON_CPU_MIPS_IMP_LS2K)
>> + return CHIP_LS2K1000;
>> +#endif
> Can you drop the ifdefs here? The code blocks do not seem to conflict
> with each other.

OK, no problem. Will be fixed at the next version.

>> + }
>> + }
>> +
>> + return chip_id;
>> +}
>> diff --git a/drivers/gpu/drm/loongson/lsdc_probe.h b/drivers/gpu/drm/loongson/lsdc_probe.h
>> index 8bb6de2e3c64..8c630c5c90ce 100644
>> --- a/drivers/gpu/drm/loongson/lsdc_probe.h
>> +++ b/drivers/gpu/drm/loongson/lsdc_probe.h
>> @@ -9,4 +9,6 @@
>> /* Helpers for chip detection */
>> unsigned int loongson_cpu_get_prid(u8 *impl, u8 *rev);
>>
>> +enum loongson_chip_id loongson_chip_id_fixup(enum loongson_chip_id chip_id);
>> +
>> #endif
>> --
>> 2.34.1
>>
>

2023-10-30 08:54:07

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi,

On 2023/10/30 12:13, Sui Jingfeng wrote:
> handle them one by one.
>    It is actually to review and understand.


Handle the cases one by one.

It is actually more easier to review the untangled code.
It is also more easier to edit and maintain.

2023-10-30 09:14:11

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi,


On 2023/10/30 07:10, Dmitry Baryshkov wrote:
>> +
>> +static void lsdc_pipe1_hdmi_encoder_reset(struct drm_encoder *encoder)
>> +{
>> + struct drm_device *ddev = encoder->dev;
>> + struct lsdc_device *ldev = to_lsdc(ddev);
>> + u32 val;
>> +
>> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
>> + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
>> +
>> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
>> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HW_I2C_EN);
>> +
>> + /* Help the HDMI phy get out of reset state */
>> + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
>> +
>> + drm_dbg(ddev, "%s reset\n", encoder->name);
>> +
>> + mdelay(20);
>> +}
>> +
>> +const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs = {
>> + .reset = lsdc_pipe1_hdmi_encoder_reset,
>> + .destroy = drm_encoder_cleanup,
>> +};
>> +
>> +/* Built-in DVO encoder helper funcs */
>> +
>> +static void lsdc_dvo_atomic_disable(struct drm_encoder *encoder,
>> + struct drm_atomic_state *state)
>> +{
>> +}
>> +
>> +static void lsdc_dvo_atomic_enable(struct drm_encoder *encoder,
>> + struct drm_atomic_state *state)
>> +{
>> +}
>> +
>> +static void lsdc_dvo_atomic_modeset(struct drm_encoder *encoder,
>> + struct drm_crtc_state *crtc_state,
>> + struct drm_connector_state *conn_state)
>> +{
>> +}
> You do not need these empty callbacks, do you?


Thanks a lot for the sharpen eyes.

Yes. I do not need these empty callbacks.
I have some program experience with Linux-4.19 kernel.
I clearly remember that these empty callbacks was need when I add lt8619 display bridge driver.
I have just remove it and tested, it seems that those is not need anymore.

Thanks a lot for review, this problem will be fixed at the next version.




2023-10-30 09:17:23

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

On Mon, 30 Oct 2023 at 06:13, Sui Jingfeng <[email protected]> wrote:
>
> Hi,
>
>
> On 2023/10/30 07:10, Dmitry Baryshkov wrote:
> >> +
> >> +/* Built-in HDMI encoder funcs on display pipe 0 */
> >> +
> >> +static void lsdc_pipe0_hdmi_encoder_reset(struct drm_encoder *encoder)
> >> +{
> >> + struct drm_device *ddev = encoder->dev;
> >> + struct lsdc_device *ldev = to_lsdc(ddev);
> >> + u32 val;
> >> +
> >> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> >> + lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val);
> >> +
> >> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
> >> + lsdc_ureg32_clr(ldev, LSDC_HDMI0_INTF_CTRL_REG, HW_I2C_EN);
> >> +
> >> + /* Help the HDMI phy get out of reset state */
> >> + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> >> +
> >> + drm_dbg(ddev, "%s reset\n", encoder->name);
> >> +
> >> + mdelay(20);
> >> +}
> >> +
> >> +const struct drm_encoder_funcs lsdc_pipe0_hdmi_encoder_funcs = {
> >> + .reset = lsdc_pipe0_hdmi_encoder_reset,
> >> + .destroy = drm_encoder_cleanup,
> >> +};
> >> +
> >> +/* Built-in HDMI encoder funcs on display pipe 1 */
> > All pipe 1 code looks like a pipe0, just the registers were changed.
> > Could you please refactor that to use a single instance of all
> > functions and pass pipe id through the data struct?
> > Then you can use macros to determine whether to use pipe 0 or pipe 1 register.
> >
>
> Yes, you are right. But please allow me to explain something.
>
> In the past, Thomas told me to untangle it, despite this idea lead to duplicated code(or pattern).
> but at the long run, this pay off.
>
> Because the method of passing pipe id will introduce the "if and else" side effects.
> But my functions have no if and else.
>
>
> ```
> if (pipe == 0) {
> ...
> } else if (pipe == 1) {
> ...
> }
> ```

I was thinking about something easier:

static void lsdc_pipe_hdmi_encoder_reset(struct drm_encoder *encoder)
{
....
lsdc_wreg32(ldev, LSDC_CRTCn_DVO_CONF_REG(ldev->pipe_id), val);
...
};

So, no ifs, just define per-pipe registers.


>
> Using the C program language's Macro(#define XXX) to generate code is not fun to me.
> Because every time you want to change it, It needs my brains to thinking it twice. Maybe
> more than twice.
>
> 1) It needs my brains to replace the macros manually each time I saw the code.
>
> 2) When I want to change(alter) the prototype, I need to worry about all of the instances.
> sometimes it is not symmetry. The DVO port and HDMI phy itself is symmetry, but the
> external display bridge connected with them are not symmetry. So, there are some registers
> located at the domain of this display controller side should change according to the
> different type of external display bridge.
>
> 3) Code duplication is actually less harmful than unmaintainable.
> macros are abstract, as noob level programmer, we completely drop the idea of abstract.
> Bad abstract means design failure, this is what we are most afraid of.
> Generally, we would like divide the whole into small cases, handle them one by one.
> It is actually to review and understand.

Code duplication is both harmful and unmaintainable. It is _very_hard_
to spot the difference between pipe0 and pipe1. And it is _very_easy_
to patch just one instance of these functions leaving the issue in the
second one. So, I'd say, all the c&p functions are a no-go.

>
> 4) From the viewpoint of the hardware, the display output hardware suffer from changes.
> Because users always want *new* display interface. The need of the users are also varies.
> Personally, I think macros are best for the symmetry case, while the output part of a
> display pipe always suffer from change.
>
> >> +
> >> +static void lsdc_pipe1_hdmi_encoder_reset(struct drm_encoder *encoder)
> >> +{
> >> + struct drm_device *ddev = encoder->dev;
> >> + struct lsdc_device *ldev = to_lsdc(ddev);
> >> + u32 val;
> >> +
> >> + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN;
> >> + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val);
> >> +
> >> + /* Using built-in GPIO emulated I2C instead of the hardware I2C */
> >> + lsdc_ureg32_clr(ldev, LSDC_HDMI1_INTF_CTRL_REG, HW_I2C_EN);
> >> +
> >> + /* Help the HDMI phy get out of reset state */
> >> + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N);
> >> +
> >> + drm_dbg(ddev, "%s reset\n", encoder->name);
> >> +
> >> + mdelay(20);
> >> +}
> >> +
> >> +const struct drm_encoder_funcs lsdc_pipe1_hdmi_encoder_funcs = {
> >> + .reset = lsdc_pipe1_hdmi_encoder_reset,
> >> + .destroy = drm_encoder_cleanup,
> >> +};
>


--
With best wishes
Dmitry

2023-10-30 09:42:47

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

Hi,


On 2023/10/30 06:53, Dmitry Baryshkov wrote:
> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
>> chip to support HDMI output. Thus add a drm bridge based driver for it.
>> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
> Please use the original bridge driver instead of adding a new one.

I'm agree with the spirit of code sharing, but this is nearly impossible for non-DT system.

Because the original bridge driver(say it66121.ko) is fully dependent on the DT.
UEFI+ACPI based system can not use with it.

Our I2C adapter is created by the drm/loongson.ko on the runtime.
The potential problem is that *cyclic dependency* !

I2C adapter driver is depend on drm/loongson
drm/loongson depend on drm bridge driver (say it66121.ko)
drm bridge driver (say it66121.ko) depend on I2C adapter to setup.

This plus the defer probe mechanism is totally a trap,
incurring troubles and don't work.


> If
> it needs to be changed in any way, please help everyone else by
> improving it instead of introducing new driver.
>
>> Signed-off-by: Sui Jingfeng <[email protected]>
>> ---
>> drivers/gpu/drm/loongson/Kconfig | 1 +
>> drivers/gpu/drm/loongson/Makefile | 2 +
>> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
>> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
>> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
>> 5 files changed, 1039 insertions(+)
>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h
>

2023-10-30 10:14:58

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

On Mon, 30 Oct 2023 at 11:42, Sui Jingfeng <[email protected]> wrote:
>
> Hi,
>
>
> On 2023/10/30 06:53, Dmitry Baryshkov wrote:
> > On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
> >> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
> >> chip to support HDMI output. Thus add a drm bridge based driver for it.
> >> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
> > Please use the original bridge driver instead of adding a new one.
>
> I'm agree with the spirit of code sharing, but this is nearly impossible for non-DT system.
>
> Because the original bridge driver(say it66121.ko) is fully dependent on the DT.

I can not agree here. It doesn't depend on DT. It has fully populated
i2c_device_id structures, so it will work with bare I2C buses.
Most likely you will have to change of calls into fwnode calls, that's it.

> UEFI+ACPI based system can not use with it.
>
> Our I2C adapter is created by the drm/loongson.ko on the runtime.
> The potential problem is that *cyclic dependency* !
>
> I2C adapter driver is depend on drm/loongson
> drm/loongson depend on drm bridge driver (say it66121.ko)
> drm bridge driver (say it66121.ko) depend on I2C adapter to setup.
>
> This plus the defer probe mechanism is totally a trap,
> incurring troubles and don't work.

Welcome. We had this kind of issue for DP AUX buses.

I can suggest the following approach:
- In the root probe function you can create an i2c bus and populate it
with the i2c devices.
- I have not checked whether you use components or not. If not, please
use an auxiliary or a platform device for the main DRM functionality.
- In the subdevice probe / bind function you check for the next
bridge. Then you get one of the following:drm_bridge pointer,
-EPROBE_DEFER, or any other error case. Your driver can react
accordingly.

Basically duplicating the existing driver code is not really a way to
go. Consider somebody adding a new feature or fixing a bug in your
driver copy. Then they have to check if the fix applies to the driver
at drivers/gpu/drm/bridge/ite-it66121.c. And vice versa. After fixing
an issue in the standard driver one has to keep in mind to check your
private copy.

So, please, use the OF code as an inspiration and register all your
devices in the device tree. Yes, this requires some effort from your
side. Yes, this pays off in the longer distance.

> > If
> > it needs to be changed in any way, please help everyone else by
> > improving it instead of introducing new driver.
> >
> >> Signed-off-by: Sui Jingfeng <[email protected]>
> >> ---
> >> drivers/gpu/drm/loongson/Kconfig | 1 +
> >> drivers/gpu/drm/loongson/Makefile | 2 +
> >> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
> >> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
> >> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
> >> 5 files changed, 1039 insertions(+)
> >> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
> >> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
> >> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h


--
With best wishes
Dmitry

2023-10-30 10:20:57

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

Hi,

On Mon, Oct 30, 2023 at 05:42:28PM +0800, Sui Jingfeng wrote:
> On 2023/10/30 06:53, Dmitry Baryshkov wrote:
> > On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
> > > The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
> > > chip to support HDMI output. Thus add a drm bridge based driver for it.
> > > This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
> > Please use the original bridge driver instead of adding a new one.

FTR I agree with Dmitry here, duplicating drivers because it's easier is
a show-stopper

> I'm agree with the spirit of code sharing, but this is nearly
> impossible for non-DT system.
>
> Because the original bridge driver(say it66121.ko) is fully dependent
> on the DT. UEFI+ACPI based system can not use with it.
>
> Our I2C adapter is created by the drm/loongson.ko on the runtime.
> The potential problem is that *cyclic dependency* !
>
> I2C adapter driver is depend on drm/loongson
> drm/loongson depend on drm bridge driver (say it66121.ko)
> drm bridge driver (say it66121.ko) depend on I2C adapter to setup.
>
> This plus the defer probe mechanism is totally a trap,
> incurring troubles and don't work.

I'm sure all those issues can be fixed :)

Maxime


Attachments:
(No filename) (1.27 kB)
signature.asc (235.00 B)
Download all attachments

2023-10-30 13:26:30

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

Hi,


On 2023/10/30 18:01, Dmitry Baryshkov wrote:
> On Mon, 30 Oct 2023 at 11:42, Sui Jingfeng <[email protected]> wrote:
>> Hi,
>>
>>
>> On 2023/10/30 06:53, Dmitry Baryshkov wrote:
>>> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>>>> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
>>>> chip to support HDMI output. Thus add a drm bridge based driver for it.
>>>> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
>>> Please use the original bridge driver instead of adding a new one.
>> I'm agree with the spirit of code sharing, but this is nearly impossible for non-DT system.
>>
>> Because the original bridge driver(say it66121.ko) is fully dependent on the DT.
> I can not agree here. It doesn't depend on DT. It has fully populated
> i2c_device_id structures, so it will work with bare I2C buses.
> Most likely you will have to change of calls into fwnode calls, that's it.
>
>> UEFI+ACPI based system can not use with it.
>>
>> Our I2C adapter is created by the drm/loongson.ko on the runtime.
>> The potential problem is that *cyclic dependency* !
>>
>> I2C adapter driver is depend on drm/loongson
>> drm/loongson depend on drm bridge driver (say it66121.ko)
>> drm bridge driver (say it66121.ko) depend on I2C adapter to setup.
>>
>> This plus the defer probe mechanism is totally a trap,
>> incurring troubles and don't work.
> Welcome. We had this kind of issue for DP AUX buses.
>
> I can suggest the following approach:
> - In the root probe function you can create an i2c bus and populate it
> with the i2c devices.
> - I have not checked whether you use components or not. If not, please
> use an auxiliary or a platform device for the main DRM functionality.
> - In the subdevice probe / bind function you check for the next
> bridge. Then you get one of the following:drm_bridge pointer,
> -EPROBE_DEFER, or any other error case. Your driver can react
> accordingly.

I have similar way to solve this problem, and I have solved it one and a half years ago.
See [1] for a reference.

[1] https://patchwork.freedesktop.org/patch/478998/?series=99512&rev=11

When the PCI device get probed, we create the I2C bus first.
This ensure that when drm/lsdc.ko get loaded, the I2C bus is presence
and ready to be get by the drm_bridge driver.
This is basically a PCI-to-GPIO-emulated-I2C adapter,
then wait the display bridges driver get loaded and set up.

I also need to create a virtual platform device for the display controller.
which allow the drm drivers instance for this virtual platform device
be able to probed due to defer probe mechanism.

This solution made the framework of my driver distortion severely,
and in the end we still solve a easy problem by workaround.

I know how to use the component framework also, but the component framework just
a wrapper. Similar with above approach, it brings no gains in the end.
It does not make this driver better. I got trapped one years ago,
and I don't want to got trapped another time.
And I know how solve such a problem by workaround, but that's not worthy for the effort.

I think my approach provide a solution, while still keep the bridges drivers
to a modular at the same time. Despite simple, it indeed solve the problem.
It simple because of explicit control of the loading order by myself, not by
rely on the framework or something else (say component)

It is not totally duplicating, I have rewrite part of them. You can compare
to see what I'm changed. It is just that it66162 was upstream-ed earlier than
our solution. But I also have write display drivers for lt8618 and lt8619
completely by myself.


Even though our local drm bridges driver will not be able to enjoy the updates.
We will accept such a results(or pain). I can maintain our local drm bridges
drivers by myself. Sorry, on this technique point, we will not follow your idea.
I'm sure that my approach is toward to right direction for our device at now.
If someone invent a better solution to handle this problem, which make the
various drm bridges drivers usable out of box, then I will follow and cooperate
to test.


> Basically duplicating the existing driver code is not really a way to
> go. Consider somebody adding a new feature or fixing a bug in your
> driver copy. Then they have to check if the fix applies to the driver
> at drivers/gpu/drm/bridge/ite-it66121.c. And vice versa. After fixing
> an issue in the standard driver one has to keep in mind to check your
> private copy.
>
> So, please, use the OF code as an inspiration and register all your
> devices in the device tree. Yes, this requires some effort from your
> side. Yes, this pays off in the longer distance.
>
>>> If
>>> it needs to be changed in any way, please help everyone else by
>>> improving it instead of introducing new driver.
>>>
>>>> Signed-off-by: Sui Jingfeng <[email protected]>
>>>> ---
>>>> drivers/gpu/drm/loongson/Kconfig | 1 +
>>>> drivers/gpu/drm/loongson/Makefile | 2 +
>>>> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
>>>> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
>>>> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
>>>> 5 files changed, 1039 insertions(+)
>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h
>

2023-10-30 13:39:35

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

On Mon, Oct 30, 2023 at 09:25:50PM +0800, Sui Jingfeng wrote:
> I think my approach provide a solution, while still keep the bridges drivers
> to a modular at the same time. Despite simple, it indeed solve the problem.
> It simple because of explicit control of the loading order by myself, not by
> rely on the framework or something else (say component)
>
> It is not totally duplicating, I have rewrite part of them. You can compare
> to see what I'm changed. It is just that it66162 was upstream-ed earlier than
> our solution. But I also have write display drivers for lt8618 and lt8619
> completely by myself.
>
> Even though our local drm bridges driver will not be able to enjoy the updates.
> We will accept such a results(or pain). I can maintain our local drm bridges
> drivers by myself. Sorry, on this technique point, we will not follow your idea.
> I'm sure that my approach is toward to right direction for our device at now.
> If someone invent a better solution to handle this problem, which make the
> various drm bridges drivers usable out of box, then I will follow and cooperate
> to test.

As far as I'm concerned, the two options are either you reuse the
already existing driver or this series isn't merged.

Ignoring what issue we raised and still merging your patch isn't on the
table, sorry.

Maxime


Attachments:
(No filename) (1.32 kB)
signature.asc (235.00 B)
Download all attachments

2023-10-30 13:48:39

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

On Mon, 30 Oct 2023 at 15:26, Sui Jingfeng <[email protected]> wrote:
>
> Hi,
>
>
> On 2023/10/30 18:01, Dmitry Baryshkov wrote:
> > On Mon, 30 Oct 2023 at 11:42, Sui Jingfeng <[email protected]> wrote:
> >> Hi,
> >>
> >>
> >> On 2023/10/30 06:53, Dmitry Baryshkov wrote:
> >>> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
> >>>> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
> >>>> chip to support HDMI output. Thus add a drm bridge based driver for it.
> >>>> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
> >>> Please use the original bridge driver instead of adding a new one.
> >> I'm agree with the spirit of code sharing, but this is nearly impossible for non-DT system.
> >>
> >> Because the original bridge driver(say it66121.ko) is fully dependent on the DT.
> > I can not agree here. It doesn't depend on DT. It has fully populated
> > i2c_device_id structures, so it will work with bare I2C buses.
> > Most likely you will have to change of calls into fwnode calls, that's it.
> >
> >> UEFI+ACPI based system can not use with it.
> >>
> >> Our I2C adapter is created by the drm/loongson.ko on the runtime.
> >> The potential problem is that *cyclic dependency* !
> >>
> >> I2C adapter driver is depend on drm/loongson
> >> drm/loongson depend on drm bridge driver (say it66121.ko)
> >> drm bridge driver (say it66121.ko) depend on I2C adapter to setup.
> >>
> >> This plus the defer probe mechanism is totally a trap,
> >> incurring troubles and don't work.
> > Welcome. We had this kind of issue for DP AUX buses.
> >
> > I can suggest the following approach:
> > - In the root probe function you can create an i2c bus and populate it
> > with the i2c devices.
> > - I have not checked whether you use components or not. If not, please
> > use an auxiliary or a platform device for the main DRM functionality.
> > - In the subdevice probe / bind function you check for the next
> > bridge. Then you get one of the following:drm_bridge pointer,
> > -EPROBE_DEFER, or any other error case. Your driver can react
> > accordingly.
>
> I have similar way to solve this problem, and I have solved it one and a half years ago.
> See [1] for a reference.
>
> [1] https://patchwork.freedesktop.org/patch/478998/?series=99512&rev=11
>
> When the PCI device get probed, we create the I2C bus first.
> This ensure that when drm/lsdc.ko get loaded, the I2C bus is presence
> and ready to be get by the drm_bridge driver.
> This is basically a PCI-to-GPIO-emulated-I2C adapter,
> then wait the display bridges driver get loaded and set up.
>
> I also need to create a virtual platform device for the display controller.
> which allow the drm drivers instance for this virtual platform device
> be able to probed due to defer probe mechanism.
>
> This solution made the framework of my driver distortion severely,

I don't think I could catch this phrase. Did you see distortions on the screen?

> and in the end we still solve a easy problem by workaround.

No workarounds for the kernel subsystems are allowed.

>
> I know how to use the component framework also, but the component framework just
> a wrapper. Similar with above approach, it brings no gains in the end.
> It does not make this driver better. I got trapped one years ago,
> and I don't want to got trapped another time.
> And I know how solve such a problem by workaround, but that's not worthy for the effort.
>
> I think my approach provide a solution, while still keep the bridges drivers
> to a modular at the same time. Despite simple, it indeed solve the problem.
> It simple because of explicit control of the loading order by myself, not by
> rely on the framework or something else (say component)

PCI media drivers have had this issue for ages. And all of them found
a way to work.

> It is not totally duplicating, I have rewrite part of them.

This is even worse. Now one can not apply fixes to the second one.

> You can compare
> to see what I'm changed. It is just that it66162 was upstream-ed earlier than
> our solution. But I also have write display drivers for lt8618 and lt8619
> completely by myself.
>
>
> Even though our local drm bridges driver will not be able to enjoy the updates.
> We will accept such a results(or pain). I can maintain our local drm bridges
> drivers by myself.

What happens if anybody wants to reuse your bridge driver for their
own platform?
Linux kernel uses driver model and frameworks to improve code sharing,
not to reduce it.

> Sorry, on this technique point, we will not follow your idea.
> I'm sure that my approach is toward to right direction for our device at now.
> If someone invent a better solution to handle this problem, which make the
> various drm bridges drivers usable out of box, then I will follow and cooperate
> to test.
>
>
> > Basically duplicating the existing driver code is not really a way to
> > go. Consider somebody adding a new feature or fixing a bug in your
> > driver copy. Then they have to check if the fix applies to the driver
> > at drivers/gpu/drm/bridge/ite-it66121.c. And vice versa. After fixing
> > an issue in the standard driver one has to keep in mind to check your
> > private copy.
> >
> > So, please, use the OF code as an inspiration and register all your
> > devices in the device tree. Yes, this requires some effort from your
> > side. Yes, this pays off in the longer distance.
> >
> >>> If
> >>> it needs to be changed in any way, please help everyone else by
> >>> improving it instead of introducing new driver.
> >>>
> >>>> Signed-off-by: Sui Jingfeng <[email protected]>
> >>>> ---
> >>>> drivers/gpu/drm/loongson/Kconfig | 1 +
> >>>> drivers/gpu/drm/loongson/Makefile | 2 +
> >>>> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
> >>>> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
> >>>> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
> >>>> 5 files changed, 1039 insertions(+)
> >>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
> >>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
> >>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h
> >
>


--
With best wishes
Dmitry

2023-10-30 14:26:47

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

Hi,


On 2023/10/30 21:48, Dmitry Baryshkov wrote:
> On Mon, 30 Oct 2023 at 15:26, Sui Jingfeng <[email protected]> wrote:
>> Hi,
>>
>>
>> On 2023/10/30 18:01, Dmitry Baryshkov wrote:
>>> On Mon, 30 Oct 2023 at 11:42, Sui Jingfeng <[email protected]> wrote:
>>>> Hi,
>>>>
>>>>
>>>> On 2023/10/30 06:53, Dmitry Baryshkov wrote:
>>>>> On Sun, 29 Oct 2023 at 21:46, Sui Jingfeng <[email protected]> wrote:
>>>>>> The IT66121 is a DVO to HDMI converter, LS3A5000+LS7A1000 ML5A_MB use this
>>>>>> chip to support HDMI output. Thus add a drm bridge based driver for it.
>>>>>> This patch is developed with drivers/gpu/drm/bridge/ite-it66121.c as base.
>>>>> Please use the original bridge driver instead of adding a new one.
>>>> I'm agree with the spirit of code sharing, but this is nearly impossible for non-DT system.
>>>>
>>>> Because the original bridge driver(say it66121.ko) is fully dependent on the DT.
>>> I can not agree here. It doesn't depend on DT. It has fully populated
>>> i2c_device_id structures, so it will work with bare I2C buses.
>>> Most likely you will have to change of calls into fwnode calls, that's it.
>>>
>>>> UEFI+ACPI based system can not use with it.
>>>>
>>>> Our I2C adapter is created by the drm/loongson.ko on the runtime.
>>>> The potential problem is that *cyclic dependency* !
>>>>
>>>> I2C adapter driver is depend on drm/loongson
>>>> drm/loongson depend on drm bridge driver (say it66121.ko)
>>>> drm bridge driver (say it66121.ko) depend on I2C adapter to setup.
>>>>
>>>> This plus the defer probe mechanism is totally a trap,
>>>> incurring troubles and don't work.
>>> Welcome. We had this kind of issue for DP AUX buses.
>>>
>>> I can suggest the following approach:
>>> - In the root probe function you can create an i2c bus and populate it
>>> with the i2c devices.
>>> - I have not checked whether you use components or not. If not, please
>>> use an auxiliary or a platform device for the main DRM functionality.
>>> - In the subdevice probe / bind function you check for the next
>>> bridge. Then you get one of the following:drm_bridge pointer,
>>> -EPROBE_DEFER, or any other error case. Your driver can react
>>> accordingly.
>> I have similar way to solve this problem, and I have solved it one and a half years ago.
>> See [1] for a reference.
>>
>> [1] https://patchwork.freedesktop.org/patch/478998/?series=99512&rev=11
>>
>> When the PCI device get probed, we create the I2C bus first.
>> This ensure that when drm/lsdc.ko get loaded, the I2C bus is presence
>> and ready to be get by the drm_bridge driver.
>> This is basically a PCI-to-GPIO-emulated-I2C adapter,
>> then wait the display bridges driver get loaded and set up.
>>
>> I also need to create a virtual platform device for the display controller.
>> which allow the drm drivers instance for this virtual platform device
>> be able to probed due to defer probe mechanism.
>>
>> This solution made the framework of my driver distortion severely,
> I don't think I could catch this phrase. Did you see distortions on the screen?


I means that it destroy the my drm driver's framework.
I means that we are all-in-one driver. The solution you
mentioned have side effect also. That is because user-space
will open the PCI device first, not your created virtual platform device.



>> and in the end we still solve a easy problem by workaround.
> No workarounds for the kernel subsystems are allowed.


I means that the idea(solution) you told me is still a workaround.
bring no benifits to the drm driver itself.


>> I know how to use the component framework also, but the component framework just
>> a wrapper. Similar with above approach, it brings no gains in the end.
>> It does not make this driver better. I got trapped one years ago,
>> and I don't want to got trapped another time.
>> And I know how solve such a problem by workaround, but that's not worthy for the effort.
>>
>> I think my approach provide a solution, while still keep the bridges drivers
>> to a modular at the same time. Despite simple, it indeed solve the problem.
>> It simple because of explicit control of the loading order by myself, not by
>> rely on the framework or something else (say component)
> PCI media drivers have had this issue for ages. And all of them found
> a way to work.

I have said that PCI KMS display drivers is different,  because of user
space open the PCI device.


>> It is not totally duplicating, I have rewrite part of them.
> This is even worse. Now one can not apply fixes to the second one.


I don't need to either, I want to maintain this by myself.


>> You can compare
>> to see what I'm changed. It is just that it66162 was upstream-ed earlier than
>> our solution. But I also have write display drivers for lt8618 and lt8619
>> completely by myself.
>>
>>
>> Even though our local drm bridges driver will not be able to enjoy the updates.
>> We will accept such a results(or pain). I can maintain our local drm bridges
>> drivers by myself.
> What happens if anybody wants to reuse your bridge driver for their
> own platform?

Copy and modify.

> Linux kernel uses driver model and frameworks to improve code sharing,
> not to reduce it.


Well I don't think my patch actually reduce something.
Please see i915, amdgpu, radeon and nouveau.
Non of them use the DRM bridge drivers.
It is just that the various DRM bridge drivers are not suitable to use for my driver.


>> Sorry, on this technique point, we will not follow your idea.
>> I'm sure that my approach is toward to right direction for our device at now.
>> If someone invent a better solution to handle this problem, which make the
>> various drm bridges drivers usable out of box, then I will follow and cooperate
>> to test.
>>
>>
>>> Basically duplicating the existing driver code is not really a way to
>>> go. Consider somebody adding a new feature or fixing a bug in your
>>> driver copy. Then they have to check if the fix applies to the driver
>>> at drivers/gpu/drm/bridge/ite-it66121.c. And vice versa. After fixing
>>> an issue in the standard driver one has to keep in mind to check your
>>> private copy.
>>>
>>> So, please, use the OF code as an inspiration and register all your
>>> devices in the device tree. Yes, this requires some effort from your
>>> side. Yes, this pays off in the longer distance.
>>>
>>>>> If
>>>>> it needs to be changed in any way, please help everyone else by
>>>>> improving it instead of introducing new driver.
>>>>>
>>>>>> Signed-off-by: Sui Jingfeng <[email protected]>
>>>>>> ---
>>>>>> drivers/gpu/drm/loongson/Kconfig | 1 +
>>>>>> drivers/gpu/drm/loongson/Makefile | 2 +
>>>>>> drivers/gpu/drm/loongson/ite_it66121.c | 749 ++++++++++++++++++++
>>>>>> drivers/gpu/drm/loongson/ite_it66121.h | 19 +
>>>>>> drivers/gpu/drm/loongson/ite_it66121_regs.h | 268 +++++++
>>>>>> 5 files changed, 1039 insertions(+)
>>>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.c
>>>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121.h
>>>>>> create mode 100644 drivers/gpu/drm/loongson/ite_it66121_regs.h
>

2023-10-30 14:39:52

by Sui Jingfeng

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

Hi,


On 2023/10/30 21:39, Maxime Ripard wrote:
> On Mon, Oct 30, 2023 at 09:25:50PM +0800, Sui Jingfeng wrote:
>> I think my approach provide a solution, while still keep the bridges drivers
>> to a modular at the same time. Despite simple, it indeed solve the problem.
>> It simple because of explicit control of the loading order by myself, not by
>> rely on the framework or something else (say component)
>>
>> It is not totally duplicating, I have rewrite part of them. You can compare
>> to see what I'm changed. It is just that it66162 was upstream-ed earlier than
>> our solution. But I also have write display drivers for lt8618 and lt8619
>> completely by myself.
>>
>> Even though our local drm bridges driver will not be able to enjoy the updates.
>> We will accept such a results(or pain). I can maintain our local drm bridges
>> drivers by myself. Sorry, on this technique point, we will not follow your idea.
>> I'm sure that my approach is toward to right direction for our device at now.
>> If someone invent a better solution to handle this problem, which make the
>> various drm bridges drivers usable out of box, then I will follow and cooperate
>> to test.
> As far as I'm concerned, the two options are either you reuse the
> already existing driver or this series isn't merged.

It's not that I don't want to use thealready existing display bridge driver, It is just that it is not
suitable for non DT-based system to use. Our system using UEFI+ACPI,
beside the I2C, there also have GPIO HPD interrupt hardware. ACPI-based system and DT-based system have different way to use(request) the hardware.
Can you feel my words?

If the variousdisplay bridge drivers are really ready to use, why I have to refuse?


> Ignoring what issue we raised and still merging your patch isn't on the
> table, sorry.
>
> Maxime

2023-10-30 14:57:03

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter

On Mon, Oct 30, 2023 at 10:39:32PM +0800, Sui Jingfeng wrote:
> Hi,
>
>
> On 2023/10/30 21:39, Maxime Ripard wrote:
> > On Mon, Oct 30, 2023 at 09:25:50PM +0800, Sui Jingfeng wrote:
> > > I think my approach provide a solution, while still keep the bridges drivers
> > > to a modular at the same time. Despite simple, it indeed solve the problem.
> > > It simple because of explicit control of the loading order by myself, not by
> > > rely on the framework or something else (say component)
> > >
> > > It is not totally duplicating, I have rewrite part of them. You can compare
> > > to see what I'm changed. It is just that it66162 was upstream-ed earlier than
> > > our solution. But I also have write display drivers for lt8618 and lt8619
> > > completely by myself.
> > >
> > > Even though our local drm bridges driver will not be able to enjoy the updates.
> > > We will accept such a results(or pain). I can maintain our local drm bridges
> > > drivers by myself. Sorry, on this technique point, we will not follow your idea.
> > > I'm sure that my approach is toward to right direction for our device at now.
> > > If someone invent a better solution to handle this problem, which make the
> > > various drm bridges drivers usable out of box, then I will follow and cooperate
> > > to test.
> > As far as I'm concerned, the two options are either you reuse the
> > already existing driver or this series isn't merged.
>
> It's not that I don't want to use thealready existing display bridge driver,
> It is just that it is not suitable for non DT-based system to use.

The code is there, you can modify it to make it suitable for non
DT-based systems.

> Our system using UEFI+ACPI, beside the I2C, there also have GPIO HPD
> interrupt hardware. ACPI-based system and DT-based system have
> different way to use(request) the hardware. Can you feel my words?

Not really, no. There's plenty of drivers supporting both ACPI and DT
based systems.

> If the variousdisplay bridge drivers are really ready to use

Nobody said they would be ready to use. You are expected to make them
work for you though.

> why I have to refuse?

I mean, you can totally refuse to do whatever we ask. Just like we can
also totally refuse to merge these patches.

Maxime


Attachments:
(No filename) (2.26 kB)
signature.asc (235.00 B)
Download all attachments

2023-11-01 02:43:15

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6 next-20231031]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-4-suijingfeng%40loongson.cn
patch subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()
config: x86_64-randconfig-001-20231101 (https://download.01.org/0day-ci/archive/20231101/[email protected]/config)
compiler: gcc-7 (Ubuntu 7.5.0-6ubuntu2) 7.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231101/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> drivers/gpu/drm/loongson/lsdc_output.c:555:5: warning: no previous declaration for 'lsdc_encoder_init' [-Wmissing-declarations]
int lsdc_encoder_init(struct drm_device *ddev,
^~~~~~~~~~~~~~~~~
>> drivers/gpu/drm/loongson/lsdc_output.c:578:5: warning: no previous declaration for 'lsdc_connector_init' [-Wmissing-declarations]
int lsdc_connector_init(struct drm_device *ddev,
^~~~~~~~~~~~~~~~~~~


vim +/lsdc_encoder_init +555 drivers/gpu/drm/loongson/lsdc_output.c

554
> 555 int lsdc_encoder_init(struct drm_device *ddev,
556 struct lsdc_output *output,
557 unsigned int pipe)
558 {
559 const struct lsdc_output_desc *descp = output->descp;
560 struct drm_encoder *encoder = &output->encoder;
561 int ret;
562
563 ret = drm_encoder_init(ddev,
564 encoder,
565 descp->encoder_funcs,
566 descp->encoder_type,
567 descp->name);
568 if (ret)
569 return ret;
570
571 encoder->possible_crtcs = BIT(pipe);
572
573 drm_encoder_helper_add(encoder, descp->encoder_helper_funcs);
574
575 return 0;
576 }
577
> 578 int lsdc_connector_init(struct drm_device *ddev,
579 struct lsdc_output *output,
580 struct i2c_adapter *ddc,
581 unsigned int pipe)
582 {
583 const struct lsdc_output_desc *descp = output->descp;
584 struct drm_connector *connector = &output->connector;
585 int ret;
586
587 ret = drm_connector_init_with_ddc(ddev,
588 connector,
589 descp->connector_funcs,
590 descp->connector_type,
591 ddc);
592 if (ret)
593 return ret;
594
595 drm_connector_helper_add(connector, descp->connector_helper_funcs);
596
597 drm_connector_attach_encoder(connector, &output->encoder);
598
599 connector->polled = DRM_CONNECTOR_POLL_CONNECT |
600 DRM_CONNECTOR_POLL_DISCONNECT;
601
602 connector->interlace_allowed = 0;
603 connector->doublescan_allowed = 0;
604
605 drm_info(ddev, "DisplayPipe-%u has %s\n", pipe, descp->name);
606
607 return 0;
608 }
609

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-11-04 07:42:46

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6 next-20231103]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-4-suijingfeng%40loongson.cn
patch subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()
config: x86_64-randconfig-122-20231102 (https://download.01.org/0day-ci/archive/20231104/[email protected]/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231104/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

sparse warnings: (new ones prefixed by >>)
>> drivers/gpu/drm/loongson/lsdc_output.c:555:5: sparse: sparse: symbol 'lsdc_encoder_init' was not declared. Should it be static?
>> drivers/gpu/drm/loongson/lsdc_output.c:578:5: sparse: sparse: symbol 'lsdc_connector_init' was not declared. Should it be static?

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-11-05 17:32:19

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6 next-20231103]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-4-suijingfeng%40loongson.cn
patch subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init()
config: x86_64-randconfig-122-20231102 (https://download.01.org/0day-ci/archive/20231106/[email protected]/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231106/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

sparse warnings: (new ones prefixed by >>)
>> drivers/gpu/drm/loongson/lsdc_output.c:555:5: sparse: sparse: symbol 'lsdc_encoder_init' was not declared. Should it be static?
>> drivers/gpu/drm/loongson/lsdc_output.c:578:5: sparse: sparse: symbol 'lsdc_connector_init' was not declared. Should it be static?

vim +/lsdc_encoder_init +555 drivers/gpu/drm/loongson/lsdc_output.c

554
> 555 int lsdc_encoder_init(struct drm_device *ddev,
556 struct lsdc_output *output,
557 unsigned int pipe)
558 {
559 const struct lsdc_output_desc *descp = output->descp;
560 struct drm_encoder *encoder = &output->encoder;
561 int ret;
562
563 ret = drm_encoder_init(ddev,
564 encoder,
565 descp->encoder_funcs,
566 descp->encoder_type,
567 descp->name);
568 if (ret)
569 return ret;
570
571 encoder->possible_crtcs = BIT(pipe);
572
573 drm_encoder_helper_add(encoder, descp->encoder_helper_funcs);
574
575 return 0;
576 }
577
> 578 int lsdc_connector_init(struct drm_device *ddev,
579 struct lsdc_output *output,
580 struct i2c_adapter *ddc,
581 unsigned int pipe)
582 {
583 const struct lsdc_output_desc *descp = output->descp;
584 struct drm_connector *connector = &output->connector;
585 int ret;
586
587 ret = drm_connector_init_with_ddc(ddev,
588 connector,
589 descp->connector_funcs,
590 descp->connector_type,
591 ddc);
592 if (ret)
593 return ret;
594
595 drm_connector_helper_add(connector, descp->connector_helper_funcs);
596
597 drm_connector_attach_encoder(connector, &output->encoder);
598
599 connector->polled = DRM_CONNECTOR_POLL_CONNECT |
600 DRM_CONNECTOR_POLL_DISCONNECT;
601
602 connector->interlace_allowed = 0;
603 connector->doublescan_allowed = 0;
604
605 drm_info(ddev, "DisplayPipe-%u has %s\n", pipe, descp->name);
606
607 return 0;
608 }
609

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-11-06 17:48:06

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization

Hi Sui,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on linus/master v6.6 next-20231106]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Sui-Jingfeng/drm-loongson-Introduce-a-minimal-support-for-Loongson-VBIOS/20231030-034730
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20231029194607.379459-6-suijingfeng%40loongson.cn
patch subject: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization
config: x86_64-randconfig-122-20231102 (https://download.01.org/0day-ci/archive/20231107/[email protected]/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231107/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

sparse warnings: (new ones prefixed by >>)
>> drivers/gpu/drm/loongson/lsdc_output_7a2000.c:567:31: sparse: sparse: symbol 'ls7a2000_query_output_configuration' was not declared. Should it be static?

vim +/ls7a2000_query_output_configuration +567 drivers/gpu/drm/loongson/lsdc_output_7a2000.c

559
560 /*
561 * For LS7A2000, the built-in VGA encoder is transparent. If there are
562 * external encoder exist, then the internal HDMI encoder MUST be enabled
563 * and initialized. As the internal HDMI encoder is always connected, so
564 * only the transmitters which take HDMI signal (such as HDMI to eDP, HDMI
565 * to LVDS, etc) are usable with.
566 */
> 567 const struct lsdc_output_desc *
568 ls7a2000_query_output_configuration(struct drm_device *ddev, unsigned int pipe)
569 {
570 enum loongson_vbios_encoder_name encoder_name = 0;
571 bool ret;
572
573 ret = loongson_vbios_query_encoder_info(ddev, pipe, NULL,
574 &encoder_name, NULL);
575 if (!ret)
576 goto bailout;
577
578 if (pipe == 0) {
579 switch (encoder_name) {
580 case ENCODER_CHIP_INTERNAL_HDMI:
581 return &ls7a2000_hdmi_pipe0;
582
583 /*
584 * For LS7A2000, the built-in VGA encoder is transparent.
585 */
586 case ENCODER_CHIP_INTERNAL_VGA:
587 return &ls7a2000_vga_pipe0;
588
589 /*
590 * External display bridge exists, the internal HDMI encoder
591 * MUST be enabled and initialized. Please add a drm bridge
592 * driver, and attach to this encoder.
593 */
594 default:
595 return &ls7a2000_hdmi_pipe0;
596 }
597 }
598
599 if (pipe == 1) {
600 switch (encoder_name) {
601 case ENCODER_CHIP_INTERNAL_HDMI:
602 return &ls7a2000_hdmi_pipe1;
603
604 /*
605 * External display bridge exists, the internal HDMI encoder
606 * MUST be enabled and initialized. Please add a drm bridge
607 * driver, and attach it to this encoder.
608 */
609 default:
610 return &ls7a2000_hdmi_pipe1;
611 }
612 }
613
614 bailout:
615 if (pipe == 0)
616 return &ls7a2000_vga_pipe0;
617
618 if (pipe == 1)
619 return &ls7a2000_hdmi_pipe1;
620
621 return NULL;
622 }
623

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki