pcf8574_keypad.c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /*
  2. * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander
  3. *
  4. * Copyright 2005-2008 Analog Devices Inc.
  5. *
  6. * Licensed under the GPL-2 or later.
  7. */
  8. #include <linux/module.h>
  9. #include <linux/input.h>
  10. #include <linux/interrupt.h>
  11. #include <linux/i2c.h>
  12. #include <linux/slab.h>
  13. #include <linux/workqueue.h>
  14. #define DRV_NAME "pcf8574_keypad"
  15. static const unsigned char pcf8574_kp_btncode[] = {
  16. [0] = KEY_RESERVED,
  17. [1] = KEY_ENTER,
  18. [2] = KEY_BACKSLASH,
  19. [3] = KEY_0,
  20. [4] = KEY_RIGHTBRACE,
  21. [5] = KEY_C,
  22. [6] = KEY_9,
  23. [7] = KEY_8,
  24. [8] = KEY_7,
  25. [9] = KEY_B,
  26. [10] = KEY_6,
  27. [11] = KEY_5,
  28. [12] = KEY_4,
  29. [13] = KEY_A,
  30. [14] = KEY_3,
  31. [15] = KEY_2,
  32. [16] = KEY_1
  33. };
  34. struct kp_data {
  35. unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)];
  36. struct input_dev *idev;
  37. struct i2c_client *client;
  38. char name[64];
  39. char phys[32];
  40. unsigned char laststate;
  41. };
  42. static short read_state(struct kp_data *lp)
  43. {
  44. unsigned char x, y, a, b;
  45. i2c_smbus_write_byte(lp->client, 240);
  46. x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4));
  47. i2c_smbus_write_byte(lp->client, 15);
  48. y = 0xF & (~i2c_smbus_read_byte(lp->client));
  49. for (a = 0; x > 0; a++)
  50. x = x >> 1;
  51. for (b = 0; y > 0; b++)
  52. y = y >> 1;
  53. return ((a - 1) * 4) + b;
  54. }
  55. static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id)
  56. {
  57. struct kp_data *lp = dev_id;
  58. unsigned char nextstate = read_state(lp);
  59. if (lp->laststate != nextstate) {
  60. int key_down = nextstate < ARRAY_SIZE(lp->btncode);
  61. unsigned short keycode = key_down ?
  62. lp->btncode[nextstate] : lp->btncode[lp->laststate];
  63. input_report_key(lp->idev, keycode, key_down);
  64. input_sync(lp->idev);
  65. lp->laststate = nextstate;
  66. }
  67. return IRQ_HANDLED;
  68. }
  69. static int pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id)
  70. {
  71. int i, ret;
  72. struct input_dev *idev;
  73. struct kp_data *lp;
  74. if (i2c_smbus_write_byte(client, 240) < 0) {
  75. dev_err(&client->dev, "probe: write fail\n");
  76. return -ENODEV;
  77. }
  78. lp = kzalloc(sizeof(*lp), GFP_KERNEL);
  79. if (!lp)
  80. return -ENOMEM;
  81. idev = input_allocate_device();
  82. if (!idev) {
  83. dev_err(&client->dev, "Can't allocate input device\n");
  84. ret = -ENOMEM;
  85. goto fail_allocate;
  86. }
  87. lp->idev = idev;
  88. lp->client = client;
  89. idev->evbit[0] = BIT_MASK(EV_KEY);
  90. idev->keycode = lp->btncode;
  91. idev->keycodesize = sizeof(lp->btncode[0]);
  92. idev->keycodemax = ARRAY_SIZE(lp->btncode);
  93. for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) {
  94. if (lp->btncode[i] <= KEY_MAX) {
  95. lp->btncode[i] = pcf8574_kp_btncode[i];
  96. __set_bit(lp->btncode[i], idev->keybit);
  97. }
  98. }
  99. __clear_bit(KEY_RESERVED, idev->keybit);
  100. sprintf(lp->name, DRV_NAME);
  101. sprintf(lp->phys, "kp_data/input0");
  102. idev->name = lp->name;
  103. idev->phys = lp->phys;
  104. idev->id.bustype = BUS_I2C;
  105. idev->id.vendor = 0x0001;
  106. idev->id.product = 0x0001;
  107. idev->id.version = 0x0100;
  108. lp->laststate = read_state(lp);
  109. ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler,
  110. IRQF_TRIGGER_LOW | IRQF_ONESHOT,
  111. DRV_NAME, lp);
  112. if (ret) {
  113. dev_err(&client->dev, "IRQ %d is not free\n", client->irq);
  114. goto fail_free_device;
  115. }
  116. ret = input_register_device(idev);
  117. if (ret) {
  118. dev_err(&client->dev, "input_register_device() failed\n");
  119. goto fail_free_irq;
  120. }
  121. i2c_set_clientdata(client, lp);
  122. return 0;
  123. fail_free_irq:
  124. free_irq(client->irq, lp);
  125. fail_free_device:
  126. input_free_device(idev);
  127. fail_allocate:
  128. kfree(lp);
  129. return ret;
  130. }
  131. static int pcf8574_kp_remove(struct i2c_client *client)
  132. {
  133. struct kp_data *lp = i2c_get_clientdata(client);
  134. free_irq(client->irq, lp);
  135. input_unregister_device(lp->idev);
  136. kfree(lp);
  137. return 0;
  138. }
  139. #ifdef CONFIG_PM
  140. static int pcf8574_kp_resume(struct device *dev)
  141. {
  142. struct i2c_client *client = to_i2c_client(dev);
  143. enable_irq(client->irq);
  144. return 0;
  145. }
  146. static int pcf8574_kp_suspend(struct device *dev)
  147. {
  148. struct i2c_client *client = to_i2c_client(dev);
  149. disable_irq(client->irq);
  150. return 0;
  151. }
  152. static const struct dev_pm_ops pcf8574_kp_pm_ops = {
  153. .suspend = pcf8574_kp_suspend,
  154. .resume = pcf8574_kp_resume,
  155. };
  156. #else
  157. # define pcf8574_kp_resume NULL
  158. # define pcf8574_kp_suspend NULL
  159. #endif
  160. static const struct i2c_device_id pcf8574_kp_id[] = {
  161. { DRV_NAME, 0 },
  162. { }
  163. };
  164. MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id);
  165. static struct i2c_driver pcf8574_kp_driver = {
  166. .driver = {
  167. .name = DRV_NAME,
  168. #ifdef CONFIG_PM
  169. .pm = &pcf8574_kp_pm_ops,
  170. #endif
  171. },
  172. .probe = pcf8574_kp_probe,
  173. .remove = pcf8574_kp_remove,
  174. .id_table = pcf8574_kp_id,
  175. };
  176. module_i2c_driver(pcf8574_kp_driver);
  177. MODULE_AUTHOR("Michael Hennerich");
  178. MODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574");
  179. MODULE_LICENSE("GPL");