第二期驱动篇——4.1 触摸屏驱动—基础功能实现

触摸屏驱动—基础功能实现


一、前言

  在【2.1 输入子系统——框架分析】介绍了输入子系统,在【017 ADC和触摸屏】介绍了如何编写触摸屏的裸板程序,下面将结合这两章节的内容,来编写Linux系统下的触摸屏驱动程序

二、程序编写

1、框架准备

通过分析drivers/input/keyboard/gpio_keys.cgpio_keys_probe()函数大致得到如下框架。

#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 <asm/plat-s3c24xx/ts.h>
#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>

/* 入口函数 */
static int s3c_ts_init(void)
{
	/* 1、分配input_dev结构体 */

	/* 2、设置 */
	
	/* 3、注册 */

	/* 4、硬件相关操作 */
	
	/* 5、先进入等待中断模式 */
}

/* 出口函数 */
static void s3c_ts_exit(void)
{

}

/* 修饰 */
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);

/* 协议 */
MODULE_LICENSE("GPL");

2、入口函数s3c_ts_init()编写

2.1 分配input_dev结构体

static struct input_dev *s3c_ts_inputdev;

s3c_ts_inputdev = input_allocate_device();
if (!s3c_ts_inputdev)
	return -ENOMEM;

2.2 设置

在这个的设置中,我们需要设置允许发生哪类事件,这类事件的哪些事件
通过分析drivers/input/keyboard/gpio_keys.c文件可以知道,通过调用set_bit()函数实现。

/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, s3c_ts_inputdev->evbit);			//按键类
	set_bit(EV_ABS, s3c_ts_inputdev->evbit);			//绝对位移事件类
	
	/* 2.2 能产生这类事件里的哪些事件 */
	set_bit(BTN_TOUCH, s3c_ts_inputdev->keybit);

	/* 表示支持绝对值x坐标,并设置它在坐标系中的最小值和最大值,以及干扰值和平焊位置 */
	input_set_abs_params(s3c_ts_inputdev, ABS_X, 0, 0x3FF, 0, 0);
	input_set_abs_params(s3c_ts_inputdev, ABS_Y, 0, 0x3FF, 0, 0);
	input_set_abs_params(s3c_ts_inputdev, ABS_PRESSURE, 0, 1, 0, 0);

2.3 注册设备

error = input_register_device(s3c_ts_inputdev);
if (error) {
	printk(KERN_ERR "Unable to register touch screen input device\n");
	goto fail;
	}

2.4 硬件相关操作

  1. 设置时钟:s3c2440为片上系统,有很多的模块,在上电时就会把不用的模块关上的时钟关掉。
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);
  1. 定义ADC与触摸屏使用到寄存器的结构体,完成内存映射
struct s3c_ts_regs{
	unsigned long ADCCON;
	unsigned long ADCTSC;
	unsigned long ADCDLY;
	unsigned long ADCDAT0;
	unsigned long ADCDAT1;
	unsigned long ADCUPDN;
};

static volatile struct s3c_ts_regs *s3c_ts_regs;

s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
if (s3c_ts_regs == NULL) {
	printk(KERN_ERR "Failed to remap register 0x58000000\n");
	return -ENOMEM;
}
  1. 设置相关寄存器
/* ADCCON
 * bit[14]  : 1-A/D converter prescaler enable
 * bit[13:6]: A/D converter prescaler value,
 *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
 * bit[0]	: A/D conversion starts by enable. 先设为0
 */
s3c_ts_regs->ADCCON &=~ ((1 << 14) | (0xff << 6) | (1 << 0));		//先清零
s3c_ts_regs->ADCCON |= ((1 << 14)  | (49 << 6)   | (0 << 0));		//后设置	
  1. 设置触摸屏中断
error = request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
if (error != 0) {
	printk(KERN_ERR "s3c2440_ts.c: Could not allocate ts IRQ_TC !\n");
	printk("error = %d\n\r", error);
	iounmap(s3c_ts_regs);
	goto fail;
}

2.5 先进入等待中断模式

enter_wait_pen_down_mode();

3、出口函数s3c_ts_exit()的编写

static void s3c_ts_exit(void)
{
	iounmap(s3c_ts_regs);							//取消映射
	free_irq(IRQ_TC, NULL);							//释放中断
	input_unregister_device(s3c_ts_inputdev);		//注销设备
	input_free_device(s3c_ts_inputdev);				//释放input_dev结构体
}

4、其他函数编写

4.1 等待按键按下模式

static void enter_wait_pen_down_mode(void)
{
	s3c_ts_regs->ADCTSC = 0xd3;
}

4.2 等待按键松开模式

static void enter_wait_pen_up_mode(void)
{
	s3c_ts_regs->ADCTSC = 0x1d3;
}

4.3 触摸屏中断函数

static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
	if (s3c_ts_regs->ADCDAT0 & (1<<15))
	{
		printk("pen up\n");
		enter_wait_pen_down_mode();
	}
	else
	{
		printk("pen down\n");
		enter_wait_pen_up_mode();
	}
	return IRQ_HANDLED;
}

5、完整的程序


#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 <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>

struct s3c_ts_regs{
	unsigned long ADCCON;
	unsigned long ADCTSC;
	unsigned long ADCDLY;
	unsigned long ADCDAT0;
	unsigned long ADCDAT1;
	unsigned long ADCUPDN;
};

static struct input_dev *s3c_ts_inputdev;
static volatile struct s3c_ts_regs *s3c_ts_regs;

/* 等待按键按下模式 */
static void enter_wait_pen_down_mode(void)
{
	s3c_ts_regs->ADCTSC = 0xd3;
}

/* 等待按键松开模式 */
static void enter_wait_pen_up_mode(void)
{
	s3c_ts_regs->ADCTSC = 0x1d3;
}

/* 触摸屏中断函数 */
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
	if (s3c_ts_regs->ADCDAT0 & (1<<15))
	{
		printk("pen up\n");
		enter_wait_pen_down_mode();
	}
	else
	{
		printk("pen down\n");
		enter_wait_pen_up_mode();
	}
	return IRQ_HANDLED;
}

/* 入口函数 */
static int s3c_ts_init(void)
{
	int error = 0;
	struct clk	*adc_clock;
	
	/* 1、分配input_dev  结构体 */
	s3c_ts_inputdev = input_allocate_device();
	if (!s3c_ts_inputdev)
		return -ENOMEM;

	/* 2、设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_KEY, s3c_ts_inputdev->evbit);			//按键类
	set_bit(EV_ABS, s3c_ts_inputdev->evbit);			//绝对位移事件类
	
	/* 2.2 能产生这类事件里的哪些事件 */
	set_bit(BTN_TOUCH, s3c_ts_inputdev->keybit);

	/* 表示支持绝对值x坐标,并设置它在坐标系中的最小值和最大值,以及干扰值和平焊位置 */
	input_set_abs_params(s3c_ts_inputdev, ABS_X, 0, 0x3FF, 0, 0);
	input_set_abs_params(s3c_ts_inputdev, ABS_Y, 0, 0x3FF, 0, 0);
	input_set_abs_params(s3c_ts_inputdev, ABS_PRESSURE, 0, 1, 0, 0);
	
	/* 3、注册 */
	error = input_register_device(s3c_ts_inputdev);
	if (error) {
		printk(KERN_ERR "Unable to register touch screen input device\n");
		goto fail;
	}

	/* 4、硬件相关操作 */
	/* 4.1 使能时钟(CLKCON[15]):s3c2440为片上系统,有很多的模块,在上电时就会把不用的关上 */
	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);

	/* 4.2 设置与ADC/TOUCH相关寄存器 */
	s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
	if (s3c_ts_regs == NULL) {
		printk(KERN_ERR "Failed to remap register 0x58000000\n");
		return -ENOMEM;
	}

	/* ADCCON
	 * bit[14]  : 1-A/D converter prescaler enable
	 * bit[13:6]: A/D converter prescaler value,
	 *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
	 * bit[0]	: A/D conversion starts by enable. 先设为0
	 */
	s3c_ts_regs->ADCCON &=~ ((1 << 14) | (0xff << 6) | (1 << 0));		//先清零
	s3c_ts_regs->ADCCON |= ((1 << 14)  | (49 << 6)   | (0 << 0));		//后设置	

	/* 4.3 注册中断 */
	error = request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
	if (error != 0) {
		printk(KERN_ERR "s3c2440_ts.c: Could not allocate ts IRQ_TC !\n");
		printk("error = %d\n\r", error);
		iounmap(s3c_ts_regs);
		goto fail;
	}

	/* 5、先进入等待中断模式 */
	enter_wait_pen_down_mode();
	return error;
	
fail:
	input_free_device(s3c_ts_inputdev);
	return error;
}

/* 出口函数 */
static void s3c_ts_exit(void)
{
	iounmap(s3c_ts_regs);							//取消映射
	free_irq(IRQ_TC, NULL);							//释放中断
	input_unregister_device(s3c_ts_inputdev);		//注销设备
	input_free_device(s3c_ts_inputdev);				//释放input_dev结构体
}

/* 修饰 */
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);

/* 协议 */
MODULE_LICENSE("GPL");

三、编译与运行

在第一次加载到内核中时,报了如下错误:

  • 原因:内核中已经有了一个触摸屏的驱动了
    在这里插入图片描述
  • 解决方法:重新编译内核
    1、在系统目录下使用make menuconfig
    2、去到内核中自掉的触摸屏驱动
    在这里插入图片描述
    3、make uImage重新编译内核,采用新内核启动

可以看到,程序能成功的运行了。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/106723434