展讯6825平台8*8按键驱动分析
一、特性
6825双核平台有特有的按键控制器和ARM的APB通讯想交互,最大能扫描8*8的按键,能够判断出按下和松开两种状态,可以同时扫描多个按键的功能,以及长按键。
1、支持最大8*8的按键扫描矩阵
2、最多可以同时探测到4个按键被按下
3、支持长按键和单按键模式
4、具有睡眠模式来节省电量
5、按键的按下或者松开防抖时间可编程
6、按键的I/O优先级可编程
7、按键扫描周期可编程
8、内部结构
二、KEYPAD控制器
默认开的是4*3矩阵。
6825按键控制器驱动分析:
就如标准的linux设备驱动程序一样,平台设备和驱动分离的思想,主要有这样一些文件:
kernel\drivers\input\keyboard\input-hook.c
kernel\drivers\input\keyboard\sc8825-keypad.c
kernel\arch\arm\mach-sc8825\devices.c
kernel/arch/arm/mach-sc8825\board.c
代码步骤:
1)首先定义一个platmform结构体,在板级文件注册的时候将此结构注册进内核。
Devices:
struct platform_device sprd_keypad_device
platform_add_devices(devices, ARRAY_SIZE(devices));
Driver:
module_init(sci_keypad_init);
platform_driver_register(&sci_keypad_driver);
static int __devinit sci_keypad_probe(struct platform_device *pdev){
struct sci_keypad_t *sci_kpd;
struct input_dev *input_dev;//输入设备结构体
struct sci_keypad_platform_data *pdata = pdev->dev.platform_data;//平台devices结构体
int error;
unsigned long value;
unsigned int row_shift, keycodemax;
row_shift = get_count_order(pdata->cols);// cols = 8 rows = 8 row_shift = 7;
keycodemax = pdata->rows << row_shift;// 8 * 2^7;
//sci_keypad 结构体其中包括 sci_keypad_t + keycodemax;
sci_kpd = kzalloc(sizeof(struct sci_keypad_t) +
keycodemax * sizeof(unsigned short), GFP_KERNEL);
//分配一个input_dev设备
input_dev = input_allocate_device();
//失败操作
if (!sci_kpd || !input_dev) {
kfree(sci_kpd);
input_free_device(input_dev);
return -ENOMEM;
}
//设置平台数据
platform_set_drvdata(pdev, sci_kpd);
//input_dev赋值
sci_kpd->input_dev = input_dev;
//行赋值
sci_kpd->rows = pdata->rows;
//列赋值
sci_kpd->cols = pdata->cols;
//用片外RAM对KPD进行复位操作
sci_glb_set(REG_GLB_SOFT_RST,BIT_KPD_RST);
//延时2ms
mdelay(2);
//清除KPD复位
sci_glb_clr(REG_GLB_SOFT_RST,BIT_KPD_RST);
//使能KPD时钟和MCU可对KPD控制器读写
sci_glb_set(REG_GLB_GEN0,BIT_KPD_EB | BIT_RTC_KPD_EB);
//对按键控制寄存器中断清除操作
keypad_writel(KPD_INT_CLR, KPD_INT_ALL);
//设置按键优先级
value = CFG_ROW_POLARITY | CFG_COL_POLARITY;
keypad_writel(KPD_POLARITY, value);//value = 0xffff
//设置按键分频参数
keypad_writel(KPD_CLK_DIV_CNT, 1);
// 设置长按键时间
keypad_writel(KPD_LONG_KEY_CNT, 0xc);
//设置防抖时间
keypad_writel(KPD_DEBOUNCE_CNT, 0x5);
//获取按键控制寄存器中断源
sci_kpd->irq = platform_get_irq(pdev, 0);
if (sci_kpd->irq < 0) {
error = -ENODEV;
dev_err(&pdev->dev, "Get irq number error,Keypad Module\n");
goto out2;
}
//申请中断
error =
request_irq(sci_kpd->irq, sci_keypad_isr, 0, "sprd-keypad", sci_kpd);
if (error) {
dev_err(&pdev->dev, "unable to claim irq %d\n", sci_kpd->irq);
goto out2;
}
//设置input设备名字
input_dev->name = pdev->name;
input_dev->phys = "sprd-key/input0";
//设置父设备
input_dev->dev.parent = &pdev->dev;
//绑定input_dev
input_set_drvdata(input_dev, sci_kpd);
//设置总线类型
input_dev->id.bustype = BUS_HOST;
//设置版本
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->keycode = &sci_kpd[1];
input_dev->keycodesize = sizeof(unsigned short);
input_dev->keycodemax = keycodemax;
//设置按键映射
matrix_keypad_build_keymap(pdata->keymap_data, row_shift,
input_dev->keycode, input_dev->keybit);
/* there are keys from hw other than keypad controller */
//设置开机键
__set_bit(KEY_POWER, input_dev->keybit);
//设置为按键时间
__set_bit(EV_KEY, input_dev->evbit);
if (pdata->repeat)
__set_bit(EV_REP, input_dev->evbit);
//注册为输入设备
error = input_register_device(input_dev);
if (error) {
dev_err(&pdev->dev, "unable to register input device\n");
goto out4;
}
device_init_wakeup(&pdev->dev, 1);
//设置按键按下释放状态
value = KPD_INT_DOWNUP;
//判断是否谁知长按键
if (pdata->support_long_key)
value |= KPD_INT_LONG;
keypad_writel(KPD_INT_EN, value);
//设置按键休眠时间
value = KPD_SLEEP_CNT_VALUE(1000);
keypad_writel(KPD_SLEEP_CNT, value);
value = KPD_SLEEP_EN | (pdata->rows_choose_hw & KPDCTL_ROW_MSK) |
(pdata->cols_choose_hw & KPDCTL_COL_MSK);
if (pdata->support_long_key)
value |= KPD_LONG_KEY_EN;
//使能按键
value |= KPD_EN;
keypad_writel(KPD_CTRL, value);d
//设置pewer键,设置状态
gpio_request(ANA_GPI_PB, "powerkey");
gpio_direction_input(ANA_GPI_PB);
//设置power按键中断
error = request_irq(gpio_to_irq(ANA_GPI_PB), sci_powerkey_isr,
IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "powerkey", sci_kpd);
if (error) {
dev_err(&pdev->dev, "unable to claim irq %d\n",
gpio_to_irq(ANA_GPI_PB));
goto out2;
}
dump_keypad_register();
return 0;
out4:
input_free_device(input_dev);
free_irq(sci_kpd->irq, pdev);
out2:
kfree(sci_kpd);
platform_set_drvdata(pdev, NULL);
return error;
}
}
以上是probe的处理过程,其中主要的操作有一下几点:
1)分配一个sci_keypad结构体
2)申请一个input_dev设备并为其赋值
3)按键控制器寄存器初始化
4)按键控制寄存器中断申请
5)按键值和对应寄存器值映射
6)Power按键中断申请
下面分析power按键中断处理程序以及按键控制器中断处理程序
error = request_irq(gpio_to_irq(ANA_GPI_PB), sci_powerkey_isr,
IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "powerkey", sci_kpd);
上面这段是power按键中断申请,主要包括四个部分
1)将ANA_GPI_P转换为中断号
2)绑定中断处理程序
3)设置高点平触发以及设置改中断不休眠
4)传参
static irqreturn_t sci_powerkey_isr(int irq, void *dev_id)
{ //TODO: if usign gpio(eic), need add row , cols to platform data.
static unsigned long last_value = 1;
unsigned short key = KEY_POWER;//power键值116 kernel/include/linux/input.h 定义
unsigned long value = !(gpio_get_value(ANA_GPI_PB));
struct sci_keypad_t *sci_kpd = dev_id;//传参强制转化
if (last_value == value) {//判断是否和上次一样
/* seems an event is missing, just report it */
input_report_key(sci_kpd->input_dev, key, last_value);//上报按键键值
input_sync(sci_kpd->input_dev);//同步
printk("%dX\n", key);
}
if (value) {
/* Release : low level */
input_report_key_hook(sci_kpd->input_dev, key, 0);//上报键值0
input_report_key(sci_kpd->input_dev, key, 0);
input_sync(sci_kpd->input_dev);//同步
printk("Powerkey:%dU\n", key);
irq_set_irq_type(irq, IRQF_TRIGGER_HIGH);//设置高电平触发
} else {
/* Press : high level *///按下
input_report_key_hook(sci_kpd->input_dev, key, 1);//上报键值1
input_report_key(sci_kpd->input_dev, key, 1);
input_sync(sci_kpd->input_dev);//同步
printk("Powerkey:%dD\n", key);
irq_set_irq_type(irq, IRQF_TRIGGER_LOW);//设置低点平触发
}
last_value = value;//备份值
return IRQ_HANDLED;//返回IRQ
}
普通按键处理程序
static irqreturn_t sci_keypad_isr(int irq, void *dev_id)
{
unsigned short key = 0;
unsigned long value;
struct sci_keypad_t *sci_kpd = dev_id;
unsigned long int_status = keypad_readl(KPD_INT_MASK_STATUS);//读按中断寄存器的状态
unsigned long key_status = keypad_readl(KPD_KEY_STATUS);//读按键状态
unsigned short *keycodes = sci_kpd->input_dev->keycode;
unsigned int row_shift = get_count_order(sci_kpd->cols);
int col, row;
value = keypad_readl(KPD_INT_CLR);
value |= KPD_INT_ALL;
keypad_writel(KPD_INT_CLR, value);//清除
if ((int_status & KPD_PRESS_INT0)) {//判断是否是按下0行
col = KPD_INT0_COL(key_status);//取出行
row = KPD_INT0_ROW(key_status);//取出列
key = keycodes[MATRIX_SCAN_CODE(row, col, row_shift)];//获得对应映射键值
input_report_key(sci_kpd->input_dev, key, 1);//上报
input_sync(sci_kpd->input_dev);
printk("%03dD\n", key);
}
}
以上是6825按键中断处理程序,相比6820上的按键中断,6825的按键中断相对于简单多了,主要是展讯代码整合较为好。