]>
Commit | Line | Data |
---|---|---|
72d1f234 JL |
1 | /* |
2 | * TM2 touchkey device driver | |
3 | * | |
4 | * Copyright 2005 Phil Blundell | |
5 | * Copyright 2016 Samsung Electronics Co., Ltd. | |
6 | * | |
7 | * Author: Beomho Seo <beomho.seo@samsung.com> | |
8 | * Author: Jaechul Lee <jcsing.lee@samsung.com> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | */ | |
14 | ||
15 | #include <linux/bitops.h> | |
16 | #include <linux/delay.h> | |
17 | #include <linux/device.h> | |
18 | #include <linux/i2c.h> | |
19 | #include <linux/input.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/irq.h> | |
22 | #include <linux/leds.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/of.h> | |
25 | #include <linux/pm.h> | |
26 | #include <linux/regulator/consumer.h> | |
27 | ||
28 | #define TM2_TOUCHKEY_DEV_NAME "tm2-touchkey" | |
29 | #define TM2_TOUCHKEY_KEYCODE_REG 0x03 | |
30 | #define TM2_TOUCHKEY_BASE_REG 0x00 | |
31 | #define TM2_TOUCHKEY_CMD_LED_ON 0x10 | |
32 | #define TM2_TOUCHKEY_CMD_LED_OFF 0x20 | |
33 | #define TM2_TOUCHKEY_BIT_PRESS_EV BIT(3) | |
34 | #define TM2_TOUCHKEY_BIT_KEYCODE GENMASK(2, 0) | |
35 | #define TM2_TOUCHKEY_LED_VOLTAGE_MIN 2500000 | |
36 | #define TM2_TOUCHKEY_LED_VOLTAGE_MAX 3300000 | |
37 | ||
38 | enum { | |
39 | TM2_TOUCHKEY_KEY_MENU = 0x1, | |
40 | TM2_TOUCHKEY_KEY_BACK, | |
41 | }; | |
42 | ||
43 | struct tm2_touchkey_data { | |
44 | struct i2c_client *client; | |
45 | struct input_dev *input_dev; | |
46 | struct led_classdev led_dev; | |
47 | struct regulator *vdd; | |
48 | struct regulator_bulk_data regulators[2]; | |
49 | }; | |
50 | ||
51 | static void tm2_touchkey_led_brightness_set(struct led_classdev *led_dev, | |
52 | enum led_brightness brightness) | |
53 | { | |
54 | struct tm2_touchkey_data *touchkey = | |
55 | container_of(led_dev, struct tm2_touchkey_data, led_dev); | |
56 | u32 volt; | |
57 | u8 data; | |
58 | ||
59 | if (brightness == LED_OFF) { | |
60 | volt = TM2_TOUCHKEY_LED_VOLTAGE_MIN; | |
61 | data = TM2_TOUCHKEY_CMD_LED_OFF; | |
62 | } else { | |
63 | volt = TM2_TOUCHKEY_LED_VOLTAGE_MAX; | |
64 | data = TM2_TOUCHKEY_CMD_LED_ON; | |
65 | } | |
66 | ||
67 | regulator_set_voltage(touchkey->vdd, volt, volt); | |
68 | i2c_smbus_write_byte_data(touchkey->client, | |
69 | TM2_TOUCHKEY_BASE_REG, data); | |
70 | } | |
71 | ||
72 | static int tm2_touchkey_power_enable(struct tm2_touchkey_data *touchkey) | |
73 | { | |
74 | int error; | |
75 | ||
76 | error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators), | |
77 | touchkey->regulators); | |
78 | if (error) | |
79 | return error; | |
80 | ||
81 | /* waiting for device initialization, at least 150ms */ | |
82 | msleep(150); | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | static void tm2_touchkey_power_disable(void *data) | |
88 | { | |
89 | struct tm2_touchkey_data *touchkey = data; | |
90 | ||
91 | regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), | |
92 | touchkey->regulators); | |
93 | } | |
94 | ||
95 | static irqreturn_t tm2_touchkey_irq_handler(int irq, void *devid) | |
96 | { | |
97 | struct tm2_touchkey_data *touchkey = devid; | |
98 | int data; | |
99 | int key; | |
100 | ||
101 | data = i2c_smbus_read_byte_data(touchkey->client, | |
102 | TM2_TOUCHKEY_KEYCODE_REG); | |
103 | if (data < 0) { | |
104 | dev_err(&touchkey->client->dev, | |
105 | "failed to read i2c data: %d\n", data); | |
106 | goto out; | |
107 | } | |
108 | ||
109 | switch (data & TM2_TOUCHKEY_BIT_KEYCODE) { | |
110 | case TM2_TOUCHKEY_KEY_MENU: | |
111 | key = KEY_PHONE; | |
112 | break; | |
113 | ||
114 | case TM2_TOUCHKEY_KEY_BACK: | |
115 | key = KEY_BACK; | |
116 | break; | |
117 | ||
118 | default: | |
119 | dev_warn(&touchkey->client->dev, | |
120 | "unhandled keycode, data %#02x\n", data); | |
121 | goto out; | |
122 | } | |
123 | ||
124 | if (data & TM2_TOUCHKEY_BIT_PRESS_EV) { | |
125 | input_report_key(touchkey->input_dev, KEY_PHONE, 0); | |
126 | input_report_key(touchkey->input_dev, KEY_BACK, 0); | |
127 | } else { | |
128 | input_report_key(touchkey->input_dev, key, 1); | |
129 | } | |
130 | ||
131 | input_sync(touchkey->input_dev); | |
132 | ||
133 | out: | |
134 | return IRQ_HANDLED; | |
135 | } | |
136 | ||
137 | static int tm2_touchkey_probe(struct i2c_client *client, | |
138 | const struct i2c_device_id *id) | |
139 | { | |
140 | struct tm2_touchkey_data *touchkey; | |
141 | int error; | |
142 | ||
143 | if (!i2c_check_functionality(client->adapter, | |
144 | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA)) { | |
145 | dev_err(&client->dev, "incompatible I2C adapter\n"); | |
146 | return -EIO; | |
147 | } | |
148 | ||
149 | touchkey = devm_kzalloc(&client->dev, sizeof(*touchkey), GFP_KERNEL); | |
150 | if (!touchkey) | |
151 | return -ENOMEM; | |
152 | ||
153 | touchkey->client = client; | |
154 | i2c_set_clientdata(client, touchkey); | |
155 | ||
156 | touchkey->regulators[0].supply = "vcc"; | |
157 | touchkey->regulators[1].supply = "vdd"; | |
158 | error = devm_regulator_bulk_get(&client->dev, | |
159 | ARRAY_SIZE(touchkey->regulators), | |
160 | touchkey->regulators); | |
161 | if (error) { | |
162 | dev_err(&client->dev, "failed to get regulators: %d\n", error); | |
163 | return error; | |
164 | } | |
165 | ||
166 | /* Save VDD for easy access */ | |
167 | touchkey->vdd = touchkey->regulators[1].consumer; | |
168 | ||
169 | error = tm2_touchkey_power_enable(touchkey); | |
170 | if (error) { | |
171 | dev_err(&client->dev, "failed to power up device: %d\n", error); | |
172 | return error; | |
173 | } | |
174 | ||
175 | error = devm_add_action_or_reset(&client->dev, | |
176 | tm2_touchkey_power_disable, touchkey); | |
177 | if (error) { | |
178 | dev_err(&client->dev, | |
179 | "failed to install poweroff handler: %d\n", error); | |
180 | return error; | |
181 | } | |
182 | ||
183 | /* input device */ | |
184 | touchkey->input_dev = devm_input_allocate_device(&client->dev); | |
185 | if (!touchkey->input_dev) { | |
186 | dev_err(&client->dev, "failed to allocate input device\n"); | |
187 | return -ENOMEM; | |
188 | } | |
189 | ||
190 | touchkey->input_dev->name = TM2_TOUCHKEY_DEV_NAME; | |
191 | touchkey->input_dev->id.bustype = BUS_I2C; | |
192 | ||
193 | input_set_capability(touchkey->input_dev, EV_KEY, KEY_PHONE); | |
194 | input_set_capability(touchkey->input_dev, EV_KEY, KEY_BACK); | |
195 | ||
72d1f234 JL |
196 | error = input_register_device(touchkey->input_dev); |
197 | if (error) { | |
198 | dev_err(&client->dev, | |
199 | "failed to register input device: %d\n", error); | |
200 | return error; | |
201 | } | |
202 | ||
203 | error = devm_request_threaded_irq(&client->dev, client->irq, | |
204 | NULL, tm2_touchkey_irq_handler, | |
205 | IRQF_ONESHOT, | |
206 | TM2_TOUCHKEY_DEV_NAME, touchkey); | |
207 | if (error) { | |
208 | dev_err(&client->dev, | |
209 | "failed to request threaded irq: %d\n", error); | |
210 | return error; | |
211 | } | |
212 | ||
213 | /* led device */ | |
214 | touchkey->led_dev.name = TM2_TOUCHKEY_DEV_NAME; | |
215 | touchkey->led_dev.brightness = LED_FULL; | |
c4beedb8 | 216 | touchkey->led_dev.max_brightness = LED_ON; |
72d1f234 JL |
217 | touchkey->led_dev.brightness_set = tm2_touchkey_led_brightness_set; |
218 | ||
219 | error = devm_led_classdev_register(&client->dev, &touchkey->led_dev); | |
220 | if (error) { | |
221 | dev_err(&client->dev, | |
222 | "failed to register touchkey led: %d\n", error); | |
223 | return error; | |
224 | } | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
229 | static int __maybe_unused tm2_touchkey_suspend(struct device *dev) | |
230 | { | |
231 | struct i2c_client *client = to_i2c_client(dev); | |
232 | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); | |
233 | ||
234 | disable_irq(client->irq); | |
235 | tm2_touchkey_power_disable(touchkey); | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static int __maybe_unused tm2_touchkey_resume(struct device *dev) | |
241 | { | |
242 | struct i2c_client *client = to_i2c_client(dev); | |
243 | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); | |
244 | int ret; | |
245 | ||
246 | enable_irq(client->irq); | |
247 | ||
248 | ret = tm2_touchkey_power_enable(touchkey); | |
249 | if (ret) | |
250 | dev_err(dev, "failed to enable power: %d\n", ret); | |
251 | ||
252 | return ret; | |
253 | } | |
254 | ||
255 | static SIMPLE_DEV_PM_OPS(tm2_touchkey_pm_ops, | |
256 | tm2_touchkey_suspend, tm2_touchkey_resume); | |
257 | ||
258 | static const struct i2c_device_id tm2_touchkey_id_table[] = { | |
259 | { TM2_TOUCHKEY_DEV_NAME, 0 }, | |
260 | { }, | |
261 | }; | |
262 | MODULE_DEVICE_TABLE(i2c, tm2_touchkey_id_table); | |
263 | ||
264 | static const struct of_device_id tm2_touchkey_of_match[] = { | |
265 | { .compatible = "cypress,tm2-touchkey", }, | |
266 | { }, | |
267 | }; | |
268 | MODULE_DEVICE_TABLE(of, tm2_touchkey_of_match); | |
269 | ||
270 | static struct i2c_driver tm2_touchkey_driver = { | |
271 | .driver = { | |
272 | .name = TM2_TOUCHKEY_DEV_NAME, | |
273 | .pm = &tm2_touchkey_pm_ops, | |
274 | .of_match_table = of_match_ptr(tm2_touchkey_of_match), | |
275 | }, | |
276 | .probe = tm2_touchkey_probe, | |
277 | .id_table = tm2_touchkey_id_table, | |
278 | }; | |
279 | module_i2c_driver(tm2_touchkey_driver); | |
280 | ||
281 | MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); | |
282 | MODULE_AUTHOR("Jaechul Lee <jcsing.lee@samsung.com>"); | |
283 | MODULE_DESCRIPTION("Samsung touchkey driver"); | |
284 | MODULE_LICENSE("GPL v2"); |