[RFC] tablet buttons driver for fujitsu siemens laptops
From: Robert Gerlach
Date: Fri Jun 01 2007 - 21:00:01 EST
Hi,
I have written a driver for the tablet buttons of (some?) Fujitsu Siemens
tablet notebook. Can someone please review this (I'm a newbie here).
Other questions, where should the modification button (fn) handled (kernel- or
userspace)? This button should work like stickykey's in gnome (for
one-finger-use). Currently, I have a small userspace daemon for this.
Some models doesn't have a brightness up and down, only a backlight on and off
button. What event should reported there.
Thanks,
Robert
---
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.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/delay.h>
#define MODULENAME "fsc_btns"
#define MODULEDESC "Fujitsu Siemens Application Panel Driver for T-Series
Lifebooks"
#define MODULEVERS "0.30a"
struct keymap_entry { /* keymap_entry */
unsigned int mask;
unsigned int code;
};
/* TODO: no brightness/backlight toggle ? */
#define KEY_BACKLIGHTTOGGLE 241
static struct keymap_entry keymap_t4010[] = {
{ 0x0010, KEY_SCROLLDOWN },
{ 0x0020, KEY_SCROLLUP },
{ 0x0040, KEY_DIRECTION },
{ 0x0080, KEY_FN },
{ 0x0100, KEY_BRIGHTNESSUP },
{ 0x0200, KEY_BRIGHTNESSDOWN },
{ 0x0400, KEY_BACKLIGHTTOGGLE },
{ 0x8000, KEY_MENU },
{ 0x0000, 0},
};
#define default_keymap keymap_t4010
static struct fscbtns_t { /* fscbtns_t */
unsigned int interrupt;
unsigned int address;
struct keymap_entry *keymap;
int display_direction;
struct platform_device *pdev;
#ifdef CONFIG_ACPI
struct acpi_device *adev;
#endif
struct input_dev *idev;
char idev_phys[16];
} fscbtns = {
#ifndef CONFIG_ACPI
/* XXX: is this always true ??? */
.interrupt = 5,
.address = 0xfd70,
#endif
.keymap = default_keymap
};
static unsigned int repeat_rate = 16;
static unsigned int repeat_delay = 500;
#ifdef DEBUG
# define debug(m, a...) printk( KERN_DEBUG MODULENAME ": " m "\n", ##a)
#else
# define debug(m, a...) do {} while(0)
#endif
#define info(m, a...) printk( KERN_INFO MODULENAME ": " m "\n", ##a)
#define warn(m, a...) printk( KERN_WARNING MODULENAME ": " m "\n", ##a)
#define error(m, a...) printk( KERN_ERR MODULENAME ": " m "\n", ##a)
/*** INPUT ***/
static int input_fscbtns_setup(void)
{
struct keymap_entry *key;
struct input_dev *idev;
int error;
snprintf(fscbtns.idev_phys, sizeof(fscbtns.idev_phys),
"%s/input0",
#ifdef CONFIG_ACPI
acpi_device_hid(fscbtns.adev)
#else
MODULENAME
#endif
);
fscbtns.idev = idev = input_allocate_device();
if(!idev)
return -ENOMEM;
idev->phys = fscbtns.idev_phys;
idev->name = MODULEDESC;
idev->id.bustype = BUS_HOST;
idev->id.vendor = 0x1734; /* "Fujitsu Siemens Computer GmbH" from pci.ids */
idev->id.product = 0x0001;
idev->id.version = 0x0101;
idev->cdev.dev = &(fscbtns.pdev->dev);
set_bit(EV_REP, idev->evbit);
set_bit(EV_KEY, idev->evbit);
for(key = fscbtns.keymap; key->mask; key++)
set_bit(key->code, idev->keybit);
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;
}
return 0;
}
static void input_fscbtns_remove(void)
{
input_unregister_device(fscbtns.idev);
}
static void fscbtns_set_repeat_rate(int delay, int period)
{
fscbtns.idev->rep[REP_DELAY] = delay;
fscbtns.idev->rep[REP_PERIOD] = period;
}
static void fscbtns_event(void)
{
u8 i;
unsigned int keymask;
unsigned int changed;
static unsigned int prev_keymask = 0;
struct keymap_entry *key;
outb(0xdd, fscbtns.address);
i = inb(fscbtns.address+4) ^ 0xff;
if(i != fscbtns.display_direction) {
debug("display_direction change (%d)", i);
fscbtns.display_direction = i;
input_report_switch(fscbtns.idev, SW_TABLET_MODE, i);
}
outb(0xde, fscbtns.address);
keymask = inb(fscbtns.address+4) ^ 0xff;
outb(0xdf, fscbtns.address);
keymask |= (inb(fscbtns.address+4) ^ 0xff) << 8;
changed = keymask ^ prev_keymask;
debug("keymask: 0x%04x (0x%04x)", keymask, changed);
if(changed) {
for(key = fscbtns.keymap; key->mask; key++)
if(key->mask == changed) {
debug("send %d %s", key->code, (keymask &
changed ? "pressed" : "released"));
input_report_key(fscbtns.idev, key->code, !!(keymask & changed));
break;
}
prev_keymask = keymask;
}
input_sync(fscbtns.idev);
}
/*** INTERRUPT ***/
static void fscbtns_isr_do(struct work_struct *work)
{
fscbtns_event();
inb(fscbtns.address+2);
}
static DECLARE_WORK(isr_wq, fscbtns_isr_do);
static irqreturn_t fscbtns_isr(int irq, void *dev_id)
{
int irq_me = inb(fscbtns.address+6) & 0x01;
debug("INTERRUPT (0:%d)", irq_me);
if(!irq_me)
return IRQ_NONE;
schedule_work(&isr_wq);
return IRQ_HANDLED;
}
/*** DEVICE ***/
static int fscbtns_busywait(void)
{
int timeout_counter = 100;
while(inb(fscbtns.address+6) & 0x02 && --timeout_counter)
msleep(10);
debug("busywait done (rest: %d)", timeout_counter);
return !timeout_counter;
}
static int __devinit fscbtns_probe(struct platform_device *dev)
{
int error;
struct resource *resource;
error = input_fscbtns_setup();
if(error)
return error;
fscbtns_set_repeat_rate(repeat_delay, 1000 / repeat_rate);
resource = request_region(fscbtns.address, 8, MODULENAME);
if(!resource) {
error("request_region failed!");
error = 1;
goto err_input;
}
error = request_irq(fscbtns.interrupt,
fscbtns_isr, SA_INTERRUPT, MODULENAME, fscbtns_isr);
if(error) {
error("request_irq failed!");
goto err_io;
}
inb(fscbtns.address+2);
if(!fscbtns_busywait())
debug("device ready");
else
debug("timeout, need a reset?");
return 0;
//err_irq:
// free_irq(fscbtns.interrupt, fscbtns_isr);
err_io:
release_region(fscbtns.address, 8);
err_input:
input_fscbtns_remove();
return error;
}
static int __devexit fscbtns_remove(struct platform_device *dev)
{
free_irq(fscbtns.interrupt, fscbtns_isr);
release_region(fscbtns.address, 8);
input_fscbtns_remove();
return 0;
}
static int fscbtns_suspend(struct platform_device *dev, pm_message_t state)
{
debug("suspend (%d)", state.event);
return 0;
}
static int fscbtns_resume(struct platform_device *dev)
{
debug("resume:");
inb(fscbtns.address+2);
return 0;
}
static struct platform_driver fscbtns_platform_driver = {
.driver = {
.name = MODULENAME,
.owner = THIS_MODULE,
},
.probe = fscbtns_probe,
.remove = __devexit_p(fscbtns_remove),
.suspend = fscbtns_suspend,
.resume = fscbtns_resume,
};
static inline int fscbtns_register_platfrom_driver(void)
{
int error;
debug("register platform driver");
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;
debug("platform driver registered");
return 0;
err_pdev:
platform_device_put(fscbtns.pdev);
err_pdrv:
platform_driver_unregister(&fscbtns_platform_driver);
err:
debug("failed to register driver");
return error;
}
/*** ACPI ***/
#ifdef CONFIG_ACPI
static acpi_status fscbtns_walk_resources(struct acpi_resource *res, void
*data)
{
debug("acpi walk: %d", res->type);
switch(res->type) {
case ACPI_RESOURCE_TYPE_IRQ:
if(fscbtns.interrupt)
return_ACPI_STATUS(AE_OK);
debug("acpi walk: res: interrupt (nr=%d)",
res->data.irq.interrupts[0]);
fscbtns.interrupt = res->data.irq.interrupts[0];
return_ACPI_STATUS(AE_OK);
case ACPI_RESOURCE_TYPE_IO:
if(fscbtns.address)
return_ACPI_STATUS(AE_OK);
debug("acpi walk: res: ioports (min=0x%08x max=0x%08x len=0x%08x)",
res->data.io.minimum,
res->data.io.maximum,
res->data.io.address_length);
fscbtns.address = res->data.io.minimum;
return_ACPI_STATUS(AE_OK);
case ACPI_RESOURCE_TYPE_END_TAG:
debug("acpi walk: end");
if(fscbtns.interrupt && fscbtns.address)
return_ACPI_STATUS(AE_OK);
warn("acpi walk: incomplete");
return_ACPI_STATUS(AE_NOT_FOUND);
default:
debug("acpi walk: other (type=0x%08x)", res->type);
return_ACPI_STATUS(AE_ERROR);
}
}
static int acpi_fscbtns_add(struct acpi_device *device)
{
acpi_status status;
if(!device) {
error("acpi device not found");
return -EINVAL;
}
fscbtns.adev = device;
debug("acpi: walking...");
status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
fscbtns_walk_resources, NULL);
if(ACPI_FAILURE(status)) {
error("acpi walk failed");
return -ENODEV;
}
return 0;
}
static int acpi_fscbtns_remove(struct acpi_device *device, int type)
{
return 0;
}
static struct acpi_driver acpi_fscbtns_driver = {
.name = MODULEDESC,
.class = "hotkey",
.ids = "FUJ02BF",
.ops = {
.add = acpi_fscbtns_add,
.remove = acpi_fscbtns_remove
}
};
#endif /* CONFIG_ACPI */
/*** MODULE ***/
static int __init fscbtns_module_init(void)
{
int error = -EINVAL;
#ifdef CONFIG_ACPI
debug("register acpi driver");
error = acpi_bus_register_driver(&acpi_fscbtns_driver);
if(ACPI_FAILURE(error)) {
error("acpi_bus_register_driver failed");
return error;
}
error = -ENODEV;
#endif
if(!fscbtns.interrupt || !fscbtns.address)
goto err;
debug("register platform driver");
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;
debug("module loaded");
return 0;
err_pdev:
platform_device_put(fscbtns.pdev);
err_pdrv:
platform_driver_unregister(&fscbtns_platform_driver);
err:
#ifdef CONFIG_ACPI
acpi_bus_unregister_driver(&acpi_fscbtns_driver);
#endif
debug("failed to register driver");
return error;
}
static void __exit fscbtns_module_exit(void)
{
platform_device_unregister(fscbtns.pdev);
platform_driver_unregister(&fscbtns_platform_driver);
#ifdef CONFIG_ACPI
acpi_bus_unregister_driver(&acpi_fscbtns_driver);
#endif
debug("module removed");
}
MODULE_AUTHOR("Robert Gerlach <r.gerlach@xxxxxxxx>");
MODULE_DESCRIPTION(MODULEDESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(MODULEVERS);
module_param_named(irq, fscbtns.interrupt, uint, 0);
MODULE_PARM_DESC(irq, "interrupt");
module_param_named(io, fscbtns.address, uint, 0);
MODULE_PARM_DESC(io, "io address");
module_param_named(rate, repeat_rate, uint, 0);
MODULE_PARM_DESC(rate, "repeat rate");
module_param_named(delay, repeat_delay, uint, 0);
MODULE_PARM_DESC(delay, "repeat delay");
static struct pnp_device_id pnp_ids[] = {
{ .id = "FUJ02bf" },
{ .id = "" }
};
MODULE_DEVICE_TABLE(pnp, pnp_ids);
module_init(fscbtns_module_init);
module_exit(fscbtns_module_exit);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/