aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard/tc35894xbg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/keyboard/tc35894xbg.c')
-rw-r--r--drivers/input/keyboard/tc35894xbg.c722
1 files changed, 722 insertions, 0 deletions
diff --git a/drivers/input/keyboard/tc35894xbg.c b/drivers/input/keyboard/tc35894xbg.c
new file mode 100644
index 00000000000..68e197dbe20
--- /dev/null
+++ b/drivers/input/keyboard/tc35894xbg.c
@@ -0,0 +1,722 @@
+/*
+ * tc35894xbg.c: Keypad driver for Toshiba TC35894XBG
+ *
+ * (C) Copyright 2010 Intel Corporation
+ * Author: Charlie Paul (z8cpaul@windriver.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+#include <linux/i2c/tc35894xbg.h>
+#include "tc35894xbg_regs.h"
+
+struct tc35894xbg_keypad_chip {
+ /* device lock */
+ struct mutex lock;
+ struct i2c_client *client;
+ struct work_struct work;
+ struct input_dev *idev;
+ bool kp_enabled;
+ bool pm_suspend;
+ char phys[32];
+ struct tc35894xbg_platform_data pd;
+ unsigned char keymap_index;
+ unsigned int kp_reg_addr;
+};
+
+#define work_to_keypad(w) container_of(w, struct tc35894xbg_keypad_chip, \
+ work)
+#define client_to_keypad(c) container_of(c, struct tc35894xbg_keypad_chip, \
+ client)
+#define dev_to_keypad(d) container_of(c, struct tc35894xbg_keypad_chip, \
+ client->dev)
+
+#define KEYPAD_MAX_DATA 8
+
+/*
+ * To write, access the chip's address in write mode, and dump the
+ * command and data on the bus. The command and data are taken as
+ * sequential u8s out of varargs, to a maxinum of KEYPAD_MAX_DATA
+ */
+static int keypad_write(struct tc35894xbg_keypad_chip *tc, int len, ...)
+{
+ int ret, i;
+ va_list ap;
+ u8 data[KEYPAD_MAX_DATA];
+
+ va_start(ap, len);
+ if (len > KEYPAD_MAX_DATA) {
+ dev_err(&tc->client->dev, "tried to send %d bytes\n", len);
+ va_end(ap);
+ return 0;
+ }
+
+ for (i = 0; i < len; i++)
+ data[i] = va_arg(ap, int);
+ va_end(ap);
+
+#ifdef DEBUG
+ dev_dbg(&tc->client->dev, "Register write: register:0x%02x", data[0]);
+ for (i = 1; i < len; i++)
+ dev_dbg(&tc->client->dev, ", value:0x%02x", data[i]);
+ dev_dbg(&tc->client->dev, "\n");
+#endif
+ /*
+ * In case of host's asleep, send again when get NACK
+ */
+ ret = i2c_master_send(tc->client, data, len);
+ if (ret == -EREMOTEIO)
+ ret = i2c_master_send(tc->client, data, len);
+
+ if (ret != len)
+ dev_err(&tc->client->dev, "sent %d bytes of %d total\n",
+ len, ret);
+
+ return ret;
+}
+
+/*
+ * To read, first send the command byte and end the transaction.
+ * Then we can get the data in read mode.
+ */
+static int keypad_read(struct tc35894xbg_keypad_chip *tc, u8 cmd, u8 * buf,
+ int len)
+{
+#ifdef DEBUG
+ int i;
+#endif
+ int ret;
+
+ /*
+ * In case of host's asleep, send again when get NACK
+ */
+ ret = i2c_master_send(tc->client, &cmd, 1);
+ if (ret == -EREMOTEIO)
+ ret = i2c_master_send(tc->client, &cmd, 1);
+
+ if (ret != 1) {
+ dev_err(&tc->client->dev, "sending command 0x%2x failed.\n",
+ cmd);
+ return 0;
+ }
+
+ ret = i2c_master_recv(tc->client, buf, len);
+ if (ret != len)
+ dev_err(&tc->client->dev, "want %d bytes, got %d\n", len, ret);
+
+#ifdef DEBUG
+ dev_dbg(&tc->client->dev, "Register read: register:0x%02x", cmd);
+ for (i = 0; i < len; i++)
+ dev_dbg(&tc->client->dev, ", value:0x%02x", buf[i]);
+ dev_dbg(&tc->client->dev, "\n");
+#endif
+ return ret;
+}
+
+/*software reset */
+static void keypad_reset(struct tc35894xbg_keypad_chip *tc)
+{
+ /*
+ * Three reset mode, one is software reset by
+ * control the RSTCTRL register.
+ */
+ keypad_write(tc, 2, TC_REG_RSTCTRL, (TC_VAL_IRQRST | TC_VAL_TIMRST
+ | TC_VAL_KBDRST | TC_VAL_GPIRST));
+ /*
+ * Once reset bit is set, need write back to 0
+ */
+ keypad_write(tc, 2, TC_REG_RSTCTRL, 0x0);
+}
+
+/*
+ * Read the manufacturer ID and SW revision registers. Return them
+ * to the caller, if the caller has supplied pointers.
+ */
+static int keypad_checkid(struct tc35894xbg_keypad_chip *tc,
+ int *mfg_id_ret, int *sw_rev_ret)
+{
+ u8 mfg_id;
+ u8 sw_rev;
+
+ if (keypad_read(tc, TC_REG_MANUFACT_CODE, &mfg_id, 1) != 1)
+ return -EREMOTEIO;
+ if (keypad_read(tc, TC_REG_SW_VERSION, &sw_rev, 1) != 1)
+ return -EREMOTEIO;
+
+ if (mfg_id_ret != NULL)
+ *mfg_id_ret = (int)mfg_id;
+ if (sw_rev_ret != NULL)
+ *sw_rev_ret = (int)sw_rev;
+ return 0;
+}
+
+static int keypad_configure(struct tc35894xbg_keypad_chip *tc)
+{
+ /* enable the modified feature */
+ keypad_write(tc, 2, TC_REG_KBDMFS, TC_VAL_MFSEN);
+
+ /* enable the SYSCLK in KBD and timer */
+ keypad_write(tc, 2, TC_REG_CLKEN, 0x28 | TC_VAL_KBDEN);
+ /* when clock source is RC osci NOTE: Needs to be written twice */
+ keypad_write(tc, 2, TC_REG_CLKEN, 0x28 | TC_VAL_KBDEN);
+
+ dev_dbg(&tc->client->dev, "keypad internal clock setting\n");
+ /* CLKCFG : select the RC-osc:2MHZ, disable doubler, divider:2 */
+ /* CLK_IN = internal clock / 2 = 65KHZ / 2 = 32KHZ */
+ keypad_write(tc, 2, TC_REG_CLKCFG, TC_VAL_CLKSRCSEL | 0x01);
+
+ dev_dbg(&tc->client->dev, "keypad keyboard setting\n");
+ /* keyboard settings */
+ keypad_write(tc, 2, TC_REG_KBDSETTLE, tc->pd.settle_time);
+ keypad_write(tc, 2, TC_REG_KBD_BOUNCE, tc->pd.debounce_time);
+ keypad_write(tc, 2, TC_REG_KBDSIZE, ((tc->pd.size_x << 4)
+ | tc->pd.size_y));
+ keypad_write(tc, 3, TC_REG_DEDCFG_COL, tc->pd.col_setting,
+ tc->pd.rowcol_setting);
+
+ dev_dbg(&tc->client->dev, "keypad keyboard interrupt setting\n");
+ /*XXX: set again */
+ keypad_write(tc, 2, TC_REG_DKBDMSK, 0x03);
+
+ /* clear pending interrupts before irq enabled */
+ keypad_write(tc, 2, TC_REG_KBDIC, (TC_VAL_EVTIC | TC_VAL_KBDIC));
+
+ /* Enable keycode lost intr & keyboard status intr */
+ keypad_write(tc, 2, TC_REG_KBDMSK, 0x00);
+
+ return 0;
+}
+
+/*
+ * AT-style: low 7 bits are the keycode, and the top
+ * bit indicates the state( 1 for down, 0 for up)
+ */
+static inline u8 keypad_whichkey(u8 event)
+{
+ /* bit[7-4]:key row, bit[3-0]:key col */
+ u8 row, col;
+ u8 key;
+ row = (event & 0x70) >> 4;
+ col = (event & 0x0F);
+
+ key = row * 8 + col;
+
+ return key;
+}
+
+static inline int keypad_ispress(u8 event)
+{
+ /* 1: pressed, 0: released */
+ return (event & 0x80) ? 0 : 1;
+}
+
+/* reset the keybit of input */
+static void set_keymap_bit(struct tc35894xbg_keypad_chip *tc)
+{
+ int i;
+ unsigned temp;
+ for (i = 0; i < tc->pd.keymap_size; i++) {
+ temp = (tc->pd.keymap[tc->keymap_index][i] & ~(SHIFT_NEEDED));
+ __set_bit(temp, tc->idev->keybit);
+ }
+
+ __clear_bit(KEY_RESERVED, tc->idev->keybit);
+}
+
+
+/* report the 'right shift' key */
+static void report_shift_key(struct tc35894xbg_keypad_chip *tc, int isdown)
+{
+ if (tc->kp_enabled) {
+ input_report_key(tc->idev,
+ tc->pd.keymap[TC_DEFAULT_KEYMAP][tc->pd.right_shift_key],
+ isdown);
+ input_sync(tc->idev);
+ }
+}
+
+/* report the key code */
+static void submit_key(struct tc35894xbg_keypad_chip *tc, u8 key,
+ unsigned short keycode, int isdown)
+{
+ unsigned short saved_keycode = keycode;
+
+ dev_vdbg(&tc->client->dev, "key 0x%02x %s\n",
+ key, isdown ? "down" : "up");
+ /*
+ * Translate the non-exist keycode keys.
+ * when key press down, report the 'shift' key pressed ahead.
+ */
+ if ((keycode & SHIFT_NEEDED) && isdown) {
+ keycode = keycode & ~(SHIFT_NEEDED);
+ report_shift_key(tc, isdown);
+ }
+
+ /* report the key */
+ if (tc->kp_enabled) {
+ input_report_key(tc->idev, (keycode & ~(SHIFT_NEEDED)), isdown);
+ input_sync(tc->idev);
+ }
+
+ /*
+ * When key press up, report the 'shift' up followed.
+ */
+ if ((saved_keycode & SHIFT_NEEDED) && !isdown)
+ report_shift_key(tc, isdown);
+}
+
+/* key event interrupt handler */
+static inline void process_keys(struct tc35894xbg_keypad_chip *tc)
+{
+ u8 event;
+ int ret, i = 0;
+ static u8 queue[TC35894XBG_MAX_FIFO];
+ static int tail;
+
+ ret = keypad_read(tc, TC_REG_EVTCODE, &event, 1);
+ if (ret < 0) {
+ dev_err(&tc->client->dev, "Failed reading fifo\n");
+ /* clear event buffer */
+ keypad_write(tc, 2, TC_REG_KBDIC, 0x83);
+ return;
+ }
+
+ /* clear event buffer */
+ keypad_write(tc, 2, TC_REG_KBDIC, 0x83);
+
+ i = 0;
+ /* modified feature enable on KBDMFS */
+ if (event != 0x7F && event != 0xFF) {
+
+ u8 key = keypad_whichkey(event);
+ int isdown = keypad_ispress(event);
+ unsigned short keycode = tc->pd.keymap[tc->keymap_index][key];
+
+ /* The function key pressed */
+ if ((key == tc->pd.function_key) && isdown) {
+ tc->keymap_index = TC_ALT_KEYMAP;
+ set_keymap_bit(tc);
+ return;
+ }
+
+ /* Function key press up */
+ if ((key == tc->pd.function_key) && !isdown) {
+ /*
+ * dequeue the queue,
+ * where keys stored while FN is pressed
+ */
+ int j;
+ unsigned short temp_key;
+ for (j = 0; j < tail; j++) { /* keys up */
+ temp_key = tc->pd.keymap[TC_ALT_KEYMAP][queue[j]];
+ submit_key(tc, queue[j], temp_key, 0);
+ }
+ tail = 0;
+
+ tc->keymap_index = TC_DEFAULT_KEYMAP;
+ set_keymap_bit(tc);
+ return;
+ }
+
+ if (tc->keymap_index == TC_ALT_KEYMAP)
+ queue[tail++] = key;
+
+ submit_key(tc, key, keycode, isdown);
+ }
+
+}
+
+/*
+ * Bottom Half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately
+ */
+static void keypad_work(struct work_struct *work)
+{
+ struct tc35894xbg_keypad_chip *tc = work_to_keypad(work);
+ u8 ints = 0;
+
+
+ mutex_lock(&tc->lock);
+ while ((keypad_read(tc, TC_REG_IRQST, &ints, 1) == 1) && ints) {
+ if (ints & TC_VAL_KBDIRQ) {
+ /* keycode revert from the FIFO buffer */
+ process_keys(tc);
+ }
+ }
+ mutex_unlock(&tc->lock);
+}
+
+/*
+ * We cannot use I2c in interrupt context, so we just schedule work.
+ */
+static irqreturn_t keypad_irq(int irq, void *data)
+{
+ struct tc35894xbg_keypad_chip *tc = data;
+ schedule_work(&tc->work);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Sysfs interface
+ */
+static ssize_t keypad_show_disable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tc35894xbg_keypad_chip *tc = dev_get_drvdata(dev);
+ return sprintf(buf, "%u\n", !tc->kp_enabled);
+}
+
+static ssize_t keypad_set_disable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tc35894xbg_keypad_chip *tc = dev_get_drvdata(dev);
+ int ret;
+ unsigned long i;
+
+ ret = strict_strtoul(buf, 10, &i);
+
+ mutex_lock(&tc->lock);
+ tc->kp_enabled = !i;
+ mutex_unlock(&tc->lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(disable_kp, 0644,
+ keypad_show_disable, keypad_set_disable);
+
+static ssize_t keypad_show_addr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tc35894xbg_keypad_chip *tc = dev_get_drvdata(dev);
+ return sprintf(buf, "0x%02X\n", tc->kp_reg_addr);
+}
+
+static ssize_t keypad_set_addr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tc35894xbg_keypad_chip *tc = dev_get_drvdata(dev);
+ int ret;
+ unsigned long i;
+
+ ret = strict_strtoul(buf, 0, &i);
+
+ mutex_lock(&tc->lock);
+ tc->kp_reg_addr = i;
+ mutex_unlock(&tc->lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(addr_kp, 0644,
+ keypad_show_addr, keypad_set_addr);
+
+static ssize_t keypad_show_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tc35894xbg_keypad_chip *tc = dev_get_drvdata(dev);
+ u8 val;
+
+ mutex_lock(&tc->lock);
+ if (keypad_read(tc, tc->kp_reg_addr, &val, 1) == 1) {
+ mutex_unlock(&tc->lock);
+ return sprintf(buf, "0x%02X\n", val);
+ }
+ mutex_unlock(&tc->lock);
+ return 0;
+}
+
+static ssize_t keypad_set_data(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return 0;
+}
+
+static DEVICE_ATTR(data_kp, 0644,
+ keypad_show_data, keypad_set_data);
+
+static struct attribute *tc35894_attributes[] = {
+ &dev_attr_disable_kp.attr,
+ &dev_attr_addr_kp.attr,
+ &dev_attr_data_kp.attr,
+ NULL
+};
+
+static const struct attribute_group tc35894_attr_group = {
+ .attrs = tc35894_attributes,
+};
+
+static int __devinit
+keypad_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+
+ struct tc35894xbg_platform_data *pdata = client->dev.platform_data;
+ struct input_dev *idev;
+ struct tc35894xbg_keypad_chip *tc;
+
+ int err;
+ int sw_rev;
+ int mfg_id;
+ unsigned long tmo;
+ u8 data[2];
+ unsigned int irq;
+
+
+ dev_dbg(&client->dev, "keypad probe\n");
+
+ if (!pdata || !pdata->size_x || !pdata->size_y) {
+ dev_err(&client->dev, "missing platform_data\n");
+ return -EINVAL;
+ }
+
+ if (pdata->size_x > 8) {
+ dev_err(&client->dev, "invalid x size %d specified\n",
+ pdata->size_x);
+ return -EINVAL;
+ }
+
+ if (pdata->size_y > 12) {
+ dev_err(&client->dev, "invalid y size %d specified\n",
+ pdata->size_y);
+ return -EINVAL;
+ }
+
+ tc = kzalloc(sizeof(*tc), GFP_KERNEL);
+ if (!tc) {
+ err = -ENOMEM;
+ goto fail0;
+ }
+ idev = input_allocate_device();
+ if (!idev) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ memcpy(&tc->pd, pdata, sizeof(struct tc35894xbg_platform_data));
+
+ i2c_set_clientdata(client, tc);
+
+ tc->client = client;
+ tc->idev = idev;
+ mutex_init(&tc->lock);
+ INIT_WORK(&tc->work, keypad_work);
+
+ dev_dbg(&client->dev, "Reset GPIO ID: %d\n", tc->pd.gpio_reset);
+ dev_dbg(&client->dev, "Keypad size:%d x %d\n",
+ tc->pd.size_x, tc->pd.size_y);
+
+ /*
+ * Take controller out of reset
+ */
+ if (pdata->gpio_reset != -1) {
+ dev_dbg(&client->dev, "Release TC35894XBG reset\n");
+ if (pdata->reset_ctrl == NULL) {
+ dev_err(&client->dev, "No reset_ctrl function\n");
+ return -ENODEV;
+ }
+ pdata->reset_ctrl(client, 1);
+ }
+
+ /*
+ * Nothing's set up to service the IRQ yet, so just spin for max.
+ * 280us util we can configure.(Tp1 + Tp2)
+ */
+ tmo = jiffies + usecs_to_jiffies(280);
+ while (keypad_read(tc, TC_REG_IRQST, data, 1) == 1) {
+ if (data[0] & TC_VAL_PORIRQ) { /* power on reset complete */
+ /* clear the PORIRQ bit */
+ keypad_write(tc, 2, TC_REG_RSTINTCLR,
+ TC_VAL_IRQCLR);
+ break;
+ }
+ if (time_after(jiffies, tmo)) {
+ dev_err(&client->dev,
+ "timeout waiting for initialisation\n");
+ break;
+ }
+ udelay(1);
+ }
+
+ /* Confirm device ID register */
+ err = keypad_checkid(tc, &mfg_id, &sw_rev);
+ if (err != 0) {
+ dev_err(&client->dev, "Could not read ID and revision\n");
+ goto fail1;
+ } else {
+ dev_dbg(&client->dev, "Controller ID/Rev: 0x%02X/0x%02X\n",
+ mfg_id, sw_rev);
+ }
+
+ /* Software reset can be achieved only after power-on complete */
+ dev_dbg(&client->dev, "Controller reset by software\n");
+ keypad_reset(tc);
+
+ /* detach the RESETN from the global reset tree */
+ keypad_write(tc, 2, TC_REG_EXTRSTN, TC_VAL_EXTRSTN);
+
+ dev_dbg(&client->dev, "keypad configure start\n");
+ keypad_configure(tc);
+
+ tc->kp_enabled = true;
+
+ idev->name = "KEYPAD";
+ snprintf(tc->phys, sizeof(tc->phys), "%s/input-kp",
+ dev_name(&client->dev));
+ idev->phys = tc->phys;
+ /* the two bit set */
+ idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_REL);
+
+ tc->keymap_index = TC_DEFAULT_KEYMAP;
+ set_keymap_bit(tc);
+
+ err = input_register_device(idev);
+ if (err) {
+ dev_dbg(&client->dev, "error register input device\n");
+ goto fail2;
+ }
+
+ irq = gpio_to_irq(pdata->gpio_irq);
+ if (irq < 0) {
+ dev_err(&client->dev, "Failed to get IRQ to GPIO %d\n", irq);
+ goto fail2;
+ }
+ client->irq = irq;
+
+ dev_dbg(&client->dev, "keypad irq register\n");
+ err = request_irq(client->irq, keypad_irq, IRQ_TYPE_EDGE_FALLING
+ | IRQF_SHARED, "keypad", tc);
+ if (err) {
+ dev_err(&client->dev, "could not get IRQ %d\n", irq);
+ goto fail3;
+ }
+
+ /* Register sysfs hooks */
+ err = sysfs_create_group(&client->dev.kobj, &tc35894_attr_group);
+ if (err)
+ goto fail3;
+
+ device_init_wakeup(&client->dev, 1);
+ enable_irq_wake(client->irq);
+
+ return 0;
+
+fail3: input_unregister_device(idev);
+ idev = NULL;
+
+fail2: device_remove_file(&client->dev, &dev_attr_disable_kp);
+
+fail1: input_free_device(idev);
+
+fail0: kfree(tc);
+
+ return err;
+}
+
+static int __devexit keypad_remove(struct i2c_client *client)
+{
+ struct tc35894xbg_keypad_chip *tc = i2c_get_clientdata(client);
+
+ dev_dbg(&client->dev, "keypad driver remove\n");
+
+ disable_irq_wake(client->irq);
+ free_irq(client->irq, tc);
+ cancel_work_sync(&tc->work);
+
+ dev_dbg(&client->dev, "keypad input device unregister\n");
+ sysfs_remove_group(&client->dev.kobj, &tc35894_attr_group);
+ input_unregister_device(tc->idev);
+ device_remove_file(&tc->client->dev, &dev_attr_disable_kp);
+
+ kfree(tc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * the chip can switch off when no activity, so
+ * explicitly suspend is no need
+ */
+
+static int keypad_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct tc35894xbg_keypad_chip *tc = i2c_get_clientdata(client);
+
+ set_irq_wake(client->irq, 0);
+ disable_irq(client->irq);
+
+ mutex_lock(&tc->lock);
+ tc->pm_suspend = true;
+ mutex_unlock(&tc->lock);
+ return 0;
+}
+
+static int keypad_resume(struct i2c_client *client)
+{
+ struct tc35894xbg_keypad_chip *tc = i2c_get_clientdata(client);
+
+ mutex_lock(&tc->lock);
+ tc->pm_suspend = false;
+ mutex_unlock(&tc->lock);
+
+ enable_irq(client->irq);
+ set_irq_wake(client->irq, 1);
+ return 0;
+}
+
+#else
+#define keypad_suspend NULL
+#define keypad_resume NULL
+
+#endif
+
+static const struct i2c_device_id keypad_id[] = {
+ { "i2c_TC35894-nEB1", 0 },
+ { "i2c_TC35894-i", 0 },
+ { }
+};
+
+static struct i2c_driver keypad_i2c_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {.name = "keypad",},
+ .probe = keypad_probe,
+ .remove = __devexit_p(keypad_remove),
+ .suspend = keypad_suspend,
+ .resume = keypad_resume,
+ .id_table = keypad_id,
+};
+
+MODULE_DEVICE_TABLE(i2c, keypad_id);
+
+static int __init keypad_init(void)
+{
+ return i2c_add_driver(&keypad_i2c_driver);
+}
+
+static void __exit keypad_exit(void)
+{
+ i2c_del_driver(&keypad_i2c_driver);
+}
+
+module_init(keypad_init);
+module_exit(keypad_exit);
+
+MODULE_AUTHOR("Charlie Paul");
+MODULE_DESCRIPTION("TC35894XBG expander driver");
+MODULE_LICENSE("GPL");