Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- drivers/gpu/drm/imx/Kconfig | 6 + drivers/gpu/drm/imx/Makefile | 1 + drivers/gpu/drm/imx/imx-lcdc-crtc.c | 523 +++++++++++++++++++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 drivers/gpu/drm/imx/imx-lcdc-crtc.c diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig index fdd0f5d..b554ecf 100644 --- a/drivers/gpu/drm/imx/Kconfig +++ b/drivers/gpu/drm/imx/Kconfig @@ -15,6 +15,12 @@ config DRM_IMX_FB_HELPER for your device. This is necessary to get a framebuffer console and also for appplications using the legacy framebuffer API +config DRM_IMX_LCDC + tristate "DRM Support for Freescale i.MX1 and i.MX2" + depends on DRM_IMX + help + Choose this if you have a i.MX1, i.MX21, i.MX25 or i.MX27 processor. + config DRM_IMX_PARALLEL_DISPLAY tristate "Support for parallel displays" depends on DRM_IMX diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile index f5a793f..6c7dd2d 100644 --- a/drivers/gpu/drm/imx/Makefile +++ b/drivers/gpu/drm/imx/Makefile @@ -4,4 +4,5 @@ imxdrm-objs := imx-drm-core.o imx-fb.o obj-$(CONFIG_DRM_IMX) += imxdrm.o obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += imx-parallel-display.o +obj-$(CONFIG_DRM_IMX_LCDC) += imx-lcdc-crtc.o obj-$(CONFIG_DRM_IMX_FB_HELPER) += imx-fbdev.o diff --git a/drivers/gpu/drm/imx/imx-lcdc-crtc.c b/drivers/gpu/drm/imx/imx-lcdc-crtc.c new file mode 100644 index 0000000..5c72c47 --- /dev/null +++ b/drivers/gpu/drm/imx/imx-lcdc-crtc.c @@ -0,0 +1,523 @@ +/* + * i.MX LCDC crtc driver + * + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * + * 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/device.h> +#include <linux/platform_device.h> +#include <drm/drmP.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <mach/hardware.h> +#include <mach/imxfb.h> +#include <generated/mach-types.h> +#include <drm/drm_gem_cma_helper.h> + +#include "imx-drm.h" + +#define LCDC_SSA 0x00 +#define LCDC_SIZE 0x04 +#define LCDC_VPW 0x08 +#define LCDC_CPOS 0x0C +#define LCDC_LCWHB 0x10 +#define LCDC_LCHCC 0x14 +#define LCDC_PCR 0x18 +#define LCDC_HCR 0x1C +#define LCDC_VCR 0x20 +#define LCDC_POS 0x24 +#define LCDC_LSCR1 0x28 +#define LCDC_PWMR 0x2C +#define LCDC_DMACR 0x30 +#define LCDC_RMCR 0x34 +#define LCDC_LCDICR 0x38 +#define LCDC_LIER 0x3c +#define LCDC_LISR 0x40 + +#define SIZE_XMAX(x) ((((x) >> 4) & 0x3f) << 20) + +#define YMAX_MASK (cpu_is_mx1() ? 0x1ff : 0x3ff) +#define SIZE_YMAX(y) ((y) & YMAX_MASK) + +#define VPW_VPW(x) ((x) & 0x3ff) + +#define HCR_H_WIDTH(x) (((x) & 0x3f) << 26) +#define HCR_H_WAIT_1(x) (((x) & 0xff) << 8) +#define HCR_H_WAIT_2(x) ((x) & 0xff) + +#define VCR_V_WIDTH(x) (((x) & 0x3f) << 26) +#define VCR_V_WAIT_1(x) (((x) & 0xff) << 8) +#define VCR_V_WAIT_2(x) ((x) & 0xff) + +#define RMCR_LCDC_EN_MX1 (1 << 1) + +#define RMCR_SELF_REF (1 << 0) + +#define LIER_EOF (1 << 1) + +struct imx_crtc { + struct drm_crtc base; + struct imx_drm_crtc *imx_drm_crtc; + int di_no; + int enabled; + void __iomem *regs; + u32 pwmr; + u32 lscr1; + u32 dmacr; + u32 pcr; + struct clk *clk; + struct device *dev; + int vblank_enable; + + struct drm_pending_vblank_event *page_flip_event; + struct drm_framebuffer *newfb; +}; + +#define to_imx_crtc(x) container_of(x, struct imx_crtc, base) + +static void imx_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +#define PCR_BPIX_8 (3 << 25) +#define PCR_BPIX_12 (4 << 25) +#define PCR_BPIX_16 (5 << 25) +#define PCR_BPIX_18 (6 << 25) +#define PCR_END_SEL (1 << 18) +#define PCR_END_BYTE_SWAP (1 << 17) + +static const char *fourcc_to_str(u32 fourcc) +{ + static char buf[5]; + + *(u32 *)buf = fourcc; + buf[4] = 0; + + return buf; +} + +static int imx_drm_crtc_set(struct drm_crtc *crtc, + struct drm_display_mode *mode) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + struct drm_framebuffer *fb = crtc->fb; + int lower_margin = mode->vsync_start - mode->vdisplay; + int upper_margin = mode->vtotal - mode->vsync_end; + int vsync_len = mode->vsync_end - mode->vsync_start; + int hsync_len = mode->hsync_end - mode->hsync_start; + int right_margin = mode->hsync_start - mode->hdisplay; + int left_margin = mode->htotal - mode->hsync_end; + unsigned long lcd_clk; + u32 pcr; + + lcd_clk = clk_get_rate(imx_crtc->clk) / 1000; + + if (!mode->clock) + return -EINVAL; + + pcr = DIV_ROUND_CLOSEST(lcd_clk, mode->clock); + if (--pcr > 0x3f) + pcr = 0x3f; + + switch (fb->pixel_format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + pcr |= PCR_BPIX_18; + pcr |= PCR_END_SEL | PCR_END_BYTE_SWAP; + break; + case DRM_FORMAT_RGB565: + if (cpu_is_mx1()) + pcr |= PCR_BPIX_12; + else + pcr |= PCR_BPIX_16; + break; + case DRM_FORMAT_RGB332: + pcr |= PCR_BPIX_8; + break; + default: + dev_err(imx_crtc->dev, "unsupported pixel format %s\n", + fourcc_to_str(fb->pixel_format)); + return -EINVAL; + } + + /* add sync polarities */ + pcr |= imx_crtc->pcr & ~(0x3f | (7 << 25)); + + dev_dbg(imx_crtc->dev, + "xres=%d hsync_len=%d left_margin=%d right_margin=%d\n", + mode->hdisplay, hsync_len, + left_margin, right_margin); + dev_dbg(imx_crtc->dev, + "yres=%d vsync_len=%d upper_margin=%d lower_margin=%d\n", + mode->vdisplay, vsync_len, + upper_margin, lower_margin); + + writel(VPW_VPW(mode->hdisplay * fb->bits_per_pixel / 8 / 4), + imx_crtc->regs + LCDC_VPW); + + writel(HCR_H_WIDTH(hsync_len - 1) | + HCR_H_WAIT_1(right_margin - 1) | + HCR_H_WAIT_2(left_margin - 3), + imx_crtc->regs + LCDC_HCR); + + writel(VCR_V_WIDTH(vsync_len) | + VCR_V_WAIT_1(lower_margin) | + VCR_V_WAIT_2(upper_margin), + imx_crtc->regs + LCDC_VCR); + + writel(SIZE_XMAX(mode->hdisplay) | SIZE_YMAX(mode->vdisplay), + imx_crtc->regs + LCDC_SIZE); + + writel(pcr, imx_crtc->regs + LCDC_PCR); + writel(imx_crtc->pwmr, imx_crtc->regs + LCDC_PWMR); + writel(imx_crtc->lscr1, imx_crtc->regs + LCDC_LSCR1); + /* reset default */ + writel(0x00040060, imx_crtc->regs + LCDC_DMACR); + + return 0; +} + +static int imx_drm_set_base(struct drm_crtc *crtc, int x, int y) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + struct drm_gem_cma_object *cma_obj; + struct drm_framebuffer *fb = crtc->fb; + unsigned long phys; + + cma_obj = drm_fb_cma_get_gem_obj(fb, 0); + if (!cma_obj) + return -EFAULT; + + phys = cma_obj->paddr; + phys += x * (fb->bits_per_pixel >> 3); + phys += y * fb->pitches[0]; + + dev_dbg(imx_crtc->dev, "%s: phys: 0x%lx\n", __func__, phys); + dev_dbg(imx_crtc->dev, "%s: xy: %dx%d\n", __func__, x, y); + + writel(phys, imx_crtc->regs + LCDC_SSA); + + return 0; +} + +static int imx_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + imx_drm_set_base(crtc, x, y); + + dev_dbg(imx_crtc->dev, "mode->hdisplay: %d\n", mode->hdisplay); + dev_dbg(imx_crtc->dev, "mode->vdisplay: %d\n", mode->vdisplay); + + return imx_drm_crtc_set(crtc, mode); +} + +static void imx_crtc_enable(struct imx_crtc *imx_crtc) +{ + if (!imx_crtc->enabled) + clk_prepare_enable(imx_crtc->clk); + imx_crtc->enabled = 1; +} + +static void imx_crtc_disable(struct imx_crtc *imx_crtc) +{ + if (imx_crtc->enabled) + clk_disable_unprepare(imx_crtc->clk); + imx_crtc->enabled = 0; +} + +static void imx_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + dev_dbg(imx_crtc->dev, "%s mode: %d\n", __func__, mode); + + switch (mode) { + case DRM_MODE_DPMS_ON: + imx_crtc_enable(imx_crtc); + break; + default: + imx_crtc_disable(imx_crtc); + break; + } +} + +static bool imx_crtc_mode_fixup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void imx_crtc_prepare(struct drm_crtc *crtc) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + imx_crtc_disable(imx_crtc); +} + +static void imx_crtc_commit(struct drm_crtc *crtc) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + imx_crtc_enable(imx_crtc); +} + +static struct drm_crtc_helper_funcs imx_helper_funcs = { + .dpms = imx_crtc_dpms, + .mode_fixup = imx_crtc_mode_fixup, + .mode_set = imx_crtc_mode_set, + .prepare = imx_crtc_prepare, + .commit = imx_crtc_commit, + .load_lut = imx_crtc_load_lut, +}; + +static void imx_drm_handle_pageflip(struct imx_crtc *imx_crtc) +{ + struct drm_pending_vblank_event *e; + struct timeval now; + unsigned long flags; + struct drm_device *drm = imx_crtc->base.dev; + + spin_lock_irqsave(&drm->event_lock, flags); + + e = imx_crtc->page_flip_event; + + if (!e) { + spin_unlock_irqrestore(&drm->event_lock, flags); + return; + } + + do_gettimeofday(&now); + e->event.sequence = 0; + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + imx_crtc->page_flip_event = NULL; + + list_add_tail(&e->base.link, &e->base.file_priv->event_list); + + wake_up_interruptible(&e->base.file_priv->event_wait); + + spin_unlock_irqrestore(&drm->event_lock, flags); +} + +static int imx_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + writel(LIER_EOF, imx_crtc->regs + LCDC_LIER); + + return 0; +} + +static void imx_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + writel(0, imx_crtc->regs + LCDC_LIER); +} + +static irqreturn_t imx_irq_handler(int irq, void *dev_id) +{ + struct imx_crtc *imx_crtc = dev_id; + struct drm_device *drm = imx_crtc->base.dev; + + /* Acknowledge interrupt */ + readl(imx_crtc->regs + LCDC_LISR); + + drm_handle_vblank(drm, 0); + + if (imx_crtc->newfb) { + imx_crtc->base.fb = imx_crtc->newfb; + imx_crtc->newfb = NULL; + imx_drm_set_base(&imx_crtc->base, 0, 0); + imx_drm_handle_pageflip(imx_crtc); + imx_drm_crtc_vblank_put(imx_crtc->imx_drm_crtc); + } + + return IRQ_HANDLED; +} + +static int imx_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct imx_crtc *imx_crtc = to_imx_crtc(crtc); + + if (imx_crtc->newfb) + return -EBUSY; + + imx_crtc->newfb = fb; + imx_crtc->page_flip_event = event; + imx_drm_crtc_vblank_get(imx_crtc->imx_drm_crtc); + + return 0; +} + +static const struct drm_crtc_funcs imx_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .destroy = drm_crtc_cleanup, + .page_flip = imx_page_flip, +}; + +static const struct imx_drm_crtc_helper_funcs imx_imx_drm_helper = { + .enable_vblank = imx_crtc_enable_vblank, + .disable_vblank = imx_crtc_disable_vblank, + .crtc_funcs = &imx_crtc_funcs, + .crtc_helper_funcs = &imx_helper_funcs, +}; + +#define DRIVER_NAME "imx-lcdc-crtc" + +/* + * the pcr bits to be allowed to set in platform data + */ +#define PDATA_PCR (PCR_PIXPOL | PCR_FLMPOL | PCR_LPPOL | \ + PCR_CLKPOL | PCR_OEPOL | PCR_TFT | PCR_COLOR | \ + PCR_PBSIZ_8 | PCR_ACD(0x7f) | PCR_ACD_SEL | \ + PCR_SCLK_SEL | PCR_SHARP) + +static int __devinit imx_crtc_probe(struct platform_device *pdev) +{ + struct imx_crtc *imx_crtc; + struct resource *res; + int ret, irq; + u32 pcr_value = 0xf00080c0; + u32 lscr1_value = 0x00120300; + u32 pwmr_value = 0x00a903ff; + + imx_crtc = devm_kzalloc(&pdev->dev, sizeof(*imx_crtc), GFP_KERNEL); + if (!imx_crtc) + return -ENOMEM; + + imx_crtc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + res = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), DRIVER_NAME); + if (!res) + return -EBUSY; + + imx_crtc->regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!imx_crtc->regs) { + dev_err(&pdev->dev, "Cannot map frame buffer registers\n"); + return -EBUSY; + } + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(&pdev->dev, irq, imx_irq_handler, 0, "imx_drm", + imx_crtc); + if (ret < 0) { + dev_err(&pdev->dev, "irq request failed with %d\n", ret); + return ret; + } + + imx_crtc->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(imx_crtc->clk)) { + ret = PTR_ERR(imx_crtc->clk); + dev_err(&pdev->dev, "unable to get clock: %d\n", ret); + return ret; + } + + clk_prepare_enable(imx_crtc->clk); + imx_crtc->enabled = 1; + + platform_set_drvdata(pdev, imx_crtc); + + imx_crtc->pcr = pcr_value & PDATA_PCR; + + if (imx_crtc->pcr != pcr_value) + dev_err(&pdev->dev, "invalid bits set in pcr: 0x%08x\n", + pcr_value & ~PDATA_PCR); + + imx_crtc->lscr1 = lscr1_value; + imx_crtc->pwmr = pwmr_value; + + ret = imx_drm_add_crtc(&imx_crtc->base, + &imx_crtc->imx_drm_crtc, + &imx_imx_drm_helper, THIS_MODULE, + pdev->dev.of_node, 0); + if (ret) + goto err_init; + + dev_info(&pdev->dev, "probed\n"); + + return 0; + +err_init: + clk_disable_unprepare(imx_crtc->clk); + clk_put(imx_crtc->clk); + + return ret; +} + +static int __devexit imx_crtc_remove(struct platform_device *pdev) +{ + struct imx_crtc *imx_crtc = platform_get_drvdata(pdev); + + imx_drm_remove_crtc(imx_crtc->imx_drm_crtc); + + writel(0, imx_crtc->regs + LCDC_LIER); + + clk_disable_unprepare(imx_crtc->clk); + clk_put(imx_crtc->clk); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id imx_lcdc_dt_ids[] = { + { .compatible = "fsl,imx1-lcdc", .data = NULL, }, + { .compatible = "fsl,imx21-lcdc", .data = NULL, }, + { /* sentinel */ } +}; + +static struct platform_driver imx_crtc_driver = { + .remove = __devexit_p(imx_crtc_remove), + .probe = imx_crtc_probe, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = imx_lcdc_dt_ids, + }, +}; + +static int __init imx_lcdc_init(void) +{ + return platform_driver_register(&imx_crtc_driver); +} + +static void __exit imx_lcdc_exit(void) +{ + platform_driver_unregister(&imx_crtc_driver); +} + +module_init(imx_lcdc_init); +module_exit(imx_lcdc_exit) + +MODULE_DESCRIPTION("Freescale i.MX framebuffer driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); -- 1.7.10 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel