--- drivers/input/misc/Kconfig | 11 + drivers/input/misc/Makefile | 1 drivers/input/misc/apanel.c | 387 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 4 files changed, 400 insertions(+) --- lifebook.orig/drivers/input/misc/Kconfig 2007-01-03 14:44:55.000000000 -0800 +++ lifebook/drivers/input/misc/Kconfig 2007-01-03 14:44:57.000000000 -0800 @@ -50,6 +50,17 @@ To compile this driver as a module, choose M here: the module will be called wistron_btns. +config INPUT_APANEL + tristate "Fujitsu Lifebook Application Panel buttons" + depends on X86 && !X86_64 + select I2C + help + Say Y here for support of the Application Panel buttons, used on + Fujitsu Lifebook. + + To compile this driver as a module, choose M here: the module will + be called apanel. + config INPUT_IXP4XX_BEEPER tristate "IXP4XX Beeper support" depends on ARCH_IXP4XX --- lifebook.orig/drivers/input/misc/Makefile 2007-01-03 14:44:55.000000000 -0800 +++ lifebook/drivers/input/misc/Makefile 2007-01-03 14:44:57.000000000 -0800 @@ -9,5 +9,6 @@ obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o +obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o --- lifebook.orig/include/linux/i2c-id.h 2007-01-03 14:44:55.000000000 -0800 +++ lifebook/include/linux/i2c-id.h 2007-01-03 14:44:57.000000000 -0800 @@ -115,6 +115,7 @@ #define I2C_DRIVERID_KS0127 86 /* Samsung ks0127 video decoder */ #define I2C_DRIVERID_TLV320AIC23B 87 /* TI TLV320AIC23B audio codec */ #define I2C_DRIVERID_ISL1208 88 /* Intersil ISL1208 RTC */ +#define I2C_DRIVERID_APANEL 89 /* Lifebook Application Panel */ #define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ lifebook/drivers/input/misc/apanel.c 2007-01-03 16:14:41.000000000 -0800 @@ -0,0 +1,387 @@ +/* + SMBus client for the Fujitsu Lifebook Application Panel + + Copyright (C) 2007 Stephen Hemminger + Copyright (C) 2001-2003 Jochen Eisinger + + 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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* List of systems known to work */ +static struct dmi_system_id apanel_dmi_table[] __initdata = { + { + .ident = "Lifebook S", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S Series"), + }, + }, + { + .ident = "Lifebook B6210", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Lifebook B6210"), + }, + }, + { } +}; + +enum apanel_devid { + APANEL_DEV_NONE = 0, + APANEL_DEV_APPBTN=1, + APANEL_DEV_CDBTN= 2, + APANEL_DEV_LCD = 3, + APANEL_DEV_LED = 4, + + APANEL_DEV_MAX, +}; + +enum apanel_chip { + CHIP_NONE =0, + CHIP_OZ992C =1, + CHIP_OZ163T =2, + CHIP_OZ711M3=4, +}; + +static u8 device_chip[APANEL_DEV_MAX]; + +static const char *device_names[APANEL_DEV_MAX] = { + [APANEL_DEV_APPBTN] = "Application Buttons", + [APANEL_DEV_LCD] = "LCD", + [APANEL_DEV_LED] = "LED", + [APANEL_DEV_CDBTN] = "CD Buttons", +}; + +struct apanel { + struct input_dev *input; + struct i2c_client client; + struct delayed_work poll_timer; + struct work_struct led_work; +}; + +#define POLL_FREQUENCY 10 /* Number of polls per second */ + +#if POLL_FREQUENCY > HZ +#error "POLL_FREQUENCY too high" +#endif + +MODULE_AUTHOR("Stephen Hemminger , Jochen Eisinger "); +MODULE_DESCRIPTION("Fujitsu Lifebook Application Panel driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.2"); + +/* NB: can't call this 'force' because of I2C_INSMOD macro foolishness. */ +static int dmi_force; +module_param(dmi_force, bool, 0); +MODULE_PARM_DESC(dmi_force, "Load even if computer is not in database"); + +/* definitions for i2c (smbus) interface */ +static int apanel_detach_client(struct i2c_client *client); +static int apanel_attach_adapter(struct i2c_adapter *adap); + +static struct i2c_driver apanel_driver = { + .driver = { + .name = "apanel", + }, + .id = I2C_DRIVERID_APANEL, + .attach_adapter = &apanel_attach_adapter, + .detach_client = &apanel_detach_client, +}; + +/* for now, we only support one address */ +static unsigned short normal_i2c[] = {0, I2C_CLIENT_END}; + +/* generate some stupid additional structures. */ +I2C_CLIENT_INSMOD; + +static void report_key(struct input_dev *input, u8 key) +{ + input_report_key(input, key, 1); + input_sync(input); + input_report_key(input, key, 0); + input_sync(input); +} + +/* Poll for key changes every 100ms + * + * Read Application keys via SMI + * A (0x4), B (0x8), Internet (0x2), Email (0x1). + */ +static void apanel_poll(struct work_struct *work) +{ + struct apanel *ap = container_of(work, struct apanel, poll_timer.work); + u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; + s32 data; + + data = i2c_smbus_read_word_data(&ap->client, cmd); + i2c_smbus_write_word_data(&ap->client, cmd, 0); + + if (data) { + pr_debug("apanel: app key data=%#x\n", data); + if (data & 0x1) + report_key(ap->input, KEY_EMAIL); + if (data & 0x2) + report_key(ap->input, KEY_WWW); + if (data & 0x4) + report_key(ap->input, KEY_PROG1); + if (data & 0x8) + report_key(ap->input, KEY_PROG2); + + if (data & 0x400) + report_key(ap->input, KEY_STOPCD); + if (data & 0x800) + report_key(ap->input, KEY_PLAYPAUSE); + if (data & 0x200) + report_key(ap->input, KEY_REWIND); + if (data & 0x100) + report_key(ap->input, KEY_FORWARD); + + } + schedule_delayed_work(&ap->poll_timer, POLL_FREQUENCY); +} + +/* Track state changes of LED */ +static void apanel_led(struct work_struct *work) +{ + struct apanel *ap = container_of(work, struct apanel, led_work); + int onoff = test_bit(LED_MISC, ap->input->led); + + pr_debug("apanel: led %s\n", onoff ? "on" : "off"); + i2c_smbus_write_word_data(&ap->client, 0x10, onoff ? 0x8000 : 0); +} + +/* Callback from input layer when state change happens. + * Used to handle LED control. + */ +static int apanel_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct apanel *ap = dev->private; + + pr_debug("apanel event type %d\n", type); + if (device_chip[APANEL_DEV_LED] == CHIP_NONE || type != EV_LED) + return -1; + + schedule_work(&ap->led_work); + return 0; +} + +/* + basically this function should probe the i2c client, but we know that it has + to be the one we're looking for - and I have no idea how I should probe for + it, so we just register... + */ +static int apanel_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct apanel *ap; + struct input_dev *input; + int err = -ENOMEM; + + ap = kzalloc(sizeof(*ap), GFP_KERNEL); + if (!ap) + goto out1; + + input = input_allocate_device(); + if (!input) + goto out1; + + ap->input = input; + strcpy(ap->client.name, "apanel"); + ap->client.driver = &apanel_driver; + ap->client.adapter = adap; + ap->client.addr = addr; + INIT_DELAYED_WORK(&ap->poll_timer, apanel_poll); + INIT_WORK(&ap->led_work, apanel_led); + i2c_set_clientdata(&ap->client, ap); + + input->name = "Lifebook Panel buttons"; + input->phys = "apanel/input0"; + input->id.bustype = BUS_HOST; + input->cdev.dev = &ap->client.dev; + input->private = ≈ + + if (device_chip[APANEL_DEV_APPBTN] != CHIP_NONE) { + input->evbit[LONG(EV_KEY)] = BIT(EV_KEY); + set_bit(KEY_PROG1, input->keybit); + set_bit(KEY_PROG2, input->keybit); + set_bit(KEY_EMAIL, input->keybit); + set_bit(KEY_WWW, input->keybit); + } + + if (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) { + input->evbit[LONG(EV_KEY)] = BIT(EV_KEY); + set_bit(KEY_STOPCD, input->keybit); + set_bit(KEY_PLAYPAUSE, input->keybit); + set_bit(KEY_REWIND, input->keybit); + set_bit(KEY_FORWARD, input->keybit); + } + + if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { + input->event = apanel_event; + input->evbit[0] |= BIT(EV_LED); + input->ledbit[0] = BIT(LED_MISC); + } + + err = i2c_attach_client(&ap->client); + if (err) + goto out2; + + err = input_register_device(input); + if (err) + goto out3; + + schedule_delayed_work(&ap->poll_timer, POLL_FREQUENCY); + return 0; +out3: + i2c_detach_client(&ap->client); +out2: + kfree(input); +out1: + kfree(ap); + return err; +} + +static int apanel_detach_client(struct i2c_client *client) +{ + struct apanel *ap = i2c_get_clientdata(client); + + pr_debug("detach_client\n"); + cancel_rearming_delayed_work(&ap->poll_timer); + input_unregister_device(ap->input); + input_free_device(ap->input); + + i2c_detach_client(&ap->client); + kfree(ap); + return 0; +} + +/* this function is invoked for every i2c adapter, that has a device at the + * address we've specified + */ +static int apanel_attach_adapter(struct i2c_adapter *adap) +{ + /* try to attach on any smbus adapter */ + if (adap->algo->smbus_xfer) + return i2c_probe(adap, &addr_data, apanel_probe); + + return -ENODEV; +} + +/* scan the system rom for the signature "FJKEYINF" */ +static __init void __iomem *bios_signature(void) +{ + void __iomem *bios; + ssize_t offset; + const unsigned char signature[] = "FJKEYINF"; + + bios = ioremap(0xF0000, 0x10000); /* Can't fail */ + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(bios + offset, signature, + sizeof(signature)-1)) + return bios + offset; + } + + printk(KERN_DEBUG + "apanel: Fujitsu BIOS signature '%s' not found...\n", signature); + iounmap(bios); + return NULL; +} + +static int __init apanel_init(void) +{ + void __iomem *bios; + u8 devno; + int found = 0; + + if (!dmi_check_system(apanel_dmi_table)) { + if (dmi_force) + printk(KERN_INFO "apanel: system not found in database forced\n"); + else + return -ENODEV; + } + + bios = bios_signature(); + if (!bios) + return -ENODEV; + + bios += 8; + + /* just use the first address */ + normal_i2c[0] = readb(bios+3) >> 1; + + for ( ; (devno = readb(bios)) & 0x7f; bios += 4) { + unsigned char method, slave, chip; + + method = readb(bios + 1); + chip = readb(bios + 2); + slave = readb(bios + 3) >> 1; + + if (slave != normal_i2c[0]) { + printk(KERN_INFO "apanel: only one SMBus slave " + "address supported, skiping device...\n"); + continue; + } + + /* translate alternative device numbers */ + switch (devno) { + case 6: + devno = APANEL_DEV_APPBTN; + break; + case 7: + devno = APANEL_DEV_LED; + break; + } + + if (devno >= APANEL_DEV_MAX) + printk(KERN_INFO "apanel: unknown device %d found\n", devno); + else if (device_chip[devno] != CHIP_NONE) + printk(KERN_INFO "apanel: duplicate entry for device %s\n", + device_names[devno]); + + else if (method != 1 && method != 2 && method != 4) { + printk(KERN_INFO "apanel: unknown access method %u for %s\n", + method, device_names[devno]); + } else { + pr_debug("apanel: %s found, chip=%d\n", + device_names[devno], chip); + + device_chip[devno] = (enum apanel_chip) chip; + ++found; + } + } + iounmap(bios); + + if (found == 0) { + printk(KERN_INFO "apanel: no input devices reported by bios\n"); + return -ENODEV; + } + return i2c_add_driver(&apanel_driver); +} +module_init(apanel_init); + +static void __exit apanel_cleanup(void) +{ + i2c_del_driver(&apanel_driver); +} +module_exit(apanel_cleanup);