drivers: pwm: Add a support for TI MSPM0 PWM capture
TI MSPM0 timer module has capture block used to capture timings of input signal. Add a support for TI MSPM0 PWM capture. Signed-off-by: Saravanan Sekar <saravanan@linumiz.com>
This commit is contained in:
parent
9709452cb6
commit
e051ec23ca
@ -24,6 +24,12 @@ LOG_MODULE_REGISTER(pwm_mspm0, CONFIG_PWM_LOG_LEVEL);
|
||||
/* capture and compare block count per timer */
|
||||
#define MSPM0_TIMER_CC_COUNT 2
|
||||
#define MSPM0_TIMER_CC_MAX 4
|
||||
#define MSPM0_CC_INTR_BIT_OFFSET 4
|
||||
|
||||
enum mspm0_capture_mode {
|
||||
CMODE_EDGE_TIME,
|
||||
CMODE_PULSE_WIDTH
|
||||
};
|
||||
|
||||
struct pwm_mspm0_config {
|
||||
const struct mspm0_sys_clock clock_subsys;
|
||||
@ -31,8 +37,12 @@ struct pwm_mspm0_config {
|
||||
const struct device *clock_dev;
|
||||
GPTIMER_Regs *base;
|
||||
DL_Timer_ClockConfig clk_config;
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
void (*irq_config_func)(const struct device *dev);
|
||||
#endif
|
||||
uint8_t cc_idx[MSPM0_TIMER_CC_MAX];
|
||||
uint8_t cc_idx_cnt;
|
||||
bool is_capture;
|
||||
};
|
||||
|
||||
struct pwm_mspm0_data {
|
||||
@ -41,6 +51,14 @@ struct pwm_mspm0_data {
|
||||
struct k_mutex lock;
|
||||
|
||||
DL_TIMER_PWM_MODE out_mode;
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
uint32_t last_sample;
|
||||
enum mspm0_capture_mode cmode;
|
||||
pwm_capture_callback_handler_t callback;
|
||||
pwm_flags_t flags;
|
||||
void *user_data;
|
||||
bool is_synced;
|
||||
#endif
|
||||
};
|
||||
|
||||
static void mspm0_setup_pwm_out(const struct pwm_mspm0_config *config,
|
||||
@ -137,6 +155,197 @@ static int mspm0_pwm_get_cycles_per_sec(const struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
#define MSPM0_CTRCTL_CAC_CCCTL_ACOND(x) (x << 10)
|
||||
|
||||
static void mspm0_set_combined_mode(const struct pwm_mspm0_config *config,
|
||||
struct pwm_mspm0_data *data)
|
||||
{
|
||||
DL_Timer_setLoadValue(config->base, data->period);
|
||||
DL_Timer_setCaptureCompareInput(config->base, 0,
|
||||
((config->cc_idx[0] & 0x1) ?
|
||||
DL_TIMER_CC_IN_SEL_CCPX : DL_TIMER_CC_IN_SEL_CCP0),
|
||||
config->cc_idx[0]);
|
||||
|
||||
DL_Timer_setCaptureCompareInput(config->base, 0,
|
||||
GPTIMER_IFCTL_01_ISEL_CCPX_INPUT_PAIR,
|
||||
(config->cc_idx[0] ^ 1));
|
||||
|
||||
DL_Timer_setCaptureCompareCtl(config->base,
|
||||
DL_TIMER_CC_MODE_CAPTURE,
|
||||
DL_TIMER_CC_CCOND_TRIG_FALL,
|
||||
config->cc_idx[0]);
|
||||
|
||||
DL_Timer_setCaptureCompareCtl(config->base,
|
||||
DL_TIMER_CC_MODE_CAPTURE,
|
||||
DL_TIMER_CC_CCOND_TRIG_RISE,
|
||||
(config->cc_idx[0] ^ 1));
|
||||
|
||||
DL_Timer_setCCPDirection(config->base, DL_TIMER_CC0_INPUT);
|
||||
|
||||
DL_Timer_setCounterControl(config->base,
|
||||
DL_TIMER_CZC_CCCTL0_ZCOND,
|
||||
MSPM0_CTRCTL_CAC_CCCTL_ACOND(config->cc_idx[0]),
|
||||
DL_TIMER_CLC_CCCTL0_LCOND);
|
||||
|
||||
DL_Timer_setCounterRepeatMode(config->base, DL_TIMER_REPEAT_MODE_ENABLED);
|
||||
DL_Timer_setCounterMode(config->base, DL_TIMER_COUNT_MODE_DOWN);
|
||||
}
|
||||
|
||||
static void mspm0_setup_capture(const struct device *dev,
|
||||
const struct pwm_mspm0_config *config,
|
||||
struct pwm_mspm0_data *data)
|
||||
{
|
||||
if (data->cmode == CMODE_EDGE_TIME) {
|
||||
DL_Timer_CaptureConfig cc_cfg = { 0 };
|
||||
|
||||
cc_cfg.inputChan = config->cc_idx[0];
|
||||
cc_cfg.period = data->period;
|
||||
cc_cfg.edgeCaptMode = DL_TIMER_CAPTURE_EDGE_DETECTION_MODE_RISING;
|
||||
|
||||
DL_Timer_initCaptureMode(config->base, &cc_cfg);
|
||||
} else {
|
||||
mspm0_set_combined_mode(config, data);
|
||||
}
|
||||
|
||||
DL_Timer_enableClock(config->base);
|
||||
config->irq_config_func(dev);
|
||||
}
|
||||
|
||||
static int mspm0_capture_configure(const struct device *dev,
|
||||
uint32_t channel,
|
||||
pwm_flags_t flags,
|
||||
pwm_capture_callback_handler_t cb,
|
||||
void *user_data)
|
||||
{
|
||||
const struct pwm_mspm0_config *config = dev->config;
|
||||
struct pwm_mspm0_data *data = dev->data;
|
||||
uint32_t intr_mask;
|
||||
|
||||
if (config->is_capture != true ||
|
||||
channel != 0) {
|
||||
LOG_ERR("Invalid channel %d", channel);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (flags & PWM_CAPTURE_TYPE_MASK) {
|
||||
case PWM_CAPTURE_TYPE_PULSE:
|
||||
case PWM_CAPTURE_TYPE_BOTH:
|
||||
case PWM_CAPTURE_TYPE_PERIOD:
|
||||
/* CCD1/CCD0 event for capture index 0/1 respectively */
|
||||
intr_mask = BIT(!(config->cc_idx[0]) + MSPM0_CC_INTR_BIT_OFFSET) |
|
||||
DL_TIMER_INTERRUPT_ZERO_EVENT;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* edge time event */
|
||||
intr_mask = BIT(config->cc_idx[0] + MSPM0_CC_INTR_BIT_OFFSET);
|
||||
}
|
||||
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
|
||||
/* If interrupt is enabled --> channel is on-going */
|
||||
if (DL_Timer_getEnabledInterrupts(config->base, intr_mask)) {
|
||||
LOG_ERR("Channel %d is busy", channel);
|
||||
k_mutex_unlock(&data->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
data->flags = flags;
|
||||
data->callback = cb;
|
||||
data->user_data = user_data;
|
||||
|
||||
k_mutex_unlock(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mspm0_capture_enable(const struct device *dev, uint32_t channel)
|
||||
{
|
||||
const struct pwm_mspm0_config *config = dev->config;
|
||||
struct pwm_mspm0_data *data = dev->data;
|
||||
uint32_t intr_mask;
|
||||
|
||||
if (config->is_capture != true ||
|
||||
channel != 0) {
|
||||
LOG_ERR("Invalid capture mode or channel");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!data->callback) {
|
||||
LOG_ERR("Callback is not configured");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (data->flags & PWM_CAPTURE_TYPE_MASK) {
|
||||
case PWM_CAPTURE_TYPE_PULSE:
|
||||
case PWM_CAPTURE_TYPE_BOTH:
|
||||
case PWM_CAPTURE_TYPE_PERIOD:
|
||||
/* CCD1/CCD0 event for capture index 0/1 respectively */
|
||||
intr_mask = BIT(!(config->cc_idx[0]) + MSPM0_CC_INTR_BIT_OFFSET) |
|
||||
DL_TIMER_INTERRUPT_ZERO_EVENT;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* edge time event */
|
||||
intr_mask = BIT(config->cc_idx[0] + MSPM0_CC_INTR_BIT_OFFSET);
|
||||
}
|
||||
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
|
||||
/* If interrupt is enabled --> channel is on-going */
|
||||
if (DL_Timer_getEnabledInterrupts(config->base, intr_mask)) {
|
||||
LOG_ERR("Channel %d is busy", channel);
|
||||
k_mutex_unlock(&data->lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
DL_Timer_setTimerCount(config->base, data->period);
|
||||
DL_Timer_startCounter(config->base);
|
||||
DL_Timer_clearInterruptStatus(config->base, intr_mask);
|
||||
DL_Timer_enableInterrupt(config->base, intr_mask);
|
||||
|
||||
k_mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mspm0_capture_disable(const struct device *dev, uint32_t channel)
|
||||
{
|
||||
const struct pwm_mspm0_config *config = dev->config;
|
||||
struct pwm_mspm0_data *data = dev->data;
|
||||
uint32_t intr_mask;
|
||||
|
||||
if (config->is_capture != true ||
|
||||
channel != 0) {
|
||||
LOG_ERR("Invalid channel");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (data->flags & PWM_CAPTURE_TYPE_MASK) {
|
||||
case PWM_CAPTURE_TYPE_PULSE:
|
||||
case PWM_CAPTURE_TYPE_BOTH:
|
||||
case PWM_CAPTURE_TYPE_PERIOD:
|
||||
/* CCD1/CCD0 event for capture index 0/1 respectively */
|
||||
intr_mask = BIT(!(config->cc_idx[0]) + MSPM0_CC_INTR_BIT_OFFSET) |
|
||||
DL_TIMER_INTERRUPT_ZERO_EVENT;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* edge time event */
|
||||
intr_mask = BIT(config->cc_idx[0] + MSPM0_CC_INTR_BIT_OFFSET);
|
||||
}
|
||||
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
|
||||
DL_Timer_disableInterrupt(config->base, intr_mask);
|
||||
DL_Timer_stopCounter(config->base);
|
||||
data->is_synced = false;
|
||||
k_mutex_unlock(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int pwm_mspm0_init(const struct device *dev)
|
||||
{
|
||||
const struct pwm_mspm0_config *config = dev->config;
|
||||
@ -163,8 +372,13 @@ static int pwm_mspm0_init(const struct device *dev)
|
||||
delay_cycles(CONFIG_MSPM0_PERIPH_STARTUP_DELAY);
|
||||
DL_Timer_setClockConfig(config->base,
|
||||
(DL_Timer_ClockConfig *)&config->clk_config);
|
||||
|
||||
mspm0_setup_pwm_out(config, data);
|
||||
if (config->is_capture) {
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
mspm0_setup_capture(dev, config, data);
|
||||
#endif
|
||||
} else {
|
||||
mspm0_setup_pwm_out(config, data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -172,9 +386,102 @@ static int pwm_mspm0_init(const struct device *dev)
|
||||
static const struct pwm_driver_api pwm_mspm0_driver_api = {
|
||||
.set_cycles = mspm0_pwm_set_cycles,
|
||||
.get_cycles_per_sec = mspm0_pwm_get_cycles_per_sec,
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
.configure_capture = mspm0_capture_configure,
|
||||
.enable_capture = mspm0_capture_enable,
|
||||
.disable_capture = mspm0_capture_disable,
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PWM_CAPTURE
|
||||
static void mspm0_cc_isr(const struct device *dev)
|
||||
{
|
||||
const struct pwm_mspm0_config *config = dev->config;
|
||||
struct pwm_mspm0_data *data = dev->data;
|
||||
uint32_t status;
|
||||
uint32_t cc1 = 0;
|
||||
uint32_t cc0 = 0;
|
||||
uint32_t period = 0;
|
||||
uint32_t pulse = 0;
|
||||
|
||||
status = DL_Timer_getPendingInterrupt(config->base);
|
||||
|
||||
switch (status) {
|
||||
case DL_TIMER_IIDX_CC0_DN:
|
||||
case DL_TIMER_IIDX_CC1_DN:
|
||||
break;
|
||||
|
||||
/* Timer reached zero no pwm signal is detected */
|
||||
case DL_TIMERG_IIDX_ZERO:
|
||||
if (data->callback &&
|
||||
!(data->flags & PWM_CAPTURE_MODE_CONTINUOUS)) {
|
||||
data->callback(dev, 0, 0, 0, -ERANGE, data->user_data);
|
||||
DL_Timer_stopCounter(config->base);
|
||||
}
|
||||
__fallthrough;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->flags & PWM_CAPTURE_TYPE_PERIOD) {
|
||||
cc1 = DL_Timer_getCaptureCompareValue(config->base,
|
||||
config->cc_idx[0] ^ 0x1);
|
||||
}
|
||||
|
||||
/* ignore the unsynced counter value for pwm mode */
|
||||
if (data->is_synced == false &&
|
||||
data->cmode != CMODE_EDGE_TIME) {
|
||||
data->last_sample = cc1;
|
||||
data->is_synced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->flags & PWM_CAPTURE_TYPE_PULSE ||
|
||||
data->cmode == CMODE_EDGE_TIME) {
|
||||
cc0 = DL_Timer_getCaptureCompareValue(config->base,
|
||||
config->cc_idx[0]);
|
||||
}
|
||||
|
||||
if (!(data->flags & PWM_CAPTURE_MODE_CONTINUOUS)) {
|
||||
DL_Timer_stopCounter(config->base);
|
||||
data->is_synced = false;
|
||||
}
|
||||
|
||||
|
||||
period = ((data->last_sample - cc1 + UINT16_MAX) % UINT16_MAX);
|
||||
pulse = ((data->last_sample - cc0 + UINT16_MAX) % UINT16_MAX);
|
||||
|
||||
/* fixme: random intermittent cc0 is greater than cc1 due to capture block error */
|
||||
if (pulse > period) {
|
||||
pulse -= period;
|
||||
}
|
||||
|
||||
if (data->callback && period) {
|
||||
data->callback(dev, 0, period, pulse, 0, data->user_data);
|
||||
}
|
||||
|
||||
data->last_sample = cc1;
|
||||
}
|
||||
|
||||
#define MSP_CC_IRQ_REGISTER(n) \
|
||||
static void mspm0_cc_## n ##_irq_register(const struct device *dev) \
|
||||
{ \
|
||||
const struct pwm_mspm0_config *config = dev->config; \
|
||||
if (!config->is_capture) { \
|
||||
return; \
|
||||
} \
|
||||
IRQ_CONNECT(DT_IRQN(DT_INST_PARENT(n)), \
|
||||
DT_IRQ(DT_INST_PARENT(n), priority), mspm0_cc_isr, \
|
||||
DEVICE_DT_INST_GET(n), 0); \
|
||||
irq_enable(DT_IRQN(DT_INST_PARENT(n))); \
|
||||
}
|
||||
#else
|
||||
#define MSP_CC_IRQ_REGISTER(n)
|
||||
#endif
|
||||
|
||||
#define MSPM0_PWM_MODE(mode) DT_CAT(DL_TIMER_PWM_MODE_, mode)
|
||||
#define MSPM0_CAPTURE_MODE(mode) DT_CAT(CMODE_, mode)
|
||||
#define MSPM0_CLK_DIV(div) DT_CAT(DL_TIMER_CLOCK_DIVIDE_, div)
|
||||
|
||||
#define MSPM0_CC_IDX_ARRAY(node_id, prop, idx) \
|
||||
@ -184,13 +491,20 @@ static const struct pwm_driver_api pwm_mspm0_driver_api = {
|
||||
.out_mode = MSPM0_PWM_MODE(DT_STRING_TOKEN(DT_DRV_INST(n), \
|
||||
ti_pwm_mode)),
|
||||
|
||||
#define MSPM0_CAPTURE_DATA(n) \
|
||||
IF_ENABLED(CONFIG_PWM_CAPTURE, \
|
||||
(.cmode = MSPM0_CAPTURE_MODE(DT_STRING_TOKEN(DT_DRV_INST(n), ti_cc_mode)),))
|
||||
|
||||
#define PWM_DEVICE_INIT_MSPM0(n) \
|
||||
static struct pwm_mspm0_data pwm_mspm0_data_ ## n = { \
|
||||
.period = DT_PROP(DT_DRV_INST(n), ti_period), \
|
||||
COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_pwm_mode), \
|
||||
(MSPM0_PWM_DATA(n)), ()) \
|
||||
COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_cc_mode), \
|
||||
(MSPM0_CAPTURE_DATA(n)), ()) \
|
||||
}; \
|
||||
PINCTRL_DT_INST_DEFINE(n); \
|
||||
COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_cc_mode), (MSP_CC_IRQ_REGISTER(n)), ()) \
|
||||
\
|
||||
static const struct pwm_mspm0_config pwm_mspm0_config_ ## n = { \
|
||||
.base = (GPTIMER_Regs *)DT_REG_ADDR(DT_INST_PARENT(n)), \
|
||||
@ -213,6 +527,10 @@ static const struct pwm_driver_api pwm_mspm0_driver_api = {
|
||||
ti_clk_div)), \
|
||||
.prescale = DT_PROP(DT_INST_PARENT(n), ti_clk_prescaler),\
|
||||
}, \
|
||||
.is_capture = DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_cc_mode), \
|
||||
IF_ENABLED(CONFIG_PWM_CAPTURE, \
|
||||
(.irq_config_func = COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), ti_cc_mode), \
|
||||
(mspm0_cc_## n ##_irq_register), (NULL)))) \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, \
|
||||
|
||||
@ -48,6 +48,18 @@ properties:
|
||||
- "EDGE_ALIGN_UP"
|
||||
- "CENTER_ALIGN"
|
||||
|
||||
ti,cc-mode:
|
||||
type: string
|
||||
description: |
|
||||
Select input capture mode:
|
||||
- EDGE_TIME: capture edge time mode.
|
||||
|
||||
- PULSE_WIDTH: capture pulse width and/or period mode.
|
||||
|
||||
enum:
|
||||
- "EDGE_TIME"
|
||||
- "PULSE_WIDTH"
|
||||
|
||||
ti,period:
|
||||
type: int
|
||||
required: true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user