aboutsummaryrefslogtreecommitdiff
path: root/drivers/video/hisilicon/hi3620_fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/hisilicon/hi3620_fb.c')
-rw-r--r--drivers/video/hisilicon/hi3620_fb.c650
1 files changed, 650 insertions, 0 deletions
diff --git a/drivers/video/hisilicon/hi3620_fb.c b/drivers/video/hisilicon/hi3620_fb.c
new file mode 100644
index 000000000000..83cce2022743
--- /dev/null
+++ b/drivers/video/hisilicon/hi3620_fb.c
@@ -0,0 +1,650 @@
+/*
+ * Framebuffer driver of Hisilicon Hi3620 SoC
+ *
+ * Copyright (c) 2013 Linaro Ltd.
+ * Copyright (c) 2013 Hisilicon Ltd.
+ *
+ * Author: Haojian Zhuang <haojian.zhuang@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/fb.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <video/of_display_timing.h>
+#include <video/display_timing.h>
+#include "hi3620_fb.h"
+
+static unsigned int hi3620fb_pseudo_palette[16] = {
+ 0, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL,
+ ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL, ~0UL,
+};
+
+static int match_fmt_555(struct fb_var_screeninfo *var)
+{
+ if (var->blue.offset == 0 && var->green.offset <= 5 &&
+ var->red.offset <= 10 && var->transp.offset <= 15 &&
+ var->blue.offset <= 5 && var->green.length <= 5 &&
+ var->red.length <= 5)
+ return 1;
+ return 0;
+}
+
+static int match_fmt_565(struct fb_var_screeninfo *var)
+{
+ if (var->blue.offset == 0 && var->green.offset <= 5 &&
+ var->red.offset <= 11 && var->transp.offset == 0 &&
+ var->blue.length <= 5 && var->green.length <= 6 &&
+ var->red.length <= 5 && var->transp.length == 0)
+ return 1;
+ return 0;
+}
+
+static int match_fmt_888(struct fb_var_screeninfo *var)
+{
+ if (var->blue.offset == 0 && var->green.offset <= 8 &&
+ var->red.offset <= 16 && var->transp.offset <= 24 &&
+ var->blue.length <= 8 && var->green.length <= 8 &&
+ var->red.length <= 8)
+ return 1;
+ return 0;
+}
+
+static int find_best_pix_fmt(struct fb_var_screeninfo *var)
+{
+ if (var->bits_per_pixel == 16) {
+ /* RGB565/RGBA5551/RGBX5551 */
+ if (match_fmt_555(var)) {
+ if (var->transp.length == 1)
+ return IMG_PIXEL_FORMAT_ARGB1555;
+ else if (var->transp.length == 0)
+ return IMG_PIXEL_FORMAT_RGB555;
+ } else if (match_fmt_565(var))
+ return IMG_PIXEL_FORMAT_RGB565;
+ } else if (var->bits_per_pixel == 32) {
+ if (match_fmt_888(var)) {
+ if (var->transp.length == 8)
+ return IMG_PIXEL_FORMAT_ARGB8888;
+ else if (var->transp.length == 0)
+ return IMG_PIXEL_FORMAT_RGB888;
+ }
+ }
+ return -EINVAL;
+}
+
+static void set_pix_fmt(struct hi3620fb_info *info, int pix_fmt)
+{
+ struct fb_info *fb = info->fb;
+ struct fb_var_screeninfo *var = &fb->var;
+
+ switch (pix_fmt) {
+ case IMG_PIXEL_FORMAT_RGB565:
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ var->green.offset = 5;
+ var->green.length = 6;
+ var->red.offset = 11;
+ var->red.length = 5;
+ var->transp.offset = 0;
+ var->transp.length = 0;
+ var->bits_per_pixel = 16;
+ break;
+ case IMG_PIXEL_FORMAT_ARGB1555:
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ var->green.offset = 5;
+ var->green.length = 5;
+ var->red.offset = 10;
+ var->red.length = 5;
+ var->transp.offset = 15;
+ var->transp.length = 1;
+ var->bits_per_pixel = 16;
+ break;
+ case IMG_PIXEL_FORMAT_RGB555:
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ var->green.offset = 5;
+ var->green.length = 5;
+ var->red.offset = 10;
+ var->red.length = 5;
+ var->transp.offset = 15;
+ var->transp.length = 0;
+ var->bits_per_pixel = 16;
+ break;
+ case IMG_PIXEL_FORMAT_ARGB8888:
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->transp.offset = 24;
+ var->transp.length = 8;
+ var->bits_per_pixel = 32;
+ break;
+ case IMG_PIXEL_FORMAT_RGB888:
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->transp.offset = 24;
+ var->transp.length = 0;
+ var->bits_per_pixel = 32;
+ break;
+ default:
+ return;
+ }
+ info->pix_fmt = pix_fmt;
+}
+
+static int hi3620fb_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *fb)
+{
+ struct hi3620fb_info *info = fb->par;
+ int pix_fmt;
+
+ /*
+ * Determine which pixel format we're going to use.
+ */
+ pix_fmt = find_best_pix_fmt(var);
+ if (pix_fmt < 0)
+ return pix_fmt;
+ set_pix_fmt(info, pix_fmt);
+
+ /*
+ * Basic geometry sanity checks.
+ */
+ if (var->xoffset + var->xres > var->xres_virtual)
+ return -EINVAL;
+ if (var->yoffset + var->yres > var->yres_virtual)
+ return -EINVAL;
+ if (var->xres + var->right_margin +
+ var->hsync_len + var->left_margin > 2048)
+ return -EINVAL;
+ if (var->yres + var->lower_margin +
+ var->vsync_len + var->upper_margin > 2048)
+ return -EINVAL;
+
+ /*
+ * Check size of framebuffer.
+ */
+ if (var->xres_virtual * var->yres_virtual *
+ (var->bits_per_pixel >> 3) > fb->fix.smem_len)
+ return -EINVAL;
+ return 0;
+}
+
+/* It's used to make EDC configuration work. */
+static void update_edc(void __iomem *base)
+{
+ unsigned int data;
+
+ data = readl_relaxed(base + EDC_DISP_CTL);
+ writel_relaxed(data | EDC_CFG_OK, base + EDC_DISP_CTL);
+ data &= ~EDC_CFG_OK;
+ writel_relaxed(data, base + EDC_DISP_CTL);
+}
+
+static void set_panel_control(struct fb_info *fb)
+{
+ struct fb_videomode *fb_vm = fb->mode;
+ struct hi3620fb_info *info = fb->par;
+ void __iomem *base = info->reg_base;
+ u32 ldi, dpi;
+
+ ldi = readl_relaxed(base + LDI_PLR_CTRL) & ~LDI_POLARITY_MASK;
+ dpi = readl_relaxed(base + DSI_DPI_CFG) & ~DSI_DPI_POLARITY_MASK;
+ if (fb_vm->sync & FB_SYNC_HOR_HIGH_ACT) {
+ ldi &= ~LDI_HSYNC_POLARITY;
+ dpi &= ~DSI_DPI_HSYNC_POLARITY;
+ } else {
+ ldi |= LDI_HSYNC_POLARITY;
+ dpi |= DSI_DPI_HSYNC_POLARITY;
+ }
+ if (fb_vm->sync & FB_SYNC_VERT_HIGH_ACT) {
+ ldi &= ~LDI_VSYNC_POLARITY;
+ dpi &= ~DSI_DPI_VSYNC_POLARITY;
+ } else {
+ ldi |= LDI_VSYNC_POLARITY;
+ dpi |= DSI_DPI_VSYNC_POLARITY;
+ }
+ if (fb_vm->flag & FB_FLAG_DE_HIGH) {
+ ldi &= ~LDI_DATAEN_POLARITY;
+ dpi &= ~DSI_DPI_DATAEN_POLARITY;
+ }
+ if (fb_vm->flag & FB_FLAG_DE_LOW) {
+ ldi |= LDI_DATAEN_POLARITY;
+ dpi |= DSI_DPI_DATAEN_POLARITY;
+ }
+ if (fb_vm->flag & FB_FLAG_PIXDATA_POSEDGE)
+ ldi |= LDI_PIXELCLK_POLARITY;
+ if (fb_vm->flag & FB_FLAG_PIXDATA_NEGEDGE)
+ ldi &= ~LDI_PIXELCLK_POLARITY;
+ writel_relaxed(ldi, base + LDI_PLR_CTRL);
+ /* always set color mode & shutdown high active */
+ writel_relaxed(dpi, info->reg_base + DSI_DPI_CFG);
+}
+
+static void set_screen_dimensions(struct fb_info *fb)
+{
+ struct fb_var_screeninfo *var = &fb->var;
+ struct fb_videomode *fb_vm = fb->mode;
+ struct hi3620fb_info *info = fb->par;
+ void __iomem *base = info->reg_base;
+ unsigned long long int tmp;
+ u32 data, lane_rate, timing;
+
+ data = (var->left_margin & 0xfff) << 20;
+ data |= var->right_margin & 0xfff;
+ writel_relaxed(data, base + LDI_HRZ_CTRL0);
+ data = (var->hsync_len - 1) & 0xfff;
+ writel_relaxed(data, base + LDI_HRZ_CTRL1);
+ data = (var->upper_margin & 0xfff) << 20;
+ data |= var->lower_margin & 0xfff;
+ writel_relaxed(data, base + LDI_VRT_CTRL0);
+ data = (var->vsync_len - 1) & 0xfff;
+ writel_relaxed(data, base + LDI_VRT_CTRL1);
+
+ data = (var->xres - 1) & 0xfff;
+ data |= ((var->yres - 1) & 0xfff) << 20;
+ writel_relaxed(data, base + LDI_DSP_SIZE);
+
+ data = (fb->var.xres_virtual - 1) << 16 | (fb->var.yres_virtual - 1);
+ writel_relaxed(data, base + EDC_VIDEO_CHAN_SIZE);
+
+ /* setup line timing */
+ lane_rate = clk_get_rate(info->clk_lane);
+ data = fb_vm->hsync_len * lane_rate / fb_vm->pixclock;
+ timing = data & 0x1ff;
+ data = fb_vm->left_margin * lane_rate / fb_vm->pixclock;
+ timing |= (data & 0x1ff) << 9;
+ tmp = (unsigned long long int)(fb_vm->left_margin + fb_vm->xres +
+ fb_vm->right_margin + fb_vm->hsync_len);
+ tmp *= lane_rate;
+ do_div(tmp, fb_vm->pixclock);
+ data = (unsigned int)tmp;
+ timing |= data << 18;
+ writel_relaxed(timing, info->reg_base + DSI_TMR_LINE_CFG);
+
+ /* setup frame timing */
+ timing = fb_vm->vsync_len & 0xf;
+ timing |= (fb_vm->upper_margin & 0x3f) << 4;
+ timing |= (fb_vm->lower_margin & 0x3f) << 10;
+ timing |= (fb_vm->yres & 0x7ff) << 16;
+ writel_relaxed(timing, info->reg_base + DSI_VTIMING_CFG);
+}
+
+static void set_graphics_start(struct fb_info *fb, int xoffset, int yoffset)
+{
+ struct hi3620fb_info *info = fb->par;
+ void __iomem *base = info->reg_base;
+ u32 data;
+
+ data = yoffset & 0xfff;
+ data |= (xoffset & 0xfff) << 16;
+ writel_relaxed(data, base + EDC_VIDEO_CHAN_XY);
+ /* setup dma address */
+ writel_relaxed(fb->fix.smem_start, base + EDC_VIDEO_CHAN_ADDR);
+}
+
+static void set_dma_control(struct fb_info *fb)
+{
+ struct hi3620fb_info *info = fb->par;
+ void __iomem *base = info->reg_base;
+
+ /* setup dma stride */
+ writel_relaxed(fb->fix.line_length, base + EDC_VIDEO_CHAN_STRIDE);
+}
+
+static int hi3620fb_set_par(struct fb_info *fb)
+{
+ struct fb_var_screeninfo *var = &fb->var;
+ struct hi3620fb_info *info = fb->par;
+ void __iomem *base = info->reg_base;
+ unsigned int ctrl = 0;
+
+ fb->fix.ypanstep = var->yres;
+
+ ctrl = readl_relaxed(base + EDC_VIDEO_CHAN_CTRL);
+ if (var->blue.offset)
+ ctrl |= EDC_CHAN_CTRL_BGR; /* BGR format */
+ ctrl &= ~(7 << 16);
+ switch (info->pix_fmt) {
+ case IMG_PIXEL_FORMAT_ARGB1555:
+ case IMG_PIXEL_FORMAT_RGB555:
+ break;
+ case IMG_PIXEL_FORMAT_RGB565:
+ ctrl |= 1 << 16;
+ break;
+ case IMG_PIXEL_FORMAT_RGB888:
+ ctrl |= 2 << 16;
+ break;
+ case IMG_PIXEL_FORMAT_ARGB8888:
+ ctrl |= 3 << 16;
+ break;
+ }
+ ctrl |= EDC_VIDEO_CHAN_CTRL_EN;
+ /* color key & rotate is always disabled, linear format */
+// ctrl |= 1 << 24; /* enable channel */
+// ctrl &= ~0xfff;
+// ctrl |= 0xa;
+// ctrl |= var->yres - 1; /* debug. add for interrupt */
+ writel_relaxed(ctrl, base + EDC_VIDEO_CHAN_CTRL);
+
+ set_panel_control(fb);
+ set_screen_dimensions(fb);
+ set_dma_control(fb);
+ update_edc(base);
+ return 0;
+}
+
+static int hi3620fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *fb)
+{
+ return 0;
+}
+
+static struct fb_ops hi3620fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = hi3620fb_check_var,
+ .fb_set_par = hi3620fb_set_par,
+ .fb_pan_display = hi3620fb_pan_display,
+};
+
+static int hi3620_parse_dt(struct device_node *np, struct hi3620fb_info *info)
+{
+ const char *name;
+ int ret;
+
+ ret = of_property_read_u32(np, "hisilicon,dsi-clock-frequency",
+ &info->dsi_rate);
+ if (ret)
+ return ret;
+ ret = of_property_read_string(np, "hisilicon,mipi-mode", &name);
+ if (ret < 0)
+ return ret;
+ info->mipi_mode_name = kstrdup(name, GFP_KERNEL);
+ ret = of_property_read_u32(np, "hisilicon,mipi-lanes", &info->lane_cnt);
+ if (ret < 0)
+ return ret;
+ ret = of_property_read_u32(np, "hisilicon,color-mode", &info->color_mode);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int hi3620_init_mode(struct device_node *np, struct fb_info *fb)
+{
+ struct fb_fix_screeninfo *fix = &fb->fix;
+ struct fb_var_screeninfo *var = &fb->var;
+ struct display_timings *disp;
+ struct fb_videomode *fb_vm;
+ struct hi3620fb_info *info = fb->par;
+ const char *pix_name;
+ int ret = 0, pix_fmt, i, length;
+
+ fb_vm = kzalloc(sizeof(*fb_vm), GFP_KERNEL);
+ if (!fb_vm)
+ return -ENOMEM;
+ fb->mode = fb_vm;
+ disp = of_get_display_timings(np);
+ if (!disp)
+ return -ENOENT;
+ /* How to handle multiple display timings ???,
+ * add_videomode is implemented by register_framebuffer() */
+ for (i = 0; i < disp->num_timings; i++) {
+ ret = of_get_fb_videomode(np, fb_vm, i);
+ if (ret)
+ goto out;
+ ret = of_property_read_string(np, "hisilicon,pixel-format",
+ &pix_name);
+ if (ret)
+ goto out;
+ if (!strncmp(pix_name, "RGBA8888", 8))
+ pix_fmt = IMG_PIXEL_FORMAT_ARGB8888;
+ else if (!strncmp(pix_name, "RGBX8888", 8))
+ pix_fmt = IMG_PIXEL_FORMAT_RGB888;
+ else if (!strncmp(pix_name, "RGBA5551", 8))
+ pix_fmt = IMG_PIXEL_FORMAT_ARGB1555;
+ else if (!strncmp(pix_name, "RGBX5551", 8))
+ pix_fmt = IMG_PIXEL_FORMAT_RGB555;
+ else if (!strncmp(pix_name, "RGB565", 6))
+ pix_fmt = IMG_PIXEL_FORMAT_RGB565;
+ else {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ set_pix_fmt(info, pix_fmt);
+ fb_videomode_to_var(var, fb_vm);
+ var->xres_virtual = fb_vm->xres;
+ var->yres_virtual = fb_vm->yres;
+ var->grayscale = 0;
+ var->accel_flags = FB_ACCEL_NONE;
+ /* Now assume that video mode is only 1 in DTS. */
+ //fb_add_videomode(&vm, &fb->modelist);
+ }
+ of_display_timings_exist(np);
+
+ fix->type_aux = 0;
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ypanstep = 1;
+ fix->ywrapstep = 0;
+ fix->mmio_start = 0; /* No MMIO address */
+ fix->mmio_len = 0; /* No MMIO address */
+ fix->accel = FB_ACCEL_NONE; /* No hardware accelerator */
+
+ length = var->xres_virtual * var->bits_per_pixel / 8;
+ fb->fix.line_length = ALIGN(length, 64);
+ fb->fix.smem_len = ALIGN(fb->fix.line_length * fb->var.yres_virtual,
+ PAGE_SIZE);
+ hi3620_parse_dt(np, info);
+out:
+ return ret;
+}
+
+static irqreturn_t edc_irq_handler(int irq, void *data)
+{
+ struct hi3620fb_info *info = data;
+
+ pr_err("#%s, %d, ints:0x%x, inte:0x%x\n",
+ __func__, __LINE__, readl_relaxed(info->reg_base + EDC_INTS),
+ readl_relaxed(info->reg_base + EDC_INTE));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ldi_irq_handler(int irq, void *data)
+{
+ struct hi3620fb_info *info = data;
+ u32 value;
+
+ value = readl_relaxed(info->reg_base + LDI_ORG_INT);
+ writel_relaxed(value, info->reg_base + LDI_INT_CLR);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dsi_irq_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+
+static int hi3620_fb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct hi3620fb_info *info;
+ struct resource *res;
+ struct fb_info *fb;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no memory resource defined\n");
+ return -ENODEV;
+ }
+
+ fb = framebuffer_alloc(sizeof(*info), dev);
+ if (!fb) {
+ dev_err(dev, "failed to allocate framebuffer\n");
+ return -ENOMEM;
+ }
+ fb->dev = &pdev->dev;
+ info = fb->par;
+ info->fb = fb;
+ info->dev = &pdev->dev;
+ info->irq_edc = platform_get_irq_byname(pdev, "edc");
+ if (info->irq_edc < 0) {
+ ret = -ENOENT;
+ goto err_fb;
+ }
+ info->irq_ldi = platform_get_irq_byname(pdev, "ldi");
+ if (info->irq_ldi < 0) {
+ ret = -ENOENT;
+ goto err_fb;
+ }
+ info->irq_dsi = platform_get_irq_byname(pdev, "dsi");
+ if (info->irq_dsi < 0) {
+ ret = -ENOENT;
+ goto err_fb;
+ }
+
+ info->reg_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (!info->reg_base) {
+ ret = -EADDRNOTAVAIL;
+ goto err_fb;
+ }
+ info->vedc = devm_regulator_get(dev, "vedc");
+ if (IS_ERR_OR_NULL(info->vedc)) {
+ if (IS_ERR(info->vedc)) {
+ dev_err(dev, "failed to get vedc regulator\n");
+ info->vedc = NULL;
+ }
+ }
+ info->clk_ldi = of_clk_get_by_name(np, "ldi");
+ if (IS_ERR(info->clk_ldi)) {
+ dev_err(dev, "failed to get ldi clock\n");
+ ret = PTR_ERR(info->clk_ldi);
+ goto err_fb;
+ }
+ info->clk_edc = of_clk_get_by_name(np, "edc");
+ if (IS_ERR(info->clk_edc)) {
+ dev_err(dev, "failed to get edc clock\n");
+ ret = PTR_ERR(info->clk_edc);
+ goto err_fb;
+ }
+ info->clk_dsi = of_clk_get_by_name(np, "dsi");
+ if (IS_ERR(info->clk_dsi)) {
+ dev_err(dev, "failed to get dsi clock\n");
+ ret = PTR_ERR(info->clk_dsi);
+ goto err_clk;
+ }
+ info->clk_lane = of_clk_get_by_name(np, "lane");
+ if (IS_ERR(info->clk_lane)) {
+ dev_err(dev, "failed to get lane clock\n");
+ ret = PTR_ERR(info->clk_lane);
+ goto err_clk;
+ }
+ if (info->vedc)
+ regulator_enable(info->vedc);
+ clk_prepare_enable(info->clk_ldi);
+ //clk_prepare_enable(info->clk_edc); /* debug for keep display on after boot */
+
+ fb->fbops = &hi3620fb_ops;
+ fb->pseudo_palette = &hi3620fb_pseudo_palette;
+
+ ret = hi3620_init_mode(np, fb);
+ if (ret)
+ goto err_clk;
+
+#if 1
+ fb->screen_base = dma_alloc_coherent(fb->dev, fb->fix.smem_len,
+ &info->fb_start_dma,
+ GFP_KERNEL);
+ fb->screen_size = fb->fix.smem_len;
+ fb->fix.smem_start = info->fb_start_dma;
+#else
+ /* debug for remapping the display region in bootloader */
+ info->fb_start_dma = PAGE_ALIGN(0x35b00130 + 0x40000000);
+ fb->fix.smem_start = info->fb_start_dma;
+ fb->screen_size = fb->fix.smem_len;
+ fb->screen_base = __va(info->fb_start_dma);
+#endif
+ hi3620_mipi_enable(info);
+ set_graphics_start(fb, 0, 0);
+ hi3620fb_set_par(fb);
+
+
+ /* clear IRQ status & enable IRQ */
+ writel_relaxed(0, info->reg_base + EDC_INTS);
+ writel_relaxed(0x2c8, info->reg_base + EDC_INTE);
+ writel_relaxed(0x4, info->reg_base + LDI_INT_EN); /* disable front porch int for debugging */
+ writel_relaxed(0x3fff, info->reg_base + LDI_INT_CLR);
+
+ ret = devm_request_irq(dev, info->irq_edc, edc_irq_handler,
+ IRQF_DISABLED, "edc", info);
+ if (ret < 0) {
+ dev_err(dev, "failed to request edc irq\n");
+ goto err_clk;
+ }
+ ret = devm_request_irq(dev, info->irq_ldi, ldi_irq_handler,
+ IRQF_DISABLED, "ldi", info);
+ if (ret < 0) {
+ dev_err(dev, "failed to request ldi irq\n");
+ goto err_clk;
+ }
+ ret = devm_request_irq(dev, info->irq_dsi, dsi_irq_handler,
+ IRQF_DISABLED, "dsi", info);
+ if (ret < 0) {
+ dev_err(dev, "failed to request dsi irq\n");
+ goto err_clk;
+ }
+ ret = register_framebuffer(fb);
+ if (ret < 0) {
+ dev_err(dev, "failed to register hi3620 framebuffer\n");
+ goto err_clk;
+ }
+ platform_set_drvdata(pdev, info);
+ /* clock rate of ldi */
+ return 0;
+err_clk:
+ clk_disable_unprepare(info->clk_edc);
+ clk_disable_unprepare(info->clk_ldi);
+err_fb:
+ framebuffer_release(fb);
+ return ret;
+}
+
+static int hi3620_fb_remove(struct platform_device *pdev)
+{
+ struct hi3620fb_info *info = platform_get_drvdata(pdev);
+ hi3620_mipi_disable(info);
+ return 0;
+}
+
+static const struct of_device_id hi3620_fb_of_match[] = {
+ { .compatible = "hisilicon,hi3620-fb", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, hi3620_fb_of_match);
+
+static struct platform_driver hi3620_fb_driver = {
+ .probe = hi3620_fb_probe,
+ .remove = hi3620_fb_remove,
+ .driver = {
+ .name = "hi3620-fb",
+ .owner = THIS_MODULE,
+ .of_match_table = hi3620_fb_of_match,
+ },
+};
+module_platform_driver(hi3620_fb_driver);