From 1ee9e1046a6e16b58d85d396e7cddb0387a53aed Mon Sep 17 00:00:00 2001 From: Daniel Leung Date: Wed, 18 Sep 2019 16:45:20 -0700 Subject: [PATCH] drivers/pwm: Add support for Microchip's XEC PWM devices A 16bits on/off based PWM, found on MEC1501. Signed-off-by: Tomasz Bursztyka Signed-off-by: Daniel Leung --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.xec | 13 + drivers/pwm/pwm_mchp_xec.c | 488 ++++++++++++++++++ .../mec1501/Kconfig.defconfig.mec1501hsz | 7 + 5 files changed, 511 insertions(+) create mode 100644 drivers/pwm/Kconfig.xec create mode 100644 drivers/pwm/pwm_mchp_xec.c diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 6fb43178bda..d23ce60d912 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -13,5 +13,6 @@ zephyr_library_sources_ifdef(CONFIG_PWM_IMX pwm_imx.c) zephyr_library_sources_ifdef(CONFIG_PWM_LED_ESP32 pwm_led_esp32.c) zephyr_library_sources_ifdef(CONFIG_PWM_SAM pwm_sam.c) zephyr_library_sources_ifdef(CONFIG_PWM_MCUX pwm_mcux.c) +zephyr_library_sources_ifdef(CONFIG_PWM_XEC pwm_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 7dab932cf0c..ecf20e78354 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -54,4 +54,6 @@ source "drivers/pwm/Kconfig.sam" source "drivers/pwm/Kconfig.mcux" +source "drivers/pwm/Kconfig.xec" + endif # PWM diff --git a/drivers/pwm/Kconfig.xec b/drivers/pwm/Kconfig.xec new file mode 100644 index 00000000000..1d7a2c8569f --- /dev/null +++ b/drivers/pwm/Kconfig.xec @@ -0,0 +1,13 @@ +# Kconfig.xec - Microchip XEC PWM configuration options +# +# +# Copyright (c) 2019 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +config PWM_XEC + bool "Microchip XEC PWM" + depends on SOC_FAMILY_MEC + help + Enable driver to utilize PWM on the Microchip XEC IP block. diff --git a/drivers/pwm/pwm_mchp_xec.c b/drivers/pwm/pwm_mchp_xec.c new file mode 100644 index 00000000000..a04aed7a5e1 --- /dev/null +++ b/drivers/pwm/pwm_mchp_xec.c @@ -0,0 +1,488 @@ +/* pwm_mchp_xec.c - Microchip XEC PWM driver */ + +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(counter_mchp_xec, CONFIG_PWM_LOG_LEVEL); + +#include +#include +#include + +#include + +/* Minimal on/off are 1 & 1 both are incremented, so 4. + * 0 cannot be set (used for full low/high output) so a + * combination of on_off of 2 is not possible. + */ +#define XEC_PWM_LOWEST_ON_OFF 4U +/* Maximal on/off are UINT16_T, both are incremented. + * Multiplied by the highest divider: 16 + */ +#define XEC_PWM_HIGHEST_ON_OFF (2U * (UINT16_MAX + 1U) * 16U) + +#define XEC_PWM_MIN_HIGH_CLK_FREQ \ + (MCHP_PWM_INPUT_FREQ_HI / XEC_PWM_HIGHEST_ON_OFF) +#define XEC_PWM_MAX_LOW_CLK_FREQ \ + (MCHP_PWM_INPUT_FREQ_LO / XEC_PWM_LOWEST_ON_OFF) +/* Precision factor for frequency calculation + * To mitigate frequency comparision up to the firt digit after 0. + */ +#define XEC_PWM_FREQ_PF 10U +/* Precision factor for DC calculation + * To avoid losing some digits after 0. + */ +#define XEC_PWM_DC_PF 100000U +/* Lowest reachable frequency */ +#define XEC_PWM_FREQ_LIMIT 1 /* 0.1hz * XEC_PWM_FREQ_PF */ + +struct pwm_xec_config { + u32_t base_address; +}; + +#define PWM_XEC_REG_BASE(_dev) \ + ((PWM_Type *) \ + ((const struct pwm_xec_config * const) \ + _dev->config->config_info)->base_address) + +#define PWM_XEC_CONFIG(_dev) \ + (((const struct pwm_xec_config * const) \ + _dev->config->config_info)) + +struct xec_params { + u32_t on; + u32_t off; + u8_t div; +}; + +u32_t max_freq_high_on_div[16] = { + 48000000, + 24000000, + 16000000, + 12000000, + 9600000, + 8000000, + 6857142, + 6000000, + 5333333, + 4800000, + 4363636, + 4000000, + 3692307, + 3428571, + 3200000, + 3000000 +}; + +u32_t max_freq_low_on_div[16] = { + 100000, + 50000, + 33333, + 25000, + 20000, + 16666, + 14285, + 12500, + 11111, + 10000, + 9090, + 8333, + 7692, + 7142, + 6666, + 6250 +}; + +static u32_t xec_compute_frequency(u32_t clk, u32_t on, u32_t off) +{ + return ((clk * XEC_PWM_FREQ_PF)/((on + 1) + (off + 1))); +} + +static u16_t xec_select_div(u32_t freq, u32_t max_freq[16]) +{ + u8_t i; + + if (freq >= max_freq[3]) { + return 0; + } + + freq *= XEC_PWM_LOWEST_ON_OFF; + + for (i = 0; i < 15; i++) { + if (freq >= max_freq[i]) { + break; + } + } + + return i; +} + +static void xec_compute_on_off(u32_t freq, u32_t dc, u32_t clk, + u32_t *on, u32_t *off) +{ + u32_t on_off; + + on_off = (clk * 10) / freq; + + *on = ((on_off * dc) / XEC_PWM_DC_PF) - 1; + *off = on_off - *on - 2; +} + +static u32_t xec_compute_dc(u32_t on, u32_t off) +{ + int dc = (on + 1) + (off + 1); + + dc = (((on + 1) * XEC_PWM_DC_PF) / dc); + + return (u32_t)dc; +} + +static u16_t xec_compare_div_on_off(u32_t target_freq, u32_t dc, + u32_t max_freq[16], + u8_t div_a, u8_t div_b, + u32_t *on_a, u32_t *off_a) +{ + u32_t freq_a, freq_b, on_b, off_b; + + xec_compute_on_off(target_freq, dc, max_freq[div_a], + on_a, off_a); + + freq_a = xec_compute_frequency(max_freq[div_a], *on_a, *off_a); + + xec_compute_on_off(target_freq, dc, max_freq[div_b], + &on_b, &off_b); + + freq_b = xec_compute_frequency(max_freq[div_b], on_b, off_b); + + if ((target_freq - freq_a) < (target_freq - freq_b)) { + if ((*on_a <= UINT16_MAX) && (*off_a <= UINT16_MAX)) { + return div_a; + } + } + + if ((on_b <= UINT16_MAX) && (off_b <= UINT16_MAX)) { + *on_a = on_b; + *off_a = off_b; + + return div_b; + } + + return div_a; +} + +static u8_t xec_select_best_div_on_off(u32_t target_freq, u32_t dc, + u32_t max_freq[16], + u32_t *on, u32_t *off) +{ + int div_comp; + u8_t div; + + div = xec_select_div(target_freq, max_freq); + + for (div_comp = (int)div - 1; div_comp >= 0; div_comp--) { + div = xec_compare_div_on_off(target_freq, dc, max_freq, + div, div_comp, on, off); + } + + return div; +} + +static struct xec_params *xec_compare_params(u32_t target_freq, + struct xec_params *hc_params, + struct xec_params *lc_params) +{ + struct xec_params *params; + u32_t freq_h = 0; + u32_t freq_l = 0; + + if (hc_params->div < UINT8_MAX) { + freq_h = xec_compute_frequency( + max_freq_high_on_div[hc_params->div], + hc_params->on, + hc_params->off); + } + + if (lc_params->div < UINT8_MAX) { + freq_l = xec_compute_frequency( + max_freq_low_on_div[lc_params->div], + lc_params->on, + lc_params->off); + } + + if (abs(target_freq - freq_h) < abs(target_freq - freq_l)) { + params = hc_params; + } else { + params = lc_params; + } + + LOG_DBG("\tFrequency (x%u): %u", XEC_PWM_FREQ_PF, freq_h); + LOG_DBG("\tOn %s clock, ON %u OFF %u DIV %u", + params == hc_params ? "High" : "Low", + params->on, params->off, params->div); + + return params; +} + +static void xec_compute_and_set_parameters(struct device *dev, + u32_t target_freq, + u32_t on, u32_t off) +{ + PWM_Type *pwm_regs = PWM_XEC_REG_BASE(dev); + bool compute_high, compute_low; + struct xec_params hc_params; + struct xec_params lc_params; + struct xec_params *params; + u32_t dc, reg; + + dc = xec_compute_dc(on, off); + + compute_high = (target_freq >= XEC_PWM_MIN_HIGH_CLK_FREQ); + compute_low = (target_freq <= XEC_PWM_MAX_LOW_CLK_FREQ); + + LOG_DBG("Target freq (x%u): %u and DC %u per-cent", + XEC_PWM_FREQ_PF, target_freq, (dc / 1000)); + + if (compute_high) { + if (!compute_low + && (on <= UINT16_MAX) + && (off <= UINT16_MAX)) { + hc_params.on = on; + hc_params.off = off; + hc_params.div = 0; + lc_params.div = UINT8_MAX; + + goto done; + } + + hc_params.div = xec_select_best_div_on_off( + target_freq, dc, + max_freq_high_on_div, + &hc_params.on, + &hc_params.off); + LOG_DBG("Best div high: %u (on/off: %u/%u)", + hc_params.div, hc_params.on, hc_params.off); + } else { + hc_params.div = UINT8_MAX; + } + + if (compute_low) { + lc_params.div = xec_select_best_div_on_off( + target_freq, dc, + max_freq_low_on_div, + &lc_params.on, + &lc_params.off); + LOG_DBG("Best div low: %u (on/off: %u/%u)", + lc_params.div, lc_params.on, lc_params.off); + } else { + lc_params.div = UINT8_MAX; + } +done: + pwm_regs->CONFIG &= ~MCHP_PWM_CFG_ENABLE; + + reg = pwm_regs->CONFIG; + + params = xec_compare_params(target_freq, &hc_params, &lc_params); + if (params == &hc_params) { + reg |= MCHP_PWM_CFG_CLK_SEL_48M; + } else { + reg |= MCHP_PWM_CFG_CLK_SEL_100K; + } + + pwm_regs->COUNT_ON = params->on; + pwm_regs->COUNT_OFF = params->off; + reg |= MCHP_PWM_CFG_CLK_PRE_DIV(params->div); + reg |= MCHP_PWM_CFG_ENABLE; + + pwm_regs->CONFIG = reg; +} + +static int pwm_xec_pin_set(struct device *dev, u32_t pwm, + u32_t period_cycles, u32_t pulse_cycles) +{ + PWM_Type *pwm_regs = PWM_XEC_REG_BASE(dev); + u32_t target_freq; + u32_t on, off; + + if (pwm > 0) { + return -EIO; + } + + if (pulse_cycles > period_cycles) { + return -EINVAL; + } + + on = pulse_cycles; + off = period_cycles - pulse_cycles; + + target_freq = xec_compute_frequency(MCHP_PWM_INPUT_FREQ_HI, on, off); + if (target_freq < XEC_PWM_FREQ_LIMIT) { + LOG_DBG("Target frequency below limit"); + return -EINVAL; + } + + if ((pulse_cycles == 0U) && (period_cycles == 0U)) { + pwm_regs->CONFIG &= ~MCHP_PWM_CFG_ENABLE; + } else if ((pulse_cycles == 0U) && (period_cycles > 0U)) { + pwm_regs->COUNT_ON = 0; + pwm_regs->COUNT_OFF = 1; + } else if ((pulse_cycles > 0U) && (period_cycles == 0U)) { + pwm_regs->COUNT_ON = 1; + pwm_regs->COUNT_OFF = 0; + } else { + xec_compute_and_set_parameters(dev, target_freq, on, off); + } + + return 0; +} + +static int pwm_xec_get_cyclet_per_sec(struct device *dev, u32_t pwm, + u64_t *cycles) +{ + ARG_UNUSED(dev); + + if (pwm > 0) { + return -EIO; + } + + if (cycles) { + /* User does not have to know about lowest clock, + * the driver will select the most relevant one. + */ + *cycles = MCHP_PWM_INPUT_FREQ_HI; + } + + return 0; +} + +static int pwm_xec_init(struct device *dev) +{ + ARG_UNUSED(dev); + + return 0; +} + +static struct pwm_driver_api pwm_xec_api = { + .pin_set = pwm_xec_pin_set, + .get_cycles_per_sec = pwm_xec_get_cyclet_per_sec +}; + +#if defined(DT_INST_0_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_0 = { + .base_address = DT_INST_0_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_0, DT_INST_0_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_0, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_0_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_1_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_1 = { + .base_address = DT_INST_1_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_1, DT_INST_1_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_1, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_1_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_2_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_2 = { + .base_address = DT_INST_2_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_2, DT_INST_2_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_2, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_2_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_3_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_3 = { + .base_address = DT_INST_3_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_3, DT_INST_3_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_3, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_3_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_4_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_4 = { + .base_address = DT_INST_4_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_4, DT_INST_4_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_4, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_4_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_5_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_5 = { + .base_address = DT_INST_5_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_5, DT_INST_5_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_5, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_5_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_6_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_6 = { + .base_address = DT_INST_6_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_6, DT_INST_6_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_6, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_6_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_7_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_7 = { + .base_address = DT_INST_7_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_7, DT_INST_7_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_7, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_7_MICROCHIP_XEC_PWM */ + +#if defined(DT_INST_8_MICROCHIP_XEC_PWM) + +static struct pwm_xec_config pwm_xec_dev_config_8 = { + .base_address = DT_INST_8_MICROCHIP_XEC_PWM_BASE_ADDRESS +}; + +DEVICE_AND_API_INIT(pwm_xec_8, DT_INST_8_MICROCHIP_XEC_PWM_LABEL, + pwm_xec_init, NULL, &pwm_xec_dev_config_8, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &pwm_xec_api); + +#endif /* DT_INST_8_MICROCHIP_XEC_PWM */ diff --git a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz index a36308c6da8..6e3d9f245ae 100644 --- a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz +++ b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz @@ -56,4 +56,11 @@ config PS2_XEC endif # PS2 +if PWM + +config PWM_XEC + def_bool y + +endif # PWM + endif # SOC_MEC1501_HSZ