|  | /* | 
|  | * TM2 touchkey device driver | 
|  | * | 
|  | * Copyright 2005 Phil Blundell | 
|  | * Copyright 2016 Samsung Electronics Co., Ltd. | 
|  | * | 
|  | * Author: Beomho Seo <beomho.seo@samsung.com> | 
|  | * Author: Jaechul Lee <jcsing.lee@samsung.com> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License version 2 as | 
|  | * published by the Free Software Foundation. | 
|  | */ | 
|  |  | 
|  | #include <linux/bitops.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/device.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/input.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/irq.h> | 
|  | #include <linux/leds.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/pm.h> | 
|  | #include <linux/regulator/consumer.h> | 
|  |  | 
|  | #define TM2_TOUCHKEY_DEV_NAME		"tm2-touchkey" | 
|  | #define TM2_TOUCHKEY_KEYCODE_REG	0x03 | 
|  | #define TM2_TOUCHKEY_BASE_REG		0x00 | 
|  | #define TM2_TOUCHKEY_CMD_LED_ON		0x10 | 
|  | #define TM2_TOUCHKEY_CMD_LED_OFF	0x20 | 
|  | #define TM2_TOUCHKEY_BIT_PRESS_EV	BIT(3) | 
|  | #define TM2_TOUCHKEY_BIT_KEYCODE	GENMASK(2, 0) | 
|  | #define TM2_TOUCHKEY_LED_VOLTAGE_MIN	2500000 | 
|  | #define TM2_TOUCHKEY_LED_VOLTAGE_MAX	3300000 | 
|  |  | 
|  | enum { | 
|  | TM2_TOUCHKEY_KEY_MENU = 0x1, | 
|  | TM2_TOUCHKEY_KEY_BACK, | 
|  | }; | 
|  |  | 
|  | struct tm2_touchkey_data { | 
|  | struct i2c_client *client; | 
|  | struct input_dev *input_dev; | 
|  | struct led_classdev led_dev; | 
|  | struct regulator *vdd; | 
|  | struct regulator_bulk_data regulators[2]; | 
|  | }; | 
|  |  | 
|  | static void tm2_touchkey_led_brightness_set(struct led_classdev *led_dev, | 
|  | enum led_brightness brightness) | 
|  | { | 
|  | struct tm2_touchkey_data *touchkey = | 
|  | container_of(led_dev, struct tm2_touchkey_data, led_dev); | 
|  | u32 volt; | 
|  | u8 data; | 
|  |  | 
|  | if (brightness == LED_OFF) { | 
|  | volt = TM2_TOUCHKEY_LED_VOLTAGE_MIN; | 
|  | data = TM2_TOUCHKEY_CMD_LED_OFF; | 
|  | } else { | 
|  | volt = TM2_TOUCHKEY_LED_VOLTAGE_MAX; | 
|  | data = TM2_TOUCHKEY_CMD_LED_ON; | 
|  | } | 
|  |  | 
|  | regulator_set_voltage(touchkey->vdd, volt, volt); | 
|  | i2c_smbus_write_byte_data(touchkey->client, | 
|  | TM2_TOUCHKEY_BASE_REG, data); | 
|  | } | 
|  |  | 
|  | static int tm2_touchkey_power_enable(struct tm2_touchkey_data *touchkey) | 
|  | { | 
|  | int error; | 
|  |  | 
|  | error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators), | 
|  | touchkey->regulators); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* waiting for device initialization, at least 150ms */ | 
|  | msleep(150); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void tm2_touchkey_power_disable(void *data) | 
|  | { | 
|  | struct tm2_touchkey_data *touchkey = data; | 
|  |  | 
|  | regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), | 
|  | touchkey->regulators); | 
|  | } | 
|  |  | 
|  | static irqreturn_t tm2_touchkey_irq_handler(int irq, void *devid) | 
|  | { | 
|  | struct tm2_touchkey_data *touchkey = devid; | 
|  | int data; | 
|  | int key; | 
|  |  | 
|  | data = i2c_smbus_read_byte_data(touchkey->client, | 
|  | TM2_TOUCHKEY_KEYCODE_REG); | 
|  | if (data < 0) { | 
|  | dev_err(&touchkey->client->dev, | 
|  | "failed to read i2c data: %d\n", data); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | switch (data & TM2_TOUCHKEY_BIT_KEYCODE) { | 
|  | case TM2_TOUCHKEY_KEY_MENU: | 
|  | key = KEY_PHONE; | 
|  | break; | 
|  |  | 
|  | case TM2_TOUCHKEY_KEY_BACK: | 
|  | key = KEY_BACK; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dev_warn(&touchkey->client->dev, | 
|  | "unhandled keycode, data %#02x\n", data); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (data & TM2_TOUCHKEY_BIT_PRESS_EV) { | 
|  | input_report_key(touchkey->input_dev, KEY_PHONE, 0); | 
|  | input_report_key(touchkey->input_dev, KEY_BACK, 0); | 
|  | } else { | 
|  | input_report_key(touchkey->input_dev, key, 1); | 
|  | } | 
|  |  | 
|  | input_sync(touchkey->input_dev); | 
|  |  | 
|  | out: | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int tm2_touchkey_probe(struct i2c_client *client, | 
|  | const struct i2c_device_id *id) | 
|  | { | 
|  | struct tm2_touchkey_data *touchkey; | 
|  | int error; | 
|  |  | 
|  | if (!i2c_check_functionality(client->adapter, | 
|  | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA)) { | 
|  | dev_err(&client->dev, "incompatible I2C adapter\n"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | touchkey = devm_kzalloc(&client->dev, sizeof(*touchkey), GFP_KERNEL); | 
|  | if (!touchkey) | 
|  | return -ENOMEM; | 
|  |  | 
|  | touchkey->client = client; | 
|  | i2c_set_clientdata(client, touchkey); | 
|  |  | 
|  | touchkey->regulators[0].supply = "vcc"; | 
|  | touchkey->regulators[1].supply = "vdd"; | 
|  | error = devm_regulator_bulk_get(&client->dev, | 
|  | ARRAY_SIZE(touchkey->regulators), | 
|  | touchkey->regulators); | 
|  | if (error) { | 
|  | dev_err(&client->dev, "failed to get regulators: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* Save VDD for easy access */ | 
|  | touchkey->vdd = touchkey->regulators[1].consumer; | 
|  |  | 
|  | error = tm2_touchkey_power_enable(touchkey); | 
|  | if (error) { | 
|  | dev_err(&client->dev, "failed to power up device: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = devm_add_action_or_reset(&client->dev, | 
|  | tm2_touchkey_power_disable, touchkey); | 
|  | if (error) { | 
|  | dev_err(&client->dev, | 
|  | "failed to install poweroff handler: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* input device */ | 
|  | touchkey->input_dev = devm_input_allocate_device(&client->dev); | 
|  | if (!touchkey->input_dev) { | 
|  | dev_err(&client->dev, "failed to allocate input device\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | touchkey->input_dev->name = TM2_TOUCHKEY_DEV_NAME; | 
|  | touchkey->input_dev->id.bustype = BUS_I2C; | 
|  |  | 
|  | input_set_capability(touchkey->input_dev, EV_KEY, KEY_PHONE); | 
|  | input_set_capability(touchkey->input_dev, EV_KEY, KEY_BACK); | 
|  |  | 
|  | error = input_register_device(touchkey->input_dev); | 
|  | if (error) { | 
|  | dev_err(&client->dev, | 
|  | "failed to register input device: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = devm_request_threaded_irq(&client->dev, client->irq, | 
|  | NULL, tm2_touchkey_irq_handler, | 
|  | IRQF_ONESHOT, | 
|  | TM2_TOUCHKEY_DEV_NAME, touchkey); | 
|  | if (error) { | 
|  | dev_err(&client->dev, | 
|  | "failed to request threaded irq: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* led device */ | 
|  | touchkey->led_dev.name = TM2_TOUCHKEY_DEV_NAME; | 
|  | touchkey->led_dev.brightness = LED_FULL; | 
|  | touchkey->led_dev.max_brightness = LED_ON; | 
|  | touchkey->led_dev.brightness_set = tm2_touchkey_led_brightness_set; | 
|  |  | 
|  | error = devm_led_classdev_register(&client->dev, &touchkey->led_dev); | 
|  | if (error) { | 
|  | dev_err(&client->dev, | 
|  | "failed to register touchkey led: %d\n", error); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __maybe_unused tm2_touchkey_suspend(struct device *dev) | 
|  | { | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); | 
|  |  | 
|  | disable_irq(client->irq); | 
|  | tm2_touchkey_power_disable(touchkey); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __maybe_unused tm2_touchkey_resume(struct device *dev) | 
|  | { | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); | 
|  | int ret; | 
|  |  | 
|  | enable_irq(client->irq); | 
|  |  | 
|  | ret = tm2_touchkey_power_enable(touchkey); | 
|  | if (ret) | 
|  | dev_err(dev, "failed to enable power: %d\n", ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static SIMPLE_DEV_PM_OPS(tm2_touchkey_pm_ops, | 
|  | tm2_touchkey_suspend, tm2_touchkey_resume); | 
|  |  | 
|  | static const struct i2c_device_id tm2_touchkey_id_table[] = { | 
|  | { TM2_TOUCHKEY_DEV_NAME, 0 }, | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(i2c, tm2_touchkey_id_table); | 
|  |  | 
|  | static const struct of_device_id tm2_touchkey_of_match[] = { | 
|  | { .compatible = "cypress,tm2-touchkey", }, | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, tm2_touchkey_of_match); | 
|  |  | 
|  | static struct i2c_driver tm2_touchkey_driver = { | 
|  | .driver = { | 
|  | .name = TM2_TOUCHKEY_DEV_NAME, | 
|  | .pm = &tm2_touchkey_pm_ops, | 
|  | .of_match_table = of_match_ptr(tm2_touchkey_of_match), | 
|  | }, | 
|  | .probe = tm2_touchkey_probe, | 
|  | .id_table = tm2_touchkey_id_table, | 
|  | }; | 
|  | module_i2c_driver(tm2_touchkey_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); | 
|  | MODULE_AUTHOR("Jaechul Lee <jcsing.lee@samsung.com>"); | 
|  | MODULE_DESCRIPTION("Samsung touchkey driver"); | 
|  | MODULE_LICENSE("GPL v2"); |