Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/counter/i8254.c
26278 views
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 <linux/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
449