Skip to content

Commit

Permalink
Input: gpio_keys - add support for device-tree platform data
Browse files Browse the repository at this point in the history
This patch enables fetching configuration data, which is normally provided
via platform_data, from the device-tree instead.

If the device is configured from device-tree data, the platform_data struct
is not used, and button data needs to be allocated dynamically. Big part of
this patch deals with confining pdata usage to the probe function, to make
this possible.

Signed-off-by: David Jander <david@protonic.nl>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
  • Loading branch information
David Jander authored and Dmitry Torokhov committed Jul 10, 2011
1 parent eadba0c commit fd05d08
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 16 deletions.
36 changes: 36 additions & 0 deletions Documentation/devicetree/bindings/gpio/gpio_keys.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Device-Tree bindings for input/gpio_keys.c keyboard driver

Required properties:
- compatible = "gpio-keys";

Optional properties:
- autorepeat: Boolean, Enable auto repeat feature of Linux input
subsystem.

Each button (key) is represented as a sub-node of "gpio-keys":
Subnode properties:

- gpios: OF devcie-tree gpio specificatin.
- label: Descriptive name of the key.
- linux,code: Keycode to emit.

Optional subnode-properties:
- linux,input-type: Specify event type this button/key generates.
If not specified defaults to <1> == EV_KEY.
- debounce-interval: Debouncing interval time in milliseconds.
If not specified defaults to 5.
- gpio-key,wakeup: Boolean, button can wake-up the system.

Example nodes:

gpio_keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
button@21 {
label = "GPIO Key UP";
linux,code = <103>;
gpios = <&gpio1 0 1>;
};
...
148 changes: 132 additions & 16 deletions drivers/input/keyboard/gpio_keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Driver for keys on GPIO lines capable of generating interrupts.
*
* Copyright 2005 Phil Blundell
* Copyright 2010, 2011 David Jander <david@protonic.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
Expand All @@ -25,6 +26,8 @@
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>

struct gpio_button_data {
struct gpio_keys_button *button;
Expand Down Expand Up @@ -445,15 +448,120 @@ static void gpio_keys_close(struct input_dev *input)
ddata->disable(input->dev.parent);
}

/*
* Handlers for alternative sources of platform_data
*/
#ifdef CONFIG_OF
/*
* Translate OpenFirmware node properties into platform_data
*/
static int gpio_keys_get_devtree_pdata(struct device *dev,
struct gpio_keys_platform_data *pdata)
{
struct device_node *node, *pp;
int i;
struct gpio_keys_button *buttons;
const u32 *reg;
int len;

node = dev->of_node;
if (node == NULL)
return -ENODEV;

memset(pdata, 0, sizeof *pdata);

pdata->rep = !!of_get_property(node, "autorepeat", &len);

/* First count the subnodes */
pdata->nbuttons = 0;
pp = NULL;
while ((pp = of_get_next_child(node, pp)))
pdata->nbuttons++;

if (pdata->nbuttons == 0)
return -ENODEV;

buttons = kzalloc(pdata->nbuttons * (sizeof *buttons), GFP_KERNEL);
if (!buttons)
return -ENODEV;

pp = NULL;
i = 0;
while ((pp = of_get_next_child(node, pp))) {
enum of_gpio_flags flags;

if (!of_find_property(pp, "gpios", NULL)) {
pdata->nbuttons--;
dev_warn(dev, "Found button without gpios\n");
continue;
}
buttons[i].gpio = of_get_gpio_flags(pp, 0, &flags);
buttons[i].active_low = flags & OF_GPIO_ACTIVE_LOW;

reg = of_get_property(pp, "linux,code", &len);
if (!reg) {
dev_err(dev, "Button without keycode: 0x%x\n", buttons[i].gpio);
goto out_fail;
}
buttons[i].code = be32_to_cpup(reg);

buttons[i].desc = of_get_property(pp, "label", &len);

reg = of_get_property(pp, "linux,input-type", &len);
buttons[i].type = reg ? be32_to_cpup(reg) : EV_KEY;

buttons[i].wakeup = !!of_get_property(pp, "gpio-key,wakeup", NULL);

reg = of_get_property(pp, "debounce-interval", &len);
buttons[i].debounce_interval = reg ? be32_to_cpup(reg) : 5;

i++;
}

pdata->buttons = buttons;

return 0;

out_fail:
kfree(buttons);
return -ENODEV;
}

static struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

#else

static int gpio_keys_get_devtree_pdata(struct device *dev,
struct gpio_keys_platform_data *altp)
{
return -ENODEV;
}

#define gpio_keys_of_match NULL

#endif

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata;
struct device *dev = &pdev->dev;
struct gpio_keys_platform_data alt_pdata;
struct input_dev *input;
int i, error;
int wakeup = 0;

if (!pdata) {
error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
if (error)
return error;
pdata = &alt_pdata;
}

ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
Expand Down Expand Up @@ -544,13 +652,15 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev)
fail1:
input_free_device(input);
kfree(ddata);
/* If we have no platform_data, we allocated buttons dynamically. */
if (!pdev->dev.platform_data)
kfree(pdata->buttons);

return error;
}

static int __devexit gpio_keys_remove(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
struct input_dev *input = ddata->input;
int i;
Expand All @@ -559,32 +669,39 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev)

device_init_wakeup(&pdev->dev, 0);

for (i = 0; i < pdata->nbuttons; i++) {
int irq = gpio_to_irq(pdata->buttons[i].gpio);
for (i = 0; i < ddata->n_buttons; i++) {
int irq = gpio_to_irq(ddata->data[i].button->gpio);
free_irq(irq, &ddata->data[i]);
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);
gpio_free(ddata->data[i].button->gpio);
}

input_unregister_device(input);

/*
* If we had no platform_data, we allocated buttons dynamically, and
* must free them here. ddata->data[0].button is the pointer to the
* beginning of the allocated array.
*/
if (!pdev->dev.platform_data)
kfree(ddata->data[0].button);

kfree(ddata);

return 0;
}


#ifdef CONFIG_PM
static int gpio_keys_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
int i;

if (device_may_wakeup(&pdev->dev)) {
for (i = 0; i < pdata->nbuttons; i++) {
struct gpio_keys_button *button = &pdata->buttons[i];
if (device_may_wakeup(dev)) {
for (i = 0; i < ddata->n_buttons; i++) {
struct gpio_keys_button *button = ddata->data[i].button;
if (button->wakeup) {
int irq = gpio_to_irq(button->gpio);
enable_irq_wake(irq);
Expand All @@ -597,15 +714,13 @@ static int gpio_keys_suspend(struct device *dev)

static int gpio_keys_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
int i;

for (i = 0; i < pdata->nbuttons; i++) {
for (i = 0; i < ddata->n_buttons; i++) {

struct gpio_keys_button *button = &pdata->buttons[i];
if (button->wakeup && device_may_wakeup(&pdev->dev)) {
struct gpio_keys_button *button = ddata->data[i].button;
if (button->wakeup && device_may_wakeup(dev)) {
int irq = gpio_to_irq(button->gpio);
disable_irq_wake(irq);
}
Expand All @@ -632,6 +747,7 @@ static struct platform_driver gpio_keys_device_driver = {
#ifdef CONFIG_PM
.pm = &gpio_keys_pm_ops,
#endif
.of_match_table = gpio_keys_of_match,
}
};

Expand Down

0 comments on commit fd05d08

Please sign in to comment.