㉔AW-H3 Linux驱动开发之HDMI驱动程序

HDMI: High Definition Multimedia Interface,高清多媒体接口,是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号。HDMI有4种类型的接口,分别为 type A, type B, type C和type D,下面我们来看一下这4种接口的定义:

Pin type A type B type C type D
1 TMDS Data2+ TMDS Data2+ TMDS Data2 Shield Hot Plug Detect
2 TMDS Data2 Shield TMDS Data2 Shield TMDS Data2+ Utility
3 TMDS Data2– TMDS Data2– TMDS Data2– TMDS Data2+
4 TMDS Data1+ TMDS Data1+ TMDS Data1 Shield TMDS Data2 Shield
5 TMDS Data1 Shield TMDS Data1 Shield TMDS Data1+ TMDS Data2-
6 TMDS Data1– TMDS Data1– TMDS Data1– TMDS Data1+
7 TMDS Data0+ TMDS Data0+ TMDS Data0 Shield TMDS Data1 Shield
8 TMDS Data0 Shield TMDS Data0 Shield TMDS Data0+ TMDS Data1-
9 TMDS Data0– TMDS Data0– TMDS Data0– TMDS Data0+
10 TMDS Clock+ TMDS Clock+ TMDS Clock Shield TMDS Data0 Shield
11 TMDS Clock Shield TMDS Clock Shield TMDS Clock+ TMDS Data0-
12 TMDS Clock– TMDS Clock– TMDS Clock– TMDS Clock+
13 CEC TMDS Data5+ DDC/CEC Ground TMDS Clock Shield
14 Reserved(N.C. on device) TMDS Data5 Shield CEC TMDS Clock-
15 SCL TMDS Data5- SCL CEC
16 SDA TMDS Data4+ SDA DDC/CEC Ground
17 DDC/CEC Ground TMDS Data4 Shield Reserved(N.C. on device) SCL
18 +5V Power TMDS Data4- +5V Power SDA
19 Hot Plug Detect TMDS Data3+ Hot Plug Detect +5V Power
20 ---- TMDS Data3 Shield ---- ----
21 ---- TMDS Data3- ---- ----
22 ---- CEC ---- ----
23 ---- Reserved(N.C. on device) ---- ----
24 ---- Reserved(N.C. on device) ---- ----
25 ---- SCL ---- ----
26 ---- SDA ---- ----
27 ---- DDC/CEC Ground ---- ----
28 ---- +5V Power ---- ----
29 ---- Hot Plug Detect ---- ----

这里我们用的是type A的接口,电路图如下:

从上面可以看出HDMI采用差分数据的形式进行数据传输,有3组数据差分和一组时钟差分,类似的形式比如CAN, USB等都是通过差分的形式进行数据交互的,采用这种形式的好处是抗干扰能力强,而且速度快,比较新的HDMI 2.1最大传输速率可以达到42.6G bit/s,我们这里用到的全志的CPU是HDMI 1.4,理论上最大支持速率到8.16G bit/s,这个其实已经很快了,allwinner官方的datasheet上说可以支持30 fps的4K画质播放.下边是不同HDMI版本的区别:

接下来我们分析一下HDMI的硬件框架,上面的电路图上pin脚可以分为4部分:

第一部分:4组差分线,数据交互等工作.
第二部分:一组I2C总线,这里称为DDC,主要进行CPU和显示设备之间HDCP数据密钥交换和获取EDID的接口.
第三部分:CEC线,在HDMI上有多个显示器时,如果显示器都支持CEC,那么一个遥控就可以控制其它设备.
第四部分:HHPD线,热插拔检测引脚,驱动程序中会根据这个引脚的变化做拔插相关工作.

上面还有两个概念需要说一下:
HDCP: 高带宽数据内容保护,目的是对数据加密,保护知识产权.
EDID: 包含显示器及其性能参数.

allwinner H3上有集成HDMI控制器,大概的一个框架图如下:

从上面框架可以看出,支持HDMI和TV两种形式,datasheet上了解到,这款芯片支持同时输出HDMI和TV,这个的TV指的是CVBS,那么,我们可以通过这个功能实现双屏,说到双屏,这里简单提一下,双屏主要分为3种显示形式: 双屏同显, 双屏异显,双屏合显,当然,单屏也可以实现类似效果,这里只说一下双屏,实现双屏的形式有很多种方式,比如DSI + HDMI, DSI + CVBS,DSI + SPI,...有很多形式,当然,最好的方法其实是两个屏幕用同一组接口,通过片选或者地址选择,双屏同显看起来比较容易,双屏在公交车上也有用到,公交车的广告机屏1播放传媒视频,屏2播放PPT式广告,手机上比如努比亚,ZTE等都有推出双屏的手机.

好了,回到正题,继续说HDMI,接下来说一下软件部分:

本次使用的是Android进行验证,源码方面是7.0, Android N的代码,kernel使用的是Linux-4.4,和上次camera那一节使用的Linux-3.4代码上做了一些升级,HDMI这一部分还是有很大变动的,无论是HDMI专有部分还是通用部分,都进行了代码优化和变动,不过大致的框架还是差不多的,代码方面依然可以去参考Linux-3.4的,这里贴一下代码路径:

Linux-3.4在线看代码: https://github.com/orangepi-xunlong/orangepi_h3_linux

Android N全部压缩源代码: https://pan.baidu.com/s/1o9HIBii#list/path=%2F

这里简单说一下全志平台Android N的编译步骤,首先从百度云下载7.0的全部压缩包H3-sdk7.0-2017-11-03.tar.gzaa到*.gzan,然后在Ubuntu执行:

cat H3-sdk7.0-2017-11-03.tar.gza* > H3-Android7.0.tar.gz
tar -xvf H3-Android7.0.tar.gz

就可以解压得到Android和kernel源码,然后根据我之前写的编译环境搭建,安装以下Android源码的编译依赖,不过要注意,Android 7.0需要openjdk-8-jdk,不是6或者7,如果安装错了,则编译报错,如果有安装多个openjdk则根据我之前写的编译环境搭建,做一下切换即可.安装完编译依赖环境之后,上述所说的解压文件会得到两个文件夹,一个是android,另一个是lichee,我们进入lichee,做如下配置:

phoebus@Linux:~/xshark/Android/AndroidN/lichee$ ./build.sh config
Welcome to mkscript setup progress
All available chips:
   0. sun50iw1p1
   1. sun50iw2p1
   2. sun50iw6p1
   3. sun8iw11p1
   4. sun8iw12p1
   5. sun8iw6p1
   6. sun8iw7p1
   7. sun8iw8p1
   8. sun9iw1p1
Choice: 6
All available platforms:
   0. android
   1. dragonboard
   2. linux
   3. camdroid
Choice: 0
All available business:
   0. dolphin
   1. secure
   2. karaok
Choice: 0
LICHEE_BUSINESS=dolphin
using kernel 'linux-3.10':
All available kernel:
   0. linux-4.4
Choice: 0

然后会自动编译,如果没有自动编译,则直接运行./build.sh即可,等待编译完成,然后,进入android目录执行:

source ./build/envsetup.sh
lunch 28
extract-bsp
make -j8 && pack

因为我用的是H3,所以选择lunch 28,其它CPU根据实际情况选择编译对应版本的Android.下面主要说一下HDMI驱动的实现.在全志平台,video子系统这部分可以分为两部分,一部分是通用代码,另一部分是具体控制器专用驱动.这里用的H3只集成了HDMI和CVBS,所以目录结构如下:

通用部分代码量还是不少的,这里暂时主题是具体驱动实现,所以,暂时不讨论通用部分,CVBS使用的很少了,这里也先不说,重点说一下HDMI目录,目录结构如下:

可以看出来sun8iw11和sun50iw1工程,全志只是提供了动态库,并没有给出源码,应该是这部分涉及到一些全志的特色技术,但是不得不说,全志这种做法已经违反了GPL协议,GNU组织没有告全志,可能是觉得这个vendor应该太小了,来一趟还不够油钱,用<<惠子相梁>>形容一下.

因为这里用的是H3,代号是sun8iw7p1,这里正好这部分是开源的,所以动态库,对本次没有影响.我们先来看一下,这个目录的makefile:

obj-$(CONFIG_HDMI_DISP2_SUNXI) += hdmi.o

hdmi-y := drv_hdmi.o hdmi_core.o hdmi_edid.o
ifeq ($(CONFIG_ARM64),y)
$(shell cp $(obj)/libhdmi_sun50iw1 $(obj)/libhdmi_sun50iw1.a)
hdmi-$(CONFIG_ARCH_SUN50IW1P1) += libhdmi_sun50iw1.a
hdmi-$(CONFIG_ARCH_SUN50IW2P1) += libhdmi_sun50iw1.a
endif

ifeq ($(CONFIG_ARM),y)
$(shell cp $(obj)/libhdmi_sun8iw11 $(obj)/libhdmi_sun8iw11.a)
hdmi-$(CONFIG_ARCH_SUN50IW1P1) += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN50IW2P1) += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN8IW11)   += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN8IW12) += hdmi_bsp_sun8iw12.o
hdmi-$(CONFIG_ARCH_SUN8IW7) += hdmi_bsp_sun8iw7.o
endif

可以看出,这个目录的文件编译之后,会链接成一个hdmi.o,这个文件包括:drv_hdmi.c, hdmi_core.c, hdmi_edid.c,然后这里是sun8iw7p1,所以,还会包含:hdmi_bsp_sun8iw7.c,这就是HDMI功能实现的驱动代码,代码量不是很多,3K多行,它们的组织关系是, hdmi_bsp_sun8iw7.c和hdmi_edid.c为hdmi_core.c提供API, hdmi_core.c为drv_hdmi.c提供API,可以看出,主要实现就是在drv_hdmi.c中,这个文件是重点, 然后,需要我们实现的是hdmi_bsp_sun8iw7.c,不过这个全志已经实现了,所以,这里主要就是两个文件是我们比较关注的.因为时间比较紧,所以没有画这个驱动的框架图,这里先直接贴一下这两个文件的代码:

hdmi_bsp_sun8iw7.c

/*
 * Allwinner SoCs hdmi driver.
 *
 * Copyright (C) 2017 Allwinner.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include "hdmi_bsp.h"
#include "hdmi_core.h"

static unsigned int hdmi_base_addr;
static unsigned int hdmi_version;
/*static struct audio_para glb_audio;*/
static unsigned int tmp_rcal_100, tmp_rcal_200;
static unsigned int rcal_flag;

#define get_bvalue(addr) (*((volatile unsigned char *)(addr)))
#define put_bvalue(addr, v) \
    (*((volatile unsigned char *)(addr)) = (unsigned char)(v))
#define get_wvalue(addr) (*((volatile unsigned long *)(addr)))
#define put_wvalue(addr, v) \
    (*((volatile unsigned long *)(addr)) = (unsigned long)(v))

static int hdmi_phy_set(struct video_para *video);

struct para_tab
{
    unsigned int para[19];
};

struct pcm_sf
{
    unsigned int sf;
    unsigned char cs_sf;
};

static struct para_tab ptbl[] = {
    {{6, 1, 1, 1, 5, 3, 0, 1, 4, 0, 0, 160, 20, 38, 124, 240, 22, 0, 0}},
    {{21, 11, 1, 1, 5, 3, 1, 1, 2, 0, 0, 160, 32, 24, 126, 32, 24, 0, 0}},
    {{2, 11, 0, 0, 2, 6, 1, 0, 9, 0, 0, 208, 138, 16, 62, 224, 45, 0, 0}},
    {{17, 11, 0, 0, 2, 5, 2, 0, 5, 0, 0, 208, 144, 12, 64, 64, 49, 0, 0}},
    {{19, 4, 0, 96, 5, 5, 2, 2, 5, 1, 0, 0, 188, 184, 40, 208, 30, 1, 1}},
    {{4, 4, 0, 96, 5, 5, 2, 1, 5, 0, 0, 0, 114, 110, 40, 208, 30, 1, 1}},
    {{20, 4, 0, 97, 7, 5, 4, 2, 2, 2, 0, 128, 208, 16, 44, 56, 22, 1, 1}},
    {{5, 4, 0, 97, 7, 5, 4, 1, 2, 0, 0, 128, 24, 88, 44, 56, 22, 1, 1}},
    {{31, 2, 0, 96, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45, 1, 1}},
    {{16, 2, 0, 96, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45, 1, 1}},
    {{32, 4, 0, 96, 7, 5, 4, 3, 4, 2, 0, 128, 62, 126, 44, 56, 45, 1, 1}},
    {{33, 4, 0, 0, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45, 1, 1}},
    {{34, 4, 0, 0, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45, 1, 1}},
    {{160, 2, 0, 96, 7, 5, 8, 3, 4, 2, 0, 128, 62, 126, 44, 157, 45, 1, 1}},
    {{147, 2, 0, 96, 5, 5, 5, 2, 5, 1, 0, 0, 188, 184, 40, 190, 30, 1, 1}},
    {{132, 2, 0, 96, 5, 5, 5, 1, 5, 0, 0, 0, 114, 110, 40, 160, 30, 1, 1}},
    {{257, 1, 0, 96, 15, 10, 8, 2, 8, 0, 0, 0, 48, 176, 88, 112, 90, 1, 1}},
    {{258, 1, 0, 96, 15, 10, 8, 5, 8, 4, 0, 0, 160, 32, 88, 112, 90, 1, 1}},
    {{259, 1, 0, 96, 15, 10, 8, 6, 8, 4, 0, 0, 124, 252, 88, 112, 90, 1, 1}},
    {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
};

static unsigned char ca_table[64] = {
    0x00, 0x11, 0x01, 0x13, 0x02, 0x31, 0x03, 0x33, 0x04, 0x15, 0x05,
    0x17, 0x06, 0x35, 0x07, 0x37, 0x08, 0x55, 0x09, 0x57, 0x0a, 0x75,
    0x0b, 0x77, 0x0c, 0x5d, 0x0d, 0x5f, 0x0e, 0x7d, 0x0f, 0x7f, 0x10,
    0xdd, 0x11, 0xdf, 0x12, 0xfd, 0x13, 0xff, 0x14, 0x99, 0x15, 0x9b,
    0x16, 0xb9, 0x17, 0xbb, 0x18, 0x9d, 0x19, 0x9f, 0x1a, 0xbd, 0x1b,
    0xbf, 0x1c, 0xdd, 0x1d, 0xdf, 0x1e, 0xfd, 0x1f, 0xff,
};

static struct pcm_sf sf[10] = {
    {22050, 0x04},
    {44100, 0x00},
    {88200, 0x08},
    {176400, 0x0c},
    {24000, 0x06},
    {48000, 0x02},
    {96000, 0x0a},
    {192000, 0x0e},
    {32000, 0x03},
    {768000, 0x09},
};

static unsigned int n_table[21] = {
    32000, 3072, 4096, 44100, 4704, 6272, 88200,
    9408, 12544, 176400, 18816, 25088, 48000, 5120,
    6144, 96000, 10240, 12288, 192000, 20480, 24576,
};

/* byte write */
static void hdmi_write(unsigned int addr, unsigned char data)
{
    put_bvalue(hdmi_base_addr + addr, data);
}

/* word write */
static void hdmi_writel(unsigned int addr, unsigned int data)
{
    put_wvalue(hdmi_base_addr + addr, data);
}

/* byte read */
static unsigned char hdmi_read(unsigned int addr)
{
    return get_bvalue(hdmi_base_addr + addr);
}

/* word read */
static unsigned int hdmi_readl(unsigned int addr)
{
    return get_wvalue(hdmi_base_addr + addr);
}

/* hdmi us delay */
static void hdmi_udelay(unsigned long us)
{
    hdmi_delay_us(us);
}

static void hdmi_phy_init(struct video_para *video)
{
    unsigned int to_cnt;
    unsigned int tmp;

    hdmi_writel(0x10020, 0);
    hdmi_writel(0x10020, (1 << 0));
    hdmi_udelay(5);
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 16));
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 1));
    hdmi_udelay(10);
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 2));
    hdmi_udelay(5);
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 3));
    hdmi_udelay(40);
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 19));
    hdmi_udelay(100);
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 18));
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (7 << 4));

    to_cnt = 10;

    while (1)
    {
        if ((hdmi_readl(0x10038) & 0x80) == 0x80)
            break;
        hdmi_udelay(200);

        to_cnt--;
        if (to_cnt == 0) {
            pr_warn("%s, timeout\n", __func__);
            break;
        }
    }

    hdmi_writel(0x10020, hdmi_readl(0x10020) | (0xf << 8));
    /*hdmi_writel(0x10020,hdmi_readl(0x10020)&(~(1<<19)));*/
    hdmi_writel(0x10020, hdmi_readl(0x10020) | (1 << 7));
    /*hdmi_writel(0x10020,hdmi_readl(0x10020)|(0xf<<12));*/

    hdmi_writel(0x1002c, 0x39dc5040);
    hdmi_writel(0x10030, 0x80084343);
    hdmi_udelay(10000);
    hdmi_writel(0x10034, 0x00000001);
    hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x02000000);
    hdmi_udelay(100000);
    tmp = hdmi_readl(0x10038);
    tmp_rcal_100 = (tmp & 0x3f) >> 1;
    tmp_rcal_200 = (tmp & 0x3f) >> 2;
    rcal_flag = 1;
    hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0xC0000000);
    hdmi_writel(0x1002c, hdmi_readl(0x1002c) | ((tmp & 0x1f800) >> 11));
    hdmi_writel(0x10020, 0x01FF0F7F);
    hdmi_writel(0x10024, 0x80639000);
    hdmi_writel(0x10028, 0x0F81C405);
}

static unsigned int get_vid(unsigned int id)
{
    unsigned int i, count;

    count = sizeof(ptbl) / sizeof(struct para_tab) - 1;
    for (i = 0; i < count; i++)
    {
        if (id == ptbl[i].para[0])
            return i;
    }
    ptbl[i].para[0] = id;
    return i;
}

static int hdmi_phy_set(struct video_para *video)
{
    unsigned int id;
    unsigned int count;
    unsigned int tmp;
    unsigned int div;

    count = sizeof(ptbl) / sizeof(struct para_tab);

    if (rcal_flag == 0)
        hdmi_phy_init(video);

    id = get_vid(video->vic);
    if (id == (count - 1))
        div = video->clk_div - 1;
    else
        div = ptbl[id].para[1] - 1;
    
    div &= 0xf;
    hdmi_writel(0x10020, hdmi_readl(0x10020) & (~0xf000));
    
    switch (ptbl[id].para[1])
    {
        case 1:
            if (hdmi_version == 0)
                hdmi_writel(0x1002c, 0x31dc5fc0);
            else
                hdmi_writel(0x1002c, 0x30dc5fc0);
            hdmi_writel(0x10030, 0x800863C0 | div);
            hdmi_udelay(10000);

            hdmi_writel(0x10034, 0x00000001);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x02000000);
            hdmi_udelay(200000);
            
            tmp = hdmi_readl(0x10038);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0xC0000000);
            if (((tmp & 0x1f800) >> 11) < 0x3d)
                hdmi_writel(0x1002c, hdmi_readl(0x1002c) | (((tmp & 0x1f800) >> 11) + 2));
            else
                hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x3f);
            hdmi_udelay(100000);
            
            hdmi_writel(0x10020, 0x01FFFF7F);
            hdmi_writel(0x10024, 0x8063b000);
            hdmi_writel(0x10028, 0x0F8246B5);
            break;

        case 2:
            hdmi_writel(0x1002c, 0x39dc5040);
            hdmi_writel(0x10030, 0x80084380 | div);
            hdmi_udelay(10000);

            hdmi_writel(0x10034, 0x00000001);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x02000000);
            hdmi_udelay(100000);
            
            tmp = hdmi_readl(0x10038);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0xC0000000);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | ((tmp & 0x1f800) >> 11));
            hdmi_writel(0x10020, 0x01FFFF7F);
            hdmi_writel(0x10024, 0x8063a800);
            hdmi_writel(0x10028, 0x0F81C485);
            break;

        case 4:
            hdmi_writel(0x1002c, 0x39dc5040);
            hdmi_writel(0x10030, 0x80084340 | div);
            hdmi_udelay(10000);

            hdmi_writel(0x10034, 0x00000001);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x02000000);
            hdmi_udelay(100000);
            
            tmp = hdmi_readl(0x10038);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0xC0000000);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | ((tmp & 0x1f800) >> 11));
            hdmi_writel(0x10020, 0x11FFFF7F);
            hdmi_writel(0x10024, 0x80623000 | tmp_rcal_200);
            hdmi_writel(0x10028, 0x0F814385);
            break;

        case 11:
            hdmi_writel(0x1002c, 0x39dc5040);
            hdmi_writel(0x10030, 0x80084300 | div);
            hdmi_udelay(10000);

            hdmi_writel(0x10034, 0x00000001);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0x02000000);
            hdmi_udelay(100000);
            
            tmp = hdmi_readl(0x10038);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | 0xC0000000);
            hdmi_writel(0x1002c, hdmi_readl(0x1002c) | ((tmp & 0x1f800) >> 11));
            hdmi_writel(0x10020, 0x11FFFF7F);
            hdmi_writel(0x10024, 0x80623000 | tmp_rcal_200);
            hdmi_writel(0x10028, 0x0F80C285);
            break;

        default:
            return -1;
    }
    return 0;
}

void bsp_hdmi_set_version(unsigned int version)
{
    hdmi_version = version;
}

void bsp_hdmi_set_addr(uintptr_t base_addr)
{
    hdmi_base_addr = base_addr;
    rcal_flag = 0;
}

void bsp_hdmi_inner_init(void)
{
    hdmi_write(0x10010, 0x45);
    hdmi_write(0x10011, 0x45);
    hdmi_write(0x10012, 0x52);
    hdmi_write(0x10013, 0x54);
    hdmi_write(0x8080, 0x00);
    hdmi_udelay(1);
    hdmi_write(0xF01F, 0x00);
    hdmi_write(0x8403, 0xff);
    hdmi_write(0x904C, 0xff);
    hdmi_write(0x904E, 0xff);
    hdmi_write(0xD04C, 0xff);
    hdmi_write(0x8250, 0xff);
    hdmi_write(0x8A50, 0xff);
    hdmi_write(0x8272, 0xff);
    hdmi_write(0x40C0, 0xff);
    hdmi_write(0x86F0, 0xff);
    hdmi_write(0x0EE3, 0xff);
    hdmi_write(0x8EE2, 0xff);
    hdmi_write(0xA049, 0xf0);
    hdmi_write(0xB045, 0x1e);
    hdmi_write(0x00C1, 0x00);
    hdmi_write(0x00C1, 0x03);
    hdmi_write(0x00C0, 0x00);
    hdmi_write(0x40C1, 0x10);
    hdmi_write(0x0081, 0xfd);
    hdmi_write(0x0081, 0x00);
    hdmi_write(0x0081, 0xfd);
    hdmi_write(0x0010, 0xff);
    hdmi_write(0x0011, 0xff);
    hdmi_write(0x8010, 0xff);
    hdmi_write(0x8011, 0xff);
    hdmi_write(0x0013, 0xff);
    hdmi_write(0x8012, 0xff);
    hdmi_write(0x8013, 0xff);
}

void bsp_hdmi_init(void)
{
    struct video_para vpara;

    vpara.vic = 17;
    hdmi_phy_init(&vpara);
    bsp_hdmi_inner_init();
}

void bsp_hdmi_set_video_en(unsigned char enable)
{
    if (enable)
        hdmi_writel(0x10020, hdmi_readl(0x10020) | (0xf << 12));
    else
        hdmi_writel(0x10020, hdmi_readl(0x10020) & (~(0xf << 12)));
}

int bsp_hdmi_video_get_div(unsigned int pixel_clk)
{
    int div = 1;

    if (pixel_clk > 148500000)
        div = 1;
    else if (pixel_clk > 74250000)
        div = 2;
    else if (pixel_clk > 27000000)
        div = 4;
    else
        div = 11;

    return div;
}

int bsp_hdmi_video(struct video_para *video)
{
    unsigned int count;
    unsigned int id = get_vid(video->vic);

    switch (video->vic)
    {
        case 2:
        case 6:
        case 17:
        case 21:
            video->csc = BT601;
            break;
        default:
            video->csc = BT709;
            break;
    }

    count = sizeof(ptbl) / sizeof(struct para_tab);
    if (id == count - 1) {
        ptbl[id].para[1] = bsp_hdmi_video_get_div(video->pixel_clk);
        ptbl[id].para[2] = video->pixel_repeat;
        ptbl[id].para[3] = ((video->hor_sync_polarity & 1) << 5) |
                           ((video->ver_sync_polarity & 1) << 6) |
                           (video->b_interlace & 1);
        ptbl[id].para[4] = video->x_res / 256;
        ptbl[id].para[5] = video->ver_sync_time;
        ptbl[id].para[6] = video->y_res / 256;
        ptbl[id].para[7] = (video->hor_total_time - video->x_res) / 256;
        ptbl[id].para[8] = video->ver_front_porch;
        ptbl[id].para[9] = video->hor_front_porch / 256;
        ptbl[id].para[10] = video->hor_sync_time / 256;
        
        ptbl[id].para[11] = video->x_res % 256;
        ptbl[id].para[12] = (video->hor_total_time - video->x_res) % 256;
        ptbl[id].para[13] = video->hor_front_porch % 256;
        ptbl[id].para[14] = video->hor_sync_time % 256;
        ptbl[id].para[15] = video->y_res % 256;
        ptbl[id].para[16] = video->ver_total_time - video->y_res;
        ptbl[id].para[17] = 1;
        if (video->x_res <= 736 && video->y_res <= 576)
            video->csc = BT601;
        else
            video->csc = BT709;
    }

    if (hdmi_phy_set(video) != 0)
        return -1;

    bsp_hdmi_inner_init();

    hdmi_write(0x0840, 0x01);
    hdmi_write(0x4845, 0x00);
    hdmi_write(0x0040, ptbl[id].para[3] | 0x10);
    hdmi_write(0x10001, ((ptbl[id].para[3] < 96) ? 0x03 : 0x00));
    hdmi_write(0x8040, ptbl[id].para[4]);
    hdmi_write(0x4043, ptbl[id].para[5]);
    hdmi_write(0x8042, ptbl[id].para[6]);
    hdmi_write(0x0042, ptbl[id].para[7]);
    hdmi_write(0x4042, ptbl[id].para[8]);
    hdmi_write(0x4041, ptbl[id].para[9]);
    hdmi_write(0xC041, ptbl[id].para[10]);
    hdmi_write(0x0041, ptbl[id].para[11]);
    hdmi_write(0x8041, ptbl[id].para[12]);
    hdmi_write(0x4040, ptbl[id].para[13]);
    hdmi_write(0xC040, ptbl[id].para[14]);
    hdmi_write(0x0043, ptbl[id].para[15]);
    hdmi_write(0x8043, ptbl[id].para[16]);
    hdmi_write(0x0045, 0x0c);
    hdmi_write(0x8044, 0x20);
    hdmi_write(0x8045, 0x01);
    hdmi_write(0x0046, 0x0b);
    hdmi_write(0x0047, 0x16);
    hdmi_write(0x8046, 0x21);
    hdmi_write(0x3048, ptbl[id].para[2] ? 0x21 : 0x10);
    hdmi_write(0x0401, ptbl[id].para[2] ? 0x01 : 0x00);
    hdmi_write(0x8400, 0x07);
    hdmi_write(0x8401, 0x00);
    hdmi_write(0x0402, 0x47);
    hdmi_write(0x0800, 0x01);
    hdmi_write(0x0801, 0x07);
    hdmi_write(0x8800, 0x00);
    hdmi_write(0x8801, 0x00);
    hdmi_write(0x0802, 0x00);
    hdmi_write(0x0803, 0x00);
    hdmi_write(0x8802, 0x00);
    hdmi_write(0x8803, 0x00);

    if (video->is_hdmi)
    {
        hdmi_write(0xB045, 0x08);
        hdmi_write(0x2045, 0x00);
        hdmi_write(0x2044, 0x0c);
        hdmi_write(0x6041, 0x03);
        hdmi_write(0xA044, ((ptbl[id].para[0] & 0x100) == 0x100)
                ? 0x20
                : (((ptbl[id].para[0] & 0x80) == 0x80) ? 0x40 : 0x00));
        hdmi_write(0xA045, ((ptbl[id].para[0] & 0x100) == 0x100)
                       ? (ptbl[id].para[0] & 0x7f)
                       : 0x00);
        hdmi_write(0x2046, 0x00);
        hdmi_write(0x3046, 0x01);
        hdmi_write(0x3047, 0x11);
        hdmi_write(0x4044, 0x00);
        hdmi_write(0x0052, 0x00);
        hdmi_write(0x8051, 0x11);
        hdmi_write(0x10010, 0x45);
        hdmi_write(0x10011, 0x45);
        hdmi_write(0x10012, 0x52);
        hdmi_write(0x10013, 0x54);
        hdmi_write(0x0040, hdmi_read(0x0040) | 0x08);
        hdmi_write(0x10010, 0x52);
        hdmi_write(0x10011, 0x54);
        hdmi_write(0x10012, 0x41);
        hdmi_write(0x10013, 0x57);
        hdmi_write(0x4045, video->is_yuv ? 0x02 : 0x00);
        if (ptbl[id].para[17] == 0)
            hdmi_write(0xC044, (video->csc << 6) | 0x18);
        else if (ptbl[id].para[17] == 1)
            hdmi_write(0xC044, (video->csc << 6) | 0x28);
        else
            hdmi_write(0xC044, (video->csc << 6) | 0x08);

        hdmi_write(0xC045, video->is_yuv ? 0x00 : 0x04);
        hdmi_write(0x4046, ((ptbl[id].para[0] & 0x100) == 0x100)
                       ? 0x00
                       : (ptbl[id].para[0] & 0x7f));
    }

    if (video->is_hcts)
    {
        hdmi_write(0x00C0, video->is_hdmi ? 0x91 : 0x90);
        hdmi_write(0x00C1, 0x05);
        hdmi_write(0x40C1, (ptbl[id].para[3] < 96) ? 0x10 : 0x1a);
        hdmi_write(0x80C2, 0xff);
        hdmi_write(0x40C0, 0xfd);
        hdmi_write(0xC0C0, 0x40);
        hdmi_write(0x00C1, 0x04);
        hdmi_write(0x10010, 0x45);
        hdmi_write(0x10011, 0x45);
        hdmi_write(0x10012, 0x52);
        hdmi_write(0x10013, 0x54);
        hdmi_write(0x0040, hdmi_read(0x0040) | 0x80);
        hdmi_write(0x00C0, video->is_hdmi ? 0x95 : 0x94);
        hdmi_write(0x10010, 0x52);
        hdmi_write(0x10011, 0x54);
        hdmi_write(0x10012, 0x41);
        hdmi_write(0x10013, 0x57);
    }

    hdmi_write(0x0082, 0x00);
    hdmi_write(0x0081, 0x00);

    hdmi_write(0x0840, 0x00);

    return 0;
}

int bsp_hdmi_audio(struct audio_para *audio)
{
    unsigned int i;
    unsigned int n;
    unsigned int count;
    unsigned id = get_vid(audio->vic);

    count = sizeof(ptbl) / sizeof(struct para_tab);

    hdmi_write(0xA049, (audio->ch_num > 2) ? 0xf1 : 0xf0);

    for (i = 0; i < 64; i += 2) {
        if (audio->ca == ca_table[i]) {
            hdmi_write(0x204B, ~ca_table[i + 1]);
            break;
        }
    }

    hdmi_write(0xA04A, 0x00);
    hdmi_write(0xA04B, 0x30);
    hdmi_write(0x6048, 0x00);
    hdmi_write(0x6049, 0x01);
    hdmi_write(0xE048, 0x42);
    hdmi_write(0xE049, 0x86);
    hdmi_write(0x604A, 0x31);
    hdmi_write(0x604B, 0x75);
    hdmi_write(0xE04A, 0x00 | 0x01);

    for (i = 0; i < 10; i += 1) {
        if (audio->sample_rate == sf[i].sf) {
            hdmi_write(0xE04A, 0x00 | sf[i].cs_sf);
            break;
        }
    }

    hdmi_write(0xE04B, 0x00 | (audio->sample_bit == 16)
                   ? 0x02
                   : ((audio->sample_bit == 24) ? 0xb : 0x0));

    hdmi_write(0x0251, audio->sample_bit);

    n = 6272;
    /*cts = 0;*/
    for (i = 0; i < 21; i += 3) {
        if (audio->sample_rate == n_table[i]) {
            if ((id != count - 1) && (ptbl[id].para[1] == 1))
                n = n_table[i + 1];
            else
                n = n_table[i + 2];

            /*cts = (n / 128) * (glb_video.tmds_clk / 100) /*/
            /*(audio->sample_rate / 100);*/
            break;
        }
    }

    hdmi_write(0x0A40, n);
    hdmi_write(0x0A41, n >> 8);
    hdmi_write(0x8A40, n >> 16);
    hdmi_write(0x0A43, 0x00);
    hdmi_write(0x8A42, 0x04);
    hdmi_write(0xA049, (audio->ch_num > 2) ? 0x01 : 0x00);
    if (audio->type == PCM)
        hdmi_write(0x2043, (audio->ch_num - 1) * 16);
    else
        hdmi_write(0x2043, 0x00);
    hdmi_write(0xA042, 0x00);
    hdmi_write(0xA043, audio->ca);
    hdmi_write(0x6040, 0x00);

    if (audio->type == PCM) {
        hdmi_write(0x8251, 0x00);
    } else if ((audio->type == DTS_HD) || (audio->type == MAT)) {
        hdmi_write(0x8251, 0x03);
        hdmi_write(0x0251, 0x15);
        hdmi_write(0xA043, 0);
    } else {
        hdmi_write(0x8251, 0x02);
        hdmi_write(0x0251, 0x15);
        hdmi_write(0xA043, 0);
    }

    hdmi_write(0x0250, 0x00);
    hdmi_write(0x0081, 0x08);
    hdmi_write(0x8080, 0xf7);
    hdmi_udelay(100);
    hdmi_write(0x0250, 0xaf);
    hdmi_udelay(100);
    hdmi_write(0x0081, 0x00);

    return 0;
}

/* hdmi_edid调用, ddc-->i2c */
int bsp_hdmi_ddc_read(char cmd, char pointer, char offset, int nbyte, char *pbuf)
{
    unsigned char off = offset;
    unsigned int to_cnt;
    int ret = 0;

    hdmi_write(0x10010, 0x45);
    hdmi_write(0x10011, 0x45);
    hdmi_write(0x10012, 0x52);
    hdmi_write(0x10013, 0x54);
    hdmi_write(0x4EE1, 0x00);

    to_cnt = 50;
    while ((hdmi_read(0x4EE1) & 0x01) != 0x01)
    {
        hdmi_udelay(10);
        to_cnt--; /* wait for 500us for timeout*/
        if (to_cnt == 0)
        {
            pr_warn("ddc rst timeout\n");
            break;
        }
    }

    hdmi_write(0x8EE3, 0x05);
    hdmi_write(0x0EE3, 0x08);
    hdmi_write(0x4EE2, 0xd8);
    hdmi_write(0xCEE2, 0xfe);

    to_cnt = 10;
    while (nbyte > 0)
    {
        to_cnt = 10;
        hdmi_write(0x0EE0, 0xa0 >> 1);
        hdmi_write(0x0EE1, off);
        hdmi_write(0x4EE0, 0x60 >> 1);
        hdmi_write(0xCEE0, pointer);
        hdmi_write(0x0EE2, 0x02);

        while (1)
        {
            to_cnt--; /* wait for 10ms for timeout*/
            if (to_cnt == 0) {
                pr_warn("ddc read timeout, byte cnt = %d\n",
                        nbyte);
                break;
            }

            if ((hdmi_read(0x0013) & 0x02) == 0x02) {
                hdmi_write(0x0013, hdmi_read(0x0013) & 0x02);
                *pbuf++ = hdmi_read(0x8EE1);
                break;
            } else if ((hdmi_read(0x0013) & 0x01) == 0x01) {
                hdmi_write(0x0013, hdmi_read(0x0013) & 0x01);
                ret = -1;
                break;
            }
            hdmi_udelay(1000);
        }
        nbyte--;
        off++;
    }
    hdmi_write(0x10010, 0x52);
    hdmi_write(0x10011, 0x54);
    hdmi_write(0x10012, 0x41);
    hdmi_write(0x10013, 0x57);

    return ret;
}

unsigned int bsp_hdmi_get_hpd(void)
{
    unsigned int ret = 0;

    hdmi_write(0x10010, 0x45);
    hdmi_write(0x10011, 0x45);
    hdmi_write(0x10012, 0x52);
    hdmi_write(0x10013, 0x54);

    if (hdmi_readl(0x10038) & 0x80000)
        ret = 1;
    else
        ret = 0;

    hdmi_write(0x10010, 0x52);
    hdmi_write(0x10011, 0x54);
    hdmi_write(0x10012, 0x41);
    hdmi_write(0x10013, 0x57);

    return ret;
}

void bsp_hdmi_standby(void)
{
    hdmi_write(0x10020, 0x07);
    hdmi_write(0x1002c, 0x00);
}

void bsp_hdmi_hrst(void)
{
    hdmi_write(0x00C1, 0x04);
    hdmi_write(0x0081, 0x40);
}

void bsp_hdmi_hdl(void)
{
}

int bsp_hdmi_hdcp_err_check(void)
{
    int ret = 0;

    hdmi_write(0x10010, 0x45);
    hdmi_write(0x10011, 0x45);
    hdmi_write(0x10012, 0x52);
    hdmi_write(0x10013, 0x54);
    if ((hdmi_read(0x80c0) & 0xfe) != 0x40)
        hdmi_write(0x00c1, hdmi_read(0x00c1) & 0xfe);
    hdmi_write(0x10010, 0x52);
    hdmi_write(0x10011, 0x54);
    hdmi_write(0x10012, 0x41);
    hdmi_write(0x10013, 0x57);

    return ret;
}

int bsp_hdmi_cec_get_simple_msg(unsigned char *msg)
{
    return 0;
}

int bsp_hdmi_cec_send(char *buf, unsigned char bytes)
{
    return 0;
}

void bsp_hdmi_cec_free_time_set(unsigned char value)
{
}

int bsp_hdmi_set_func(hdmi_bsp_func *func)
{
    return 0;
}

drv_hdmi.c

/*
 * Allwinner SoCs hdmi driver.
 *
 * Copyright (C) 2016 Allwinner.
 *
 * This file is licensed under the terms of the GNU General Public
 * License version 2.  This program is licensed "as is" without any
 * warranty of any kind, whether express or implied.
 */

#include "drv_hdmi_i.h"
#include "hdmi_core.h"
#include "../disp/disp_sys_intf.h"
#include "../disp/dev_disp.h"
#include <linux/regulator/consumer.h>
#include <linux/clk-provider.h>
#include <linux/clk/sunxi.h>
#include <linux/sunxi-clk-prepare.h>
#if defined(CONFIG_EXTCON)
#include <linux/extcon.h>
#endif
#include <linux/sunxi-sid.h>

static u32 io_enable_count;

static struct semaphore *run_sem;
static struct task_struct *HDMI_task;
static struct task_struct *cec_task;
static bool hdmi_cec_support;
static char hdmi_power[25];
static char hdmi_io_regulator[25];
static bool hdmi_power_used;
static bool hdmi_io_regulator_used;
static bool hdmi_used;
static bool boot_hdmi;
#if defined(CONFIG_COMMON_CLK)
static struct clk *hdmi_clk;
static struct clk *hdmi_ddc_clk;
static struct clk *hdmi_clk_parent;
#if defined(CONFIG_ARCH_SUN8IW12)
static struct clk *hdmi_cec_clk;
#endif
#endif
static u32 power_enable_count;
static u32 io_regulator_enable_count;
static u32 clk_enable_count;
static struct mutex mlock;
#if defined(CONFIG_SND_SUNXI_SOC_SUNXI_HDMIAUDIO)
static bool audio_enable;
#endif
static bool b_hdmi_suspend;
static bool b_hdmi_suspend_pre;

static struct cdev *my_cdev;
static dev_t devid;
static struct class *hdmi_class;
hdmi_info_t ghdmi;

void hdmi_delay_ms(unsigned long ms)
{
	u32 timeout = ms*HZ/1000;

	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(timeout);
}

void hdmi_delay_us(unsigned long us)
{
	udelay(us);
}

unsigned int hdmi_get_soc_version(void)
{
	unsigned int version = 0;
#if defined(CONFIG_ARCH_SUN8IW7)
#if defined(SUN8IW7P1_REV_A) || defined(SUN8IW7P2_REV_B)
	unsigned int chip_ver = sunxi_get_soc_ver();

	switch (chip_ver) {
	case SUN8IW7P1_REV_A:
	case SUN8IW7P2_REV_A:
		version = 0;
		break;
	case SUN8IW7P1_REV_B:
	case SUN8IW7P2_REV_B:
		version = 1;
	}
#else
	version = 1;
#endif /*endif  SUN8IW7P1_REV_A*/
#endif
	return version;
}

static int hdmi_parse_io_config(void)
{
	disp_sys_pin_set_state("hdmi", DISP_PIN_STATE_ACTIVE);
	return 0;
}

static int hdmi_io_config(u32 bon)
{
	return disp_sys_pin_set_state(
	    "hdmi", (bon == 1) ? DISP_PIN_STATE_ACTIVE : DISP_PIN_STATE_SLEEP);
}

#if defined(CONFIG_COMMON_CLK)
static int hdmi_clk_enable(void)
{
	int ret = 0;

	if (hdmi_clk)
		ret = clk_prepare_enable(hdmi_clk);
	if (ret != 0)
		return ret;

	if (hdmi_ddc_clk)
		ret = clk_prepare_enable(hdmi_ddc_clk);

#if defined(CONFIG_ARCH_SUN8IW12)
	if (hdmi_cec_clk)
		ret = clk_prepare_enable(hdmi_cec_clk);
#endif /*endif CONFIG_ARCH_SUN8IW12 */

	if (ret != 0)
		clk_disable(hdmi_clk);

	return ret;
}

static int hdmi_clk_disable(void)
{
	if (hdmi_clk)
		clk_disable(hdmi_clk);
	if (hdmi_ddc_clk)
		clk_disable(hdmi_ddc_clk);

#if defined(CONFIG_ARCH_SUN8IW12)
	if (hdmi_cec_clk)
		clk_disable(hdmi_cec_clk);
#endif /*endif CONFIG_ARCH_SUN8IW12 */

	return 0;
}

static int hdmi_clk_config(u32 vic)
{
	int index = 0;

	index = hdmi_core_get_video_info(vic);

	if (hdmi_clk)
		clk_set_rate(hdmi_clk, video_timing[index].pixel_clk);

	return 0;
}
#else
static int hdmi_clk_enable(void){}
static int hdmi_clk_disable(void){}
static int hdmi_clk_config(u32 vic) {}
#endif

/* hdmi_clk_enable_prepare - prepare for hdmi enable
 * if there is some other clk will affect hdmi module,
 * should enable these clk before enable hdmi
 */
int hdmi_clk_enable_prepare(void)
{
	int ret = 0;

	pr_warn("%s()L%d\n", __func__, __LINE__);
	if (hdmi_clk)
		ret = sunxi_clk_enable_prepare(hdmi_clk);
	if (ret != 0)
		return ret;

	return ret;
}

/* hdmi_clk_disable_prepare - prepare for hdmi disable
 * if there is some other clk will affect hdmi module,
 * should disable these clk after disable hdmi
 */
int hdmi_clk_disable_prepare(void)
{
	pr_warn("%s()L%d\n", __func__, __LINE__);
	if (hdmi_clk)
		sunxi_clk_disable_prepare(hdmi_clk);

	return 0;
}

unsigned int hdmi_clk_get_div(void)
{
	unsigned long rate = 1, rate_parent = 1;
	unsigned int div = 4;

	if (!hdmi_clk || !hdmi_clk_parent) {
		pr_warn("%s, get clk div fail\n", __func__);
		goto exit;
	}

	if (hdmi_clk)
		rate = clk_get_rate(hdmi_clk);
	if (hdmi_clk_parent)
		rate_parent = clk_get_rate(hdmi_clk_parent);

	if (rate != 0)
		div = rate_parent / rate;
	else
		pr_warn("%s, hdmi clk rate is ZERO!\n", __func__);

exit:
	return div;
}

#ifdef CONFIG_AW_AXP
static int hdmi_power_enable(char *name)
{
	struct regulator *regu = NULL;
	int ret = -1;

	regu = regulator_get(NULL, name);
	if (IS_ERR(regu)) {
		pr_err("%s: some error happen, fail to get regulator %s\n",
				__func__, name);
		goto exit;
	}

	/* enalbe regulator */
	ret = regulator_enable(regu);
	if (ret != 0) {
		pr_err("%s: some error happen, fail to enable regulator %s!\n",
				__func__, name);
		goto exit1;
	} else {
		hdmi_inf("suceess to enable regulator %s!\n", name);
	}

exit1:
	/* put regulater, when module exit */
	regulator_put(regu);
exit:
	return ret;
}

static int hdmi_power_disable(char *name)
{
	struct regulator *regu = NULL;
	int ret = 0;

	regu = regulator_get(NULL, name);
	if (IS_ERR(regu)) {
		hdmi_wrn("%s: some error happen, fail to get regulator %s\n",
				__func__, name);
		goto exit;
	}

	/* disalbe regulator */
	ret = regulator_disable(regu);
	if (ret != 0) {
		hdmi_wrn(
		    "%s: some error happen, fail to disable regulator %s!\n",
		    __func__, name);
		goto exit1;
	} else {
		hdmi_inf("suceess to disable regulator %s!\n", name);
	}

exit1:
	/* put regulater, when module exit */
	regulator_put(regu);
exit:
	return ret;
}
#else
static int hdmi_power_enable(char *name) {return 0; }
static int hdmi_power_disable(char *name) {return 0; }
#endif

static s32 hdmi_enable(void)
{
	hdmi_inf("[hdmi_enable]\n");

	mutex_lock(&mlock);
	if (ghdmi.bopen != 1) {
		hdmi_clk_config(ghdmi.mode);
		hdmi_core_set_video_enable(1);
		ghdmi.bopen = 1;
	}
	mutex_unlock(&mlock);
	return 0;
}

static s32 hdmi_disable(void)
{
	hdmi_inf("[hdmi_disable]\n");

	mutex_lock(&mlock);
	if (ghdmi.bopen != 0) {
		hdmi_core_set_video_enable(0);
		ghdmi.bopen = 0;
	}
	mutex_unlock(&mlock);
	return 0;
}

static struct disp_hdmi_mode hdmi_mode_tbl[] = {
	{DISP_TV_MOD_480I,                HDMI1440_480I,     },
	{DISP_TV_MOD_576I,                HDMI1440_576I,     },
	{DISP_TV_MOD_480P,                HDMI480P,          },
	{DISP_TV_MOD_576P,                HDMI576P,          },
	{DISP_TV_MOD_720P_50HZ,           HDMI720P_50,       },
	{DISP_TV_MOD_720P_60HZ,           HDMI720P_60,       },
	{DISP_TV_MOD_1080I_50HZ,          HDMI1080I_50,      },
	{DISP_TV_MOD_1080I_60HZ,          HDMI1080I_60,      },
	{DISP_TV_MOD_1080P_24HZ,          HDMI1080P_24,      },
	{DISP_TV_MOD_1080P_50HZ,          HDMI1080P_50,      },
	{DISP_TV_MOD_1080P_60HZ,          HDMI1080P_60,      },
	{DISP_TV_MOD_1080P_25HZ,          HDMI1080P_25,      },
	{DISP_TV_MOD_1080P_30HZ,          HDMI1080P_30,      },
	{DISP_TV_MOD_1080P_24HZ_3D_FP,    HDMI1080P_24_3D_FP,},
	{DISP_TV_MOD_720P_50HZ_3D_FP,     HDMI720P_50_3D_FP, },
	{DISP_TV_MOD_720P_60HZ_3D_FP,     HDMI720P_60_3D_FP, },
	{DISP_TV_MOD_3840_2160P_30HZ,     HDMI3840_2160P_30, },
	{DISP_TV_MOD_3840_2160P_25HZ,     HDMI3840_2160P_25, },
	{DISP_TV_MOD_3840_2160P_24HZ,     HDMI3840_2160P_24, },
	{DISP_TV_MOD_4096_2160P_24HZ,     HDMI4096_2160P_24, },
};

static u32 hdmi_get_vic(u32 mode)
{
	u32 hdmi_mode = DISP_TV_MOD_720P_50HZ;
	u32 i;
	bool find = false;

	for (i = 0; i < sizeof(hdmi_mode_tbl)/sizeof(struct disp_hdmi_mode);
			i++) {
		if (hdmi_mode_tbl[i].mode == mode) {
			hdmi_mode = hdmi_mode_tbl[i].hdmi_mode;
			find = true;
			break;
		}
	}

	if (false == find)
		pr_warn("[HDMI]can't find vic for mode(%d)\n", mode);

	return hdmi_mode;
}

static s32 hdmi_set_display_mode(u32 mode)
{
	u32 hdmi_mode;
	u32 i;
	bool find = false;

	hdmi_inf("[hdmi_set_display_mode],mode:%d\n", mode);

	for (i = 0; i < sizeof(hdmi_mode_tbl)/sizeof(struct disp_hdmi_mode);
			i++) {
		if (hdmi_mode_tbl[i].mode == (enum disp_tv_mode)mode) {
			hdmi_mode = hdmi_mode_tbl[i].hdmi_mode;
			find = true;
			break;
		}
	}

	if (find) {
		ghdmi.mode = hdmi_mode;
		return hdmi_core_set_video_mode(hdmi_mode);
	}
	hdmi_wrn("unsupported video mode %d when set display mode\n", mode);
	return -1;

}

#if defined(CONFIG_SND_SUNXI_SOC_SUNXI_HDMIAUDIO)
static s32 hdmi_audio_enable(u8 mode, u8 channel)
{
	hdmi_inf("[hdmi_audio_enable],mode:%d,ch:%d\n", mode, channel);
	mutex_lock(&mlock);
	audio_enable = mode;
	mutex_unlock(&mlock);
	return hdmi_core_set_audio_enable(audio_enable);
}

static s32 hdmi_set_audio_para(hdmi_audio_t *audio_para)
{
	hdmi_inf("[hdmi_set_audio_para]\n");
	return hdmi_core_audio_config(audio_para);
}
#endif

static s32 hdmi_mode_support(u32 mode)
{
	u32 hdmi_mode;
	u32 i;
	bool find = false;

	for (i = 0; i < sizeof(hdmi_mode_tbl)/sizeof(struct disp_hdmi_mode);
			i++) {
		if (hdmi_mode_tbl[i].mode == (enum disp_tv_mode)mode) {
			hdmi_mode = hdmi_mode_tbl[i].hdmi_mode;
			find = true;
			break;
		}
	}

	if (find)
		return hdmi_core_mode_support(hdmi_mode);
	else
		return 0;
}

static s32 hdmi_get_HPD_status(void)
{
	return hdmi_core_hpd_check();
}

static s32 hdmi_get_hdcp_enable(void)
{
	return hdmi_core_get_hdcp_enable();
}

static s32 hdmi_get_video_timming_info(struct disp_video_timings **video_info)
{
	struct disp_video_timings *info;
	int ret = -1;
	int i, list_num;

	info = video_timing;
	list_num = hdmi_core_get_list_num();
	for (i = 0; i < list_num; i++) {
		if (info->vic == ghdmi.mode) {
			*video_info = info;
			ret = 0;
			break;
		}
		info++;
	}
	return ret;
}

static s32 hdmi_get_input_csc(void)
{
	return hdmi_core_get_csc_type();
}

static int hdmi_run_thread(void *parg)
{
	while (1) {
		if (kthread_should_stop())
			break;

		mutex_lock(&mlock);
		if (false == b_hdmi_suspend) {
			/* normal state */
			b_hdmi_suspend_pre = b_hdmi_suspend;
			mutex_unlock(&mlock);
			hdmi_core_loop();

			if (false == b_hdmi_suspend) {
				if (hdmi_get_hdcp_enable() == 1)
					hdmi_delay_ms(100);
				else
					hdmi_delay_ms(200);
			}
		} else {
			/* suspend state */
			if (false == b_hdmi_suspend_pre) {
				/* first time after enter suspend state */
				/* hdmi_core_enter_lp(); */
			}
			b_hdmi_suspend_pre = b_hdmi_suspend;
			mutex_unlock(&mlock);
		}
	}

	return 0;
}

void cec_msg_sent(char *buf)
{
	char *envp[2];

	envp[0] = buf;
	envp[1] = NULL;
	kobject_uevent_env(&ghdmi.dev->kobj, KOBJ_CHANGE, envp);
}

#define CEC_BUF_SIZE 32
static int cec_thread(void *parg)
{
	int ret = 0;
	char buf[CEC_BUF_SIZE];
	unsigned char msg;

	while (1) {
		if (kthread_should_stop())
			break;

		mutex_lock(&mlock);
		ret = -1;
		if (false == b_hdmi_suspend)
			ret = hdmi_core_cec_get_simple_msg(&msg);
		mutex_unlock(&mlock);
		if (ret == 0) {
			memset(buf, 0, CEC_BUF_SIZE);
			snprintf(buf, sizeof(buf), "CEC_MSG=0x%x", msg);
			cec_msg_sent(buf);
		}
		hdmi_delay_ms(10);
	}

	return 0;
}

#if defined(CONFIG_EXTCON)
static const unsigned int hdmi_cable[] = {
	EXTCON_DISP_HDMI,
	EXTCON_NONE,
};
static struct extcon_dev extcon_hdmi = {
	.name = "hdmi",
};

/* s32 disp_set_hdmi_detect(bool hpd); */
static void hdmi_report_hpd_work(struct work_struct *work)
{
	if (hdmi_get_HPD_status()) {
		extcon_set_state(&extcon_hdmi, STATUE_OPEN);
		disp_set_hdmi_detect(1);
		hdmi_inf("switch_set_state 1\n");
	} else {
		extcon_set_state(&extcon_hdmi, STATUE_CLOSE);
		disp_set_hdmi_detect(0);
		hdmi_inf("switch_set_state 0\n");
	}
}

s32 hdmi_hpd_state(u32 state)
{
	if (state == 0)
		extcon_set_state(&extcon_hdmi, STATUE_CLOSE);
	else
		extcon_set_state(&extcon_hdmi, STATUE_OPEN);

	return 0;
}
#else
static void hdmi_report_hpd_work(struct work_struct *work)
{
}

s32 hdmi_hpd_state(u32 state)
{
	return 0;
}
#endif
/**
 * hdmi_hpd_report - report hdmi hot plug state to user space
 * @hotplug:	0: hdmi plug out;   1:hdmi plug in
 *
 * always return success.
 */
s32 hdmi_hpd_event(void)
{
	schedule_work(&ghdmi.hpd_work);
	return 0;
}

static s32 hdmi_suspend(void)
{
	hdmi_core_update_detect_time(0);
	if (hdmi_used && (false == b_hdmi_suspend)) {
		if (HDMI_task) {
			kthread_stop(HDMI_task);
			HDMI_task = NULL;
		}

		if (hdmi_cec_support && cec_task) {
			kthread_stop(cec_task);
			cec_task = NULL;
		}
		mutex_lock(&mlock);
		b_hdmi_suspend = true;
		hdmi_core_enter_lp();

		if (clk_enable_count != 0) {
			hdmi_clk_disable();
			clk_enable_count--;
		}

		if (io_enable_count != 0) {
			hdmi_io_config(0);
			io_enable_count--;
		}

		if ((hdmi_power_used) && (power_enable_count != 0)) {
			hdmi_power_disable(hdmi_power);
			power_enable_count--;
		}

		if (hdmi_io_regulator_used && io_regulator_enable_count) {
			hdmi_power_disable(hdmi_io_regulator);
			--io_regulator_enable_count;
		}

		mutex_unlock(&mlock);
		pr_info("[HDMI]hdmi suspend\n");
	}

	return 0;
}

static s32 hdmi_resume(void)
{
	int ret;

	mutex_lock(&mlock);
	if (hdmi_used && (true == b_hdmi_suspend)) {
		/* normal state */
		if (clk_enable_count == 0) {
			ret = hdmi_clk_enable();
			if (ret == 0)
				clk_enable_count++;
			else {
				pr_warn("fail to enable hdmi's clock\n");
				goto exit;
			}
		}

		if (hdmi_io_regulator_used && !io_regulator_enable_count) {
			hdmi_power_enable(hdmi_io_regulator);
			++io_regulator_enable_count;
		}

		if ((hdmi_power_used) && (power_enable_count == 0)) {
			hdmi_power_enable(hdmi_power);
			power_enable_count++;
		}

		if (io_enable_count == 0) {
			hdmi_io_config(1);
			io_enable_count++;
		}

		/* first time after exit suspend state */
		hdmi_core_exit_lp();

		HDMI_task = kthread_create(hdmi_run_thread, (void *)0, "hdmi proc");
		if (IS_ERR(HDMI_task)) {
			s32 err = 0;

			pr_warn("Unable to start kernel thread %s.\n\n", "hdmi proc");
			err = PTR_ERR(HDMI_task);
			HDMI_task = NULL;
		} else
			wake_up_process(HDMI_task);

		if (hdmi_cec_support) {
			cec_task = kthread_create(cec_thread, (void *)0, "cec proc");
			if (IS_ERR(cec_task)) {
				s32 err = 0;

				pr_warn("Unable to start kernel thread %s.\n\n", "cec proc");
				err = PTR_ERR(cec_task);
				cec_task = NULL;
			} else
				wake_up_process(cec_task);
		}

		pr_info("[HDMI]hdmi resume\n");
	}

exit:
	mutex_unlock(&mlock);

	hdmi_core_update_detect_time(200);/* 200ms */
	b_hdmi_suspend = false;

	return  0;
}

#if defined(CONFIG_SND_SUNXI_SOC_SUNXI_HDMIAUDIO)
extern void audio_set_hdmi_func(__audio_hdmi_func *hdmi_func);
#endif
/* extern s32 disp_set_hdmi_func(struct disp_device_func *func); */
/* extern unsigned int disp_boot_para_parse(const char *name); */

s32 hdmi_init(struct platform_device *pdev)
{
#if defined(CONFIG_SND_SUNXI_SOC_SUNXI_HDMIAUDIO)
	__audio_hdmi_func audio_func;
#if defined(CONFIG_SND_SUNXI_SOC_AUDIOHUB_INTERFACE)
	__audio_hdmi_func audio_func_muti;
#endif
#endif
	unsigned int value, output_type0, output_mode0;
	unsigned int output_type1, output_mode1;
	struct disp_device_func disp_func;
	int ret = 0;
	uintptr_t reg_base;

	hdmi_used = 0;
	b_hdmi_suspend_pre = b_hdmi_suspend = false;
	hdmi_power_used = 0;
	hdmi_used = 1;

	/*  parse boot para */
	value = disp_boot_para_parse("boot_disp");
	output_type0 = (value >> 8) & 0xff;
	output_mode0 = (value) & 0xff;

	output_type1 = (value >> 24) & 0xff;
	output_mode1 = (value >> 16) & 0xff;
	if ((output_type0 == DISP_OUTPUT_TYPE_HDMI) ||
		(output_type1 == DISP_OUTPUT_TYPE_HDMI)) {
		boot_hdmi = true;
		ghdmi.bopen = 1;
		ghdmi.mode = (output_type0 == DISP_OUTPUT_TYPE_HDMI) ? output_mode0 : output_mode1;
		ghdmi.mode = hdmi_get_vic(ghdmi.mode);
	}
	/* iomap */
	reg_base = (uintptr_t __force)of_iomap(pdev->dev.of_node, 0);
	if (reg_base == 0) {
		dev_err(&pdev->dev, "unable to map hdmi registers\n");
		ret = -EINVAL;
		goto err_iomap;
	}
	hdmi_core_set_base_addr(reg_base);

	/* get clk */
	hdmi_clk = of_clk_get(pdev->dev.of_node, 0);
	if (IS_ERR(hdmi_clk)) {
		dev_err(&pdev->dev, "fail to get clk for hdmi\n");
		goto err_clk_get;
	}

	hdmi_clk_parent = clk_get_parent(hdmi_clk);
	if (IS_ERR(hdmi_clk_parent)) {
		dev_err(&pdev->dev, "fail to get clk parent for hdmi\n");
		goto err_power;
	}

	clk_enable_count = __clk_get_enable_count(hdmi_clk);
	hdmi_ddc_clk = of_clk_get(pdev->dev.of_node, 1);
	if (IS_ERR(hdmi_ddc_clk)) {
		dev_err(&pdev->dev, "fail to get clk for hdmi ddc\n");
		goto err_power;
	}

#if defined(CONFIG_ARCH_SUN8IW12)
	hdmi_cec_clk = of_clk_get(pdev->dev.of_node, 2);
	if (IS_ERR_OR_NULL(hdmi_cec_clk)) {
		dev_err(&pdev->dev, "fail to get hdmi_cec_clk\n");
		goto err_power;
	}
#endif /*endif CONFIG_ARCH_SUN8IW12 */
	/* parse io config */
	hdmi_parse_io_config();
	mutex_init(&mlock);

	if (io_enable_count == 0) {
		hdmi_io_config(1);
		io_enable_count++;
	}
	mutex_lock(&mlock);
	if (clk_enable_count == 0) {
		hdmi_wrn("hdmi_clk_enable\n");
		ret = hdmi_clk_enable();
		clk_enable_count++;
	}
	mutex_unlock(&mlock);
	if (ret != 0) {
		clk_enable_count--;
		dev_err(&pdev->dev, "fail to enable hdmi clk\n");
		goto err_clk_enable;
	}

	INIT_WORK(&ghdmi.hpd_work, hdmi_report_hpd_work);

#if defined(CONFIG_EXTCON)
	extcon_hdmi.supported_cable = hdmi_cable;
	extcon_dev_register_attr(&extcon_hdmi, &pdev->dev);
#endif
	ret = disp_sys_script_get_item("hdmi", "hdmi_io_regulator",
				       (int *)hdmi_io_regulator, 2);
	if (ret == 2) {
		mutex_lock(&mlock);
		ret = hdmi_power_enable(hdmi_io_regulator);
		mutex_unlock(&mlock);
		if (ret) {
			dev_err(&pdev->dev, "fail to enable hdmi io power %s\n", hdmi_io_regulator);
		} else {
			++io_regulator_enable_count;
			hdmi_io_regulator_used = 1;
		}
	}


	ret = disp_sys_script_get_item("hdmi", "hdmi_power", (int *)hdmi_power, 2);
	if (ret == 2) {
		hdmi_power_used = 1;
		if (hdmi_power_used) {
			pr_info("[HDMI] power %s\n", hdmi_power);
			mutex_lock(&mlock);
			ret = hdmi_power_enable(hdmi_power);
			power_enable_count++;
			mutex_unlock(&mlock);
			if (ret != 0) {
				power_enable_count--;
				dev_err(&pdev->dev,
				"fail to enable hdmi power %s\n", hdmi_power);
				goto err_power;
			}
		}
	}


	ret = disp_sys_script_get_item("hdmi", "hdmi_cts_compatibility", &value, 1);
	if (ret == 1)
		hdmi_core_set_cts_enable(value);
	ret = disp_sys_script_get_item("hdmi", "hdmi_hdcp_enable", &value, 1);
	if (ret == 1)
		hdmi_core_set_hdcp_enable(value);

	ret = disp_sys_script_get_item("hdmi", "hdmi_hpd_mask", &value, 1);
	if (ret == 1)
		hdmi_hpd_mask = value;

	ret = disp_sys_script_get_item("hdmi", "hdmi_cec_support", &value, 1);
	if ((ret == 1) && (value == 1))
		hdmi_cec_support = true;
	hdmi_core_cec_enable(hdmi_cec_support);
	pr_info("[HDMI] cec support = %d\n", hdmi_cec_support);

	hdmi_core_initial(boot_hdmi);

	run_sem = kmalloc(sizeof(struct semaphore), GFP_KERNEL | __GFP_ZERO);
	if (!run_sem) {
		dev_err(&pdev->dev, "fail to kmalloc memory for run_sem\n");
		goto err_sem;
	}
	sema_init((struct semaphore *)run_sem, 0);

	HDMI_task = kthread_create(hdmi_run_thread, (void *)0, "hdmi proc");
	if (IS_ERR(HDMI_task)) {
		s32 err = 0;

		dev_err(&pdev->dev, "Unable to start kernel thread %s.\n\n",
			"hdmi proc");
		err = PTR_ERR(HDMI_task);
		HDMI_task = NULL;
		goto err_thread;
	}
	wake_up_process(HDMI_task);

	if (hdmi_cec_support) {
		cec_task = kthread_create(cec_thread, (void *)0, "cec proc");
		if (IS_ERR(cec_task)) {
			s32 err = 0;

			dev_err(&pdev->dev,
				"Unable to start kernel thread %s.\n\n",
				"cec proc");
			err = PTR_ERR(cec_task);
			cec_task = NULL;
			goto err_thread;
		}
		wake_up_process(cec_task);
	}

#if defined(CONFIG_SND_SUNXI_SOC_SUNXI_HDMIAUDIO)
	audio_func.hdmi_audio_enable = hdmi_audio_enable;
	audio_func.hdmi_set_audio_para = hdmi_set_audio_para;
	audio_set_hdmi_func(&audio_func);
#if defined(CONFIG_SND_SUNXI_SOC_AUDIOHUB_INTERFACE)
	audio_func_muti.hdmi_audio_enable = hdmi_audio_enable;
	audio_func_muti.hdmi_set_audio_para = hdmi_set_audio_para;
	audio_set_muti_hdmi_func(&audio_func_muti);
#endif
#endif
	memset(&disp_func, 0, sizeof(struct disp_device_func));
	disp_func.enable = hdmi_enable;
	disp_func.disable = hdmi_disable;
	disp_func.set_mode = hdmi_set_display_mode;
	disp_func.mode_support = hdmi_mode_support;
	disp_func.get_HPD_status = hdmi_get_HPD_status;
	disp_func.get_input_csc = hdmi_get_input_csc;
	disp_func.get_video_timing_info = hdmi_get_video_timming_info;
	disp_func.suspend = hdmi_suspend;
	disp_func.resume = hdmi_resume;
	disp_set_hdmi_func(&disp_func);

	return 0;

err_thread:
	kfree(run_sem);
err_sem:
	hdmi_power_disable(hdmi_power);
err_power:
	hdmi_clk_disable();
err_clk_enable:
err_clk_get:
	iounmap((char __iomem *)reg_base);
err_iomap:
	return -1;
}

s32 hdmi_exit(void)
{
	if (hdmi_used) {
		hdmi_core_exit();

		kfree(run_sem);

		run_sem = NULL;
		if (HDMI_task) {
			kthread_stop(HDMI_task);
			HDMI_task = NULL;
		}

		if (hdmi_cec_support && cec_task) {
			kthread_stop(cec_task);
			cec_task = NULL;
		}

		if ((hdmi_power_used == 1) && (power_enable_count != 0))
			hdmi_power_disable(hdmi_power);

		if (hdmi_io_regulator_used && io_regulator_enable_count)
			hdmi_power_disable(hdmi_io_regulator);
		if (clk_enable_count != 0) {
			hdmi_clk_disable();
			clk_enable_count--;
		}
	}

	return 0;
}

#ifndef CONFIG_OF
static struct resource hdmi_resource[1] = {

	[0] = {
		.start = 0x01c16000,
		.end   = 0x01c165ff,
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_device hdmi_device = {

	.name		   = "hdmi",
	.id				= -1,
	.num_resources  = ARRAY_SIZE(hdmi_resource),
	.resource		= hdmi_resource,
	.dev			= {}
};
#else
static const struct of_device_id sunxi_hdmi_match[] = {
	{ .compatible = "allwinner,sunxi-hdmi", },
	{},
};
#endif

static ssize_t 
hdmi_debug_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "debug=%s\n", hdmi_print ? "on" : "off");
}

static ssize_t 
hdmi_debug_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	if (count < 1)
		return -EINVAL;

	if (strncasecmp(buf, "on", 2) == 0 || strncasecmp(buf, "1", 1) == 0)
		hdmi_print = 1;
	else if (strncasecmp(buf, "off", 3) == 0 ||
			strncasecmp(buf, "0", 1) == 0)
		hdmi_print = 0;
	else
		return -EINVAL;

	return count;
}

#if defined(HDMI_ENABLE_DUMP_WRITE)

static ssize_t 
hdmi_dump_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{

	static long long val;
	long long reg, num, i = 0;
	unsigned char value_r[128];
	s32 ret = -1;

	ret = kstrtoll(buf, 0, &val);
	reg = (val >> 8);
	num = val & 0xff;
	pr_alert("\n");
	pr_alert("read:start add:0x%llx,count:0x%llx\n", reg, num);
	do {
		bsp_hdmi_read(reg, &value_r[i]);
		pr_alert("0x%llx: 0x%04x ", reg, value_r[i]);
		reg += 1;
		i++;
		if (i == num)
			pr_alert("\n");
		if (i % 4 == 0)
			pr_alert("\n");
	} while (i < num);

	return count;
}

static ssize_t 
hdmi_write_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	static long long val;
	long long reg;
	long long value_w;
	s32 ret = -1;

	ret = kstrtoll(buf, 0, &val);
	if (ret != 0) {
		pr_alert("convert string fail!\n");
		return 0;
	}

	reg = (val >> 16);
	value_w =  val & 0xFFFF;
	bsp_hdmi_write(reg, value_w);
	pr_alert("write 0x%llx to reg:0x%llx\n", value_w, reg);
	return count;
}

static ssize_t 
hdmi_dump_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	pr_alert("echo reg|count > dump\n");
	pr_alert("eg read star address=0x0006,count 0x10:echo 0x610 > dump\n");
	pr_alert("eg read star address=0x2000,count 0x10:echo 0x200010 > dump\n");

	return 0;
}

static ssize_t 
hdmi_write_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	pr_alert("echo reg|val > write\n");
	pr_alert("eg write value:0x13fe to address:0x0004 :echo 0x413fe > write\n");
	pr_alert("eg write value:0x6 to address:0x2000 :echo 0x20000006 > write\n");

	return 0;
}

#endif /*endif HDMI_ENABLE_DUMP_WRITE */
static DEVICE_ATTR(debug, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_debug_show, hdmi_debug_store);

#if defined(HDMI_ENABLE_DUMP_WRITE)
static DEVICE_ATTR(dump, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_dump_show, hdmi_dump_store);

static DEVICE_ATTR(write, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_write_show, hdmi_write_store);
#endif

/* s32 hdmi_hpd_state(u32 state); */
static ssize_t 
hdmi_state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "nothing\n");
}

static ssize_t 
hdmi_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	if (count < 1)
		return -EINVAL;

	if (strncasecmp(buf, "1", 1) == 0)
		hdmi_hpd_state(1);
	else
		hdmi_hpd_state(0);

	return count;
}

static DEVICE_ATTR(state, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_state_show, hdmi_state_store);

static ssize_t 
hdmi_rgb_only_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "rgb_only=%s\n", rgb_only ? "on" : "off");
}

static ssize_t 
hdmi_rgb_only_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	if (count < 1)
		return -EINVAL;

	if (strncasecmp(buf, "on", 2) == 0 || strncasecmp(buf, "1", 1) == 0)
		rgb_only = 1;
	else if (strncasecmp(buf, "off", 3) == 0 ||
			strncasecmp(buf, "0", 1) == 0)
		rgb_only = 0;
	else
		return -EINVAL;

	return count;
}

static DEVICE_ATTR(rgb_only, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_rgb_only_show, hdmi_rgb_only_store);

static ssize_t 
hdmi_hpd_mask_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "0x%x\n", hdmi_hpd_mask);
}

static ssize_t 
hdmi_hpd_mask_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int err;
	unsigned long val;

	if (count < 1)
		return -EINVAL;

	err = kstrtoul(buf, 16, &val);
	if (err) {
		pr_err("Invalid size\n");
		return err;
	}

	pr_info("val=0x%x\n", (u32)val);
	hdmi_hpd_mask = val;

	return count;
}

static DEVICE_ATTR(hpd_mask, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_hpd_mask_show, hdmi_hpd_mask_store);

static ssize_t 
hdmi_edid_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	void *pedid = (void *)hdmi_edid_get_data();

	memcpy(buf, pedid, HDMI_EDID_LEN);
	return HDMI_EDID_LEN;
}

static ssize_t 
hdmi_edid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	return count;
}

static DEVICE_ATTR(edid, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_edid_show, hdmi_edid_store);

static ssize_t 
hdmi_hdcp_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", hdmi_core_get_hdcp_enable());
}

static ssize_t 
hdmi_hdcp_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	if (count < 1)
		return -EINVAL;

	if (strncasecmp(buf, "1", 1) == 0) {
		if (hdmi_core_get_hdcp_enable() != 1)
			hdmi_core_set_hdcp_enable(1);
	} else {
		if (hdmi_core_get_hdcp_enable() != 0)
			hdmi_core_set_hdcp_enable(0);
	}

	return count;
}

// /sys/.../hdcp_enable
static DEVICE_ATTR(hdcp_enable, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_hdcp_enable_show, hdmi_hdcp_enable_store);

static int hdmi_probe(struct platform_device *pdev)
{
	hdmi_inf("hdmi_probe call\n");
	memset(&ghdmi, 0, sizeof(hdmi_info_t));
	ghdmi.dev = &pdev->dev;
	hdmi_init(pdev);
	return 0;
}

static int hdmi_remove(struct platform_device *pdev)
{
	hdmi_inf("hdmi_remove call\n");
	hdmi_exit();
	return 0;
}

static struct platform_driver hdmi_driver = {
	.probe  = hdmi_probe,
	.remove = hdmi_remove,
	.driver = {
		.name   = "hdmi",
		.owner  = THIS_MODULE,
		.of_match_table = sunxi_hdmi_match,
	},
};

static int hdmi_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int hdmi_release(struct inode *inode, struct file *file)
{
	return 0;
}


static ssize_t hdmi_read(struct file *file, char __user *buf,
			size_t count, loff_t *ppos)
{
	return -EINVAL;
}

static ssize_t hdmi_write(struct file *file, const char __user *buf,
			size_t count, loff_t *ppos)
{
	return -EINVAL;
}

static int hdmi_mmap(struct file *file, struct vm_area_struct *vma)
{
	return 0;
}

static long hdmi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	return 0;
}


static const struct file_operations hdmi_fops = {

	.owner		= THIS_MODULE,
	.open		= hdmi_open,
	.release	= hdmi_release,
	.write	    = hdmi_write,
	.read		= hdmi_read,
	.unlocked_ioctl	= hdmi_ioctl,
	.mmap	    = hdmi_mmap,
};

static struct attribute *hdmi_attributes[] = {

	&dev_attr_debug.attr,
	&dev_attr_state.attr,
	&dev_attr_rgb_only.attr,
	&dev_attr_hpd_mask.attr,
	&dev_attr_edid.attr,
	&dev_attr_hdcp_enable.attr,
#if defined(HDMI_ENABLE_DUMP_WRITE)
	&dev_attr_dump.attr,
	&dev_attr_write.attr,
#endif
	NULL
};

static struct attribute_group hdmi_attribute_group = {
	.name = "attr",
	.attrs = hdmi_attributes
};

static int __init hdmi_module_init(void)
{
	int ret = 0, err;

	alloc_chrdev_region(&devid, 0, 1, "hdmi");
	my_cdev = cdev_alloc();
	cdev_init(my_cdev, &hdmi_fops);
	my_cdev->owner = THIS_MODULE;
	err = cdev_add(my_cdev, devid, 1);
	if (err) {
		hdmi_wrn("cdev_add fail.\n");
		return -1;
	}

	hdmi_class = class_create(THIS_MODULE, "hdmi");
	if (IS_ERR(hdmi_class)) {
		hdmi_wrn("class_create fail\n");
		return -1;
	}

	ghdmi.dev = device_create(hdmi_class, NULL, devid, NULL, "hdmi");

	ret = sysfs_create_group(&ghdmi.dev->kobj, &hdmi_attribute_group);

#ifndef CONFIG_OF
	ret = platform_device_register(&hdmi_device);
#endif
	if (ret == 0)
		ret = platform_driver_register(&hdmi_driver);

	hdmi_inf("hdmi_module_init\n");
	return ret;
}

static void __exit hdmi_module_exit(void)
{
	hdmi_inf("hdmi_module_exit\n");
	platform_driver_unregister(&hdmi_driver);
#ifndef CONFIG_OF
	platform_device_unregister(&hdmi_device);
#endif
	class_destroy(hdmi_class);
	cdev_del(my_cdev);
}

late_initcall(hdmi_module_init);
module_exit(hdmi_module_exit);

MODULE_AUTHOR("tyle");
MODULE_DESCRIPTION("hdmi driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:hdmi");

这次的驱动增加了sys fs节点的一些操作特性,这里还是简单说一下,sys fs是用户空间可以操作的,这下面的节点我们可以进行echo或者cat等操作,这里代码只要是如下:

(1):
ret = sysfs_create_group(&ghdmi.dev->kobj, &hdmi_attribute_group);

(2):
static struct attribute_group hdmi_attribute_group = {
	.name = "attr",
	.attrs = hdmi_attributes
};

(3):
static struct attribute *hdmi_attributes[] = {

	&dev_attr_debug.attr,
	&dev_attr_state.attr,
	&dev_attr_rgb_only.attr,
	&dev_attr_hpd_mask.attr,
	&dev_attr_edid.attr,
	&dev_attr_hdcp_enable.attr,
#if defined(HDMI_ENABLE_DUMP_WRITE)
	&dev_attr_dump.attr,
	&dev_attr_write.attr,
#endif
	NULL
};

(4):
static ssize_t 
hdmi_hdcp_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", hdmi_core_get_hdcp_enable());
}

static ssize_t 
hdmi_hdcp_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	if (count < 1)
		return -EINVAL;

	if (strncasecmp(buf, "1", 1) == 0) {
		if (hdmi_core_get_hdcp_enable() != 1)
			hdmi_core_set_hdcp_enable(1);
	} else {
		if (hdmi_core_get_hdcp_enable() != 0)
			hdmi_core_set_hdcp_enable(0);
	}

	return count;
}

// /sys/.../hdcp_enable
static DEVICE_ATTR(hdcp_enable, S_IRUGO|S_IWUSR|S_IWGRP, hdmi_hdcp_enable_show, hdmi_hdcp_enable_store);

...

关于这部分,可以参考:https://blog.csdn.net/njuitjf/article/details/16849333

在一些时候,这些用户空间驱动程序节点是很有用处的,在实际的上层软件调用驱动时,这种方式也是有的,比如我们公司的ENGPC工程,就调用/sys/下面的节点大量使用.

就先说这么多吧,后续,如果有机会会进行一定补充.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/86250580
今日推荐