So far this adds support for one Netgear model only, but it's designed and ready to add many more device. We could hopefully import database from OpenWrt. Signed-off-by: Rafał Miłecki <zajec5@xxxxxxxxx> --- This should apply cleanly on top of (still not pushed): MIPS: BCM47XX: Prepare support for LEDs --- arch/mips/bcm47xx/Kconfig | 2 + arch/mips/bcm47xx/Makefile | 2 +- arch/mips/bcm47xx/bcm47xx_private.h | 3 + arch/mips/bcm47xx/buttons.c | 225 +++++++++++++++++++++++++++++++++++ arch/mips/bcm47xx/setup.c | 1 + 5 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 arch/mips/bcm47xx/buttons.c diff --git a/arch/mips/bcm47xx/Kconfig b/arch/mips/bcm47xx/Kconfig index 09fc922..f3e9684 100644 --- a/arch/mips/bcm47xx/Kconfig +++ b/arch/mips/bcm47xx/Kconfig @@ -11,6 +11,7 @@ config BCM47XX_SSB select SSB_PCICORE_HOSTMODE if PCI select SSB_DRIVER_GPIO select GPIOLIB + select INPUT select LEDS_GPIO_REGISTER default y help @@ -28,6 +29,7 @@ config BCM47XX_BCMA select BCMA_DRIVER_PCI_HOSTMODE if PCI select BCMA_DRIVER_GPIO select GPIOLIB + select INPUT select LEDS_GPIO_REGISTER default y help diff --git a/arch/mips/bcm47xx/Makefile b/arch/mips/bcm47xx/Makefile index 84e9aed..006a05e 100644 --- a/arch/mips/bcm47xx/Makefile +++ b/arch/mips/bcm47xx/Makefile @@ -4,5 +4,5 @@ # obj-y += irq.o nvram.o prom.o serial.o setup.o time.o sprom.o -obj-y += board.o leds.o +obj-y += board.o buttons.o leds.o obj-$(CONFIG_BCM47XX_SSB) += wgt634u.o diff --git a/arch/mips/bcm47xx/bcm47xx_private.h b/arch/mips/bcm47xx/bcm47xx_private.h index 1a1e600..5c94ace 100644 --- a/arch/mips/bcm47xx/bcm47xx_private.h +++ b/arch/mips/bcm47xx/bcm47xx_private.h @@ -3,6 +3,9 @@ #include <linux/kernel.h> +/* buttons.c */ +int __init bcm47xx_buttons_register(void); + /* leds.c */ void __init bcm47xx_leds_register(void); diff --git a/arch/mips/bcm47xx/buttons.c b/arch/mips/bcm47xx/buttons.c new file mode 100644 index 0000000..058cb64 --- /dev/null +++ b/arch/mips/bcm47xx/buttons.c @@ -0,0 +1,225 @@ +#include "bcm47xx_private.h" + +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/ssb/ssb_embedded.h> +#include <bcm47xx_board.h> +#include <bcm47xx.h> + +struct input_dev *input; + +/************************************************** + * Database + **************************************************/ + +struct bcm47xx_button { + unsigned int code; + int gpio; + bool active_low; + bool last_value; + struct work_struct work; +}; + +static struct bcm47xx_button +bcm47xx_buttons_netgear_wndr4500_v1[] = { + { + .code = KEY_WPS_BUTTON, + .gpio = 4, + .active_low = 1, + }, + { + .code = KEY_RFKILL, + .gpio = 5, + .active_low = 1, + }, + { + .code = KEY_RESTART, + .gpio = 6, + .active_low = 1, + }, +}; + +/************************************************** + * Handlers + **************************************************/ + +static void bcm47xx_buttons_work(struct work_struct *work) +{ + struct bcm47xx_button *button = + container_of(work, struct bcm47xx_button, work); + + input_event(input, EV_KEY, button->code, button->last_value ^ 1); + input_sync(input); +} + +static void bcm47xx_buttons_gpio_polarity(u32 mask, u32 value) +{ + switch (bcm47xx_bus_type) { +#ifdef CONFIG_BCM47XX_SSB + case BCM47XX_BUS_TYPE_SSB: + ssb_gpio_polarity(&bcm47xx_bus.ssb, mask, value); + return; +#endif +#ifdef CONFIG_BCM47XX_BCMA + case BCM47XX_BUS_TYPE_BCMA: + bcma_chipco_gpio_polarity(&bcm47xx_bus.bcma.bus.drv_cc, mask, + value); + return; +#endif + } + WARN_ON(1); +} + +static irqreturn_t bcm47xx_buttons_irq(int irq, void *dev_id) +{ + struct bcm47xx_button *button = dev_id; + int gpio = button->gpio; + u32 val = __gpio_get_value(gpio); + + if (val != button->last_value) { + button->last_value = val; + + /* + * As soon as button state changes, adjust interrupt polarity. + * This prevents hardware from keep generating interrupts for + * the current state. + */ + bcm47xx_buttons_gpio_polarity(BIT(gpio), val ? BIT(gpio) : 0); + + schedule_work(&button->work); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/************************************************** + * Init + **************************************************/ + +struct { + struct bcm47xx_button *buttons; + int num_buttons; +} bcm47xx_buttons_bdata; + +#define bcm47xx_set_bdata(dev_buttons) do { \ + bcm47xx_buttons_bdata.buttons = dev_buttons; \ + bcm47xx_buttons_bdata.num_buttons = ARRAY_SIZE(dev_buttons); \ +} while (0) + +static int __init bcm47xx_button_setup(struct bcm47xx_button *button) +{ + int gpio = button->gpio; + int val, err; + + err = gpio_request_one(gpio, GPIOF_IN, "button"); + if (err) { + pr_err("Failed to request GPIO %d, error %d\n", gpio, err); + return err; + } + + gpio_direction_input(gpio); + val = __gpio_get_value(gpio); + bcm47xx_buttons_gpio_polarity(BIT(gpio), val ? BIT(gpio) : 0); + button->last_value = val; + + val = gpio_to_irq(button->gpio); + err = request_irq(val, bcm47xx_buttons_irq, IRQF_SHARED, "gpio", + button); + if (err) { + pr_err("Failed to reqeust irq %d\n", val); + gpio_free(gpio); + return err; + } + + INIT_WORK(&button->work, bcm47xx_buttons_work); + + return 0; +} + +int __init bcm47xx_buttons_register(void) +{ +#ifdef CONFIG_BCM47XX_SSB + struct ssb_bus *ssb; +#endif +#ifdef CONFIG_BCM47XX_BCMA + struct bcma_drv_cc *bcma_cc; +#endif + enum bcm47xx_board board = bcm47xx_board_get(); + u32 gpio_mask = 0; + int i, err; + + switch (board) { + case BCM47XX_BOARD_NETGEAR_WNDR4500V1: + bcm47xx_set_bdata(bcm47xx_buttons_netgear_wndr4500_v1); + break; + default: + pr_debug("No buttons configuration found for this device\n"); + return -ENOTSUPP; + } + + input = input_allocate_device(); + if (!input) { + err = -ENOMEM; + goto err_input_alloc; + } + + input->name = "bcm47xx_buttons"; + + for (i = 0; i < bcm47xx_buttons_bdata.num_buttons; i++) { + struct bcm47xx_button *button; + + button = &bcm47xx_buttons_bdata.buttons[i]; + gpio_mask |= BIT(button->gpio); + err = bcm47xx_button_setup(button); + if (err) + goto err_buttons_setup; + input_set_capability(input, EV_KEY, button->code); + } + + err = input_register_device(input); + if (err) { + pr_err("Unable to register input device, error: %d\n", err); + goto err_input_reg; + } + + /* + * Set a list of GPIOs that should generate interrupts and enable GPIO + * interrupts in the ChipCommon core. + */ + switch (bcm47xx_bus_type) { +#ifdef CONFIG_BCM47XX_SSB + case BCM47XX_BUS_TYPE_SSB: + ssb = &bcm47xx_bus.ssb; + ssb_gpio_intmask(ssb, gpio_mask, gpio_mask); + if (&ssb->chipco) + chipco_set32(&ssb->chipco, SSB_CHIPCO_IRQMASK, + SSB_CHIPCO_IRQ_GPIO); + break; +#endif +#ifdef CONFIG_BCM47XX_BCMA + case BCM47XX_BUS_TYPE_BCMA: + bcma_cc = &bcm47xx_bus.bcma.bus.drv_cc; + bcma_chipco_gpio_intmask(bcma_cc, gpio_mask, gpio_mask); + bcma_cc_set32(bcma_cc, BCMA_CC_IRQMASK, BCMA_CC_IRQ_GPIO); + break; +#endif + } + + return 0; + +err_input_reg: +err_buttons_setup: + for (i = i - 1; i >= 0; i--) { + struct bcm47xx_button *button; + + button = &bcm47xx_buttons_bdata.buttons[i]; + free_irq(gpio_to_irq(button->gpio), button); + gpio_free(button->gpio); + } + input_free_device(input); +err_input_alloc: + pr_err("Failed to register bcm47xx buttons, error: %d\n", err); + return err; +} diff --git a/arch/mips/bcm47xx/setup.c b/arch/mips/bcm47xx/setup.c index 7e61c0b..a791124 100644 --- a/arch/mips/bcm47xx/setup.c +++ b/arch/mips/bcm47xx/setup.c @@ -242,6 +242,7 @@ static int __init bcm47xx_register_bus_complete(void) #endif } + bcm47xx_buttons_register(); bcm47xx_leds_register(); return 0; -- 1.7.10.4