diff options
author | Andi Shyti <andi.shyti@samsung.com> | 2017-06-05 18:29:18 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2017-06-05 18:35:34 -0400 |
commit | 78bcac7b2ae1e4f6e96c68ff353c140669ea231c (patch) | |
tree | 3d9baece29da1fe05c16b6996a57958dd3547497 | |
parent | 131b3de7016b73fca1aba8ffb528217ac95b2505 (diff) |
Input: add support for the STMicroelectronics FingerTip touchscreen
The stmfts (ST-Microelectronics FingerTip S) touchscreen device is a
capacitive multi-touch controller mainly for mobile use. It's connected
through i2c bus at the address 0x49 and it interfaces with userspace
through input event interface.
At the current state it provides a touchscreen multitouch functionality up
to 10 fingers. Each finger is enumerated with a distinctive id (from 0 to
9).
If enabled the device can support single "touch" hovering, by providing
three coordinates, x, y and distance.
It is possible to select the touchkey functionality which provides a basic
two keys interface for "home" and "back" menu, typical in mobile phones.
Signed-off-by: Andi Shyti <andi.shyti@samsung.com>
Reviewed-by: Javier Martinez Canillas <javier@osg.samsung.com>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
-rw-r--r-- | Documentation/devicetree/bindings/input/touchscreen/st,stmfts.txt | 43 | ||||
-rw-r--r-- | drivers/input/touchscreen/Kconfig | 11 | ||||
-rw-r--r-- | drivers/input/touchscreen/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/touchscreen/stmfts.c | 822 |
4 files changed, 877 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.txt b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.txt new file mode 100644 index 000000000000..9683595cd0f5 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.txt | |||
@@ -0,0 +1,43 @@ | |||
1 | * ST-Microelectronics FingerTip touchscreen controller | ||
2 | |||
3 | The ST-Microelectronics FingerTip device provides a basic touchscreen | ||
4 | functionality. Along with it the user can enable the touchkey which can work as | ||
5 | a basic HOME and BACK key for phones. | ||
6 | |||
7 | The driver supports also hovering as an absolute single touch event with x, y, z | ||
8 | coordinates. | ||
9 | |||
10 | Required properties: | ||
11 | - compatible : must be "st,stmfts" | ||
12 | - reg : I2C slave address, (e.g. 0x49) | ||
13 | - interrupt-parent : the phandle to the interrupt controller which provides | ||
14 | the interrupt | ||
15 | - interrupts : interrupt specification | ||
16 | - avdd-supply : analogic power supply | ||
17 | - vdd-supply : power supply | ||
18 | - touchscreen-size-x : see touchscreen.txt | ||
19 | - touchscreen-size-y : see touchscreen.txt | ||
20 | |||
21 | Optional properties: | ||
22 | - touch-key-connected : specifies whether the touchkey feature is connected | ||
23 | - ledvdd-supply : power supply to the touch key leds | ||
24 | |||
25 | Example: | ||
26 | |||
27 | i2c@00000000 { | ||
28 | |||
29 | /* ... */ | ||
30 | |||
31 | touchscreen@49 { | ||
32 | compatible = "st,stmfts"; | ||
33 | reg = <0x49>; | ||
34 | interrupt-parent = <&gpa1>; | ||
35 | interrupts = <1 IRQ_TYPE_NONE>; | ||
36 | touchscreen-size-x = <1599>; | ||
37 | touchscreen-size-y = <2559>; | ||
38 | touch-key-connected; | ||
39 | avdd-supply = <&ldo30_reg>; | ||
40 | vdd-supply = <&ldo31_reg>; | ||
41 | ledvdd-supply = <&ldo33_reg>; | ||
42 | }; | ||
43 | }; | ||
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index cf26ca49ae6d..64b30fe273fd 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig | |||
@@ -1114,6 +1114,17 @@ config TOUCHSCREEN_ST1232 | |||
1114 | To compile this driver as a module, choose M here: the | 1114 | To compile this driver as a module, choose M here: the |
1115 | module will be called st1232_ts. | 1115 | module will be called st1232_ts. |
1116 | 1116 | ||
1117 | config TOUCHSCREEN_STMFTS | ||
1118 | tristate "STMicroelectronics STMFTS touchscreen" | ||
1119 | depends on I2C | ||
1120 | depends on LEDS_CLASS | ||
1121 | help | ||
1122 | Say Y here if you want support for STMicroelectronics | ||
1123 | STMFTS touchscreen. | ||
1124 | |||
1125 | To compile this driver as a module, choose M here: the | ||
1126 | module will be called stmfts. | ||
1127 | |||
1117 | config TOUCHSCREEN_STMPE | 1128 | config TOUCHSCREEN_STMPE |
1118 | tristate "STMicroelectronics STMPE touchscreens" | 1129 | tristate "STMicroelectronics STMPE touchscreens" |
1119 | depends on MFD_STMPE | 1130 | depends on MFD_STMPE |
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 18e476948e44..6badce87037b 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile | |||
@@ -67,6 +67,7 @@ obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o | |||
67 | obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o | 67 | obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o |
68 | obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o | 68 | obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o |
69 | obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o | 69 | obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o |
70 | obj-$(CONFIG_TOUCHSCREEN_STMFTS) += stmfts.o | ||
70 | obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o | 71 | obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o |
71 | obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o | 72 | obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o |
72 | obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o | 73 | obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o |
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c new file mode 100644 index 000000000000..802d0e82d034 --- /dev/null +++ b/drivers/input/touchscreen/stmfts.c | |||
@@ -0,0 +1,822 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2017 Samsung Electronics Co., Ltd. | ||
3 | * Author: Andi Shyti <andi.shyti@samsung.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * STMicroelectronics FTS Touchscreen device driver | ||
10 | */ | ||
11 | |||
12 | #include <linux/delay.h> | ||
13 | #include <linux/i2c.h> | ||
14 | #include <linux/input/mt.h> | ||
15 | #include <linux/input/touchscreen.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/irq.h> | ||
18 | #include <linux/leds.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/pm_runtime.h> | ||
21 | #include <linux/regulator/consumer.h> | ||
22 | |||
23 | /* I2C commands */ | ||
24 | #define STMFTS_READ_INFO 0x80 | ||
25 | #define STMFTS_READ_STATUS 0x84 | ||
26 | #define STMFTS_READ_ONE_EVENT 0x85 | ||
27 | #define STMFTS_READ_ALL_EVENT 0x86 | ||
28 | #define STMFTS_LATEST_EVENT 0x87 | ||
29 | #define STMFTS_SLEEP_IN 0x90 | ||
30 | #define STMFTS_SLEEP_OUT 0x91 | ||
31 | #define STMFTS_MS_MT_SENSE_OFF 0x92 | ||
32 | #define STMFTS_MS_MT_SENSE_ON 0x93 | ||
33 | #define STMFTS_SS_HOVER_SENSE_OFF 0x94 | ||
34 | #define STMFTS_SS_HOVER_SENSE_ON 0x95 | ||
35 | #define STMFTS_MS_KEY_SENSE_OFF 0x9a | ||
36 | #define STMFTS_MS_KEY_SENSE_ON 0x9b | ||
37 | #define STMFTS_SYSTEM_RESET 0xa0 | ||
38 | #define STMFTS_CLEAR_EVENT_STACK 0xa1 | ||
39 | #define STMFTS_FULL_FORCE_CALIBRATION 0xa2 | ||
40 | #define STMFTS_MS_CX_TUNING 0xa3 | ||
41 | #define STMFTS_SS_CX_TUNING 0xa4 | ||
42 | |||
43 | /* events */ | ||
44 | #define STMFTS_EV_NO_EVENT 0x00 | ||
45 | #define STMFTS_EV_MULTI_TOUCH_DETECTED 0x02 | ||
46 | #define STMFTS_EV_MULTI_TOUCH_ENTER 0x03 | ||
47 | #define STMFTS_EV_MULTI_TOUCH_LEAVE 0x04 | ||
48 | #define STMFTS_EV_MULTI_TOUCH_MOTION 0x05 | ||
49 | #define STMFTS_EV_HOVER_ENTER 0x07 | ||
50 | #define STMFTS_EV_HOVER_LEAVE 0x08 | ||
51 | #define STMFTS_EV_HOVER_MOTION 0x09 | ||
52 | #define STMFTS_EV_KEY_STATUS 0x0e | ||
53 | #define STMFTS_EV_ERROR 0x0f | ||
54 | #define STMFTS_EV_CONTROLLER_READY 0x10 | ||
55 | #define STMFTS_EV_SLEEP_OUT_CONTROLLER_READY 0x11 | ||
56 | #define STMFTS_EV_STATUS 0x16 | ||
57 | #define STMFTS_EV_DEBUG 0xdb | ||
58 | |||
59 | /* multi touch related event masks */ | ||
60 | #define STMFTS_MASK_EVENT_ID 0x0f | ||
61 | #define STMFTS_MASK_TOUCH_ID 0xf0 | ||
62 | #define STMFTS_MASK_LEFT_EVENT 0x0f | ||
63 | #define STMFTS_MASK_X_MSB 0x0f | ||
64 | #define STMFTS_MASK_Y_LSB 0xf0 | ||
65 | |||
66 | /* key related event masks */ | ||
67 | #define STMFTS_MASK_KEY_NO_TOUCH 0x00 | ||
68 | #define STMFTS_MASK_KEY_MENU 0x01 | ||
69 | #define STMFTS_MASK_KEY_BACK 0x02 | ||
70 | |||
71 | #define STMFTS_EVENT_SIZE 8 | ||
72 | #define STMFTS_STACK_DEPTH 32 | ||
73 | #define STMFTS_DATA_MAX_SIZE (STMFTS_EVENT_SIZE * STMFTS_STACK_DEPTH) | ||
74 | #define STMFTS_MAX_FINGERS 10 | ||
75 | #define STMFTS_DEV_NAME "stmfts" | ||
76 | |||
77 | enum stmfts_regulators { | ||
78 | STMFTS_REGULATOR_VDD, | ||
79 | STMFTS_REGULATOR_AVDD, | ||
80 | }; | ||
81 | |||
82 | struct stmfts_data { | ||
83 | struct i2c_client *client; | ||
84 | struct input_dev *input; | ||
85 | struct led_classdev led_cdev; | ||
86 | struct mutex mutex; | ||
87 | |||
88 | struct touchscreen_properties prop; | ||
89 | |||
90 | struct regulator_bulk_data regulators[2]; | ||
91 | |||
92 | /* | ||
93 | * Presence of ledvdd will be used also to check | ||
94 | * whether the LED is supported. | ||
95 | */ | ||
96 | struct regulator *ledvdd; | ||
97 | |||
98 | u16 chip_id; | ||
99 | u8 chip_ver; | ||
100 | u16 fw_ver; | ||
101 | u8 config_id; | ||
102 | u8 config_ver; | ||
103 | |||
104 | u8 data[STMFTS_DATA_MAX_SIZE]; | ||
105 | |||
106 | struct completion cmd_done; | ||
107 | |||
108 | bool use_key; | ||
109 | bool led_status; | ||
110 | bool hover_enabled; | ||
111 | bool running; | ||
112 | }; | ||
113 | |||
114 | static void stmfts_brightness_set(struct led_classdev *led_cdev, | ||
115 | enum led_brightness value) | ||
116 | { | ||
117 | struct stmfts_data *sdata = container_of(led_cdev, | ||
118 | struct stmfts_data, led_cdev); | ||
119 | int err; | ||
120 | |||
121 | if (value == sdata->led_status || !sdata->ledvdd) | ||
122 | return; | ||
123 | |||
124 | if (!value) { | ||
125 | regulator_disable(sdata->ledvdd); | ||
126 | } else { | ||
127 | err = regulator_enable(sdata->ledvdd); | ||
128 | if (err) | ||
129 | dev_warn(&sdata->client->dev, | ||
130 | "failed to disable ledvdd regulator: %d\n", | ||
131 | err); | ||
132 | } | ||
133 | |||
134 | sdata->led_status = value; | ||
135 | } | ||
136 | |||
137 | static enum led_brightness stmfts_brightness_get(struct led_classdev *led_cdev) | ||
138 | { | ||
139 | struct stmfts_data *sdata = container_of(led_cdev, | ||
140 | struct stmfts_data, led_cdev); | ||
141 | |||
142 | return !!regulator_is_enabled(sdata->ledvdd); | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * We can't simply use i2c_smbus_read_i2c_block_data because we | ||
147 | * need to read more than 255 bytes ( | ||
148 | */ | ||
149 | static int stmfts_read_events(struct stmfts_data *sdata) | ||
150 | { | ||
151 | u8 cmd = STMFTS_READ_ALL_EVENT; | ||
152 | struct i2c_msg msgs[2] = { | ||
153 | { | ||
154 | .addr = sdata->client->addr, | ||
155 | .len = 1, | ||
156 | .buf = &cmd, | ||
157 | }, | ||
158 | { | ||
159 | .addr = sdata->client->addr, | ||
160 | .flags = I2C_M_RD, | ||
161 | .len = STMFTS_DATA_MAX_SIZE, | ||
162 | .buf = sdata->data, | ||
163 | }, | ||
164 | }; | ||
165 | int ret; | ||
166 | |||
167 | ret = i2c_transfer(sdata->client->adapter, msgs, ARRAY_SIZE(msgs)); | ||
168 | if (ret < 0) | ||
169 | return ret; | ||
170 | |||
171 | return ret == ARRAY_SIZE(msgs) ? 0 : -EIO; | ||
172 | } | ||
173 | |||
174 | static void stmfts_report_contact_event(struct stmfts_data *sdata, | ||
175 | const u8 event[]) | ||
176 | { | ||
177 | u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4; | ||
178 | u16 x = event[1] | ((event[2] & STMFTS_MASK_X_MSB) << 8); | ||
179 | u16 y = (event[2] >> 4) | (event[3] << 4); | ||
180 | u8 maj = event[4]; | ||
181 | u8 min = event[5]; | ||
182 | u8 orientation = event[6]; | ||
183 | u8 area = event[7]; | ||
184 | |||
185 | input_mt_slot(sdata->input, slot_id); | ||
186 | |||
187 | input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, true); | ||
188 | input_report_abs(sdata->input, ABS_MT_POSITION_X, x); | ||
189 | input_report_abs(sdata->input, ABS_MT_POSITION_Y, y); | ||
190 | input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, maj); | ||
191 | input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min); | ||
192 | input_report_abs(sdata->input, ABS_MT_PRESSURE, area); | ||
193 | input_report_abs(sdata->input, ABS_MT_ORIENTATION, orientation); | ||
194 | |||
195 | input_sync(sdata->input); | ||
196 | } | ||
197 | |||
198 | static void stmfts_report_contact_release(struct stmfts_data *sdata, | ||
199 | const u8 event[]) | ||
200 | { | ||
201 | u8 slot_id = (event[0] & STMFTS_MASK_TOUCH_ID) >> 4; | ||
202 | |||
203 | input_mt_slot(sdata->input, slot_id); | ||
204 | input_mt_report_slot_state(sdata->input, MT_TOOL_FINGER, false); | ||
205 | |||
206 | input_sync(sdata->input); | ||
207 | } | ||
208 | |||
209 | static void stmfts_report_hover_event(struct stmfts_data *sdata, | ||
210 | const u8 event[]) | ||
211 | { | ||
212 | u16 x = (event[2] << 4) | (event[4] >> 4); | ||
213 | u16 y = (event[3] << 4) | (event[4] & STMFTS_MASK_Y_LSB); | ||
214 | u8 z = event[5]; | ||
215 | |||
216 | input_report_abs(sdata->input, ABS_X, x); | ||
217 | input_report_abs(sdata->input, ABS_Y, y); | ||
218 | input_report_abs(sdata->input, ABS_DISTANCE, z); | ||
219 | |||
220 | input_sync(sdata->input); | ||
221 | } | ||
222 | |||
223 | static void stmfts_report_key_event(struct stmfts_data *sdata, const u8 event[]) | ||
224 | { | ||
225 | switch (event[2]) { | ||
226 | case 0: | ||
227 | input_report_key(sdata->input, KEY_BACK, 0); | ||
228 | input_report_key(sdata->input, KEY_MENU, 0); | ||
229 | break; | ||
230 | |||
231 | case STMFTS_MASK_KEY_BACK: | ||
232 | input_report_key(sdata->input, KEY_BACK, 1); | ||
233 | break; | ||
234 | |||
235 | case STMFTS_MASK_KEY_MENU: | ||
236 | input_report_key(sdata->input, KEY_MENU, 1); | ||
237 | break; | ||
238 | |||
239 | default: | ||
240 | dev_warn(&sdata->client->dev, | ||
241 | "unknown key event: %#02x\n", event[2]); | ||
242 | break; | ||
243 | } | ||
244 | |||
245 | input_sync(sdata->input); | ||
246 | } | ||
247 | |||
248 | static void stmfts_parse_events(struct stmfts_data *sdata) | ||
249 | { | ||
250 | int i; | ||
251 | |||
252 | for (i = 0; i < STMFTS_STACK_DEPTH; i++) { | ||
253 | u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE]; | ||
254 | |||
255 | switch (event[0]) { | ||
256 | |||
257 | case STMFTS_EV_CONTROLLER_READY: | ||
258 | case STMFTS_EV_SLEEP_OUT_CONTROLLER_READY: | ||
259 | case STMFTS_EV_STATUS: | ||
260 | complete(&sdata->cmd_done); | ||
261 | /* fall through */ | ||
262 | |||
263 | case STMFTS_EV_NO_EVENT: | ||
264 | case STMFTS_EV_DEBUG: | ||
265 | return; | ||
266 | } | ||
267 | |||
268 | switch (event[0] & STMFTS_MASK_EVENT_ID) { | ||
269 | |||
270 | case STMFTS_EV_MULTI_TOUCH_ENTER: | ||
271 | case STMFTS_EV_MULTI_TOUCH_MOTION: | ||
272 | stmfts_report_contact_event(sdata, event); | ||
273 | break; | ||
274 | |||
275 | case STMFTS_EV_MULTI_TOUCH_LEAVE: | ||
276 | stmfts_report_contact_release(sdata, event); | ||
277 | break; | ||
278 | |||
279 | case STMFTS_EV_HOVER_ENTER: | ||
280 | case STMFTS_EV_HOVER_LEAVE: | ||
281 | case STMFTS_EV_HOVER_MOTION: | ||
282 | stmfts_report_hover_event(sdata, event); | ||
283 | break; | ||
284 | |||
285 | case STMFTS_EV_KEY_STATUS: | ||
286 | stmfts_report_key_event(sdata, event); | ||
287 | break; | ||
288 | |||
289 | case STMFTS_EV_ERROR: | ||
290 | dev_warn(&sdata->client->dev, | ||
291 | "error code: 0x%x%x%x%x%x%x", | ||
292 | event[6], event[5], event[4], | ||
293 | event[3], event[2], event[1]); | ||
294 | break; | ||
295 | |||
296 | default: | ||
297 | dev_err(&sdata->client->dev, | ||
298 | "unknown event %#02x\n", event[0]); | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | static irqreturn_t stmfts_irq_handler(int irq, void *dev) | ||
304 | { | ||
305 | struct stmfts_data *sdata = dev; | ||
306 | int err; | ||
307 | |||
308 | mutex_lock(&sdata->mutex); | ||
309 | |||
310 | err = stmfts_read_events(sdata); | ||
311 | if (unlikely(err)) | ||
312 | dev_err(&sdata->client->dev, | ||
313 | "failed to read events: %d\n", err); | ||
314 | else | ||
315 | stmfts_parse_events(sdata); | ||
316 | |||
317 | mutex_unlock(&sdata->mutex); | ||
318 | return IRQ_HANDLED; | ||
319 | } | ||
320 | |||
321 | static int stmfts_command(struct stmfts_data *sdata, const u8 cmd) | ||
322 | { | ||
323 | int err; | ||
324 | |||
325 | reinit_completion(&sdata->cmd_done); | ||
326 | |||
327 | err = i2c_smbus_write_byte(sdata->client, cmd); | ||
328 | if (err) | ||
329 | return err; | ||
330 | |||
331 | if (!wait_for_completion_timeout(&sdata->cmd_done, | ||
332 | msecs_to_jiffies(1000))) | ||
333 | return -ETIMEDOUT; | ||
334 | |||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | static int stmfts_input_open(struct input_dev *dev) | ||
339 | { | ||
340 | struct stmfts_data *sdata = input_get_drvdata(dev); | ||
341 | int err; | ||
342 | |||
343 | err = pm_runtime_get_sync(&sdata->client->dev); | ||
344 | if (err < 0) | ||
345 | return err; | ||
346 | |||
347 | err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_ON); | ||
348 | if (err) | ||
349 | return err; | ||
350 | |||
351 | mutex_lock(&sdata->mutex); | ||
352 | sdata->running = true; | ||
353 | |||
354 | if (sdata->hover_enabled) { | ||
355 | err = i2c_smbus_write_byte(sdata->client, | ||
356 | STMFTS_SS_HOVER_SENSE_ON); | ||
357 | if (err) | ||
358 | dev_warn(&sdata->client->dev, | ||
359 | "failed to enable hover\n"); | ||
360 | } | ||
361 | mutex_unlock(&sdata->mutex); | ||
362 | |||
363 | if (sdata->use_key) { | ||
364 | err = i2c_smbus_write_byte(sdata->client, | ||
365 | STMFTS_MS_KEY_SENSE_ON); | ||
366 | if (err) | ||
367 | /* I can still use only the touch screen */ | ||
368 | dev_warn(&sdata->client->dev, | ||
369 | "failed to enable touchkey\n"); | ||
370 | } | ||
371 | |||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | static void stmfts_input_close(struct input_dev *dev) | ||
376 | { | ||
377 | struct stmfts_data *sdata = input_get_drvdata(dev); | ||
378 | int err; | ||
379 | |||
380 | err = i2c_smbus_write_byte(sdata->client, STMFTS_MS_MT_SENSE_OFF); | ||
381 | if (err) | ||
382 | dev_warn(&sdata->client->dev, | ||
383 | "failed to disable touchscreen: %d\n", err); | ||
384 | |||
385 | mutex_lock(&sdata->mutex); | ||
386 | |||
387 | sdata->running = false; | ||
388 | |||
389 | if (sdata->hover_enabled) { | ||
390 | err = i2c_smbus_write_byte(sdata->client, | ||
391 | STMFTS_SS_HOVER_SENSE_OFF); | ||
392 | if (err) | ||
393 | dev_warn(&sdata->client->dev, | ||
394 | "failed to disable hover: %d\n", err); | ||
395 | } | ||
396 | mutex_unlock(&sdata->mutex); | ||
397 | |||
398 | if (sdata->use_key) { | ||
399 | err = i2c_smbus_write_byte(sdata->client, | ||
400 | STMFTS_MS_KEY_SENSE_OFF); | ||
401 | if (err) | ||
402 | dev_warn(&sdata->client->dev, | ||
403 | "failed to disable touchkey: %d\n", err); | ||
404 | } | ||
405 | |||
406 | pm_runtime_put_sync(&sdata->client->dev); | ||
407 | } | ||
408 | |||
409 | static ssize_t stmfts_sysfs_chip_id(struct device *dev, | ||
410 | struct device_attribute *attr, char *buf) | ||
411 | { | ||
412 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
413 | |||
414 | return sprintf(buf, "%#x\n", sdata->chip_id); | ||
415 | } | ||
416 | |||
417 | static ssize_t stmfts_sysfs_chip_version(struct device *dev, | ||
418 | struct device_attribute *attr, char *buf) | ||
419 | { | ||
420 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
421 | |||
422 | return sprintf(buf, "%u\n", sdata->chip_ver); | ||
423 | } | ||
424 | |||
425 | static ssize_t stmfts_sysfs_fw_ver(struct device *dev, | ||
426 | struct device_attribute *attr, char *buf) | ||
427 | { | ||
428 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
429 | |||
430 | return sprintf(buf, "%u\n", sdata->fw_ver); | ||
431 | } | ||
432 | |||
433 | static ssize_t stmfts_sysfs_config_id(struct device *dev, | ||
434 | struct device_attribute *attr, char *buf) | ||
435 | { | ||
436 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
437 | |||
438 | return sprintf(buf, "%#x\n", sdata->config_id); | ||
439 | } | ||
440 | |||
441 | static ssize_t stmfts_sysfs_config_version(struct device *dev, | ||
442 | struct device_attribute *attr, char *buf) | ||
443 | { | ||
444 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
445 | |||
446 | return sprintf(buf, "%u\n", sdata->config_ver); | ||
447 | } | ||
448 | |||
449 | static ssize_t stmfts_sysfs_read_status(struct device *dev, | ||
450 | struct device_attribute *attr, char *buf) | ||
451 | { | ||
452 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
453 | u8 status[4]; | ||
454 | int err; | ||
455 | |||
456 | err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_STATUS, | ||
457 | sizeof(status), status); | ||
458 | if (err) | ||
459 | return err; | ||
460 | |||
461 | return sprintf(buf, "%#02x\n", status[0]); | ||
462 | } | ||
463 | |||
464 | static ssize_t stmfts_sysfs_hover_enable_read(struct device *dev, | ||
465 | struct device_attribute *attr, char *buf) | ||
466 | { | ||
467 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
468 | |||
469 | return sprintf(buf, "%u\n", sdata->hover_enabled); | ||
470 | } | ||
471 | |||
472 | static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev, | ||
473 | struct device_attribute *attr, | ||
474 | const char *buf, size_t len) | ||
475 | { | ||
476 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
477 | unsigned long value; | ||
478 | int err = 0; | ||
479 | |||
480 | if (kstrtoul(buf, 0, &value)) | ||
481 | return -EINVAL; | ||
482 | |||
483 | mutex_lock(&sdata->mutex); | ||
484 | |||
485 | if (value & sdata->hover_enabled) | ||
486 | goto out; | ||
487 | |||
488 | if (sdata->running) | ||
489 | err = i2c_smbus_write_byte(sdata->client, | ||
490 | value ? STMFTS_SS_HOVER_SENSE_ON : | ||
491 | STMFTS_SS_HOVER_SENSE_OFF); | ||
492 | |||
493 | if (!err) | ||
494 | sdata->hover_enabled = !!value; | ||
495 | |||
496 | out: | ||
497 | mutex_unlock(&sdata->mutex); | ||
498 | |||
499 | return len; | ||
500 | } | ||
501 | |||
502 | static DEVICE_ATTR(chip_id, 0444, stmfts_sysfs_chip_id, NULL); | ||
503 | static DEVICE_ATTR(chip_version, 0444, stmfts_sysfs_chip_version, NULL); | ||
504 | static DEVICE_ATTR(fw_ver, 0444, stmfts_sysfs_fw_ver, NULL); | ||
505 | static DEVICE_ATTR(config_id, 0444, stmfts_sysfs_config_id, NULL); | ||
506 | static DEVICE_ATTR(config_version, 0444, stmfts_sysfs_config_version, NULL); | ||
507 | static DEVICE_ATTR(status, 0444, stmfts_sysfs_read_status, NULL); | ||
508 | static DEVICE_ATTR(hover_enable, 0644, stmfts_sysfs_hover_enable_read, | ||
509 | stmfts_sysfs_hover_enable_write); | ||
510 | |||
511 | static struct attribute *stmfts_sysfs_attrs[] = { | ||
512 | &dev_attr_chip_id.attr, | ||
513 | &dev_attr_chip_version.attr, | ||
514 | &dev_attr_fw_ver.attr, | ||
515 | &dev_attr_config_id.attr, | ||
516 | &dev_attr_config_version.attr, | ||
517 | &dev_attr_status.attr, | ||
518 | &dev_attr_hover_enable.attr, | ||
519 | NULL | ||
520 | }; | ||
521 | |||
522 | static struct attribute_group stmfts_attribute_group = { | ||
523 | .attrs = stmfts_sysfs_attrs | ||
524 | }; | ||
525 | |||
526 | static int stmfts_power_on(struct stmfts_data *sdata) | ||
527 | { | ||
528 | int err; | ||
529 | u8 reg[8]; | ||
530 | |||
531 | err = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators), | ||
532 | sdata->regulators); | ||
533 | if (err) | ||
534 | return err; | ||
535 | |||
536 | /* | ||
537 | * The datasheet does not specify the power on time, but considering | ||
538 | * that the reset time is < 10ms, I sleep 20ms to be sure | ||
539 | */ | ||
540 | msleep(20); | ||
541 | |||
542 | err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_INFO, | ||
543 | sizeof(reg), reg); | ||
544 | if (err < 0) | ||
545 | return err; | ||
546 | if (err != sizeof(reg)) | ||
547 | return -EIO; | ||
548 | |||
549 | sdata->chip_id = be16_to_cpup((__be16 *)®[6]); | ||
550 | sdata->chip_ver = reg[0]; | ||
551 | sdata->fw_ver = be16_to_cpup((__be16 *)®[2]); | ||
552 | sdata->config_id = reg[4]; | ||
553 | sdata->config_ver = reg[5]; | ||
554 | |||
555 | enable_irq(sdata->client->irq); | ||
556 | |||
557 | msleep(50); | ||
558 | |||
559 | err = stmfts_command(sdata, STMFTS_SYSTEM_RESET); | ||
560 | if (err) | ||
561 | return err; | ||
562 | |||
563 | err = stmfts_command(sdata, STMFTS_SLEEP_OUT); | ||
564 | if (err) | ||
565 | return err; | ||
566 | |||
567 | /* optional tuning */ | ||
568 | err = stmfts_command(sdata, STMFTS_MS_CX_TUNING); | ||
569 | if (err) | ||
570 | dev_warn(&sdata->client->dev, | ||
571 | "failed to perform mutual auto tune: %d\n", err); | ||
572 | |||
573 | /* optional tuning */ | ||
574 | err = stmfts_command(sdata, STMFTS_SS_CX_TUNING); | ||
575 | if (err) | ||
576 | dev_warn(&sdata->client->dev, | ||
577 | "failed to perform self auto tune: %d\n", err); | ||
578 | |||
579 | err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION); | ||
580 | if (err) | ||
581 | return err; | ||
582 | |||
583 | /* | ||
584 | * At this point no one is using the touchscreen | ||
585 | * and I don't really care about the return value | ||
586 | */ | ||
587 | (void) i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN); | ||
588 | |||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | static void stmfts_power_off(void *data) | ||
593 | { | ||
594 | struct stmfts_data *sdata = data; | ||
595 | |||
596 | disable_irq(sdata->client->irq); | ||
597 | regulator_bulk_disable(ARRAY_SIZE(sdata->regulators), | ||
598 | sdata->regulators); | ||
599 | } | ||
600 | |||
601 | /* This function is void because I don't want to prevent using the touch key | ||
602 | * only because the LEDs don't get registered | ||
603 | */ | ||
604 | static int stmfts_enable_led(struct stmfts_data *sdata) | ||
605 | { | ||
606 | int err; | ||
607 | |||
608 | /* get the regulator for powering the leds on */ | ||
609 | sdata->ledvdd = devm_regulator_get(&sdata->client->dev, "ledvdd"); | ||
610 | if (IS_ERR(sdata->ledvdd)) | ||
611 | return PTR_ERR(sdata->ledvdd); | ||
612 | |||
613 | sdata->led_cdev.name = STMFTS_DEV_NAME; | ||
614 | sdata->led_cdev.max_brightness = LED_ON; | ||
615 | sdata->led_cdev.brightness = LED_OFF; | ||
616 | sdata->led_cdev.brightness_set = stmfts_brightness_set; | ||
617 | sdata->led_cdev.brightness_get = stmfts_brightness_get; | ||
618 | |||
619 | err = devm_led_classdev_register(&sdata->client->dev, &sdata->led_cdev); | ||
620 | if (err) { | ||
621 | devm_regulator_put(sdata->ledvdd); | ||
622 | return err; | ||
623 | } | ||
624 | |||
625 | return 0; | ||
626 | } | ||
627 | |||
628 | static int stmfts_probe(struct i2c_client *client, | ||
629 | const struct i2c_device_id *id) | ||
630 | { | ||
631 | int err; | ||
632 | struct stmfts_data *sdata; | ||
633 | |||
634 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | | ||
635 | I2C_FUNC_SMBUS_BYTE_DATA | | ||
636 | I2C_FUNC_SMBUS_I2C_BLOCK)) | ||
637 | return -ENODEV; | ||
638 | |||
639 | sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL); | ||
640 | if (!sdata) | ||
641 | return -ENOMEM; | ||
642 | |||
643 | i2c_set_clientdata(client, sdata); | ||
644 | |||
645 | sdata->client = client; | ||
646 | mutex_init(&sdata->mutex); | ||
647 | init_completion(&sdata->cmd_done); | ||
648 | |||
649 | sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd"; | ||
650 | sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd"; | ||
651 | err = devm_regulator_bulk_get(&client->dev, | ||
652 | ARRAY_SIZE(sdata->regulators), | ||
653 | sdata->regulators); | ||
654 | if (err) | ||
655 | return err; | ||
656 | |||
657 | sdata->input = devm_input_allocate_device(&client->dev); | ||
658 | if (!sdata->input) | ||
659 | return -ENOMEM; | ||
660 | |||
661 | sdata->input->name = STMFTS_DEV_NAME; | ||
662 | sdata->input->id.bustype = BUS_I2C; | ||
663 | sdata->input->open = stmfts_input_open; | ||
664 | sdata->input->close = stmfts_input_close; | ||
665 | |||
666 | touchscreen_parse_properties(sdata->input, true, &sdata->prop); | ||
667 | |||
668 | input_set_abs_params(sdata->input, ABS_MT_POSITION_X, 0, | ||
669 | sdata->prop.max_x, 0, 0); | ||
670 | input_set_abs_params(sdata->input, ABS_MT_POSITION_Y, 0, | ||
671 | sdata->prop.max_y, 0, 0); | ||
672 | input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); | ||
673 | input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0); | ||
674 | input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0); | ||
675 | input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0); | ||
676 | input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0); | ||
677 | |||
678 | sdata->use_key = device_property_read_bool(&client->dev, | ||
679 | "touch-key-connected"); | ||
680 | if (sdata->use_key) { | ||
681 | input_set_capability(sdata->input, EV_KEY, KEY_MENU); | ||
682 | input_set_capability(sdata->input, EV_KEY, KEY_BACK); | ||
683 | } | ||
684 | |||
685 | err = input_mt_init_slots(sdata->input, | ||
686 | STMFTS_MAX_FINGERS, INPUT_MT_DIRECT); | ||
687 | if (err) | ||
688 | return err; | ||
689 | |||
690 | input_set_drvdata(sdata->input, sdata); | ||
691 | |||
692 | err = devm_request_threaded_irq(&client->dev, client->irq, | ||
693 | NULL, stmfts_irq_handler, | ||
694 | IRQF_ONESHOT, | ||
695 | "stmfts_irq", sdata); | ||
696 | if (err) | ||
697 | return err; | ||
698 | |||
699 | /* stmfts_power_on expects interrupt to be disabled */ | ||
700 | disable_irq(client->irq); | ||
701 | |||
702 | dev_dbg(&client->dev, "initializing ST-Microelectronics FTS...\n"); | ||
703 | |||
704 | err = stmfts_power_on(sdata); | ||
705 | if (err) | ||
706 | return err; | ||
707 | |||
708 | err = devm_add_action_or_reset(&client->dev, stmfts_power_off, sdata); | ||
709 | if (err) | ||
710 | return err; | ||
711 | |||
712 | err = input_register_device(sdata->input); | ||
713 | if (err) | ||
714 | return err; | ||
715 | |||
716 | if (sdata->use_key) { | ||
717 | err = stmfts_enable_led(sdata); | ||
718 | if (err) { | ||
719 | /* | ||
720 | * Even if the LEDs have failed to be initialized and | ||
721 | * used in the driver, I can still use the device even | ||
722 | * without LEDs. The ledvdd regulator pointer will be | ||
723 | * used as a flag. | ||
724 | */ | ||
725 | dev_warn(&client->dev, "unable to use touchkey leds\n"); | ||
726 | sdata->ledvdd = NULL; | ||
727 | } | ||
728 | } | ||
729 | |||
730 | err = sysfs_create_group(&sdata->client->dev.kobj, | ||
731 | &stmfts_attribute_group); | ||
732 | if (err) | ||
733 | return err; | ||
734 | |||
735 | pm_runtime_enable(&client->dev); | ||
736 | |||
737 | return 0; | ||
738 | } | ||
739 | |||
740 | static int stmfts_remove(struct i2c_client *client) | ||
741 | { | ||
742 | pm_runtime_disable(&client->dev); | ||
743 | sysfs_remove_group(&client->dev.kobj, &stmfts_attribute_group); | ||
744 | |||
745 | return 0; | ||
746 | } | ||
747 | |||
748 | static int stmfts_runtime_suspend(struct device *dev) | ||
749 | { | ||
750 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
751 | int ret; | ||
752 | |||
753 | ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN); | ||
754 | if (ret) | ||
755 | dev_warn(dev, "failed to suspend device: %d\n", ret); | ||
756 | |||
757 | return ret; | ||
758 | } | ||
759 | |||
760 | static int stmfts_runtime_resume(struct device *dev) | ||
761 | { | ||
762 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
763 | int ret; | ||
764 | |||
765 | ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT); | ||
766 | if (ret) | ||
767 | dev_err(dev, "failed to resume device: %d\n", ret); | ||
768 | |||
769 | return ret; | ||
770 | } | ||
771 | |||
772 | static int __maybe_unused stmfts_suspend(struct device *dev) | ||
773 | { | ||
774 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
775 | |||
776 | stmfts_power_off(sdata); | ||
777 | |||
778 | return 0; | ||
779 | } | ||
780 | |||
781 | static int __maybe_unused stmfts_resume(struct device *dev) | ||
782 | { | ||
783 | struct stmfts_data *sdata = dev_get_drvdata(dev); | ||
784 | |||
785 | return stmfts_power_on(sdata); | ||
786 | } | ||
787 | |||
788 | static const struct dev_pm_ops stmfts_pm_ops = { | ||
789 | SET_SYSTEM_SLEEP_PM_OPS(stmfts_suspend, stmfts_resume) | ||
790 | SET_RUNTIME_PM_OPS(stmfts_runtime_suspend, stmfts_runtime_resume, NULL) | ||
791 | }; | ||
792 | |||
793 | #ifdef CONFIG_OF | ||
794 | static const struct of_device_id stmfts_of_match[] = { | ||
795 | { .compatible = "st,stmfts", }, | ||
796 | { }, | ||
797 | }; | ||
798 | MODULE_DEVICE_TABLE(of, stmfts_of_match); | ||
799 | #endif | ||
800 | |||
801 | static const struct i2c_device_id stmfts_id[] = { | ||
802 | { "stmfts", 0 }, | ||
803 | { }, | ||
804 | }; | ||
805 | MODULE_DEVICE_TABLE(i2c, stmfts_id); | ||
806 | |||
807 | static struct i2c_driver stmfts_driver = { | ||
808 | .driver = { | ||
809 | .name = STMFTS_DEV_NAME, | ||
810 | .of_match_table = of_match_ptr(stmfts_of_match), | ||
811 | .pm = &stmfts_pm_ops, | ||
812 | }, | ||
813 | .probe = stmfts_probe, | ||
814 | .remove = stmfts_remove, | ||
815 | .id_table = stmfts_id, | ||
816 | }; | ||
817 | |||
818 | module_i2c_driver(stmfts_driver); | ||
819 | |||
820 | MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>"); | ||
821 | MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen"); | ||
822 | MODULE_LICENSE("GPL v2"); | ||