blob: 622fc947059ce22a55fde83349aa28caec245cdf [file] [log] [blame]
/* Copyright 2018 SiFive, Inc */
/* SPDX-License-Identifier: Apache-2.0 */
#ifndef METAL__CLOCK_H
#define METAL__CLOCK_H
/*!
* @file clock.h
* @brief API for manipulating clock sources
*
* The clock interface allows for controlling the rate of various clocks in the system.
*/
struct metal_clock;
#include <stddef.h>
/* The generic interface to all clocks. */
struct __metal_clock_vtable {
long (*get_rate_hz)(const struct metal_clock *clk);
long (*set_rate_hz)(struct metal_clock *clk, long hz);
};
/*!
* @brief Function signature of clock rate change callbacks
*/
typedef void (*metal_clock_rate_change_callback)(void *priv);
struct _metal_clock_callback_t;
struct _metal_clock_callback_t {
/* The callback function */
metal_clock_rate_change_callback callback;
/* Private data for the callback function */
void *priv;
struct _metal_clock_callback_t *_next;
};
/*!
* @brief Type for the linked list of callbacks for clock rate changes
*/
typedef struct _metal_clock_callback_t metal_clock_callback;
/*!
* @brief Call all callbacks in the linked list, if any are registered
*/
__inline__ void _metal_clock_call_all_callbacks(const metal_clock_callback *const list) {
const metal_clock_callback *current = list;
while (current) {
current->callback(current->priv);
current = current->_next;
}
}
/*!
* @brief Append a callback to the linked list and return the head of the list
*/
__inline__ metal_clock_callback *_metal_clock_append_to_callbacks(metal_clock_callback *list, metal_clock_callback *const cb) {
cb->_next = NULL;
if (!list) {
return cb;
}
metal_clock_callback *current = list;
while ((current->_next) != NULL) {
current = current->_next;
}
current->_next = cb;
return list;
}
/*!
* @struct metal_clock
* @brief The handle for a clock
*
* Clocks are defined as a pointer to a `struct metal_clock`, the contents of which
* are implementation defined. Users of the clock interface must call functions
* which accept a `struct metal_clock *` as an argument to interract with the clock.
*
* Note that no mechanism for obtaining a pointer to a `struct metal_clock` has been
* defined, making it impossible to call any of these functions without invoking
* implementation-defined behavior.
*/
struct metal_clock {
const struct __metal_clock_vtable *vtable;
/* Pre-rate change callback linked list */
metal_clock_callback *_pre_rate_change_callback;
/* Post-rate change callback linked list */
metal_clock_callback *_post_rate_change_callback;
};
/*!
* @brief Returns the current rate of the given clock
*
* @param clk The handle for the clock
* @return The current rate of the clock in Hz
*/
__inline__ long metal_clock_get_rate_hz(const struct metal_clock *clk) { return clk->vtable->get_rate_hz(clk); }
/*!
* @brief Set the current rate of a clock
*
* @param clk The handle for the clock
* @param hz The desired rate in Hz
* @return The new rate of the clock in Hz.
*
* Attempts to set the current rate of the given clock to as close as possible
* to the given rate in Hz. Returns the actual value that's been selected, which
* could be anything!
*
* Prior to and after the rate change of the clock, this will call the registered
* pre- and post-rate change callbacks.
*/
__inline__ long metal_clock_set_rate_hz(struct metal_clock *clk, long hz)
{
_metal_clock_call_all_callbacks(clk->_pre_rate_change_callback);
long out = clk->vtable->set_rate_hz(clk, hz);
_metal_clock_call_all_callbacks(clk->_post_rate_change_callback);
return out;
}
/*!
* @brief Register a callback that must be called before a rate change
*
* @param clk The handle for the clock
* @param cb The callback to be registered
*/
__inline__ void metal_clock_register_pre_rate_change_callback(struct metal_clock *clk, metal_clock_callback *cb)
{
clk->_pre_rate_change_callback = _metal_clock_append_to_callbacks(clk->_pre_rate_change_callback, cb);
}
/*!
* @brief Registers a callback that must be called after a rate change
*
* @param clk The handle for the clock
* @param cb The callback to be registered
*/
__inline__ void metal_clock_register_post_rate_change_callback(struct metal_clock *clk, metal_clock_callback *cb)
{
clk->_post_rate_change_callback = _metal_clock_append_to_callbacks(clk->_post_rate_change_callback, cb);
}
#endif