【Linux开发】linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟
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的。
、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));
268 s3c24xx_init_clocks(12000000);
//初始化系统时钟
269 s3c24xx_init_uarts(mini2440_uartcfgs,
ARRAY_SIZE(mini2440_uartcfgs));
270 }
内核启动时会通过mini2440_map_io函数调用s3c24xx_init_clocks来初始化系统时钟,接下来看一下函数原型。
、arch/arm/plat-s3c/init.c
75 void __init
s3c24xx_init_clocks(int xtal)
76 {
77 if
(xtal == 0)
78 xtal
= 12*1000*1000;
80 if
(cpu == NULL)
81 panic("s3c24xx_init_clocks:
no cpu setup?\n");
if
(cpu->init_clocks == NULL)
84 panic("s3c24xx_init_clocks:
cpu has no clock init\n");
85 else
86 (cpu->init_clocks)(xtal);
//查找struct
cpu_table
87 }
的cpu结构体在里面的成员。
、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,继续看该函数里面做了什么操作
、/arch/arm/plat-s3c24xx/s3c244x.c
125 void __init s3c244x_init_clocks(int xtal)
126 {
127 /* initialise the clocks here, to allow other things like the
128 * console to use them, and to add new ones after the initialisation
129 */
131 s3c24xx_register_baseclocks(xtal);
//三个步骤,接下来看一下
132 s3c244x_setup_clocks();
133 s3c2410_baseclk_add();
134 }
函数里面有三个操作,接下来逐个逐个看。
、arch/arm/plat-s3c/clock.c
340 int __init s3c24xx_register_baseclocks(unsigned
long xtal)
341 {
342 printk(KERN_INFO
"S3C24XX Clocks, (c) 2004 Simtec Electronics\n");
344 clk_xtal.rate
= xtal;
346 /* register our clocks */
348 if
(s3c24xx_register_clock(&clk_xtal)
< 0) //注册时钟
349 printk(KERN_ERR
"failed to register master xtal\n");
351 if
(s3c24xx_register_clock(&clk_mpll) < 0)
352 printk(KERN_ERR
"failed to register mpll clock\n");
354 if
(s3c24xx_register_clock(&clk_upll) < 0)
355 printk(KERN_ERR
"failed to register upll clock\n");
357 if
(s3c24xx_register_clock(&clk_f) < 0)
358 printk(KERN_ERR
"failed to register cpu fclk\n");
360 if
(s3c24xx_register_clock(&clk_h) < 0)
361 printk(KERN_ERR
"failed to register cpu hclk\n");
363 if
(s3c24xx_register_clock(&clk_p) < 0)
364 printk(KERN_ERR
"failed to register cpu pclk\n");
366 return
0;
367 }
跟名字一样, s3c24xx_register_baseclocks注册了系统中基本所需的时钟,如pclk等
、arch/arm/plat-s3c24xx/s3c244x.c
76 void __init_or_cpufreq s3c244x_setup_clocks(void)
77 {
78 struct
clk *xtal_clk;
79 unsigned
long clkdiv;
80 unsigned
long camdiv;
81 unsigned
long xtal;
82 unsigned
long hclk, fclk, pclk;
83 int
hdiv = 1;
85 xtal_clk
= clk_get(NULL, "xtal");
86 xtal
= clk_get_rate(xtal_clk);
87 clk_put(xtal_clk);
89 fclk
= s3c24xx_get_pll(__raw_readl(S3C2410_MPLLCON), xtal) * 2;
91 clkdiv
= __raw_readl(S3C2410_CLKDIVN);
92 camdiv
= __raw_readl(S3C2440_CAMDIVN);
94 /* work out
clock scalings */
96 switch
(clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {
97 case
S3C2440_CLKDIVN_HDIVN_1:
98 hdiv
= 1;
99 break;
101 case
S3C2440_CLKDIVN_HDIVN_2:
102 hdiv
= 2;
103 break;
105 case
S3C2440_CLKDIVN_HDIVN_4_8:
106 hdiv
= (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;
107 break;
109 case
S3C2440_CLKDIVN_HDIVN_3_6:
110 hdiv
= (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;
111 break;
112 }
114 hclk
= fclk / hdiv;
115 pclk
= hclk / ((clkdiv & S3C2440_CLKDIVN_PDIVN) ? 2 : 1);
117 /* print brief summary of clocks, etc */
119 printk("S3C244X:
core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ld MH z\n",
120 print_mhz(fclk),
print_mhz(hclk), print_mhz(pclk));
122 s3c24xx_setup_clocks(fclk,
hclk, pclk);
123 }
上面的函数通过从寄存器读取信息并给fclk、hclk和pclk赋值,然后通过 s3c24xx_setup_clocks函数添加到指定结构体。
、arch/arm/plat-s3c24xx/s3c2410-clock.c
211 int __init s3c2410_baseclk_add(void)
212 {
213 unsigned
long clkslow = __raw_readl(S3C2410_CLKSLOW);
214 unsigned
long clkcon = __raw_readl(S3C2410_CLKCON);
215 struct
clk *clkp;
216 struct
clk *xtal;
217 int
ret;
218 int
ptr;
219
220 clk_upll.enable
= s3c2410_upll_enable;
221
222 if
(s3c24xx_register_clock(&clk_usb_bus) < 0)
223 printk(KERN_ERR
"failed to register usb bus clock\n");
224
225 /* register clocks from clock array */
226
227 clkp
= init_clocks; //该结构体中存放着需要enable的时钟成员,对应寄存器CLKCON
228 for
(ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
229 /* ensure that we note the clock state */
230
231 clkp->usage
= clkcon & clkp->ctrlbit ? 1 : 0;
232
233 ret
= s3c24xx_register_clock(clkp); //注册并使能
234 if
(ret < 0) {
235 printk(KERN_ERR
"Failed to register clock %s (%d)\n",
236 clkp->name,
ret);
237 }
238 }
239
240 /* We must be careful disabling the clocks we are not intending to
241 * be using at boot time, as subsystems such as the LCD which do
242 * their own DMA requests to the bus can cause the system to lockup
243 * if they where in the middle of requesting bus access.
244 *
245 * Disabling the LCD clock if the LCD is active is very dangerous,
246 * and therefore the bootloader should be careful to not enable
247 * the LCD clock if it is not needed.
248 */
249
250 /* install (and disable) the clocks we do not need immediately */
251
252 clkp
= init_clocks_disable; //该结构体中存放着需要disable的时钟成员,对应寄存器CLKCON
253 for (ptr
= 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {
254
255 ret
= s3c24xx_register_clock(clkp); //注册,默认使能
256 if
(ret < 0) {
257 printk(KERN_ERR
"Failed to register clock %s (%d)\n",
258 clkp->name,
ret);
259 }
260
261 s3c2410_clkcon_enable(clkp,
0); //使能后又将成员disable
262 }
263
264 /* show the clock-slow value */
265
266 xtal
= clk_get(NULL, "xtal");
267
268 printk("CLOCK:
Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n",
269 print_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
275 s3c_pwmclk_init();
276 return
0;
277 }
48 int s3c2410_clkcon_enable(struct clk *clk, int enable)
49 {
50 unsigned
int clocks = clk->ctrlbit;
51 unsigned
long clkcon;
52
53 clkcon
= __raw_readl(S3C2410_CLKCON);
54
55 if
(enable)
56 clkcon
|= clocks;
57 else
58 clkcon
&= ~clocks; //传入参数为0,所以是disable
59
60 /* ensure none of the special function bits set */
61 clkcon
&= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);
62
63 __raw_writel(clkcon,
S3C2410_CLKCON);
64
65 return
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
源代码:
13th_ts_input.rar
【Linux开发】linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟的更多相关文章
- linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-119723.html linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟 xxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(八):3.设备管理的分层与面向对象思想
linux设备驱动归纳总结(八):3.设备管理的分层与面向对象思想 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现
linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(六):2.分享中断号
linux设备驱动归纳总结(六):2.分享中断号 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(五):1.在内核空间分配内存
linux设备驱动归纳总结(五):1.在内核空间分配内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(五):2.操作硬件——IO内存
linux设备驱动归纳总结(五):2.操作硬件--IO内存 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(五):3.操作硬件——IO静态映射
linux设备驱动归纳总结(五):3.操作硬件--IO静态映射 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):5.多处理器下的竞态和并发
linux设备驱动归纳总结(四):5.多处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- 【Linux开发】linux设备驱动归纳总结(四):4.单处理器下的竞态和并发
linux设备驱动归纳总结(四):4.单处理器下的竞态和并发 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
随机推荐
- ingress部署
# 手动部署 # 部署ingress-nginx 官方地址:https://github.com/kubernetes/ingress-nginx # 参考链接:https://www.jianshu ...
- js 实现深拷贝
在ECMAScript变量中包含两种不同类型的值:基本类型值和引用类型值. 基本类型值:Undefined.Null.Boolean.Number.String 引用类型值:Object.Array. ...
- Codeforces 884E E. Binary Matrix
题 OvO http://codeforces.com/contest/884/problem/E 884e 解 考虑并查集,每个点向上方和左方的点合并,答案即为1的总数减去需要合并的次数 由于只有1 ...
- 初识java的算术运算符
总所周知,算术运算符作为日常基本算法在我们上小学时接触到了,对加.减.乘.除熟练运用,这些基础算法也被引用到Java语言中.一般越基本的东西越重要,类比生活中的阳光,空气,水,土等... 闲话少叙,那 ...
- 灰度图像--图像分割 Robert算子
学习DIP第43天 转载请标明本文出处:http://blog.csdn.net/tonyshengtan,欢迎大家转载,发现博客被某些论坛转载后,图像无法正常显示,无法正常表达本人观点,对此表示很不 ...
- clone的fork与pthread_create创建线程有何不同&pthread多线程编程的学习小结(转)
进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽 象成各种数据对象:进程控制块.虚存空间.文件系统,文件I/O.信号处理函数.所以创建一个进程的 过程就是这 ...
- HDU 5867 Water problem ——(模拟,水题)
我发这题只是想说明:有时候确实需要用水题来找找自信的~ 代码如下: #include <stdio.h> #include <algorithm> #include <s ...
- java生成10个不相等的1-20的随机数
public class Test { public static void main(String[] args){ Random ran = new Random(); Set <Integ ...
- 学号 20175329 《Java程序设计》第10周学习总结
20175329 <Java程序设计>第十周学习总结 教材学习内容总结 线程与进程 进程时程序的一次动态执行过程.线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程. J ...
- mysql使用命令行执行存储过程
编写存储过程sql 以给brand表添加phone字段为例: DROP PROCEDURE IF EXISTS UpdateColum; CREATE PROCEDURE UpdateColum() ...