// 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");