drivers: cc13xx_cc26xx: pwm: introduce pwm driver

This change introduces a new PWM driver for all CC13/26xx SoC.

See the documentation in ti,cc13xx-cc26xx-timer-pwm.yaml for detailed
usage instructions.

Signed-off-by: Florian Grandel <fgrandel@code-for-humans.de>
This commit is contained in:
Florian Grandel 2023-07-25 21:11:52 +02:00 committed by Fabio Baltieri
parent f997dc3842
commit b954ce4903
8 changed files with 400 additions and 0 deletions

View file

@ -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)

View file

@ -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"

View file

@ -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.

View file

@ -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 <zephyr/drivers/i2c.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/pwm.h>
#include <driverlib/gpio.h>
#include <driverlib/prcm.h>
#include <driverlib/timer.h>
#include <inc/hw_memmap.h>
#include <inc/hw_types.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC26XX.h>
#include <zephyr/logging/log.h>
#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);

View file

@ -8,6 +8,7 @@
#include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/i2c/i2c.h>
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
/ {
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>;

View file

@ -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

View file

@ -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

View file

@ -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