This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 986394a56d46810940f7068972503f1649715ba9 Author: Ulaş Sertan Kemeç <[email protected]> AuthorDate: Thu Sep 10 09:19:46 2026 +0300 arch/arm/am67: Add eCAP APWM lower-half driver. Adds a PWM lower-half using the eCAP modules in APWM mode as single-channel generators rather than capture units. start() reprograms the time base only when the frequency changes, so a duty-only update does not stop or zero a live counter. t3-gem-o1 registers /dev/ecap1 and /dev/ecap2; output pad selection is left to the board, as the candidates collide with I2C0. Verified on t3-gem-o1: examples/pwm runs a 1 kHz, 50% duty train on both instances and accepts a duty-only change (25% then 75%). The output pins have not been measured on copper, as no pad is assigned to them on this board. Assisted-by: Claude Code:claude-opus-4-8 Signed-off-by: Ulaş Sertan Kemeç <[email protected]> --- arch/arm/src/am67/Kconfig | 32 + arch/arm/src/am67/Make.defs | 3 + arch/arm/src/am67/am67_ecap.c | 703 +++++++++++++++++++++ .../arm/src/am67/am67_ecap.h | 89 ++- arch/arm/src/am67/am67_ecap_hw.h | 172 +++++ arch/arm/src/am67/am67_pinmux.c | 59 ++ arch/arm/src/am67/am67_pinmux.h | 1 + boards/arm/am67/t3-gem-o1/configs/nsh/defconfig | 2 + boards/arm/am67/t3-gem-o1/src/am67_bringup.c | 10 + 9 files changed, 1022 insertions(+), 49 deletions(-) diff --git a/arch/arm/src/am67/Kconfig b/arch/arm/src/am67/Kconfig index 6fed471a433..e11fb0fb044 100644 --- a/arch/arm/src/am67/Kconfig +++ b/arch/arm/src/am67/Kconfig @@ -34,6 +34,38 @@ config AM67_EPWM1 supported. +config AM67_ECAP0 + bool "AM67 eCAP0 APWM driver" + default n + depends on PWM + ---help--- + Enable the eCAP0 module as a single-channel Auxiliary PWM + (APWM) output. Registered as /dev/ecap0 (kept out of the + /dev/pwmN namespace, which belongs to the EPWM instances). + The default output pad is ball A23 (mode 8 = + ECAP0_IN_APWM0_OUT), chosen to avoid the C20 collision with + EPWM0_B; confirm it reaches the HAT header before relying on + it. + +config AM67_ECAP1 + bool "AM67 eCAP1 APWM driver" + default n + depends on PWM + ---help--- + Enable the eCAP1 module as a single-channel APWM output, + registered as /dev/ecap1. Output pad selection is a board TODO + (candidates collide with I2C0); see am67_pinmux.c. + +config AM67_ECAP2 + bool "AM67 eCAP2 APWM driver" + default n + depends on PWM + ---help--- + Enable the eCAP2 module as a single-channel APWM output, + registered as /dev/ecap2. Output pad selection is a board TODO + (candidates collide with I2C0); see am67_pinmux.c. + + config AM67_MCSPI0 bool "AM67 MCU_MCSPI0 master driver" default n diff --git a/arch/arm/src/am67/Make.defs b/arch/arm/src/am67/Make.defs index b7049b1baf9..0ef8b2e80a2 100644 --- a/arch/arm/src/am67/Make.defs +++ b/arch/arm/src/am67/Make.defs @@ -36,6 +36,9 @@ endif ifeq ($(CONFIG_AM67_EPWM0),y) CHIP_CSRCS += am67_pwm.c endif +ifneq ($(filter y,$(CONFIG_AM67_ECAP0) $(CONFIG_AM67_ECAP1) $(CONFIG_AM67_ECAP2)),) +CHIP_CSRCS += am67_ecap.c +endif ifeq ($(CONFIG_AM67_MCSPI0),y) CHIP_CSRCS += am67_mcspi.c endif diff --git a/arch/arm/src/am67/am67_ecap.c b/arch/arm/src/am67/am67_ecap.c new file mode 100644 index 00000000000..a820a37ca97 --- /dev/null +++ b/arch/arm/src/am67/am67_ecap.c @@ -0,0 +1,703 @@ +/**************************************************************************** + * arch/arm/src/am67/am67_ecap.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <assert.h> +#include <debug.h> +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> + +#include <nuttx/timers/pwm.h> + +#include "arm_internal.h" +#include "am67_pinmux.h" +#include "am67_ecap.h" +#include "am67_ecap_hw.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* eCAP FICLK = MAIN_SYSCLK0/4 = half the EPWM FICLK (measured 250 MHz). + * TRM Table 4-344. + */ + +#define AM67_ECAP_VBUSCLK_HZ 125000000u + +/* APRD is 32-bit; the few-tick floor leaves room for the duty compare. */ + +#define AM67_ECAP_MIN_TICKS 2u +#define AM67_ECAP_MAX_TICKS 0xffffffffu + +/* MAIN_CTRL_MMR partition 1 lock (kick) - the same unlock EPWM performs in + * am67_epwm_init, so CTRL_MMR-side writes can land. + */ + +#define AM67_MAIN_CTRL_MMR_BASE 0x00100000 +#define AM67_CTRL_MMR_LOCK1_KICK0 0x5008 +#define AM67_CTRL_MMR_LOCK1_KICK1 0x500c +#define AM67_CTRL_MMR_KICK0_UNLOCK_KEY 0x68ef3490 +#define AM67_CTRL_MMR_KICK0_UNLOCKED (1u << 0) +#define AM67_CTRL_MMR_KICK1_UNLOCK_KEY 0xd172bc5a + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Lower-half state; ops must stay first so this casts to and from + * struct pwm_lowerhalf_s. + */ + +struct am67_ecap_s +{ + const struct pwm_ops_s *ops; + uint32_t base; + uint32_t frequency; /* Cached running frequency (0 = stopped) */ + uint32_t period; /* Cached APRD+1 ticks, for the duty math */ + uint8_t pinmux_id; /* Module number for pad muxing */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* PWM driver methods */ + +static int am67_ecap_setup(struct pwm_lowerhalf_s *dev); +static int am67_ecap_shutdown(struct pwm_lowerhalf_s *dev); +static int am67_ecap_start(struct pwm_lowerhalf_s *dev, + const struct pwm_info_s *info); +static int am67_ecap_stop(struct pwm_lowerhalf_s *dev); +static int am67_ecap_ioctl(struct pwm_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct pwm_ops_s g_am67_ecapops = +{ + .setup = am67_ecap_setup, + .shutdown = am67_ecap_shutdown, + .start = am67_ecap_start, + .stop = am67_ecap_stop, + .ioctl = am67_ecap_ioctl, +}; + +#ifdef CONFIG_AM67_ECAP0 +static struct am67_ecap_s g_am67_ecap0 = +{ + .ops = &g_am67_ecapops, + .base = AM67_ECAP0_BASE, + .pinmux_id = 0, +}; +#endif + +#ifdef CONFIG_AM67_ECAP1 +static struct am67_ecap_s g_am67_ecap1 = +{ + .ops = &g_am67_ecapops, + .base = AM67_ECAP1_BASE, + .pinmux_id = 1, +}; +#endif + +#ifdef CONFIG_AM67_ECAP2 +static struct am67_ecap_s g_am67_ecap2 = +{ + .ops = &g_am67_ecapops, + .base = AM67_ECAP2_BASE, + .pinmux_id = 2, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_ecap_getreg + * + * Description: + * Get a 32-bit register value by offset. + * + ****************************************************************************/ + +static inline uint32_t am67_ecap_getreg(uint32_t base, uint32_t offset) +{ + return getreg32(base + offset); +} + +/**************************************************************************** + * Name: am67_ecap_putreg + * + * Description: + * Put a 32-bit register value by offset. + * + ****************************************************************************/ + +static inline void am67_ecap_putreg(uint32_t base, uint32_t offset, + uint32_t value) +{ + putreg32(value, base + offset); +} + +/**************************************************************************** + * Name: am67_ecap_enable_register_write + * + * Description: + * Unlock MAIN_CTRL_MMR partition 1 (kick lock), mirroring am67_epwm_init. + * The KICK0 status bit is read back to confirm the partition is unlocked. + * + * Returned Value: + * Zero (OK) on success; -EIO if the partition is still locked. + * + ****************************************************************************/ + +static int am67_ecap_enable_register_write(void) +{ + uint32_t regval = am67_ecap_getreg(AM67_MAIN_CTRL_MMR_BASE, + AM67_CTRL_MMR_LOCK1_KICK0); + + if ((regval & AM67_CTRL_MMR_KICK0_UNLOCKED) == 0u) + { + am67_ecap_putreg(AM67_MAIN_CTRL_MMR_BASE, AM67_CTRL_MMR_LOCK1_KICK0, + AM67_CTRL_MMR_KICK0_UNLOCK_KEY); + am67_ecap_putreg(AM67_MAIN_CTRL_MMR_BASE, AM67_CTRL_MMR_LOCK1_KICK1, + AM67_CTRL_MMR_KICK1_UNLOCK_KEY); + } + + regval = am67_ecap_getreg(AM67_MAIN_CTRL_MMR_BASE, + AM67_CTRL_MMR_LOCK1_KICK0); + + if ((regval & AM67_CTRL_MMR_KICK0_UNLOCKED) == 0u) + { + pwmerr("ERROR: Could not unlock CTRL_MMR partition 1\n"); + return -EIO; + } + + return OK; +} + +/**************************************************************************** + * Name: am67_ecap_reset_counter + * + * Description: + * Zero the time-base counter (TSCTR). + * + ****************************************************************************/ + +static void am67_ecap_reset_counter(uint32_t base) +{ + am67_ecap_putreg(base, AM67_ECAP_TSCNT_OFFSET, 0u); +} + +/**************************************************************************** + * Name: am67_ecap_config_apwm + * + * Description: + * Compose ECCTL for frozen APWM output (start() ignites the counter). + * Full write - this function owns every policy field. + * + ****************************************************************************/ + +static void am67_ecap_config_apwm(uint32_t base) +{ + uint32_t regval; + + regval = AM67_ECAP_ECCTL_CAP_APWM; /* capture -> APWM mode */ + regval |= AM67_ECAP_ECCTL_SYNCO_SEL_DIS; /* no sync-out */ + + /* Left 0: APWMPOL (active hi), TSCNTSTP, SYNCI_EN, CONT_ONESHT */ + + am67_ecap_putreg(base, AM67_ECAP_ECCTL_OFFSET, regval); +} + +/**************************************************************************** + * Name: am67_ecap_counter_run + * + * Description: + * Ignition: set TSCNTSTP so the counter counts up. + * + ****************************************************************************/ + +static void am67_ecap_counter_run(uint32_t base) +{ + uint32_t regval = am67_ecap_getreg(base, AM67_ECAP_ECCTL_OFFSET); + + regval |= AM67_ECAP_ECCTL_TSCNTSTP; + am67_ecap_putreg(base, AM67_ECAP_ECCTL_OFFSET, regval); +} + +/**************************************************************************** + * Name: am67_ecap_counter_freeze + * + * Description: + * Freeze the counter (clear TSCNTSTP). + * + ****************************************************************************/ + +static void am67_ecap_counter_freeze(uint32_t base) +{ + uint32_t regval = am67_ecap_getreg(base, AM67_ECAP_ECCTL_OFFSET); + + regval &= ~AM67_ECAP_ECCTL_TSCNTSTP; + am67_ecap_putreg(base, AM67_ECAP_ECCTL_OFFSET, regval); +} + +/**************************************************************************** + * Name: am67_ecap_calculate_period + * + * Description: + * Map a frequency to the 32-bit APWM period (FICLK / frequency). + * + * Input Parameters: + * frequency - requested output frequency in Hz. + * period - out: period in ticks (== APRD + 1). + * + * Returned Value: + * OK on success; -ERANGE if the frequency cannot be produced. + * + ****************************************************************************/ + +static int am67_ecap_calculate_period(uint32_t frequency, uint32_t *period) +{ + uint32_t ticks; + + if (frequency == 0u) + { + return -ERANGE; + } + + ticks = AM67_ECAP_VBUSCLK_HZ / frequency; + + if (ticks < AM67_ECAP_MIN_TICKS || ticks > AM67_ECAP_MAX_TICKS) + { + return -ERANGE; + } + + *period = ticks; + return OK; +} + +/**************************************************************************** + * Name: am67_ecap_write_immediate + * + * Description: + * Load period/compare into the ACTIVE CAP1/CAP2. In APWM mode this also + * copies into the shadows, so the change is immediate - use on (re)start. + * + * Input Parameters: + * base - eCAP module base address. + * period - APRD value (period in ticks minus 1). + * compare - ACMP value (on-time in ticks, active high). + * + ****************************************************************************/ + +static void am67_ecap_write_immediate(uint32_t base, uint32_t period, + uint32_t compare) +{ + am67_ecap_putreg(base, AM67_ECAP_CAP1_OFFSET, period); + am67_ecap_putreg(base, AM67_ECAP_CAP2_OFFSET, compare); +} + +/**************************************************************************** + * Name: am67_ecap_write_shadow + * + * Description: + * Load period/compare into the SHADOW CAP3/CAP4. The hardware transfers + * them to CAP1/CAP2 at the next CTR = PRD event, so a run-time update + * lands on a period boundary without chopping the pulse. + * + * Input Parameters: + * base - eCAP module base address. + * period - APRD shadow value (period in ticks minus 1). + * compare - ACMP shadow value (on-time in ticks, active high). + * + ****************************************************************************/ + +static void am67_ecap_write_shadow(uint32_t base, uint32_t period, + uint32_t compare) +{ + am67_ecap_putreg(base, AM67_ECAP_CAP3_OFFSET, period); + am67_ecap_putreg(base, AM67_ECAP_CAP4_OFFSET, compare); +} + +/**************************************************************************** + * Name: am67_ecap_park + * + * Description: + * Drive the output low via 0% duty (CMP = 0) with the counter left + * running, so a compare event lands the pin low within one period. eCAP + * has no output force, so freezing instead could hold an arbitrary level. + * + ****************************************************************************/ + +static void am67_ecap_park(struct am67_ecap_s *priv) +{ + am67_ecap_putreg(priv->base, AM67_ECAP_CAP2_OFFSET, 0u); +} + +/**************************************************************************** + * Name: am67_ecap_setup + * + * Description: + * First-open configuration: PID check, pad mux, frozen APWM mode, and a + * counter reset. All eCAP register access is here (on open) rather than + * Configure the output pad. No output until start(). + * + * Returned Value: + * Zero (OK). + * + ****************************************************************************/ + +static int am67_ecap_setup(struct pwm_lowerhalf_s *dev) +{ + struct am67_ecap_s *priv = (struct am67_ecap_s *)dev; + + am67_ecap_pinmux_init(priv->pinmux_id); + + return OK; +} + +/**************************************************************************** + * Name: am67_ecap_shutdown + * + * Description: + * Last close: park the output low via stop(). The module stays in APWM + * mode so the pin keeps being driven low (capture mode tri-states the + * pad); there is no clock to gate. + * + * Returned Value: + * Zero (OK) always. + * + ****************************************************************************/ + +static int am67_ecap_shutdown(struct pwm_lowerhalf_s *dev) +{ + return am67_ecap_stop(dev); +} + +/**************************************************************************** + * Name: am67_ecap_start + * + * Description: + * Start or update the single APWM output (channel 1). A frequency change + * reprograms the period and compare immediately and re-ignites the + * counter; a duty-only change shadow-loads so it lands on a period + * boundary. A bad request leaves any running wave untouched. + * + * Returned Value: + * Zero (OK) on success; -EINVAL for a bad channel or duty; -ERANGE if the + * frequency cannot be produced. + * + ****************************************************************************/ + +static int am67_ecap_start(struct pwm_lowerhalf_s *dev, + const struct pwm_info_s *info) +{ + struct am67_ecap_s *priv = (struct am67_ecap_s *)dev; + ub16_t duty = 0; + bool found = false; + bool freq_changed; + uint32_t period; + uint32_t compare; + int8_t ch; + int i; + + am67_ecap_config_apwm(priv->base); + am67_ecap_reset_counter(priv->base); + + /* Validate the channel array (only channel 1 exists) and read its duty. */ + + for (i = 0; i < CONFIG_PWM_NCHANNELS; i++) + { + ch = info->channels[i].channel; + + if (ch == -1) + { + break; + } + + if (ch == 0) + { + continue; + } + + if (ch != 1) + { + /* This eCAP APWM exposes only channel 1. The shared pwm + * example is built for 2 channels (EPWM has A+B), so ignore + * channel entries that aren't ours instead of failing START. + */ + + continue; + } + + if (found) + { + pwmerr("ERROR: Channel 1 requested twice\n"); + return -EINVAL; + } + + if (info->channels[i].duty > 0xffff) + { + pwmerr("ERROR: Duty out of range on channel %d\n", ch); + return -EINVAL; + } + + duty = info->channels[i].duty; + found = true; + } + + if (!found) + { + return OK; + } + + freq_changed = (info->frequency != priv->frequency); + + if (freq_changed) + { + int ret = am67_ecap_calculate_period(info->frequency, &period); + + if (ret < 0) + { + pwmerr("ERROR: Cannot produce %" PRIu32 " Hz\n", info->frequency); + return ret; + } + } + else + { + period = priv->period; + } + + /* period is 32-bit, so duty * period overflows 32 bits - use 64-bit. */ + + compare = (uint32_t)(((uint64_t)duty * period) >> 16); + + if (freq_changed) + { + am67_ecap_counter_freeze(priv->base); + am67_ecap_write_immediate(priv->base, period - 1u, compare); + am67_ecap_reset_counter(priv->base); + am67_ecap_counter_run(priv->base); + + priv->frequency = info->frequency; + priv->period = period; + } + else + { + am67_ecap_write_shadow(priv->base, period - 1u, compare); + } + + return OK; +} + +/**************************************************************************** + * Name: am67_ecap_stop + * + * Description: + * Park the output low and clear the frequency cache so the next start() + * reconfigures. The counter stays running - that keeps the parked level + * deterministic. + * + * Returned Value: + * Zero (OK) always. + * + ****************************************************************************/ + +static int am67_ecap_stop(struct pwm_lowerhalf_s *dev) +{ + struct am67_ecap_s *priv = (struct am67_ecap_s *)dev; + + am67_ecap_park(priv); + priv->frequency = 0; + + return OK; +} + +/**************************************************************************** + * Name: am67_ecap_ioctl + * + * Description: + * No platform-specific ioctl commands are supported. + * + * Returned Value: + * -ENOTTY always. + * + ****************************************************************************/ + +static int am67_ecap_ioctl(struct pwm_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: am67_ecap_init + * + * Description: + * Boot-time preparation: unlock MAIN_CTRL_MMR partition 1, mirroring + * am67_epwm_init. All eCAP register access is still deferred to setup() + * on open. Must run before pwm_register(). + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure. + * + ****************************************************************************/ + +int am67_ecap_init(void) +{ + return am67_ecap_enable_register_write(); +} + +/**************************************************************************** + * Name: am67_ecapinitialize + * + * Description: + * Return the lower-half instance for the given module so the board bringup + * can bind it with pwm_register(). No hardware is touched. + * + * Input Parameters: + * ecap - eCAP module number: 0, 1 or 2. + * + * Returned Value: + * Lower-half pointer on success; NULL on an unsupported or unconfigured + * module. + * + ****************************************************************************/ + +struct pwm_lowerhalf_s *am67_ecapinitialize(int ecap) +{ + switch (ecap) + { +#ifdef CONFIG_AM67_ECAP0 + case 0: + pwminfo("Initialize eCAP%d (APWM)\n", ecap); + return (struct pwm_lowerhalf_s *)&g_am67_ecap0; +#endif + +#ifdef CONFIG_AM67_ECAP1 + case 1: + pwminfo("Initialize eCAP%d (APWM)\n", ecap); + return (struct pwm_lowerhalf_s *)&g_am67_ecap1; +#endif + +#ifdef CONFIG_AM67_ECAP2 + case 2: + pwminfo("Initialize eCAP%d (APWM)\n", ecap); + return (struct pwm_lowerhalf_s *)&g_am67_ecap2; +#endif + + default: + pwmerr("ERROR: No such eCAP module: %d\n", ecap); + return NULL; + } +} + +void am67_ecapdev_initialize(void) +{ + int ret = am67_ecap_init(); + + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: Failed to initialize eCAP: %d\n", ret); + } + else + { + struct pwm_lowerhalf_s *lower; + + syslog(LOG_INFO, "eCAP: modules verified and pads muxed\n"); + +#ifdef CONFIG_AM67_ECAP0 + lower = am67_ecapinitialize(0); + if (lower == NULL) + { + syslog(LOG_ERR, "ERROR: Failed to get eCAP0 lower half\n"); + } + else + { + ret = pwm_register("/dev/ecap0", lower); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pwm_register failed: %d\n", ret); + } + else + { + syslog(LOG_INFO, "eCAP0: registered /dev/ecap0\n"); + } + } +#endif + +#ifdef CONFIG_AM67_ECAP1 + lower = am67_ecapinitialize(1); + if (lower == NULL) + { + syslog(LOG_ERR, "ERROR: Failed to get eCAP1 lower half\n"); + } + else + { + ret = pwm_register("/dev/ecap1", lower); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pwm_register failed: %d\n", ret); + } + else + { + syslog(LOG_INFO, "eCAP1: registered /dev/ecap1\n"); + } + } +#endif + +#ifdef CONFIG_AM67_ECAP2 + lower = am67_ecapinitialize(2); + if (lower == NULL) + { + syslog(LOG_ERR, "ERROR: Failed to get eCAP2 lower half\n"); + } + else + { + ret = pwm_register("/dev/ecap2", lower); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pwm_register failed: %d\n", ret); + } + else + { + syslog(LOG_INFO, "eCAP2: registered /dev/ecap2\n"); + } + } +#endif + } +} diff --git a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c b/arch/arm/src/am67/am67_ecap.h similarity index 50% copy from boards/arm/am67/t3-gem-o1/src/am67_bringup.c copy to arch/arm/src/am67/am67_ecap.h index a0a35a06453..692a15284e5 100644 --- a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c +++ b/arch/arm/src/am67/am67_ecap.h @@ -1,5 +1,5 @@ /**************************************************************************** - * boards/arm/am67/t3-gem-o1/src/am67_bringup.c + * arch/arm/src/am67/am67_ecap.h * * SPDX-License-Identifier: Apache-2.0 * @@ -20,74 +20,65 @@ * ****************************************************************************/ +#ifndef __ARCH_ARM_SRC_AM67_AM67_ECAP_H +#define __ARCH_ARM_SRC_AM67_AM67_ECAP_H + /**************************************************************************** * Included Files ****************************************************************************/ #include <nuttx/config.h> -#include <nuttx/fs/fs.h> -#include <debug.h> - -#include "t3-gem-o1.h" - -#ifdef CONFIG_AM67_MCSPI0 -#include "am67_mcspi.h" -#include "am67_gpio.h" -#endif -#if defined(CONFIG_AM67_I2C0) || defined(CONFIG_AM67_WKUP_I2C0) -#include "am67_i2c.h" -#endif +/* The eCAP module is driven as an Auxiliary PWM (APWM) generator, so it + * binds to the PWM upper half (pwm_register), not the capture upper half. + */ -#if defined(CONFIG_AM67_EPWM0) || defined(CONFIG_AM67_EPWM1) -#include "am67_pwm.h" -#endif +#include <nuttx/timers/pwm.h> /**************************************************************************** - * Public Functions + * Public Function Prototypes ****************************************************************************/ /**************************************************************************** - * Name: am67_bringup + * Name: am67_ecap_init * * Description: - * Perform architecture-specific initialization. + * Boot-time preparation: unlock MAIN_CTRL_MMR partition 1, mirroring + * am67_epwm_init. Must run before pwm_register(). * - * CONFIG_BOARD_LATE_INITIALIZE=y : - * Called from board_late_initialize(). - * - * CONFIG_BOARD_LATE_INITIALIZE=n && CONFIG_BOARDCTL=y : - * Called from the NSH library. + * Returned Value: + * Zero (OK) on success; a negated errno on failure. * ****************************************************************************/ -int am67_bringup(void) -{ - int ret = OK; - -#ifdef CONFIG_AM67_MCSPI0 - am67_sensors_power_enable(true); - am67_spiinitialize(); - am67_spidev_initialize(); -#endif +int am67_ecap_init(void); -#if defined(CONFIG_AM67_I2C0) || defined(CONFIG_AM67_WKUP_I2C0) - am67_i2cdev_initialize(); -#endif +/**************************************************************************** + * Name: am67_ecapinitialize + * + * Description: + * Return the lower-half instance for the given module so the board bringup + * can bind it with pwm_register(). No hardware is touched here. + * + * Input Parameters: + * ecap - eCAP module number: 0, 1 or 2. + * + * Returned Value: + * Lower-half pointer on success; NULL on an unsupported or unconfigured + * module number. + * + ****************************************************************************/ -#if defined(CONFIG_AM67_EPWM0) || defined(CONFIG_AM67_EPWM1) - am67_pwmdev_initialize(); -#endif +struct pwm_lowerhalf_s *am67_ecapinitialize(int ecap); -#ifdef CONFIG_FS_PROCFS - /* Mount the procfs file system */ +/**************************************************************************** + * Name: am67_ecapdev_initialize + * + * Description: + * Initialize the eCAP lower-half driver and register the PWM device. + * + ****************************************************************************/ - ret = nx_mount(NULL, "/proc", "procfs", 0, NULL); - if (ret < 0) - { - syslog(LOG_ERR, "ERROR: Failed to mount procfs at /proc: %d\n", ret); - } -#endif +void am67_ecapdev_initialize(void); - return ret; -} +#endif /* __ARCH_ARM_SRC_AM67_AM67_ECAP_H */ diff --git a/arch/arm/src/am67/am67_ecap_hw.h b/arch/arm/src/am67/am67_ecap_hw.h new file mode 100644 index 00000000000..92e9b2d94eb --- /dev/null +++ b/arch/arm/src/am67/am67_ecap_hw.h @@ -0,0 +1,172 @@ +/**************************************************************************** + * arch/arm/src/am67/am67_ecap_hw.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_AM67_AM67_ECAP_HW_H +#define __ARCH_ARM_SRC_AM67_AM67_ECAP_HW_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Base addresses (MAIN domain). Registers are 32-bit and there is no clock + * gate; liveness is proven via the PID readback below. + */ + +#define AM67_ECAP0_BASE 0x23100000 +#define AM67_ECAP1_BASE 0x23110000 +#define AM67_ECAP2_BASE 0x23120000 + +/* Peripheral ID. Reset value not yet copper-verified; the driver only + * treats 0 / 0xffffffff as a dead module. + */ + +#define AM67_ECAP_PID_OFFSET 0x05c + +/* Register offsets (all 32-bit) ******************************************** + * + * In APWM mode CAP1/CAP2 are the active period/compare (APRD/ACMP) and + * CAP3/CAP4 are their shadows. TRM 12.4.2.4.1.2. + */ + +#define AM67_ECAP_TSCNT_OFFSET 0x000 /* Time-base counter (TSCTR) */ +#define AM67_ECAP_CNTPHS_OFFSET 0x004 /* Counter phase (sync load) */ +#define AM67_ECAP_CAP1_OFFSET 0x008 /* CAP1 / APRD active period */ +#define AM67_ECAP_CAP2_OFFSET 0x00c /* CAP2 / ACMP active compare*/ +#define AM67_ECAP_CAP3_OFFSET 0x010 /* CAP3 / APRD shadow */ +#define AM67_ECAP_CAP4_OFFSET 0x014 /* CAP4 / ACMP shadow */ +#define AM67_ECAP_ECCTL_OFFSET 0x028 /* Control (ECCTL1 + ECCTL2) */ +#define AM67_ECAP_ECINT_EN_FLG_OFFSET 0x02c /* Int enable + flags */ +#define AM67_ECAP_ECINT_CLR_FRC_OFFSET 0x030 /* Int clear + force */ + +/* ECCTL - Control register ************************************************* + * + * TI folds the classic C2000 ECCTL1 (low half) and ECCTL2 (high half) into + * one 32-bit word. The APWM path uses only CAP_APWM, APWMPOL, TSCNTSTP and + * SYNCO_SEL; the capture-side fields are kept for completeness. + */ + +/* Per-event edge polarity: 0 = rising, 1 = falling (capture mode only) */ + +#define AM67_ECAP_ECCTL_CAP1POL (1u << 0) +#define AM67_ECAP_ECCTL_CAP2POL (1u << 2) +#define AM67_ECAP_ECCTL_CAP3POL (1u << 4) +#define AM67_ECAP_ECCTL_CAP4POL (1u << 6) + +/* Per-event counter reset (capture mode only): 0 = absolute, 1 = delta */ + +#define AM67_ECAP_ECCTL_CTRRST1 (1u << 1) +#define AM67_ECAP_ECCTL_CTRRST2 (1u << 3) +#define AM67_ECAP_ECCTL_CTRRST3 (1u << 5) +#define AM67_ECAP_ECCTL_CTRRST4 (1u << 7) + +#define AM67_ECAP_ECCTL_CAPLDEN (1u << 8) /* Enable CAPx loading */ + +#define AM67_ECAP_ECCTL_EVTFLTPS_SHIFT (9) /* Event filter prescale */ +#define AM67_ECAP_ECCTL_EVTFLTPS_MASK (0x1fu << 9) + +#define AM67_ECAP_ECCTL_FREE_SOFT_SHIFT (14) /* Emulation halt mode */ +#define AM67_ECAP_ECCTL_FREE_SOFT_MASK (3u << 14) + +#define AM67_ECAP_ECCTL_CONT_ONESHT (1u << 16) /* 0=continuous 1=oneshot*/ + +#define AM67_ECAP_ECCTL_STOPVALUE_SHIFT (17) /* Wrap/stop after event */ +#define AM67_ECAP_ECCTL_STOPVALUE_MASK (3u << 17) +#define AM67_ECAP_ECCTL_STOPVALUE_EVT1 (0u << 17) +#define AM67_ECAP_ECCTL_STOPVALUE_EVT2 (1u << 17) +#define AM67_ECAP_ECCTL_STOPVALUE_EVT3 (2u << 17) +#define AM67_ECAP_ECCTL_STOPVALUE_EVT4 (3u << 17) + +#define AM67_ECAP_ECCTL_REARM (1u << 19) /* Re-arm sequencer */ +#define AM67_ECAP_ECCTL_TSCNTSTP (1u << 20) /* 0=stop 1=run counter */ +#define AM67_ECAP_ECCTL_SYNCI_EN (1u << 21) /* Sync-in enable */ + +#define AM67_ECAP_ECCTL_SYNCO_SEL_SHIFT (22) /* 0=SYNCI 1=CTR=PRD 2/3=off */ +#define AM67_ECAP_ECCTL_SYNCO_SEL_MASK (3u << 22) +#define AM67_ECAP_ECCTL_SYNCO_SEL_DIS (2u << 22) /* Disable sync-out */ + +#define AM67_ECAP_ECCTL_SWSYNC (1u << 24) /* Software sync */ +#define AM67_ECAP_ECCTL_CAP_APWM (1u << 25) /* 0=capture 1=APWM */ +#define AM67_ECAP_ECCTL_APWMPOL (1u << 26) /* 0=active-hi 1=active-lo*/ + +#define AM67_ECAP_ECCTL_FILTER_SHIFT (27) +#define AM67_ECAP_ECCTL_FILTER_MASK (0x1fu << 27) + +/* ECINT_EN_FLG - Interrupt enable (low) + flags (high) ********************* + * + * Unused by the basic APWM output; kept for a future period-callback. In + * APWM mode the valid sources are PRDEQ (CTR = PRD) and CMPEQ (CTR = CMP). + */ + +/* Enable bits (write 1 to enable that source's interrupt) */ + +#define AM67_ECAP_INT_CEVT1_EN (1u << 1) +#define AM67_ECAP_INT_CEVT2_EN (1u << 2) +#define AM67_ECAP_INT_CEVT3_EN (1u << 3) +#define AM67_ECAP_INT_CEVT4_EN (1u << 4) +#define AM67_ECAP_INT_CNTOVF_EN (1u << 5) +#define AM67_ECAP_INT_PRDEQ_EN (1u << 6) /* CTR = PRD (APWM) */ +#define AM67_ECAP_INT_CMPEQ_EN (1u << 7) /* CTR = CMP (APWM) */ + +/* Flag bits (read-only status) */ + +#define AM67_ECAP_INT_FLG (1u << 16) /* Global interrupt flag */ +#define AM67_ECAP_INT_CEVT1_FLG (1u << 17) +#define AM67_ECAP_INT_CEVT2_FLG (1u << 18) +#define AM67_ECAP_INT_CEVT3_FLG (1u << 19) +#define AM67_ECAP_INT_CEVT4_FLG (1u << 20) +#define AM67_ECAP_INT_CNTOVF_FLG (1u << 21) +#define AM67_ECAP_INT_PRDEQ_FLG (1u << 22) +#define AM67_ECAP_INT_CMPEQ_FLG (1u << 23) + +/* ECINT_CLR_FRC - Interrupt clear (low) + force (high) ********************* + * + * Writing INT_CLR (bit 0) de-asserts the global interrupt and must be done + * in any ISR before another can assert. + */ + +#define AM67_ECAP_INT_CLR (1u << 0) /* Global interrupt clear*/ +#define AM67_ECAP_INT_CEVT1_CLR (1u << 1) +#define AM67_ECAP_INT_CEVT2_CLR (1u << 2) +#define AM67_ECAP_INT_CEVT3_CLR (1u << 3) +#define AM67_ECAP_INT_CEVT4_CLR (1u << 4) +#define AM67_ECAP_INT_CNTOVF_CLR (1u << 5) +#define AM67_ECAP_INT_PRDEQ_CLR (1u << 6) +#define AM67_ECAP_INT_CMPEQ_CLR (1u << 7) + +/* Convenience mask: clear every event flag plus the global flag */ + +#define AM67_ECAP_INT_CLR_ALL (AM67_ECAP_INT_CLR | \ + AM67_ECAP_INT_CEVT1_CLR | \ + AM67_ECAP_INT_CEVT2_CLR | \ + AM67_ECAP_INT_CEVT3_CLR | \ + AM67_ECAP_INT_CEVT4_CLR | \ + AM67_ECAP_INT_CNTOVF_CLR | \ + AM67_ECAP_INT_PRDEQ_CLR | \ + AM67_ECAP_INT_CMPEQ_CLR) + +#endif /* __ARCH_ARM_SRC_AM67_AM67_ECAP_HW_H */ diff --git a/arch/arm/src/am67/am67_pinmux.c b/arch/arm/src/am67/am67_pinmux.c index c0e2cdd8d46..432feae9e0a 100644 --- a/arch/arm/src/am67/am67_pinmux.c +++ b/arch/arm/src/am67/am67_pinmux.c @@ -104,6 +104,35 @@ static struct pinmux_conf_s g_am67_epwm1_pinmux_conf[] = {PINMUX_END, PINMUX_END} }; +/* eCAP0 pad unassigned: C20 stays with EPWM0_B (keeps the console free). */ + +static struct pinmux_conf_s g_am67_ecap0_pinmux_conf[] = +{ + {PINMUX_END, PINMUX_END} +}; + +static struct pinmux_conf_s g_am67_ecap1_pinmux_conf[] = +{ + /* eCAP1 APWM out: MCASP0_AXR3 mode 5 */ + + { + PIN_MCASP0_AXR3, + (PIN_MODE(5) | PIN_PULL_DISABLE) + }, + {PINMUX_END, PINMUX_END} +}; + +static struct pinmux_conf_s g_am67_ecap2_pinmux_conf[] = +{ + /* eCAP2 APWM out: MCASP0_ACLKX mode 2 */ + + { + PIN_MCASP0_ACLKX, + (PIN_MODE(2) | PIN_PULL_DISABLE) + }, + {PINMUX_END, PINMUX_END} +}; + static struct pinmux_conf_s g_am67_mcu_spi_pinmux_conf[] = { /* MCU_SPI0_CLK */ @@ -360,3 +389,33 @@ void am67_epwm_pinmux_init(int epwm) } } +/**************************************************************************** + * Name: am67_ecap_pinmux_init + * + * Description: + * Configure the APWM output pad for the given eCAP module. eCAP0 uses + * ball A23 (mode 8); eCAP1/eCAP2 pad tables are empty pending a board + * decision (see the table definitions above). + * + ****************************************************************************/ + +void am67_ecap_pinmux_init(int ecap) +{ + switch (ecap) + { + case 0: + am67_pinmux_config(g_am67_ecap0_pinmux_conf); + break; + + case 1: + am67_pinmux_config(g_am67_ecap1_pinmux_conf); + break; + + case 2: + am67_pinmux_config(g_am67_ecap2_pinmux_conf); + break; + + default: + break; + } +} diff --git a/arch/arm/src/am67/am67_pinmux.h b/arch/arm/src/am67/am67_pinmux.h index 23ce9724bb6..fa177a14067 100644 --- a/arch/arm/src/am67/am67_pinmux.h +++ b/arch/arm/src/am67/am67_pinmux.h @@ -316,5 +316,6 @@ void am67_pinmux_init(void); void am67_spi_pinmux_init(void); void am67_i2c_pinmux_init(void); void am67_epwm_pinmux_init(int epwm); +void am67_ecap_pinmux_init(int ecap); #endif /* __ARCH_ARM_SRC_AM67_AM67_PINMUX_H */ diff --git a/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig b/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig index b2ad3f09949..600eef35360 100644 --- a/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig +++ b/boards/arm/am67/t3-gem-o1/configs/nsh/defconfig @@ -14,6 +14,8 @@ CONFIG_16550_UART0_CLOCK=48000000 CONFIG_16550_UART0_IRQ=211 CONFIG_16550_UART0_SERIAL_CONSOLE=y CONFIG_16550_UART=y +CONFIG_AM67_ECAP1=y +CONFIG_AM67_ECAP2=y CONFIG_AM67_EPWM0=y CONFIG_AM67_EPWM1=y CONFIG_AM67_I2C=y diff --git a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c b/boards/arm/am67/t3-gem-o1/src/am67_bringup.c index a0a35a06453..489a3869db9 100644 --- a/boards/arm/am67/t3-gem-o1/src/am67_bringup.c +++ b/boards/arm/am67/t3-gem-o1/src/am67_bringup.c @@ -43,6 +43,11 @@ #include "am67_pwm.h" #endif +#if defined(CONFIG_AM67_ECAP0) || defined(CONFIG_AM67_ECAP1) || \ + defined(CONFIG_AM67_ECAP2) +#include "am67_ecap.h" +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -79,6 +84,11 @@ int am67_bringup(void) am67_pwmdev_initialize(); #endif +#if defined(CONFIG_AM67_ECAP0) || defined(CONFIG_AM67_ECAP1) || \ + defined(CONFIG_AM67_ECAP2) + am67_ecapdev_initialize(); +#endif + #ifdef CONFIG_FS_PROCFS /* Mount the procfs file system */
