linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这节的内容说不上是驱动,只是写个代码让触摸屏能够工作,随便介绍一下时钟子系统(我不知道这样叫合不合适),仅次而已。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、程序不能工作
程序的源代码在13th_ts_input/13th_ts_input/1st。大致的操作就是配置寄存器,设置触摸屏为自动坐标转换模式,具体请根据程序对照S3C2440文档。
但是写完的程序不能工作,检查原因。
1、中断注册失败:
cat
/prov/interrupt就知道,系统已经注册了adc和tc中断,为了能让我的模块加载成功,内核编译时不能加入adc和触摸屏驱动。
make
menuconfig
1、Device
Drivers ---> Input device support --->[ ] Touchscreens
2、Device
Drivers ---> Character devices --->[ ] ADC driver for
FriendlyARM Mini2440/QQ2440 development boards
重新编译后启动,模块加载成功,但触摸屏还是不能工作。
2、使能adc时钟
如果有编写过裸机程序的应该知道,ad转换和触摸屏的正常工作还依赖于时钟控制寄存器的配置,必须使能adc时钟(CLKCON[15])。
但在linux下,有一套专门的规矩来使能时钟,接下来先从内核启动时的代码开始介绍:
该网友的博客上有更详细的介绍,可以看看,虽然是2410的。2410下clock源码分析
1、arch/arm/mach-s3c2440/mach-mini2440.c
265
static void __init mini2440_map_io(void)
266
{
267
s3c24xx_init_io(mini2440_iodesc, ARRAY_SIZE(mini2440_iodesc));
268s3c24xx_init_clocks(12000000);
//初始化系统时钟
269s3c24xx_init_uarts(mini2440_uartcfgs,
ARRAY_SIZE(mini2440_uartcfgs));
270
}
内核启动时会通过mini2440_map_io函数调用s3c24xx_init_clocks来初始化系统时钟,接下来看一下函数原型。
2、arch/arm/plat-s3c/init.c
75
void __init s3c24xx_init_clocks(int xtal)
76
{
77if (xtal == 0)
78xtal = 12*1000*1000;
79
80if (cpu == NULL)
81panic("s3c24xx_init_clocks: no cpu setup?\n");
82
83if (cpu->init_clocks == NULL)
84panic("s3c24xx_init_clocks: cpu has no clock init\n");
85else
86(cpu->init_clocks)(xtal);
//查找struct
cpu_table
87
}
函数的原型很简单,就是调用了cpu结构里面的成员init_clocks。接下来看一下2440的cpu结构体在里面的成员。
3、arch/arm/plat-s3c24xx/cpu.c
68
static struct cpu_table cpu_ids[] __initdata = {
69
{
70.idcode = 0x32410000,
71.idmask = 0xffffffff,
72.map_io = s3c2410_map_io,
73.init_clocks = s3c2410_init_clocks,
74.init_uarts = s3c2410_init_uarts,
75.init = s3c2410_init,
76.name = name_s3c2410
77
},
。。。。。
87
{
88.idcode = 0x32440000,
89.idmask = 0xffffffff,
90.map_io = s3c244x_map_io,
91.init_clocks = s3c244x_init_clocks,
//2440的init_clocks函数原型在这里
92.init_uarts = s3c244x_init_uarts,
93.init = s3c2440_init,
94.name = name_s3c2440
95
},
可以看,cpu->init_clocks的原型就是s4c244x_init_clocks,继续看该函数里面做了什么操作
4、/arch/arm/plat-s3c24xx/s3c244x.c
125
void __init s3c244x_init_clocks(int xtal)
126
{
127
130
131s3c24xx_register_baseclocks(xtal);
//三个步骤,接下来看一下
132s3c244x_setup_clocks();
133s3c2410_baseclk_add();
134
}
函数里面有三个操作,接下来逐个逐个看。
5、arch/arm/plat-s3c/clock.c
340
int __inits3c24xx_register_baseclocks(unsigned
long xtal)
341
{
342printk(KERN_INFO "S3C24XX Clocks, (c) 2004 Simtec
Electronics\n");
343
344clk_xtal.rate = xtal;
345
346
347
348if (s3c24xx_register_clock(&clk_xtal)
< 0)
//注册时钟
349printk(KERN_ERR "failed to register masterxtal\n");
350
351if (s3c24xx_register_clock(&clk_mpll) < 0)
352printk(KERN_ERR "failed to registermpll
clock\n");
353
354if (s3c24xx_register_clock(&clk_upll) < 0)
355printk(KERN_ERR "failed to registerupllclock\n");
356
357if (s3c24xx_register_clock(&clk_f) < 0)
358printk(KERN_ERR "failed to register cpufclk\n");
359
360if (s3c24xx_register_clock(&clk_h) < 0)
361printk(KERN_ERR "failed to register cpuhclk\n");
362
363if (s3c24xx_register_clock(&clk_p) < 0)
364printk(KERN_ERR "failed to register cpupclk\n");
365
366return 0;
367
}
跟名字一样,s3c24xx_register_baseclocks注册了系统中基本所需的时钟,如pclk等
6、arch/arm/plat-s3c24xx/s3c244x.c
76
void __init_or_cpufreqs3c244x_setup_clocks(void)
77
{
78struct clk *xtal_clk;
79unsigned long clkdiv;
80unsigned long camdiv;
81unsigned long xtal;
82unsigned long hclk, fclk, pclk;
83int hdiv = 1;
84
85xtal_clk = clk_get(NULL, "xtal");
86xtal = clk_get_rate(xtal_clk);
87clk_put(xtal_clk);
88
89fclk = s3c24xx_get_pll(__raw_readl(S3C2410_MPLLCON), xtal) * 2;
90
91clkdiv = __raw_readl(S3C2410_CLKDIVN);
92camdiv = __raw_readl(S3C2440_CAMDIVN);
93
94
95
96switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {
97case S3C2440_CLKDIVN_HDIVN_1:
98hdiv = 1;
99break;
100
101case S3C2440_CLKDIVN_HDIVN_2:
102hdiv = 2;
103break;
104
105case S3C2440_CLKDIVN_HDIVN_4_8:
106hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;
107break;
108
109case S3C2440_CLKDIVN_HDIVN_3_6:
110hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;
111break;
112}
113
114hclk = fclk / hdiv;
115pclk = hclk / ((clkdiv & S3C2440_CLKDIVN_PDIVN) ? 2 : 1);
116
117
118
119printk("S3C244X: core %ld.%03ld MHz, memory %ld.%03ld MHz,
peripheral %ld.%03ld MH z\n",
120print_mhz(fclk), print_mhz(hclk), print_mhz(pclk));
121
122s3c24xx_setup_clocks(fclk, hclk, pclk);
123
}
上面的函数通过从寄存器读取信息并给fclk、hclk和pclk赋值,然后通过s3c24xx_setup_clocks函数添加到指定结构体。
7、arch/arm/plat-s3c24xx/s3c2410-clock.c
211 int __init
s3c2410_baseclk_add(void)
212 {
213unsigned long clkslow =
__raw_readl(S3C2410_CLKSLOW);
214unsigned long clkcon =
__raw_readl(S3C2410_CLKCON);
215struct clk *clkp;
216struct clk *xtal;
217int ret;
218int ptr;
219
220clk_upll.enable =
s3c2410_upll_enable;
221
222if
(s3c24xx_register_clock(&clk_usb_bus) < 0)
223printk(KERN_ERR "failed
to register usb bus clock\n");
224
225
226
227clkp =
init_clocks;
//该结构体中存放着需要enable的时钟成员,对应寄存器CLKCON
228for
(ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
229
230
231clkp->usage = clkcon
& clkp->ctrlbit ? 1 : 0;
232
233ret = s3c24xx_register_clock(clkp);
//注册并使能
234if (ret < 0) {
235printk(KERN_ERR
"Failed to register clock %s (%d)\n",
236clkp->name,
ret);
237}
238}
239
240
249
250
251
252clkp =
init_clocks_disable;
//该结构体中存放着需要disable的时钟成员,对应寄存器CLKCON
253for(ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++,
clkp++) {
254
255ret = s3c24xx_register_clock(clkp);
//注册,默认使能
256if (ret < 0) {
257printk(KERN_ERR
"Failed to register clock %s (%d)\n",
258clkp->name,
ret);
259}
260
261s3c2410_clkcon_enable(clkp, 0);
//使能后又将成员disable
262}
263
264
265
266xtal = clk_get(NULL,
"xtal");
267
268printk("CLOCK: Slow
mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n",
269print_mhz(clk_get_rate(xtal) /
270( 2 *
S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))),
271(clkslow &
S3C2410_CLKSLOW_SLOW) ? "slow" : "fast",
272(clkslow &
S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on",
273(clkslow &
S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on");
274
275s3c_pwmclk_init();
276return 0;
277 }
48 int s3c2410_clkcon_enable(struct
clk *clk, int enable)
49 {
50unsigned int clocks =
clk->ctrlbit;
51unsigned long clkcon;
52
53clkcon =
__raw_readl(S3C2410_CLKCON);
54
55if
(enable)
56clkcon |= clocks;
57else
58clkcon &= ~clocks;
//传入参数为0,所以是disable
59
60
61clkcon &=
~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);
62
63__raw_writel(clkcon,
S3C2410_CLKCON);
64
65return 0;
66 }
可以看到,s3c2410_baseclk_add分了两部分的操作。
第一部分:获取init_clock的数据结构,并且调用s3c24xx_register_clock来配置寄存器CLKCON并默认使能init_clock结构里面指定时钟。
第二部分:获取init_clock_disable数据结构,除了调用s3c24xx_register_clock注册后,还调用3c2410_clkcon_enable来配置寄存器CLKCON来disable该init_clock_disable结构体里面的时钟。
init_clock和init_clock_disable里面的成员与寄存器CLKCON的成员对应.
接下来看看clkp的数据结构init_clock在哪里定义:
/arch/arm/plat-s3c24xx/s3c2410-clock.c
130 static struct clk init_clocks[]
= {
131 {
132.name = "lcd",
133.id = -1,
134.parent = &clk_h,
135.enable =
s3c2410_clkcon_enable,
136.ctrlbit =
S3C2410_CLKCON_LCDC,
137 }, {
138.name = "gpio",
139.id = -1,
140.parent = &clk_p,
141.enable =
s3c2410_clkcon_enable,
142.ctrlbit =
S3C2410_CLKCON_GPIO,
143 }, {
144.name =
"usb-host",
145.id = -1,
146.parent = &clk_h,
。。。。。。
可以看到,lcd、gpio等这类的时钟是系统启动时就已经使能了,所以之前我的lcd驱动才能正常工作。
90 static struct clk
init_clocks_disable[] = {
91 {
92.name = "nand",
93.id = -1,
94.parent = &clk_h,
95.enable =
s3c2410_clkcon_enable,
96.ctrlbit =
S3C2410_CLKCON_NAND,
97 }, {
98.name = "sdi",
99.id = -1,
100.parent = &clk_p,
101.enable =
s3c2410_clkcon_enable,
102.ctrlbit =
S3C2410_CLKCON_SDI,
103 }, {
104.name = "adc",
//触摸屏的时钟放在
init_clocks_disable中,所以触摸屏不能工作!
105.id = -1,
106.parent = &clk_p,
107.enable =
s3c2410_clkcon_enable,
108.ctrlbit =
S3C2410_CLKCON_ADC,
109 }, {
。。。。。
现在就可以知道,为什么触摸屏不能工作了,因为在系统启动时被禁止了。所以需要在函数中启动它。
既然知道了原因,解决方法就好办了——将adc成员从init)clocks_disable中删除并添加到init_clocks中。
同时还有一个不用修改内核代码的方法。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、clk_enable:
第二个方法很简单,从获取内核中的adc时钟,然后将它使能。
先看获取函数:
struct clk *clk_get(struct device
*dev, const char *con_id)
第一个参数中,因为clk->id一般为-1,所以直接传入NULL就可以了。如果clk->id不为-1,函数会通过第一个参数传入的dev获取dev->bus_id。
第二个参数是一个字符串,用来指定你要获取的时钟的名字。
参数传入后,内核查找到一个dev->id(或者-1)是否与clk->id一致,并且con_id与clk->name一致的时钟,如果不一致就会获取失败。
所以,我这里的函数应该是:clk_get(NULL,
“adc”)。
使能时钟使用函数:
int clk_enable(struct clk *clk)
禁止时钟使用函数:
void clk_disable(struct clk *clk)
在原来的函数中使用这三条代码后(13th_ts_input/13th_ts_input/2nd),触摸屏就能工作了。看看效果:
[root: 2nd]# insmod ts_driver.ko
hello ts
[root: 2nd]# tc down
//当接触触摸屏是打印
x:0, y:947
tc up
tc down
x:382, y:608
tc up
tc down
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、防抖(13th_ts_input/13th_ts_input/3rd)
同样的,最后在代码中添加个定时器,实现防抖功能。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx