Linux下I2C接口触摸屏驱动分析
linux下触摸屏驱动的移植主要包括这几个步骤: (1)确定触摸屏IC接口,了解对应接口的API函数,注册设备并加入到相应总线上 (2)关联设备与驱动,并编写具体的驱动代码 (3)熟悉linux 输入设备驱动,在驱动代码中分配一个输入设备并初始化相应数据结构,在驱动实现中引用 这里对应上面几部分,分析I2C接口下触摸屏驱动的实现。先介绍linux下I2C接口
linux下触摸屏驱动的移植主要包括这几个步骤:
(1)确定触摸屏IC接口,了解对应接口的API函数,注册设备并加入到相应总线上
(2)关联设备与驱动,并编写具体的驱动代码
(3)熟悉linux 输入设备驱动,在驱动代码中分配一个输入设备并初始化相应数据结构,在驱动实现中引用
这里对应上面几部分,分析I2C接口下触摸屏驱动的实现。先介绍linux下I2C接口与输入子系统架构,然后基于FT5x0x源码逐层展开整个移植过程的实现。
一、I2C驱动架构
linux I2C驱动架构
具体的实现可分为四个层次:
1、提供adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层。主要数据结构struct i2c_adapter,这里adapter的驱动在文件/drivers/i2c/busses/i2c-omap.c中
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class;
struct i2c_algorithm *algo;/* the algorithm to access the bus */
void *algo_data;
/* --- administration stuff. */
int (*client_register)(struct i2c_client *);
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout;
int retries;
struct device dev; /* the adapter device */
struct class_device class_dev; /* the class device */
int nr;
struct list_head clients;
struct list_head list;
char name[I2C_NAME_SIZE];
struct completion dev_released;
struct completion class_dev_released;
};
这个结构体对应一个控制器。其中包含了控制器名称,algorithm数据,控制器设备等。
在模块加载后,会调用驱动的probe函数寻找对应的adapter完成驱动与adapter的匹配。这里的probe函数为
static int __devinit omap_i2c_probe(struct platform_device *pdev)
{
struct omap_i2c_dev *dev;
struct i2c_adapter *adap;
struct resource *mem, *irq, *ioarea;
struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data;
irq_handler_t isr;
int r;
u32 speed = 0;
/* NOTE: driver uses the static register mapping */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq) {
dev_err(&pdev->dev, "no irq resource?\n");
return -ENODEV;
}
ioarea = request_mem_region(mem->start, resource_size(mem),
pdev->name);
if (!ioarea) {
dev_err(&pdev->dev, "I2C region already claimed\n");
return -EBUSY;
}
dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL);
if (!dev) {
r = -ENOMEM;
goto err_release_region;
}
if (pdata != NULL) {
speed = pdata->clkrate;
dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
} else {
speed = 100; /* Default speed */
dev->set_mpu_wkup_lat = NULL;
}
dev->speed = speed;
dev->dev = &pdev->dev;
dev->irq = irq->start;
dev->base = ioremap(mem->start, resource_size(mem));
if (!dev->base) {
r = -ENOMEM;
goto err_free_mem;
}
platform_set_drvdata(pdev, dev);
dev->reg_shift = (pdata->flags >> OMAP_I2C_FLAG_BUS_SHIFT__SHIFT) & 3;
if (pdata->rev == OMAP_I2C_IP_VERSION_2)
dev->regs = (u8 *)reg_map_ip_v2;
else
dev->regs = (u8 *)reg_map_ip_v1;
pm_runtime_enable(dev->dev);
pm_runtime_get_sync(dev->dev);
dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) & 0xff;
if (dev->rev <= OMAP_I2C_REV_ON_3430)
dev->errata |= I2C_OMAP3_1P153;
if (!(pdata->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s;
/* Set up the fifo size - Get total size */
s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
dev->fifo_size = 0x8 << s;
/*
* Set up notification threshold as half the total available
* size. This is to ensure that we can handle the status on int
* call back latencies.
*/
dev->fifo_size = (dev->fifo_size / 2);
if (dev->rev >= OMAP_I2C_REV_ON_3530_4430)
dev->b_hw = 0; /* Disable hardware fixes */
else
dev->b_hw = 1; /* Enable hardware fixes */
/* calculate wakeup latency constraint for MPU */
if (dev->set_mpu_wkup_lat != NULL)
dev->latency = (1000000 * dev->fifo_size) /
(1000 * speed / 8);
}
/* reset ASAP, clearing any IRQs */
omap_i2c_init(dev);
isr = (dev->rev < OMAP_I2C_OMAP1_REV_2) ? omap_i2c_omap1_isr :
omap_i2c_isr;
r = request_irq(dev->irq, isr, 0, pdev->name, dev);
if (r) {
dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
}
dev_info(dev->dev, "bus %d rev%d.%d.%d at %d kHz\n", pdev->id,
pdata->rev, dev->rev >> 4, dev->rev & 0xf, dev->speed);
pm_runtime_put(dev->dev);
adap = &dev->adapter;
i2c_set_adapdata(adap, dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo;
adap->dev.parent = &pdev->dev;
/* i2c device drivers may be active on return from add_adapter() */
adap->nr = pdev->id;
r = i2c_add_numbered_adapter(adap);
if (r) {
dev_err(dev->dev, "failure adding adapter\n");
goto err_free_irq;
}
return 0;
err_free_irq:
free_irq(dev->irq, dev);
err_unuse_clocks:
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
pm_runtime_put(dev->dev);
iounmap(dev->base);
err_free_mem:
platform_set_drvdata(pdev, NULL);
kfree(dev);
err_release_region:
release_mem_region(mem->start, resource_size(mem));
return r;
}
2、提供adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。覆盖图中的访问抽象层、i2c核心层。主要数据结构struct i2c_algorithm
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
int (*slave_send)(struct i2c_adapter *,char*,int);
int (*slave_recv)(struct i2c_adapter *,char*,int);
u32 (*functionality) (struct i2c_adapter *);
};
这个结构体中定义了一套控制器使用的通信方法。其中关键函数是master_xfer(),实现了在物理层面上了数据传输的过程。omap平台的实现函数为omap_i2c_xfer()
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
int i;
int r;
pm_runtime_get_sync(dev->dev);
r = omap_i2c_wait_for_bb(dev);
if (r < 0)
goto out;
if (dev->set_mpu_wkup_lat != NULL)
dev->set_mpu_wkup_lat(dev->dev, dev->latency);
for (i = 0; i < num; i++) {
r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));
if (r != 0)
break;
}
if (dev->set_mpu_wkup_lat != NULL)
dev->set_mpu_wkup_lat(dev->dev, -1);
if (r == 0)
r = num;
omap_i2c_wait_for_bb(dev);
out: pm_runtime_put(dev->dev);
return r;
}
可以看到,最终用于传输的是omap_i2c_xfer_msg()函数,函数定义在drivers/i2c/busses/i2c-omap.c文件中,具体的实现可以先不管。
3、实现i2c设备驱动中的i2c_driver接口,由结构i2c_client中的数据填充,覆盖图中的driver驱动层。主要数据结构
struct i2c_client {
unsigned int flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
int usage_count; /* How many accesses currently */
/* to the client */
struct device dev; /* the device structure */
struct list_head list;
char name[I2C_NAME_SIZE];
struct completion released;
};
这个结构体中的内容是描述设备的。包含了芯片地址,设备名称,设备使用的中断号,设备所依附的控制器,设备所依附的驱动等内容。
4、实现i2c设备所对应的具体device的驱动。覆盖图中的driver驱动层。主要数据结构struct i2c_driver
struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
int (*detach_client)(struct i2c_client *);
int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);
struct device_driver driver;
struct list_head list;
};
这个结构体对应了驱动方法,重要成员函数有probe,remove,suspend,resume与中断处理函数,也是我们需要是实现的函数。另外包括一个重要的数据结构: struct i2c_device_id *id_table; 如果驱动可以支持好几个设备,那么这里面就要包含这些设备的ID。
第一层和第二层是i2c总线驱动,属于芯片内部的驱动,在linux驱动架构中已经实现,不需要我们编写或更改。第三第四属于i2c设备驱动,需要我们根据内核提供的接口去完成具体的代码。
另外就是i2c_core层,起到了承上启下的作用,包含在开发中需要用到的核心函数。源代码位于drivers/i2c/i2c-core.c中。在这里可以看到几个重要的函数。
(1)增加/删除i2c控制器的函数
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_del_adapter(struct i2c_adapter *adap)
(2)增加/删除i2c设备的函数
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
void i2c_unregister_device(struct i2c_client *client)
(3)增加/删除设备驱动的函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
(4)I2C传输、发送和接收函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
send和receive分别都调用了transfer函数,而transfer也不是直接和硬件交互,而是调用algorithm中的master_xfer()函数。
二、linux input输入子系统架构
linux输入系统框架
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(Event Handler)、输入子系统核心层(Input Core)和输入子系统设备驱动层。在图中可以看出输入设备不止包括触摸屏,还有很多其他种类的输入设备,如键盘,鼠标等等,所以linux内核已经把各式各样的输入设备进行了抽象,提供了统一的接口,让我们把设备接入到系统中。而我们要做的工作就是申请一个输入设备结构体对象,并填充其里面的数据。
1,输入子系统事件处理层:与userspace衔接,提供处理设备事件的接口,以模块编译进内核,数据结构为input_handler
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
const struct file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
其中,struct input_device_id在后面讲到handler与input_dev匹配时将用到,还分别用到match与connect函数,具体过程下面再说。
struct file_operations提供用户空间的访问方法,这里的evdev形式的fops为:
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
event函数记录输入事件的值,驱动input_report_xxx()上报的值最终会通过这个函数保存到内核区,供用户空间访问,存储这个数值的数据结构为input_event.
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
2,输入子系统核心层: 提供构建输入设备核心方法实现与数据结构定义,在文件inpu.h与input.c文件,包括
(1)注册/注销设备
input_register_device()
input_unregister_device()
(2)注册/注销handler
input_register_handler()
input_unregister_handler()
(3)注册注销handle
input_register_handle()
input_unregister_handle()
handle是device与handler链接的纽带。其定义为
详细过程如图所示:struct input_handle { void *private; int open; const char *name; struct input_dev *dev; struct input_handler *handler; struct list_head d_node; struct list_head h_node; };
当新生成一个input_dev时,需要匹配一个input_handler,即特定输入设备相应的处理方法.使用input_attach_handler()
(1) 使用input_match_device()函数匹配,采用dev匹配handler,handler被匹配。首先调用input_match_device()函数。static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { const struct input_device_id *id; int error; id = input_match_device(handler, dev); if (!id) return -ENODEV; error = handler->connect(handler, dev, id); if (error && error != -ENODEV) pr_err("failed to attach handler %s to device %s, error: %d\n", handler->name, kobject_name(&dev->dev.kobj), error); return error; }
static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev) { const struct input_device_id *id; int i; for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS) if (id->bustype != dev->id.bustype) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR) if (id->vendor != dev->id.vendor) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT) if (id->product != dev->id.product) continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION) if (id->version != dev->id.version) continue; MATCH_BIT(evbit, EV_MAX); MATCH_BIT(keybit, KEY_MAX); MATCH_BIT(relbit, REL_MAX); MATCH_BIT(absbit, ABS_MAX); MATCH_BIT(mscbit, MSC_MAX); MATCH_BIT(ledbit, LED_MAX); MATCH_BIT(sndbit, SND_MAX); MATCH_BIT(ffbit, FF_MAX); MATCH_BIT(swbit, SW_MAX); if (!handler->match || handler->match(handler, dev)) return id; } return NULL; }
handler有某一个flag设置了,input设备对应的条件必须具备。使用MATCH_BIT宏进行匹配。
(2)匹配成功,调用handler->connect()函数关联.触摸屏是事件设备,所以将匹配一个input_handler evdev_handler,对应的函数为evdev_connect,代码存放在evdev.c中。(1)定义一个比输入设备更为具体的设备——事件设备evdev,触摸屏接入到用户空间也就是事件设备。static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id) { struct evdev *evdev; int minor; int error; for (minor = 0; minor < EVDEV_MINORS; minor++) if (!evdev_table[minor]) break; if (minor == EVDEV_MINORS) { pr_err("no more free evdev devices\n"); return -ENFILE; } evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); if (!evdev) return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list); spin_lock_init(&evdev->client_lock); mutex_init(&evdev->mutex); init_waitqueue_head(&evdev->wait); dev_set_name(&evdev->dev, "event%d", minor); evdev->exist = true; evdev->minor = minor; evdev->handle.dev = input_get_device(dev); evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; evdev->handle.private = evdev; evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; evdev->dev.release = evdev_free; device_initialize(&evdev->dev); error = input_register_handle(&evdev->handle); if (error) goto err_free_evdev; error = evdev_install_chrdev(evdev); if (error) goto err_unregister_handle; error = device_add(&evdev->dev); if (error) goto err_cleanup_evdev; return 0; err_cleanup_evdev: evdev_cleanup(evdev); err_unregister_handle: input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); return error; }
struct evdev { int open; int minor; struct input_handle handle; wait_queue_head_t wait; struct evdev_client __rcu *grab; struct list_head client_list; spinlock_t client_lock; /* protects client_list */ struct mutex mutex; struct device dev; bool exist; };
(2)初始化里面的数据。
(3)设置evdev里面的值.
(4)注册handle.
(5)因为evdev_handler可以匹配多个evdev,所以有一个数据是专门用来存储这些evdev的,所以用evdev_install_chrdev()函数把它放入数组。
(6)把设备加入内核。static int evdev_install_chrdev(struct evdev *evdev) { /* * No need to do any locking here as calls to connect and * disconnect are serialized by the input core */ evdev_table[evdev->minor] = evdev; return 0; }
3,输入子系统设备驱动层:是真正设备驱动层,有具体的硬件接口,数据结构为input_dev
这里包含了所有可能输入设备的数据域, 我们驱动要定义的输入设备的参数就包含在这个数据结构中。struct input_dev { const char *name; const char *phys; const char *uniq; struct input_id id; unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; unsigned int hint_events_per_packet; unsigned int keycodemax; unsigned int keycodesize; void *keycode; int (*setkeycode)(struct input_dev *dev,const struct input_keymap_entry *ke,unsigned int *old_keycode); int (*getkeycode)(struct input_dev *dev,struct input_keymap_entry *ke); struct ff_device *ff; unsigned int repeat_key; struct timer_list timer; int rep[REP_CNT]; struct input_mt *mt; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; int (*open)(struct input_dev *dev); void (*close)(struct input_dev *dev); int (*flush)(struct input_dev *dev, struct file *file); int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; struct device dev; struct list_head h_list; struct list_head node; unsigned int num_vals; unsigned int max_vals; struct input_value *vals; };
4,把设备注册进输入子系统里一般要经过以下步骤:
(1)申请一个输入设备,定义事件集合。关于输入事件的介绍可以看这里:Linux 内核点触摸接口协议
(2)上报从设备中读到的数据。可使用函数三、基于OMAP FT5x0x驱动源码分析void input_report_key(struct input_dev *dev, unsigned int code, int value); //上报按键事件 void input_report_rel(struct input_dev *dev, unsigned int code, int value); //上报相对坐标事件 void input_report_abs(struct input_dev *dev, unsigned int code, int value); //上报绝对坐标事件
1、板级初始化代码
板级初始化代码与具体的平台有关,负责相应平台的初始化工作,这里使用的pandaboard平台的代码,在目录arch/arm/mach-omap2/board-omap4panda.c中。触摸屏设备当然需要在这里先声明并注册进系统。具体代码如下:static struct i2c_board_info __initdata panda_i2c2_boardinfo[] = { { I2C_BOARD_INFO("ft5x06_ts", 0x38), .irq = OMAP_GPIO_IRQ(34), }, }; omap_register_i2c_bus(2, 100, panda_i2c2_boardinfo,ARRAY_SIZE(panda_i2c2_boardinfo));
声明一个i2c_board_info结构的数组,这个数组包含pandaboard板上i2c接口的信息。先使用宏I2C_BOARD_INFO宏声明设备名为ft5x06_ts,地址为0x38。要说明的是这里的地址并不是指处理器中i2c寄存器的硬件地址,而是在系统加载后,位于/sys/bus/i2c/devices下的设备名。再申请这个i2c接口的中断口为GPIO34,这从板的硬件连接图可以看出。
然后把这个结构体注册进i2c总线,这里跟具体的平台有关,使用omap_register_i2c_bus函数注册。函数定义在arch/arm/plat-omap/i2c.c。尝试跟踪中断信息存储的位置,我们首先看omap_register_i2c_bus()信息info跳入i2c_register_board_info()函数int __init omap_register_i2c_bus(int bus_id, u32 clkrate,struct i2c_board_info const *info,unsigned len) { int err; BUG_ON(bus_id < 1 || bus_id > omap_i2c_nr_ports()); if (info) { err = i2c_register_board_info(bus_id, info, len); if (err) return err; } if (!i2c_pdata[bus_id - 1].clkrate) i2c_pdata[bus_id - 1].clkrate = clkrate; i2c_pdata[bus_id - 1].clkrate &= ~OMAP_I2C_CMDLINE_SETUP; return omap_i2c_add_bus(bus_id); }
可以看到用链表存储这些设别信息,存储节点为i2c_decinfo结构i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len) { int status; down_write(&__i2c_board_lock); /* dynamic bus numbers will be assigned after the last static one */ if (busnum >= __i2c_first_dynamic_bus_num) __i2c_first_dynamic_bus_num = busnum + 1; for (status = 0; len; len--, info++) { struct i2c_devinfo *devinfo; devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL); if (!devinfo) { pr_debug("i2c-core: can't register boardinfo!\n"); status = -ENOMEM; break; } devinfo->busnum = busnum; devinfo->board_info = *info; list_add_tail(&devinfo->list, &__i2c_board_list); } up_write(&__i2c_board_lock); return status; }
这个结构会在i2c_scan_static_board_info()函数被引用struct i2c_devinfo { struct list_head list; int busnum; struct i2c_board_info board_info; };
这个函数遍历设备信息链表,找出与adapter匹配的设备,并使用i2c_new_device()添加进去。这个函数在注册adapter时被i2c_register_adapter()调用。static void i2c_scan_static_board_info(struct i2c_adapter *adapter) { struct i2c_devinfo *devinfo; down_read(&__i2c_board_lock); list_for_each_entry(devinfo, &__i2c_board_list, list) { if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, &devinfo->board_info)) dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr); } up_read(&__i2c_board_lock); }
这里创建了i2c_client对象,并把devinfo内的信息存储在它里面,下面设备驱动调用的probe函数传进的i2c_client就是这里的client。struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { struct i2c_client *client; int status; client = kzalloc(sizeof *client, GFP_KERNEL); if (!client) return NULL; client->adapter = adap; client->dev.platform_data = info->platform_data; if (info->archdata) client->dev.archdata = *info->archdata; client->flags = info->flags; client->addr = info->addr; client->irq = info->irq; strlcpy(client->name, info->type, sizeof(client->name)); /* Check for address validity */ status = i2c_check_client_addr_validity(client); if (status) { dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n", client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr); goto out_err_silent; } /* Check for address business */ status = i2c_check_addr_busy(adap, client->addr); if (status) goto out_err; client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; client->dev.of_node = info->of_node; /* For 10-bit clients, add an arbitrary offset to avoid collisions */ dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap), client->addr | ((client->flags & I2C_CLIENT_TEN) ? 0xa000 : 0)); status = device_register(&client->dev); if (status) goto out_err; dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); return client; out_err: dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x " "(%d)\n", client->name, client->addr, status); out_err_silent: kfree(client); return NULL; }
2、设备与驱动绑定
设备与驱动绑定的代码位于目录drivers/input/touchscreen/ft5x0x.c中,这个文件包含了i2c设备的具体代码,以模块的形式编译进内核。static const struct i2c_device_id ft5x0x_ts_id[] = { { FT5X0X_NAME, 0 },{ } }; MODULE_DEVICE_TABLE(i2c, ft5x0x_ts_id); static struct i2c_driver ft5x0x_ts_driver = { .probe = ft5x0x_ts_probe, .remove = __devexit_p(ft5x0x_ts_remove), .id_table = ft5x0x_ts_id, .driver = { .name = FT5X0X_NAME, .owner = THIS_MODULE, }, }; static int __init ft5x0x_ts_init(void) { return i2c_add_driver(&ft5x0x_ts_driver); } static void __exit ft5x0x_ts_exit(void) { i2c_del_driver(&ft5x0x_ts_driver); } module_init(ft5x0x_ts_init); module_exit(ft5x0x_ts_exit); MODULE_AUTHOR("<wenfs@Focaltech-systems.com>"); MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver"); MODULE_LICENSE("GPL");
首先建立设备与驱动的映射表ft5x0x_ts_id,通过宏MODULE_DEVICE_TABLE关联起来,MODULE_DEVICE_TABLE第一个参数表示这个设备id是i2c总线上了,第二个参数是指设备,当系统找到这个设备时,就会通过FT5X0X_NAME把设备与这个模块关联起来。
这是个i2c驱动模块,当然需要定义一个i2c_driver结构体来标识这个驱动,并说明实现的驱动函数有probe()和remove()函数。
之后就是添加模块初始化和退出函数,分别是在模块初始化时调用i2c_add_driver()宏加入i2c驱动,i2c_add_driver()实际上调用到的是 i2c_register_driver()函数。int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { int res; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) return -EAGAIN; /* add the driver to the list of i2c drivers in the driver core */ driver->driver.owner = owner; driver->driver.bus = &i2c_bus_type; /* When registration returns, the driver core * will have called probe() for all matching-but-unbound devices. */ res = driver_register(&driver->driver); if (res) return res; /* Drivers should switch to dev_pm_ops instead. */ if (driver->suspend) pr_warn("i2c-core: driver [%s] using legacy suspend method\n", driver->driver.name); if (driver->resume) pr_warn("i2c-core: driver [%s] using legacy resume method\n", driver->driver.name); pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); INIT_LIST_HEAD(&driver->clients); /* Walk the adapters that are already present */ i2c_for_each_dev(driver, __process_new_driver); return 0; }
在模块退出时调用i2c_del_driver删除i2c驱动。
3、设备驱动程序实现
从上面i2c_driver结构可以看出,ft5x0x实现了驱动的两个接口函数,分别为probe()和remove()函数。
probe()具体实现函数为ft5x0x_ts_probe(),这个函数在内核初始化时,如果设备和驱动匹配,将调用,实现i2c设备的初始化,具体实现:这个函数代码比较长,但是可以分部分去看,每一部分对应不同的初始化工作.大概可以分为这几个部分:static int ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ft5x0x_ts_data *ft5x0x_ts; struct input_dev *input_dev; int err = 0; int rev_id = 0; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { err = -ENODEV; goto err_out; } ft5x0x_ts = kzalloc(sizeof(*ft5x0x_ts), GFP_KERNEL); if (!ft5x0x_ts) { err = -ENOMEM; goto err_free_mem; } this_client = client; i2c_set_clientdata(client, ft5x0x_ts); rev_id = i2c_smbus_read_byte_data(client, FT5X0X_REG_FT5201ID); if (rev_id != FT5X06_ID) { err = -ENODEV; dev_err(&client->dev, "failed to probe FT5X0X touchscreen device\n"); goto err_free_mem; } INIT_WORK(&ft5x0x_ts->pen_event_work, ft5x0x_ts_pen_irq_work); ft5x0x_ts->ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev)); if (!ft5x0x_ts->ts_workqueue) { err = -ESRCH; goto err_free_thread; } err = request_irq(client->irq, ft5x0x_ts_interrupt, IRQF_DISABLED | IRQF_TRIGGER_RISING, "ft5x0x_ts", ft5x0x_ts); if (err < 0) { dev_err(&client->dev, "request irq failed\n"); goto err_free_irq; } input_dev = input_allocate_device(); if (!input_dev) { err = -ENOMEM; dev_err(&client->dev, "failed to allocate input device\n"); goto err_free_input; } ft5x0x_ts->input_dev = input_dev; #ifdef CONFIG_FT5X0X_MULTITOUCH set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit); set_bit(ABS_MT_POSITION_X, input_dev->absbit); set_bit(ABS_MT_POSITION_Y, input_dev->absbit); set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit); set_bit(ABS_PRESSURE, input_dev->absbit); input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0); input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0); input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0); #else set_bit(ABS_X, input_dev->absbit); set_bit(ABS_Y, input_dev->absbit); set_bit(ABS_PRESSURE, input_dev->absbit); set_bit(BTN_TOUCH, input_dev->keybit); input_set_abs_params(input_dev, ABS_X, 0, SCREEN_MAX_X, 0, 0); input_set_abs_params(input_dev, ABS_Y, 0, SCREEN_MAX_Y, 0, 0); input_set_abs_params(input_dev, ABS_PRESSURE, 0, PRESS_MAX, 0 , 0); #endif set_bit(EV_ABS, input_dev->evbit); set_bit(EV_KEY, input_dev->evbit); input_dev->name = FT5X0X_NAME; err = input_register_device(input_dev); if (err) { dev_err(&client->dev, "failed to register input device: %s\n", dev_name(&client->dev)); goto err_free_input; } #ifdef CONFIG_HAS_EARLYSUSPEND ft5x0x_ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; ft5x0x_ts->early_suspend.suspend = ft5x0x_ts_suspend; ft5x0x_ts->early_suspend.resume = ft5x0x_ts_resume; register_early_suspend(&ft5x0x_ts->early_suspend); #endif return 0; err_free_input: input_free_device(input_dev); err_free_irq: free_irq(client->irq, ft5x0x_ts); err_free_thread: cancel_work_sync(&ft5x0x_ts->pen_event_work); destroy_workqueue(ft5x0x_ts->ts_workqueue); i2c_set_clientdata(client, NULL); err_free_mem: kfree(ft5x0x_ts); err_out: return err; }
(1)检测适配器是否支持I2C_FUNC_I2C的通信方式 :i2c_check_functionality(client->adapter, I2C_FUNC_I2C)
(2)申请内存存储驱动的数据:ft5x0x_ts = kzalloc(sizeof(*ft5x0x_ts), GFP_KERNEL);并把驱动数据赋值给系统传过来的i2c_client数据:this_client = client; i2c_set_clientdata(client, ft5x0x_ts);
(3)验证将要通信的设备ID:rev_id = i2c_smbus_read_byte_data(client, FT5X0X_REG_FT5201ID);
(4)创建触摸事件的工作队列并初始化工作队列的处理函数:INIT_WORK(&ft5x0x_ts->pen_event_work, ft5x0x_ts_pen_irq_work); ft5x0x_ts->ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev));
(5)申请系统中断并声明中断处理函数;request_irq(client->irq, ft5x0x_ts_interrupt, IRQF_DISABLED | IRQF_TRIGGER_RISING, "ft5x0x_ts", ft5x0x_ts);
(6)分配一个输入设备实例,初始化数据并注册进系统:input_dev = input_allocate_device(); input_register_device(input_dev);
(7)如果定义了earlysuspend,声明其处理函数。earlysuspend用于对触摸屏类设备的电源管理,降低功耗。
(8)最后对执行过程中可能出现的错误进行处理。
当有触摸动作时,触摸屏将会执行(5)中声明的中断处理函数ft5x0x_ts_interrupt,具体实现:
可以看到,中断处理就是在判断工作队列在没有被挂起的情况下在触摸时间加入到工作队列中去,等待工作队列处理函数ft5x0x_ts_pen_irq_work()的处理。ft5x0x_ts_pen_irq_work()的实现:static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id) { struct ft5x0x_ts_data *ft5x0x_ts = dev_id; if (!work_pending(&ft5x0x_ts->pen_event_work)) queue_work(ft5x0x_ts->ts_workqueue, &ft5x0x_ts->pen_event_work); return IRQ_HANDLED; }
概括来说这里只做了两件事:从设备中读取数据 ;把数据上报到输入子系统中。static void ft5x0x_ts_pen_irq_work(struct work_struct *work) { int ret = -1; ret = ft5x0x_read_data(); if (ret == 0) ft5x0x_report_value(); }
(1)从设备中读取数据由函数ft5x0x_read_data()实现
从设备中读取数据使用ft5x0x_i2c_rxdata()函数:static int ft5x0x_read_data(void) { struct ft5x0x_ts_data *data = i2c_get_clientdata(this_client); struct ts_event *event = &data->event; u8 buf[32] = {0}; int ret = -1; int status = 0; #ifdef CONFIG_FT5X0X_MULTITOUCH ret = ft5x0x_i2c_rxdata(buf, 31); #else ret = ft5x0x_i2c_rxdata(buf, 7); #endif if (ret < 0) { printk("%s read_data i2c_rxdata failed: %d\n", __func__, ret); return ret; } memset(event, 0, sizeof(struct ts_event)); event->touch_point = buf[2] & 0x07; if (event->touch_point == 0) { ft5x0x_ts_inactivate(); return 1; } #ifdef CONFIG_FT5X0X_MULTITOUCH switch (event->touch_point) { case 5: event->x5 = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c]; event->y5 = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e]; status = (s16)((buf[0x1b] & 0xc0) >> 6); event->touch_ID5=(s16)(buf[0x1D] & 0xF0)>>4; if (status == 1) ft5x0x_ts_release(); case 4: event->x4 = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16]; event->y4 = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18]; status = (s16)((buf[0x15] & 0xc0) >> 6); event->touch_ID4=(s16)(buf[0x17] & 0xF0)>>4; if (status == 1) ft5x0x_ts_release(); case 3: event->x3 = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10]; event->y3 = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12]; status = (s16)((buf[0x0f] & 0xc0) >> 6); event->touch_ID3=(s16)(buf[0x11] & 0xF0)>>4; if (status == 1) ft5x0x_ts_release(); case 2: event->x2 = (s16)(buf[9] & 0x0F)<<8 | (s16)buf[10]; event->y2 = (s16)(buf[11] & 0x0F)<<8 | (s16)buf[12]; status = (s16)((buf[0x9] & 0xc0) >> 6); event->touch_ID2=(s16)(buf[0x0b] & 0xF0)>>4; if (status == 1) ft5x0x_ts_release(); case 1: event->x1 = (s16)(buf[3] & 0x0F)<<8 | (s16)buf[4]; event->y1 = (s16)(buf[5] & 0x0F)<<8 | (s16)buf[6]; status = (s16)((buf[0x3] & 0xc0) >> 6); event->touch_ID1=(s16)(buf[0x05] & 0xF0)>>4; if (status == 1) ft5x0x_ts_release(); break; default: return -1; } #else if (event->touch_point == 1) { event->x1 = (s16)(buf[3] & 0x0F)<<8 | (s16)buf[4]; event->y1 = (s16)(buf[5] & 0x0F)<<8 | (s16)buf[6]; } #endif event->pressure = 200; dev_dbg(&this_client->dev, "%s: 1:%d %d 2:%d %d \n", __func__, event->x1, event->y1, event->x2, event->y2); return 0; }
这个函数里面会构建一个i2c_msg msg结构去调用i2c_transfer()函数读取数据,在前面已经说过,i2c_transfer()函数最终调用的就是i2c_algorithm中master_xfer()函数进行实际的数据传输,这个函数在adapter驱动中实现。static int ft5x0x_i2c_rxdata(char *rxdata, int length) { int ret; struct i2c_msg msgs[] = { { .addr = this_client->addr, .flags = 0, .len = 1, .buf = rxdata, }, { .addr = this_client->addr, .flags = I2C_M_RD, .len = length, .buf = rxdata, }, }; ret = i2c_transfer(this_client->adapter, msgs, 2); if (ret < 0) pr_err("msg %s i2c read error: %d\n", __func__, ret); return ret; }
对读取到的数据处理涉及到IC里面的数据格式,查找芯片的数据手册可以看到寄存器的数据分步。
ft5x0x寄存器映射表
这里为缩小篇幅,只截取一部分寄存器,其余寄存器格式可参照既给出的寄存器信息。 结合图片可以看出,代码先根据是否多点触摸,从寄存器读取数据长度,若是只需要单点触摸就读取7个字节信息,若是多点触摸,就读取31个字节的信息,从代码可以看出最多支持五点触摸。读出信息的第三个字节的低4位为触摸点数,所以event->touch_point = buf[2] & 0x07;判断触摸点数之后便分别计算触摸点的坐标,这里以第一个点为例,其余点可以依此类推。第一个点的坐标信息包含在地址03h~06h中,坐标信息由12bit组成,分布在两个字节中,另外还包含flag标志信息,用于表示触摸点的状态,还包含触摸点的ID识别。
(2)把数据上报到输入子系统中由函数ft5x0x_report_value()实现因为触摸屏使用的是绝对坐标系,上报数据使用input_report_abs()函数,参数要注意选择合适的事件集。上报玩数据要记得同步,使用input_mt_sync()表示单个手指信息结束,使用input_sync()表示整个触摸动作的结束。static void ft5x0x_report_value(void) { struct ft5x0x_ts_data *data = i2c_get_clientdata(this_client); struct ts_event *event = &data->event; #ifdef CONFIG_FT5X0X_MULTITOUCH switch(event->touch_point) { case 5: input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, event->touch_ID5); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(data->input_dev, ABS_MT_POSITION_X, SCREEN_MAX_X - event->x5); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y5); input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1); input_mt_sync(data->input_dev); case 4: input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, event->touch_ID4); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(data->input_dev, ABS_MT_POSITION_X, SCREEN_MAX_X - event->x4); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y4); input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1); input_mt_sync(data->input_dev); case 3: input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, event->touch_ID3); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(data->input_dev, ABS_MT_POSITION_X, SCREEN_MAX_X - event->x3); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y3); input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1); input_mt_sync(data->input_dev); case 2: input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, event->touch_ID2); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(data->input_dev, ABS_MT_POSITION_X, SCREEN_MAX_X - event->x2); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y2); input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1); input_mt_sync(data->input_dev); case 1: input_report_abs(data->input_dev, ABS_MT_TRACKING_ID, event->touch_ID1); input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure); input_report_abs(data->input_dev, ABS_MT_POSITION_X, SCREEN_MAX_X - event->x1); input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y1); input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1); input_mt_sync(data->input_dev); default: break; } #else /* CONFIG_FT5X0X_MULTITOUCH*/ if (event->touch_point == 1) { input_report_abs(data->input_dev, ABS_X, SCREEN_MAX_X - event->x1); input_report_abs(data->input_dev, ABS_Y, event->y1); input_report_abs(data->input_dev, ABS_PRESSURE, event->pressure); } input_report_key(data->input_dev, BTN_TOUCH, 1); #endif /* CONFIG_FT5X0X_MULTITOUCH*/ input_sync(data->input_dev); dev_dbg(&this_client->dev, "%s: 1:%d %d 2:%d %d \n", __func__, event->x1, event->y1, event->x2, event->y2); }
remove()函数的具体实现是ft5x0x_ts_remove(),做与probe()相反的工作,释放内存。至此,我们从i2c接口及输入子系统的接口清楚了整个触摸屏驱动的结构,并详细分析了触摸屏驱动中的具体实现。static int __devexit ft5x0x_ts_remove(struct i2c_client *client) { struct ft5x0x_ts_data *ft5x0x_ts = i2c_get_clientdata(client); unregister_early_suspend(&ft5x0x_ts->early_suspend); free_irq(client->irq, ft5x0x_ts); input_unregister_device(ft5x0x_ts->input_dev); kfree(ft5x0x_ts); cancel_work_sync(&ft5x0x_ts->pen_event_work); destroy_workqueue(ft5x0x_ts->ts_workqueue); i2c_set_clientdata(client, NULL); return 0; }
更多推荐
所有评论(0)