| Bryan Wu | b91c4be | 2010-03-19 22:18:15 -0700 | [diff] [blame] | 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 |  | 
 | 9 | #include <linux/module.h> | 
 | 10 | #include <linux/init.h> | 
 | 11 | #include <linux/input.h> | 
 | 12 | #include <linux/interrupt.h> | 
 | 13 | #include <linux/i2c.h> | 
 | 14 | #include <linux/slab.h> | 
 | 15 | #include <linux/workqueue.h> | 
 | 16 |  | 
 | 17 | #define DRV_NAME "pcf8574_keypad" | 
 | 18 |  | 
 | 19 | static const unsigned char pcf8574_kp_btncode[] = { | 
 | 20 | 	[0] = KEY_RESERVED, | 
 | 21 | 	[1] = KEY_ENTER, | 
 | 22 | 	[2] = KEY_BACKSLASH, | 
 | 23 | 	[3] = KEY_0, | 
 | 24 | 	[4] = KEY_RIGHTBRACE, | 
 | 25 | 	[5] = KEY_C, | 
 | 26 | 	[6] = KEY_9, | 
 | 27 | 	[7] = KEY_8, | 
 | 28 | 	[8] = KEY_7, | 
 | 29 | 	[9] = KEY_B, | 
 | 30 | 	[10] = KEY_6, | 
 | 31 | 	[11] = KEY_5, | 
 | 32 | 	[12] = KEY_4, | 
 | 33 | 	[13] = KEY_A, | 
 | 34 | 	[14] = KEY_3, | 
 | 35 | 	[15] = KEY_2, | 
 | 36 | 	[16] = KEY_1 | 
 | 37 | }; | 
 | 38 |  | 
 | 39 | struct kp_data { | 
 | 40 | 	unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)]; | 
 | 41 | 	struct input_dev *idev; | 
 | 42 | 	struct i2c_client *client; | 
 | 43 | 	char name[64]; | 
 | 44 | 	char phys[32]; | 
 | 45 | 	unsigned char laststate; | 
 | 46 | }; | 
 | 47 |  | 
 | 48 | static short read_state(struct kp_data *lp) | 
 | 49 | { | 
 | 50 | 	unsigned char x, y, a, b; | 
 | 51 |  | 
 | 52 | 	i2c_smbus_write_byte(lp->client, 240); | 
 | 53 | 	x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4)); | 
 | 54 |  | 
 | 55 | 	i2c_smbus_write_byte(lp->client, 15); | 
 | 56 | 	y = 0xF & (~i2c_smbus_read_byte(lp->client)); | 
 | 57 |  | 
 | 58 | 	for (a = 0; x > 0; a++) | 
 | 59 | 		x = x >> 1; | 
 | 60 | 	for (b = 0; y > 0; b++) | 
 | 61 | 		y = y >> 1; | 
 | 62 |  | 
 | 63 | 	return ((a - 1) * 4) + b; | 
 | 64 | } | 
 | 65 |  | 
 | 66 | static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id) | 
 | 67 | { | 
 | 68 | 	struct kp_data *lp = dev_id; | 
 | 69 | 	unsigned char nextstate = read_state(lp); | 
 | 70 |  | 
 | 71 | 	if (lp->laststate != nextstate) { | 
 | 72 | 		int key_down = nextstate <= ARRAY_SIZE(lp->btncode); | 
 | 73 | 		unsigned short keycode = key_down ? | 
 | 74 | 			lp->btncode[nextstate] : lp->btncode[lp->laststate]; | 
 | 75 |  | 
 | 76 | 		input_report_key(lp->idev, keycode, key_down); | 
 | 77 | 		input_sync(lp->idev); | 
 | 78 |  | 
 | 79 | 		lp->laststate = nextstate; | 
 | 80 | 	} | 
 | 81 |  | 
 | 82 | 	return IRQ_HANDLED; | 
 | 83 | } | 
 | 84 |  | 
 | 85 | static int __devinit pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id) | 
 | 86 | { | 
 | 87 | 	int i, ret; | 
 | 88 | 	struct input_dev *idev; | 
 | 89 | 	struct kp_data *lp; | 
 | 90 |  | 
 | 91 | 	if (i2c_smbus_write_byte(client, 240) < 0) { | 
 | 92 | 		dev_err(&client->dev, "probe: write fail\n"); | 
 | 93 | 		return -ENODEV; | 
 | 94 | 	} | 
 | 95 |  | 
 | 96 | 	lp = kzalloc(sizeof(*lp), GFP_KERNEL); | 
 | 97 | 	if (!lp) | 
 | 98 | 		return -ENOMEM; | 
 | 99 |  | 
 | 100 | 	idev = input_allocate_device(); | 
 | 101 | 	if (!idev) { | 
 | 102 | 		dev_err(&client->dev, "Can't allocate input device\n"); | 
 | 103 | 		ret = -ENOMEM; | 
 | 104 | 		goto fail_allocate; | 
 | 105 | 	} | 
 | 106 |  | 
 | 107 | 	lp->idev = idev; | 
 | 108 | 	lp->client = client; | 
 | 109 |  | 
 | 110 | 	idev->evbit[0] = BIT_MASK(EV_KEY); | 
 | 111 | 	idev->keycode = lp->btncode; | 
 | 112 | 	idev->keycodesize = sizeof(lp->btncode[0]); | 
 | 113 | 	idev->keycodemax = ARRAY_SIZE(lp->btncode); | 
 | 114 |  | 
 | 115 | 	for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) { | 
 | 116 | 		lp->btncode[i] = pcf8574_kp_btncode[i]; | 
 | 117 | 		__set_bit(lp->btncode[i] & KEY_MAX, idev->keybit); | 
 | 118 | 	} | 
 | 119 |  | 
 | 120 | 	sprintf(lp->name, DRV_NAME); | 
 | 121 | 	sprintf(lp->phys, "kp_data/input0"); | 
 | 122 |  | 
 | 123 | 	idev->name = lp->name; | 
 | 124 | 	idev->phys = lp->phys; | 
 | 125 | 	idev->id.bustype = BUS_I2C; | 
 | 126 | 	idev->id.vendor = 0x0001; | 
 | 127 | 	idev->id.product = 0x0001; | 
 | 128 | 	idev->id.version = 0x0100; | 
 | 129 |  | 
 | 130 | 	input_set_drvdata(idev, lp); | 
 | 131 |  | 
 | 132 | 	ret = input_register_device(idev); | 
 | 133 | 	if (ret) { | 
 | 134 | 		dev_err(&client->dev, "input_register_device() failed\n"); | 
 | 135 | 		goto fail_register; | 
 | 136 | 	} | 
 | 137 |  | 
 | 138 | 	lp->laststate = read_state(lp); | 
 | 139 |  | 
 | 140 | 	ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler, | 
 | 141 | 				   IRQF_TRIGGER_LOW | IRQF_ONESHOT, | 
 | 142 | 				   DRV_NAME, lp); | 
 | 143 | 	if (ret) { | 
 | 144 | 		dev_err(&client->dev, "IRQ %d is not free\n", client->irq); | 
 | 145 | 		goto fail_irq; | 
 | 146 | 	} | 
 | 147 |  | 
 | 148 | 	i2c_set_clientdata(client, lp); | 
 | 149 | 	return 0; | 
 | 150 |  | 
 | 151 |  fail_irq: | 
 | 152 | 	input_unregister_device(idev); | 
 | 153 |  fail_register: | 
 | 154 | 	input_set_drvdata(idev, NULL); | 
 | 155 | 	input_free_device(idev); | 
 | 156 |  fail_allocate: | 
 | 157 | 	kfree(lp); | 
 | 158 |  | 
 | 159 | 	return ret; | 
 | 160 | } | 
 | 161 |  | 
 | 162 | static int __devexit pcf8574_kp_remove(struct i2c_client *client) | 
 | 163 | { | 
 | 164 | 	struct kp_data *lp = i2c_get_clientdata(client); | 
 | 165 |  | 
 | 166 | 	free_irq(client->irq, lp); | 
 | 167 |  | 
 | 168 | 	input_unregister_device(lp->idev); | 
 | 169 | 	kfree(lp); | 
 | 170 |  | 
 | 171 | 	i2c_set_clientdata(client, NULL); | 
 | 172 |  | 
 | 173 | 	return 0; | 
 | 174 | } | 
 | 175 |  | 
 | 176 | #ifdef CONFIG_PM | 
 | 177 | static int pcf8574_kp_resume(struct i2c_client *client) | 
 | 178 | { | 
 | 179 | 	enable_irq(client->irq); | 
 | 180 |  | 
 | 181 | 	return 0; | 
 | 182 | } | 
 | 183 |  | 
 | 184 | static int pcf8574_kp_suspend(struct i2c_client *client, pm_message_t mesg) | 
 | 185 | { | 
 | 186 | 	disable_irq(client->irq); | 
 | 187 |  | 
 | 188 | 	return 0; | 
 | 189 | } | 
 | 190 | #else | 
 | 191 | # define pcf8574_kp_resume  NULL | 
 | 192 | # define pcf8574_kp_suspend NULL | 
 | 193 | #endif | 
 | 194 |  | 
 | 195 | static const struct i2c_device_id pcf8574_kp_id[] = { | 
 | 196 | 	{ DRV_NAME, 0 }, | 
 | 197 | 	{ } | 
 | 198 | }; | 
 | 199 | MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id); | 
 | 200 |  | 
 | 201 | static struct i2c_driver pcf8574_kp_driver = { | 
 | 202 | 	.driver = { | 
 | 203 | 		.name  = DRV_NAME, | 
 | 204 | 		.owner = THIS_MODULE, | 
 | 205 | 	}, | 
 | 206 | 	.probe    = pcf8574_kp_probe, | 
 | 207 | 	.remove   = __devexit_p(pcf8574_kp_remove), | 
 | 208 | 	.suspend  = pcf8574_kp_suspend, | 
 | 209 | 	.resume   = pcf8574_kp_resume, | 
 | 210 | 	.id_table = pcf8574_kp_id, | 
 | 211 | }; | 
 | 212 |  | 
 | 213 | static int __init pcf8574_kp_init(void) | 
 | 214 | { | 
 | 215 | 	return i2c_add_driver(&pcf8574_kp_driver); | 
 | 216 | } | 
 | 217 | module_init(pcf8574_kp_init); | 
 | 218 |  | 
 | 219 | static void __exit pcf8574_kp_exit(void) | 
 | 220 | { | 
 | 221 | 	i2c_del_driver(&pcf8574_kp_driver); | 
 | 222 | } | 
 | 223 | module_exit(pcf8574_kp_exit); | 
 | 224 |  | 
 | 225 | MODULE_AUTHOR("Michael Hennerich"); | 
 | 226 | MODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574"); | 
 | 227 | MODULE_LICENSE("GPL"); |