diff options
author | Donghwa Lee <dh09.lee@samsung.com> | 2012-02-08 12:47:39 -0800 |
---|---|---|
committer | Florian Tobias Schandinat <FlorianSchandinat@gmx.de> | 2012-02-13 03:02:03 +0000 |
commit | 7258cc14f310b912b6fea5421aedb9beb69d8581 (patch) | |
tree | 0e3c0eb87437d8f1b14d09ab6ad3f4188904e5e8 /drivers/video/exynos | |
parent | 12fa8350244d73b6111ec9bc6c2fd5d49fa601b5 (diff) |
video: support MIPI-DSI controller driver
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: Donghwa Lee <dh09.lee@samsung.com>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Acked-by: Jingoo Han <jg1.han@samsung.com>
Cc: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Florian Tobias Schandinat <FlorianSchandinat@gmx.de>
Diffstat (limited to 'drivers/video/exynos')
-rw-r--r-- | drivers/video/exynos/Kconfig | 22 | ||||
-rw-r--r-- | drivers/video/exynos/Makefile | 6 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi.c | 600 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi_common.c | 896 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi_common.h | 46 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi_lowlevel.c | 618 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi_lowlevel.h | 112 | ||||
-rw-r--r-- | drivers/video/exynos/exynos_mipi_dsi_regs.h | 149 |
8 files changed, 2449 insertions, 0 deletions
diff --git a/drivers/video/exynos/Kconfig b/drivers/video/exynos/Kconfig new file mode 100644 index 00000000000..645b8a59719 --- /dev/null +++ b/drivers/video/exynos/Kconfig @@ -0,0 +1,22 @@ +# +# Exynos Video configuration +# + +menuconfig EXYNOS_VIDEO + bool "Exynos Video driver support" + help + This enables support for EXYNOS Video device. + +if EXYNOS_VIDEO + +# +# MIPI DSI driver +# + +config EXYNOS_MIPI_DSI + bool "EXYNOS MIPI DSI driver support." + depends on (ARCH_S5PV210 || ARCH_EXYNOS) + help + This enables support for MIPI-DSI device. + +endif # EXYNOS_VIDEO diff --git a/drivers/video/exynos/Makefile b/drivers/video/exynos/Makefile new file mode 100644 index 00000000000..a1ae82abd6b --- /dev/null +++ b/drivers/video/exynos/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the exynos video drivers. +# + +obj-$(CONFIG_EXYNOS_MIPI_DSI) += exynos_mipi_dsi.o exynos_mipi_dsi_common.o \ + exynos_mipi_dsi_lowlevel.o diff --git a/drivers/video/exynos/exynos_mipi_dsi.c b/drivers/video/exynos/exynos_mipi_dsi.c new file mode 100644 index 00000000000..557091dc0e9 --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi.c @@ -0,0 +1,600 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi.c + * + * Samsung SoC MIPI-DSIM driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/memory.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/notifier.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> + +#include <video/exynos_mipi_dsim.h> + +#include <plat/fb.h> + +#include "exynos_mipi_dsi_common.h" +#include "exynos_mipi_dsi_lowlevel.h" + +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 mipi_dsim_platform_data *to_dsim_plat(struct platform_device + *pdev) +{ + return pdev->dev.platform_data; +} + +static struct regulator_bulk_data supplies[] = { + { .supply = "vdd10", }, + { .supply = "vdd18", }, +}; + +static int exynos_mipi_regulator_enable(struct mipi_dsim_device *dsim) +{ + int ret; + + mutex_lock(&dsim->lock); + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + mutex_unlock(&dsim->lock); + + return ret; +} + +static int exynos_mipi_regulator_disable(struct mipi_dsim_device *dsim) +{ + int ret; + + mutex_lock(&dsim->lock); + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + mutex_unlock(&dsim->lock); + + return ret; +} + +/* update all register settings to MIPI DSI controller. */ +static void exynos_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. + */ + exynos_mipi_dsi_stand_by(dsim, 0); + + exynos_mipi_dsi_init_dsim(dsim); + exynos_mipi_dsi_init_link(dsim); + + exynos_mipi_dsi_set_hs_enable(dsim); + + /* set display timing. */ + exynos_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. + */ + exynos_mipi_dsi_stand_by(dsim, 1); +} + +static int exynos_mipi_dsi_early_blank_mode(struct mipi_dsim_device *dsim, + int power) +{ + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + switch (power) { + case FB_BLANK_POWERDOWN: + if (dsim->suspended) + return 0; + + if (client_drv && client_drv->suspend) + client_drv->suspend(client_dev); + + clk_disable(dsim->clock); + + exynos_mipi_regulator_disable(dsim); + + dsim->suspended = true; + + break; + default: + break; + } + + return 0; +} + +static int exynos_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 = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + switch (power) { + case FB_BLANK_UNBLANK: + if (!dsim->suspended) + return 0; + + /* lcd panel power on. */ + if (client_drv && client_drv->power_on) + client_drv->power_on(client_dev, 1); + + exynos_mipi_regulator_disable(dsim); + + /* enable MIPI-DSI PHY. */ + if (dsim->pd->phy_enable) + dsim->pd->phy_enable(pdev, true); + + clk_enable(dsim->clock); + + exynos_mipi_update_cfg(dsim); + + /* set lcd panel sequence commands. */ + if (client_drv && client_drv->set_sequence) + client_drv->set_sequence(client_dev); + + dsim->suspended = false; + + break; + case FB_BLANK_NORMAL: + /* TODO. */ + break; + default: + break; + } + + return 0; +} + +int exynos_mipi_dsi_register_lcd_device(struct mipi_dsim_lcd_device *lcd_dev) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_dev->name) { + pr_err("dsim_lcd_device name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = kzalloc(sizeof(struct mipi_dsim_ddi), GFP_KERNEL); + if (!dsim_ddi) { + pr_err("failed to allocate dsim_ddi object.\n"); + return -ENOMEM; + } + + 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 *exynos_mipi_dsi_find_lcd_device(struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi, *next; + struct mipi_dsim_lcd_device *lcd_dev; + + mutex_lock(&mipi_dsim_lock); + + list_for_each_entry_safe(dsim_ddi, next, &dsim_ddi_list, list) { + if (!dsim_ddi) + goto out; + + lcd_dev = dsim_ddi->dsim_lcd_dev; + if (!lcd_dev) + continue; + + if ((strcmp(lcd_drv->name, lcd_dev->name)) == 0) { + /** + * 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; + } + + list_del(&dsim_ddi->list); + kfree(dsim_ddi); + } + +out: + mutex_unlock(&mipi_dsim_lock); + + return NULL; +} + +int exynos_mipi_dsi_register_lcd_driver(struct mipi_dsim_lcd_driver *lcd_drv) +{ + struct mipi_dsim_ddi *dsim_ddi; + + if (!lcd_drv->name) { + pr_err("dsim_lcd_driver name is NULL.\n"); + return -EFAULT; + } + + dsim_ddi = exynos_mipi_dsi_find_lcd_device(lcd_drv); + if (!dsim_ddi) { + pr_err("mipi_dsim_ddi object not found.\n"); + return -EFAULT; + } + + dsim_ddi->dsim_lcd_drv = lcd_drv; + + pr_info("registered panel driver(%s) to mipi-dsi driver.\n", + lcd_drv->name); + + return 0; + +} + +struct mipi_dsim_ddi *exynos_mipi_dsi_bind_lcd_ddi(struct mipi_dsim_device *dsim, + const char *name) +{ + struct mipi_dsim_ddi *dsim_ddi, *next; + struct mipi_dsim_lcd_driver *lcd_drv; + struct mipi_dsim_lcd_device *lcd_dev; + int ret; + + mutex_lock(&dsim->lock); + + list_for_each_entry_safe(dsim_ddi, next, &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 = exynos_mipi_dsi_rd_data, + .cmd_write = exynos_mipi_dsi_wr_data, + .get_dsim_frame_done = exynos_mipi_dsi_get_frame_done_status, + .clear_dsim_frame_done = exynos_mipi_dsi_clear_frame_done, + .set_early_blank_mode = exynos_mipi_dsi_early_blank_mode, + .set_blank_mode = exynos_mipi_dsi_blank_mode, +}; + +static int exynos_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 -ENOMEM; + } + + 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"); + goto err_clock_get; + } + /* 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"); + goto err_clock_get; + } + + dsim->dsim_config = dsim_config; + dsim->master_ops = &master_ops; + + mutex_init(&dsim->lock); + + ret = regulator_bulk_get(&pdev->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(&pdev->dev, "Failed to get regulators: %d\n", ret); + goto err_clock_get; + } + + 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 = -ENOMEM; + goto err_ioremap; + } + + mutex_init(&dsim->lock); + + /* bind lcd ddi matched with panel name. */ + dsim_ddi = exynos_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, exynos_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_wr_comp); + init_completion(&dsim_rd_comp); + + /* enable interrupt */ + exynos_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, 1); + + exynos_mipi_regulator_enable(dsim); + + /* enable MIPI-DSI PHY. */ + if (dsim->pd->phy_enable) + dsim->pd->phy_enable(pdev, true); + + exynos_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); + + dsim->suspended = false; + +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(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 exynos_mipi_dsi_remove(struct platform_device *pdev) +{ + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_ddi *dsim_ddi, *next; + 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_safe(dsim_ddi, next, &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); + } + } + + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + kfree(dsim); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos_mipi_dsi_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + disable_irq(dsim->irq); + + if (dsim->suspended) + return 0; + + if (client_drv && client_drv->suspend) + client_drv->suspend(client_dev); + + /* enable MIPI-DSI PHY. */ + if (dsim->pd->phy_enable) + dsim->pd->phy_enable(pdev, false); + + clk_disable(dsim->clock); + + exynos_mipi_regulator_disable(dsim); + + dsim->suspended = true; + + return 0; +} + +static int exynos_mipi_dsi_resume(struct platform_device *pdev) +{ + struct mipi_dsim_device *dsim = platform_get_drvdata(pdev); + struct mipi_dsim_lcd_driver *client_drv = dsim->dsim_lcd_drv; + struct mipi_dsim_lcd_device *client_dev = dsim->dsim_lcd_dev; + + enable_irq(dsim->irq); + + if (!dsim->suspended) + return 0; + + /* lcd panel power on. */ + if (client_drv && client_drv->power_on) + client_drv->power_on(client_dev, 1); + + exynos_mipi_regulator_enable(dsim); + + /* enable MIPI-DSI PHY. */ + if (dsim->pd->phy_enable) + dsim->pd->phy_enable(pdev, true); + + clk_enable(dsim->clock); + + exynos_mipi_update_cfg(dsim); + + /* set lcd panel sequence commands. */ + if (client_drv && client_drv->set_sequence) + client_drv->set_sequence(client_dev); + + dsim->suspended = false; + + return 0; +} +#else +#define exynos_mipi_dsi_suspend NULL +#define exynos_mipi_dsi_resume NULL +#endif + +static struct platform_driver exynos_mipi_dsi_driver = { + .probe = exynos_mipi_dsi_probe, + .remove = __devexit_p(exynos_mipi_dsi_remove), + .suspend = exynos_mipi_dsi_suspend, + .resume = exynos_mipi_dsi_resume, + .driver = { + .name = "exynos-mipi-dsim", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(exynos_mipi_dsi_driver); + +MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("Samusung SoC MIPI-DSI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/exynos/exynos_mipi_dsi_common.c b/drivers/video/exynos/exynos_mipi_dsi_common.c new file mode 100644 index 00000000000..14909c1d383 --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi_common.c @@ -0,0 +1,896 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_common.c + * + * Samsung SoC MIPI-DSI common driver. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd + * + * InKi Dae, <inki.dae@samsung.com> + * Donghwa Lee, <dh09.lee@samsung.com> + * + * 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/kernel.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/fb.h> +#include <linux/ctype.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/memory.h> +#include <linux/delay.h> +#include <linux/kthread.h> + +#include <video/mipi_display.h> +#include <video/exynos_mipi_dsim.h> + +#include <mach/map.h> + +#include "exynos_mipi_dsi_regs.h" +#include "exynos_mipi_dsi_lowlevel.h" +#include "exynos_mipi_dsi_common.h" + +#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250) +#define MIPI_RX_FIFO_READ_DONE 0x30800002 +#define MIPI_MAX_RX_FIFO 20 +#define MHZ (1000 * 1000) +#define FIN_HZ (24 * MHZ) + +#define DFIN_PLL_MIN_HZ (6 * MHZ) +#define DFIN_PLL_MAX_HZ (12 * MHZ) + +#define DFVCO_MIN_HZ (500 * MHZ) +#define DFVCO_MAX_HZ (1000 * MHZ) + +#define TRY_GET_FIFO_TIMEOUT (5000 * 2) +#define TRY_FIFO_CLEAR (10) + +/* MIPI-DSIM status types. */ +enum { + DSIM_STATE_INIT, /* should be initialized. */ + DSIM_STATE_STOP, /* CPU and LCDC are LP mode. */ + DSIM_STATE_HSCLKEN, /* HS clock was enabled. */ + DSIM_STATE_ULPS +}; + +/* define DSI lane types. */ +enum { + DSIM_LANE_CLOCK = (1 << 0), + DSIM_LANE_DATA0 = (1 << 1), + DSIM_LANE_DATA1 = (1 << 2), + DSIM_LANE_DATA2 = (1 << 3), + DSIM_LANE_DATA3 = (1 << 4) +}; + +static unsigned int dpll_table[15] = { + 100, 120, 170, 220, 270, + 320, 390, 450, 510, 560, + 640, 690, 770, 870, 950 +}; + +irqreturn_t exynos_mipi_dsi_interrupt_handler(int irq, void *dev_id) +{ + unsigned int intsrc = 0; + unsigned int intmsk = 0; + struct mipi_dsim_device *dsim = NULL; + + dsim = dev_id; + if (!dsim) { + dev_dbg(dsim->dev, KERN_ERR "%s:error: wrong parameter\n", + __func__); + return IRQ_HANDLED; + } + + intsrc = exynos_mipi_dsi_read_interrupt(dsim); + intmsk = exynos_mipi_dsi_read_interrupt_mask(dsim); + + intmsk = ~(intmsk) & intsrc; + + switch (intmsk) { + case INTMSK_RX_DONE: + complete(&dsim_rd_comp); + dev_dbg(dsim->dev, "MIPI INTMSK_RX_DONE\n"); + break; + case INTMSK_FIFO_EMPTY: + complete(&dsim_wr_comp); + dev_dbg(dsim->dev, "MIPI INTMSK_FIFO_EMPTY\n"); + break; + default: + break; + } + + exynos_mipi_dsi_clear_interrupt(dsim, intmsk); + + return IRQ_HANDLED; +} + +/* + * write long packet to mipi dsi slave + * @dsim: mipi dsim device structure. + * @data0: packet data to send. + * @data1: size of packet data + */ +static void exynos_mipi_dsi_long_data_wr(struct mipi_dsim_device *dsim, + const unsigned char *data0, unsigned int data_size) +{ + unsigned int data_cnt = 0, payload = 0; + + /* in case that data count is more then 4 */ + for (data_cnt = 0; data_cnt < data_size; data_cnt += 4) { + /* + * after sending 4bytes per one time, + * send remainder data less then 4. + */ + if ((data_size - data_cnt) < 4) { + if ((data_size - data_cnt) == 3) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16; + dev_dbg(dsim->dev, "count = 3 payload = %x, %x %x %x\n", + payload, data0[data_cnt], + data0[data_cnt + 1], + data0[data_cnt + 2]); + } else if ((data_size - data_cnt) == 2) { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8; + dev_dbg(dsim->dev, + "count = 2 payload = %x, %x %x\n", payload, + data0[data_cnt], + data0[data_cnt + 1]); + } else if ((data_size - data_cnt) == 1) { + payload = data0[data_cnt]; + } + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + /* send 4bytes per one time. */ + } else { + payload = data0[data_cnt] | + data0[data_cnt + 1] << 8 | + data0[data_cnt + 2] << 16 | + data0[data_cnt + 3] << 24; + + dev_dbg(dsim->dev, + "count = 4 payload = %x, %x %x %x %x\n", + payload, *(u8 *)(data0 + data_cnt), + data0[data_cnt + 1], + data0[data_cnt + 2], + data0[data_cnt + 3]); + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + } + } +} + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + const unsigned char *data0, unsigned int data_size) +{ + unsigned int check_rx_ack = 0; + + if (dsim->state == DSIM_STATE_ULPS) { + dev_err(dsim->dev, "state is ULPS.\n"); + + return -EINVAL; + } + + /* FIXME!!! why does it need this delay? */ + msleep(20); + + mutex_lock(&dsim->lock); + + switch (data_id) { + /* short packet types of packet types for command. */ + case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM: + case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM: + case MIPI_DSI_DCS_SHORT_WRITE: + case MIPI_DSI_DCS_SHORT_WRITE_PARAM: + case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]); + if (check_rx_ack) { + /* process response func should be implemented */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + /* general command */ + case MIPI_DSI_COLOR_MODE_OFF: + case MIPI_DSI_COLOR_MODE_ON: + case MIPI_DSI_SHUTDOWN_PERIPHERAL: + case MIPI_DSI_TURN_ON_PERIPHERAL: + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data0[0], data0[1]); + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + /* packet types for video data */ + case MIPI_DSI_V_SYNC_START: + case MIPI_DSI_V_SYNC_END: + case MIPI_DSI_H_SYNC_START: + case MIPI_DSI_H_SYNC_END: + case MIPI_DSI_END_OF_TRANSMISSION: + mutex_unlock(&dsim->lock); + return 0; + + /* long packet type and null packet */ + case MIPI_DSI_NULL_PACKET: + case MIPI_DSI_BLANKING_PACKET: + mutex_unlock(&dsim->lock); + return 0; + case MIPI_DSI_GENERIC_LONG_WRITE: + case MIPI_DSI_DCS_LONG_WRITE: + { + unsigned int size, payload = 0; + INIT_COMPLETION(dsim_wr_comp); + + size = data_size * 4; + + /* if data count is less then 4, then send 3bytes data. */ + if (data_size < 4) { + payload = data0[0] | + data0[1] << 8 | + data0[2] << 16; + + exynos_mipi_dsi_wr_tx_data(dsim, payload); + + dev_dbg(dsim->dev, "count = %d payload = %x,%x %x %x\n", + data_size, payload, data0[0], + data0[1], data0[2]); + + /* in case that data count is more then 4 */ + } else + exynos_mipi_dsi_long_data_wr(dsim, data0, data_size); + + /* put data into header fifo */ + exynos_mipi_dsi_wr_tx_header(dsim, data_id, data_size & 0xff, + (data_size & 0xff00) >> 8); + + if (!wait_for_completion_interruptible_timeout(&dsim_wr_comp, + MIPI_FIFO_TIMEOUT)) { + dev_warn(dsim->dev, "command write timeout.\n"); + mutex_unlock(&dsim->lock); + return -EAGAIN; + } + + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + } + + /* packet typo for video data */ + case MIPI_DSI_PACKED_PIXEL_STREAM_16: + case MIPI_DSI_PACKED_PIXEL_STREAM_18: + case MIPI_DSI_PIXEL_STREAM_3BYTE_18: + case MIPI_DSI_PACKED_PIXEL_STREAM_24: + if (check_rx_ack) { + /* process response func should be implemented. */ + mutex_unlock(&dsim->lock); + return 0; + } else { + mutex_unlock(&dsim->lock); + return -EINVAL; + } + default: + dev_warn(dsim->dev, + "data id %x is not supported current DSI spec.\n", + data_id); + + mutex_unlock(&dsim->lock); + return -EINVAL; + } + + mutex_unlock(&dsim->lock); + return 0; +} + +static unsigned int exynos_mipi_dsi_long_data_rd(struct mipi_dsim_device *dsim, + unsigned int req_size, unsigned int rx_data, u8 *rx_buf) +{ + unsigned int rcv_pkt, i, j; + u16 rxsize; + + /* for long packet */ + rxsize = (u16)((rx_data & 0x00ffff00) >> 8); + dev_dbg(dsim->dev, "mipi dsi rx size : %d\n", rxsize); + if (rxsize != req_size) { + dev_dbg(dsim->dev, + "received size mismatch received: %d, requested: %d\n", + rxsize, req_size); + goto err; + } + + for (i = 0; i < (rxsize >> 2); i++) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + dev_dbg(dsim->dev, "received pkt : %08x\n", rcv_pkt); + for (j = 0; j < 4; j++) { + rx_buf[(i * 4) + j] = + (u8)(rcv_pkt >> (j * 8)) & 0xff; + dev_dbg(dsim->dev, "received value : %02x\n", + (rcv_pkt >> (j * 8)) & 0xff); + } + } + if (rxsize % 4) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + dev_dbg(dsim->dev, "received pkt : %08x\n", rcv_pkt); + for (j = 0; j < (rxsize % 4); j++) { + rx_buf[(i * 4) + j] = + (u8)(rcv_pkt >> (j * 8)) & 0xff; + dev_dbg(dsim->dev, "received value : %02x\n", + (rcv_pkt >> (j * 8)) & 0xff); + } + } + + return rxsize; + +err: + return -EINVAL; +} + +static unsigned int exynos_mipi_dsi_response_size(unsigned int req_size) +{ + switch (req_size) { + case 1: + return MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE; + case 2: + return MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE; + default: + return MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE; + } +} + +int exynos_mipi_dsi_rd_data(struct mipi_dsim_device *dsim, unsigned int data_id, + unsigned int data0, unsigned int req_size, u8 *rx_buf) +{ + unsigned int rx_data, rcv_pkt, i; + u8 response = 0; + u16 rxsize; + + if (dsim->state == DSIM_STATE_ULPS) { + dev_err(dsim->dev, "state is ULPS.\n"); + + return -EINVAL; + } + + /* FIXME!!! */ + msleep(20); + + mutex_lock(&dsim->lock); + INIT_COMPLETION(dsim_rd_comp); + exynos_mipi_dsi_rd_tx_header(dsim, + MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE, req_size); + + response = exynos_mipi_dsi_response_size(req_size); + + switch (data_id) { + case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM: + case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM: + case MIPI_DSI_DCS_READ: + exynos_mipi_dsi_rd_tx_header(dsim, + data_id, data0); + /* process response func should be implemented. */ + break; + default: + dev_warn(dsim->dev, + "data id %x is not supported current DSI spec.\n", + data_id); + + return -EINVAL; + } + + if (!wait_for_completion_interruptible_timeout(&dsim_rd_comp, + MIPI_FIFO_TIMEOUT)) { + pr_err("RX done interrupt timeout\n"); + mutex_unlock(&dsim->lock); + return 0; + } + + msleep(20); + + rx_data = exynos_mipi_dsi_rd_rx_fifo(dsim); + + if ((u8)(rx_data & 0xff) != response) { + printk(KERN_ERR + "mipi dsi wrong response rx_data : %x, response:%x\n", + rx_data, response); + goto clear_rx_fifo; + } + + if (req_size <= 2) { + /* for short packet */ + for (i = 0; i < req_size; i++) + rx_buf[i] = (rx_data >> (8 + (i * 8))) & 0xff; + rxsize = req_size; + } else { + /* for long packet */ + rxsize = exynos_mipi_dsi_long_data_rd(dsim, req_size, rx_data, + rx_buf); + if (rxsize != req_size) + goto clear_rx_fifo; + } + + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + + msleep(20); + + if (rcv_pkt != MIPI_RX_FIFO_READ_DONE) { + dev_info(dsim->dev, + "Can't found RX FIFO READ DONE FLAG : %x\n", rcv_pkt); + goto clear_rx_fifo; + } + + mutex_unlock(&dsim->lock); + + return rxsize; + +clear_rx_fifo: + i = 0; + while (1) { + rcv_pkt = exynos_mipi_dsi_rd_rx_fifo(dsim); + if ((rcv_pkt == MIPI_RX_FIFO_READ_DONE) + || (i > MIPI_MAX_RX_FIFO)) + break; + dev_dbg(dsim->dev, + "mipi dsi clear rx fifo : %08x\n", rcv_pkt); + i++; + } + dev_info(dsim->dev, + "mipi dsi rx done count : %d, rcv_pkt : %08x\n", i, rcv_pkt); + + mutex_unlock(&dsim->lock); + + return 0; +} + +static int exynos_mipi_dsi_pll_on(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + int sw_timeout; + + if (enable) { + sw_timeout = 1000; + + exynos_mipi_dsi_enable_pll(dsim, 1); + while (1) { + sw_timeout--; + if (exynos_mipi_dsi_is_pll_stable(dsim)) + return 0; + if (sw_timeout == 0) + return -EINVAL; + } + } else + exynos_mipi_dsi_enable_pll(dsim, |