diff options
Diffstat (limited to 'drivers/gpu/imx/lcdifv3')
-rw-r--r-- | drivers/gpu/imx/lcdifv3/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/imx/lcdifv3/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/imx/lcdifv3/lcdifv3-common.c | 862 | ||||
-rw-r--r-- | drivers/gpu/imx/lcdifv3/lcdifv3-regs.h | 150 |
4 files changed, 1024 insertions, 0 deletions
diff --git a/drivers/gpu/imx/lcdifv3/Kconfig b/drivers/gpu/imx/lcdifv3/Kconfig new file mode 100644 index 000000000000..3ef509310ce7 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/Kconfig @@ -0,0 +1,9 @@ +config IMX_LCDIFV3_CORE + tristate "i.MX LCDIFV3 core support" + depends on ARCH_MXC + select RESET_CONTROLLER + help + Choose this if you have a NXP i.MX8MP platform and want to use the + LCDIFV3 display controller. This option only enables LCDIFV3 base + support. + diff --git a/drivers/gpu/imx/lcdifv3/Makefile b/drivers/gpu/imx/lcdifv3/Makefile new file mode 100644 index 000000000000..812043e52f34 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_IMX_LCDIFV3_CORE) += imx-lcdifv3-core.o + +imx-lcdifv3-core-objs := lcdifv3-common.o diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-common.c b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c new file mode 100644 index 000000000000..fa12cc78c550 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright 2019 NXP + */ + +#include <linux/busfreq-imx.h> +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/types.h> +#include <drm/drm_fourcc.h> +#include <video/imx-lcdifv3.h> +#include <video/videomode.h> + +#include "lcdifv3-regs.h" + +#define DRIVER_NAME "imx-lcdifv3" + +struct lcdifv3_soc { + struct device *dev; + + int irq; + void __iomem *base; + struct regmap *gpr; + atomic_t rpm_suspended; + + struct clk *clk_pix; + struct clk *clk_disp_axi; + struct clk *clk_disp_apb; + + u32 thres_low_mul; + u32 thres_low_div; + u32 thres_high_mul; + u32 thres_high_div; +}; + +struct lcdifv3_soc_pdata { + bool hsync_invert; + bool vsync_invert; + bool de_invert; + bool hdmimix; +}; + +struct lcdifv3_platform_reg { + struct lcdifv3_client_platformdata pdata; + char *name; +}; + +static struct lcdifv3_platform_reg client_reg[] = { + { + .pdata = { }, + .name = "imx-lcdifv3-crtc", + }, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif1_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = false, + .hdmimix = false, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif2_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = true, + .hdmimix = false, +}; + +static struct lcdifv3_soc_pdata imx8mp_lcdif3_pdata = { + .hsync_invert = false, + .vsync_invert = false, + .de_invert = false, + .hdmimix = true, +}; +static const struct of_device_id imx_lcdifv3_dt_ids[] = { + { .compatible = "fsl,imx8mp-lcdif1", .data = &imx8mp_lcdif1_pdata, }, + { .compatible = "fsl,imx8mp-lcdif2", .data = &imx8mp_lcdif2_pdata, }, + { .compatible = "fsl,imx8mp-lcdif3", .data = &imx8mp_lcdif3_pdata,}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_lcdifv3_dt_ids); + +static int lcdifv3_enable_clocks(struct lcdifv3_soc *lcdifv3) +{ + int ret; + + if (lcdifv3->clk_disp_axi) { + ret = clk_prepare_enable(lcdifv3->clk_disp_axi); + if (ret) + return ret; + } + + if (lcdifv3->clk_disp_apb) { + ret = clk_prepare_enable(lcdifv3->clk_disp_apb); + if (ret) + goto disable_disp_axi; + } + + ret = clk_prepare_enable(lcdifv3->clk_pix); + if (ret) + goto disable_disp_apb; + + return 0; + +disable_disp_apb: + if (lcdifv3->clk_disp_apb) + clk_disable_unprepare(lcdifv3->clk_disp_apb); +disable_disp_axi: + if (lcdifv3->clk_disp_axi) + clk_disable_unprepare(lcdifv3->clk_disp_axi); + + return ret; +} + +static void lcdifv3_disable_clocks(struct lcdifv3_soc *lcdifv3) +{ + clk_disable_unprepare(lcdifv3->clk_pix); + + if (lcdifv3->clk_disp_axi) + clk_disable_unprepare(lcdifv3->clk_disp_axi); + + if (lcdifv3->clk_disp_apb) + clk_disable_unprepare(lcdifv3->clk_disp_apb); +} + +static void lcdifv3_enable_plane_panic(struct lcdifv3_soc *lcdifv3) +{ + u32 panic_thres, thres_low, thres_high; + + /* apb clock has been enabled */ + + /* As suggestion, the thres_low should be 1/3 FIFO, + * and thres_high should be 2/3 FIFO (The FIFO size + * is 8KB = 512 * 128bit). + * threshold = n * 128bit (n: 0 ~ 511) + */ + thres_low = DIV_ROUND_UP(511 * lcdifv3->thres_low_mul, + lcdifv3->thres_low_div); + thres_high = DIV_ROUND_UP(511 * lcdifv3->thres_high_mul, + lcdifv3->thres_high_div); + + panic_thres = PANIC0_THRES_PANIC_THRES_LOW(thres_low) | + PANIC0_THRES_PANIC_THRES_HIGH(thres_high); + + writel(panic_thres, lcdifv3->base + LCDIFV3_PANIC0_THRES); + + /* Enable Panic: + * + * As designed, the panic won't trigger an irq, + * so it is unnecessary to handle this as an irq + * and NoC + QoS modules will handle panic + * automatically. + */ + writel(INT_ENABLE_D1_PLANE_PANIC_EN, + lcdifv3->base + LCDIFV3_INT_ENABLE_D1); +} + +int lcdifv3_vblank_irq_get(struct lcdifv3_soc *lcdifv3) +{ + return lcdifv3->irq; +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_get); + +/* TODO: use VS_BLANK or VSYNC? */ +void lcdifv3_vblank_irq_enable(struct lcdifv3_soc *lcdifv3) +{ + uint32_t int_enable_d0; + + int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + int_enable_d0 |= INT_STATUS_D0_VS_BLANK; + + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); + /* enable */ + writel(int_enable_d0, + lcdifv3->base + LCDIFV3_INT_ENABLE_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_enable); + +void lcdifv3_vblank_irq_disable(struct lcdifv3_soc *lcdifv3) +{ + uint32_t int_enable_d0; + + int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + int_enable_d0 &= ~INT_STATUS_D0_VS_BLANK; + + /* disable */ + writel(int_enable_d0, + lcdifv3->base + LCDIFV3_INT_ENABLE_D0); + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_disable); + +void lcdifv3_vblank_irq_clear(struct lcdifv3_soc *lcdifv3) +{ + /* W1C */ + writel(INT_STATUS_D0_VS_BLANK, + lcdifv3->base + LCDIFV3_INT_STATUS_D0); +} +EXPORT_SYMBOL(lcdifv3_vblank_irq_clear); + +static uint32_t lcdifv3_get_bpp_from_fmt(uint32_t format) +{ + /* TODO: only support RGB for now */ + + switch (format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + return 16; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + return 32; + default: + /* unsupported format */ + return 0; + } +} + +/* + * Get the bus format supported by LCDIF + * according to drm fourcc format + */ +int lcdifv3_get_bus_fmt_from_pix_fmt(struct lcdifv3_soc *lcdifv3, + uint32_t format) +{ + uint32_t bpp; + + bpp = lcdifv3_get_bpp_from_fmt(format); + if (!bpp) + return -EINVAL; + + switch (bpp) { + case 16: + return MEDIA_BUS_FMT_RGB565_1X16; + case 18: + return MEDIA_BUS_FMT_RGB666_1X18; + case 24: + case 32: + return MEDIA_BUS_FMT_RGB888_1X24; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(lcdifv3_get_bus_fmt_from_pix_fmt); + +int lcdifv3_set_pix_fmt(struct lcdifv3_soc *lcdifv3, u32 format) +{ + struct drm_format_name_buf format_name; + uint32_t ctrldescl0_5 = 0; + + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + ctrldescl0_5 &= ~(CTRLDESCL0_5_BPP(0xf) | CTRLDESCL0_5_YUV_FORMAT(0x3)); + + switch (format) { + case DRM_FORMAT_RGB565: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_RGB565); + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_ARGB1555); + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ARGB8888); + break; + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ABGR8888); + break; + default: + dev_err(lcdifv3->dev, "unsupported pixel format: %s\n", + drm_get_format_name(format, &format_name)); + return -EINVAL; + } + + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + return 0; +} +EXPORT_SYMBOL(lcdifv3_set_pix_fmt); + +void lcdifv3_set_bus_fmt(struct lcdifv3_soc *lcdifv3, u32 bus_format) +{ + uint32_t disp_para = 0; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + + /* clear line pattern bits */ + disp_para &= ~DISP_PARA_LINE_PATTERN(0xf); + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB565); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB888_OR_YUV444); + break; + default: + dev_err(lcdifv3->dev, "unknown bus format: %#x\n", bus_format); + return; + } + + /* config display mode: default is normal mode */ + disp_para &= ~DISP_PARA_DISP_MODE(3); + disp_para |= DISP_PARA_DISP_MODE(0); + + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); +} +EXPORT_SYMBOL(lcdifv3_set_bus_fmt); + +void lcdifv3_set_fb_addr(struct lcdifv3_soc *lcdifv3, int id, u32 addr) +{ + switch (id) { + case 0: + /* primary plane */ + writel(addr, lcdifv3->base + LCDIFV3_CTRLDESCL_LOW0_4); + break; + default: + /* TODO: add overlay support */ + return; + } +} +EXPORT_SYMBOL(lcdifv3_set_fb_addr); + +void lcdifv3_set_fb_hcrop(struct lcdifv3_soc *lcdifv3, u32 src_w, + u32 pitch, bool crop) +{ + uint32_t ctrldescl0_3 = 0; + + /* config P_SIZE and T_SIZE: + * 1. P_SIZE and T_SIZE should never + * be less than AXI bus width. + * 2. P_SIZE should never be less than T_SIZE. + */ + ctrldescl0_3 |= CTRLDESCL0_3_P_SIZE(2); + ctrldescl0_3 |= CTRLDESCL0_3_T_SIZE(2); + + /* config pitch */ + ctrldescl0_3 |= CTRLDESCL0_3_PITCH(pitch); + + /* enable frame clear to clear FIFO data on + * every vsync blank period to make sure no + * dirty data exits to affect next frame + * display, otherwise some flicker issue may + * be observed in some cases. + */ + ctrldescl0_3 |= CTRLDESCL0_3_STATE_CLEAR_VSYNC; + + writel(ctrldescl0_3, lcdifv3->base + LCDIFV3_CTRLDESCL0_3); +} +EXPORT_SYMBOL(lcdifv3_set_fb_hcrop); + + +void lcdifv3_set_mode(struct lcdifv3_soc *lcdifv3, struct videomode *vmode) +{ + const struct of_device_id *of_id = + of_match_device(imx_lcdifv3_dt_ids, lcdifv3->dev); + const struct lcdifv3_soc_pdata *soc_pdata; + u32 disp_size, hsyn_para, vsyn_para, vsyn_hsyn_width, ctrldescl0_1; + + if (unlikely(!of_id)) + return; + soc_pdata = of_id->data; + + /* set pixel clock rate */ + clk_disable_unprepare(lcdifv3->clk_pix); + clk_set_rate(lcdifv3->clk_pix, vmode->pixelclock); + clk_prepare_enable(lcdifv3->clk_pix); + + /* config display timings */ + disp_size = DISP_SIZE_DELTA_Y(vmode->vactive) | + DISP_SIZE_DELTA_X(vmode->hactive); + writel(disp_size, lcdifv3->base + LCDIFV3_DISP_SIZE); + + WARN_ON(!vmode->hback_porch || !vmode->hfront_porch); + hsyn_para = HSYN_PARA_BP_H(vmode->hback_porch) | + HSYN_PARA_FP_H(vmode->hfront_porch); + writel(hsyn_para, lcdifv3->base + LCDIFV3_HSYN_PARA); + + WARN_ON(!vmode->vback_porch || !vmode->vfront_porch); + vsyn_para = VSYN_PARA_BP_V(vmode->vback_porch) | + VSYN_PARA_FP_V(vmode->vfront_porch); + writel(vsyn_para, lcdifv3->base + LCDIFV3_VSYN_PARA); + + WARN_ON(!vmode->vsync_len || !vmode->hsync_len); + vsyn_hsyn_width = VSYN_HSYN_WIDTH_PW_V(vmode->vsync_len) | + VSYN_HSYN_WIDTH_PW_H(vmode->hsync_len); + writel(vsyn_hsyn_width, lcdifv3->base + LCDIFV3_VSYN_HSYN_WIDTH); + + /* config layer size */ + /* TODO: 32bits alignment for width */ + ctrldescl0_1 = CTRLDESCL0_1_HEIGHT(vmode->vactive) | + CTRLDESCL0_1_WIDTH(vmode->hactive); + writel(ctrldescl0_1, lcdifv3->base + LCDIFV3_CTRLDESCL0_1); + + /* Polarities */ + if (soc_pdata) { + if ((soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH) || + (!soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_LOW)) + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR); + + if ((soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH) || + (!soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_LOW)) + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR); + + if ((soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_HIGH) || + (!soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_LOW)) + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR); + } else { + if (vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR); + if (vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR); + if (vmode->flags & DISPLAY_FLAGS_DE_LOW) + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET); + else + writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR); + } + + if (vmode->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_CLR); + else + writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_SET); +} +EXPORT_SYMBOL(lcdifv3_set_mode); + +void lcdifv3_en_shadow_load(struct lcdifv3_soc *lcdifv3) +{ + u32 ctrldescl0_5; + + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + ctrldescl0_5 |= CTRLDESCL0_5_SHADOW_LOAD_EN; + + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); +} +EXPORT_SYMBOL(lcdifv3_en_shadow_load); + +void lcdifv3_enable_controller(struct lcdifv3_soc *lcdifv3) +{ + u32 disp_para, ctrldescl0_5; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* disp on */ + disp_para |= DISP_PARA_DISP_ON; + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); + + /* enable layer dma */ + ctrldescl0_5 |= CTRLDESCL0_5_EN; + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); +} +EXPORT_SYMBOL(lcdifv3_enable_controller); + +void lcdifv3_disable_controller(struct lcdifv3_soc *lcdifv3) +{ + u32 disp_para, ctrldescl0_5; + + disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA); + ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* disable dma */ + ctrldescl0_5 &= ~CTRLDESCL0_5_EN; + writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5); + + /* dma config only takes effect at the end of + * one frame, so add delay to wait dma disable + * done before turn off disp. + */ + usleep_range(20000, 25000); + + /* disp off */ + disp_para &= ~DISP_PARA_DISP_ON; + writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA); +} +EXPORT_SYMBOL(lcdifv3_disable_controller); + +long lcdifv3_pix_clk_round_rate(struct lcdifv3_soc *lcdifv3, + unsigned long rate) +{ + if (unlikely(!rate)) + return -EINVAL; + + return clk_round_rate(lcdifv3->clk_pix, rate); +} +EXPORT_SYMBOL(lcdifv3_pix_clk_round_rate); + +static int hdmimix_lcdif3_setup(struct lcdifv3_soc *lcdifv3) +{ + struct device *dev = lcdifv3->dev; + int ret; + + struct clk_bulk_data clocks[] = { + { .id = "mix_apb" }, + { .id = "mix_axi" }, + { .id = "xtl_24m" }, + { .id = "mix_pix" }, + { .id = "lcdif_apb" }, + { .id = "lcdif_axi" }, + { .id = "lcdif_pdi" }, + { .id = "lcdif_pix" }, + { .id = "lcdif_spu" }, + { .id = "noc_hdmi" }, + }; + + /* power up hdmimix lcdif and nor */ + ret = device_reset(dev); + if (ret == -EPROBE_DEFER) + return ret; + + /* enable lpcg of hdmimix lcdif and nor */ + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(clocks), clocks); + if (ret < 0) + return ret; + ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); + if (ret < 0) + return ret; + + return 0; +} + +static int platform_remove_device_fn(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static void platform_device_unregister_children(struct platform_device *pdev) +{ + device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn); +} + +static DEFINE_MUTEX(lcdifv3_client_id_mutex); +static int lcdifv3_client_id; + +static int lcdifv3_add_client_devices(struct lcdifv3_soc *lcdifv3) +{ + int ret = 0, i, id; + struct device *dev = lcdifv3->dev; + struct platform_device *pdev = NULL; + struct device_node *of_node; + + for (i = 0; i < ARRAY_SIZE(client_reg); i++) { + of_node = of_graph_get_port_by_id(dev->of_node, i); + if (!of_node) { + dev_info(dev, "no port@%d node in %s\n", + i, dev->of_node->full_name); + continue; + } + of_node_put(of_node); + + mutex_lock(&lcdifv3_client_id_mutex); + id = lcdifv3_client_id++; + mutex_unlock(&lcdifv3_client_id_mutex); + + pdev = platform_device_alloc(client_reg[i].name, id); + if (!pdev) { + dev_err(dev, "Can't allocate port pdev\n"); + ret = -ENOMEM; + goto err_register; + } + + pdev->dev.parent = dev; + client_reg[i].pdata.of_node = of_node; + + /* make child device 'dma_mask' to point to its + * coherent dma mask, otherwise later probe will + * print warning message: 'DMA mask not set'. + */ + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + ret = platform_device_add_data(pdev, &client_reg[i].pdata, + sizeof(client_reg[i].pdata)); + if (!ret) + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + goto err_register; + } + + pdev->dev.of_node = of_node; + } + + if (!pdev) + return -ENODEV; + + return 0; + +err_register: + platform_device_unregister_children(to_platform_device(dev)); + return ret; +} + +static int imx_lcdifv3_check_thres_value(u32 mul, u32 div) +{ + if (!div) + return -EINVAL; + + if (mul > div) + return -EINVAL; + + return 0; +} + +static void imx_lcdifv3_of_parse_thres(struct lcdifv3_soc *lcdifv3) +{ + int ret; + u32 thres_low[2], thres_high[2]; + struct device_node *np = lcdifv3->dev->of_node; + + /* default 'thres-low' value: FIFO * 1/3; + * default 'thres-high' value: FIFO * 2/3. + */ + lcdifv3->thres_low_mul = 1; + lcdifv3->thres_low_div = 3; + lcdifv3->thres_high_mul = 2; + lcdifv3->thres_high_div = 3; + + ret = of_property_read_u32_array(np, "thres-low", thres_low, 2); + if (!ret) { + /* check the value effectiveness */ + ret = imx_lcdifv3_check_thres_value(thres_low[0], thres_low[1]); + if (!ret) { + lcdifv3->thres_low_mul = thres_low[0]; + lcdifv3->thres_low_div = thres_low[1]; + } + } + + ret = of_property_read_u32_array(np, "thres-high", thres_high, 2); + if (!ret) { + /* check the value effectiveness */ + ret = imx_lcdifv3_check_thres_value(thres_high[0], thres_high[1]); + if (!ret) { + lcdifv3->thres_high_mul = thres_high[0]; + lcdifv3->thres_high_div = thres_high[1]; + } + } +} + +static int imx_lcdifv3_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct lcdifv3_soc *lcdifv3; + struct resource *res; + struct regmap *blk_ctl; + const struct of_device_id *of_id; + const struct lcdifv3_soc_pdata *soc_pdata; + + dev_dbg(dev, "%s: probe begin\n", __func__); + + of_id = of_match_device(imx_lcdifv3_dt_ids, dev); + if (!of_id) { + dev_err(&pdev->dev, "OF data missing\n"); + return -EINVAL; + } + + soc_pdata = of_id->data; + + lcdifv3 = devm_kzalloc(dev, sizeof(*lcdifv3), GFP_KERNEL); + if (!lcdifv3) { + dev_err(dev, "Can't allocate 'lcdifv3_soc' structure\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + lcdifv3->irq = platform_get_irq(pdev, 0); + if (lcdifv3->irq < 0) { + dev_err(dev, "No irq get\n"); + return -EPROBE_DEFER; + } + + lcdifv3->clk_pix = devm_clk_get(dev, "pix"); + if (IS_ERR(lcdifv3->clk_pix)) { + dev_err(dev, "No pix clock get\n"); + return -EPROBE_DEFER; + } + + lcdifv3->clk_disp_axi = devm_clk_get(dev, "disp-axi"); + if (IS_ERR(lcdifv3->clk_disp_axi)) + lcdifv3->clk_disp_axi = NULL; + + lcdifv3->clk_disp_apb = devm_clk_get(dev, "disp-apb"); + if (IS_ERR(lcdifv3->clk_disp_apb)) + lcdifv3->clk_disp_apb = NULL; + + lcdifv3->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lcdifv3->base)) + return PTR_ERR(lcdifv3->base); + + lcdifv3->dev = dev; + + /* reset controller to avoid any conflict + * with uboot splash screen settings. + */ + blk_ctl = syscon_regmap_lookup_by_phandle(np, "blk-ctl"); + if (IS_ERR(blk_ctl)) + blk_ctl = NULL; + + if (blk_ctl) { + clk_prepare_enable(lcdifv3->clk_disp_apb); + writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR); + + /* reset LCDIFv3 controller */ + regmap_update_bits(blk_ctl, 0x0, BIT(5), 0x0); + regmap_update_bits(blk_ctl, 0x0, BIT(5), BIT(5)); + + clk_disable_unprepare(lcdifv3->clk_disp_apb); + } + + imx_lcdifv3_of_parse_thres(lcdifv3); + + platform_set_drvdata(pdev, lcdifv3); + + if (soc_pdata->hdmimix) { + ret = hdmimix_lcdif3_setup(lcdifv3); + if (ret < 0) { + dev_err(dev, "hdmimix lcdif3 setup failed\n"); + return ret; + } + } + + atomic_set(&lcdifv3->rpm_suspended, 0); + pm_runtime_enable(dev); + atomic_inc(&lcdifv3->rpm_suspended); + + dev_dbg(dev, "%s: probe end\n", __func__); + + return lcdifv3_add_client_devices(lcdifv3); +} + +static int imx_lcdifv3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int imx_lcdifv3_runtime_suspend(struct device *dev) +{ + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev); + + if (atomic_inc_return(&lcdifv3->rpm_suspended) > 1) + return 0; + + lcdifv3_disable_clocks(lcdifv3); + + release_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static int imx_lcdifv3_runtime_resume(struct device *dev) +{ + int ret = 0; + struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev); + + if (unlikely(!atomic_read(&lcdifv3->rpm_suspended))) { + dev_warn(lcdifv3->dev, "Unbalanced %s!\n", __func__); + return 0; + } + + if (!atomic_dec_and_test(&lcdifv3->rpm_suspended)) + return 0; + + request_bus_freq(BUS_FREQ_HIGH); + + ret = lcdifv3_enable_clocks(lcdifv3); + if (ret) { + release_bus_freq(BUS_FREQ_HIGH); + return ret; + } + + /* clear sw_reset */ + writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR); + + /* enable plane FIFO panic */ + lcdifv3_enable_plane_panic(lcdifv3); + + return ret; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int imx_lcdifv3_suspend(struct device *dev) +{ + return imx_lcdifv3_runtime_suspend(dev); +} + +static int imx_lcdifv3_resume(struct device *dev) +{ + return imx_lcdifv3_runtime_resume(dev); +} +#endif + +static const struct dev_pm_ops imx_lcdifv3_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_lcdifv3_suspend, + imx_lcdifv3_resume) + SET_RUNTIME_PM_OPS(imx_lcdifv3_runtime_suspend, + imx_lcdifv3_runtime_resume, NULL) +}; + +struct platform_driver imx_lcdifv3_driver = { + .probe = imx_lcdifv3_probe, + .remove = imx_lcdifv3_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_lcdifv3_dt_ids, + .pm = &imx_lcdifv3_pm_ops, + }, +}; + +module_platform_driver(imx_lcdifv3_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIFV3 Display Controller driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h new file mode 100644 index 000000000000..d47c2f80b8c3 --- /dev/null +++ b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2019 NXP + */ + +#ifndef __LCDIFV3_REGS_H +#define __LCDIFV3_REGS_H + +/* regs offset */ +#define LCDIFV3_CTRL 0x00 +#define LCDIFV3_CTRL_SET 0x04 +#define LCDIFV3_CTRL_CLR 0x08 +#define LCDIFV3_CTRL_TOG 0x0c +#define LCDIFV3_DISP_PARA 0x10 +#define LCDIFV3_DISP_SIZE 0x14 +#define LCDIFV3_HSYN_PARA 0x18 +#define LCDIFV3_VSYN_PARA 0x1c +#define LCDIFV3_VSYN_HSYN_WIDTH 0x20 +#define LCDIFV3_INT_STATUS_D0 0x24 +#define LCDIFV3_INT_ENABLE_D0 0x28 +#define LCDIFV3_INT_STATUS_D1 0x30 +#define LCDIFV3_INT_ENABLE_D1 0x34 + +#define LCDIFV3_CTRLDESCL0_1 0x200 +#define LCDIFV3_CTRLDESCL0_3 0x208 +#define LCDIFV3_CTRLDESCL_LOW0_4 0x20c +#define LCDIFV3_CTRLDESCL_HIGH0_4 0x210 +#define LCDIFV3_CTRLDESCL0_5 0x214 +#define LCDIFV3_CSC0_CTRL 0x21c +#define LCDIFV3_CSC0_COEF0 0x220 +#define LCDIFV3_CSC0_COEF1 0x224 +#define LCDIFV3_CSC0_COEF2 0x228 +#define LCDIFV3_CSC0_COEF3 0x22c +#define LCDIFV3_CSC0_COEF4 0x230 +#define LCDIFV3_CSC0_COEF5 0x234 +#define LCDIFV3_PANIC0_THRES 0x238 + +/* reg bit manipulation */ +#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s)) +#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s)) +#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s)) + +/* regs bit fields */ +#define CTRL_SW_RESET BIT(31) +#define CTRL_FETCH_START_OPTION(x) REG_PUT((x), 9, 8) + #define FPV 0 + #define PWV 1 + #define BPV 2 + #define RESV 3 +#define CTRL_NEG BIT(4) +#define CTRL_INV_PXCK BIT(3) +#define CTRL_INV_DE BIT(2) +#define CTRL_INV_VS BIT(1) +#define CTRL_INV_HS BIT(0) + +#define DISP_PARA_DISP_ON BIT(31) +#define DISP_PARA_SWAP_EN BIT(30) +#define DISP_PARA_LINE_PATTERN(x) REG_PUT((x), 29, 26) + /* line pattern formats (output) */ + #define LP_RGB888_OR_YUV444 0x0 + #define LP_RBG888 0x1 + #define LP_GBR888 0x2 + #define LP_GRB888_OR_UYV444 0x3 + #define LP_BRG888 0x4 + #define LP_BGR888 0x5 + #define LP_RGB555 0x6 + #define LP_RGB565 0x7 + #define LP_YUYV_16_0 0x8 + #define LP_UYVY_16_0 0x9 + #define LP_YVYU_16_0 0xa + #define LP_VYUY_16_0 0xb + #define LP_YUYV_23_8 0xc + #define LP_UYVY_23_8 0xd + #define LP_YVYU_23_8 0xe + #define LP_VYUY_23_8 0xf + +#define DISP_PARA_DISP_MODE(x) REG_PUT((x), 25, 24) +#define DISP_PARA_BGND_R(x) REG_PUT((x), 23, 16) +#define DISP_PARA_BGND_G(x) REG_PUT((x), 15, 8) +#define DISP_PARA_BGND_B(x) REG_PUT((x), 7, 0) + +#define DISP_SIZE_DELTA_Y(x) REG_PUT((x), 31, 16) +#define DISP_SIZE_DELTA_X(x) REG_PUT((x), 15, 0) + +#define HSYN_PARA_BP_H(x) REG_PUT((x), 31, 16) +#define HSYN_PARA_FP_H(x) REG_PUT((x), 15, 0) + +#define VSYN_PARA_BP_V(x) REG_PUT((x), 31, 16) +#define VSYN_PARA_FP_V(x) REG_PUT((x), 15, 0) + +#define VSYN_HSYN_WIDTH_PW_V(x) REG_PUT((x), 31, 16) +#define VSYN_HSYN_WIDTH_PW_H(x) REG_PUT((x), 15, 0) + +#define INT_STATUS_D0_FIFO_EMPTY BIT(24) +#define INT_STATUS_D0_DMA_DONE BIT(16) +#define INT_STATUS_D0_DMA_ERR BIT(8) +#define INT_STATUS_D0_VS_BLANK BIT(2) +#define INT_STATUS_D0_UNDERRUN BIT(1) +#define INT_STATUS_D0_VSYNC BIT(0) + +#define INT_ENABLE_D0_FIFO_EMPTY_EN BIT(24) +#define INT_ENABLE_D0_DMA_DONE_EN BIT(16) +#define INT_ENABLE_D0_DMA_ERR_EN BIT(8) +#define INT_ENABLE_D0_VS_BLANK_EN BIT(2) +#define INT_ENABLE_D0_UNDERRUN_EN BIT(1) +#define INT_ENABLE_D0_VSYNC_EN BIT(0) + +#define INT_STATUS_D1_PLANE_PANIC BIT(0) +#define INT_ENABLE_D1_PLANE_PANIC_EN BIT(0) + +#define CTRLDESCL0_1_HEIGHT(x) REG_PUT((x), 31, 16) +#define CTRLDESCL0_1_WIDTH(x) REG_PUT((x), 15, 0) +#define CTRLDESCL0_3_STATE_CLEAR_VSYNC BIT(23) +#define CTRLDESCL0_3_P_SIZE(x) REG_PUT((x), 22, 20) +#define CTRLDESCL0_3_T_SIZE(x) REG_PUT((x), 17, 16) +#define CTRLDESCL0_3_PITCH(x) REG_PUT((x), 15, 0) +//#define CTRLDESCL_LOW0_4_ADDR_LOW(x) REG_PUT((x), 31, 0) +#define CTRLDESCL_HIGH0_4_ADDR_HIGH(x) REG_PUT((x), 3, 0) +#define CTRLDESCL0_5_EN BIT(31) /* enable layer for DMA */ +#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30) +#define CTRLDESCL0_5_BPP(x) REG_PUT((x), 27, 24) + /* layer encoding formats (input) */ + #define BPP16_RGB565 0x4 + #define BPP16_ARGB1555 0x5 + #define BPP16_ARGB4444 0x6 + #define BPP16_YCbCr422 0x7 + #define BPP24_RGB888 0x8 + #define BPP32_ARGB8888 0x9 + #define BPP32_ABGR8888 0xa +#define CTRLDESCL0_5_YUV_FORMAT(x) REG_PUT((x), 15, 14) + +#define CSC0_CTRL_CSC_MODE(x) REG_PUT((x), 2, 1) +#define CSC0_CTRL_BYPASS BIT(0) +#define CSC0_COEF0_A2(x) REG_PUT((x), 26, 16) +#define CSC0_COEF0_A1(x) REG_PUT((x), 10, 0) +#define CSC0_COEF1_B1(x) REG_PUT((x), 26, 16) +#define CSC0_COEF1_A3(x) REG_PUT((x), 10, 0) +#define CSC0_COEF2_B3(x) REG_PUT((x), 26, 16) +#define CSC0_COEF2_B2(x) REG_PUT((x), 10, 0) +#define CSC0_COEF3_C2(x) REG_PUT((x), 26, 16) +#define CSC0_COEF3_C1(x) REG_PUT((x), 10, 0) +#define CSC0_COEF4_D1(x) REG_PUT((x), 24, 16) +#define CSC0_COEF4_C3(x) REG_PUT((x), 10, 0) +#define CSC0_COEF5_D3(x) REG_PUT((x), 24, 16) +#define CSC0_COEF5_D2(x) REG_PUT((x), 8, 0) + +#define PANIC0_THRES_PANIC_THRES_LOW(x) REG_PUT((x), 24, 16) +#define PANIC0_THRES_PANIC_THRES_HIGH(x) REG_PUT((x), 8, 0) + +#endif /* __LCDIFV3_REGS_H */ |