/* 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 /* 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