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 */
 

Reply via email to