LPCXpresso55S69_MRuby/src/mrb_machine_impl/mrb_machine_pwm_impl.c

329 lines
12 KiB
C

#include <stdio.h>
/* Board */
#include "board.h"
/* SDK drivers */
#include "fsl_ctimer.h"
#include "fsl_gpio.h"
#include "fsl_iocon.h"
#include "fsl_power.h"
/* Gem private */
#include "machine-pwm/src/pwm.h"
/**
* LPC55S69 has 5 CTimers, each with 4 Match channels, each channel can route up to 2 pins
* Total: 40 PWM capable pins (at most)
* Actual: 30 pins (shared).
* Match channel 3 is special, normally it will be used as period channel
* TODO: Find a way to utilize channel 3 as normal PWM outputs
*/
#define PWM_IMPL_INST_COUNT 5
#define PWM_IMPL_INST_CH 4
/* FIXME: A reasonable clock for PWM resolution, could be determined dynamically. */
#define PWM_IMPL_REASONABLE_CLK (10000000UL) /* 10MHz */
#define PWM_IMPL_MR_RL_MASK \
(CTIMER_MCR_MR0RL_MASK | CTIMER_MCR_MR1RL_MASK | CTIMER_MCR_MR2RL_MASK | CTIMER_MCR_MR3RL_MASK)
/* Timer channel ID fields */
#define PWM_IMPL_CT_OFFSET_Pos 0
#define PWM_IMPL_CT_OFFSET_Msk (1U << PWM_IMPL_CT_OFFSET_Pos)
#define PWM_IMPL_CT_CH_Pos 1
#define PWM_IMPL_CT_CH_Msk (3U << PWM_IMPL_CT_CH_Pos)
#define PWM_IMPL_CT_INST_Pos 3
#define PWM_IMPL_CT_INST_Msk (0x1FU << PWM_IMPL_CT_INST_Pos)
#define PWM_IMPL_CT_OFFSET(x) ((x & PWM_IMPL_CT_OFFSET_Msk) >> PWM_IMPL_CT_OFFSET_Pos)
#define PWM_IMPL_CT_CH(x) ((x & PWM_IMPL_CT_CH_Msk) >> PWM_IMPL_CT_CH_Pos)
#define PWM_IMPL_CT_INST(x) ((x & PWM_IMPL_CT_INST_Msk) >> PWM_IMPL_CT_INST_Pos)
/* Pin ID fields */
#define PWM_IMPL_PIN_ID_Pos 0
#define PWM_IMPL_PIN_ID_Msk (0x1FU << PWM_IMPL_PIN_ID_Pos)
#define PWM_IMPL_PIN_PORT_Pos 5
#define PWM_IMPL_PIN_PORT_Msk (7U << PWM_IMPL_PIN_PORT_Pos)
#define PWM_IMPL_PIN_FUNC_Pos 8
#define PWM_IMPL_PIN_FUNC_Msk (0x0FU << PWM_IMPL_PIN_FUNC_Pos)
#define PWM_IMPL_PIN_VALID_Pos 31
#define PWM_IMPL_PIN_VALID_Msk (1U << PWM_IMPL_PIN_VALID_Pos)
#define PWM_IMPL_PIN_PORT(x) ((x & PWM_IMPL_PIN_PORT_Msk) >> PWM_IMPL_PIN_PORT_Pos)
#define PWM_IMPL_PIN_ID(x) ((x & PWM_IMPL_PIN_ID_Msk) >> PWM_IMPL_PIN_ID_Pos)
#define PWM_IMPL_PIN_FUNC(x) ((x & PWM_IMPL_PIN_FUNC_Msk) >> PWM_IMPL_PIN_FUNC_Pos)
#define PWM_IMPL_PIN_VALID(x) ((x & PWM_IMPL_PIN_VALID_Msk) >> PWM_IMPL_PIN_VALID_Pos)
#define PWM_IMPL_PIN(port, id, func) \
(((port << PWM_IMPL_PIN_PORT_Pos) & PWM_IMPL_PIN_PORT_Msk) | ((id << PWM_IMPL_PIN_ID_Pos) & PWM_IMPL_PIN_ID_Msk) | \
((func << PWM_IMPL_PIN_FUNC_Pos) & PWM_IMPL_PIN_FUNC_Msk) | PWM_IMPL_PIN_VALID_Msk)
static uint32_t const s_pwm_channel_map[PWM_IMPL_INST_COUNT][PWM_IMPL_INST_CH * 2] = {
{
PWM_IMPL_PIN(0, 0, 3), PWM_IMPL_PIN(0, 30, 3), // CT0MAT0, Channel ID: 0, 1
PWM_IMPL_PIN(0, 3, 2), PWM_IMPL_PIN(0, 31, 3), // CT0MAT1, Channel ID: 2, 3
PWM_IMPL_PIN(0, 19, 3), PWM_IMPL_PIN(1, 31, 3), // CT0MAT2, Channel ID: 4, 5
0x00000000UL, 0x00000000UL, // CT0MAT3, Channel ID: 6, 7
},
{
PWM_IMPL_PIN(0, 18, 3), PWM_IMPL_PIN(1, 10, 3), // CT1MAT0, Channel ID: 8, 9
PWM_IMPL_PIN(0, 20, 2), PWM_IMPL_PIN(1, 12, 3), // CT1MAT1, Channel ID: 10, 11
PWM_IMPL_PIN(0, 23, 2), PWM_IMPL_PIN(1, 14, 3), // CT1MAT2, Channel ID: 12, 13
0x00000000UL, 0x00000000UL, // CT1MAT3, Channel ID: 14, 15
},
{
PWM_IMPL_PIN(0, 10, 3), PWM_IMPL_PIN(1, 5, 3), // CT2MAT0, Channel ID: 16, 17
PWM_IMPL_PIN(1, 4, 3), PWM_IMPL_PIN(1, 6, 3), // CT2MAT1, Channel ID: 18, 19
PWM_IMPL_PIN(0, 11, 2), PWM_IMPL_PIN(1, 7, 3), // CT2MAT2, Channel ID: 20, 21
0x00000000UL, 0x00000000UL, // CT2MAT3, Channel ID: 22, 23
},
{
PWM_IMPL_PIN(0, 5, 3), 0x00000000UL, // CT3MAT0, Channel ID: 24, 25
PWM_IMPL_PIN(1, 19, 3), 0x00000000UL, // CT3MAT1, Channel ID: 26, 27
PWM_IMPL_PIN(0, 27, 3), PWM_IMPL_PIN(1, 21, 3), // CT3MAT2, Channel ID: 28, 29
0x00000000UL, 0x00000000UL, // CT3MAT3, Channel ID: 30, 31
},
{
PWM_IMPL_PIN(0, 6, 3), 0x00000000UL, // CT4MAT0, Channel ID: 32, 33
0x00000000UL, 0x00000000UL, // CT4MAT1, Channel ID: 34, 35
0x00000000UL, 0x00000000UL, // CT4MAT2, Channel ID: 36, 37
0x00000000UL, 0x00000000UL, // CT4MAT3, Channel ID: 38, 39
},
};
static clock_attach_id_t const s_pwm_clk_attach_list[] = {
kMAIN_CLK_to_CTIMER0, kMAIN_CLK_to_CTIMER1, kMAIN_CLK_to_CTIMER2, kMAIN_CLK_to_CTIMER3, kMAIN_CLK_to_CTIMER4,
};
static CTIMER_Type *const s_pwm_ct_list[] = {
CTIMER0, CTIMER1, CTIMER2, CTIMER3, CTIMER4,
};
static IRQn_Type const s_pwm_irq_list[] = {
CTIMER0_IRQn,
CTIMER1_IRQn,
CTIMER2_IRQn,
CTIMER3_IRQn,
};
static bool mrb_machine_pwm_ctimer_in_use(uint8_t ctimer_id) {
/* Determine whether CTIMERx is in use:
* 1: Check module power down status
* 2: Check module enable and counter running status
* 3: Check MATCH output settings and enable status (by MATCH action)
*/
clock_attach_id_t ct_att_id = s_pwm_clk_attach_list[ctimer_id];
const CTIMER_Type *ct_inst = s_pwm_ct_list[ctimer_id];
/* Check if the clock is configured by us */
if (ct_att_id != CLOCK_GetClockAttachId(ct_att_id)) {
return false;
}
/* Check if this CTIMER is enabled */
if ((ct_inst->TCR & CTIMER_TCR_CEN_MASK) == 0) {
return false;
}
/* Check if Match channel 3 is configured as reset. */
/* TODO: Find a way to utilize channel 3 as normal PWM outputs */
if ((ct_inst->MCR & CTIMER_MCR_MR3R_MASK) == 0) {
return false;
}
/* Check if any PWM outputs are enabled */
/* TODO: Find a way to utilize channel 3 as normal PWM outputs */
if ((ct_inst->PWMC & (CTIMER_PWMC_PWMEN0_MASK | CTIMER_PWMC_PWMEN1_MASK | CTIMER_PWMC_PWMEN2_MASK)) == 0) {
return false;
}
return true;
}
int mrb_machine_pwm_impl_init(uint32_t channel, machine_pwm_config_t *config) {
uint8_t ctimer_id = PWM_IMPL_CT_INST(channel);
uint8_t ctimer_mat = PWM_IMPL_CT_CH(channel);
uint8_t ctimer_off = PWM_IMPL_CT_OFFSET(channel);
/* Sanity checks */
if ((ctimer_id >= PWM_IMPL_INST_COUNT) || (ctimer_mat >= PWM_IMPL_INST_CH)) {
return -1;
}
uint32_t ctimer_pin = s_pwm_channel_map[ctimer_id][ctimer_mat * 2 + ctimer_off];
CTIMER_Type *ctimer_inst = s_pwm_ct_list[ctimer_id];
/* Check valid bit to see if there really is a pin for this channel */
if (PWM_IMPL_PIN_VALID(ctimer_pin) == 0) {
return -2;
}
if (!mrb_machine_pwm_ctimer_in_use(ctimer_id)) {
/* CTimer is not in use, initialize timer */
/* Disable interrupt for CTx */
DisableIRQ(s_pwm_irq_list[ctimer_id]);
CLOCK_AttachClk(s_pwm_clk_attach_list[ctimer_id]);
ctimer_config_t ct_cfg;
CTIMER_GetDefaultConfig(&ct_cfg);
ct_cfg.prescale = CLOCK_GetCTimerClkFreq(ctimer_id) / PWM_IMPL_REASONABLE_CLK;
CTIMER_Init(ctimer_inst, &ct_cfg);
CTIMER_EnableResetMatchChannel(ctimer_inst, kCTIMER_Match_3, true);
ctimer_inst->MR[3] = 0x00000000UL;
ctimer_inst->MCR |= (CTIMER_MCR_MR3RL_MASK);
CTIMER_StartTimer(ctimer_inst);
}
/* Calculate new reload value */
uint32_t rl_match_old = ctimer_inst->MR[3];
uint32_t rl_match_new = (PWM_IMPL_REASONABLE_CLK / config->freq);
uint32_t ch_enabled = ctimer_inst->MCR & PWM_IMPL_MR_RL_MASK;
/* Do not sync channels while we are fiddling with shadow registers */
ctimer_inst->MCR &= ~(PWM_IMPL_MR_RL_MASK);
/* Update period channel */
ctimer_inst->MSR[3] = rl_match_new;
/* To disable PWM output: Write MRx to 0xFFFFFFFF, to enable, enable reload from shadow. */
for (uint8_t i = 0; i < 3; i++) {
if (ctimer_inst->PWMC & (1 << i)) {
uint32_t new_duty = (uint32_t)((uint64_t)ctimer_inst->MSR[i] * rl_match_new / rl_match_old);
ctimer_inst->MSR[i] = new_duty;
}
}
/* New channel */
ctimer_inst->MSR[ctimer_mat] = rl_match_new - ((config->duty * rl_match_new) / 65536);
/* Start sync MRx values with shadow registers */
ctimer_inst->MCR |= ch_enabled | (1U << (CTIMER_MCR_MR0RL_SHIFT + ctimer_mat));
/* From now on, the pin will be controlled by MATx PWM */
ctimer_inst->PWMC |= (1 << ctimer_mat);
/* Function is retrieved from const array. */
uint32_t iocon_mode = IOCON_PIO_SLEW(0) | IOCON_DIGITAL_EN | IOCON_PIO_FUNC(PWM_IMPL_PIN_FUNC(ctimer_pin));
/* Setup pin */
IOCON_PinMuxSet(IOCON, PWM_IMPL_PIN_PORT(ctimer_pin), PWM_IMPL_PIN_ID(ctimer_pin), iocon_mode);
return 0;
}
int mrb_machine_pwm_impl_output_set(uint32_t channel, bool enable) {
uint8_t ctimer_id = PWM_IMPL_CT_INST(channel);
uint8_t ctimer_mat = PWM_IMPL_CT_CH(channel);
uint8_t ctimer_off = PWM_IMPL_CT_OFFSET(channel);
/* Sanity checks */
if ((ctimer_id >= PWM_IMPL_INST_COUNT) || (ctimer_mat >= PWM_IMPL_INST_CH)) {
return -1;
}
uint32_t ctimer_pin = s_pwm_channel_map[ctimer_id][ctimer_mat * 2 + ctimer_off];
CTIMER_Type *ctimer_inst = s_pwm_ct_list[ctimer_id];
/* Check valid bit to see if there really is a pin for this channel */
if (PWM_IMPL_PIN_VALID(ctimer_pin) == 0) {
return -2;
}
if (enable) {
ctimer_inst->MCR |= (1 << (CTIMER_MCR_MR0RL_SHIFT + ctimer_mat));
} else {
ctimer_inst->MCR &= ~(1 << (CTIMER_MCR_MR0RL_SHIFT + ctimer_mat));
ctimer_inst->MR[ctimer_mat] = 0xFFFFFFFFUL;
}
return 0;
}
bool mrb_machine_pwm_impl_output_get(uint32_t channel) {
uint8_t ctimer_id = PWM_IMPL_CT_INST(channel);
uint8_t ctimer_mat = PWM_IMPL_CT_CH(channel);
uint8_t ctimer_off = PWM_IMPL_CT_OFFSET(channel);
/* Sanity checks */
if ((ctimer_id >= PWM_IMPL_INST_COUNT) || (ctimer_mat >= PWM_IMPL_INST_CH)) {
return false;
}
uint32_t ctimer_pin = s_pwm_channel_map[ctimer_id][ctimer_off];
CTIMER_Type *ctimer_inst = s_pwm_ct_list[ctimer_id];
/* Check valid bit to see if there really is a pin for this channel */
if (PWM_IMPL_PIN_VALID(ctimer_pin) == 0) {
return false;
}
if (ctimer_inst->MCR & (1 << (CTIMER_MCR_MR0RL_SHIFT + ctimer_mat))) {
return true;
} else {
return false;
}
}
int mrb_machine_pwm_impl_duty_set(uint32_t channel, uint16_t duty) {
uint8_t ctimer_id = PWM_IMPL_CT_INST(channel);
uint8_t ctimer_mat = PWM_IMPL_CT_CH(channel);
uint8_t ctimer_off = PWM_IMPL_CT_OFFSET(channel);
/* Sanity checks */
if ((ctimer_id >= PWM_IMPL_INST_COUNT) || (ctimer_mat >= PWM_IMPL_INST_CH)) {
return -1;
}
uint32_t ctimer_pin = s_pwm_channel_map[ctimer_id][ctimer_off];
CTIMER_Type *ctimer_inst = s_pwm_ct_list[ctimer_id];
/* Check valid bit to see if there really is a pin for this channel */
if (PWM_IMPL_PIN_VALID(ctimer_pin) == 0) {
return -2;
}
ctimer_inst->MSR[ctimer_mat] = ctimer_inst->MSR[3] - ((uint32_t)duty * ctimer_inst->MSR[3] / 65536);
return 0;
}
uint16_t mrb_machine_pwm_impl_duty_get(uint32_t channel) {
uint8_t ctimer_id = PWM_IMPL_CT_INST(channel);
uint8_t ctimer_mat = PWM_IMPL_CT_CH(channel);
uint8_t ctimer_off = PWM_IMPL_CT_OFFSET(channel);
/* Sanity checks */
if ((ctimer_id >= PWM_IMPL_INST_COUNT) || (ctimer_mat >= PWM_IMPL_INST_CH)) {
return 0;
}
uint32_t ctimer_pin = s_pwm_channel_map[ctimer_id][ctimer_off];
CTIMER_Type *ctimer_inst = s_pwm_ct_list[ctimer_id];
/* Check valid bit to see if there really is a pin for this channel */
if (PWM_IMPL_PIN_VALID(ctimer_pin) == 0) {
return 0;
}
uint16_t duty = ctimer_inst->MSR[ctimer_mat] * 65536 / ctimer_inst->MSR[3];
return duty;
}