From mboxrd@z Thu Jan 1 00:00:00 1970 From: FlorianSchandinat@gmx.de (Florian Tobias Schandinat) Date: Mon, 19 Dec 2011 00:56:29 +0000 Subject: [PATCH v2] video: support MIPI-DSI controller driver In-Reply-To: <20111116005516.GA17850@july> References: <20111116005516.GA17850@july> Message-ID: <4EEE8BBD.2080300@gmx.de> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org Hi, sorry for the long delay, but at the moment I really have a lot to do. On 11/16/2011 12:55 AM, Kyungmin Park wrote: > From: Donghwa Lee > > Samsung S5PC210 and EXYNOS SoC platform has MIPI-DSI controller and MIPI-DSI > based LCD Panel could be used with it. This patch supports MIPI-DSI driver > based Samsung SoC chip. > > LCD panel driver based MIPI-DSI should be registered to MIPI-DSI driver at > machine code and LCD panel driver specific function registered to mipi_dsim_ddi > structure at lcd panel init function called system init. > In the MIPI-DSI driver, find lcd panel driver by using registered > lcd panel name, and then initialize lcd panel driver. > > Signed-off-by: Inki Dae > Signed-off-by: Kyungmin Park > Signed-off-by: Donghwa Lee > > Changes since v1: > - /plat/regs-dsim.h moves to /driver/video/s5p_mipi_dsi_regs.h, > - /plat/mipi_dsim.h moves to /linux/mipi_dsim.h > > --- > drivers/video/Kconfig | 7 + > drivers/video/Makefile | 2 + > drivers/video/s5p_mipi_dsi.c | 601 +++++++++++++++++++++ > drivers/video/s5p_mipi_dsi_common.c | 931 +++++++++++++++++++++++++++++++++ > drivers/video/s5p_mipi_dsi_common.h | 48 ++ > drivers/video/s5p_mipi_dsi_lowlevel.c | 608 +++++++++++++++++++++ > drivers/video/s5p_mipi_dsi_lowlevel.h | 108 ++++ > drivers/video/s5p_mipi_dsi_regs.h | 153 ++++++ > include/linux/mipi_dsim.h | 362 +++++++++++++ > 9 files changed, 2820 insertions(+), 0 deletions(-) > create mode 100644 drivers/video/s5p_mipi_dsi.c > create mode 100644 drivers/video/s5p_mipi_dsi_common.c > create mode 100644 drivers/video/s5p_mipi_dsi_common.h > create mode 100644 drivers/video/s5p_mipi_dsi_lowlevel.c > create mode 100644 drivers/video/s5p_mipi_dsi_lowlevel.h > create mode 100644 drivers/video/s5p_mipi_dsi_regs.h > create mode 100644 include/linux/mipi_dsim.h > > diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig > index d83e967..879177b 100644 > --- a/drivers/video/Kconfig > +++ b/drivers/video/Kconfig > @@ -2082,6 +2082,13 @@ config FB_S3C2410_DEBUG > Turn on debugging messages. Note that you can set/unset at run time > through sysfs > > +config S5P_MIPI_DSI > + tristate "Samsung SoC MIPI-DSI support." > + depends on FB_S3C && (ARCH_S5PV210 || ARCH_S5PV310 || ARCH_EXYNOS) > + default n > + help > + This enables support for MIPI-DSI device. > + > config FB_NUC900 > bool "NUC900 LCD framebuffer support" > depends on FB && ARCH_W90X900 > diff --git a/drivers/video/Makefile b/drivers/video/Makefile > index 9b9d8ff..29eb7c9 100644 > --- a/drivers/video/Makefile > +++ b/drivers/video/Makefile > @@ -120,6 +120,8 @@ obj-$(CONFIG_FB_SH7760) += sh7760fb.o > obj-$(CONFIG_FB_IMX) += imxfb.o > obj-$(CONFIG_FB_S3C) += s3c-fb.o > obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o > +obj-$(CONFIG_S5P_MIPI_DSI) += s5p_mipi_dsi.o s5p_mipi_dsi_common.o \ > + s5p_mipi_dsi_lowlevel.o > obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o > obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o > obj-$(CONFIG_FB_PNX4008_DUM) += pnx4008/ > diff --git a/drivers/video/s5p_mipi_dsi.c b/drivers/video/s5p_mipi_dsi.c > new file mode 100644 > index 0000000..09a4af8 > --- /dev/null > +++ b/drivers/video/s5p_mipi_dsi.c > @@ -0,0 +1,601 @@ > +/* linux/drivers/video/s5p_mipi_dsi.c > + * > + * Samsung SoC MIPI-DSIM driver. > + * > + * Copyright (c) 2011 Samsung Electronics Co., Ltd > + * > + * InKi Dae, > + * Donghwa Lee, > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > + > +#include "s5p_mipi_dsi_common.h" > +#include "s5p_mipi_dsi_lowlevel.h" > + > +#define master_to_driver(a) (a->dsim_lcd_drv) > +#define master_to_device(a) (a->dsim_lcd_dev) > + > +struct mipi_dsim_ddi { > + int bus_id; > + struct list_head list; > + struct mipi_dsim_lcd_device *dsim_lcd_dev; > + struct mipi_dsim_lcd_driver *dsim_lcd_drv; > +}; > + > +static LIST_HEAD(dsim_ddi_list); > + > +static DEFINE_MUTEX(mipi_dsim_lock); > + > +static struct s5p_platform_mipi_dsim *to_dsim_plat(struct platform_device *pdev) > +{ > + return (struct s5p_platform_mipi_dsim *)pdev->dev.platform_data; > +} > + > +/* update all register settings to MIPI DSI controller. */ > +static void s5p_mipi_update_cfg(struct mipi_dsim_device *dsim) > +{ > + /* > + * data from Display controller(FIMD) is not transferred in video mode > + * but in case of command mode, all settings is not updated to > + * registers. > + */ > + s5p_mipi_dsi_stand_by(dsim, 0); > + > + s5p_mipi_dsi_init_dsim(dsim); > + s5p_mipi_dsi_init_link(dsim); > + > + s5p_mipi_dsi_set_hs_enable(dsim); > + > + /* set display timing. */ > + s5p_mipi_dsi_set_display_mode(dsim, dsim->dsim_config); > + > + /* > + * data from Display controller(FIMD) is transferred in video mode > + * but in case of command mode, all settigs is updated to registers. > + */ > + s5p_mipi_dsi_stand_by(dsim, 1); > + > +} > + > +static int s5p_mipi_dsi_early_blank_mode(struct mipi_dsim_device *dsim, > + int power) > +{ > + struct platform_device *pdev = to_platform_device(dsim->dev); > + struct mipi_dsim_lcd_driver *client_drv = master_to_driver(dsim); > + struct mipi_dsim_lcd_device *client_dev = master_to_device(dsim); > + > + switch (power) { > + case FB_BLANK_POWERDOWN: > + if (client_drv && client_drv->suspend) > + client_drv->suspend(client_dev); > + > + clk_disable(dsim->clock); > + > + if (dsim->pd->mipi_power) > + dsim->pd->mipi_power(pdev, 0); > + > + atomic_set(&dsim->power_t, 0); Is atomic really appropriate here or do you need some mutex/spinlock/whatever? As far as I understand the value of dsim->power_t should contain the same value as set by dsim->pd->mipi_power? If this is correct, the critical section includes both, setting mipi_power and dsim->power_t, therefore either the atomic is useless because concurrent modification is prevented by something else or you have a race condition and need something else to prevent it. > + > + break; > + default: > + break; > + } > + > + return 0; > +} > + > +static int s5p_mipi_dsi_blank_mode(struct mipi_dsim_device *dsim, int power) > +{ > + struct platform_device *pdev = to_platform_device(dsim->dev); > + struct mipi_dsim_lcd_driver *client_drv = master_to_driver(dsim); > + struct mipi_dsim_lcd_device *client_dev = master_to_device(dsim); > + > + switch (power) { > + case FB_BLANK_UNBLANK: > + /* lcd panel power on. */ > + if (client_drv && client_drv->power_on) > + client_drv->power_on(client_dev); > + > + if (dsim->pd->mipi_power) > + dsim->pd->mipi_power(pdev, 1); > + > + /* enable MIPI-DSI PHY. */ > + if (dsim->pd->phy_enable) > + dsim->pd->phy_enable(pdev, true); > + > + clk_enable(dsim->clock); > + > + s5p_mipi_update_cfg(dsim); > + > + /* set lcd panel sequence commands. */ > + if (client_drv && client_drv->set_sequence) > + client_drv->set_sequence(client_dev); > + > + atomic_set(&dsim->power_t, 1); Same as the atomic above. > + > + break; > + case FB_BLANK_NORMAL: > + /* TODO. */ > + break; > + default: > + break; > + } > + > + return 0; > +} > + > +int s5p_mipi_dsi_register_lcd_device(struct mipi_dsim_lcd_device *lcd_dev) > +{ > + struct mipi_dsim_ddi *dsim_ddi; > + > + if (!lcd_dev) { > + printk(KERN_ERR "mipi_dsim_lcd_device is NULL.\n"); > + return -EFAULT; > + } > + > + if (!lcd_dev->name) { > + printk(KERN_ERR "dsim_lcd_device name is NULL.\n"); > + return -EFAULT; > + } > + > + dsim_ddi = kzalloc(sizeof(struct mipi_dsim_ddi), GFP_KERNEL); > + if (!dsim_ddi) { > + printk(KERN_ERR "failed to allocate dsim_ddi object.\n"); > + return -EFAULT; > + } > + > + dsim_ddi->dsim_lcd_dev = lcd_dev; > + > + mutex_lock(&mipi_dsim_lock); > + list_add_tail(&dsim_ddi->list, &dsim_ddi_list); > + mutex_unlock(&mipi_dsim_lock); > + > + return 0; > +} > + > +struct mipi_dsim_ddi > + *s5p_mipi_dsi_find_lcd_device(struct mipi_dsim_lcd_driver *lcd_drv) > +{ > + struct mipi_dsim_ddi *dsim_ddi; > + struct mipi_dsim_lcd_device *lcd_dev; > + > + mutex_lock(&mipi_dsim_lock); > + > + list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) { > + if (!dsim_ddi) > + goto out; > + > + lcd_dev = dsim_ddi->dsim_lcd_dev; > + if (!lcd_dev) > + continue; > + > + if (lcd_drv->id >= 0) { > + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0 && > + lcd_drv->id == lcd_dev->id) { > + /** > + * bus_id would be used to identify > + * connected bus. > + */ > + dsim_ddi->bus_id = lcd_dev->bus_id; > + mutex_unlock(&mipi_dsim_lock); > + > + return dsim_ddi; > + } > + } else { > + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0) { Perhaps it would be better to have this check as the outer if as it is duplicated above? > + /** > + * bus_id would be used to identify > + * connected bus. > + */ > + dsim_ddi->bus_id = lcd_dev->bus_id; > + mutex_unlock(&mipi_dsim_lock); > + > + return dsim_ddi; > + } > + } > + > + kfree(dsim_ddi); > + list_del(&dsim_ddi->list); Am I really reading this correct? First you free the memory, then you dereference it to delete it from the list? 1. first delete it from the list, than free the memory 2. list_for_each_entry is not safe against list modification/removal, you have to use list_for_each_entry_safe for such purposes > + } > + > +out: > + mutex_unlock(&mipi_dsim_lock); > + > + return NULL; > +} > + > +int s5p_mipi_dsi_register_lcd_driver(struct mipi_dsim_lcd_driver *lcd_drv) > +{ > + struct mipi_dsim_ddi *dsim_ddi; > + > + if (!lcd_drv) { > + printk(KERN_ERR "mipi_dsim_lcd_driver is NULL.\n"); > + return -EFAULT; > + } > + > + if (!lcd_drv->name) { > + printk(KERN_ERR "dsim_lcd_driver name is NULL.\n"); > + return -EFAULT; > + } > + > + dsim_ddi = s5p_mipi_dsi_find_lcd_device(lcd_drv); > + if (!dsim_ddi) { > + printk(KERN_ERR "mipi_dsim_ddi object not found.\n"); > + return -EFAULT; > + } > + > + dsim_ddi->dsim_lcd_drv = lcd_drv; > + > + printk(KERN_INFO "registered panel driver(%s) to mipi-dsi driver.\n", > + lcd_drv->name); > + > + return 0; > + > +} > + > +struct mipi_dsim_ddi > + *s5p_mipi_dsi_bind_lcd_ddi(struct mipi_dsim_device *dsim, > + const char *name) > +{ > + struct mipi_dsim_ddi *dsim_ddi; > + struct mipi_dsim_lcd_driver *lcd_drv; > + struct mipi_dsim_lcd_device *lcd_dev; > + int ret; > + > + mutex_lock(&dsim->lock); > + > + list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) { > + lcd_drv = dsim_ddi->dsim_lcd_drv; > + lcd_dev = dsim_ddi->dsim_lcd_dev; > + if (!lcd_drv || !lcd_dev || > + (dsim->id != dsim_ddi->bus_id)) > + continue; > + > + dev_dbg(dsim->dev, "lcd_drv->id = %d, lcd_dev->id = %d\n", > + lcd_drv->id, lcd_dev->id); > + dev_dbg(dsim->dev, "lcd_dev->bus_id = %d, dsim->id = %d\n", > + lcd_dev->bus_id, dsim->id); > + > + if ((strcmp(lcd_drv->name, name) == 0)) { > + lcd_dev->master = dsim; > + > + lcd_dev->dev.parent = dsim->dev; > + dev_set_name(&lcd_dev->dev, "%s", lcd_drv->name); > + > + ret = device_register(&lcd_dev->dev); > + if (ret < 0) { > + dev_err(dsim->dev, > + "can't register %s, status %d\n", > + dev_name(&lcd_dev->dev), ret); > + mutex_unlock(&dsim->lock); > + > + return NULL; > + } > + > + dsim->dsim_lcd_dev = lcd_dev; > + dsim->dsim_lcd_drv = lcd_drv; > + > + mutex_unlock(&dsim->lock); > + > + return dsim_ddi; > + } > + } > + > + mutex_unlock(&dsim->lock); > + > + return NULL; > +} > + > +/* define MIPI-DSI Master operations. */ > +static struct mipi_dsim_master_ops master_ops = { > + .cmd_read = s5p_mipi_dsi_rd_data, > + .cmd_write = s5p_mipi_dsi_wr_data, > + .get_dsim_frame_done = s5p_mipi_dsi_get_frame_done_status, > + .clear_dsim_frame_done = s5p_mipi_dsi_clear_frame_done, > + .set_early_blank_mode = s5p_mipi_dsi_early_blank_mode, > + .set_blank_mode = s5p_mipi_dsi_blank_mode, > +}; > + > +static int s5p_mipi_dsi_probe(struct platform_device *pdev) > +{ > + struct resource *res; > + struct mipi_dsim_device *dsim; > + struct mipi_dsim_config *dsim_config; > + struct mipi_dsim_platform_data *dsim_pd; > + struct mipi_dsim_ddi *dsim_ddi; > + int ret = -EINVAL; > + > + dsim = kzalloc(sizeof(struct mipi_dsim_device), GFP_KERNEL); > + if (!dsim) { > + dev_err(&pdev->dev, "failed to allocate dsim object.\n"); > + return -EFAULT; > + } > + > + dsim->pd = to_dsim_plat(pdev); > + dsim->dev = &pdev->dev; > + dsim->id = pdev->id; > + > + /* get mipi_dsim_platform_data. */ > + dsim_pd = (struct mipi_dsim_platform_data *)dsim->pd; > + if (dsim_pd == NULL) { > + dev_err(&pdev->dev, "failed to get platform data for dsim.\n"); > + return -EFAULT; > + } > + /* get mipi_dsim_config. */ > + dsim_config = dsim_pd->dsim_config; > + if (dsim_config == NULL) { > + dev_err(&pdev->dev, "failed to get dsim config data.\n"); > + return -EFAULT; > + } > + > + dsim->dsim_config = dsim_config; > + dsim->master_ops = &master_ops; > + > + dsim->clock = clk_get(&pdev->dev, "dsim0"); > + if (IS_ERR(dsim->clock)) { > + dev_err(&pdev->dev, "failed to get dsim clock source\n"); > + goto err_clock_get; > + } > + > + clk_enable(dsim->clock); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(&pdev->dev, "failed to get io memory region\n"); > + goto err_platform_get; > + } > + > + dsim->res = request_mem_region(res->start, resource_size(res), > + dev_name(&pdev->dev)); > + if (!dsim->res) { > + dev_err(&pdev->dev, "failed to request io memory region\n"); > + ret = -ENOMEM; > + goto err_mem_region; > + } > + > + dsim->reg_base = ioremap(res->start, resource_size(res)); > + if (!dsim->reg_base) { > + dev_err(&pdev->dev, "failed to remap io region\n"); > + ret = -EFAULT; > + goto err_ioremap; > + } > + > + mutex_init(&dsim->lock); > + > + /* bind lcd ddi matched with panel name. */ > + dsim_ddi = s5p_mipi_dsi_bind_lcd_ddi(dsim, dsim_pd->lcd_panel_name); > + if (!dsim_ddi) { > + dev_err(&pdev->dev, "mipi_dsim_ddi object not found.\n"); > + goto err_bind; > + } > + > + dsim->irq = platform_get_irq(pdev, 0); > + if (dsim->irq < 0) { > + dev_err(&pdev->dev, "failed to request dsim irq resource\n"); > + ret = -EINVAL; > + goto err_platform_get_irq; > + } > + > + ret = request_irq(dsim->irq, s5p_mipi_dsi_interrupt_handler, > + IRQF_SHARED, pdev->name, dsim); > + if (ret != 0) { > + dev_err(&pdev->dev, "failed to request dsim irq\n"); > + ret = -EINVAL; > + goto err_bind; > + } > + init_completion(&dsim_rd_comp); > + > + /* enable interrupt */ > + s5p_mipi_dsi_init_interrupt(dsim); > + > + /* initialize mipi-dsi client(lcd panel). */ > + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->probe) > + dsim_ddi->dsim_lcd_drv->probe(dsim_ddi->dsim_lcd_dev); > + > + /* in case that mipi got enabled at bootloader. */ > + if (dsim_pd->enabled) > + goto out; > + > + /* lcd panel power on. */ > + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->power_on) > + dsim_ddi->dsim_lcd_drv->power_on(dsim_ddi->dsim_lcd_dev); > + > + if (dsim->pd->mipi_power) > + dsim->pd->mipi_power(pdev, 1); > + > + /* enable MIPI-DSI PHY. */ > + if (dsim->pd->phy_enable) > + dsim->pd->phy_enable(pdev, true); > + > + s5p_mipi_update_cfg(dsim); > + > + /* set lcd panel sequence commands. */ > + if (dsim_ddi->dsim_lcd_drv && dsim_ddi->dsim_lcd_drv->set_sequence) > + dsim_ddi->dsim_lcd_drv->set_sequence(dsim_ddi->dsim_lcd_dev); > + > + atomic_set(&dsim->power_t, 1); Same as the atomic above. (although it probably does not really matter in probe) > + > +out: > + platform_set_drvdata(pdev, dsim); > + > + dev_dbg(&pdev->dev, "mipi-dsi driver(%s mode) has been probed.\n", > + (dsim_config->e_interface == DSIM_COMMAND) ? > + "CPU" : "RGB"); > + > + return 0; > + > +err_bind: > + iounmap((void __iomem *) dsim->reg_base); > + > +err_ioremap: > + release_mem_region(dsim->res->start, resource_size(dsim->res)); > + > +err_mem_region: > + release_resource(dsim->res); > + > +err_platform_get: > + clk_disable(dsim->clock); > + clk_put(dsim->clock); > + > +err_clock_get: > + kfree(dsim); > + > +err_platform_get_irq: > + return ret; > +} > + > +static int __devexit s5p_mipi_dsi_remove(struct platform_device *pdev) > +{ > + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); > + struct mipi_dsim_ddi *dsim_ddi; > + struct mipi_dsim_lcd_driver *dsim_lcd_drv; > + > + iounmap(dsim->reg_base); > + > + clk_disable(dsim->clock); > + clk_put(dsim->clock); > + > + release_resource(dsim->res); > + release_mem_region(dsim->res->start, resource_size(dsim->res)); > + > + list_for_each_entry(dsim_ddi, &dsim_ddi_list, list) { > + if (dsim_ddi) { > + if (dsim->id != dsim_ddi->bus_id) > + continue; > + > + dsim_lcd_drv = dsim_ddi->dsim_lcd_drv; > + > + if (dsim_lcd_drv->remove) > + dsim_lcd_drv->remove(dsim_ddi->dsim_lcd_dev); > + > + kfree(dsim_ddi); Probably you have to use list_for_each_entry_safe here. > + } > + } > + > + kfree(dsim); > + > + return 0; > +} > + > +#ifdef CONFIG_PM > +static int s5p_mipi_dsi_suspend(struct platform_device *pdev, > + pm_message_t state) > +{ > + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); > + > + disable_irq(dsim->irq); > + > + if (!atomic_read(&dsim->power_t)) > + return 0; > + > + if (master_to_driver(dsim) && (master_to_driver(dsim))->suspend) > + (master_to_driver(dsim))->suspend(master_to_device(dsim)); > + > + /* enable MIPI-DSI PHY. */ > + if (dsim->pd->phy_enable) > + dsim->pd->phy_enable(pdev, false); > + > + clk_disable(dsim->clock); > + > + if (dsim->pd->mipi_power) > + dsim->pd->mipi_power(pdev, 0); > + > + atomic_set(&dsim->power_t, 0); Probably everything between reading and setting power_t needs to be synchronized appropriatly unless you already know that nothing else that could modify it runs concurrently to ssusped/resume. > + > + return 0; > +} > + > +static int s5p_mipi_dsi_resume(struct platform_device *pdev) > +{ > + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); > + struct mipi_dsim_lcd_driver *client_drv = master_to_driver(dsim); > + struct mipi_dsim_lcd_device *client_dev = master_to_device(dsim); > + > + enable_irq(dsim->irq); > + > + if (atomic_read(&dsim->power_t)) > + return 0; > + > + /* lcd panel power on. */ > + if (client_drv && client_drv->power_on) > + client_drv->power_on(client_dev); > + > + if (dsim->pd->mipi_power) > + dsim->pd->mipi_power(pdev, 1); > + > + /* enable MIPI-DSI PHY. */ > + if (dsim->pd->phy_enable) > + dsim->pd->phy_enable(pdev, true); > + > + clk_enable(dsim->clock); > + > + s5p_mipi_update_cfg(dsim); > + > + /* set lcd panel sequence commands. */ > + if (client_drv && client_drv->set_sequence) > + client_drv->set_sequence(client_dev); > + > + atomic_set(&dsim->power_t, 1); > + > + return 0; > +} > +#else > +#define s5p_mipi_dsi_suspend NULL > +#define s5p_mipi_dsi_resume NULL > +#endif > + > +static struct platform_driver s5p_mipi_dsi_driver = { > + .probe = s5p_mipi_dsi_probe, > + .remove = __devexit_p(s5p_mipi_dsi_remove), > + .suspend = s5p_mipi_dsi_suspend, > + .resume = s5p_mipi_dsi_resume, > + .driver = { > + .name = "s5p-mipi-dsim", > + .owner = THIS_MODULE, > + }, > +}; > + > +static int s5p_mipi_dsi_register(void) > +{ > + platform_driver_register(&s5p_mipi_dsi_driver); > + > + return 0; > +} > + > +static void s5p_mipi_dsi_unregister(void) > +{ > + platform_driver_unregister(&s5p_mipi_dsi_driver); > +} > + > +module_init(s5p_mipi_dsi_register); > +module_exit(s5p_mipi_dsi_unregister); Recently Axel Lin posted a series to make drivers use module_platform_driver(), I think you can/should also use it. > + > +MODULE_AUTHOR("InKi Dae "); > +MODULE_DESCRIPTION("Samusung SoC MIPI-DSI driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/video/s5p_mipi_dsi_common.c b/drivers/video/s5p_mipi_dsi_common.c > new file mode 100644 > index 0000000..f3452b2 > --- /dev/null > +++ b/drivers/video/s5p_mipi_dsi_common.c > @@ -0,0 +1,931 @@ > +/* linux/drivers/video/s5p_mipi_dsi_common.c > + * > + * Samsung SoC MIPI-DSI common driver. > + * > + * Copyright (c) 2011 Samsung Electronics Co., Ltd > + * > + * InKi Dae, > + * Donghwa Lee, > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include