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:
parent
f997dc3842
commit
b954ce4903
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
9
drivers/pwm/Kconfig.cc13xx_cc26xx_timer
Normal file
9
drivers/pwm/Kconfig.cc13xx_cc26xx_timer
Normal 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.
|
241
drivers/pwm/pwm_cc13xx_cc26xx_timer.c
Normal file
241
drivers/pwm/pwm_cc13xx_cc26xx_timer.c
Normal 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);
|
|
@ -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>;
|
||||
|
|
72
dts/bindings/pwm/ti,cc13xx-cc26xx-timer-pwm.yaml
Normal file
72
dts/bindings/pwm/ti,cc13xx-cc26xx-timer-pwm.yaml
Normal 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
|
15
dts/bindings/timer/ti,cc13xx-cc26xx-timer.yaml
Normal file
15
dts/bindings/timer/ti,cc13xx-cc26xx-timer.yaml
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue