From f0cd8eb186934996a3e37fe6a69148801c8215fc Mon Sep 17 00:00:00 2001 From: Zheng Yang Date: Sat, 31 Mar 2012 15:13:35 +0800 Subject: [PATCH] rk30: support hdmi. --- arch/arm/mach-rk30/devices.c | 25 + drivers/video/rockchip/Kconfig | 1 + drivers/video/rockchip/Makefile | 1 + drivers/video/rockchip/hdmi/Kconfig | 7 + drivers/video/rockchip/hdmi/Makefile | 1 + drivers/video/rockchip/hdmi/rk30_hdmi.c | 196 +++++++ drivers/video/rockchip/hdmi/rk30_hdmi.h | 73 +++ drivers/video/rockchip/hdmi/rk30_hdmi_edid.c | 410 +++++++++++++++ drivers/video/rockchip/hdmi/rk30_hdmi_hw.c | 440 ++++++++++++++++ drivers/video/rockchip/hdmi/rk30_hdmi_hw.h | 262 ++++++++++ drivers/video/rockchip/hdmi/rk30_hdmi_lcdc.c | 505 +++++++++++++++++++ drivers/video/rockchip/hdmi/rk30_hdmi_task.c | 181 +++++++ drivers/video/rockchip/hdmi/rk_hdmi.h | 198 ++++++++ 13 files changed, 2300 insertions(+) create mode 100644 drivers/video/rockchip/hdmi/Kconfig create mode 100755 drivers/video/rockchip/hdmi/Makefile create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi.c create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi.h create mode 100644 drivers/video/rockchip/hdmi/rk30_hdmi_edid.c create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi_hw.c create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi_hw.h create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi_lcdc.c create mode 100755 drivers/video/rockchip/hdmi/rk30_hdmi_task.c create mode 100755 drivers/video/rockchip/hdmi/rk_hdmi.h diff --git a/arch/arm/mach-rk30/devices.c b/arch/arm/mach-rk30/devices.c index 2dd1c4ef3406..dc0677036f61 100755 --- a/arch/arm/mach-rk30/devices.c +++ b/arch/arm/mach-rk30/devices.c @@ -782,6 +782,28 @@ static struct platform_device device_lcdc1 = { }; #endif +#ifdef CONFIG_HDMI_RK30 +static struct resource resource_hdmi[] = { + [0] = { + .start = RK30_HDMI_PHYS, + .end = RK30_HDMI_PHYS + RK30_HDMI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_HDMI, + .end = IRQ_HDMI, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device device_hdmi = { + .name = "rk30-hdmi", + .id = -1, + .num_resources = ARRAY_SIZE(resource_hdmi), + .resource = resource_hdmi, +}; +#endif + #ifdef CONFIG_RGA_RK30 static struct resource resource_rga[] = { [0] = { @@ -1089,6 +1111,9 @@ static int __init rk30_init_devices(void) #ifdef CONFIG_LCDC1_RK30 platform_device_register(&device_lcdc1); #endif +#ifdef CONFIG_HDMI_RK30 + platform_device_register(&device_hdmi); +#endif #ifdef CONFIG_ADC_RK30 platform_device_register(&device_adc); #endif diff --git a/drivers/video/rockchip/Kconfig b/drivers/video/rockchip/Kconfig index d68e5f450b6c..bd4490fd3c79 100644 --- a/drivers/video/rockchip/Kconfig +++ b/drivers/video/rockchip/Kconfig @@ -27,4 +27,5 @@ config LCDC1_RK30 help Support rk30 lcdc1 if you say y here +source "drivers/video/rockchip/hdmi/Kconfig" source "drivers/video/rockchip/rga/Kconfig" diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile index 90f3b91aa591..31ef7412ce19 100644 --- a/drivers/video/rockchip/Makefile +++ b/drivers/video/rockchip/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_FB_ROCKCHIP) += rk_fb.o rkfb_sysfs.o obj-$(CONFIG_LCDC_RK30) += chips/rk30_lcdc.o obj-$(CONFIG_RGA_RK30) += rga/ +obj-$(CONFIG_HDMI_RK30) += hdmi/ diff --git a/drivers/video/rockchip/hdmi/Kconfig b/drivers/video/rockchip/hdmi/Kconfig new file mode 100644 index 000000000000..f92621d0e3ab --- /dev/null +++ b/drivers/video/rockchip/hdmi/Kconfig @@ -0,0 +1,7 @@ +config HDMI_RK30 + bool "hdmi support" + depends on LCDC_RK30 +# default y + help + Support rk30 hdmi if you say y here + diff --git a/drivers/video/rockchip/hdmi/Makefile b/drivers/video/rockchip/hdmi/Makefile new file mode 100755 index 000000000000..470549a7992c --- /dev/null +++ b/drivers/video/rockchip/hdmi/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_HDMI_RK30) += rk30_hdmi_hw.o rk30_hdmi_edid.o rk30_hdmi_lcdc.o rk30_hdmi_task.o rk30_hdmi.o diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi.c b/drivers/video/rockchip/hdmi/rk30_hdmi.c new file mode 100755 index 000000000000..cbc08af82b9e --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "rk30_hdmi.h" +#include "rk30_hdmi_hw.h" + +struct hdmi *hdmi = NULL; + +extern irqreturn_t hdmi_irq(int irq, void *priv); +extern void hdmi_work(struct work_struct *work); +extern struct rk_lcdc_device_driver * rk_get_lcdc_drv(int id); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void hdmi_early_suspend(struct early_suspend *h) +{ + hdmi_dbg(hdmi->dev, "hdmi enter early suspend\n"); + disable_irq(hdmi->irq); + if(hdmi->hotplug) + hdmi_sys_remove(); + return; +} + +static void hdmi_early_resume(struct early_suspend *h) +{ + hdmi_dbg(hdmi->dev, "hdmi exit early resume\n"); + enable_irq(hdmi->irq); + return; +} +#endif + + + + + +static inline void hdmi_io_remap(void) +{ + unsigned int value; + + // Remap HDMI IO Pin + rk30_mux_api_set(GPIO0A2_HDMII2CSDA_NAME, GPIO0A_HDMI_I2C_SDA); + rk30_mux_api_set(GPIO0A1_HDMII2CSCL_NAME, GPIO0A_HDMI_I2C_SCL); + rk30_mux_api_set(GPIO0A0_HDMIHOTPLUGIN_NAME, GPIO0A_HDMI_HOT_PLUG_IN); + + // Select LCDC0 as video source and enabled. + value = (HDMI_SOURCE_DEFAULT << 14) | (1 << 30); + writel(value, GRF_SOC_CON0 + RK30_GRF_BASE); + + // internal hclk = hdmi_hclk/32 + HDMIWrReg(0x800, 19); + + hdmi->lcdc = rk_get_lcdc_drv(HDMI_SOURCE_DEFAULT); +} + +static int __devinit rk30_hdmi_probe (struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct resource *mem; + + hdmi = kmalloc(sizeof(struct hdmi), GFP_KERNEL); + if(!hdmi) + { + dev_err(&pdev->dev, ">>rk30 lcdc inf kmalloc fail!"); + return -ENOMEM; + } + memset(hdmi, 0, sizeof(struct hdmi)); + hdmi->dev = &pdev->dev; + platform_set_drvdata(pdev, hdmi); + + hdmi->hclk = clk_get(NULL,"hclk_hdmi"); + if(IS_ERR(hdmi->hclk)) + { + dev_err(hdmi->dev, "Unable to get hdmi hclk\n"); + ret = -ENXIO; + goto err0; + } + clk_enable(hdmi->hclk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(hdmi->dev, "Unable to get register resource\n"); + ret = -ENXIO; + goto err0; + } + + mem = request_mem_region(res->start, (res->end - res->start) + 1, pdev->name); + if (!mem) + { + dev_err(hdmi->dev, "failed to request mem region for hdmi\n"); + ret = -ENOENT; + goto err0; + } + + + hdmi->regbase = (int)ioremap(res->start, (res->end - res->start) + 1); + if (!hdmi->regbase) { + dev_err(hdmi->dev, "cannot ioremap registers\n"); + ret = -ENXIO; + goto err1; + } + + hdmi_io_remap(); + + hdmi_sys_init(); + + hdmi->workqueue = create_singlethread_workqueue("hdmi"); + INIT_DELAYED_WORK(&(hdmi->delay_work), hdmi_work); + + #ifdef CONFIG_HAS_EARLYSUSPEND + hdmi->early_suspend.suspend = hdmi_early_suspend; + hdmi->early_suspend.resume = hdmi_early_resume; + hdmi->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN - 1; + register_early_suspend(&hdmi->early_suspend); + #endif + + /* get the IRQ */ + hdmi->irq = platform_get_irq(pdev, 0); + if(hdmi->irq <= 0) { + dev_err(hdmi->dev, "failed to get hdmi irq resource (%d).\n", hdmi->irq); + ret = -ENXIO; + goto err2; + } + hdmi_dbg(hdmi->dev, "[%s] hdmi irq is 0x%x\n", __FUNCTION__, hdmi->irq); + /* request the IRQ */ + ret = request_irq(hdmi->irq, hdmi_irq, 0, dev_name(&pdev->dev), hdmi); + if (ret) + { + dev_err(hdmi->dev, "hdmi request_irq failed (%d).\n", ret); + goto err2; + } + + hdmi_dbg(hdmi->dev, "rk30 hdmi probe sucess.\n"); + return 0; +err2: +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&hdmi->early_suspend); +#endif + iounmap((void*)hdmi->regbase); +err1: + release_mem_region(res->start,(res->end - res->start) + 1); + clk_disable(hdmi->hclk); +err0: + kfree(hdmi); + hdmi_dbg(hdmi->dev, "rk30 hdmi probe error.\n"); + return ret; +} + +static int __devexit rk30_hdmi_remove(struct platform_device *pdev) +{ + return 0; +} + +static void rk30_hdmi_shutdown(struct platform_device *pdev) +{ + +} + +static struct platform_driver rk30_hdmi_driver = { + .probe = rk30_hdmi_probe, + .remove = __devexit_p(rk30_hdmi_remove), + .driver = { + .name = "rk30-hdmi", + .owner = THIS_MODULE, + }, + .shutdown = rk30_hdmi_shutdown, +}; + +static int __init rk30_hdmi_init(void) +{ + return platform_driver_register(&rk30_hdmi_driver); +} + +static void __exit rk30_hdmi_exit(void) +{ + platform_driver_unregister(&rk30_hdmi_driver); +} + + +fs_initcall(rk30_hdmi_init); +//module_init(rk30_hdmi_init); +module_exit(rk30_hdmi_exit); \ No newline at end of file diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi.h b/drivers/video/rockchip/hdmi/rk30_hdmi.h new file mode 100755 index 000000000000..61fa20280b67 --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi.h @@ -0,0 +1,73 @@ +#ifndef __RK30_HDMI_H__ +#define __RK30_HDMI_H__ + +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include "../../display/screen/screen.h" +#include +#include "rk_hdmi.h" + +// HDMI video source +enum { + HDMI_SOURCE_LCDC0 = 0, + HDMI_SOURCE_LCDC1 +}; + +#define HDMI_SOURCE_DEFAULT HDMI_SOURCE_LCDC1 + +/* default HDMI output video mode */ +#define HDMI_VIDEO_DEFAULT_MODE HDMI_1280x720p_60Hz//HDMI_1920x1080p_60Hz +#define HDMI_AUDIO_DEFAULT_CHANNEL 2 +#define HDMI_AUDIO_DEFAULT_RATE HDMI_AUDIO_FS_44100 +#define HDMI_AUDIO_DEFAULT_WORD_LENGTH HDMI_AUDIO_WORD_LENGTH_16bit + +struct hdmi { + struct device *dev; + struct clk *hclk; //HDMI AHP clk + int regbase; + int irq; + struct rk_lcdc_device_driver *lcdc; + + struct workqueue_struct *workqueue; + struct delayed_work delay_work; + + int wait; + struct completion complete; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif + + struct hdmi_edid edid; + int enable; // Enable HDMI output or not + int vic; // HDMI output video mode code + struct hdmi_audio audio; // HDMI output audio type. + + int pwr_mode; // power mode + int hotplug; // hot plug status + int state; // hdmi state machine status + int autoconfig; // if true, auto config hdmi output mode according to EDID. + int command; // HDMI configuration command + +}; + +extern struct hdmi *hdmi; + +extern int hdmi_sys_init(void); +extern int hdmi_sys_parse_edid(struct hdmi* hdmi); +extern const char *hdmi_get_video_mode_name(unsigned char vic); +extern int hdmi_videomode_to_vic(struct fb_videomode *vmode); +extern const struct fb_videomode* hdmi_vic_to_videomode(int vic); +extern int hdmi_add_videomode(const struct fb_videomode *mode, struct list_head *head); +extern struct hdmi_video_timing * hdmi_find_mode(int vic); +extern int hdmi_find_best_mode(struct hdmi* hdmi, int vic); +extern int hdmi_ouputmode_select(struct hdmi *hdmi, int edid_ok); +extern int hdmi_switch_fb(struct hdmi *hdmi, int vic); +extern void hdmi_sys_remove(void); +#endif /* __RK30_HDMI_H__ */ \ No newline at end of file diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_edid.c b/drivers/video/rockchip/hdmi/rk30_hdmi_edid.c new file mode 100644 index 000000000000..a5f291e42703 --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_edid.c @@ -0,0 +1,410 @@ +#include "rk30_hdmi.h" +#include "rk30_hdmi_hw.h" +#include "../../edid.h" + +#define hdmi_edid_error(fmt, ...) \ + printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) + +#if 0 +#define hdmi_edid_debug(fmt, ...) \ + printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) +#else +#define hdmi_edid_debug(fmt, ...) +#endif + +typedef enum HDMI_EDID_ERRORCODE +{ + E_HDMI_EDID_SUCCESS = 0, + E_HDMI_EDID_PARAM, + E_HDMI_EDID_HEAD, + E_HDMI_EDID_CHECKSUM, + E_HDMI_EDID_VERSION, + E_HDMI_EDID_UNKOWNDATA, + E_HDMI_EDID_NOMEMORY +}HDMI_EDID_ErrorCode; + +static const unsigned int double_aspect_vic[] = {3, 7, 9, 11, 13, 15, 18, 22, 24, 26, 28, 30, 36, 38, 43, 45, 49, 51, 53, 55, 57, 59}; +static int hdmi_edid_checksum(unsigned char *buf) +{ + int i; + int checksum = 0; + + for(i = 0; i < HDMI_EDID_BLOCK_SIZE; i++) + checksum += buf[i]; + + checksum &= 0xff; + + if(checksum == 0) + return E_HDMI_EDID_SUCCESS; + else + return E_HDMI_EDID_CHECKSUM; +} + +/* + @Des Parse Detail Timing Descriptor. + @Param buf : pointer to DTD data. + @Param pvic: VIC of DTD descripted. + */ +static int hdmi_edid_parse_dtd(unsigned char *block, struct fb_videomode *mode) +{ + mode->xres = H_ACTIVE; + mode->yres = V_ACTIVE; + mode->pixclock = PIXEL_CLOCK; +// mode->pixclock /= 1000; +// mode->pixclock = KHZ2PICOS(mode->pixclock); + mode->right_margin = H_SYNC_OFFSET; + mode->left_margin = (H_ACTIVE + H_BLANKING) - + (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); + mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - + V_SYNC_WIDTH; + mode->lower_margin = V_SYNC_OFFSET; + mode->hsync_len = H_SYNC_WIDTH; + mode->vsync_len = V_SYNC_WIDTH; + if (HSYNC_POSITIVE) + mode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (VSYNC_POSITIVE) + mode->sync |= FB_SYNC_VERT_HIGH_ACT; + mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * + (V_ACTIVE + V_BLANKING)); + if (INTERLACED) { + mode->yres *= 2; + mode->upper_margin *= 2; + mode->lower_margin *= 2; + mode->vsync_len *= 2; + mode->vmode |= FB_VMODE_INTERLACED; + } + mode->flag = FB_MODE_IS_DETAILED; + + hdmi_edid_debug("<<<<<<<>>>>>>>>\n"); + hdmi_edid_debug("%d KHz Refresh %d Hz", PIXEL_CLOCK/1000, mode->refresh); + hdmi_edid_debug("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, + H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); + hdmi_edid_debug("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, + V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); + hdmi_edid_debug("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", + (VSYNC_POSITIVE) ? "+" : "-"); + return E_HDMI_EDID_SUCCESS; +} + +static int hdmi_edid_parse_base(unsigned char *buf, int *extend_num, struct hdmi_edid *pedid) +{ + int rc; + + if(buf == NULL || extend_num == NULL) + return E_HDMI_EDID_PARAM; + + #ifdef DEBUG + for(i = 0; i < HDMI_EDID_BLOCK_SIZE; i++) + { + hdmi_edid_debug("%02x ", buf[i]&0xff); + if((i+1) % 16 == 0) + hdmi_edid_debug("\n"); + } + #endif + + // Check first 8 byte to ensure it is an edid base block. + if( buf[0] != 0x00 || + buf[1] != 0xFF || + buf[2] != 0xFF || + buf[3] != 0xFF || + buf[4] != 0xFF || + buf[5] != 0xFF || + buf[6] != 0xFF || + buf[7] != 0x00) + { + hdmi_edid_error("[EDID] check header error\n"); + return E_HDMI_EDID_HEAD; + } + + *extend_num = buf[0x7e]; + #ifdef DEBUG + hdmi_edid_debug("[EDID] extend block num is %d\n", buf[0x7e]); + #endif + + // Checksum + rc = hdmi_edid_checksum(buf); + if( rc != E_HDMI_EDID_SUCCESS) + { + hdmi_edid_error("[EDID] base block checksum error\n"); + return E_HDMI_EDID_CHECKSUM; + } + + pedid->specs = kzalloc(sizeof(struct fb_monspecs), GFP_KERNEL); + if(pedid->specs == NULL) + return E_HDMI_EDID_NOMEMORY; + + fb_edid_to_monspecs(buf, pedid->specs); + + return E_HDMI_EDID_SUCCESS; +} + +// Parse CEA Short Video Descriptor +static int hdmi_edid_get_cea_svd(unsigned char *buf, struct hdmi_edid *pedid) +{ + const struct fb_videomode *mode; + int count, i, j, vic; + + count = buf[0] & 0x1F; + for(i = 0; i < count; i++) + { + hdmi_edid_debug("[EDID-CEA] %02x VID %d native %d\n", buf[1 + i], buf[1 + i] & 0x7f, buf[1 + i] >> 7); + vic = buf[1 + i] & 0x7f; + for(j = 0; j < ARRAY_SIZE(double_aspect_vic); j++) + { + if(vic == double_aspect_vic[j]) + { + vic--; + break; + } + } + if(vic) + { + mode = hdmi_vic_to_videomode(vic); + if(mode) + { + hdmi_add_videomode(mode, &pedid->modelist); + } + } + } + return 0; +} + +// Parse CEA Short Audio Descriptor +static int hdmi_edid_parse_cea_sad(unsigned char *buf, struct hdmi_edid *pedid) +{ + int i, count; + + count = buf[0] & 0x1F; + pedid->audio = kmalloc((count/3)*sizeof(struct hdmi_audio), GFP_KERNEL); + if(pedid->audio == NULL) + return E_HDMI_EDID_NOMEMORY; + pedid->audio_num = count/3; + for(i = 0; i < pedid->audio_num; i++) + { + pedid->audio[i].type = (buf[1 + i*3] >> 3) & 0x0F; + pedid->audio[i].channel = (buf[1 + i*3] & 0x07) + 1; + pedid->audio[i].rate = buf[1 + i*3 + 1]; + if(pedid->audio[i].type == HDMI_AUDIO_LPCM)//LPCM + { + pedid->audio[i].word_length = buf[1 + i*3 + 2]; + } +// printk("[EDID-CEA] type %d channel %d rate %d word length %d\n", +// pedid->audio[i].type, pedid->audio[i].channel, pedid->audio[i].rate, pedid->audio[i].word_length); + } + return E_HDMI_EDID_SUCCESS; +} +// Parse CEA 861 Serial Extension. +static int hdmi_edid_parse_extensions_cea(unsigned char *buf, struct hdmi_edid *pedid) +{ + unsigned int ddc_offset, native_dtd_num, cur_offset = 4; + unsigned int underscan_support, baseaudio_support; + unsigned int tag, IEEEOUI = 0; +// unsigned int supports_ai, dc_48bit, dc_36bit, dc_30bit, dc_y444; +// unsigned char vic; + + if(buf == NULL) + return E_HDMI_EDID_PARAM; + + // Check ces extension version + if(buf[1] != 3) + { + hdmi_edid_error("[EDID-CEA] error version.\n"); + return E_HDMI_EDID_VERSION; + } + + ddc_offset = buf[2]; + underscan_support = (buf[3] >> 7) & 0x01; + baseaudio_support = (buf[3] >> 6) & 0x01; + pedid->ycbcr444 = (buf[3] >> 5) & 0x01; + pedid->ycbcr422 = (buf[3] >> 4) & 0x01; + native_dtd_num = buf[3] & 0x0F; + hdmi_edid_debug("[EDID-CEA] ddc_offset %d underscan_support %d baseaudio_support %d yuv_support %d native_dtd_num %d\n", ddc_offset, underscan_support, baseaudio_support, yuv_support, native_dtd_num); + // Parse data block + while(cur_offset < ddc_offset) + { + tag = buf[cur_offset] >> 5; + switch(tag) + { + case 0x02: // Video Data Block + hdmi_edid_debug("[EDID-CEA] It is a Video Data Block.\n"); + hdmi_edid_get_cea_svd(buf + cur_offset, pedid); + break; + case 0x01: // Audio Data Block + hdmi_edid_debug("[EDID-CEA] It is a Audio Data Block.\n"); + hdmi_edid_parse_cea_sad(buf + cur_offset, pedid); + break; + case 0x04: // Speaker Allocation Data Block + hdmi_edid_debug("[EDID-CEA] It is a Speaker Allocatio Data Block.\n"); + break; + case 0x03: // Vendor Specific Data Block + hdmi_edid_debug("[EDID-CEA] It is a Vendor Specific Data Block.\n"); + + IEEEOUI = buf[cur_offset + 2 + 1]; + IEEEOUI <<= 8; + IEEEOUI += buf[cur_offset + 1 + 1]; + IEEEOUI <<= 8; + IEEEOUI += buf[cur_offset + 1]; + hdmi_edid_debug("[EDID-CEA] IEEEOUI is 0x%08x.\n", IEEEOUI); + if(IEEEOUI == 0x0c03) + pedid->sink_hdmi = 1; +// if(count > 5) +// { +// pedid->deepcolor = (buf[cur_offset + 5] >> 3) & 0x0F; +// supports_ai = buf[cur_offset + 5] >> 7; +// dc_48bit = (buf[cur_offset + 5] >> 6) & 0x1; +// dc_36bit = (buf[cur_offset + 5] >> 5) & 0x1; +// dc_30bit = (buf[cur_offset + 5] >> 4) & 0x1; +// dc_y444 = (buf[cur_offset + 5] >> 3) & 0x1; +// hdmi_edid_debug("[EDID-CEA] supports_ai %d dc_48bit %d dc_36bit %d dc_30bit %d dc_y444 %d \n", supports_ai, dc_48bit, dc_36bit, dc_30bit, dc_y444); +// } +// if(count > 6) +// pedid->maxtmdsclock = buf[cur_offset + 6] * 5000000; +// if(count > 7) +// { +// pedid->latency_fields_present = (buf[cur_offset + 7] & 0x80) ? 1:0; +// pedid->i_latency_fields_present = (buf[cur_offset + 7] & 0x40) ? 1:0; +// } +// if(count > 9 && pedid->latency_fields_present) +// { +// pedid->video_latency = buf[cur_offset + 8]; +// pedid->audio_latency = buf[cur_offset + 9]; +// } +// if(count > 11 && pedid->i_latency_fields_present) +// { +// pedid->interlaced_video_latency = buf[cur_offset + 10]; +// pedid->interlaced_audio_latency = buf[cur_offset + 11]; +// } + break; + case 0x05: // VESA DTC Data Block + hdmi_edid_debug("[EDID-CEA] It is a VESA DTC Data Block.\n"); + break; + case 0x07: // Use Extended Tag + hdmi_edid_debug("[EDID-CEA] It is a Use Extended Tag Data Block.\n"); + break; + default: + hdmi_edid_error("[EDID-CEA] unkowned data block tag.\n"); + break; + } + cur_offset += (buf[cur_offset] & 0x1F) + 1; + } +#if 1 +{ + // Parse DTD + struct fb_videomode *vmode = kmalloc(sizeof(struct fb_videomode), GFP_KERNEL); + if(vmode == NULL) + return E_HDMI_EDID_SUCCESS; + while(ddc_offset < HDMI_EDID_BLOCK_SIZE - 2) //buf[126] = 0 and buf[127] = checksum + { + if(!buf[ddc_offset] && !buf[ddc_offset + 1]) + break; + memset(vmode, 0, sizeof(struct fb_videomode)); + hdmi_edid_parse_dtd(buf + ddc_offset, vmode); + hdmi_add_videomode(vmode, &pedid->modelist); + ddc_offset += 18; + } + kfree(vmode); +} +#endif + return E_HDMI_EDID_SUCCESS; +} + +static int hdmi_edid_parse_extensions(unsigned char *buf, struct hdmi_edid *pedid) +{ + int rc; + + if(buf == NULL || pedid == NULL) + return E_HDMI_EDID_PARAM; + + // Checksum + rc = hdmi_edid_checksum(buf); + if( rc != E_HDMI_EDID_SUCCESS) + { + hdmi_edid_error("[EDID] extensions block checksum error\n"); + return E_HDMI_EDID_CHECKSUM; + } + + switch(buf[0]) + { + case 0xF0: + hdmi_edid_debug("[EDID-EXTEND] It is a extensions block map.\n"); + break; + case 0x02: + hdmi_edid_debug("[EDID-EXTEND] It is a CEA 861 Series Extension.\n"); + hdmi_edid_parse_extensions_cea(buf, pedid); + break; + case 0x10: + hdmi_edid_debug("[EDID-EXTEND] It is a Video Timing Block Extension.\n"); + break; + case 0x40: + hdmi_edid_debug("[EDID-EXTEND] It is a Display Information Extension.\n"); + break; + case 0x50: + hdmi_edid_debug("[EDID-EXTEND] It is a Localized String Extension.\n"); + break; + case 0x60: + hdmi_edid_debug("[EDID-EXTEND] It is a Digital Packet Video Link Extension.\n"); + break; + default: + hdmi_edid_debug("[EDID-EXTEND] Unkowned extension.\n"); + break; + } + + return E_HDMI_EDID_SUCCESS; +} + + +int hdmi_sys_parse_edid(struct hdmi* hdmi) +{ + struct hdmi_edid *pedid; + unsigned char *buff = NULL; + int rc = HDMI_ERROR_SUCESS, extendblock = 0, i; + + if(hdmi == NULL) + return HDMI_ERROR_FALSE; + + pedid = &(hdmi->edid); + memset(pedid, 0, sizeof(struct hdmi_edid)); + INIT_LIST_HEAD(&pedid->modelist); + + buff = kmalloc(HDMI_EDID_BLOCK_SIZE, GFP_KERNEL); + if(buff == NULL) + { + hdmi_dbg(hdmi->dev, "[%s] can not allocate memory for edid buff.\n", __FUNCTION__); + return -1; + } + // Read base block edid. + memset(buff, 0 , HDMI_EDID_BLOCK_SIZE); + rc = rk30_hdmi_read_edid(0, buff); + if(rc) + { + dev_err(hdmi->dev, "[HDMI] read edid base block error\n"); + goto out; + } + rc = hdmi_edid_parse_base(buff, &extendblock, pedid); + if(rc) + { + dev_err(hdmi->dev, "[HDMI] parse edid base block error\n"); + goto out; + } + for(i = 1; i < extendblock + 1; i++) + { + memset(buff, 0 , HDMI_EDID_BLOCK_SIZE); + rc = rk30_hdmi_read_edid(i, buff); + if(rc) + { + printk("[HDMI] read edid block %d error\n", i); + goto out; + } + rc = hdmi_edid_parse_extensions(buff, pedid); + if(rc) + { + dev_err(hdmi->dev, "[HDMI] parse edid block %d error\n", i); + continue; + } + } +out: + if(buff) + kfree(buff); + rc = hdmi_ouputmode_select(hdmi, rc); + return rc; +} \ No newline at end of file diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c new file mode 100755 index 000000000000..9686744039b6 --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.c @@ -0,0 +1,440 @@ +#include +#include +#include +#include "rk30_hdmi.h" +#include "rk30_hdmi_hw.h" + +static char interrupt1 = 0, interrupt2 = 0; + +static inline void delay100us(void) +{ + msleep(1); +} + +static void rk30_hdmi_set_pwr_mode(int mode) +{ + if(hdmi->pwr_mode == mode) + return; + hdmi_dbg(hdmi->dev, "[%s] mode %d\n", __FUNCTION__, mode); + switch(mode) + { + case PWR_SAVE_MODE_A: + HDMIWrReg(SYS_CTRL, 0x10); + break; + case PWR_SAVE_MODE_B: + HDMIWrReg(SYS_CTRL, 0x20); + break; + case PWR_SAVE_MODE_D: + // reset PLL A&B + HDMIWrReg(SYS_CTRL, 0x4C); + delay100us(); + // release PLL A reset + HDMIWrReg(SYS_CTRL, 0x48); + delay100us(); + // release PLL B reset + HDMIWrReg(SYS_CTRL, 0x40); + break; + case PWR_SAVE_MODE_E: + HDMIWrReg(SYS_CTRL, 0x80); + break; + } + hdmi->pwr_mode = mode; + msleep(10); +} + +int rk30_hdmi_detect_hotplug(void) +{ + int value = HDMIRdReg(HPD_MENS_STA); + + hdmi_dbg(hdmi->dev, "[%s] value %02x\n", __FUNCTION__, value); + if( (value & (m_HOTPLUG_STATUS | m_MSEN_STATUS)) == (m_HOTPLUG_STATUS | m_MSEN_STATUS) ) + return HDMI_HPD_INSERT; + else + return HDMI_HPD_REMOVED; +} + +#define HDMI_EDID_DDC_CLK 100000 +int rk30_hdmi_read_edid(int block, unsigned char *buff) +{ + int value, ret = -ENXIO, ddc_bus_freq = 0; + char interrupt = 0; + + hdmi_dbg(hdmi->dev, "[%s] block %d\n", __FUNCTION__, block); + + //Before Phy parameter was set, DDC_CLK is equal to PLLA freq which is 30MHz. + //Set DDC I2C CLK which devided from DDC_CLK to 100KHz. + ddc_bus_freq = (30000000/HDMI_EDID_DDC_CLK)/4; + HDMIWrReg(DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF); + HDMIWrReg(DDC_BUS_FREQ_L, (ddc_bus_freq >> 8) & 0xFF); + + // Enable edid interrupt +// HDMIMskReg(value, INTR_MASK1, (m_INT_EDID_ERR | m_INT_EDID_READY), (m_INT_EDID_ERR | m_INT_EDID_READY)); + HDMIWrReg(INTR_MASK1, m_INT_EDID_ERR | m_INT_EDID_READY | m_INT_HOTPLUG | m_INT_MSENS); + // Config EDID block and segment addr + HDMIWrReg(EDID_WORD_ADDR, (block%2) * 0x80); + HDMIWrReg(EDID_SEGMENT_POINTER, block/2); + + value = 200; + while(value--) + { + interrupt = interrupt1; +// hdmi_dbg(hdmi->dev, "[%s] interrupt %02x\n", __FUNCTION__, interrupt); + if(interrupt & (m_INT_EDID_ERR | m_INT_EDID_READY)) + { + interrupt1 &= ~(m_INT_EDID_ERR | m_INT_EDID_READY); + break; + } + msleep(10); + } + hdmi_dbg(hdmi->dev, "[%s] edid read value %d\n", __FUNCTION__, value); + // Disable edid interrupt + HDMIMskReg(value, INTR_MASK1, (m_INT_EDID_ERR|m_INT_EDID_READY), 0); + if(interrupt & m_INT_EDID_READY) + { + for(value = 0; value < HDMI_EDID_BLOCK_SIZE; value++) + buff[value] = HDMIRdReg(DDC_READ_FIFO_ADDR); + ret = 0; + hdmi_dbg(hdmi->dev, "[%s] edid read sucess\n", __FUNCTION__); +// for(value = 0; value < 128; value++) { +// printk("%02x ,", buff[value]); +// if( (value + 1) % 8 == 0) +// printk("\n"); +// } + } + if(interrupt & m_INT_EDID_ERR) + { + hdmi_dbg(hdmi->dev, "[%s] edid read error\n", __FUNCTION__); + + } + return ret; +} + +static inline void rk30_hdmi_config_phy_reg(int reg, int value) +{ + HDMIWrReg(reg, value); + HDMIWrReg(SYS_CTRL, 0x2C); + delay100us(); + HDMIWrReg(SYS_CTRL, 0x20); + msleep(1); +} + +static void rk30_hdmi_config_phy(unsigned char vic) +{ + hdmi_dbg(hdmi->dev, "[%s] line %d\n", __FUNCTION__, __LINE__); + HDMIWrReg(DEEP_COLOR_MODE, 0x22); // tmds frequency same as input dlck + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_B); + switch(vic) + { + case HDMI_1920x1080p_60Hz: + case HDMI_1920x1080p_50Hz: + rk30_hdmi_config_phy_reg(0x158, 0x0E); + rk30_hdmi_config_phy_reg(0x15c, 0x00); + rk30_hdmi_config_phy_reg(0x160, 0x60); + rk30_hdmi_config_phy_reg(0x164, 0x00); + rk30_hdmi_config_phy_reg(0x168, 0xDA); + rk30_hdmi_config_phy_reg(0x16c, 0xA2); + rk30_hdmi_config_phy_reg(0x170, 0x0e); + rk30_hdmi_config_phy_reg(0x174, 0x22); + rk30_hdmi_config_phy_reg(0x178, 0x00); + break; + case HDMI_1280x720p_60Hz: + case HDMI_1280x720p_50Hz: + rk30_hdmi_config_phy_reg(0x158, 0x06); + rk30_hdmi_config_phy_reg(0x15c, 0x00); + rk30_hdmi_config_phy_reg(0x160, 0x60); + rk30_hdmi_config_phy_reg(0x164, 0x00); + rk30_hdmi_config_phy_reg(0x168, 0xCA); + rk30_hdmi_config_phy_reg(0x16c, 0xA3); + rk30_hdmi_config_phy_reg(0x170, 0x0e); + rk30_hdmi_config_phy_reg(0x174, 0x20); + rk30_hdmi_config_phy_reg(0x178, 0x00); + break; + case HDMI_720x480p_60Hz_4_3: + case HDMI_720x480p_60Hz_16_9: + rk30_hdmi_config_phy_reg(0x158, 0x02); + rk30_hdmi_config_phy_reg(0x15c, 0x00); + rk30_hdmi_config_phy_reg(0x160, 0x60); + rk30_hdmi_config_phy_reg(0x164, 0x00); + rk30_hdmi_config_phy_reg(0x168, 0xC2); + rk30_hdmi_config_phy_reg(0x16c, 0xA2); + rk30_hdmi_config_phy_reg(0x170, 0x0e); + rk30_hdmi_config_phy_reg(0x174, 0x20); + rk30_hdmi_config_phy_reg(0x178, 0x00); + break; + default: + hdmi_dbg(hdmi->dev, "not support such vic %d\n", vic); + break; + } +} + +static void rk30_hdmi_config_avi(unsigned char vic, unsigned char output_color) +{ + int i; + char info[SIZE_AVI_INFOFRAME]; + + HDMIWrReg(CONTROL_PACKET_BUF_INDEX, INFOFRAME_AVI); + HDMIWrReg(CONTROL_PACKET_HB0, 0x82); + HDMIWrReg(CONTROL_PACKET_HB1, 0x2); + HDMIWrReg(CONTROL_PACKET_HB2, 0x0D); + memset(info, 0, SIZE_AVI_INFOFRAME); + + info[1] = (AVI_COLOR_MODE_RGB << 5); + info[2] = (AVI_COLORIMETRY_NO_DATA << 6) | (AVI_CODED_FRAME_ASPECT_NO_DATA << 4) | ACTIVE_ASPECT_RATE_SAME_AS_CODED_FRAME; + info[3] = 0; + info[4] = vic; + info[5] = 0; + + // Calculate AVI InfoFrame ChecKsum + info[0] = 0x82 + 0x02 +0x0D; + for (i = 1; i < SIZE_AVI_INFOFRAME; i++) + { + info[0] += info[i]; + } + info[0] = 0x100 - info[0]; + + for(i = 0; i < SIZE_AVI_INFOFRAME; i++) + HDMIWrReg(CONTROL_PACKET_PB_ADDR + i*4, info[i]); +} + +int rk30_hdmi_config_video(int vic, int output_color, int output_mode) +{ + int value; + struct fb_videomode *mode; + + hdmi_dbg(hdmi->dev, "[%s]\n", __FUNCTION__); + + if(hdmi->pwr_mode == PWR_SAVE_MODE_E) + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_D); + if(hdmi->pwr_mode == PWR_SAVE_MODE_D || hdmi->pwr_mode == PWR_SAVE_MODE_A) + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_B); + + // Input video mode is RGB24bit, Data enable signal from external + HDMIMskReg(value, AV_CTRL1, m_INPUT_VIDEO_MODE | m_DE_SIGNAL_SELECT, \ + v_INPUT_VIDEO_MODE(VIDEO_INPUT_RGB_YCBCR_444) | EXTERNAL_DE) + HDMIMskReg(value, VIDEO_CTRL1, m_VIDEO_OUTPUT_MODE | m_VIDEO_INPUT_DEPTH | m_VIDEO_INPUT_COLOR_MODE, \ + v_VIDEO_OUTPUT_MODE(output_color) | v_VIDEO_INPUT_DEPTH(VIDEO_INPUT_DEPTH_8BIT) | VIDEO_INPUT_COLOR_RGB) + + // Set HDMI Mode + HDMIWrReg(HDCP_CTRL, v_HDMI_DVI(output_mode)); + + // Set ext video + mode = (struct fb_videomode *)hdmi_vic_to_videomode(vic); + if(mode == NULL) + { + hdmi_dbg(hdmi->dev, "[%s] not found vic %d\n", __FUNCTION__, vic); + return -ENOENT; + } + value = v_EXT_VIDEO_ENABLE(1) | v_INTERLACE(mode->vmode); + if(mode->sync & FB_SYNC_HOR_HIGH_ACT) + value |= v_HSYNC_POLARITY(1); + if(mode->sync | FB_SYNC_VERT_HIGH_ACT) + value |= v_VSYNC_POLARITY(1); + HDMIWrReg(EXT_VIDEO_PARA, value); + value = mode->left_margin + mode->xres + mode->right_margin + mode->hsync_len; + HDMIWrReg(EXT_VIDEO_PARA_HTOTAL_L, value & 0xFF); + HDMIWrReg(EXT_VIDEO_PARA_HTOTAL_H, (value >> 8) & 0xFF); + + value = mode->left_margin + mode->right_margin + mode->hsync_len; + HDMIWrReg(EXT_VIDEO_PARA_HBLANK_L, value & 0xFF); + HDMIWrReg(EXT_VIDEO_PARA_HBLANK_H, (value >> 8) & 0xFF); + + value = mode->left_margin + mode->hsync_len; + HDMIWrReg(EXT_VIDEO_PARA_HDELAY_L, value & 0xFF); + HDMIWrReg(EXT_VIDEO_PARA_HDELAY_H, (value >> 8) & 0xFF); + + value = mode->hsync_len; + HDMIWrReg(EXT_VIDEO_PARA_HSYNCWIDTH_L, value & 0xFF); + HDMIWrReg(EXT_VIDEO_PARA_HSYNCWIDTH_H, (value >> 8) & 0xFF); + + value = mode->upper_margin + mode->yres + mode->lower_margin + mode->vsync_len; + HDMIWrReg(EXT_VIDEO_PARA_VTOTAL_L, value & 0xFF); + HDMIWrReg(EXT_VIDEO_PARA_VTOTAL_H, (value >> 8) & 0xFF); + + value = mode->upper_margin + mode->vsync_len + mode->lower_margin; + HDMIWrReg(EXT_VIDEO_PARA_VBLANK_L, value & 0xFF); + + value = mode->upper_margin + mode->vsync_len; + HDMIWrReg(EXT_VIDEO_PARA_VDELAY, value & 0xFF); + + value = mode->vsync_len; + HDMIWrReg(EXT_VIDEO_PARA_VSYNCWIDTH, value & 0xFF); + + if(output_mode == OUTPUT_HDMI) { + rk30_hdmi_config_avi(vic, output_mode); + hdmi_dbg(hdmi->dev, "[%s] sucess output HDMI.\n", __FUNCTION__); + } + else { + hdmi_dbg(hdmi->dev, "[%s] sucess output DVI.\n", __FUNCTION__); + } + rk30_hdmi_config_phy(vic); + rk30_hdmi_control_output(0); + return 0; +} + +static void rk30_hdmi_config_aai(void) +{ + int i; + char info[SIZE_AUDIO_INFOFRAME]; + + memset(info, 0, SIZE_AUDIO_INFOFRAME); + + info[0] = 0x84; + info[1] = 0x01; + info[2] = 0x0A; + + info[3] = info[0] + info[1] + info[2]; + for (i = 4; i < SIZE_AUDIO_INFOFRAME; i++) + info[3] += info[i]; + + info[3] = 0x100 - info[3]; + + HDMIWrReg(CONTROL_PACKET_BUF_INDEX, INFOFRAME_AAI); + for(i = 0; i < SIZE_AUDIO_INFOFRAME; i++) + HDMIWrReg(CONTROL_PACKET_HB0 + i*4, info[i]); +} + +int rk30_hdmi_config_audio(struct hdmi_audio *audio) +{ + int value, rate, N; + char word_length; + + switch(audio->rate) + { + case HDMI_AUDIO_FS_32000: + rate = AUDIO_32K; + N = N_32K; + break; + case HDMI_AUDIO_FS_44100: + rate = AUDIO_441K; + N = N_441K; + break; + case HDMI_AUDIO_FS_48000: + rate = AUDIO_48K; + N = N_48K; + break; + case HDMI_AUDIO_FS_88200: + rate = AUDIO_882K; + N = N_882K; + break; + case HDMI_AUDIO_FS_96000: + rate = AUDIO_96K; + N = N_96K; + break; + case HDMI_AUDIO_FS_176400: + rate = AUDIO_1764K; + N = N_1764K; + break; + case HDMI_AUDIO_FS_192000: + rate = AUDIO_192K; + N = N_192K; + break; + default: + dev_err(hdmi->dev, "[%s] not support such sample rate %d\n", __FUNCTION__, audio->rate); + return -ENOENT; + } + switch(audio->word_length) + { + case HDMI_AUDIO_WORD_LENGTH_16bit: + word_length = 0x02; + break; + case HDMI_AUDIO_WORD_LENGTH_20bit: + word_length = 0x0a; + break; + case HDMI_AUDIO_WORD_LENGTH_24bit: + word_length = 0x0b; + break; + default: + dev_err(hdmi->dev, "[%s] not support such word length %d\n", __FUNCTION__, audio->word_length); + return -ENOENT; + } + //set_audio_if I2S + HDMIWrReg(AUDIO_CTRL1, 0x00); //internal CTS, disable down sample, i2s input, disable MCLK + HDMIWrReg(AUDIO_CTRL2, 0x40); + HDMIWrReg(I2S_AUDIO_CTRL, v_I2S_MODE(I2S_MODE_STANDARD) | v_I2S_CHANNEL( (audio->channel + audio->channel%2)/2)); + HDMIWrReg(I2S_INPUT_SWAP, 0x00); //no swap + HDMIMskReg(value, AV_CTRL1, m_AUDIO_SAMPLE_RATE, v_AUDIO_SAMPLE_RATE(rate)) + HDMIWrReg(SRC_NUM_AUDIO_LEN, word_length); + + //Set N value 6144, fs=48kHz + HDMIWrReg(N_1, N & 0xFF); + HDMIWrReg(N_2, (N >> 8) & 0xFF); + HDMIWrReg(LR_SWAP_N3, (N >> 16) & 0x0F); + + rk30_hdmi_config_aai(); + return 0; +} + +static void rk30_hdmi_audio_reset(void) +{ + int value; + + HDMIMskReg(value, VIDEO_SETTING2, m_AUDIO_RESET, AUDIO_CAPTURE_RESET) + msleep(1); + HDMIMskReg(value, VIDEO_SETTING2, m_AUDIO_RESET, 0) +} + +void rk30_hdmi_control_output(int enable) +{ + hdmi_dbg(hdmi->dev, "[%s] %d\n", __FUNCTION__, enable); + if(enable == 0) { + HDMIWrReg(VIDEO_SETTING2, 0x03); + } + else { + // Switch to power save mode_d + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_D); + // Switch to power save mode_e + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_E); + HDMIWrReg(VIDEO_SETTING2, 0x00); + rk30_hdmi_audio_reset(); + } +} + +int rk30_hdmi_removed(void) +{ + if(hdmi->pwr_mode == PWR_SAVE_MODE_E) + { + HDMIWrReg(VIDEO_SETTING2, 0x00); + rk30_hdmi_audio_reset(); + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_D); + } + if(hdmi->pwr_mode == PWR_SAVE_MODE_D) + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_B); + if(hdmi->pwr_mode == PWR_SAVE_MODE_B) + rk30_hdmi_set_pwr_mode(PWR_SAVE_MODE_A); + + return HDMI_ERROR_SUCESS; +} + + + + +irqreturn_t hdmi_irq(int irq, void *priv) +{ + unsigned int count = 0x10000; + if(hdmi->pwr_mode == PWR_SAVE_MODE_A) + { + HDMIWrReg(SYS_CTRL, 0x20); + hdmi->pwr_mode = PWR_SAVE_MODE_B; + while(count--); + } + else + { + interrupt1 = HDMIRdReg(INTR_STATUS1); + interrupt2 = HDMIRdReg(INTR_STATUS2); + HDMIWrReg(INTR_STATUS1, interrupt1); + HDMIWrReg(INTR_STATUS2, interrupt2); +// hdmi_dbg(hdmi->dev, "[%s] interrupt1 %02x\n", __FUNCTION__, interrupt1); + if( interrupt1 & (m_INT_HOTPLUG | m_INT_MSENS) ) + { + if(hdmi->state == HDMI_SLEEP) + hdmi->state = WAIT_HOTPLUG; + interrupt1 &= ~(m_INT_HOTPLUG | m_INT_MSENS); + queue_delayed_work(hdmi->workqueue, &hdmi->delay_work, 100); + } +// else if(hdmi->state == HDMI_SLEEP) +// HDMIWrReg(SYS_CTRL, 0x10); + + } + return IRQ_HANDLED; +} + diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_hw.h b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.h new file mode 100755 index 000000000000..c326a68cae09 --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_hw.h @@ -0,0 +1,262 @@ +#ifndef __RK30_HDMI_HW_H__ +#define __RK30_HDMI_HW_H__ + +/* HDMI_SYS_CONTROL */ +#define SYS_CTRL 0x0 + +enum { + PWR_SAVE_MODE_A = 1, + PWR_SAVE_MODE_B = 2, + PWR_SAVE_MODE_D = 4, + PWR_SAVE_MODE_E = 8 +}; +#define m_PWR_SAVE_MODE 0xF0 +#define v_PWR_SAVE_MODE(n) (n << 4) +#define PLL_B_RESET (1 << 3) + +#define N_32K 0x1000 +#define N_441K 0x1880 +#define N_882K 0x3100 +#define N_1764K 0x6200 +#define N_48K 0x1800 +#define N_96K 0x3000 +#define N_192K 0x6000 + +#define LR_SWAP_N3 0x04 +#define N_2 0x08 +#define N_1 0x0c + +#define AUDIO_CTRL1 0x28 +#define AUDIO_CTRL2 0x2c +#define I2S_AUDIO_CTRL 0x30 +enum { + I2S_MODE_STANDARD = 0, + I2S_MODE_RIGHT_JUSTIFIED, + I2S_MODE_LEFT_JUSTIFIED +}; +#define v_I2S_MODE(n) n +enum { + I2S_CHANNEL_1_2 = 1, + I2S_CHANNEL_3_4 = 3, + I2S_CHANNEL_5_6 = 7, + I2S_CHANNEL_7_8 = 0xf +}; +#define v_I2S_CHANNEL(n) ( n << 2 ) + +#define I2S_INPUT_SWAP 0x40 + +#define SRC_NUM_AUDIO_LEN 0x50 + +/* HDMI_AV_CTRL1*/ +#define AV_CTRL1 0x54 +enum { + AUDIO_32K = 0x3, + AUDIO_441K = 0x0, + AUDIO_48K = 0x2, + AUDIO_882K = 0x8, + AUDIO_96K = 0xa, + AUDIO_1764K = 0xc, + AUDIO_192K = 0xe, +}; +#define m_AUDIO_SAMPLE_RATE 0xF0 +#define v_AUDIO_SAMPLE_RATE(n) (n << 4) +enum { + VIDEO_INPUT_RGB_YCBCR_444 = 0, + VIDEO_INPUT_YCBCR422, + VIDEO_INPUT_YCBCR422_EMBEDDED_SYNC, + VIDEO_INPUT_2X_CLOCK, + VIDEO_INPUT_2X_CLOCK_EMBEDDED_SYNC, + VIDEO_INPUT_RGB444_DDR, + VIDEO_INPUT_YCBCR422_DDR +}; +#define m_INPUT_VIDEO_MODE (7 << 1) +#define v_INPUT_VIDEO_MODE(n) (n << 1) +enum { + INTERNAL_DE = 0, + EXTERNAL_DE +}; +#define m_DE_SIGNAL_SELECT (1 << 0) + +/* HDMI_AV_CTRL2 */ +#define VIDEO_CTRL1 0x58 +enum { + VIDEO_OUTPUT_RGB444 = 0, + VIDEO_OUTPUT_YCBCR444, + VIDEO_OUTPUT_YCBCR422 +}; +#define m_VIDEO_OUTPUT_MODE (0x3 << 6) +#define v_VIDEO_OUTPUT_MODE(n) (n << 6) +enum { + VIDEO_INPUT_DEPTH_12BIT = 0, + VIDEO_INPUT_DEPTH_10BIT = 0x1, + VIDEO_INPUT_DEPTH_8BIT = 0x3 +}; +#define m_VIDEO_INPUT_DEPTH (3 << 4) +#define v_VIDEO_INPUT_DEPTH(n) (n << 4) +enum { + VIDEO_EMBEDDED_SYNC_LOCATION_0 = 0, + VIDEO_EMBEDDED_SYNC_LOCATION_1, + VIDEO_EMBEDDED_SYNC_LOCATION_2 +}; +#define m_VIDEO_EMBEDDED_SYNC_LOCATION (3 << 2) +#define VIDEO_EMBEDDED_SYNC_LOCATION(n) (n << 2) +enum { + VIDEO_INPUT_COLOR_RGB = 0, + VIDEO_INPUT_COLOR_YCBCR +}; +#define m_VIDEO_INPUT_COLOR_MODE (1 << 0) + +/* DEEP_COLOR_MODE */ +#define DEEP_COLOR_MODE 0x5c +enum{ + TMDS_CLOCK_MODE_8BIT = 0, + TMDS_CLOKK_MODE_10BIT, + TMDS_CLOKK_MODE_12BIT +}; +#define TMDS_CLOCK_MODE_MASK 0x3 << 6 +#define TMDS_CLOCK_MODE(n) (n) << 6 + +/* VIDEO_SETTING2 */ +#define VIDEO_SETTING2 0x114 +#define m_UNMUTE (1 << 7) +#define m_MUTE (1 << 6) +#define m_AUDIO_RESET (1 << 2) +#define m_NOT_SEND_AUDIO (1 << 1) +#define m_NOT_SEND_VIDEO (1 << 0) +#define AV_UNMUTE (1 << 7) // Unmute video and audio, send normal video and audio data +#define AV_MUTE (1 << 6) // Mute video and audio, send black video data and silent audio data +#define AUDIO_CAPTURE_RESET (1 << 2) // Reset audio process logic, only available in pwr_e mode. +#define NOT_SEND_AUDIO (1 << 1) // Send silent audio data +#define NOT_SEND_VIDEO (1 << 0) // Send black video data + +/* CONTROL_PACKET_BUF_INDEX */ +#define CONTROL_PACKET_BUF_INDEX 0x17c +enum { + INFOFRAME_AVI = 0x06, + INFOFRAME_AAI = 0x08 +}; +#define CONTROL_PACKET_HB0 0x180 +#define CONTROL_PACKET_HB1 0x184 +#define CONTROL_PACKET_HB2 0x188 +#define CONTROL_PACKET_PB_ADDR 0x18c +#define SIZE_AVI_INFOFRAME 0xe // 14 bytes +#define SIZE_AUDIO_INFOFRAME 0x0F // 15 bytes +enum { + AVI_COLOR_MODE_RGB = 0, + AVI_COLOR_MODE_YCBCR422, + AVI_COLOR_MODE_YCBCR444 +}; +enum { + AVI_COLORIMETRY_NO_DATA = 0, + AVI_COLORIMETRY_SMPTE_170M, + AVI_COLORIMETRY_ITU709, + AVI_COLORIMETRY_EXTENDED +}; +enum { + AVI_CODED_FRAME_ASPECT_NO_DATA, + AVI_CODED_FRAME_ASPECT_4_3, + AVI_CODED_FRAME_ASPECT_16_9 +}; +enum { + ACTIVE_ASPECT_RATE_SAME_AS_CODED_FRAME = 0x08, + ACTIVE_ASPECT_RATE_4_3, + ACTIVE_ASPECT_RATE_16_9, + ACTIVE_ASPECT_RATE_14_9 +}; + + +/* HDCP_CTRL */ +#define HDCP_CTRL 0x2bc + +enum { + OUTPUT_DVI = 0, + OUTPUT_HDMI +}; +#define m_HDMI_DVI (1 << 1) +#define v_HDMI_DVI(n) (n << 1) + +#define EXT_VIDEO_PARA 0xC0 +#define m_VSYNC_OFFSET (0xF << 4) +#define m_VSYNC_POLARITY (1 << 3) +#define m_HSYNC_POLARITY (1 << 2) +#define m_INTERLACE (1 << 1) +#define m_EXT_VIDEO_ENABLE (1 << 0) + +#define v_VSYNC_OFFSET(n) (n << 4) +#define v_VSYNC_POLARITY(n) (n << 3) +#define v_HSYNC_POLARITY(n) (n << 2) +#define v_INTERLACE(n) (n << 1) +#define v_EXT_VIDEO_ENABLE(n) (n << 0) + +#define EXT_VIDEO_PARA_HTOTAL_L 0xC4 +#define EXT_VIDEO_PARA_HTOTAL_H 0xC8 +#define EXT_VIDEO_PARA_HBLANK_L 0xCC +#define EXT_VIDEO_PARA_HBLANK_H 0xD0 +#define EXT_VIDEO_PARA_HDELAY_L 0xD4 +#define EXT_VIDEO_PARA_HDELAY_H 0xD8 +#define EXT_VIDEO_PARA_HSYNCWIDTH_L 0xDC +#define EXT_VIDEO_PARA_HSYNCWIDTH_H 0xE0 + +#define EXT_VIDEO_PARA_VTOTAL_L 0xE4 +#define EXT_VIDEO_PARA_VTOTAL_H 0xE8 +#define EXT_VIDEO_PARA_VBLANK_L 0xF4 +#define EXT_VIDEO_PARA_VDELAY 0xF8 +#define EXT_VIDEO_PARA_VSYNCWIDTH 0xFC + +#define INTR_MASK1 0x248 +#define INTR_MASK2 0x24c +#define INTR_MASK3 0x258 +#define INTR_MASK4 0x25c +#define INTR_STATUS1 0x250 +#define INTR_STATUS2 0x254 +#define INTR_STATUS3 0x260 +#define INTR_STATUS4 0x264 + +#define m_INT_HOTPLUG (1 << 7) +#define m_INT_MSENS (1 << 6) +#define m_INT_VSYNC (1 << 5) +#define m_INT_AUDIO_FIFO_FULL (1 << 4) +#define m_INT_EDID_READY (1 << 2) +#define m_INT_EDID_ERR (1 << 1) + +#define DDC_READ_FIFO_ADDR 0x200 +#define DDC_BUS_FREQ_L 0x204 +#define DDC_BUS_FREQ_H 0x208 +#define DDC_BUS_CTRL 0x2dc +#define DDC_I2C_LEN 0x278 +#define DDC_I2C_OFFSET 0x280 +#define DDC_I2C_CTRL 0x284 +#define DDC_I2C_READ_BUF0 0x288 +#define DDC_I2C_READ_BUF1 0x28c +#define DDC_I2C_READ_BUF2 0x290 +#define DDC_I2C_READ_BUF3 0x294 +#define DDC_I2C_WRITE_BUF0 0x298 +#define DDC_I2C_WRITE_BUF1 0x29c +#define DDC_I2C_WRITE_BUF2 0x2a0 +#define DDC_I2C_WRITE_BUF3 0x2a4 +#define DDC_I2C_WRITE_BUF4 0x2ac +#define DDC_I2C_WRITE_BUF5 0x2b0 +#define DDC_I2C_WRITE_BUF6 0x2b4 + +#define EDID_SEGMENT_POINTER 0x310 +#define EDID_WORD_ADDR 0x314 +#define EDID_FIFO_ADDR 0x318 + +#define HPD_MENS_STA 0x37c +#define m_HOTPLUG_STATUS (1 << 7) +#define m_MSEN_STATUS (1 << 6) + + +#define HDMIRdReg(addr) __raw_readl(hdmi->regbase + addr) +#define HDMIWrReg(addr, val) __raw_writel(val, hdmi->regbase + addr); +#define HDMIMskReg(temp, addr, msk, val) \ + temp = __raw_readl(hdmi->regbase + addr) & (0xFF - (msk)) ; \ + __raw_writel(temp | ( (val) & (msk) ), hdmi->regbase + addr); + +extern int rk30_hdmi_detect_hotplug(void); +extern int rk30_hdmi_read_edid(int block, unsigned char *buff); +extern int rk30_hdmi_removed(void); +extern int rk30_hdmi_config_video(int vic, int output_color, int output_mode); +extern int rk30_hdmi_config_audio(struct hdmi_audio *audio); +extern void rk30_hdmi_control_output(int enable); +#endif \ No newline at end of file diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_lcdc.c b/drivers/video/rockchip/hdmi/rk30_hdmi_lcdc.c new file mode 100755 index 000000000000..b5f889989fbe --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_lcdc.c @@ -0,0 +1,505 @@ +#include +#include "rk30_hdmi.h" +#include "rk30_hdmi_hw.h" +#include + +#define OUT_TYPE SCREEN_HDMI +#define OUT_FACE OUT_P888 +#define DCLK_POL 1 +#define SWAP_RB 0 +#define LCD_ACLK 800000000 + +const struct fb_videomode hdmi_mode [] = { + //name refresh xres yres pixclock h_bp h_fp v_bp v_fp h_pw v_pw polariry PorI flag(used for vic) +//{ "640x480p@60Hz", 60, 640, 480, 25175000, 48, 16, 33, 10, 96, 2, 0, 0, 1 }, +//{ "720x480i@60Hz", 60, 720, 480, 27000000, 114, 38, 15, 4, 124, 3, 0, 1, 6 }, +//{ "720x576i@50Hz", 50, 720, 576, 27000000, 138, 24, 19, 2, 126, 3, 0, 1, 21 }, +{ "720x480p@60Hz", 60, 720, 480, 27000000, 60, 16, 30, 9, 62, 6, 0, 0, 2 }, +{ "720x576p@50Hz", 50, 720, 576, 27000000, 68, 12, 39, 5, 64, 5, 0, 0, 17 }, +//{ "1280x720p@24Hz", 24, 1280, 720, 59400000, 220, 1760, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 60 }, +//{ "1280x720p@25Hz", 25, 1280, 720, 74250000, 220, 2420, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 61 }, +//{ "1280x720p@30Hz", 30, 1280, 720, 74250000, 220, 1760, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 62 }, +{ "1280x720p@50Hz", 50, 1280, 720, 74250000, 220, 440, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 19 }, +{ "1280x720p@60Hz", 60, 1280, 720, 74250000, 220, 110, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 4 }, +//{ "1920x1080p@24Hz", 24, 1920, 1080, 74250000, 148, 638, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 32 }, +//{ "1920x1080p@25Hz", 25, 1920, 1080, 74250000, 148, 528, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 33 }, +//{ "1920x1080p@30Hz", 30, 1920, 1080, 74250000, 148, 88, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 34 }, +//{ "1920x1080i@50Hz_2",50, 1920, 1080, 72000000, 184, 32, 57, 23, 168, 5, FB_SYNC_HOR_HIGH_ACT, 1, 39 }, +//{ "1920x1080i@50Hz", 50, 1920, 1080, 74250000, 148, 528, 15, 2, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 1, 20 }, +//{ "1920x1080i@60Hz", 60, 1920, 1080, 74250000, 148, 88, 15, 2, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 1, 5 }, +{ "1920x1080p@50Hz", 50, 1920, 1080, 148500000, 148, 528, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 31 }, +{ "1920x1080p@60Hz", 60, 1920, 1080, 148500000, 148, 88, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, 0, 16 }, +/* +{ "1440x288p@50Hz", 50, 720, 480, 27000000, 138, 24, 19, 2, 126, 3, 0, 0, 23 }, +{ "2880x576i@50Hz", 50, 1440, 240, 54000000, 276, 48, 19, 2, 252, 3, 0, 1, 25 }, +{ "2880x288p@50Hz", 50, 2880, 480, 54000000, 276, 48, 19, 3, 252, 3, 0, 0, 27 }, +{ "1440x576p@50Hz", 50, 2880, 480, 54000000, 136, 24, 39, 5, 128, 5, 0, 0, 29 }, +{ "2880x576p@50Hz", 50, 1920, 1080, 108000000, 272, 48, 39, 5, 256, 5, 0, 0, 37 }, +{ "1440x240p@60Hz", 60, 1440, 240, 27000000, 114, 38, 15, 4, 124, 3, 0, 0, 8 }, +{ "2880x480i@60Hz", 60, 2880, 480, 54000000, 228, 76, 15, 4, 248, 3, 0, 1, 10 }, +{ "2880x480p@60Hz", 60, 2880, 480, 54000000, 228, 76, 15, 4, 248, 3, 0, 0, 12 }, +{ "1440x480p@60Hz", 60, 1440, 480, 54000000, 120, 32, 30, 9, 124, 6, 0, 0, 14 }, +{ "2880x480p@60Hz", 60, 2880, 480, 54000000, 240, 64, 30, 9, 248, 6, 0, 0, 35 }, + +{ "1920x1080i@100Hz", 100, 1920, 1080, 148500000, 148, 528, 15, 2, 44, 5, 1, 1, 40 }, +{ "1280x720p@100Hz", 100, 1280, 720, 148500000, 220, 440, 20, 5, 40, 5, 1, 0, 41 }, +{ "720x576p@100Hz", 100, 720, 576, 54000000, 68, 12, 39, 5, 64, 5, 0, 0, 42 }, +{ "1440x576i@100Hz", 100, 1440, 576, 54000000, 138, 24, 19, 2, 12, 3, 0, 1, 44 }, +{ "1920x1080p@100Hz", 100, 1920, 1080, 297000000, 148, 528, 36, 4, 44, 5, 1, 0, 64 }, + +{ "1920x1080i@120Hz", 120, 1920, 1080, 148500000, 148, 88, 15, 2, 44, 5, 1, 1, 46 }, +{ "1280x720p@120Hz", 120, 1280, 720, 148500000, 220, 110, 20, 5, 40, 5, 1, 0, 47 }, +{ "720x480p@120Hz", 120, 720, 480, 54000000, 60, 16, 30, 9, 62, 6, 0, 0, 48 }, +{ "1440x480i@120Hz", 120, 1440, 480, 54000000, 114, 38, 15, 4, 12, 3, 0, 1, 50 }, +{ "1920x1080p@120Hz", 120, 1920, 1080, 297000000, 148, 88, 36, 4, 44, 5, 1, 0, 63 }, + +{ "720x576p@200Hz", 200, 720, 576, 108000000, 68, 12, 39, 5, 64, 5, 0, 0, 52 }, +{ "1440x576i@200Hz", 200, 1920, 1080, 108000000, 138, 24, 19, 2, 12, 3, 0, 1, 54 }, + +{ "720x480p@240Hz", 240, 720, 480, 108000000, 60, 16, 30, 9, 62, 6, 0, 0, 56 }, +{ "1440x480i@240Hz", 240, 1440, 480, 108000000, 114, 38, 15, 4, 12, 3, 0, 1, 58 }, +*/ + +}; + +int hdmi_set_info(struct rk29fb_screen *screen, unsigned int vic) +{ + int i; + + if(screen == NULL || vic == 0) + return -1; + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) + { + if(hdmi_mode[i].flag == vic) + break; + } + if(i == ARRAY_SIZE(hdmi_mode)) + return -1; + + memset(screen, 0, sizeof(struct rk29fb_screen)); + + /* screen type & face */ + screen->type = OUT_TYPE; + screen->face = OUT_FACE; + + /* Screen size */ + screen->x_res = hdmi_mode[i].xres; + screen->y_res = hdmi_mode[i].yres; + + /* Timing */ + screen->pixclock = hdmi_mode[i].pixclock; + screen->lcdc_aclk = LCD_ACLK; + screen->left_margin = hdmi_mode[i].left_margin; + screen->right_margin = hdmi_mode[i].right_margin; + screen->hsync_len = hdmi_mode[i].hsync_len; + screen->upper_margin = hdmi_mode[i].upper_margin; + screen->lower_margin = hdmi_mode[i].lower_margin; + screen->vsync_len = hdmi_mode[i].vsync_len; + + /* Pin polarity */ + if(FB_SYNC_HOR_HIGH_ACT & hdmi_mode[i].sync) + screen->pin_hsync = 1; + else + screen->pin_hsync = 0; + if(FB_SYNC_VERT_HIGH_ACT & hdmi_mode[i].sync) + screen->pin_vsync = 1; + else + screen->pin_vsync = 0; + screen->pin_den = 0; + screen->pin_dclk = DCLK_POL; + + /* Swap rule */ + screen->swap_rb = SWAP_RB; + screen->swap_rg = 0; + screen->swap_gb = 0; + screen->swap_delta = 0; + screen->swap_dumy = 0; + + /* Operation function*/ + screen->init = NULL; + screen->standby = NULL; + + return 0; +} + +static void hdmi_show_sink_info(struct hdmi *hdmi) +{ + struct list_head *pos, *head = &hdmi->edid.modelist; + struct fb_modelist *modelist; + struct fb_videomode *m; + int i; + struct hdmi_audio *audio; + + hdmi_dbg(hdmi->dev, "******** Show Sink Info ********\n"); + hdmi_dbg(hdmi->dev, "Support video mode: \n"); + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + hdmi_dbg(hdmi->dev, " %s.\n", m->name); + } + + for(i = 0; i < hdmi->edid.audio_num; i++) + { + audio = &(hdmi->edid.audio[i]); + switch(audio->type) + { + case HDMI_AUDIO_LPCM: + hdmi_dbg(hdmi->dev, "Support audio type: LPCM\n"); + break; + case HDMI_AUDIO_AC3: + hdmi_dbg(hdmi->dev, "Support audio type: AC3\n"); + break; + case HDMI_AUDIO_MPEG1: + hdmi_dbg(hdmi->dev, "Support audio type: MPEG1\n"); + break; + case HDMI_AUDIO_MP3: + hdmi_dbg(hdmi->dev, "Support audio type: MP3\n"); + break; + case HDMI_AUDIO_MPEG2: + hdmi_dbg(hdmi->dev, "Support audio type: MPEG2\n"); + break; + case HDMI_AUDIO_AAC_LC: + hdmi_dbg(hdmi->dev, "Support audio type: AAC\n"); + break; + case HDMI_AUDIO_DTS: + hdmi_dbg(hdmi->dev, "Support audio type: DTS\n"); + break; + case HDMI_AUDIO_ATARC: + hdmi_dbg(hdmi->dev, "Support audio type: ATARC\n"); + break; + case HDMI_AUDIO_DSD: + hdmi_dbg(hdmi->dev, "Support audio type: DSD\n"); + break; + case HDMI_AUDIO_E_AC3: + hdmi_dbg(hdmi->dev, "Support audio type: E-AC3\n"); + break; + case HDMI_AUDIO_DTS_HD: + hdmi_dbg(hdmi->dev, "Support audio type: DTS-HD\n"); + break; + case HDMI_AUDIO_MLP: + hdmi_dbg(hdmi->dev, "Support audio type: MLP\n"); + break; + case HDMI_AUDIO_DST: + hdmi_dbg(hdmi->dev, "Support audio type: DST\n"); + break; + case HDMI_AUDIO_WMA_PRO: + hdmi_dbg(hdmi->dev, "Support audio type: WMP-PRO\n"); + break; + default: + hdmi_dbg(hdmi->dev, "Support audio type: Unkown\n"); + break; + } + + hdmi_dbg(hdmi->dev, "Support audio sample rate: \n"); + if(audio->rate & HDMI_AUDIO_FS_32000) + hdmi_dbg(hdmi->dev, " 32000\n"); + if(audio->rate & HDMI_AUDIO_FS_44100) + hdmi_dbg(hdmi->dev, " 44100\n"); + if(audio->rate & HDMI_AUDIO_FS_48000) + hdmi_dbg(hdmi->dev, " 48000\n"); + if(audio->rate & HDMI_AUDIO_FS_88200) + hdmi_dbg(hdmi->dev, " 88200\n"); + if(audio->rate & HDMI_AUDIO_FS_96000) + hdmi_dbg(hdmi->dev, " 96000\n"); + if(audio->rate & HDMI_AUDIO_FS_176400) + hdmi_dbg(hdmi->dev, " 176400\n"); + if(audio->rate & HDMI_AUDIO_FS_192000) + hdmi_dbg(hdmi->dev, " 192000\n"); + + hdmi_dbg(hdmi->dev, "Support audio word lenght: \n"); + if(audio->rate & HDMI_AUDIO_WORD_LENGTH_16bit) + hdmi_dbg(hdmi->dev, " 16bit\n"); + if(audio->rate & HDMI_AUDIO_WORD_LENGTH_20bit) + hdmi_dbg(hdmi->dev, " 20bit\n"); + if(audio->rate & HDMI_AUDIO_WORD_LENGTH_24bit) + hdmi_dbg(hdmi->dev, " 24bit\n"); + } + hdmi_dbg(hdmi->dev, "******** Show Sink Info ********\n"); +} + +/** + * hdmi_ouputmode_select - select hdmi transmitter output mode: hdmi or dvi? + * @hdmi: handle of hdmi + * @edid_ok: get EDID data success or not, HDMI_ERROR_SUCESS means success. + */ +int hdmi_ouputmode_select(struct hdmi *hdmi, int edid_ok) +{ + struct list_head *head = &hdmi->edid.modelist; + struct fb_monspecs *specs = hdmi->edid.specs; + struct fb_videomode *modedb = NULL; + int i, pixclock; + + if(edid_ok != HDMI_ERROR_SUCESS) { + dev_err(hdmi->dev, "warning: EDID error, assume sink as HDMI !!!!"); + hdmi->edid.sink_hdmi = 1; + } + + if(edid_ok != HDMI_ERROR_SUCESS) { + hdmi->edid.ycbcr444 = 0; + hdmi->edid.ycbcr422 = 0; + hdmi->autoconfig = HDMI_DISABLE; + } + if(head->next == head) { + dev_info(hdmi->dev, "warning: no CEA video mode parsed from EDID !!!!"); + // If EDID get error, list all system supported mode. + // If output mode is set to DVI and EDID is ok, check + // the output timing. + + if(hdmi->edid.sink_hdmi == 0 && specs && specs->modedb_len) { + /* Get max resolution timing */ + modedb = &specs->modedb[0]; + for (i = 0; i < specs->modedb_len; i++) { + if(specs->modedb[i].xres > modedb->xres) + modedb = &specs->modedb[i]; + else if(specs->modedb[i].yres > modedb->yres) + modedb = &specs->modedb[i]; + } + // For some monitor, the max pixclock read from EDID is smaller + // than the clock of max resolution mode supported. We fix it. + pixclock = PICOS2KHZ(modedb->pixclock); + pixclock /= 250; + pixclock *= 250; + pixclock *= 1000; + if(pixclock == 148250000) + pixclock = 148500000; + if(pixclock > specs->dclkmax) + specs->dclkmax = pixclock; + } + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) { + if(modedb) { + if( (hdmi_mode[i].pixclock < specs->dclkmin) || + (hdmi_mode[i].pixclock > specs->dclkmax) || + (hdmi_mode[i].refresh < specs->vfmin) || + (hdmi_mode[i].refresh > specs->vfmax) || + (hdmi_mode[i].xres > modedb->xres) || + (hdmi_mode[i].yres > modedb->yres) ) + continue; + } + hdmi_add_videomode(&hdmi_mode[i], head); + } + } + + #ifdef HDMI_DEBUG + hdmi_show_sink_info(hdmi); + #endif + return HDMI_ERROR_SUCESS; +} +/** + * hdmi_videomode_compare - compare 2 videomodes + * @mode1: first videomode + * @mode2: second videomode + * + * RETURNS: + * 1 if mode1 > mode2, 0 if mode1 = mode2, -1 mode1 < mode2 + */ +static int hdmi_videomode_compare(const struct fb_videomode *mode1, + const struct fb_videomode *mode2) +{ + if(mode1->xres > mode2->xres) + return 1; + else if(mode1->xres == mode2->xres) + { + if(mode1->yres > mode2->yres) + return 1; + else if(mode1->yres == mode2->yres) + { + if(mode1->pixclock > mode2->pixclock) + return 1; + else if(mode1->pixclock == mode2->pixclock) + { + if(mode1->refresh > mode2->refresh) + return 1; + else if(mode1->refresh == mode2->refresh) + return 0; + } + } + } + return -1; +} +/** + * hdmi_add_videomode: adds videomode entry to modelist + * @mode: videomode to add + * @head: struct list_head of modelist + * + * NOTES: + * Will only add unmatched mode entries + */ +int hdmi_add_videomode(const struct fb_videomode *mode, struct list_head *head) +{ + struct list_head *pos; + struct fb_modelist *modelist, *modelist_new; + struct fb_videomode *m; + int i, found = 0; + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) + { + m =(struct fb_videomode*) &hdmi_mode[i]; + if (fb_mode_is_equal(m, mode)) { + found = 1; + break; + } + } + + if (found) { + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + if (fb_mode_is_equal(m, mode)) { + // m == mode + return 0; + } + else + { + if(hdmi_videomode_compare(m, mode) == -1) { + break; + } + } + } + + modelist_new = kmalloc(sizeof(struct fb_modelist), + GFP_KERNEL); + if (!modelist_new) + return -ENOMEM; + modelist_new->mode = hdmi_mode[i]; + list_add_tail(&modelist_new->list, pos); + } + + return 0; +} + +/** + * hdmi_videomode_to_vic: transverse video mode to vic + * @vmode: videomode to transverse + * + */ +int hdmi_videomode_to_vic(struct fb_videomode *vmode) +{ + unsigned char vic = 0; + int i = 0; + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) + { + if( vmode->vmode == hdmi_mode[i].vmode && + vmode->refresh == hdmi_mode[i].refresh && + vmode->xres == hdmi_mode[i].xres && + vmode->left_margin == hdmi_mode[i].left_margin && + vmode->right_margin == hdmi_mode[i].right_margin && + vmode->upper_margin == hdmi_mode[i].upper_margin && + vmode->lower_margin == hdmi_mode[i].lower_margin && + vmode->hsync_len == hdmi_mode[i].hsync_len && + vmode->vsync_len == hdmi_mode[i].vsync_len) + { + if( (vmode->vmode == FB_VMODE_NONINTERLACED && vmode->yres == hdmi_mode[i].yres) || + (vmode->vmode == FB_VMODE_INTERLACED && vmode->yres == hdmi_mode[i].yres/2)) + { + vic = hdmi_mode[i].flag; + break; + } + } + } + return vic; +} + +/** + * hdmi_vic_to_videomode: transverse vic mode to video mode + * @vmode: vic to transverse + * + */ +const struct fb_videomode* hdmi_vic_to_videomode(int vic) +{ + int i; + + if(vic == 0) + return NULL; + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) + { + if(hdmi_mode[i].flag == vic) + return &hdmi_mode[i]; + } + return NULL; +} + +/** + * hdmi_find_best_mode: find the video mode nearest to input vic + * @hdmi: + * @vic: input vic + * + * NOTES: + * If vic is zero, return the high resolution video mode vic. + */ +int hdmi_find_best_mode(struct hdmi* hdmi, int vic) +{ + struct list_head *pos, *head = &hdmi->edid.modelist; + struct fb_modelist *modelist; + struct fb_videomode *m = NULL; + int found = 0; + + if(vic) + { + list_for_each(pos, head) { + modelist = list_entry(pos, struct fb_modelist, list); + m = &modelist->mode; + if(m->flag == vic) + { + found = 1; + break; + } + } + } + if( (vic == 0 || found == 0) && head->next != head) + { + modelist = list_entry(head->next, struct fb_modelist, list); + m = &modelist->mode; + } + if(m != NULL) + return m->flag; + else + return 0; +} + +const char *hdmi_get_video_mode_name(unsigned char vic) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(hdmi_mode); i++) + { + if(vic == hdmi_mode[i].flag) + break; + } + if(i == ARRAY_SIZE(hdmi_mode)) + return NULL; + else + return hdmi_mode[i].name; +} + +/** + * hdmi_switch_fb: switch lcdc mode to required video mode + * @hdmi: + * @type: + * + * NOTES: + * + */ +int hdmi_switch_fb(struct hdmi *hdmi, int vic) +{ + int rc = 0; + +// if(hdmi->config_set.resolution == 0) +// hdmi->config_set.resolution = HDMI_DEFAULT_RESOLUTION; + + if(hdmi->lcdc == NULL || hdmi->lcdc->screen == NULL) { + printk("***************\n"); + return -1; + } + + rc = hdmi_set_info(hdmi->lcdc->screen, vic); + + if(rc == 0 && hdmi->lcdc->load_screen) { + + rc = hdmi->lcdc->load_screen(hdmi->lcdc, 0); + } + return rc; +} diff --git a/drivers/video/rockchip/hdmi/rk30_hdmi_task.c b/drivers/video/rockchip/hdmi/rk30_hdmi_task.c new file mode 100755 index 000000000000..00a59ebe8f0a --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk30_hdmi_task.c @@ -0,0 +1,181 @@ +#include +#include +#include "rk30_hdmi.h" +#include "rk30_hdmi_hw.h" + + +#define HDMI_MAX_TRY_TIMES 1 + +static char *envp[] = {"INTERFACE=HDMI", NULL}; + +int hdmi_sys_init(void) +{ + hdmi->pwr_mode = PWR_SAVE_MODE_A; + hdmi->hotplug = HDMI_HPD_REMOVED; + hdmi->state = HDMI_SLEEP; + hdmi->enable = HDMI_ENABLE; + + hdmi->vic = HDMI_VIDEO_DEFAULT_MODE; + hdmi->audio.channel = HDMI_AUDIO_DEFAULT_CHANNEL; + hdmi->audio.rate = HDMI_AUDIO_DEFAULT_RATE; + hdmi->audio.word_length = HDMI_AUDIO_DEFAULT_WORD_LENGTH; + + return 0; +} + +void hdmi_sys_remove(void) +{ + rk30_hdmi_removed(); + fb_destroy_modelist(&hdmi->edid.modelist); + if(hdmi->edid.audio) + kfree(hdmi->edid.audio); + if(hdmi->edid.specs) + { + if(hdmi->edid.specs->modedb) + kfree(hdmi->edid.specs->modedb); + kfree(hdmi->edid.specs); + } + memset(&hdmi->edid, 0, sizeof(struct hdmi_edid)); + INIT_LIST_HEAD(&hdmi->edid.modelist); + hdmi->state = HDMI_SLEEP; + hdmi->hotplug = HDMI_HPD_REMOVED; +} + +static int hdmi_process_command(void) +{ + int change, state = hdmi->state; + + change = hdmi->command; + if(change != HDMI_CONFIG_NONE) + { + hdmi->command = HDMI_CONFIG_NONE; + switch(change) + { + case HDMI_CONFIG_ENABLE: + /* disable HDMI */ + if(!hdmi->enable) + { + if(hdmi->hotplug) + hdmi_sys_remove(); + state = HDMI_SLEEP; + } + if(hdmi->wait == 1) { + complete(&hdmi->complete); + hdmi->wait = 0; + } + break; + case HDMI_CONFIG_COLOR: + if(state > CONFIG_VIDEO) + state = CONFIG_VIDEO; + break; + case HDMI_CONFIG_HDCP: + break; + case HDMI_CONFIG_DISPLAY: + break; + case HDMI_CONFIG_AUDIO: + if(state > CONFIG_AUDIO) + state = CONFIG_AUDIO; + break; + case HDMI_CONFIG_VIDEO: + default: + if(state > SYSTEM_CONFIG) + state = SYSTEM_CONFIG; + else + { + if(hdmi->wait == 1) { + complete(&hdmi->complete); + hdmi->wait = 0; + } + } + break; + } + } + + return state; +} + +void hdmi_work(struct work_struct *work) +{ + int hotplug, state_last, state; + int rc = HDMI_ERROR_SUCESS, trytimes = 0; + /* Process hdmi command */ + state = hdmi_process_command(); + + if(!hdmi->enable) + return; + + hotplug = rk30_hdmi_detect_hotplug(); + hdmi_dbg(hdmi->dev, "[%s] hotplug %02x curvalue %d\n", __FUNCTION__, hotplug, hdmi->hotplug); + if(hotplug != hdmi->hotplug) + { + hdmi->hotplug = hotplug; + if(hdmi->hotplug == HDMI_HPD_INSERT) + state = READ_PARSE_EDID; + else { + hdmi_sys_remove(); + kobject_uevent_env(&hdmi->dev->kobj, KOBJ_REMOVE, envp); + return; + } + } + do { + state_last = state; + switch(state) + { + case READ_PARSE_EDID: + rc = hdmi_sys_parse_edid(hdmi); + if(rc == HDMI_ERROR_SUCESS) + { + state = SYSTEM_CONFIG; + kobject_uevent_env(&hdmi->dev->kobj, KOBJ_ADD, envp); + } + break; + case SYSTEM_CONFIG: + if(hdmi->autoconfig) + hdmi->vic = hdmi_find_best_mode(hdmi, 0); + else + hdmi->vic = hdmi_find_best_mode(hdmi, hdmi->vic); + rc = hdmi_switch_fb(hdmi, hdmi->vic); + if(rc == HDMI_ERROR_SUCESS) + state = CONFIG_VIDEO; + break; + case CONFIG_VIDEO: + rc = rk30_hdmi_config_video(hdmi->vic, VIDEO_OUTPUT_RGB444, hdmi->edid.sink_hdmi); + if(rc == HDMI_ERROR_SUCESS) + { + if(hdmi->edid.sink_hdmi) + state = CONFIG_AUDIO; + else + state = PLAY_BACK; + } + break; + case CONFIG_AUDIO: + rc = rk30_hdmi_config_audio(&(hdmi->audio)); + + if(rc == HDMI_ERROR_SUCESS) + state = PLAY_BACK; + break; + case PLAY_BACK: + rk30_hdmi_control_output(1); + if(hdmi->wait == 1) { + complete(&hdmi->complete); + hdmi->wait = 0; + } + break; + default: + break; + } + if(rc != HDMI_ERROR_SUCESS) + { + trytimes++; + msleep(10); + } + if(state != state_last) + trytimes = 0; + }while((state != state_last || (rc != HDMI_ERROR_SUCESS) ) && trytimes < HDMI_MAX_TRY_TIMES); + + if(trytimes == HDMI_MAX_TRY_TIMES) + { + if(hdmi->hotplug) + hdmi_sys_remove(); + } +} \ No newline at end of file diff --git a/drivers/video/rockchip/hdmi/rk_hdmi.h b/drivers/video/rockchip/hdmi/rk_hdmi.h new file mode 100755 index 000000000000..c40a180fa2ed --- /dev/null +++ b/drivers/video/rockchip/hdmi/rk_hdmi.h @@ -0,0 +1,198 @@ +#ifndef __RK_HDMI_H__ +#define __RK_HDMI_H__ + +/******************************************************************** +** ½á¹¹¶¨Òå * +********************************************************************/ +/* HDMI video mode code according CEA-861-E*/ +enum hdmi_video_mode +{ + HDMI_640x480p_60Hz = 1, + HDMI_720x480p_60Hz_4_3, + HDMI_720x480p_60Hz_16_9, + HDMI_1280x720p_60Hz, + HDMI_1920x1080i_60Hz, //5 + HDMI_720x480i_60Hz_4_3, + HDMI_720x480i_60Hz_16_9, + HDMI_720x240p_60Hz_4_3, + HDMI_720x240p_60Hz_16_9, + HDMI_2880x480i_60Hz_4_3, //10 + HDMI_2880x480i_60Hz_16_9, + HDMI_2880x240p_60Hz_4_3, + HDMI_2880x240p_60Hz_16_9, + HDMI_1440x480p_60Hz_4_3, + HDMI_1440x480p_60Hz_16_9, //15 + HDMI_1920x1080p_60Hz, + HDMI_720x576p_50Hz_4_3, + HDMI_720x576p_50Hz_16_9, + HDMI_1280x720p_50Hz, + HDMI_1920x1080i_50Hz, //20 + HDMI_720x576i_50Hz_4_3, + HDMI_720x576i_50Hz_16_9, + HDMI_720x288p_50Hz_4_3, + HDMI_720x288p_50Hz_16_9, + HDMI_2880x576i_50Hz_4_3, //25 + HDMI_2880x576i_50Hz_16_9, + HDMI_2880x288p_50Hz_4_3, + HDMI_2880x288p_50Hz_16_9, + HDMI_1440x576p_50Hz_4_3, + HDMI_1440x576p_50Hz_16_9, //30 + HDMI_1920x1080p_50Hz, + HDMI_1920x1080p_24Hz, + HDMI_1920x1080p_25Hz, + HDMI_1920x1080p_30Hz, + HDMI_2880x480p_60Hz_4_3, //35 + HDMI_2880x480p_60Hz_16_9, + HDMI_2880x576p_50Hz_4_3, + HDMI_2880x576p_50Hz_16_9, + HDMI_1920x1080i_50Hz_2, // V Line 1250 total + HDMI_1920x1080i_100Hz, //40 + HDMI_1280x720p_100Hz, + HDMI_720x576p_100Hz_4_3, + HDMI_720x576p_100Hz_16_9, + HDMI_720x576i_100Hz_4_3, + HDMI_720x576i_100Hz_16_9, //45 + HDMI_1920x1080i_120Hz, + HDMI_1280x720p_120Hz, + HDMI_720x480p_120Hz_4_3, + HDMI_720x480p_120Hz_16_9, + HDMI_720x480i_120Hz_4_3, //50 + HDMI_720x480i_120Hz_16_9, + HDMI_720x576p_200Hz_4_3, + HDMI_720x576p_200Hz_16_9, + HDMI_720x576i_200Hz_4_3, + HDMI_720x576i_200Hz_16_9, //55 + HDMI_720x480p_240Hz_4_3, + HDMI_720x480p_240Hz_16_9, + HDMI_720x480i_240Hz_4_3, + HDMI_720x480i_240Hz_16_9, + HDMI_1280x720p_24Hz, //60 + HDMI_1280x720p_25Hz, + HDMI_1280x720p_30Hz, + HDMI_1920x1080p_120Hz, + HDMI_1920x1080p_100Hz, +}; + +/* HDMI Video Data Color Mode */ +enum { + HDMI_COLOR_RGB = 0, + HDMI_COLOR_YCbCr422, + HDMI_COLOR_YCbCr444 +}; + +/* HDMI Audio type */ +enum hdmi_audio_type +{ + HDMI_AUDIO_LPCM = 1, + HDMI_AUDIO_AC3, + HDMI_AUDIO_MPEG1, + HDMI_AUDIO_MP3, + HDMI_AUDIO_MPEG2, + HDMI_AUDIO_AAC_LC, //AAC + HDMI_AUDIO_DTS, + HDMI_AUDIO_ATARC, + HDMI_AUDIO_DSD, //One bit Audio + HDMI_AUDIO_E_AC3, + HDMI_AUDIO_DTS_HD, + HDMI_AUDIO_MLP, + HDMI_AUDIO_DST, + HDMI_AUDIO_WMA_PRO +}; + +/* I2S Fs */ +enum hdmi_audio_fs { + HDMI_AUDIO_FS_32000 = 0x1, + HDMI_AUDIO_FS_44100 = 0x2, + HDMI_AUDIO_FS_48000 = 0x4, + HDMI_AUDIO_FS_88200 = 0x8, + HDMI_AUDIO_FS_96000 = 0x10, + HDMI_AUDIO_FS_176400 = 0x20, + HDMI_AUDIO_FS_192000 = 0x40 +}; + +/* Audio Word Length */ +enum hdmi_audio_word_length { + HDMI_AUDIO_WORD_LENGTH_16bit = 0x1, + HDMI_AUDIO_WORD_LENGTH_20bit = 0x2, + HDMI_AUDIO_WORD_LENGTH_24bit = 0x4 +}; + +/* EDID block size */ +#define HDMI_EDID_BLOCK_SIZE 128 + +// HDMI state machine +enum hdmi_state{ + HDMI_SLEEP = 0, + HDMI_INITIAL, + WAIT_HOTPLUG, + READ_PARSE_EDID, + WAIT_HDMI_ENABLE, + SYSTEM_CONFIG, + CONFIG_VIDEO, + CONFIG_AUDIO, + PLAY_BACK, +}; + +// HDMI configuration command +enum hdmi_change { + HDMI_CONFIG_NONE = 0, + HDMI_CONFIG_VIDEO, + HDMI_CONFIG_AUDIO, + HDMI_CONFIG_COLOR, + HDMI_CONFIG_HDCP, + HDMI_CONFIG_ENABLE, + HDMI_CONFIG_DISABLE, + HDMI_CONFIG_DISPLAY +}; + +// HDMI Hotplug status +enum { + HDMI_HPD_REMOVED = 0, + HDMI_HPD_INSERT +}; + +/* HDMI STATUS */ +#define HDMI_DISABLE 0 +#define HDMI_ENABLE 1 +#define HDMI_UNKOWN 0xFF + +/* HDMI Error Code */ +enum hdmi_errorcode +{ + HDMI_ERROR_SUCESS = 0, + HDMI_ERROR_FALSE, + HDMI_ERROR_I2C, + HDMI_ERROR_EDID, +}; + +/* HDMI audio parameters */ +struct hdmi_audio { + u32 type; //Audio type + u32 channel; //Audio channel number + u32 rate; //Audio sampling rate + u32 word_length; //Audio data word length +}; + +struct hdmi_edid { + unsigned char sink_hdmi; //HDMI display device flag + unsigned char ycbcr444; //Display device support YCbCr444 + unsigned char ycbcr422; //Display device support YCbCr422 + unsigned char deepcolor; //bit3:DC_48bit; bit2:DC_36bit; bit1:DC_30bit; bit0:DC_Y444; + struct fb_monspecs *specs; //Device spec + struct list_head modelist; //Device supported display mode list + struct hdmi_audio *audio; //Device supported audio info + int audio_num; //Device supported audio type number +}; + +extern const struct fb_videomode hdmi_mode[]; + +#define HDMI_DEBUG + +#ifdef HDMI_DEBUG +#define hdmi_dbg(dev, format, arg...) \ + dev_printk(KERN_INFO , dev , format , ## arg) +#else +#define hdmi_dbg(dev, format, arg...) +#endif + +#endif -- 2.34.1