From patchwork Sun Oct 29 19:46:00 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439877 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 88E21C4332F for ; Sun, 29 Oct 2023 19:46:27 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 7C75D10E19C; Sun, 29 Oct 2023 19:46:19 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 59DA010E19C for ; Sun, 29 Oct 2023 19:46:14 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8DxBfGCtj5lsZs1AA--.39530S3; Mon, 30 Oct 2023 03:46:10 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S3; Mon, 30 Oct 2023 03:46:06 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 1/8] drm/loongson: Introduce a minimal support for Loongson VBIOS Date: Mon, 30 Oct 2023 03:46:00 +0800 Message-Id: <20231029194607.379459-2-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S3 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj9fXoW3urWDWF1rGw1UtF1kWr1kXrc_yoW8Wr4kCo WxZan8Zw4Fgr4UZayvgr1Fqr1xXFyFqF1av3s3Zr4DuFWY9F4UJFW3Xry3W34FyF13Xr43 A34rKw4xGr47Jrs5l-sFpf9Il3svdjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8wcxFpf 9Il3svdxBIdaVrn0xqx4xG64xvF2IEw4CE5I8CrVC2j2Jv73VFW2AGmfu7bjvjm3AaLaJ3 UjIYCTnIWjp_UUUYU7kC6x804xWl14x267AKxVWUJVW8JwAFc2x0x2IEx4CE42xK8VAvwI 8IcIk0rVWrJVCq3wAFIxvE14AKwVWUXVWUAwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xG Y2AK021l84ACjcxK6xIIjxv20xvE14v26r1I6r4UM28EF7xvwVC0I7IYx2IY6xkF7I0E14 v26r1j6r4UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUAVWUtwAv7VC2z280aVAF wI0_Jr0_Gr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26r1j6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVWUJVW8JwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Jr0_Gr1lIxAIcVC2z280aVCY1x 0267AKxVWUJVW8JbIYCTnIWIevJa73UjIFyTuYvjxU2nYFDUUUU X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Because some boards are equipped with non-transparent display bridges, which need the VBIOS to provided parameters. Signed-off-by: Sui Jingfeng --- 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 +#include + +#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 + +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 #include +#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]; }; From patchwork Sun Oct 29 19:46:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439882 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id F0396C4167B for ; Sun, 29 Oct 2023 19:46:43 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 4953610E1A0; Sun, 29 Oct 2023 19:46:42 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 8700A10E19E for ; Sun, 29 Oct 2023 19:46:14 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8AxqOiDtj5ltJs1AA--.3099S3; Mon, 30 Oct 2023 03:46:11 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S4; Mon, 30 Oct 2023 03:46:10 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 2/8] drm/loongson: Introduce a drm bridge driver for it66121 HDMI transmitter Date: Mon, 30 Oct 2023 03:46:01 +0800 Message-Id: <20231029194607.379459-3-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S4 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj9fXoWftFyDGr1fZw4kXw13GFWrXrc_yoW5ZrW7Go WvvFnxtw15XryxZws7Wa12gF1DZ3WDtrWFvr4Fq3ykuayUKwn8Ja17JwnrZr1aqwn7tr13 Jr1ktrs5Gr43Cr18l-sFpf9Il3svdjkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8wcxFpf 9Il3svdxBIdaVrn0xqx4xG64xvF2IEw4CE5I8CrVC2j2Jv73VFW2AGmfu7bjvjm3AaLaJ3 UjIYCTnIWjp_UUUYU7kC6x804xWl14x267AKxVWUJVW8JwAFc2x0x2IEx4CE42xK8VAvwI 8IcIk0rVWrJVCq3wAFIxvE14AKwVWUAVWUZwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xG Y2AK021l84ACjcxK6xIIjxv20xvE14v26r4j6ryUM28EF7xvwVC0I7IYx2IY6xkF7I0E14 v26r4j6F4UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUtVWrXwAv7VC2z280aVAF wI0_Gr0_Cr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26r1I6r4UMIIF0xvE2Ix0cI8IcVCY1x0267AKxVWUJVW8JwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Jr0_Gr1lIxAIcVC2z280aVCY1x 0267AKxVWUJVW8JbIYCTnIWIevJa73UjIFyTuYvjxUco7KUUUUU X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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 + * Copyright (C) 2018-2019, Artem Mygaiev + * Copyright (C) 2017, Fresco Logic, Incorporated. + * + * IT66121 HDMI transmitter driver + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include + +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 + * 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 From patchwork Sun Oct 29 19:46:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439880 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 2CC54C00142 for ; Sun, 29 Oct 2023 19:46:35 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id EEE5710E1A1; Sun, 29 Oct 2023 19:46:20 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 76E8B10E192 for ; Sun, 29 Oct 2023 19:46:14 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8Dx_7uDtj5lt5s1AA--.45611S3; Mon, 30 Oct 2023 03:46:11 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S5; Mon, 30 Oct 2023 03:46:11 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 3/8] drm/loongson: Allow attach drm bridge driver by calling lsdc_output_init() Date: Mon, 30 Oct 2023 03:46:02 +0800 Message-Id: <20231029194607.379459-4-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S5 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj9fXoWfuF4rZFyxAr4ftw1fKFyUJwc_yoW8KFy8to WxXwnavw18KryIqrZ5KF18KFWDZa1Fq3W3Gwn5GFZrua98Cw4Yq34xG34UKFWSqr1Sgr4U Z3Wqq3s7XFsrZF4kl-sFpf9Il3svdjkaLaAFLSUrUUUUbb8apTn2vfkv8UJUUUU8wcxFpf 9Il3svdxBIdaVrn0xqx4xG64xvF2IEw4CE5I8CrVC2j2Jv73VFW2AGmfu7bjvjm3AaLaJ3 UjIYCTnIWjp_UUUYj7kC6x804xWl14x267AKxVWUJVW8JwAFc2x0x2IEx4CE42xK8VAvwI 8IcIk0rVWrJVCq3wAFIxvE14AKwVWUAVWUZwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xG Y2AK021l84ACjcxK6xIIjxv20xvE14v26r4j6ryUM28EF7xvwVC0I7IYx2IY6xkF7I0E14 v26r4j6F4UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUtVWrXwAv7VC2z280aVAF wI0_Gr0_Cr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26r4j6ryUMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x 0267AKxVW8JVW8JrUvcSsGvfC2KfnxnUUI43ZEXa7IU8gAw7UUUUU== X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" Move the sharable subroutine into lsdc_output.c and refactor. Signed-off-by: Sui Jingfeng --- 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 + +#include +#include +#include +#include +#include + +#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 +#include +#include + +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 From patchwork Sun Oct 29 19:46:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439883 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 2222BC4332F for ; Sun, 29 Oct 2023 19:46:46 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 8C08310E1A4; Sun, 29 Oct 2023 19:46:44 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 20C3E10E19F for ; Sun, 29 Oct 2023 19:46:14 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8DxRvGEtj5lvJs1AA--.39307S3; Mon, 30 Oct 2023 03:46:12 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S6; Mon, 30 Oct 2023 03:46:11 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 4/8] drm/loongson: Started to attach display bridge driver for LS7A1000 Date: Mon, 30 Oct 2023 03:46:03 +0800 Message-Id: <20231029194607.379459-5-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S6 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj93XoW3Gry5trW8Ar4fZFy8JF17XFc_yoWxArWUpF s8t393tr48XF1rWr4vyr1DWw15ArWqkFyUtrs7uw1S9as3Krn0qF4xtr1DW3WDXa95ur1U twsFqw43CF18CwcCm3ZEXasCq-sJn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7KY7ZEXa sCq-sGcSsGvfJ3Ic02F40EFcxC0VAKzVAqx4xG6I80ebIjqfuFe4nvWSU5nxnvy29KBjDU 0xBIdaVrnRJUUUk0b4IE77IF4wAFF20E14v26r1j6r4UM7CY07I20VC2zVCF04k26cxKx2 IYs7xG6rWj6s0DM7CIcVAFz4kK6r1Y6r17M28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr0_Cr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_ Gr0_Gr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l57IF6xkI12xvs2x26I8E6xACxx1l5I 8CrVACY4xI64kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1q6rW5McIj6I8E87Iv67AK xVW8JVWxJwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41l42xK82IYc2Ij64 vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC20s026x8G jcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r126r1DMIIYrxkI7VAKI48JMIIF0xvE2I x0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4j6F4UMIIF0xvE42xK 8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I 0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuYvjxUcCD7UUUUU X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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); } From patchwork Sun Oct 29 19:46:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439878 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 56E96C00142 for ; Sun, 29 Oct 2023 19:46:30 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id B96A610E19F; Sun, 29 Oct 2023 19:46:20 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 6B82A10E19F for ; Sun, 29 Oct 2023 19:46:15 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8Cxc_CEtj5lv5s1AA--.39348S3; Mon, 30 Oct 2023 03:46:12 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S7; Mon, 30 Oct 2023 03:46:12 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 5/8] drm/loongson: Using vbios for the LS7A2000 output initialization Date: Mon, 30 Oct 2023 03:46:04 +0800 Message-Id: <20231029194607.379459-6-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S7 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj93XoW3Gr4UAw4kGw45JF4DZw1rKrX_yoWxtw4Dpr sxtrZ3Jr1kZF1Fyr1kAr1kX34YyrWvkFySy3s29w1Sy34fJr90qF47tr1UW3WUJa9Y9r12 vrsrXw4ak3WUC3gCm3ZEXasCq-sJn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7KY7ZEXa sCq-sGcSsGvfJ3Ic02F40EFcxC0VAKzVAqx4xG6I80ebIjqfuFe4nvWSU5nxnvy29KBjDU 0xBIdaVrnRJUUUk0b4IE77IF4wAFF20E14v26r1j6r4UM7CY07I20VC2zVCF04k26cxKx2 IYs7xG6rWj6s0DM7CIcVAFz4kK6r1Y6r17M28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr0_Cr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_ Gr0_Gr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l57IF6xkI12xvs2x26I8E6xACxx1l5I 8CrVACY4xI64kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1q6rW5McIj6I8E87Iv67AK xVW8JVWxJwAm72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41l42xK82IYc2Ij64 vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC20s026x8G jcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r126r1DMIIYrxkI7VAKI48JMIIF0xvE2I x0cI8IcVAFwI0_Xr0_Ar1lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4j6F4UMIIF0xvE42xK 8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I 0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuYvjxUcCD7UUUUU X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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); } From patchwork Sun Oct 29 19:46:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439881 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 77507C4167D for ; Sun, 29 Oct 2023 19:46:43 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 4BF4910E1A2; Sun, 29 Oct 2023 19:46:42 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 6FEDD10E19C for ; Sun, 29 Oct 2023 19:46:15 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8AxTeuFtj5lxJs1AA--.33914S3; Mon, 30 Oct 2023 03:46:13 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S8; Mon, 30 Oct 2023 03:46:12 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 6/8] drm/loongson: Clean up the output part of LS7A2000 Date: Mon, 30 Oct 2023 03:46:05 +0800 Message-Id: <20231029194607.379459-7-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S8 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj9fXoWfJry8tr1xJr1kWrW7ZFWfCrX_yoW8Wr1UZo W7Zwna9w10gry7XFs8tF15KFyDZa10q3W3Jw18GFWDuanxGa1jq34xGw15KrWSqF13WF4j y3Wvqwn7XF17uan5l-sFpf9Il3svdjkaLaAFLSUrUUUUUb8apTn2vfkv8UJUUUU8wcxFpf 9Il3svdxBIdaVrn0xqx4xG64xvF2IEw4CE5I8CrVC2j2Jv73VFW2AGmfu7bjvjm3AaLaJ3 UjIYCTnIWjp_UUUYj7kC6x804xWl14x267AKxVWUJVW8JwAFc2x0x2IEx4CE42xK8VAvwI 8IcIk0rVWrJVCq3wAFIxvE14AKwVWUXVWUAwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xG Y2AK021l84ACjcxK6xIIjxv20xvE14v26ryj6F1UM28EF7xvwVC0I7IYx2IY6xkF7I0E14 v26r4j6F4UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUtVWrXwAv7VC2z280aVAF wI0_Gr0_Cr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26ryj6F1UMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x 0267AKxVW8JVW8JrUvcSsGvfC2KfnxnUUI43ZEXa7IU8l38UUUUUU== X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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, From patchwork Sun Oct 29 19:46:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439876 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id A570AC4332F for ; Sun, 29 Oct 2023 19:46:23 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 0ADAD10E192; Sun, 29 Oct 2023 19:46:19 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 845CE10E192 for ; Sun, 29 Oct 2023 19:46:15 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8CxtPCFtj5lx5s1AA--.40173S3; Mon, 30 Oct 2023 03:46:13 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S9; Mon, 30 Oct 2023 03:46:13 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 7/8] drm/loongson: Support to infer DC reversion from CPU's PRID value Date: Mon, 30 Oct 2023 03:46:06 +0800 Message-Id: <20231029194607.379459-8-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S9 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj93XoWxJw4kKw4UCw15AF1ruFWfJFc_yoWrGw4xpr ZxAFySkryDGw12y39xAr18Aa4fAa4fXFZ3uFZ2kw1qgw1UAa4UWFyUCF4YvrZrZryxJry2 v3sakrWUuF1aywcCm3ZEXasCq-sJn29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7KY7ZEXa sCq-sGcSsGvfJ3Ic02F40EFcxC0VAKzVAqx4xG6I80ebIjqfuFe4nvWSU5nxnvy29KBjDU 0xBIdaVrnRJUUUkYb4IE77IF4wAFF20E14v26r1j6r4UM7CY07I20VC2zVCF04k26cxKx2 IYs7xG6rWj6s0DM7CIcVAFz4kK6r1Y6r17M28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr0_Cr1l84ACjcxK6I8E87Iv67AKxVWxJVW8Jr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUtVWrXwAv7VC2z280aVAF wI0_Gr0_Cr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26ryj6F1UMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x 0267AKxVW8JVW8JrUvcSsGvfC2KfnxnUUI43ZEXa7IU8l38UUUUUU== X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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 From patchwork Sun Oct 29 19:46:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13439879 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id C6799C4332F for ; Sun, 29 Oct 2023 19:46:32 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 5F6F510E1A3; Sun, 29 Oct 2023 19:46:21 +0000 (UTC) Received: from mail.loongson.cn (mail.loongson.cn [114.242.206.163]) by gabe.freedesktop.org (Postfix) with ESMTP id 8836F10E192 for ; Sun, 29 Oct 2023 19:46:17 +0000 (UTC) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8BxnuuHtj5ly5s1AA--.36829S3; Mon, 30 Oct 2023 03:46:15 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8AxXNx+tj5lKq02AA--.51878S10; Mon, 30 Oct 2023 03:46:13 +0800 (CST) From: Sui Jingfeng To: Maxime Ripard , Thomas Zimmermann Subject: [PATCH 8/8] drm/loongson: Add support for the display subsystem in LS2K2000 Date: Mon, 30 Oct 2023 03:46:07 +0800 Message-Id: <20231029194607.379459-9-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231029194607.379459-1-suijingfeng@loongson.cn> References: <20231029194607.379459-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8AxXNx+tj5lKq02AA--.51878S10 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBj93XoW3KF47KF4xtFy5XFyfWFy5WrX_yoWDWFWxpa 13A3ySgr48tFnI939xtr1UXw1YkFyayFZayFWfGw1rW3srAr18tFnYyF4FqFW7XFy5Jr12 qrn7G3yIk3WUGabCm3ZEXasCq-sJn29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7KY7ZEXa sCq-sGcSsGvfJ3Ic02F40EFcxC0VAKzVAqx4xG6I80ebIjqfuFe4nvWSU5nxnvy29KBjDU 0xBIdaVrnRJUUUkYb4IE77IF4wAFF20E14v26r1j6r4UM7CY07I20VC2zVCF04k26cxKx2 IYs7xG6rWj6s0DM7CIcVAFz4kK6r1Y6r17M28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Xr0_Ar1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr0_Cr1l84ACjcxK6I8E87Iv67AKxVWxJVW8Jr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4j6r4UJwAS0I0E0xvYzxvE52x082IY62kv0487Mc804VCY07AIYIkI8VC2zVCFFI0UMc 02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWrXVW3AwAv7VC2z280aVAF wI0_Gr0_Cr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JMxAIw28IcxkI7V AKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCj r7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6x IIjxv20xvE14v26ryj6F1UMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAI w20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aVAFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x 0267AKxVW8JVW8JrUvcSsGvfC2KfnxnUUI43ZEXa7IU0lPfDUUUUU== X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 --- 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 #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; +}