mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
260 lines
7.2 KiB
C
260 lines
7.2 KiB
C
/*
|
|
* Copyright (C) 2023 ML!PA Consulting GmbH
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_sam0_common
|
|
* @ingroup drivers_periph_freqm
|
|
* @{
|
|
*
|
|
* @file freqm.c
|
|
* @brief Frequency meter driver implementation
|
|
*
|
|
* @author Urs Gompper <urs.gompper@ml-pa.com>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "periph/freqm.h"
|
|
|
|
/* TODO: Remove defines when Microchip vendor files (which include these
|
|
* defines) get updated.
|
|
*/
|
|
/* FREQM_GCLK_ID_REF is defined in newer versions of vendor header files */
|
|
#ifndef FREQM_GCLK_ID_REF
|
|
#define FREQM_GCLK_ID_REF (FREQM_GCLK_ID_MSR + 1)
|
|
#endif
|
|
|
|
/* Channel Enable Mask */
|
|
#define GCLK_PCHCTRL_CHEN_Msk (_U_(0x1) << GCLK_PCHCTRL_CHEN_Pos)
|
|
/* Enable Mask */
|
|
#define FREQM_CTRLA_ENABLE_Msk (_U_(0x1) << FREQM_CTRLA_ENABLE_Pos)
|
|
/* Start Measurement Mask */
|
|
#define FREQM_CTRLB_START_Msk (_U_(0x1) << FREQM_CTRLB_START_Pos)
|
|
/* Measurement Done Interrupt Enable Mask */
|
|
#define FREQM_INTENSET_DONE_Msk (_U_(0x1) << FREQM_INTENSET_DONE_Pos)
|
|
/* Measurement Done Mask */
|
|
#define FREQM_INTFLAG_DONE_Msk (_U_(0x1) << FREQM_INTFLAG_DONE_Pos)
|
|
/* FREQM Status Mask */
|
|
#define FREQM_STATUS_BUSY_Msk (_U_(0x1) << FREQM_STATUS_BUSY_Pos)
|
|
/* Sticky Count Value Overflow Mask */
|
|
#define FREQM_STATUS_OVF_Msk (_U_(0x1) << FREQM_STATUS_OVF_Pos)
|
|
|
|
/* check if pin has peripheral function GCLK */
|
|
static int _freqm_pin(gpio_t pin)
|
|
{
|
|
for (unsigned i = 0; i < ARRAY_SIZE(gclk_io_pins); ++i) {
|
|
if (gclk_io_pins[i] == pin) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void _gclk_connect(uint8_t id, uint8_t src, uint32_t flags)
|
|
{
|
|
GCLK->GENCTRL[id].reg = GCLK_GENCTRL_SRC(src) | GCLK_GENCTRL_GENEN | flags | GCLK_GENCTRL_IDC;
|
|
while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL(id)) {}
|
|
}
|
|
|
|
static int _freqm_gpio_init(gpio_t msr_gpio_src, uint8_t *gclk_io_id)
|
|
{
|
|
/* Check if selected pin has peripheral function GCLK */
|
|
int index = _freqm_pin(msr_gpio_src);
|
|
|
|
/* Fail assertion if pin has no peripheral function GCLK */
|
|
assert(index > 0);
|
|
|
|
/* Lookup which GCLK_IO[x] must be used */
|
|
*gclk_io_id = gclk_io_ids[index];
|
|
/* GCLK_IO[0] and GCLK_IO[1] can't be used here. They are associated with
|
|
GCLKGEN[0] and GCLKGEN[1] respectively. These in turn are used by
|
|
SAM0_GCLK_MAIN and SAM0_GCLK_32KHZ respectively */
|
|
assert(*gclk_io_id > 1);
|
|
|
|
/* Initialize GPIO as input */
|
|
gpio_init(msr_gpio_src, GPIO_IN);
|
|
/* Enable peripheral function GCLK/IO on GPIO */
|
|
gpio_init_mux(msr_gpio_src, GPIO_MUX_M);
|
|
/* Connect GCLK_IO[*gclk_io_id] with input pin */
|
|
_gclk_connect(*gclk_io_id, GCLK_SOURCE_GCLKIN, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _freqm_clock_init(uint8_t pin, uint8_t gclk_src)
|
|
{
|
|
/* Selection of the Generator and write Lock for FREQM_MSR */
|
|
GCLK->PCHCTRL[FREQM_GCLK_ID_MSR].reg = GCLK_PCHCTRL_GEN(pin) | GCLK_PCHCTRL_CHEN_Msk;
|
|
/* Wait for synchronization */
|
|
while ((GCLK->PCHCTRL[FREQM_GCLK_ID_MSR].reg & GCLK_PCHCTRL_CHEN_Msk) !=
|
|
GCLK_PCHCTRL_CHEN_Msk) {}
|
|
|
|
/* Selection of the Generator and write Lock for FREQM_REF */
|
|
GCLK->PCHCTRL[FREQM_GCLK_ID_REF].reg = GCLK_PCHCTRL_GEN(gclk_src) | GCLK_PCHCTRL_CHEN_Msk;
|
|
/* Wait for synchronization */
|
|
while ((GCLK->PCHCTRL[FREQM_GCLK_ID_REF].reg & GCLK_PCHCTRL_CHEN_Msk) !=
|
|
GCLK_PCHCTRL_CHEN_Msk) {}
|
|
}
|
|
|
|
static struct {
|
|
freqm_cb_t callback;
|
|
void *context;
|
|
freqm_t idx;
|
|
uint8_t period_cnt;
|
|
} freqm_obj;
|
|
|
|
struct _sync_ctx {
|
|
mutex_t lock; /**< Mutex for blocking till measurement is done */
|
|
uint32_t hz; /**< Measured frequency in Hz */
|
|
bool overflow; /**< Overflow in FREQM counter */
|
|
};
|
|
|
|
/**
|
|
* @brief Mutex for locking the FREQM device
|
|
*/
|
|
static mutex_t msr_lock = MUTEX_INIT;
|
|
|
|
static void _freqm_enable(uint8_t refnum)
|
|
{
|
|
mutex_lock(&msr_lock);
|
|
|
|
/* Save refnum for frequency calculation */
|
|
freqm_obj.period_cnt = refnum;
|
|
|
|
FREQM->CFGA.reg = (uint16_t)(FREQM_CFGA_REFNUM(refnum));
|
|
|
|
/* Enable DONE Interrupt */
|
|
FREQM->INTENSET.reg = FREQM_INTENSET_DONE_Msk;
|
|
|
|
/* Enable FREQM */
|
|
FREQM->CTRLA.reg = FREQM_CTRLA_ENABLE_Msk;
|
|
|
|
/* Wait for Sync */
|
|
while ((FREQM->SYNCBUSY.reg) != 0U) {}
|
|
}
|
|
|
|
static void _freqm_disable(void)
|
|
{
|
|
/* Disable DONE Interrupt */
|
|
FREQM->INTENCLR.reg = FREQM_INTENCLR_MASK;
|
|
/* Disable FREQM */
|
|
FREQM->CTRLA.reg &= ~FREQM_CTRLA_ENABLE_Msk;
|
|
/* Wait for Sync */
|
|
while ((FREQM->SYNCBUSY.reg) != 0U) {}
|
|
|
|
mutex_unlock(&msr_lock);
|
|
}
|
|
|
|
bool _freqm_get_measurement(uint32_t *result)
|
|
{
|
|
const freqm_config_t *config = &freqm_config[freqm_obj.idx];
|
|
|
|
/* Calculate measured frequency */
|
|
uint64_t result_tmp = FREQM->VALUE.reg * (uint64_t)(sam0_gclk_freq(config->gclk_src));
|
|
|
|
result_tmp = result_tmp / freqm_obj.period_cnt;
|
|
*result = (uint32_t)result_tmp;
|
|
|
|
/* Read overflow status */
|
|
bool overflow_condition = ((int)FREQM->STATUS.reg & FREQM_STATUS_OVF_Msk);
|
|
|
|
/* Clear overflow status */
|
|
FREQM->STATUS.reg = FREQM_STATUS_OVF_Msk;
|
|
|
|
return overflow_condition;
|
|
}
|
|
|
|
uint32_t _us_to_ref_clock_counts(uint32_t period_us, uint8_t clock_id)
|
|
{
|
|
uint64_t clk_cnt = (uint64_t)period_us * sam0_gclk_freq(clock_id) / US_PER_SEC;
|
|
|
|
if (clk_cnt > UINT8_MAX) {
|
|
return UINT8_MAX;
|
|
}
|
|
else if (clk_cnt == 0) {
|
|
return 1;
|
|
}
|
|
else {
|
|
return clk_cnt;
|
|
}
|
|
}
|
|
|
|
static void _sync_cb(uint32_t res, bool overflow, void *_ctx)
|
|
{
|
|
struct _sync_ctx *ctx = _ctx;
|
|
|
|
ctx->hz = res;
|
|
ctx->overflow = overflow;
|
|
mutex_unlock(&ctx->lock);
|
|
}
|
|
|
|
int freqm_frequency_get(freqm_t idx, uint32_t *result, uint32_t period_us)
|
|
{
|
|
struct _sync_ctx ctx = { .lock = MUTEX_INIT_LOCKED };
|
|
|
|
/* Invoke non-blocking FREQM measure function */
|
|
freqm_frequency_get_async(idx, _sync_cb, &ctx, period_us);
|
|
|
|
/* Block until measurement is done */
|
|
mutex_lock(&ctx.lock);
|
|
|
|
*result = ctx.hz;
|
|
return ctx.overflow ? -EOVERFLOW : 0;
|
|
}
|
|
|
|
void freqm_frequency_get_async(freqm_t idx, freqm_cb_t freqm_cb, void *context, uint32_t period_us)
|
|
{
|
|
const freqm_config_t *config = &freqm_config[idx];
|
|
|
|
uint8_t refnum = _us_to_ref_clock_counts(period_us, config->gclk_src);
|
|
|
|
_freqm_enable(refnum);
|
|
|
|
/* Register callback function */
|
|
freqm_obj.callback = freqm_cb;
|
|
freqm_obj.context = context;
|
|
freqm_obj.idx = idx;
|
|
|
|
/* Clear the Done Interrupt flag */
|
|
FREQM->INTFLAG.reg = FREQM_INTFLAG_DONE_Msk;
|
|
|
|
/* Start measurement */
|
|
FREQM->CTRLB.reg = FREQM_CTRLB_START_Msk;
|
|
}
|
|
|
|
void irq_freqm(void)
|
|
{
|
|
/* Clear the Done Interrupt flag */
|
|
FREQM->INTFLAG.reg = FREQM_INTFLAG_DONE_Msk;
|
|
|
|
uint32_t result = 0;
|
|
|
|
bool overflow_condition = _freqm_get_measurement(&result);
|
|
|
|
/* Invoke the callback function */
|
|
freqm_obj.callback(result, overflow_condition, freqm_obj.context);
|
|
|
|
_freqm_disable();
|
|
}
|
|
|
|
void freqm_init(freqm_t idx)
|
|
{
|
|
uint8_t gclk_io_id = 0;
|
|
const freqm_config_t *config = &freqm_config[idx];
|
|
|
|
/* Sanity check configuration */
|
|
assert(config->gclk_src <= GCLK_GEN_NUM_MSB);
|
|
|
|
_freqm_gpio_init(config->pin, &gclk_io_id);
|
|
_freqm_clock_init(gclk_io_id, config->gclk_src);
|
|
|
|
/* Enable interrupt */
|
|
NVIC_EnableIRQ(FREQM_IRQn);
|
|
}
|