xref: /openbmc/linux/drivers/counter/i8254.c (revision 703e7713)
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(priv->map, I8254_CONTROL_REG, I8254_COUNTER_LATCH(count->id));
67 	if (ret) {
68 		mutex_unlock(&priv->lock);
69 		return ret;
70 	}
71 	ret = regmap_noinc_read(priv->map, I8254_COUNTER_REG(count->id), value, sizeof(value));
72 	if (ret) {
73 		mutex_unlock(&priv->lock);
74 		return ret;
75 	}
76 
77 	mutex_unlock(&priv->lock);
78 
79 	*val = get_unaligned_le16(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(&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(priv->map, I8254_CONTROL_REG,
225 			   I8254_PROGRAM_COUNTER(count->id, out_mode));
226 
227 	mutex_unlock(&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(&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(&priv->lock);
284 			return -EINVAL;
285 		}
286 	}
287 
288 	priv->preset[count->id] = preset;
289 
290 	put_unaligned_le16(preset, value);
291 	ret = regmap_noinc_write(priv->map, I8254_COUNTER_REG(count->id), value, 2);
292 
293 	mutex_unlock(&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));
417 	if (!counter)
418 		return -ENOMEM;
419 	priv = counter_priv(counter);
420 	priv->map = config->map;
421 
422 	counter->name = dev_name(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(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, "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