Android Kernel powerkey 인터럽트 설정

안드로이드를 포팅하다 잘되던 power key가 어느 순간 작동하지 않았다. 원인은 g-sensor를 인터럽트로 신호를 받았더니 power key가 동작을 하지 않는 것이다.

g-sensor에 인터럽트가 필요했기 때문에 power key또한 인터럽트가 작동하도록 만드는 것이 필요했다.

일단 dts의 power-key 부분을 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&adc {
status = "okay";
rk_key: rockchip-key {
compatible = "rockchip,key";
io-channels = <&adc 0>;
status = "okay";

vol-up-key {
linux,code = <113>;
label = "F12";
rockchip,adc_value = <4>;
};

power-key {
gpios = <&gpio0 GPIO_A2 GPIO_ACTIVE_LOW>;
linux,code = <116>;
label = "POWER";
gpio-key,wakeup;
// rockchip,adc_value = <4>;
};
};
};

이렇게 되어있다.

기존 sdk의 dts는 power-key를 adc로 받았던 것 같은데 우리의 디바이스는 gpio0_a2와 key가 연결되어 있기 때문에 수정을 하였다.

여기서 잠깐 설명을 하면 linux,code = <116>KEY_POWER를 의미한다. 이는 linux/input.h에 정의되어 있다.

다시 돌아와서 여기서 dts를 잘 살펴보면 compatible = "rockchip,key";이 보인다.

rockchip,key라는 compatible이 설정되어 있는 driver와 연결되어 있다는 의미이다.

kernel/drivers를 열심히 검색했더니 rk_keys.c라는 파일에 compatible = "rockchip,key";이 설정되어 있는 것을 확인했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const struct of_device_id rk_key_match[] = {
{ .compatible = "rockchip,key", .data = NULL},
{},
};

static struct platform_driver keys_device_driver = {
.probe = keys_probe,
.remove = keys_remove,
.driver = {
.name = "rk-keypad",
.owner = THIS_MODULE,
.of_match_table = rk_key_match,
#ifdef CONFIG_PM
.pm = &keys_pm_ops,
#endif
}
};

이제 해당 소스에서 key의 interrupt 관련된 부분을 찾아 봤다.

이에 앞서 먼저 g-sensor의 interrupt부분은 다음과 같이 이루어져 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static void  sensor_delaywork_func(struct work_struct *work)
{
struct delayed_work *delaywork = container_of(work, struct delayed_work, work);
struct sensor_private_data *sensor = container_of(delaywork, struct sensor_private_data, delaywork);
struct i2c_client *client = sensor->client;

mutex_lock(&sensor->sensor_mutex);
if (sensor_get_data(client) < 0)
DBG(KERN_ERR "%s: Get data failed\n",__func__);

if(!sensor->pdata->irq_enable)//restart work while polling
schedule_delayed_work(&sensor->delaywork, msecs_to_jiffies(sensor->pdata->poll_delay_ms));
//else
//{
//if((sensor->ops->trig == IRQF_TRIGGER_LOW) || (sensor->ops->trig == IRQF_TRIGGER_HIGH))
//enable_irq(sensor->client->irq);
//}
mutex_unlock(&sensor->sensor_mutex);

DBG("%s:%s\n",__func__,sensor->i2c_id->name);
}

static int sensor_irq_init(struct i2c_client *client)
{
struct sensor_private_data *sensor =
(struct sensor_private_data *) i2c_get_clientdata(client);
int result = 0;
int irq;
if((sensor->pdata->irq_enable)&&(sensor->pdata->irq_flags!= SENSOR_UNKNOW_DATA))
{
INIT_DELAYED_WORK(&sensor->delaywork, sensor_delaywork_func);
if(sensor->pdata->poll_delay_ms < 0)
sensor->pdata->poll_delay_ms = 30;
result = gpio_request(client->irq, sensor->i2c_id->name);
if (result)
{
printk("%s:fail to request gpio :%d\n",__func__,client->irq);
}

//gpio_pull_updown(client->irq, PullEnable);
irq = gpio_to_irq(client->irq);
//result = request_irq(irq, sensor_interrupt, sensor->ops->trig, sensor->ops->name, sensor);
result = request_threaded_irq(irq, NULL, sensor_interrupt, sensor->ops->trig, sensor->ops->name, sensor);
//result = devm_request_threaded_irq(&client->dev, irq, NULL, sensor_interrupt, sensor->pdata->irq_flags | IRQF_ONESHOT, sensor->ops->name, sensor);
if (result) {
printk(KERN_INFO "%s:fail to request irq = %d, ret = 0x%x\n",__func__, irq, result);
goto error;
}
client->irq = irq;
if((sensor->pdata->type == SENSOR_TYPE_GYROSCOPE) || (sensor->pdata->type == SENSOR_TYPE_ACCEL) || (sensor->pdata->type == SENSOR_TYPE_ANGLE))
disable_irq_nosync(client->irq);//disable irq
if(((sensor->pdata->type == SENSOR_TYPE_LIGHT) || (sensor->pdata->type == SENSOR_TYPE_PROXIMITY))&& (!(sensor->ops->trig & IRQF_SHARED)))
disable_irq_nosync(client->irq);//disable irq
if(((sensor->pdata->type == SENSOR_TYPE_TEMPERATURE) || (sensor->pdata->type == SENSOR_TYPE_PRESSURE))&& (!(sensor->ops->trig & IRQF_SHARED)))
disable_irq_nosync(client->irq);//disable irq
DBG("%s:use irq=%d\n",__func__,irq);
}
else if(!sensor->pdata->irq_enable)
{
INIT_DELAYED_WORK(&sensor->delaywork, sensor_delaywork_func);
if(sensor->pdata->poll_delay_ms < 0)
sensor->pdata->poll_delay_ms = 30;

DBG("%s:use polling,delay=%d ms\n",__func__,sensor->pdata->poll_delay_ms);
}

error:
return result;
}

소스가 길지만 여기서 눈여겨 볼 것은 INIT_DELAYED_WORKschedule_delayed_work이다.

이 인터럽트는 위 함수를 쓴거와 같이 workqueue 인터럽트를 사용했다.

workqueue 인터럽트는 바톰 하프의 한 종류로서 softirq, tasklet, workqueue_struct중 세번째에 해당된다.

특징으로는

  • 사용자 편의성이 높고
  • 지연된 작업이 프로세스 컨텍스트에서 수행되어야 하는 경우
  • 네트워크 서브시스템과 같이 초당 수천번의 인터럽트가 발생하는 상황에는 적합하지 않음
  • 지연된 작업을 처리하기 위해 스케줄 가능한 요소, 즉 휴면(blocking)해야할 필요가 있는 경우
    에 적합하다.

다시 돌아와서 내가 관심같는 key의 소스를 보면 역시 interrupt가 구현되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
static int keys_probe(struct platform_device *pdev)
{
...

for (i = 0; i < ddata->nbuttons; i++) {
struct rk_keys_button *button = &ddata->button[i];

button->dev = &pdev->dev;
if (button->type == TYPE_GPIO) {
int irq;

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;
}
}
}

input_set_capability(input, EV_KEY, KEY_WAKEUP);
/* adc polling work */
if (ddata->chan)
{
INIT_DELAYED_WORK(&ddata->adc_poll_work, adc_key_poll);
schedule_delayed_work(&ddata->adc_poll_work,
ADC_SAMPLE_JIFFIES);
}

...

}

해당 소스를 보면 열심히 key와 관련되어서 초기화 시 interrupt를 설정하였다.

그런데 왜 power-key가 작동하지 않았을까…

결론은 마지막에 if (ddata->chan)의 조건문을 만족하지 못해서 workqueue가 등록되지 안았다.

여기서 chanchan = iio_channel_get(&pdev->dev, NULL);에서 얻어온다.

io-chaanel인가를 뜻하는 거 같은데… 잘 모르겠다.
하여튼 내가 쓰는 key는 일단 power-key 하나이므로 과감히 해당 조건문을 주석처리 했다.

결론적으로 g-sensor와 power-key가 동시에 잘 동작하는 것을 확인했다.

공유하기