diff --git a/drivers/pwm/pwm_mspm0.c b/drivers/pwm/pwm_mspm0.c index 11211aebda6..b7d96416dd6 100644 --- a/drivers/pwm/pwm_mspm0.c +++ b/drivers/pwm/pwm_mspm0.c @@ -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, \ diff --git a/dts/bindings/pwm/ti,mspm0-pwm.yaml b/dts/bindings/pwm/ti,mspm0-pwm.yaml index 48b4f307be6..7ccb9656612 100644 --- a/dts/bindings/pwm/ti,mspm0-pwm.yaml +++ b/dts/bindings/pwm/ti,mspm0-pwm.yaml @@ -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