|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander | 
|  | * | 
|  | * Copyright 2005-2008 Analog Devices Inc. | 
|  | */ | 
|  |  | 
|  | #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"); |