1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Intel 8254 Programmable Interval Timer |
4 | * Copyright (C) William Breathitt Gray |
5 | */ |
6 | #include <linux/bitfield.h> |
7 | #include <linux/bits.h> |
8 | #include <linux/counter.h> |
9 | #include <linux/device.h> |
10 | #include <linux/err.h> |
11 | #include <linux/export.h> |
12 | #include <linux/i8254.h> |
13 | #include <linux/limits.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/regmap.h> |
17 | |
18 | #include <asm/unaligned.h> |
19 | |
20 | #define I8254_COUNTER_REG(_counter) (_counter) |
21 | #define I8254_CONTROL_REG 0x3 |
22 | |
23 | #define I8254_SC GENMASK(7, 6) |
24 | #define I8254_RW GENMASK(5, 4) |
25 | #define I8254_M GENMASK(3, 1) |
26 | #define I8254_CONTROL(_sc, _rw, _m) \ |
27 | (u8_encode_bits(_sc, I8254_SC) | u8_encode_bits(_rw, I8254_RW) | \ |
28 | u8_encode_bits(_m, I8254_M)) |
29 | |
30 | #define I8254_RW_TWO_BYTE 0x3 |
31 | #define I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT 0 |
32 | #define I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT 1 |
33 | #define I8254_MODE_RATE_GENERATOR 2 |
34 | #define I8254_MODE_SQUARE_WAVE_MODE 3 |
35 | #define I8254_MODE_SOFTWARE_TRIGGERED_STROBE 4 |
36 | #define I8254_MODE_HARDWARE_TRIGGERED_STROBE 5 |
37 | |
38 | #define I8254_COUNTER_LATCH(_counter) I8254_CONTROL(_counter, 0x0, 0x0) |
39 | #define I8254_PROGRAM_COUNTER(_counter, _mode) I8254_CONTROL(_counter, I8254_RW_TWO_BYTE, _mode) |
40 | |
41 | #define I8254_NUM_COUNTERS 3 |
42 | |
43 | /** |
44 | * struct i8254 - I8254 device private data structure |
45 | * @lock: synchronization lock to prevent I/O race conditions |
46 | * @preset: array of Counter Register states |
47 | * @out_mode: array of mode configuration states |
48 | * @map: Regmap for the device |
49 | */ |
50 | struct i8254 { |
51 | struct mutex lock; |
52 | u16 preset[I8254_NUM_COUNTERS]; |
53 | u8 out_mode[I8254_NUM_COUNTERS]; |
54 | struct regmap *map; |
55 | }; |
56 | |
57 | static int i8254_count_read(struct counter_device *const counter, struct counter_count *const count, |
58 | u64 *const val) |
59 | { |
60 | struct i8254 *const priv = counter_priv(counter); |
61 | int ret; |
62 | u8 value[2]; |
63 | |
64 | mutex_lock(&priv->lock); |
65 | |
66 | ret = regmap_write(map: priv->map, I8254_CONTROL_REG, I8254_COUNTER_LATCH(count->id)); |
67 | if (ret) { |
68 | mutex_unlock(lock: &priv->lock); |
69 | return ret; |
70 | } |
71 | ret = regmap_noinc_read(map: priv->map, I8254_COUNTER_REG(count->id), val: value, val_len: sizeof(value)); |
72 | if (ret) { |
73 | mutex_unlock(lock: &priv->lock); |
74 | return ret; |
75 | } |
76 | |
77 | mutex_unlock(lock: &priv->lock); |
78 | |
79 | *val = get_unaligned_le16(p: value); |
80 | |
81 | return ret; |
82 | } |
83 | |
84 | static int i8254_function_read(struct counter_device *const counter, |
85 | struct counter_count *const count, |
86 | enum counter_function *const function) |
87 | { |
88 | *function = COUNTER_FUNCTION_DECREASE; |
89 | return 0; |
90 | } |
91 | |
92 | #define I8254_SYNAPSES_PER_COUNT 2 |
93 | #define I8254_SIGNAL_ID_CLK 0 |
94 | #define I8254_SIGNAL_ID_GATE 1 |
95 | |
96 | static int i8254_action_read(struct counter_device *const counter, |
97 | struct counter_count *const count, |
98 | struct counter_synapse *const synapse, |
99 | enum counter_synapse_action *const action) |
100 | { |
101 | struct i8254 *const priv = counter_priv(counter); |
102 | |
103 | switch (synapse->signal->id % I8254_SYNAPSES_PER_COUNT) { |
104 | case I8254_SIGNAL_ID_CLK: |
105 | *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE; |
106 | return 0; |
107 | case I8254_SIGNAL_ID_GATE: |
108 | switch (priv->out_mode[count->id]) { |
109 | case I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT: |
110 | case I8254_MODE_RATE_GENERATOR: |
111 | case I8254_MODE_SQUARE_WAVE_MODE: |
112 | case I8254_MODE_HARDWARE_TRIGGERED_STROBE: |
113 | *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE; |
114 | return 0; |
115 | default: |
116 | *action = COUNTER_SYNAPSE_ACTION_NONE; |
117 | return 0; |
118 | } |
119 | default: |
120 | /* should never reach this path */ |
121 | return -EINVAL; |
122 | } |
123 | } |
124 | |
125 | static int i8254_count_ceiling_read(struct counter_device *const counter, |
126 | struct counter_count *const count, u64 *const ceiling) |
127 | { |
128 | struct i8254 *const priv = counter_priv(counter); |
129 | |
130 | mutex_lock(&priv->lock); |
131 | |
132 | switch (priv->out_mode[count->id]) { |
133 | case I8254_MODE_RATE_GENERATOR: |
134 | /* Rate Generator decrements 0 by one and the counter "wraps around" */ |
135 | *ceiling = (priv->preset[count->id] == 0) ? U16_MAX : priv->preset[count->id]; |
136 | break; |
137 | case I8254_MODE_SQUARE_WAVE_MODE: |
138 | if (priv->preset[count->id] % 2) |
139 | *ceiling = priv->preset[count->id] - 1; |
140 | else if (priv->preset[count->id] == 0) |
141 | /* Square Wave Mode decrements 0 by two and the counter "wraps around" */ |
142 | *ceiling = U16_MAX - 1; |
143 | else |
144 | *ceiling = priv->preset[count->id]; |
145 | break; |
146 | default: |
147 | *ceiling = U16_MAX; |
148 | break; |
149 | } |
150 | |
151 | mutex_unlock(lock: &priv->lock); |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | static int i8254_count_mode_read(struct counter_device *const counter, |
157 | struct counter_count *const count, |
158 | enum counter_count_mode *const count_mode) |
159 | { |
160 | const struct i8254 *const priv = counter_priv(counter); |
161 | |
162 | switch (priv->out_mode[count->id]) { |
163 | case I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT: |
164 | *count_mode = COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT; |
165 | return 0; |
166 | case I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT: |
167 | *count_mode = COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT; |
168 | return 0; |
169 | case I8254_MODE_RATE_GENERATOR: |
170 | *count_mode = COUNTER_COUNT_MODE_RATE_GENERATOR; |
171 | return 0; |
172 | case I8254_MODE_SQUARE_WAVE_MODE: |
173 | *count_mode = COUNTER_COUNT_MODE_SQUARE_WAVE_MODE; |
174 | return 0; |
175 | case I8254_MODE_SOFTWARE_TRIGGERED_STROBE: |
176 | *count_mode = COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE; |
177 | return 0; |
178 | case I8254_MODE_HARDWARE_TRIGGERED_STROBE: |
179 | *count_mode = COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE; |
180 | return 0; |
181 | default: |
182 | /* should never reach this path */ |
183 | return -EINVAL; |
184 | } |
185 | } |
186 | |
187 | static int i8254_count_mode_write(struct counter_device *const counter, |
188 | struct counter_count *const count, |
189 | const enum counter_count_mode count_mode) |
190 | { |
191 | struct i8254 *const priv = counter_priv(counter); |
192 | u8 out_mode; |
193 | int ret; |
194 | |
195 | switch (count_mode) { |
196 | case COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT: |
197 | out_mode = I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT; |
198 | break; |
199 | case COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT: |
200 | out_mode = I8254_MODE_HARDWARE_RETRIGGERABLE_ONESHOT; |
201 | break; |
202 | case COUNTER_COUNT_MODE_RATE_GENERATOR: |
203 | out_mode = I8254_MODE_RATE_GENERATOR; |
204 | break; |
205 | case COUNTER_COUNT_MODE_SQUARE_WAVE_MODE: |
206 | out_mode = I8254_MODE_SQUARE_WAVE_MODE; |
207 | break; |
208 | case COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE: |
209 | out_mode = I8254_MODE_SOFTWARE_TRIGGERED_STROBE; |
210 | break; |
211 | case COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE: |
212 | out_mode = I8254_MODE_HARDWARE_TRIGGERED_STROBE; |
213 | break; |
214 | default: |
215 | /* should never reach this path */ |
216 | return -EINVAL; |
217 | } |
218 | |
219 | mutex_lock(&priv->lock); |
220 | |
221 | /* Counter Register is cleared when the counter is programmed */ |
222 | priv->preset[count->id] = 0; |
223 | priv->out_mode[count->id] = out_mode; |
224 | ret = regmap_write(map: priv->map, I8254_CONTROL_REG, |
225 | I8254_PROGRAM_COUNTER(count->id, out_mode)); |
226 | |
227 | mutex_unlock(lock: &priv->lock); |
228 | |
229 | return ret; |
230 | } |
231 | |
232 | static int i8254_count_floor_read(struct counter_device *const counter, |
233 | struct counter_count *const count, u64 *const floor) |
234 | { |
235 | struct i8254 *const priv = counter_priv(counter); |
236 | |
237 | mutex_lock(&priv->lock); |
238 | |
239 | switch (priv->out_mode[count->id]) { |
240 | case I8254_MODE_RATE_GENERATOR: |
241 | /* counter is always reloaded after 1, but 0 is a possible reload value */ |
242 | *floor = (priv->preset[count->id] == 0) ? 0 : 1; |
243 | break; |
244 | case I8254_MODE_SQUARE_WAVE_MODE: |
245 | /* counter is always reloaded after 2 for even preset values */ |
246 | *floor = (priv->preset[count->id] % 2 || priv->preset[count->id] == 0) ? 0 : 2; |
247 | break; |
248 | default: |
249 | *floor = 0; |
250 | break; |
251 | } |
252 | |
253 | mutex_unlock(lock: &priv->lock); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | static int i8254_count_preset_read(struct counter_device *const counter, |
259 | struct counter_count *const count, u64 *const preset) |
260 | { |
261 | const struct i8254 *const priv = counter_priv(counter); |
262 | |
263 | *preset = priv->preset[count->id]; |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | static int i8254_count_preset_write(struct counter_device *const counter, |
269 | struct counter_count *const count, const u64 preset) |
270 | { |
271 | struct i8254 *const priv = counter_priv(counter); |
272 | int ret; |
273 | u8 value[2]; |
274 | |
275 | if (preset > U16_MAX) |
276 | return -ERANGE; |
277 | |
278 | mutex_lock(&priv->lock); |
279 | |
280 | if (priv->out_mode[count->id] == I8254_MODE_RATE_GENERATOR || |
281 | priv->out_mode[count->id] == I8254_MODE_SQUARE_WAVE_MODE) { |
282 | if (preset == 1) { |
283 | mutex_unlock(lock: &priv->lock); |
284 | return -EINVAL; |
285 | } |
286 | } |
287 | |
288 | priv->preset[count->id] = preset; |
289 | |
290 | put_unaligned_le16(val: preset, p: value); |
291 | ret = regmap_noinc_write(map: priv->map, I8254_COUNTER_REG(count->id), val: value, val_len: 2); |
292 | |
293 | mutex_unlock(lock: &priv->lock); |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | static int i8254_init_hw(struct regmap *const map) |
299 | { |
300 | unsigned long i; |
301 | int ret; |
302 | |
303 | for (i = 0; i < I8254_NUM_COUNTERS; i++) { |
304 | /* Initialize each counter to Mode 0 */ |
305 | ret = regmap_write(map, I8254_CONTROL_REG, |
306 | I8254_PROGRAM_COUNTER(i, I8254_MODE_INTERRUPT_ON_TERMINAL_COUNT)); |
307 | if (ret) |
308 | return ret; |
309 | } |
310 | |
311 | return 0; |
312 | } |
313 | |
314 | static const struct counter_ops i8254_ops = { |
315 | .count_read = i8254_count_read, |
316 | .function_read = i8254_function_read, |
317 | .action_read = i8254_action_read, |
318 | }; |
319 | |
320 | #define I8254_SIGNAL(_id, _name) { \ |
321 | .id = (_id), \ |
322 | .name = (_name), \ |
323 | } |
324 | |
325 | static struct counter_signal i8254_signals[] = { |
326 | I8254_SIGNAL(0, "CLK 0" ), I8254_SIGNAL(1, "GATE 0" ), |
327 | I8254_SIGNAL(2, "CLK 1" ), I8254_SIGNAL(3, "GATE 1" ), |
328 | I8254_SIGNAL(4, "CLK 2" ), I8254_SIGNAL(5, "GATE 2" ), |
329 | }; |
330 | |
331 | static const enum counter_synapse_action i8254_clk_actions[] = { |
332 | COUNTER_SYNAPSE_ACTION_FALLING_EDGE, |
333 | }; |
334 | static const enum counter_synapse_action i8254_gate_actions[] = { |
335 | COUNTER_SYNAPSE_ACTION_NONE, |
336 | COUNTER_SYNAPSE_ACTION_RISING_EDGE, |
337 | }; |
338 | |
339 | #define I8254_SYNAPSES_BASE(_id) ((_id) * I8254_SYNAPSES_PER_COUNT) |
340 | #define I8254_SYNAPSE_CLK(_id) { \ |
341 | .actions_list = i8254_clk_actions, \ |
342 | .num_actions = ARRAY_SIZE(i8254_clk_actions), \ |
343 | .signal = &i8254_signals[I8254_SYNAPSES_BASE(_id) + 0], \ |
344 | } |
345 | #define I8254_SYNAPSE_GATE(_id) { \ |
346 | .actions_list = i8254_gate_actions, \ |
347 | .num_actions = ARRAY_SIZE(i8254_gate_actions), \ |
348 | .signal = &i8254_signals[I8254_SYNAPSES_BASE(_id) + 1], \ |
349 | } |
350 | |
351 | static struct counter_synapse i8254_synapses[] = { |
352 | I8254_SYNAPSE_CLK(0), I8254_SYNAPSE_GATE(0), |
353 | I8254_SYNAPSE_CLK(1), I8254_SYNAPSE_GATE(1), |
354 | I8254_SYNAPSE_CLK(2), I8254_SYNAPSE_GATE(2), |
355 | }; |
356 | |
357 | static const enum counter_function i8254_functions_list[] = { |
358 | COUNTER_FUNCTION_DECREASE, |
359 | }; |
360 | |
361 | static const enum counter_count_mode i8254_count_modes[] = { |
362 | COUNTER_COUNT_MODE_INTERRUPT_ON_TERMINAL_COUNT, |
363 | COUNTER_COUNT_MODE_HARDWARE_RETRIGGERABLE_ONESHOT, |
364 | COUNTER_COUNT_MODE_RATE_GENERATOR, |
365 | COUNTER_COUNT_MODE_SQUARE_WAVE_MODE, |
366 | COUNTER_COUNT_MODE_SOFTWARE_TRIGGERED_STROBE, |
367 | COUNTER_COUNT_MODE_HARDWARE_TRIGGERED_STROBE, |
368 | }; |
369 | |
370 | static DEFINE_COUNTER_AVAILABLE(i8254_count_modes_available, i8254_count_modes); |
371 | |
372 | static struct counter_comp i8254_count_ext[] = { |
373 | COUNTER_COMP_CEILING(i8254_count_ceiling_read, NULL), |
374 | COUNTER_COMP_COUNT_MODE(i8254_count_mode_read, i8254_count_mode_write, |
375 | i8254_count_modes_available), |
376 | COUNTER_COMP_FLOOR(i8254_count_floor_read, NULL), |
377 | COUNTER_COMP_PRESET(i8254_count_preset_read, i8254_count_preset_write), |
378 | }; |
379 | |
380 | #define I8254_COUNT(_id, _name) { \ |
381 | .id = (_id), \ |
382 | .name = (_name), \ |
383 | .functions_list = i8254_functions_list, \ |
384 | .num_functions = ARRAY_SIZE(i8254_functions_list), \ |
385 | .synapses = &i8254_synapses[I8254_SYNAPSES_BASE(_id)], \ |
386 | .num_synapses = I8254_SYNAPSES_PER_COUNT, \ |
387 | .ext = i8254_count_ext, \ |
388 | .num_ext = ARRAY_SIZE(i8254_count_ext) \ |
389 | } |
390 | |
391 | static struct counter_count i8254_counts[I8254_NUM_COUNTERS] = { |
392 | I8254_COUNT(0, "Counter 0" ), I8254_COUNT(1, "Counter 1" ), I8254_COUNT(2, "Counter 2" ), |
393 | }; |
394 | |
395 | /** |
396 | * devm_i8254_regmap_register - Register an i8254 Counter device |
397 | * @dev: device that is registering this i8254 Counter device |
398 | * @config: configuration for i8254_regmap_config |
399 | * |
400 | * Registers an Intel 8254 Programmable Interval Timer Counter device. Returns 0 on success and |
401 | * negative error number on failure. |
402 | */ |
403 | int devm_i8254_regmap_register(struct device *const dev, |
404 | const struct i8254_regmap_config *const config) |
405 | { |
406 | struct counter_device *counter; |
407 | struct i8254 *priv; |
408 | int err; |
409 | |
410 | if (!config->parent) |
411 | return -EINVAL; |
412 | |
413 | if (!config->map) |
414 | return -EINVAL; |
415 | |
416 | counter = devm_counter_alloc(dev, sizeof_priv: sizeof(*priv)); |
417 | if (!counter) |
418 | return -ENOMEM; |
419 | priv = counter_priv(counter); |
420 | priv->map = config->map; |
421 | |
422 | counter->name = dev_name(dev: config->parent); |
423 | counter->parent = config->parent; |
424 | counter->ops = &i8254_ops; |
425 | counter->counts = i8254_counts; |
426 | counter->num_counts = ARRAY_SIZE(i8254_counts); |
427 | counter->signals = i8254_signals; |
428 | counter->num_signals = ARRAY_SIZE(i8254_signals); |
429 | |
430 | mutex_init(&priv->lock); |
431 | |
432 | err = i8254_init_hw(map: priv->map); |
433 | if (err) |
434 | return err; |
435 | |
436 | err = devm_counter_add(dev, counter); |
437 | if (err < 0) |
438 | return dev_err_probe(dev, err, fmt: "Failed to add counter\n" ); |
439 | |
440 | return 0; |
441 | } |
442 | EXPORT_SYMBOL_NS_GPL(devm_i8254_regmap_register, I8254); |
443 | |
444 | MODULE_AUTHOR("William Breathitt Gray" ); |
445 | MODULE_DESCRIPTION("Intel 8254 Programmable Interval Timer" ); |
446 | MODULE_LICENSE("GPL" ); |
447 | MODULE_IMPORT_NS(COUNTER); |
448 | |