/* * Touchkey driver for MELFAS MCS5000/5080 controller * * Copyright (C) 2010 Samsung Electronics Co.Ltd * Author: HeungJun Kim <riverful.kim@samsung.com> * Author: Joonyoung Shim <jy0922.shim@samsung.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; either version 2 of the License, or (at your * option) any later version. */ #include <linux/module.h> #include <linux/init.h> #include <linux/i2c.h> #include <linux/i2c/mcs.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/irq.h> #include <linux/slab.h> #include <linux/pm.h> /* MCS5000 Touchkey */ #define MCS5000_TOUCHKEY_STATUS 0x04 #define MCS5000_TOUCHKEY_STATUS_PRESS 7 #define MCS5000_TOUCHKEY_FW 0x0a #define MCS5000_TOUCHKEY_BASE_VAL 0x61 /* MCS5080 Touchkey */ #define MCS5080_TOUCHKEY_STATUS 0x00 #define MCS5080_TOUCHKEY_STATUS_PRESS 3 #define MCS5080_TOUCHKEY_FW 0x01 #define MCS5080_TOUCHKEY_BASE_VAL 0x1 enum mcs_touchkey_type { MCS5000_TOUCHKEY, MCS5080_TOUCHKEY, }; struct mcs_touchkey_chip { unsigned int status_reg; unsigned int pressbit; unsigned int press_invert; unsigned int baseval; }; struct mcs_touchkey_data { void (*poweron)(bool); struct i2c_client *client; struct input_dev *input_dev; struct mcs_touchkey_chip chip; unsigned int key_code; unsigned int key_val; unsigned short keycodes[]; }; static irqreturn_t mcs_touchkey_interrupt(int irq, void *dev_id) { struct mcs_touchkey_data *data = dev_id; struct mcs_touchkey_chip *chip = &data->chip; struct i2c_client *client = data->client; struct input_dev *input = data->input_dev; unsigned int key_val; unsigned int pressed; int val; val = i2c_smbus_read_byte_data(client, chip->status_reg); if (val < 0) { dev_err(&client->dev, "i2c read error [%d]\n", val); goto out; } pressed = (val & (1 << chip->pressbit)) >> chip->pressbit; if (chip->press_invert) pressed ^= chip->press_invert; /* key_val is 0 when released, so we should use key_val of press. */ if (pressed) { key_val = val & (0xff >> (8 - chip->pressbit)); if (!key_val) goto out; key_val -= chip->baseval; data->key_code = data->keycodes[key_val]; data->key_val = key_val; } input_event(input, EV_MSC, MSC_SCAN, data->key_val); input_report_key(input, data->key_code, pressed); input_sync(input); dev_dbg(&client->dev, "key %d %d %s\n", data->key_val, data->key_code, pressed ? "pressed" : "released"); out: return IRQ_HANDLED; } static int __devinit mcs_touchkey_probe(struct i2c_client *client, const struct i2c_device_id *id) { const struct mcs_platform_data *pdata; struct mcs_touchkey_data *data; struct input_dev *input_dev; unsigned int fw_reg; int fw_ver; int error; int i; pdata = client->dev.platform_data; if (!pdata) { dev_err(&client->dev, "no platform data defined\n"); return -EINVAL; } data = kzalloc(sizeof(struct mcs_touchkey_data) + sizeof(data->keycodes[0]) * (pdata->key_maxval + 1), GFP_KERNEL); input_dev = input_allocate_device(); if (!data || !input_dev) { dev_err(&client->dev, "Failed to allocate memory\n"); error = -ENOMEM; goto err_free_mem; } data->client = client; data->input_dev = input_dev; if (id->driver_data == MCS5000_TOUCHKEY) { data->chip.status_reg = MCS5000_TOUCHKEY_STATUS; data->chip.pressbit = MCS5000_TOUCHKEY_STATUS_PRESS; data->chip.baseval = MCS5000_TOUCHKEY_BASE_VAL; fw_reg = MCS5000_TOUCHKEY_FW; } else { data->chip.status_reg = MCS5080_TOUCHKEY_STATUS; data->chip.pressbit = MCS5080_TOUCHKEY_STATUS_PRESS; data->chip.press_invert = 1; data->chip.baseval = MCS5080_TOUCHKEY_BASE_VAL; fw_reg = MCS5080_TOUCHKEY_FW; } fw_ver = i2c_smbus_read_byte_data(client, fw_reg); if (fw_ver < 0) { error = fw_ver; dev_err(&client->dev, "i2c read error[%d]\n", error); goto err_free_mem; } dev_info(&client->dev, "Firmware version: %d\n", fw_ver); input_dev->name = "MELPAS MCS Touchkey"; input_dev->id.bustype = BUS_I2C; input_dev->dev.parent = &client->dev; input_dev->evbit[0] = BIT_MASK(EV_KEY); if (!pdata->no_autorepeat) input_dev->evbit[0] |= BIT_MASK(EV_REP); input_dev->keycode = data->keycodes; input_dev->keycodesize = sizeof(data->keycodes[0]); input_dev->keycodemax = pdata->key_maxval + 1; for (i = 0; i < pdata->keymap_size; i++) { unsigned int val = MCS_KEY_VAL(pdata->keymap[i]); unsigned int code = MCS_KEY_CODE(pdata->keymap[i]); data->keycodes[val] = code; __set_bit(code, input_dev->keybit); } input_set_capability(input_dev, EV_MSC, MSC_SCAN); input_set_drvdata(input_dev, data); if (pdata->cfg_pin) pdata->cfg_pin(); if (pdata->poweron) { data->poweron = pdata->poweron; data->poweron(true); } error = request_threaded_irq(client->irq, NULL, mcs_touchkey_interrupt, IRQF_TRIGGER_FALLING, client->dev.driver->name, data); if (error) { dev_err(&client->dev, "Failed to register interrupt\n"); goto err_free_mem; } error = input_register_device(input_dev); if (error) goto err_free_irq; i2c_set_clientdata(client, data); return 0; err_free_irq: free_irq(client->irq, data); err_free_mem: input_free_device(input_dev); kfree(data); return error; } static int __devexit mcs_touchkey_remove(struct i2c_client *client) { struct mcs_touchkey_data *data = i2c_get_clientdata(client); free_irq(client->irq, data); if (data->poweron) data->poweron(false); input_unregister_device(data->input_dev); kfree(data); return 0; } static void mcs_touchkey_shutdown(struct i2c_client *client) { struct mcs_touchkey_data *data = i2c_get_clientdata(client); if (data->poweron) data->poweron(false); } #ifdef CONFIG_PM_SLEEP static int mcs_touchkey_suspend(struct device *dev) { struct mcs_touchkey_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; /* Disable the work */ disable_irq(client->irq); /* Finally turn off the power */ if (data->poweron) data->poweron(false); return 0; } static int mcs_touchkey_resume(struct device *dev) { struct mcs_touchkey_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; /* Enable the device first */ if (data->poweron) data->poweron(true); /* Enable irq again */ enable_irq(client->irq); return 0; } #endif static SIMPLE_DEV_PM_OPS(mcs_touchkey_pm_ops, mcs_touchkey_suspend, mcs_touchkey_resume); static const struct i2c_device_id mcs_touchkey_id[] = { { "mcs5000_touchkey", MCS5000_TOUCHKEY }, { "mcs5080_touchkey", MCS5080_TOUCHKEY }, { } }; MODULE_DEVICE_TABLE(i2c, mcs_touchkey_id); static struct i2c_driver mcs_touchkey_driver = { .driver = { .name = "mcs_touchkey", .owner = THIS_MODULE, .pm = &mcs_touchkey_pm_ops, }, .probe = mcs_touchkey_probe, .remove = __devexit_p(mcs_touchkey_remove), .shutdown = mcs_touchkey_shutdown, .id_table = mcs_touchkey_id, }; static int __init mcs_touchkey_init(void) { return i2c_add_driver(&mcs_touchkey_driver); } static void __exit mcs_touchkey_exit(void) { i2c_del_driver(&mcs_touchkey_driver); } module_init(mcs_touchkey_init); module_exit(mcs_touchkey_exit); /* Module information */ MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); MODULE_AUTHOR("HeungJun Kim <riverful.kim@samsung.com>"); MODULE_DESCRIPTION("Touchkey driver for MELFAS MCS5000/5080 controller"); MODULE_LICENSE("GPL");