diff options
Diffstat (limited to 'drivers/video')
39 files changed, 7958 insertions, 185 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6ca0c407c14..8951cbd2d2f 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1123,6 +1123,18 @@ config FB_RIVA_BACKLIGHT help Say Y here if you want to control the backlight of your display. +config FB_I740 + tristate "Intel740 support (EXPERIMENTAL)" + depends on EXPERIMENTAL && FB && PCI + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VGASTATE + select FB_DDC + help + This driver supports graphics cards based on Intel740 chip. + config FB_I810 tristate "Intel 810/815 support (EXPERIMENTAL)" depends on EXPERIMENTAL && FB && PCI && X86_32 && AGP_INTEL @@ -2411,7 +2423,7 @@ config FB_PUV3_UNIGFX source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" - +source "drivers/video/exynos/Kconfig" source "drivers/video/backlight/Kconfig" if VT diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 142606814d9..9356add945b 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -15,6 +15,8 @@ obj-$(CONFIG_VT) += console/ obj-$(CONFIG_LOGO) += logo/ obj-y += backlight/ +obj-$(CONFIG_EXYNOS_VIDEO) += exynos/ + obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o @@ -37,6 +39,7 @@ obj-$(CONFIG_FB_GRVGA) += grvga.o obj-$(CONFIG_FB_PM2) += pm2fb.o obj-$(CONFIG_FB_PM3) += pm3fb.o +obj-$(CONFIG_FB_I740) += i740fb.o obj-$(CONFIG_FB_MATROX) += matrox/ obj-$(CONFIG_FB_RIVA) += riva/ obj-$(CONFIG_FB_NVIDIA) += nvidia/ diff --git a/drivers/video/atmel_lcdfb.c b/drivers/video/atmel_lcdfb.c index e40c00f2c2b..d99505b1637 100644 --- a/drivers/video/atmel_lcdfb.c +++ b/drivers/video/atmel_lcdfb.c @@ -421,24 +421,18 @@ static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var, var->red.length = var->green.length = var->blue.length = var->bits_per_pixel; break; - case 15: case 16: if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB) { /* RGB:565 mode */ var->red.offset = 11; var->blue.offset = 0; - var->green.length = 6; - } else if (sinfo->lcd_wiring_mode == ATMEL_LCDC_WIRING_RGB555) { - var->red.offset = 10; - var->blue.offset = 0; - var->green.length = 5; } else { - /* BGR:555 mode */ + /* BGR:565 mode */ var->red.offset = 0; - var->blue.offset = 10; - var->green.length = 5; + var->blue.offset = 11; } var->green.offset = 5; + var->green.length = 6; var->red.length = var->blue.length = 5; break; case 32: diff --git a/drivers/video/au1100fb.c b/drivers/video/au1100fb.c index de9da6774fd..befcbd8ef01 100644 --- a/drivers/video/au1100fb.c +++ b/drivers/video/au1100fb.c @@ -477,7 +477,8 @@ static int __devinit au1100fb_drv_probe(struct platform_device *dev) u32 sys_clksrc; /* Allocate new device private */ - fbdev = kzalloc(sizeof(struct au1100fb_device), GFP_KERNEL); + fbdev = devm_kzalloc(&dev->dev, sizeof(struct au1100fb_device), + GFP_KERNEL); if (!fbdev) { print_err("fail to allocate device private record"); return -ENOMEM; @@ -498,8 +499,9 @@ static int __devinit au1100fb_drv_probe(struct platform_device *dev) au1100fb_fix.mmio_start = regs_res->start; au1100fb_fix.mmio_len = resource_size(regs_res); - if (!request_mem_region(au1100fb_fix.mmio_start, au1100fb_fix.mmio_len, - DRIVER_NAME)) { + if (!devm_request_mem_region(au1100fb_fix.mmio_start, + au1100fb_fix.mmio_len, + DRIVER_NAME)) { print_err("fail to lock memory region at 0x%08lx", au1100fb_fix.mmio_start); return -EBUSY; @@ -514,8 +516,9 @@ static int __devinit au1100fb_drv_probe(struct platform_device *dev) fbdev->fb_len = fbdev->panel->xres * fbdev->panel->yres * (fbdev->panel->bpp >> 3) * AU1100FB_NBR_VIDEO_BUFFERS; - fbdev->fb_mem = dma_alloc_coherent(&dev->dev, PAGE_ALIGN(fbdev->fb_len), - &fbdev->fb_phys, GFP_KERNEL); + fbdev->fb_mem = dmam_alloc_coherent(&dev->dev, &dev->dev, + PAGE_ALIGN(fbdev->fb_len), + &fbdev->fb_phys, GFP_KERNEL); if (!fbdev->fb_mem) { print_err("fail to allocate frambuffer (size: %dK))", fbdev->fb_len / 1024); @@ -557,14 +560,14 @@ static int __devinit au1100fb_drv_probe(struct platform_device *dev) fbdev->info.fbops = &au1100fb_ops; fbdev->info.fix = au1100fb_fix; - if (!(fbdev->info.pseudo_palette = kzalloc(sizeof(u32) * 16, GFP_KERNEL))) { + fbdev->info.pseudo_palette = + devm_kzalloc(&dev->dev, sizeof(u32) * 16, GFP_KERNEL); + if (!fbdev->info.pseudo_palette) return -ENOMEM; - } if (fb_alloc_cmap(&fbdev->info.cmap, AU1100_LCD_NBR_PALETTE_ENTRIES, 0) < 0) { print_err("Fail to allocate colormap (%d entries)", AU1100_LCD_NBR_PALETTE_ENTRIES); - kfree(fbdev->info.pseudo_palette); return -EFAULT; } @@ -582,9 +585,6 @@ static int __devinit au1100fb_drv_probe(struct platform_device *dev) return 0; failed: - if (fbdev->regs) { - release_mem_region(fbdev->regs_phys, fbdev->regs_len); - } if (fbdev->fb_mem) { dma_free_noncoherent(&dev->dev, fbdev->fb_len, fbdev->fb_mem, fbdev->fb_phys); @@ -592,10 +592,9 @@ failed: if (fbdev->info.cmap.len != 0) { fb_dealloc_cmap(&fbdev->info.cmap); } - kfree(fbdev); platform_set_drvdata(dev, NULL); - return 0; + return -ENODEV; } int au1100fb_drv_remove(struct platform_device *dev) @@ -615,14 +614,7 @@ int au1100fb_drv_remove(struct platform_device *dev) /* Clean up all probe data */ unregister_framebuffer(&fbdev->info); - release_mem_region(fbdev->regs_phys, fbdev->regs_len); - - dma_free_coherent(&dev->dev, PAGE_ALIGN(fbdev->fb_len), fbdev->fb_mem, - fbdev->fb_phys); - fb_dealloc_cmap(&fbdev->info.cmap); - kfree(fbdev->info.pseudo_palette); - kfree((void*)fbdev); return 0; } diff --git a/drivers/video/au1200fb.c b/drivers/video/au1200fb.c index 04e4479d5af..3e9a773db09 100644 --- a/drivers/video/au1200fb.c +++ b/drivers/video/au1200fb.c @@ -1724,7 +1724,7 @@ static int __devinit au1200fb_drv_probe(struct platform_device *dev) /* Allocate the framebuffer to the maximum screen size */ fbdev->fb_len = (win->w[plane].xres * win->w[plane].yres * bpp) / 8; - fbdev->fb_mem = dma_alloc_noncoherent(&dev->dev, + fbdev->fb_mem = dmam_alloc_noncoherent(&dev->dev, &dev->dev, PAGE_ALIGN(fbdev->fb_len), &fbdev->fb_phys, GFP_KERNEL); if (!fbdev->fb_mem) { @@ -1788,9 +1788,6 @@ static int __devinit au1200fb_drv_probe(struct platform_device *dev) failed: /* NOTE: This only does the current plane/window that failed; others are still active */ - if (fbdev->fb_mem) - dma_free_noncoherent(&dev->dev, PAGE_ALIGN(fbdev->fb_len), - fbdev->fb_mem, fbdev->fb_phys); if (fbi) { if (fbi->cmap.len != 0) fb_dealloc_cmap(&fbi->cmap); @@ -1817,10 +1814,6 @@ static int __devexit au1200fb_drv_remove(struct platform_device *dev) /* Clean up all probe data */ unregister_framebuffer(fbi); - if (fbdev->fb_mem) - dma_free_noncoherent(&dev->dev, - PAGE_ALIGN(fbdev->fb_len), - fbdev->fb_mem, fbdev->fb_phys); if (fbi->cmap.len != 0) fb_dealloc_cmap(&fbi->cmap); kfree(fbi->pseudo_palette); diff --git a/drivers/video/bf537-lq035.c b/drivers/video/bf537-lq035.c index bea53c1a495..befbc80d11f 100644 --- a/drivers/video/bf537-lq035.c +++ b/drivers/video/bf537-lq035.c @@ -383,23 +383,19 @@ static int __devinit request_ports(void) } #if (defined(UD) && defined(LBR)) - if (gpio_request(UD, KBUILD_MODNAME)) { + if (gpio_request_one(UD, GPIOF_OUT_INIT_LOW, KBUILD_MODNAME)) { pr_err("requesting GPIO %d failed\n", UD); return -EBUSY; } - if (gpio_request(LBR, KBUILD_MODNAME)) { + if (gpio_request_one(LBR, GPIOF_OUT_INIT_HIGH, KBUILD_MODNAME)) { pr_err("requesting GPIO %d failed\n", LBR); gpio_free(UD); return -EBUSY; } - - gpio_direction_output(UD, 0); - gpio_direction_output(LBR, 1); - #endif - if (gpio_request(MOD, KBUILD_MODNAME)) { + if (gpio_request_one(MOD, GPIOF_OUT_INIT_HIGH, KBUILD_MODNAME)) { pr_err("requesting GPIO %d failed\n", MOD); #if (defined(UD) && defined(LBR)) gpio_free(LBR); @@ -408,8 +404,6 @@ static int __devinit request_ports(void) return -EBUSY; } - gpio_direction_output(MOD, 1); - SSYNC(); return 0; } diff --git a/drivers/video/bf54x-lq043fb.c b/drivers/video/bf54x-lq043fb.c index 46b03f53985..dc2f0047769 100644 --- a/drivers/video/bf54x-lq043fb.c +++ b/drivers/video/bf54x-lq043fb.c @@ -240,7 +240,7 @@ static int request_ports(struct bfin_bf54xfb_info *fbi) u16 eppi_req_18[] = EPPI0_18; u16 disp = fbi->mach_info->disp; - if (gpio_request(disp, DRIVER_NAME)) { + if (gpio_request_one(disp, GPIOF_OUT_INIT_HIGH, DRIVER_NAME)) { printk(KERN_ERR "Requesting GPIO %d failed\n", disp); return -EFAULT; } @@ -263,8 +263,6 @@ static int request_ports(struct bfin_bf54xfb_info *fbi) } } - gpio_direction_output(disp, 1); - return 0; } diff --git a/drivers/video/bfin-lq035q1-fb.c b/drivers/video/bfin-lq035q1-fb.c index c633068372c..86922ac8441 100644 --- a/drivers/video/bfin-lq035q1-fb.c +++ b/drivers/video/bfin-lq035q1-fb.c @@ -365,10 +365,10 @@ static int __devinit bfin_lq035q1_request_ports(struct platform_device *pdev, * Drive PPI_FS3 Low */ if (ANOMALY_05000400) { - int ret = gpio_request(P_IDENT(P_PPI0_FS3), "PPI_FS3"); + int ret = gpio_request_one(P_IDENT(P_PPI0_FS3), + GPIOF_OUT_INIT_LOW, "PPI_FS3"); if (ret) return ret; - gpio_direction_output(P_IDENT(P_PPI0_FS3), 0); } if (ppi16) @@ -716,14 +716,14 @@ static int __devinit bfin_lq035q1_probe(struct platform_device *pdev) } if (info->disp_info->use_bl) { - ret = gpio_request(info->disp_info->gpio_bl, "LQ035 Backlight"); + ret = gpio_request_one(info->disp_info->gpio_bl, + GPIOF_OUT_INIT_LOW, "LQ035 Backlight"); if (ret) { dev_err(&pdev->dev, "failed to request GPIO %d\n", info->disp_info->gpio_bl); goto out9; } - gpio_direction_output(info->disp_info->gpio_bl, 0); } ret = register_framebuffer(fbinfo); diff --git a/drivers/video/bfin_adv7393fb.c b/drivers/video/bfin_adv7393fb.c index 811dd7f6aa4..c9928142921 100644 --- a/drivers/video/bfin_adv7393fb.c +++ b/drivers/video/bfin_adv7393fb.c @@ -411,12 +411,13 @@ static int __devinit bfin_adv7393_fb_probe(struct i2c_client *client, /* Workaround "PPI Does Not Start Properly In Specific Mode" */ if (ANOMALY_05000400) { - if (gpio_request(P_IDENT(P_PPI0_FS3), "PPI0_FS3")) { + ret = gpio_request_one(P_IDENT(P_PPI0_FS3), GPIOF_OUT_INIT_LOW, + "PPI0_FS3") + if (ret) { dev_err(&client->dev, "PPI0_FS3 GPIO request failed\n"); ret = -EBUSY; goto out_8; } - gpio_direction_output(P_IDENT(P_PPI0_FS3), 0); } if (peripheral_request_list(ppi_pins, DRIVER_NAME)) { diff --git a/drivers/video/da8xx-fb.c b/drivers/video/da8xx-fb.c index 29577bf1f55..dd80386b0ed 100644 --- a/drivers/video/da8xx-fb.c +++ b/drivers/video/da8xx-fb.c @@ -32,6 +32,7 @@ #include <linux/console.h> #include <linux/slab.h> #include <video/da8xx-fb.h> +#include <asm/div64.h> #define DRIVER_NAME "da8xx_lcdc" @@ -161,6 +162,7 @@ struct da8xx_fb_par { int vsync_timeout; #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; + unsigned int lcd_fck_rate; #endif void (*panel_power_ctrl)(int); }; @@ -174,7 +176,6 @@ static struct fb_var_screeninfo da8xx_fb_var __devinitdata = { .activate = 0, .height = -1, .width = -1, - .pixclock = 46666, /* 46us - AUO display */ .accel_flags = 0, .left_margin = LEFT_MARGIN, .right_margin = RIGHT_MARGIN, @@ -840,11 +841,13 @@ static int lcd_da8xx_cpufreq_transition(struct notifier_block *nb, struct da8xx_fb_par *par; par = container_of(nb, struct da8xx_fb_par, freq_transition); - if (val == CPUFREQ_PRECHANGE) { - lcd_disable_raster(); - } else if (val == CPUFREQ_POSTCHANGE) { - lcd_calc_clk_divider(par); - lcd_enable_raster(); + if (val == CPUFREQ_POSTCHANGE) { + if (par->lcd_fck_rate != clk_get_rate(par->lcdc_clk)) { + par->lcd_fck_rate = clk_get_rate(par->lcdc_clk); + lcd_disable_raster(); + lcd_calc_clk_divider(par); + lcd_enable_raster(); + } } return 0; @@ -1048,6 +1051,22 @@ static struct fb_ops da8xx_fb_ops = { .fb_blank = cfb_blank, }; +/* Calculate and return pixel clock period in pico seconds */ +static unsigned int da8xxfb_pixel_clk_period(struct da8xx_fb_par *par) +{ + unsigned int lcd_clk, div; + unsigned int configured_pix_clk; + unsigned long long pix_clk_period_picosec = 1000000000000ULL; + + lcd_clk = clk_get_rate(par->lcdc_clk); + div = lcd_clk / par->pxl_clk; + configured_pix_clk = (lcd_clk / div); + + do_div(pix_clk_period_picosec, configured_pix_clk); + + return pix_clk_period_picosec; +} + static int __devinit fb_probe(struct platform_device *device) { struct da8xx_lcdc_platform_data *fb_pdata = @@ -1137,6 +1156,9 @@ static int __devinit fb_probe(struct platform_device *device) par = da8xx_fb_info->par; par->lcdc_clk = fb_clk; +#ifdef CONFIG_CPU_FREQ + par->lcd_fck_rate = clk_get_rate(fb_clk); +#endif par->pxl_clk = lcdc_info->pxl_clk; if (fb_pdata->panel_power_ctrl) { par->panel_power_ctrl = fb_pdata->panel_power_ctrl; @@ -1209,6 +1231,7 @@ static int __devinit fb_probe(struct platform_device *device) da8xx_fb_var.hsync_len = lcdc_info->hsw; da8xx_fb_var.vsync_len = lcdc_info->vsw; + da8xx_fb_var.pixclock = da8xxfb_pixel_clk_period(par); /* Initialize fbinfo */ da8xx_fb_info->flags = FBINFO_FLAG_DEFAULT; @@ -1264,8 +1287,8 @@ static int __devinit fb_probe(struct platform_device *device) irq_freq: #ifdef CONFIG_CPU_FREQ lcd_da8xx_cpufreq_deregister(par); -#endif err_cpu_freq: +#endif unregister_framebuffer(da8xx_fb_info); err_dealloc_cmap: diff --git a/drivers/video/exynos/Kconfig b/drivers/video/exynos/Kconfig new file mode 100644 index 00000000000..1b035b2eb6b --- /dev/null +++ b/drivers/video/exynos/Kconfig @@ -0,0 +1,37 @@ +# +# 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. + +config EXYNOS_LCD_S6E8AX0 + bool "S6E8AX0 MIPI AMOLED LCD Driver" + depends on (EXYNOS_MIPI_DSI && BACKLIGHT_CLASS_DEVICE && LCD_CLASS_DEVICE) + default n + help + If you have an S6E8AX0 MIPI AMOLED LCD Panel, say Y to enable its + LCD control driver. + +config EXYNOS_DP + bool "EXYNOS DP driver support" + depends on ARCH_EXYNOS + default n + help + This enables support for DP device. + +endif # EXYNOS_VIDEO diff --git a/drivers/video/exynos/Makefile b/drivers/video/exynos/Makefile new file mode 100644 index 00000000000..ec7772e452a --- /dev/null +++ b/drivers/video/exynos/Makefile @@ -0,0 +1,8 @@ +# +# 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 +obj-$(CONFIG_EXYNOS_LCD_S6E8AX0) += s6e8ax0.o +obj-$(CONFIG_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o diff --git a/drivers/video/exynos/exynos_dp_core.c b/drivers/video/exynos/exynos_dp_core.c new file mode 100644 index 00000000000..2a4481cf260 --- /dev/null +++ b/drivers/video/exynos/exynos_dp_core.c @@ -0,0 +1,1058 @@ +/* + * Samsung SoC DP (Display Port) interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <video/exynos_dp.h> + +#include <plat/cpu.h> + +#include "exynos_dp_core.h" + +static int exynos_dp_init_dp(struct exynos_dp_device *dp) +{ + exynos_dp_reset(dp); + + /* SW defined function Normal operation */ + exynos_dp_enable_sw_function(dp); + + exynos_dp_config_interrupt(dp); + exynos_dp_init_analog_func(dp); + + exynos_dp_init_hpd(dp); + exynos_dp_init_aux(dp); + + return 0; +} + +static int exynos_dp_detect_hpd(struct exynos_dp_device *dp) +{ + int timeout_loop = 0; + + exynos_dp_init_hpd(dp); + + udelay(200); + + while (exynos_dp_get_plug_in_status(dp) != 0) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "failed to get hpd plug status\n"); + return -ETIMEDOUT; + } + udelay(10); + } + + return 0; +} + +static unsigned char exynos_dp_calc_edid_check_sum(unsigned char *edid_data) +{ + int i; + unsigned char sum = 0; + + for (i = 0; i < EDID_BLOCK_LENGTH; i++) + sum = sum + edid_data[i]; + + return sum; +} + +static int exynos_dp_read_edid(struct exynos_dp_device *dp) +{ + unsigned char edid[EDID_BLOCK_LENGTH * 2]; + unsigned int extend_block = 0; + unsigned char sum; + unsigned char test_vector; + int retval; + + /* + * EDID device address is 0x50. + * However, if necessary, you must have set upper address + * into E-EDID in I2C device, 0x30. + */ + + /* Read Extension Flag, Number of 128-byte EDID extension blocks */ + exynos_dp_read_byte_from_i2c(dp, I2C_EDID_DEVICE_ADDR, + EDID_EXTENSION_FLAG, + &extend_block); + + if (extend_block > 0) { + dev_dbg(dp->dev, "EDID data includes a single extension!\n"); + + /* Read EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, I2C_EDID_DEVICE_ADDR, + EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, + &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + /* Read additional EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_BLOCK_LENGTH, + EDID_BLOCK_LENGTH, + &edid[EDID_BLOCK_LENGTH]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + exynos_dp_read_byte_from_dpcd(dp, DPCD_ADDR_TEST_REQUEST, + &test_vector); + if (test_vector & DPCD_TEST_EDID_READ) { + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TEST_EDID_CHECKSUM, + edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]); + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TEST_RESPONSE, + DPCD_TEST_EDID_CHECKSUM_WRITE); + } + } else { + dev_info(dp->dev, "EDID data does not include any extensions.\n"); + + /* Read EDID data */ + retval = exynos_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, + &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = exynos_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + exynos_dp_read_byte_from_dpcd(dp, + DPCD_ADDR_TEST_REQUEST, + &test_vector); + if (test_vector & DPCD_TEST_EDID_READ) { + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TEST_EDID_CHECKSUM, + edid[EDID_CHECKSUM]); + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TEST_RESPONSE, + DPCD_TEST_EDID_CHECKSUM_WRITE); + } + } + + dev_err(dp->dev, "EDID Read success!\n"); + return 0; +} + +static int exynos_dp_handle_edid(struct exynos_dp_device *dp) +{ + u8 buf[12]; + int i; + int retval; + + /* Read DPCD DPCD_ADDR_DPCD_REV~RECEIVE_PORT1_CAP_1 */ + exynos_dp_read_bytes_from_dpcd(dp, + DPCD_ADDR_DPCD_REV, + 12, buf); + + /* Read EDID */ + for (i = 0; i < 3; i++) { + retval = exynos_dp_read_edid(dp); + if (retval == 0) + break; + } + + return retval; +} + +static void exynos_dp_enable_rx_to_enhanced_mode(struct exynos_dp_device *dp, + bool enable) +{ + u8 data; + + exynos_dp_read_byte_from_dpcd(dp, DPCD_ADDR_LANE_COUNT_SET, &data); + + if (enable) + exynos_dp_write_byte_to_dpcd(dp, DPCD_ADDR_LANE_COUNT_SET, + DPCD_ENHANCED_FRAME_EN | + DPCD_LANE_COUNT_SET(data)); + else + exynos_dp_write_byte_to_dpcd(dp, DPCD_ADDR_LANE_COUNT_SET, + DPCD_LANE_COUNT_SET(data)); +} + +static int exynos_dp_is_enhanced_mode_available(struct exynos_dp_device *dp) +{ + u8 data; + int retval; + + exynos_dp_read_byte_from_dpcd(dp, DPCD_ADDR_MAX_LANE_COUNT, &data); + retval = DPCD_ENHANCED_FRAME_CAP(data); + + return retval; +} + +static void exynos_dp_set_enhanced_mode(struct exynos_dp_device *dp) +{ + u8 data; + + data = exynos_dp_is_enhanced_mode_available(dp); + exynos_dp_enable_rx_to_enhanced_mode(dp, data); + exynos_dp_enable_enhanced_mode(dp, data); +} + +static void exynos_dp_training_pattern_dis(struct exynos_dp_device *dp) +{ + exynos_dp_set_training_pattern(dp, DP_NONE); + + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + DPCD_TRAINING_PATTERN_DISABLED); +} + +static void exynos_dp_set_lane_lane_pre_emphasis(struct exynos_dp_device *dp, + int pre_emphasis, int lane) +{ + switch (lane) { + case 0: + exynos_dp_set_lane0_pre_emphasis(dp, pre_emphasis); + break; + case 1: + exynos_dp_set_lane1_pre_emphasis(dp, pre_emphasis); + break; + + case 2: + exynos_dp_set_lane2_pre_emphasis(dp, pre_emphasis); + break; + + case 3: + exynos_dp_set_lane3_pre_emphasis(dp, pre_emphasis); + break; + } +} + +static void exynos_dp_link_start(struct exynos_dp_device *dp) +{ + u8 buf[5]; + int lane; + int lane_count; + + lane_count = dp->link_train.lane_count; + + dp->link_train.lt_state = CLOCK_RECOVERY; + dp->link_train.eq_loop = 0; + + for (lane = 0; lane < lane_count; lane++) + dp->link_train.cr_loop[lane] = 0; + + /* Set sink to D0 (Sink Not Ready) mode. */ + exynos_dp_write_byte_to_dpcd(dp, DPCD_ADDR_SINK_POWER_STATE, + DPCD_SET_POWER_STATE_D0); + + /* Set link rate and count as you want to establish*/ + exynos_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + exynos_dp_set_lane_count(dp, dp->link_train.lane_count); + + /* Setup RX configuration */ + buf[0] = dp->link_train.link_rate; + buf[1] = dp->link_train.lane_count; + exynos_dp_write_bytes_to_dpcd(dp, DPCD_ADDR_LINK_BW_SET, + 2, buf); + + /* Set TX pre-emphasis to minimum */ + for (lane = 0; lane < lane_count; lane++) + exynos_dp_set_lane_lane_pre_emphasis(dp, + PRE_EMPHASIS_LEVEL_0, lane); + + /* Set training pattern 1 */ + exynos_dp_set_training_pattern(dp, TRAINING_PTN1); + + /* Set RX training pattern */ + buf[0] = DPCD_SCRAMBLING_DISABLED | + DPCD_TRAINING_PATTERN_1; + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, buf[0]); + + for (lane = 0; lane < lane_count; lane++) + buf[lane] = DPCD_PRE_EMPHASIS_PATTERN2_LEVEL0 | + DPCD_VOLTAGE_SWING_PATTERN1_LEVEL0; + exynos_dp_write_bytes_to_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + lane_count, buf); +} + +static unsigned char exynos_dp_get_lane_status(u8 link_status[6], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = link_status[lane>>1]; + + return (link_value >> shift) & 0xf; +} + +static int exynos_dp_clock_recovery_ok(u8 link_status[6], int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = exynos_dp_get_lane_status(link_status, lane); + if ((lane_status & DPCD_LANE_CR_DONE) == 0) + return -EINVAL; + } + return 0; +} + +static int exynos_dp_channel_eq_ok(u8 link_status[6], int lane_count) +{ + int lane; + u8 lane_align; + u8 lane_status; + + lane_align = link_status[2]; + if ((lane_align == DPCD_INTERLANE_ALIGN_DONE) == 0) + return -EINVAL; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = exynos_dp_get_lane_status(link_status, lane); + lane_status &= DPCD_CHANNEL_EQ_BITS; + if (lane_status != DPCD_CHANNEL_EQ_BITS) + return -EINVAL; + } + return 0; +} + +static unsigned char exynos_dp_get_adjust_request_voltage(u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane>>1]; + + return (link_value >> shift) & 0x3; +} + +static unsigned char exynos_dp_get_adjust_request_pre_emphasis( + u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane>>1]; + + return ((link_value >> shift) & 0xc) >> 2; +} + +static void exynos_dp_set_lane_link_training(struct exynos_dp_device *dp, + u8 training_lane_set, int lane) +{ + switch (lane) { + case 0: + exynos_dp_set_lane0_link_training(dp, training_lane_set); + break; + case 1: + exynos_dp_set_lane1_link_training(dp, training_lane_set); + break; + + case 2: + exynos_dp_set_lane2_link_training(dp, training_lane_set); + break; + + case 3: + exynos_dp_set_lane3_link_training(dp, training_lane_set); + break; + } +} + +static unsigned int exynos_dp_get_lane_link_training( + struct exynos_dp_device *dp, + int lane) +{ + u32 reg; + + switch (lane) { + case 0: + reg = exynos_dp_get_lane0_link_training(dp); + break; + case 1: + reg = exynos_dp_get_lane1_link_training(dp); + break; + case 2: + reg = exynos_dp_get_lane2_link_training(dp); + break; + case 3: + reg = exynos_dp_get_lane3_link_training(dp); + break; + } + + return reg; +} + +static void exynos_dp_reduce_link_rate(struct exynos_dp_device *dp) +{ + if (dp->link_train.link_rate == LINK_RATE_2_70GBPS) { + /* set to reduced bit rate */ + dp->link_train.link_rate = LINK_RATE_1_62GBPS; + dev_err(dp->dev, "set to bandwidth %.2x\n", + dp->link_train.link_rate); + dp->link_train.lt_state = START; + } else { + exynos_dp_training_pattern_dis(dp); + /* set enhanced mode if available */ + exynos_dp_set_enhanced_mode(dp); + dp->link_train.lt_state = FAILED; + } +} + +static void exynos_dp_get_adjust_train(struct exynos_dp_device *dp, + u8 adjust_request[2]) +{ + int lane; + int lane_count; + u8 voltage_swing; + u8 pre_emphasis; + u8 training_lane; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + voltage_swing = exynos_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | + DPCD_PRE_EMPHASIS_SET(pre_emphasis); + + if (voltage_swing == VOLTAGE_LEVEL_3 || + pre_emphasis == PRE_EMPHASIS_LEVEL_3) { + training_lane |= DPCD_MAX_SWING_REACHED; + training_lane |= DPCD_MAX_PRE_EMPHASIS_REACHED; + } + dp->link_train.training_lane[lane] = training_lane; + } +} + +static int exynos_dp_check_max_cr_loop(struct exynos_dp_device *dp, + u8 voltage_swing) +{ + int lane; + int lane_count; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + if (voltage_swing == VOLTAGE_LEVEL_3 || + dp->link_train.cr_loop[lane] == MAX_CR_LOOP) + return -EINVAL; + } + return 0; +} + +static int exynos_dp_process_clock_recovery(struct exynos_dp_device *dp) +{ + u8 data; + u8 link_status[6]; + int lane; + int lane_count; + u8 buf[5]; + + u8 *adjust_request; + u8 voltage_swing; + u8 pre_emphasis; + u8 training_lane; + + udelay(100); + + exynos_dp_read_bytes_from_dpcd(dp, DPCD_ADDR_LANE0_1_STATUS, + 6, link_status); + lane_count = dp->link_train.lane_count; + + if (exynos_dp_clock_recovery_ok(link_status, lane_count) == 0) { + /* set training pattern 2 for EQ */ + exynos_dp_set_training_pattern(dp, TRAINING_PTN2); + + adjust_request = link_status + (DPCD_ADDR_ADJUST_REQUEST_LANE0_1 + - DPCD_ADDR_LANE0_1_STATUS); + + exynos_dp_get_adjust_train(dp, adjust_request); + + buf[0] = DPCD_SCRAMBLING_DISABLED | + DPCD_TRAINING_PATTERN_2; + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_LANE0_SET, + buf[0]); + + for (lane = 0; lane < lane_count; lane++) { + exynos_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], + lane); + buf[lane] = dp->link_train.training_lane[lane]; + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_LANE0_SET + lane, + buf[lane]); + } + dp->link_train.lt_state = EQUALIZER_TRAINING; + } else { + exynos_dp_read_byte_from_dpcd(dp, + DPCD_ADDR_ADJUST_REQUEST_LANE0_1, + &data); + adjust_request[0] = data; + + exynos_dp_read_byte_from_dpcd(dp, + DPCD_ADDR_ADJUST_REQUEST_LANE2_3, + &data); + adjust_request[1] = data; + + for (lane = 0; lane < lane_count; lane++) { + training_lane = exynos_dp_get_lane_link_training( + dp, lane); + voltage_swing = exynos_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + if ((DPCD_VOLTAGE_SWING_GET(training_lane) == voltage_swing) && + (DPCD_PRE_EMPHASIS_GET(training_lane) == pre_emphasis)) + dp->link_train.cr_loop[lane]++; + dp->link_train.training_lane[lane] = training_lane; + } + + if (exynos_dp_check_max_cr_loop(dp, voltage_swing) != 0) { + exynos_dp_reduce_link_rate(dp); + } else { + exynos_dp_get_adjust_train(dp, adjust_request); + + for (lane = 0; lane < lane_count; lane++) { + exynos_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], + lane); + buf[lane] = dp->link_train.training_lane[lane]; + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_LANE0_SET + lane, + buf[lane]); + } + } + } + + return 0; +} + +static int exynos_dp_process_equalizer_training(struct exynos_dp_device *dp) +{ + u8 link_status[6]; + int lane; + int lane_count; + u8 buf[5]; + u32 reg; + + u8 *adjust_request; + + udelay(400); + + exynos_dp_read_bytes_from_dpcd(dp, DPCD_ADDR_LANE0_1_STATUS, + 6, link_status); + lane_count = dp->link_train.lane_count; + + if (exynos_dp_clock_recovery_ok(link_status, lane_count) == 0) { + adjust_request = link_status + (DPCD_ADDR_ADJUST_REQUEST_LANE0_1 + - DPCD_ADDR_LANE0_1_STATUS); + + if (exynos_dp_channel_eq_ok(link_status, lane_count) == 0) { + /* traing pattern Set to Normal */ + exynos_dp_training_pattern_dis(dp); + + dev_info(dp->dev, "Link Training success!\n"); + + exynos_dp_get_link_bandwidth(dp, ®); + dp->link_train.link_rate = reg; + dev_dbg(dp->dev, "final bandwidth = %.2x\n", + dp->link_train.link_rate); + + exynos_dp_get_lane_count(dp, ®); + dp->link_train.lane_count = reg; + dev_dbg(dp->dev, "final lane count = %.2x\n", + dp->link_train.lane_count); + /* set enhanced mode if available */ + exynos_dp_set_enhanced_mode(dp); + + dp->link_train.lt_state = FINISHED; + } else { + /* not all locked */ + dp->link_train.eq_loop++; + + if (dp->link_train.eq_loop > MAX_EQ_LOOP) { + exynos_dp_reduce_link_rate(dp); + } else { + exynos_dp_get_adjust_train(dp, adjust_request); + + for (lane = 0; lane < lane_count; lane++) { + exynos_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], + lane); + buf[lane] = dp->link_train.training_lane[lane]; + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_LANE0_SET + lane, + buf[lane]); + } + } + } + } else { + exynos_dp_reduce_link_rate(dp); + } + + return 0; +} + +static void exynos_dp_get_max_rx_bandwidth(struct exynos_dp_device *dp, + u8 *bandwidth) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps + */ + exynos_dp_read_byte_from_dpcd(dp, DPCD_ADDR_MAX_LINK_RATE, &data); + *bandwidth = data; +} + +static void exynos_dp_get_max_rx_lane_count(struct exynos_dp_device *dp, + u8 *lane_count) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum number of Main Link lanes + * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes + */ + exynos_dp_read_byte_from_dpcd(dp, DPCD_ADDR_MAX_LANE_COUNT, &data); + *lane_count = DPCD_MAX_LANE_COUNT(data); +} + +static void exynos_dp_init_training(struct exynos_dp_device *dp, + enum link_lane_count_type max_lane, + enum link_rate_type max_rate) +{ + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + exynos_dp_reset_macro(dp); + + /* Initialize by reading RX's DPCD */ + exynos_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); + exynos_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); + + if ((dp->link_train.link_rate != LINK_RATE_1_62GBPS) && + (dp->link_train.link_rate != LINK_RATE_2_70GBPS)) { + dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", + dp->link_train.link_rate); + dp->link_train.link_rate = LINK_RATE_1_62GBPS; + } + + if (dp->link_train.lane_count == 0) { + dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", + dp->link_train.lane_count); + dp->link_train.lane_count = (u8)LANE_COUNT1; + } + + /* Setup TX lane count & rate */ + if (dp->link_train.lane_count > max_lane) + dp->link_train.lane_count = max_lane; + if (dp->link_train.link_rate > max_rate) + dp->link_train.link_rate = max_rate; + + /* All DP analog module power up */ + exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); +} + +static int exynos_dp_sw_link_training(struct exynos_dp_device *dp) +{ + int retval = 0; + int training_finished; + + /* Turn off unnecessary lane */ + if (dp->link_train.lane_count == 1) + exynos_dp_set_analog_power_down(dp, CH1_BLOCK, 1); + + training_finished = 0; + + dp->link_train.lt_state = START; + + /* Process here */ + while (!training_finished) { + switch (dp->link_train.lt_state) { + case START: + exynos_dp_link_start(dp); + break; + case CLOCK_RECOVERY: + exynos_dp_process_clock_recovery(dp); + break; + case EQUALIZER_TRAINING: + exynos_dp_process_equalizer_training(dp); + break; + case FINISHED: + training_finished = 1; + break; + case FAILED: + return -EREMOTEIO; + } + } + + return retval; +} + +static int exynos_dp_set_link_train(struct exynos_dp_device *dp, + u32 count, + u32 bwtype) +{ + int i; + int retval; + + for (i = 0; i < DP_TIMEOUT_LOOP_COUNT; i++) { + exynos_dp_init_training(dp, count, bwtype); + retval = exynos_dp_sw_link_training(dp); + if (retval == 0) + break; + + udelay(100); + } + + return retval; +} + +static int exynos_dp_config_video(struct exynos_dp_device *dp, + struct video_info *video_info) +{ + int retval = 0; + int timeout_loop = 0; + int done_count = 0; + + exynos_dp_config_video_slave_mode(dp, video_info); + + exynos_dp_set_video_color_format(dp, video_info->color_depth, + video_info->color_space, + video_info->dynamic_range, + video_info->ycbcr_coeff); + + if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + dev_err(dp->dev, "PLL is not locked yet.\n"); + return -EINVAL; + } + + for (;;) { + timeout_loop++; + if (exynos_dp_is_slave_video_stream_clock_on(dp) == 0) + break; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + mdelay(100); + } + + /* Set to use the register calculated M/N video */ + exynos_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + exynos_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); + + /* Disable video mute */ + exynos_dp_enable_video_mute(dp, 0); + + /* Configure video slave mode */ + exynos_dp_enable_video_master(dp, 0); + + /* Enable video */ + exynos_dp_start_video(dp); + + timeout_loop = 0; + + for (;;) { + timeout_loop++; + if (exynos_dp_is_video_stream_on(dp) == 0) { + done_count++; + if (done_count > 10) + break; + } else if (done_count) { + done_count = 0; + } + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + mdelay(100); + } + + if (retval != 0) + dev_err(dp->dev, "Video stream is not detected!\n"); + + return retval; +} + +static void exynos_dp_enable_scramble(struct exynos_dp_device *dp, bool enable) +{ + u8 data; + + if (enable) { + exynos_dp_enable_scrambling(dp); + + exynos_dp_read_byte_from_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + &data); + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + (u8)(data & ~DPCD_SCRAMBLING_DISABLED)); + } else { + exynos_dp_disable_scrambling(dp); + + exynos_dp_read_byte_from_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + &data); + exynos_dp_write_byte_to_dpcd(dp, + DPCD_ADDR_TRAINING_PATTERN_SET, + (u8)(data | DPCD_SCRAMBLING_DISABLED)); + } +} + +static irqreturn_t exynos_dp_irq_handler(int irq, void *arg) +{ + struct exynos_dp_device *dp = arg; + + dev_err(dp->dev, "exynos_dp_irq_handler\n"); + return IRQ_HANDLED; +} + +static int __devinit exynos_dp_probe(struct platform_device *pdev) +{ + struct resource *res; + struct exynos_dp_device *dp; + struct exynos_dp_platdata *pdata; + + int ret = 0; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + dp = kzalloc(sizeof(struct exynos_dp_device), GFP_KERNEL); + if (!dp) { + dev_err(&pdev->dev, "no memory for device data\n"); + return -ENOMEM; + } + + dp->dev = &pdev->dev; + + dp->clock = clk_get(&pdev->dev, "dp"); + if (IS_ERR(dp->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(dp->clock); + goto err_dp; + } + + clk_enable(dp->clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get registers\n"); + ret = -EINVAL; + goto err_clock; + } + + res = request_mem_region(res->start, resource_size(res), + dev_name(&pdev->dev)); + if (!res) { + dev_err(&pdev->dev, "failed to request registers region\n"); + ret = -EINVAL; + goto err_clock; + } + + dp->res = res; + + dp->reg_base = ioremap(res->start, resource_size(res)); + if (!dp->reg_base) { + dev_err(&pdev->dev, "failed to ioremap\n"); + ret = -ENOMEM; + goto err_req_region; + } + + dp->irq = platform_get_irq(pdev, 0); + if (!dp->irq) { + dev_err(&pdev->dev, "failed to get irq\n"); + ret = -ENODEV; + goto err_ioremap; + } + + ret = request_irq(dp->irq, exynos_dp_irq_handler, 0, + "exynos-dp", dp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + goto err_ioremap; + } + + dp->video_info = pdata->video_info; + if (pdata->phy_init) + pdata->phy_init(); + + exynos_dp_init_dp(dp); + + ret = exynos_dp_detect_hpd(dp); + if (ret) { + dev_err(&pdev->dev, "unable to detect hpd\n"); + goto err_irq; + } + + exynos_dp_handle_edid(dp); + + ret = exynos_dp_set_link_train(dp, dp->video_info->lane_count, + dp->video_info->link_rate); + if (ret) { + dev_err(&pdev->dev, "unable to do link train\n"); + goto err_irq; + } + + exynos_dp_enable_scramble(dp, 1); + exynos_dp_enable_rx_to_enhanced_mode(dp, 1); + exynos_dp_enable_enhanced_mode(dp, 1); + + exynos_dp_set_lane_count(dp, dp->video_info->lane_count); + exynos_dp_set_link_bandwidth(dp, dp->video_info->link_rate); + + exynos_dp_init_video(dp); + ret = exynos_dp_config_video(dp, dp->video_info); + if (ret) { + dev_err(&pdev->dev, "unable to config video\n"); + goto err_irq; + } + + platform_set_drvdata(pdev, dp); + + return 0; + +err_irq: + free_irq(dp->irq, dp); +err_ioremap: + iounmap(dp->reg_base); +err_req_region: + release_mem_region(res->start, resource_size(res)); +err_clock: + clk_put(dp->clock); +err_dp: + kfree(dp); + + return ret; +} + +static int __devexit exynos_dp_remove(struct platform_device *pdev) +{ + struct exynos_dp_platdata *pdata = pdev->dev.platform_data; + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(); + + free_irq(dp->irq, dp); + iounmap(dp->reg_base); + + clk_disable(dp->clock); + clk_put(dp->clock); + + release_mem_region(dp->res->start, resource_size(dp->res)); + + kfree(dp); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_dp_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_dp_platdata *pdata = pdev->dev.platform_data; + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(); + + clk_disable(dp->clock); + + return 0; +} + +static int exynos_dp_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_dp_platdata *pdata = pdev->dev.platform_data; + struct exynos_dp_device *dp = platform_get_drvdata(pdev); + + if (pdata && pdata->phy_init) + pdata->phy_init(); + + clk_enable(dp->clock); + + exynos_dp_init_dp(dp); + + exynos_dp_detect_hpd(dp); + exynos_dp_handle_edid(dp); + + exynos_dp_set_link_train(dp, dp->video_info->lane_count, + dp->video_info->link_rate); + + exynos_dp_enable_scramble(dp, 1); + exynos_dp_enable_rx_to_enhanced_mode(dp, 1); + exynos_dp_enable_enhanced_mode(dp, 1); + + exynos_dp_set_lane_count(dp, dp->video_info->lane_count); + exynos_dp_set_link_bandwidth(dp, dp->video_info->link_rate); + + exynos_dp_init_video(dp); + exynos_dp_config_video(dp, dp->video_info); + + return 0; +} +#endif + +static const struct dev_pm_ops exynos_dp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(exynos_dp_suspend, exynos_dp_resume) +}; + +static struct platform_driver exynos_dp_driver = { + .probe = exynos_dp_probe, + .remove = __devexit_p(exynos_dp_remove), + .driver = { + .name = "exynos-dp", + .owner = THIS_MODULE, + .pm = &exynos_dp_pm_ops, + }, +}; + +module_platform_driver(exynos_dp_driver); + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC DP Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/exynos/exynos_dp_core.h b/drivers/video/exynos/exynos_dp_core.h new file mode 100644 index 00000000000..90ceaca0fa2 --- /dev/null +++ b/drivers/video/exynos/exynos_dp_core.h @@ -0,0 +1,206 @@ +/* + * Header file for Samsung DP (Display Port) interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * 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. + */ + +#ifndef _EXYNOS_DP_CORE_H +#define _EXYNOS_DP_CORE_H + +struct link_train { + int eq_loop; + int cr_loop[4]; + + u8 link_rate; + u8 lane_count; + u8 training_lane[4]; + + enum link_training_state lt_state; +}; + +struct exynos_dp_device { + struct device *dev; + struct resource *res; + struct clk *clock; + unsigned int irq; + void __iomem *reg_base; + + struct video_info *video_info; + struct link_train link_train; +}; + +/* exynos_dp_reg.c */ +void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable); +void exynos_dp_stop_video(struct exynos_dp_device *dp); +void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable); +void exynos_dp_init_interrupt(struct exynos_dp_device *dp); +void exynos_dp_reset(struct exynos_dp_device *dp); +void exynos_dp_config_interrupt(struct exynos_dp_device *dp); +u32 exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp); +void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable); +void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, + enum analog_power_block block, + bool enable); +void exynos_dp_init_analog_func(struct exynos_dp_device *dp); +void exynos_dp_init_hpd(struct exynos_dp_device *dp); +void exynos_dp_reset_aux(struct exynos_dp_device *dp); +void exynos_dp_init_aux(struct exynos_dp_device *dp); +int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp); +void exynos_dp_enable_sw_function(struct exynos_dp_device *dp); +int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp); +int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char data); +int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char *data); +int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr); +int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data); +int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]); +void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype); +void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype); +void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count); +void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count); +void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype); +void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype); +void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count); +void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count); +void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable); +void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, + enum pattern_set pattern); +void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level); +void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, + u32 training_lane); +void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, + u32 training_lane); +u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp); +u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp); +void exynos_dp_reset_macro(struct exynos_dp_device *dp); +int exynos_dp_init_video(struct exynos_dp_device *dp); + +void exynos_dp_set_video_color_format(struct exynos_dp_device *dp, + u32 color_depth, + u32 color_space, + u32 dynamic_range, + u32 ycbcr_coeff); +int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp); +void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value); +void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type); +void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable); +void exynos_dp_start_video(struct exynos_dp_device *dp); +int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp); +void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp, + struct video_info *video_info); +void exynos_dp_enable_scrambling(struct exynos_dp_device *dp); +void exynos_dp_disable_scrambling(struct exynos_dp_device *dp); + +/* I2C EDID Chip ID, Slave Address */ +#define I2C_EDID_DEVICE_ADDR 0x50 +#define I2C_E_EDID_DEVICE_ADDR 0x30 + +#define EDID_BLOCK_LENGTH 0x80 +#define EDID_HEADER_PATTERN 0x00 +#define EDID_EXTENSION_FLAG 0x7e +#define EDID_CHECKSUM 0x7f + +/* Definition for DPCD Register */ +#define DPCD_ADDR_DPCD_REV 0x0000 +#define DPCD_ADDR_MAX_LINK_RATE 0x0001 +#define DPCD_ADDR_MAX_LANE_COUNT 0x0002 +#define DPCD_ADDR_LINK_BW_SET 0x0100 +#define DPCD_ADDR_LANE_COUNT_SET 0x0101 +#define DPCD_ADDR_TRAINING_PATTERN_SET 0x0102 +#define DPCD_ADDR_TRAINING_LANE0_SET 0x0103 +#define DPCD_ADDR_LANE0_1_STATUS 0x0202 +#define DPCD_ADDR_LANE_ALIGN__STATUS_UPDATED 0x0204 +#define DPCD_ADDR_ADJUST_REQUEST_LANE0_1 0x0206 +#define DPCD_ADDR_ADJUST_REQUEST_LANE2_3 0x0207 +#define DPCD_ADDR_TEST_REQUEST 0x0218 +#define DPCD_ADDR_TEST_RESPONSE 0x0260 +#define DPCD_ADDR_TEST_EDID_CHECKSUM 0x0261 +#define DPCD_ADDR_SINK_POWER_STATE 0x0600 + +/* DPCD_ADDR_MAX_LANE_COUNT */ +#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) +#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) + +/* DPCD_ADDR_LANE_COUNT_SET */ +#define DPCD_ENHANCED_FRAME_EN (0x1 << 7) +#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) + +/* DPCD_ADDR_TRAINING_PATTERN_SET */ +#define DPCD_SCRAMBLING_DISABLED (0x1 << 5) +#define DPCD_SCRAMBLING_ENABLED (0x0 << 5) +#define DPCD_TRAINING_PATTERN_2 (0x2 << 0) +#define DPCD_TRAINING_PATTERN_1 (0x1 << 0) +#define DPCD_TRAINING_PATTERN_DISABLED (0x0 << 0) + +/* DPCD_ADDR_TRAINING_LANE0_SET */ +#define DPCD_MAX_PRE_EMPHASIS_REACHED (0x1 << 5) +#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) +#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) +#define DPCD_PRE_EMPHASIS_PATTERN2_LEVEL0 (0x0 << 3) +#define DPCD_MAX_SWING_REACHED (0x1 << 2) +#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) +#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) +#define DPCD_VOLTAGE_SWING_PATTERN1_LEVEL0 (0x0 << 0) + +/* DPCD_ADDR_LANE0_1_STATUS */ +#define DPCD_LANE_SYMBOL_LOCKED (0x1 << 2) +#define DPCD_LANE_CHANNEL_EQ_DONE (0x1 << 1) +#define DPCD_LANE_CR_DONE (0x1 << 0) +#define DPCD_CHANNEL_EQ_BITS (DPCD_LANE_CR_DONE| \ + DPCD_LANE_CHANNEL_EQ_DONE|\ + DPCD_LANE_SYMBOL_LOCKED) + +/* DPCD_ADDR_LANE_ALIGN__STATUS_UPDATED */ +#define DPCD_LINK_STATUS_UPDATED (0x1 << 7) +#define DPCD_DOWNSTREAM_PORT_STATUS_CHANGED (0x1 << 6) +#define DPCD_INTERLANE_ALIGN_DONE (0x1 << 0) + +/* DPCD_ADDR_TEST_REQUEST */ +#define DPCD_TEST_EDID_READ (0x1 << 2) + +/* DPCD_ADDR_TEST_RESPONSE */ +#define DPCD_TEST_EDID_CHECKSUM_WRITE (0x1 << 2) + +/* DPCD_ADDR_SINK_POWER_STATE */ +#define DPCD_SET_POWER_STATE_D0 (0x1 << 0) +#define DPCD_SET_POWER_STATE_D4 (0x2 << 0) + +#endif /* _EXYNOS_DP_CORE_H */ diff --git a/drivers/video/exynos/exynos_dp_reg.c b/drivers/video/exynos/exynos_dp_reg.c new file mode 100644 index 00000000000..6548afa0e3d --- /dev/null +++ b/drivers/video/exynos/exynos_dp_reg.c @@ -0,0 +1,1173 @@ +/* + * Samsung DP (Display port) register interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * 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. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> + +#include <video/exynos_dp.h> + +#include <plat/cpu.h> + +#include "exynos_dp_core.h" +#include "exynos_dp_reg.h" + +#define COMMON_INT_MASK_1 (0) +#define COMMON_INT_MASK_2 (0) +#define COMMON_INT_MASK_3 (0) +#define COMMON_INT_MASK_4 (0) +#define INT_STA_MASK (0) + +void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg |= HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg &= ~HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + } +} + +void exynos_dp_stop_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg &= ~VIDEO_EN; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); +} + +void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) + reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | + LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; + else + reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | + LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + + writel(reg, dp->reg_base + EXYNOS_DP_LANE_MAP); +} + +void exynos_dp_init_interrupt(struct exynos_dp_device *dp) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL, dp->reg_base + EXYNOS_DP_INT_CTL); + + /* Clear pending regisers */ + writel(0xff, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + writel(0x4f, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_2); + writel(0xe0, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_3); + writel(0xe7, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + writel(0x63, dp->reg_base + EXYNOS_DP_INT_STA); + + /* 0:mask,1: unmask */ + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); + writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); + writel(0x00, dp->reg_base + EXYNOS_DP_INT_STA_MASK); +} + +void exynos_dp_reset(struct exynos_dp_device *dp) +{ + u32 reg; + + writel(RESET_DP_TX, dp->reg_base + EXYNOS_DP_TX_SW_RESET); + + exynos_dp_stop_video(dp); + exynos_dp_enable_video_mute(dp, 0); + + reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | + AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | + HDCP_FUNC_EN_N | SW_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); + + reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | + SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); + + udelay(20); + + exynos_dp_lane_swap(dp, 0); + + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + writel(0x40, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + + writel(0x0, dp->reg_base + EXYNOS_DP_PKT_SEND_CTL); + writel(0x0, dp->reg_base + EXYNOS_DP_HDCP_CTL); + + writel(0x5e, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_L); + writel(0x1a, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_H); + + writel(0x10, dp->reg_base + EXYNOS_DP_LINK_DEBUG_CTL); + + writel(0x0, dp->reg_base + EXYNOS_DP_PHY_TEST); + + writel(0x0, dp->reg_base + EXYNOS_DP_VIDEO_FIFO_THRD); + writel(0x20, dp->reg_base + EXYNOS_DP_AUDIO_MARGIN); + + writel(0x4, dp->reg_base + EXYNOS_DP_M_VID_GEN_FILTER_TH); + writel(0x2, dp->reg_base + EXYNOS_DP_M_AUD_GEN_FILTER_TH); + + writel(0x00000101, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + + exynos_dp_init_interrupt(dp); +} + +void exynos_dp_config_interrupt(struct exynos_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_1; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); + + reg = COMMON_INT_MASK_2; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); + + reg = COMMON_INT_MASK_3; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); + + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA_MASK); +} + +u32 exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); + if (reg & PLL_LOCK) + return PLL_LOCKED; + else + return PLL_UNLOCKED; +} + +void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); + reg |= DP_PLL_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); + reg &= ~DP_PLL_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); + } +} + +void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, + enum analog_power_block block, + bool enable) +{ + u32 reg; + + switch (block) { + case AUX_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= AUX_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~AUX_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH0_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH1_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH1_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH1_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH2_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH2_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH2_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case CH3_BLOCK: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= CH3_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~CH3_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case ANALOG_TOTAL: + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg |= DP_PHY_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); + reg &= ~DP_PHY_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + case POWER_ALL: + if (enable) { + reg = DP_PHY_PD | AUX_PD | CH3_PD | CH2_PD | + CH1_PD | CH0_PD; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); + } else { + writel(0x00, dp->reg_base + EXYNOS_DP_PHY_PD); + } + break; + default: + break; + } +} + +void exynos_dp_init_analog_func(struct exynos_dp_device *dp) +{ + u32 reg; + + exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); + + reg = PLL_LOCK_CHG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + + reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); + reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); + writel(reg, dp->reg_base + EXYNOS_DP_DEBUG_CTL); + + /* Power up PLL */ + if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) + exynos_dp_set_pll_power_down(dp, 0); + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N + | AUX_FUNC_EN_N); + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +void exynos_dp_init_hpd(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = HOTPLUG_CHG | HPD_LOST | PLUG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); + + reg = INT_HPD; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + reg &= ~(F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); +} + +void exynos_dp_reset_aux(struct exynos_dp_device *dp) +{ + u32 reg; + + /* Disable AUX channel module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg |= AUX_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +void exynos_dp_init_aux(struct exynos_dp_device *dp) +{ + u32 reg; + + /* Clear inerrupts related to AUX channel */ + reg = RPLY_RECEIV | AUX_ERR; + writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); + + exynos_dp_reset_aux(dp); + + /* Disable AUX transaction H/W retry */ + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3) | AUX_HW_RETRY_COUNT_SEL(0)| + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_HW_RETRY_CTL) ; + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + reg = DEFER_CTRL_EN | DEFER_COUNT(1); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_DEFER_CTL); + + /* Enable AUX channel module */ + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); + reg &= ~AUX_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); +} + +int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + + return -EINVAL; +} + +void exynos_dp_enable_sw_function(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); + reg &= ~SW_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); +} + +int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp) +{ + int reg; + int retval = 0; + + /* Enable AUX CH operation */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + reg |= AUX_EN; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + + /* Is AUX CH command reply received? */ + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + while (!(reg & RPLY_RECEIV)) + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + EXYNOS_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); + if (reg & AUX_ERR) { + writel(AUX_ERR, dp->reg_base + EXYNOS_DP_INT_STA); + return -EREMOTEIO; + } + + /* Check AUX CH error access status */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_STA); + if ((reg & AUX_STATUS_MASK) != 0) { + dev_err(dp->dev, "AUX CH error happens: %d\n\n", + reg & AUX_STATUS_MASK); + return -EREMOTEIO; + } + + return retval; +} + +int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* Write data buffer */ + reg = (unsigned int)data; + writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + /* + * Set DisplayPort transaction and write 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + + return retval; +} + +int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned char *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 10; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + + /* Read data buffer */ + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); + *data = (unsigned char)(reg & 0xff); + + return retval; +} + +int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + for (i = 0; i < 10; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = data[start_offset + cur_data_idx]; + writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + } + + /* + * Set DisplayPort transaction and write + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + + start_offset += cur_data_count; + } + + return retval; +} + +int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + /* AUX CH Request Transaction process */ + for (i = 0; i < 10; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + data[start_offset + cur_data_idx] = + (unsigned char)reg; + } + + start_offset += cur_data_count; + } + + return retval; +} + +int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr) +{ + u32 reg; + int retval; + + /* Set EDID device address */ + reg = device_addr; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); + writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); + writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); + + /* Set offset from base address of EDID device */ + writel(reg_addr, dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + /* + * Set I2C transaction and write address + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | + AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval != 0) + dev_err(dp->dev, "Aux Transaction fail!\n"); + + return retval; +} + +int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 10; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Select EDID device */ + retval = exynos_dp_select_i2c_device(dp, device_addr, reg_addr); + if (retval != 0) { + dev_err(dp->dev, "Select EDID device fail!\n"); + continue; + } + + /* + * Set I2C transaction and read data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + + /* Read data */ + if (retval == 0) + *data = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); + + return retval; +} + +int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]) +{ + u32 reg; + unsigned int i, j; + unsigned int cur_data_idx; + unsigned int defer = 0; + int retval = 0; + + for (i = 0; i < count; i += 16) { + for (j = 0; j < 100; j++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); + + /* Set normal AUX CH command */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + reg &= ~ADDR_ONLY; + writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); + + /* + * If Rx sends defer, Tx sends only reads + * request without sending addres + */ + if (!defer) + retval = exynos_dp_select_i2c_device(dp, + device_addr, reg_addr + i); + else + defer = 0; + + if (retval == 0) { + /* + * Set I2C transaction and write data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(16) | + AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + + EXYNOS_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = exynos_dp_start_aux_transaction(dp); + if (retval == 0) + break; + else + dev_err(dp->dev, "Aux Transaction fail!\n"); + } + /* Check if Rx sends defer */ + reg = readl(dp->reg_base + EXYNOS_DP_AUX_RX_COMM); + if (reg == AUX_RX_COMM_AUX_DEFER || + reg == AUX_RX_COMM_I2C_DEFER) { + dev_err(dp->dev, "Defer: %d\n\n", reg); + defer = 1; + } + } + + for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { + reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 + + 4 * cur_data_idx); + edid[i + cur_data_idx] = (unsigned char)reg; + } + } + + return retval; +} + +void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype) +{ + u32 reg; + + reg = bwtype; + if ((bwtype == LINK_RATE_2_70GBPS) || (bwtype == LINK_RATE_1_62GBPS)) + writel(reg, dp->reg_base + EXYNOS_DP_LINK_BW_SET); +} + +void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LINK_BW_SET); + *bwtype = reg; +} + +void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count) +{ + u32 reg; + + reg = count; + writel(reg, dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); +} + +void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); + *count = reg; +} + +void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg |= ENHANCED; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg &= ~ENHANCED; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + } +} + +void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, + enum pattern_set pattern) +{ + u32 reg; + + switch (pattern) { + case PRBS7: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case D10_2: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN1: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN2: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + case DP_NONE: + reg = SCRAMBLING_ENABLE | + LINK_QUAL_PATTERN_SET_DISABLE | + SW_TRAINING_PATTERN_SET_NORMAL; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + break; + default: + break; + } +} + +void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level) +{ + u32 reg; + + reg = level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); +} + +void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); +} + +u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); + return reg; +} + +u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); + return reg; +} + +void exynos_dp_reset_macro(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_PHY_TEST); + reg |= MACRO_RST; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); + + /* 10 us is the minimum reset time. */ + udelay(10); + + reg &= ~MACRO_RST; + writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); +} + +int exynos_dp_init_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; + writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); + + reg = 0x0; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + reg = CHA_CRI(4) | CHA_CTRL; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + + reg = 0x0; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + + reg = VID_HRES_TH(2) | VID_VRES_TH(0); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_8); + + return 0; +} + +void exynos_dp_set_video_color_format(struct exynos_dp_device *dp, + u32 color_depth, + u32 color_space, + u32 dynamic_range, + u32 ycbcr_coeff) +{ + u32 reg; + + /* Configure the input color depth, color space, dynamic range */ + reg = (dynamic_range << IN_D_RANGE_SHIFT) | + (color_depth << IN_BPC_SHIFT) | + (color_space << IN_COLOR_F_SHIFT); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_2); + + /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); + reg &= ~IN_YC_COEFFI_MASK; + if (ycbcr_coeff) + reg |= IN_YC_COEFFI_ITU709; + else + reg |= IN_YC_COEFFI_ITU601; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); +} + +int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); + + if (!(reg & DET_STA)) { + dev_dbg(dp->dev, "Input stream clock not detected.\n"); + return -EINVAL; + } + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); + dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); + + if (reg & CHA_STA) { + dev_dbg(dp->dev, "Input stream clk is changing\n"); + return -EINVAL; + } + + return 0; +} + +void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value) +{ + u32 reg; + + if (type == REGISTER_M) { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg |= FIX_M_VID; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg = m_value & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_0); + reg = (m_value >> 8) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_1); + reg = (m_value >> 16) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_M_VID_2); + + reg = n_value & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_0); + reg = (n_value >> 8) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_1); + reg = (n_value >> 16) & 0xff; + writel(reg, dp->reg_base + EXYNOS_DP_N_VID_2); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); + reg &= ~FIX_M_VID; + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); + + writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_0); + writel(0x80, dp->reg_base + EXYNOS_DP_N_VID_1); + writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_2); + } +} + +void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type) +{ + u32 reg; + + if (type == VIDEO_TIMING_FROM_CAPTURE) { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~FORMAT_SEL; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg |= FORMAT_SEL; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + } +} + +void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + } else { + reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); + } +} + +void exynos_dp_start_video(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); + reg |= VIDEO_EN; + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); +} + +int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); + + reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); + if (!(reg & STRM_VALID)) { + dev_dbg(dp->dev, "Input video stream is not detected.\n"); + return -EINVAL; + } + + return 0; +} + +void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp, + struct video_info *video_info) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); + reg &= ~(MASTER_VID_FUNC_EN_N|SLAVE_VID_FUNC_EN_N); + reg |= MASTER_VID_FUNC_EN_N; + writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~INTERACE_SCAN_CFG; + reg |= (video_info->interlaced << 2); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~VSYNC_POLARITY_CFG; + reg |= (video_info->v_sync_polarity << 1); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + reg &= ~HSYNC_POLARITY_CFG; + reg |= (video_info->h_sync_polarity << 0); + writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); + + reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); +} + +void exynos_dp_enable_scrambling(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + reg &= ~SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); +} + +void exynos_dp_disable_scrambling(struct exynos_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); + reg |= SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); +} diff --git a/drivers/video/exynos/exynos_dp_reg.h b/drivers/video/exynos/exynos_dp_reg.h new file mode 100644 index 00000000000..42f608e2a43 --- /dev/null +++ b/drivers/video/exynos/exynos_dp_reg.h @@ -0,0 +1,335 @@ +/* + * Register definition file for Samsung DP driver + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@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. + */ + +#ifndef _EXYNOS_DP_REG_H +#define _EXYNOS_DP_REG_H + +#define EXYNOS_DP_TX_SW_RESET 0x14 +#define EXYNOS_DP_FUNC_EN_1 0x18 +#define EXYNOS_DP_FUNC_EN_2 0x1C +#define EXYNOS_DP_VIDEO_CTL_1 0x20 +#define EXYNOS_DP_VIDEO_CTL_2 0x24 +#define EXYNOS_DP_VIDEO_CTL_3 0x28 + +#define EXYNOS_DP_VIDEO_CTL_8 0x3C +#define EXYNOS_DP_VIDEO_CTL_10 0x44 + +#define EXYNOS_DP_LANE_MAP 0x35C + +#define EXYNOS_DP_AUX_HW_RETRY_CTL 0x390 + +#define EXYNOS_DP_COMMON_INT_STA_1 0x3C4 +#define EXYNOS_DP_COMMON_INT_STA_2 0x3C8 +#define EXYNOS_DP_COMMON_INT_STA_3 0x3CC +#define EXYNOS_DP_COMMON_INT_STA_4 0x3D0 +#define EXYNOS_DP_INT_STA 0x3DC +#define EXYNOS_DP_COMMON_INT_MASK_1 0x3E0 +#define EXYNOS_DP_COMMON_INT_MASK_2 0x3E4 +#define EXYNOS_DP_COMMON_INT_MASK_3 0x3E8 +#define EXYNOS_DP_COMMON_INT_MASK_4 0x3EC +#define EXYNOS_DP_INT_STA_MASK 0x3F8 +#define EXYNOS_DP_INT_CTL 0x3FC + +#define EXYNOS_DP_SYS_CTL_1 0x600 +#define EXYNOS_DP_SYS_CTL_2 0x604 +#define EXYNOS_DP_SYS_CTL_3 0x608 +#define EXYNOS_DP_SYS_CTL_4 0x60C + +#define EXYNOS_DP_PKT_SEND_CTL 0x640 +#define EXYNOS_DP_HDCP_CTL 0x648 + +#define EXYNOS_DP_LINK_BW_SET 0x680 +#define EXYNOS_DP_LANE_COUNT_SET 0x684 +#define EXYNOS_DP_TRAINING_PTN_SET 0x688 +#define EXYNOS_DP_LN0_LINK_TRAINING_CTL 0x68C +#define EXYNOS_DP_LN1_LINK_TRAINING_CTL 0x690 +#define EXYNOS_DP_LN2_LINK_TRAINING_CTL 0x694 +#define EXYNOS_DP_LN3_LINK_TRAINING_CTL 0x698 + +#define EXYNOS_DP_DEBUG_CTL 0x6C0 +#define EXYNOS_DP_HPD_DEGLITCH_L 0x6C4 +#define EXYNOS_DP_HPD_DEGLITCH_H 0x6C8 +#define EXYNOS_DP_LINK_DEBUG_CTL 0x6E0 + +#define EXYNOS_DP_M_VID_0 0x700 +#define EXYNOS_DP_M_VID_1 0x704 +#define EXYNOS_DP_M_VID_2 0x708 +#define EXYNOS_DP_N_VID_0 0x70C +#define EXYNOS_DP_N_VID_1 0x710 +#define EXYNOS_DP_N_VID_2 0x714 + +#define EXYNOS_DP_PLL_CTL 0x71C +#define EXYNOS_DP_PHY_PD 0x720 +#define EXYNOS_DP_PHY_TEST 0x724 + +#define EXYNOS_DP_VIDEO_FIFO_THRD 0x730 +#define EXYNOS_DP_AUDIO_MARGIN 0x73C + +#define EXYNOS_DP_M_VID_GEN_FILTER_TH 0x764 +#define EXYNOS_DP_M_AUD_GEN_FILTER_TH 0x778 +#define EXYNOS_DP_AUX_CH_STA 0x780 +#define EXYNOS_DP_AUX_CH_DEFER_CTL 0x788 +#define EXYNOS_DP_AUX_RX_COMM 0x78C +#define EXYNOS_DP_BUFFER_DATA_CTL 0x790 +#define EXYNOS_DP_AUX_CH_CTL_1 0x794 +#define EXYNOS_DP_AUX_ADDR_7_0 0x798 +#define EXYNOS_DP_AUX_ADDR_15_8 0x79C +#define EXYNOS_DP_AUX_ADDR_19_16 0x7A0 +#define EXYNOS_DP_AUX_CH_CTL_2 0x7A4 + +#define EXYNOS_DP_BUF_DATA_0 0x7C0 + +#define EXYNOS_DP_SOC_GENERAL_CTL 0x800 + +/* EXYNOS_DP_TX_SW_RESET */ +#define RESET_DP_TX (0x1 << 0) + +/* EXYNOS_DP_FUNC_EN_1 */ +#define MASTER_VID_FUNC_EN_N (0x1 << 7) +#define SLAVE_VID_FUNC_EN_N (0x1 << 5) +#define AUD_FIFO_FUNC_EN_N (0x1 << 4) +#define AUD_FUNC_EN_N (0x1 << 3) +#define HDCP_FUNC_EN_N (0x1 << 2) +#define CRC_FUNC_EN_N (0x1 << 1) +#define SW_FUNC_EN_N (0x1 << 0) + +/* EXYNOS_DP_FUNC_EN_2 */ +#define SSC_FUNC_EN_N (0x1 << 7) +#define AUX_FUNC_EN_N (0x1 << 2) +#define SERDES_FIFO_FUNC_EN_N (0x1 << 1) +#define LS_CLK_DOMAIN_FUNC_EN_N (0x1 << 0) + +/* EXYNOS_DP_VIDEO_CTL_1 */ +#define VIDEO_EN (0x1 << 7) +#define HDCP_VIDEO_MUTE (0x1 << 6) + +/* EXYNOS_DP_VIDEO_CTL_1 */ +#define IN_D_RANGE_MASK (0x1 << 7) +#define IN_D_RANGE_SHIFT (7) +#define IN_D_RANGE_CEA (0x1 << 7) +#define IN_D_RANGE_VESA (0x0 << 7) +#define IN_BPC_MASK (0x7 << 4) +#define IN_BPC_SHIFT (4) +#define IN_BPC_12_BITS (0x3 << 4) +#define IN_BPC_10_BITS (0x2 << 4) +#define IN_BPC_8_BITS (0x1 << 4) +#define IN_BPC_6_BITS (0x0 << 4) +#define IN_COLOR_F_MASK (0x3 << 0) +#define IN_COLOR_F_SHIFT (0) +#define IN_COLOR_F_YCBCR444 (0x2 << 0) +#define IN_COLOR_F_YCBCR422 (0x1 << 0) +#define IN_COLOR_F_RGB (0x0 << 0) + +/* EXYNOS_DP_VIDEO_CTL_3 */ +#define IN_YC_COEFFI_MASK (0x1 << 7) +#define IN_YC_COEFFI_SHIFT (7) +#define IN_YC_COEFFI_ITU709 (0x1 << 7) +#define IN_YC_COEFFI_ITU601 (0x0 << 7) +#define VID_CHK_UPDATE_TYPE_MASK (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_SHIFT (4) +#define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) + +/* EXYNOS_DP_VIDEO_CTL_8 */ +#define VID_HRES_TH(x) (((x) & 0xf) << 4) +#define VID_VRES_TH(x) (((x) & 0xf) << 0) + +/* EXYNOS_DP_VIDEO_CTL_10 */ +#define FORMAT_SEL (0x1 << 4) +#define INTERACE_SCAN_CFG (0x1 << 2) +#define VSYNC_POLARITY_CFG (0x1 << 1) +#define HSYNC_POLARITY_CFG (0x1 << 0) + +/* EXYNOS_DP_LANE_MAP */ +#define LANE3_MAP_LOGIC_LANE_0 (0x0 << 6) +#define LANE3_MAP_LOGIC_LANE_1 (0x1 << 6) +#define LANE3_MAP_LOGIC_LANE_2 (0x2 << 6) +#define LANE3_MAP_LOGIC_LANE_3 (0x3 << 6) +#define LANE2_MAP_LOGIC_LANE_0 (0x0 << 4) +#define LANE2_MAP_LOGIC_LANE_1 (0x1 << 4) +#define LANE2_MAP_LOGIC_LANE_2 (0x2 << 4) +#define LANE2_MAP_LOGIC_LANE_3 (0x3 << 4) +#define LANE1_MAP_LOGIC_LANE_0 (0x0 << 2) +#define LANE1_MAP_LOGIC_LANE_1 (0x1 << 2) +#define LANE1_MAP_LOGIC_LANE_2 (0x2 << 2) +#define LANE1_MAP_LOGIC_LANE_3 (0x3 << 2) +#define LANE0_MAP_LOGIC_LANE_0 (0x0 << 0) +#define LANE0_MAP_LOGIC_LANE_1 (0x1 << 0) +#define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) +#define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) + +/* EXYNOS_DP_AUX_HW_RETRY_CTL */ +#define AUX_BIT_PERIOD_EXPECTED_DELAY(x) (((x) & 0x7) << 8) +#define AUX_HW_RETRY_INTERVAL_MASK (0x3 << 3) +#define AUX_HW_RETRY_INTERVAL_600_MICROSECONDS (0x0 << 3) +#define AUX_HW_RETRY_INTERVAL_800_MICROSECONDS (0x1 << 3) +#define AUX_HW_RETRY_INTERVAL_1000_MICROSECONDS (0x2 << 3) +#define AUX_HW_RETRY_INTERVAL_1800_MICROSECONDS (0x3 << 3) +#define AUX_HW_RETRY_COUNT_SEL(x) (((x) & 0x7) << 0) + +/* EXYNOS_DP_COMMON_INT_STA_1 */ +#define VSYNC_DET (0x1 << 7) +#define PLL_LOCK_CHG (0x1 << 6) +#define SPDIF_ERR (0x1 << 5) +#define SPDIF_UNSTBL (0x1 << 4) +#define VID_FORMAT_CHG (0x1 << 3) +#define AUD_CLK_CHG (0x1 << 2) +#define VID_CLK_CHG (0x1 << 1) +#define SW_INT (0x1 << 0) + +/* EXYNOS_DP_COMMON_INT_STA_2 */ +#define ENC_EN_CHG (0x1 << 6) +#define HW_BKSV_RDY (0x1 << 3) +#define HW_SHA_DONE (0x1 << 2) +#define HW_AUTH_STATE_CHG (0x1 << 1) +#define HW_AUTH_DONE (0x1 << 0) + +/* EXYNOS_DP_COMMON_INT_STA_3 */ +#define AFIFO_UNDER (0x1 << 7) +#define AFIFO_OVER (0x1 << 6) +#define R0_CHK_FLAG (0x1 << 5) + +/* EXYNOS_DP_COMMON_INT_STA_4 */ +#define PSR_ACTIVE (0x1 << 7) +#define PSR_INACTIVE (0x1 << 6) +#define SPDIF_BI_PHASE_ERR (0x1 << 5) +#define HOTPLUG_CHG (0x1 << 2) +#define HPD_LOST (0x1 << 1) +#define PLUG (0x1 << 0) + +/* EXYNOS_DP_INT_STA */ +#define INT_HPD (0x1 << 6) +#define HW_TRAINING_FINISH (0x1 << 5) +#define RPLY_RECEIV (0x1 << 1) +#define AUX_ERR (0x1 << 0) + +/* EXYNOS_DP_INT_CTL */ +#define SOFT_INT_CTRL (0x1 << 2) +#define INT_POL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_1 */ +#define DET_STA (0x1 << 2) +#define FORCE_DET (0x1 << 1) +#define DET_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_2 */ +#define CHA_CRI(x) (((x) & 0xf) << 4) +#define CHA_STA (0x1 << 2) +#define FORCE_CHA (0x1 << 1) +#define CHA_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_3 */ +#define HPD_STATUS (0x1 << 6) +#define F_HPD (0x1 << 5) +#define HPD_CTRL (0x1 << 4) +#define HDCP_RDY (0x1 << 3) +#define STRM_VALID (0x1 << 2) +#define F_VALID (0x1 << 1) +#define VALID_CTRL (0x1 << 0) + +/* EXYNOS_DP_SYS_CTL_4 */ +#define FIX_M_AUD (0x1 << 4) +#define ENHANCED (0x1 << 3) +#define FIX_M_VID (0x1 << 2) +#define M_VID_UPDATE_CTRL (0x3 << 0) + +/* EXYNOS_DP_TRAINING_PTN_SET */ +#define SCRAMBLER_TYPE (0x1 << 9) +#define HW_LINK_TRAINING_PATTERN (0x1 << 8) +#define SCRAMBLING_DISABLE (0x1 << 5) +#define SCRAMBLING_ENABLE (0x0 << 5) +#define LINK_QUAL_PATTERN_SET_MASK (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_PRBS7 (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_D10_2 (0x1 << 2) +#define LINK_QUAL_PATTERN_SET_DISABLE (0x0 << 2) +#define SW_TRAINING_PATTERN_SET_MASK (0x3 << 0) +#define SW_TRAINING_PATTERN_SET_PTN2 (0x2 << 0) +#define SW_TRAINING_PATTERN_SET_PTN1 (0x1 << 0) +#define SW_TRAINING_PATTERN_SET_NORMAL (0x0 << 0) + +/* EXYNOS_DP_LN0_LINK_TRAINING_CTL */ +#define PRE_EMPHASIS_SET_SHIFT (3) + +/* EXYNOS_DP_DEBUG_CTL */ +#define PLL_LOCK (0x1 << 4) +#define F_PLL_LOCK (0x1 << 3) +#define PLL_LOCK_CTRL (0x1 << 2) +#define PN_INV (0x1 << 0) + +/* EXYNOS_DP_PLL_CTL */ +#define DP_PLL_PD (0x1 << 7) +#define DP_PLL_RESET (0x1 << 6) +#define DP_PLL_LOOP_BIT_DEFAULT (0x1 << 4) +#define DP_PLL_REF_BIT_1_1250V (0x5 << 0) +#define DP_PLL_REF_BIT_1_2500V (0x7 << 0) + +/* EXYNOS_DP_PHY_PD */ +#define DP_PHY_PD (0x1 << 5) +#define AUX_PD (0x1 << 4) +#define CH3_PD (0x1 << 3) +#define CH2_PD (0x1 << 2) +#define CH1_PD (0x1 << 1) +#define CH0_PD (0x1 << 0) + +/* EXYNOS_DP_PHY_TEST */ +#define MACRO_RST (0x1 << 5) +#define CH1_TEST (0x1 << 1) +#define CH0_TEST (0x1 << 0) + +/* EXYNOS_DP_AUX_CH_STA */ +#define AUX_BUSY (0x1 << 4) +#define AUX_STATUS_MASK (0xf << 0) + +/* EXYNOS_DP_AUX_CH_DEFER_CTL */ +#define DEFER_CTRL_EN (0x1 << 7) +#define DEFER_COUNT(x) (((x) & 0x7f) << 0) + +/* EXYNOS_DP_AUX_RX_COMM */ +#define AUX_RX_COMM_I2C_DEFER (0x2 << 2) +#define AUX_RX_COMM_AUX_DEFER (0x2 << 0) + +/* EXYNOS_DP_BUFFER_DATA_CTL */ +#define BUF_CLR (0x1 << 7) +#define BUF_DATA_COUNT(x) (((x) & 0x1f) << 0) + +/* EXYNOS_DP_AUX_CH_CTL_1 */ +#define AUX_LENGTH(x) (((x - 1) & 0xf) << 4) +#define AUX_TX_COMM_MASK (0xf << 0) +#define AUX_TX_COMM_DP_TRANSACTION (0x1 << 3) +#define AUX_TX_COMM_I2C_TRANSACTION (0x0 << 3) +#define AUX_TX_COMM_MOT (0x1 << 2) +#define AUX_TX_COMM_WRITE (0x0 << 0) +#define AUX_TX_COMM_READ (0x1 << 0) + +/* EXYNOS_DP_AUX_ADDR_7_0 */ +#define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff) + +/* EXYNOS_DP_AUX_ADDR_15_8 */ +#define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff) + +/* EXYNOS_DP_AUX_ADDR_19_16 */ +#define AUX_ADDR_19_16(x) (((x) >> 16) & 0x0f) + +/* EXYNOS_DP_AUX_CH_CTL_2 */ +#define ADDR_ONLY (0x1 << 1) +#define AUX_EN (0x1 << 0) + +/* EXYNOS_DP_SOC_GENERAL_CTL */ +#define AUDIO_MODE_SPDIF_MODE (0x1 << 8) +#define AUDIO_MODE_MASTER_MODE (0x0 << 8) +#define MASTER_VIDEO_INTERLACE_EN (0x1 << 4) +#define VIDEO_MASTER_CLK_SEL (0x1 << 2) +#define VIDEO_MASTER_MODE_EN (0x1 << 1) +#define VIDEO_MODE_MASK (0x1 << 0) +#define VIDEO_MODE_SLAVE_MODE (0x1 << 0) +#define VIDEO_MODE_MASTER_MODE (0x0 << 0) + +#endif /* _EXYNOS_DP_REG_H */ 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, 0); + + return 0; +} + +static unsigned long exynos_mipi_dsi_change_pll(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + unsigned long dfin_pll, dfvco, dpll_out; + unsigned int i, freq_band = 0xf; + + dfin_pll = (FIN_HZ / pre_divider); + + /****************************************************** + * Serial Clock(=ByteClk X 8) FreqBand[3:0] * + ****************************************************** + * ~ 99.99 MHz 0000 + * 100 ~ 119.99 MHz 0001 + * 120 ~ 159.99 MHz 0010 + * 160 ~ 199.99 MHz 0011 + * 200 ~ 239.99 MHz 0100 + * 140 ~ 319.99 MHz 0101 + * 320 ~ 389.99 MHz 0110 + * 390 ~ 449.99 MHz 0111 + * 450 ~ 509.99 MHz 1000 + * 510 ~ 559.99 MHz 1001 + * 560 ~ 639.99 MHz 1010 + * 640 ~ 689.99 MHz 1011 + * 690 ~ 769.99 MHz 1100 + * 770 ~ 869.99 MHz 1101 + * 870 ~ 949.99 MHz 1110 + * 950 ~ 1000 MHz 1111 + ******************************************************/ + if (dfin_pll < DFIN_PLL_MIN_HZ || dfin_pll > DFIN_PLL_MAX_HZ) { + dev_warn(dsim->dev, "fin_pll range should be 6MHz ~ 12MHz\n"); + exynos_mipi_dsi_enable_afc(dsim, 0, 0); + } else { + if (dfin_pll < 7 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x1); + else if (dfin_pll < 8 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x0); + else if (dfin_pll < 9 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x3); + else if (dfin_pll < 10 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x2); + else if (dfin_pll < 11 * MHZ) + exynos_mipi_dsi_enable_afc(dsim, 1, 0x5); + else + exynos_mipi_dsi_enable_afc(dsim, 1, 0x4); + } + + dfvco = dfin_pll * main_divider; + dev_dbg(dsim->dev, "dfvco = %lu, dfin_pll = %lu, main_divider = %d\n", + dfvco, dfin_pll, main_divider); + if (dfvco < DFVCO_MIN_HZ || dfvco > DFVCO_MAX_HZ) + dev_warn(dsim->dev, "fvco range should be 500MHz ~ 1000MHz\n"); + + dpll_out = dfvco / (1 << scaler); + dev_dbg(dsim->dev, "dpll_out = %lu, dfvco = %lu, scaler = %d\n", + dpll_out, dfvco, scaler); + + for (i = 0; i < ARRAY_SIZE(dpll_table); i++) { + if (dpll_out < dpll_table[i] * MHZ) { + freq_band = i; + break; + } + } + + dev_dbg(dsim->dev, "freq_band = %d\n", freq_band); + + exynos_mipi_dsi_pll_freq(dsim, pre_divider, main_divider, scaler); + + exynos_mipi_dsi_hs_zero_ctrl(dsim, 0); + exynos_mipi_dsi_prep_ctrl(dsim, 0); + + /* Freq Band */ + exynos_mipi_dsi_pll_freq_band(dsim, freq_band); + + /* Stable time */ + exynos_mipi_dsi_pll_stable_time(dsim, dsim->dsim_config->pll_stable_time); + + /* Enable PLL */ + dev_dbg(dsim->dev, "FOUT of mipi dphy pll is %luMHz\n", + (dpll_out / MHZ)); + + return dpll_out; +} + +static int exynos_mipi_dsi_set_clock(struct mipi_dsim_device *dsim, + unsigned int byte_clk_sel, unsigned int enable) +{ + unsigned int esc_div; + unsigned long esc_clk_error_rate; + unsigned long hs_clk = 0, byte_clk = 0, escape_clk = 0; + + if (enable) { + dsim->e_clk_src = byte_clk_sel; + + /* Escape mode clock and byte clock source */ + exynos_mipi_dsi_set_byte_clock_src(dsim, byte_clk_sel); + + /* DPHY, DSIM Link : D-PHY clock out */ + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) { + hs_clk = exynos_mipi_dsi_change_pll(dsim, + dsim->dsim_config->p, dsim->dsim_config->m, + dsim->dsim_config->s); + if (hs_clk == 0) { + dev_err(dsim->dev, + "failed to get hs clock.\n"); + return -EINVAL; + } + + byte_clk = hs_clk / 8; + exynos_mipi_dsi_enable_pll_bypass(dsim, 0); + exynos_mipi_dsi_pll_on(dsim, 1); + /* DPHY : D-PHY clock out, DSIM link : external clock out */ + } else if (byte_clk_sel == DSIM_EXT_CLK_DIV8) { + dev_warn(dsim->dev, "this project is not support\n"); + dev_warn(dsim->dev, + "external clock source for MIPI DSIM.\n"); + } else if (byte_clk_sel == DSIM_EXT_CLK_BYPASS) { + dev_warn(dsim->dev, "this project is not support\n"); + dev_warn(dsim->dev, + "external clock source for MIPI DSIM\n"); + } + + /* escape clock divider */ + esc_div = byte_clk / (dsim->dsim_config->esc_clk); + dev_dbg(dsim->dev, + "esc_div = %d, byte_clk = %lu, esc_clk = %lu\n", + esc_div, byte_clk, dsim->dsim_config->esc_clk); + if ((byte_clk / esc_div) >= (20 * MHZ) || + (byte_clk / esc_div) > + dsim->dsim_config->esc_clk) + esc_div += 1; + + escape_clk = byte_clk / esc_div; + dev_dbg(dsim->dev, + "escape_clk = %lu, byte_clk = %lu, esc_div = %d\n", + escape_clk, byte_clk, esc_div); + + /* enable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 1); + + /* enable byte clk and escape clock */ + exynos_mipi_dsi_set_esc_clk_prs(dsim, 1, esc_div); + /* escape clock on lane */ + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 1); + + dev_dbg(dsim->dev, "byte clock is %luMHz\n", + (byte_clk / MHZ)); + dev_dbg(dsim->dev, "escape clock that user's need is %lu\n", + (dsim->dsim_config->esc_clk / MHZ)); + dev_dbg(dsim->dev, "escape clock divider is %x\n", esc_div); + dev_dbg(dsim->dev, "escape clock is %luMHz\n", + ((byte_clk / esc_div) / MHZ)); + + if ((byte_clk / esc_div) > escape_clk) { + esc_clk_error_rate = escape_clk / + (byte_clk / esc_div); + dev_warn(dsim->dev, "error rate is %lu over.\n", + (esc_clk_error_rate / 100)); + } else if ((byte_clk / esc_div) < (escape_clk)) { + esc_clk_error_rate = (byte_clk / esc_div) / + escape_clk; + dev_warn(dsim->dev, "error rate is %lu under.\n", + (esc_clk_error_rate / 100)); + } + } else { + exynos_mipi_dsi_enable_esc_clk_on_lane(dsim, + (DSIM_LANE_CLOCK | dsim->data_lane), 0); + exynos_mipi_dsi_set_esc_clk_prs(dsim, 0, 0); + + /* disable escape clock. */ + exynos_mipi_dsi_enable_byte_clock(dsim, 0); + + if (byte_clk_sel == DSIM_PLL_OUT_DIV8) + exynos_mipi_dsi_pll_on(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim) +{ + dsim->state = DSIM_STATE_INIT; + + switch (dsim->dsim_config->e_no_data_lane) { + case DSIM_DATA_LANE_1: + dsim->data_lane = DSIM_LANE_DATA0; + break; + case DSIM_DATA_LANE_2: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1; + break; + case DSIM_DATA_LANE_3: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2; + break; + case DSIM_DATA_LANE_4: + dsim->data_lane = DSIM_LANE_DATA0 | DSIM_LANE_DATA1 | + DSIM_LANE_DATA2 | DSIM_LANE_DATA3; + break; + default: + dev_info(dsim->dev, "data lane is invalid.\n"); + return -EINVAL; + }; + + exynos_mipi_dsi_sw_reset(dsim); + exynos_mipi_dsi_func_reset(dsim); + + exynos_mipi_dsi_dp_dn_swap(dsim, 0); + + return 0; +} + +void exynos_mipi_dsi_init_interrupt(struct mipi_dsim_device *dsim) +{ + unsigned int src = 0; + + src = (INTSRC_SFR_FIFO_EMPTY | INTSRC_RX_DATA_DONE); + exynos_mipi_dsi_set_interrupt(dsim, src, 1); + + src = 0; + src = ~(INTMSK_RX_DONE | INTMSK_FIFO_EMPTY); + exynos_mipi_dsi_set_interrupt_mask(dsim, src, 1); +} + +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + /* enable only frame done interrupt */ + exynos_mipi_dsi_set_interrupt_mask(dsim, INTMSK_FRAME_DONE, enable); + + return 0; +} + +void exynos_mipi_dsi_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + + /* consider Main display and Sub display. */ + + exynos_mipi_dsi_set_main_stand_by(dsim, enable); +} + +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + struct mipi_dsim_platform_data *dsim_pd; + struct fb_videomode *timing; + + dsim_pd = (struct mipi_dsim_platform_data *)dsim->pd; + timing = (struct fb_videomode *)dsim_pd->lcd_panel_info; + + /* in case of VIDEO MODE (RGB INTERFACE), it sets polarities. */ + if (dsim_config->e_interface == (u32) DSIM_VIDEO) { + if (dsim_config->auto_vertical_cnt == 0) { + exynos_mipi_dsi_set_main_disp_vporch(dsim, + dsim_config->cmd_allow, + timing->upper_margin, + timing->lower_margin); + exynos_mipi_dsi_set_main_disp_hporch(dsim, + timing->left_margin, + timing->right_margin); + exynos_mipi_dsi_set_main_disp_sync_area(dsim, + timing->vsync_len, + timing->hsync_len); + } + } + + exynos_mipi_dsi_set_main_disp_resol(dsim, timing->xres, + timing->yres); + + exynos_mipi_dsi_display_config(dsim, dsim_config); + + dev_info(dsim->dev, "lcd panel ==> width = %d, height = %d\n", + timing->xres, timing->yres); + + return 0; +} + +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim) +{ + unsigned int time_out = 100; + + switch (dsim->state) { + case DSIM_STATE_INIT: + exynos_mipi_dsi_init_fifo_pointer(dsim, 0x1f); + + /* dsi configuration */ + exynos_mipi_dsi_init_config(dsim); + exynos_mipi_dsi_enable_lane(dsim, DSIM_LANE_CLOCK, 1); + exynos_mipi_dsi_enable_lane(dsim, dsim->data_lane, 1); + + /* set clock configuration */ + exynos_mipi_dsi_set_clock(dsim, dsim->dsim_config->e_byte_clk, 1); + + /* check clock and data lane state are stop state */ + while (!(exynos_mipi_dsi_is_lane_state(dsim))) { + time_out--; + if (time_out == 0) { + dev_err(dsim->dev, + "DSI Master is not stop state.\n"); + dev_err(dsim->dev, + "Check initialization process\n"); + + return -EINVAL; + } + } + if (time_out != 0) { + dev_info(dsim->dev, + "DSI Master driver has been completed.\n"); + dev_info(dsim->dev, "DSI Master state is stop state\n"); + } + + dsim->state = DSIM_STATE_STOP; + + /* BTA sequence counters */ + exynos_mipi_dsi_set_stop_state_counter(dsim, + dsim->dsim_config->stop_holding_cnt); + exynos_mipi_dsi_set_bta_timeout(dsim, + dsim->dsim_config->bta_timeout); + exynos_mipi_dsi_set_lpdr_timeout(dsim, + dsim->dsim_config->rx_timeout); + + return 0; + default: + dev_info(dsim->dev, "DSI Master is already init.\n"); + return 0; + } + + return 0; +} + +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim) +{ + if (dsim->state != DSIM_STATE_STOP) { + dev_warn(dsim->dev, "DSIM is not in stop state.\n"); + return 0; + } + + if (dsim->e_clk_src == DSIM_EXT_CLK_BYPASS) { + dev_warn(dsim->dev, "clock source is external bypass.\n"); + return 0; + } + + dsim->state = DSIM_STATE_HSCLKEN; + + /* set LCDC and CPU transfer mode to HS. */ + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + exynos_mipi_dsi_enable_hs_clock(dsim, 1); + + return 0; +} + +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode) +{ + if (mode) { + if (dsim->state != DSIM_STATE_HSCLKEN) { + dev_err(dsim->dev, "HS Clock lane is not enabled.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_lcdc_transfer_mode(dsim, 0); + } else { + if (dsim->state == DSIM_STATE_INIT || dsim->state == + DSIM_STATE_ULPS) { + dev_err(dsim->dev, + "DSI Master is not STOP or HSDT state.\n"); + return -EINVAL; + } + + exynos_mipi_dsi_set_cpu_transfer_mode(dsim, 0); + } + + return 0; +} + +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim) +{ + return _exynos_mipi_dsi_get_frame_done_status(dsim); +} + +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + _exynos_mipi_dsi_clear_frame_done(dsim); + + return 0; +} + +int exynos_mipi_dsi_fifo_clear(struct mipi_dsim_device *dsim, + unsigned int val) +{ + int try = TRY_FIFO_CLEAR; + + exynos_mipi_dsi_sw_reset_release(dsim); + exynos_mipi_dsi_func_reset(dsim); + + do { + if (exynos_mipi_dsi_get_sw_reset_release(dsim)) { + exynos_mipi_dsi_init_interrupt(dsim); + dev_dbg(dsim->dev, "reset release done.\n"); + return 0; + } + } while (--try); + + dev_err(dsim->dev, "failed to clear dsim fifo.\n"); + return -EAGAIN; +} + +MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("Samusung SoC MIPI-DSI common driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/exynos/exynos_mipi_dsi_common.h b/drivers/video/exynos/exynos_mipi_dsi_common.h new file mode 100644 index 00000000000..412552274df --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi_common.h @@ -0,0 +1,46 @@ +/* linux/drivers/video/exynos_mipi_dsi_common.h + * + * Header file for 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_COMMON_H +#define _EXYNOS_MIPI_DSI_COMMON_H + +static DECLARE_COMPLETION(dsim_rd_comp); +static DECLARE_COMPLETION(dsim_wr_comp); + +int exynos_mipi_dsi_wr_data(struct mipi_dsim_device *dsim, unsigned int data_id, + const unsigned char *data0, unsigned int data_size); +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); +irqreturn_t exynos_mipi_dsi_interrupt_handler(int irq, void *dev_id); +void exynos_mipi_dsi_init_interrupt(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_init_dsim(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable); +int exynos_mipi_dsi_set_display_mode(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_info); +int exynos_mipi_dsi_init_link(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_hs_enable(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_set_data_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int mode); +int exynos_mipi_dsi_enable_frame_done_int(struct mipi_dsim_device *dsim, + unsigned int enable); +int exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); + +extern struct fb_info *registered_fb[FB_MAX] __read_mostly; + +int exynos_mipi_dsi_fifo_clear(struct mipi_dsim_device *dsim, + unsigned int val); + +#endif /* _EXYNOS_MIPI_DSI_COMMON_H */ diff --git a/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c new file mode 100644 index 00000000000..0ef38ce72af --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c @@ -0,0 +1,618 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_lowlevel.c + * + * Samsung SoC MIPI-DSI lowlevel 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/delay.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/ctype.h> +#include <linux/io.h> + +#include <video/exynos_mipi_dsim.h> + +#include <mach/map.h> + +#include "exynos_mipi_dsi_regs.h" + +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_SWRST); + + reg |= DSIM_FUNCRST; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SWRST); +} + +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_SWRST); + + reg |= DSIM_SWRST; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SWRST); +} + +void exynos_mipi_dsi_sw_reset_release(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + reg |= INTSRC_SW_RST_RELEASE; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +int exynos_mipi_dsi_get_sw_reset_release(struct mipi_dsim_device *dsim) +{ + return (readl(dsim->reg_base + EXYNOS_DSIM_INTSRC)) & + INTSRC_SW_RST_RELEASE; +} + +unsigned int exynos_mipi_dsi_read_interrupt_mask(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_INTMSK); + + return reg; +} + +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask) +{ + unsigned int reg = 0; + + if (mask) + reg |= mode; + else + reg &= ~mode; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTMSK); +} + +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); + + writel(reg & ~(cfg), dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); + mdelay(10); + reg |= cfg; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_FIFOCTRL); +} + +/* + * this function set PLL P, M and S value in D-PHY + */ +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value) +{ + writel(DSIM_AFC_CTL(value), dsim->reg_base + EXYNOS_DSIM_PHYACCHR); +} + +void exynos_mipi_dsi_set_main_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_MDRESOL); + + reg &= ~DSIM_MAIN_STAND_BY; + + if (enable) + reg |= DSIM_MAIN_STAND_BY; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); +} + +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol) +{ + unsigned int reg; + + /* standby should be set after configuration so set to not ready*/ + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MDRESOL)) & + ~(DSIM_MAIN_STAND_BY); + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); + + reg &= ~((0x7ff << 16) | (0x7ff << 0)); + reg |= DSIM_MAIN_VRESOL(height_resol) | DSIM_MAIN_HRESOL(width_resol); + + reg |= DSIM_MAIN_STAND_BY; + writel(reg, dsim->reg_base + EXYNOS_DSIM_MDRESOL); +} + +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MVPORCH)) & + ~((DSIM_CMD_ALLOW_MASK) | (DSIM_STABLE_VFP_MASK) | + (DSIM_MAIN_VBP_MASK)); + + reg |= (DSIM_CMD_ALLOW_SHIFT(cmd_allow & 0xf) | + DSIM_STABLE_VFP_SHIFT(vfront & 0x7ff) | + DSIM_MAIN_VBP_SHIFT(vback & 0x7ff)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MVPORCH); +} + +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MHPORCH)) & + ~((DSIM_MAIN_HFP_MASK) | (DSIM_MAIN_HBP_MASK)); + + reg |= DSIM_MAIN_HFP_SHIFT(front) | DSIM_MAIN_HBP_SHIFT(back); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MHPORCH); +} + +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_MSYNC)) & + ~((DSIM_MAIN_VSA_MASK) | (DSIM_MAIN_HSA_MASK)); + + reg |= (DSIM_MAIN_VSA_SHIFT(vert & 0x3ff) | + DSIM_MAIN_HSA_SHIFT(hori)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_MSYNC); +} + +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori) +{ + unsigned int reg; + + reg = (readl(dsim->reg_base + EXYNOS_DSIM_SDRESOL)) & + ~(DSIM_SUB_STANDY_MASK); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); + + reg &= ~(DSIM_SUB_VRESOL_MASK) | ~(DSIM_SUB_HRESOL_MASK); + reg |= (DSIM_SUB_VRESOL_SHIFT(vert & 0x7ff) | + DSIM_SUB_HRESOL_SHIFT(hori & 0x7ff)); + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); + + reg |= DSIM_SUB_STANDY_SHIFT(1); + writel(reg, dsim->reg_base + EXYNOS_DSIM_SDRESOL); +} + +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim) +{ + struct mipi_dsim_config *dsim_config = dsim->dsim_config; + + unsigned int cfg = (readl(dsim->reg_base + EXYNOS_DSIM_CONFIG)) & + ~((1 << 28) | (0x1f << 20) | (0x3 << 5)); + + cfg = ((DSIM_AUTO_FLUSH(dsim_config->auto_flush)) | + (DSIM_EOT_DISABLE(dsim_config->eot_disable)) | + (DSIM_AUTO_MODE_SHIFT(dsim_config->auto_vertical_cnt)) | + (DSIM_HSE_MODE_SHIFT(dsim_config->hse)) | + (DSIM_HFP_MODE_SHIFT(dsim_config->hfp)) | + (DSIM_HBP_MODE_SHIFT(dsim_config->hbp)) | + (DSIM_HSA_MODE_SHIFT(dsim_config->hsa)) | + (DSIM_NUM_OF_DATALANE_SHIFT(dsim_config->e_no_data_lane))); + + writel(cfg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config) +{ + u32 reg = (readl(dsim->reg_base + EXYNOS_DSIM_CONFIG)) & + ~((0x3 << 26) | (1 << 25) | (0x3 << 18) | (0x7 << 12) | + (0x3 << 16) | (0x7 << 8)); + + if (dsim_config->e_interface == DSIM_VIDEO) + reg |= (1 << 25); + else if (dsim_config->e_interface == DSIM_COMMAND) + reg &= ~(1 << 25); + else { + dev_err(dsim->dev, "unknown lcd type.\n"); + return; + } + + /* main lcd */ + reg |= ((u8) (dsim_config->e_burst_mode) & 0x3) << 26 | + ((u8) (dsim_config->e_virtual_ch) & 0x3) << 18 | + ((u8) (dsim_config->e_pixel_format) & 0x7) << 12; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, unsigned int lane, + unsigned int enable) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_CONFIG); + + if (enable) + reg |= DSIM_LANE_ENx(lane); + else + reg &= ~DSIM_LANE_ENx(lane); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + + +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count) +{ + unsigned int cfg; + + /* get the data lane number. */ + cfg = DSIM_NUM_OF_DATALANE_SHIFT(count); + + writel(cfg, dsim->reg_base + EXYNOS_DSIM_CONFIG); +} + +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, unsigned int enable, + unsigned int afc_code) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PHYACCHR); + + if (enable) { + reg |= (1 << 14); + reg &= ~(0x7 << 5); + reg |= (afc_code & 0x7) << 5; + } else + reg &= ~(1 << 14); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PHYACCHR); +} + +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_PLL_BYPASS_SHIFT(0x1)); + + reg |= DSIM_PLL_BYPASS_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_set_pll_pms(struct mipi_dsim_device *dsim, unsigned int p, + unsigned int m, unsigned int s) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL); + + reg |= ((p & 0x3f) << 13) | ((m & 0x1ff) << 4) | ((s & 0x7) << 1); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(DSIM_FREQ_BAND_SHIFT(0x1f)); + + reg |= DSIM_FREQ_BAND_SHIFT(freq_band & 0x1f); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0x7ffff << 1); + + reg |= (pre_divider & 0x3f) << 13 | (main_divider & 0x1ff) << 4 | + (scaler & 0x7) << 1; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time) +{ + writel(lock_time, dsim->reg_base + EXYNOS_DSIM_PLLTMR); +} + +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(DSIM_PLL_EN_SHIFT(0x1)); + + reg |= DSIM_PLL_EN_SHIFT(enable & 0x1); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_BYTE_CLK_SRC_SHIFT(0x3)); + + reg |= (DSIM_BYTE_CLK_SRC_SHIFT(src)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_BYTE_CLKEN_SHIFT(0x1)); + + reg |= DSIM_BYTE_CLKEN_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_ESC_CLKEN_SHIFT(0x1) | 0xffff); + + reg |= DSIM_ESC_CLKEN_SHIFT(enable); + if (enable) + reg |= prs_val; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL); + + if (enable) + reg |= DSIM_LANE_ESC_CLKEN(lane_sel); + else + + reg &= ~DSIM_LANE_ESC_CLKEN(lane_sel); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE)) & + ~(DSIM_FORCE_STOP_STATE_SHIFT(0x1)); + + reg |= (DSIM_FORCE_STOP_STATE_SHIFT(enable & 0x1)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_STATUS); + + /** + * check clock and data lane states. + * if MIPI-DSI controller was enabled at bootloader then + * TX_READY_HS_CLK is enabled otherwise STOP_STATE_CLK. + * so it should be checked for two case. + */ + if ((reg & DSIM_STOP_STATE_DAT(0xf)) && + ((reg & DSIM_STOP_STATE_CLK) || + (reg & DSIM_TX_READY_HS_CLK))) + return 1; + + return 0; +} + +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE)) & + ~(DSIM_STOP_STATE_CNT_SHIFT(0x7ff)); + + reg |= (DSIM_STOP_STATE_CNT_SHIFT(cnt_val & 0x7ff)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_TIMEOUT)) & + ~(DSIM_BTA_TOUT_SHIFT(0xff)); + + reg |= (DSIM_BTA_TOUT_SHIFT(timeout)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_TIMEOUT); +} + +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_TIMEOUT)) & + ~(DSIM_LPDR_TOUT_SHIFT(0xffff)); + + reg |= (DSIM_LPDR_TOUT_SHIFT(timeout)); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_TIMEOUT); +} + +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE); + + reg &= ~DSIM_CMD_LPDT_LP; + + if (lp) + reg |= DSIM_CMD_LPDT_LP; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_ESCMODE); + + reg &= ~DSIM_TX_LPDT_LP; + + if (lp) + reg |= DSIM_TX_LPDT_LP; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_ESCMODE); +} + +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_CLKCTRL)) & + ~(DSIM_TX_REQUEST_HSCLK_SHIFT(0x1)); + + reg |= DSIM_TX_REQUEST_HSCLK_SHIFT(enable); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_CLKCTRL); +} + +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_PHYACCHR1); + + reg &= ~(0x3 << 0); + reg |= (swap_en & 0x3) << 0; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PHYACCHR1); +} + +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0xf << 28); + + reg |= ((hs_zero & 0xf) << 28); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep) +{ + unsigned int reg = (readl(dsim->reg_base + EXYNOS_DSIM_PLLCTRL)) & + ~(0x7 << 20); + + reg |= ((prep & 0x7) << 20); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PLLCTRL); +} + +unsigned int exynos_mipi_dsi_read_interrupt(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim, + unsigned int src) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + reg |= src; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_set_interrupt(struct mipi_dsim_device *dsim, + unsigned int src, unsigned int enable) +{ + unsigned int reg = 0; + + if (enable) + reg |= src; + else + reg &= ~src; + + writel(reg, dsim->reg_base + EXYNOS_DSIM_INTSRC); +} + +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim) +{ + unsigned int reg; + + reg = readl(dsim->reg_base + EXYNOS_DSIM_STATUS); + + return reg & (1 << 31) ? 1 : 0; +} + +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_FIFOCTRL) & ~(0x1f); +} + +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0, unsigned int data1) +{ + unsigned int reg = (data1 << 16) | (data0 << 8) | ((di & 0x3f) << 0); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PKTHDR); +} + +void exynos_mipi_dsi_rd_tx_header(struct mipi_dsim_device *dsim, + unsigned int di, unsigned int data0) +{ + unsigned int reg = (data0 << 8) | (di << 0); + + writel(reg, dsim->reg_base + EXYNOS_DSIM_PKTHDR); +} + +unsigned int exynos_mipi_dsi_rd_rx_fifo(struct mipi_dsim_device *dsim) +{ + return readl(dsim->reg_base + EXYNOS_DSIM_RXFIFO); +} + +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + return (reg & INTSRC_FRAME_DONE) ? 1 : 0; +} + +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim) +{ + unsigned int reg = readl(dsim->reg_base + EXYNOS_DSIM_INTSRC); + + writel(reg | INTSRC_FRAME_DONE, dsim->reg_base + + EXYNOS_DSIM_INTSRC); +} + +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data) +{ + writel(tx_data, dsim->reg_base + EXYNOS_DSIM_PAYLOAD); +} diff --git a/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h new file mode 100644 index 00000000000..85460701c7e --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h @@ -0,0 +1,112 @@ +/* linux/drivers/video/exynos/exynos_mipi_dsi_lowlevel.h + * + * Header file for Samsung SoC MIPI-DSI lowlevel 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_LOWLEVEL_H +#define _EXYNOS_MIPI_DSI_LOWLEVEL_H + +void exynos_mipi_dsi_func_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_reset(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_sw_reset_release(struct mipi_dsim_device *dsim); +int exynos_mipi_dsi_get_sw_reset_release(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_interrupt_mask(struct mipi_dsim_device *dsim, + unsigned int mode, unsigned int mask); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_init_fifo_pointer(struct mipi_dsim_device *dsim, + unsigned int cfg); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_phy_tunning(struct mipi_dsim_device *dsim, + unsigned int value); +void exynos_mipi_dsi_set_main_stand_by(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_main_disp_resol(struct mipi_dsim_device *dsim, + unsigned int width_resol, unsigned int height_resol); +void exynos_mipi_dsi_set_main_disp_vporch(struct mipi_dsim_device *dsim, + unsigned int cmd_allow, unsigned int vfront, unsigned int vback); +void exynos_mipi_dsi_set_main_disp_hporch(struct mipi_dsim_device *dsim, + unsigned int front, unsigned int back); +void exynos_mipi_dsi_set_main_disp_sync_area(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_set_sub_disp_resol(struct mipi_dsim_device *dsim, + unsigned int vert, unsigned int hori); +void exynos_mipi_dsi_init_config(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_display_config(struct mipi_dsim_device *dsim, + struct mipi_dsim_config *dsim_config); +void exynos_mipi_dsi_set_data_lane_number(struct mipi_dsim_device *dsim, + unsigned int count); +void exynos_mipi_dsi_enable_lane(struct mipi_dsim_device *dsim, unsigned int lane, + unsigned int enable); +void exynos_mipi_dsi_enable_afc(struct mipi_dsim_device *dsim, unsigned int enable, + unsigned int afc_code); +void exynos_mipi_dsi_enable_pll_bypass(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_pll_pms(struct mipi_dsim_device *dsim, unsigned int p, + unsigned int m, unsigned int s); +void exynos_mipi_dsi_pll_freq_band(struct mipi_dsim_device *dsim, + unsigned int freq_band); +void exynos_mipi_dsi_pll_freq(struct mipi_dsim_device *dsim, + unsigned int pre_divider, unsigned int main_divider, + unsigned int scaler); +void exynos_mipi_dsi_pll_stable_time(struct mipi_dsim_device *dsim, + unsigned int lock_time); +void exynos_mipi_dsi_enable_pll(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_byte_clock_src(struct mipi_dsim_device *dsim, + unsigned int src); +void exynos_mipi_dsi_enable_byte_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_set_esc_clk_prs(struct mipi_dsim_device *dsim, + unsigned int enable, unsigned int prs_val); +void exynos_mipi_dsi_enable_esc_clk_on_lane(struct mipi_dsim_device *dsim, + unsigned int lane_sel, unsigned int enable); +void exynos_mipi_dsi_force_dphy_stop_state(struct mipi_dsim_device *dsim, + unsigned int enable); +unsigned int exynos_mipi_dsi_is_lane_state(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_set_stop_state_counter(struct mipi_dsim_device *dsim, + unsigned int cnt_val); +void exynos_mipi_dsi_set_bta_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lpdr_timeout(struct mipi_dsim_device *dsim, + unsigned int timeout); +void exynos_mipi_dsi_set_lcdc_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_set_cpu_transfer_mode(struct mipi_dsim_device *dsim, + unsigned int lp); +void exynos_mipi_dsi_enable_hs_clock(struct mipi_dsim_device *dsim, + unsigned int enable); +void exynos_mipi_dsi_dp_dn_swap(struct mipi_dsim_device *dsim, + unsigned int swap_en); +void exynos_mipi_dsi_hs_zero_ctrl(struct mipi_dsim_device *dsim, + unsigned int hs_zero); +void exynos_mipi_dsi_prep_ctrl(struct mipi_dsim_device *dsim, unsigned int prep); +unsigned int exynos_mipi_dsi_read_interrupt(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_read_interrupt_mask(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_clear_interrupt(struct mipi_dsim_device *dsim, + unsigned int src); +void exynos_mipi_dsi_set_interrupt(struct mipi_dsim_device *dsim, + unsigned int src, unsigned int enable); +unsigned int exynos_mipi_dsi_is_pll_stable(struct mipi_dsim_device *dsim); +unsigned int exynos_mipi_dsi_get_fifo_state(struct mipi_dsim_device *dsim); +unsigned int _exynos_mipi_dsi_get_frame_done_status(struct mipi_dsim_device *dsim); +void _exynos_mipi_dsi_clear_frame_done(struct mipi_dsim_device *dsim); +void exynos_mipi_dsi_wr_tx_header(struct mipi_dsim_device *dsim, unsigned int di, + unsigned int data0, unsigned int data1); +void exynos_mipi_dsi_wr_tx_data(struct mipi_dsim_device *dsim, + unsigned int tx_data); +void exynos_mipi_dsi_rd_tx_header(struct mipi_dsim_device *dsim, + unsigned int data0, unsigned int data1); +unsigned int exynos_mipi_dsi_rd_rx_fifo(struct mipi_dsim_device *dsim); + +#endif /* _EXYNOS_MIPI_DSI_LOWLEVEL_H */ diff --git a/drivers/video/exynos/exynos_mipi_dsi_regs.h b/drivers/video/exynos/exynos_mipi_dsi_regs.h new file mode 100644 index 00000000000..4227106d3fd --- /dev/null +++ b/drivers/video/exynos/exynos_mipi_dsi_regs.h @@ -0,0 +1,149 @@ +/* linux/driver/video/exynos/exynos_mipi_dsi_regs.h + * + * Register definition file for Samsung 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. +*/ + +#ifndef _EXYNOS_MIPI_DSI_REGS_H +#define _EXYNOS_MIPI_DSI_REGS_H + +#define EXYNOS_DSIM_STATUS 0x0 /* Status register */ +#define EXYNOS_DSIM_SWRST 0x4 /* Software reset register */ +#define EXYNOS_DSIM_CLKCTRL 0x8 /* Clock control register */ +#define EXYNOS_DSIM_TIMEOUT 0xc /* Time out register */ +#define EXYNOS_DSIM_CONFIG 0x10 /* Configuration register */ +#define EXYNOS_DSIM_ESCMODE 0x14 /* Escape mode register */ + +/* Main display image resolution register */ +#define EXYNOS_DSIM_MDRESOL 0x18 +#define EXYNOS_DSIM_MVPORCH 0x1c /* Main display Vporch register */ +#define EXYNOS_DSIM_MHPORCH 0x20 /* Main display Hporch register */ +#define EXYNOS_DSIM_MSYNC 0x24 /* Main display sync area register */ + +/* Sub display image resolution register */ +#define EXYNOS_DSIM_SDRESOL 0x28 +#define EXYNOS_DSIM_INTSRC 0x2c /* Interrupt source register */ +#define EXYNOS_DSIM_INTMSK 0x30 /* Interrupt mask register */ +#define EXYNOS_DSIM_PKTHDR 0x34 /* Packet Header FIFO register */ +#define EXYNOS_DSIM_PAYLOAD 0x38 /* Payload FIFO register */ +#define EXYNOS_DSIM_RXFIFO 0x3c /* Read FIFO register */ +#define EXYNOS_DSIM_FIFOTHLD 0x40 /* FIFO threshold level register */ +#define EXYNOS_DSIM_FIFOCTRL 0x44 /* FIFO status and control register */ + +/* FIFO memory AC characteristic register */ +#define EXYNOS_DSIM_PLLCTRL 0x4c /* PLL control register */ +#define EXYNOS_DSIM_PLLTMR 0x50 /* PLL timer register */ +#define EXYNOS_DSIM_PHYACCHR 0x54 /* D-PHY AC characteristic register */ +#define EXYNOS_DSIM_PHYACCHR1 0x58 /* D-PHY AC characteristic register1 */ + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +/* EXYNOS_DSIM_TIMEOUT */ +#define DSIM_LPDR_TOUT_SHIFT(x) ((x) << 0) +#define DSIM_BTA_TOUT_SHIFT(x) ((x) << 16) + +/* EXYNOS_DSIM_CLKCTRL */ +#define DSIM_LANE_ESC_CLKEN(x) (((x) & 0x1f) << 19) +#define DSIM_BYTE_CLKEN_SHIFT(x) ((x) << 24) +#define DSIM_BYTE_CLK_SRC_SHIFT(x) ((x) << 25) +#define DSIM_PLL_BYPASS_SHIFT(x) ((x) << 27) +#define DSIM_ESC_CLKEN_SHIFT(x) ((x) << 28) +#define DSIM_TX_REQUEST_HSCLK_SHIFT(x) ((x) << 31) + +/* EXYNOS_DSIM_CONFIG */ +#define DSIM_LANE_ENx(x) (((x) & 0x1f) << 0) +#define DSIM_NUM_OF_DATALANE_SHIFT(x) ((x) << 5) +#define DSIM_HSA_MODE_SHIFT(x) ((x) << 20) +#define DSIM_HBP_MODE_SHIFT(x) ((x) << 21) +#define DSIM_HFP_MODE_SHIFT(x) ((x) << 22) +#define DSIM_HSE_MODE_SHIFT(x) ((x) << 23) +#define DSIM_AUTO_MODE_SHIFT(x) ((x) << 24) +#define DSIM_EOT_DISABLE(x) ((x) << 28) +#define DSIM_AUTO_FLUSH(x) ((x) << 29) + +#define DSIM_NUM_OF_DATA_LANE(x) ((x) << DSIM_NUM_OF_DATALANE_SHIFT) + +/* EXYNOS_DSIM_ESCMODE */ +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_STOP_STATE_SHIFT(x) ((x) << 20) +#define DSIM_STOP_STATE_CNT_SHIFT(x) ((x) << 21) + +/* EXYNOS_DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x) (((x) & 0x7ff) << 16) +#define DSIM_MAIN_HRESOL(x) (((x) & 0X7ff) << 0) + +/* EXYNOS_DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW_SHIFT(x) ((x) << 28) +#define DSIM_STABLE_VFP_SHIFT(x) ((x) << 16) +#define DSIM_MAIN_VBP_SHIFT(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* EXYNOS_DSIM_MHPORCH */ +#define DSIM_MAIN_HFP_SHIFT(x) ((x) << 16) +#define DSIM_MAIN_HBP_SHIFT(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* EXYNOS_DSIM_MSYNC */ +#define DSIM_MAIN_VSA_SHIFT(x) ((x) << 22) +#define DSIM_MAIN_HSA_SHIFT(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* EXYNOS_DSIM_SDRESOL */ +#define DSIM_SUB_STANDY_SHIFT(x) ((x) << 31) +#define DSIM_SUB_VRESOL_SHIFT(x) ((x) << 16) +#define DSIM_SUB_HRESOL_SHIFT(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* EXYNOS_DSIM_INTSRC */ +#define INTSRC_PLL_STABLE (1 << 31) +#define INTSRC_SW_RST_RELEASE (1 << 30) +#define INTSRC_SFR_FIFO_EMPTY (1 << 29) +#define INTSRC_FRAME_DONE (1 << 24) +#define INTSRC_RX_DATA_DONE (1 << 18) + +/* EXYNOS_DSIM_INTMSK */ +#define INTMSK_FIFO_EMPTY (1 << 29) +#define INTMSK_BTA (1 << 25) +#define INTMSK_FRAME_DONE (1 << 24) +#define INTMSK_RX_TIMEOUT (1 << 21) +#define INTMSK_BTA_TIMEOUT (1 << 20) +#define INTMSK_RX_DONE (1 << 18) +#define INTMSK_RX_TE (1 << 17) +#define INTMSK_RX_ACK (1 << 16) +#define INTMSK_RX_ECC_ERR (1 << 15) +#define INTMSK_RX_CRC_ERR (1 << 14) + +/* EXYNOS_DSIM_FIFOCTRL */ +#define SFR_HEADER_EMPTY (1 << 22) + +/* EXYNOS_DSIM_PHYACCHR */ +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* EXYNOS_DSIM_PLLCTRL */ +#define DSIM_PLL_EN_SHIFT(x) ((x) << 23) +#define DSIM_FREQ_BAND_SHIFT(x) ((x) << 24) + +#endif /* _EXYNOS_MIPI_DSI_REGS_H */ diff --git a/drivers/video/exynos/s6e8ax0.c b/drivers/video/exynos/s6e8ax0.c new file mode 100644 index 00000000000..4aa9ac6218b --- /dev/null +++ b/drivers/video/exynos/s6e8ax0.c @@ -0,0 +1,898 @@ +/* linux/drivers/video/exynos/s6e8ax0.c + * + * MIPI-DSI based s6e8ax0 AMOLED lcd 4.65 inch panel driver. + * + * 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/ctype.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/lcd.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/regulator/consumer.h> + +#include <video/mipi_display.h> +#include <video/exynos_mipi_dsim.h> + +#define LDI_MTP_LENGTH 24 +#define DSIM_PM_STABLE_TIME 10 +#define MIN_BRIGHTNESS 0 +#define MAX_BRIGHTNESS 24 +#define GAMMA_TABLE_COUNT 26 + +#define POWER_IS_ON(pwr) ((pwr) == FB_BLANK_UNBLANK) +#define POWER_IS_OFF(pwr) ((pwr) == FB_BLANK_POWERDOWN) +#define POWER_IS_NRM(pwr) ((pwr) == FB_BLANK_NORMAL) + +#define lcd_to_master(a) (a->dsim_dev->master) +#define lcd_to_master_ops(a) ((lcd_to_master(a))->master_ops) + +enum { + DSIM_NONE_STATE = 0, + DSIM_RESUME_COMPLETE = 1, + DSIM_FRAME_DONE = 2, +}; + +struct s6e8ax0 { + struct device *dev; + unsigned int power; + unsigned int id; + unsigned int gamma; + unsigned int acl_enable; + unsigned int cur_acl; + + struct lcd_device *ld; + struct backlight_device *bd; + + struct mipi_dsim_lcd_device *dsim_dev; + struct lcd_platform_data *ddi_pd; + struct mutex lock; + bool enabled; +}; + + +static struct regulator_bulk_data supplies[] = { + { .supply = "vdd3", }, + { .supply = "vci", }, +}; + +static void s6e8ax0_regulator_enable(struct s6e8ax0 *lcd) +{ + int ret = 0; + struct lcd_platform_data *pd = NULL; + + pd = lcd->ddi_pd; + mutex_lock(&lcd->lock); + if (!lcd->enabled) { + ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies); + if (ret) + goto out; + + lcd->enabled = true; + } + msleep(pd->power_on_delay); +out: + mutex_unlock(&lcd->lock); +} + +static void s6e8ax0_regulator_disable(struct s6e8ax0 *lcd) +{ + int ret = 0; + + mutex_lock(&lcd->lock); + if (lcd->enabled) { + ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies); + if (ret) + goto out; + + lcd->enabled = false; + } +out: + mutex_unlock(&lcd->lock); +} + +static const unsigned char s6e8ax0_22_gamma_30[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xf5, 0x00, 0xff, 0xad, 0xaf, + 0xbA, 0xc3, 0xd8, 0xc5, 0x9f, 0xc6, 0x9e, 0xc1, 0xdc, 0xc0, + 0x00, 0x61, 0x00, 0x5a, 0x00, 0x74, +}; + +static const unsigned char s6e8ax0_22_gamma_50[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xe8, 0x1f, 0xf7, 0xad, 0xc0, + 0xb5, 0xc4, 0xdc, 0xc4, 0x9e, 0xc6, 0x9c, 0xbb, 0xd8, 0xbb, + 0x00, 0x70, 0x00, 0x68, 0x00, 0x86, +}; + +static const unsigned char s6e8ax0_22_gamma_60[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xde, 0x1f, 0xef, 0xad, 0xc4, + 0xb3, 0xc3, 0xdd, 0xc4, 0x9e, 0xc6, 0x9c, 0xbc, 0xd6, 0xba, + 0x00, 0x75, 0x00, 0x6e, 0x00, 0x8d, +}; + +static const unsigned char s6e8ax0_22_gamma_70[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xd8, 0x1f, 0xe7, 0xaf, 0xc8, + 0xb4, 0xc4, 0xdd, 0xc3, 0x9d, 0xc6, 0x9c, 0xbb, 0xd6, 0xb9, + 0x00, 0x7a, 0x00, 0x72, 0x00, 0x93, +}; + +static const unsigned char s6e8ax0_22_gamma_80[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc9, 0x1f, 0xde, 0xae, 0xc9, + 0xb1, 0xc3, 0xdd, 0xc2, 0x9d, 0xc5, 0x9b, 0xbc, 0xd6, 0xbb, + 0x00, 0x7f, 0x00, 0x77, 0x00, 0x99, +}; + +static const unsigned char s6e8ax0_22_gamma_90[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xc7, 0x1f, 0xd9, 0xb0, 0xcc, + 0xb2, 0xc3, 0xdc, 0xc1, 0x9c, 0xc6, 0x9c, 0xbc, 0xd4, 0xb9, + 0x00, 0x83, 0x00, 0x7b, 0x00, 0x9e, +}; + +static const unsigned char s6e8ax0_22_gamma_100[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xbd, 0x80, 0xcd, 0xba, 0xce, + 0xb3, 0xc4, 0xde, 0xc3, 0x9c, 0xc4, 0x9, 0xb8, 0xd3, 0xb6, + 0x00, 0x88, 0x00, 0x80, 0x00, 0xa5, +}; + +static const unsigned char s6e8ax0_22_gamma_120[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb9, 0x95, 0xc8, 0xb1, 0xcf, + 0xb2, 0xc6, 0xdf, 0xc5, 0x9b, 0xc3, 0x99, 0xb6, 0xd2, 0xb6, + 0x00, 0x8f, 0x00, 0x86, 0x00, 0xac, +}; + +static const unsigned char s6e8ax0_22_gamma_130[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc7, 0xb1, 0xd0, + 0xb2, 0xc4, 0xdd, 0xc3, 0x9a, 0xc3, 0x98, 0xb6, 0xd0, 0xb4, + 0x00, 0x92, 0x00, 0x8a, 0x00, 0xb1, +}; + +static const unsigned char s6e8ax0_22_gamma_140[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb7, 0xa0, 0xc5, 0xb2, 0xd0, + 0xb3, 0xc3, 0xde, 0xc3, 0x9b, 0xc2, 0x98, 0xb6, 0xd0, 0xb4, + 0x00, 0x95, 0x00, 0x8d, 0x00, 0xb5, +}; + +static const unsigned char s6e8ax0_22_gamma_150[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xa0, 0xc2, 0xb2, 0xd0, + 0xb2, 0xc1, 0xdd, 0xc2, 0x9b, 0xc2, 0x98, 0xb4, 0xcf, 0xb1, + 0x00, 0x99, 0x00, 0x90, 0x00, 0xba, +}; + +static const unsigned char s6e8ax0_22_gamma_160[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xa5, 0xbf, 0xb0, 0xd0, + 0xb1, 0xc3, 0xde, 0xc2, 0x99, 0xc1, 0x97, 0xb4, 0xce, 0xb1, + 0x00, 0x9c, 0x00, 0x93, 0x00, 0xbe, +}; + +static const unsigned char s6e8ax0_22_gamma_170[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb5, 0xbf, 0xb1, 0xd1, + 0xb1, 0xc3, 0xde, 0xc3, 0x99, 0xc0, 0x96, 0xb4, 0xce, 0xb1, + 0x00, 0x9f, 0x00, 0x96, 0x00, 0xc2, +}; + +static const unsigned char s6e8ax0_22_gamma_180[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb7, 0xbe, 0xb3, 0xd2, + 0xb3, 0xc3, 0xde, 0xc2, 0x97, 0xbf, 0x95, 0xb4, 0xcd, 0xb1, + 0x00, 0xa2, 0x00, 0x99, 0x00, 0xc5, +}; + +static const unsigned char s6e8ax0_22_gamma_190[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbe, 0xb2, 0xd2, + 0xb2, 0xc3, 0xdd, 0xc3, 0x98, 0xbf, 0x95, 0xb2, 0xcc, 0xaf, + 0x00, 0xa5, 0x00, 0x9c, 0x00, 0xc9, +}; + +static const unsigned char s6e8ax0_22_gamma_200[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xaf, 0xb9, 0xbc, 0xb2, 0xd2, + 0xb1, 0xc4, 0xdd, 0xc3, 0x97, 0xbe, 0x95, 0xb1, 0xcb, 0xae, + 0x00, 0xa8, 0x00, 0x9f, 0x00, 0xcd, +}; + +static const unsigned char s6e8ax0_22_gamma_210[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc1, 0xbd, 0xb1, 0xd1, + 0xb1, 0xc2, 0xde, 0xc2, 0x97, 0xbe, 0x94, 0xB0, 0xc9, 0xad, + 0x00, 0xae, 0x00, 0xa4, 0x00, 0xd4, +}; + +static const unsigned char s6e8ax0_22_gamma_220[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc7, 0xbd, 0xb1, 0xd1, + 0xb1, 0xc2, 0xdd, 0xc2, 0x97, 0xbd, 0x94, 0xb0, 0xc9, 0xad, + 0x00, 0xad, 0x00, 0xa2, 0x00, 0xd3, +}; + +static const unsigned char s6e8ax0_22_gamma_230[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xc3, 0xbd, 0xb2, 0xd1, + 0xb1, 0xc3, 0xdd, 0xc1, 0x96, 0xbd, 0x94, 0xb0, 0xc9, 0xad, + 0x00, 0xb0, 0x00, 0xa7, 0x00, 0xd7, +}; + +static const unsigned char s6e8ax0_22_gamma_240[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb1, 0xcb, 0xbd, 0xb1, 0xd2, + 0xb1, 0xc3, 0xdD, 0xc2, 0x95, 0xbd, 0x93, 0xaf, 0xc8, 0xab, + 0x00, 0xb3, 0x00, 0xa9, 0x00, 0xdb, +}; + +static const unsigned char s6e8ax0_22_gamma_250[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xcc, 0xbe, 0xb0, 0xd2, + 0xb0, 0xc3, 0xdD, 0xc2, 0x94, 0xbc, 0x92, 0xae, 0xc8, 0xab, + 0x00, 0xb6, 0x00, 0xab, 0x00, 0xde, +}; + +static const unsigned char s6e8ax0_22_gamma_260[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb3, 0xd0, 0xbe, 0xaf, 0xd1, + 0xaf, 0xc2, 0xdd, 0xc1, 0x96, 0xbc, 0x93, 0xaf, 0xc8, 0xac, + 0x00, 0xb7, 0x00, 0xad, 0x00, 0xe0, +}; + +static const unsigned char s6e8ax0_22_gamma_270[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xcF, 0xbd, 0xb0, 0xd2, + 0xaf, 0xc2, 0xdc, 0xc1, 0x95, 0xbd, 0x93, 0xae, 0xc6, 0xaa, + 0x00, 0xba, 0x00, 0xb0, 0x00, 0xe4, +}; + +static const unsigned char s6e8ax0_22_gamma_280[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb2, 0xd0, 0xbd, 0xaf, 0xd0, + 0xad, 0xc4, 0xdd, 0xc3, 0x95, 0xbd, 0x93, 0xac, 0xc5, 0xa9, + 0x00, 0xbd, 0x00, 0xb2, 0x00, 0xe7, +}; + +static const unsigned char s6e8ax0_22_gamma_300[] = { + 0xfa, 0x01, 0x60, 0x10, 0x60, 0xb5, 0xd3, 0xbd, 0xb1, 0xd2, + 0xb0, 0xc0, 0xdc, 0xc0, 0x94, 0xba, 0x91, 0xac, 0xc5, 0xa9, + 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xed, +}; + +static const unsigned char *s6e8ax0_22_gamma_table[] = { + s6e8ax0_22_gamma_30, + s6e8ax0_22_gamma_50, + s6e8ax0_22_gamma_60, + s6e8ax0_22_gamma_70, + s6e8ax0_22_gamma_80, + s6e8ax0_22_gamma_90, + s6e8ax0_22_gamma_100, + s6e8ax0_22_gamma_120, + s6e8ax0_22_gamma_130, + s6e8ax0_22_gamma_140, + s6e8ax0_22_gamma_150, + s6e8ax0_22_gamma_160, + s6e8ax0_22_gamma_170, + s6e8ax0_22_gamma_180, + s6e8ax0_22_gamma_190, + s6e8ax0_22_gamma_200, + s6e8ax0_22_gamma_210, + s6e8ax0_22_gamma_220, + s6e8ax0_22_gamma_230, + s6e8ax0_22_gamma_240, + s6e8ax0_22_gamma_250, + s6e8ax0_22_gamma_260, + s6e8ax0_22_gamma_270, + s6e8ax0_22_gamma_280, + s6e8ax0_22_gamma_300, +}; + +static void s6e8ax0_panel_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + static const unsigned char data_to_send[] = { + 0xf8, 0x3d, 0x35, 0x00, 0x00, 0x00, 0x93, 0x00, 0x3c, 0x7d, + 0x08, 0x27, 0x7d, 0x3f, 0x00, 0x00, 0x00, 0x20, 0x04, 0x08, + 0x6e, 0x00, 0x00, 0x00, 0x02, 0x08, 0x08, 0x23, 0x23, 0xc0, + 0xc8, 0x08, 0x48, 0xc1, 0x00, 0xc1, 0xff, 0xff, 0xc8 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf2, 0x80, 0x03, 0x0d + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +/* Gamma 2.2 Setting (200cd, 7500K, 10MPCD) */ +static void s6e8ax0_gamma_cond(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + unsigned int gamma = lcd->bd->props.brightness; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + s6e8ax0_22_gamma_table[gamma], + GAMMA_TABLE_COUNT); +} + +static void s6e8ax0_gamma_update(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf7, 0x03 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, data_to_send, + ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond1(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xd1, 0xfe, 0x80, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x40, + 0x0d, 0x00, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond2(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xb6, 0x0c, 0x02, 0x03, 0x32, 0xff, 0x44, 0x44, 0xc0, + 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond3(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe1, 0x10, 0x1c, 0x17, 0x08, 0x1d + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond4(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe2, 0xed, 0x07, 0xc3, 0x13, 0x0d, 0x03 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond5(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf4, 0xcf, 0x0a, 0x12, 0x10, 0x19, 0x33, 0x02 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} +static void s6e8ax0_etc_cond6(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe3, 0x40 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE_PARAM, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_etc_cond7(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xe4, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_elvss_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xb1, 0x04, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_elvss_nvm_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xd9, 0x5c, 0x20, 0x0c, 0x0f, 0x41, 0x00, 0x10, 0x11, + 0x12, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x80, 0xcb, 0xed, + 0x64, 0xaf + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_sleep_in(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x10, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_sleep_out(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x11, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_on(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x29, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_display_off(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0x28, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_apply_level2_key(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xf0, 0x5a, 0x5a + }; + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_acl_on(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xc0, 0x01 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +static void s6e8ax0_acl_off(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + static const unsigned char data_to_send[] = { + 0xc0, 0x00 + }; + + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_SHORT_WRITE, + data_to_send, ARRAY_SIZE(data_to_send)); +} + +/* Full white 50% reducing setting */ +static void s6e8ax0_acl_ctrl_set(struct s6e8ax0 *lcd) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + /* Full white 50% reducing setting */ + static const unsigned char cutoff_50[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, 0x2a, 0x31, 0x38, + 0x3f, 0x46 + }; + /* Full white 45% reducing setting */ + static const unsigned char cutoff_45[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x0d, 0x13, 0x19, 0x1f, 0x25, 0x2b, 0x31, + 0x37, 0x3d + }; + /* Full white 40% reducing setting */ + static const unsigned char cutoff_40[] = { + 0xc1, 0x47, 0x53, 0x13, 0x53, 0x00, 0x00, 0x02, 0xcf, + 0x00, 0x00, 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x0c, 0x11, 0x16, 0x1c, 0x21, 0x26, 0x2b, + 0x31, 0x36 + }; + + if (lcd->acl_enable) { + if (lcd->cur_acl == 0) { + if (lcd->gamma == 0 || lcd->gamma == 1) { + s6e8ax0_acl_off(lcd); + dev_dbg(&lcd->ld->dev, + "cur_acl=%d\n", lcd->cur_acl); + } else + s6e8ax0_acl_on(lcd); + } + switch (lcd->gamma) { + case 0: /* 30cd */ + s6e8ax0_acl_off(lcd); + lcd->cur_acl = 0; + break; + case 1 ... 3: /* 50cd ~ 90cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_40, + ARRAY_SIZE(cutoff_40)); + lcd->cur_acl = 40; + break; + case 4 ... 7: /* 120cd ~ 210cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_45, + ARRAY_SIZE(cutoff_45)); + lcd->cur_acl = 45; + break; + case 8 ... 10: /* 220cd ~ 300cd */ + ops->cmd_write(lcd_to_master(lcd), + MIPI_DSI_DCS_LONG_WRITE, + cutoff_50, + ARRAY_SIZE(cutoff_50)); + lcd->cur_acl = 50; + break; + default: + break; + } + } else { + s6e8ax0_acl_off(lcd); + lcd->cur_acl = 0; + dev_dbg(&lcd->ld->dev, "cur_acl = %d\n", lcd->cur_acl); + } +} + +static void s6e8ax0_read_id(struct s6e8ax0 *lcd, u8 *mtp_id) +{ + unsigned int ret; + unsigned int addr = 0xd1; /* MTP ID */ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ret = ops->cmd_read(lcd_to_master(lcd), + MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM, + addr, 3, mtp_id); +} + +static int s6e8ax0_panel_init(struct s6e8ax0 *lcd) +{ + s6e8ax0_apply_level2_key(lcd); + s6e8ax0_sleep_out(lcd); + msleep(1); + s6e8ax0_panel_cond(lcd); + s6e8ax0_display_cond(lcd); + s6e8ax0_gamma_cond(lcd); + s6e8ax0_gamma_update(lcd); + + s6e8ax0_etc_cond1(lcd); + s6e8ax0_etc_cond2(lcd); + s6e8ax0_etc_cond3(lcd); + s6e8ax0_etc_cond4(lcd); + s6e8ax0_etc_cond5(lcd); + s6e8ax0_etc_cond6(lcd); + s6e8ax0_etc_cond7(lcd); + + s6e8ax0_elvss_nvm_set(lcd); + s6e8ax0_elvss_set(lcd); + + s6e8ax0_acl_ctrl_set(lcd); + s6e8ax0_acl_on(lcd); + + /* if ID3 value is not 33h, branch private elvss mode */ + msleep(lcd->ddi_pd->power_on_delay); + + return 0; +} + +static int s6e8ax0_update_gamma_ctrl(struct s6e8ax0 *lcd, int brightness) +{ + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + + ops->cmd_write(lcd_to_master(lcd), MIPI_DSI_DCS_LONG_WRITE, + s6e8ax0_22_gamma_table[brightness], + ARRAY_SIZE(s6e8ax0_22_gamma_table)); + + /* update gamma table. */ + s6e8ax0_gamma_update(lcd); + lcd->gamma = brightness; + + return 0; +} + +static int s6e8ax0_gamma_ctrl(struct s6e8ax0 *lcd, int gamma) +{ + s6e8ax0_update_gamma_ctrl(lcd, gamma); + + return 0; +} + +static int s6e8ax0_set_power(struct lcd_device *ld, int power) +{ + struct s6e8ax0 *lcd = lcd_get_data(ld); + struct mipi_dsim_master_ops *ops = lcd_to_master_ops(lcd); + int ret = 0; + + if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN && + power != FB_BLANK_NORMAL) { + dev_err(lcd->dev, "power value should be 0, 1 or 4.\n"); + return -EINVAL; + } + + if ((power == FB_BLANK_UNBLANK) && ops->set_blank_mode) { + /* LCD power on */ + if ((POWER_IS_ON(power) && POWER_IS_OFF(lcd->power)) + || (POWER_IS_ON(power) && POWER_IS_NRM(lcd->power))) { + ret = ops->set_blank_mode(lcd_to_master(lcd), power); + if (!ret && lcd->power != power) + lcd->power = power; + } + } else if ((power == FB_BLANK_POWERDOWN) && ops->set_early_blank_mode) { + /* LCD power off */ + if ((POWER_IS_OFF(power) && POWER_IS_ON(lcd->power)) || + (POWER_IS_ON(lcd->power) && POWER_IS_NRM(power))) { + ret = ops->set_early_blank_mode(lcd_to_master(lcd), + power); + if (!ret && lcd->power != power) + lcd->power = power; + } + } + + return ret; +} + +static int s6e8ax0_get_power(struct lcd_device *ld) +{ + struct s6e8ax0 *lcd = lcd_get_data(ld); + + return lcd->power; +} + +static int s6e8ax0_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int s6e8ax0_set_brightness(struct backlight_device *bd) +{ + int ret = 0, brightness = bd->props.brightness; + struct s6e8ax0 *lcd = bl_get_data(bd); + + if (brightness < MIN_BRIGHTNESS || + brightness > bd->props.max_brightness) { + dev_err(lcd->dev, "lcd brightness should be %d to %d.\n", + MIN_BRIGHTNESS, MAX_BRIGHTNESS); + return -EINVAL; + } + + ret = s6e8ax0_gamma_ctrl(lcd, brightness); + if (ret) { + dev_err(&bd->dev, "lcd brightness setting failed.\n"); + return -EIO; + } + + return ret; +} + +static struct lcd_ops s6e8ax0_lcd_ops = { + .set_power = s6e8ax0_set_power, + .get_power = s6e8ax0_get_power, +}; + +static const struct backlight_ops s6e8ax0_backlight_ops = { + .get_brightness = s6e8ax0_get_brightness, + .update_status = s6e8ax0_set_brightness, +}; + +static void s6e8ax0_power_on(struct mipi_dsim_lcd_device *dsim_dev, int power) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + msleep(lcd->ddi_pd->power_on_delay); + + /* lcd power on */ + if (power) + s6e8ax0_regulator_enable(lcd); + else + s6e8ax0_regulator_disable(lcd); + + msleep(lcd->ddi_pd->reset_delay); + + /* lcd reset */ + if (lcd->ddi_pd->reset) + lcd->ddi_pd->reset(lcd->ld); + msleep(5); +} + +static void s6e8ax0_set_sequence(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_panel_init(lcd); + s6e8ax0_display_on(lcd); + + lcd->power = FB_BLANK_UNBLANK; +} + +static int s6e8ax0_probe(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd; + int ret; + u8 mtp_id[3] = {0, }; + + lcd = kzalloc(sizeof(struct s6e8ax0), GFP_KERNEL); + if (!lcd) { + dev_err(&dsim_dev->dev, "failed to allocate s6e8ax0 structure.\n"); + return -ENOMEM; + } + + lcd->dsim_dev = dsim_dev; + lcd->ddi_pd = (struct lcd_platform_data *)dsim_dev->platform_data; + lcd->dev = &dsim_dev->dev; + + mutex_init(&lcd->lock); + + ret = regulator_bulk_get(lcd->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(lcd->dev, "Failed to get regulators: %d\n", ret); + goto err_lcd_register; + } + + lcd->ld = lcd_device_register("s6e8ax0", lcd->dev, lcd, + &s6e8ax0_lcd_ops); + if (IS_ERR(lcd->ld)) { + dev_err(lcd->dev, "failed to register lcd ops.\n"); + ret = PTR_ERR(lcd->ld); + goto err_lcd_register; + } + + lcd->bd = backlight_device_register("s6e8ax0-bl", lcd->dev, lcd, + &s6e8ax0_backlight_ops, NULL); + if (IS_ERR(lcd->bd)) { + dev_err(lcd->dev, "failed to register backlight ops.\n"); + ret = PTR_ERR(lcd->bd); + goto err_backlight_register; + } + + lcd->bd->props.max_brightness = MAX_BRIGHTNESS; + lcd->bd->props.brightness = MAX_BRIGHTNESS; + + s6e8ax0_read_id(lcd, mtp_id); + if (mtp_id[0] == 0x00) + dev_err(lcd->dev, "read id failed\n"); + + dev_info(lcd->dev, "Read ID : %x, %x, %x\n", + mtp_id[0], mtp_id[1], mtp_id[2]); + + if (mtp_id[2] == 0x33) + dev_info(lcd->dev, + "ID-3 is 0xff does not support dynamic elvss\n"); + else + dev_info(lcd->dev, + "ID-3 is 0x%x support dynamic elvss\n", mtp_id[2]); + + lcd->acl_enable = 1; + lcd->cur_acl = 0; + + dev_set_drvdata(&dsim_dev->dev, lcd); + + dev_dbg(lcd->dev, "probed s6e8ax0 panel driver.\n"); + + return 0; + +err_backlight_register: + lcd_device_unregister(lcd->ld); + +err_lcd_register: + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + kfree(lcd); + + return ret; +} + +#ifdef CONFIG_PM +static int s6e8ax0_suspend(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_sleep_in(lcd); + msleep(lcd->ddi_pd->power_off_delay); + s6e8ax0_display_off(lcd); + + s6e8ax0_regulator_disable(lcd); + + return 0; +} + +static int s6e8ax0_resume(struct mipi_dsim_lcd_device *dsim_dev) +{ + struct s6e8ax0 *lcd = dev_get_drvdata(&dsim_dev->dev); + + s6e8ax0_sleep_out(lcd); + msleep(lcd->ddi_pd->power_on_delay); + + s6e8ax0_regulator_enable(lcd); + s6e8ax0_set_sequence(dsim_dev); + + return 0; +} +#else +#define s6e8ax0_suspend NULL +#define s6e8ax0_resume NULL +#endif + +static struct mipi_dsim_lcd_driver s6e8ax0_dsim_ddi_driver = { + .name = "s6e8ax0", + .id = -1, + + .power_on = s6e8ax0_power_on, + .set_sequence = s6e8ax0_set_sequence, + .probe = s6e8ax0_probe, + .suspend = s6e8ax0_suspend, + .resume = s6e8ax0_resume, +}; + +static int s6e8ax0_init(void) +{ + exynos_mipi_dsi_register_lcd_driver(&s6e8ax0_dsim_ddi_driver); + + return 0; +} + +static void s6e8ax0_exit(void) +{ + return; +} + +module_init(s6e8ax0_init); +module_exit(s6e8ax0_exit); + +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("MIPI-DSI based s6e8ax0 AMOLED LCD Panel Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/exynos/s6e8ax0.h b/drivers/video/exynos/s6e8ax0.h new file mode 100644 index 00000000000..1f1b270484b --- /dev/null +++ b/drivers/video/exynos/s6e8ax0.h @@ -0,0 +1,21 @@ +/* linux/drivers/video/backlight/s6e8ax0.h + * + * MIPI-DSI based s6e8ax0 AMOLED LCD Panel definitions. + * + * Copyright (c) 2011 Samsung Electronics + * + * 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. +*/ + +#ifndef _S6E8AX0_H +#define _S6E8AX0_H + +extern void s6e8ax0_init(void); + +#endif + diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c index ac9141b8535..c6ce416ab58 100644 --- a/drivers/video/fbmem.c +++ b/drivers/video/fbmem.c @@ -1665,6 +1665,7 @@ static int do_unregister_framebuffer(struct fb_info *fb_info) if (ret) return -EINVAL; + unlink_framebuffer(fb_info); if (fb_info->pixmap.addr && (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) kfree(fb_info->pixmap.addr); @@ -1672,7 +1673,6 @@ static int do_unregister_framebuffer(struct fb_info *fb_info) registered_fb[i] = NULL; num_registered_fb--; fb_cleanup_device(fb_info); - device_destroy(fb_class, MKDEV(FB_MAJOR, i)); event.info = fb_info; fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); @@ -1681,6 +1681,22 @@ static int do_unregister_framebuffer(struct fb_info *fb_info) return 0; } +int unlink_framebuffer(struct fb_info *fb_info) +{ + int i; + + i = fb_info->node; + if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info) + return -EINVAL; + + if (fb_info->dev) { + device_destroy(fb_class, MKDEV(FB_MAJOR, i)); + fb_info->dev = NULL; + } + return 0; +} +EXPORT_SYMBOL(unlink_framebuffer); + void remove_conflicting_framebuffers(struct apertures_struct *a, const char *name, bool primary) { diff --git a/drivers/video/i740_reg.h b/drivers/video/i740_reg.h new file mode 100644 index 00000000000..91bac76549d --- /dev/null +++ b/drivers/video/i740_reg.h @@ -0,0 +1,309 @@ +/************************************************************************** + +Copyright 1998-1999 Precision Insight, Inc., Cedar Park, Texas. +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +/* + * Authors: + * Kevin E. Martin <kevin@precisioninsight.com> + */ + +/* I/O register offsets */ +#define SRX VGA_SEQ_I +#define GRX VGA_GFX_I +#define ARX VGA_ATT_IW +#define XRX 0x3D6 +#define MRX 0x3D2 + +/* VGA Color Palette Registers */ +#define DACMASK 0x3C6 +#define DACSTATE 0x3C7 +#define DACRX 0x3C7 +#define DACWX 0x3C8 +#define DACDATA 0x3C9 + +/* CRT Controller Registers (CRX) */ +#define START_ADDR_HI 0x0C +#define START_ADDR_LO 0x0D +#define VERT_SYNC_END 0x11 +#define EXT_VERT_TOTAL 0x30 +#define EXT_VERT_DISPLAY 0x31 +#define EXT_VERT_SYNC_START 0x32 +#define EXT_VERT_BLANK_START 0x33 +#define EXT_HORIZ_TOTAL 0x35 +#define EXT_HORIZ_BLANK 0x39 +#define EXT_START_ADDR 0x40 +#define EXT_START_ADDR_ENABLE 0x80 +#define EXT_OFFSET 0x41 +#define EXT_START_ADDR_HI 0x42 +#define INTERLACE_CNTL 0x70 +#define INTERLACE_ENABLE 0x80 +#define INTERLACE_DISABLE 0x00 + +/* Miscellaneous Output Register */ +#define MSR_R 0x3CC +#define MSR_W 0x3C2 +#define IO_ADDR_SELECT 0x01 + +#define MDA_BASE 0x3B0 +#define CGA_BASE 0x3D0 + +/* System Configuration Extension Registers (XRX) */ +#define IO_CTNL 0x09 +#define EXTENDED_ATTR_CNTL 0x02 +#define EXTENDED_CRTC_CNTL 0x01 + +#define ADDRESS_MAPPING 0x0A +#define PACKED_MODE_ENABLE 0x04 +#define LINEAR_MODE_ENABLE 0x02 +#define PAGE_MAPPING_ENABLE 0x01 + +#define BITBLT_CNTL 0x20 +#define COLEXP_MODE 0x30 +#define COLEXP_8BPP 0x00 +#define COLEXP_16BPP 0x10 +#define COLEXP_24BPP 0x20 +#define COLEXP_RESERVED 0x30 +#define CHIP_RESET 0x02 +#define BITBLT_STATUS 0x01 + +#define DISPLAY_CNTL 0x40 +#define VGA_WRAP_MODE 0x02 +#define VGA_WRAP_AT_256KB 0x00 +#define VGA_NO_WRAP 0x02 +#define GUI_MODE 0x01 +#define STANDARD_VGA_MODE 0x00 +#define HIRES_MODE 0x01 + +#define DRAM_ROW_TYPE 0x50 +#define DRAM_ROW_0 0x07 +#define DRAM_ROW_0_SDRAM 0x00 +#define DRAM_ROW_0_EMPTY 0x07 +#define DRAM_ROW_1 0x38 +#define DRAM_ROW_1_SDRAM 0x00 +#define DRAM_ROW_1_EMPTY 0x38 +#define DRAM_ROW_CNTL_LO 0x51 +#define DRAM_CAS_LATENCY 0x10 +#define DRAM_RAS_TIMING 0x08 +#define DRAM_RAS_PRECHARGE 0x04 +#define DRAM_ROW_CNTL_HI 0x52 +#define DRAM_EXT_CNTL 0x53 +#define DRAM_REFRESH_RATE 0x03 +#define DRAM_REFRESH_DISABLE 0x00 +#define DRAM_REFRESH_60HZ 0x01 +#define DRAM_REFRESH_FAST_TEST 0x02 +#define DRAM_REFRESH_RESERVED 0x03 +#define DRAM_TIMING 0x54 +#define DRAM_ROW_BNDRY_0 0x55 +#define DRAM_ROW_BNDRY_1 0x56 + +#define DPMS_SYNC_SELECT 0x61 +#define VSYNC_CNTL 0x08 +#define VSYNC_ON 0x00 +#define VSYNC_OFF 0x08 +#define HSYNC_CNTL 0x02 +#define HSYNC_ON 0x00 +#define HSYNC_OFF 0x02 + +#define PIXPIPE_CONFIG_0 0x80 +#define DAC_8_BIT 0x80 +#define DAC_6_BIT 0x00 +#define HW_CURSOR_ENABLE 0x10 +#define EXTENDED_PALETTE 0x01 + +#define PIXPIPE_CONFIG_1 0x81 +#define DISPLAY_COLOR_MODE 0x0F +#define DISPLAY_VGA_MODE 0x00 +#define DISPLAY_8BPP_MODE 0x02 +#define DISPLAY_15BPP_MODE 0x04 +#define DISPLAY_16BPP_MODE 0x05 +#define DISPLAY_24BPP_MODE 0x06 +#define DISPLAY_32BPP_MODE 0x07 + +#define PIXPIPE_CONFIG_2 0x82 +#define DISPLAY_GAMMA_ENABLE 0x08 +#define DISPLAY_GAMMA_DISABLE 0x00 +#define OVERLAY_GAMMA_ENABLE 0x04 +#define OVERLAY_GAMMA_DISABLE 0x00 + +#define CURSOR_CONTROL 0xA0 +#define CURSOR_ORIGIN_SCREEN 0x00 +#define CURSOR_ORIGIN_DISPLAY 0x10 +#define CURSOR_MODE 0x07 +#define CURSOR_MODE_DISABLE 0x00 +#define CURSOR_MODE_32_4C_AX 0x01 +#define CURSOR_MODE_128_2C 0x02 +#define CURSOR_MODE_128_1C 0x03 +#define CURSOR_MODE_64_3C 0x04 +#define CURSOR_MODE_64_4C_AX 0x05 +#define CURSOR_MODE_64_4C 0x06 +#define CURSOR_MODE_RESERVED 0x07 +#define CURSOR_BASEADDR_LO 0xA2 +#define CURSOR_BASEADDR_HI 0xA3 +#define CURSOR_X_LO 0xA4 +#define CURSOR_X_HI 0xA5 +#define CURSOR_X_POS 0x00 +#define CURSOR_X_NEG 0x80 +#define CURSOR_Y_LO 0xA6 +#define CURSOR_Y_HI 0xA7 +#define CURSOR_Y_POS 0x00 +#define CURSOR_Y_NEG 0x80 + +#define VCLK2_VCO_M 0xC8 +#define VCLK2_VCO_N 0xC9 +#define VCLK2_VCO_MN_MSBS 0xCA +#define VCO_N_MSBS 0x30 +#define VCO_M_MSBS 0x03 +#define VCLK2_VCO_DIV_SEL 0xCB +#define POST_DIV_SELECT 0x70 +#define POST_DIV_1 0x00 +#define POST_DIV_2 0x10 +#define POST_DIV_4 0x20 +#define POST_DIV_8 0x30 +#define POST_DIV_16 0x40 +#define POST_DIV_32 0x50 +#define VCO_LOOP_DIV_BY_4M 0x00 +#define VCO_LOOP_DIV_BY_16M 0x04 +#define REF_CLK_DIV_BY_5 0x02 +#define REF_DIV_4 0x00 +#define REF_DIV_1 0x01 + +#define PLL_CNTL 0xCE +#define PLL_MEMCLK_SEL 0x03 +#define PLL_MEMCLK__66667KHZ 0x00 +#define PLL_MEMCLK__75000KHZ 0x01 +#define PLL_MEMCLK__88889KHZ 0x02 +#define PLL_MEMCLK_100000KHZ 0x03 + +/* Multimedia Extension Registers (MRX) */ +#define ACQ_CNTL_1 0x02 +#define ACQ_CNTL_2 0x03 +#define FRAME_CAP_MODE 0x01 +#define CONT_CAP_MODE 0x00 +#define SINGLE_CAP_MODE 0x01 +#define ACQ_CNTL_3 0x04 +#define COL_KEY_CNTL_1 0x3C +#define BLANK_DISP_OVERLAY 0x20 + +/* FIFOs */ +#define LP_FIFO 0x1000 +#define HP_FIFO 0x2000 +#define INSTPNT 0x3040 +#define LP_FIFO_COUNT 0x3040 +#define HP_FIFO_COUNT 0x3041 + +/* FIFO Commands */ +#define CLIENT 0xE0000000 +#define CLIENT_2D 0x60000000 + +/* Command Parser Mode Register */ +#define COMPARS 0x3038 +#define TWO_D_INST_DISABLE 0x08 +#define THREE_D_INST_DISABLE 0x04 +#define STATE_VAR_UPDATE_DISABLE 0x02 +#define PAL_STIP_DISABLE 0x01 + +/* Interrupt Control Registers */ +#define IER 0x3030 +#define IIR 0x3032 +#define IMR 0x3034 +#define ISR 0x3036 +#define VMIINTB_EVENT 0x2000 +#define GPIO4_INT 0x1000 +#define DISP_FLIP_EVENT 0x0800 +#define DVD_PORT_DMA 0x0400 +#define DISP_VBLANK 0x0200 +#define FIFO_EMPTY_DMA_DONE 0x0100 +#define INST_PARSER_ERROR 0x0080 +#define USER_DEFINED 0x0040 +#define BREAKPOINT 0x0020 +#define DISP_HORIZ_COUNT 0x0010 +#define DISP_VSYNC 0x0008 +#define CAPTURE_HORIZ_COUNT 0x0004 +#define CAPTURE_VSYNC 0x0002 +#define THREE_D_PIPE_FLUSHED 0x0001 + +/* FIFO Watermark and Burst Length Control Register */ +#define FWATER_BLC 0x00006000 +#define LMI_BURST_LENGTH 0x7F000000 +#define LMI_FIFO_WATERMARK 0x003F0000 +#define AGP_BURST_LENGTH 0x00007F00 +#define AGP_FIFO_WATERMARK 0x0000003F + +/* BitBLT Registers */ +#define SRC_DST_PITCH 0x00040000 +#define DST_PITCH 0x1FFF0000 +#define SRC_PITCH 0x00001FFF +#define COLEXP_BG_COLOR 0x00040004 +#define COLEXP_FG_COLOR 0x00040008 +#define MONO_SRC_CNTL 0x0004000C +#define MONO_USE_COLEXP 0x00000000 +#define MONO_USE_SRCEXP 0x08000000 +#define MONO_DATA_ALIGN 0x07000000 +#define MONO_BIT_ALIGN 0x01000000 +#define MONO_BYTE_ALIGN 0x02000000 +#define MONO_WORD_ALIGN 0x03000000 +#define MONO_DWORD_ALIGN 0x04000000 +#define MONO_QWORD_ALIGN 0x05000000 +#define MONO_SRC_INIT_DSCRD 0x003F0000 +#define MONO_SRC_RIGHT_CLIP 0x00003F00 +#define MONO_SRC_LEFT_CLIP 0x0000003F +#define BITBLT_CONTROL 0x00040010 +#define BLTR_STATUS 0x80000000 +#define DYN_DEPTH 0x03000000 +#define DYN_DEPTH_8BPP 0x00000000 +#define DYN_DEPTH_16BPP 0x01000000 +#define DYN_DEPTH_24BPP 0x02000000 +#define DYN_DEPTH_32BPP 0x03000000 /* Unimplemented on the i740 */ +#define DYN_DEPTH_ENABLE 0x00800000 +#define PAT_VERT_ALIGN 0x00700000 +#define SOLID_PAT_SELECT 0x00080000 +#define PAT_IS_IN_COLOR 0x00000000 +#define PAT_IS_MONO 0x00040000 +#define MONO_PAT_TRANSP 0x00020000 +#define COLOR_TRANSP_ROP 0x00000000 +#define COLOR_TRANSP_DST 0x00008000 +#define COLOR_TRANSP_EQ 0x00000000 +#define COLOR_TRANSP_NOT_EQ 0x00010000 +#define COLOR_TRANSP_ENABLE 0x00004000 +#define MONO_SRC_TRANSP 0x00002000 +#define SRC_IS_IN_COLOR 0x00000000 +#define SRC_IS_MONO 0x00001000 +#define SRC_USE_SRC_ADDR 0x00000000 +#define SRC_USE_BLTDATA 0x00000400 +#define BLT_TOP_TO_BOT 0x00000000 +#define BLT_BOT_TO_TOP 0x00000200 +#define BLT_LEFT_TO_RIGHT 0x00000000 +#define BLT_RIGHT_TO_LEFT 0x00000100 +#define BLT_ROP 0x000000FF +#define BLT_PAT_ADDR 0x00040014 +#define BLT_SRC_ADDR 0x00040018 +#define BLT_DST_ADDR 0x0004001C +#define BLT_DST_H_W 0x00040020 +#define BLT_DST_HEIGHT 0x1FFF0000 +#define BLT_DST_WIDTH 0x00001FFF +#define SRCEXP_BG_COLOR 0x00040024 +#define SRCEXP_FG_COLOR 0x00040028 +#define BLTDATA 0x00050000 diff --git a/drivers/video/i740fb.c b/drivers/video/i740fb.c new file mode 100644 index 00000000000..fe574d84ed9 --- /dev/null +++ b/drivers/video/i740fb.c @@ -0,0 +1,1337 @@ +/* + * i740fb - framebuffer driver for Intel740 + * Copyright (c) 2011 Ondrej Zary + * + * Based on old i740fb driver (c) 2001-2002 Andrey Ulanov <drey@rt.mipt.ru> + * which was partially based on: + * VGA 16-color framebuffer driver (c) 1999 Ben Pfaff <pfaffben@debian.org> + * and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * i740 driver from XFree86 (c) 1998-1999 Precision Insight, Inc., Cedar Park, + * Texas. + * i740fb by Patrick LERDA, v0.9 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> +#include <linux/console.h> +#include <video/vga.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "i740_reg.h" + +static char *mode_option __devinitdata; + +#ifdef CONFIG_MTRR +static int mtrr __devinitdata = 1; +#endif + +struct i740fb_par { + unsigned char __iomem *regs; + bool has_sgram; +#ifdef CONFIG_MTRR + int mtrr_reg; +#endif + bool ddc_registered; + struct i2c_adapter ddc_adapter; + struct i2c_algo_bit_data ddc_algo; + u32 pseudo_palette[16]; + struct mutex open_lock; + unsigned int ref_count; + + u8 crtc[VGA_CRT_C]; + u8 atc[VGA_ATT_C]; + u8 gdc[VGA_GFX_C]; + u8 seq[VGA_SEQ_C]; + u8 misc; + u8 vss; + + /* i740 specific registers */ + u8 display_cntl; + u8 pixelpipe_cfg0; + u8 pixelpipe_cfg1; + u8 pixelpipe_cfg2; + u8 video_clk2_m; + u8 video_clk2_n; + u8 video_clk2_mn_msbs; + u8 video_clk2_div_sel; + u8 pll_cntl; + u8 address_mapping; + u8 io_cntl; + u8 bitblt_cntl; + u8 ext_vert_total; + u8 ext_vert_disp_end; + u8 ext_vert_sync_start; + u8 ext_vert_blank_start; + u8 ext_horiz_total; + u8 ext_horiz_blank; + u8 ext_offset; + u8 interlace_cntl; + u32 lmi_fifo_watermark; + u8 ext_start_addr; + u8 ext_start_addr_hi; +}; + +#define DACSPEED8 203 +#define DACSPEED16 163 +#define DACSPEED24_SG 136 +#define DACSPEED24_SD 128 +#define DACSPEED32 86 + +static struct fb_fix_screeninfo i740fb_fix __devinitdata = { + .id = "i740fb", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 8, + .ypanstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static inline void i740outb(struct i740fb_par *par, u16 port, u8 val) +{ + vga_mm_w(par->regs, port, val); +} +static inline u8 i740inb(struct i740fb_par *par, u16 port) +{ + return vga_mm_r(par->regs, port); +} +static inline void i740outreg(struct i740fb_par *par, u16 port, u8 reg, u8 val) +{ + vga_mm_w_fast(par->regs, port, reg, val); +} +static inline u8 i740inreg(struct i740fb_par *par, u16 port, u8 reg) +{ + vga_mm_w(par->regs, port, reg); + return vga_mm_r(par->regs, port+1); +} +static inline void i740outreg_mask(struct i740fb_par *par, u16 port, u8 reg, + u8 val, u8 mask) +{ + vga_mm_w_fast(par->regs, port, reg, (val & mask) + | (i740inreg(par, port, reg) & ~mask)); +} + +#define REG_DDC_DRIVE 0x62 +#define REG_DDC_STATE 0x63 +#define DDC_SCL (1 << 3) +#define DDC_SDA (1 << 2) + +static void i740fb_ddc_setscl(void *data, int val) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SCL, DDC_SCL); + i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SCL : 0, DDC_SCL); +} + +static void i740fb_ddc_setsda(void *data, int val) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SDA, DDC_SDA); + i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SDA : 0, DDC_SDA); +} + +static int i740fb_ddc_getscl(void *data) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SCL); + + return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SCL); +} + +static int i740fb_ddc_getsda(void *data) +{ + struct i740fb_par *par = data; + + i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SDA); + + return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SDA); +} + +static int __devinit i740fb_setup_ddc_bus(struct fb_info *info) +{ + struct i740fb_par *par = info->par; + + strlcpy(par->ddc_adapter.name, info->fix.id, + sizeof(par->ddc_adapter.name)); + par->ddc_adapter.owner = THIS_MODULE; + par->ddc_adapter.class = I2C_CLASS_DDC; + par->ddc_adapter.algo_data = &par->ddc_algo; + par->ddc_adapter.dev.parent = info->device; + par->ddc_algo.setsda = i740fb_ddc_setsda; + par->ddc_algo.setscl = i740fb_ddc_setscl; + par->ddc_algo.getsda = i740fb_ddc_getsda; + par->ddc_algo.getscl = i740fb_ddc_getscl; + par->ddc_algo.udelay = 10; + par->ddc_algo.timeout = 20; + par->ddc_algo.data = par; + + i2c_set_adapdata(&par->ddc_adapter, par); + + return i2c_bit_add_bus(&par->ddc_adapter); +} + +static int i740fb_open(struct fb_info *info, int user) +{ + struct i740fb_par *par = info->par; + + mutex_lock(&(par->open_lock)); + par->ref_count++; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static int i740fb_release(struct fb_info *info, int user) +{ + struct i740fb_par *par = info->par; + + mutex_lock(&(par->open_lock)); + if (par->ref_count == 0) { + printk(KERN_ERR "fb%d: release called with zero refcount\n", + info->node); + mutex_unlock(&(par->open_lock)); + return -EINVAL; + } + + par->ref_count--; + mutex_unlock(&(par->open_lock)); + + return 0; +} + +static u32 i740_calc_fifo(struct i740fb_par *par, u32 freq, int bpp) +{ + /* + * Would like to calculate these values automatically, but a generic + * algorithm does not seem possible. Note: These FIFO water mark + * values were tested on several cards and seem to eliminate the + * all of the snow and vertical banding, but fine adjustments will + * probably be required for other cards. + */ + + u32 wm; + + switch (bpp) { + case 8: + if (freq > 200) + wm = 0x18120000; + else if (freq > 175) + wm = 0x16110000; + else if (freq > 135) + wm = 0x120E0000; + else + wm = 0x100D0000; + break; + case 15: + case 16: + if (par->has_sgram) { + if (freq > 140) + wm = 0x2C1D0000; + else if (freq > 120) + wm = 0x2C180000; + else if (freq > 100) + wm = 0x24160000; + else if (freq > 90) + wm = 0x18120000; + else if (freq > 50) + wm = 0x16110000; + else if (freq > 32) + wm = 0x13100000; + else + wm = 0x120E0000; + } else { + if (freq > 160) + wm = 0x28200000; + else if (freq > 140) + wm = 0x2A1E0000; + else if (freq > 130) + wm = 0x2B1A0000; + else if (freq > 120) + wm = 0x2C180000; + else if (freq > 100) + wm = 0x24180000; + else if (freq > 90) + wm = 0x18120000; + else if (freq > 50) + wm = 0x16110000; + else if (freq > 32) + wm = 0x13100000; + else + wm = 0x120E0000; + } + break; + case 24: + if (par->has_sgram) { + if (freq > 130) + wm = 0x31200000; + else if (freq > 120) + wm = 0x2E200000; + else if (freq > 100) + wm = 0x2C1D0000; + else if (freq > 80) + wm = 0x25180000; + else if (freq > 64) + wm = 0x24160000; + else if (freq > 49) + wm = 0x18120000; + else if (freq > 32) + wm = 0x16110000; + else + wm = 0x13100000; + } else { + if (freq > 120) + wm = 0x311F0000; + else if (freq > 100) + wm = 0x2C1D0000; + else if (freq > 80) + wm = 0x25180000; + else if (freq > 64) + wm = 0x24160000; + else if (freq > 49) + wm = 0x18120000; + else if (freq > 32) + wm = 0x16110000; + else + wm = 0x13100000; + } + break; + case 32: + if (par->has_sgram) { + if (freq > 80) + wm = 0x2A200000; + else if (freq > 60) + wm = 0x281A0000; + else if (freq > 49) + wm = 0x25180000; + else if (freq > 32) + wm = 0x18120000; + else + wm = 0x16110000; + } else { + if (freq > 80) + wm = 0x29200000; + else if (freq > 60) + wm = 0x281A0000; + else if (freq > 49) + wm = 0x25180000; + else if (freq > 32) + wm = 0x18120000; + else + wm = 0x16110000; + } + break; + } + + return wm; +} + +/* clock calculation from i740fb by Patrick LERDA */ + +#define I740_RFREQ 1000000 +#define TARGET_MAX_N 30 +#define I740_FFIX (1 << 8) +#define I740_RFREQ_FIX (I740_RFREQ / I740_FFIX) +#define I740_REF_FREQ (6667 * I740_FFIX / 100) /* 66.67 MHz */ +#define I740_MAX_VCO_FREQ (450 * I740_FFIX) /* 450 MHz */ + +static void i740_calc_vclk(u32 freq, struct i740fb_par *par) +{ + const u32 err_max = freq / (200 * I740_RFREQ / I740_FFIX); + const u32 err_target = freq / (1000 * I740_RFREQ / I740_FFIX); + u32 err_best = 512 * I740_FFIX; + u32 f_err, f_vco; + int m_best = 0, n_best = 0, p_best = 0, d_best = 0; + int m, n; + + p_best = min(15, ilog2(I740_MAX_VCO_FREQ / (freq / I740_RFREQ_FIX))); + d_best = 0; + f_vco = (freq * (1 << p_best)) / I740_RFREQ_FIX; + freq = freq / I740_RFREQ_FIX; + + n = 2; + do { + n++; + m = ((f_vco * n) / I740_REF_FREQ + 2) / 4; + + if (m < 3) + m = 3; + + { + u32 f_out = (((m * I740_REF_FREQ * (4 << 2 * d_best)) + / n) + ((1 << p_best) / 2)) / (1 << p_best); + + f_err = (freq - f_out); + + if (abs(f_err) < err_max) { + m_best = m; + n_best = n; + err_best = f_err; + } + } + } while ((abs(f_err) >= err_target) && + ((n <= TARGET_MAX_N) || (abs(err_best) > err_max))); + + if (abs(f_err) < err_target) { + m_best = m; + n_best = n; + } + + par->video_clk2_m = (m_best - 2) & 0xFF; + par->video_clk2_n = (n_best - 2) & 0xFF; + par->video_clk2_mn_msbs = ((((n_best - 2) >> 4) & VCO_N_MSBS) + | (((m_best - 2) >> 8) & VCO_M_MSBS)); + par->video_clk2_div_sel = + ((p_best << 4) | (d_best ? 4 : 0) | REF_DIV_1); +} + +static int i740fb_decode_var(const struct fb_var_screeninfo *var, + struct i740fb_par *par, struct fb_info *info) +{ + /* + * Get the video params out of 'var'. + * If a value doesn't fit, round it up, if it's too big, return -EINVAL. + */ + + u32 xres, right, hslen, left, xtotal; + u32 yres, lower, vslen, upper, ytotal; + u32 vxres, xoffset, vyres, yoffset; + u32 bpp, base, dacspeed24, mem; + u8 r7; + int i; + + dev_dbg(info->device, "decode_var: xres: %i, yres: %i, xres_v: %i, xres_v: %i\n", + var->xres, var->yres, var->xres_virtual, var->xres_virtual); + dev_dbg(info->device, " xoff: %i, yoff: %i, bpp: %i, graysc: %i\n", + var->xoffset, var->yoffset, var->bits_per_pixel, + var->grayscale); + dev_dbg(info->device, " activate: %i, nonstd: %i, vmode: %i\n", + var->activate, var->nonstd, var->vmode); + dev_dbg(info->device, " pixclock: %i, hsynclen:%i, vsynclen:%i\n", + var->pixclock, var->hsync_len, var->vsync_len); + dev_dbg(info->device, " left: %i, right: %i, up:%i, lower:%i\n", + var->left_margin, var->right_margin, var->upper_margin, + var->lower_margin); + + + bpp = var->bits_per_pixel; + switch (bpp) { + case 1 ... 8: + bpp = 8; + if ((1000000 / var->pixclock) > DACSPEED8) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 8bpp)\n", + 1000000 / var->pixclock, DACSPEED8); + return -EINVAL; + } + break; + case 9 ... 15: + bpp = 15; + case 16: + if ((1000000 / var->pixclock) > DACSPEED16) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 15/16bpp)\n", + 1000000 / var->pixclock, DACSPEED16); + return -EINVAL; + } + break; + case 17 ... 24: + bpp = 24; + dacspeed24 = par->has_sgram ? DACSPEED24_SG : DACSPEED24_SD; + if ((1000000 / var->pixclock) > dacspeed24) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 24bpp)\n", + 1000000 / var->pixclock, dacspeed24); + return -EINVAL; + } + break; + case 25 ... 32: + bpp = 32; + if ((1000000 / var->pixclock) > DACSPEED32) { + dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 32bpp)\n", + 1000000 / var->pixclock, DACSPEED32); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + xres = ALIGN(var->xres, 8); + vxres = ALIGN(var->xres_virtual, 16); + if (vxres < xres) + vxres = xres; + + xoffset = ALIGN(var->xoffset, 8); + if (xres + xoffset > vxres) + xoffset = vxres - xres; + + left = ALIGN(var->left_margin, 8); + right = ALIGN(var->right_margin, 8); + hslen = ALIGN(var->hsync_len, 8); + + yres = var->yres; + vyres = var->yres_virtual; + if (yres > vyres) + vyres = yres; + + yoffset = var->yoffset; + if (yres + yoffset > vyres) + yoffset = vyres - yres; + + lower = var->lower_margin; + vslen = var->vsync_len; + upper = var->upper_margin; + + mem = vxres * vyres * ((bpp + 1) / 8); + if (mem > info->screen_size) { + dev_err(info->device, "not enough video memory (%d KB requested, %ld KB avaliable)\n", + mem >> 10, info->screen_size >> 10); + return -ENOMEM; + } + + if (yoffset + yres > vyres) + yoffset = vyres - yres; + + xtotal = xres + right + hslen + left; + ytotal = yres + lower + vslen + upper; + + par->crtc[VGA_CRTC_H_TOTAL] = (xtotal >> 3) - 5; + par->crtc[VGA_CRTC_H_DISP] = (xres >> 3) - 1; + par->crtc[VGA_CRTC_H_BLANK_START] = ((xres + right) >> 3) - 1; + par->crtc[VGA_CRTC_H_SYNC_START] = (xres + right) >> 3; + par->crtc[VGA_CRTC_H_SYNC_END] = (((xres + right + hslen) >> 3) & 0x1F) + | ((((xres + right + hslen) >> 3) & 0x20) << 2); + par->crtc[VGA_CRTC_H_BLANK_END] = ((xres + right + hslen) >> 3 & 0x1F) + | 0x80; + + par->crtc[VGA_CRTC_V_TOTAL] = ytotal - 2; + + r7 = 0x10; /* disable linecompare */ + if (ytotal & 0x100) + r7 |= 0x01; + if (ytotal & 0x200) + r7 |= 0x20; + + par->crtc[VGA_CRTC_PRESET_ROW] = 0; + par->crtc[VGA_CRTC_MAX_SCAN] = 0x40; /* 1 scanline, no linecmp */ + if (var->vmode & FB_VMODE_DOUBLE) + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x80; + par->crtc[VGA_CRTC_CURSOR_START] = 0x00; + par->crtc[VGA_CRTC_CURSOR_END] = 0x00; + par->crtc[VGA_CRTC_CURSOR_HI] = 0x00; + par->crtc[VGA_CRTC_CURSOR_LO] = 0x00; + par->crtc[VGA_CRTC_V_DISP_END] = yres-1; + if ((yres-1) & 0x100) + r7 |= 0x02; + if ((yres-1) & 0x200) + r7 |= 0x40; + + par->crtc[VGA_CRTC_V_BLANK_START] = yres + lower - 1; + par->crtc[VGA_CRTC_V_SYNC_START] = yres + lower - 1; + if ((yres + lower - 1) & 0x100) + r7 |= 0x0C; + if ((yres + lower - 1) & 0x200) { + par->crtc[VGA_CRTC_MAX_SCAN] |= 0x20; + r7 |= 0x80; + } + + /* disabled IRQ */ + par->crtc[VGA_CRTC_V_SYNC_END] = + ((yres + lower - 1 + vslen) & 0x0F) & ~0x10; + /* 0x7F for VGA, but some SVGA chips require all 8 bits to be set */ + par->crtc[VGA_CRTC_V_BLANK_END] = (yres + lower - 1 + vslen) & 0xFF; + + par->crtc[VGA_CRTC_UNDERLINE] = 0x00; + par->crtc[VGA_CRTC_MODE] = 0xC3 ; + par->crtc[VGA_CRTC_LINE_COMPARE] = 0xFF; + par->crtc[VGA_CRTC_OVERFLOW] = r7; + + par->vss = 0x00; /* 3DA */ + + for (i = 0x00; i < 0x10; i++) + par->atc[i] = i; + par->atc[VGA_ATC_MODE] = 0x81; + par->atc[VGA_ATC_OVERSCAN] = 0x00; /* 0 for EGA, 0xFF for VGA */ + par->atc[VGA_ATC_PLANE_ENABLE] = 0x0F; + par->atc[VGA_ATC_COLOR_PAGE] = 0x00; + + par->misc = 0xC3; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->misc &= ~0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->misc &= ~0x80; + + par->seq[VGA_SEQ_CLOCK_MODE] = 0x01; + par->seq[VGA_SEQ_PLANE_WRITE] = 0x0F; + par->seq[VGA_SEQ_CHARACTER_MAP] = 0x00; + par->seq[VGA_SEQ_MEMORY_MODE] = 0x06; + + par->gdc[VGA_GFX_SR_VALUE] = 0x00; + par->gdc[VGA_GFX_SR_ENABLE] = 0x00; + par->gdc[VGA_GFX_COMPARE_VALUE] = 0x00; + par->gdc[VGA_GFX_DATA_ROTATE] = 0x00; + par->gdc[VGA_GFX_PLANE_READ] = 0; + par->gdc[VGA_GFX_MODE] = 0x02; + par->gdc[VGA_GFX_MISC] = 0x05; + par->gdc[VGA_GFX_COMPARE_MASK] = 0x0F; + par->gdc[VGA_GFX_BIT_MASK] = 0xFF; + + base = (yoffset * vxres + (xoffset & ~7)) >> 2; + switch (bpp) { + case 8: + par->crtc[VGA_CRTC_OFFSET] = vxres >> 3; + par->ext_offset = vxres >> 11; + par->pixelpipe_cfg1 = DISPLAY_8BPP_MODE; + par->bitblt_cntl = COLEXP_8BPP; + break; + case 15: /* 0rrrrrgg gggbbbbb */ + case 16: /* rrrrrggg gggbbbbb */ + par->pixelpipe_cfg1 = (var->green.length == 6) ? + DISPLAY_16BPP_MODE : DISPLAY_15BPP_MODE; + par->crtc[VGA_CRTC_OFFSET] = vxres >> 2; + par->ext_offset = vxres >> 10; + par->bitblt_cntl = COLEXP_16BPP; + base *= 2; + break; + case 24: + par->crtc[VGA_CRTC_OFFSET] = (vxres * 3) >> 3; + par->ext_offset = (vxres * 3) >> 11; + par->pixelpipe_cfg1 = DISPLAY_24BPP_MODE; + par->bitblt_cntl = COLEXP_24BPP; + base &= 0xFFFFFFFE; /* ...ignore the last bit. */ + base *= 3; + break; + case 32: + par->crtc[VGA_CRTC_OFFSET] = vxres >> 1; + par->ext_offset = vxres >> 9; + par->pixelpipe_cfg1 = DISPLAY_32BPP_MODE; + par->bitblt_cntl = COLEXP_RESERVED; /* Unimplemented on i740 */ + base *= 4; + break; + } + + par->crtc[VGA_CRTC_START_LO] = base & 0x000000FF; + par->crtc[VGA_CRTC_START_HI] = (base & 0x0000FF00) >> 8; + par->ext_start_addr = + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE; + par->ext_start_addr_hi = (base & 0x3FC00000) >> 22; + + par->pixelpipe_cfg0 = DAC_8_BIT; + + par->pixelpipe_cfg2 = DISPLAY_GAMMA_ENABLE | OVERLAY_GAMMA_ENABLE; + par->io_cntl = EXTENDED_CRTC_CNTL; + par->address_mapping = LINEAR_MODE_ENABLE | PAGE_MAPPING_ENABLE; + par->display_cntl = HIRES_MODE; + + /* Set the MCLK freq */ + par->pll_cntl = PLL_MEMCLK_100000KHZ; /* 100 MHz -- use as default */ + + /* Calculate the extended CRTC regs */ + par->ext_vert_total = (ytotal - 2) >> 8; + par->ext_vert_disp_end = (yres - 1) >> 8; + par->ext_vert_sync_start = (yres + lower) >> 8; + par->ext_vert_blank_start = (yres + lower) >> 8; + par->ext_horiz_total = ((xtotal >> 3) - 5) >> 8; + par->ext_horiz_blank = (((xres + right) >> 3) & 0x40) >> 6; + + par->interlace_cntl = INTERLACE_DISABLE; + + /* Set the overscan color to 0. (NOTE: This only affects >8bpp mode) */ + par->atc[VGA_ATC_OVERSCAN] = 0; + + /* Calculate VCLK that most closely matches the requested dot clock */ + i740_calc_vclk((((u32)1e9) / var->pixclock) * (u32)(1e3), par); + + /* Since we program the clocks ourselves, always use VCLK2. */ + par->misc |= 0x0C; + + /* Calculate the FIFO Watermark and Burst Length. */ + par->lmi_fifo_watermark = + i740_calc_fifo(par, 1000000 / var->pixclock, bpp); + + return 0; +} + +static int i740fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + switch (var->bits_per_pixel) { + case 8: + var->red.offset = var->green.offset = var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 16: + switch (var->green.length) { + default: + case 5: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + break; + case 6: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = var->blue.length = 5; + break; + } + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = var->green.length = var->blue.length = 8; + break; + case 32: + var->transp.offset = 24; + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->transp.length = 8; + var->red.length = var->green.length = var->blue.length = 8; + break; + default: + return -EINVAL; + } + + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + + if (info->monspecs.hfmax && info->monspecs.vfmax && + info->monspecs.dclkmax && fb_validate_mode(var, info) < 0) + return -EINVAL; + + return 0; +} + +static void vga_protect(struct i740fb_par *par) +{ + /* disable the display */ + i740outreg_mask(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, 0x20, 0x20); + + i740inb(par, 0x3DA); + i740outb(par, VGA_ATT_W, 0x00); /* enable pallete access */ +} + +static void vga_unprotect(struct i740fb_par *par) +{ + /* reenable display */ + i740outreg_mask(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, 0, 0x20); + + i740inb(par, 0x3DA); + i740outb(par, VGA_ATT_W, 0x20); /* disable pallete access */ +} + +static int i740fb_set_par(struct fb_info *info) +{ + struct i740fb_par *par = info->par; + u32 itemp; + int i; + + i = i740fb_decode_var(&info->var, par, info); + if (i) + return i; + + memset(info->screen_base, 0, info->screen_size); + + vga_protect(par); + + i740outreg(par, XRX, DRAM_EXT_CNTL, DRAM_REFRESH_DISABLE); + + mdelay(1); + + i740outreg(par, XRX, VCLK2_VCO_M, par->video_clk2_m); + i740outreg(par, XRX, VCLK2_VCO_N, par->video_clk2_n); + i740outreg(par, XRX, VCLK2_VCO_MN_MSBS, par->video_clk2_mn_msbs); + i740outreg(par, XRX, VCLK2_VCO_DIV_SEL, par->video_clk2_div_sel); + + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_0, + par->pixelpipe_cfg0 & DAC_8_BIT, 0x80); + + i740inb(par, 0x3DA); + i740outb(par, 0x3C0, 0x00); + + /* update misc output register */ + i740outb(par, VGA_MIS_W, par->misc | 0x01); + + /* synchronous reset on */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_RESET, 0x01); + /* write sequencer registers */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_CLOCK_MODE, + par->seq[VGA_SEQ_CLOCK_MODE] | 0x20); + for (i = 2; i < VGA_SEQ_C; i++) + i740outreg(par, VGA_SEQ_I, i, par->seq[i]); + + /* synchronous reset off */ + i740outreg(par, VGA_SEQ_I, VGA_SEQ_RESET, 0x03); + + /* deprotect CRT registers 0-7 */ + i740outreg(par, VGA_CRT_IC, VGA_CRTC_V_SYNC_END, + par->crtc[VGA_CRTC_V_SYNC_END]); + + /* write CRT registers */ + for (i = 0; i < VGA_CRT_C; i++) + i740outreg(par, VGA_CRT_IC, i, par->crtc[i]); + + /* write graphics controller registers */ + for (i = 0; i < VGA_GFX_C; i++) + i740outreg(par, VGA_GFX_I, i, par->gdc[i]); + + /* write attribute controller registers */ + for (i = 0; i < VGA_ATT_C; i++) { + i740inb(par, VGA_IS1_RC); /* reset flip-flop */ + i740outb(par, VGA_ATT_IW, i); + i740outb(par, VGA_ATT_IW, par->atc[i]); + } + + i740inb(par, VGA_IS1_RC); + i740outb(par, VGA_ATT_IW, 0x20); + + i740outreg(par, VGA_CRT_IC, EXT_VERT_TOTAL, par->ext_vert_total); + i740outreg(par, VGA_CRT_IC, EXT_VERT_DISPLAY, par->ext_vert_disp_end); + i740outreg(par, VGA_CRT_IC, EXT_VERT_SYNC_START, + par->ext_vert_sync_start); + i740outreg(par, VGA_CRT_IC, EXT_VERT_BLANK_START, + par->ext_vert_blank_start); + i740outreg(par, VGA_CRT_IC, EXT_HORIZ_TOTAL, par->ext_horiz_total); + i740outreg(par, VGA_CRT_IC, EXT_HORIZ_BLANK, par->ext_horiz_blank); + i740outreg(par, VGA_CRT_IC, EXT_OFFSET, par->ext_offset); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR_HI, par->ext_start_addr_hi); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR, par->ext_start_addr); + + i740outreg_mask(par, VGA_CRT_IC, INTERLACE_CNTL, + par->interlace_cntl, INTERLACE_ENABLE); + i740outreg_mask(par, XRX, ADDRESS_MAPPING, par->address_mapping, 0x1F); + i740outreg_mask(par, XRX, BITBLT_CNTL, par->bitblt_cntl, COLEXP_MODE); + i740outreg_mask(par, XRX, DISPLAY_CNTL, + par->display_cntl, VGA_WRAP_MODE | GUI_MODE); + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_0, par->pixelpipe_cfg0, 0x9B); + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_2, par->pixelpipe_cfg2, 0x0C); + + i740outreg(par, XRX, PLL_CNTL, par->pll_cntl); + + i740outreg_mask(par, XRX, PIXPIPE_CONFIG_1, + par->pixelpipe_cfg1, DISPLAY_COLOR_MODE); + + itemp = readl(par->regs + FWATER_BLC); + itemp &= ~(LMI_BURST_LENGTH | LMI_FIFO_WATERMARK); + itemp |= par->lmi_fifo_watermark; + writel(itemp, par->regs + FWATER_BLC); + + i740outreg(par, XRX, DRAM_EXT_CNTL, DRAM_REFRESH_60HZ); + + i740outreg_mask(par, MRX, COL_KEY_CNTL_1, 0, BLANK_DISP_OVERLAY); + i740outreg_mask(par, XRX, IO_CTNL, + par->io_cntl, EXTENDED_ATTR_CNTL | EXTENDED_CRTC_CNTL); + + if (par->pixelpipe_cfg1 != DISPLAY_8BPP_MODE) { + i740outb(par, VGA_PEL_MSK, 0xFF); + i740outb(par, VGA_PEL_IW, 0x00); + for (i = 0; i < 256; i++) { + itemp = (par->pixelpipe_cfg0 & DAC_8_BIT) ? i : i >> 2; + i740outb(par, VGA_PEL_D, itemp); + i740outb(par, VGA_PEL_D, itemp); + i740outb(par, VGA_PEL_D, itemp); + } + } + + /* Wait for screen to stabilize. */ + mdelay(50); + vga_unprotect(par); + + info->fix.line_length = + info->var.xres_virtual * info->var.bits_per_pixel / 8; + if (info->var.bits_per_pixel == 8) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + return 0; +} + +static int i740fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + u32 r, g, b; + + dev_dbg(info->device, "setcolreg: regno: %i, red=%d, green=%d, blue=%d, transp=%d, bpp=%d\n", + regno, red, green, blue, transp, info->var.bits_per_pixel); + + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + if (regno >= 256) + return -EINVAL; + i740outb(info->par, VGA_PEL_IW, regno); + i740outb(info->par, VGA_PEL_D, red >> 8); + i740outb(info->par, VGA_PEL_D, green >> 8); + i740outb(info->par, VGA_PEL_D, blue >> 8); + break; + case FB_VISUAL_TRUECOLOR: + if (regno >= 16) + return -EINVAL; + r = (red >> (16 - info->var.red.length)) + << info->var.red.offset; + b = (blue >> (16 - info->var.blue.length)) + << info->var.blue.offset; + g = (green >> (16 - info->var.green.length)) + << info->var.green.offset; + ((u32 *) info->pseudo_palette)[regno] = r | g | b; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int i740fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct i740fb_par *par = info->par; + u32 base = (var->yoffset * info->var.xres_virtual + + (var->xoffset & ~7)) >> 2; + + dev_dbg(info->device, "pan_display: xoffset: %i yoffset: %i base: %i\n", + var->xoffset, var->yoffset, base); + + switch (info->var.bits_per_pixel) { + case 8: + break; + case 15: + case 16: + base *= 2; + break; + case 24: + /* + * The last bit does not seem to have any effect on the start + * address register in 24bpp mode, so... + */ + base &= 0xFFFFFFFE; /* ...ignore the last bit. */ + base *= 3; + break; + case 32: + base *= 4; + break; + } + + par->crtc[VGA_CRTC_START_LO] = base & 0x000000FF; + par->crtc[VGA_CRTC_START_HI] = (base & 0x0000FF00) >> 8; + par->ext_start_addr_hi = (base & 0x3FC00000) >> 22; + par->ext_start_addr = + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE; + + i740outreg(par, VGA_CRT_IC, VGA_CRTC_START_LO, base & 0x000000FF); + i740outreg(par, VGA_CRT_IC, VGA_CRTC_START_HI, + (base & 0x0000FF00) >> 8); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR_HI, + (base & 0x3FC00000) >> 22); + i740outreg(par, VGA_CRT_IC, EXT_START_ADDR, + ((base & 0x003F0000) >> 16) | EXT_START_ADDR_ENABLE); + + return 0; +} + +static int i740fb_blank(int blank_mode, struct fb_info *info) +{ + struct i740fb_par *par = info->par; + + unsigned char SEQ01; + int DPMSSyncSelect; + + switch (blank_mode) { + case FB_BLANK_UNBLANK: + case FB_BLANK_NORMAL: + SEQ01 = 0x00; + DPMSSyncSelect = HSYNC_ON | VSYNC_ON; + break; + case FB_BLANK_VSYNC_SUSPEND: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_ON | VSYNC_OFF; + break; + case FB_BLANK_HSYNC_SUSPEND: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_OFF | VSYNC_ON; + break; + case FB_BLANK_POWERDOWN: + SEQ01 = 0x20; + DPMSSyncSelect = HSYNC_OFF | VSYNC_OFF; + break; + default: + return -EINVAL; + } + /* Turn the screen on/off */ + i740outb(par, SRX, 0x01); + SEQ01 |= i740inb(par, SRX + 1) & ~0x20; + i740outb(par, SRX, 0x01); + i740outb(par, SRX + 1, SEQ01); + + /* Set the DPMS mode */ + i740outreg(par, XRX, DPMS_SYNC_SELECT, DPMSSyncSelect); + + /* Let fbcon do a soft blank for us */ + return (blank_mode == FB_BLANK_NORMAL) ? 1 : 0; +} + +static struct fb_ops i740fb_ops = { + .owner = THIS_MODULE, + .fb_open = i740fb_open, + .fb_release = i740fb_release, + .fb_check_var = i740fb_check_var, + .fb_set_par = i740fb_set_par, + .fb_setcolreg = i740fb_setcolreg, + .fb_blank = i740fb_blank, + .fb_pan_display = i740fb_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, +}; + +/* ------------------------------------------------------------------------- */ + +static int __devinit i740fb_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + struct fb_info *info; + struct i740fb_par *par; + int ret, tmp; + bool found = false; + u8 *edid; + + info = framebuffer_alloc(sizeof(struct i740fb_par), &(dev->dev)); + if (!info) { + dev_err(&(dev->dev), "cannot allocate framebuffer\n"); + return -ENOMEM; + } + + par = info->par; + mutex_init(&par->open_lock); + + info->var.activate = FB_ACTIVATE_NOW; + info->var.bits_per_pixel = 8; + info->fbops = &i740fb_ops; + info->pseudo_palette = par->pseudo_palette; + + ret = pci_enable_device(dev); + if (ret) { + dev_err(info->device, "cannot enable PCI device\n"); + goto err_enable_device; + } + + ret = pci_request_regions(dev, info->fix.id); + if (ret) { + dev_err(info->device, "error requesting regions\n"); + goto err_request_regions; + } + + info->screen_base = pci_ioremap_bar(dev, 0); + if (!info->screen_base) { + dev_err(info->device, "error remapping base\n"); + ret = -ENOMEM; + goto err_ioremap_1; + } + + par->regs = pci_ioremap_bar(dev, 1); + if (!par->regs) { + dev_err(info->device, "error remapping MMIO\n"); + ret = -ENOMEM; + goto err_ioremap_2; + } + + /* detect memory size */ + if ((i740inreg(par, XRX, DRAM_ROW_TYPE) & DRAM_ROW_1) + == DRAM_ROW_1_SDRAM) + i740outb(par, XRX, DRAM_ROW_BNDRY_1); + else + i740outb(par, XRX, DRAM_ROW_BNDRY_0); + info->screen_size = i740inb(par, XRX + 1) * 1024 * 1024; + /* detect memory type */ + tmp = i740inreg(par, XRX, DRAM_ROW_CNTL_LO); + par->has_sgram = !((tmp & DRAM_RAS_TIMING) || + (tmp & DRAM_RAS_PRECHARGE)); + + printk(KERN_INFO "fb%d: Intel740 on %s, %ld KB %s\n", info->node, + pci_name(dev), info->screen_size >> 10, + par->has_sgram ? "SGRAM" : "SDRAM"); + + info->fix = i740fb_fix; + info->fix.mmio_start = pci_resource_start(dev, 1); + info->fix.mmio_len = pci_resource_len(dev, 1); + info->fix.smem_start = pci_resource_start(dev, 0); + info->fix.smem_len = info->screen_size; + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; + + if (i740fb_setup_ddc_bus(info) == 0) { + par->ddc_registered = true; + edid = fb_ddc_read(&par->ddc_adapter); + if (edid) { + fb_edid_to_monspecs(edid, &info->monspecs); + kfree(edid); + if (!info->monspecs.modedb) + dev_err(info->device, + "error getting mode database\n"); + else { + const struct fb_videomode *m; + + fb_videomode_to_modelist( + info->monspecs.modedb, + info->monspecs.modedb_len, + &info->modelist); + m = fb_find_best_display(&info->monspecs, + &info->modelist); + if (m) { + fb_videomode_to_var(&info->var, m); + /* fill all other info->var's fields */ + if (!i740fb_check_var(&info->var, info)) + found = true; + } + } + } + } + + if (!mode_option && !found) + mode_option = "640x480-8@60"; + + if (mode_option) { + ret = fb_find_mode(&info->var, info, mode_option, + info->monspecs.modedb, + info->monspecs.modedb_len, + NULL, info->var.bits_per_pixel); + if (!ret || ret == 4) { + dev_err(info->device, "mode %s not found\n", + mode_option); + ret = -EINVAL; + } + } + + fb_destroy_modedb(info->monspecs.modedb); + info->monspecs.modedb = NULL; + + /* maximize virtual vertical size for fast scrolling */ + info->var.yres_virtual = info->fix.smem_len * 8 / + (info->var.bits_per_pixel * info->var.xres_virtual); + + if (ret == -EINVAL) + goto err_find_mode; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + dev_err(info->device, "cannot allocate colormap\n"); + goto err_alloc_cmap; + } + + ret = register_framebuffer(info); + if (ret) { + dev_err(info->device, "error registering framebuffer\n"); + goto err_reg_framebuffer; + } + + printk(KERN_INFO "fb%d: %s frame buffer device\n", + info->node, info->fix.id); + pci_set_drvdata(dev, info); +#ifdef CONFIG_MTRR + if (mtrr) { + par->mtrr_reg = -1; + par->mtrr_reg = mtrr_add(info->fix.smem_start, + info->fix.smem_len, MTRR_TYPE_WRCOMB, 1); + } +#endif + return 0; + +err_reg_framebuffer: + fb_dealloc_cmap(&info->cmap); +err_alloc_cmap: +err_find_mode: + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + pci_iounmap(dev, par->regs); +err_ioremap_2: + pci_iounmap(dev, info->screen_base); +err_ioremap_1: + pci_release_regions(dev); +err_request_regions: +/* pci_disable_device(dev); */ +err_enable_device: + framebuffer_release(info); + return ret; +} + +static void __devexit i740fb_remove(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + + if (info) { + struct i740fb_par *par = info->par; + +#ifdef CONFIG_MTRR + if (par->mtrr_reg >= 0) { + mtrr_del(par->mtrr_reg, 0, 0); + par->mtrr_reg = -1; + } +#endif + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + if (par->ddc_registered) + i2c_del_adapter(&par->ddc_adapter); + pci_iounmap(dev, par->regs); + pci_iounmap(dev, info->screen_base); + pci_release_regions(dev); +/* pci_disable_device(dev); */ + pci_set_drvdata(dev, NULL); + framebuffer_release(info); + } +} + +#ifdef CONFIG_PM +static int i740fb_suspend(struct pci_dev *dev, pm_message_t state) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i740fb_par *par = info->par; + + /* don't disable console during hibernation and wakeup from it */ + if (state.event == PM_EVENT_FREEZE || state.event == PM_EVENT_PRETHAW) + return 0; + + console_lock(); + mutex_lock(&(par->open_lock)); + + /* do nothing if framebuffer is not active */ + if (par->ref_count == 0) { + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; + } + + fb_set_suspend(info, 1); + + pci_save_state(dev); + pci_disable_device(dev); + pci_set_power_state(dev, pci_choose_state(dev, state)); + + mutex_unlock(&(par->open_lock)); + console_unlock(); + + return 0; +} + +static int i740fb_resume(struct pci_dev *dev) +{ + struct fb_info *info = pci_get_drvdata(dev); + struct i740fb_par *par = info->par; + + console_lock(); + mutex_lock(&(par->open_lock)); + + if (par->ref_count == 0) + goto fail; + + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + if (pci_enable_device(dev)) + goto fail; + + i740fb_set_par(info); + fb_set_suspend(info, 0); + +fail: + mutex_unlock(&(par->open_lock)); + console_unlock(); + return 0; +} +#else +#define i740fb_suspend NULL +#define i740fb_resume NULL +#endif /* CONFIG_PM */ + +#define I740_ID_PCI 0x00d1 +#define I740_ID_AGP 0x7800 + +static DEFINE_PCI_DEVICE_TABLE(i740fb_id_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, I740_ID_PCI) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, I740_ID_AGP) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, i740fb_id_table); + +static struct pci_driver i740fb_driver = { + .name = "i740fb", + .id_table = i740fb_id_table, + .probe = i740fb_probe, + .remove = __devexit_p(i740fb_remove), + .suspend = i740fb_suspend, + .resume = i740fb_resume, +}; + +#ifndef MODULE +static int __init i740fb_setup(char *options) +{ + char *opt; + + if (!options || !*options) + return 0; + + while ((opt = strsep(&options, ",")) != NULL) { + if (!*opt) + continue; +#ifdef CONFIG_MTRR + else if (!strncmp(opt, "mtrr:", 5)) + mtrr = simple_strtoul(opt + 5, NULL, 0); +#endif + else + mode_option = opt; + } + + return 0; +} +#endif + +int __init i740fb_init(void) +{ +#ifndef MODULE + char *option = NULL; + + if (fb_get_options("i740fb", &option)) + return -ENODEV; + i740fb_setup(option); +#endif + + return pci_register_driver(&i740fb_driver); +} + +static void __exit i740fb_exit(void) +{ + pci_unregister_driver(&i740fb_driver); +} + +module_init(i740fb_init); +module_exit(i740fb_exit); + +MODULE_AUTHOR("(c) 2011 Ondrej Zary <linux@rainbow-software.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("fbdev driver for Intel740"); + +module_param(mode_option, charp, 0444); +MODULE_PARM_DESC(mode_option, "Default video mode ('640x480-8@60', etc)"); + +#ifdef CONFIG_MTRR +module_param(mtrr, int, 0444); +MODULE_PARM_DESC(mtrr, "Enable write-combining with MTRR (1=enable, 0=disable, default=1)"); +#endif diff --git a/drivers/video/msm/mddi_client_nt35399.c b/drivers/video/msm/mddi_client_nt35399.c index f239f4a25e0..7fcd67e132b 100644 --- a/drivers/video/msm/mddi_client_nt35399.c +++ b/drivers/video/msm/mddi_client_nt35399.c @@ -155,14 +155,10 @@ static int setup_vsync(struct panel_info *panel, int init) ret = 0; goto uninit; } - ret = gpio_request(gpio, "vsync"); + ret = gpio_request_one(gpio, GPIOF_IN, "vsync"); if (ret) goto err_request_gpio_failed; - ret = gpio_direction_input(gpio); - if (ret) - goto err_gpio_direction_input_failed; - ret = irq = gpio_to_irq(gpio); if (ret < 0) goto err_get_irq_num_failed; @@ -180,7 +176,6 @@ uninit: free_irq(gpio_to_irq(gpio), panel->client_data); err_request_irq_failed: err_get_irq_num_failed: -err_gpio_direction_input_failed: gpio_free(gpio); err_request_gpio_failed: return ret; diff --git a/drivers/video/msm/mddi_client_toshiba.c b/drivers/video/msm/mddi_client_toshiba.c index f9bc932ac46..053eb687733 100644 --- a/drivers/video/msm/mddi_client_toshiba.c +++ b/drivers/video/msm/mddi_client_toshiba.c @@ -186,14 +186,10 @@ static int setup_vsync(struct panel_info *panel, ret = 0; goto uninit; } - ret = gpio_request(gpio, "vsync"); + ret = gpio_request_one(gpio, GPIOF_IN, "vsync"); if (ret) goto err_request_gpio_failed; - ret = gpio_direction_input(gpio); - if (ret) - goto err_gpio_direction_input_failed; - ret = irq = gpio_to_irq(gpio); if (ret < 0) goto err_get_irq_num_failed; @@ -210,7 +206,6 @@ uninit: free_irq(gpio_to_irq(gpio), panel); err_request_irq_failed: err_get_irq_num_failed: -err_gpio_direction_input_failed: gpio_free(gpio); err_request_gpio_failed: return ret; diff --git a/drivers/video/omap/lcd_inn1610.c b/drivers/video/omap/lcd_inn1610.c index 7e8bd8e08a9..e3d3d135aa4 100644 --- a/drivers/video/omap/lcd_inn1610.c +++ b/drivers/video/omap/lcd_inn1610.c @@ -22,7 +22,7 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include <asm/gpio.h> +#include <linux/gpio.h> #include "omapfb.h" #define MODULE_NAME "omapfb-lcd_h3" @@ -32,20 +32,18 @@ static int innovator1610_panel_init(struct lcd_panel *panel, { int r = 0; - if (gpio_request(14, "lcd_en0")) { + /* configure GPIO(14, 15) as outputs */ + if (gpio_request_one(14, GPIOF_OUT_INIT_LOW, "lcd_en0")) { pr_err(MODULE_NAME ": can't request GPIO 14\n"); r = -1; goto exit; } - if (gpio_request(15, "lcd_en1")) { + if (gpio_request_one(15, GPIOF_OUT_INIT_LOW, "lcd_en1")) { pr_err(MODULE_NAME ": can't request GPIO 15\n"); gpio_free(14); r = -1; goto exit; } - /* configure GPIO(14, 15) as outputs */ - gpio_direction_output(14, 0); - gpio_direction_output(15, 0); exit: return r; } diff --git a/drivers/video/omap/lcd_mipid.c b/drivers/video/omap/lcd_mipid.c index 8d546dd55e8..e3880c4a0bb 100644 --- a/drivers/video/omap/lcd_mipid.c +++ b/drivers/video/omap/lcd_mipid.c @@ -609,19 +609,7 @@ static struct spi_driver mipid_spi_driver = { .remove = __devexit_p(mipid_spi_remove), }; -static int __init mipid_drv_init(void) -{ - spi_register_driver(&mipid_spi_driver); - - return 0; -} -module_init(mipid_drv_init); - -static void __exit mipid_drv_cleanup(void) -{ - spi_unregister_driver(&mipid_spi_driver); -} -module_exit(mipid_drv_cleanup); +module_spi_driver(mipid_spi_driver); MODULE_DESCRIPTION("MIPI display driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap2/displays/panel-acx565akm.c b/drivers/video/omap2/displays/panel-acx565akm.c index 51a87e149e2..d26f37ac69d 100644 --- a/drivers/video/omap2/displays/panel-acx565akm.c +++ b/drivers/video/omap2/displays/panel-acx565akm.c @@ -809,18 +809,7 @@ static struct spi_driver acx565akm_spi_driver = { .remove = __devexit_p(acx565akm_spi_remove), }; -static int __init acx565akm_init(void) -{ - return spi_register_driver(&acx565akm_spi_driver); -} - -static void __exit acx565akm_exit(void) -{ - spi_unregister_driver(&acx565akm_spi_driver); -} - -module_init(acx565akm_init); -module_exit(acx565akm_exit); +module_spi_driver(acx565akm_spi_driver); MODULE_AUTHOR("Nokia Corporation"); MODULE_DESCRIPTION("acx565akm LCD Driver"); diff --git a/drivers/video/omap2/displays/panel-lgphilips-lb035q02.c b/drivers/video/omap2/displays/panel-lgphilips-lb035q02.c index e0eb35be303..0841cc2b3f7 100644 --- a/drivers/video/omap2/displays/panel-lgphilips-lb035q02.c +++ b/drivers/video/omap2/displays/panel-lgphilips-lb035q02.c @@ -264,16 +264,6 @@ static struct spi_driver lb035q02_spi_driver = { .remove = __devexit_p(lb035q02_panel_spi_remove), }; -static int __init lb035q02_panel_drv_init(void) -{ - return spi_register_driver(&lb035q02_spi_driver); -} - -static void __exit lb035q02_panel_drv_exit(void) -{ - spi_unregister_driver(&lb035q02_spi_driver); -} +module_spi_driver(lb035q02_spi_driver); -module_init(lb035q02_panel_drv_init); -module_exit(lb035q02_panel_drv_exit); MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap2/displays/panel-nec-nl8048hl11-01b.c b/drivers/video/omap2/displays/panel-nec-nl8048hl11-01b.c index 0eb31caddca..8b38b39213f 100644 --- a/drivers/video/omap2/displays/panel-nec-nl8048hl11-01b.c +++ b/drivers/video/omap2/displays/panel-nec-nl8048hl11-01b.c @@ -350,18 +350,8 @@ static struct spi_driver nec_8048_spi_driver = { }, }; -static int __init nec_8048_lcd_init(void) -{ - return spi_register_driver(&nec_8048_spi_driver); -} - -static void __exit nec_8048_lcd_exit(void) -{ - return spi_unregister_driver(&nec_8048_spi_driver); -} +module_spi_driver(nec_8048_spi_driver); -module_init(nec_8048_lcd_init); -module_exit(nec_8048_lcd_exit); MODULE_AUTHOR("Erik Gilling <konkers@android.com>"); MODULE_DESCRIPTION("NEC-nl8048hl11-01b Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap2/displays/panel-taal.c b/drivers/video/omap2/displays/panel-taal.c index 00c5c615585..0f21fa5a16a 100644 --- a/drivers/video/omap2/displays/panel-taal.c +++ b/drivers/video/omap2/displays/panel-taal.c @@ -1019,14 +1019,12 @@ static int taal_probe(struct omap_dss_device *dssdev) if (panel_data->use_ext_te) { int gpio = panel_data->ext_te_gpio; - r = gpio_request(gpio, "taal irq"); + r = gpio_request_one(gpio, GPIOF_IN, "taal irq"); if (r) { dev_err(&dssdev->dev, "GPIO request failed\n"); goto err_gpio; } - gpio_direction_input(gpio); - r = request_irq(gpio_to_irq(gpio), taal_te_isr, IRQF_TRIGGER_RISING, "taal vsync", dssdev); diff --git a/drivers/video/omap2/displays/panel-tpo-td043mtea1.c b/drivers/video/omap2/displays/panel-tpo-td043mtea1.c index e6649aa8959..0ca9644f9ea 100644 --- a/drivers/video/omap2/displays/panel-tpo-td043mtea1.c +++ b/drivers/video/omap2/displays/panel-tpo-td043mtea1.c @@ -408,17 +408,12 @@ static int tpo_td043_probe(struct omap_dss_device *dssdev) } if (gpio_is_valid(nreset_gpio)) { - ret = gpio_request(nreset_gpio, "lcd reset"); + ret = gpio_request_one(nreset_gpio, GPIOF_OUT_INIT_LOW, + "lcd reset"); if (ret < 0) { dev_err(&dssdev->dev, "couldn't request reset GPIO\n"); goto fail_gpio_req; } - - ret = gpio_direction_output(nreset_gpio, 0); - if (ret < 0) { - dev_err(&dssdev->dev, "couldn't set GPIO direction\n"); - goto fail_gpio_direction; - } } ret = sysfs_create_group(&dssdev->dev.kobj, &tpo_td043_attr_group); @@ -427,8 +422,6 @@ static int tpo_td043_probe(struct omap_dss_device *dssdev) return 0; -fail_gpio_direction: - gpio_free(nreset_gpio); fail_gpio_req: regulator_put(tpo_td043->vcc_reg); fail_regulator: @@ -518,18 +511,7 @@ static struct spi_driver tpo_td043_spi_driver = { .remove = __devexit_p(tpo_td043_spi_remove), }; -static int __init tpo_td043_init(void) -{ - return spi_register_driver(&tpo_td043_spi_driver); -} - -static void __exit tpo_td043_exit(void) -{ - spi_unregister_driver(&tpo_td043_spi_driver); -} - -module_init(tpo_td043_init); -module_exit(tpo_td043_exit); +module_spi_driver(tpo_td043_spi_driver); MODULE_AUTHOR("GraÅžvydas Ignotas <notasas@gmail.com>"); MODULE_DESCRIPTION("TPO TD043MTEA1 LCD Driver"); diff --git a/drivers/video/riva/fbdev.c b/drivers/video/riva/fbdev.c index 2f58cf9c813..90df1a60bd1 100644 --- a/drivers/video/riva/fbdev.c +++ b/drivers/video/riva/fbdev.c @@ -1816,6 +1816,8 @@ static void __devinit riva_update_default_var(struct fb_var_screeninfo *var, specs->modedb, specs->modedb_len, NULL, 8); } else if (specs->modedb != NULL) { + /* get first mode in database as fallback */ + modedb = specs->modedb[0]; /* get preferred timing */ if (info->monspecs.misc & FB_MISC_1ST_DETAIL) { int i; @@ -1826,9 +1828,6 @@ static void __devinit riva_update_default_var(struct fb_var_screeninfo *var, break; } } - } else { - /* otherwise, get first mode in database */ - modedb = specs->modedb[0]; } var->bits_per_pixel = 8; riva_update_var(var, &modedb); diff --git a/drivers/video/s3c-fb.c b/drivers/video/s3c-fb.c index 0c63b69b634..e22bf9ddf28 100644 --- a/drivers/video/s3c-fb.c +++ b/drivers/video/s3c-fb.c @@ -48,7 +48,8 @@ #undef writel #define writel(v, r) do { \ printk(KERN_DEBUG "%s: %08x => %p\n", __func__, (unsigned int)v, r); \ - __raw_writel(v, r); } while (0) + __raw_writel(v, r); \ +} while (0) #endif /* FB_S3C_DEBUG_REGWRITE */ /* irq_flags bits */ @@ -81,7 +82,9 @@ struct s3c_fb; * @palette: Address of palette memory, or 0 if none. * @has_prtcon: Set if has PRTCON register. * @has_shadowcon: Set if has SHADOWCON register. + * @has_blendcon: Set if has BLENDCON register. * @has_clksel: Set if VIDCON0 register has CLKSEL bit. + * @has_fixvclk: Set if VIDCON1 register has FIXVCLK bits. */ struct s3c_fb_variant { unsigned int is_2443:1; @@ -99,7 +102,9 @@ struct s3c_fb_variant { unsigned int has_prtcon:1; unsigned int has_shadowcon:1; + unsigned int has_blendcon:1; unsigned int has_clksel:1; + unsigned int has_fixvclk:1; }; /** @@ -186,7 +191,6 @@ struct s3c_fb_vsync { * struct s3c_fb - overall hardware state of the hardware * @slock: The spinlock protection for this data sturcture. * @dev: The device that we bound to, for printing, etc. - * @regs_res: The resource we claimed for the IO registers. * @bus_clk: The clk (hclk) feeding our interface and possibly pixclk. * @lcd_clk: The clk (sclk) feeding pixclk. * @regs: The mapped hardware registers. @@ -202,7 +206,6 @@ struct s3c_fb_vsync { struct s3c_fb { spinlock_t slock; struct device *dev; - struct resource *regs_res; struct clk *bus_clk; struct clk *lcd_clk; void __iomem *regs; @@ -692,6 +695,17 @@ static int s3c_fb_set_par(struct fb_info *info) writel(data, regs + sfb->variant.wincon + (win_no * 4)); writel(0x0, regs + sfb->variant.winmap + (win_no * 4)); + /* Set alpha value width */ + if (sfb->variant.has_blendcon) { + data = readl(sfb->regs + BLENDCON); + data &= ~BLENDCON_NEW_MASK; + if (var->transp.length > 4) + data |= BLENDCON_NEW_8BIT_ALPHA_VALUE; + else + data |= BLENDCON_NEW_4BIT_ALPHA_VALUE; + writel(data, sfb->regs + BLENDCON); + } + shadow_protect_win(win, 0); pm_runtime_put_sync(sfb->dev); @@ -1346,6 +1360,7 @@ static int __devinit s3c_fb_probe(struct platform_device *pdev) struct resource *res; int win; int ret = 0; + u32 reg; platid = platform_get_device_id(pdev); fbdrv = (struct s3c_fb_driverdata *)platid->driver_data; @@ -1361,7 +1376,7 @@ static int __devinit s3c_fb_probe(struct platform_device *pdev) return -EINVAL; } - sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL); + sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL); if (!sfb) { dev_err(dev, "no memory for framebuffers\n"); return -ENOMEM; @@ -1404,33 +1419,25 @@ static int __devinit s3c_fb_probe(struct platform_device *pdev) goto err_lcd_clk; } - sfb->regs_res = request_mem_region(res->start, resource_size(res), - dev_name(dev)); - if (!sfb->regs_res) { - dev_err(dev, "failed to claim register region\n"); - ret = -ENOENT; - goto err_lcd_clk; - } - - sfb->regs = ioremap(res->start, resource_size(res)); + sfb->regs = devm_request_and_ioremap(dev, res); if (!sfb->regs) { dev_err(dev, "failed to map registers\n"); ret = -ENXIO; - goto err_req_region; + goto err_lcd_clk; } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(dev, "failed to acquire irq resource\n"); ret = -ENOENT; - goto err_ioremap; + goto err_lcd_clk; } sfb->irq_no = res->start; ret = request_irq(sfb->irq_no, s3c_fb_irq, 0, "s3c_fb", sfb); if (ret) { dev_err(dev, "irq request failed\n"); - goto err_ioremap; + goto err_lcd_clk; } dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs); @@ -1444,6 +1451,14 @@ static int __devinit s3c_fb_probe(struct platform_device *pdev) writel(pd->vidcon1, sfb->regs + VIDCON1); + /* set video clock running at under-run */ + if (sfb->variant.has_fixvclk) { + reg = readl(sfb->regs + VIDCON1); + reg &= ~VIDCON1_VCLK_MASK; + reg |= VIDCON1_VCLK_RUN; + writel(reg, sfb->regs + VIDCON1); + } + /* zero all windows before we do anything */ for (win = 0; win < fbdrv->variant.nr_windows; win++) @@ -1486,12 +1501,6 @@ err_pm_runtime: pm_runtime_put_sync(sfb->dev); free_irq(sfb->irq_no, sfb); -err_ioremap: - iounmap(sfb->regs); - -err_req_region: - release_mem_region(sfb->regs_res->start, resource_size(sfb->regs_res)); - err_lcd_clk: pm_runtime_disable(sfb->dev); @@ -1505,7 +1514,6 @@ err_bus_clk: clk_put(sfb->bus_clk); err_sfb: - kfree(sfb); return ret; } @@ -1529,8 +1537,6 @@ static int __devexit s3c_fb_remove(struct platform_device *pdev) free_irq(sfb->irq_no, sfb); - iounmap(sfb->regs); - if (!sfb->variant.has_clksel) { clk_disable(sfb->lcd_clk); clk_put(sfb->lcd_clk); @@ -1539,12 +1545,9 @@ static int __devexit s3c_fb_remove(struct platform_device *pdev) clk_disable(sfb->bus_clk); clk_put(sfb->bus_clk); - release_mem_region(sfb->regs_res->start, resource_size(sfb->regs_res)); - pm_runtime_put_sync(sfb->dev); pm_runtime_disable(sfb->dev); - kfree(sfb); return 0; } @@ -1579,6 +1582,7 @@ static int s3c_fb_resume(struct device *dev) struct s3c_fb_platdata *pd = sfb->pdata; struct s3c_fb_win *win; int win_no; + u32 reg; clk_enable(sfb->bus_clk); @@ -1589,6 +1593,14 @@ static int s3c_fb_resume(struct device *dev) pd->setup_gpio(); writel(pd->vidcon1, sfb->regs + VIDCON1); + /* set video clock running at under-run */ + if (sfb->variant.has_fixvclk) { + reg = readl(sfb->regs + VIDCON1); + reg &= ~VIDCON1_VCLK_MASK; + reg |= VIDCON1_VCLK_RUN; + writel(reg, sfb->regs + VIDCON1); + } + /* zero all windows before we do anything */ for (win_no = 0; win_no < sfb->variant.nr_windows; win_no++) s3c_fb_clear_win(sfb, win_no); @@ -1819,6 +1831,7 @@ static struct s3c_fb_driverdata s3c_fb_data_s5pc100 = { }, .has_prtcon = 1, + .has_blendcon = 1, .has_clksel = 1, }, .win[0] = &s3c_fb_data_s5p_wins[0], @@ -1850,7 +1863,9 @@ static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = { }, .has_shadowcon = 1, + .has_blendcon = 1, .has_clksel = 1, + .has_fixvclk = 1, }, .win[0] = &s3c_fb_data_s5p_wins[0], .win[1] = &s3c_fb_data_s5p_wins[1], @@ -1881,6 +1896,8 @@ static struct s3c_fb_driverdata s3c_fb_data_exynos4 = { }, .has_shadowcon = 1, + .has_blendcon = 1, + .has_fixvclk = 1, }, .win[0] = &s3c_fb_data_s5p_wins[0], .win[1] = &s3c_fb_data_s5p_wins[1], @@ -1944,6 +1961,9 @@ static struct s3c_fb_driverdata s3c_fb_data_s5p64x0 = { [1] = 0x2800, [2] = 0x2c00, }, + + .has_blendcon = 1, + .has_fixvclk = 1, }, .win[0] = &s3c_fb_data_s5p_wins[0], .win[1] = &s3c_fb_data_s5p_wins[1], diff --git a/drivers/video/udlfb.c b/drivers/video/udlfb.c index a19773149bd..a40c05ebbdc 100644 --- a/drivers/video/udlfb.c +++ b/drivers/video/udlfb.c @@ -1739,7 +1739,7 @@ static void dlfb_usb_disconnect(struct usb_interface *interface) for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) device_remove_file(info->dev, &fb_device_attrs[i]); device_remove_bin_file(info->dev, &edid_attr); - + unlink_framebuffer(info); usb_set_intfdata(interface, NULL); /* if clients still have us open, will be freed on last close */ |