1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * TM2 touchkey device driver |
4 | * |
5 | * Copyright 2005 Phil Blundell |
6 | * Copyright 2016 Samsung Electronics Co., Ltd. |
7 | * |
8 | * Author: Beomho Seo <beomho.seo@samsung.com> |
9 | * Author: Jaechul Lee <jcsing.lee@samsung.com> |
10 | */ |
11 | |
12 | #include <linux/bitops.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/device.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/input.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/irq.h> |
19 | #include <linux/leds.h> |
20 | #include <linux/module.h> |
21 | #include <linux/of.h> |
22 | #include <linux/pm.h> |
23 | #include <linux/regulator/consumer.h> |
24 | |
25 | #define TM2_TOUCHKEY_DEV_NAME "tm2-touchkey" |
26 | |
27 | #define ARIES_TOUCHKEY_CMD_LED_ON 0x1 |
28 | #define ARIES_TOUCHKEY_CMD_LED_OFF 0x2 |
29 | #define TM2_TOUCHKEY_CMD_LED_ON 0x10 |
30 | #define TM2_TOUCHKEY_CMD_LED_OFF 0x20 |
31 | #define TM2_TOUCHKEY_BIT_PRESS_EV BIT(3) |
32 | #define TM2_TOUCHKEY_BIT_KEYCODE GENMASK(2, 0) |
33 | #define TM2_TOUCHKEY_LED_VOLTAGE_MIN 2500000 |
34 | #define TM2_TOUCHKEY_LED_VOLTAGE_MAX 3300000 |
35 | |
36 | struct touchkey_variant { |
37 | u8 keycode_reg; |
38 | u8 base_reg; |
39 | u8 cmd_led_on; |
40 | u8 cmd_led_off; |
41 | bool no_reg; |
42 | bool fixed_regulator; |
43 | }; |
44 | |
45 | struct tm2_touchkey_data { |
46 | struct i2c_client *client; |
47 | struct input_dev *input_dev; |
48 | struct led_classdev led_dev; |
49 | struct regulator *vdd; |
50 | struct regulator_bulk_data regulators[3]; |
51 | const struct touchkey_variant *variant; |
52 | u32 keycodes[4]; |
53 | int num_keycodes; |
54 | }; |
55 | |
56 | static const struct touchkey_variant tm2_touchkey_variant = { |
57 | .keycode_reg = 0x03, |
58 | .base_reg = 0x00, |
59 | .cmd_led_on = TM2_TOUCHKEY_CMD_LED_ON, |
60 | .cmd_led_off = TM2_TOUCHKEY_CMD_LED_OFF, |
61 | }; |
62 | |
63 | static const struct touchkey_variant midas_touchkey_variant = { |
64 | .keycode_reg = 0x00, |
65 | .base_reg = 0x00, |
66 | .cmd_led_on = TM2_TOUCHKEY_CMD_LED_ON, |
67 | .cmd_led_off = TM2_TOUCHKEY_CMD_LED_OFF, |
68 | }; |
69 | |
70 | static struct touchkey_variant aries_touchkey_variant = { |
71 | .no_reg = true, |
72 | .fixed_regulator = true, |
73 | .cmd_led_on = ARIES_TOUCHKEY_CMD_LED_ON, |
74 | .cmd_led_off = ARIES_TOUCHKEY_CMD_LED_OFF, |
75 | }; |
76 | |
77 | static const struct touchkey_variant tc360_touchkey_variant = { |
78 | .keycode_reg = 0x00, |
79 | .base_reg = 0x00, |
80 | .fixed_regulator = true, |
81 | .cmd_led_on = TM2_TOUCHKEY_CMD_LED_ON, |
82 | .cmd_led_off = TM2_TOUCHKEY_CMD_LED_OFF, |
83 | }; |
84 | |
85 | static int tm2_touchkey_led_brightness_set(struct led_classdev *led_dev, |
86 | enum led_brightness brightness) |
87 | { |
88 | struct tm2_touchkey_data *touchkey = |
89 | container_of(led_dev, struct tm2_touchkey_data, led_dev); |
90 | u32 volt; |
91 | u8 data; |
92 | |
93 | if (brightness == LED_OFF) { |
94 | volt = TM2_TOUCHKEY_LED_VOLTAGE_MIN; |
95 | data = touchkey->variant->cmd_led_off; |
96 | } else { |
97 | volt = TM2_TOUCHKEY_LED_VOLTAGE_MAX; |
98 | data = touchkey->variant->cmd_led_on; |
99 | } |
100 | |
101 | if (!touchkey->variant->fixed_regulator) |
102 | regulator_set_voltage(regulator: touchkey->vdd, min_uV: volt, max_uV: volt); |
103 | |
104 | return touchkey->variant->no_reg ? |
105 | i2c_smbus_write_byte(client: touchkey->client, value: data) : |
106 | i2c_smbus_write_byte_data(client: touchkey->client, |
107 | command: touchkey->variant->base_reg, value: data); |
108 | } |
109 | |
110 | static int tm2_touchkey_power_enable(struct tm2_touchkey_data *touchkey) |
111 | { |
112 | int error; |
113 | |
114 | error = regulator_bulk_enable(ARRAY_SIZE(touchkey->regulators), |
115 | consumers: touchkey->regulators); |
116 | if (error) |
117 | return error; |
118 | |
119 | /* waiting for device initialization, at least 150ms */ |
120 | msleep(msecs: 150); |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static void tm2_touchkey_power_disable(void *data) |
126 | { |
127 | struct tm2_touchkey_data *touchkey = data; |
128 | |
129 | regulator_bulk_disable(ARRAY_SIZE(touchkey->regulators), |
130 | consumers: touchkey->regulators); |
131 | } |
132 | |
133 | static irqreturn_t tm2_touchkey_irq_handler(int irq, void *devid) |
134 | { |
135 | struct tm2_touchkey_data *touchkey = devid; |
136 | int data; |
137 | int index; |
138 | int i; |
139 | |
140 | if (touchkey->variant->no_reg) |
141 | data = i2c_smbus_read_byte(client: touchkey->client); |
142 | else |
143 | data = i2c_smbus_read_byte_data(client: touchkey->client, |
144 | command: touchkey->variant->keycode_reg); |
145 | if (data < 0) { |
146 | dev_err(&touchkey->client->dev, |
147 | "failed to read i2c data: %d\n" , data); |
148 | goto out; |
149 | } |
150 | |
151 | index = (data & TM2_TOUCHKEY_BIT_KEYCODE) - 1; |
152 | if (index < 0 || index >= touchkey->num_keycodes) { |
153 | dev_warn(&touchkey->client->dev, |
154 | "invalid keycode index %d\n" , index); |
155 | goto out; |
156 | } |
157 | |
158 | input_event(dev: touchkey->input_dev, EV_MSC, MSC_SCAN, value: index); |
159 | |
160 | if (data & TM2_TOUCHKEY_BIT_PRESS_EV) { |
161 | for (i = 0; i < touchkey->num_keycodes; i++) |
162 | input_report_key(dev: touchkey->input_dev, |
163 | code: touchkey->keycodes[i], value: 0); |
164 | } else { |
165 | input_report_key(dev: touchkey->input_dev, |
166 | code: touchkey->keycodes[index], value: 1); |
167 | } |
168 | |
169 | input_sync(dev: touchkey->input_dev); |
170 | |
171 | out: |
172 | if (touchkey->variant->fixed_regulator && |
173 | data & TM2_TOUCHKEY_BIT_PRESS_EV) { |
174 | /* touch turns backlight on, so make sure we're in sync */ |
175 | if (touchkey->led_dev.brightness == LED_OFF) |
176 | tm2_touchkey_led_brightness_set(led_dev: &touchkey->led_dev, |
177 | brightness: LED_OFF); |
178 | } |
179 | |
180 | return IRQ_HANDLED; |
181 | } |
182 | |
183 | static int tm2_touchkey_probe(struct i2c_client *client) |
184 | { |
185 | struct device_node *np = client->dev.of_node; |
186 | struct tm2_touchkey_data *touchkey; |
187 | int error; |
188 | int i; |
189 | |
190 | if (!i2c_check_functionality(adap: client->adapter, |
191 | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA)) { |
192 | dev_err(&client->dev, "incompatible I2C adapter\n" ); |
193 | return -EIO; |
194 | } |
195 | |
196 | touchkey = devm_kzalloc(dev: &client->dev, size: sizeof(*touchkey), GFP_KERNEL); |
197 | if (!touchkey) |
198 | return -ENOMEM; |
199 | |
200 | touchkey->client = client; |
201 | i2c_set_clientdata(client, data: touchkey); |
202 | |
203 | touchkey->variant = of_device_get_match_data(dev: &client->dev); |
204 | |
205 | touchkey->regulators[0].supply = "vcc" ; |
206 | touchkey->regulators[1].supply = "vdd" ; |
207 | touchkey->regulators[2].supply = "vddio" ; |
208 | error = devm_regulator_bulk_get(dev: &client->dev, |
209 | ARRAY_SIZE(touchkey->regulators), |
210 | consumers: touchkey->regulators); |
211 | if (error) { |
212 | dev_err(&client->dev, "failed to get regulators: %d\n" , error); |
213 | return error; |
214 | } |
215 | |
216 | /* Save VDD for easy access */ |
217 | touchkey->vdd = touchkey->regulators[1].consumer; |
218 | |
219 | touchkey->num_keycodes = of_property_read_variable_u32_array(np, |
220 | propname: "linux,keycodes" , out_values: touchkey->keycodes, sz_min: 0, |
221 | ARRAY_SIZE(touchkey->keycodes)); |
222 | if (touchkey->num_keycodes <= 0) { |
223 | /* default keycodes */ |
224 | touchkey->keycodes[0] = KEY_PHONE; |
225 | touchkey->keycodes[1] = KEY_BACK; |
226 | touchkey->num_keycodes = 2; |
227 | } |
228 | |
229 | error = tm2_touchkey_power_enable(touchkey); |
230 | if (error) { |
231 | dev_err(&client->dev, "failed to power up device: %d\n" , error); |
232 | return error; |
233 | } |
234 | |
235 | error = devm_add_action_or_reset(&client->dev, |
236 | tm2_touchkey_power_disable, touchkey); |
237 | if (error) { |
238 | dev_err(&client->dev, |
239 | "failed to install poweroff handler: %d\n" , error); |
240 | return error; |
241 | } |
242 | |
243 | /* input device */ |
244 | touchkey->input_dev = devm_input_allocate_device(&client->dev); |
245 | if (!touchkey->input_dev) { |
246 | dev_err(&client->dev, "failed to allocate input device\n" ); |
247 | return -ENOMEM; |
248 | } |
249 | |
250 | touchkey->input_dev->name = TM2_TOUCHKEY_DEV_NAME; |
251 | touchkey->input_dev->id.bustype = BUS_I2C; |
252 | |
253 | touchkey->input_dev->keycode = touchkey->keycodes; |
254 | touchkey->input_dev->keycodemax = touchkey->num_keycodes; |
255 | touchkey->input_dev->keycodesize = sizeof(touchkey->keycodes[0]); |
256 | |
257 | input_set_capability(dev: touchkey->input_dev, EV_MSC, MSC_SCAN); |
258 | for (i = 0; i < touchkey->num_keycodes; i++) |
259 | input_set_capability(dev: touchkey->input_dev, EV_KEY, |
260 | code: touchkey->keycodes[i]); |
261 | |
262 | error = input_register_device(touchkey->input_dev); |
263 | if (error) { |
264 | dev_err(&client->dev, |
265 | "failed to register input device: %d\n" , error); |
266 | return error; |
267 | } |
268 | |
269 | error = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, |
270 | NULL, thread_fn: tm2_touchkey_irq_handler, |
271 | IRQF_ONESHOT, |
272 | TM2_TOUCHKEY_DEV_NAME, dev_id: touchkey); |
273 | if (error) { |
274 | dev_err(&client->dev, |
275 | "failed to request threaded irq: %d\n" , error); |
276 | return error; |
277 | } |
278 | |
279 | /* led device */ |
280 | touchkey->led_dev.name = TM2_TOUCHKEY_DEV_NAME; |
281 | touchkey->led_dev.brightness = LED_ON; |
282 | touchkey->led_dev.max_brightness = LED_ON; |
283 | touchkey->led_dev.brightness_set_blocking = |
284 | tm2_touchkey_led_brightness_set; |
285 | |
286 | error = devm_led_classdev_register(parent: &client->dev, led_cdev: &touchkey->led_dev); |
287 | if (error) { |
288 | dev_err(&client->dev, |
289 | "failed to register touchkey led: %d\n" , error); |
290 | return error; |
291 | } |
292 | |
293 | if (touchkey->variant->fixed_regulator) |
294 | tm2_touchkey_led_brightness_set(led_dev: &touchkey->led_dev, brightness: LED_ON); |
295 | |
296 | return 0; |
297 | } |
298 | |
299 | static int tm2_touchkey_suspend(struct device *dev) |
300 | { |
301 | struct i2c_client *client = to_i2c_client(dev); |
302 | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); |
303 | |
304 | disable_irq(irq: client->irq); |
305 | tm2_touchkey_power_disable(data: touchkey); |
306 | |
307 | return 0; |
308 | } |
309 | |
310 | static int tm2_touchkey_resume(struct device *dev) |
311 | { |
312 | struct i2c_client *client = to_i2c_client(dev); |
313 | struct tm2_touchkey_data *touchkey = i2c_get_clientdata(client); |
314 | int ret; |
315 | |
316 | enable_irq(irq: client->irq); |
317 | |
318 | ret = tm2_touchkey_power_enable(touchkey); |
319 | if (ret) |
320 | dev_err(dev, "failed to enable power: %d\n" , ret); |
321 | |
322 | return ret; |
323 | } |
324 | |
325 | static DEFINE_SIMPLE_DEV_PM_OPS(tm2_touchkey_pm_ops, |
326 | tm2_touchkey_suspend, tm2_touchkey_resume); |
327 | |
328 | static const struct i2c_device_id tm2_touchkey_id_table[] = { |
329 | { TM2_TOUCHKEY_DEV_NAME, 0 }, |
330 | { }, |
331 | }; |
332 | MODULE_DEVICE_TABLE(i2c, tm2_touchkey_id_table); |
333 | |
334 | static const struct of_device_id tm2_touchkey_of_match[] = { |
335 | { |
336 | .compatible = "cypress,tm2-touchkey" , |
337 | .data = &tm2_touchkey_variant, |
338 | }, { |
339 | .compatible = "cypress,midas-touchkey" , |
340 | .data = &midas_touchkey_variant, |
341 | }, { |
342 | .compatible = "cypress,aries-touchkey" , |
343 | .data = &aries_touchkey_variant, |
344 | }, { |
345 | .compatible = "coreriver,tc360-touchkey" , |
346 | .data = &tc360_touchkey_variant, |
347 | }, |
348 | { }, |
349 | }; |
350 | MODULE_DEVICE_TABLE(of, tm2_touchkey_of_match); |
351 | |
352 | static struct i2c_driver tm2_touchkey_driver = { |
353 | .driver = { |
354 | .name = TM2_TOUCHKEY_DEV_NAME, |
355 | .pm = pm_sleep_ptr(&tm2_touchkey_pm_ops), |
356 | .of_match_table = tm2_touchkey_of_match, |
357 | }, |
358 | .probe = tm2_touchkey_probe, |
359 | .id_table = tm2_touchkey_id_table, |
360 | }; |
361 | module_i2c_driver(tm2_touchkey_driver); |
362 | |
363 | MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>" ); |
364 | MODULE_AUTHOR("Jaechul Lee <jcsing.lee@samsung.com>" ); |
365 | MODULE_DESCRIPTION("Samsung touchkey driver" ); |
366 | MODULE_LICENSE("GPL v2" ); |
367 | |