1、DTS修改
DTS 节点在 kernel/arch/arm64/boot/dts/rockchip/rk3288.dtsi 文件中定义,如下所示:
saradc: saradc@ff100000 {
compatible = "rockchip,saradc";
reg = <0x0 0xff100000 0x0 0x100>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
#io-channel-cells = <1>;
clocks = <&cru SCLK_SARADC>, <&cru PCLK_SARADC>;
clock-names = "saradc", "apb_pclk";
resets = <&cru SRST_SARADC>;
reset-names = "saradc-apb";
status = "disabled";
};
在DTS文件中添加自己ADC的资源描述:
rk_key{
compatible = "rockchip,key";//"rockchip,key"该名字用来匹配rk_key.c这个驱动
status = "okay";
io-channels = <&saradc 1>;//根据硬件来选择adc 0/1/2
vol-up-key {//添加子节点
linux,code = <114>;//驱动要上报的按键code
label = "volume up";
rockchip,adc_value = <1>;//按键的adc对应电压值
};
vol-down-key {
linux,code = <115>;
label = "volume down";
rockchip,adc_value = <170>;
};
power-key {
//gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
linux,code = <116>;
label = "power";
gpio-key,wakeup;
rockchip,adc_value = <509>;
};
menu-key {
linux,code = <59>;
label = "menu";
rockchip,adc_value = <609>;
};
home-key {
linux,code = <102>;
label = "home";
rockchip,adc_value = <842>;
};
back-key {
linux,code = <158>;
label = "back";
rockchip,adc_value = <973>;
};
};
2、匹配驱动:
对应匹配的驱动:kernel\drivers\input\keyboard\rk_key.c
怎么匹配的呢??看驱动的这个函数:
static const struct of_device_id rk_key_match[] = {
{ .compatible = "rockchip,key", .data = NULL},
{},
};
带有of_xxxxx开头的函数一般是用来解析dts的资源的,比如上面的这个函数中的.compatible = "rockchip,key"
就是和dts匹配的,驱动的 "rockchip,key"
与 "rockchip,key"
名字是一样的,具体的怎么匹配的,怎么实现的不用太深入,知道这个是用来匹配的就好。
2.1、驱动中解析dts设定的adc电压值的函数:
注:rk_key_type_get函数可判断是io口还是adc按键
static int rk_key_type_get(struct device_node *node,
struct rk_keys_button *button)
{
u32 adc_value;
if (!of_property_read_u32(node, "rockchip,adc_value", &adc_value))
return TYPE_ADC;
else if (of_get_gpio(node, 0) >= 0)
return TYPE_GPIO;
else
return -1;
}
/*************************************************************/
> of_property_read_u32(node, "rockchip,adc_value", &adc_value)
>参数node:对于dts的节点
>参数 "rockchip,adc_value":进行匹配
>参数 adc_value:将dts设定的按键电压值赋给参数adc_value
/*************************************************************/
驱动中还有:of_property_read_u32(child_node, "linux,code", &code))
、 of_get_property(child_node, "label", NULL);
等是同一个道理分析的。
2.2、获取ADC通道
获取对应的通道 struct iio_channel *chan;
定义 IIO 通道结构体chan = iio_channel_get(&pdev->dev, NULL);
获取 IIO 通道结构体
static int rk_keys_parse_dt(struct rk_keys_drvdata *pdata,
struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *child_node;
struct iio_channel *chan;
int ret, gpio, i = 0;
u32 code, adc_value, flags, drift;
if (of_property_read_u32(node, "adc-drift", &drift))
pdata->drift_advalue = DRIFT_DEFAULT_ADVALUE;
else
pdata->drift_advalue = (int)drift;
chan = iio_channel_get(&pdev->dev, NULL);
if (IS_ERR(chan)) {
dev_info(&pdev->dev, "no io-channels defined\n");
chan = NULL;
}
2.3、获取ADC值
在adc_key polling调用了iio_read_channel_raw()函数读取 AD 采集的原始数据,并存入 val
static int rk_key_adc_iio_read(struct rk_keys_drvdata *data)
{
struct iio_channel *channel = data->chan;
int val, ret;
if (!channel)
return INVALID_ADVALUE;
ret = iio_read_channel_raw(channel, &val);
//printk("val=%d\n",val);//打印按键的实际电压值
if (ret < 0) {
pr_err("read channel() error: %d\n", ret);
return ret;
}
return val;
}
3、驱动是通过内核input子系统来将keys注册供用户空间使用
static int keys_probe(struct platform_device *pdev)
{
***
input = devm_input_allocate_device(dev);
input->name = "rk29-keypad"; /* pdev->name; */
input->phys = "gpio-keys/input0";
input->dev.parent = dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
***
for (i = 0; i < ddata->nbuttons; i++) {
struct rk_keys_button *button = &ddata->button[i];
if (button->type == TYPE_GPIO) {
int irq;
//为io口申请这里的type即为TYPE_GPIO 而不是adc
error =
devm_gpio_request(dev, button->gpio,
button->desc ? : "keys");
if (error < 0) {
pr_err("gpio-keys: failed to request GPIO %d, error %d\n",
button->gpio, error);
goto fail1;
}
//设置为输入
error = gpio_direction_input(button->gpio);
if (error < 0) {
pr_err("gpio-keys: failed to configure input direction for GPIO %d, error %d\n",
button->gpio, error);
gpio_free(button->gpio);
goto fail1;
}
irq = gpio_to_irq(button->gpio);
if (irq < 0) {
error = irq;
pr_err("gpio-keys: Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
gpio_free(button->gpio);
goto fail1;
}
//申请中断
error = devm_request_irq(dev, irq, keys_isr,
button->active_low ?
IRQF_TRIGGER_FALLING :
IRQF_TRIGGER_RISING,
button->desc ?
button->desc : "keys",
button);
if (error) {
pr_err("gpio-keys: Unable to claim irq %d; error %d\n",
irq, error);
gpio_free(button->gpio);
goto fail1;
}
}
}
***
}
4、rk_keys_parse_dt()实现
每个key都会注册一个定时器函数来处理状态变化并通知用户空间。
for (i = 0; i < ddata->nbuttons; i++) {
if (button->code) {
setup_timer(&button->timer,
keys_timer, (unsigned long)button);
}
}
keys_timer():
static void keys_timer(unsigned long _data)
{
//普通gpio直接读取
if (button->type == TYPE_GPIO)
state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^
button->active_low);
else
//adc转成bool状态值
state = !!button->adc_state;
//状态变化上报事件
if (button->state != state) {
button->state = state;
input_event(input, EV_KEY, button->code, button->state);
input_event(input, EV_KEY, button->code, button->state);
input_sync(input);
}
//10ms后启动定时器
if (state)
mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);
}
定时器会处理普通gpio和adc两种类型的按键,当状态变化时,会向用户空间上报当前事件、键值、状态。默认开机时,定时器处理函数因为检测不到状态变化而关闭退出。定时器的开启有两个地方会被调用:
a.系统开机会启一个工作队列,每100ms周期性调用一次检测有没有按键触发
static void adc_key_poll(struct work_struct *work)
{
if (!ddata->in_suspend) {
//读取adc电压
result = rk_key_adc_iio_read(ddata);
for (i = 0; i < ddata->nbuttons; i++) {
//允许值有一定范围的漂移
if (result < button->adc_value + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE)
button->adc_state = 1;
else
button->adc_state = 0;
if (button->state != button->adc_state)
mod_timer(&button->timer,
jiffies + DEBOUNCE_JIFFIES);
}
}
//周期性调用。ADC_SAMPLE_JIFFIES为100ms
schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES);
}
b、 power key唤醒时中断处理会被触发
static irqreturn_t keys_isr(int irq, void *dev_id)
{
//上报power key事件
if (button->wakeup && pdata->in_suspend) {
button->state = 1;
input_event(input, EV_KEY, button->code, button->state);
input_sync(input);
}
if (button->wakeup)
wake_lock_timeout(&pdata->wake_lock, WAKE_LOCK_JIFFIES);
mod_timer(&button->timer, jiffies + DEBOUNCE_JIFFIES);
return IRQ_HANDLED;
}