public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC] tablet buttons driver for fujitsu siemens laptops
@ 2007-06-02  0:59 Robert Gerlach
  2007-06-02  1:30 ` Stephen Hemminger
  0 siblings, 1 reply; 5+ messages in thread
From: Robert Gerlach @ 2007-06-02  0:59 UTC (permalink / raw)
  To: linux-kernel

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@snafu.de>");
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);

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC] tablet buttons driver for fujitsu siemens laptops
  2007-06-02  0:59 [RFC] tablet buttons driver for fujitsu siemens laptops Robert Gerlach
@ 2007-06-02  1:30 ` Stephen Hemminger
  2007-06-06  8:45   ` Robert Gerlach
  0 siblings, 1 reply; 5+ messages in thread
From: Stephen Hemminger @ 2007-06-02  1:30 UTC (permalink / raw)
  To: linux-kernel

On Sat, 2 Jun 2007 02:59:33 +0200
Robert Gerlach <khnz@gmx.de> wrote:

> 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
> 
> 
> #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)
>
>

Please don't reinvent
	pr_debug
	pr_info
	pr_warn,...


-- 
Stephen Hemminger <shemminger@linux-foundation.org>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC] tablet buttons driver for fujitsu siemens laptops
  2007-06-02  1:30 ` Stephen Hemminger
@ 2007-06-06  8:45   ` Robert Gerlach
  2007-06-06 16:03     ` Stephen Hemminger
  0 siblings, 1 reply; 5+ messages in thread
From: Robert Gerlach @ 2007-06-06  8:45 UTC (permalink / raw)
  To: linux-kernel

On Saturday 02 June 2007 03:30:54 Stephen Hemminger wrote:
> On Sat, 2 Jun 2007 02:59:33 +0200
> > #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)
>
> Please don't reinvent
> 	pr_debug
> 	pr_info

Changed.

> 	pr_warn,...

There is no pr_warn. Is it ok to define it like pr_err in spider_net.c or is 
there a better way?


Many thanks,
Robert


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC] tablet buttons driver for fujitsu siemens laptops
  2007-06-06  8:45   ` Robert Gerlach
@ 2007-06-06 16:03     ` Stephen Hemminger
  0 siblings, 0 replies; 5+ messages in thread
From: Stephen Hemminger @ 2007-06-06 16:03 UTC (permalink / raw)
  To: Robert Gerlach; +Cc: linux-kernel

On Wed, 6 Jun 2007 10:45:42 +0200
Robert Gerlach <khnz@khnz.de> wrote:

> On Saturday 02 June 2007 03:30:54 Stephen Hemminger wrote:
> > On Sat, 2 Jun 2007 02:59:33 +0200
> > > #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)
> >
> > Please don't reinvent
> > 	pr_debug
> > 	pr_info
> 
> Changed.
> 
> > 	pr_warn,...
> 
> There is no pr_warn. Is it ok to define it like pr_err in spider_net.c or is 
> there a better way?
> 

The best would be to use the following if you have a device
	dev_err, dev_info, dev_warn, dev_notice

Also, you need to format this a proper patch with config, makefile, etc.

-- 
Stephen Hemminger <shemminger@linux-foundation.org>

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC] tablet buttons driver for fujitsu siemens laptops
@ 2007-08-01  8:51 Evgeny Stambulchik
  0 siblings, 0 replies; 5+ messages in thread
From: Evgeny Stambulchik @ 2007-08-01  8:51 UTC (permalink / raw)
  To: linux-kernel, r.gerlach

Hello,

Just wanted to mention that the driver by Robert Gerlach also works[1] 
with a Fujitsu P1610 convertible, after a trivial modification:

static struct keymap_entry keymap_t1610[] = {
     { 0x0010, KEY_SCROLLDOWN },
     { 0x0020, KEY_SCROLLUP },
     { 0x0040, KEY_DIRECTION },
     { 0x0080, KEY_FN },
     { 0x8000, KEY_MENU },
     { 0x0400, KEY_KBDILLUMTOGGLE },
     { 0x0000, 0},
};

[1] evtest reports all events correctly and I can access them from user 
space, however, acpi_listen seems to be not getting those... Perhaps 
something trivial?

Regards,

Evgeny

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2007-08-01  9:00 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-06-02  0:59 [RFC] tablet buttons driver for fujitsu siemens laptops Robert Gerlach
2007-06-02  1:30 ` Stephen Hemminger
2007-06-06  8:45   ` Robert Gerlach
2007-06-06 16:03     ` Stephen Hemminger
  -- strict thread matches above, loose matches on Subject: below --
2007-08-01  8:51 Evgeny Stambulchik

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox