123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- /*
- * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander
- *
- * Copyright 2005-2008 Analog Devices Inc.
- *
- * Licensed under the GPL-2 or later.
- */
- #include <linux/module.h>
- #include <linux/input.h>
- #include <linux/interrupt.h>
- #include <linux/i2c.h>
- #include <linux/slab.h>
- #include <linux/workqueue.h>
- #define DRV_NAME "pcf8574_keypad"
- static const unsigned char pcf8574_kp_btncode[] = {
- [0] = KEY_RESERVED,
- [1] = KEY_ENTER,
- [2] = KEY_BACKSLASH,
- [3] = KEY_0,
- [4] = KEY_RIGHTBRACE,
- [5] = KEY_C,
- [6] = KEY_9,
- [7] = KEY_8,
- [8] = KEY_7,
- [9] = KEY_B,
- [10] = KEY_6,
- [11] = KEY_5,
- [12] = KEY_4,
- [13] = KEY_A,
- [14] = KEY_3,
- [15] = KEY_2,
- [16] = KEY_1
- };
- struct kp_data {
- unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)];
- struct input_dev *idev;
- struct i2c_client *client;
- char name[64];
- char phys[32];
- unsigned char laststate;
- };
- static short read_state(struct kp_data *lp)
- {
- unsigned char x, y, a, b;
- i2c_smbus_write_byte(lp->client, 240);
- x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4));
- i2c_smbus_write_byte(lp->client, 15);
- y = 0xF & (~i2c_smbus_read_byte(lp->client));
- for (a = 0; x > 0; a++)
- x = x >> 1;
- for (b = 0; y > 0; b++)
- y = y >> 1;
- return ((a - 1) * 4) + b;
- }
- static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id)
- {
- struct kp_data *lp = dev_id;
- unsigned char nextstate = read_state(lp);
- if (lp->laststate != nextstate) {
- int key_down = nextstate < ARRAY_SIZE(lp->btncode);
- unsigned short keycode = key_down ?
- lp->btncode[nextstate] : lp->btncode[lp->laststate];
- input_report_key(lp->idev, keycode, key_down);
- input_sync(lp->idev);
- lp->laststate = nextstate;
- }
- return IRQ_HANDLED;
- }
- static int pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id)
- {
- int i, ret;
- struct input_dev *idev;
- struct kp_data *lp;
- if (i2c_smbus_write_byte(client, 240) < 0) {
- dev_err(&client->dev, "probe: write fail\n");
- return -ENODEV;
- }
- lp = kzalloc(sizeof(*lp), GFP_KERNEL);
- if (!lp)
- return -ENOMEM;
- idev = input_allocate_device();
- if (!idev) {
- dev_err(&client->dev, "Can't allocate input device\n");
- ret = -ENOMEM;
- goto fail_allocate;
- }
- lp->idev = idev;
- lp->client = client;
- idev->evbit[0] = BIT_MASK(EV_KEY);
- idev->keycode = lp->btncode;
- idev->keycodesize = sizeof(lp->btncode[0]);
- idev->keycodemax = ARRAY_SIZE(lp->btncode);
- for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) {
- if (lp->btncode[i] <= KEY_MAX) {
- lp->btncode[i] = pcf8574_kp_btncode[i];
- __set_bit(lp->btncode[i], idev->keybit);
- }
- }
- __clear_bit(KEY_RESERVED, idev->keybit);
- sprintf(lp->name, DRV_NAME);
- sprintf(lp->phys, "kp_data/input0");
- idev->name = lp->name;
- idev->phys = lp->phys;
- idev->id.bustype = BUS_I2C;
- idev->id.vendor = 0x0001;
- idev->id.product = 0x0001;
- idev->id.version = 0x0100;
- lp->laststate = read_state(lp);
- ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- DRV_NAME, lp);
- if (ret) {
- dev_err(&client->dev, "IRQ %d is not free\n", client->irq);
- goto fail_free_device;
- }
- ret = input_register_device(idev);
- if (ret) {
- dev_err(&client->dev, "input_register_device() failed\n");
- goto fail_free_irq;
- }
- i2c_set_clientdata(client, lp);
- return 0;
- fail_free_irq:
- free_irq(client->irq, lp);
- fail_free_device:
- input_free_device(idev);
- fail_allocate:
- kfree(lp);
- return ret;
- }
- static int pcf8574_kp_remove(struct i2c_client *client)
- {
- struct kp_data *lp = i2c_get_clientdata(client);
- free_irq(client->irq, lp);
- input_unregister_device(lp->idev);
- kfree(lp);
- return 0;
- }
- #ifdef CONFIG_PM
- static int pcf8574_kp_resume(struct device *dev)
- {
- struct i2c_client *client = to_i2c_client(dev);
- enable_irq(client->irq);
- return 0;
- }
- static int pcf8574_kp_suspend(struct device *dev)
- {
- struct i2c_client *client = to_i2c_client(dev);
- disable_irq(client->irq);
- return 0;
- }
- static const struct dev_pm_ops pcf8574_kp_pm_ops = {
- .suspend = pcf8574_kp_suspend,
- .resume = pcf8574_kp_resume,
- };
- #else
- # define pcf8574_kp_resume NULL
- # define pcf8574_kp_suspend NULL
- #endif
- static const struct i2c_device_id pcf8574_kp_id[] = {
- { DRV_NAME, 0 },
- { }
- };
- MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id);
- static struct i2c_driver pcf8574_kp_driver = {
- .driver = {
- .name = DRV_NAME,
- #ifdef CONFIG_PM
- .pm = &pcf8574_kp_pm_ops,
- #endif
- },
- .probe = pcf8574_kp_probe,
- .remove = pcf8574_kp_remove,
- .id_table = pcf8574_kp_id,
- };
- module_i2c_driver(pcf8574_kp_driver);
- MODULE_AUTHOR("Michael Hennerich");
- MODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574");
- MODULE_LICENSE("GPL");
|