+
+ return pci_register_driver(&lsdc_pci_driver);
+}
+module_init(lsdc_module_init);
+
+static void __exit lsdc_module_exit(void)
+{
+ pci_unregister_driver(&lsdc_pci_driver);
+}
+module_exit(lsdc_module_exit);
+
+MODULE_DEVICE_TABLE(pci, lsdc_pciid_list);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h
b/drivers/gpu/drm/lsdc/lsdc_drv.h
new file mode 100644
index 000000000000..d67dae0414ca
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_drv.h
@@ -0,0 +1,274 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * KMS driver for Loongson display controller
+ * Copyright (C) 2022 Loongson Corporation
+ *
+ * Authors:
+ * Li Yi <liyi@xxxxxxxxxxx>
+ * Li Chenyang <lichenyang@xxxxxxxxxxx>
+ * Sui Jingfeng <suijingfeng@xxxxxxxxxxx>
+ */
+
+#ifndef __LSDC_DRV_H__
+#define __LSDC_DRV_H__
+
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include <drm/drm_print.h>
+#include <drm/drm_device.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_atomic.h>
+
+#include "lsdc_regs.h"
+#include "lsdc_pll.h"
+
+#define LSDC_NUM_CRTC 2
+
+/*
+ * The display controller in LS7A2000 integrate three loongson
self-made
+ * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI
phy,
+ * they are parallel. Display pipe 1 has only one HDMI phy.
+ * ______________________ _____________
+ * | +-----+ | | |
+ * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA
Monitor |<---+
+ * | | +-----+ | |_____________| |
+ * | | | ______________ |
+ * | | +------+ | | | |
+ * | +--> | HDMI | ----> HDMI Connector --> | HDMI
Monitor |<--+
+ * | +------+ | |______________| |
+ * | +------+
| |
+ * | | i2c6 |
<-------------------------------------------+
+ * | +------+ |
+ * | |
+ * | DC in LS7A2000 |
+ * | |
+ * | +------+ |
+ * | | i2c7 | <--------------------------------+
+ * | +------+ | |
+ * | | ______|_______
+ * | +------+ | | |
+ * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI
Monitor |
+ * | +------+ | |______________|
+ * |______________________|
+ *
+ *
+ * The display controller in LS7A1000 integrate two-way DVO, external
+ * encoder is required except use directly with dpi(rgb888) panel.
+ * ___________________ _________
+ * | -------| | |
+ * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> |
Display |
+ * | _ _ -------| ^ ^ |_________|
+ * | | | | | -------| | |
+ * | |_| |_| | i2c6 <--------+-------------+
+ * | -------|
+ * | _ _ -------|
+ * | | | | | | i2c7 <--------+-------------+
+ * | |_| |_| -------| | | _________
+ * | -------| | | | |
+ * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> |
Panel |
+ * | -------| |_________|
+ * |___________________|
+ *
+ *
+ * The display controller in LS2K1000.
+ * ___________________ _________
+ * | -------| | |
+ * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> |
Display |
+ * | _ _ -------| ^ ^ |_________|
+ * | | | | | | | |
+ * | |_| |_| | +------+ |
+ * | <---->| i2c0 |<---------+
+ * | DC in LS2K1000 | +------+
+ * | _ _ | +------+
+ * | | | | | <---->| i2c1 |----------+
+ * | |_| |_| | +------+ | _________
+ * | -------| | | | |
+ * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> |
Panel |
+ * | -------| |_________|
+ * |___________________|
+ *
+ *
+ * The display controller in LS2K0500, LS2K0500 has a built-in
transparent
+ * VGA encoder which is connected to display pipe 1(CRTC1).
+ * ___________________ _________
+ * | -------| | |
+ * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> |
Display |
+ * | _ _ -------| ^ ^ |_________|
+ * | | | | | | | |
+ * | |_| |_| | +------+ |
+ * | <---->| i2c4 |<---------+
+ * | DC in LS2K0500 | +------+
+ * | _ _ | +------+
+ * | | | | | <---->| i2c5 |-------------------+
+ * | |_| |_| | +------+ ______|______
+ * | +-----+ | | |
+ * | CRTC1 --> | VGA | ------------------------> | VGA Monitor |
+ * | +-----+ | |_____________|
+ * |___________________|
+ *
+ * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM.
+ * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have
dediacated
+ * Video RAM. The dc in LS2K2000 is basicly same with the dc in
LS7A1000
+ * except that LS2K2000 don't have a video RAM and have only one
built-in
+ * hdmi encoder.
+ *
+ * There is only a 1:1 mapping of encoders and connectors for the DC,
+ * each CRTC have two FB address registers.
+ */
+
+enum loongson_chip_family {
+ CHIP_UNKNOWN = 0,
+ CHIP_LS7A1000 = 1, /* North bridge of
LS3A3000/LS3A4000/LS3A5000 */
+ CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with
built-in HDMI encoder */
+ CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */
+ CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0
Ghz */
+ CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */
+ CHIP_LAST,
+};
+
+struct lsdc_desc {
+ enum loongson_chip_family chip;
+ u32 num_of_crtc;
+ u32 max_pixel_clk;
+ u32 max_width;
+ u32 max_height;
+ u32 num_of_hw_cursor;
+ u32 hw_cursor_w;
+ u32 hw_cursor_h;
+ u32 pitch_align; /* DMA alignment constraint */
+ u64 mc_bits; /* physical address bus bit width */
+ bool has_vblank_counter;
+ bool has_builtin_i2c;
+ bool has_vram;
+ bool has_hpd_reg;
+ bool is_soc;
+};
+
+struct lsdc_i2c {
+ struct i2c_adapter adapter;
+ struct i2c_algo_bit_data bit;
+ struct drm_device *ddev;
+ void __iomem *reg_base;
+ void __iomem *dir_reg;
+ void __iomem *dat_reg;
+ /* pin bit mask */
+ u8 sda;
+ u8 scl;
+};
+
+/*
+ * struct lsdc_display_pipe - Abstraction of hardware display
pipeline.
+ * @crtc: CRTC control structure
+ * @primary: framebuffer plane control structure
+ * @cursor: cursor plane control structure
+ * @output: output control structure of this display pipe bind
+ * @pixpll: pixel pll control structure
+ * @index: the index corresponding to the hardware display pipe
+ */
+struct lsdc_display_pipe {
+ struct drm_crtc crtc;
+ struct drm_plane primary;
+ struct drm_plane cursor;
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct lsdc_pll pixpll;
+ struct lsdc_i2c *li2c;
+ int index;
+};
+
+static inline struct lsdc_display_pipe *
+crtc_to_display_pipe(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct lsdc_display_pipe, crtc);
+}
+
+static inline struct lsdc_display_pipe *
+cursor_to_display_pipe(struct drm_plane *cursor)
+{
+ return container_of(cursor, struct lsdc_display_pipe, cursor);
+}
+
+static inline struct lsdc_display_pipe *
+connector_to_display_pipe(struct drm_connector *conn)
+{
+ return container_of(conn, struct lsdc_display_pipe, connector);
+}
+
+static inline struct lsdc_display_pipe *
+encoder_to_display_pipe(struct drm_encoder *enc)
+{
+ return container_of(enc, struct lsdc_display_pipe, encoder);
+}
+
+struct lsdc_crtc_state {
+ struct drm_crtc_state base;
+ struct lsdc_pll_parms pparms;
+};
+
+struct lsdc_device {
+ struct drm_device base;
+
+ /* @reglock: protects concurrent register access */
+ spinlock_t reglock;
+ void __iomem *reg_base;
+ void __iomem *vram;
+ resource_size_t vram_base;
+ resource_size_t vram_size;
+ u64 mc_mask;
+
+ struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC];
+
+ /* @num_output: count the number of active display pipe */
+ unsigned int num_output;
+
+ /* @descp: features description of the DC variant */
+ const struct lsdc_desc *descp;
+
+ u32 irq_status;
+};
+
+static inline struct lsdc_device *
+to_lsdc(struct drm_device *ddev)
+{
+ return container_of(ddev, struct lsdc_device, base);
+}
+
+static inline struct lsdc_crtc_state *
+to_lsdc_crtc_state(struct drm_crtc_state *base)
+{
+ return container_of(base, struct lsdc_crtc_state, base);
+}
+
+void lsdc_debugfs_init(struct drm_minor *minor);
+
+int lsdc_crtc_init(struct drm_device *ddev,
+ struct drm_crtc *crtc,
+ unsigned int index,
+ struct drm_plane *primary,
+ struct drm_plane *cursor);
+
+int lsdc_plane_init(struct lsdc_device *ldev,
+ struct drm_plane *plane,
+ enum drm_plane_type type,
+ unsigned int index);
+
+int lsdc_create_output(struct lsdc_device *ldev, unsigned int index);
+
+struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev,
+ void *base,
+ unsigned int index);
+
+struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev,
int index);
+
+irqreturn_t lsdc_irq_thread_handler(int irq, void *arg);
+irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev);
+
+u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset);
+void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32
val);
+
+#endif
diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c
b/drivers/gpu/drm/lsdc/lsdc_i2c.c
new file mode 100644
index 000000000000..b380098409ca
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/i2c.h>
+#include <drm/drm_managed.h>
+#include "lsdc_drv.h"
+#include "lsdc_regs.h"
+
+/*
+ * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask
+ * @mask: gpio pin mask
+ * @state: "0" for low, "1" for high
+ */
+static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int
mask, int state)
+{
+ struct lsdc_device *ldev = to_lsdc(li2c->ddev);
+ unsigned long flags;
+ u8 val;
+
+ spin_lock_irqsave(&ldev->reglock, flags);
+
+ if (state) {
+ /*
+ * Setting this pin as input directly, write 1 for Input.
+ * The external pull-up resistor will pull the level up
+ */
+ val = readb(li2c->dir_reg);
+ val |= mask;
+ writeb(val, li2c->dir_reg);
+ } else {
+ /* First set this pin as output, write 0 for Output */
+ val = readb(li2c->dir_reg);
+ val &= ~mask;
+ writeb(val, li2c->dir_reg);
+
+ /* Then, make this pin output 0 */
+ val = readb(li2c->dat_reg);
+ val &= ~mask;
+ writeb(val, li2c->dat_reg);
+ }
+
+ spin_unlock_irqrestore(&ldev->reglock, flags);
+}
+
+/*
+ * ls7a_gpio_i2c_get - read value back from the gpio pin indicated
by mask
+ * @mask: gpio pin mask
+ * return "0" for low, "1" for high
+ */
+static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask)
+{
+ struct lsdc_device *ldev = to_lsdc(li2c->ddev);
+ unsigned long flags;
+ u8 val;
+
+ spin_lock_irqsave(&ldev->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(&ldev->reglock, flags);
+
+ return (val & mask) ? 1 : 0;
+}
+
+static void ls7a_i2c_set_sda(void *i2c, int state)
+{
+ struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
+ /* set state on the li2c->sda pin */
+ return ls7a_gpio_i2c_set(li2c, li2c->sda, state);
+}
+
+static void ls7a_i2c_set_scl(void *i2c, int state)
+{
+ struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
+ /* set state on the li2c->scl pin */
+ return ls7a_gpio_i2c_set(li2c, li2c->scl, state);
+}
+
+static int ls7a_i2c_get_sda(void *i2c)
+{
+ struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
+ /* read value from the li2c->sda pin */
+ return ls7a_gpio_i2c_get(li2c, li2c->sda);
+}
+
+static int ls7a_i2c_get_scl(void *i2c)
+{
+ struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
+ /* read the value from the li2c->scl pin */
+ return ls7a_gpio_i2c_get(li2c, li2c->scl);
+}
+
+static void lsdc_destroy_i2c(struct drm_device *ddev, void *data)
+{
+ struct lsdc_i2c *li2c = (struct lsdc_i2c *)data;
+
+ if (li2c) {
+ i2c_del_adapter(&li2c->adapter);
+ kfree(li2c);
+ }
+}
+
+/*
+ * The DC in ls7a1000/ls7a2000 have builtin gpio hardware
+ *
+ * @base: gpio reg base
+ * @index: output channel index, 0 for DVO0, 1 for DVO1
+ */
+struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev,
+ void *base,
+ unsigned int index)
+{
+ struct i2c_adapter *adapter;
+ struct lsdc_i2c *li2c;
+ int ret;
+
+ li2c = kzalloc(sizeof(*li2c), GFP_KERNEL);
+ if (!li2c)
+ return ERR_PTR(-ENOMEM);
+
+ if (index == 0) {
+ li2c->sda = 0x01; /* pin 0 */
+ li2c->scl = 0x02; /* pin 1 */
+ } else if (index == 1) {
+ li2c->sda = 0x04; /* pin 2 */
+ li2c->scl = 0x08; /* pin 3 */
+ }
+
+ li2c->reg_base = base;
+ li2c->ddev = ddev;
+ 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 = 5;
+ li2c->bit.timeout = usecs_to_jiffies(2200);
+ li2c->bit.data = li2c;
+
+ adapter = &li2c->adapter;
+ adapter->algo_data = &li2c->bit;
+ adapter->owner = THIS_MODULE;
+ adapter->class = I2C_CLASS_DDC;
+ adapter->dev.parent = ddev->dev;
+ adapter->nr = -1;
+
+ snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u",
index);
+
+ i2c_set_adapdata(adapter, li2c);
+
+ ret = i2c_bit_add_bus(adapter);
+ if (ret) {
+ kfree(li2c);
+ return ERR_PTR(ret);
+ }
+
+ ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c);
+ if (ret)
+ return NULL;
+
+ drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n",
+ adapter->name, li2c->sda, li2c->scl, index);
+
+ return li2c;
+}
+
+static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+
+ if (descp->chip == CHIP_LS2K1000)
+ return index;
+
+ if (descp->chip == CHIP_LS2K0500)
+ return index + 2;
+
+ return index;
+}
+
+struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev,
+ int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+ struct lsdc_display_pipe *dispipe = &ldev->dispipe[index];
+
+ if (descp->has_builtin_i2c) {
+ struct lsdc_i2c *li2c = dispipe->li2c;
+
+ if (li2c)
+ return &li2c->adapter;
+ }
+
+ return i2c_get_adapter(lsdc_get_i2c_id(ldev, index));
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c
b/drivers/gpu/drm/lsdc/lsdc_irq.c
new file mode 100644
index 000000000000..71c85f09bb6f
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_irq.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_vblank.h>
+#include "lsdc_drv.h"
+#include "lsdc_regs.h"
+
+/*
+ * For the DC in ls7a2000, clearing interrupt status is achieved by
+ * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and
+ * ls2k0500, clearing interrupt status is achieved by write "0" to
+ * LSDC_INT_REG. Two different hardware engineer of Loongson modify
+ * it as their will.
+ */
+
+/* For the DC in ls7a2000 */
+static irqreturn_t lsdc_irq_handler(int irq, void *arg)
+{
+ struct drm_device *ddev = arg;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u32 val;
+
+ /* Read the interrupt status */
+ val = lsdc_rreg32(ldev, LSDC_INT_REG);
+ if ((val & INT_STATUS_MASK) == 0) {
+ drm_warn(ddev, "no interrupt occurs\n");
+ return IRQ_NONE;
+ }
+
+ ldev->irq_status = val;
+
+ /* write "1" to clear the interrupt status */
+ lsdc_wreg32(ldev, LSDC_INT_REG, val);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */
+static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg)
+{
+ struct drm_device *ddev = arg;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ u32 val;
+
+ /* Read the interrupt status */
+ val = lsdc_rreg32(ldev, LSDC_INT_REG);
+ if ((val & INT_STATUS_MASK) == 0) {
+ drm_warn(ddev, "no interrupt occurs\n");
+ return IRQ_NONE;
+ }
+
+ ldev->irq_status = val;
+
+ /* write "0" to clear the interrupt status */
+ lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK);
+
+ return IRQ_WAKE_THREAD;
+}
+
+irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+
+ if (descp->chip == CHIP_LS7A2000)
+ return lsdc_irq_handler;
+
+ return lsdc_irq_handler_legacy;
+}
+
+irqreturn_t lsdc_irq_thread_handler(int irq, void *arg)
+{
+ struct drm_device *ddev = arg;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct drm_crtc *crtc;
+
+ if (ldev->irq_status & INT_CRTC0_VSYNC) {
+ crtc = drm_crtc_from_index(ddev, 0);
+ drm_crtc_handle_vblank(crtc);
+ }
+
+ if (ldev->irq_status & INT_CRTC1_VSYNC) {
+ crtc = drm_crtc_from_index(ddev, 1);
+ drm_crtc_handle_vblank(crtc);
+ }
+
+ return IRQ_HANDLED;
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c
b/drivers/gpu/drm/lsdc/lsdc_output.c
new file mode 100644
index 000000000000..d4bc7666d9ea
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_output.c
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_edid.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_connector.h>
+#include "lsdc_drv.h"
+
+static int lsdc_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 enum drm_connector_status
+lsdc_unknown_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;
+ } else {
+ if (connector->connector_type == DRM_MODE_CONNECTOR_DPI)
+ return connector_status_connected;
+
+ if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
+ return connector_status_connected;
+ }
+
+ return connector_status_unknown;
+}
+
+static enum drm_connector_status
+lsdc_hdmi_connector_detect(struct drm_connector *connector, bool
force)
+{
+ struct lsdc_display_pipe *pipe =
connector_to_display_pipe(connector);
+ struct lsdc_device *ldev = to_lsdc(connector->dev);
+ u32 val;
+
+ val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
+
+ if (pipe->index == 0) {
+ if (val & HDMI0_HPD_FLAG)
+ return connector_status_connected;
+ }
+
+ if (pipe->index == 1) {
+ if (val & HDMI1_HPD_FLAG)
+ return connector_status_connected;
+ }
+
+ return connector_status_disconnected;
+}
+
+static enum drm_connector_status
+lsdc_hdmi_vga_connector_detect(struct drm_connector *connector,
bool force)
+{
+ struct lsdc_display_pipe *pipe =
connector_to_display_pipe(connector);
+ struct drm_device *ddev = connector->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct i2c_adapter *ddc;
+ u32 val;
+
+ val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG);
+
+ if (pipe->index == 1) {
+ if (val & HDMI1_HPD_FLAG)
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+ }
+
+ if (pipe->index == 0) {
+ if (val & HDMI0_HPD_FLAG)
+ return connector_status_connected;
+
+ ddc = connector->ddc;
+ if (ddc) {
+ if (drm_probe_ddc(ddc))
+ return connector_status_connected;
+
+ return connector_status_disconnected;
+ }
+ }
+
+ return connector_status_unknown;
+}
+
+static struct drm_encoder *
+lsdc_connector_get_best_encoder(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_display_pipe *pipe =
connector_to_display_pipe(connector);
+
+ return &pipe->encoder;
+}
+
+static const struct drm_connector_helper_funcs
lsdc_connector_helpers = {
+ .atomic_best_encoder = lsdc_connector_get_best_encoder,
+ .get_modes = lsdc_get_modes,
+};
+
+static const struct drm_connector_funcs
lsdc_unknown_connector_funcs = {
+ .detect = lsdc_unknown_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 const struct drm_connector_funcs
lsdc_hdmi_vga_connector_funcs = {
+ .detect = lsdc_hdmi_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,
+};
+
+static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = {
+ .detect = lsdc_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,
+};
+
+static const struct drm_connector_funcs *
+lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+
+ if (descp->chip == CHIP_LS7A2000) {
+ if (index == 0)
+ return &lsdc_hdmi_vga_connector_funcs;
+
+ if (index == 1)
+ return &lsdc_hdmi_connector_funcs;
+ }
+
+ return &lsdc_unknown_connector_funcs;
+}
+
+/*
+ * we provide a default support before DT/VBIOS is supported
+ */
+static int lsdc_get_encoder_type(struct lsdc_device *ldev,
+ unsigned int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+
+ if (descp->chip == CHIP_LS7A2000) {
+ if (index == 0)
+ return DRM_MODE_ENCODER_DAC;
+ if (index == 1)
+ return DRM_MODE_ENCODER_TMDS;
+ }
+
+ if (descp->chip == CHIP_LS7A1000 || descp->chip ==
CHIP_LS2K1000) {
+ if (index == 0)
+ return DRM_MODE_ENCODER_DPI;
+ if (index == 1)
+ return DRM_MODE_ENCODER_DPI;
+ }
+
+ if (descp->chip == CHIP_LS2K0500) {
+ if (index == 0)
+ return DRM_MODE_ENCODER_DPI;
+ if (index == 1)
+ return DRM_MODE_ENCODER_DAC;
+ }
+
+ return DRM_MODE_ENCODER_NONE;
+}
+
+/*
+ * provide a default before DT/VBIOS support is upstreamed
+ */
+static int lsdc_get_connector_type(struct lsdc_device *ldev,
+ unsigned int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+
+ if (descp->chip == CHIP_LS7A2000) {
+ if (index == 0)
+ return DRM_MODE_CONNECTOR_VGA;
+ if (index == 1)
+ return DRM_MODE_CONNECTOR_HDMIA;
+ }
+
+ if (descp->chip == CHIP_LS7A1000 || descp->chip ==
CHIP_LS2K1000) {
+ if (index == 0)
+ return DRM_MODE_CONNECTOR_DPI;
+ if (index == 1)
+ return DRM_MODE_CONNECTOR_DPI;
+ }
+
+ if (descp->chip == CHIP_LS2K0500) {
+ if (index == 0)
+ return DRM_MODE_CONNECTOR_DPI;
+ if (index == 1)
+ return DRM_MODE_CONNECTOR_VGA;
+ }
+
+ return DRM_MODE_CONNECTOR_Unknown;
+}
+
+static void lsdc_hdmi_disable(struct drm_encoder *encoder)
+{
+ struct lsdc_display_pipe *dispipe =
encoder_to_display_pipe(encoder);
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ unsigned int index = dispipe->index;
+ u32 val;
+
+ if (index == 0)
+ val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG);
+ else if (index == 1)
+ val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG);
+
+ val &= ~HDMI_PHY_EN;
+
+ if (index == 0)
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val);
+ else if (index == 1)
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val);
+
+ drm_dbg(ddev, "HDMI-%u disabled\n", index);
+}
+
+static void lsdc_hdmi_enable(struct drm_encoder *encoder)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct lsdc_display_pipe *dispipe =
encoder_to_display_pipe(encoder);
+ unsigned int index = dispipe->index;
+ u32 val;
+
+ /* we are using software gpio emulated i2c */
+ val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN |
+ HDMI_PACKET_EN | HDMI_INTERFACE_EN;
+
+ if (index == 0) {
+ /* Enable HDMI-0 */
+ lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val);
+
+ lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040);
+ } else if (index == 1) {
+ /* Enable HDMI-1 */
+ lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val);
+
+ lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040);
+ }
+
+ 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;
+
+ if (index == 0)
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val);
+ else if (index == 1)
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val);
+
+ drm_dbg(ddev, "HDMI-%u enabled\n", index);
+}
+
+/*
+ * Fout = M * Fin
+ *
+ * M = (4 * LF) / (IDF * ODF)
+ *
+ * Loongson HDMI require M = 10
+ */
+static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev,
+ int index,
+ int clock)
+{
+ struct drm_device *ddev = &ldev->base;
+ int count = 0;
+ u32 val;
+
+ /* disable phy pll */
+ if (index == 0)
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0);
+ else if (index == 1)
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0);
+
+ /*
+ * 10 = (4 * 40) / (8 * 2)
+ */
+ val = (8 << HDMI_PLL_IDF_SHIFT) |
+ (40 << HDMI_PLL_LF_SHIFT) |
+ (1 << HDMI_PLL_ODF_SHIFT) |
+ HDMI_PLL_ENABLE;
+
+ drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock);
+
+ if (index == 0)
+ lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val);
+ else if (index == 1)
+ lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val);
+
+ /* wait hdmi phy pll lock */
+ do {
+ if (index == 0)
+ val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG);
+ else if (index == 1)
+ val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG);
+
+ ++count;
+
+ if (val & HDMI_PLL_LOCKED) {
+ drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d
cycles)\n",
+ index, count);
+ break;
+ }
+ } while (count < 1000);
+}
+
+static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct lsdc_display_pipe *dispipe =
encoder_to_display_pipe(encoder);
+ unsigned int index = dispipe->index;
+ struct drm_display_mode *mode = &crtc_state->mode;
+
+ lsdc_hdmi_phy_pll_config(ldev, index, mode->clock);
+
+ drm_dbg(ddev, "HDMI-%u modeset\n", index);
+}
+
+static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs
= {
+ .disable = lsdc_hdmi_disable,
+ .enable = lsdc_hdmi_enable,
+ .atomic_mode_set = lsdc_hdmi_atomic_mode_set,
+};
+
+static void lsdc_hdmi_reset(struct drm_encoder *encoder)
+{
+ struct drm_device *ddev = encoder->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct lsdc_display_pipe *dispipe =
encoder_to_display_pipe(encoder);
+ unsigned int index = dispipe->index;
+ u32 val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN |
+ HDMI_PACKET_EN | HDMI_INTERFACE_EN;
+
+ lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val);
+ lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val);
+
+ drm_dbg(ddev, "HDMI-%u Reset\n", index);
+}
+
+static const struct drm_encoder_funcs lsdc_encoder_funcs = {
+ .reset = lsdc_hdmi_reset,
+ .destroy = drm_encoder_cleanup,
+};
+
+int lsdc_create_output(struct lsdc_device *ldev, unsigned int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+ struct drm_device *ddev = &ldev->base;
+ struct lsdc_display_pipe *dispipe = &ldev->dispipe[index];
+ struct drm_encoder *encoder = &dispipe->encoder;
+ struct drm_connector *connector = &dispipe->connector;
+ struct i2c_adapter *ddc = NULL;
+ struct lsdc_i2c *li2c;
+ int ret;
+
+ ret = drm_encoder_init(ddev,
+ encoder,
+ &lsdc_encoder_funcs,
+ lsdc_get_encoder_type(ldev, index),
+ "encoder-%u",
+ index);
+ if (ret) {
+ drm_err(ddev, "Failed to init encoder: %d\n", ret);
+ return ret;
+ }
+
+ encoder->possible_crtcs = BIT(index);
+
+ if (descp->chip == CHIP_LS7A2000)
+ drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs);
+
+ if (descp->has_builtin_i2c) {
+ li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index);
+ if (IS_ERR(li2c))
+ return PTR_ERR(li2c);
+
+ dispipe->li2c = li2c;
+
+ ddc = &li2c->adapter;
+ } else {
+ ddc = lsdc_get_i2c_adapter(ldev, index);
+ if (IS_ERR(ddc)) {
+ drm_err(ddev, "Error get ddc for output-%u\n", index);
+ return PTR_ERR(ddc);
+ }
+ }
+
+ ret = drm_connector_init_with_ddc(ddev,
+ connector,
+ lsdc_get_connector_func(ldev, index),
+ lsdc_get_connector_type(ldev, index),
+ ddc);
+ if (ret) {
+ drm_err(ddev, "Init connector-%d failed\n", index);
+ return ret;
+ }
+
+ drm_connector_helper_add(connector, &lsdc_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;
+
+ ldev->num_output++;
+
+ drm_info(ddev, "output-%u initialized\n", index);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c
b/drivers/gpu/drm/lsdc/lsdc_plane.c
new file mode 100644
index 000000000000..0f779c97d53b
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_plane.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_gem_vram_helper.h>
+#include "lsdc_drv.h"
+#include "lsdc_regs.h"
+#include "lsdc_pll.h"
+
+static const u32 lsdc_primary_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+};
+
+static const u32 lsdc_cursor_formats[] = {
+ DRM_FORMAT_ARGB8888,
+};
+
+static const u64 lsdc_fb_format_modifiers[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID
+};
+
+static void lsdc_update_fb_format(struct lsdc_device *ldev,
+ struct drm_crtc *crtc,
+ const struct drm_format_info *fmt_info)
+{
+ unsigned int index = drm_crtc_index(crtc);
+ u32 val;
+ u32 fmt;
+
+ switch (fmt_info->format) {
+ case DRM_FORMAT_XRGB8888:
+ fmt = LSDC_PF_XRGB8888;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ fmt = LSDC_PF_XRGB8888;
+ break;
+ default:
+ fmt = LSDC_PF_XRGB8888;
+ break;
+ }
+
+ if (index == 0) {
+ val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG);
+ val = (val & ~CFG_PIX_FMT_MASK) | fmt;
+ lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val);
+ } else if (index == 1) {
+ val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG);
+ val = (val & ~CFG_PIX_FMT_MASK) | fmt;
+ lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val);
+ }
+}
+
+static void lsdc_update_fb_start_addr(struct lsdc_device *ldev,
+ struct drm_crtc *crtc,
+ u64 paddr)
+{
+ struct drm_device *ddev = &ldev->base;
+ unsigned int index = drm_crtc_index(crtc);
+ u32 lo_addr_reg;
+ u32 hi_addr_reg;
+ u32 val;
+
+ /*
+ * Find which framebuffer address register should update.
+ * if FB_ADDR0_REG is in using, we write the address to
FB_ADDR0_REG,
+ * if FB_ADDR1_REG is in using, we write the address to
FB_ADDR1_REG
+ * for each CRTC, the switch using one fb register to another is
+ * trigger by triggered by set CFG_PAGE_FLIP bit of
LSDC_CRTCx_CFG_REG
+ */
+ if (index == 0) {
+ val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG);
+ if (val & CFG_FB_IN_USING) {
+ lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG;
+ hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG;
+ drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n");
+ } else {
+ lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG;
+ hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG;
+ drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n");
+ }
+ } else if (index == 1) {
+ val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG);
+ if (val & CFG_FB_IN_USING) {
+ lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG;
+ hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG;
+ drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n");
+ } else {
+ lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG;
+ hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG;
+ drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n");
+ }
+ }
+
+ /* 40-bit width physical address bus */
+ lsdc_wreg32(ldev, lo_addr_reg, paddr);
+ lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF);
+
+ drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr);
+}
+
+static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb,
+ struct drm_plane_state *state,
+ unsigned int plane)
+{
+ unsigned int offset = fb->offsets[plane];
+
+ offset += fb->format->cpp[plane] * (state->src_x >> 16);
+ offset += fb->pitches[plane] * (state->src_y >> 16);
+
+ return offset;
+}
+
+static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb)
+{
+ struct drm_gem_vram_object *gbo;
+ s64 gpu_addr;
+
+ gbo = drm_gem_vram_of_gem(fb->obj[0]);
+ gpu_addr = drm_gem_vram_offset(gbo);
+
+ return gpu_addr;
+}
+
+static int lsdc_primary_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *plane_state =
drm_atomic_get_new_plane_state(state, plane);
+ struct drm_crtc *crtc = plane_state->crtc;
+ struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
+
+ if (!crtc)
+ return 0;
+
+ return drm_atomic_helper_check_plane_state(plane_state,
+ crtc_state,
+ DRM_PLANE_NO_SCALING,
+ DRM_PLANE_NO_SCALING,
+ false,
+ true);
+}
+
+static void lsdc_update_fb_stride(struct lsdc_device *ldev,
+ struct drm_crtc *crtc,
+ unsigned int stride)
+{
+ unsigned int index = drm_crtc_index(crtc);
+
+ if (index == 0)
+ lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride);
+ else if (index == 1)
+ lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride);
+
+ drm_dbg(crtc->dev, "update stride to %u\n", stride);
+}
+
+static void lsdc_primary_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct lsdc_device *ldev = to_lsdc(plane->dev);
+ struct drm_plane_state *new_plane_state =
drm_atomic_get_new_plane_state(state, plane);
+ struct drm_crtc *crtc = new_plane_state->crtc;
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0);
+ dma_addr_t fb_addr;
+ s64 gpu_addr;
+
+ gpu_addr = lsdc_get_vram_bo_offset(fb);
+ if (gpu_addr < 0)
+ return;
+
+ fb_addr = ldev->vram_base + gpu_addr + fb_offset;
+
+ lsdc_update_fb_start_addr(ldev, crtc, fb_addr);
+
+ lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]);
+
+ lsdc_update_fb_format(ldev, crtc, fb->format);
+}
+
+static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ drm_dbg(plane->dev, "%s disabled\n", plane->name);
+}
+
+static const struct drm_plane_helper_funcs
lsdc_primary_plane_helpers = {
+ .prepare_fb = drm_gem_vram_plane_helper_prepare_fb,
+ .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb,
+ .atomic_check = lsdc_primary_plane_atomic_check,
+ .atomic_update = lsdc_primary_plane_atomic_update,
+ .atomic_disable = lsdc_primary_plane_atomic_disable,
+};
+
+static int lsdc_cursor_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *new_plane_state =
drm_atomic_get_new_plane_state(state, plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct drm_crtc *crtc = new_plane_state->crtc;
+ struct drm_crtc_state *crtc_state;
+ int ret;
+
+ /* no need for further checks if the plane is being disabled */
+ if (!crtc || !fb)
+ return 0;
+
+ if (!new_plane_state->visible)
+ return 0;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state,
+ new_plane_state->crtc);
+
+ ret = drm_atomic_helper_check_plane_state(new_plane_state,
+ crtc_state,
+ DRM_PLANE_NO_SCALING,
+ DRM_PLANE_NO_SCALING,
+ true,
+ true);
+
+ return ret;
+}
+
+/*
+ * There is only one hardware cursor in ls7a1000, ls2k1000 and
ls2k0500.
+ * we made it shared by the two CRTC, which can satisfy peoples who
use
+ * double screen extend mode only. On clone screen usage case, the
cursor
+ * on display pipe 1 will not be able to display.
+ *
+ * Update location of the cursor, attach it to CRTC0 or CRTC1 on
the runtime.
+ */
+static void lsdc_cursor_update_location_quirks(struct lsdc_device
*ldev,
+ struct drm_crtc *crtc)
+{
+ u32 val = CURSOR_FORMAT_ARGB8888;
+
+ /*
+ * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be
+ * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then
+ * the cursor will be locate at CRTC-0. The cursor is alway on the
+ * top of the primary. Compositing the primary plane and cursor
+ * plane is automatically done by hardware.
+ */
+ if (drm_crtc_index(crtc))
+ val |= CURSOR_LOCATION;
+
+ lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val);
+}
+
+/* update the position of the cursor */
+static void lsdc_cursor_update_position_quirks(struct lsdc_device
*ldev,
+ int x,
+ int y)
+{
+ if (x < 0)
+ x = 0;
+
+ if (y < 0)
+ y = 0;
+
+ lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x);
+}
+
+static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct drm_plane_state *new_plane_state =
drm_atomic_get_new_plane_state(state, plane);
+ struct drm_plane_state *old_plane_state =
drm_atomic_get_old_plane_state(state, plane);
+ struct drm_framebuffer *new_fb = new_plane_state->fb;
+ struct drm_framebuffer *old_fb = old_plane_state->fb;
+
+ if (new_fb != old_fb) {
+ s64 offset = lsdc_get_vram_bo_offset(new_fb);
+ u64 cursor_addr = ldev->vram_base + offset;
+
+ drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset);
+
+ lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr);
+ lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>
32) & 0xFF);
+ }
+
+ lsdc_cursor_update_position_quirks(ldev,
new_plane_state->crtc_x, new_plane_state->crtc_y);
+
+ lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc);
+}
+
+/* update the format, size and location of the cursor */
+static void lsdc_cursor_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane);
+ struct drm_plane_state *new_plane_state =
drm_atomic_get_new_plane_state(state, plane);
+ struct drm_framebuffer *new_fb = new_plane_state->fb;
+ int x = new_plane_state->crtc_x;
+ int y = new_plane_state->crtc_y;
+ u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64;
+ u64 cursor_addr = ldev->vram_base +
lsdc_get_vram_bo_offset(new_fb);
+
+ if (x < 0)
+ x = 0;
+
+ if (y < 0)
+ y = 0;
+
+ if (dispipe->index == 0) {
+ lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>
32) & 0xFF);
+ lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr);
+ /* Attach Cursor-0 to CRTC-0 */
+ lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf &
~CURSOR_LOCATION);
+ lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x);
+ return;
+ }
+
+ if (dispipe->index == 1) {
+ lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >>
32) & 0xFF);
+ lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr);
+ /* Attach Cursor-1 to CRTC-1 */
+ lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf |
CURSOR_LOCATION);
+ lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x);
+ return;
+ }
+}
+
+static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+
+ /* Set the format to 0 actually display the cursor */
+ lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0);
+
+ drm_dbg(ddev, "%s disabled\n", plane->name);
+}
+
+static void lsdc_cursor_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *ddev = plane->dev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane);
+
+ if (dispipe->index == 0)
+ lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0);
+ else if (dispipe->index == 1)
+ lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0);
+
+ drm_dbg(ddev, "%s disabled\n", plane->name);
+}
+
+static const struct drm_plane_helper_funcs
lsdc_cursor_helpers_quirk = {
+ .prepare_fb = drm_gem_vram_plane_helper_prepare_fb,
+ .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb,
+ .atomic_check = lsdc_cursor_atomic_check,
+ .atomic_update = lsdc_cursor_atomic_update_quirks,
+ .atomic_disable = lsdc_cursor_atomic_disable_quirks,
+};
+
+static const struct drm_plane_helper_funcs
lsdc_cursor_plane_helpers = {
+ .prepare_fb = drm_gem_vram_plane_helper_prepare_fb,
+ .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb,
+ .atomic_check = lsdc_cursor_atomic_check,
+ .atomic_update = lsdc_cursor_atomic_update,
+ .atomic_disable = lsdc_cursor_atomic_disable,
+};
+
+static const struct drm_plane_funcs lsdc_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = drm_plane_cleanup,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static const struct drm_plane_helper_funcs *
+lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp)
+{
+ if (descp->chip == CHIP_LS7A2000)
+ return &lsdc_cursor_plane_helpers;
+
+ return &lsdc_cursor_helpers_quirk;
+}
+
+int lsdc_plane_init(struct lsdc_device *ldev,
+ struct drm_plane *plane,
+ enum drm_plane_type type,
+ unsigned int index)
+{
+ const struct lsdc_desc *descp = ldev->descp;
+ struct drm_device *ddev = &ldev->base;
+ unsigned int format_count;
+ const u32 *formats;
+ const char *name;
+ int ret;
+
+ switch (type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ formats = lsdc_primary_formats;
+ format_count = ARRAY_SIZE(lsdc_primary_formats);
+ name = "primary-%u";
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ formats = lsdc_cursor_formats;
+ format_count = ARRAY_SIZE(lsdc_cursor_formats);
+ name = "cursor-%u";
+ break;
+ case DRM_PLANE_TYPE_OVERLAY:
+ drm_err(ddev, "overlay plane is not supported\n");
+ break;
+ }
+
+ ret = drm_universal_plane_init(ddev, plane, 1 << index,
+ &lsdc_plane_funcs,
+ formats, format_count,
+ lsdc_fb_format_modifiers,
+ type, name, index);
+ if (ret) {
+ drm_err(ddev, "%s failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ switch (type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ drm_plane_helper_add(plane, &lsdc_primary_plane_helpers);
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ drm_plane_helper_add(plane,
lsdc_get_cursor_helper_funcs(descp));
+ break;
+ case DRM_PLANE_TYPE_OVERLAY:
+ drm_err(ddev, "overlay plane is not supported\n");
+ break;
+ }
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c
b/drivers/gpu/drm/lsdc/lsdc_pll.c
new file mode 100644
index 000000000000..6ed74989a6f5
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_pll.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "lsdc_drv.h"
+#include "lsdc_regs.h"
+#include "lsdc_pll.h"
+
+/*
+ * The structure of the pixel PLL register is evolved with times.
+ * All loongson's cpu is little endian.
+ */
+
+/* u64 */
+struct ls7a1000_pixpll_bitmap {
+ /* Byte 0 ~ Byte 3 */
+ unsigned div_out : 7; /* 0 : 6 output clock
divider */
+ unsigned __1 : 14; /* 7 :
20 */
+ unsigned loopc : 9; /* 21 : 29 clock
multiplier */
+ unsigned __2 : 2; /* 30 :
31 */
+
+ /* Byte 4 ~ Byte 7 */
+ unsigned div_ref : 7; /* 0 : 6 input clock
divider */
+ unsigned locked : 1; /* 7 PLL locked
status */
+ unsigned sel_out : 1; /* 8 output clk
selector */
+ unsigned __3 : 2; /* 9 : 10 reserved */
+ unsigned set_param : 1; /* 11 trigger the
update */
+ unsigned bypass : 1; /* 12 */
+ unsigned powerdown : 1; /* 13 */
+ unsigned __4 : 18; /* 14 :
31 */
+};
+
+/* u128 */
+struct ls2k1000_pixpll_bitmap {
+ /* Byte 0 ~ Byte 3 */
+ unsigned sel_out : 1; /* 0 select this
PLL */
+ unsigned __1 : 1; /* 1 */
+ unsigned sw_adj_en : 1; /* 2 allow software
adjust */
+ unsigned bypass : 1; /* 3 bypass L1
PLL */
+ unsigned __2 : 3; /* 4:6 */
+ unsigned lock_en : 1; /* 7 enable lock L1
PLL */
+ unsigned __3 : 2; /* 8:9 */
+ unsigned lock_check : 2; /* 10:11 precision
check */
+ unsigned __4 : 4; /*
12:15 */
+
+ unsigned locked : 1; /* 16 PLL locked flag
bit */
+ unsigned __5 : 2; /*
17:18 */
+ unsigned powerdown : 1; /* 19 powerdown the pll if
set */
+ unsigned __6 : 6; /*
20:25 */
+ unsigned div_ref : 6; /* 26:31 L1
Prescaler */
+
+ /* Byte 4 ~ Byte 7 */
+ unsigned loopc : 10; /* 32:41 Clock
Multiplier */
+ unsigned l1_div : 6; /* 42:47 not
used */
+ unsigned __7 : 16; /*
48:63 */
+
+ /* Byte 8 ~ Byte 15 */
+ unsigned div_out : 6; /* 0 : 5 output clock
divider */
+ unsigned __8 : 26; /* 6 :
31 */
+ unsigned __9 : 32; /* 70:
127 */
+};
+
+/* u32 */
+struct ls2k0500_pixpll_bitmap {
+ /* Byte 0 ~ Byte 1 */
+ unsigned sel_out : 1;
+ unsigned __1 : 2;
+ unsigned sw_adj_en : 1; /* allow software
adjust */
+ unsigned bypass : 1; /* bypass L1
PLL */
+ unsigned powerdown : 1; /* write 1 to powerdown the
PLL */
+ unsigned __2 : 1;
+ unsigned locked : 1; /* 7 Is L1 PLL locked, read
only */
+ unsigned div_ref : 6; /* 8:13 ref clock
divider */
+ unsigned __3 : 2; /*
14:15 */
+ /* Byte 2 ~ Byte 3 */
+ unsigned loopc : 8; /* 16:23 Clock
Multiplier */
+ unsigned div_out : 6; /* 24:29 output clock
divider */
+ unsigned __4 : 2; /*
30:31 */
+};
+
+union lsdc_pixpll_bitmap {
+ struct ls7a1000_pixpll_bitmap ls7a1000;
+ struct ls2k1000_pixpll_bitmap ls2k1000;
+ struct ls2k0500_pixpll_bitmap ls2k0500;
+
+ u32 dword[4];
+};
+
+struct pixclk_to_pll_parm {
+ /* kHz */
+ unsigned int clock;
+
+ unsigned short width;
+ unsigned short height;
+ unsigned short vrefresh;
+
+ /* Stores parameters for programming the Hardware PLLs */
+ unsigned short div_out;
+ unsigned short loopc;
+ unsigned short div_ref;
+};
+
+/*
+ * Pixel clock to PLL parameters translation table.
+ * Small static cached value to speed up PLL parameters calculation.
+ */
+static const struct pixclk_to_pll_parm pll_param_table[] = {
+ {148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */
+ /* 1920x1080@50Hz */
+ {174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */
+ {181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */
+ {146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */
+ {135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */
+
+ {108000, 1600, 900, 60, 15, 81, 5}, /* 1600x900@60Hz */
+ /* 1280x1024@60Hz */
+ /* 1280x960@60Hz */
+ /* 1152x864@75Hz */
+
+ {106500, 1440, 900, 60, 19, 81, 4}, /* 1440x900@60Hz */
+ {88750, 1440, 900, 60, 16, 71, 5}, /* 1440x900@60Hz */
+ {83500, 1280, 800, 60, 17, 71, 5}, /* 1280x800@60Hz */
+ {71000, 1280, 800, 60, 20, 71, 5}, /* 1280x800@60Hz */
+
+ {74250, 1280, 720, 60, 22, 49, 3}, /* 1280x720@60Hz */
+ /* 1280x720@50Hz */
+
+ {78750, 1024, 768, 75, 16, 63, 5}, /* 1024x768@75Hz */
+ {75000, 1024, 768, 70, 29, 87, 4}, /* 1024x768@70Hz */
+ {65000, 1024, 768, 60, 20, 39, 3}, /* 1024x768@60Hz */
+
+ {51200, 1024, 600, 60, 25, 64, 5}, /* 1024x600@60Hz */
+
+ {57284, 832, 624, 75, 24, 55, 4}, /* 832x624@75Hz */
+ {49500, 800, 600, 75, 40, 99, 5}, /* 800x600@75Hz */
+ {50000, 800, 600, 72, 44, 88, 4}, /* 800x600@72Hz */
+ {40000, 800, 600, 60, 30, 36, 3}, /* 800x600@60Hz */
+ {36000, 800, 600, 56, 50, 72, 4}, /* 800x600@56Hz */
+ {31500, 640, 480, 75, 40, 63, 5}, /* 640x480@75Hz */
+ /* 640x480@73Hz */
+
+ {30240, 640, 480, 67, 62, 75, 4}, /* 640x480@67Hz */
+ {27000, 720, 576, 50, 50, 54, 4}, /* 720x576@60Hz */
+ {25175, 640, 480, 60, 85, 107, 5}, /* 640x480@60Hz */
+ {25200, 640, 480, 60, 50, 63, 5}, /* 640x480@60Hz */
+ /* 720x480@60Hz */
+};
+
+/*
+ * lsdc_pixpll_setup - ioremap the device dependent PLL registers
+ *
+ * @this: point to the object where this function is called from
+ */
+static int lsdc_pixpll_setup(struct lsdc_pll * const this)
+{
+ this->mmio = ioremap(this->reg_base, this->reg_size);
+
+ return 0;
+}
+
+/*
+ * Find a set of pll parameters (to generate pixel clock) from a
static
+ * local table, which avoid to compute the pll parameter eachtime a
+ * modeset is triggered.
+ *
+ * @this: point to the object which this function is called from
+ * @clock: the desired output pixel clock, the unit is kHz
+ * @pout: point to where the parameters to store if found
+ *
+ * Return true if hit, otherwise return false.
+ */
+static bool lsdc_pixpll_find(struct lsdc_pll * const this,
+ unsigned int clock,
+ struct lsdc_pll_parms * const pout)
+{
+ unsigned int num = ARRAY_SIZE(pll_param_table);
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ if (clock != pll_param_table[i].clock)
+ continue;
+
+ pout->div_ref = pll_param_table[i].div_ref;
+ pout->loopc = pll_param_table[i].loopc;
+ pout->div_out = pll_param_table[i].div_out;
+
+ return true;
+ }
+
+ drm_dbg(this->ddev, "pixel clock %u: miss\n", clock);
+
+ return false;
+}
+
+/*
+ * Find a set of pll parameters which have minimal difference with
the desired
+ * pixel clock frequency. It does that by computing all of the
possible
+ * combination. Compute the diff and find the combination with
minimal diff.
+ *
+ * clock_out = refclk / div_ref * loopc / div_out
+ *
+ * refclk is determined by the oscillator mounted board(Here is
100MHz in
+ * almost all case)
+ *
+ * @this: point to the object from which this function is called
+ * @clock_khz: the desired output pixel clock, the unit is kHz
+ * @pout: point to the out struct of lsdc_pll_parms
+ *
+ * Return true if a parameter is found, otherwise return false
+ */
+static bool lsdc_pixpll_compute(struct lsdc_pll * const this,
+ unsigned int clock_khz,
+ struct lsdc_pll_parms *pout)
+{
+ unsigned int refclk = this->ref_clock;
+ const unsigned int tolerance = 1000;
+ unsigned int min = tolerance;
+ unsigned int div_out, loopc, div_ref;
+ unsigned int computed;
+
+ if (lsdc_pixpll_find(this, clock_khz, pout))
+ return true;
+
+ for (div_out = 6; div_out < 64; div_out++) {
+ for (div_ref = 3; div_ref < 6; div_ref++) {
+ for (loopc = 6; loopc < 161; loopc++) {
+ int diff;
+
+ if (loopc < 12 * div_ref)
+ continue;
+ if (loopc > 32 * div_ref)
+ continue;
+
+ computed = refclk * loopc / div_ref / div_out;
+
+ if (clock_khz > computed)
+ diff = clock_khz - computed;
+ else if (clock_khz < computed)
+ diff = computed - clock_khz;
+
+ if (diff < min) {
+ min = diff;
+ pout->div_ref = div_ref;
+ pout->div_out = div_out;
+ pout->loopc = loopc;
+
+ if (diff == 0)
+ return true;
+ }
+ }
+ }
+ }
+
+ return min < tolerance;
+}
+
+/*
+ * Update the pll parameters to hardware, target to the pixpll in
ls7a1000
+ *
+ * @this: point to the object from which this function is called
+ * @pin: point to the struct of lsdc_pll_parms passed in
+ *
+ * return 0 if successful.
+ */
+static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this,
+ struct lsdc_pll_parms const *pin)
+{
+ void __iomem *reg = this->mmio;
+ unsigned int counter = 0;
+ bool locked;
+ u32 val;
+
+ /* Bypass the software configured PLL, using refclk directly */
+ val = readl(reg + 0x4);
+ val &= ~(1 << 8);
+ writel(val, reg + 0x4);
+
+ /* Powerdown the PLL */
+ val = readl(reg + 0x4);
+ val |= (1 << 13);
+ writel(val, reg + 0x4);
+
+ /* Clear the pll parameters */
+ val = readl(reg + 0x4);
+ val &= ~(1 << 11);
+ writel(val, reg + 0x4);
+
+ /* clear old value & config new value */
+ val = readl(reg + 0x04);
+ val &= ~0x7F;
+ val |= pin->div_ref; /* div_ref */
+ writel(val, reg + 0x4);
+
+ val = readl(reg);
+ val &= ~0x7f;
+ val |= pin->div_out; /* div_out */
+
+ val &= ~(0x1ff << 21);
+ val |= pin->loopc << 21; /* loopc */
+ writel(val, reg);
+
+ /* Set the pll the parameters */
+ val = readl(reg + 0x4);
+ val |= (1 << 11);
+ writel(val, reg + 0x4);
+
+ /* Powerup the PLL */
+ val = readl(reg + 0x4);
+ val &= ~(1 << 13);
+ writel(val, reg + 0x4);
+
+ /* Wait the PLL lock */
+ do {
+ val = readl(reg + 0x4);
+ locked = val & 0x80;
+ counter++;
+ } while (!locked && (counter < 2000));
+
+ drm_dbg(this->ddev, "%u loop waited\n", counter);
+
+ /* Switch to the software configured pll */
+ val = readl(reg + 0x4);
+ val |= (1UL << 8);
+ writel(val, reg + 0x4);
+
+ return 0;
+}
+
+/*
+ * Update the pll parameters to hardware, target to the pixpll in
ls2k1000
+ *
+ * @this: point to the object from which this function is called
+ * @pin: point to the struct of lsdc_pll_parms passed in
+ *
+ * return 0 if successful.
+ */
+static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this,
+ struct lsdc_pll_parms const *pin)
+{
+ void __iomem *reg = this->mmio;
+ unsigned int counter = 0;
+ bool locked = false;
+ u32 val;
+
+ val = readl(reg);
+ /* Bypass the software configured PLL, using refclk directly */
+ val &= ~(1 << 0);
+ writel(val, reg);
+
+ /* Powerdown the PLL */
+ val |= (1 << 19);
+ writel(val, reg);
+
+ /* Allow the software configuration */
+ val &= ~(1 << 2);
+ writel(val, reg);
+
+ /* allow L1 PLL lock */
+ val = (1L << 7) | (3L << 10);
+ writel(val, reg);
+
+ /* clear div_ref bit field */
+ val &= ~(0x3f << 26);
+ /* set div_ref bit field */
+ val |= pin->div_ref << 26;
+ writel(val, reg);
+
+ val = readl(reg + 4);
+ /* clear loopc bit field */
+ val &= ~0x0fff;
+ /* set loopc bit field */
+ val |= pin->loopc;
+ writel(val, reg + 4);
+
+ /* set div_out */
+ writel(pin->div_out, reg + 8);
+
+ val = readl(reg);
+ /* use this parms configured just now */
+ val |= (1 << 2);
+ /* powerup the PLL */
+ val &= ~(1 << 19);
+ writel(val, reg);
+
+ /* wait pll setup and locked */
+ do {
+ val = readl(reg);
+ locked = val & 0x10000;
+ counter++;
+ } while (!locked && (counter < 2000));
+
+ drm_dbg(this->ddev, "%u loop waited\n", counter);
+
+ /* Switch to software configured PLL instead of refclk */
+ val |= 1;
+ writel(val, reg);
+
+ return 0;
+}
+
+/*
+ * Update the pll parameters to hardware, target to the pixpll in
ls2k0500
+ *
+ * @this: point to the object which calling this function
+ * @param: pointer to where the parameters passed in
+ *
+ * return 0 if successful.
+ */
+static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this,
+ struct lsdc_pll_parms const *param)
+{
+ void __iomem *reg = this->mmio;
+ unsigned int counter = 0;
+ bool locked = false;
+ u32 val;
+
+ /* Bypass the software configured PLL, using refclk directly */
+ val = readl(reg);
+ val &= ~(1 << 0);
+ writel(val, reg);
+
+ /* Powerdown the PLL */
+ val = readl(reg);
+ val |= (1 << 5);
+ writel(val, reg);
+
+ /* Allow the software configuration */
+ val |= (1 << 3);
+ writel(val, reg);
+
+ /* Update the pll params */
+ val = (param->div_out << 24) |
+ (param->loopc << 16) |
+ (param->div_ref << 8);
+
+ writel(val, reg);
+
+ /* Powerup the PLL */
+ val = readl(reg);
+ val &= ~(1 << 5);
+ writel(val, reg);
+
+ /* wait pll setup and locked */
+ do {
+ val = readl(reg);
+ locked = val & 0x80;
+ counter++;
+ } while (!locked && (counter < 10000));
+
+ drm_dbg(this->ddev, "%u loop waited\n", counter);
+
+ /* Switch to software configured PLL instead of refclk */
+ writel((val | 1), reg);
+
+ return 0;
+}
+
+static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this,
+ struct lsdc_pll_parms *pout)
+{
+ struct drm_device *ddev = this->ddev;
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ const struct lsdc_desc * const descp = ldev->descp;
+ unsigned int out;
+ union lsdc_pixpll_bitmap parms;
+
+ if (descp->chip == CHIP_LS7A1000 || descp->chip ==
CHIP_LS7A2000) {
+ struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000;
+
+ parms.dword[0] = readl(this->mmio);
+ parms.dword[1] = readl(this->mmio + 4);
+ out = this->ref_clock / obj->div_ref * obj->loopc /
obj->div_out;
+ if (pout) {
+ pout->div_ref = obj->div_ref;
+ pout->loopc = obj->loopc;
+ pout->div_out = obj->div_out;
+ }
+ } else if (descp->chip == CHIP_LS2K1000) {
+ struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000;
+
+ parms.dword[0] = readl(this->mmio);
+ parms.dword[1] = readl(this->mmio + 4);
+ parms.dword[2] = readl(this->mmio + 8);
+ parms.dword[3] = readl(this->mmio + 12);
+ out = this->ref_clock / obj->div_ref * obj->loopc /
obj->div_out;
+ if (pout) {
+ pout->div_ref = obj->div_ref;
+ pout->loopc = obj->loopc;
+ pout->div_out = obj->div_out;
+ }
+ } else if (descp->chip == CHIP_LS2K0500) {
+ struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500;
+
+ parms.dword[0] = readl(this->mmio);
+ out = this->ref_clock / obj->div_ref * obj->loopc /
obj->div_out;
+ if (pout) {
+ pout->div_ref = obj->div_ref;
+ pout->loopc = obj->loopc;
+ pout->div_out = obj->div_out;
+ }
+ } else {
+ drm_err(ddev, "unknown chip, the driver need update\n");
+ return 0;
+ }
+
+ return out;
+}
+
+static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = {
+ .setup = lsdc_pixpll_setup,
+ .compute = lsdc_pixpll_compute,
+ .update = ls7a1000_pixpll_param_update,
+ .get_clock_rate = lsdc_get_clock_rate,
+};
+
+static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = {
+ .setup = lsdc_pixpll_setup,
+ .compute = lsdc_pixpll_compute,
+ .update = ls2k1000_pixpll_param_update,
+ .get_clock_rate = lsdc_get_clock_rate,
+};
+
+static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = {
+ .setup = lsdc_pixpll_setup,
+ .compute = lsdc_pixpll_compute,
+ .update = ls2k0500_pixpll_param_update,
+ .get_clock_rate = lsdc_get_clock_rate,
+};
+
+int lsdc_pixpll_init(struct lsdc_pll * const this,
+ struct drm_device *ddev,
+ unsigned int index)
+{
+ struct lsdc_device *ldev = to_lsdc(ddev);
+ const struct lsdc_desc *descp = ldev->descp;
+
+ this->ddev = ddev;
+ this->index = index;
+ this->ref_clock = LSDC_PLL_REF_CLK;
+
+ /* LS7A1000 and LS7A2000's pixpll setting registers is same */
+ if (descp->chip == CHIP_LS7A2000 || descp->chip ==
CHIP_LS7A1000) {
+ if (index == 0)
+ this->reg_base = LS7A1000_CFG_REG_BASE +
LS7A1000_PIX_PLL0_REG;
+ else if (index == 1)
+ this->reg_base = LS7A1000_CFG_REG_BASE +
LS7A1000_PIX_PLL1_REG;
+ this->reg_size = 8;
+ this->funcs = &ls7a1000_pixpll_funcs;
+ } else if (descp->chip == CHIP_LS2K1000) {
+ if (index == 0)
+ this->reg_base = LS2K1000_CFG_REG_BASE +
LS2K1000_PIX_PLL0_REG;
+ else if (index == 1)
+ this->reg_base = LS2K1000_CFG_REG_BASE +
LS2K1000_PIX_PLL1_REG;
+
+ this->reg_size = 16;
+ this->funcs = &ls2k1000_pixpll_funcs;
+ } else if (descp->chip == CHIP_LS2K0500) {
+ if (index == 0)
+ this->reg_base = LS2K0500_CFG_REG_BASE +
LS2K0500_PIX_PLL0_REG;
+ else if (index == 1)
+ this->reg_base = LS2K0500_CFG_REG_BASE +
LS2K0500_PIX_PLL1_REG;
+
+ this->reg_size = 4;
+ this->funcs = &ls2k0500_pixpll_funcs;
+ } else {
+ drm_err(this->ddev, "unknown chip, the driver need update\n");
+ return -ENOENT;
+ }
+
+ return this->funcs->setup(this);
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h
b/drivers/gpu/drm/lsdc/lsdc_pll.h
new file mode 100644
index 000000000000..538739f461f2
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_pll.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LSDC_PLL_H__
+#define __LSDC_PLL_H__
+
+#include <drm/drm_device.h>
+
+/*
+ * Loongson Pixel PLL hardware structure
+ *
+ * refclk: reference frequency, 100 MHz from external oscillator
+ * outclk: output frequency desired.
+ *
+ *
+ * L1 Fref Fvco L2
+ * refclk +-----------+ +------------------+ +---------+
outclk
+ * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider
| -------->
+ * | +-----------+ +------------------+ +---------+ ^
+ * | ^ ^ ^ |
+ * | | | | |
+ * | | | | |
+ * | div_ref loopc div_out |
+ * | |
+ * +--- sel_out (bypass above software configurable clock if
set) ----+
+ *
+ * sel_out: PLL clock output selector (for debug purpose only).
+ *
+ * If sel_out == 1, it will take refclk as output directly,
+ * the L1 Prescaler and the out divider will be bypassed.
+ *
+ * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out;
+ *
+ * PLL working requirements:
+ *
+ * 1) 20 MHz <= refclk / div_ref <= 40Mhz
+ * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz
+ */
+
+struct lsdc_pll_parms {
+ unsigned int div_ref;
+ unsigned int loopc;
+ unsigned int div_out;
+};
+
+struct lsdc_pll;
+
+struct lsdc_pixpll_funcs {
+ int (*setup)(struct lsdc_pll * const this);
+ bool (*compute)(struct lsdc_pll * const this,
+ unsigned int clock,
+ struct lsdc_pll_parms *pout);
+ int (*update)(struct lsdc_pll * const this,
+ struct lsdc_pll_parms const *pin);
+ unsigned int (*get_clock_rate)(struct lsdc_pll * const this,
+ struct lsdc_pll_parms *pout);
+};
+
+struct lsdc_pll {
+ const struct lsdc_pixpll_funcs *funcs;
+ struct drm_device *ddev;
+ void __iomem *mmio;
+
+ /* PLL register offset */
+ u32 reg_base;
+ /* PLL register size in bytes */
+ u32 reg_size;
+
+ /* 100000kHz, fixed on all board found */
+ unsigned int ref_clock;
+
+ unsigned int index;
+};
+
+int lsdc_pixpll_init(struct lsdc_pll * const this,
+ struct drm_device *ddev,
+ unsigned int index);
+
+#endif
diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c
b/drivers/gpu/drm/lsdc/lsdc_regs.c
new file mode 100644
index 000000000000..c7950c43968f
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_regs.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "lsdc_drv.h"
+#include "lsdc_regs.h"
+
+u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset)
+{
+ unsigned long flags;
+ u32 ret;
+
+ spin_lock_irqsave(&ldev->reglock, flags);
+
+ ret = readl(ldev->reg_base + offset);
+
+ spin_unlock_irqrestore(&ldev->reglock, flags);
+
+ return ret;
+}
+
+void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ldev->reglock, flags);
+
+ writel(val, ldev->reg_base + offset);
+
+ spin_unlock_irqrestore(&ldev->reglock, flags);
+}
diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h
b/drivers/gpu/drm/lsdc/lsdc_regs.h
new file mode 100644
index 000000000000..828956633137
--- /dev/null
+++ b/drivers/gpu/drm/lsdc/lsdc_regs.h
@@ -0,0 +1,296 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LSDC_REGS_H__
+#define __LSDC_REGS_H__
+
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+/*
+ * PIXEL PLL Reference clock
+ */
+#define LSDC_PLL_REF_CLK 100000 /* kHz */
+
+/*
+ * Those PLL registers are not located at DC reg bar space,
+ * there are relative to LSXXXXX_CFG_REG_BASE.
+ * XXXXX = 7A1000, 2K1000, 2K0500
+ */
+
+/* LS2K1000 */
+#define LS2K1000_PIX_PLL0_REG 0x04B0
+#define LS2K1000_PIX_PLL1_REG 0x04C0
+#define LS2K1000_CFG_REG_BASE 0x1fe10000
+
+/* LS7A1000 and LS2K2000 */
+#define LS7A1000_PIX_PLL0_REG 0x04B0
+#define LS7A1000_PIX_PLL1_REG 0x04C0
+#define LS7A1000_CFG_REG_BASE 0x10010000
+
+/* LS2K0500 */
+#define LS2K0500_PIX_PLL0_REG 0x0418
+#define LS2K0500_PIX_PLL1_REG 0x0420
+#define LS2K0500_CFG_REG_BASE 0x1fe10000
+
+/*
+ * CRTC CFG REG
+ */
+#define CFG_PIX_FMT_MASK GENMASK(2, 0)
+
+enum lsdc_pixel_format {
+ LSDC_PF_NONE = 0,
+ LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16
bits] */
+ LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */
+ LSDC_PF_RGB565 = 3, /* RGB [16 bits] */
+ LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */
+ LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */
+};
+
+/*
+ * Each crtc has two set fb address registers usable,
CFG_FB_IN_USING of
+ * LSDC_CRTCx_CFG_REG specify which fb address register is currently
+ * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is
used to
+ * trigger the switch which will be finished at the very vblank. If
you
+ * want it switch to another again, you must set CFG_PAGE_FLIP again.
+ */
+#define CFG_PAGE_FLIP BIT(7)
+#define CFG_OUTPUT_EN BIT(8)
+/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware
logic */
+#define CFG_PANEL_SWITCH BIT(9)
+/* Indicate witch fb addr reg is in using, currently */
+#define CFG_FB_IN_USING BIT(11)
+#define CFG_GAMMA_EN BIT(12)
+
+/* CRTC get soft reset if voltage level change from 1 -> 0 */
+#define CFG_RESET_N BIT(20)
+
+#define CFG_HSYNC_EN BIT(30)
+#define CFG_HSYNC_INV BIT(31)
+#define CFG_VSYNC_EN BIT(30)
+#define CFG_VSYNC_INV BIT(31)
+
+/* THE DMA step of the DC in LS7A2000 is configurable */
+#define LSDC_DMA_STEP_MASK GENMASK(17, 16)
+enum lsdc_dma_steps_supported {
+ LSDC_DMA_STEP_256_BYTES = 0,
+ LSDC_DMA_STEP_128_BYTES = 1,
+ LSDC_DMA_STEP_64_BYTES = 2,
+ LSDC_DMA_STEP_32_BYTES = 3,
+};
+
+/******** CRTC0 & DVO0 ********/
+#define LSDC_CRTC0_CFG_REG 0x1240
+#define LSDC_CRTC0_FB0_LO_ADDR_REG 0x1260
+#define LSDC_CRTC0_FB0_HI_ADDR_REG 0x15A0
+#define LSDC_CRTC0_FB1_LO_ADDR_REG 0x1580
+#define LSDC_CRTC0_FB1_HI_ADDR_REG 0x15C0
+#define LSDC_CRTC0_STRIDE_REG 0x1280
+#define LSDC_CRTC0_FB_ORIGIN_REG 0x1300
+#define LSDC_CRTC0_HDISPLAY_REG 0x1400
+#define LSDC_CRTC0_HSYNC_REG 0x1420
+#define LSDC_CRTC0_VDISPLAY_REG 0x1480
+#define LSDC_CRTC0_VSYNC_REG 0x14A0
+#define LSDC_CRTC0_GAMMA_INDEX_REG 0x14E0
+#define LSDC_CRTC0_GAMMA_DATA_REG 0x1500
+
+/******** CTRC1 & DVO1 ********/
+#define LSDC_CRTC1_CFG_REG 0x1250
+#define LSDC_CRTC1_FB0_LO_ADDR_REG 0x1270
+#define LSDC_CRTC1_FB0_HI_ADDR_REG 0x15B0
+#define LSDC_CRTC1_FB1_LO_ADDR_REG 0x1590
+#define LSDC_CRTC1_FB1_HI_ADDR_REG 0x15D0
+#define LSDC_CRTC1_STRIDE_REG 0x1290
+#define LSDC_CRTC1_FB_ORIGIN_REG 0x1310
+#define LSDC_CRTC1_HDISPLAY_REG 0x1410
+#define LSDC_CRTC1_HSYNC_REG 0x1430
+#define LSDC_CRTC1_VDISPLAY_REG 0x1490
+#define LSDC_CRTC1_VSYNC_REG 0x14B0
+#define LSDC_CRTC1_GAMMA_INDEX_REG 0x14F0
+#define LSDC_CRTC1_GAMMA_DATA_REG 0x1510
+
+/*
+ * LS7A2000 has the hardware which record the scan position of the
CRTC
+ * [31:16] : current X position, [15:0] : current Y position
+ */
+#define LSDC_CRTC0_SCAN_POS_REG 0x14C0
+#define LSDC_CRTC1_SCAN_POS_REG 0x14D0
+
+/*
+ * LS7A2000 has the hardware which count the number of vblank
generated
+ */
+#define LSDC_CRTC0_VSYNC_COUNTER_REG 0x1A00
+#define LSDC_CRTC1_VSYNC_COUNTER_REG 0x1A10
+
+/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */
+
+/*
+ * There is only one hardware cursor unit in ls7a1000, ls2k1000 and
ls2k0500.
+ * well, ls7a2000 has two hardware cursor unit.
+ */
+#define CURSOR_FORMAT_MASK GENMASK(1, 0)
+enum lsdc_cursor_format {
+ CURSOR_FORMAT_DISABLE = 0,
+ CURSOR_FORMAT_MONOCHROME = 1,
+ CURSOR_FORMAT_ARGB8888 = 2,
+};
+
+#define CURSOR_SIZE_64X64 BIT(2)
+#define CURSOR_LOCATION BIT(4)
+
+#define LSDC_CURSOR0_CFG_REG 0x1520
+#define LSDC_CURSOR0_ADDR_LO_REG 0x1530
+#define LSDC_CURSOR0_ADDR_HI_REG 0x15e0
+#define LSDC_CURSOR0_POSITION_REG 0x1540
+#define LSDC_CURSOR0_BG_COLOR_REG 0x1550 /* background color */
+#define LSDC_CURSOR0_FG_COLOR_REG 0x1560 /* foreground color */
+
+#define LSDC_CURSOR1_CFG_REG 0x1670
+#define LSDC_CURSOR1_ADDR_LO_REG 0x1680
+#define LSDC_CURSOR1_ADDR_HI_REG 0x16e0
+#define LSDC_CURSOR1_POSITION_REG 0x1690 /* [31:16] Y,
[15:0] X */
+#define LSDC_CURSOR1_BG_COLOR_REG 0x16A0 /* background color */
+#define LSDC_CURSOR1_FG_COLOR_REG 0x16B0 /* foreground color */
+
+/*
+ * DC Interrupt Control Register, 32bit, Address Offset: 1570
+ *
+ * Bits 15:0 inidicate the interrupt status
+ * Bits 31:16 control enable interrupts corresponding to bit 15:0
or not
+ * Write 1 to enable, write 0 to disable
+ *
+ * RF: Read Finished
+ * IDBU: Internal Data Buffer Underflow
+ * IDBFU: Internal Data Buffer Fatal Underflow
+ * CBRF: Cursor Buffer Read Finished Flag, no use.
+ *
+ *
+-------+--------------------------+-------+--------+--------+-------+
+ * | 31:27 | 26:16 | 15:11 | 10 | 9 | 8 |
+ *
+-------+--------------------------+-------+--------+--------+-------+
+ * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 |
IDBU0 |
+ *
+-------+--------------------------+-------+--------+--------+-------+
+ *
+ * +-------+-----+-----+------+--------+--------+--------+--------+
+ * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * +-------+-----+-----+------+--------+--------+--------+--------+
+ * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 |
+ * +-------+-----+-----+------+--------+--------+--------+--------+
+ */
+
+#define LSDC_INT_REG 0x1570
+
+#define INT_CRTC0_VSYNC BIT(2)
+#define INT_CRTC0_HSYNC BIT(3)
+#define INT_CRTC0_RF BIT(6)
+#define INT_CRTC0_IDBU BIT(8)
+#define INT_CRTC0_IDBFU BIT(10)
+
+#define INT_CRTC1_VSYNC BIT(0)
+#define INT_CRTC1_HSYNC BIT(1)
+#define INT_CRTC1_RF BIT(5)
+#define INT_CRTC1_IDBU BIT(7)
+#define INT_CRTC1_IDBFU BIT(9)
+
+#define INT_CRTC0_VS_EN BIT(18)
+#define INT_CRTC0_HS_EN BIT(19)
+#define INT_CRTC0_RF_EN BIT(22)
+#define INT_CRTC0_IDBU_EN BIT(24)
+#define INT_CRTC0_IDBFU_EN BIT(26)
+
+#define INT_CRTC1_VS_EN BIT(16)
+#define INT_CRTC1_HS_EN BIT(17)
+#define INT_CRTC1_RF_EN BIT(21)
+#define INT_CRTC1_IDBU_EN BIT(23)
+#define INT_CRTC1_IDBFU_EN BIT(25)
+
+#define INT_STATUS_MASK GENMASK(15, 0)
+
+/*
+ * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C.
+ * They are under control of the LS7A_DC_GPIO_DAT_REG and
LS7A_DC_GPIO_DIR_REG
+ * register, Those GPIOs has no relationship whth the GPIO hardware
on the
+ * bridge chip itself. Those offsets are relative to DC register
base address
+ *
+ * LS2k1000 and LS2K0500 don't have those registers, they use
hardware i2c
+ * or general GPIO emulated i2c from linux i2c subsystem.
+ *
+ * GPIO data register, address offset: 0x1650
+ * +---------------+-----------+-----------+
+ * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ * +---------------+-----------+-----------+
+ * | | DVO1 | DVO0 |
+ * + N/A +-----------+-----------+
+ * | | SCL | SDA | SCL | SDA |
+ * +---------------+-----------+-----------+
+ */
+#define LS7A_DC_GPIO_DAT_REG 0x1650
+
+/*
+ * GPIO Input/Output direction control register, address offset:
0x1660
+ */
+#define LS7A_DC_GPIO_DIR_REG 0x1660
+
+/*
+ * LS7A2000 has two built-in HDMI Encoder and one VGA encoder
+ */
+
+/*
+ * Number of continuous packets may be present
+ * in HDMI hblank and vblank zone, should >= 48
+ */
+#define LSDC_HDMI0_ZONE_REG 0x1700
+#define LSDC_HDMI1_ZONE_REG 0x1710
+
+#define HDMI_INTERFACE_EN BIT(0)
+#define HDMI_PACKET_EN BIT(1)
+#define HDMI_AUDIO_EN BIT(2)
+#define HDMI_VIDEO_PREAMBLE_MASK GENMASK(7, 4)
+#define HDMI_HW_I2C_EN BIT(8)
+#define HDMI_CTL_PERIOD_MODE BIT(9)
+#define LSDC_HDMI0_CTRL_REG 0x1720
+#define LSDC_HDMI1_CTRL_REG 0x1730
+
+#define HDMI_PHY_EN BIT(0)
+#define HDMI_PHY_RESET_N BIT(1)
+#define HDMI_PHY_TERM_L_EN BIT(8)
+#define HDMI_PHY_TERM_H_EN BIT(9)
+#define HDMI_PHY_TERM_DET_EN BIT(10)
+#define HDMI_PHY_TERM_STATUS BIT(11)
+#define LSDC_HDMI0_PHY_CTRL_REG 0x1800
+#define LSDC_HDMI1_PHY_CTRL_REG 0x1810
+
+/*
+ * 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 |
+ * +--------------------------------------------------------+
+ */
+#define LSDC_HDMI0_PHY_PLL_REG 0x1820
+#define LSDC_HDMI1_PHY_PLL_REG 0x1830
+
+#define HDMI_PLL_ENABLE BIT(0)
+#define HDMI_PLL_LOCKED BIT(16)
+#define HDMI_PLL_BYPASS BIT(17)
+
+#define HDMI_PLL_IDF_SHIFT 1
+#define HDMI_PLL_IDF_MASK GENMASK(5, 1)
+#define HDMI_PLL_LF_SHIFT 6
+#define HDMI_PLL_LF_MASK GENMASK(12, 6)
+#define HDMI_PLL_ODF_SHIFT 13
+#define HDMI_PLL_ODF_MASK GENMASK(15, 13)
+
+/* LS7A2000 have hpd support */
+#define LSDC_HDMI_HPD_STATUS_REG 0x1BA0
+#define HDMI0_HPD_FLAG BIT(0)
+#define HDMI1_HPD_FLAG BIT(1)
+
+#endif