Subject: [RFC PATCH 1/2] Hardware button support for Wireless cards: radiobtn

From: Ivo van Doorn <[email-masked]>
Date: Thu, 25 May 2006 17:16:00 +0200

Add radiobtn driver.
This driver creates an iput device for each registered button
and will poll the device frequently to check the latest status of the button.
Once the status has changed it will try to enable or disable the radio
and send an event to the input device.

Signed-off-by: Ivo van Doorn <[email-masked]>

diff -uprN wireless-dev/drivers/input/misc/Kconfig wireless-dev-radiobtn/drivers/input/misc/Kconfig
--- wireless-dev/drivers/input/misc/Kconfig	2006-04-27 00:52:53.000000000 +0200
+++ wireless-dev-radiobtn/drivers/input/misc/Kconfig	2006-05-25 16:29:23.000000000 +0200
@@ -79,4 +79,13 @@ config HP_SDC_RTC
 	  Say Y here if you want to support the built-in real time clock
 	  of the HP SDC controller.
 
+config RADIOBTN
+	tristate "Hardware radio button support"
+	help
+	  Say Y here if you have an integrated WiFi or Bluetooth device
+	  which contains an hardware button for enabling or disabling the radio.
+	  When this driver is used, this driver will make sure the radio will
+	  be correctly enabled and disabled when needed. It will then also
+	  use the created input device to signal user space of this event
+	  which allows userspace to take additional actions.
 endif
diff -uprN wireless-dev/drivers/input/misc/Makefile wireless-dev-radiobtn/drivers/input/misc/Makefile
--- wireless-dev/drivers/input/misc/Makefile	2006-04-27 00:52:53.000000000 +0200
+++ wireless-dev-radiobtn/drivers/input/misc/Makefile	2006-05-25 16:25:43.000000000 +0200
@@ -11,3 +11,4 @@ obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
 obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
 obj-$(CONFIG_HP_SDC_RTC)		+= hp_sdc_rtc.o
 obj-$(CONFIG_INPUT_IXP4XX_BEEPER)	+= ixp4xx-beeper.o
+obj-$(CONFIG_RADIOBTN)			+= radiobtn.o
diff -uprN wireless-dev/drivers/input/misc/radiobtn.c wireless-dev-radiobtn/drivers/input/misc/radiobtn.c
--- wireless-dev/drivers/input/misc/radiobtn.c	1970-01-01 01:00:00.000000000 +0100
+++ wireless-dev-radiobtn/drivers/input/misc/radiobtn.c	2006-05-25 16:23:59.000000000 +0200
@@ -0,0 +1,158 @@
+/*
+	Copyright (C) 2006 Ivo van Doorn
+
+	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.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the
+	Free Software Foundation, Inc.,
+	59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+	Radio hardware button support
+	Poll frequently all registered hardware for hardware button status,
+	if changed enabled or disable the radio of that hardware device.
+	Send signal to input device to inform userspace about the new status.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/radiobtn.h>
+
+MODULE_AUTHOR("Ivo van Doorn <[email-masked]>");
+MODULE_VERSION("1.0");
+MODULE_DESCRIPTION("Radio hardware button support");
+MODULE_LICENSE("GPL");
+
+void radiobtn_poll(unsigned long data)
+{
+	struct radio_button *radiobtn = (struct radio_button*)data;
+	int state;
+
+	/*
+	 * Poll for the new state.
+	 * Check if the state has changed.
+	 */
+	state = !!radiobtn->button_poll(radiobtn->data);
+	if (state != radiobtn->current_state) {
+		radiobtn->current_state = state;
+
+		/*
+		 * Enable or disable the radio when this
+		 * should be done in software.
+		 */
+		if (state && radiobtn->enable_radio)
+			radiobtn->enable_radio(radiobtn->data);
+		else if (!state && radiobtn->disable_radio)
+			radiobtn->disable_radio(radiobtn->data);
+
+		/*
+		 * Report key event.
+		 */
+		input_report_key(radiobtn->input_dev, KEY_RADIO, 1);
+		input_sync(radiobtn->input_dev);
+		input_report_key(radiobtn->input_dev, KEY_RADIO, 0);
+		input_sync(radiobtn->input_dev);
+	}
+
+	/*
+	 * Check if polling has been disabled.
+	 */
+	if (radiobtn->poll_delay != 0) {
+		radiobtn->poll_timer.expires =
+			jiffies + msecs_to_jiffies(radiobtn->poll_delay);
+		add_timer(&radiobtn->poll_timer);
+	}
+}
+
+int radiobtn_register_device(struct radio_button *radiobtn)
+{
+	int status;
+
+	/*
+	 * Check if all mandatory fields have been set.
+	 */
+	if (radiobtn->poll_delay == 0 || radiobtn->button_poll == NULL)
+		return -EINVAL;
+
+	/*
+	 * Allocate, initialize and register input device.
+	 */
+	radiobtn->input_dev = input_allocate_device();
+	if (!radiobtn->input_dev) {
+		printk(KERN_ERR "Failed to allocate input device %s.\n",
+			radiobtn->dev_name);
+		return -ENOMEM;
+	}
+
+	radiobtn->input_dev->name = "Radio button";
+	radiobtn->input_dev->phys = strcat("radiobtn/", radiobtn->dev_name);
+	radiobtn->input_dev->id.bustype = BUS_HOST;
+	set_bit(KEY_RADIO, radiobtn->input_dev->keybit);
+
+	status = input_register_device(radiobtn->input_dev);
+	if (status) {
+		printk(KERN_ERR "Failed to register input device %s.\n",
+			radiobtn->dev_name);
+		input_free_device(radiobtn->input_dev);
+		return status;
+	}
+
+	/*
+	 * Initialize timer.
+	 */
+	init_timer(&radiobtn->poll_timer);
+	radiobtn->poll_timer.function = radiobtn_poll;
+	radiobtn->poll_timer.data = (unsigned long)radiobtn;
+	radiobtn->poll_timer.expires =
+		jiffies + msecs_to_jiffies(radiobtn->poll_delay);
+	add_timer(&radiobtn->poll_timer);
+
+	printk(KERN_INFO "Created new %s: %s.\n",
+		radiobtn->input_dev->name, radiobtn->input_dev->phys);
+
+	return 0;
+}
+
+void radiobtn_unregister_device(struct radio_button *radiobtn)
+{
+	/*
+	 * Stop timer.
+	 */
+	radiobtn->poll_delay = 0;
+	del_timer_sync(&radiobtn->poll_timer);
+
+	/*
+	 * Remove input device.
+	 */
+	input_unregister_device(radiobtn->input_dev);
+	input_free_device(radiobtn->input_dev);
+}
+
+static int __init radiobtn_init(void)
+{
+	printk(KERN_INFO "Loading radio button driver.\n");
+	return 0;
+}
+
+static void __exit radiobtn_exit(void)
+{
+	printk(KERN_INFO "Unloading radio button driver.\n");
+}
+
+EXPORT_SYMBOL(radiobtn_register_device);
+EXPORT_SYMBOL(radiobtn_unregister_device);
+
+module_init(radiobtn_init);
+module_exit(radiobtn_exit);
diff -uprN wireless-dev/include/linux/radiobtn.h wireless-dev-radiobtn/include/linux/radiobtn.h
--- wireless-dev/include/linux/radiobtn.h	1970-01-01 01:00:00.000000000 +0100
+++ wireless-dev-radiobtn/include/linux/radiobtn.h	2006-05-25 15:02:33.000000000 +0200
@@ -0,0 +1,83 @@
+/*
+	Copyright (C) 2006 Ivo van Doorn
+
+	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.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the
+	Free Software Foundation, Inc.,
+	59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+	Radio hardware button support
+	Laptops are quite often equiped with support with a hardware button
+	to enabled and disable the radio of the integrated wireless network
+	or bluetooth interface.
+	Altough some devices will make sure that when pressed the radio
+	is disabled in hardware, other device depend on the software
+	to enabled or disable the radio accordingly.
+	This driver will create an input device and will poll registered
+	hardware frequently to listen if the button has been pressed.
+	When the device requires the software to disable or enable
+	the radio it will do so correctly, but it will also in all cases
+	send a signal to the input device to inform any listening daemon
+	the state has changed and will allow userspace to handle certain
+	tasks as well if required.
+ */
+
+#ifndef RADIOBTN_H
+#define RADIOBTN_H
+
+#include <linux/input.h>
+
+/**
+ * struct radio_button - radio hardware structure.
+ * @dev_name: Name of the interface. This will become the name
+ * 	of the input device created in /dev/radio/.
+ * @data: Private data which will be passed along with the radio handlers.
+ * @button_poll(unsigned long data): Handler which will be called
+ * 	with the poll_delay interval.
+ * @enable_radio(unsigned long data): Optional handler to enable the radio
+ * 	once the button has been pressed when the hardware does not do this
+ * 	automaticly.
+ * @disable_radio(unsigned long data): Optional handler to disable the radio
+ * 	once the button has been pressed when the hardware does not do this
+ * 	automaticly.
+ * @poll_delay: Delay in msecs between each poll.
+ * @current_state: Current state of the button.
+ * @input_dev: Pointer to input device for this radio button.
+ * 	(Should not be touched by driver)
+ * @poll_timer: Timer used to poll for the button status.
+ *	(Should not be touched by driver)
+ */
+struct radio_button {
+	const char *dev_name;
+
+	unsigned long data;
+
+	int (*button_poll)(unsigned long data);
+	void (*enable_radio)(unsigned long data);
+	void (*disable_radio)(unsigned long data);
+
+	unsigned int poll_delay;
+
+	unsigned int current_state;
+
+	struct input_dev *input_dev;
+
+	struct timer_list poll_timer;
+}
+
+int radiobtn_register_device(struct radio_button *);
+void radiobtn_unregister_device(struct radio_button *);
+
+#endif /* RADIOBTN_H */