18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2005-2008 Analog Devices Inc. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/input.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/i2c.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/workqueue.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#define DRV_NAME "pcf8574_keypad" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic const unsigned char pcf8574_kp_btncode[] = { 188c2ecf20Sopenharmony_ci [0] = KEY_RESERVED, 198c2ecf20Sopenharmony_ci [1] = KEY_ENTER, 208c2ecf20Sopenharmony_ci [2] = KEY_BACKSLASH, 218c2ecf20Sopenharmony_ci [3] = KEY_0, 228c2ecf20Sopenharmony_ci [4] = KEY_RIGHTBRACE, 238c2ecf20Sopenharmony_ci [5] = KEY_C, 248c2ecf20Sopenharmony_ci [6] = KEY_9, 258c2ecf20Sopenharmony_ci [7] = KEY_8, 268c2ecf20Sopenharmony_ci [8] = KEY_7, 278c2ecf20Sopenharmony_ci [9] = KEY_B, 288c2ecf20Sopenharmony_ci [10] = KEY_6, 298c2ecf20Sopenharmony_ci [11] = KEY_5, 308c2ecf20Sopenharmony_ci [12] = KEY_4, 318c2ecf20Sopenharmony_ci [13] = KEY_A, 328c2ecf20Sopenharmony_ci [14] = KEY_3, 338c2ecf20Sopenharmony_ci [15] = KEY_2, 348c2ecf20Sopenharmony_ci [16] = KEY_1 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct kp_data { 388c2ecf20Sopenharmony_ci unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)]; 398c2ecf20Sopenharmony_ci struct input_dev *idev; 408c2ecf20Sopenharmony_ci struct i2c_client *client; 418c2ecf20Sopenharmony_ci char name[64]; 428c2ecf20Sopenharmony_ci char phys[32]; 438c2ecf20Sopenharmony_ci unsigned char laststate; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic short read_state(struct kp_data *lp) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci unsigned char x, y, a, b; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci i2c_smbus_write_byte(lp->client, 240); 518c2ecf20Sopenharmony_ci x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4)); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci i2c_smbus_write_byte(lp->client, 15); 548c2ecf20Sopenharmony_ci y = 0xF & (~i2c_smbus_read_byte(lp->client)); 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci for (a = 0; x > 0; a++) 578c2ecf20Sopenharmony_ci x = x >> 1; 588c2ecf20Sopenharmony_ci for (b = 0; y > 0; b++) 598c2ecf20Sopenharmony_ci y = y >> 1; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci return ((a - 1) * 4) + b; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct kp_data *lp = dev_id; 678c2ecf20Sopenharmony_ci unsigned char nextstate = read_state(lp); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci if (lp->laststate != nextstate) { 708c2ecf20Sopenharmony_ci int key_down = nextstate < ARRAY_SIZE(lp->btncode); 718c2ecf20Sopenharmony_ci unsigned short keycode = key_down ? 728c2ecf20Sopenharmony_ci lp->btncode[nextstate] : lp->btncode[lp->laststate]; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci input_report_key(lp->idev, keycode, key_down); 758c2ecf20Sopenharmony_ci input_sync(lp->idev); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci lp->laststate = nextstate; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci return IRQ_HANDLED; 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci int i, ret; 868c2ecf20Sopenharmony_ci struct input_dev *idev; 878c2ecf20Sopenharmony_ci struct kp_data *lp; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (i2c_smbus_write_byte(client, 240) < 0) { 908c2ecf20Sopenharmony_ci dev_err(&client->dev, "probe: write fail\n"); 918c2ecf20Sopenharmony_ci return -ENODEV; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci lp = kzalloc(sizeof(*lp), GFP_KERNEL); 958c2ecf20Sopenharmony_ci if (!lp) 968c2ecf20Sopenharmony_ci return -ENOMEM; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci idev = input_allocate_device(); 998c2ecf20Sopenharmony_ci if (!idev) { 1008c2ecf20Sopenharmony_ci dev_err(&client->dev, "Can't allocate input device\n"); 1018c2ecf20Sopenharmony_ci ret = -ENOMEM; 1028c2ecf20Sopenharmony_ci goto fail_allocate; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci lp->idev = idev; 1068c2ecf20Sopenharmony_ci lp->client = client; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci idev->evbit[0] = BIT_MASK(EV_KEY); 1098c2ecf20Sopenharmony_ci idev->keycode = lp->btncode; 1108c2ecf20Sopenharmony_ci idev->keycodesize = sizeof(lp->btncode[0]); 1118c2ecf20Sopenharmony_ci idev->keycodemax = ARRAY_SIZE(lp->btncode); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) { 1148c2ecf20Sopenharmony_ci if (lp->btncode[i] <= KEY_MAX) { 1158c2ecf20Sopenharmony_ci lp->btncode[i] = pcf8574_kp_btncode[i]; 1168c2ecf20Sopenharmony_ci __set_bit(lp->btncode[i], idev->keybit); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci __clear_bit(KEY_RESERVED, idev->keybit); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci sprintf(lp->name, DRV_NAME); 1228c2ecf20Sopenharmony_ci sprintf(lp->phys, "kp_data/input0"); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci idev->name = lp->name; 1258c2ecf20Sopenharmony_ci idev->phys = lp->phys; 1268c2ecf20Sopenharmony_ci idev->id.bustype = BUS_I2C; 1278c2ecf20Sopenharmony_ci idev->id.vendor = 0x0001; 1288c2ecf20Sopenharmony_ci idev->id.product = 0x0001; 1298c2ecf20Sopenharmony_ci idev->id.version = 0x0100; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci lp->laststate = read_state(lp); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler, 1348c2ecf20Sopenharmony_ci IRQF_TRIGGER_LOW | IRQF_ONESHOT, 1358c2ecf20Sopenharmony_ci DRV_NAME, lp); 1368c2ecf20Sopenharmony_ci if (ret) { 1378c2ecf20Sopenharmony_ci dev_err(&client->dev, "IRQ %d is not free\n", client->irq); 1388c2ecf20Sopenharmony_ci goto fail_free_device; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci ret = input_register_device(idev); 1428c2ecf20Sopenharmony_ci if (ret) { 1438c2ecf20Sopenharmony_ci dev_err(&client->dev, "input_register_device() failed\n"); 1448c2ecf20Sopenharmony_ci goto fail_free_irq; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci i2c_set_clientdata(client, lp); 1488c2ecf20Sopenharmony_ci return 0; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci fail_free_irq: 1518c2ecf20Sopenharmony_ci free_irq(client->irq, lp); 1528c2ecf20Sopenharmony_ci fail_free_device: 1538c2ecf20Sopenharmony_ci input_free_device(idev); 1548c2ecf20Sopenharmony_ci fail_allocate: 1558c2ecf20Sopenharmony_ci kfree(lp); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci return ret; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic int pcf8574_kp_remove(struct i2c_client *client) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct kp_data *lp = i2c_get_clientdata(client); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci free_irq(client->irq, lp); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci input_unregister_device(lp->idev); 1678c2ecf20Sopenharmony_ci kfree(lp); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci return 0; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 1738c2ecf20Sopenharmony_cistatic int pcf8574_kp_resume(struct device *dev) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci enable_irq(client->irq); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int pcf8574_kp_suspend(struct device *dev) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct i2c_client *client = to_i2c_client(dev); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci disable_irq(client->irq); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci return 0; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic const struct dev_pm_ops pcf8574_kp_pm_ops = { 1928c2ecf20Sopenharmony_ci .suspend = pcf8574_kp_suspend, 1938c2ecf20Sopenharmony_ci .resume = pcf8574_kp_resume, 1948c2ecf20Sopenharmony_ci}; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci#else 1978c2ecf20Sopenharmony_ci# define pcf8574_kp_resume NULL 1988c2ecf20Sopenharmony_ci# define pcf8574_kp_suspend NULL 1998c2ecf20Sopenharmony_ci#endif 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic const struct i2c_device_id pcf8574_kp_id[] = { 2028c2ecf20Sopenharmony_ci { DRV_NAME, 0 }, 2038c2ecf20Sopenharmony_ci { } 2048c2ecf20Sopenharmony_ci}; 2058c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, pcf8574_kp_id); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic struct i2c_driver pcf8574_kp_driver = { 2088c2ecf20Sopenharmony_ci .driver = { 2098c2ecf20Sopenharmony_ci .name = DRV_NAME, 2108c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2118c2ecf20Sopenharmony_ci .pm = &pcf8574_kp_pm_ops, 2128c2ecf20Sopenharmony_ci#endif 2138c2ecf20Sopenharmony_ci }, 2148c2ecf20Sopenharmony_ci .probe = pcf8574_kp_probe, 2158c2ecf20Sopenharmony_ci .remove = pcf8574_kp_remove, 2168c2ecf20Sopenharmony_ci .id_table = pcf8574_kp_id, 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cimodule_i2c_driver(pcf8574_kp_driver); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Michael Hennerich"); 2228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574"); 2238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 224