summaryrefslogtreecommitdiff
path: root/drivers/video/imx
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/imx')
-rw-r--r--drivers/video/imx/Kconfig9
-rw-r--r--drivers/video/imx/Makefile4
-rw-r--r--drivers/video/imx/lcdif.c314
-rw-r--r--drivers/video/imx/ldb.c251
4 files changed, 577 insertions, 1 deletions
diff --git a/drivers/video/imx/Kconfig b/drivers/video/imx/Kconfig
index 34e8b640595..12f11c2eea8 100644
--- a/drivers/video/imx/Kconfig
+++ b/drivers/video/imx/Kconfig
@@ -13,3 +13,12 @@ config IMX_VIDEO_SKIP
config IMX_HDMI
bool "Enable HDMI support in IPUv3"
depends on VIDEO_IPUV3
+
+config IMX_LDB
+ bool "Freescale i.MX8MP LDB bridge"
+ depends on VIDEO_BRIDGE
+ help
+ Support for i.MX8MP DPI-to-LVDS on-SoC encoder.
+
+config IMX_LCDIF
+ bool "i.MX LCDIFv3 LCD controller"
diff --git a/drivers/video/imx/Makefile b/drivers/video/imx/Makefile
index 179ea651fe8..1edf5a6bdf0 100644
--- a/drivers/video/imx/Makefile
+++ b/drivers/video/imx/Makefile
@@ -3,4 +3,6 @@
# (C) Copyright 2000-2007
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
-obj-y += mxc_ipuv3_fb.o ipu_common.o ipu_disp.o
+obj-$(CONFIG_VIDEO_IPUV3) += mxc_ipuv3_fb.o ipu_common.o ipu_disp.o
+obj-$(CONFIG_IMX_LDB) += ldb.o
+obj-$(CONFIG_IMX_LCDIF) += lcdif.o
diff --git a/drivers/video/imx/lcdif.c b/drivers/video/imx/lcdif.c
new file mode 100644
index 00000000000..9f4fc7f5152
--- /dev/null
+++ b/drivers/video/imx/lcdif.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * i.MX8 LCD interface driver inspired from the Linux driver
+ * Copyright 2019 NXP
+ * Copyright 2024 Bootlin
+ * Adapted by Miquel Raynal <miquel.raynal@bootlin.com>
+ */
+
+#include <asm/io.h>
+#include <asm/mach-imx/dma.h>
+#include <clk.h>
+#include <dm.h>
+#include <panel.h>
+#include <power-domain.h>
+#include <video.h>
+#include <video_bridge.h>
+#include <linux/delay.h>
+
+#include "../videomodes.h"
+
+#define LCDIFV3_CTRL 0x0
+#define LCDIFV3_CTRL_SET 0x4
+#define LCDIFV3_CTRL_CLR 0x8
+#define CTRL_INV_HS BIT(0)
+#define CTRL_INV_VS BIT(1)
+#define CTRL_INV_DE BIT(2)
+#define CTRL_INV_PXCK BIT(3)
+#define CTRL_CLK_GATE BIT(30)
+#define CTRL_SW_RESET BIT(31)
+
+#define LCDIFV3_DISP_PARA 0x10
+#define DISP_PARA_DISP_MODE_NORMAL 0
+#define DISP_PARA_LINE_PATTERN_RGB_YUV 0
+#define DISP_PARA_DISP_ON BIT(31)
+
+#define LCDIFV3_DISP_SIZE 0x14
+#define DISP_SIZE_DELTA_X(x) ((x) & 0xffff)
+#define DISP_SIZE_DELTA_Y(x) ((x) << 16)
+
+#define LCDIFV3_HSYN_PARA 0x18
+#define HSYN_PARA_FP_H(x) ((x) & 0xffff)
+#define HSYN_PARA_BP_H(x) ((x) << 16)
+
+#define LCDIFV3_VSYN_PARA 0x1C
+#define VSYN_PARA_FP_V(x) ((x) & 0xffff)
+#define VSYN_PARA_BP_V(x) ((x) << 16)
+
+#define LCDIFV3_VSYN_HSYN_WIDTH 0x20
+#define VSYN_HSYN_PW_H(x) ((x) & 0xffff)
+#define VSYN_HSYN_PW_V(x) ((x) << 16)
+
+#define LCDIFV3_CTRLDESCL0_1 0x200
+#define CTRLDESCL0_1_WIDTH(x) ((x) & 0xffff)
+#define CTRLDESCL0_1_HEIGHT(x) ((x) << 16)
+
+#define LCDIFV3_CTRLDESCL0_3 0x208
+#define CTRLDESCL0_3_PITCH(x) ((x) & 0xFFFF)
+
+#define LCDIFV3_CTRLDESCL_LOW0_4 0x20C
+#define LCDIFV3_CTRLDESCL_HIGH0_4 0x210
+
+#define LCDIFV3_CTRLDESCL0_5 0x214
+#define CTRLDESCL0_5_YUV_FORMAT(x) (((x) & 0x3) << 14)
+#define CTRLDESCL0_5_BPP(x) (((x) & 0xf) << 24)
+#define BPP32_ARGB8888 0x9
+#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30)
+#define CTRLDESCL0_5_EN BIT(31)
+
+struct lcdifv3_priv {
+ void __iomem *base;
+ struct clk pix_clk;
+ struct power_domain pd;
+ struct udevice *panel;
+ struct udevice *bridge;
+};
+
+static void lcdifv3_set_mode(struct lcdifv3_priv *priv,
+ struct display_timing *timings)
+{
+ u32 reg;
+
+ writel(DISP_SIZE_DELTA_X(timings->hactive.typ) |
+ DISP_SIZE_DELTA_Y(timings->vactive.typ),
+ priv->base + LCDIFV3_DISP_SIZE);
+
+ writel(HSYN_PARA_FP_H(timings->hfront_porch.typ) |
+ HSYN_PARA_BP_H(timings->hback_porch.typ),
+ priv->base + LCDIFV3_HSYN_PARA);
+
+ writel(VSYN_PARA_BP_V(timings->vback_porch.typ) |
+ VSYN_PARA_FP_V(timings->vfront_porch.typ),
+ priv->base + LCDIFV3_VSYN_PARA);
+
+ writel(VSYN_HSYN_PW_H(timings->hsync_len.typ) |
+ VSYN_HSYN_PW_V(timings->vsync_len.typ),
+ priv->base + LCDIFV3_VSYN_HSYN_WIDTH);
+
+ writel(CTRLDESCL0_1_WIDTH(timings->hactive.typ) |
+ CTRLDESCL0_1_HEIGHT(timings->vactive.typ),
+ priv->base + LCDIFV3_CTRLDESCL0_1);
+
+ if (timings->flags & DISPLAY_FLAGS_HSYNC_LOW)
+ writel(CTRL_INV_HS, priv->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_HS, priv->base + LCDIFV3_CTRL_CLR);
+
+ if (timings->flags & DISPLAY_FLAGS_VSYNC_LOW)
+ writel(CTRL_INV_VS, priv->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_VS, priv->base + LCDIFV3_CTRL_CLR);
+
+ if (timings->flags & DISPLAY_FLAGS_DE_LOW)
+ writel(CTRL_INV_DE, priv->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_DE, priv->base + LCDIFV3_CTRL_CLR);
+
+ if (timings->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
+ writel(CTRL_INV_PXCK, priv->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_PXCK, priv->base + LCDIFV3_CTRL_CLR);
+
+ writel(0, priv->base + LCDIFV3_DISP_PARA);
+
+ reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5);
+ reg &= ~(CTRLDESCL0_5_BPP(0xf) | CTRLDESCL0_5_YUV_FORMAT(0x3));
+ reg |= CTRLDESCL0_5_BPP(BPP32_ARGB8888);
+ writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5);
+}
+
+static void lcdifv3_enable_controller(struct lcdifv3_priv *priv)
+{
+ u32 reg;
+
+ reg = readl(priv->base + LCDIFV3_DISP_PARA);
+ reg |= DISP_PARA_DISP_ON;
+ writel(reg, priv->base + LCDIFV3_DISP_PARA);
+
+ reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5);
+ reg |= CTRLDESCL0_5_EN;
+ writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5);
+}
+
+static int lcdifv3_video_sync(struct udevice *dev)
+{
+ struct lcdifv3_priv *priv = dev_get_priv(dev);
+ u32 reg;
+
+ reg = readl(priv->base + LCDIFV3_CTRLDESCL0_5);
+ reg |= CTRLDESCL0_5_SHADOW_LOAD_EN;
+ writel(reg, priv->base + LCDIFV3_CTRLDESCL0_5);
+
+ return 0;
+}
+
+static void lcdifv3_init(struct udevice *dev, struct display_timing *timings)
+{
+ struct video_uc_plat *plat = dev_get_uclass_plat(dev);
+ struct lcdifv3_priv *priv = dev_get_priv(dev);
+
+ clk_set_rate(&priv->pix_clk, timings->pixelclock.typ);
+
+ writel(CTRL_SW_RESET | CTRL_CLK_GATE, priv->base + LCDIFV3_CTRL_CLR);
+ udelay(10);
+
+ lcdifv3_set_mode(priv, timings);
+
+ writel(plat->base & 0xFFFFFFFF, priv->base + LCDIFV3_CTRLDESCL_LOW0_4);
+ writel(plat->base >> 32, priv->base + LCDIFV3_CTRLDESCL_HIGH0_4);
+
+ writel(CTRLDESCL0_3_PITCH(timings->hactive.typ * 4), /* 32bpp */
+ priv->base + LCDIFV3_CTRLDESCL0_3);
+
+ lcdifv3_enable_controller(priv);
+}
+
+static int lcdifv3_video_probe(struct udevice *dev)
+{
+ struct video_uc_plat *plat = dev_get_uclass_plat(dev);
+ struct video_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct lcdifv3_priv *priv = dev_get_priv(dev);
+ struct clk axi_clk, disp_axi_clk;
+ struct display_timing timings;
+ u32 fb_start, fb_end;
+ int ret;
+
+ ret = power_domain_get(dev, &priv->pd);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_get_by_name(dev, "pix", &priv->pix_clk);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_get_by_name(dev, "axi", &axi_clk);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_get_by_name(dev, "disp_axi", &disp_axi_clk);
+ if (ret < 0)
+ return ret;
+
+ ret = power_domain_on(&priv->pd);
+ if (ret)
+ return ret;
+
+ ret = clk_enable(&priv->pix_clk);
+ if (ret)
+ goto dis_pd;
+
+ ret = clk_enable(&axi_clk);
+ if (ret)
+ goto dis_pix_clk;
+
+ ret = clk_enable(&disp_axi_clk);
+ if (ret)
+ goto dis_axi_clk;
+
+ priv->base = dev_remap_addr(dev);
+ if (!priv->base) {
+ ret = -EINVAL;
+ goto dis_clks;
+ }
+
+ /* Attach bridge */
+ ret = uclass_get_device_by_endpoint(UCLASS_VIDEO_BRIDGE, dev,
+ -1, -1, &priv->bridge);
+ if (ret)
+ goto dis_clks;
+
+ ret = video_bridge_attach(priv->bridge);
+ if (ret)
+ goto dis_clks;
+
+ ret = video_bridge_set_backlight(priv->bridge, 80);
+ if (ret)
+ goto dis_clks;
+
+ /* Attach panels */
+ ret = uclass_get_device_by_endpoint(UCLASS_PANEL, priv->bridge,
+ 1, -1, &priv->panel);
+ if (ret) {
+ ret = uclass_get_device_by_endpoint(UCLASS_PANEL, priv->bridge,
+ 2, -1, &priv->panel);
+ if (ret)
+ goto dis_clks;
+ }
+
+ ret = panel_get_display_timing(priv->panel, &timings);
+ if (ret) {
+ ret = ofnode_decode_display_timing(dev_ofnode(priv->panel),
+ 0, &timings);
+ if (ret) {
+ printf("Cannot decode panel timings (%d)\n", ret);
+ goto dis_clks;
+ }
+ }
+
+ lcdifv3_init(dev, &timings);
+
+ /* Only support 32bpp for now */
+ uc_priv->bpix = VIDEO_BPP32;
+ uc_priv->xsize = timings.hactive.typ;
+ uc_priv->ysize = timings.vactive.typ;
+
+ /* Enable dcache for the frame buffer */
+ fb_start = plat->base & ~(MMU_SECTION_SIZE - 1);
+ fb_end = ALIGN(plat->base + plat->size, 1 << MMU_SECTION_SHIFT);
+ mmu_set_region_dcache_behaviour(fb_start, fb_end - fb_start,
+ DCACHE_WRITEBACK);
+ video_set_flush_dcache(dev, true);
+
+ return 0;
+
+dis_clks:
+ clk_disable(&disp_axi_clk);
+dis_axi_clk:
+ clk_disable(&axi_clk);
+dis_pix_clk:
+ clk_disable(&priv->pix_clk);
+dis_pd:
+ power_domain_off(&priv->pd);
+
+ return ret;
+}
+
+static int lcdifv3_video_bind(struct udevice *dev)
+{
+ struct video_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ /* Max size supported by LCDIF */
+ plat->size = 1920 * 1080 * VNBYTES(VIDEO_BPP32);
+
+ return 0;
+}
+
+static const struct udevice_id lcdifv3_video_ids[] = {
+ { .compatible = "fsl,imx8mp-lcdif" },
+ { }
+};
+
+static struct video_ops lcdifv3_video_ops = {
+ .video_sync = lcdifv3_video_sync,
+};
+
+U_BOOT_DRIVER(lcdifv3_video) = {
+ .name = "lcdif",
+ .id = UCLASS_VIDEO,
+ .of_match = lcdifv3_video_ids,
+ .bind = lcdifv3_video_bind,
+ .ops = &lcdifv3_video_ops,
+ .probe = lcdifv3_video_probe,
+ .priv_auto = sizeof(struct lcdifv3_priv),
+ .flags = DM_FLAG_PRE_RELOC | DM_FLAG_OS_PREPARE,
+};
diff --git a/drivers/video/imx/ldb.c b/drivers/video/imx/ldb.c
new file mode 100644
index 00000000000..e918341c0a3
--- /dev/null
+++ b/drivers/video/imx/ldb.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Derived work from:
+ * Philippe Cornu <philippe.cornu@st.com>
+ * Yannick Fertre <yannick.fertre@st.com>
+ * Adapted by Miquel Raynal <miquel.raynal@bootlin.com>
+ */
+
+#define LOG_CATEGORY UCLASS_VIDEO_BRIDGE
+
+#include <clk.h>
+#include <dm.h>
+#include <log.h>
+#include <panel.h>
+#include <video_bridge.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+
+#define LDB_CTRL_CH0_ENABLE BIT(0)
+#define LDB_CTRL_CH1_ENABLE BIT(2)
+#define LDB_CTRL_CH0_DATA_WIDTH BIT(5)
+#define LDB_CTRL_CH0_BIT_MAPPING BIT(6)
+#define LDB_CTRL_CH1_DATA_WIDTH BIT(7)
+#define LDB_CTRL_CH1_BIT_MAPPING BIT(8)
+#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9)
+#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10)
+
+#define LVDS_CTRL_CH0_EN BIT(0)
+#define LVDS_CTRL_CH1_EN BIT(1)
+#define LVDS_CTRL_VBG_EN BIT(2)
+#define LVDS_CTRL_PRE_EMPH_EN BIT(4)
+#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5)
+#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11)
+
+struct imx_ldb_priv {
+ struct clk ldb_clk;
+ void __iomem *ldb_ctrl;
+ void __iomem *lvds_ctrl;
+ struct udevice *lvds1;
+ struct udevice *lvds2;
+};
+
+static int imx_ldb_set_backlight(struct udevice *dev, int percent)
+{
+ struct imx_ldb_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (priv->lvds1) {
+ ret = panel_enable_backlight(priv->lvds1);
+ if (ret) {
+ debug("ldb: Cannot enable lvds1 backlight\n");
+ return ret;
+ }
+
+ ret = panel_set_backlight(priv->lvds1, percent);
+ if (ret)
+ return ret;
+ }
+
+ if (priv->lvds2) {
+ ret = panel_enable_backlight(priv->lvds2);
+ if (ret) {
+ debug("ldb: Cannot enable lvds2 backlight\n");
+ return ret;
+ }
+
+ ret = panel_set_backlight(priv->lvds2, percent);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_ldb_of_to_plat(struct udevice *dev)
+{
+ struct imx_ldb_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 1, -1, &priv->lvds1);
+ uclass_get_device_by_endpoint(UCLASS_PANEL, dev, 2, -1, &priv->lvds2);
+ if (!priv->lvds1 && !priv->lvds2) {
+ debug("ldb: No remote panel for '%s' (ret=%d)\n",
+ dev_read_name(dev), ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* The block has a mysterious x7 internal divisor (x3.5 in dual configuration) */
+#define IMX_LDB_INTERNAL_DIVISOR(x) (((x) * 70) / 10)
+#define IMX_LDB_INTERNAL_DIVISOR_DUAL(x) (((x) * 35) / 10)
+
+static ulong imx_ldb_input_rate(struct imx_ldb_priv *priv,
+ struct display_timing *timings)
+{
+ ulong target_rate = timings->pixelclock.typ;
+
+ if (priv->lvds1 && priv->lvds2)
+ return IMX_LDB_INTERNAL_DIVISOR_DUAL(target_rate);
+
+ return IMX_LDB_INTERNAL_DIVISOR(target_rate);
+}
+
+static int imx_ldb_attach(struct udevice *dev)
+{
+ struct imx_ldb_priv *priv = dev_get_priv(dev);
+ struct display_timing timings;
+ bool format_jeida = false;
+ bool format_24bpp = true;
+ u32 ldb_ctrl = 0, lvds_ctrl;
+ ulong ldb_rate;
+ int ret;
+
+ /* TODO: update the 24bpp/jeida booleans with proper checks when they
+ * will be supported.
+ */
+ if (priv->lvds1) {
+ ret = panel_get_display_timing(priv->lvds1, &timings);
+ if (ret) {
+ ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds1),
+ 0, &timings);
+ if (ret) {
+ printf("Cannot decode lvds1 timings (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ ldb_ctrl |= LDB_CTRL_CH0_ENABLE;
+ if (format_24bpp)
+ ldb_ctrl |= LDB_CTRL_CH0_DATA_WIDTH;
+ if (format_jeida)
+ ldb_ctrl |= LDB_CTRL_CH0_BIT_MAPPING;
+ if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ ldb_ctrl |= LDB_CTRL_DI0_VSYNC_POLARITY;
+ }
+
+ if (priv->lvds2) {
+ ret = panel_get_display_timing(priv->lvds2, &timings);
+ if (ret) {
+ ret = ofnode_decode_display_timing(dev_ofnode(priv->lvds2),
+ 0, &timings);
+ if (ret) {
+ printf("Cannot decode lvds2 timings (%d)\n", ret);
+ return ret;
+ }
+ }
+
+ ldb_ctrl |= LDB_CTRL_CH1_ENABLE;
+ if (format_24bpp)
+ ldb_ctrl |= LDB_CTRL_CH1_DATA_WIDTH;
+ if (format_jeida)
+ ldb_ctrl |= LDB_CTRL_CH1_BIT_MAPPING;
+ if (timings.flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ ldb_ctrl |= LDB_CTRL_DI1_VSYNC_POLARITY;
+ }
+
+ /*
+ * Not all pixel clocks will work, as the final rate (after internal
+ * integer division) should be identical to the LCDIF clock, otherwise
+ * the rendering will appear resized/shimmering.
+ */
+ ldb_rate = imx_ldb_input_rate(priv, &timings);
+ clk_set_rate(&priv->ldb_clk, ldb_rate);
+
+ writel(ldb_ctrl, priv->ldb_ctrl);
+
+ lvds_ctrl = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN |
+ LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN;
+ writel(lvds_ctrl, priv->lvds_ctrl);
+
+ /* Wait for VBG to stabilize. */
+ udelay(15);
+
+ if (priv->lvds1)
+ lvds_ctrl |= LVDS_CTRL_CH0_EN;
+ if (priv->lvds2)
+ lvds_ctrl |= LVDS_CTRL_CH1_EN;
+
+ writel(lvds_ctrl, priv->lvds_ctrl);
+
+ return 0;
+}
+
+static int imx_ldb_probe(struct udevice *dev)
+{
+ struct imx_ldb_priv *priv = dev_get_priv(dev);
+ struct udevice *parent = dev_get_parent(dev);
+ fdt_addr_t parent_addr, child_addr;
+ int ret;
+
+ ret = clk_get_by_name(dev, "ldb", &priv->ldb_clk);
+ if (ret < 0)
+ return ret;
+
+ parent_addr = dev_read_addr(parent);
+ if (parent_addr == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ child_addr = dev_read_addr_name(dev, "ldb");
+ if (child_addr == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ priv->ldb_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE);
+ if (!priv->ldb_ctrl)
+ return -EINVAL;
+
+ child_addr = dev_read_addr_name(dev, "lvds");
+ if (child_addr == FDT_ADDR_T_NONE)
+ return -EINVAL;
+
+ priv->lvds_ctrl = map_physmem(parent_addr + child_addr, 0, MAP_NOCACHE);
+ if (!priv->lvds_ctrl)
+ return -EINVAL;
+
+ ret = clk_enable(&priv->ldb_clk);
+ if (ret)
+ return ret;
+
+ ret = video_bridge_set_active(dev, true);
+ if (ret)
+ goto dis_clk;
+
+ return 0;
+
+dis_clk:
+ clk_disable(&priv->ldb_clk);
+
+ return ret;
+}
+
+struct video_bridge_ops imx_ldb_ops = {
+ .attach = imx_ldb_attach,
+ .set_backlight = imx_ldb_set_backlight,
+};
+
+static const struct udevice_id imx_ldb_ids[] = {
+ { .compatible = "fsl,imx8mp-ldb"},
+ { }
+};
+
+U_BOOT_DRIVER(imx_ldb) = {
+ .name = "imx-lvds-display-bridge",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = imx_ldb_ids,
+ .probe = imx_ldb_probe,
+ .of_to_plat = imx_ldb_of_to_plat,
+ .ops = &imx_ldb_ops,
+ .priv_auto = sizeof(struct imx_ldb_priv),
+};