Commit | Line | Data |
---|---|---|
f213729f DL |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (C) 2019 David Lechner <david@lechnology.com> | |
4 | * | |
5 | * Counter driver for Texas Instruments Enhanced Quadrature Encoder Pulse (eQEP) | |
6 | */ | |
7 | ||
8 | #include <linux/bitops.h> | |
9 | #include <linux/counter.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/mod_devicetable.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/platform_device.h> | |
14 | #include <linux/pm_runtime.h> | |
15 | #include <linux/regmap.h> | |
aaec1a0f | 16 | #include <linux/types.h> |
f213729f DL |
17 | |
18 | /* 32-bit registers */ | |
19 | #define QPOSCNT 0x0 | |
20 | #define QPOSINIT 0x4 | |
21 | #define QPOSMAX 0x8 | |
22 | #define QPOSCMP 0xc | |
23 | #define QPOSILAT 0x10 | |
24 | #define QPOSSLAT 0x14 | |
25 | #define QPOSLAT 0x18 | |
26 | #define QUTMR 0x1c | |
27 | #define QUPRD 0x20 | |
28 | ||
29 | /* 16-bit registers */ | |
30 | #define QWDTMR 0x0 /* 0x24 */ | |
31 | #define QWDPRD 0x2 /* 0x26 */ | |
32 | #define QDECCTL 0x4 /* 0x28 */ | |
33 | #define QEPCTL 0x6 /* 0x2a */ | |
34 | #define QCAPCTL 0x8 /* 0x2c */ | |
35 | #define QPOSCTL 0xa /* 0x2e */ | |
36 | #define QEINT 0xc /* 0x30 */ | |
37 | #define QFLG 0xe /* 0x32 */ | |
38 | #define QCLR 0x10 /* 0x34 */ | |
39 | #define QFRC 0x12 /* 0x36 */ | |
40 | #define QEPSTS 0x14 /* 0x38 */ | |
41 | #define QCTMR 0x16 /* 0x3a */ | |
42 | #define QCPRD 0x18 /* 0x3c */ | |
43 | #define QCTMRLAT 0x1a /* 0x3e */ | |
44 | #define QCPRDLAT 0x1c /* 0x40 */ | |
45 | ||
46 | #define QDECCTL_QSRC_SHIFT 14 | |
47 | #define QDECCTL_QSRC GENMASK(15, 14) | |
48 | #define QDECCTL_SOEN BIT(13) | |
49 | #define QDECCTL_SPSEL BIT(12) | |
50 | #define QDECCTL_XCR BIT(11) | |
51 | #define QDECCTL_SWAP BIT(10) | |
52 | #define QDECCTL_IGATE BIT(9) | |
53 | #define QDECCTL_QAP BIT(8) | |
54 | #define QDECCTL_QBP BIT(7) | |
55 | #define QDECCTL_QIP BIT(6) | |
56 | #define QDECCTL_QSP BIT(5) | |
57 | ||
58 | #define QEPCTL_FREE_SOFT GENMASK(15, 14) | |
59 | #define QEPCTL_PCRM GENMASK(13, 12) | |
60 | #define QEPCTL_SEI GENMASK(11, 10) | |
61 | #define QEPCTL_IEI GENMASK(9, 8) | |
62 | #define QEPCTL_SWI BIT(7) | |
63 | #define QEPCTL_SEL BIT(6) | |
64 | #define QEPCTL_IEL GENMASK(5, 4) | |
65 | #define QEPCTL_PHEN BIT(3) | |
66 | #define QEPCTL_QCLM BIT(2) | |
67 | #define QEPCTL_UTE BIT(1) | |
68 | #define QEPCTL_WDE BIT(0) | |
69 | ||
70 | /* EQEP Inputs */ | |
71 | enum { | |
72 | TI_EQEP_SIGNAL_QEPA, /* QEPA/XCLK */ | |
73 | TI_EQEP_SIGNAL_QEPB, /* QEPB/XDIR */ | |
74 | }; | |
75 | ||
76 | /* Position Counter Input Modes */ | |
aaec1a0f | 77 | enum ti_eqep_count_func { |
f213729f DL |
78 | TI_EQEP_COUNT_FUNC_QUAD_COUNT, |
79 | TI_EQEP_COUNT_FUNC_DIR_COUNT, | |
80 | TI_EQEP_COUNT_FUNC_UP_COUNT, | |
81 | TI_EQEP_COUNT_FUNC_DOWN_COUNT, | |
82 | }; | |
83 | ||
f213729f DL |
84 | struct ti_eqep_cnt { |
85 | struct counter_device counter; | |
86 | struct regmap *regmap32; | |
87 | struct regmap *regmap16; | |
88 | }; | |
89 | ||
60f07e74 UKK |
90 | static struct ti_eqep_cnt *ti_eqep_count_from_counter(struct counter_device *counter) |
91 | { | |
8817c2d0 | 92 | return counter_priv(counter); |
60f07e74 UKK |
93 | } |
94 | ||
f213729f | 95 | static int ti_eqep_count_read(struct counter_device *counter, |
aaec1a0f | 96 | struct counter_count *count, u64 *val) |
f213729f | 97 | { |
60f07e74 | 98 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f DL |
99 | u32 cnt; |
100 | ||
101 | regmap_read(priv->regmap32, QPOSCNT, &cnt); | |
d49e6ee2 | 102 | *val = cnt; |
f213729f DL |
103 | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static int ti_eqep_count_write(struct counter_device *counter, | |
aaec1a0f | 108 | struct counter_count *count, u64 val) |
f213729f | 109 | { |
60f07e74 | 110 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
d49e6ee2 | 111 | u32 max; |
f213729f DL |
112 | |
113 | regmap_read(priv->regmap32, QPOSMAX, &max); | |
d49e6ee2 | 114 | if (val > max) |
f213729f DL |
115 | return -EINVAL; |
116 | ||
d49e6ee2 | 117 | return regmap_write(priv->regmap32, QPOSCNT, val); |
f213729f DL |
118 | } |
119 | ||
aaec1a0f WBG |
120 | static int ti_eqep_function_read(struct counter_device *counter, |
121 | struct counter_count *count, | |
122 | enum counter_function *function) | |
f213729f | 123 | { |
60f07e74 | 124 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f DL |
125 | u32 qdecctl; |
126 | ||
127 | regmap_read(priv->regmap16, QDECCTL, &qdecctl); | |
aaec1a0f WBG |
128 | |
129 | switch ((qdecctl & QDECCTL_QSRC) >> QDECCTL_QSRC_SHIFT) { | |
130 | case TI_EQEP_COUNT_FUNC_QUAD_COUNT: | |
131 | *function = COUNTER_FUNCTION_QUADRATURE_X4; | |
132 | break; | |
133 | case TI_EQEP_COUNT_FUNC_DIR_COUNT: | |
134 | *function = COUNTER_FUNCTION_PULSE_DIRECTION; | |
135 | break; | |
136 | case TI_EQEP_COUNT_FUNC_UP_COUNT: | |
137 | *function = COUNTER_FUNCTION_INCREASE; | |
138 | break; | |
139 | case TI_EQEP_COUNT_FUNC_DOWN_COUNT: | |
140 | *function = COUNTER_FUNCTION_DECREASE; | |
141 | break; | |
142 | } | |
f213729f DL |
143 | |
144 | return 0; | |
145 | } | |
146 | ||
aaec1a0f WBG |
147 | static int ti_eqep_function_write(struct counter_device *counter, |
148 | struct counter_count *count, | |
149 | enum counter_function function) | |
f213729f | 150 | { |
60f07e74 | 151 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
aaec1a0f WBG |
152 | enum ti_eqep_count_func qsrc; |
153 | ||
154 | switch (function) { | |
155 | case COUNTER_FUNCTION_QUADRATURE_X4: | |
156 | qsrc = TI_EQEP_COUNT_FUNC_QUAD_COUNT; | |
157 | break; | |
158 | case COUNTER_FUNCTION_PULSE_DIRECTION: | |
159 | qsrc = TI_EQEP_COUNT_FUNC_DIR_COUNT; | |
160 | break; | |
161 | case COUNTER_FUNCTION_INCREASE: | |
162 | qsrc = TI_EQEP_COUNT_FUNC_UP_COUNT; | |
163 | break; | |
164 | case COUNTER_FUNCTION_DECREASE: | |
165 | qsrc = TI_EQEP_COUNT_FUNC_DOWN_COUNT; | |
166 | break; | |
167 | default: | |
168 | /* should never reach this path */ | |
169 | return -EINVAL; | |
170 | } | |
f213729f DL |
171 | |
172 | return regmap_write_bits(priv->regmap16, QDECCTL, QDECCTL_QSRC, | |
aaec1a0f | 173 | qsrc << QDECCTL_QSRC_SHIFT); |
f213729f DL |
174 | } |
175 | ||
aaec1a0f WBG |
176 | static int ti_eqep_action_read(struct counter_device *counter, |
177 | struct counter_count *count, | |
178 | struct counter_synapse *synapse, | |
179 | enum counter_synapse_action *action) | |
f213729f | 180 | { |
60f07e74 | 181 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
aaec1a0f | 182 | enum counter_function function; |
f213729f DL |
183 | u32 qdecctl; |
184 | int err; | |
185 | ||
aaec1a0f | 186 | err = ti_eqep_function_read(counter, count, &function); |
f213729f DL |
187 | if (err) |
188 | return err; | |
189 | ||
190 | switch (function) { | |
aaec1a0f | 191 | case COUNTER_FUNCTION_QUADRATURE_X4: |
f213729f DL |
192 | /* In quadrature mode, the rising and falling edge of both |
193 | * QEPA and QEPB trigger QCLK. | |
194 | */ | |
aaec1a0f | 195 | *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; |
b11eed15 | 196 | return 0; |
aaec1a0f | 197 | case COUNTER_FUNCTION_PULSE_DIRECTION: |
f213729f DL |
198 | /* In direction-count mode only rising edge of QEPA is counted |
199 | * and QEPB gives direction. | |
200 | */ | |
201 | switch (synapse->signal->id) { | |
202 | case TI_EQEP_SIGNAL_QEPA: | |
aaec1a0f | 203 | *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE; |
b11eed15 WBG |
204 | return 0; |
205 | case TI_EQEP_SIGNAL_QEPB: | |
aaec1a0f | 206 | *action = COUNTER_SYNAPSE_ACTION_NONE; |
b11eed15 WBG |
207 | return 0; |
208 | default: | |
209 | /* should never reach this path */ | |
210 | return -EINVAL; | |
f213729f | 211 | } |
aaec1a0f WBG |
212 | case COUNTER_FUNCTION_INCREASE: |
213 | case COUNTER_FUNCTION_DECREASE: | |
f213729f DL |
214 | /* In up/down-count modes only QEPA is counted and QEPB is not |
215 | * used. | |
216 | */ | |
217 | switch (synapse->signal->id) { | |
218 | case TI_EQEP_SIGNAL_QEPA: | |
219 | err = regmap_read(priv->regmap16, QDECCTL, &qdecctl); | |
220 | if (err) | |
221 | return err; | |
222 | ||
223 | if (qdecctl & QDECCTL_XCR) | |
aaec1a0f | 224 | *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; |
f213729f | 225 | else |
aaec1a0f | 226 | *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE; |
b11eed15 WBG |
227 | return 0; |
228 | case TI_EQEP_SIGNAL_QEPB: | |
aaec1a0f | 229 | *action = COUNTER_SYNAPSE_ACTION_NONE; |
b11eed15 WBG |
230 | return 0; |
231 | default: | |
232 | /* should never reach this path */ | |
233 | return -EINVAL; | |
f213729f | 234 | } |
b11eed15 WBG |
235 | default: |
236 | /* should never reach this path */ | |
237 | return -EINVAL; | |
f213729f | 238 | } |
f213729f DL |
239 | } |
240 | ||
241 | static const struct counter_ops ti_eqep_counter_ops = { | |
242 | .count_read = ti_eqep_count_read, | |
243 | .count_write = ti_eqep_count_write, | |
aaec1a0f WBG |
244 | .function_read = ti_eqep_function_read, |
245 | .function_write = ti_eqep_function_write, | |
246 | .action_read = ti_eqep_action_read, | |
f213729f DL |
247 | }; |
248 | ||
aaec1a0f WBG |
249 | static int ti_eqep_position_ceiling_read(struct counter_device *counter, |
250 | struct counter_count *count, | |
251 | u64 *ceiling) | |
f213729f | 252 | { |
60f07e74 | 253 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f DL |
254 | u32 qposmax; |
255 | ||
256 | regmap_read(priv->regmap32, QPOSMAX, &qposmax); | |
257 | ||
aaec1a0f WBG |
258 | *ceiling = qposmax; |
259 | ||
260 | return 0; | |
f213729f DL |
261 | } |
262 | ||
aaec1a0f WBG |
263 | static int ti_eqep_position_ceiling_write(struct counter_device *counter, |
264 | struct counter_count *count, | |
265 | u64 ceiling) | |
f213729f | 266 | { |
60f07e74 | 267 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f | 268 | |
aaec1a0f WBG |
269 | if (ceiling != (u32)ceiling) |
270 | return -ERANGE; | |
f213729f | 271 | |
aaec1a0f | 272 | regmap_write(priv->regmap32, QPOSMAX, ceiling); |
f213729f | 273 | |
aaec1a0f | 274 | return 0; |
f213729f DL |
275 | } |
276 | ||
aaec1a0f WBG |
277 | static int ti_eqep_position_enable_read(struct counter_device *counter, |
278 | struct counter_count *count, u8 *enable) | |
f213729f | 279 | { |
60f07e74 | 280 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f DL |
281 | u32 qepctl; |
282 | ||
283 | regmap_read(priv->regmap16, QEPCTL, &qepctl); | |
284 | ||
aaec1a0f WBG |
285 | *enable = !!(qepctl & QEPCTL_PHEN); |
286 | ||
287 | return 0; | |
f213729f DL |
288 | } |
289 | ||
aaec1a0f WBG |
290 | static int ti_eqep_position_enable_write(struct counter_device *counter, |
291 | struct counter_count *count, u8 enable) | |
f213729f | 292 | { |
60f07e74 | 293 | struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter); |
f213729f | 294 | |
aaec1a0f | 295 | regmap_write_bits(priv->regmap16, QEPCTL, QEPCTL_PHEN, enable ? -1 : 0); |
f213729f | 296 | |
aaec1a0f | 297 | return 0; |
f213729f DL |
298 | } |
299 | ||
aaec1a0f WBG |
300 | static struct counter_comp ti_eqep_position_ext[] = { |
301 | COUNTER_COMP_CEILING(ti_eqep_position_ceiling_read, | |
302 | ti_eqep_position_ceiling_write), | |
303 | COUNTER_COMP_ENABLE(ti_eqep_position_enable_read, | |
304 | ti_eqep_position_enable_write), | |
f213729f DL |
305 | }; |
306 | ||
307 | static struct counter_signal ti_eqep_signals[] = { | |
308 | [TI_EQEP_SIGNAL_QEPA] = { | |
309 | .id = TI_EQEP_SIGNAL_QEPA, | |
310 | .name = "QEPA" | |
311 | }, | |
312 | [TI_EQEP_SIGNAL_QEPB] = { | |
313 | .id = TI_EQEP_SIGNAL_QEPB, | |
314 | .name = "QEPB" | |
315 | }, | |
316 | }; | |
317 | ||
394a0150 | 318 | static const enum counter_function ti_eqep_position_functions[] = { |
aaec1a0f WBG |
319 | COUNTER_FUNCTION_QUADRATURE_X4, |
320 | COUNTER_FUNCTION_PULSE_DIRECTION, | |
321 | COUNTER_FUNCTION_INCREASE, | |
322 | COUNTER_FUNCTION_DECREASE, | |
f213729f DL |
323 | }; |
324 | ||
325 | static const enum counter_synapse_action ti_eqep_position_synapse_actions[] = { | |
aaec1a0f WBG |
326 | COUNTER_SYNAPSE_ACTION_BOTH_EDGES, |
327 | COUNTER_SYNAPSE_ACTION_RISING_EDGE, | |
328 | COUNTER_SYNAPSE_ACTION_NONE, | |
f213729f DL |
329 | }; |
330 | ||
331 | static struct counter_synapse ti_eqep_position_synapses[] = { | |
332 | { | |
333 | .actions_list = ti_eqep_position_synapse_actions, | |
334 | .num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions), | |
335 | .signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPA], | |
336 | }, | |
337 | { | |
338 | .actions_list = ti_eqep_position_synapse_actions, | |
339 | .num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions), | |
340 | .signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPB], | |
341 | }, | |
342 | }; | |
343 | ||
344 | static struct counter_count ti_eqep_counts[] = { | |
345 | { | |
346 | .id = 0, | |
347 | .name = "QPOSCNT", | |
348 | .functions_list = ti_eqep_position_functions, | |
349 | .num_functions = ARRAY_SIZE(ti_eqep_position_functions), | |
350 | .synapses = ti_eqep_position_synapses, | |
351 | .num_synapses = ARRAY_SIZE(ti_eqep_position_synapses), | |
352 | .ext = ti_eqep_position_ext, | |
353 | .num_ext = ARRAY_SIZE(ti_eqep_position_ext), | |
354 | }, | |
355 | }; | |
356 | ||
357 | static const struct regmap_config ti_eqep_regmap32_config = { | |
358 | .name = "32-bit", | |
359 | .reg_bits = 32, | |
360 | .val_bits = 32, | |
361 | .reg_stride = 4, | |
271b3392 | 362 | .max_register = QUPRD, |
f213729f DL |
363 | }; |
364 | ||
365 | static const struct regmap_config ti_eqep_regmap16_config = { | |
366 | .name = "16-bit", | |
367 | .reg_bits = 16, | |
368 | .val_bits = 16, | |
369 | .reg_stride = 2, | |
271b3392 | 370 | .max_register = QCPRDLAT, |
f213729f DL |
371 | }; |
372 | ||
373 | static int ti_eqep_probe(struct platform_device *pdev) | |
374 | { | |
375 | struct device *dev = &pdev->dev; | |
02758cb2 | 376 | struct counter_device *counter; |
f213729f DL |
377 | struct ti_eqep_cnt *priv; |
378 | void __iomem *base; | |
379 | int err; | |
380 | ||
02758cb2 UKK |
381 | counter = devm_counter_alloc(dev, sizeof(*priv)); |
382 | if (!counter) | |
f213729f | 383 | return -ENOMEM; |
02758cb2 | 384 | priv = counter_priv(counter); |
f213729f DL |
385 | |
386 | base = devm_platform_ioremap_resource(pdev, 0); | |
387 | if (IS_ERR(base)) | |
388 | return PTR_ERR(base); | |
389 | ||
390 | priv->regmap32 = devm_regmap_init_mmio(dev, base, | |
391 | &ti_eqep_regmap32_config); | |
392 | if (IS_ERR(priv->regmap32)) | |
393 | return PTR_ERR(priv->regmap32); | |
394 | ||
395 | priv->regmap16 = devm_regmap_init_mmio(dev, base + 0x24, | |
396 | &ti_eqep_regmap16_config); | |
397 | if (IS_ERR(priv->regmap16)) | |
398 | return PTR_ERR(priv->regmap16); | |
399 | ||
02758cb2 UKK |
400 | counter->name = dev_name(dev); |
401 | counter->parent = dev; | |
402 | counter->ops = &ti_eqep_counter_ops; | |
403 | counter->counts = ti_eqep_counts; | |
404 | counter->num_counts = ARRAY_SIZE(ti_eqep_counts); | |
405 | counter->signals = ti_eqep_signals; | |
406 | counter->num_signals = ARRAY_SIZE(ti_eqep_signals); | |
f213729f | 407 | |
02758cb2 | 408 | platform_set_drvdata(pdev, counter); |
f213729f DL |
409 | |
410 | /* | |
411 | * Need to make sure power is turned on. On AM33xx, this comes from the | |
412 | * parent PWMSS bus driver. On AM17xx, this comes from the PSC power | |
413 | * domain. | |
414 | */ | |
415 | pm_runtime_enable(dev); | |
416 | pm_runtime_get_sync(dev); | |
417 | ||
02758cb2 | 418 | err = counter_add(counter); |
f213729f DL |
419 | if (err < 0) { |
420 | pm_runtime_put_sync(dev); | |
421 | pm_runtime_disable(dev); | |
422 | return err; | |
423 | } | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
428 | static int ti_eqep_remove(struct platform_device *pdev) | |
429 | { | |
02758cb2 | 430 | struct counter_device *counter = platform_get_drvdata(pdev); |
f213729f DL |
431 | struct device *dev = &pdev->dev; |
432 | ||
02758cb2 | 433 | counter_unregister(counter); |
bbbeac88 | 434 | pm_runtime_put_sync(dev); |
f213729f DL |
435 | pm_runtime_disable(dev); |
436 | ||
437 | return 0; | |
438 | } | |
439 | ||
440 | static const struct of_device_id ti_eqep_of_match[] = { | |
441 | { .compatible = "ti,am3352-eqep", }, | |
442 | { }, | |
443 | }; | |
444 | MODULE_DEVICE_TABLE(of, ti_eqep_of_match); | |
445 | ||
446 | static struct platform_driver ti_eqep_driver = { | |
447 | .probe = ti_eqep_probe, | |
448 | .remove = ti_eqep_remove, | |
449 | .driver = { | |
450 | .name = "ti-eqep-cnt", | |
451 | .of_match_table = ti_eqep_of_match, | |
452 | }, | |
453 | }; | |
454 | module_platform_driver(ti_eqep_driver); | |
455 | ||
456 | MODULE_AUTHOR("David Lechner <david@lechnology.com>"); | |
457 | MODULE_DESCRIPTION("TI eQEP counter driver"); | |
458 | MODULE_LICENSE("GPL v2"); | |
3216e551 | 459 | MODULE_IMPORT_NS(COUNTER); |