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、匹配驱动:
对应匹配的驱动:kerneldriversinputkeyboardrk_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 definedn");
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=%dn",val);//打印按键的实际电压值
if (ret 《 0) {
pr_err("read channel() error: %dn", 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;
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 %dn",
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 %dn",
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 %dn",
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 %dn",
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;
}
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、匹配驱动:
对应匹配的驱动:kerneldriversinputkeyboardrk_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 definedn");
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=%dn",val);//打印按键的实际电压值
if (ret 《 0) {
pr_err("read channel() error: %dn", 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;
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 %dn",
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 %dn",
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 %dn",
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 %dn",
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;
}
举报