mirror of
https://github.com/torvalds/linux.git
synced 2024-11-18 01:51:53 +00:00
5f62615ef5
The MCS50XX series has a HW bug that requires explicit chip power down. If chip is not powered down before shutting the system down the control pins (powerup, interrupt) are pulled up and residue current continues flowing into the chips making them continue consuming power. Signed-off-by: Heungjun Kim <riverful.kim@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
295 lines
7.3 KiB
C
295 lines
7.3 KiB
C
/*
|
|
* 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");
|