diff options
Diffstat (limited to 'drivers/gpu/imx/lcdif/lcdif-common.c')
-rw-r--r-- | drivers/gpu/imx/lcdif/lcdif-common.c | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c new file mode 100644 index 000000000000..06abc666ad52 --- /dev/null +++ b/drivers/gpu/imx/lcdif/lcdif-common.c @@ -0,0 +1,799 @@ +/* + * Copyright 2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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/types.h> +#include <drm/drm_fourcc.h> +#include <video/imx-lcdif.h> +#include <video/videomode.h> + +#include "lcdif-regs.h" + +#define DRIVER_NAME "imx-lcdif" + +/* TODO: add this to platform data later */ +#define DISP_MIX_SFT_RSTN_CSR 0x00 +#define DISP_MIX_CLK_EN_CSR 0x04 + +/* 'DISP_MIX_SFT_RSTN_CSR' bit fields */ +#define BUS_RSTN_BLK_SYNC_SFT_EN BIT(6) + +/* 'DISP_MIX_CLK_EN_CSR' bit fields */ +#define BUS_BLK_CLK_SFT_EN BIT(12) +#define LCDIF_PIXEL_CLK_SFT_EN BIT(7) +#define LCDIF_APB_CLK_SFT_EN BIT(6) + +struct lcdif_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; +}; + +struct lcdif_soc_pdata { + bool hsync_invert; + bool vsync_invert; + bool de_invert; +}; + +struct lcdif_platform_reg { + struct lcdif_client_platformdata pdata; + char *name; +}; + +struct lcdif_platform_reg client_reg[] = { + { + .pdata = { }, + .name = "imx-lcdif-crtc", + }, +}; + +struct lcdif_soc_pdata imx8mm_pdata = { + .hsync_invert = true, + .vsync_invert = true, + .de_invert = true, +}; + +static const struct of_device_id imx_lcdif_dt_ids[] = { + { .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lcdif_dt_ids); + +#ifdef CONFIG_PM +static int imx_lcdif_runtime_suspend(struct device *dev); +static int imx_lcdif_runtime_resume(struct device *dev); +#else +static int imx_lcdif_runtime_suspend(struct device *dev) +{ + return 0; +} +static int imx_lcdif_runtime_resume(struct device *dev) +{ + return 0; +} +#endif + +void disp_mix_bus_rstn_reset(struct regmap *gpr, bool reset) +{ + if (!reset) + /* release reset */ + regmap_update_bits(gpr, DISP_MIX_SFT_RSTN_CSR, + BUS_RSTN_BLK_SYNC_SFT_EN, + BUS_RSTN_BLK_SYNC_SFT_EN); + else + /* hold reset */ + regmap_update_bits(gpr, DISP_MIX_SFT_RSTN_CSR, + BUS_RSTN_BLK_SYNC_SFT_EN, + 0x0); +} + +void disp_mix_lcdif_clks_enable(struct regmap *gpr, bool enable) +{ + if (enable) + /* enable lcdif clks */ + regmap_update_bits(gpr, DISP_MIX_CLK_EN_CSR, + LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN, + LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN); + else + /* disable lcdif clks */ + regmap_update_bits(gpr, DISP_MIX_CLK_EN_CSR, + LCDIF_PIXEL_CLK_SFT_EN | LCDIF_APB_CLK_SFT_EN, + 0x0); +} + +static int lcdif_enable_clocks(struct lcdif_soc *lcdif) +{ + int ret; + + if (lcdif->clk_disp_axi) { + ret = clk_prepare_enable(lcdif->clk_disp_axi); + if (ret) + return ret; + } + + if (lcdif->clk_disp_apb) { + ret = clk_prepare_enable(lcdif->clk_disp_apb); + if (ret) + goto disable_disp_axi; + } + + ret = clk_prepare_enable(lcdif->clk_pix); + if (ret) + goto disable_disp_apb; + + return 0; + +disable_disp_apb: + if (lcdif->clk_disp_apb) + clk_disable_unprepare(lcdif->clk_disp_apb); +disable_disp_axi: + if (lcdif->clk_disp_axi) + clk_disable_unprepare(lcdif->clk_disp_axi); + + return ret; +} + +static void lcdif_disable_clocks(struct lcdif_soc *lcdif) +{ + clk_disable_unprepare(lcdif->clk_pix); + + if (lcdif->clk_disp_axi) + clk_disable_unprepare(lcdif->clk_disp_axi); + + if (lcdif->clk_disp_apb) + clk_disable_unprepare(lcdif->clk_disp_apb); +} + +int lcdif_vblank_irq_get(struct lcdif_soc *lcdif) +{ + return lcdif->irq; +} +EXPORT_SYMBOL(lcdif_vblank_irq_get); + +void lcdif_dump_registers(struct lcdif_soc *lcdif) +{ + pr_info("%#x : %#x\n", LCDIF_CTRL, + readl(lcdif->base + LCDIF_CTRL)); + pr_info("%#x : %#x\n", LCDIF_CTRL1, + readl(lcdif->base + LCDIF_CTRL1)); + pr_info("%#x : %#x\n", LCDIF_CTRL2, + readl(lcdif->base + LCDIF_CTRL2)); + pr_info("%#x : %#x\n", LCDIF_TRANSFER_COUNT, + readl(lcdif->base + LCDIF_TRANSFER_COUNT)); + pr_info("%#x : %#x\n", LCDIF_CUR_BUF, + readl(lcdif->base + LCDIF_CUR_BUF)); + pr_info("%#x : %#x\n", LCDIF_NEXT_BUF, + readl(lcdif->base + LCDIF_NEXT_BUF)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL0, + readl(lcdif->base + LCDIF_VDCTRL0)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL1, + readl(lcdif->base + LCDIF_VDCTRL1)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL2, + readl(lcdif->base + LCDIF_VDCTRL2)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL3, + readl(lcdif->base + LCDIF_VDCTRL3)); + pr_info("%#x : %#x\n", LCDIF_VDCTRL4, + readl(lcdif->base + LCDIF_VDCTRL4)); +} +EXPORT_SYMBOL(lcdif_dump_registers); + +void lcdif_vblank_irq_enable(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); + writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_SET); +} +EXPORT_SYMBOL(lcdif_vblank_irq_enable); + +void lcdif_vblank_irq_disable(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_CLR); + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); +} +EXPORT_SYMBOL(lcdif_vblank_irq_disable); + +void lcdif_vblank_irq_clear(struct lcdif_soc *lcdif) +{ + writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR); +} +EXPORT_SYMBOL(lcdif_vblank_irq_clear); + +static uint32_t lcdif_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 lcdif_get_bus_fmt_from_pix_fmt(struct lcdif_soc *lcdif, + uint32_t format) +{ + uint32_t bpp; + + bpp = lcdif_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(lcdif_get_bus_fmt_from_pix_fmt); + +int lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format) +{ + u32 ctrl = 0, ctrl1 = 0; + + /* TODO: lcdif should be disabled to set pixel format */ + + ctrl = readl(lcdif->base + LCDIF_CTRL); + ctrl1 = readl(lcdif->base + LCDIF_CTRL1); + + /* clear pixel format related bits */ + ctrl &= ~(CTRL_SHIFT_NUM(0x3f) | CTRL_INPUT_SWIZZLE(0x3) | + CTRL_CSC_SWIZZLE(0x3) | CTRL_SET_WORD_LENGTH(0x3)); + + ctrl1 &= ~CTRL1_SET_BYTE_PACKAGING(0xf); + + /* default is 'RGB' order */ + writel(CTRL2_ODD_LINE_PATTERN(0x7) | + CTRL2_EVEN_LINE_PATTERN(0x7), + lcdif->base + LCDIF_CTRL2 + REG_CLR); + + switch (format) { + /* bpp 16 */ + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + /* Data format */ + ctrl = (format == DRM_FORMAT_RGB565 || + format == DRM_FORMAT_BGR565) ? + (ctrl & ~CTRL_DF16) : (ctrl | CTRL_DF16); + + ctrl |= CTRL_SET_WORD_LENGTH(0x0); + + /* Byte packing */ + ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf); + + /* 'BGR' order */ + if (format == DRM_FORMAT_BGR565 || + format == DRM_FORMAT_ABGR1555 || + format == DRM_FORMAT_XBGR1555) + writel(CTRL2_ODD_LINE_PATTERN(0x5) | + CTRL2_EVEN_LINE_PATTERN(0x5), + lcdif->base + LCDIF_CTRL2 + REG_SET); + break; + /* bpp 32 */ + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + /*Data format */ + ctrl &= ~CTRL_DF24; + ctrl |= CTRL_SET_WORD_LENGTH(3); + + if (format == DRM_FORMAT_RGBA8888 || + format == DRM_FORMAT_RGBX8888) + ctrl |= CTRL_SHIFT_DIR(1) | CTRL_SHIFT_NUM(8); + + /* Byte packing */ + ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7); + + /* 'BGR' order */ + if (format == DRM_FORMAT_ABGR8888 || + format == DRM_FORMAT_XBGR8888) + writel(CTRL2_ODD_LINE_PATTERN(0x5) | + CTRL2_EVEN_LINE_PATTERN(0x5), + lcdif->base + LCDIF_CTRL2 + REG_SET); + break; + default: + dev_err(lcdif->dev, "unsupported pixel format: %s\n", + drm_get_format_name(format)); + return -EINVAL; + } + + writel(ctrl, lcdif->base + LCDIF_CTRL); + writel(ctrl1, lcdif->base + LCDIF_CTRL1); + + return 0; +} +EXPORT_SYMBOL(lcdif_set_pix_fmt); + +void lcdif_set_bus_fmt(struct lcdif_soc *lcdif, u32 bus_format) +{ + u32 bus_width; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT); + break; + case MEDIA_BUS_FMT_RGB666_1X18: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT); + break; + default: + dev_err(lcdif->dev, "unknown bus format: %#x\n", bus_format); + return; + } + + writel(CTRL_SET_BUS_WIDTH(0x3), lcdif->base + LCDIF_CTRL + REG_CLR); + writel(bus_width, lcdif->base + LCDIF_CTRL + REG_SET); +} +EXPORT_SYMBOL(lcdif_set_bus_fmt); + +void lcdif_set_fb_addr(struct lcdif_soc *lcdif, int id, u32 addr) +{ + switch (id) { + case 0: + /* primary plane */ + writel(addr, lcdif->base + LCDIF_NEXT_BUF); + break; + default: + /* TODO: add overlay support */ + return; + } +} +EXPORT_SYMBOL(lcdif_set_fb_addr); + +void lcdif_set_fb_hcrop(struct lcdif_soc *lcdif, u32 src_w, + u32 fb_w, bool crop) +{ + u32 mask_cnt, htotal, hcount; + u32 vdctrl2, vdctrl3, vdctrl4, transfer_count; + u32 pigeon_12_0, pigeon_12_1, pigeon_12_2; + + if (!crop) { + writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_0); + writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_1); + + return; + } + + /* transfer_count's hcount, vdctrl2's htotal and vdctrl4's + * H_VALID_DATA_CNT should use fb width instead of hactive + * when requires cropping. + * */ + transfer_count = readl(lcdif->base + LCDIF_TRANSFER_COUNT); + hcount = TRANSFER_COUNT_GET_HCOUNT(transfer_count); + + transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff); + transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w); + writel(transfer_count, lcdif->base + LCDIF_TRANSFER_COUNT); + + vdctrl2 = readl(lcdif->base + LCDIF_VDCTRL2); + htotal = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2); + htotal += fb_w - hcount; + vdctrl2 &= ~VDCTRL2_SET_HSYNC_PERIOD(0x3ffff); + vdctrl2 |= VDCTRL2_SET_HSYNC_PERIOD(htotal); + writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2); + + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff); + vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w); + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); + + /* configure related pigeon registers */ + vdctrl3 = readl(lcdif->base + LCDIF_VDCTRL3); + mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5; + + pigeon_12_0 = PIGEON_12_0_SET_STATE_MASK(0x24) | + PIGEON_12_0_SET_MASK_CNT(mask_cnt) | + PIGEON_12_0_SET_MASK_CNT_SEL(0x6) | + PIGEON_12_0_POL_ACTIVE_LOW | + PIGEON_12_0_EN; + writel(pigeon_12_0, lcdif->base + HW_EPDC_PIGEON_12_0); + + pigeon_12_1 = PIGEON_12_1_SET_CLR_CNT(src_w) | + PIGEON_12_1_SET_SET_CNT(0x0); + writel(pigeon_12_1, lcdif->base + HW_EPDC_PIGEON_12_1); + + pigeon_12_2 = 0x0; + writel(pigeon_12_2, lcdif->base + HW_EPDC_PIGEON_12_2); +} +EXPORT_SYMBOL(lcdif_set_fb_hcrop); + + +void lcdif_set_mode(struct lcdif_soc *lcdif, struct videomode *vmode) +{ + const struct of_device_id *of_id = + of_match_device(imx_lcdif_dt_ids, lcdif->dev); + const struct lcdif_soc_pdata *soc_pdata = of_id->data; + u32 vdctrl0, vdctrl1, vdctrl2, vdctrl3, vdctrl4, htotal; + + /* Clear the FIFO */ + writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_SET); + writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_CLR); + + /* set pixel clock rate */ + clk_disable_unprepare(lcdif->clk_pix); + clk_set_rate(lcdif->clk_pix, vmode->pixelclock); + clk_prepare_enable(lcdif->clk_pix); + + /* config display timings */ + writel(TRANSFER_COUNT_SET_VCOUNT(vmode->vactive) | + TRANSFER_COUNT_SET_HCOUNT(vmode->hactive), + lcdif->base + LCDIF_TRANSFER_COUNT); + + vdctrl0 = VDCTRL0_ENABLE_PRESENT | + VDCTRL0_VSYNC_PERIOD_UNIT | + VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | + VDCTRL0_SET_VSYNC_PULSE_WIDTH(vmode->vsync_len); + + /* Polarities */ + if (soc_pdata) { + if ((soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) || + (!soc_pdata->hsync_invert && + vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH)) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + + if ((soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) || + (!soc_pdata->vsync_invert && + vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH)) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + + if ((soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_LOW) || + (!soc_pdata->de_invert && + vmode->flags & DISPLAY_FLAGS_DE_HIGH)) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + } else { + if (vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH) + vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH; + if (vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH) + vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH; + if (vmode->flags & DISPLAY_FLAGS_DE_HIGH) + vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH; + } + + if (vmode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) + vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING; + + writel(vdctrl0, lcdif->base + LCDIF_VDCTRL0); + + vdctrl1 = vmode->vactive + vmode->vsync_len + + vmode->vfront_porch + vmode->vback_porch; + writel(vdctrl1, lcdif->base + LCDIF_VDCTRL1); + + htotal = vmode->hactive + vmode->hsync_len + + vmode->hfront_porch + vmode->hback_porch; + vdctrl2 = VDCTRL2_SET_HSYNC_PULSE_WIDTH(vmode->hsync_len) | + VDCTRL2_SET_HSYNC_PERIOD(htotal); + writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2); + + vdctrl3 = SET_HOR_WAIT_CNT(vmode->hsync_len + vmode->hback_porch) | + SET_VERT_WAIT_CNT(vmode->vsync_len + vmode->vback_porch); + writel(vdctrl3, lcdif->base + LCDIF_VDCTRL3); + + vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(vmode->hactive); + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); +} +EXPORT_SYMBOL(lcdif_set_mode); + +void lcdif_enable_controller(struct lcdif_soc *lcdif) +{ + u32 ctrl2, vdctrl4; + + ctrl2 = readl(lcdif->base + LCDIF_CTRL2); + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + + ctrl2 &= ~CTRL2_OUTSTANDING_REQS(0x7); + ctrl2 |= CTRL2_OUTSTANDING_REQS(REQ_16); + writel(ctrl2, lcdif->base + LCDIF_CTRL2); + + /* Continous dotclock mode */ + writel(CTRL_BYPASS_COUNT | CTRL_DOTCLK_MODE, + lcdif->base + LCDIF_CTRL + REG_SET); + + /* enable the SYNC signals first, then the DMA engine */ + vdctrl4 |= VDCTRL4_SYNC_SIGNALS_ON; + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); + + /* enable underflow recovery */ + writel(CTRL1_RECOVERY_ON_UNDERFLOW, + lcdif->base + LCDIF_CTRL1 + REG_SET); + + /* run lcdif */ + writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_SET); + writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_SET); +} +EXPORT_SYMBOL(lcdif_enable_controller); + +void lcdif_disable_controller(struct lcdif_soc *lcdif) +{ + int ret; + u32 ctrl, vdctrl4; + + writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_CLR); + writel(CTRL_DOTCLK_MODE, lcdif->base + LCDIF_CTRL + REG_CLR); + + ret = readl_poll_timeout(lcdif->base + LCDIF_CTRL, ctrl, + !(ctrl & CTRL_RUN), 0, 1000); + if (WARN_ON(ret)) + dev_err(lcdif->dev, "disable lcdif run timeout\n"); + + writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_CLR); + + vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4); + vdctrl4 &= ~VDCTRL4_SYNC_SIGNALS_ON; + writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4); +} +EXPORT_SYMBOL(lcdif_disable_controller); + +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 int lcdif_add_client_devices(struct lcdif_soc *lcdif) +{ + int ret = 0, i; + struct device *dev = lcdif->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); + + pdev = platform_device_alloc(client_reg[i].name, i); + 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; + + 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_lcdif_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct lcdif_soc *lcdif; + struct resource *res; + + dev_dbg(dev, "%s: probe begin\n", __func__); + + lcdif = devm_kzalloc(dev, sizeof(*lcdif), GFP_KERNEL); + if (!lcdif) { + dev_err(dev, "Can't allocate 'lcdif_soc' structure\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + lcdif->irq = platform_get_irq(pdev, 0); + if (lcdif->irq < 0) + return -ENODEV; + + lcdif->clk_pix = devm_clk_get(dev, "pix"); + if (IS_ERR(lcdif->clk_pix)) + return PTR_ERR(lcdif->clk_pix); + + lcdif->clk_disp_axi = devm_clk_get(dev, "disp-axi"); + if (IS_ERR(lcdif->clk_disp_axi)) + lcdif->clk_disp_axi = NULL; + + lcdif->clk_disp_apb = devm_clk_get(dev, "disp-apb"); + if (IS_ERR(lcdif->clk_disp_apb)) + lcdif->clk_disp_apb = NULL; + + lcdif->base = devm_ioremap_resource(dev, res); + if (IS_ERR(lcdif->base)) + return PTR_ERR(lcdif->base); + + lcdif->gpr = syscon_regmap_lookup_by_phandle(np, "lcdif-gpr"); + if (IS_ERR(lcdif->gpr)) + return PTR_ERR(lcdif->gpr); + + lcdif->dev = dev; + platform_set_drvdata(pdev, lcdif); + + atomic_set(&lcdif->rpm_suspended, 0); + pm_runtime_enable(dev); + atomic_inc(&lcdif->rpm_suspended); + + dev_dbg(dev, "%s: probe end\n", __func__); + + return lcdif_add_client_devices(lcdif); +} + +static int imx_lcdif_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx_lcdif_suspend(struct device *dev) +{ + return imx_lcdif_runtime_suspend(dev); +} + +static int imx_lcdif_resume(struct device *dev) +{ + return imx_lcdif_runtime_resume(dev); +} +#else +static int imx_lcdif_suspend(struct device *dev) +{ + return 0; +} +static int imx_lcdif_resume(struct device *dev) +{ + return 0; +} +#endif + +#ifdef CONFIG_PM +static int imx_lcdif_runtime_suspend(struct device *dev) +{ + struct lcdif_soc *lcdif = dev_get_drvdata(dev); + + if (atomic_inc_return(&lcdif->rpm_suspended) > 1) + return 0; + + lcdif_disable_clocks(lcdif); + + release_bus_freq(BUS_FREQ_HIGH); + + return 0; +} + +static int imx_lcdif_runtime_resume(struct device *dev) +{ + int ret = 0; + struct lcdif_soc *lcdif = dev_get_drvdata(dev); + + if (unlikely(!atomic_read(&lcdif->rpm_suspended))) { + dev_warn(lcdif->dev, "Unbalanced %s!\n", __func__); + return 0; + } + + if (!atomic_dec_and_test(&lcdif->rpm_suspended)) + return 0; + + request_bus_freq(BUS_FREQ_HIGH); + + ret = lcdif_enable_clocks(lcdif); + if (ret) { + release_bus_freq(BUS_FREQ_HIGH); + return ret; + } + + disp_mix_bus_rstn_reset(lcdif->gpr, false); + disp_mix_lcdif_clks_enable(lcdif->gpr, true); + + /* Pull LCDIF out of reset */ + writel(0x0, lcdif->base + LCDIF_CTRL); + + return ret; +} +#endif + +static const struct dev_pm_ops imx_lcdif_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx_lcdif_suspend, imx_lcdif_resume) + SET_RUNTIME_PM_OPS(imx_lcdif_runtime_suspend, + imx_lcdif_runtime_resume, NULL) +}; + +struct platform_driver imx_lcdif_driver = { + .probe = imx_lcdif_probe, + .remove = imx_lcdif_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_lcdif_dt_ids, + .pm = &imx_lcdif_pm_ops, + }, +}; + +module_platform_driver(imx_lcdif_driver); + +MODULE_DESCRIPTION("NXP i.MX LCDIF Display Controller driver"); +MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>"); +MODULE_LICENSE("GPL"); |