1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Novatek NT11205 i2c touchscreen controller as found |
4 | * on the Acer Iconia One 7 B1-750 tablet. |
5 | * |
6 | * Copyright (c) 2023 Hans de Goede <hdegoede@redhat.com> |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/input.h> |
14 | #include <linux/input/mt.h> |
15 | #include <linux/input/touchscreen.h> |
16 | #include <linux/module.h> |
17 | |
18 | #include <asm/unaligned.h> |
19 | |
20 | #define NVT_TS_TOUCH_START 0x00 |
21 | #define NVT_TS_TOUCH_SIZE 6 |
22 | |
23 | #define NVT_TS_PARAMETERS_START 0x78 |
24 | /* These are offsets from NVT_TS_PARAMETERS_START */ |
25 | #define NVT_TS_PARAMS_WIDTH 0x04 |
26 | #define NVT_TS_PARAMS_HEIGHT 0x06 |
27 | #define NVT_TS_PARAMS_MAX_TOUCH 0x09 |
28 | #define NVT_TS_PARAMS_MAX_BUTTONS 0x0a |
29 | #define NVT_TS_PARAMS_IRQ_TYPE 0x0b |
30 | #define NVT_TS_PARAMS_WAKE_TYPE 0x0c |
31 | #define NVT_TS_PARAMS_CHIP_ID 0x0e |
32 | #define NVT_TS_PARAMS_SIZE 0x0f |
33 | |
34 | #define NVT_TS_SUPPORTED_WAKE_TYPE 0x05 |
35 | #define NVT_TS_SUPPORTED_CHIP_ID 0x05 |
36 | |
37 | #define NVT_TS_MAX_TOUCHES 10 |
38 | #define NVT_TS_MAX_SIZE 4096 |
39 | |
40 | #define NVT_TS_TOUCH_INVALID 0xff |
41 | #define NVT_TS_TOUCH_SLOT_SHIFT 3 |
42 | #define NVT_TS_TOUCH_TYPE_MASK GENMASK(2, 0) |
43 | #define NVT_TS_TOUCH_NEW 1 |
44 | #define NVT_TS_TOUCH_UPDATE 2 |
45 | #define NVT_TS_TOUCH_RELEASE 3 |
46 | |
47 | static const int nvt_ts_irq_type[4] = { |
48 | IRQF_TRIGGER_RISING, |
49 | IRQF_TRIGGER_FALLING, |
50 | IRQF_TRIGGER_LOW, |
51 | IRQF_TRIGGER_HIGH |
52 | }; |
53 | |
54 | struct nvt_ts_data { |
55 | struct i2c_client *client; |
56 | struct input_dev *input; |
57 | struct gpio_desc *reset_gpio; |
58 | struct touchscreen_properties prop; |
59 | int max_touches; |
60 | u8 buf[NVT_TS_TOUCH_SIZE * NVT_TS_MAX_TOUCHES]; |
61 | }; |
62 | |
63 | static int nvt_ts_read_data(struct i2c_client *client, u8 reg, u8 *data, int count) |
64 | { |
65 | struct i2c_msg msg[2] = { |
66 | { |
67 | .addr = client->addr, |
68 | .len = 1, |
69 | .buf = ®, |
70 | }, |
71 | { |
72 | .addr = client->addr, |
73 | .flags = I2C_M_RD, |
74 | .len = count, |
75 | .buf = data, |
76 | } |
77 | }; |
78 | int ret; |
79 | |
80 | ret = i2c_transfer(adap: client->adapter, msgs: msg, ARRAY_SIZE(msg)); |
81 | if (ret != ARRAY_SIZE(msg)) { |
82 | dev_err(&client->dev, "Error reading from 0x%02x: %d\n" , reg, ret); |
83 | return (ret < 0) ? ret : -EIO; |
84 | } |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static irqreturn_t nvt_ts_irq(int irq, void *dev_id) |
90 | { |
91 | struct nvt_ts_data *data = dev_id; |
92 | struct device *dev = &data->client->dev; |
93 | int i, error, slot, x, y; |
94 | bool active; |
95 | u8 *touch; |
96 | |
97 | error = nvt_ts_read_data(client: data->client, NVT_TS_TOUCH_START, data: data->buf, |
98 | count: data->max_touches * NVT_TS_TOUCH_SIZE); |
99 | if (error) |
100 | return IRQ_HANDLED; |
101 | |
102 | for (i = 0; i < data->max_touches; i++) { |
103 | touch = &data->buf[i * NVT_TS_TOUCH_SIZE]; |
104 | |
105 | if (touch[0] == NVT_TS_TOUCH_INVALID) |
106 | continue; |
107 | |
108 | slot = touch[0] >> NVT_TS_TOUCH_SLOT_SHIFT; |
109 | if (slot < 1 || slot > data->max_touches) { |
110 | dev_warn(dev, "slot %d out of range, ignoring\n" , slot); |
111 | continue; |
112 | } |
113 | |
114 | switch (touch[0] & NVT_TS_TOUCH_TYPE_MASK) { |
115 | case NVT_TS_TOUCH_NEW: |
116 | case NVT_TS_TOUCH_UPDATE: |
117 | active = true; |
118 | break; |
119 | case NVT_TS_TOUCH_RELEASE: |
120 | active = false; |
121 | break; |
122 | default: |
123 | dev_warn(dev, "slot %d unknown state %d\n" , slot, touch[0] & 7); |
124 | continue; |
125 | } |
126 | |
127 | slot--; |
128 | x = (touch[1] << 4) | (touch[3] >> 4); |
129 | y = (touch[2] << 4) | (touch[3] & 0x0f); |
130 | |
131 | input_mt_slot(dev: data->input, slot); |
132 | input_mt_report_slot_state(dev: data->input, MT_TOOL_FINGER, active); |
133 | touchscreen_report_pos(input: data->input, prop: &data->prop, x, y, multitouch: true); |
134 | } |
135 | |
136 | input_mt_sync_frame(dev: data->input); |
137 | input_sync(dev: data->input); |
138 | |
139 | return IRQ_HANDLED; |
140 | } |
141 | |
142 | static int nvt_ts_start(struct input_dev *dev) |
143 | { |
144 | struct nvt_ts_data *data = input_get_drvdata(dev); |
145 | |
146 | enable_irq(irq: data->client->irq); |
147 | gpiod_set_value_cansleep(desc: data->reset_gpio, value: 0); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static void nvt_ts_stop(struct input_dev *dev) |
153 | { |
154 | struct nvt_ts_data *data = input_get_drvdata(dev); |
155 | |
156 | disable_irq(irq: data->client->irq); |
157 | gpiod_set_value_cansleep(desc: data->reset_gpio, value: 1); |
158 | } |
159 | |
160 | static int nvt_ts_suspend(struct device *dev) |
161 | { |
162 | struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
163 | |
164 | mutex_lock(&data->input->mutex); |
165 | if (input_device_enabled(dev: data->input)) |
166 | nvt_ts_stop(dev: data->input); |
167 | mutex_unlock(lock: &data->input->mutex); |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int nvt_ts_resume(struct device *dev) |
173 | { |
174 | struct nvt_ts_data *data = i2c_get_clientdata(to_i2c_client(dev)); |
175 | |
176 | mutex_lock(&data->input->mutex); |
177 | if (input_device_enabled(dev: data->input)) |
178 | nvt_ts_start(dev: data->input); |
179 | mutex_unlock(lock: &data->input->mutex); |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | static DEFINE_SIMPLE_DEV_PM_OPS(nvt_ts_pm_ops, nvt_ts_suspend, nvt_ts_resume); |
185 | |
186 | static int nvt_ts_probe(struct i2c_client *client) |
187 | { |
188 | struct device *dev = &client->dev; |
189 | int error, width, height, irq_type; |
190 | struct nvt_ts_data *data; |
191 | struct input_dev *input; |
192 | |
193 | if (!client->irq) { |
194 | dev_err(dev, "Error no irq specified\n" ); |
195 | return -EINVAL; |
196 | } |
197 | |
198 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
199 | if (!data) |
200 | return -ENOMEM; |
201 | |
202 | data->client = client; |
203 | i2c_set_clientdata(client, data); |
204 | |
205 | data->reset_gpio = devm_gpiod_get(dev, con_id: "reset" , flags: GPIOD_OUT_LOW); |
206 | error = PTR_ERR_OR_ZERO(ptr: data->reset_gpio); |
207 | if (error) { |
208 | dev_err(dev, "failed to request reset GPIO: %d\n" , error); |
209 | return error; |
210 | } |
211 | |
212 | /* Wait for controller to come out of reset before params read */ |
213 | msleep(msecs: 100); |
214 | error = nvt_ts_read_data(client: data->client, NVT_TS_PARAMETERS_START, |
215 | data: data->buf, NVT_TS_PARAMS_SIZE); |
216 | gpiod_set_value_cansleep(desc: data->reset_gpio, value: 1); /* Put back in reset */ |
217 | if (error) |
218 | return error; |
219 | |
220 | width = get_unaligned_be16(p: &data->buf[NVT_TS_PARAMS_WIDTH]); |
221 | height = get_unaligned_be16(p: &data->buf[NVT_TS_PARAMS_HEIGHT]); |
222 | data->max_touches = data->buf[NVT_TS_PARAMS_MAX_TOUCH]; |
223 | irq_type = data->buf[NVT_TS_PARAMS_IRQ_TYPE]; |
224 | |
225 | if (width > NVT_TS_MAX_SIZE || height >= NVT_TS_MAX_SIZE || |
226 | data->max_touches > NVT_TS_MAX_TOUCHES || |
227 | irq_type >= ARRAY_SIZE(nvt_ts_irq_type) || |
228 | data->buf[NVT_TS_PARAMS_WAKE_TYPE] != NVT_TS_SUPPORTED_WAKE_TYPE || |
229 | data->buf[NVT_TS_PARAMS_CHIP_ID] != NVT_TS_SUPPORTED_CHIP_ID) { |
230 | dev_err(dev, "Unsupported touchscreen parameters: %*ph\n" , |
231 | NVT_TS_PARAMS_SIZE, data->buf); |
232 | return -EIO; |
233 | } |
234 | |
235 | dev_dbg(dev, "Detected %dx%d touchscreen with %d max touches\n" , |
236 | width, height, data->max_touches); |
237 | |
238 | if (data->buf[NVT_TS_PARAMS_MAX_BUTTONS]) |
239 | dev_warn(dev, "Touchscreen buttons are not supported\n" ); |
240 | |
241 | input = devm_input_allocate_device(dev); |
242 | if (!input) |
243 | return -ENOMEM; |
244 | |
245 | input->name = client->name; |
246 | input->id.bustype = BUS_I2C; |
247 | input->open = nvt_ts_start; |
248 | input->close = nvt_ts_stop; |
249 | |
250 | input_set_abs_params(dev: input, ABS_MT_POSITION_X, min: 0, max: width - 1, fuzz: 0, flat: 0); |
251 | input_set_abs_params(dev: input, ABS_MT_POSITION_Y, min: 0, max: height - 1, fuzz: 0, flat: 0); |
252 | touchscreen_parse_properties(input, multitouch: true, prop: &data->prop); |
253 | |
254 | error = input_mt_init_slots(dev: input, num_slots: data->max_touches, |
255 | INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); |
256 | if (error) |
257 | return error; |
258 | |
259 | data->input = input; |
260 | input_set_drvdata(dev: input, data); |
261 | |
262 | error = devm_request_threaded_irq(dev, irq: client->irq, NULL, thread_fn: nvt_ts_irq, |
263 | IRQF_ONESHOT | IRQF_NO_AUTOEN | |
264 | nvt_ts_irq_type[irq_type], |
265 | devname: client->name, dev_id: data); |
266 | if (error) { |
267 | dev_err(dev, "failed to request irq: %d\n" , error); |
268 | return error; |
269 | } |
270 | |
271 | error = input_register_device(input); |
272 | if (error) { |
273 | dev_err(dev, "failed to register input device: %d\n" , error); |
274 | return error; |
275 | } |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static const struct i2c_device_id nvt_ts_i2c_id[] = { |
281 | { "NVT-ts" }, |
282 | { } |
283 | }; |
284 | MODULE_DEVICE_TABLE(i2c, nvt_ts_i2c_id); |
285 | |
286 | static struct i2c_driver nvt_ts_driver = { |
287 | .driver = { |
288 | .name = "novatek-nvt-ts" , |
289 | .pm = pm_sleep_ptr(&nvt_ts_pm_ops), |
290 | }, |
291 | .probe = nvt_ts_probe, |
292 | .id_table = nvt_ts_i2c_id, |
293 | }; |
294 | |
295 | module_i2c_driver(nvt_ts_driver); |
296 | |
297 | MODULE_DESCRIPTION("Novatek NT11205 touchscreen driver" ); |
298 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
299 | MODULE_LICENSE("GPL" ); |
300 | |