diff mbox series

[1/8] drm/loongson: Introduce a minimal support for Loongson VBIOS

Message ID 20231029194607.379459-2-suijingfeng@loongson.cn (mailing list archive)
State New, archived
Headers show
Series drm/loongson: Submit a mini VBIOS support and a display bridge driver | expand

Commit Message

Sui Jingfeng Oct. 29, 2023, 7:46 p.m. UTC
Because some boards are equipped with non-transparent display bridges,
which need the VBIOS to provided parameters.

Signed-off-by: Sui Jingfeng <suijingfeng@loongson.cn>
---
 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

Comments

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

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

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

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

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

flash?

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

storing

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

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

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

get_version.

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


Thanks a lot for reviewing!


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


Then, is singleunderscore(_loongson_vbios) OK ?

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

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

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


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

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

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

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


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


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


Thanks a lot,  :-)


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


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


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

Patch

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