blob: ee6d9d05b1f2cb867dee0d54b276edd4f0d92597 [file] [log] [blame]
/*
* Copyright (c) 2025 Microchip Technology Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file pwm_mchp_tcc_g1.c
* @brief PWM driver for Microchip tcc g1 peripheral.
*
* This file provides the implementation of pwm functions
* for Microchip tcc g1 Peripheral.
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/mchp_clock_control.h>
/******************************************************************************
* @brief Devicetree definitions
*****************************************************************************/
#define DT_DRV_COMPAT microchip_tcc_g1_pwm
/*******************************************
* Const and Macro Defines
*******************************************/
LOG_MODULE_REGISTER(pwm_mchp_tcc_g1, CONFIG_PWM_LOG_LEVEL);
#define PWM_REG(addr) ((tcc_registers_t *)addr)
#define MCHP_PWM_SUCCESS 0
/**
* @brief Timeout duration for acquiring the PWM lock.
*
* This macro defines the timeout duration for acquiring the PWM lock.
* The timeout is specified in milliseconds.
*/
#define MCHP_PWM_LOCK_TIMEOUT K_MSEC(10)
/**
* @brief Initialize the PWM lock.
*
* This macro initializes the PWM lock.
*
* @param p_lock Pointer to the lock to be initialized.
*/
#define MCHP_PWM_DATA_LOCK_INIT(p_lock) k_mutex_init(p_lock)
/**
* @brief Acquire the PWM lock.
*
* This macro acquires the PWM lock. If the lock is not available, the
* function will wait for the specified timeout duration.
*
* @param p_lock Pointer to the lock to be acquired.
* @return 0 if the lock was successfully acquired, or a negative error code.
*/
#define MCHP_PWM_DATA_LOCK(p_lock) k_mutex_lock(p_lock, MCHP_PWM_LOCK_TIMEOUT)
/**
* @brief Release the PWM lock.
*
* This macro releases the PWM lock.
*
* @param p_lock Pointer to the lock to be released.
* @return 0 if the lock was successfully released, or a negative error code.
*/
#define MCHP_PWM_DATA_UNLOCK(p_lock) k_mutex_unlock(p_lock)
/* Timeout values for WAIT_FOR macro */
#define TIMEOUT_VALUE_US 1000
#define DELAY_US 2
/***********************************
* Typedefs and Enum Declarations
**********************************/
typedef enum {
PWM_PRESCALE_1 = 1,
PWM_PRESCALE_2 = 2,
PWM_PRESCALE_4 = 4,
PWM_PRESCALE_8 = 8,
PWM_PRESCALE_16 = 16,
PWM_PRESCALE_32 = 32,
PWM_PRESCALE_64 = 64,
PWM_PRESCALE_128 = 128,
PWM_PRESCALE_256 = 256,
PWM_PRESCALE_512 = 512,
PWM_PRESCALE_1024 = 1024
} pwm_prescale_modes_t;
/**
* @brief Structure for managing flags for the pwm.
*/
typedef enum {
PWM_MCHP_FLAGS_CAPTURE_TYPE_PERIOD,
PWM_MCHP_FLAGS_CAPTURE_TYPE_PULSE,
PWM_MCHP_FLAGS_CAPTURE_TYPE_BOTH,
PWM_MCHP_FLAGS_CAPTURE_MODE_SINGLE,
PWM_MCHP_FLAGS_CAPTURE_MODE_CONTINUOUS,
} pwm_mchp_flags_t;
/**
* @brief Structure to hold PWM data specific to Microchip hardware.
*/
typedef struct {
struct k_mutex lock; /* Lock type for PWM configuration */
} pwm_mchp_data_t;
typedef struct mchp_counter_clock {
/* Clock driver */
const struct device *clock_dev;
/* Main clock subsystem. */
clock_control_subsys_t host_core_sync_clk;
/* Generic clock subsystem. */
clock_control_subsys_t periph_async_clk;
} pwm_mchp_clock_t;
/**
* @brief Structure to hold the configuration for Microchip PWM.
*/
typedef struct {
pwm_mchp_clock_t pwm_clock; /* PWM clock configuration */
const struct pinctrl_dev_config *pinctrl_config; /* Pin control configuration */
void *regs; /* Pointer to PWM peripheral registers */
uint32_t max_bit_width; /* Used for finding out the resolution of the pwm peripheral */
uint16_t prescaler; /* Prescaler value for PWM */
uint8_t channels; /* Number of PWM channels */
uint32_t freq; /* Frequency of the PWM signal */
} pwm_mchp_config_t;
/***********************************
* Internal functions
***********************************/
/**
*Get the prescale value based on the given prescaler.
*/
static uint32_t tcc_get_prescale_val(uint32_t prescaler)
{
uint32_t prescaler_val = 0;
switch (prescaler) {
case PWM_PRESCALE_1:
prescaler_val = TCC_CTRLA_PRESCALER_DIV1;
break;
case PWM_PRESCALE_2:
prescaler_val = TCC_CTRLA_PRESCALER_DIV2;
break;
case PWM_PRESCALE_4:
prescaler_val = TCC_CTRLA_PRESCALER_DIV4;
break;
case PWM_PRESCALE_8:
prescaler_val = TCC_CTRLA_PRESCALER_DIV8;
break;
case PWM_PRESCALE_16:
prescaler_val = TCC_CTRLA_PRESCALER_DIV16;
break;
case PWM_PRESCALE_64:
prescaler_val = TCC_CTRLA_PRESCALER_DIV64;
break;
case PWM_PRESCALE_256:
prescaler_val = TCC_CTRLA_PRESCALER_DIV256;
break;
case PWM_PRESCALE_1024:
prescaler_val = TCC_CTRLA_PRESCALER_DIV1024;
break;
default:
prescaler_val = TCC_CTRLA_PRESCALER_DIV1; /* Default fallback */
LOG_ERR("Unsupported prescaler specified in dts. Initialising with default "
"prescaler of DIV1");
break;
}
return prescaler_val;
}
/**
*Enable or disable the PWM instance.
*/
static inline void tcc_enable(void *pwm_reg, bool enable)
{
if (enable != 0) {
PWM_REG(pwm_reg)->TCC_CTRLA |= TCC_CTRLA_ENABLE(1);
} else {
PWM_REG(pwm_reg)->TCC_CTRLA &= ~TCC_CTRLA_ENABLE(1);
}
LOG_DBG("%s %d invoked", __func__, enable);
}
/**
*Wait for the PWM instance to complete synchronization.
*/
static inline void tcc_sync_wait(void *pwm_reg)
{
if (!WAIT_FOR(((PWM_REG(pwm_reg)->TCC_SYNCBUSY) != 0), TIMEOUT_VALUE_US,
k_busy_wait(DELAY_US))) {
LOG_ERR("TCC_SYNCBUSY wait timed out");
}
LOG_DBG("%s invoked", __func__);
}
/**
*Set the output inversion for a specific PWM channel.
*/
static int32_t tcc_set_invert(void *pwm_reg, uint32_t channel)
{
uint32_t invert_mask = 1 << (channel + TCC_DRVCTRL_INVEN0_Pos);
tcc_enable(pwm_reg, false);
tcc_sync_wait(pwm_reg);
PWM_REG(pwm_reg)->TCC_DRVCTRL |= invert_mask;
tcc_enable(pwm_reg, true);
tcc_sync_wait(pwm_reg);
LOG_DBG("tcc set invert 0x%x invoked", invert_mask);
return MCHP_PWM_SUCCESS;
}
/**
*Initialize the PWM instance with the specified prescaler.
*/
void tcc_init(void *pwm_reg, uint32_t prescaler)
{
prescaler = tcc_get_prescale_val(prescaler);
PWM_REG(pwm_reg)->TCC_CTRLA = TCC_CTRLA_SWRST(1);
tcc_sync_wait(pwm_reg);
PWM_REG(pwm_reg)->TCC_CTRLA |= prescaler;
PWM_REG(pwm_reg)->TCC_WAVE = TCC_WAVE_WAVEGEN_NPWM;
PWM_REG(pwm_reg)->TCC_PER = TCC_PER_PER(0);
tcc_enable(pwm_reg, true);
}
/**
*Get the output inversion status for a specific PWM channel.
*/
static inline bool tcc_get_invert_status(void *pwm_reg, uint32_t channel)
{
uint32_t invert_status = 0;
uint32_t invert_mask = 1 << (channel + TCC_DRVCTRL_INVEN0_Pos);
LOG_DBG("tcc get invert status 0x%x invoked", invert_mask);
invert_status = PWM_REG(pwm_reg)->TCC_DRVCTRL & invert_mask;
return (invert_status == 0) ? true : false;
}
/***********************************
* Zephyr APIs
**********************************/
/**
* @brief Set the PWM cycles for a specific channel.
*
* This function sets the PWM period and pulse width for a specified channel. It also handles the
* polarity inversion if required.
*
* @param pwm_dev Pointer to the PWM device structure.
* @param channel PWM channel number.
* @param period PWM period in cycles.
* @param pulse PWM pulse width in cycles.
* @param flags PWM flags (e.g., polarity inversion).
*
* @return 0 on success, -EINVAL if the channel is invalid or the period/pulse is out of range.
*/
static int pwm_mchp_set_cycles(const struct device *pwm_dev, uint32_t channel, uint32_t period,
uint32_t pulse, pwm_flags_t flags)
{
const pwm_mchp_config_t *const mchp_pwm_cfg = pwm_dev->config;
pwm_mchp_data_t *mchp_pwm_data = pwm_dev->data;
int ret_val = -EINVAL;
uint32_t top = (BIT(mchp_pwm_cfg->max_bit_width) - 1);
MCHP_PWM_DATA_LOCK(&mchp_pwm_data->lock);
if (channel >= mchp_pwm_cfg->channels) {
LOG_ERR("channel %d is invalid", channel);
} else if ((period > top) || (pulse > top)) {
LOG_ERR("period or pulse is out of range");
} else {
bool invert_flag_set = ((flags & PWM_POLARITY_INVERTED) != 0);
bool not_inverted = tcc_get_invert_status(mchp_pwm_cfg->regs, channel);
if ((invert_flag_set == true) && (not_inverted == true)) {
tcc_set_invert(mchp_pwm_cfg->regs, channel);
}
PWM_REG(mchp_pwm_cfg->regs)->TCC_CCBUF[channel] = TCC_CCBUF_CCBUF(pulse);
PWM_REG(mchp_pwm_cfg->regs)->TCC_PER = TCC_PER_PER(period);
ret_val = MCHP_PWM_SUCCESS;
}
MCHP_PWM_DATA_UNLOCK(&mchp_pwm_data->lock);
return ret_val;
}
/**
* @brief Get the number of PWM cycles per second for a specific channel.
*
* This function retrieves the frequency of the PWM signal in cycles per second for a specified
* channel.
*
* @param pwm_dev Pointer to the PWM device structure.
* @param channel PWM channel number.
* @param cycles Pointer to store the number of cycles per second.
*
* @return 0 on success, -EINVAL if the channel is invalid.
*/
static int pwm_mchp_get_cycles_per_sec(const struct device *pwm_dev, uint32_t channel,
uint64_t *cycles)
{
const pwm_mchp_config_t *const mchp_pwm_cfg = pwm_dev->config;
pwm_mchp_data_t *mchp_pwm_data = pwm_dev->data;
uint32_t periph_clk_freq = 0;
int ret_val = -EINVAL;
MCHP_PWM_DATA_LOCK(&mchp_pwm_data->lock);
if (channel < (mchp_pwm_cfg->channels)) {
/* clang-format off */
clock_control_get_rate(
mchp_pwm_cfg->pwm_clock.clock_dev,
mchp_pwm_cfg->pwm_clock.periph_async_clk,
&periph_clk_freq);
/* clang-format on */
*cycles = periph_clk_freq / mchp_pwm_cfg->prescaler;
ret_val = MCHP_PWM_SUCCESS;
} else {
LOG_ERR("channel %d is invalid", channel);
}
MCHP_PWM_DATA_UNLOCK(&mchp_pwm_data->lock);
return ret_val;
}
/******************************************************************************
* @brief Zephyr driver instance creation
*****************************************************************************/
/**
* @brief PWM driver API structure for the Microchip PWM device.
*
* This structure defines the API functions for the Microchip PWM driver, including setting PWM
* cycles, getting the number of cycles per second, and optionally configuring, enabling, and
* disabling PWM capture.
*/
static DEVICE_API(pwm, pwm_mchp_api) = {
.set_cycles = pwm_mchp_set_cycles,
.get_cycles_per_sec = pwm_mchp_get_cycles_per_sec,
};
/**
* @brief Initialize the Microchip PWM device.
*
* This function initializes the Microchip PWM device by applying the pin control configuration and
* initializing the PWM hardware with the specified prescaler.
*
* @param pwm_dev Pointer to the PWM device structure.
*
* @return 0 on success, negative error code on failure.
*/
static int pwm_mchp_init(const struct device *pwm_dev)
{
int ret_val;
const pwm_mchp_config_t *const mchp_pwm_cfg = pwm_dev->config;
pwm_mchp_data_t *mchp_pwm_data = pwm_dev->data;
MCHP_PWM_DATA_LOCK_INIT(&mchp_pwm_data->lock);
do {
ret_val = clock_control_on(mchp_pwm_cfg->pwm_clock.clock_dev,
mchp_pwm_cfg->pwm_clock.periph_async_clk);
if ((ret_val < 0) && (ret_val != -EALREADY)) {
LOG_ERR("Failed to enable the periph_async_clk for PWM: %d", ret_val);
break;
}
ret_val = clock_control_on(mchp_pwm_cfg->pwm_clock.clock_dev,
mchp_pwm_cfg->pwm_clock.host_core_sync_clk);
if ((ret_val < 0) && (ret_val != -EALREADY)) {
LOG_ERR("Failed to enable the host_core_sync_clk for PWM: %d", ret_val);
break;
}
ret_val = pinctrl_apply_state(mchp_pwm_cfg->pinctrl_config, PINCTRL_STATE_DEFAULT);
if (ret_val < 0) {
LOG_ERR("Pincontrol apply state failed %d", ret_val);
break;
}
tcc_init(mchp_pwm_cfg->regs, mchp_pwm_cfg->prescaler);
} while (0);
ret_val = (ret_val == -EALREADY) ? 0 : ret_val;
return ret_val;
}
/**
* @brief Macro to define the PWM data structure for a specific instance.
*
* This macro defines the PWM data structure for a specific instance of the Microchip PWM device.
*
* @param n Instance number.
* @param ip IP identifier.
*/
#define PWM_MCHP_DATA_DEFN(n) static pwm_mchp_data_t pwm_mchp_data_##n
/**
* @brief Macro to assign clock configurations for the Microchip PWM device.
*
* This macro assigns the clock configurations for the PWM device, including the
* host core synchronous clock, and
* peripheral asynchronous clock (conditionally).
*
* @param n Device tree node number.
*
* @note This macro conditionally includes peripheral asynchronous clock configurations based on
* the presence of relevant device tree properties.
*/
/* clang-format off */
#define PWM_MCHP_CLOCK_ASSIGN(n) \
.pwm_clock.clock_dev = DEVICE_DT_GET(DT_NODELABEL(clock)), \
.pwm_clock.host_core_sync_clk = (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, subsystem)),\
COND_CODE_1(DT_NODE_EXISTS(DT_INST_CLOCKS_CTLR_BY_NAME(n, rtcclk)), \
(.pwm_clock.periph_async_clk = \
(void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, rtcclk, subsystem)),), \
()) COND_CODE_1(DT_NODE_EXISTS(DT_INST_CLOCKS_CTLR_BY_NAME(n, gclk)), \
(.pwm_clock.periph_async_clk = (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, subsystem)),),\
())
/* clang-format on */
/**
* @brief Macro to define the PWM configuration structure for a specific instance.
*
* This macro defines the PWM configuration structure for a specific instance of the Microchip PWM
* device.
*
* @param n Instance number.
*/
#define PWM_MCHP_CONFIG_DEFN(n) \
static const pwm_mchp_config_t pwm_mchp_config_##n = { \
.prescaler = DT_INST_PROP(n, prescaler), \
.pinctrl_config = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
.channels = DT_INST_PROP(n, channels), \
.regs = (void *)DT_INST_REG_ADDR(n), \
.max_bit_width = DT_INST_PROP(n, max_bit_width), \
PWM_MCHP_CLOCK_ASSIGN(n)}
/**
* @brief Macro to define the device structure for a specific instance of the PWM device.
*
* This macro defines the device structure for a specific instance of the Microchip PWM device.
* It uses the DEVICE_DT_INST_DEFINE macro to create the device instance with the specified
* initialization function, data structure, configuration structure, and driver API.
*
* @param n Instance number.
*/
#define PWM_MCHP_DEVICE_DT_DEFN(n) \
DEVICE_DT_INST_DEFINE(n, pwm_mchp_init, NULL, &pwm_mchp_data_##n, &pwm_mchp_config_##n, \
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, &pwm_mchp_api)
/**
*Initialize the PWM device with pin control, data, and configuration definitions.
*/
#define PWM_MCHP_DEVICE_INIT(n) \
PINCTRL_DT_INST_DEFINE(n); \
PWM_MCHP_DATA_DEFN(n); \
PWM_MCHP_CONFIG_DEFN(n); \
PWM_MCHP_DEVICE_DT_DEFN(n);
/* Run init macro for each pwm-generic node */
DT_INST_FOREACH_STATUS_OKAY(PWM_MCHP_DEVICE_INIT)