按照天嵌官方的《linux移植之step by step》手册上的方法,做linux2.6.30.4触摸屏驱动程序的移植没有成功,经过一番摸索发现是2.6.30.4没有合适的ADC驱动,所以就着手自己做ADC和触摸屏驱动的移植,下面是我解决问题的详细过程:
1.找到天嵌给的linux2.6.30.4的源码,进入drivers/char 目录,把ADC驱动程序的源码抠出来,注意还有那个ADC驱动程序要调用的头文件,我的是直接把头文件加到ADC驱动程序的源码里面,下面是加入头文件修改后的ADC驱动程序的源代码(我把他命名为my_adc.c):
#include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #include <linux/delay.h> #include <linux/clk.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <mach/regs-clock.h> #include <plat/regs-timer.h> #include <plat/regs-adc.h> #include <mach/regs-gpio.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #ifndef _S3C2410_ADC_H_ #define _S3C2410_ADC_H_ #define ADC_WRITE(ch, prescale) ((ch)<<16|(prescale)) #define ADC_WRITE_GETCH(data) (((data)>>16)&0x7) #define ADC_WRITE_GETPRE(data) ((data)&0xff) #endif /* _S3C2410_ADC_H_ */ #undef DEBUG //#define DEBUG #ifdef DEBUG #define DPRINTK(x...) {printk("EmbedSky_adc: " x);} #else #define DPRINTK(x...) (void)(0) #endif #define DEVICE_NAME "adc" #define ch_0 0 #define ch_1 1 #define ch_2 2 #define ch_3 3 static void __iomem *base_addr; typedef struct { wait_queue_head_t wait; int channel; int prescale; }ADC_DEV; DECLARE_MUTEX(ADC_LOCK); static int ADC_enable = 0; static ADC_DEV adcdev; static volatile int ev_adc = 0; static int adc_data; static struct clk *adc_clock; #define ADCCON (*(volatile unsigned long *)(base_addr + S3C2410_ADCCON)) //ADC control #define ADCTSC (*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC)) //ADC touch screen control #define ADCDLY (*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY)) //ADC start or Interval Delay #define ADCDAT0 (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0)) //ADC conversion data 0 #define ADCDAT1 (*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1)) //ADC conversion data 1 #define ADCUPDN (*(volatile unsigned long *)(base_addr + 0x14)) //Stylus Up/Down interrupt status #define PRESCALE_DIS (0 << 14) #define PRESCALE_EN (1 << 14) #define PRSCVL(x) ((x) << 6) #define ADC_INPUT(x) ((x) << 3) #define ADC_START (1 << 0) #define ADC_ENDCVT (1 << 15) #define START_ADC_AIN(ch, prescale) do{ ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; ADCCON |= ADC_START; }while(0) int iSaveADCTSC=0; static int tq2440_adc_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { if (arg > 4) { return -EINVAL; } switch(cmd) { case ch_0: adcdev.channel=0; return 0; case ch_1: adcdev.channel=1; return 0; case ch_2: adcdev.channel=2; return 0; case ch_3: adcdev.channel=3; return 0; default: return -EINVAL; } } static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos) { char str[20]; int value; size_t len; if (down_trylock(&ADC_LOCK) == 0) { ADC_enable = 1; START_ADC_AIN(adcdev.channel, adcdev.prescale); iSaveADCTSC=ADCTSC; ADCTSC=ADCTSC&(~0x1<<2); ADCCON|=0x01; while(ADCCON&0x1); while(!ADCCON&(0x1<<15)); adc_data = ADCDAT0 & 0x3ff; ADCTSC=iSaveADCTSC; ev_adc = 0; DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ((ADCCON & 0x80) ? 1:0)); value = adc_data; sprintf(str,"%5d", adc_data); copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data)); ADC_enable = 0; up(&ADC_LOCK); len = sprintf(str, "%d\n", value); if (count >= len) { int r = copy_to_user(buffer, str, len); return r ? r : len; } else { return -1; } } else { return -1; //value = -1; } } static int tq2440_adc_open(struct inode *inode, struct file *filp) { init_waitqueue_head(&(adcdev.wait)); adcdev.channel=2; adcdev.prescale=0xff; DPRINTK( "ADC opened\n"); return 0; } static int tq2440_adc_release(struct inode *inode, struct file *filp) { DPRINTK( "ADC closed\n"); return 0; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = tq2440_adc_open, .ioctl = tq2440_adc_ioctl, .read = tq2440_adc_read, .release = tq2440_adc_release, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init dev_init(void) { int ret; base_addr=ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) { printk(KERN_ERR "failed to remap register block\n"); return -ENOMEM; } adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_enable(adc_clock); ADCTSC = 0; ret = misc_register(&misc); printk (DEVICE_NAME" initialized\n"); return ret; } static void __exit dev_exit(void) { if (adc_clock) { clk_disable(adc_clock); clk_put(adc_clock); adc_clock = NULL; } misc_deregister(&misc); } EXPORT_SYMBOL(ADC_LOCK); module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL");
上述程序的源码以后再作分析。
2.进入2.6.30.4源码目录:drivers/input/touchscreen ,把触摸屏驱动程序的源码抠出来(我把他命名为my_ts.c):
#include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <asm/io.h> #include <asm/irq.h> #include <plat/regs-adc.h> #include <mach/regs-gpio.h> #define S3C2410TSVERSION 0x0101 #define WAIT4INT(x) (((x)<<8) | S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_XY_PST(3)) #define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)) static char *tq2440ts_name = "TQ2440 TouchScreen"; static struct input_dev *dev; static long xp; static long yp; static int count; extern struct semaphore ADC_LOCK; static int OwnADC = 0; static void __iomem *base_addr; static void touch_timer_fire(unsigned long data) { unsigned long data0; unsigned long data1; int updown; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); if (updown) { if (count != 0) { long tmp; tmp = xp; xp = yp; yp = tmp; xp >>= 2; yp >>= 2; input_report_abs(dev, ABS_X, xp); input_report_abs(dev, ABS_Y, yp); input_report_key(dev, BTN_TOUCH, 1); input_report_abs(dev, ABS_PRESSURE, 1); input_sync(dev); } xp = 0; yp = 0; count = 0; iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { count = 0; input_report_key(dev, BTN_TOUCH, 0); input_report_abs(dev, ABS_PRESSURE, 0); input_sync(dev); iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); if (OwnADC) { OwnADC = 0; up(&ADC_LOCK); } } } static struct timer_list touch_timer = TIMER_INITIALIZER(touch_timer_fire, 0, 0); static irqreturn_t stylus_updown(int irq, void *dev_id) { unsigned long data0; unsigned long data1; int updown; if (down_trylock(&ADC_LOCK) == 0) { OwnADC = 1; data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); if (updown) { touch_timer_fire(0); } else { OwnADC = 0; up(&ADC_LOCK); } } return IRQ_HANDLED; } static irqreturn_t stylus_action(int irq, void *dev_id) { unsigned long data0; unsigned long data1; if (OwnADC) { data0 = ioread32(base_addr+S3C2410_ADCDAT0); data1 = ioread32(base_addr+S3C2410_ADCDAT1); xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK; count++; if (count < (1<<2)) { iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC); iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON); } else { mod_timer(&touch_timer, jiffies+1); iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC); } } return IRQ_HANDLED; } static struct clk *adc_clock; static int __init tq2440ts_init(void) { struct input_dev *input_dev; adc_clock = clk_get(NULL, "adc"); if (!adc_clock) { printk(KERN_ERR "failed to get adc clock source\n"); return -ENOENT; } clk_enable(adc_clock); base_addr=ioremap(S3C2410_PA_ADC,0x20); if (base_addr == NULL) { printk(KERN_ERR "Failed to remap register block\n"); return -ENOMEM; } iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON); iowrite32(0xffff, base_addr+S3C2410_ADCDLY); iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC); /* Initialise input stuff */ input_dev = input_allocate_device(); if (!input_dev) { printk(KERN_ERR "Unable to allocate the input device !!\n"); return -ENOMEM; } dev = input_dev; dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); dev->name = tq2440ts_name; dev->id.bustype = BUS_RS232; dev->id.vendor = 0xDEAD; dev->id.product = 0xBEEF; dev->id.version = S3C2410TSVERSION; if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, tq2440ts_name, dev)) { printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n"); iounmap(base_addr); return -EIO; } if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, tq2440ts_name, dev)) { printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n"); iounmap(base_addr); return -EIO; } printk(KERN_INFO "%s successfully loaded\n", tq2440ts_name); input_register_device(dev); return 0; } static void __exit tq2440ts_exit(void) { disable_irq(IRQ_ADC); disable_irq(IRQ_TC); free_irq(IRQ_TC,dev); free_irq(IRQ_ADC,dev); if (adc_clock) { clk_disable(adc_clock); clk_put(adc_clock); adc_clock = NULL; } input_unregister_device(dev); iounmap(base_addr); } module_init(tq2440ts_init); module_exit(tq2440ts_exit);
源码暂时不作分析。
3.个人建议自己先把上述源码都交叉编译成内核模块,至于怎么交叉编译成内核模块,这里就不再赘述了,在编译模块的过程中可以检查驱动程序的错误与否,不然你要是直接抠出来就放进内核源码里面,要是等下你编译内核的时候出错,这个就比较麻烦和耗时间了,好了,如果编译内核模块的这个过程中驱动程序源码没有什么问题的话,接下来你就可以放心大胆地将my_adc.c和my_ts.c放进内核源码中,并且修改相应的kconfig和Makefile文件了,具体步骤如下:
3.1 把my_adc.c放入内核源码drivers/char 这个目录下,并且修改这个目录下的kconfig和Makefile文件:
在kconfig里面把config TQ2440_ADC的代码改成下面所示的代码:
config MY_ADC bool "My ADC driver for TQ2440" depends on ARCH_S3C2440 default y if ARCH_S3C2440 help My ADC driver for TQ2440.
在Makefile里面把obj-$(CONFIG_TQ2440_ADC) += EmbedSky_adc.o 改成下面所示的代码:
obj-$(CONFIG_MY_ADC) += my_adc.o
3.2 把my_ts.c放入内核源码drivers/input/touchscreen下,并修改这个目录下的kconfig和Makefile文件:
在kconfig里面把原来的config TOUCHSCREEN_TQ2440的代码修改成如下所示的代码:
config TOUCHSCREEN_MY2440 tristate " MY2440 TouchScreen input driver" depends on ARCH_S3C2410 && INPUT && INPUT_TOUCHSCREEN && MY_ADC help Say Y here if you have the MY2440 TouchScreen. and depends on MY_ADC If unsure, say N. To compile this driver as a module, choose M here: the module will be called my_ts.
在Makefile里面把原来的obj-$(CONFIG_TOUCHSCREEN_TQ2440) += tq2440_ts.o 修改成下面所示的代码:
obj-$(CONFIG_TOUCHSCREEN_MY2440) += my_ts.o
4.至此修改linux内核源码的工作就结束了,接下来我们来配置和编译内核:
进入修改后的源码目录linux2.6.30.4下,找到config_EmbedSky_W43(我的显示屏是4.3寸的),然后输入指令:cp config_EmbedSky_W43 .config ,再然后输入指令:make menuconfig ,接着会弹出内核配置的图形界面,具体操作如下:
4.1 进入Device Drivers--> Character devices 把 My ADC driver for TQ2440 这个选项选上,如下图所示:
4.2 进入 Device Drivers --->Input device support ---> Touchscreens --->MY2440 TouchScreen input driver 这个选项选上,如下图所示:
4.3 最后回到 Linux Kernel Configuration ,选择 Save an Alternate Configuration File ,保存你刚才所做的所有设置。
5.好了,到此内核源码的修改和内核的配置工作你都完成了,接下来你就可以编译内核了,输入指令:make 然后就开始编译内核了,等到结束后,找到zImage.bin这个文件便是我们编译好的内核镜像文件,然后把这个内核烧写进开发板,你会发现触摸屏就奇迹般地能够用了,至此TQ2440触摸屏驱动程序的移植就结束了。