<strong>I2C</strong>
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。
对于linux输入子系统的框架结构如下图1所示:
图1 linux输入子系统框架结构
由上图所展现的内容就是linux输入子系统的分层结构。
/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
下图2简单描述了linux输入子系统的事件处理机制:
图2 linux输入子系统事件处理机制
由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。
作为输入设备的驱动开发者,需要做以下几步:
1、 在驱动加载模块中,设置你的input设备支持的事件类型,类型参见表1设置
2、 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
3、 将输入设备注册到输入子系统中
本文是基于TI的TCA8424芯片所修改的驱动
static const struct i2c_device_id tca8424_id[] = {
{ TCA8424_NAME, 8424, },
{},
};
MODULE_DEVICE_TABLE(i2c, tca8424_id);
// #define TCA8424_NAME "tca8424_keypad"
static struct i2c_driver tca8424_keypad_driver = {
.driver = {
.name = TCA8424_NAME,
.owner = THIS_MODULE,
},
.probe = tca8424_keypad_probe,
.id_table = tca8424_id,
};
//将驱动添加到i2c的设备列表中
static int __init tca8424_keypad_init(void)
{
return i2c_add_driver(&tca8424_keypad_driver);
}
接着主要看看probe函数,上面已经说过input设备需要3个只要步骤,首先是定义input设备事件类型
static int tca8424_keypad_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
const struct tca8424_keypad_platform_data *pdata =
dev_get_platdata(dev);
struct tca8424_keypad *keypad_data;
struct input_dev *input;
const struct matrix_keymap_data *keymap_data = NULL;
u32 rows = 0, cols = 0;
bool rep = false;
bool irq_is_gpio = false;
int irq;
int error, row_shift, max_keys;
/* Copy the platform data */
if (pdata) {
if (!pdata->keymap_data) {
dev_err(dev, "no keymap data defined\n");
return -EINVAL;
}
keymap_data = pdata->keymap_data;
rows = pdata->rows;
cols = pdata->cols;
rep = pdata->rep;
irq_is_gpio = pdata->irq_is_gpio;
}
if (!rows || rows > TCA8424_MAX_ROWS) {
dev_err(dev, "invalid rows\n");
return -EINVAL;
}
if (!cols || cols > TCA8424_MAX_COLS) {
dev_err(dev, "invalid columns\n");
return -EINVAL;
}
/* Check i2c driver capabilities */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE)) {
dev_err(dev, "%s adapter not supported\n",
dev_driver_string(&client->adapter->dev));
return -ENODEV;
}
row_shift = get_count_order(cols);
max_keys = rows << row_shift;
/* Allocate memory for keypad_data and input device */
keypad_data = devm_kzalloc(dev, sizeof(*keypad_data), GFP_KERNEL);
if (!keypad_data)
return -ENOMEM;
keypad_data->client = client;
keypad_data->row_shift = row_shift;
i2c_set_clientdata(client, keypad_data);
/* Initialize the chip or fail if chip isn't present */
error = tca8424_configure(keypad_data, rows, cols);
if (error < 0)
return error;
/* Configure input device
input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;*/
input = input_allocate_device();
if (!input) {
error = -ENOMEM;
return error;
}
keypad_data->input = input;
#ifdef WORK_LIST
INIT_DELAYED_WORK(&keypad_data->dwork, tca8424_keys_work_func);
#endif
input->phys = "tca8424_keys/input0";<span style="font-family: Arial, Helvetica, sans-serif;">//申请input事件设备号</span>
input->name = client->name;
input->dev.parent = &client->dev;
input->id.bustype = BUS_I2C;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->keycode = (void *)keymap_data->keymap;
//导入键盘键值映射表
matrix_keypad_build_keymap(keymap_data, row_shift, input->keycode, input->keybit);
if (!input->keybit) {
dev_err(dev, "Failed to build keymap\n");
return error;
}
if (rep)
__set_bit(EV_REP, input->evbit);
__set_bit(EV_KEY, input->evbit);
input_set_drvdata(input, keypad_data);
//申请中断,这里有的input驱动用到的是工作队列的方式,是隔一段时间去自动检测,有兴趣的可以了解一下
//需要注意的是这里中断参数的设置:IRQF_ONESHOT和IRQF_SHARED不能同时使用,这个可以追踪源码查看
irq = client->irq;
if (irq_is_gpio)
irq = gpio_to_irq(irq);
error = devm_request_threaded_irq(dev, irq, NULL, tca8424_irq_handler,
IRQF_TRIGGER_FALLING |
// IRQF_TRIGGER_RISING |
// IRQF_SHARED |
IRQF_ONESHOT,
client->name, keypad_data);
if (error) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
client->irq, error);
return error;
}
//第三步:将设备注册到input子设备中
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
return error;
}
printk("[TCA8424] tca8424_keypad_probe.\n");
return 0;
}
其中的EV_REP表示按键重复,即当一个按钮按下之后,系统会每隔一段时间发送一次按下事件,在收到弹起事件后结束重复发送。
EV_KEY表示键盘事件,要与鼠标事件EV_MAC区分。
当probe函数被调用后,就会与中断函数绑定,并且在/dev/input/下生成对应的设备文件。
在中断函数中获取键盘码并上传void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value),驱动源文件可以到http://download.youkuaiyun.com/detail/sddsighhz/8352103 下载。
在board文件中添加键盘设备。
static struct tca8424_keypad_platform_data tca8424_date = {
.keymap_data = &tca8424_mkdata,
.rows=16,
.cols=8,
.rep=1,
.irq_is_gpio=1,
};
static struct i2c_board_info mxc_i2c2_board_info[] __initdata = {
{
I2C_BOARD_INFO("rtc-pcf8563", 0x51),
.type = "pcf8563"
},
{
I2C_BOARD_INFO("tca8424_keypad", 0x3b),
.type = "tca8424_keypad" ,
.irq = MX53_TCA8424_IRQ,
.platform_data = &tca8424_date
},
};