From fb76dd10b91146e9cefbb3cd4e6812c5a95ee43b Mon Sep 17 00:00:00 2001 From: Luotao Fu Date: Thu, 10 Jun 2010 12:05:23 -0700 Subject: Input: matrix_keypad - add support for clustered irq This one adds support of a combined irq source for the whole matrix keypad. This can be useful if all rows and columns of the keypad are e.g. connected to a GPIO expander, which only has one interrupt line for all events on every single GPIO. Signed-off-by: Luotao Fu Acked-by: Eric Miao Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/matrix_keypad.c | 108 ++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 28 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c index b443e088fd3..b02e4268e18 100644 --- a/drivers/input/keyboard/matrix_keypad.c +++ b/drivers/input/keyboard/matrix_keypad.c @@ -37,6 +37,7 @@ struct matrix_keypad { spinlock_t lock; bool scan_pending; bool stopped; + bool gpio_all_disabled; }; /* @@ -87,8 +88,12 @@ static void enable_row_irqs(struct matrix_keypad *keypad) const struct matrix_keypad_platform_data *pdata = keypad->pdata; int i; - for (i = 0; i < pdata->num_row_gpios; i++) - enable_irq(gpio_to_irq(pdata->row_gpios[i])); + if (pdata->clustered_irq > 0) + enable_irq(pdata->clustered_irq); + else { + for (i = 0; i < pdata->num_row_gpios; i++) + enable_irq(gpio_to_irq(pdata->row_gpios[i])); + } } static void disable_row_irqs(struct matrix_keypad *keypad) @@ -96,8 +101,12 @@ static void disable_row_irqs(struct matrix_keypad *keypad) const struct matrix_keypad_platform_data *pdata = keypad->pdata; int i; - for (i = 0; i < pdata->num_row_gpios; i++) - disable_irq_nosync(gpio_to_irq(pdata->row_gpios[i])); + if (pdata->clustered_irq > 0) + disable_irq_nosync(pdata->clustered_irq); + else { + for (i = 0; i < pdata->num_row_gpios; i++) + disable_irq_nosync(gpio_to_irq(pdata->row_gpios[i])); + } } /* @@ -216,45 +225,69 @@ static void matrix_keypad_stop(struct input_dev *dev) } #ifdef CONFIG_PM -static int matrix_keypad_suspend(struct device *dev) +static void matrix_keypad_enable_wakeup(struct matrix_keypad *keypad) { - struct platform_device *pdev = to_platform_device(dev); - struct matrix_keypad *keypad = platform_get_drvdata(pdev); const struct matrix_keypad_platform_data *pdata = keypad->pdata; + unsigned int gpio; int i; - matrix_keypad_stop(keypad->input_dev); + if (pdata->clustered_irq > 0) { + if (enable_irq_wake(pdata->clustered_irq) == 0) + keypad->gpio_all_disabled = true; + } else { - if (device_may_wakeup(&pdev->dev)) { for (i = 0; i < pdata->num_row_gpios; i++) { if (!test_bit(i, keypad->disabled_gpios)) { - unsigned int gpio = pdata->row_gpios[i]; + gpio = pdata->row_gpios[i]; if (enable_irq_wake(gpio_to_irq(gpio)) == 0) __set_bit(i, keypad->disabled_gpios); } } } - - return 0; } -static int matrix_keypad_resume(struct device *dev) +static void matrix_keypad_disable_wakeup(struct matrix_keypad *keypad) { - struct platform_device *pdev = to_platform_device(dev); - struct matrix_keypad *keypad = platform_get_drvdata(pdev); const struct matrix_keypad_platform_data *pdata = keypad->pdata; + unsigned int gpio; int i; - if (device_may_wakeup(&pdev->dev)) { + if (pdata->clustered_irq > 0) { + if (keypad->gpio_all_disabled) { + disable_irq_wake(pdata->clustered_irq); + keypad->gpio_all_disabled = false; + } + } else { for (i = 0; i < pdata->num_row_gpios; i++) { if (test_and_clear_bit(i, keypad->disabled_gpios)) { - unsigned int gpio = pdata->row_gpios[i]; - + gpio = pdata->row_gpios[i]; disable_irq_wake(gpio_to_irq(gpio)); } } } +} + +static int matrix_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct matrix_keypad *keypad = platform_get_drvdata(pdev); + + matrix_keypad_stop(keypad->input_dev); + + if (device_may_wakeup(&pdev->dev)) + matrix_keypad_enable_wakeup(keypad); + + return 0; +} + +static int matrix_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct matrix_keypad *keypad = platform_get_drvdata(pdev); + + if (device_may_wakeup(&pdev->dev)) + matrix_keypad_disable_wakeup(keypad); matrix_keypad_start(keypad->input_dev); @@ -296,17 +329,31 @@ static int __devinit init_matrix_gpio(struct platform_device *pdev, gpio_direction_input(pdata->row_gpios[i]); } - for (i = 0; i < pdata->num_row_gpios; i++) { - err = request_irq(gpio_to_irq(pdata->row_gpios[i]), + if (pdata->clustered_irq > 0) { + err = request_irq(pdata->clustered_irq, matrix_keypad_interrupt, - IRQF_DISABLED | - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + pdata->clustered_irq_flags, "matrix-keypad", keypad); if (err) { dev_err(&pdev->dev, - "Unable to acquire interrupt for GPIO line %i\n", - pdata->row_gpios[i]); - goto err_free_irqs; + "Unable to acquire clustered interrupt\n"); + goto err_free_rows; + } + } else { + for (i = 0; i < pdata->num_row_gpios; i++) { + err = request_irq(gpio_to_irq(pdata->row_gpios[i]), + matrix_keypad_interrupt, + IRQF_DISABLED | + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "matrix-keypad", keypad); + if (err) { + dev_err(&pdev->dev, + "Unable to acquire interrupt " + "for GPIO line %i\n", + pdata->row_gpios[i]); + goto err_free_irqs; + } } } @@ -418,11 +465,16 @@ static int __devexit matrix_keypad_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, 0); - for (i = 0; i < pdata->num_row_gpios; i++) { - free_irq(gpio_to_irq(pdata->row_gpios[i]), keypad); - gpio_free(pdata->row_gpios[i]); + if (pdata->clustered_irq > 0) { + free_irq(pdata->clustered_irq, keypad); + } else { + for (i = 0; i < pdata->num_row_gpios; i++) + free_irq(gpio_to_irq(pdata->row_gpios[i]), keypad); } + for (i = 0; i < pdata->num_row_gpios; i++) + gpio_free(pdata->row_gpios[i]); + for (i = 0; i < pdata->num_col_gpios; i++) gpio_free(pdata->col_gpios[i]); -- cgit v1.2.3-70-g09d2 From 69a4af606ed4836faa2ec69b1d217f384b8235e7 Mon Sep 17 00:00:00 2001 From: Xiaolong CHEN Date: Thu, 24 Jun 2010 19:10:40 -0700 Subject: Input: adp5588-keys - support GPI events for ADP5588 devices A column or row configured as a GPI can be programmed to be part of the key event table and therefore also capable of generating a key event interrupt. A key event interrupt caused by a GPI follows the same process flow as a key event interrupt caused by a key press. GPIs configured as part of the key event table allow single key switches and other GPI interrupts to be monitored. As part of the event table, GPIs are represented by the decimal value 97 (0x61 or 1100001) through the decimal value 114 (0x72 or 1110010). See table below for GPI event number assignments for rows and columns. GPI Event Number Assignments for Rows Row0 Row1 Row2 Row3 Row4 Row5 Row6 Row7 97 98 99 100 101 102 103 104 GPI Event Number Assignments for Cols Col0 Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 105 106 107 108 109 110 111 112 113 114 Signed-off-by: Xiaolong Chen Signed-off-by: Yuanbo Ye Signed-off-by: Tao Hu Acked-by: Michael Hennerich Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 134 ++++++++++++++++++++++++++++++++-- include/linux/i2c/adp5588.h | 36 +++++++++ 2 files changed, 163 insertions(+), 7 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index 4771ab172b5..4ef789ef104 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -67,6 +67,8 @@ struct adp5588_kpad { struct delayed_work work; unsigned long delay; unsigned short keycode[ADP5588_KEYMAPSIZE]; + const struct adp5588_gpi_map *gpimap; + unsigned short gpimapsize; }; static int adp5588_read(struct i2c_client *client, u8 reg) @@ -84,12 +86,37 @@ static int adp5588_write(struct i2c_client *client, u8 reg, u8 val) return i2c_smbus_write_byte_data(client, reg, val); } +static void adp5588_report_events(struct adp5588_kpad *kpad, int ev_cnt) +{ + int i, j; + + for (i = 0; i < ev_cnt; i++) { + int key = adp5588_read(kpad->client, Key_EVENTA + i); + int key_val = key & KEY_EV_MASK; + + if (key_val >= GPI_PIN_BASE && key_val <= GPI_PIN_END) { + for (j = 0; j < kpad->gpimapsize; j++) { + if (key_val == kpad->gpimap[j].pin) { + input_report_switch(kpad->input, + kpad->gpimap[j].sw_evt, + key & KEY_EV_PRESSED); + break; + } + } + } else { + input_report_key(kpad->input, + kpad->keycode[key_val - 1], + key & KEY_EV_PRESSED); + } + } +} + static void adp5588_work(struct work_struct *work) { struct adp5588_kpad *kpad = container_of(work, struct adp5588_kpad, work.work); struct i2c_client *client = kpad->client; - int i, key, status, ev_cnt; + int status, ev_cnt; status = adp5588_read(client, INT_STAT); @@ -99,12 +126,7 @@ static void adp5588_work(struct work_struct *work) if (status & KE_INT) { ev_cnt = adp5588_read(client, KEY_LCK_EC_STAT) & KEC; if (ev_cnt) { - for (i = 0; i < ev_cnt; i++) { - key = adp5588_read(client, Key_EVENTA + i); - input_report_key(kpad->input, - kpad->keycode[(key & KEY_EV_MASK) - 1], - key & KEY_EV_PRESSED); - } + adp5588_report_events(kpad, ev_cnt); input_sync(kpad->input); } } @@ -130,6 +152,7 @@ static int __devinit adp5588_setup(struct i2c_client *client) { struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; int i, ret; + unsigned char evt_mode1 = 0, evt_mode2 = 0, evt_mode3 = 0; ret = adp5588_write(client, KP_GPIO1, KP_SEL(pdata->rows)); ret |= adp5588_write(client, KP_GPIO2, KP_SEL(pdata->cols) & 0xFF); @@ -144,6 +167,23 @@ static int __devinit adp5588_setup(struct i2c_client *client) for (i = 0; i < KEYP_MAX_EVENT; i++) ret |= adp5588_read(client, Key_EVENTA); + for (i = 0; i < pdata->gpimapsize; i++) { + unsigned short pin = pdata->gpimap[i].pin; + + if (pin <= GPI_PIN_ROW_END) { + evt_mode1 |= (1 << (pin - GPI_PIN_ROW_BASE)); + } else { + evt_mode2 |= ((1 << (pin - GPI_PIN_COL_BASE)) & 0xFF); + evt_mode3 |= ((1 << (pin - GPI_PIN_COL_BASE)) >> 8); + } + } + + if (pdata->gpimapsize) { + ret |= adp5588_write(client, GPI_EM1, evt_mode1); + ret |= adp5588_write(client, GPI_EM2, evt_mode2); + ret |= adp5588_write(client, GPI_EM3, evt_mode3); + } + ret |= adp5588_write(client, INT_STAT, CMP2_INT | CMP1_INT | OVR_FLOW_INT | K_LCK_INT | GPI_INT | KE_INT); /* Status is W1C */ @@ -158,6 +198,44 @@ static int __devinit adp5588_setup(struct i2c_client *client) return 0; } +static void __devinit adp5588_report_switch_state(struct adp5588_kpad *kpad) +{ + int gpi_stat1 = adp5588_read(kpad->client, GPIO_DAT_STAT1); + int gpi_stat2 = adp5588_read(kpad->client, GPIO_DAT_STAT2); + int gpi_stat3 = adp5588_read(kpad->client, GPIO_DAT_STAT3); + int gpi_stat_tmp, pin_loc; + int i; + + for (i = 0; i < kpad->gpimapsize; i++) { + unsigned short pin = kpad->gpimap[i].pin; + + if (pin <= GPI_PIN_ROW_END) { + gpi_stat_tmp = gpi_stat1; + pin_loc = pin - GPI_PIN_ROW_BASE; + } else if ((pin - GPI_PIN_COL_BASE) < 8) { + gpi_stat_tmp = gpi_stat2; + pin_loc = pin - GPI_PIN_COL_BASE; + } else { + gpi_stat_tmp = gpi_stat3; + pin_loc = pin - GPI_PIN_COL_BASE - 8; + } + + if (gpi_stat_tmp < 0) { + dev_err(&kpad->client->dev, + "Can't read GPIO_DAT_STAT switch %d default to OFF\n", + pin); + gpi_stat_tmp = 0; + } + + input_report_switch(kpad->input, + kpad->gpimap[i].sw_evt, + !(gpi_stat_tmp & (1 << pin_loc))); + } + + input_sync(kpad->input); +} + + static int __devinit adp5588_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -189,6 +267,37 @@ static int __devinit adp5588_probe(struct i2c_client *client, return -EINVAL; } + if (!pdata->gpimap && pdata->gpimapsize) { + dev_err(&client->dev, "invalid gpimap from pdata\n"); + return -EINVAL; + } + + if (pdata->gpimapsize > ADP5588_GPIMAPSIZE_MAX) { + dev_err(&client->dev, "invalid gpimapsize\n"); + return -EINVAL; + } + + for (i = 0; i < pdata->gpimapsize; i++) { + unsigned short pin = pdata->gpimap[i].pin; + + if (pin < GPI_PIN_BASE || pin > GPI_PIN_END) { + dev_err(&client->dev, "invalid gpi pin data\n"); + return -EINVAL; + } + + if (pin <= GPI_PIN_ROW_END) { + if (pin - GPI_PIN_ROW_BASE + 1 <= pdata->rows) { + dev_err(&client->dev, "invalid gpi row data\n"); + return -EINVAL; + } + } else { + if (pin - GPI_PIN_COL_BASE + 1 <= pdata->cols) { + dev_err(&client->dev, "invalid gpi col data\n"); + return -EINVAL; + } + } + } + if (!client->irq) { dev_err(&client->dev, "no IRQ?\n"); return -EINVAL; @@ -233,6 +342,9 @@ static int __devinit adp5588_probe(struct i2c_client *client, memcpy(kpad->keycode, pdata->keymap, pdata->keymapsize * input->keycodesize); + kpad->gpimap = pdata->gpimap; + kpad->gpimapsize = pdata->gpimapsize; + /* setup input device */ __set_bit(EV_KEY, input->evbit); @@ -243,6 +355,11 @@ static int __devinit adp5588_probe(struct i2c_client *client, __set_bit(kpad->keycode[i] & KEY_MAX, input->keybit); __clear_bit(KEY_RESERVED, input->keybit); + if (kpad->gpimapsize) + __set_bit(EV_SW, input->evbit); + for (i = 0; i < kpad->gpimapsize; i++) + __set_bit(kpad->gpimap[i].sw_evt, input->swbit); + error = input_register_device(input); if (error) { dev_err(&client->dev, "unable to register input device\n"); @@ -261,6 +378,9 @@ static int __devinit adp5588_probe(struct i2c_client *client, if (error) goto err_free_irq; + if (kpad->gpimapsize) + adp5588_report_switch_state(kpad); + device_init_wakeup(&client->dev, 1); i2c_set_clientdata(client, kpad); diff --git a/include/linux/i2c/adp5588.h b/include/linux/i2c/adp5588.h index 02c9af37474..b5f57c498e2 100644 --- a/include/linux/i2c/adp5588.h +++ b/include/linux/i2c/adp5588.h @@ -78,6 +78,40 @@ #define ADP5588_KEYMAPSIZE 80 +#define GPI_PIN_ROW0 97 +#define GPI_PIN_ROW1 98 +#define GPI_PIN_ROW2 99 +#define GPI_PIN_ROW3 100 +#define GPI_PIN_ROW4 101 +#define GPI_PIN_ROW5 102 +#define GPI_PIN_ROW6 103 +#define GPI_PIN_ROW7 104 +#define GPI_PIN_COL0 105 +#define GPI_PIN_COL1 106 +#define GPI_PIN_COL2 107 +#define GPI_PIN_COL3 108 +#define GPI_PIN_COL4 109 +#define GPI_PIN_COL5 110 +#define GPI_PIN_COL6 111 +#define GPI_PIN_COL7 112 +#define GPI_PIN_COL8 113 +#define GPI_PIN_COL9 114 + +#define GPI_PIN_ROW_BASE GPI_PIN_ROW0 +#define GPI_PIN_ROW_END GPI_PIN_ROW7 +#define GPI_PIN_COL_BASE GPI_PIN_COL0 +#define GPI_PIN_COL_END GPI_PIN_COL9 + +#define GPI_PIN_BASE GPI_PIN_ROW_BASE +#define GPI_PIN_END GPI_PIN_COL_END + +#define ADP5588_GPIMAPSIZE_MAX (GPI_PIN_END - GPI_PIN_BASE + 1) + +struct adp5588_gpi_map { + unsigned short pin; + unsigned short sw_evt; +}; + struct adp5588_kpad_platform_data { int rows; /* Number of rows */ int cols; /* Number of columns */ @@ -87,6 +121,8 @@ struct adp5588_kpad_platform_data { unsigned en_keylock:1; /* Enable Key Lock feature */ unsigned short unlock_key1; /* Unlock Key 1 */ unsigned short unlock_key2; /* Unlock Key 2 */ + const struct adp5588_gpi_map *gpimap; + unsigned short gpimapsize; }; struct adp5588_gpio_platform_data { -- cgit v1.2.3-70-g09d2 From 28ed684fa3c0a75b59a00e209afef98aff7fa617 Mon Sep 17 00:00:00 2001 From: Grazvydas Ignotas Date: Mon, 28 Jun 2010 10:59:32 -0700 Subject: Input: gpio-keys - add gpiolib debounce support gpiolib now has debounce support added in .35, so let's make use of it. This allows to use hardware GPIO debouncing on some platforms like OMAP. In case gpiolib debounce setup fails for some GPIO, the driver will fall back to timer based debouncing, which is what it used before. Signed-off-by: Grazvydas Ignotas Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/gpio_keys.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index b8213fd13c3..a9fd147f2ba 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -31,6 +31,7 @@ struct gpio_button_data { struct input_dev *input; struct timer_list timer; struct work_struct work; + int timer_debounce; /* in msecs */ bool disabled; }; @@ -109,7 +110,7 @@ static void gpio_keys_disable_button(struct gpio_button_data *bdata) * Disable IRQ and possible debouncing timer. */ disable_irq(gpio_to_irq(bdata->button->gpio)); - if (bdata->button->debounce_interval) + if (bdata->timer_debounce) del_timer_sync(&bdata->timer); bdata->disabled = true; @@ -347,9 +348,9 @@ static irqreturn_t gpio_keys_isr(int irq, void *dev_id) BUG_ON(irq != gpio_to_irq(button->gpio)); - if (button->debounce_interval) + if (bdata->timer_debounce) mod_timer(&bdata->timer, - jiffies + msecs_to_jiffies(button->debounce_interval)); + jiffies + msecs_to_jiffies(bdata->timer_debounce)); else schedule_work(&bdata->work); @@ -383,6 +384,14 @@ static int __devinit gpio_keys_setup_key(struct platform_device *pdev, goto fail3; } + if (button->debounce_interval) { + error = gpio_set_debounce(button->gpio, + button->debounce_interval * 1000); + /* use timer if gpiolib doesn't provide debounce */ + if (error < 0) + bdata->timer_debounce = button->debounce_interval; + } + irq = gpio_to_irq(button->gpio); if (irq < 0) { error = irq; @@ -498,7 +507,7 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) fail2: while (--i >= 0) { free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]); - if (pdata->buttons[i].debounce_interval) + if (ddata->data[i].timer_debounce) del_timer_sync(&ddata->data[i].timer); cancel_work_sync(&ddata->data[i].work); gpio_free(pdata->buttons[i].gpio); @@ -526,7 +535,7 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev) for (i = 0; i < pdata->nbuttons; i++) { int irq = gpio_to_irq(pdata->buttons[i].gpio); free_irq(irq, &ddata->data[i]); - if (pdata->buttons[i].debounce_interval) + if (ddata->data[i].timer_debounce) del_timer_sync(&ddata->data[i].timer); cancel_work_sync(&ddata->data[i].work); gpio_free(pdata->buttons[i].gpio); -- cgit v1.2.3-70-g09d2 From 1796b983cc4cbbed5e9e478b03591609a2c21987 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 28 Jun 2010 11:34:24 -0700 Subject: Input: lm8323 - fix error handling in lm8323_probe() We reuse the "i" variable later on so if we goto fail3 or fail4 then "i" will be set to the wrong thing and cause a crash. Signed-off-by: Dan Carpenter Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/lm8323.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c index bc696931fed..6ecd37a845f 100644 --- a/drivers/input/keyboard/lm8323.c +++ b/drivers/input/keyboard/lm8323.c @@ -642,6 +642,7 @@ static int __devinit lm8323_probe(struct i2c_client *client, struct lm8323_platform_data *pdata = client->dev.platform_data; struct input_dev *idev; struct lm8323_chip *lm; + int pwm; int i, err; unsigned long tmo; u8 data[2]; @@ -710,8 +711,9 @@ static int __devinit lm8323_probe(struct i2c_client *client, goto fail1; } - for (i = 0; i < LM8323_NUM_PWMS; i++) { - err = init_pwm(lm, i + 1, &client->dev, pdata->pwm_names[i]); + for (pwm = 0; pwm < LM8323_NUM_PWMS; pwm++) { + err = init_pwm(lm, pwm + 1, &client->dev, + pdata->pwm_names[pwm]); if (err < 0) goto fail2; } @@ -764,9 +766,9 @@ fail4: fail3: device_remove_file(&client->dev, &dev_attr_disable_kp); fail2: - while (--i >= 0) - if (lm->pwm[i].enabled) - led_classdev_unregister(&lm->pwm[i].cdev); + while (--pwm >= 0) + if (lm->pwm[pwm].enabled) + led_classdev_unregister(&lm->pwm[pwm].cdev); fail1: input_free_device(idev); kfree(lm); -- cgit v1.2.3-70-g09d2 From 312e8e8a9e2471b0ada7366497fffb3ff1a40e2c Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Sun, 4 Jul 2010 01:21:25 -0700 Subject: Input: mcs - Add MCS touchkey driver This adds support for MELPAS MCS5000/MSC5080 touch key controllers. Signed-off-by: Joonyoung Shim Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 12 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/mcs_touchkey.c | 239 +++++++++++++++++++++++++++++++++ drivers/input/touchscreen/mcs5000_ts.c | 6 +- include/linux/i2c/mcs.h | 34 +++++ include/linux/i2c/mcs5000_ts.h | 24 ---- 6 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 drivers/input/keyboard/mcs_touchkey.c create mode 100644 include/linux/i2c/mcs.h delete mode 100644 include/linux/i2c/mcs5000_ts.h (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index d8fa5d724c5..c7480934cee 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -297,6 +297,18 @@ config KEYBOARD_MAX7359 To compile this driver as a module, choose M here: the module will be called max7359_keypad. +config KEYBOARD_MCS + tristate "MELFAS MCS Touchkey" + depends on I2C + help + Say Y here if you have the MELFAS MCS5000/5080 touchkey controller + chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mcs_touchkey. + config KEYBOARD_IMX tristate "IMX keypad support" depends on ARCH_MXC diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4596d0c6f92..0a16674ed3d 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o +obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o diff --git a/drivers/input/keyboard/mcs_touchkey.c b/drivers/input/keyboard/mcs_touchkey.c new file mode 100644 index 00000000000..63b849d7e90 --- /dev/null +++ b/drivers/input/keyboard/mcs_touchkey.c @@ -0,0 +1,239 @@ +/* + * mcs_touchkey.c - Touchkey driver for MELFAS MCS5000/5080 controller + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: HeungJun Kim + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* MCS5000 Touchkey */ +#define MCS5000_TOUCHKEY_STATUS 0x04 +#define MCS5000_TOUCHKEY_STATUS_PRESS 7 +#define MCS5000_TOUCHKEY_FW 0x0a +#define MCS5000_TOUCHKEY_BASE_VAL 0x61 + +/* MCS5080 Touchkey */ +#define MCS5080_TOUCHKEY_STATUS 0x00 +#define MCS5080_TOUCHKEY_STATUS_PRESS 3 +#define MCS5080_TOUCHKEY_FW 0x01 +#define MCS5080_TOUCHKEY_BASE_VAL 0x1 + +enum mcs_touchkey_type { + MCS5000_TOUCHKEY, + MCS5080_TOUCHKEY, +}; + +struct mcs_touchkey_chip { + unsigned int status_reg; + unsigned int pressbit; + unsigned int press_invert; + unsigned int baseval; +}; + +struct mcs_touchkey_data { + struct i2c_client *client; + struct input_dev *input_dev; + struct mcs_touchkey_chip chip; + unsigned int key_code; + unsigned int key_val; + unsigned short keycodes[]; +}; + +static irqreturn_t mcs_touchkey_interrupt(int irq, void *dev_id) +{ + struct mcs_touchkey_data *data = dev_id; + struct mcs_touchkey_chip *chip = &data->chip; + struct i2c_client *client = data->client; + struct input_dev *input = data->input_dev; + unsigned int key_val; + unsigned int pressed; + int val; + + val = i2c_smbus_read_byte_data(client, chip->status_reg); + if (val < 0) { + dev_err(&client->dev, "i2c read error [%d]\n", val); + goto out; + } + + pressed = (val & (1 << chip->pressbit)) >> chip->pressbit; + if (chip->press_invert) + pressed ^= chip->press_invert; + + /* key_val is 0 when released, so we should use key_val of press. */ + if (pressed) { + key_val = val & (0xff >> (8 - chip->pressbit)); + if (!key_val) + goto out; + key_val -= chip->baseval; + data->key_code = data->keycodes[key_val]; + data->key_val = key_val; + } + + input_event(input, EV_MSC, MSC_SCAN, data->key_val); + input_report_key(input, data->key_code, pressed); + input_sync(input); + + dev_dbg(&client->dev, "key %d %d %s\n", data->key_val, data->key_code, + pressed ? "pressed" : "released"); + + out: + return IRQ_HANDLED; +} + +static int __devinit mcs_touchkey_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct mcs_platform_data *pdata; + struct mcs_touchkey_data *data; + struct input_dev *input_dev; + unsigned int fw_reg; + int fw_ver; + int error; + int i; + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "no platform data defined\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(struct mcs_touchkey_data) + + sizeof(data->keycodes[0]) * (pdata->key_maxval + 1), + GFP_KERNEL); + input_dev = input_allocate_device(); + if (!data || !input_dev) { + dev_err(&client->dev, "Failed to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + data->client = client; + data->input_dev = input_dev; + + if (id->driver_data == MCS5000_TOUCHKEY) { + data->chip.status_reg = MCS5000_TOUCHKEY_STATUS; + data->chip.pressbit = MCS5000_TOUCHKEY_STATUS_PRESS; + data->chip.baseval = MCS5000_TOUCHKEY_BASE_VAL; + fw_reg = MCS5000_TOUCHKEY_FW; + } else { + data->chip.status_reg = MCS5080_TOUCHKEY_STATUS; + data->chip.pressbit = MCS5080_TOUCHKEY_STATUS_PRESS; + data->chip.press_invert = 1; + data->chip.baseval = MCS5080_TOUCHKEY_BASE_VAL; + fw_reg = MCS5080_TOUCHKEY_FW; + } + + fw_ver = i2c_smbus_read_byte_data(client, fw_reg); + if (fw_ver < 0) { + error = fw_ver; + dev_err(&client->dev, "i2c read error[%d]\n", error); + goto err_free_mem; + } + dev_info(&client->dev, "Firmware version: %d\n", fw_ver); + + input_dev->name = "MELPAS MCS Touchkey"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY); + if (!pdata->no_autorepeat) + input_dev->evbit[0] |= BIT_MASK(EV_REP); + input_dev->keycode = data->keycodes; + input_dev->keycodesize = sizeof(data->keycodes[0]); + input_dev->keycodemax = pdata->key_maxval + 1; + + for (i = 0; i < pdata->keymap_size; i++) { + unsigned int val = MCS_KEY_VAL(pdata->keymap[i]); + unsigned int code = MCS_KEY_CODE(pdata->keymap[i]); + + data->keycodes[val] = code; + __set_bit(code, input_dev->keybit); + } + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + input_set_drvdata(input_dev, data); + + if (pdata->cfg_pin) + pdata->cfg_pin(); + + error = request_threaded_irq(client->irq, NULL, mcs_touchkey_interrupt, + IRQF_TRIGGER_FALLING, client->dev.driver->name, data); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_free_mem; + } + + error = input_register_device(input_dev); + if (error) + goto err_free_irq; + + i2c_set_clientdata(client, data); + return 0; + +err_free_irq: + free_irq(client->irq, data); +err_free_mem: + input_free_device(input_dev); + kfree(data); + return error; +} + +static int __devexit mcs_touchkey_remove(struct i2c_client *client) +{ + struct mcs_touchkey_data *data = i2c_get_clientdata(client); + + free_irq(client->irq, data); + input_unregister_device(data->input_dev); + kfree(data); + + return 0; +} + +static const struct i2c_device_id mcs_touchkey_id[] = { + { "mcs5000_touchkey", MCS5000_TOUCHKEY }, + { "mcs5080_touchkey", MCS5080_TOUCHKEY }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcs_touchkey_id); + +static struct i2c_driver mcs_touchkey_driver = { + .driver = { + .name = "mcs_touchkey", + .owner = THIS_MODULE, + }, + .probe = mcs_touchkey_probe, + .remove = __devexit_p(mcs_touchkey_remove), + .id_table = mcs_touchkey_id, +}; + +static int __init mcs_touchkey_init(void) +{ + return i2c_add_driver(&mcs_touchkey_driver); +} + +static void __exit mcs_touchkey_exit(void) +{ + i2c_del_driver(&mcs_touchkey_driver); +} + +module_init(mcs_touchkey_init); +module_exit(mcs_touchkey_exit); + +/* Module information */ +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_AUTHOR("HeungJun Kim "); +MODULE_DESCRIPTION("Touchkey driver for MELFAS MCS5000/5080 controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/mcs5000_ts.c b/drivers/input/touchscreen/mcs5000_ts.c index 1fb0c2f06a4..6ee9940aaf5 100644 --- a/drivers/input/touchscreen/mcs5000_ts.c +++ b/drivers/input/touchscreen/mcs5000_ts.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -105,7 +105,7 @@ enum mcs5000_ts_read_offset { struct mcs5000_ts_data { struct i2c_client *client; struct input_dev *input_dev; - const struct mcs5000_ts_platform_data *platform_data; + const struct mcs_platform_data *platform_data; }; static irqreturn_t mcs5000_ts_interrupt(int irq, void *dev_id) @@ -164,7 +164,7 @@ static irqreturn_t mcs5000_ts_interrupt(int irq, void *dev_id) static void mcs5000_ts_phys_init(struct mcs5000_ts_data *data) { - const struct mcs5000_ts_platform_data *platform_data = + const struct mcs_platform_data *platform_data = data->platform_data; struct i2c_client *client = data->client; diff --git a/include/linux/i2c/mcs.h b/include/linux/i2c/mcs.h new file mode 100644 index 00000000000..725ae7c313f --- /dev/null +++ b/include/linux/i2c/mcs.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009 - 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * Author: HeungJun Kim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __LINUX_MCS_H +#define __LINUX_MCS_H + +#define MCS_KEY_MAP(v, c) ((((v) & 0xff) << 16) | ((c) & 0xffff)) +#define MCS_KEY_VAL(v) (((v) >> 16) & 0xff) +#define MCS_KEY_CODE(v) ((v) & 0xffff) + +struct mcs_platform_data { + void (*cfg_pin)(void); + + /* touchscreen */ + unsigned int x_size; + unsigned int y_size; + + /* touchkey */ + const u32 *keymap; + unsigned int keymap_size; + unsigned int key_maxval; + bool no_autorepeat; +}; + +#endif /* __LINUX_MCS_H */ diff --git a/include/linux/i2c/mcs5000_ts.h b/include/linux/i2c/mcs5000_ts.h deleted file mode 100644 index 5a117b5ca15..00000000000 --- a/include/linux/i2c/mcs5000_ts.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * mcs5000_ts.h - * - * Copyright (C) 2009 Samsung Electronics Co.Ltd - * Author: Joonyoung Shim - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#ifndef __LINUX_MCS5000_TS_H -#define __LINUX_MCS5000_TS_H - -/* platform data for the MELFAS MCS-5000 touchscreen driver */ -struct mcs5000_ts_platform_data { - void (*cfg_pin)(void); - int x_size; - int y_size; -}; - -#endif /* __LINUX_MCS5000_TS_H */ -- cgit v1.2.3-70-g09d2 From 0fffed27f92d9d7a34de9fe017b7082b5958bb93 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Wed, 21 Jul 2010 00:45:10 -0700 Subject: Input: samsung-keypad - Add samsung keypad driver This patch adds support for keypad driver running on Samsung cpus. This driver is tested on GONI and Aquila board using S5PC110 cpu. [ch.naveen@samsung.com: tested on SMDK6410, SMDKC100, and SMDKV210] Signed-off-by: Joonyoung Shim Signed-off-by: Kyungmin Park Tested-by: Naveen Krishna Ch Acked-by: Kukjin Kim Signed-off-by: Dmitry Torokhov --- arch/arm/plat-samsung/include/plat/keypad.h | 43 +++ drivers/input/keyboard/Kconfig | 9 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/samsung-keypad.c | 491 ++++++++++++++++++++++++++++ 4 files changed, 544 insertions(+) create mode 100644 arch/arm/plat-samsung/include/plat/keypad.h create mode 100644 drivers/input/keyboard/samsung-keypad.c (limited to 'drivers/input/keyboard') diff --git a/arch/arm/plat-samsung/include/plat/keypad.h b/arch/arm/plat-samsung/include/plat/keypad.h new file mode 100644 index 00000000000..3a70c125fe5 --- /dev/null +++ b/arch/arm/plat-samsung/include/plat/keypad.h @@ -0,0 +1,43 @@ +/* + * Samsung Platform - Keypad platform data definitions + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __PLAT_SAMSUNG_KEYPAD_H +#define __PLAT_SAMSUNG_KEYPAD_H + +#include + +#define SAMSUNG_MAX_ROWS 8 +#define SAMSUNG_MAX_COLS 8 + +/** + * struct samsung_keypad_platdata - Platform device data for Samsung Keypad. + * @keymap_data: pointer to &matrix_keymap_data. + * @rows: number of keypad row supported. + * @cols: number of keypad col supported. + * @no_autorepeat: disable key autorepeat. + * @wakeup: controls whether the device should be set up as wakeup source. + * @cfg_gpio: configure the GPIO. + * + * Initialisation data specific to either the machine or the platform + * for the device driver to use or call-back when configuring gpio. + */ +struct samsung_keypad_platdata { + const struct matrix_keymap_data *keymap_data; + unsigned int rows; + unsigned int cols; + bool no_autorepeat; + bool wakeup; + + void (*cfg_gpio)(unsigned int rows, unsigned int cols); +}; + +#endif /* __PLAT_SAMSUNG_KEYPAD_H */ diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index c7480934cee..451c38bc580 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -354,6 +354,15 @@ config KEYBOARD_PXA930_ROTARY To compile this driver as a module, choose M here: the module will be called pxa930_rotary. +config KEYBOARD_SAMSUNG + tristate "Samsung keypad support" + depends on SAMSUNG_DEV_KEYPAD + help + Say Y here if you want to use the Samsung keypad. + + To compile this driver as a module, choose M here: the + module will be called samsung-keypad. + config KEYBOARD_STOWAWAY tristate "Stowaway keyboard" select SERIO diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 0a16674ed3d..1a66d5f1ca8 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o +obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o obj-$(CONFIG_KEYBOARD_SUNKBD) += sunkbd.o diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c new file mode 100644 index 00000000000..f689f49e310 --- /dev/null +++ b/drivers/input/keyboard/samsung-keypad.c @@ -0,0 +1,491 @@ +/* + * Samsung keypad driver + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * Author: Donghwa Lee + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SAMSUNG_KEYIFCON 0x00 +#define SAMSUNG_KEYIFSTSCLR 0x04 +#define SAMSUNG_KEYIFCOL 0x08 +#define SAMSUNG_KEYIFROW 0x0c +#define SAMSUNG_KEYIFFC 0x10 + +/* SAMSUNG_KEYIFCON */ +#define SAMSUNG_KEYIFCON_INT_F_EN (1 << 0) +#define SAMSUNG_KEYIFCON_INT_R_EN (1 << 1) +#define SAMSUNG_KEYIFCON_DF_EN (1 << 2) +#define SAMSUNG_KEYIFCON_FC_EN (1 << 3) +#define SAMSUNG_KEYIFCON_WAKEUPEN (1 << 4) + +/* SAMSUNG_KEYIFSTSCLR */ +#define SAMSUNG_KEYIFSTSCLR_P_INT_MASK (0xff << 0) +#define SAMSUNG_KEYIFSTSCLR_R_INT_MASK (0xff << 8) +#define SAMSUNG_KEYIFSTSCLR_R_INT_OFFSET 8 +#define S5PV210_KEYIFSTSCLR_P_INT_MASK (0x3fff << 0) +#define S5PV210_KEYIFSTSCLR_R_INT_MASK (0x3fff << 16) +#define S5PV210_KEYIFSTSCLR_R_INT_OFFSET 16 + +/* SAMSUNG_KEYIFCOL */ +#define SAMSUNG_KEYIFCOL_MASK (0xff << 0) +#define S5PV210_KEYIFCOLEN_MASK (0xff << 8) + +/* SAMSUNG_KEYIFROW */ +#define SAMSUNG_KEYIFROW_MASK (0xff << 0) +#define S5PV210_KEYIFROW_MASK (0x3fff << 0) + +/* SAMSUNG_KEYIFFC */ +#define SAMSUNG_KEYIFFC_MASK (0x3ff << 0) + +enum samsung_keypad_type { + KEYPAD_TYPE_SAMSUNG, + KEYPAD_TYPE_S5PV210, +}; + +struct samsung_keypad { + struct input_dev *input_dev; + struct clk *clk; + void __iomem *base; + wait_queue_head_t wait; + bool stopped; + int irq; + unsigned int row_shift; + unsigned int rows; + unsigned int cols; + unsigned int row_state[SAMSUNG_MAX_COLS]; + unsigned short keycodes[]; +}; + +static int samsung_keypad_is_s5pv210(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + enum samsung_keypad_type type = + platform_get_device_id(pdev)->driver_data; + + return type == KEYPAD_TYPE_S5PV210; +} + +static void samsung_keypad_scan(struct samsung_keypad *keypad, + unsigned int *row_state) +{ + struct device *dev = keypad->input_dev->dev.parent; + unsigned int col; + unsigned int val; + + for (col = 0; col < keypad->cols; col++) { + if (samsung_keypad_is_s5pv210(dev)) { + val = S5PV210_KEYIFCOLEN_MASK; + val &= ~(1 << col) << 8; + } else { + val = SAMSUNG_KEYIFCOL_MASK; + val &= ~(1 << col); + } + + writel(val, keypad->base + SAMSUNG_KEYIFCOL); + mdelay(1); + + val = readl(keypad->base + SAMSUNG_KEYIFROW); + row_state[col] = ~val & ((1 << keypad->rows) - 1); + } + + /* KEYIFCOL reg clear */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); +} + +static bool samsung_keypad_report(struct samsung_keypad *keypad, + unsigned int *row_state) +{ + struct input_dev *input_dev = keypad->input_dev; + unsigned int changed; + unsigned int pressed; + unsigned int key_down = 0; + unsigned int val; + unsigned int col, row; + + for (col = 0; col < keypad->cols; col++) { + changed = row_state[col] ^ keypad->row_state[col]; + key_down |= row_state[col]; + if (!changed) + continue; + + for (row = 0; row < keypad->rows; row++) { + if (!(changed & (1 << row))) + continue; + + pressed = row_state[col] & (1 << row); + + dev_dbg(&keypad->input_dev->dev, + "key %s, row: %d, col: %d\n", + pressed ? "pressed" : "released", row, col); + + val = MATRIX_SCAN_CODE(row, col, keypad->row_shift); + + input_event(input_dev, EV_MSC, MSC_SCAN, val); + input_report_key(input_dev, + keypad->keycodes[val], pressed); + } + input_sync(keypad->input_dev); + } + + memcpy(keypad->row_state, row_state, sizeof(keypad->row_state)); + + return key_down; +} + +static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) +{ + struct samsung_keypad *keypad = dev_id; + unsigned int row_state[SAMSUNG_MAX_COLS]; + unsigned int val; + bool key_down; + + do { + val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR); + /* Clear interrupt. */ + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + + samsung_keypad_scan(keypad, row_state); + + key_down = samsung_keypad_report(keypad, row_state); + if (key_down) + wait_event_timeout(keypad->wait, keypad->stopped, + msecs_to_jiffies(50)); + + } while (key_down && !keypad->stopped); + + return IRQ_HANDLED; +} + +static void samsung_keypad_start(struct samsung_keypad *keypad) +{ + unsigned int val; + + /* Tell IRQ thread that it may poll the device. */ + keypad->stopped = false; + + clk_enable(keypad->clk); + + /* Enable interrupt bits. */ + val = readl(keypad->base + SAMSUNG_KEYIFCON); + val |= SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN; + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + /* KEYIFCOL reg clear. */ + writel(0, keypad->base + SAMSUNG_KEYIFCOL); +} + +static void samsung_keypad_stop(struct samsung_keypad *keypad) +{ + unsigned int val; + + /* Signal IRQ thread to stop polling and disable the handler. */ + keypad->stopped = true; + wake_up(&keypad->wait); + disable_irq(keypad->irq); + + /* Clear interrupt. */ + writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR); + + /* Disable interrupt bits. */ + val = readl(keypad->base + SAMSUNG_KEYIFCON); + val &= ~(SAMSUNG_KEYIFCON_INT_F_EN | SAMSUNG_KEYIFCON_INT_R_EN); + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); + + /* + * Now that chip should not generate interrupts we can safely + * re-enable the handler. + */ + enable_irq(keypad->irq); +} + +static int samsung_keypad_open(struct input_dev *input_dev) +{ + struct samsung_keypad *keypad = input_get_drvdata(input_dev); + + samsung_keypad_start(keypad); + + return 0; +} + +static void samsung_keypad_close(struct input_dev *input_dev) +{ + struct samsung_keypad *keypad = input_get_drvdata(input_dev); + + samsung_keypad_stop(keypad); +} + +static int __devinit samsung_keypad_probe(struct platform_device *pdev) +{ + const struct samsung_keypad_platdata *pdata; + const struct matrix_keymap_data *keymap_data; + struct samsung_keypad *keypad; + struct resource *res; + struct input_dev *input_dev; + unsigned int row_shift; + unsigned int keymap_size; + int error; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data defined\n"); + return -EINVAL; + } + + keymap_data = pdata->keymap_data; + if (!keymap_data) { + dev_err(&pdev->dev, "no keymap data defined\n"); + return -EINVAL; + } + + if (!pdata->rows || pdata->rows > SAMSUNG_MAX_ROWS) + return -EINVAL; + + if (!pdata->cols || pdata->cols > SAMSUNG_MAX_COLS) + return -EINVAL; + + /* initialize the gpio */ + if (pdata->cfg_gpio) + pdata->cfg_gpio(pdata->rows, pdata->cols); + + row_shift = get_count_order(pdata->cols); + keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]); + + keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL); + input_dev = input_allocate_device(); + if (!keypad || !input_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + error = -ENODEV; + goto err_free_mem; + } + + keypad->base = ioremap(res->start, resource_size(res)); + if (!keypad->base) { + error = -EBUSY; + goto err_free_mem; + } + + keypad->clk = clk_get(&pdev->dev, "keypad"); + if (IS_ERR(keypad->clk)) { + dev_err(&pdev->dev, "failed to get keypad clk\n"); + error = PTR_ERR(keypad->clk); + goto err_unmap_base; + } + + keypad->input_dev = input_dev; + keypad->row_shift = row_shift; + keypad->rows = pdata->rows; + keypad->cols = pdata->cols; + init_waitqueue_head(&keypad->wait); + + input_dev->name = pdev->name; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + input_set_drvdata(input_dev, keypad); + + input_dev->open = samsung_keypad_open; + input_dev->close = samsung_keypad_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY); + if (!pdata->no_autorepeat) + input_dev->evbit[0] |= BIT_MASK(EV_REP); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + input_dev->keycode = keypad->keycodes; + input_dev->keycodesize = sizeof(keypad->keycodes[0]); + input_dev->keycodemax = pdata->rows << row_shift; + + matrix_keypad_build_keymap(keymap_data, row_shift, + input_dev->keycode, input_dev->keybit); + + keypad->irq = platform_get_irq(pdev, 0); + if (keypad->irq < 0) { + error = keypad->irq; + goto err_put_clk; + } + + error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq, + IRQF_ONESHOT, dev_name(&pdev->dev), keypad); + if (error) { + dev_err(&pdev->dev, "failed to register keypad interrupt\n"); + goto err_put_clk; + } + + error = input_register_device(keypad->input_dev); + if (error) + goto err_free_irq; + + device_init_wakeup(&pdev->dev, pdata->wakeup); + platform_set_drvdata(pdev, keypad); + return 0; + +err_free_irq: + free_irq(keypad->irq, keypad); +err_put_clk: + clk_put(keypad->clk); +err_unmap_base: + iounmap(keypad->base); +err_free_mem: + input_free_device(input_dev); + kfree(keypad); + + return error; +} + +static int __devexit samsung_keypad_remove(struct platform_device *pdev) +{ + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + platform_set_drvdata(pdev, NULL); + + input_unregister_device(keypad->input_dev); + + /* + * It is safe to free IRQ after unregistering device because + * samsung_keypad_close will shut off interrupts. + */ + free_irq(keypad->irq, keypad); + + clk_put(keypad->clk); + + iounmap(keypad->base); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, + bool enable) +{ + struct device *dev = keypad->input_dev->dev.parent; + unsigned int val; + + clk_enable(keypad->clk); + + val = readl(keypad->base + SAMSUNG_KEYIFCON); + if (enable) { + val |= SAMSUNG_KEYIFCON_WAKEUPEN; + if (device_may_wakeup(dev)) + enable_irq_wake(keypad->irq); + } else { + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; + if (device_may_wakeup(dev)) + disable_irq_wake(keypad->irq); + } + writel(val, keypad->base + SAMSUNG_KEYIFCON); + + clk_disable(keypad->clk); +} + +static int samsung_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + samsung_keypad_stop(keypad); + + samsung_keypad_toggle_wakeup(keypad, true); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static int samsung_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct samsung_keypad *keypad = platform_get_drvdata(pdev); + struct input_dev *input_dev = keypad->input_dev; + + mutex_lock(&input_dev->mutex); + + samsung_keypad_toggle_wakeup(keypad, false); + + if (input_dev->users) + samsung_keypad_start(keypad); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static const struct dev_pm_ops samsung_keypad_pm_ops = { + .suspend = samsung_keypad_suspend, + .resume = samsung_keypad_resume, +}; +#endif + +static struct platform_device_id samsung_keypad_driver_ids[] = { + { + .name = "samsung-keypad", + .driver_data = KEYPAD_TYPE_SAMSUNG, + }, { + .name = "s5pv210-keypad", + .driver_data = KEYPAD_TYPE_S5PV210, + }, + { }, +}; +MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids); + +static struct platform_driver samsung_keypad_driver = { + .probe = samsung_keypad_probe, + .remove = __devexit_p(samsung_keypad_remove), + .driver = { + .name = "samsung-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &samsung_keypad_pm_ops, +#endif + }, + .id_table = samsung_keypad_driver_ids, +}; + +static int __init samsung_keypad_init(void) +{ + return platform_driver_register(&samsung_keypad_driver); +} +module_init(samsung_keypad_init); + +static void __exit samsung_keypad_exit(void) +{ + platform_driver_unregister(&samsung_keypad_driver); +} +module_exit(samsung_keypad_exit); + +MODULE_DESCRIPTION("Samsung keypad driver"); +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_AUTHOR("Donghwa Lee "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-keypad"); -- cgit v1.2.3-70-g09d2 From ba9f507a1bea5ca2fc4a19e227c56b60fd5faca3 Mon Sep 17 00:00:00 2001 From: Xiaolong Chen Date: Mon, 26 Jul 2010 01:01:11 -0700 Subject: Input: adp5588-keys - export unused GPIO pins This patch allows exporting GPIO pins not used by the keypad itself to be accessible from elsewhere. Signed-off-by: Xiaolong Chen Signed-off-by: Yuanbo Ye Signed-off-by: Tao Hu Acked-by: Michael Hennerich Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 209 +++++++++++++++++++++++++++++++++- include/linux/i2c/adp5588.h | 1 + 2 files changed, 208 insertions(+), 2 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index 9096db73c3c..c39ec93c0c5 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,10 @@ #define KEYP_MAX_EVENT 10 +#define MAXGPIO 18 +#define ADP_BANK(offs) ((offs) >> 3) +#define ADP_BIT(offs) (1u << ((offs) & 0x7)) + /* * Early pre 4.0 Silicon required to delay readout by at least 25ms, * since the Event Counter Register updated 25ms after the interrupt @@ -69,6 +74,14 @@ struct adp5588_kpad { unsigned short keycode[ADP5588_KEYMAPSIZE]; const struct adp5588_gpi_map *gpimap; unsigned short gpimapsize; +#ifdef CONFIG_GPIOLIB + unsigned char gpiomap[MAXGPIO]; + bool export_gpio; + struct gpio_chip gc; + struct mutex gpio_lock; /* Protect cached dir, dat_out */ + u8 dat_out[3]; + u8 dir[3]; +#endif }; static int adp5588_read(struct i2c_client *client, u8 reg) @@ -86,6 +99,183 @@ static int adp5588_write(struct i2c_client *client, u8 reg, u8 val) return i2c_smbus_write_byte_data(client, reg, val); } +#ifdef CONFIG_GPIOLIB +static int adp5588_gpio_get_value(struct gpio_chip *chip, unsigned off) +{ + struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + + return !!(adp5588_read(kpad->client, GPIO_DAT_STAT1 + bank) & bit); +} + +static void adp5588_gpio_set_value(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + + mutex_lock(&kpad->gpio_lock); + + if (val) + kpad->dat_out[bank] |= bit; + else + kpad->dat_out[bank] &= ~bit; + + adp5588_write(kpad->client, GPIO_DAT_OUT1 + bank, + kpad->dat_out[bank]); + + mutex_unlock(&kpad->gpio_lock); +} + +static int adp5588_gpio_direction_input(struct gpio_chip *chip, unsigned off) +{ + struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + int ret; + + mutex_lock(&kpad->gpio_lock); + + kpad->dir[bank] &= ~bit; + ret = adp5588_write(kpad->client, GPIO_DIR1 + bank, kpad->dir[bank]); + + mutex_unlock(&kpad->gpio_lock); + + return ret; +} + +static int adp5588_gpio_direction_output(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + int ret; + + mutex_lock(&kpad->gpio_lock); + + kpad->dir[bank] |= bit; + + if (val) + kpad->dat_out[bank] |= bit; + else + kpad->dat_out[bank] &= ~bit; + + ret = adp5588_write(kpad->client, GPIO_DAT_OUT1 + bank, + kpad->dat_out[bank]); + ret |= adp5588_write(kpad->client, GPIO_DIR1 + bank, + kpad->dir[bank]); + + mutex_unlock(&kpad->gpio_lock); + + return ret; +} + +static int __devinit adp5588_gpio_add(struct device *dev) +{ + struct adp5588_kpad *kpad = dev_get_drvdata(dev); + const struct adp5588_kpad_platform_data *pdata = dev->platform_data; + const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; + int i, error; + + if (gpio_data) { + int j = 0; + bool pin_used[MAXGPIO]; + + for (i = 0; i < pdata->rows; i++) + pin_used[i] = true; + + for (i = 0; i < pdata->cols; i++) + pin_used[i + GPI_PIN_COL_BASE - GPI_PIN_BASE] = true; + + for (i = 0; i < kpad->gpimapsize; i++) + pin_used[kpad->gpimap[i].pin - GPI_PIN_BASE] = true; + + for (i = 0; i < MAXGPIO; i++) { + if (!pin_used[i]) + kpad->gpiomap[j++] = i; + } + kpad->gc.ngpio = j; + + if (kpad->gc.ngpio) + kpad->export_gpio = true; + } + + if (!kpad->export_gpio) { + dev_info(dev, "No unused gpios left to export\n"); + return 0; + } + + kpad->gc.direction_input = adp5588_gpio_direction_input; + kpad->gc.direction_output = adp5588_gpio_direction_output; + kpad->gc.get = adp5588_gpio_get_value; + kpad->gc.set = adp5588_gpio_set_value; + kpad->gc.can_sleep = 1; + + kpad->gc.base = gpio_data->gpio_start; + kpad->gc.label = kpad->client->name; + kpad->gc.owner = THIS_MODULE; + + mutex_init(&kpad->gpio_lock); + + error = gpiochip_add(&kpad->gc); + if (error) { + dev_err(dev, "gpiochip_add failed, err: %d\n", error); + return error; + } + + for (i = 0; i <= ADP_BANK(MAXGPIO); i++) { + kpad->dat_out[i] = adp5588_read(kpad->client, + GPIO_DAT_OUT1 + i); + kpad->dir[i] = adp5588_read(kpad->client, GPIO_DIR1 + i); + } + + if (gpio_data->setup) { + error = gpio_data->setup(kpad->client, + kpad->gc.base, kpad->gc.ngpio, + gpio_data->context); + if (error) + dev_warn(dev, "setup failed, %d\n", error); + } + + return 0; +} + +static void __devexit adp5588_gpio_remove(struct device *dev) +{ + struct adp5588_kpad *kpad = dev_get_drvdata(dev); + const struct adp5588_kpad_platform_data *pdata = dev->platform_data; + const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; + int error; + + if (!kpad->export_gpio) + return; + + if (gpio_data->teardown) { + error = gpio_data->teardown(kpad->client, + kpad->gc.base, kpad->gc.ngpio, + gpio_data->context); + if (error) + dev_warn(dev, "teardown failed %d\n", error); + } + + error = gpiochip_remove(&kpad->gc); + if (error) + dev_warn(dev, "gpiochip_remove failed %d\n", error); +} +#else +static inline int adp5588_gpio_add(struct device *dev) +{ + return 0; +} + +static inline void adp5588_gpio_remove(struct device *dev) +{ +} +#endif + static void adp5588_report_events(struct adp5588_kpad *kpad, int ev_cnt) { int i, j; @@ -150,7 +340,8 @@ static irqreturn_t adp5588_irq(int irq, void *handle) static int __devinit adp5588_setup(struct i2c_client *client) { - struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; + const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; + const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; int i, ret; unsigned char evt_mode1 = 0, evt_mode2 = 0, evt_mode3 = 0; @@ -184,6 +375,15 @@ static int __devinit adp5588_setup(struct i2c_client *client) ret |= adp5588_write(client, GPI_EM3, evt_mode3); } + if (gpio_data) { + for (i = 0; i <= ADP_BANK(MAXGPIO); i++) { + int pull_mask = gpio_data->pullup_dis_mask; + + ret |= adp5588_write(client, GPIO_PULL1 + i, + (pull_mask >> (8 * i)) & 0xFF); + } + } + ret |= adp5588_write(client, INT_STAT, CMP2_INT | CMP1_INT | OVR_FLOW_INT | K_LCK_INT | GPI_INT | KE_INT); /* Status is W1C */ @@ -240,7 +440,7 @@ static int __devinit adp5588_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct adp5588_kpad *kpad; - struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; + const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data; struct input_dev *input; unsigned int revid; int ret, i; @@ -381,6 +581,10 @@ static int __devinit adp5588_probe(struct i2c_client *client, if (kpad->gpimapsize) adp5588_report_switch_state(kpad); + error = adp5588_gpio_add(&client->dev); + if (error) + goto err_free_irq; + device_init_wakeup(&client->dev, 1); i2c_set_clientdata(client, kpad); @@ -407,6 +611,7 @@ static int __devexit adp5588_remove(struct i2c_client *client) free_irq(client->irq, kpad); cancel_delayed_work_sync(&kpad->work); input_unregister_device(kpad->input); + adp5588_gpio_remove(&client->dev); kfree(kpad); return 0; diff --git a/include/linux/i2c/adp5588.h b/include/linux/i2c/adp5588.h index b5f57c498e2..269181b8f62 100644 --- a/include/linux/i2c/adp5588.h +++ b/include/linux/i2c/adp5588.h @@ -123,6 +123,7 @@ struct adp5588_kpad_platform_data { unsigned short unlock_key2; /* Unlock Key 2 */ const struct adp5588_gpi_map *gpimap; unsigned short gpimapsize; + const struct adp5588_gpio_platform_data *gpio_data; }; struct adp5588_gpio_platform_data { -- cgit v1.2.3-70-g09d2 From 0d87c7228a49e8342d60dd552892e470e0b291fa Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 2 Aug 2010 18:33:26 -0700 Subject: Input: adp5588-keypad - fix NULL dereference in adp5588_gpio_add() The kpad structure is assigned to i2c client via i2s_set_clientdata() at the end of adp5588_probe(), but in adp5588_gpio_add() we tried to access it (via dev_get_drvdata! which is not nice at all) causing an oops. Let's pass pointer to kpad directly into adp5588_gpio_add() and adp5588_gpio_remove() to avoid accessing driver data before it is set up. Also split out building of gpiomap into a separate function to clear the logic. Reported-by: Michael Hennerich Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/adp5588-keys.c | 66 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 29 deletions(-) (limited to 'drivers/input/keyboard') diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c index c39ec93c0c5..d6918cb966c 100644 --- a/drivers/input/keyboard/adp5588-keys.c +++ b/drivers/input/keyboard/adp5588-keys.c @@ -173,41 +173,49 @@ static int adp5588_gpio_direction_output(struct gpio_chip *chip, return ret; } -static int __devinit adp5588_gpio_add(struct device *dev) +static int __devinit adp5588_build_gpiomap(struct adp5588_kpad *kpad, + const struct adp5588_kpad_platform_data *pdata) { - struct adp5588_kpad *kpad = dev_get_drvdata(dev); - const struct adp5588_kpad_platform_data *pdata = dev->platform_data; - const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; - int i, error; + bool pin_used[MAXGPIO]; + int n_unused = 0; + int i; - if (gpio_data) { - int j = 0; - bool pin_used[MAXGPIO]; + memset(pin_used, 0, sizeof(pin_used)); - for (i = 0; i < pdata->rows; i++) - pin_used[i] = true; + for (i = 0; i < pdata->rows; i++) + pin_used[i] = true; - for (i = 0; i < pdata->cols; i++) - pin_used[i + GPI_PIN_COL_BASE - GPI_PIN_BASE] = true; + for (i = 0; i < pdata->cols; i++) + pin_used[i + GPI_PIN_COL_BASE - GPI_PIN_BASE] = true; - for (i = 0; i < kpad->gpimapsize; i++) - pin_used[kpad->gpimap[i].pin - GPI_PIN_BASE] = true; + for (i = 0; i < kpad->gpimapsize; i++) + pin_used[kpad->gpimap[i].pin - GPI_PIN_BASE] = true; - for (i = 0; i < MAXGPIO; i++) { - if (!pin_used[i]) - kpad->gpiomap[j++] = i; - } - kpad->gc.ngpio = j; + for (i = 0; i < MAXGPIO; i++) + if (!pin_used[i]) + kpad->gpiomap[n_unused++] = i; - if (kpad->gc.ngpio) - kpad->export_gpio = true; - } + return n_unused; +} - if (!kpad->export_gpio) { +static int __devinit adp5588_gpio_add(struct adp5588_kpad *kpad) +{ + struct device *dev = &kpad->client->dev; + const struct adp5588_kpad_platform_data *pdata = dev->platform_data; + const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; + int i, error; + + if (!gpio_data) + return 0; + + kpad->gc.ngpio = adp5588_build_gpiomap(kpad, pdata); + if (kpad->gc.ngpio == 0) { dev_info(dev, "No unused gpios left to export\n"); return 0; } + kpad->export_gpio = true; + kpad->gc.direction_input = adp5588_gpio_direction_input; kpad->gc.direction_output = adp5588_gpio_direction_output; kpad->gc.get = adp5588_gpio_get_value; @@ -243,9 +251,9 @@ static int __devinit adp5588_gpio_add(struct device *dev) return 0; } -static void __devexit adp5588_gpio_remove(struct device *dev) +static void __devexit adp5588_gpio_remove(struct adp5588_kpad *kpad) { - struct adp5588_kpad *kpad = dev_get_drvdata(dev); + struct device *dev = &kpad->client->dev; const struct adp5588_kpad_platform_data *pdata = dev->platform_data; const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data; int error; @@ -266,12 +274,12 @@ static void __devexit adp5588_gpio_remove(struct device *dev) dev_warn(dev, "gpiochip_remove failed %d\n", error); } #else -static inline int adp5588_gpio_add(struct device *dev) +static inline int adp5588_gpio_add(struct adp5588_kpad *kpad) { return 0; } -static inline void adp5588_gpio_remove(struct device *dev) +static inline void adp5588_gpio_remove(struct adp5588_kpad *kpad) { } #endif @@ -581,7 +589,7 @@ static int __devinit adp5588_probe(struct i2c_client *client, if (kpad->gpimapsize) adp5588_report_switch_state(kpad); - error = adp5588_gpio_add(&client->dev); + error = adp5588_gpio_add(kpad); if (error) goto err_free_irq; @@ -611,7 +619,7 @@ static int __devexit adp5588_remove(struct i2c_client *client) free_irq(client->irq, kpad); cancel_delayed_work_sync(&kpad->work); input_unregister_device(kpad->input); - adp5588_gpio_remove(&client->dev); + adp5588_gpio_remove(kpad); kfree(kpad); return 0; -- cgit v1.2.3-70-g09d2