Simulate HDMI CEC by GPIO

Simulate HDMI CEC by GPIO

Use the gpio tosimulate the function of HDMI CEC.

 

分为两部分介绍:

HDMI CEC 的协议标准

Rockchip 使用GPIO 模拟CEC的软件


1,HDMI CEC的协议标准

1.1   具体的CEC标准在“HDMI1.4b  Spec”的217页.

Hdmi 1.4spec的一个下载地址:

http://download.youkuaiyun.com/download/jasonwan23/4882713

 

1.2  CEC的硬件要求

接27k ohms 上拉到3.3v。

 

1.3  CEC的bit 时序

1.3.1       Start Bit Timing


Start bit总长度4.5ms,其中低电平3.7ms,高电平0.8ms。容错时间是前后0.2ms。

 

1.3.2       Data Bit Timing



Logic“0”:Start bit总长度2.4ms,其中低电平1.5ms,高电平0.9ms。容错时间是前后0.2ms。

Logic“1”:Start bit总长度2.4ms,其中低电平0.6ms,高电平1.8ms。容错时间是前后0.2ms。

注意:

start bit、data bit0、data bit1,在上面的波形图中,高电平之后拉低。

实际当中,此拉低动作由下一码元去拉低,而非本次码元自己拉低(需要释放CEC到高电平)。

Start bit+1+0的持续的波形图如下(最后的拉低为下一码元的动作):

 

1.4  CEC的数据格式

1.4.1       Cec 数据的构成和长度

cec发送data都是由frame构成的。

每一个frame都是由startbit和一个或多个Block组成。

HeaderBlock和每个Data Block的大小都是 10 bit (8bit data + 1bit EOM + 1bit ACK)。

总长度是受限制的,最多只能传16个Block(包括HeaderBlock)。

 

1.4.2       Header/Data Block description


Header Block和每个Data Block的大小都是 10 bit (8bit data + 1bit EOM + 1bit ACK)。

高位在前,传输的时候从高位往地位开始传送。

static int GPIO_CecSendByte(char value,char EOM) {
 for(i = 7; i>= 0; i--)
cec_send_bit((value >> i) & 0x1);
cec_send_bit(EOM);
returncec_read_ack();
 }


1.4.3       EOM (End of Message)

logic“1”表示后面还有Block。

logic“0”表示后面没有Block。

 

1.4.4       ACK (Acknowledge)

A,定向通信(有具体某个设备的destination):

1,发送端应该set 1(NAK)

2,接收端应该set 0(ACK),表示接受成功

 

B,广播通信(destination为广播地址)

1,发送端应该set 1(NAK)

2,接收端set 0(ACK) 表示拒绝接受该广播。

3,接收端set 1(NAK) 表示接受该广播。

 

注意:

即便是发送端,在readACK阶段也需要先发送NACK(logic1)出去给接收端。

NAK logic“1”,在timing上,是0.6ms的低电平。而ACK是logic“0”,是1.5ms的低电平。

发送端在读ACK时候,先发出logic1,会先拉低0.6ms,然后再释放CEC,等待高电平,读出低电平的持续时间。

如果释放CEC的时候,由于上拉特性,应该自动回到高电平。此时读出的低电平时间是0.6ms,判断为logic1,即NAK

如果释放cec的时候,由于接收端有回应,接收端拉低,使低电平保持到1.5ms,然后才由接收端释放cec,自动回到高电平(发送端已经在0.6ms的时候释放cec)。此时读出的低电平时间是1.5ms,判断为logic0,即ACK

static int cec_read_ack(void)
{
                structtimeval  ts_start, ts_end;
                unsignedint time;
               
                do_gettimeofday(&ts_start);
                gpio_set_value(GPIOCec.gpio,GPIO_LOW);/*EOM发送完是高电平,此时要先拉低进低电平*/
                udelay(600);
                gpio_direction_input(GPIOCec.gpio);/*发送端持续拉低0.6ms后释放cec,即发送logic 1*/
                udelay(100);
                while(gpio_get_value(GPIOCec.gpio)== 0);/*等待回到高电平*/
                do_gettimeofday(&ts_end);
                time= GPIO_CecComputePeriod(&ts_start, &ts_end);/*计算低电平持续时间*/
                if(time> 1300 && time < 1700) {
                                udelay(900);
                                return0;/*延时掉logic0 后续的高电平时间*/
                }
                else{
                                udelay(1800);
                                return1; /*延时掉logic1 后续的高电平时间*/
                }
}
 

 

1.4.5       Header Block Details


这里的initator、destination指的是发送端和接收端的逻辑地址(LA)。

 

不同类型设备的LA在spec里的定义:

   CEC_LOGADDR_TV          = 0x00,
CEC_LOGADDR_PLAYBACK1   = 0x04,    // DVD1 in Spev v1.3
CEC_LOGADDR_UNREGORBC   = 0x0F/*广播地址*/

填充Header Block里的LA地址:

#defineMAKE_SRCDEST( src, dest) (( src << 4) | dest )
GPIOCec.address_logic = CEC_LOGADDR_PLAYBACK1;/*需要polling到可用的LA*/
cecFrame.srcDestAddr   = MAKE_SRCDEST( GPIOCec.address_logic, CEC_LOGADDR_TV);

 

1.5   Logicaddress 和Physical address

PA是从电视的edid里拿出来的,LA是设备自己发message polling得到的。

PA:在edid的VSDB的第4和第5字节,PA =((data4 << 8) | data5);

 

LA :发送端将Header Block里的initator、destination都填上CEC_LOGADDR_PLAYBACK1,发出去。看看有没有收到ACK,没有收到就采用该地址作为自己的LA。如果有收到ACK,表示该LA已经被其他设备使用。改用CEC_LOGADDR_PLAYBACK2再polling。。。

 

广播地址LA: CEC_LOGADDR_UNREGORBC   = 0x0F/*广播地址*/

接收端收到广播,无需回应ACK。如果回ACK,反而表示拒绝该广播。

 

1,Gpio模拟CEC 软件

 

Github上,完整的Rockchip使用gpio模拟cec的代码:

https://github.com/virajkanwade/rk3188_android_kernel/blob/e6afb80688d7f5a3628dce8d733f653abd7f4a53/drivers/video/rockchip/hdmi/softcec/gpio-cec.c

 

 

下载下来的部分RK代码和在MTK平台上使用的部分代码:

 

2.1   软件结构

 

2.2   CEC软件在mtk平台上的修改

1.2.1       使用pinctrl操作gpio。

1.2.2       加入local_irq_disable()防止发送时候被中断打断。

1.2.3       使用udelay替代usleep_range。

1.2.4       修改部分ACK\NAK错误。

1.2.5       由于平台限制,RX的eint无法实现,RX未验证。

1.2.6      mtk平台测试  gpio-cec.c

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
//#include <mach/gpio.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include "gpio-cec.h"

 GPIO_Cec_t GPIOCec;

static void GPIOCecWorkFunc(struct work_struct *work);
static void GPIO_CecStartRead(void);
static char LA_Player[3] = { CEC_LOGADDR_PLAYBACK1, CEC_LOGADDR_PLAYBACK2, CEC_LOGADDR_PLAYBACK3 };

size_t hdmi_gpio_cec_log = hdmi_gpio_cec_log_all;
struct platform_device *hdmitx_pdev = NULL;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_cec_default;
struct pinctrl_state *pinctrl_hdmi_dtsi_soft_cec_pin_in;
struct pinctrl_state *pinctrl_hdmi_dtsi_soft_cec_pin_out_high;
struct pinctrl_state *pinctrl_hdmi_dtsi_soft_cec_pin_out_low;
struct pinctrl_state *pinctrl_hdmi_dtsi_sda_out_high;
struct pinctrl_state *pinctrl_hdmi_dtsi_sda_out_low;
struct pinctrl_state *pinctrl_hdmi_dtsi_sda;
struct pinctrl_state *pinctrl_hdmi_dtsi_sda_in;
struct pinctrl_state *pinctrl_hdmi_dtsi_sck_out_high;
struct pinctrl_state *pinctrl_hdmi_dtsi_sck_out_low;
struct pinctrl_state *pinctrl_hdmi_dtsi_sck;


static inline unsigned int GPIO_CecComputePeriod(struct timeval *pre_time, struct timeval *cur_time)
{
	if(pre_time->tv_sec == 0 && pre_time->tv_usec == 0) {
		HDMI_GPIO_CEC_LOG_DBG("%s tv_sec == 0 && pre_time->tv_usec == 0\n", __FUNCTION__);
		return 0;
	} else
		return (cur_time->tv_sec - pre_time->tv_sec)*1000000 + (cur_time->tv_usec - pre_time->tv_usec);
}

static void GPIO_CecSubmitWork(int event, int delay, void *data)
{
	struct GPIO_Cec_delayed_work *work;

	HDMI_GPIO_CEC_LOG_DBG("%s event %04x delay %d\n", __FUNCTION__, event, delay);
	
	work = kmalloc(sizeof(struct GPIO_Cec_delayed_work), GFP_ATOMIC);

	if (work) {
		INIT_DELAYED_WORK(&work->work, GPIOCecWorkFunc);
		work->event = event;
		work->data = data;
		queue_delayed_work(GPIOCec.workqueue,
				   &work->work,
				   msecs_to_jiffies(delay));
	} else {
		HDMI_GPIO_CEC_LOG_DBG("GPIO CEC: Cannot allocate memory to "
				    "create work\n");;
	}
}

int cpu_irq_disable_times = 0;
int is_cpu_irq_disable = 0;
static void cec_local_irq_enable(unsigned int enable)
{

	if (enable == 1 && cpu_irq_disable_times == 0)
		return;

	if (enable == 0) {
		cpu_irq_disable_times = cpu_irq_disable_times + 1;
	} else {
		cpu_irq_disable_times = cpu_irq_disable_times - 1;
	}

	if (cpu_irq_disable_times == 1 && enable == 0) {
		local_irq_disable();
		is_cpu_irq_disable = 1;
	} else if (cpu_irq_disable_times == 0 && enable == 1) {
		local_irq_enable();
		is_cpu_irq_disable = 0;
	}
}


#if GPIO_CEC_DISABLE_LOCAL_IRQ
static void cec_udelay(unsigned long u)
{
	cec_local_irq_enable(0);
	udelay(u);
	cec_local_irq_enable(1);
}

static void cec_send_bit(char bit)
{
	//struct timeval	ts_start, ts_end;
	//unsigned int time_low, time_high;

	if (is_cpu_irq_disable == 1) {
		HDMI_GPIO_CEC_LOG_DBG("[ERROR]cec_send_bit is_cpu_irq_disable=1\n");
	}

	GPIO_CecPinSet(SDA_PINCTRL_MODE_OUT_LOW);//chunhui

	GPIO_CecPinSet(CEC_PINCTRL_MODE_OUT_LOW);//chunhui
	//gpio_set_value(GPIOCec.gpio, GPIO_LOW);
	//gpio_direction_output(GPIOCec.gpio, GPIO_DIR_OUT);//chunhui
	//gpio_set_value(GPIOCec.gpio, GPIO_LOW);//chunhui
	if(bit == BIT_0)
		cec_udelay(BIT_0_LOWLEVEL_PERIOD_NOR);
	else if(bit == BIT_1)
		cec_udelay(BIT_1_LOWLEVEL_PERIOD_NOR);
	else {
		//do_gettimeofday(&ts_start);
		cec_udelay(BIT_START_LOWLEVEL_PERIOD_NOR);
	}
	//gpio_set_value(GPIOCec.gpio, GPIO_HIGH);
	GPIO_CecPinSet(CEC_PINCTRL_MODE_OUT_HIGH);//chunhui
	//gpio_direction_output(GPIOCec.gpio, GPIO_DIR_OUT);//chunhui
	//gpio_set_value(GPIOCec.gpio, GPIO_HIGH);//chunhui
	if(bit == BIT_0)
		cec_udelay(BIT_0_HIGHLEVEL_PERIOD_NOR - BIT_0_LOWLEVEL_PERIOD_NOR);
	else if(bit == BIT_1)
		cec_udelay(BIT_1_HIGHLEVEL_PERIOD_NOR - BIT_1_LOWLEVEL_PERIOD_NOR);
	else {
		//do_gettimeofday(&ts_end);
		//time_low = GPIO_CecComputePeriod(&ts_start, &ts_end);
		//do_gettimeofday(&ts_start);
		cec_udelay(BIT_START_HIGHLEVEL_PERIOD_NOR - BIT_START_LOWLEVEL_PERIOD_NOR);
		//do_gettimeofday(&ts_end);
		//time_high = GPIO_CecComputePeriod(&ts_start, &ts_end);
		//HDMI_GPIO_CEC_LOG_DBG("START,time_low=%d,time_high=%d\n", time_low, time_high);
	}
	GPIO_CecPinSet(SDA_PINCTRL_MODE_OUT_HIGH);//chunhui
	GPIO_CecPinSet(SDA_PINCTRL_MODE);//chunhui
}
#else

static void cec_send_bit(char bit)
{
#if GPIO_CEC_USE_HRTIMER

	struct timeval	ts_start, ts_end;
	unsigned int time_low, time_high;


	gpio_set_value(GPIOCec.gpio, GPIO_LOW);
	//gpio_direction_output(GPIOCec.gpio, GPIO_DIR_OUT);//chunhui
	//gpio_set_value(GPIOCec.gpio, GPIO_LOW);//chunhui
	if(bit == BIT_0)
		usleep_range(BIT_0_LOWLEVEL_PERIOD_MIN, BIT_0_LOWLEVEL_PERIOD_MAX);
	else if(bit == BIT_1)
		usleep_range(BIT_1_LOWLEVEL_PERIOD_MIN, BIT_1_LOWLEVEL_PERIOD_MAX);
	else {
		do_gettimeofday(&ts_start);
		usleep_range(BIT_START_LOWLEVEL_PERIOD_MIN, BIT_START_LOWLEVEL_PERIOD_MAX);
	}
	gpio_set_value(GPIOCec.gpio, GPIO_HIGH);
	//gpio_direction_output(GPIOCec.gpio, GPIO_DIR_OUT);//chunhui
	//gpio_set_value(GPIOCec.gpio, GPIO_HIGH);//chunhui
	if(bit == BIT_0)
		usleep_range(BIT_0_HIGHLEVEL_PERIOD_MIN - BIT_0_LOWLEVEL_PERIOD_MIN, BIT_0_HIGHLEVEL_PERIOD_MAX - BIT_0_LOWLEVEL_PERIOD_MAX);
	else if(bit == BIT_1)
		usleep_range(BIT_1_HIGHLEVEL_PERIOD_MIN - BIT_1_LOWLEVEL_PERIOD_MIN, BIT_1_HIGHLEVEL_PERIOD_MAX - BIT_1_LOWLEVEL_PERIOD_MAX);
	else {
		do_gettimeofday(&ts_end);
		time_low = GPIO_CecComputePeriod(&ts_start, &ts_end);
		do_gettimeofday(&ts_start);
		usleep_range(BIT_START_HIGHLEVEL_PERIOD_MIN - BIT_START_LOWLEVEL_PERIOD_MIN, BIT_START_HIGHLEVEL_PERIOD_MAX - BIT_START_LOWLEVEL_PERIOD_MAX);
		do_gettimeofday(&ts_end);
		time_high = GPIO_CecComputePeriod(&ts_start, &ts_end);
		HDMI_GPIO_CEC_LOG_DBG("START,time_low=%d,time_high=%d\n", time_low, time_high);
	}
	
#else
	struct timeval  ts_start, ts_end;
	unsigned int time = 0 ,count = 0, last_time;
	
	gpio_set_value(GPIOCec.gpio, GPIO_LOW);
	do_gettimeofday(&ts_start);
	while(1) {
		do_gettimeofday(&ts_end);
		last_time = time;
		time = GPIO_CecComputePeriod(&ts_start, &ts_end);
		count++;
		if( (( bit == BIT_1) && (time > BIT_1_LOWLEVEL_PERIOD_NOR)) || 
			(( bit == BIT_0) && (time > BIT_0_LOWLEVEL_PERIOD_NOR)) ||
			(( bit == BIT_START) && (time > BIT_START_LOWLEVEL_PERIOD_NOR)) )
		{
//			HDMI_GPIO_CEC_LOG_DBG(" 0 level is %d %d lasttiem %d\n", time, count, last_time);
			break;
		}
	}
	gpio_set_value(GPIOCec.gpio, GPIO_HIGH);
	count = 0;
	while(1) {
		do_gettimeofday(&ts_end);
		last_time = time;
		time = GPIO_CecComputePeriod(&ts_start, &ts_end);
		count++;
		if( (( bit == BIT_1) && (time > BIT_1_HIGHLEVEL_PERIOD_NOR)) || 
			
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值