1. 前言
限于作者能力水平,本文可能存在的谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 背景
门禁设备加入一款 TTP229 芯片,作为输入键盘,没有驱动代码提供,需为该设备编写Linux input驱动。设备相关原理图如下:
3. 实现
3.1 定义设备数据结构
struct ttp229_device {
struct platform_device *pdev;
struct input_dev *input_dev;
#if (SAMPLE_FACILITY == 0)
struct timer_list input_timer; /* 使用 timer 轮询方式 */
#elif (SAMPLE_FACILITY == 1)
struct delayed_work input_work; /* 使用 work 方式 */
#endif
__u16 state;
};
3.2 设备初始化
static int ttp229_probe(struct platform_device *pdev)
{
struct ttp229_device *ttp229;
int result;
/* 创建设备对象 */
ttp229 = kzalloc(sizeof(*ttp229), GFP_KERNEL);
if (!ttp229) {
result = -ENOMEM;
dev_err(&pdev->dev, "out of memory\n");
goto err_out;
}
ttp229->pdev = pdev;
platform_set_drvdata(pdev, ttp229);
/* TTP229 是 I2C 设备,但硬件设计上,是通过 GPIO 模拟 I2C bus */
result = ttp229_timing_init(ttp229);
if (result)
goto err_init;
/* 创建input设备,注册到内核inut子系统 */
result = ttp229_input_init(ttp229);
if (result)
goto err_init;
dev_info(&pdev->dev, "%s successful.\n", __func__);
return 0;
err_init:
kfree(ttp229);
err_out:
dev_err(&pdev->dev, "%s failed, result = %d\n", __func__, result);
return result;
}
用两个 GPIO 模拟 I2C 的 SCL,SDO:
static int ttp229_timing_init(struct ttp229_device *ttp229)
{
int result;
if (!ttp229)
return -EINVAL;
result = ttp229_gpio_request(TTP229_SCL, "ttp229-scl");
if (result)
goto scl_req_err;
gpio_direction_output(TTP229_SCL, !SAMPLE_LEVEL);
result = ttp229_gpio_request(TTP229_SDO, "ttp229-sdo");
if (result)
goto sdo_req_err;
gpio_direction_output(TTP229_SDO, !SAMPLE_LEVEL);
ttp229->state = 0xFFFF;
return 0;
sdo_req_err:
gpio_free(TTP229_SCL);
scl_req_err:
return result;
}
注册设备到内核input子系统:
static int ttp229_input_init(struct ttp229_device *ttp229)
{
struct input_dev *input_dev;
int i, result;
input_dev = input_allocate_device();
if (!input_dev) {
result = -ENOMEM;
dev_err(&ttp229->pdev->dev, "input_allocate_device error\n");
goto input_alloc_err;
}
input_dev->name = "ttp229-keypad";
input_dev->dev.parent = &ttp229->pdev->dev;
for(i = 0; i < ARRAY_SIZE(key_hash_tb); i++)
input_set_capability(input_dev, EV_KEY, key_hash_tb[i].code);
__set_bit(EV_REP, input_dev->evbit);
ttp229->input_dev = input_dev;
result = input_register_device(ttp229->input_dev);
if (result) {
dev_err(&ttp229->pdev->dev, "input_register_device error\n");
goto input_register_err;
}
/* 无中断信号,只能用 timer 或者 work 方式轮训输入 */
#if (SAMPLE_FACILITY == 0)
setup_timer(&ttp229->input_timer, ttp229_input_timer_fn, (unsigned long)ttp229);
mod_timer(&ttp229->input_timer, jiffies + msecs_to_jiffies(5));
#elif (SAMPLE_FACILITY == 1)
INIT_DELAYED_WORK(&ttp229->input_work, ttp229_input_work);
schedule_delayed_work(&ttp229->input_work, msecs_to_jiffies(5));
#endif
return 0;
input_register_err:
input_free_device(ttp229->input_dev);
input_alloc_err:
return result;
}
3.3 按键采样和上报
#if (SAMPLE_FACILITY == 0)
static void ttp229_input_timer_fn(unsigned long data)
{
struct ttp229_device *ttp229 = (struct ttp229_device *)data;
ttp229_key_report(ttp229, ttp229_sample());
mod_timer(&ttp229->input_timer, jiffies + msecs_to_jiffies(SAMPLE_PERIOD));
}
#elif (SAMPLE_FACILITY == 1)
static void ttp229_input_work(struct work_struct *work)
{
struct delayed_work *input_work = to_delayed_work(work);
struct ttp229_device *ttp229 =
container_of(input_work, struct ttp229_device, input_work);
ttp229_key_report(ttp229, ttp229_sample());
schedule_delayed_work(&ttp229->input_work, msecs_to_jiffies(SAMPLE_PERIOD));
}
#endif
3.3.1 按键采样
static const struct ttp229_key_map {
__u16 data;
unsigned int code;
} key_hash_tb[] = {
{ 0xFDFD, KEY_1 },
{ 0xFBFB, KEY_2 },
{ 0xF7F7, KEY_3 },
{ 0xEFEF, KEY_4 },
{ 0xDFDF, KEY_5 },
{ 0xBFBF, KEY_6 },
{ 0x7F7F, KEY_7 },
{ 0xFEFF, KEY_8 },
#if 0
/* TODO: remain 8 keys */
{ 0xFFFF, KEY_9 },
{ 0xFFFF, KEY_0 },
{ 0xFFFF, KEY_F1 },
{ 0xFFFF, KEY_F2 },
{ 0xFFFF, KEY_F3 },
{ 0xFFFF, KEY_F4 },
{ 0xFFFF, KEY_F5 },
{ 0xFFFF, KEY_F6 },
#endif
};
static unsigned int ttp229_key_hash(__u16 data)
{
int i;
for (i = 0; i < ARRAY_SIZE(key_hash_tb); i++)
if (data == key_hash_tb[i].data)
return key_hash_tb[i].code;
return 0xFFFF;
}
static __u16 ttp229_sample(void)
{
__u16 data = 0x0000;
int i;
gpio_direction_output(TTP229_SDO, SAMPLE_LEVEL);
udelay(93);
gpio_set_value(TTP229_SDO, !SAMPLE_LEVEL);
udelay(10);
gpio_direction_input(TTP229_SDO);
for (i = 0; i < 16; ++i) {
gpio_set_value(TTP229_SCL, SAMPLE_LEVEL);
data |= (gpio_get_value(TTP229_SDO) << i);
udelay(SAMPLE_CLK_T_US - 20);
gpio_set_value(TTP229_SCL, !SAMPLE_LEVEL);;
udelay(SAMPLE_CLK_T_US);
}
gpio_direction_output(TTP229_SDO, !SAMPLE_LEVEL);
return data;
}
3.3.2 按键上报
static void ttp229_key_report(struct ttp229_device *ttp229, __u16 new_state)
{
if (ttp229->state == new_state) /* long tap not support now!!! */
return;
input_report_key(ttp229->input_dev,
ttp229_key_hash(new_state == 0xFFFF ? ttp229->state : new_state),
new_state == 0xFFFF ? 0 : 1);
input_sync(ttp229->input_dev);
ttp229->state = new_state;
}
完整的驱动代码见TTP229驱动