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 <[EMAIL PROTECTED]>");
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 [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to