From 3e7ca8f2ad3a52525ac133ae6f02ddcfa29ddeee Mon Sep 17 00:00:00 2001 From: Henrik Brix Andersen Date: Tue, 3 Dec 2019 17:22:22 +0100 Subject: [PATCH] drivers: pwm: add driver for the RV32M1 Timer/PWM module Add driver for the OpenISA Timer/PWM (TPM) module present in the RV32M1 SoC. Signed-off-by: Henrik Brix Andersen --- drivers/pwm/CMakeLists.txt | 1 + drivers/pwm/Kconfig | 2 + drivers/pwm/Kconfig.rv32m1_tpm | 10 ++ drivers/pwm/pwm_rv32m1_tpm.c | 215 +++++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 drivers/pwm/Kconfig.rv32m1_tpm create mode 100644 drivers/pwm/pwm_rv32m1_tpm.c diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 86e23c80336..dcdd0c7b6ca 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -15,6 +15,7 @@ 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_PWM_LITEX pwm_litex.c) +zephyr_library_sources_ifdef(CONFIG_PWM_RV32M1_TPM pwm_rv32m1_tpm.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c) zephyr_library_sources_ifdef(CONFIG_PWM_SHELL pwm_shell.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c25c3702dba..6d07ed40171 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -61,4 +61,6 @@ source "drivers/pwm/Kconfig.xec" source "drivers/pwm/Kconfig.litex" +source "drivers/pwm/Kconfig.rv32m1_tpm" + endif # PWM diff --git a/drivers/pwm/Kconfig.rv32m1_tpm b/drivers/pwm/Kconfig.rv32m1_tpm new file mode 100644 index 00000000000..bc78ada3078 --- /dev/null +++ b/drivers/pwm/Kconfig.rv32m1_tpm @@ -0,0 +1,10 @@ +# RV32M1 TPM PWM + +# Copyright (c) 2019 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +config PWM_RV32M1_TPM + bool "RV32M1 TPM PWM driver" + depends on HAS_RV32M1_TPM && CLOCK_CONTROL + help + Enable the RV32M1 TPM PWM driver. diff --git a/drivers/pwm/pwm_rv32m1_tpm.c b/drivers/pwm/pwm_rv32m1_tpm.c new file mode 100644 index 00000000000..ed84ecc3e09 --- /dev/null +++ b/drivers/pwm/pwm_rv32m1_tpm.c @@ -0,0 +1,215 @@ +/* + * Copyright 2019 Henrik Brix Andersen + * + * Heavily based on pwm_mcux_ftm.c, which is: + * Copyright (c) 2017, NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL +#include +LOG_MODULE_REGISTER(pwm_rv32m1_tpm); + +#define MAX_CHANNELS ARRAY_SIZE(TPM0->CONTROLS) + +struct rv32m1_tpm_config { + TPM_Type *base; + char *clock_name; + clock_control_subsys_t clock_subsys; + tpm_clock_source_t tpm_clock_source; + tpm_clock_prescale_t prescale; + u8_t channel_count; + tpm_pwm_mode_t mode; +}; + +struct rv32m1_tpm_data { + u32_t clock_freq; + u32_t period_cycles; + tpm_chnl_pwm_signal_param_t channel[MAX_CHANNELS]; +}; + +static int rv32m1_tpm_pin_set(struct device *dev, u32_t pwm, + u32_t period_cycles, u32_t pulse_cycles, + pwm_flags_t flags) +{ + const struct rv32m1_tpm_config *config = dev->config->config_info; + struct rv32m1_tpm_data *data = dev->driver_data; + u8_t duty_cycle; + + if ((period_cycles == 0U) || (pulse_cycles > period_cycles)) { + LOG_ERR("Invalid combination: period_cycles=%d, " + "pulse_cycles=%d", period_cycles, pulse_cycles); + return -EINVAL; + } + + if (pwm >= config->channel_count) { + LOG_ERR("Invalid channel"); + return -ENOTSUP; + } + + duty_cycle = pulse_cycles * 100U / period_cycles; + data->channel[pwm].dutyCyclePercent = duty_cycle; + + if ((flags & PWM_POLARITY_INVERTED) == 0) { + data->channel[pwm].level = kTPM_HighTrue; + } else { + data->channel[pwm].level = kTPM_LowTrue; + } + + LOG_DBG("pulse_cycles=%d, period_cycles=%d, duty_cycle=%d, flags=%d", + pulse_cycles, period_cycles, duty_cycle, flags); + + if (period_cycles != data->period_cycles) { + u32_t pwm_freq; + status_t status; + + if (data->period_cycles != 0) { + /* Only warn when not changing from zero */ + LOG_WRN("Changing period cycles from %d to %d" + " affects all %d channels in %s", + data->period_cycles, period_cycles, + config->channel_count, dev->config->name); + } + + data->period_cycles = period_cycles; + + pwm_freq = (data->clock_freq >> config->prescale) / + period_cycles; + + LOG_DBG("pwm_freq=%d, clock_freq=%d", pwm_freq, + data->clock_freq); + + if (pwm_freq == 0U) { + LOG_ERR("Could not set up pwm_freq=%d", pwm_freq); + return -EINVAL; + } + + TPM_StopTimer(config->base); + + status = TPM_SetupPwm(config->base, data->channel, + config->channel_count, config->mode, + pwm_freq, data->clock_freq); + + if (status != kStatus_Success) { + LOG_ERR("Could not set up pwm"); + return -ENOTSUP; + } + TPM_StartTimer(config->base, config->tpm_clock_source); + } else { + TPM_UpdateChnlEdgeLevelSelect(config->base, pwm, + data->channel[pwm].level); + TPM_UpdatePwmDutycycle(config->base, pwm, config->mode, + duty_cycle); + } + + return 0; +} + +static int rv32m1_tpm_get_cycles_per_sec(struct device *dev, u32_t pwm, + u64_t *cycles) +{ + const struct rv32m1_tpm_config *config = dev->config->config_info; + struct rv32m1_tpm_data *data = dev->driver_data; + + *cycles = data->clock_freq >> config->prescale; + + return 0; +} + +static int rv32m1_tpm_init(struct device *dev) +{ + const struct rv32m1_tpm_config *config = dev->config->config_info; + struct rv32m1_tpm_data *data = dev->driver_data; + tpm_chnl_pwm_signal_param_t *channel = data->channel; + struct device *clock_dev; + tpm_config_t tpm_config; + int i; + + if (config->channel_count > ARRAY_SIZE(data->channel)) { + LOG_ERR("Invalid channel count"); + return -EINVAL; + } + + clock_dev = device_get_binding(config->clock_name); + if (clock_dev == NULL) { + LOG_ERR("Could not get clock device"); + return -EINVAL; + } + + if (clock_control_on(clock_dev, config->clock_subsys)) { + LOG_ERR("Could not turn on clock"); + return -EINVAL; + } + + if (clock_control_get_rate(clock_dev, config->clock_subsys, + &data->clock_freq)) { + LOG_ERR("Could not get clock frequency"); + return -EINVAL; + } + + for (i = 0; i < config->channel_count; i++) { + channel->chnlNumber = i; + channel->level = kTPM_NoPwmSignal; + channel->dutyCyclePercent = 0; + channel->firstEdgeDelayPercent = 0; + channel++; + } + + TPM_GetDefaultConfig(&tpm_config); + tpm_config.prescale = config->prescale; + + TPM_Init(config->base, &tpm_config); + + return 0; +} + +static const struct pwm_driver_api rv32m1_tpm_driver_api = { + .pin_set = rv32m1_tpm_pin_set, + .get_cycles_per_sec = rv32m1_tpm_get_cycles_per_sec, +}; + +#define TPM_DEVICE(n) \ + static const struct rv32m1_tpm_config rv32m1_tpm_config_##n = { \ + .base = (TPM_Type *) \ + DT_INST_##n##_OPENISA_RV32M1_TPM_BASE_ADDRESS, \ + .clock_name = \ + DT_INST_##n##_OPENISA_RV32M1_TPM_CLOCK_CONTROLLER, \ + .clock_subsys = (clock_control_subsys_t) \ + DT_INST_##n##_OPENISA_RV32M1_TPM_CLOCK_NAME, \ + .tpm_clock_source = kTPM_SystemClock, \ + .prescale = kTPM_Prescale_Divide_16, \ + .channel_count = FSL_FEATURE_TPM_CHANNEL_COUNTn((TPM_Type *) \ + DT_INST_##n##_OPENISA_RV32M1_TPM_BASE_ADDRESS), \ + .mode = kTPM_EdgeAlignedPwm, \ + }; \ + static struct rv32m1_tpm_data rv32m1_tpm_data_##n; \ + DEVICE_AND_API_INIT(rv32m1_tpm_##n, \ + DT_INST_##n##_OPENISA_RV32M1_TPM_LABEL, \ + &rv32m1_tpm_init, &rv32m1_tpm_data_##n, \ + &rv32m1_tpm_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &rv32m1_tpm_driver_api) + +#if DT_INST_0_OPENISA_RV32M1_TPM +TPM_DEVICE(0); +#endif /* DT_INST_0_OPENISA_RV32M1_TPM */ + +#if DT_INST_1_OPENISA_RV32M1_TPM +TPM_DEVICE(1); +#endif /* DT_INST_1_OPENISA_RV32M1_TPM */ + +#if DT_INST_2_OPENISA_RV32M1_TPM +TPM_DEVICE(2); +#endif /* DT_INST_2_OPENISA_RV32M1_TPM */ + +#if DT_INST_3_OPENISA_RV32M1_TPM +TPM_DEVICE(3); +#endif /* DT_INST_3_OPENISA_RV32M1_TPM */