Skip to content

Commit a3b9a99

Browse files
havmindgregkh
authored andcommitted
counter: add FlexTimer Module Quadrature decoder counter driver
This driver exposes the counter for the quadrature decoder of the FlexTimer Module, present in the LS1021A soc. Signed-off-by: Patrick Havelange <[email protected]> Signed-off-by: William Breathitt Gray <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 7f2e3ea commit a3b9a99

File tree

3 files changed

+366
-0
lines changed

3 files changed

+366
-0
lines changed

drivers/counter/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,13 @@ config STM32_LPTIMER_CNT
4848
To compile this driver as a module, choose M here: the
4949
module will be called stm32-lptimer-cnt.
5050

51+
config FTM_QUADDEC
52+
tristate "Flex Timer Module Quadrature decoder driver"
53+
help
54+
Select this option to enable the Flex Timer Quadrature decoder
55+
driver.
56+
57+
To compile this driver as a module, choose M here: the
58+
module will be called ftm-quaddec.
59+
5160
endif # COUNTER

drivers/counter/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ obj-$(CONFIG_COUNTER) += counter.o
77
obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
88
obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o
99
obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o
10+
obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o

drivers/counter/ftm-quaddec.c

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Flex Timer Module Quadrature decoder
4+
*
5+
* This module implements a driver for decoding the FTM quadrature
6+
* of ex. a LS1021A
7+
*/
8+
9+
#include <linux/fsl/ftm.h>
10+
#include <linux/module.h>
11+
#include <linux/platform_device.h>
12+
#include <linux/of.h>
13+
#include <linux/io.h>
14+
#include <linux/mutex.h>
15+
#include <linux/counter.h>
16+
#include <linux/bitfield.h>
17+
18+
#define FTM_FIELD_UPDATE(ftm, offset, mask, val) \
19+
({ \
20+
uint32_t flags; \
21+
ftm_read(ftm, offset, &flags); \
22+
flags &= ~mask; \
23+
flags |= FIELD_PREP(mask, val); \
24+
ftm_write(ftm, offset, flags); \
25+
})
26+
27+
struct ftm_quaddec {
28+
struct counter_device counter;
29+
struct platform_device *pdev;
30+
void __iomem *ftm_base;
31+
bool big_endian;
32+
struct mutex ftm_quaddec_mutex;
33+
};
34+
35+
static void ftm_read(struct ftm_quaddec *ftm, uint32_t offset, uint32_t *data)
36+
{
37+
if (ftm->big_endian)
38+
*data = ioread32be(ftm->ftm_base + offset);
39+
else
40+
*data = ioread32(ftm->ftm_base + offset);
41+
}
42+
43+
static void ftm_write(struct ftm_quaddec *ftm, uint32_t offset, uint32_t data)
44+
{
45+
if (ftm->big_endian)
46+
iowrite32be(data, ftm->ftm_base + offset);
47+
else
48+
iowrite32(data, ftm->ftm_base + offset);
49+
}
50+
51+
/* Hold mutex before modifying write protection state */
52+
static void ftm_clear_write_protection(struct ftm_quaddec *ftm)
53+
{
54+
uint32_t flag;
55+
56+
/* First see if it is enabled */
57+
ftm_read(ftm, FTM_FMS, &flag);
58+
59+
if (flag & FTM_FMS_WPEN)
60+
FTM_FIELD_UPDATE(ftm, FTM_MODE, FTM_MODE_WPDIS, 1);
61+
}
62+
63+
static void ftm_set_write_protection(struct ftm_quaddec *ftm)
64+
{
65+
FTM_FIELD_UPDATE(ftm, FTM_FMS, FTM_FMS_WPEN, 1);
66+
}
67+
68+
static void ftm_reset_counter(struct ftm_quaddec *ftm)
69+
{
70+
/* Reset hardware counter to CNTIN */
71+
ftm_write(ftm, FTM_CNT, 0x0);
72+
}
73+
74+
static void ftm_quaddec_init(struct ftm_quaddec *ftm)
75+
{
76+
ftm_clear_write_protection(ftm);
77+
78+
/*
79+
* Do not write in the region from the CNTIN register through the
80+
* PWMLOAD register when FTMEN = 0.
81+
* Also reset other fields to zero
82+
*/
83+
ftm_write(ftm, FTM_MODE, FTM_MODE_FTMEN);
84+
ftm_write(ftm, FTM_CNTIN, 0x0000);
85+
ftm_write(ftm, FTM_MOD, 0xffff);
86+
ftm_write(ftm, FTM_CNT, 0x0);
87+
/* Set prescaler, reset other fields to zero */
88+
ftm_write(ftm, FTM_SC, FTM_SC_PS_1);
89+
90+
/* Select quad mode, reset other fields to zero */
91+
ftm_write(ftm, FTM_QDCTRL, FTM_QDCTRL_QUADEN);
92+
93+
/* Unused features and reset to default section */
94+
ftm_write(ftm, FTM_POL, 0x0);
95+
ftm_write(ftm, FTM_FLTCTRL, 0x0);
96+
ftm_write(ftm, FTM_SYNCONF, 0x0);
97+
ftm_write(ftm, FTM_SYNC, 0xffff);
98+
99+
/* Lock the FTM */
100+
ftm_set_write_protection(ftm);
101+
}
102+
103+
static void ftm_quaddec_disable(struct ftm_quaddec *ftm)
104+
{
105+
ftm_clear_write_protection(ftm);
106+
ftm_write(ftm, FTM_MODE, 0);
107+
ftm_write(ftm, FTM_QDCTRL, 0);
108+
/*
109+
* This is enough to disable the counter. No clock has been
110+
* selected by writing to FTM_SC in init()
111+
*/
112+
ftm_set_write_protection(ftm);
113+
}
114+
115+
static int ftm_quaddec_get_prescaler(struct counter_device *counter,
116+
struct counter_count *count,
117+
size_t *cnt_mode)
118+
{
119+
struct ftm_quaddec *ftm = counter->priv;
120+
uint32_t scflags;
121+
122+
ftm_read(ftm, FTM_SC, &scflags);
123+
124+
*cnt_mode = FIELD_GET(FTM_SC_PS_MASK, scflags);
125+
126+
return 0;
127+
}
128+
129+
static int ftm_quaddec_set_prescaler(struct counter_device *counter,
130+
struct counter_count *count,
131+
size_t cnt_mode)
132+
{
133+
struct ftm_quaddec *ftm = counter->priv;
134+
135+
mutex_lock(&ftm->ftm_quaddec_mutex);
136+
137+
ftm_clear_write_protection(ftm);
138+
FTM_FIELD_UPDATE(ftm, FTM_SC, FTM_SC_PS_MASK, cnt_mode);
139+
ftm_set_write_protection(ftm);
140+
141+
/* Also resets the counter as it is undefined anyway now */
142+
ftm_reset_counter(ftm);
143+
144+
mutex_unlock(&ftm->ftm_quaddec_mutex);
145+
return 0;
146+
}
147+
148+
static const char * const ftm_quaddec_prescaler[] = {
149+
"1", "2", "4", "8", "16", "32", "64", "128"
150+
};
151+
152+
static struct counter_count_enum_ext ftm_quaddec_prescaler_enum = {
153+
.items = ftm_quaddec_prescaler,
154+
.num_items = ARRAY_SIZE(ftm_quaddec_prescaler),
155+
.get = ftm_quaddec_get_prescaler,
156+
.set = ftm_quaddec_set_prescaler
157+
};
158+
159+
enum ftm_quaddec_synapse_action {
160+
FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES,
161+
};
162+
163+
static enum counter_synapse_action ftm_quaddec_synapse_actions[] = {
164+
[FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES] =
165+
COUNTER_SYNAPSE_ACTION_BOTH_EDGES
166+
};
167+
168+
enum ftm_quaddec_count_function {
169+
FTM_QUADDEC_COUNT_ENCODER_MODE_1,
170+
};
171+
172+
static const enum counter_count_function ftm_quaddec_count_functions[] = {
173+
[FTM_QUADDEC_COUNT_ENCODER_MODE_1] =
174+
COUNTER_COUNT_FUNCTION_QUADRATURE_X4
175+
};
176+
177+
static int ftm_quaddec_count_read(struct counter_device *counter,
178+
struct counter_count *count,
179+
struct counter_count_read_value *val)
180+
{
181+
struct ftm_quaddec *const ftm = counter->priv;
182+
uint32_t cntval;
183+
184+
ftm_read(ftm, FTM_CNT, &cntval);
185+
186+
counter_count_read_value_set(val, COUNTER_COUNT_POSITION, &cntval);
187+
188+
return 0;
189+
}
190+
191+
static int ftm_quaddec_count_write(struct counter_device *counter,
192+
struct counter_count *count,
193+
struct counter_count_write_value *val)
194+
{
195+
struct ftm_quaddec *const ftm = counter->priv;
196+
u32 cnt;
197+
int err;
198+
199+
err = counter_count_write_value_get(&cnt, COUNTER_COUNT_POSITION, val);
200+
if (err)
201+
return err;
202+
203+
if (cnt != 0) {
204+
dev_warn(&ftm->pdev->dev, "Can only accept '0' as new counter value\n");
205+
return -EINVAL;
206+
}
207+
208+
ftm_reset_counter(ftm);
209+
210+
return 0;
211+
}
212+
213+
static int ftm_quaddec_count_function_get(struct counter_device *counter,
214+
struct counter_count *count,
215+
size_t *function)
216+
{
217+
*function = FTM_QUADDEC_COUNT_ENCODER_MODE_1;
218+
219+
return 0;
220+
}
221+
222+
static int ftm_quaddec_action_get(struct counter_device *counter,
223+
struct counter_count *count,
224+
struct counter_synapse *synapse,
225+
size_t *action)
226+
{
227+
*action = FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES;
228+
229+
return 0;
230+
}
231+
232+
static const struct counter_ops ftm_quaddec_cnt_ops = {
233+
.count_read = ftm_quaddec_count_read,
234+
.count_write = ftm_quaddec_count_write,
235+
.function_get = ftm_quaddec_count_function_get,
236+
.action_get = ftm_quaddec_action_get,
237+
};
238+
239+
static struct counter_signal ftm_quaddec_signals[] = {
240+
{
241+
.id = 0,
242+
.name = "Channel 1 Phase A"
243+
},
244+
{
245+
.id = 1,
246+
.name = "Channel 1 Phase B"
247+
}
248+
};
249+
250+
static struct counter_synapse ftm_quaddec_count_synapses[] = {
251+
{
252+
.actions_list = ftm_quaddec_synapse_actions,
253+
.num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions),
254+
.signal = &ftm_quaddec_signals[0]
255+
},
256+
{
257+
.actions_list = ftm_quaddec_synapse_actions,
258+
.num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions),
259+
.signal = &ftm_quaddec_signals[1]
260+
}
261+
};
262+
263+
static const struct counter_count_ext ftm_quaddec_count_ext[] = {
264+
COUNTER_COUNT_ENUM("prescaler", &ftm_quaddec_prescaler_enum),
265+
COUNTER_COUNT_ENUM_AVAILABLE("prescaler", &ftm_quaddec_prescaler_enum),
266+
};
267+
268+
static struct counter_count ftm_quaddec_counts = {
269+
.id = 0,
270+
.name = "Channel 1 Count",
271+
.functions_list = ftm_quaddec_count_functions,
272+
.num_functions = ARRAY_SIZE(ftm_quaddec_count_functions),
273+
.synapses = ftm_quaddec_count_synapses,
274+
.num_synapses = ARRAY_SIZE(ftm_quaddec_count_synapses),
275+
.ext = ftm_quaddec_count_ext,
276+
.num_ext = ARRAY_SIZE(ftm_quaddec_count_ext)
277+
};
278+
279+
static int ftm_quaddec_probe(struct platform_device *pdev)
280+
{
281+
struct ftm_quaddec *ftm;
282+
283+
struct device_node *node = pdev->dev.of_node;
284+
struct resource *io;
285+
int ret;
286+
287+
ftm = devm_kzalloc(&pdev->dev, sizeof(*ftm), GFP_KERNEL);
288+
if (!ftm)
289+
return -ENOMEM;
290+
291+
platform_set_drvdata(pdev, ftm);
292+
293+
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
294+
if (!io) {
295+
dev_err(&pdev->dev, "Failed to get memory region\n");
296+
return -ENODEV;
297+
}
298+
299+
ftm->pdev = pdev;
300+
ftm->big_endian = of_property_read_bool(node, "big-endian");
301+
ftm->ftm_base = devm_ioremap(&pdev->dev, io->start, resource_size(io));
302+
303+
if (!ftm->ftm_base) {
304+
dev_err(&pdev->dev, "Failed to map memory region\n");
305+
return -EINVAL;
306+
}
307+
ftm->counter.name = dev_name(&pdev->dev);
308+
ftm->counter.parent = &pdev->dev;
309+
ftm->counter.ops = &ftm_quaddec_cnt_ops;
310+
ftm->counter.counts = &ftm_quaddec_counts;
311+
ftm->counter.num_counts = 1;
312+
ftm->counter.signals = ftm_quaddec_signals;
313+
ftm->counter.num_signals = ARRAY_SIZE(ftm_quaddec_signals);
314+
ftm->counter.priv = ftm;
315+
316+
mutex_init(&ftm->ftm_quaddec_mutex);
317+
318+
ftm_quaddec_init(ftm);
319+
320+
ret = counter_register(&ftm->counter);
321+
if (ret)
322+
ftm_quaddec_disable(ftm);
323+
324+
return ret;
325+
}
326+
327+
static int ftm_quaddec_remove(struct platform_device *pdev)
328+
{
329+
struct ftm_quaddec *ftm = platform_get_drvdata(pdev);
330+
331+
counter_unregister(&ftm->counter);
332+
333+
ftm_quaddec_disable(ftm);
334+
335+
return 0;
336+
}
337+
338+
static const struct of_device_id ftm_quaddec_match[] = {
339+
{ .compatible = "fsl,ftm-quaddec" },
340+
{},
341+
};
342+
343+
static struct platform_driver ftm_quaddec_driver = {
344+
.driver = {
345+
.name = "ftm-quaddec",
346+
.of_match_table = ftm_quaddec_match,
347+
},
348+
.probe = ftm_quaddec_probe,
349+
.remove = ftm_quaddec_remove,
350+
};
351+
352+
module_platform_driver(ftm_quaddec_driver);
353+
354+
MODULE_LICENSE("GPL");
355+
MODULE_AUTHOR("Kjeld Flarup <[email protected]");
356+
MODULE_AUTHOR("Patrick Havelange <[email protected]");

0 commit comments

Comments
 (0)