diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index faa741fa783..3bf0c3a8b6c 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -5,6 +5,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/pwm.h) zephyr_library() zephyr_library_sources_ifdef(CONFIG_PWM_TELINK_B91 pwm_b91.c) +zephyr_library_sources_ifdef(CONFIG_PWM_CC13XX_CC26XX_TIMER pwm_cc13xx_cc26xx_timer.c) zephyr_library_sources_ifdef(CONFIG_PWM_STM32 pwm_stm32.c) zephyr_library_sources_ifdef(CONFIG_PWM_SIFIVE pwm_sifive.c) zephyr_library_sources_ifdef(CONFIG_PWM_NRF5_SW pwm_nrf5_sw.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c7a7833496c..8b72b4ba4b7 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -35,6 +35,8 @@ config PWM_CAPTURE source "drivers/pwm/Kconfig.b91" +source "drivers/pwm/Kconfig.cc13xx_cc26xx_timer" + source "drivers/pwm/Kconfig.stm32" source "drivers/pwm/Kconfig.sifive" diff --git a/drivers/pwm/Kconfig.cc13xx_cc26xx_timer b/drivers/pwm/Kconfig.cc13xx_cc26xx_timer new file mode 100644 index 00000000000..c111ad307fa --- /dev/null +++ b/drivers/pwm/Kconfig.cc13xx_cc26xx_timer @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Zephyr Project +# SPDX-License-Identifier: Apache-2.0 + +config PWM_CC13XX_CC26XX_TIMER + bool "TI SimpleLink CC13xx/CC26xx GPT timer PWM driver" + default y + depends on DT_HAS_TI_CC13XX_CC26XX_TIMER_PWM_ENABLED + help + Enables TI SimpleLink CC13xx/CC26xx GPT timer PWM driver. diff --git a/drivers/pwm/pwm_cc13xx_cc26xx_timer.c b/drivers/pwm/pwm_cc13xx_cc26xx_timer.c new file mode 100644 index 00000000000..4855c134aa6 --- /dev/null +++ b/drivers/pwm/pwm_cc13xx_cc26xx_timer.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2023 Zephyr Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_cc13xx_cc26xx_timer_pwm + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#define LOG_MODULE_NAME pwm_cc13xx_cc26xx_timer +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_PWM_LOG_LEVEL); + +/* TODO: Clock frequency can be settable via KConfig, see TOP:PRCM:GPTCLKDIV */ +#define CPU_FREQ ((uint32_t)DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency)) + +/* GPT peripherals in 16 bit mode have maximum 24 counter bits incl. the + * prescaler. Count is set to (2^24 - 2) to allow for a glitch free 100% duty + * cycle at max. period count. + */ +#define PWM_COUNT_MAX 0xFFFFFE +#define PWM_INITIAL_PERIOD PWM_COUNT_MAX +#define PWM_INITIAL_DUTY 0U /* initially off */ + +struct pwm_cc13xx_cc26xx_data { +}; + +struct pwm_cc13xx_cc26xx_config { + const uint32_t gpt_base; /* GPT register base address */ + const struct pinctrl_dev_config *pcfg; + + LOG_INSTANCE_PTR_DECLARE(log); +}; + +static void write_value(const struct pwm_cc13xx_cc26xx_config *config, uint32_t value, + uint32_t prescale_register, uint32_t value_register) +{ + /* Upper byte represents the prescaler value. */ + uint8_t prescaleValue = 0xff & (value >> 16); + + HWREG(config->gpt_base + prescale_register) = prescaleValue; + + /* The remaining bytes represent the load / match value. */ + HWREG(config->gpt_base + value_register) = value & 0xffff; +} + +static int set_period_and_pulse(const struct pwm_cc13xx_cc26xx_config *config, uint32_t period, + uint32_t pulse) +{ + uint32_t match_value = pulse; + + if (pulse == 0U) { + TimerDisable(config->gpt_base, TIMER_B); +#ifdef CONFIG_PM + Power_releaseConstraint(PowerCC26XX_DISALLOW_STANDBY); +#endif + match_value = period + 1; + } + + /* Fail if period is out of range */ + if ((period > PWM_COUNT_MAX) || (period == 0)) { + LOG_ERR("Period (%d) is out of range.", period); + return -EINVAL; + } + + /* Compare to new period and fail if invalid */ + if (period < (match_value - 1) || (match_value < 0)) { + LOG_ERR("Period (%d) is shorter than pulse (%d).", period, pulse); + return -EINVAL; + } + + /* Store new period and update timer */ + write_value(config, period, GPT_O_TBPR, GPT_O_TBILR); + write_value(config, match_value, GPT_O_TBPMR, GPT_O_TBMATCHR); + + if (pulse > 0U) { +#ifdef CONFIG_PM + Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY); +#endif + TimerEnable(config->gpt_base, TIMER_B); + } + + LOG_DBG("Period and pulse successfully set."); + return 0; +} + +static int set_cycles(const struct device *dev, uint32_t channel, uint32_t period, uint32_t pulse, + pwm_flags_t flags) +{ + const struct pwm_cc13xx_cc26xx_config *config = dev->config; + + if (channel != 0) { + return -EIO; + } + + set_period_and_pulse(config, period, pulse); + + return 0; +} + +static int get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles) +{ + if (channel > 0) { + return -EIO; + } + + if (cycles) { + *cycles = CPU_FREQ; + } + + return 0; +} + +static const struct pwm_driver_api pwm_driver_api = { + .set_cycles = set_cycles, + .get_cycles_per_sec = get_cycles_per_sec, +}; + +#ifdef CONFIG_PM +static int get_timer_inst_number(const struct pwm_cc13xx_cc26xx_config *config) +{ + switch (config->gpt_base) { + case GPT0_BASE: + return 0; + case GPT1_BASE: + return 1; + case GPT2_BASE: + return 2; + case GPT3_BASE: + return 3; + default: + __ASSERT_UNREACHABLE; + } +} +#else +static int get_timer_peripheral(const struct pwm_cc13xx_cc26xx_config *config) +{ + switch (config->gpt_base) { + case GPT0_BASE: + return PRCM_PERIPH_TIMER0; + case GPT1_BASE: + return PRCM_PERIPH_TIMER1; + case GPT2_BASE: + return PRCM_PERIPH_TIMER2; + case GPT3_BASE: + return PRCM_PERIPH_TIMER3; + default: + __ASSERT_UNREACHABLE; + } +} +#endif /* CONFIG_PM */ + +static int init_pwm(const struct device *dev) +{ + const struct pwm_cc13xx_cc26xx_config *config = dev->config; + pinctrl_soc_pin_t pin = config->pcfg->states[0].pins[0]; + int ret; + +#ifdef CONFIG_PM + /* Set dependency on gpio resource to turn on power domains */ + Power_setDependency(get_timer_inst_number(config)); +#else + /* Enable peripheral power domain. */ + PRCMPowerDomainOn(PRCM_DOMAIN_PERIPH); + + /* Enable GPIO peripheral. */ + PRCMPeripheralRunEnable(get_timer_peripheral(config)); + + /* Load PRCM settings. */ + PRCMLoadSet(); + while (!PRCMLoadGet()) { + continue; + } +#endif /* CONFIG_PM */ + + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("failed to setup PWM pinctrl"); + return ret; + } + + /* Configures the PWM idle output level. + * + * TODO: Make PWM idle high/low configurable via custom DT PWM flag. + */ + GPIO_writeDio(pin.pin, 0); + + GPIO_setOutputEnableDio(pin.pin, GPIO_OUTPUT_ENABLE); + + /* Peripheral should not be accessed until power domain is on. */ + while (PRCMPowerDomainsAllOn(PRCM_DOMAIN_PERIPH) != PRCM_DOMAIN_POWER_ON) { + continue; + } + + TimerDisable(config->gpt_base, TIMER_B); + + HWREG(config->gpt_base + GPT_O_CFG) = GPT_CFG_CFG_16BIT_TIMER; + /* Stall timer when debugging. + * + * TODO: Make debug stall configurable via custom DT prop. + */ + HWREG(config->gpt_base + GPT_O_CTL) |= GPT_CTL_TBSTALL; + + /* TODO: Make PWM polarity configurable via DT PWM flag. */ + HWREG(config->gpt_base + GPT_O_TBMR) = GPT_TBMR_TBAMS_PWM | GPT_TBMR_TBMRSU_TOUPDATE | + GPT_TBMR_TBPWMIE_EN | GPT_TBMR_TBMR_PERIODIC; + + set_period_and_pulse(config, PWM_INITIAL_PERIOD, PWM_INITIAL_DUTY); + + return 0; +} + +#define DT_TIMER(idx) DT_INST_PARENT(idx) +#define DT_TIMER_BASE_ADDR(idx) (DT_REG_ADDR(DT_TIMER(idx))) + +#define PWM_DEVICE_INIT(idx) \ + PINCTRL_DT_INST_DEFINE(idx); \ + LOG_INSTANCE_REGISTER(LOG_MODULE_NAME, idx, CONFIG_PWM_LOG_LEVEL); \ + static const struct pwm_cc13xx_cc26xx_config pwm_cc13xx_cc26xx_##idx##_config = { \ + .gpt_base = DT_TIMER_BASE_ADDR(idx), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx), \ + LOG_INSTANCE_PTR_INIT(log, LOG_MODULE_NAME, idx)}; \ + \ + static struct pwm_cc13xx_cc26xx_data pwm_cc13xx_cc26xx_##idx##_data; \ + \ + DEVICE_DT_INST_DEFINE(idx, init_pwm, NULL, &pwm_cc13xx_cc26xx_##idx##_data, \ + &pwm_cc13xx_cc26xx_##idx##_config, POST_KERNEL, \ + CONFIG_PWM_INIT_PRIORITY, &pwm_driver_api) + +DT_INST_FOREACH_STATUS_OKAY(PWM_DEVICE_INIT); diff --git a/dts/arm/ti/cc13xx_cc26xx.dtsi b/dts/arm/ti/cc13xx_cc26xx.dtsi index b57349eb837..5c67e9df3f4 100644 --- a/dts/arm/ti/cc13xx_cc26xx.dtsi +++ b/dts/arm/ti/cc13xx_cc26xx.dtsi @@ -8,6 +8,7 @@ #include #include #include +#include / { chosen { @@ -95,6 +96,62 @@ }; }; + gpt0: timer@40010000 { + compatible = "ti,cc13xx-cc26xx-timer"; + reg = <0x40010000 0x1000>; + interrupts = <15 0 16 0>; + interrupt-names = "gpt0a", "gpt0b"; + status = "disabled"; + + pwm0: pwm { + compatible = "ti,cc13xx-cc26xx-timer-pwm"; + #pwm-cells = <1>; + status = "disabled"; + }; + }; + + gpt1: timer@40011000 { + compatible = "ti,cc13xx-cc26xx-timer"; + reg = <0x40011000 0x1000>; + interrupts = <17 0 18 0>; + interrupt-names = "gpt1a", "gpt1b"; + status = "disabled"; + + pwm1: pwm { + compatible = "ti,cc13xx-cc26xx-timer-pwm"; + #pwm-cells = <1>; + status = "disabled"; + }; + }; + + gpt2: timer@40012000 { + compatible = "ti,cc13xx-cc26xx-timer"; + reg = <0x40012000 0x1000>; + interrupts = <19 0 20 0>; + interrupt-names = "gpt2a", "gpt2b"; + status = "disabled"; + + pwm2: pwm { + compatible = "ti,cc13xx-cc26xx-timer-pwm"; + #pwm-cells = <1>; + status = "disabled"; + }; + }; + + gpt3: timer@40013000 { + compatible = "ti,cc13xx-cc26xx-timer"; + reg = <0x40013000 0x1000>; + interrupts = <21 0 22 0>; + interrupt-names = "gpt3a", "gpt3b"; + status = "disabled"; + + pwm3: pwm { + compatible = "ti,cc13xx-cc26xx-timer-pwm"; + #pwm-cells = <1>; + status = "disabled"; + }; + }; + uart0: uart@40001000 { compatible = "ti,cc13xx-cc26xx-uart"; reg = <0x40001000 0x1000>; diff --git a/dts/bindings/pwm/ti,cc13xx-cc26xx-timer-pwm.yaml b/dts/bindings/pwm/ti,cc13xx-cc26xx-timer-pwm.yaml new file mode 100644 index 00000000000..c63fc7ac1d7 --- /dev/null +++ b/dts/bindings/pwm/ti,cc13xx-cc26xx-timer-pwm.yaml @@ -0,0 +1,72 @@ +# Copyright (c) 2023, Zephyr Project +# SPDX-License-Identifier: Apache-2.0 + +description: | + TI SimpleLink CC13xx/CC26xx GPT timer PWM Controller Node + + To configure a PWM node, you first need to define a board overlay with a + pinctrl configuration for the pin on which the PWM signal should be present: + + &pinctrl { + gpt0_pwm: gpt0_pwm { + pinmux = <25 IOC_PORT_MCU_PORT_EVENT1>; + bias-disable; + drive-strength = <8>; /* in mA, can be 2, 4 or 8 */ + }; + }; + + Please be aware that the port event depends on the GPT instance chosen. The + following port events must be used for PWM: + - GPT0: IOC_PORT_MCU_PORT_EVENT1 + - GPT1: IOC_PORT_MCU_PORT_EVENT3 + - GPT2: IOC_PORT_MCU_PORT_EVENT5 + - GPT3: IOC_PORT_MCU_PORT_EVENT7 + + Be careful not to choose a pin that is already in use on your board, this + might irreversible damage to your board as the given pin will be configured as + output and actively driven by the PWM driver. + + Then enable the corresponding timer and PWM nodes and add a reference to the + pinctrl entry: + + &gpt0 { + status = "okay"; + }; + + &pwm0 { + status = "okay"; + pinctrl-0 = <&gpt0_pwm>; + pinctrl-names = "default"; + }; + + Now you can programmatically enable the PWM signal in your code: + + static const struct device *pwm = DEVICE_DT_GET(DT_NODELABEL(pwm0)); + + int init_pwm(void) + { + uint32_t pwm_period_ns, pwm_pulse_ns; + uint32_t pwm_duty_percent = 50U; + uint32_t pwm_frequency = 1000U; /* 1kHz */ + + if (!device_is_ready(pwm)) { + LOG_ERR("Error: PWM device %s is not ready\n", pwm->name); + return -ENODEV; + } + + pwm_period_ns = NSEC_PER_SEC / pwm_frequency; + pwm_pulse_ns = (pwm_duty_percent * pwm_period_ns) / 100; + + return pwm_set(pwm, 0, pwm_period_ns, pwm_pulse_ns, 0); + } + +compatible: "ti,cc13xx-cc26xx-timer-pwm" + +include: [base.yaml, pwm-controller.yaml, pinctrl-device.yaml] + +properties: + pinctrl-0: + required: true + +pwm-cells: +- period diff --git a/dts/bindings/timer/ti,cc13xx-cc26xx-timer.yaml b/dts/bindings/timer/ti,cc13xx-cc26xx-timer.yaml new file mode 100644 index 00000000000..33712c4364a --- /dev/null +++ b/dts/bindings/timer/ti,cc13xx-cc26xx-timer.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2023 Zephyr Project +# SPDX-License-Identifier: Apache-2.0 + +description: TI SimpleLink CC13xx/CC26xx Timer Node + +compatible: "ti,cc13xx-cc26xx-timer" + +include: base.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/tests/drivers/build_all/pwm/testcase.yaml b/tests/drivers/build_all/pwm/testcase.yaml index be5af6253f4..5a4477374ff 100644 --- a/tests/drivers/build_all/pwm/testcase.yaml +++ b/tests/drivers/build_all/pwm/testcase.yaml @@ -4,6 +4,9 @@ common: - drivers - pwm tests: + drivers.pwm.cc13xx_cc26xx_timer.build: + platform_allow: cc1352p1_launchxl + tags: pwm_cc13xx_cc26xx_timer drivers.pwm.gecko.build: platform_allow: efr32_radio_brd4250b tags: pwm_gecko