blob: 86b106956e654856235c9f03b1d3f9d041a5891a [file] [log] [blame]
/*
* 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)
{
struct drm_format_name_buf format_name;
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, &format_name));
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");