[PATCH] MIPS: BCM47XX: Prepare support for GPIO buttons

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux