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/下面的节点大量使用.
就先说这么多吧,后续,如果有机会会进行一定补充.