1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for Himax hx83112b touchscreens |
4 | * |
5 | * Copyright (C) 2022 Job Noorman <job@noorman.info> |
6 | * |
7 | * This code is based on "Himax Android Driver Sample Code for QCT platform": |
8 | * |
9 | * Copyright (C) 2017 Himax Corporation. |
10 | */ |
11 | |
12 | #include <linux/delay.h> |
13 | #include <linux/err.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/i2c.h> |
16 | #include <linux/input.h> |
17 | #include <linux/input/mt.h> |
18 | #include <linux/input/touchscreen.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/kernel.h> |
21 | #include <linux/regmap.h> |
22 | |
23 | #define HIMAX_ID_83112B 0x83112b |
24 | |
25 | #define HIMAX_MAX_POINTS 10 |
26 | |
27 | #define HIMAX_REG_CFG_SET_ADDR 0x00 |
28 | #define HIMAX_REG_CFG_INIT_READ 0x0c |
29 | #define HIMAX_REG_CFG_READ_VALUE 0x08 |
30 | #define HIMAX_REG_READ_EVENT 0x30 |
31 | |
32 | #define HIMAX_CFG_PRODUCT_ID 0x900000d0 |
33 | |
34 | #define HIMAX_INVALID_COORD 0xffff |
35 | |
36 | struct himax_event_point { |
37 | __be16 x; |
38 | __be16 y; |
39 | } __packed; |
40 | |
41 | struct himax_event { |
42 | struct himax_event_point points[HIMAX_MAX_POINTS]; |
43 | u8 majors[HIMAX_MAX_POINTS]; |
44 | u8 pad0[2]; |
45 | u8 num_points; |
46 | u8 pad1[2]; |
47 | u8 checksum_fix; |
48 | } __packed; |
49 | |
50 | static_assert(sizeof(struct himax_event) == 56); |
51 | |
52 | struct himax_ts_data { |
53 | struct gpio_desc *gpiod_rst; |
54 | struct input_dev *input_dev; |
55 | struct i2c_client *client; |
56 | struct regmap *regmap; |
57 | struct touchscreen_properties props; |
58 | }; |
59 | |
60 | static const struct regmap_config himax_regmap_config = { |
61 | .reg_bits = 8, |
62 | .val_bits = 32, |
63 | .val_format_endian = REGMAP_ENDIAN_LITTLE, |
64 | }; |
65 | |
66 | static int himax_read_config(struct himax_ts_data *ts, u32 address, u32 *dst) |
67 | { |
68 | int error; |
69 | |
70 | error = regmap_write(map: ts->regmap, HIMAX_REG_CFG_SET_ADDR, val: address); |
71 | if (error) |
72 | return error; |
73 | |
74 | error = regmap_write(map: ts->regmap, HIMAX_REG_CFG_INIT_READ, val: 0x0); |
75 | if (error) |
76 | return error; |
77 | |
78 | error = regmap_read(map: ts->regmap, HIMAX_REG_CFG_READ_VALUE, val: dst); |
79 | if (error) |
80 | return error; |
81 | |
82 | return 0; |
83 | } |
84 | |
85 | static void himax_reset(struct himax_ts_data *ts) |
86 | { |
87 | gpiod_set_value_cansleep(desc: ts->gpiod_rst, value: 1); |
88 | |
89 | /* Delay copied from downstream driver */ |
90 | msleep(msecs: 20); |
91 | gpiod_set_value_cansleep(desc: ts->gpiod_rst, value: 0); |
92 | |
93 | /* |
94 | * The downstream driver doesn't contain this delay but is seems safer |
95 | * to include it. The range is just a guess that seems to work well. |
96 | */ |
97 | usleep_range(min: 1000, max: 1100); |
98 | } |
99 | |
100 | static int himax_read_product_id(struct himax_ts_data *ts, u32 *product_id) |
101 | { |
102 | int error; |
103 | |
104 | error = himax_read_config(ts, HIMAX_CFG_PRODUCT_ID, dst: product_id); |
105 | if (error) |
106 | return error; |
107 | |
108 | *product_id >>= 8; |
109 | return 0; |
110 | } |
111 | |
112 | static int himax_check_product_id(struct himax_ts_data *ts) |
113 | { |
114 | int error; |
115 | u32 product_id; |
116 | |
117 | error = himax_read_product_id(ts, product_id: &product_id); |
118 | if (error) |
119 | return error; |
120 | |
121 | dev_dbg(&ts->client->dev, "Product id: %x\n" , product_id); |
122 | |
123 | switch (product_id) { |
124 | case HIMAX_ID_83112B: |
125 | return 0; |
126 | |
127 | default: |
128 | dev_err(&ts->client->dev, |
129 | "Unknown product id: %x\n" , product_id); |
130 | return -EINVAL; |
131 | } |
132 | } |
133 | |
134 | static int himax_input_register(struct himax_ts_data *ts) |
135 | { |
136 | int error; |
137 | |
138 | ts->input_dev = devm_input_allocate_device(&ts->client->dev); |
139 | if (!ts->input_dev) { |
140 | dev_err(&ts->client->dev, "Failed to allocate input device\n" ); |
141 | return -ENOMEM; |
142 | } |
143 | |
144 | ts->input_dev->name = "Himax Touchscreen" ; |
145 | |
146 | input_set_capability(dev: ts->input_dev, EV_ABS, ABS_MT_POSITION_X); |
147 | input_set_capability(dev: ts->input_dev, EV_ABS, ABS_MT_POSITION_Y); |
148 | input_set_abs_params(dev: ts->input_dev, ABS_MT_WIDTH_MAJOR, min: 0, max: 200, fuzz: 0, flat: 0); |
149 | input_set_abs_params(dev: ts->input_dev, ABS_MT_TOUCH_MAJOR, min: 0, max: 200, fuzz: 0, flat: 0); |
150 | |
151 | touchscreen_parse_properties(input: ts->input_dev, multitouch: true, prop: &ts->props); |
152 | |
153 | error = input_mt_init_slots(dev: ts->input_dev, HIMAX_MAX_POINTS, |
154 | INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); |
155 | if (error) { |
156 | dev_err(&ts->client->dev, |
157 | "Failed to initialize MT slots: %d\n" , error); |
158 | return error; |
159 | } |
160 | |
161 | error = input_register_device(ts->input_dev); |
162 | if (error) { |
163 | dev_err(&ts->client->dev, |
164 | "Failed to register input device: %d\n" , error); |
165 | return error; |
166 | } |
167 | |
168 | return 0; |
169 | } |
170 | |
171 | static u8 himax_event_get_num_points(const struct himax_event *event) |
172 | { |
173 | if (event->num_points == 0xff) |
174 | return 0; |
175 | else |
176 | return event->num_points & 0x0f; |
177 | } |
178 | |
179 | static bool himax_process_event_point(struct himax_ts_data *ts, |
180 | const struct himax_event *event, |
181 | int point_index) |
182 | { |
183 | const struct himax_event_point *point = &event->points[point_index]; |
184 | u16 x = be16_to_cpu(point->x); |
185 | u16 y = be16_to_cpu(point->y); |
186 | u8 w = event->majors[point_index]; |
187 | |
188 | if (x == HIMAX_INVALID_COORD || y == HIMAX_INVALID_COORD) |
189 | return false; |
190 | |
191 | input_mt_slot(dev: ts->input_dev, slot: point_index); |
192 | input_mt_report_slot_state(dev: ts->input_dev, MT_TOOL_FINGER, active: true); |
193 | touchscreen_report_pos(input: ts->input_dev, prop: &ts->props, x, y, multitouch: true); |
194 | input_report_abs(dev: ts->input_dev, ABS_MT_TOUCH_MAJOR, value: w); |
195 | input_report_abs(dev: ts->input_dev, ABS_MT_WIDTH_MAJOR, value: w); |
196 | return true; |
197 | } |
198 | |
199 | static void himax_process_event(struct himax_ts_data *ts, |
200 | const struct himax_event *event) |
201 | { |
202 | int i; |
203 | int num_points_left = himax_event_get_num_points(event); |
204 | |
205 | for (i = 0; i < HIMAX_MAX_POINTS && num_points_left > 0; i++) { |
206 | if (himax_process_event_point(ts, event, point_index: i)) |
207 | num_points_left--; |
208 | } |
209 | |
210 | input_mt_sync_frame(dev: ts->input_dev); |
211 | input_sync(dev: ts->input_dev); |
212 | } |
213 | |
214 | static bool himax_verify_checksum(struct himax_ts_data *ts, |
215 | const struct himax_event *event) |
216 | { |
217 | u8 *data = (u8 *)event; |
218 | int i; |
219 | u16 checksum = 0; |
220 | |
221 | for (i = 0; i < sizeof(*event); i++) |
222 | checksum += data[i]; |
223 | |
224 | if ((checksum & 0x00ff) != 0) { |
225 | dev_err(&ts->client->dev, "Wrong event checksum: %04x\n" , |
226 | checksum); |
227 | return false; |
228 | } |
229 | |
230 | return true; |
231 | } |
232 | |
233 | static int himax_handle_input(struct himax_ts_data *ts) |
234 | { |
235 | int error; |
236 | struct himax_event event; |
237 | |
238 | error = regmap_raw_read(map: ts->regmap, HIMAX_REG_READ_EVENT, val: &event, |
239 | val_len: sizeof(event)); |
240 | if (error) { |
241 | dev_err(&ts->client->dev, "Failed to read input event: %d\n" , |
242 | error); |
243 | return error; |
244 | } |
245 | |
246 | /* |
247 | * Only process the current event when it has a valid checksum but |
248 | * don't consider it a fatal error when it doesn't. |
249 | */ |
250 | if (himax_verify_checksum(ts, event: &event)) |
251 | himax_process_event(ts, event: &event); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static irqreturn_t himax_irq_handler(int irq, void *dev_id) |
257 | { |
258 | int error; |
259 | struct himax_ts_data *ts = dev_id; |
260 | |
261 | error = himax_handle_input(ts); |
262 | if (error) |
263 | return IRQ_NONE; |
264 | |
265 | return IRQ_HANDLED; |
266 | } |
267 | |
268 | static int himax_probe(struct i2c_client *client) |
269 | { |
270 | int error; |
271 | struct device *dev = &client->dev; |
272 | struct himax_ts_data *ts; |
273 | |
274 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_I2C)) { |
275 | dev_err(dev, "I2C check functionality failed\n" ); |
276 | return -ENXIO; |
277 | } |
278 | |
279 | ts = devm_kzalloc(dev, size: sizeof(*ts), GFP_KERNEL); |
280 | if (!ts) |
281 | return -ENOMEM; |
282 | |
283 | i2c_set_clientdata(client, data: ts); |
284 | ts->client = client; |
285 | |
286 | ts->regmap = devm_regmap_init_i2c(client, &himax_regmap_config); |
287 | error = PTR_ERR_OR_ZERO(ptr: ts->regmap); |
288 | if (error) { |
289 | dev_err(dev, "Failed to initialize regmap: %d\n" , error); |
290 | return error; |
291 | } |
292 | |
293 | ts->gpiod_rst = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
294 | error = PTR_ERR_OR_ZERO(ptr: ts->gpiod_rst); |
295 | if (error) { |
296 | dev_err(dev, "Failed to get reset GPIO: %d\n" , error); |
297 | return error; |
298 | } |
299 | |
300 | himax_reset(ts); |
301 | |
302 | error = himax_check_product_id(ts); |
303 | if (error) |
304 | return error; |
305 | |
306 | error = himax_input_register(ts); |
307 | if (error) |
308 | return error; |
309 | |
310 | error = devm_request_threaded_irq(dev, irq: client->irq, NULL, |
311 | thread_fn: himax_irq_handler, IRQF_ONESHOT, |
312 | devname: client->name, dev_id: ts); |
313 | if (error) |
314 | return error; |
315 | |
316 | return 0; |
317 | } |
318 | |
319 | static int himax_suspend(struct device *dev) |
320 | { |
321 | struct himax_ts_data *ts = dev_get_drvdata(dev); |
322 | |
323 | disable_irq(irq: ts->client->irq); |
324 | return 0; |
325 | } |
326 | |
327 | static int himax_resume(struct device *dev) |
328 | { |
329 | struct himax_ts_data *ts = dev_get_drvdata(dev); |
330 | |
331 | enable_irq(irq: ts->client->irq); |
332 | return 0; |
333 | } |
334 | |
335 | static DEFINE_SIMPLE_DEV_PM_OPS(himax_pm_ops, himax_suspend, himax_resume); |
336 | |
337 | static const struct i2c_device_id himax_ts_id[] = { |
338 | { "hx83112b" , 0 }, |
339 | { /* sentinel */ } |
340 | }; |
341 | MODULE_DEVICE_TABLE(i2c, himax_ts_id); |
342 | |
343 | #ifdef CONFIG_OF |
344 | static const struct of_device_id himax_of_match[] = { |
345 | { .compatible = "himax,hx83112b" }, |
346 | { /* sentinel */ } |
347 | }; |
348 | MODULE_DEVICE_TABLE(of, himax_of_match); |
349 | #endif |
350 | |
351 | static struct i2c_driver himax_ts_driver = { |
352 | .probe = himax_probe, |
353 | .id_table = himax_ts_id, |
354 | .driver = { |
355 | .name = "Himax-hx83112b-TS" , |
356 | .of_match_table = of_match_ptr(himax_of_match), |
357 | .pm = pm_sleep_ptr(&himax_pm_ops), |
358 | }, |
359 | }; |
360 | module_i2c_driver(himax_ts_driver); |
361 | |
362 | MODULE_AUTHOR("Job Noorman <job@noorman.info>" ); |
363 | MODULE_DESCRIPTION("Himax hx83112b touchscreen driver" ); |
364 | MODULE_LICENSE("GPL" ); |
365 | |