// SPDX-License-Identifier: GPL-2.0+ /* * comedi_8254.c * Generic 8254 timer/counter support * Copyright (C) 2014 H Hartley Sweeten <[email protected]> * * Based on 8253.h and various subdevice implementations in comedi drivers. * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 2000 David A. Schleef <[email protected]> */ /* * Module: comedi_8254 * Description: Generic 8254 timer/counter support * Author: H Hartley Sweeten <[email protected]> * Updated: Thu Jan 8 16:45:45 MST 2015 * Status: works * * This module is not used directly by end-users. Rather, it is used by other * drivers to provide support for an 8254 Programmable Interval Timer. These * counters are typically used to generate the pacer clock used for data * acquisition. Some drivers also expose the counters for general purpose use. * * This module provides the following basic functions: * * comedi_8254_io_alloc() / comedi_8254_mm_alloc() * Initializes this module to access the 8254 registers. The _mm version * sets up the module for MMIO register access; the _io version sets it * up for PIO access. These functions return a pointer to a struct * comedi_8254 on success, or an ERR_PTR value on failure. The pointer * returned from these functions is normally stored in the comedi_device * dev->pacer and will be freed by the comedi core during the driver * (*detach). If a driver has multiple 8254 devices, they need to be * stored in the drivers private data and freed when the driver is * detached. If the ERR_PTR value is stored, code should check the * pointer value with !IS_ERR(pointer) before freeing. * * NOTE: The counters are reset by setting them to I8254_MODE0 as part of * this initialization. * * comedi_8254_set_mode() * Sets a counters operation mode: * I8254_MODE0 Interrupt on terminal count * I8254_MODE1 Hardware retriggerable one-shot * I8254_MODE2 Rate generator * I8254_MODE3 Square wave mode * I8254_MODE4 Software triggered strobe * I8254_MODE5 Hardware triggered strobe (retriggerable) * * In addition I8254_BCD and I8254_BINARY specify the counting mode: * I8254_BCD BCD counting * I8254_BINARY Binary counting * * comedi_8254_write() * Writes an initial value to a counter. * * The largest possible initial count is 0; this is equivalent to 2^16 * for binary counting and 10^4 for BCD counting. * * NOTE: The counter does not stop when it reaches zero. In Mode 0, 1, 4, * and 5 the counter "wraps around" to the highest count, either 0xffff * for binary counting or 9999 for BCD counting, and continues counting. * Modes 2 and 3 are periodic; the counter reloads itself with the initial * count and continues counting from there. * * comedi_8254_read() * Reads the current value from a counter. * * comedi_8254_status() * Reads the status of a counter. * * comedi_8254_load() * Sets a counters operation mode and writes the initial value. * * Typically the pacer clock is created by cascading two of the 16-bit counters * to create a 32-bit rate generator (I8254_MODE2). These functions are * provided to handle the cascaded counters: * * comedi_8254_ns_to_timer() * Calculates the divisor value needed for a single counter to generate * ns timing. * * comedi_8254_cascade_ns_to_timer() * Calculates the two divisor values needed to the generate the pacer * clock (in ns). * * comedi_8254_update_divisors() * Transfers the intermediate divisor values to the current divisors. * * comedi_8254_pacer_enable() * Programs the mode of the cascaded counters and writes the current * divisor values. * * To expose the counters as a subdevice for general purpose use the following * functions a provided: * * comedi_8254_subdevice_init() * Initializes a comedi_subdevice to use the 8254 timer. * * comedi_8254_set_busy() * Internally flags a counter as "busy". This is done to protect the * counters that are used for the cascaded 32-bit pacer. * * The subdevice provides (*insn_read) and (*insn_write) operations to read * the current value and write an initial value to a counter. A (*insn_config) * operation is also provided to handle the following comedi instructions: * * INSN_CONFIG_SET_COUNTER_MODE calls comedi_8254_set_mode() * INSN_CONFIG_8254_READ_STATUS calls comedi_8254_status() * * The (*insn_config) member of comedi_8254 can be initialized by the external * driver to handle any additional instructions. * * NOTE: Gate control, clock routing, and any interrupt handling for the * counters is not handled by this module. These features are driver dependent. */ #include <linux/module.h> #include <linux/slab.h> #include <linux/io.h> #include <linux/comedi/comedidev.h> #include <linux/comedi/comedi_8254.h> #ifdef CONFIG_HAS_IOPORT static unsigned int i8254_io8_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } static unsigned int i8254_io16_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } static unsigned int i8254_io32_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } #endif /* CONFIG_HAS_IOPORT */ static unsigned int i8254_mmio8_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } static unsigned int i8254_mmio16_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } static unsigned int i8254_mmio32_cb(struct comedi_8254 *i8254, int dir, unsigned int reg, unsigned int val) { … } static unsigned int __i8254_read(struct comedi_8254 *i8254, unsigned int reg) { … } static void __i8254_write(struct comedi_8254 *i8254, unsigned int val, unsigned int reg) { … } /** * comedi_8254_status - return the status of a counter * @i8254: comedi_8254 struct for the timer * @counter: the counter number */ unsigned int comedi_8254_status(struct comedi_8254 *i8254, unsigned int counter) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_read - read the current counter value * @i8254: comedi_8254 struct for the timer * @counter: the counter number */ unsigned int comedi_8254_read(struct comedi_8254 *i8254, unsigned int counter) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_write - load a 16-bit initial counter value * @i8254: comedi_8254 struct for the timer * @counter: the counter number * @val: the initial value */ void comedi_8254_write(struct comedi_8254 *i8254, unsigned int counter, unsigned int val) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_set_mode - set the mode of a counter * @i8254: comedi_8254 struct for the timer * @counter: the counter number * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY */ int comedi_8254_set_mode(struct comedi_8254 *i8254, unsigned int counter, unsigned int mode) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_load - program the mode and initial count of a counter * @i8254: comedi_8254 struct for the timer * @counter: the counter number * @mode: the I8254_MODEx and I8254_BCD|I8254_BINARY * @val: the initial value */ int comedi_8254_load(struct comedi_8254 *i8254, unsigned int counter, unsigned int val, unsigned int mode) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_pacer_enable - set the mode and load the cascaded counters * @i8254: comedi_8254 struct for the timer * @counter1: the counter number for the first divisor * @counter2: the counter number for the second divisor * @enable: flag to enable (load) the counters */ void comedi_8254_pacer_enable(struct comedi_8254 *i8254, unsigned int counter1, unsigned int counter2, bool enable) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_update_divisors - update the divisors for the cascaded counters * @i8254: comedi_8254 struct for the timer */ void comedi_8254_update_divisors(struct comedi_8254 *i8254) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_cascade_ns_to_timer - calculate the cascaded divisor values * @i8254: comedi_8254 struct for the timer * @nanosec: the desired ns time * @flags: comedi_cmd flags */ void comedi_8254_cascade_ns_to_timer(struct comedi_8254 *i8254, unsigned int *nanosec, unsigned int flags) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_ns_to_timer - calculate the divisor value for nanosec timing * @i8254: comedi_8254 struct for the timer * @nanosec: the desired ns time * @flags: comedi_cmd flags */ void comedi_8254_ns_to_timer(struct comedi_8254 *i8254, unsigned int *nanosec, unsigned int flags) { … } EXPORT_SYMBOL_GPL(…); /** * comedi_8254_set_busy - set/clear the "busy" flag for a given counter * @i8254: comedi_8254 struct for the timer * @counter: the counter number * @busy: set/clear flag */ void comedi_8254_set_busy(struct comedi_8254 *i8254, unsigned int counter, bool busy) { … } EXPORT_SYMBOL_GPL(…); static int comedi_8254_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { … } static int comedi_8254_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { … } static int comedi_8254_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { … } /** * comedi_8254_subdevice_init - initialize a comedi_subdevice for the 8254 timer * @s: comedi_subdevice struct * @i8254: comedi_8254 struct */ void comedi_8254_subdevice_init(struct comedi_subdevice *s, struct comedi_8254 *i8254) { … } EXPORT_SYMBOL_GPL(…); static struct comedi_8254 *__i8254_init(comedi_8254_iocb_fn *iocb, unsigned long context, unsigned int osc_base, unsigned int iosize, unsigned int regshift) { … } #ifdef CONFIG_HAS_IOPORT /** * comedi_8254_io_alloc - allocate and initialize the 8254 device for pio access * @iobase: port I/O base address * @osc_base: base time of the counter in ns * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() * @iosize: I/O register size * @regshift: register gap shift * * Return: A pointer to a struct comedi_8254 or an ERR_PTR value. */ struct comedi_8254 *comedi_8254_io_alloc(unsigned long iobase, unsigned int osc_base, unsigned int iosize, unsigned int regshift) { … } EXPORT_SYMBOL_GPL(…); #endif /* CONFIG_HAS_IOPORT */ /** * comedi_8254_mm_alloc - allocate and initialize the 8254 device for mmio access * @mmio: memory mapped I/O base address * @osc_base: base time of the counter in ns * OPTIONAL - only used by comedi_8254_cascade_ns_to_timer() * @iosize: I/O register size * @regshift: register gap shift * * Return: A pointer to a struct comedi_8254 or an ERR_PTR value. */ struct comedi_8254 *comedi_8254_mm_alloc(void __iomem *mmio, unsigned int osc_base, unsigned int iosize, unsigned int regshift) { … } EXPORT_SYMBOL_GPL(…); static int __init comedi_8254_module_init(void) { … } module_init(…) …; static void __exit comedi_8254_module_exit(void) { … } module_exit(comedi_8254_module_exit); MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …;