S5PV210 I2C电容触摸屏:
1.gslX680(上海思立微电子科技有限公司-多点电容触控 IC)
2.有4根线引出,SCL、SDA、IRQ、SHUTDOWN
SCL:I2C时钟线
SDA:I2C数据线
IRQ:中断线,有数据包的时候,gslX680拉高IRQ,提示主机有新的数据包到来
SHUTDOWN:睡眠线,GSL1680 SHUTDOWN 引脚拉低时,会进入睡眠模式
3.地址前7位为:0x40,写的时候最后一位是0,读的时候最后一位是1
所以写地址是x40,读地址是0x41
4.IRQ是高电平有效的中断输出引脚,当gslX600把IRQ拉高的时候,用来提醒主机,新的数据或上电复 位事件。这样减少I2C兼容总线的浪费。
5.主机应该始终只在中断到来时才读取消息。避免查询方式。如果查询不可避免,建议同一数据读两次 两次读取结果一致则数据有效。避免数据更新和读取同时发生引起的数据错误
6.睡眠模式
当把GSL1680 SHUTDOWN 引脚拉低时,会进入睡眠模式, 此时 GSL1680 消耗最少的能
量。 SHUTDOWN 引脚拉高, GSL1680 从睡眠模式唤醒,准备接受主机的命令,进入运行模式。
建议将 SHUTDOWN 引脚连接到主机的 GPIO。当触摸屏不需要运行时, 主机可以把控制
GSL1680 进入睡眠模式。
7.运行模式
GSL1680 运行模式由很多扫描周期组成。 每个周期对触摸屏扫描一次,剩下的时间里
GSL1680 处于静止。
按扫描周期的长短, GSL1680 运行模式包含三个状态, 正常,低速和绿色状态。每个状态
的扫描速度分别由 ACTIVE_SCANDELAY, LOW_SCANDELAY 和 GREEN_SCANDELAY 配置。
如果有通道被触摸, GSL1680 进入正常状态, 并停留在此状态直到距离最后一次触摸得时
间超过 ACTIVE_SCANDELAY 中的设置。 然后器件进入低速扫描状态,如果任何通道被触摸,设备将
立即返回到正常状态,否则,它停留在此状态直到停留时间超过 LOW_TIMEOUT 中的设置,然后进
入绿色状态。 一旦有通道被触摸, GSL1680 立即返回正常状态
默认的正常,低速和绿色状态的报帧率,分别为 60, 30 和 5。最大允许的报帧率是 200 帧
/秒,最小允许的报告率是 0.5 帧/秒。同一帧可能报告多个触摸点
linux中的I2C驱动,可以分为三个部分:
1.I2C核心
2.I2C总线驱动,也是I2C适配器的驱动
3.I2C设备驱动,I2C总线上挂接具体的设备的驱动
4.对应4个结构体:
i2c_adapter
i2c_driver
i2c_client
i2c_algorithm
i2c-core.c:这个文件实现了i2c核心的功能,i2c核心给总线驱动和设备驱动都提供了相应的注册/注销函数,i2C传输/发送/接收等函数(这些函数在i2c设备驱动中要用的)
i2c-dev.c:这个文件实现了 /dev/i2c-%d,负责了设备节点的实现,提供给应用层open等接口可以直接去操作i2c适配器
S5PV210 I2C控制器驱动
使用的是platform 平台总线
platform_device部分:
1.SOC I2C控制器的初始化的使用的platform平台总线,I2C1的platform_device部分:
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC1, //0xFAB00000
.end = S3C_PA_IIC1 + SZ_4K - 1, //0xFAB00000 + 0x00001000 - 1=0x0xFAB00FFF
//其实I2C1的控制器根本用不了这么多的内存, + 16就可以满足控制所有的寄存器了
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC1, //S5P_IRQ_VIC2(13)
.end = IRQ_IIC1,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_i2c1 = {
.name = “s3c2410-i2c”,
.id = 1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
2.smdkc110_machine_init
s3c_i2c1_set_platdata(NULL); 把default_i2c_data1挂接到s3c_device_i2c1.dev.platform_data中去了,并且把gpio配置函数
配置好了,这里把platform data部分进行了赋值,之后platform_device 部分就有了resource
和platform_data部分了
platform_driver部分:
1.static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = “s3c2410-i2c”,
.driver_data = TYPE_S3C2410,
}, {
.name = “s3c2440-i2c”,
.driver_data = TYPE_S3C2440,
}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = “s3c-i2c”,
.pm = S3C24XX_DEV_PM_OPS,
},
};
2.driver 匹配到 device的时候,就会去执行probe函数
3.probe函数分析
(1).获取platform data部分,在platform device部分装进去的, 其实里面就是
default_i2c_data1(s3c2410_platform_i2c类型) + s3c_i2c1_cfg_gpio
(2).定义了一个十分重要的结构体指针 struct s3c24xx_i2c *i2c; 并且分配内存
(3).i2c_adapter 结构体相关成员的赋值:名字、i2c_algorithm等
(4).自旋锁和等待队列初始化
(5).获取I2C控制器时钟,使能I2C时钟
(6).获取 platform_device 部分定义的内存资源,就是寄存器的物理内存地址(其实就是寄存器
的物理地址),有开始地址和结束地址
(7).request_mem_region 申请一块物理内存的使用权限, 为了得到寄存器的操作权限
(8).ioremap 把申请的这块内存的首地址赋值为一个指针
(9).把定义的i2c变量赋值给了i2c->adap.algo_data, i2c->adap.algo_data = i2c;
(10).i2c->adap.dev.parent = &pdev->dev; 这个是不是把/sys/bus/i2c/devices 设置为
i2c-x 的父级目录?
(11).s3c24xx_i2c_init I2C控制器初始化
第一步:配置GPIO口为I2C模式
第二步:把slave的地址写进I2CADD 寄存器
第三步:I2CCON寄存器赋值,I2CCON |= ((1<<5) | (1<<7));
1<<5: 使能I2C TX/RX中断
1<<7: master 端ack设置,TX的时候释放,RX的时候拉低
第四步:设置源时钟(控制部分的时钟),移位寄存器部分的时钟(源时钟分频得到的)
第五步:SDA delay 和 FILTER的设置
(12).得到 platform_device里面的irq号,并注册irq和处理函数
(13).i2c_add_numbered_adapter 注册i2C控制器
(14).设置 pdev->dev.p.driver_data = i2c, 把这个很重要的结构体放进去 platform device
中去了
(15).disable 时钟
4.我们的adap.algo.master_xfer函数会在i2c_transfer的时候被调用,adap.algo.master_xfer里面
只是产生一个start信号,然后触发中断,在刚才申请的irq对应的isr中去处理READ or WRITE,
所以数据控制器真正的去发送或者接收数据,就是在这个isr中的。
5.分析:
i2c_transfer
调用:
s3c24xx_i2c_xfer
调用:
s3c24xx_i2c_doxfer
<1>使能中断
调用:
s3c24xx_i2c_message_start
<1>使能RX/TX
<2>设置主机是发送/接收模式, 并设置地址,读地址和写地址是不同的
<3>使能ack,发送的时候SDA线在ack的时候,被拉低,接收的地址,SDA线被拉低
<4>把从地址写到IICDS寄存器中
<5>发出开始信号 start信号,开始信号发出之后,就会把地址写出去
<6>发出起始信号之后,会触发中断,I2C对应的中断isr会执行,里面会对每一个msg进行发送(发送完一个msg之后,如果不是最后一个,就再发start信号)
I2C 设备驱动 gslX680.c
1.i2c_driver 结构体定义:
static struct i2c_driver gsl_ts_driver = {
.driver = {
.name = GSLX680_I2C_NAME,
.owner = THIS_MODULE,
},
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = gsl_ts_suspend,
.resume = gsl_ts_resume,
#endif
.probe = gsl_ts_probe,
.remove = __devexit_p(gsl_ts_remove),
.id_table = gsl_ts_id,
};
2.i2c_board_info结构体定义:i2c_client的信息通常在BSP板文件中通过i2c_board_info填充
/* I2C1 */
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_VIDEO_TV20
{
I2C_BOARD_INFO(“s5p_ddc”, (0x74>>1)),
},
#endif
#ifdef CONFIG_TOUCHSCREEN_GSLX680
{
I2C_BOARD_INFO(“gslX680”, 0x40),
},
#endif
};
换了触摸屏的话,这里肯定是需要移植的,因为addr不是一样的
3.所以移植触摸屏驱动的时候,需要在mach-x210.c中去添加i2c_board_info信息,需要填写前7bit
的地址。
smdkc110_machine_init
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
4.使用i2c_board_info注册i2c_client之后,i2c_driver加载的时候,gsl_ts_init函数调用
i2c_add_driver函数之后就会去执行i2c_driver的probe函数部分
5.driver的probe函数分析:
(1).struct gsl_ts *ts; 定义了一个很重要的结构体指针
(2).检查adapter是否支持I2C_FUNC_I2C功能,其实就是调用adapter.algo.functionality函数
(3).给ts变量分配内存, ts->client = client;
(4).client->dev->p->driver_data = ts
(5).gslX680_ts_init
<1>input_allocate_device 分配一个input设备
<2>set_bit(EV_ABS, input_device->evbit); 支持绝对坐标事件,用于触摸屏或者摇杆
<3>set_bit(EV_KEY, input_device->evbit); 支持按键事件
<4>设置 x和y的开始坐标和终止坐标,其实就是屏幕分辨率,设置压力大小
<5>设置gslX680的irq引脚对应我们板子的哪个irq引脚 这里设置的是EINT7
<6>创建一个工作队列,并绑定gslX680_ts_worker函数
<7>input_register_device 注册input设备
(6).gslX680_init 这里是需要移植的,移植方式对应我们板子的接线
<1>GPH0_6, 对应的是shutdown引脚,上拉,输出模式,先输出0进入睡眠模式,delay
再输出1,设为运行模式
<2>GPH0_6, 对应的是IRQ引脚,设置为中断模式,上升沿触发,当触摸屏有数据的时候
会把IRQ引脚拉高,此时就会有一个上升沿
<3>这里是需要移植的,移植方式对应我们板子的接线
(7).init_chip
<1>先让gslX680睡眠,然后唤醒
<2>test_i2c,, send/recv其实最后调用的都是algotithim里面的xfer函数,都是 以i2c_msg来指导i2c控制器工作的
<1>gsl_ts_read 先读地址0xf0的数据
gsl_ts_write 先发地址0xf0写下去
i2c_master_send 把地址0xf0写下去
i2c_master_recv 读收到的数据
<2>gsl_ts_write 把地址0xf0写上数据0x12
i2c_master_send 地址和数据都写下去了
<3>gsl_ts_read 读地址0xf0的数据
gsl_ts_write
i2c_master_send
i2c_master_recv
<4>这里其实可以判断一下写到0xf0的数据和读回来的是不是一致的
<3>读写GslX680 内部的一些寄存器, 使用I2C去读写内部的一些地址
<4>reset_chip
<5>startup_chip
(8).check_mem_data 检查gslX680内部寄存器上的一些值是不是对的
(9).request_irq绑定上升沿和isr程序gsl_ts_irq
(10).绑定挂起函数gsl_ts_early_suspend,绑定重启函数gsl_ts_late_resume
(11).当中断来临的时候,就会触发gsl_ts_irq函数,执行:queue_work(ts->wq, &ts->work);
(12).就会去执行刚才绑定到workqueue的gslX680_ts_worker函数
(13).gslX680_ts_worker
gsl_ts_read 读取地址0x80上面的数据
进行多触摸点等相关的计算,得到x,y坐标
report_data
input_report_abs(ts->input, ABS_X, x);
input_report_abs(ts->input, ABS_Y, y);
input_report_key(ts->input, BTN_TOUCH, 1);
input_report_abs(ts->input, ABS_PRESSURE, 1);
input_sync(ts->input);
(14).所以数据上报的流程就是:
<1>gslX680有数据的时候,先拉高gslX680的irq引脚,这样会触发210上面的一个中断
就会去执行中断处理函数,
<2>处理函数里面调度了 workqueue任务,gslX680_ts_worker
<3>gslX680_ts_worker 函数里面进行坐标x,y,等信息的上传,最后发一个同步包
(15).tasklet 和 workqueue的区别?
中断的下半部的实现机制有:tasklet workqueue 软中断 内核线程
用tasklet和软中断绑定的下半部函数。因为工作在
中断上下文环境,运行时机是上半部返回的时候。
而workqueue 的执行上下文是内核线程,因此可以被调度和休眠