[PATCH v11 7/7] drm/lsdc: add drm driver for loongson display controller
Rob Herring
robh at kernel.org
Tue Mar 22 20:49:11 UTC 2022
On Tue, Mar 22, 2022 at 12:29:16AM +0800, Sui Jingfeng wrote:
> From: suijingfeng <suijingfeng at loongson.cn>
>
> There is a display controller in loongson's LS2K1000 SoC and LS7A1000
> bridge chip, the display controller is a PCI device in those chips. It
> has two display pipes but with only one hardware cursor. Each way has
> a DVO interface which provide RGB888 signals, vertical & horizontal
> synchronisations, data enable and the pixel clock. Each CRTC is able to
> scanout from 1920x1080 resolution at 60Hz, the maxmium resolution is
> 2048x2048 according to the hardware spec. Loongson display controllers
> are simple which require scanout buffers to be physically contiguous.
>
> For LS7A1000 bridge chip, the DC is equipped with a dedicated video RAM
> which is typically 64MB or more. In this case, VRAM helper based driver
> is intend to be used. While LS2K1000 is a SoC, only system memory is
> available. Therefore CMA helper based driver is intend to be used. It is
> possible to use VRAM helper based solution by carving out part of system
> memory as VRAM though.
>
> For LS7A1000, there are 4 dedicated GPIOs whose control register is
> located at the DC register space, They are used to emulate two way i2c.
> One for DVO0, another for DVO1. LS2K1000 and LS2K0500 SoC don't have such
> GPIO hardwared, they grab i2c adapter from other module, either general
> purpose GPIO emulated i2c or hardware i2c adapter.
>
> +------+ +-----------------------------------+
> | DDR4 | | +-------------------+ |
> +------+ | | PCIe Root complex | LS7A1000 |
> || MC0 | +--++---------++----+ |
> +----------+ HT 3.0 | || || |
> | LS3A4000 |<-------->| +---++---+ +--++--+ +---------+ +------+
> | CPU |<-------->| | GC1000 | | LSDC |<-->| DDR3 MC |<->| VRAM |
> +----------+ | +--------+ +-+--+-+ +---------+ +------+
> || MC1 +---------------|--|----------------+
> +------+ | |
> | DDR4 | +-------+ DVO0 | | DVO1 +------+
> +------+ VGA <--|ADV7125|<--------+ +-------->|TFP410|--> DVI/HDMI
> +-------+ +------+
>
> The above picture give a simple usage of LS7A1000, note that the encoder
> is not necessary adv7125 or tfp410, other candicates can be ch7034b,
> sil9022, ite66121 and lt8618 etc.
>
> v2: Fixup warnings reported by kernel test robot
>
> v3: Fix more grammar mistakes in Kconfig reported by Randy Dunlap and give
> more details about lsdc.
>
> v4:
> 1) Add dts required and explain why device tree is required.
> 2) Give more description about lsdc and VRAM helper based driver.
> 3) Fix warnings reported by kernel test robot.
> 4) Introduce stride_alignment member into struct lsdc_chip_desc, the
> stride alignment is 256 bytes for ls7a1000, ls2k1000 and ls2k0500.
>
> v5:
> 1) Using writel and readl replace writeq and readq, to fix kernel test
> robot report build error on other archtecture.
> 2) Set default fb format to XRGB8888 at crtc reset time.
>
> v6:
> 1) Explain why we are not switch to drm dridge subsystem on ls2k1000.
> 2) Explain why tiny drm driver is not suitable for us.
> 3) Give a short description of the trival dirty uppdate implement based
> on CMA helper.
>
> v7:
> 1) Remove select I2C_GPIO and I2C_LS2X in Kconfig, it is not ready now
> 2) Licensing issues are fixed suggested by Krzysztof Kozlowski.
> 3) Remove lsdc_pixpll_print(), part of it move to debugfs.
> 4) Set prefer_shadow to true if vram based driver is in using.
> 5) Replace double blank lines with single line in all files.
> 6) Verbose cmd line parameter is replaced with drm_dbg()
> 7) All warnnings reported by ./scripts/checkpatch.pl --strict are fixed
> 8) Get edid from dtb support is removed as suggested by Maxime Ripard
> 9) Fix typos and various improvement
>
> v8:
> 1) Drop damage update implement and its command line.
> 2) Drop DRM_LSDC_VRAM_DRIVER config option as suggested by Maxime.
> 3) Deduce DC's identification from its compatible property.
> 4) Drop the board specific dts patch.
> 5) Add documention about the display controller device node.
>
> v9:
> 1) Fix the warnings reported by checkpatch script and fix typos
>
> v10:
> 1) Pass `make dt_binding_check` validation
> 2) Fix warnings reported by kernel test robot
>
> v11:
> 1) Convert the driver to use drm bridge and of graph framework.
> 2) Dump register value support through debugfs.
>
> Reported-by: kernel test robot <lkp at intel.com>
> Signed-off-by: suijingfeng <suijingfeng at loongson.cn>
> Signed-off-by: Sui Jingfeng <15330273260 at 189.cn>
> Signed-off-by: suijingfeng <suijingfeng at loongson.cn>
> ---
> drivers/gpu/drm/Kconfig | 2 +
> drivers/gpu/drm/Makefile | 1 +
> drivers/gpu/drm/lsdc/Kconfig | 23 ++
> drivers/gpu/drm/lsdc/Makefile | 13 +
> drivers/gpu/drm/lsdc/lsdc_crtc.c | 396 +++++++++++++++++++
> drivers/gpu/drm/lsdc/lsdc_drv.c | 547 ++++++++++++++++++++++++++
> drivers/gpu/drm/lsdc/lsdc_drv.h | 197 ++++++++++
> drivers/gpu/drm/lsdc/lsdc_i2c.c | 235 ++++++++++++
> drivers/gpu/drm/lsdc/lsdc_i2c.h | 42 ++
> drivers/gpu/drm/lsdc/lsdc_irq.c | 58 +++
> drivers/gpu/drm/lsdc/lsdc_irq.h | 18 +
> drivers/gpu/drm/lsdc/lsdc_output.c | 262 +++++++++++++
> drivers/gpu/drm/lsdc/lsdc_output.h | 24 ++
> drivers/gpu/drm/lsdc/lsdc_pci_drv.c | 328 ++++++++++++++++
> drivers/gpu/drm/lsdc/lsdc_plane.c | 470 +++++++++++++++++++++++
> drivers/gpu/drm/lsdc/lsdc_pll.c | 574 ++++++++++++++++++++++++++++
> drivers/gpu/drm/lsdc/lsdc_pll.h | 88 +++++
> drivers/gpu/drm/lsdc/lsdc_regs.h | 220 +++++++++++
> 18 files changed, 3498 insertions(+)
> create mode 100644 drivers/gpu/drm/lsdc/Kconfig
> create mode 100644 drivers/gpu/drm/lsdc/Makefile
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.h
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.h
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.h
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_pci_drv.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h
> create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h
[...]
> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c b/drivers/gpu/drm/lsdc/lsdc_i2c.c
> new file mode 100644
> index 000000000000..55beed9266fa
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
Not really a useful comment since every file has the same one.
> + * Copyright (C) 2022 Loongson Corporation
> + */
> +
> +/*
> + * Authors:
> + * Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/pci.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_i2c.h"
> +
> +/*
> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask
> + * @mask: gpio pin mask
> + */
> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state)
> +{
> + unsigned long flags;
> + u8 val;
> +
> + spin_lock_irqsave(&li2c->reglock, flags);
What are you protecting? Doesn't the caller serialize calls to these
functions?
> +
> + if (state) {
> + val = readb(li2c->dir_reg);
> + val |= mask;
> + writeb(val, li2c->dir_reg);
> + } else {
> + val = readb(li2c->dir_reg);
> + val &= ~mask;
> + writeb(val, li2c->dir_reg);
> +
> + val = readb(li2c->dat_reg);
> + if (state)
This condition is never true. We're in the 'else' because !state.
> + val |= mask;
> + else
> + val &= ~mask;
> + writeb(val, li2c->dat_reg);
Shouldn't you set the data register low first and then change the
direction? Otherwise, you may be driving high for a moment. However, if
high is always done by setting the direction as input, why write the
data register each time? I'm assuming whatever is written to the dat_reg
is maintained regardless of pin state.
> + }
> +
> + spin_unlock_irqrestore(&li2c->reglock, flags);
> +}
> +
> +/*
> + * ls7a_gpio_i2c_get - read value back from gpio pin
> + * @mask: gpio pin mask
> + */
> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask)
> +{
> + unsigned long flags;
> + u8 val;
> +
> + spin_lock_irqsave(&li2c->reglock, flags);
> +
> + /* first set this pin as input */
> + val = readb(li2c->dir_reg);
> + val |= mask;
> + writeb(val, li2c->dir_reg);
> +
> + /* then get level state from this pin */
> + val = readb(li2c->dat_reg);
> +
> + spin_unlock_irqrestore(&li2c->reglock, flags);
> +
> + return (val & mask) ? 1 : 0;
> +}
> +
> +/* set the state on the i2c->sda pin */
> +static void ls7a_i2c_set_sda(void *i2c, int state)
> +{
> + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> + return ls7a_gpio_i2c_set(li2c, li2c->sda, state);
> +}
> +
> +/* set the state on the i2c->scl pin */
> +static void ls7a_i2c_set_scl(void *i2c, int state)
> +{
> + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> + return ls7a_gpio_i2c_set(li2c, li2c->scl, state);
> +}
> +
> +/* read the value from the i2c->sda pin */
> +static int ls7a_i2c_get_sda(void *i2c)
> +{
> + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> + return ls7a_gpio_i2c_get(li2c, li2c->sda);
> +}
> +
> +/* read the value from the i2c->scl pin */
> +static int ls7a_i2c_get_scl(void *i2c)
> +{
> + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> + return ls7a_gpio_i2c_get(li2c, li2c->scl);
> +}
> +
> +/*
> + * mainly for dc in ls7a1000 which have builtin gpio emulated i2c
> + *
> + * @index : output channel index, 0 for DVO0, 1 for DVO1
> + */
> +struct lsdc_i2c *lsdc_create_i2c_chan(struct device *dev, void *base, unsigned int index)
> +{
> + char compat[32] = {0};
> + unsigned int udelay = 5;
> + unsigned int timeout = 2200;
> + int nr = -1;
> + struct i2c_adapter *adapter;
> + struct lsdc_i2c *li2c;
> + struct device_node *i2c_np;
> + int ret;
> +
> + li2c = devm_kzalloc(dev, sizeof(*li2c), GFP_KERNEL);
> + if (!li2c)
> + return ERR_PTR(-ENOMEM);
> +
> + li2c->index = index;
> + li2c->dev = dev;
> +
> + if (index == 0) {
> + li2c->sda = 0x01;
> + li2c->scl = 0x02;
> + } else if (index == 1) {
> + li2c->sda = 0x04;
> + li2c->scl = 0x08;
Just require this to be in DT rather than having some default.
> + }
> +
> + spin_lock_init(&li2c->reglock);
> +
> + snprintf(compat, sizeof(compat), "lsdc,i2c-gpio-%d", index);
compatible values shouldn't have an index and you shouldn't need a
index in DT. You need to iterate over child nodes with matching
compatible.
> + i2c_np = of_find_compatible_node(dev->of_node, NULL, compat);
> + if (i2c_np) {
> + u32 sda, scl;
> +
> + dev_dbg(dev, "Has %s property in the DT", compat);
> +
> + /* */
> + ret = of_property_read_u32(i2c_np, "sda", &sda);
Custom properties need a vendor prefix.
> + if (ret == 0)
> + li2c->sda = 1 << sda;
> +
> + ret = of_property_read_u32(i2c_np, "scl", &scl);
> + if (ret == 0)
> + li2c->scl = 1 << scl;
> +
> + /* Optional properties which made the driver more flexible */
> + of_property_read_u32(i2c_np, "udelay", &udelay);
> + of_property_read_u32(i2c_np, "timeout", &timeout);
These aren't documented. Do you really need them in DT?
> + of_property_read_u32(i2c_np, "reg", &nr);
> + }
> +
> + dev_dbg(dev, "%s: sda=%u, scl=%u, nr=%d, udelay=%u, timeout=%u\n",
> + compat, li2c->sda, li2c->scl, nr, udelay, timeout);
> +
> + li2c->reg_base = base;
> +
> + li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG;
> + li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG;
> +
> + li2c->bit.setsda = ls7a_i2c_set_sda;
> + li2c->bit.setscl = ls7a_i2c_set_scl;
> + li2c->bit.getsda = ls7a_i2c_get_sda;
> + li2c->bit.getscl = ls7a_i2c_get_scl;
> + li2c->bit.udelay = udelay;
> + li2c->bit.timeout = usecs_to_jiffies(timeout);
> + li2c->bit.data = li2c;
> +
> + adapter = &li2c->adapter;
> + adapter->algo_data = &li2c->bit;
> + adapter->owner = THIS_MODULE;
> + adapter->class = I2C_CLASS_DDC;
> + adapter->dev.parent = dev;
> + adapter->nr = nr;
> + if (i2c_np) {
> + adapter->dev.of_node = i2c_np;
> + of_node_put(i2c_np);
> + }
> +
> + strscpy(adapter->name, &compat[5], sizeof(adapter->name));
> +
> + i2c_set_adapdata(adapter, li2c);
> +
> + ret = i2c_bit_add_numbered_bus(adapter);
Why do you care what the bus number is? You shouldn't need to.
> + if (ret) {
> + if (i2c_np)
> + of_node_put(i2c_np);
> +
> + devm_kfree(dev, li2c);
> + return ERR_PTR(ret);
> + }
> +
> + return li2c;
> +}
> +
> +void lsdc_destroy_i2c(struct drm_device *ddev, struct lsdc_i2c *li2c)
> +{
> + struct i2c_adapter *adapter;
> +
> + if (li2c) {
> + adapter = &li2c->adapter;
> +
> + if (adapter && adapter->dev.of_node)
> + of_node_put(adapter->dev.of_node);
> +
> + devm_kfree(ddev->dev, li2c);
> + }
> +}
> +
> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev,
> + unsigned int index)
> +{
> + const struct lsdc_chip_desc * const descp = ldev->desc;
> + struct lsdc_i2c *li2c;
> +
> + if (index >= descp->num_of_crtc) {
> + drm_err(ldev->ddev, "I2c adapter is no more than %u, %u\n",
> + descp->num_of_crtc, index);
> + return NULL;
> + }
> +
> + li2c = ldev->li2c[index];
> + if (li2c)
> + return &li2c->adapter;
> +
> + return NULL;
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.h b/drivers/gpu/drm/lsdc/lsdc_i2c.h
> new file mode 100644
> index 000000000000..4ab825143eb4
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.h
> @@ -0,0 +1,42 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KMS driver for Loongson display controller
> + * Copyright (C) 2022 Loongson Corporation
> + */
> +
> +/*
> + * Authors:
> + * Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#ifndef __LSDC_I2C__
> +#define __LSDC_I2C__
> +
> +#include <linux/i2c.h>
> +#include <linux/i2c-algo-bit.h>
> +#include <linux/pci.h>
> +
> +struct lsdc_i2c {
> + struct device *dev;
> + struct i2c_adapter adapter;
> + struct i2c_algo_bit_data bit;
> + /* @reglock: protects concurrent register access */
> + spinlock_t reglock;
> + void __iomem *reg_base;
> + void __iomem *dir_reg;
> + void __iomem *dat_reg;
> + int index;
> + /* pin bit mask */
> + u8 sda;
> + u8 scl;
> +};
> +
> +void lsdc_destroy_i2c(struct drm_device *ddev, struct lsdc_i2c *li2c);
> +
> +struct lsdc_i2c *lsdc_create_i2c_chan(struct device *dev,
> + void *base,
> + unsigned int index);
> +
> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev,
> + unsigned int index);
> +#endif
More information about the dri-devel
mailing list