@@ -54,6 +54,17 @@ config INPUT_APANEL
To compile this driver as a module, choose M here: the module will
be called apanel.
+config INPUT_FSC_BTNS
+ tristate "Fujitsu tablet PCs buttons"
+ depends on X86
+ help
+ Say Y here for support of the tablet buttons and the display
+ orientation switch, used on many Fujitsu tablet PCs (like
+ Stylistic ST5xxx, Lifebook P1xxx and Lifebook T-Series).
+
+ To compile this driver as a module, choose M here: the module will
+ be called fsc_btns.
+
config INPUT_IXP4XX_BEEPER
tristate "IXP4XX Beeper support"
depends on ARCH_IXP4XX
@@ -5,6 +5,7 @@
# Each configuration option enables a list of files.
obj-$(CONFIG_INPUT_APANEL) += apanel.o
+obj-$(CONFIG_INPUT_FSC_BTNS) += fsc_btns.o
obj-$(CONFIG_INPUT_ATI_REMOTE) += ati_remote.o
obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o
obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o
new file mode 100644
@@ -0,0 +1,680 @@
+/* driver for FSC tablet PC buttons
+ *
+ * Copyright (C) 2006-2009 Robert Gerlach <khnz@users.sourceforge.net>
+ * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com>
+ *
+ * You can redistribute and/or modify this program under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation.
+ *
+ * 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.
+ */
+
+#define REPEAT_RATE 16
+#define REPEAT_DELAY 700
+#define STICKY_TIMEOUT 1400 /* msec */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/dmi.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+
+#define MODULENAME "fsc_btns"
+
+MODULE_AUTHOR("Robert Gerlach <khnz@users.sourceforge.net>");
+MODULE_DESCRIPTION("Fujitsu Siemens tablet button driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.1.0");
+
+static const struct acpi_device_id fscbtns_ids[] = {
+ { .id = "FUJ02BD" },
+ { .id = "FUJ02BF" },
+ { .id = "" }
+};
+MODULE_DEVICE_TABLE(acpi, fscbtns_ids);
+
+struct fscbtns_config {
+ int invert_orientation_bit;
+ unsigned int keymap[16];
+};
+
+static struct fscbtns_config config_Lifebook_Tseries __initdata = {
+ .invert_orientation_bit = 1,
+ .keymap = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_SCROLLDOWN,
+ KEY_SCROLLUP,
+ KEY_DIRECTION,
+ KEY_LEFTCTRL,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_BRIGHTNESS_ZERO,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_LEFTALT
+ }
+};
+
+static struct fscbtns_config config_Lifebook_U810 __initdata = {
+ .invert_orientation_bit = 1,
+ .keymap = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_PROG1,
+ KEY_PROG2,
+ KEY_DIRECTION,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_UP,
+ KEY_DOWN,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_FN,
+ KEY_SLEEP
+ }
+};
+
+static struct fscbtns_config config_Stylistic_Tseries __initdata = {
+ .invert_orientation_bit = 0,
+ .keymap = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_PRINT,
+ KEY_BACKSPACE,
+ KEY_SPACE,
+ KEY_ENTER,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_DOWN,
+ KEY_UP,
+ KEY_SCROLLUP,
+ KEY_SCROLLDOWN,
+ KEY_FN
+ }
+};
+
+static struct fscbtns_config config_Stylistic_ST5xxx __initdata = {
+ .invert_orientation_bit = 0,
+ .keymap = {
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_RESERVED,
+ KEY_MAIL,
+ KEY_DIRECTION,
+ KEY_ESC,
+ KEY_ENTER,
+ KEY_BRIGHTNESSUP,
+ KEY_BRIGHTNESSDOWN,
+ KEY_DOWN,
+ KEY_UP,
+ KEY_SCROLLUP,
+ KEY_SCROLLDOWN,
+ KEY_FN,
+ KEY_LEFTALT
+ }
+};
+
+static const unsigned long modification_mask[BITS_TO_LONGS(KEY_MAX)] = {
+ [BIT_WORD(KEY_LEFTSHIFT)] = BIT_MASK(KEY_LEFTSHIFT),
+ [BIT_WORD(KEY_RIGHTSHIFT)] = BIT_MASK(KEY_RIGHTSHIFT),
+ [BIT_WORD(KEY_LEFTCTRL)] = BIT_MASK(KEY_LEFTCTRL),
+ [BIT_WORD(KEY_RIGHTCTRL)] = BIT_MASK(KEY_RIGHTCTRL),
+ [BIT_WORD(KEY_LEFTALT)] = BIT_MASK(KEY_LEFTALT),
+ [BIT_WORD(KEY_RIGHTALT)] = BIT_MASK(KEY_RIGHTALT),
+ [BIT_WORD(KEY_LEFTMETA)] = BIT_MASK(KEY_LEFTMETA),
+ [BIT_WORD(KEY_RIGHTMETA)] = BIT_MASK(KEY_RIGHTMETA),
+ [BIT_WORD(KEY_COMPOSE)] = BIT_MASK(KEY_COMPOSE),
+ [BIT_WORD(KEY_LEFTALT)] = BIT_MASK(KEY_LEFTALT),
+ [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN)};
+
+static struct { /* fscbtns_t */
+ struct platform_device *pdev;
+ struct input_dev *idev_b; /* tablet buttons */
+ struct input_dev *idev_s; /* orientation switch */
+ struct timer_list timer; /* timer for sicky keys */
+
+ unsigned int interrupt;
+ unsigned int address;
+ struct fscbtns_config config;
+
+ int orientation;
+} fscbtns;
+
+
+/*** HELPER
*******************************************************************/
+
+static inline u8 fscbtns_ack(void)
+{
+ return inb(fscbtns.address+2);
+}
+
+static inline u8 fscbtns_status(void)
+{
+ return inb(fscbtns.address+6);
+}
+
+static inline u8 fscbtns_read_register(const u8 addr)
+{
+ outb(addr, fscbtns.address);
+ return inb(fscbtns.address+4);
+}
+
+static inline void fscbtns_use_config(struct fscbtns_config *config)
+{
+ memcpy(&fscbtns.config, config, sizeof(struct fscbtns_config));
+}
+
+
+/*** INPUT
********************************************************************/
+
+static int __devinit input_fscbtns_setup_buttons(struct device *dev)
+{
+ struct input_dev *idev;
+ int error;
+ int x;
+
+ idev = input_allocate_device();
+ if (!idev)
+ return -ENOMEM;
+
+ idev->dev.parent = dev;
+ idev->phys = "fsc/input0";
+ idev->name = "fsc tablet buttons";
+ idev->id.bustype = BUS_HOST;
+ idev->id.vendor = 0x1734;
+ idev->id.product = 0x0001;
+ idev->id.version = 0x0101;
+
+ idev->keycode = fscbtns.config.keymap;
+ idev->keycodesize = sizeof(unsigned int);
+ idev->keycodemax = sizeof(fscbtns.config.keymap) / idev->keycodesize;
+
+ set_bit(EV_REP, idev->evbit);
+ set_bit(EV_KEY, idev->evbit);
+
+ for (x = 0; x < idev->keycodemax; x++)
+ if (((unsigned int *)idev->keycode)[x])
+ set_bit(((unsigned int *)idev->keycode)[x],
+ idev->keybit);
+
+ set_bit(EV_MSC, idev->evbit);
+ set_bit(MSC_SCAN, idev->mscbit);
+
+ error = input_register_device(idev);
+ if (error) {
+ input_free_device(idev);
+ return error;
+ }
+
+ idev->rep[REP_DELAY] = REPEAT_DELAY;
+ idev->rep[REP_PERIOD] = 1000 / REPEAT_RATE;
+
+ fscbtns.idev_b = idev;
+ return 0;
+}
+
+static int __devinit input_fscbtns_setup_switch(struct device *dev)
+{
+ struct input_dev *idev;
+ int error;
+
+ idev = input_allocate_device();
+ if (!idev)
+ return -ENOMEM;
+
+ idev->dev.parent = dev;
+ idev->phys = "fsc/input1";
+ idev->name = "fsc tablet switch";
+ idev->id.bustype = BUS_HOST;
+ idev->id.vendor = 0x1734;
+ idev->id.product = 0x0002;
+ idev->id.version = 0x0101;
+
+ set_bit(EV_SW, idev->evbit);
+ set_bit(SW_TABLET_MODE, idev->swbit);
+
+ error = input_register_device(idev);
+ if (error) {
+ input_free_device(idev);
+ return error;
+ }
+
+ fscbtns.idev_s = idev;
+ return 0;
+}
+
+static void input_fscbtns_remove(void)
+{
+ if (fscbtns.idev_b)
+ input_unregister_device(fscbtns.idev_b);
+ if (fscbtns.idev_s)
+ input_unregister_device(fscbtns.idev_s);
+}
+
+static void fscbtns_report_orientation(void)
+{
+ int orientation = fscbtns_read_register(0xdd);
+
+ if (orientation & 0x02) {
+ orientation ^= fscbtns.config.invert_orientation_bit;
+ orientation &= 0x01;
+
+ if (orientation != fscbtns.orientation) {
+ input_report_switch(fscbtns.idev_s, SW_TABLET_MODE,
+ fscbtns.orientation = orientation);
+ input_sync(fscbtns.idev_s);
+ }
+ }
+}
+
+static void fscbtns_sticky_timeout(unsigned long keycode)
+{
+ input_report_key(fscbtns.idev_b, keycode, 0);
+ input_sync(fscbtns.idev_b);
+ fscbtns.timer.data = 0;
+}
+
+static inline int fscbtns_sticky_report_key(unsigned int keycode, int
pressed)
+{
+ if (pressed) {
+ del_timer(&fscbtns.timer);
+ fscbtns.timer.expires = jiffies + (STICKY_TIMEOUT*HZ)/1000;
+
+ if (fscbtns.timer.data == keycode) {
+ input_report_key(fscbtns.idev_b, keycode, 0);
+ input_sync(fscbtns.idev_b);
+ }
+
+ return 0;
+ }
+
+ if ((fscbtns.timer.data) && (fscbtns.timer.data != keycode)) {
+ input_report_key(fscbtns.idev_b, keycode, 0);
+ input_sync(fscbtns.idev_b);
+ input_report_key(fscbtns.idev_b, fscbtns.timer.data, 0);
+ fscbtns.timer.data = 0;
+ return 1;
+ }
+
+ if (test_bit(keycode, modification_mask) &&
+ (fscbtns.timer.expires > jiffies)) {
+ fscbtns.timer.data = keycode;
+ fscbtns.timer.function = fscbtns_sticky_timeout;
+ add_timer(&fscbtns.timer);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void fscbtns_report_key(unsigned int kmindex, int pressed)
+{
+ unsigned int keycode = fscbtns.config.keymap[kmindex];
+ if (keycode == KEY_RESERVED)
+ return;
+
+ if (pressed)
+ input_event(fscbtns.idev_b, EV_MSC, MSC_SCAN, kmindex);
+
+ if (fscbtns_sticky_report_key(keycode, pressed))
+ return;
+
+ input_report_key(fscbtns.idev_b, keycode, pressed);
+ input_sync(fscbtns.idev_b);
+}
+
+static void fscbtns_event(void)
+{
+ unsigned long keymask;
+ unsigned long changed;
+ static unsigned long prev_keymask;
+
+ fscbtns_report_orientation();
+
+ keymask = fscbtns_read_register(0xde);
+ keymask |= fscbtns_read_register(0xdf) << 8;
+ keymask ^= 0xffff;
+
+ changed = keymask ^ prev_keymask;
+
+ if (changed) {
+ int x = 0;
+ int pressed = !!(keymask & changed);
+
+ /* save current state and filter not changed bits */
+ prev_keymask = keymask;
+
+ /* get number of changed bit */
+ while (!test_bit(x, &changed))
+ x++;
+
+ fscbtns_report_key(x, pressed);
+ }
+}
+
+
+/*** INTERRUPT
****************************************************************/
+
+static void fscbtns_isr_do(struct work_struct *work)
+{
+ fscbtns_event();
+ fscbtns_ack();
+}
+
+static DECLARE_WORK(isr_wq, fscbtns_isr_do);
+
+static irqreturn_t fscbtns_isr(int irq, void *dev_id)
+{
+ if (!(fscbtns_status() & 0x01))
+ return IRQ_NONE;
+
+ schedule_work(&isr_wq);
+ return IRQ_HANDLED;
+}
+
+
+/*** DEVICE
*******************************************************************/
+
+static int fscbtns_busywait(void)
+{
+ int timeout_counter = 100;
+
+ while (fscbtns_status() & 0x02 && --timeout_counter)
+ msleep(10);
+
+ return !timeout_counter;
+}
+
+static void fscbtns_reset(void)
+{
+ fscbtns_ack();
+ if (fscbtns_busywait())
+ printk(KERN_WARNING MODULENAME ": timeout, reset needed!\n");
+}
+
+static int __devinit fscbtns_probe(struct platform_device *pdev)
+{
+ int error;
+
+ error = input_fscbtns_setup_buttons(&pdev->dev);
+ if (error)
+ goto err_input;
+
+ error = input_fscbtns_setup_switch(&pdev->dev);
+ if (error)
+ goto err_input;
+
+ if (!request_region(fscbtns.address, 8, MODULENAME)) {
+ printk(KERN_ERR MODULENAME ": region 0x%04x busy\n",
+ fscbtns.address);
+ error = -EBUSY;
+ goto err_input;
+ }
+
+ fscbtns_reset();
+
+ fscbtns_report_orientation();
+ input_sync(fscbtns.idev_b);
+
+ error = request_irq(fscbtns.interrupt, fscbtns_isr,
+ IRQF_SHARED, MODULENAME, fscbtns_isr);
+ if (error) {
+ printk(KERN_ERR MODULENAME ": unable to get irq %d\n",
+ fscbtns.interrupt);
+ goto err_io;
+ }
+
+ return 0;
+
+err_io:
+ release_region(fscbtns.address, 8);
+err_input:
+ input_fscbtns_remove();
+ return error;
+}
+
+static int __devexit fscbtns_remove(struct platform_device *pdev)
+{
+ free_irq(fscbtns.interrupt, fscbtns_isr);
+ release_region(fscbtns.address, 8);
+ input_fscbtns_remove();
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fscbtns_resume(struct platform_device *pdev)
+{
+ fscbtns_reset();
+#if 0 /* because Xorg Bug #9623 */
+ fscbtns_report_orientation();
+#endif
+ return 0;
+}
+#endif
+
+static struct platform_driver fscbtns_platform_driver = {
+ .driver = {
+ .name = MODULENAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = fscbtns_probe,
+ .remove = __devexit_p(fscbtns_remove),
+#ifdef CONFIG_PM
+ .resume = fscbtns_resume,
+#endif
+};
+
+
+/*** ACPI
*********************************************************************/
+
+static acpi_status fscbtns_walk_resources(struct acpi_resource *res, void
*data)
+{
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IRQ:
+ fscbtns.interrupt = res->data.irq.interrupts[0];
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_IO:
+ fscbtns.address = res->data.io.minimum;
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ if (fscbtns.interrupt && fscbtns.address)
+ return AE_OK;
+ else
+ return AE_NOT_FOUND;
+
+ default:
+ return AE_ERROR;
+ }
+}
+
+static int acpi_fscbtns_add(struct acpi_device *adev)
+{
+ acpi_status status;
+
+ if (!adev)
+ return -EINVAL;
+
+ status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
+ fscbtns_walk_resources, NULL);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ return 0;
+}
+
+static struct acpi_driver acpi_fscbtns_driver = {
+ .name = MODULENAME,
+ .class = "hotkey",
+ .ids = fscbtns_ids,
+ .ops = {
+ .add = acpi_fscbtns_add
+ }
+};
+
+
+/*** DMI
**********************************************************************/
+
+static int __init fscbtns_dmi_matched(const struct dmi_system_id *dmi)
+{
+ printk(KERN_INFO MODULENAME ": found: %s\n", dmi->ident);
+ fscbtns_use_config(dmi->driver_data);
+ return 1;
+}
+
+static struct dmi_system_id dmi_ids[] __initdata = {
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu Siemens P/T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK")
+ },
+ .driver_data = &config_Lifebook_Tseries
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu Lifebook T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T")
+ },
+ .driver_data = &config_Lifebook_Tseries
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu Siemens Stylistic T Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic T")
+ },
+ .driver_data = &config_Stylistic_Tseries
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu LifeBook U810",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook U810")
+ },
+ .driver_data = &config_Lifebook_U810
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "STYLISTIC ST5")
+ },
+ .driver_data = &config_Stylistic_ST5xxx
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Fujitsu Siemens Stylistic ST5xxx Series",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic ST5")
+ },
+ .driver_data = &config_Stylistic_ST5xxx
+ },
+ {
+ .callback = fscbtns_dmi_matched,
+ .ident = "Unknown (using defaults)",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, ""),
+ DMI_MATCH(DMI_PRODUCT_NAME, "")
+ },
+ .driver_data = &config_Lifebook_Tseries
+ },
+ { NULL }
+};
+
+
+/*** MODULE
*******************************************************************/
+
+static int __init fscbtns_module_init(void)
+{
+ int error;
+
+ dmi_check_system(dmi_ids);
+
+ error = acpi_bus_register_driver(&acpi_fscbtns_driver);
+ if (ACPI_FAILURE(error))
+ return -EINVAL;
+
+ if (!fscbtns.interrupt || !fscbtns.address)
+ return -ENODEV;
+
+ init_timer(&fscbtns.timer);
+
+ error = platform_driver_register(&fscbtns_platform_driver);
+ if (error)
+ goto err;
+
+ fscbtns.pdev = platform_device_alloc(MODULENAME, -1);
+ if (!fscbtns.pdev) {
+ error = -ENOMEM;
+ goto err_pdrv;
+ }
+
+ error = platform_device_add(fscbtns.pdev);
+ if (error)
+ goto err_pdev;
+
+ return 0;
+
+err_pdev:
+ platform_device_put(fscbtns.pdev);
+err_pdrv:
+ platform_driver_unregister(&fscbtns_platform_driver);
+err:
+ acpi_bus_unregister_driver(&acpi_fscbtns_driver);
+
+ del_timer_sync(&fscbtns.timer);
+
+ return error;
+}
+
+static void __exit fscbtns_module_exit(void)
+{