1 | /* |
2 | * Touchscreen driver for the TS-4800 board |
3 | * |
4 | * Copyright (c) 2015 - Savoir-faire Linux |
5 | * |
6 | * This file is licensed under the terms of the GNU General Public |
7 | * License version 2. This program is licensed "as is" without any |
8 | * warranty of any kind, whether express or implied. |
9 | */ |
10 | |
11 | #include <linux/bitops.h> |
12 | #include <linux/input.h> |
13 | #include <linux/io.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | /* polling interval in ms */ |
22 | #define POLL_INTERVAL 3 |
23 | |
24 | #define DEBOUNCE_COUNT 1 |
25 | |
26 | /* sensor values are 12-bit wide */ |
27 | #define MAX_12BIT ((1 << 12) - 1) |
28 | |
29 | #define PENDOWN_MASK 0x1 |
30 | |
31 | #define X_OFFSET 0x0 |
32 | #define Y_OFFSET 0x2 |
33 | |
34 | struct ts4800_ts { |
35 | struct input_dev *input; |
36 | struct device *dev; |
37 | char phys[32]; |
38 | |
39 | void __iomem *base; |
40 | struct regmap *regmap; |
41 | unsigned int reg; |
42 | unsigned int bit; |
43 | |
44 | bool pendown; |
45 | int debounce; |
46 | }; |
47 | |
48 | static int ts4800_ts_open(struct input_dev *input_dev) |
49 | { |
50 | struct ts4800_ts *ts = input_get_drvdata(dev: input_dev); |
51 | int error; |
52 | |
53 | ts->pendown = false; |
54 | ts->debounce = DEBOUNCE_COUNT; |
55 | |
56 | error = regmap_update_bits(map: ts->regmap, reg: ts->reg, mask: ts->bit, val: ts->bit); |
57 | if (error) { |
58 | dev_warn(ts->dev, "Failed to enable touchscreen: %d\n" , error); |
59 | return error; |
60 | } |
61 | |
62 | return 0; |
63 | } |
64 | |
65 | static void ts4800_ts_close(struct input_dev *input_dev) |
66 | { |
67 | struct ts4800_ts *ts = input_get_drvdata(dev: input_dev); |
68 | int ret; |
69 | |
70 | ret = regmap_update_bits(map: ts->regmap, reg: ts->reg, mask: ts->bit, val: 0); |
71 | if (ret) |
72 | dev_warn(ts->dev, "Failed to disable touchscreen\n" ); |
73 | |
74 | } |
75 | |
76 | static void ts4800_ts_poll(struct input_dev *input_dev) |
77 | { |
78 | struct ts4800_ts *ts = input_get_drvdata(dev: input_dev); |
79 | u16 last_x = readw(addr: ts->base + X_OFFSET); |
80 | u16 last_y = readw(addr: ts->base + Y_OFFSET); |
81 | bool pendown = last_x & PENDOWN_MASK; |
82 | |
83 | if (pendown) { |
84 | if (ts->debounce) { |
85 | ts->debounce--; |
86 | return; |
87 | } |
88 | |
89 | if (!ts->pendown) { |
90 | input_report_key(dev: input_dev, BTN_TOUCH, value: 1); |
91 | ts->pendown = true; |
92 | } |
93 | |
94 | last_x = ((~last_x) >> 4) & MAX_12BIT; |
95 | last_y = ((~last_y) >> 4) & MAX_12BIT; |
96 | |
97 | input_report_abs(dev: input_dev, ABS_X, value: last_x); |
98 | input_report_abs(dev: input_dev, ABS_Y, value: last_y); |
99 | input_sync(dev: input_dev); |
100 | } else if (ts->pendown) { |
101 | ts->pendown = false; |
102 | ts->debounce = DEBOUNCE_COUNT; |
103 | input_report_key(dev: input_dev, BTN_TOUCH, value: 0); |
104 | input_sync(dev: input_dev); |
105 | } |
106 | } |
107 | |
108 | static int ts4800_parse_dt(struct platform_device *pdev, |
109 | struct ts4800_ts *ts) |
110 | { |
111 | struct device *dev = &pdev->dev; |
112 | struct device_node *np = dev->of_node; |
113 | struct device_node *syscon_np; |
114 | u32 reg, bit; |
115 | int error; |
116 | |
117 | syscon_np = of_parse_phandle(np, phandle_name: "syscon" , index: 0); |
118 | if (!syscon_np) { |
119 | dev_err(dev, "no syscon property\n" ); |
120 | return -ENODEV; |
121 | } |
122 | |
123 | ts->regmap = syscon_node_to_regmap(np: syscon_np); |
124 | of_node_put(node: syscon_np); |
125 | if (IS_ERR(ptr: ts->regmap)) { |
126 | dev_err(dev, "cannot get parent's regmap\n" ); |
127 | return PTR_ERR(ptr: ts->regmap); |
128 | } |
129 | |
130 | error = of_property_read_u32_index(np, propname: "syscon" , index: 1, out_value: ®); |
131 | if (error < 0) { |
132 | dev_err(dev, "no offset in syscon\n" ); |
133 | return error; |
134 | } |
135 | |
136 | ts->reg = reg; |
137 | |
138 | error = of_property_read_u32_index(np, propname: "syscon" , index: 2, out_value: &bit); |
139 | if (error < 0) { |
140 | dev_err(dev, "no bit in syscon\n" ); |
141 | return error; |
142 | } |
143 | |
144 | ts->bit = BIT(bit); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static int ts4800_ts_probe(struct platform_device *pdev) |
150 | { |
151 | struct input_dev *input_dev; |
152 | struct ts4800_ts *ts; |
153 | int error; |
154 | |
155 | ts = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ts), GFP_KERNEL); |
156 | if (!ts) |
157 | return -ENOMEM; |
158 | |
159 | error = ts4800_parse_dt(pdev, ts); |
160 | if (error) |
161 | return error; |
162 | |
163 | ts->base = devm_platform_ioremap_resource(pdev, index: 0); |
164 | if (IS_ERR(ptr: ts->base)) |
165 | return PTR_ERR(ptr: ts->base); |
166 | |
167 | input_dev = devm_input_allocate_device(&pdev->dev); |
168 | if (!input_dev) |
169 | return -ENOMEM; |
170 | |
171 | snprintf(buf: ts->phys, size: sizeof(ts->phys), fmt: "%s/input0" , dev_name(dev: &pdev->dev)); |
172 | ts->input = input_dev; |
173 | ts->dev = &pdev->dev; |
174 | |
175 | input_set_drvdata(dev: input_dev, data: ts); |
176 | |
177 | input_dev->name = "TS-4800 Touchscreen" ; |
178 | input_dev->phys = ts->phys; |
179 | |
180 | input_dev->open = ts4800_ts_open; |
181 | input_dev->close = ts4800_ts_close; |
182 | |
183 | input_set_capability(dev: input_dev, EV_KEY, BTN_TOUCH); |
184 | input_set_abs_params(dev: input_dev, ABS_X, min: 0, MAX_12BIT, fuzz: 0, flat: 0); |
185 | input_set_abs_params(dev: input_dev, ABS_Y, min: 0, MAX_12BIT, fuzz: 0, flat: 0); |
186 | |
187 | error = input_setup_polling(dev: input_dev, poll_fn: ts4800_ts_poll); |
188 | if (error) { |
189 | dev_err(&pdev->dev, "Unable to set up polling: %d\n" , error); |
190 | return error; |
191 | } |
192 | |
193 | input_set_poll_interval(dev: input_dev, POLL_INTERVAL); |
194 | |
195 | error = input_register_device(input_dev); |
196 | if (error) { |
197 | dev_err(&pdev->dev, |
198 | "Unable to register input device: %d\n" , error); |
199 | return error; |
200 | } |
201 | |
202 | return 0; |
203 | } |
204 | |
205 | static const struct of_device_id ts4800_ts_of_match[] = { |
206 | { .compatible = "technologic,ts4800-ts" , }, |
207 | { }, |
208 | }; |
209 | MODULE_DEVICE_TABLE(of, ts4800_ts_of_match); |
210 | |
211 | static struct platform_driver ts4800_ts_driver = { |
212 | .driver = { |
213 | .name = "ts4800-ts" , |
214 | .of_match_table = ts4800_ts_of_match, |
215 | }, |
216 | .probe = ts4800_ts_probe, |
217 | }; |
218 | module_platform_driver(ts4800_ts_driver); |
219 | |
220 | MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>" ); |
221 | MODULE_DESCRIPTION("TS-4800 Touchscreen Driver" ); |
222 | MODULE_LICENSE("GPL v2" ); |
223 | MODULE_ALIAS("platform:ts4800_ts" ); |
224 | |