blob: fc5674c41e4b251a39519c7623077da37615e27b [file] [log] [blame]
/*
* Copyright (c) 2025 Sequans Communications
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @ingroup hwspinlock_interface
* @brief Main header file for hardware spinlock driver API.
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_HWSPINLOCK_H_
#define ZEPHYR_INCLUDE_DRIVERS_HWSPINLOCK_H_
/**
* @brief Interfaces for hardware spinlocks.
* @defgroup hwspinlock_interface Hardware Spinlock
* @ingroup io_interfaces
* @{
*/
#include <zephyr/types.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/device.h>
#include <zephyr/spinlock.h>
#include <zephyr/devicetree.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HW spinlock controller runtime context
*/
struct hwspinlock_context {
/**
* @internal
* Per HW spinlock lock
* @note HW spinlock protects resources across clusters, but we need to protect the
* access to HW spinlock inside of the same cluster, so a single thread may claim the
* lock at a time.
*/
struct k_spinlock lock;
};
/**
* @brief Opaque type to represent a hwspinlock runtime context.
*
* This type is not meant to be inspected by application code.
*/
typedef struct hwspinlock_context hwspinlock_ctx_t;
/**
* @brief Complete hardware spinlock DT information
*/
struct hwspinlock_dt_spec {
/** HW spinlock device */
const struct device *dev;
/** HW spinlock id */
uint32_t id;
/** Runtime context */
hwspinlock_ctx_t ctx;
};
/**
* @brief Initializer for a hwspinlock_ctx_t
*
* @note We must declare each field individually because a struct k_spinlock might have no field
* depending on Kconfig options, and gcc requires struct without any field to be initialized
* explicitly, instead of just being able to do `{0}`.
*/
#define HWSPINLOCK_CTX_INITIALIZER \
{ \
.lock = {0}, \
}
/**
* @brief Structure initializer for struct hwspinlock_dt_spec from devicetree by index
*
* This helper macro expands to a static initializer for a struct hwspinlock_dt_spec
* by reading the relevant device controller and id number from the devicetree.
*
* Example devicetree fragment:
*
* @code{.devicetree}
* n: node {
* hwlocks = <&hwlock 8>,
* <&hwlock 9>;
* };
* @endcode
*
* Example usage:
*
* @code{.c}
* const struct hwspinlock_dt_spec spec = HWSPINLOCK_DT_SPEC_GET_BY_IDX(DT_NODELABEL(n), 0);
* @endcode
*
* @param node_id Devicetree node identifier for the HWSPINLOCK device
* @param idx Index of hwlocks element
*
* @return static initializer for a struct hwspinlock_dt_spec
*/
#define HWSPINLOCK_DT_SPEC_GET_BY_IDX(node_id, idx) \
{ \
.dev = DEVICE_DT_GET(DT_HWSPINLOCK_CTRL_BY_IDX(node_id, idx)), \
.id = DT_HWSPINLOCK_ID_BY_IDX(node_id, idx), \
.ctx = HWSPINLOCK_CTX_INITIALIZER, \
}
/**
* @brief Structure initializer for struct hwspinlock_dt_spec from devicetree by name
*
* This helper macro expands to a static initializer for a struct hwspinlock_dt_spec
* by reading the relevant device controller and id number from the devicetree.
*
* Example devicetree fragment:
*
* @code{.devicetree}
* n: node {
* hwlocks = <&hwlock 8>,
* <&hwlock 9>;
* hwlock-names = "rd", "wr";
* };
* @endcode
*
* Example usage:
*
* @code{.c}
* const struct hwspinlock_dt_spec spec = HWSPINLOCK_DT_SPEC_GET_BY_NAME(DT_NODELABEL(n), wr);
* @endcode
*
* @param node_id Devicetree node identifier for the HWSPINLOCK device
* @param name lowercase-and-underscores name of the hwlocks element
*
* @return static initializer for a struct hwspinlock_dt_spec
*/
#define HWSPINLOCK_DT_SPEC_GET_BY_NAME(node_id, name) \
{ \
.dev = DEVICE_DT_GET(DT_HWSPINLOCK_CTRL_BY_NAME(node_id, name)), \
.id = DT_HWSPINLOCK_ID_BY_NAME(node_id, name), \
.ctx = HWSPINLOCK_CTX_INITIALIZER, \
}
/**
* @brief Structure initializer for struct hwspinlock_dt_spec from devicetree.
*
* This is equivalent to HWSPINLOCK_DT_SPEC_GET_BY_IDX(node_id, 0)
*
* @param node_id Devicetree node identifier for the HWSPINLOCK device
* @see HWSPINLOCK_DT_SPEC_GET_BY_IDX()
*/
#define HWSPINLOCK_DT_SPEC_GET(node_id) \
HWSPINLOCK_DT_SPEC_GET_BY_IDX(node_id, 0)
/**
* @brief Instance version of HWSPINLOCK_DT_SPEC_GET_BY_IDX()
*
* @param inst DT_DRV_COMPAT instance number
* @param idx Index of the hwlocks element
*
* @see HWSPINLOCK_DT_SPEC_GET_BY_IDX()
*/
#define HWSPINLOCK_DT_SPEC_INST_GET_BY_IDX(inst, idx) \
HWSPINLOCK_DT_SPEC_GET_BY_IDX(DT_DRV_INST(inst), idx)
/**
* @brief Instance version of HWSPINLOCK_DT_SPEC_GET_BY_NAME()
*
* @param inst DT_DRV_COMPAT instance number
* @param name lowercase-and-underscores name of the hwlocks element
*
* @see HWSPINLOCK_DT_SPEC_GET_BY_NAME()
*/
#define HWSPINLOCK_DT_SPEC_INST_GET_BY_NAME(inst, name) \
HWSPINLOCK_DT_SPEC_GET_BY_NAME(DT_DRV_INST(inst), name)
/**
* @brief Instance version of HWSPINLOCK_DT_SPEC_GET()
*
* @param inst DT_DRV_COMPAT instance number
*
* @see HWSPINLOCK_DT_SPEC_GET()
*/
#define HWSPINLOCK_DT_SPEC_INST_GET(inst) \
HWSPINLOCK_DT_SPEC_GET(DT_DRV_INST(inst))
/** @cond INTERNAL_HIDDEN */
/**
* @brief Callback API for trying to lock HW spinlock
*
* This callback is optional. If not implemented, hw_spin_trylock() will return -ENOSYS.
*
* @see hw_spin_trylock
*/
typedef int (*hwspinlock_api_trylock)(const struct device *dev, uint32_t id);
/**
* @brief Callback API to lock HW spinlock
*
* This callback must be implemented.
*
* @see hw_spin_lock
*/
typedef void (*hwspinlock_api_lock)(const struct device *dev, uint32_t id);
/**
* @brief Callback API to unlock HW spinlock
*
* This callback must be implemented.
*
* @see hw_spin_unlock
*/
typedef void (*hwspinlock_api_unlock)(const struct device *dev, uint32_t id);
/**
* @brief Callback API to get HW spinlock max ID
*
* This callback must be implemented.
*
* @see hw_spinlock_get_max_id
*/
typedef uint32_t (*hwspinlock_api_get_max_id)(const struct device *dev);
__subsystem struct hwspinlock_driver_api {
hwspinlock_api_trylock trylock;
hwspinlock_api_lock lock;
hwspinlock_api_unlock unlock;
hwspinlock_api_get_max_id get_max_id;
};
/**
* @endcond
*/
/**
* @brief Try to lock HW spinlock
*
* This function is used when trying to lock an HW spinlock. If the spinlock is already locked by
* another cluster, exits with -EBUSY.
*
* @see hw_spin_lock
*
* @param dev HW spinlock device instance.
* @param ctx HW spinlock runtime context.
* @param id Spinlock identifier.
* @param[out] key A pointer to the spinlock key.
*
* @retval 0 On success.
* @retval -ENOSYS If the operation is not implemented.
* @retval -EINVAL If HW spinlock id is invalid.
* @retval -EBUSY If HW spinlock is already locked by someone else.
*/
static inline int hw_spin_trylock(const struct device *dev, hwspinlock_ctx_t *ctx, uint32_t id,
k_spinlock_key_t *key)
{
const struct hwspinlock_driver_api *api = (const struct hwspinlock_driver_api *)dev->api;
int ret;
if (api->trylock == NULL) {
return -ENOSYS;
}
ret = k_spin_trylock(&ctx->lock, key);
if (ret) {
return ret;
}
return api->trylock(dev, id);
}
/**
* @brief Lock HW spinlock
*
* This function is used when locking an HW spinlock. If the spinlock is already locked by
* another party, waits for it to be released.
* This is useful when protecting a resource which is shared between multiple clusters.
*
* @note The goal is not to replace regular spinlocks, but to protect shared resources between
* clusters. However, because we don't want another thread to take the same HW spinlock twice, the
* locking mechanism acts as a regular spinlock as well.
*
* Because this uses a regular zephyr spinlock in conjunction with a hwspinlock, the same rules
* applies. Separate hwspinlock may be nested. It is legal to lock another (unlocked) hwspinlock
* while holding a lock. However, an attempt to acquire a hwspinlock that the CPU already holds
* will deadlock.
* @see k_spin_lock
*
* @param dev HW spinlock device instance.
* @param ctx HW spinlock runtime context.
* @param id Spinlock identifier.
*
* @return A key value that must be passed to hw_spin_unlock() when the
* lock is released.
*/
static inline k_spinlock_key_t hw_spin_lock(const struct device *dev, hwspinlock_ctx_t *ctx,
uint32_t id)
{
const struct hwspinlock_driver_api *api = (const struct hwspinlock_driver_api *)dev->api;
k_spinlock_key_t k;
__ASSERT(api->lock != NULL, "hwspinlock lock callback must be implemented");
k = k_spin_lock(&ctx->lock);
api->lock(dev, id);
return k;
}
/**
* @brief Unlock HW spinlock
*
* This function to unlock an HW spinlock
*
* @param dev HW spinlock device instance.
* @param ctx HW spinlock runtime context.
* @param id Spinlock identifier.
* @param key The value returned from hw_spin_lock() when this lock was
* acquired
*/
static inline void hw_spin_unlock(const struct device *dev, hwspinlock_ctx_t *ctx, uint32_t id,
k_spinlock_key_t key)
{
const struct hwspinlock_driver_api *api = (const struct hwspinlock_driver_api *)dev->api;
__ASSERT(api->unlock != NULL, "hwspinlock unlock callback must be implemented");
api->unlock(dev, id);
k_spin_unlock(&ctx->lock, key);
}
/**
* @brief Get HW spinlock max ID
*
* This function is used to get the HW spinlock maximum ID. It should
* be called before attempting to lock/unlock a specific HW spinlock.
*
* @param dev HW spinlock device instance.
*
* @retval HW spinlock max ID.
*/
static inline uint32_t hw_spinlock_get_max_id(const struct device *dev)
{
const struct hwspinlock_driver_api *api = (const struct hwspinlock_driver_api *)dev->api;
__ASSERT(api->get_max_id != NULL, "hwspinlock get_max_id callback must be implemented");
return api->get_max_id(dev);
}
/**
* @brief Try to lock HW spinlock from a struct hwspinlock_dt_spec
*
* This is the dt_spec equivalent of hw_spin_trylock()
* @see hw_spin_trylock
*
* @param spec HWSPINLOCK specification from devicetree
* @param[out] key A pointer to the spinlock key.
*
* @returns See return values from hw_spin_trylock()
*/
static inline int hw_spin_trylock_dt(struct hwspinlock_dt_spec *spec, k_spinlock_key_t *key)
{
return hw_spin_trylock(spec->dev, &spec->ctx, spec->id, key);
}
/**
* @brief Lock HW spinlock from a struct hwspinlock_dt_spec
*
* This is the dt_spec equivalent of hw_spin_lock()
* @see hw_spin_lock
*
* @param spec HWSPINLOCK specification from devicetree
*
* @returns See return values from hw_spin_lock()
*/
static inline k_spinlock_key_t hw_spin_lock_dt(struct hwspinlock_dt_spec *spec)
{
return hw_spin_lock(spec->dev, &spec->ctx, spec->id);
}
/**
* @brief Unlock HW spinlock from a struct hwspinlock_dt_spec
*
* This is the dt_spec equivalent of hw_spin_unlock()
* @see hw_spin_unlock
*
* @param spec HWSPINLOCK specification from devicetree
* @param key The value returned from hw_spin_lock() when this lock was
* acquired
*/
static inline void hw_spin_unlock_dt(struct hwspinlock_dt_spec *spec, k_spinlock_key_t key)
{
hw_spin_unlock(spec->dev, &spec->ctx, spec->id, key);
}
/**
* @brief Get HW spinlock max ID from a struct hwspinlock_dt_spec
*
* This is the dt_spec equivalent of hw_spinlock_get_max_id()
* @see hw_spinlock_get_max_id
*
* @param spec HWSPINLOCK specification from devicetree
*
* @returns See return values from hw_spinlock_get_max_id()
*/
static inline uint32_t hw_spinlock_get_max_id_dt(struct hwspinlock_dt_spec *spec)
{
return hw_spinlock_get_max_id(spec->dev);
}
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* ZEPHYR_INCLUDE_DRIVERS_HWSPINLOCK_H_ */