Index: linux-2.6.27/drivers/input/misc/cy3218-btns.c =================================================================== --- /dev/null +++ linux-2.6.27/drivers/input/misc/cy3218-btns.c @@ -0,0 +1,373 @@ +/* + * CAPSENSE Interface driver + * + * + * Copyright (C) 2008, CenoSYS (www.cenosys.com). + * + * Guillaume Ligneul + * Jeremy Lainé + * Sylvain Giroudon + * + * This software program is licensed subject to the GNU General Public License + * (GPL).Version 2,June 1991, available at http://www.fsf.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include + +static int capsense_attach_adapter(struct i2c_adapter *adapter); +static int capsense_detach_client(struct i2c_client *client); +#ifdef CONFIG_PM +static int capsense_suspend(struct i2c_client *client, pm_message_t mesg); +static int capsense_resume(struct i2c_client *client); +#endif + +#define CAPSENSE_NAME "Capsense" + +/* i2c configuration */ +#define CAPSENSE_I2C_ADDR 0x25 +// To debug (may be add in include/linux/i2c-id.h) +#define I2C_DRIVERID_CAPSENSE 98 + +#define BUTTONS_POLL_INTERVAL 30 /* msec */ + +#define CAP_OUTPUT_PORT(port) (0x04+(port)) +#define CAP_OP_SEL(port,bit) (0x1C+(25*(port))+(5*(bit))) +#define CAP_READ_STATUS(port) (0x88+(port)) + +#define MASK0 0x10 +#define MASK1 0x4 +#define MASK2 0x8 +#define MASK3 0x1 + +#define CAP_NLEDS 5 + +static int poll_interval = BUTTONS_POLL_INTERVAL; +module_param_named(poll, poll_interval, uint, 0); +MODULE_PARM_DESC(poll, "poll interval in msec (30=default)"); + +static const unsigned short normal_i2c[] = { + CAPSENSE_I2C_ADDR , I2C_CLIENT_END +}; +I2C_CLIENT_INSMOD; + +static struct i2c_driver capsense_driver = { + .driver = { + .name = CAPSENSE_NAME, + }, + .id = I2C_DRIVERID_CAPSENSE, + .attach_adapter = &capsense_attach_adapter, + .detach_client = &capsense_detach_client, +#ifdef CONFIG_PM + .suspend = &capsense_suspend, + .resume = &capsense_resume, +#endif +}; + +struct cy3218_led { + struct led_classdev cdev; + struct cy3218 *capsense; + int port; + int bit; +}; + +struct cy3218 { + struct input_polled_dev *ipdev; + struct i2c_client client; + unsigned char key_state; + struct cy3218_led leds[CAP_NLEDS]; + unsigned char led_state[2]; + struct mutex mutex; +}; + +static unsigned short keymap[] = { + // GP0 + KEY_F1, + KEY_ENTER, + KEY_DOWN, + KEY_BACKSPACE, + // GP1 + KEY_UP, +}; + +struct cy3218_ledmap { + char *name; + int port, bit; +}; + +static struct cy3218_ledmap ledmap[CAP_NLEDS] = { + { "capsense:blue:back", 0, 1 }, + { "capsense:blue:info", 1, 0 }, + { "capsense:blue:down", 1, 1 }, + { "capsense:blue:ok", 1, 2 }, + { "capsense:blue:up", 1, 3 }, +}; + +static void handle_buttons(struct input_polled_dev *dev) +{ + struct cy3218 *capsense = dev->private; + u8 port_value; + u8 new_state = 0; + u8 changed; + int i; + + mutex_lock(&capsense->mutex); + + // read status + port_value = i2c_smbus_read_byte_data(&capsense->client, CAP_READ_STATUS(0)); + if (port_value & MASK0) new_state |= 0x01; + if (port_value & MASK1) new_state |= 0x02; + if (port_value & MASK2) new_state |= 0x04; + if (port_value & MASK3) new_state |= 0x08; + + port_value = i2c_smbus_read_byte_data(&capsense->client, CAP_READ_STATUS(1)); + if (port_value & MASK0) new_state |= 0x10; + + mutex_unlock(&capsense->mutex); + + // update keyboard state + changed = capsense->key_state ^ new_state; + for (i = 0; i < ARRAY_SIZE(keymap); i++) + if (changed & (1 << i)) + input_report_key(dev->input, keymap[i], (new_state & (1 << i))); + capsense->key_state = new_state; + input_sync(dev->input); +} + + +static void +capsense_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct cy3218_led *led = (struct cy3218_led *) led_cdev; + struct cy3218 *capsense = led->capsense; + int port = led->port; + unsigned char mask = (1 << led->bit); + + if ( value ) + capsense->led_state[port] |= mask; + else + capsense->led_state[port] &= ~mask; + + mutex_lock(&capsense->mutex); + i2c_smbus_write_byte_data(&capsense->client, CAP_OUTPUT_PORT(port), capsense->led_state[port]); + mutex_unlock(&capsense->mutex); +} + +static void +capsense_led_enable(struct cy3218_led *led, int state) +{ + struct cy3218 *capsense = led->capsense; + + mutex_lock(&capsense->mutex); + i2c_smbus_write_byte_data(&capsense->client, CAP_OP_SEL(led->port, led->bit), state ? 0x00 : 0x80); + mutex_unlock(&capsense->mutex); +} + +static int +capsense_led_init(struct cy3218 *capsense) +{ + int i; + int ret; + + for (i = 0; i < CAP_NLEDS; i++) { + struct cy3218_led *led = &(capsense->leds[i]); + + led->cdev.name = ledmap[i].name; + led->cdev.brightness_set = capsense_led_set; + led->capsense = capsense; + led->port = ledmap[i].port; + led->bit = ledmap[i].bit; + + ret = led_classdev_register(&capsense->ipdev->input->dev, &led->cdev); + if ( ret < 0 ) + return -1; + + capsense_led_enable(led, 1); + } + + /* Switch all leds off */ + mutex_lock(&capsense->mutex); + + capsense->led_state[0] = 0x00; + i2c_smbus_write_byte_data(&capsense->client, CAP_OUTPUT_PORT(0), 0x00); + + capsense->led_state[1] = 0x00; + i2c_smbus_write_byte_data(&capsense->client, CAP_OUTPUT_PORT(1), 0x00); + + mutex_unlock(&capsense->mutex); + + return 0; +} + + +static void +capsense_led_exit(struct cy3218 *capsense) +{ + int i; + + for (i = 0; i < CAP_NLEDS; i++) { + led_classdev_unregister(&capsense->leds[i].cdev); + } +} + + +static inline void +capsense_led_suspend(struct cy3218 *capsense) +{ + int i; + + for (i = 0; i < CAP_NLEDS; i++) { + struct cy3218_led *led = &(capsense->leds[i]); + + led_classdev_suspend(&led->cdev); + capsense_led_enable(led, 0); + } +} + + +static inline void +capsense_led_resume(struct cy3218 *capsense) +{ + int i; + + for (i = 0; i < CAP_NLEDS; i++) { + struct cy3218_led *led = &(capsense->leds[i]); + + capsense_led_enable(led, 1); + led_classdev_resume(&led->cdev); + } +} + + +static int +capsense_probe(struct i2c_adapter *adapter, int addr, int kind) +{ + struct cy3218 *capsense; + struct input_polled_dev *ipdev; + struct input_dev *input; + int rc = 0, err = -ENOMEM, i=0; + + capsense = kzalloc(sizeof(*capsense), GFP_KERNEL); + if (!capsense) + goto failout; + + mutex_init(&capsense->mutex); + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { + goto failout; + } + + ipdev = input_allocate_polled_device(); + if (!ipdev) + goto failout; + + capsense->key_state = 0; + capsense->ipdev = ipdev; + capsense->client.adapter = adapter; + capsense->client.addr = addr; + capsense->client.driver = &capsense_driver; + strlcpy(capsense->client.name, CAPSENSE_NAME, I2C_NAME_SIZE); + i2c_set_clientdata(&capsense->client, capsense); + + rc = i2c_attach_client(&capsense->client); + if (rc) + goto out_attach; + + ipdev->poll = handle_buttons; + ipdev->private = capsense; + ipdev->poll_interval = poll_interval; + + input = ipdev->input; + input->name = "Capsense buttons"; + input->phys = "capsense/input0"; + input->id.bustype = BUS_I2C; + input->dev.parent = &capsense->client.dev; + + input->keycode = keymap; + input->keycodemax = ARRAY_SIZE(keymap); + input->keycodesize = sizeof(unsigned short); + + input_set_capability(input, EV_MSC, MSC_SCAN); + set_bit(EV_KEY, ipdev->input->evbit); + + for (i = 0; i < ARRAY_SIZE(keymap); i++) + set_bit(keymap[i], ipdev->input->keybit); + + rc = input_register_polled_device(ipdev); + if(rc) + goto out_polled; + + if ( capsense_led_init(capsense) ) + goto out_registered; + + return 0; + +out_registered: + input_unregister_polled_device(ipdev); +out_polled: + i2c_detach_client(&capsense->client); +out_attach: + input_free_polled_device(ipdev); +failout: + return err; +} + +static int +capsense_attach_adapter (struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, capsense_probe); +} + +static int +capsense_detach_client(struct i2c_client *client) +{ + struct cy3218 *capsense = i2c_get_clientdata(client); + + capsense_led_exit(capsense); + input_unregister_polled_device(capsense->ipdev); + i2c_detach_client(&capsense->client); + input_free_polled_device(capsense->ipdev); + return 0; +} + +#ifdef CONFIG_PM +static int capsense_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cy3218 *capsense = i2c_get_clientdata(client); + + capsense_led_suspend(capsense); + + return 0; +} + +static int capsense_resume(struct i2c_client *client) +{ + struct cy3218 *capsense = i2c_get_clientdata(client); + + capsense_led_resume(capsense); + + return 0; +} +#endif + + +static int __init capsense_buttons_init(void) +{ + return i2c_add_driver(&capsense_driver); +} + +static void __exit capsense_buttons_exit(void) +{ + i2c_del_driver(&capsense_driver); +} + +MODULE_AUTHOR("Guillaume Ligneul "); +MODULE_DESCRIPTION("Capsense CY3218 Input driver"); +MODULE_LICENSE("GPL"); +module_init(capsense_buttons_init); +module_exit(capsense_buttons_exit); Index: linux-2.6.27/drivers/input/misc/Kconfig =================================================================== --- linux-2.6.27.orig/drivers/input/misc/Kconfig +++ linux-2.6.27/drivers/input/misc/Kconfig @@ -207,4 +207,13 @@ config HP_SDC_RTC Say Y here if you want to support the built-in real time clock of the HP SDC controller. +config INPUT_CAPSENSE_BTNS + tristate "CAPSENSE CY3218 button interface" + select INPUT_POLLDEV + select LEDS_CLASS + help + To compile this driver as a module, choose M here: the + module will be called cy3218-btns. + To change poll interval, invoque poll parameter in msecs. + endif Index: linux-2.6.27/drivers/input/misc/Makefile =================================================================== --- linux-2.6.27.orig/drivers/input/misc/Makefile +++ linux-2.6.27/drivers/input/misc/Makefile @@ -20,3 +20,5 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc. obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_APANEL) += apanel.o obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o +obj-$(CONFIG_INPUT_CAPSENSE_BTNS) += cy3218-btns.o +