From dd236f7df5b8160489d50ae219c7af35cb03541c Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:07:29 +0200 Subject: [PATCH 01/14] !drivers: separate pulse count feature from PWM driver BREAKING CHANGE: separate pulse count feature from PWM driver. Coupling PWM driver with pulse count feature was bad decision from the beginning, these are two different things: - PWM is a modulation scheme: it continuously represents a value by varying duty cycle, usually at a fixed frequency. - Pulse train generation is a finite waveform transaction: generate N edges/pulses with selected timing, then complete. This change introduce a new pulse count driver with new API. Now user can generate pulse train by providing: - high pulse length in ns - low pulse length in ns - pulse count All architectures supporting pulse count have been adapted in subsequent commits. Users must migrate their code to use the new driver with new API. Signed-off-by: raiden00pl --- .../examples/pulsecount/index.rst | 21 + .../applications/examples/pwm/index.rst | 8 +- .../drivers/character/timers/index.rst | 1 + .../drivers/character/timers/pulsecount.rst | 97 +++++ .../drivers/character/timers/pwm.rst | 8 +- drivers/timers/CMakeLists.txt | 4 + drivers/timers/Kconfig | 12 +- drivers/timers/Make.defs | 6 + drivers/timers/pulsecount.c | 361 ++++++++++++++++++ drivers/timers/pwm.c | 163 +------- include/nuttx/fs/ioctl.h | 6 + include/nuttx/timers/pulsecount.h | 189 +++++++++ include/nuttx/timers/pwm.h | 71 +--- 13 files changed, 704 insertions(+), 243 deletions(-) create mode 100644 Documentation/applications/examples/pulsecount/index.rst create mode 100644 Documentation/components/drivers/character/timers/pulsecount.rst create mode 100644 drivers/timers/pulsecount.c create mode 100644 include/nuttx/timers/pulsecount.h diff --git a/Documentation/applications/examples/pulsecount/index.rst b/Documentation/applications/examples/pulsecount/index.rst new file mode 100644 index 0000000000000..b9b34e18d4a34 --- /dev/null +++ b/Documentation/applications/examples/pulsecount/index.rst @@ -0,0 +1,21 @@ +==================================== +``pulsecount`` Pulse Count Example +==================================== + +This example starts a finite pulse train on a pulsecount device and +waits for completion when the device is opened in blocking mode. + +Required configuration: + +- ``CONFIG_PULSECOUNT`` – Enables pulsecount support. +- ``CONFIG_NSH_BUILTIN_APPS`` – Builds the example as an NSH built-in. + +Specific configuration options: + +- ``CONFIG_EXAMPLES_PULSECOUNT_DEVPATH`` – The default pulsecount device. + Default: ``/dev/pulsecount0``. +- ``CONFIG_EXAMPLES_PULSECOUNT_HIGH_NS`` – The pulse high time. Default: + ``5000000`` ns. +- ``CONFIG_EXAMPLES_PULSECOUNT_LOW_NS`` – The pulse low time. Default: + ``5000000`` ns. +- ``CONFIG_EXAMPLES_PULSECOUNT_COUNT`` – The finite pulse count. diff --git a/Documentation/applications/examples/pwm/index.rst b/Documentation/applications/examples/pwm/index.rst index 33519d722f1f6..90c0002929e89 100644 --- a/Documentation/applications/examples/pwm/index.rst +++ b/Documentation/applications/examples/pwm/index.rst @@ -10,8 +10,6 @@ This test depends on these specific PWM/NSH configurations settings (your specific PWM settings might require additional settings). - ``CONFIG_PWM`` – Enables PWM support. -- ``CONFIG_PWM_PULSECOUNT`` – Enables PWM pulse count support (if the hardware - supports it). - ``CONFIG_NSH_BUILTIN_APPS`` – Build the PWM test as an NSH built-in function. Specific configuration options for this example include: @@ -22,8 +20,4 @@ Specific configuration options for this example include: - ``CONFIG_EXAMPLES_PWM_DUTYPCT`` – The initial PWM duty as a percentage. Default: ``50%``. - ``CONFIG_EXAMPLES_PWM_DURATION`` – The initial PWM pulse train duration in - seconds. Used only if the current pulse count is zero (pulse count is only - supported if ``CONFIG_PWM_PULSECOUNT`` is defined). Default: ``5`` seconds. -- ``CONFIG_EXAMPLES_PWM_PULSECOUNT`` – The initial PWM pulse count. This option is - only available if ``CONFIG_PWM_PULSECOUNT`` is non-zero. Default: ``0`` (i.e., use - the duration, not the count). + seconds. Default: ``5`` seconds. diff --git a/Documentation/components/drivers/character/timers/index.rst b/Documentation/components/drivers/character/timers/index.rst index b615da0cbef0e..51ce8415fda16 100644 --- a/Documentation/components/drivers/character/timers/index.rst +++ b/Documentation/components/drivers/character/timers/index.rst @@ -8,6 +8,7 @@ Timers Drivers timer.rst oneshot.rst pwm.rst + pulsecount.rst watchdog.rst rtc.rst capture.rst diff --git a/Documentation/components/drivers/character/timers/pulsecount.rst b/Documentation/components/drivers/character/timers/pulsecount.rst new file mode 100644 index 0000000000000..627be45a1654f --- /dev/null +++ b/Documentation/components/drivers/character/timers/pulsecount.rst @@ -0,0 +1,97 @@ +=================== +Pulsecount Drivers +=================== + +The pulsecount driver generates a finite pulse train and reports completion +after the requested number of pulses has been produced. It is intended for +hardware that can generate pulse output with a fixed repetition count, often +using PWM/timer peripherals internally. + +This interface is separate from the PWM driver. PWM describes a continuous +periodic output using frequency and duty cycle. Pulsecount describes a finite +waveform using explicit high time, low time, and pulse count. + +Driver Model +============ + +The NuttX pulsecount driver is split into two parts: + +#. An upper-half character driver that provides the common application + interface. +#. A lower-half platform driver that programs the hardware timer/PWM + peripheral and calls ``pulsecount_expired()`` when the finite pulse train + completes. + +Files supporting pulsecount can be found in the following locations: + +- ``include/nuttx/timers/pulsecount.h`` - public interface and lower-half + callbacks. +- ``drivers/timers/pulsecount.c`` - generic upper-half driver. +- ``arch//src//*pulsecount*.c`` - platform lower-half + drivers. + +Application Interface +===================== + +Applications use the pulsecount driver through a character device such as +``/dev/pulsecount0``. Include the pulsecount header: + +.. code-block:: c + + #include + +The driver is controlled through ``ioctl`` commands: + +- ``PULSECOUNTIOC_SETCHARACTERISTICS`` +- ``PULSECOUNTIOC_GETCHARACTERISTICS`` +- ``PULSECOUNTIOC_START`` +- ``PULSECOUNTIOC_STOP`` + +Pulse Characteristics +===================== + +``PULSECOUNTIOC_SETCHARACTERISTICS`` takes a pointer to +``struct pulsecount_info_s``: + +.. code-block:: c + + struct pulsecount_info_s + { + uint32_t high_ns; /* Pulse high time in nanoseconds */ + uint32_t low_ns; /* Pulse low time in nanoseconds */ + uint32_t count; /* Number of pulses to generate */ + }; + +``high_ns`` and ``low_ns`` must both be non-zero. Their sum is the pulse +period. ``count`` is the number of complete high/low pulses to generate. + +This API previously followed the PWM-style ``frequency`` plus ``duty`` +model. It now uses explicit high/low nanosecond timing because that is more +user-friendly for finite pulse trains: applications can describe the waveform +directly and do not need to convert timing requirements into fixed-point duty +cycle values. + +The lower-half driver may quantize the requested timings to the nearest values +that the hardware timer can represent. Very long periods or very short +pulses can be rejected if they are outside the timer's clock and counter +range. + +Starting And Stopping +===================== + +After setting the pulse characteristics, start the pulse train with +``PULSECOUNTIOC_START``. By default, this call blocks until the requested +pulse count completes. Open the device with ``O_NONBLOCK`` to start the +pulse train and return immediately. + +``PULSECOUNTIOC_STOP`` stops pulse generation before the count completes. +TODO: support cancelling a blocking ``PULSECOUNTIOC_START``. + +Example +======= + +The ``apps/examples/pulsecount`` example starts a finite pulse train from +NSH. It can be built with ``CONFIG_EXAMPLES_PULSECOUNT`` and configured with +``CONFIG_EXAMPLES_PULSECOUNT_HIGH_NS``, +``CONFIG_EXAMPLES_PULSECOUNT_LOW_NS``, and +``CONFIG_EXAMPLES_PULSECOUNT_COUNT``. diff --git a/Documentation/components/drivers/character/timers/pwm.rst b/Documentation/components/drivers/character/timers/pwm.rst index 61bb6dca34050..59169ba92e4e7 100644 --- a/Documentation/components/drivers/character/timers/pwm.rst +++ b/Documentation/components/drivers/character/timers/pwm.rst @@ -5,8 +5,8 @@ PWM Drivers For the purposes of this driver, a PWM device is any device that generates periodic output pulses of controlled frequency and pulse width. Such a device might be used, for example, to perform -pulse-width modulated output or frequency/pulse-count modulated -output (such as might be needed to control a stepper motor). +pulse-width modulated output. Finite pulse-count output is provided +through the separate pulsecount driver interface. The NuttX PWM driver is split into two parts: @@ -209,3 +209,7 @@ wait for an end of cycle. The ``CONFIG_PWM_DEADTIME`` option brings the possibility to introduce dead time values between complementary PWM outputs. + +Finite pulse trains are not configured through ``pwm_info_s``. Use the +:doc:`pulsecount ` driver for hardware that supports a fixed pulse +count. diff --git a/drivers/timers/CMakeLists.txt b/drivers/timers/CMakeLists.txt index 1107f1139af45..da67a9cabd1d2 100644 --- a/drivers/timers/CMakeLists.txt +++ b/drivers/timers/CMakeLists.txt @@ -86,6 +86,10 @@ if(CONFIG_PWM) list(APPEND SRCS pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS pulsecount.c) +endif() + if(CONFIG_DSHOT) list(APPEND SRCS dshot.c) endif() diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig index b34721657af50..143446ceb8e32 100644 --- a/drivers/timers/Kconfig +++ b/drivers/timers/Kconfig @@ -5,7 +5,7 @@ menu "Timer Driver Support" -config ARCH_HAVE_PWM_PULSECOUNT +config ARCH_HAVE_PULSECOUNT bool default n @@ -24,18 +24,18 @@ config PWM This selection enables building of the "upper-half" PWM driver. See include/nuttx/timers/pwm.h for further PWM driver information. -if PWM - -config PWM_PULSECOUNT - bool "PWM Pulse Count Support" +config PULSECOUNT + bool "Pulse Count Support" default n - depends on ARCH_HAVE_PWM_PULSECOUNT + depends on ARCH_HAVE_PULSECOUNT ---help--- Some hardware will support generation of a fixed number of pulses. This might be used, for example to support a stepper motor. If the hardware will support a fixed pulse count, then this configuration should be set to enable the capability. +if PWM + config PWM_OVERWRITE bool "PWM Overwrite Support" default n diff --git a/drivers/timers/Make.defs b/drivers/timers/Make.defs index 6e671ebdbd1c2..51debd2db42ef 100644 --- a/drivers/timers/Make.defs +++ b/drivers/timers/Make.defs @@ -118,6 +118,12 @@ ifeq ($(CONFIG_PWM),y) TMRVPATH = :timers endif +ifeq ($(CONFIG_PULSECOUNT),y) + CSRCS += pulsecount.c + TMRDEPPATH = --dep-path timers + TMRVPATH = :timers +endif + ifeq ($(CONFIG_DSHOT),y) CSRCS += dshot.c TMRDEPPATH = --dep-path timers diff --git a/drivers/timers/pulsecount.c b/drivers/timers/pulsecount.c new file mode 100644 index 0000000000000..d2c1677efc7d3 --- /dev/null +++ b/drivers/timers/pulsecount.c @@ -0,0 +1,361 @@ +/**************************************************************************** + * drivers/timers/pulsecount.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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CONFIG_PULSECOUNT + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pulsecount_upperhalf_s +{ + uint8_t crefs; /* The number of times the device has + * been opened */ + volatile bool started; /* True: pulse output is active */ + volatile bool waiting; /* True: a thread is waiting for expiry */ + mutex_t lock; /* Supports mutual exclusion */ + sem_t waitsem; /* Waits for finite pulse completion */ + struct pulsecount_info_s info; /* Pulse output characteristics */ + + /* Lower half state */ + + FAR struct pulsecount_lowerhalf_s *dev; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int pulsecount_open(FAR struct file *filep); +static int pulsecount_close(FAR struct file *filep); +static ssize_t pulsecount_read(FAR struct file *filep, + FAR char *buffer, size_t buflen); +static ssize_t pulsecount_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static int pulsecount_start(FAR struct pulsecount_upperhalf_s *upper, + unsigned int oflags); +static int pulsecount_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_pulsecount_fops = +{ + pulsecount_open, /* open */ + pulsecount_close, /* close */ + pulsecount_read, /* read */ + pulsecount_write, /* write */ + NULL, /* seek */ + pulsecount_ioctl, /* ioctl */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void pulsecount_dump(FAR const char *msg, + FAR const struct pulsecount_info_s *info, + bool started) +{ + _info("%s: high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 " started: %d\n", + msg, info->high_ns, info->low_ns, info->count, started); +} + +static int pulsecount_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pulsecount_upperhalf_s *upper = inode->i_private; + FAR struct pulsecount_lowerhalf_s *lower; + uint8_t tmp; + int ret; + + ret = nxmutex_lock(&upper->lock); + if (ret < 0) + { + return ret; + } + + tmp = upper->crefs + 1; + if (tmp == 0) + { + ret = -EMFILE; + goto errout_with_lock; + } + + if (tmp == 1) + { + lower = upper->dev; + DEBUGASSERT(lower->ops->setup != NULL); + + ret = lower->ops->setup(lower); + if (ret < 0) + { + goto errout_with_lock; + } + } + + upper->crefs = tmp; + ret = OK; + +errout_with_lock: + nxmutex_unlock(&upper->lock); + return ret; +} + +static int pulsecount_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pulsecount_upperhalf_s *upper = inode->i_private; + FAR struct pulsecount_lowerhalf_s *lower; + int ret; + + ret = nxmutex_lock(&upper->lock); + if (ret < 0) + { + return ret; + } + + if (upper->crefs > 1) + { + upper->crefs--; + } + else + { + lower = upper->dev; + upper->crefs = 0; + + DEBUGASSERT(lower->ops->shutdown != NULL); + lower->ops->shutdown(lower); + } + + nxmutex_unlock(&upper->lock); + return OK; +} + +static ssize_t pulsecount_read(FAR struct file *filep, + FAR char *buffer, size_t buflen) +{ + return 0; +} + +static ssize_t pulsecount_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + return 0; +} + +static int pulsecount_start(FAR struct pulsecount_upperhalf_s *upper, + unsigned int oflags) +{ + FAR struct pulsecount_lowerhalf_s *lower; + int ret = OK; + + DEBUGASSERT(upper != NULL); + lower = upper->dev; + DEBUGASSERT(lower != NULL && lower->ops->start != NULL); + + if (upper->info.count == 0) + { + return -EINVAL; + } + + if (!upper->started) + { + upper->waiting = (oflags & O_NONBLOCK) == 0; + upper->started = true; + + ret = lower->ops->start(lower, &upper->info, upper); + if (ret == OK) + { + while (upper->waiting) + { + ret = nxsem_wait_uninterruptible(&upper->waitsem); + if (ret < 0) + { + upper->started = false; + upper->waiting = false; + } + } + } + else + { + upper->started = false; + upper->waiting = false; + } + } + + return ret; +} + +static int pulsecount_ioctl(FAR struct file *filep, int cmd, + unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pulsecount_upperhalf_s *upper = inode->i_private; + FAR struct pulsecount_lowerhalf_s *lower = upper->dev; + int ret; + + ret = nxmutex_lock(&upper->lock); + if (ret < 0) + { + return ret; + } + + switch (cmd) + { + case PULSECOUNTIOC_SETCHARACTERISTICS: + { + FAR const struct pulsecount_info_s *info = + (FAR const struct pulsecount_info_s *)((uintptr_t)arg); + DEBUGASSERT(info != NULL); + + if (info->high_ns == 0 || info->low_ns == 0 || + pulsecount_frequency(info) == 0 || info->count == 0) + { + ret = -EINVAL; + break; + } + + pulsecount_dump("PULSECOUNTIOC_SETCHARACTERISTICS", info, + upper->started); + memcpy(&upper->info, info, sizeof(struct pulsecount_info_s)); + + if (upper->started) + { + ret = lower->ops->start(lower, &upper->info, upper); + } + } + break; + + case PULSECOUNTIOC_GETCHARACTERISTICS: + { + FAR struct pulsecount_info_s *info = + (FAR struct pulsecount_info_s *)((uintptr_t)arg); + DEBUGASSERT(info != NULL); + + memcpy(info, &upper->info, sizeof(struct pulsecount_info_s)); + pulsecount_dump("PULSECOUNTIOC_GETCHARACTERISTICS", info, + upper->started); + } + break; + + case PULSECOUNTIOC_START: + { + pulsecount_dump("PULSECOUNTIOC_START", &upper->info, + upper->started); + ret = pulsecount_start(upper, filep->f_oflags); + } + break; + + case PULSECOUNTIOC_STOP: + { + if (upper->started) + { + ret = lower->ops->stop(lower); + upper->started = false; + upper->waiting = false; + } + } + break; + + default: + { + DEBUGASSERT(lower->ops->ioctl != NULL); + ret = lower->ops->ioctl(lower, cmd, arg); + } + break; + } + + nxmutex_unlock(&upper->lock); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int pulsecount_register(FAR const char *path, + FAR struct pulsecount_lowerhalf_s *dev) +{ + FAR struct pulsecount_upperhalf_s *upper; + + upper = kmm_zalloc(sizeof(struct pulsecount_upperhalf_s)); + if (upper == NULL) + { + _err("ERROR: Allocation failed\n"); + return -ENOMEM; + } + + nxmutex_init(&upper->lock); + nxsem_init(&upper->waitsem, 0, 0); + + upper->dev = dev; + + _info("Registering %s\n", path); + return register_driver(path, &g_pulsecount_fops, 0666, upper); +} + +void pulsecount_expired(FAR void *handle) +{ + FAR struct pulsecount_upperhalf_s *upper = + (FAR struct pulsecount_upperhalf_s *)handle; + + if (upper->started) + { + if (upper->waiting) + { + upper->waiting = false; + nxsem_post(&upper->waitsem); + } + + upper->started = false; + } +} + +#endif /* CONFIG_PULSECOUNT */ diff --git a/drivers/timers/pwm.c b/drivers/timers/pwm.c index cd583de509e1d..8714abbc74578 100644 --- a/drivers/timers/pwm.c +++ b/drivers/timers/pwm.c @@ -41,12 +41,9 @@ #include #include #include -#include #include #include -#include - #ifdef CONFIG_PWM /**************************************************************************** @@ -61,15 +58,7 @@ struct pwm_upperhalf_s * been opened */ volatile bool started; /* True: pulsed output is being * generated */ -#ifdef CONFIG_PWM_PULSECOUNT - volatile bool waiting; /* True: Caller is waiting for the pulse - * count to expire */ -#endif mutex_t lock; /* Supports mutual exclusion */ -#ifdef CONFIG_PWM_PULSECOUNT - sem_t waitsem; /* Used to wait for the pulse count to - * expire */ -#endif struct pwm_info_s info; /* Pulsed output characteristics */ FAR struct pwm_lowerhalf_s *dev; /* lower-half state */ }; @@ -126,10 +115,6 @@ static void pwm_dump(FAR const char *msg, FAR const struct pwm_info_s *info, info->channels[i].channel, info->channels[i].duty); } -#ifdef CONFIG_PWM_PULSECOUNT - pwminfo(" count: %" PRIx32 "\n", info->channels[0].count); -#endif - pwminfo(" started: %d\n", started); } @@ -295,79 +280,12 @@ static ssize_t pwm_write(FAR struct file *filep, FAR const char *buffer, * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags) { FAR struct pwm_lowerhalf_s *lower; - irqstate_t flags; int ret = OK; - DEBUGASSERT(upper != NULL); - lower = upper->dev; - DEBUGASSERT(lower != NULL && lower->ops->start != NULL); - - /* Verify that the PWM is not already running */ - - if (!upper->started) - { - /* Indicate that if will be waiting for the pulse count to complete. - * Note that we will only wait if a non-zero pulse count is specified - * and if the PWM driver was opened in normal, blocking mode. Also - * assume for now that the pulse train will be successfully started. - * - * We do these things before starting the PWM to avoid race conditions. - */ - - upper->waiting = (upper->info.channels[0].count > 0) && - ((oflags & O_NONBLOCK) == 0); - upper->started = true; - - /* Invoke the bottom half method to start the pulse train */ - - ret = lower->ops->start(lower, &upper->info, upper); - - /* A return value of zero means that the pulse train was started - * successfully. - */ - - if (ret == OK) - { - /* Should we wait for the pulse output to complete? Loop in - * in case the wakeup form nxsem_wait() is a false alarm. - */ - - while (upper->waiting) - { - /* Wait until we are awakened by pwm_expired(). When - * pwm_expired is called, it will post the waitsem and - * clear the waiting flag. - */ - - ret = nxsem_wait_uninterruptible(&upper->waitsem); - if (ret < 0) - { - upper->started = false; - upper->waiting = false; - } - } - } - else - { - /* Looks like we won't be waiting after all */ - - pwminfo("start failed: %d\n", ret); - upper->started = false; - upper->waiting = false; - } - } - - return ret; -} -#else -static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags) -{ - FAR struct pwm_lowerhalf_s *lower; - int ret = OK; + UNUSED(oflags); DEBUGASSERT(upper != NULL); lower = upper->dev; @@ -395,7 +313,6 @@ static int pwm_start(FAR struct pwm_upperhalf_s *upper, unsigned int oflags) return ret; } -#endif /**************************************************************************** * Name: pwm_ioctl @@ -453,11 +370,7 @@ static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg) if (upper->started) { -#ifdef CONFIG_PWM_PULSECOUNT - ret = lower->ops->start(lower, &upper->info, upper); -#else ret = lower->ops->start(lower, &upper->info); -#endif } } break; @@ -513,12 +426,6 @@ static int pwm_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { ret = lower->ops->stop(lower); upper->started = false; -#ifdef CONFIG_PWM_PULSECOUNT - if (upper->waiting) - { - upper->waiting = false; - } -#endif } } break; @@ -588,9 +495,6 @@ int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev) */ nxmutex_init(&upper->lock); -#ifdef CONFIG_PWM_PULSECOUNT - nxsem_init(&upper->waitsem, 0, 0); -#endif upper->dev = dev; @@ -600,69 +504,4 @@ int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev) return register_driver(path, &g_pwmops, 0666, upper); } -/**************************************************************************** - * Name: pwm_expired - * - * Description: - * If CONFIG_PWM_PULSECOUNT is defined and the pulse count was configured - * to a non-zero value, then the "upper half" driver will wait for the - * pulse count to expire. The sequence of expected events is as follows: - * - * 1. The upper half driver calls the start method, providing the lower - * half driver with the pulse train characteristics. If a fixed - * number of pulses is required, the 'count' value will be nonzero. - * 2. The lower half driver's start() method must verify that it can - * support the request pulse train (frequency, duty, AND pulse count). - * If it cannot, it should return an error. If the pulse count is - * non-zero, it should set up the hardware for that number of pulses - * and return success. NOTE: That is CONFIG_PWM_PULSECOUNT is - * defined, the start() method receives an additional parameter - * that must be used in this callback. - * 3. When the start() method returns success, the upper half driver - * will "sleep" until the pwm_expired method is called. - * 4. When the lower half detects that the pulse count has expired - * (probably through an interrupt), it must call the pwm_expired - * interface using the handle that was previously passed to the - * start() method - * - * Input Parameters: - * handle - This is the handle that was provided to the lower-half - * start() method. - * - * Returned Value: - * None - * - * Assumptions: - * This function may be called from an interrupt handler. - * - ****************************************************************************/ - -#ifdef CONFIG_PWM_PULSECOUNT -void pwm_expired(FAR void *handle) -{ - FAR struct pwm_upperhalf_s *upper = (FAR struct pwm_upperhalf_s *)handle; - - pwminfo("started: %d waiting: %d\n", upper->started, upper->waiting); - - /* Make sure that the PWM is started */ - - if (upper->started) - { - /* Is there a thread waiting for the pulse train to complete? */ - - if (upper->waiting) - { - /* Yes.. clear the waiting flag and awakened the waiting thread */ - - upper->waiting = false; - nxsem_post(&upper->waitsem); - } - - /* The PWM is now stopped */ - - upper->started = false; - } -} -#endif - #endif /* CONFIG_PWM */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 6a520f24a414c..f6ec263ff3d1a 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -114,6 +114,7 @@ #define _EEPIOCBASE (0x4600) /* EEPROM driver ioctl commands */ #define _PTPBASE (0x4700) /* PTP ioctl commands */ #define _DSHOTIOCBASE (0x4800) /* Dshot device ioctl commands */ +#define _PULSECOUNTBASE (0x4900) /* Pulse count driver ioctl commands */ #define _WLIOCBASE (0x8b00) /* Wireless modules ioctl network commands */ /* boardctl() commands share the same number space */ @@ -815,6 +816,11 @@ #define _DSHOTIOCVALID(c) (_IOC_TYPE(c)==_DSHOTIOCBASE) #define _DSHOTIOC(nr) _IOC(_DSHOTIOCBASE,nr) +/* Pulse count driver ioctl definitions *************************************/ + +#define _PULSECOUNTIOCVALID(c) (_IOC_TYPE(c)==_PULSECOUNTBASE) +#define _PULSECOUNTIOC(nr) _IOC(_PULSECOUNTBASE,nr) + /**************************************************************************** * Public Type Definitions ****************************************************************************/ diff --git a/include/nuttx/timers/pulsecount.h b/include/nuttx/timers/pulsecount.h new file mode 100644 index 0000000000000..a6d0ec3c4aa76 --- /dev/null +++ b/include/nuttx/timers/pulsecount.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * include/nuttx/timers/pulsecount.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 __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H +#define __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H + +/* A pulsecount device generates a finite pulse train with controlled high + * time, low time, and pulse count. The driver is split into two parts: + * + * 1. An "upper half", generic character driver that provides the common + * pulsecount interface to application code. + * 2. A "lower half", platform-specific driver that implements the timer + * programming needed to generate the pulse train. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* IOCTL Commands ***********************************************************/ + +/* PULSECOUNTIOC_SETCHARACTERISTICS - Set the pulse train characteristics. + * + * ioctl argument: A read-only reference to struct pulsecount_info_s. + * + * PULSECOUNTIOC_GETCHARACTERISTICS - Get the currently selected pulse train + * characteristics. + * + * ioctl argument: A writable reference to struct pulsecount_info_s. + * + * PULSECOUNTIOC_START - Start the configured pulse train. The + * PULSECOUNTIOC_SETCHARACTERISTICS command must have previously been sent. + * By default this command blocks until the configured pulse count + * completes. That blocking behavior can be overridden by opening the + * device with O_NONBLOCK. + * + * ioctl argument: None. + * + * PULSECOUNTIOC_STOP - Stop pulse generation and return immediately. + * + * TODO: Support cancelling a blocking PULSECOUNTIOC_START. + * + * ioctl argument: None. + */ + +#define PULSECOUNTIOC_SETCHARACTERISTICS _PULSECOUNTIOC(1) +#define PULSECOUNTIOC_GETCHARACTERISTICS _PULSECOUNTIOC(2) +#define PULSECOUNTIOC_START _PULSECOUNTIOC(3) +#define PULSECOUNTIOC_STOP _PULSECOUNTIOC(4) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct pulsecount_info_s +{ + uint32_t high_ns; /* Pulse high time in nanoseconds */ + uint32_t low_ns; /* Pulse low time in nanoseconds */ + uint32_t count; /* Number of pulses to generate */ +}; + +/**************************************************************************** + * Public Inline Functions + ****************************************************************************/ + +static inline uint64_t +pulsecount_period_ns(FAR const struct pulsecount_info_s *info) +{ + return (uint64_t)info->high_ns + info->low_ns; +} + +static inline uint32_t +pulsecount_frequency(FAR const struct pulsecount_info_s *info) +{ + return NSEC_PER_SEC / pulsecount_period_ns(info); +} + +static inline ub16_t +pulsecount_duty(FAR const struct pulsecount_info_s *info) +{ + return (ub16_t)(((uint64_t)info->high_ns << 16) / + pulsecount_period_ns(info)); +} + +struct pulsecount_lowerhalf_s; +struct pulsecount_ops_s +{ + /* This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not generate pulses until the start method is called. + */ + + CODE int (*setup)(FAR struct pulsecount_lowerhalf_s *dev); + + /* This method is called when the driver is closed. The lower half driver + * should stop pulse output, free resources, disable the timer, and put the + * system into the lowest possible power usage state. + */ + + CODE int (*shutdown)(FAR struct pulsecount_lowerhalf_s *dev); + + /* Configure the timer and start the finite pulse train. The lower half + * must call pulsecount_expired() with the provided handle after the + * programmed pulse count completes. + */ + + CODE int (*start)(FAR struct pulsecount_lowerhalf_s *dev, + FAR const struct pulsecount_info_s *info, + FAR void *handle); + + /* Stop the pulse train and reset the timer resources. */ + + CODE int (*stop)(FAR struct pulsecount_lowerhalf_s *dev); + + /* Lower-half logic may support platform-specific ioctl commands. */ + + CODE int (*ioctl)(FAR struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); +}; + +struct pulsecount_lowerhalf_s +{ + /* The first field of this state structure must be a pointer to the + * pulsecount callback structure. + */ + + FAR const struct pulsecount_ops_s *ops; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +int pulsecount_register(FAR const char *path, + FAR struct pulsecount_lowerhalf_s *dev); + +/* This callback is called by the lower half after a finite pulse train has + * completed. The handle must be the value passed to the lower half start() + * method. + */ + +void pulsecount_expired(FAR void *handle); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_TIMERS_PULSECOUNT_H */ diff --git a/include/nuttx/timers/pwm.h b/include/nuttx/timers/pwm.h index d0c2a2f9efa45..86c8f80c60cfc 100644 --- a/include/nuttx/timers/pwm.h +++ b/include/nuttx/timers/pwm.h @@ -24,10 +24,9 @@ #define __INCLUDE_NUTTX_TIMERS_PWM_H /* For the purposes of this driver, a PWM device is any device that generates - * periodic output pulses s of controlled frequency and pulse width. Such a - * device might be used, for example, to perform pulse-width modulated output - * or frequency/pulse-count modulated output (such as might be needed to - * control a stepper motor). + * periodic output pulses of controlled frequency and pulse width. Such a + * device might be used, for example, to perform pulse-width modulated + * output. * * The PWM driver is split into two parts: * @@ -59,10 +58,6 @@ /* Configuration ************************************************************/ /* CONFIG_PWM - Enables because PWM driver support - * CONFIG_PWM_PULSECOUNT - Some hardware will support generation of a fixed - * number of pulses. This might be used, for example to support a stepper - * motor. If the hardware will support a fixed pulse count, then this - * configuration should be set to enable the capability. * CONFIG_DEBUG_PWM_INFO - This will generate output that can be use to * debug the PWM driver. */ @@ -98,11 +93,7 @@ * characteristics of the pulsed output. * * PWMIOC_START - Start the pulsed output. The PWMIOC_SETCHARACTERISTICS - * command must have previously been sent. If CONFIG_PWM_PULSECOUNT is - * defined and the pulse count was configured to a non-zero value, then - * this ioctl call will, by default, block until the programmed pulse count - * completes. That default blocking behavior can be overridden by using - * the O_NONBLOCK flag when the PWM driver is opened. + * command must have previously been sent. * * ioctl argument: None * @@ -174,11 +165,6 @@ struct pwm_chan_s uint8_t cpol; uint8_t dcpol; int8_t channel; - -#ifdef CONFIG_PWM_PULSECOUNT - uint32_t count; /* The number of pulse to generate. 0 means to - * generate an indefinite number of pulses */ -#endif }; /* This structure describes the characteristics of the pulsed output */ @@ -219,17 +205,11 @@ struct pwm_ops_s /* (Re-)initialize the timer resources and start the pulsed output. The * start method should return an error if it cannot start the timer with - * the given parameter (frequency, duty, or optionally pulse count) + * the given parameter (frequency or duty) */ -#ifdef CONFIG_PWM_PULSECOUNT - CODE int (*start)(FAR struct pwm_lowerhalf_s *dev, - FAR const struct pwm_info_s *info, - FAR void *handle); -#else CODE int (*start)(FAR struct pwm_lowerhalf_s *dev, FAR const struct pwm_info_s *info); -#endif /* Stop the pulsed output and reset the timer resources */ @@ -313,47 +293,6 @@ extern "C" int pwm_register(FAR const char *path, FAR struct pwm_lowerhalf_s *dev); -/**************************************************************************** - * Name: pwm_expired - * - * Description: - * If CONFIG_PWM_PULSECOUNT is defined and the pulse count was configured - * to a non-zero value, then the "upper half" driver will wait for the - * pulse count to expire. The sequence of expected events is as follows: - * - * 1. The upper half driver calls the start method, providing the lower - * half driver with the pulse train characteristics. If a fixed - * number of pulses is required, the 'count' value will be nonzero. - * 2. The lower half driver's start() method must verify that it can - * support the request pulse train (frequency, duty, AND pulse count). - * If it cannot, it should return an error. If the pulse count is - * non-zero, it should set up the hardware for that number of pulses - * and return success. NOTE: That is CONFIG_PWM_PULSECOUNT is - * defined, the start() method receives an additional parameter - * that must be used in this callback. - * 3. When the start() method returns success, the upper half driver - * will "sleep" until the pwm_expired method is called. - * 4. When the lower half detects that the pulse count has expired - * (probably through an interrupt), it must call the pwm_expired - * interface using the handle that was previously passed to the - * start() method - * - * Input Parameters: - * handle - This is the handle that was provided to the lower-half - * start() method. - * - * Returned Value: - * None - * - * Assumptions: - * This function may be called from an interrupt handler. - * - ****************************************************************************/ - -#ifdef CONFIG_PWM_PULSECOUNT -void pwm_expired(FAR void *handle); -#endif - /**************************************************************************** * Platform-Independent "Lower-Half" PWM Driver Interfaces ****************************************************************************/ From 8f8ab6e32d0b98143554539362dfd31f3b37341a Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:14:30 +0200 Subject: [PATCH 02/14] !arch/at32: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/at32/Kconfig | 116 +- arch/arm/src/at32/Make.defs | 5 +- arch/arm/src/at32/at32_pulsecount.c | 1887 +++++++++++++++++++++++++++ arch/arm/src/at32/at32_pulsecount.h | 39 + arch/arm/src/at32/at32_pwm.c | 562 +------- 5 files changed, 2038 insertions(+), 571 deletions(-) create mode 100644 arch/arm/src/at32/at32_pulsecount.c create mode 100644 arch/arm/src/at32/at32_pulsecount.h diff --git a/arch/arm/src/at32/Kconfig b/arch/arm/src/at32/Kconfig index 6d43630fef065..a8e821da370ee 100644 --- a/arch/arm/src/at32/Kconfig +++ b/arch/arm/src/at32/Kconfig @@ -1052,7 +1052,6 @@ config AT32_TIM config AT32_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config AT32_CAP bool @@ -1108,7 +1107,6 @@ config ARCH_BOARD_AT32_CUSTOM_CLOCKCONFIG ---help--- Enables special, board-specific AT32 clock configuration. - config AT32_DMACAPABLE bool "Workaround non-DMA capable memory" depends on ARCH_DMA @@ -3662,6 +3660,113 @@ config AT32_PWM_TRGO ---help--- Enable TRGO support for PWM driver +config AT32_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config AT32_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on AT32_TIM1 + select AT32_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if AT32_TIM1_PULSECOUNT + +config AT32_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config AT32_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config AT32_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config AT32_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # AT32_TIM1_PULSECOUNT + +config AT32_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on AT32_TIM8 + select AT32_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if AT32_TIM8_PULSECOUNT + +config AT32_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config AT32_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config AT32_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config AT32_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # AT32_TIM8_PULSECOUNT + +config AT32_TIM20_PULSECOUNT + bool "TIM20 pulse count" + default n + depends on AT32_TIM20 + select AT32_PULSECOUNT + ---help--- + Reserve timer 20 for pulse count output. + +if AT32_TIM20_PULSECOUNT + +config AT32_TIM20_PULSECOUNT_TDTS + int "TIM20 pulse count clock division" + default 0 + range 0 2 + +config AT32_TIM20_PULSECOUNT_CHANNEL + int "TIM20 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config AT32_TIM20_PULSECOUNT_POL + int "TIM20 pulse count output polarity" + default 0 + range 0 1 + +config AT32_TIM20_PULSECOUNT_IDLE + int "TIM20 pulse count idle state" + default 0 + range 0 1 + +endif # AT32_TIM20_PULSECOUNT config AT32_TIM1_ADC bool "TIM1 ADC" default n @@ -6008,7 +6113,6 @@ config AT32_ADC5_JEXTSEL endmenu - config AT32_USART bool default n @@ -6807,7 +6911,6 @@ config AT32_HCIUART_RXDMAPRIO ---help--- Select HCI UART DMA priority. - config AT32_HCIUART_SW_RXFLOW bool "Use Software UART RTS flow control" default n @@ -6940,7 +7043,6 @@ config AT32_SPI4_DMA_BUFFER endmenu # SPI Configuration - menu "I2C Configuration" depends on AT32_I2C @@ -7027,7 +7129,6 @@ config AT32_SDIO_DMAPRIO For AT32 , options are: 0x00000000 low, 0x00001000 medium, 0x00002000 high, 0x00003000 very high. Default: medium. - config AT32_SDIO_WIDTH_D1_ONLY bool "Use D1 only" default n @@ -7036,7 +7137,6 @@ config AT32_SDIO_WIDTH_D1_ONLY endmenu - config AT32_HAVE_RTC_COUNTER bool default n @@ -7433,7 +7533,6 @@ config OTG_ID_GPIO_DISABLE endmenu - menu "CAN driver configuration" depends on AT32_CAN @@ -7488,7 +7587,6 @@ config AT32_CAN_REGDEBUG endmenu # "CAN driver configuration" - menu "AT32 QEncoder Driver" depends on SENSORS_QENCODER depends on AT32_TIM1 || AT32_TIM2 || AT32_TIM3 || AT32_TIM4 || AT32_TIM5 || AT32_TIM8 diff --git a/arch/arm/src/at32/Make.defs b/arch/arm/src/at32/Make.defs index 851e6b4f504d5..67be986fd580a 100644 --- a/arch/arm/src/at32/Make.defs +++ b/arch/arm/src/at32/Make.defs @@ -146,6 +146,10 @@ ifeq ($(CONFIG_AT32_PWM),y) CHIP_CSRCS += at32_pwm.c endif +ifeq ($(CONFIG_AT32_PULSECOUNT),y) +CHIP_CSRCS += at32_pulsecount.c +endif + ifeq ($(CONFIG_AT32_CAP),y) CHIP_CSRCS += at32_capture_lowerhalf.c endif @@ -180,4 +184,3 @@ ifeq ($(CONFIG_AT32_FOC),y) CHIP_CSRCS += at32_foc.c endif - diff --git a/arch/arm/src/at32/at32_pulsecount.c b/arch/arm/src/at32/at32_pulsecount.c new file mode 100644 index 0000000000000..c9501c538912d --- /dev/null +++ b/arch/arm/src/at32/at32_pulsecount.c @@ -0,0 +1,1887 @@ +/**************************************************************************** + * arch/arm/src/at32/at32_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "at32_pulsecount.h" +#include "at32_rcc.h" +#include "at32_gpio.h" +#include "at32_tim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the pulsecount upper half driver. + * + * It implements support for both: + * 1. AT32 TIMER IP version 1 - F43x + */ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED +#define TIMTYPE_TIM20 TIMTYPE_ADVANCED + +/* Advanced timer clock source, RCC EN offset, enable bit, + * RCC RST offset, reset bit to use + */ + +# define TIMCLK_TIM1 AT32_APB2_TIM1_CLKIN +# define TIMRCCEN_TIM1 AT32_CRM_APB2EN +# define TIMEN_TIM1 CRM_APB2EN_TMR1EN +# define TIMRCCRST_TIM1 AT32_CRM_APB2RST +# define TIMRST_TIM1 CRM_APB2RST_TMR1RST +# define TIMCLK_TIM8 AT32_APB2_TIM8_CLKIN +# define TIMRCCEN_TIM8 AT32_CRM_APB2EN +# define TIMEN_TIM8 CRM_APB2EN_TMR8EN +# define TIMRCCRST_TIM8 AT32_CRM_APB2RST +# define TIMRST_TIM8 CRM_APB2RST_TMR8RST +# define TIMCLK_TIM20 AT32_APB2_TIM20_CLKIN +# define TIMRCCEN_TIM20 AT32_CRM_APB2EN +# define TIMEN_TIM20 CRM_APB2EN_TMR20EN +# define TIMRCCRST_TIM20 AT32_CRM_APB2RST +# define TIMRST_TIM20 CRM_APB2RST_TMR20RST + +/* Default GPIO pins state */ + +#define PINCFG_DEFAULT (GPIO_INPUT | GPIO_FLOAT) + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) at32_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct at32_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct at32_chan_s +{ + uint8_t channel; + struct at32_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct at32_tim_s +{ + struct at32_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct at32_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct at32_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pulsecount_getreg(struct at32_tim_s *priv, int offset); +static void pulsecount_putreg(struct at32_tim_s *priv, int offset, + uint32_t value); +static void pulsecount_modifyreg(struct at32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct at32_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct at32_tim_s *priv, + struct at32_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_AT32_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_AT32_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_AT32_TIM20_PULSECOUNT +static int pulsecount_tim20interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_AT32_TIM1_PULSECOUNT + +static struct at32_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_AT32_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_AT32_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_AT32_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_AT32_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_AT32_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_AT32_TIM1_PULSECOUNT_TDTS, + .irq = AT32_IRQ_TIM1UP, + .base = AT32_TMR1_BASE, + .pclk = TIMCLK_TIM1, +}; + +#endif /* CONFIG_AT32_TIM1_PULSECOUNT */ + +#ifdef CONFIG_AT32_TIM8_PULSECOUNT + +static struct at32_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_AT32_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_AT32_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_AT32_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_AT32_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_AT32_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_AT32_TIM8_PULSECOUNT_TDTS, + .irq = AT32_IRQ_TIM8UP, + .base = AT32_TMR8_BASE, + .pclk = TIMCLK_TIM8, +}; + +#endif /* CONFIG_AT32_TIM8_PULSECOUNT */ + +#ifdef CONFIG_AT32_TIM20_PULSECOUNT + +static struct at32_tim_s g_pulsecount20dev = +{ + .channel = + { + .channel = CONFIG_AT32_TIM20_PULSECOUNT_CHANNEL, +#if CONFIG_AT32_TIM20_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM20_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM20_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM20_CH1OUT, + }, +#elif CONFIG_AT32_TIM20_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM20_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM20_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM20_CH2OUT, + }, +#elif CONFIG_AT32_TIM20_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM20_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM20_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM20_CH3OUT, + }, +#elif CONFIG_AT32_TIM20_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_AT32_TIM20_PULSECOUNT_POL, + .idle = CONFIG_AT32_TIM20_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM20_CH4OUT, + }, +#endif + }, + .timid = 20, + .timtype = TIMTYPE_TIM20, + .t_dts = CONFIG_AT32_TIM20_PULSECOUNT_TDTS, + .irq = AT32_IRQ_TIM20UP, + .base = AT32_TMR20_BASE, + .pclk = TIMCLK_TIM20, +}; + +#endif /* CONFIG_AT32_TIM20_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_AT32_TIM1_PULSECOUNT +static struct at32_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_AT32_TIM8_PULSECOUNT +static struct at32_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +#ifdef CONFIG_AT32_TIM20_PULSECOUNT +static struct at32_pulsecount_s g_pulsecount20lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount20dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_reg_is_32bit + ****************************************************************************/ + +static bool pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + bool ret = false; + + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == AT32_ATIM_CR2_OFFSET || + offset == AT32_ATIM_CCMR1_OFFSET || + offset == AT32_ATIM_CCMR2_OFFSET || + offset == AT32_ATIM_CCER_OFFSET || + offset == AT32_ATIM_BDTR_OFFSET) + { + ret = true; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pulsecount_getreg(struct at32_tim_s *priv, int offset) +{ + uint32_t retval = 0; + + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct at32_tim_s *priv, int offset, + uint32_t value) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct at32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); + } +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, AT32_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_DIER_OFFSET)); + + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, AT32_GTIM_SR_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CCMR2_OFFSET)); + + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, AT32_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_ARR_OFFSET)); + + if (priv->timid == 1 || priv->timid == 8 || priv->timid == 20) + { + _info(" RCR: %04x BDTR: %04x\n", + pulsecount_getreg(priv, AT32_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, AT32_ATIM_BDTR_OFFSET)); + } + + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, AT32_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_CCR4_OFFSET)); + + _info(" DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, AT32_GTIM_DCR_OFFSET), + pulsecount_getreg(priv, AT32_GTIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t offset = 0; + + /* CCR channel indices are one-based to match timer channel numbers. */ + + switch (index) + { + case 1: + { + offset = AT32_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = AT32_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = AT32_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = AT32_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, AT32_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32" frequency: %" PRIu32 + " TIMCLK: %" PRIu32 " " + "prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, AT32_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, AT32_GTIM_PSC_OFFSET, (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct at32_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, AT32_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, AT32_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* Get CCMR offset */ + + switch (channel) + { + case 1: + case 2: + { + offset = AT32_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = AT32_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + /* Reset current channel 1/3 mode configuration */ + + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE); + + /* Configure CC1/3 as output */ + + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + + /* Configure Compare 1/3 mode */ + + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + + /* Enable CCR1/3 preload */ + + ocmode |= ATIM_CCMR1_OC1PE; + +#ifdef HAVE_IP_TIMERS_V2 + /* Reset current OC bit */ + + ccmr &= ~(ATIM_CCMR1_OC1M); + + /* Clear the additional OC1/3M bit */ +#endif + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + /* Reset current channel 2/4 mode configuration */ + + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE); + + /* Configure CC2/4 as output */ + + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + + /* Configure Compare 2/4 mode */ + + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + + /* Enable CCR2/4 preload */ + + ocmode |= ATIM_CCMR1_OC2PE; + +#ifdef HAVE_IP_TIMERS_V2 + /* Reset current OC bit */ + + ccmr &= ~(ATIM_CCMR1_OC2M); + + /* Clear the additional OC2/4M bit */ +#endif + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct at32_tim_s *priv, + struct at32_chan_s *chan) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + uint8_t channel = 0; + + /* Get channel */ + + channel = chan->channel; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, AT32_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, AT32_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (chan->out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (chan->out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + +#ifdef HAVE_IP_TIMERS_V2 + /* Channels 5 and 6 are outside this split. */ + + cr2 &= ~(ATIM_CR2_OIS5 | ATIM_CR2_OIS6); + + /* Channels 5 and 6 are outside this split. */ + + ccer &= ~(ATIM_CCER_CC5P | ATIM_CCER_CC6P); +#endif /* HAVE_IP_TIMERS_V2 */ + } + + /* Write registers */ + + pulsecount_modifyreg(priv, AT32_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, AT32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum at32_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, AT32_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + +#ifdef HAVE_IP_TIMERS_V2 + regval |= ((outputs & (1 << 8)) ? ATIM_CCER_CC5E : 0); + regval |= ((outputs & (1 << 10)) ? ATIM_CCER_CC6E : 0); +#endif + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, AT32_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, AT32_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, AT32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct at32_tim_s *priv, uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer in PULSECOUNT mode + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, AT32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channels */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, &priv->channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, AT32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, AT32_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, AT32_ATIM_RCR_OFFSET, priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, AT32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, AT32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, AT32_ATIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, AT32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, AT32_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, AT32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, AT32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, AT32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, AT32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the master output to stop the output as + * quickly as possible. + */ + + pulsecount_moe_enable(dev, false); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, AT32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output. + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_AT32_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_AT32_TIM1_PULSECOUNT */ + +#ifdef CONFIG_AT32_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_AT32_TIM8_PULSECOUNT */ + +#ifdef CONFIG_AT32_TIM20_PULSECOUNT +static int pulsecount_tim20interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount20dev); +} +#endif /* CONFIG_AT32_TIM20_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* Use the advanced-timer repetition counter limit. */ + + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_set_apb_clock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_set_apb_clock(struct at32_tim_s *priv, bool on) +{ + uint32_t en_bit = 0; + uint32_t regaddr = 0; + int ret = OK; + + _info("timer %d clock enable: %d\n", priv->timid, on ? 1 : 0); + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_AT32_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCEN_TIM1; + en_bit = TIMEN_TIM1; + break; + } +#endif + +#ifdef CONFIG_AT32_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCEN_TIM8; + en_bit = TIMEN_TIM8; + break; + } +#endif + +#ifdef CONFIG_AT32_TIM20_PULSECOUNT + case 20: + { + regaddr = TIMRCCEN_TIM20; + en_bit = TIMEN_TIM20; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + _info("RCC_APBxENR base: %08" PRIx32 " bits: %04" PRIx32 "\n", + regaddr, en_bit); + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, true); + if (ret < 0) + { + goto errout; + } + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + /* Do not configure the pin if pincfg is not specified. + * This prevents overwriting the PA0 configuration if the + * channel is used internally. + */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + at32_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= PINCFG_DEFAULT; + + at32_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + irqstate_t flags = 0; + uint16_t outputs = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Stopped so frequency is zero */ + + priv->frequency = 0; + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, AT32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, AT32_GTIM_SR_OFFSET, 0); + + /* Disable the timer and timer outputs */ + + pulsecount_modifyreg(priv, AT32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + outputs = pulsecount_outputs_from_channels(priv, 0); + ret = pulsecount_outputs_enable(dev, outputs, false); + + leave_critical_section(flags); + + pulsecount_dumpregs(dev, "After stop"); + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct at32_tim_s *priv = (struct at32_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_pulsecount_s *pulse = (struct at32_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_pulsecount_s *pulse = (struct at32_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct at32_pulsecount_s *pulse = (struct at32_pulsecount_s *)dev; + struct at32_tim_s *priv = pulse->timer; + + if (info->count > 0) + { + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + priv->handle = handle; + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct at32_pulsecount_s *pulse = (struct at32_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct at32_pulsecount_s *pulse = (struct at32_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *at32_pulsecountinitialize(int timer) +{ + struct at32_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_AT32_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_AT32_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_AT32_TIM20_PULSECOUNT + case 20: + { + lower = &g_pulsecount20lower; + irq_attach(lower->timer->irq, pulsecount_tim20interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} diff --git a/arch/arm/src/at32/at32_pulsecount.h b/arch/arm/src/at32/at32_pulsecount.h new file mode 100644 index 0000000000000..9fc6e28d89aae --- /dev/null +++ b/arch/arm/src/at32/at32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/at32/at32_pulsecount.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_AT32_AT32_PULSECOUNT_H +#define __ARCH_ARM_SRC_AT32_AT32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *at32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_AT32_AT32_PULSECOUNT_H */ diff --git a/arch/arm/src/at32/at32_pwm.c b/arch/arm/src/at32/at32_pwm.c index 3867f59c220c3..1dd25c307d7b0 100644 --- a/arch/arm/src/at32/at32_pwm.c +++ b/arch/arm/src/at32/at32_pwm.c @@ -186,17 +186,6 @@ # undef HAVE_ADVTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_AT32_TIM1_PWM) || defined(CONFIG_AT32_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* TRGO/TRGO2 support */ #ifdef CONFIG_AT32_PWM_TRGO @@ -288,21 +277,12 @@ struct at32_pwmtimer_s uint8_t trgo; /* TRGO configuration: * 4 LSB = TRGO, 4 MSB = TRGO2 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ #endif uint32_t frequency; /* Current frequency setting */ uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral * clock that drives the timer module */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -363,41 +343,15 @@ static uint16_t pwm_rcr_get(struct pwm_lowerhalf_s *dev); static int pwm_rcr_update(struct pwm_lowerhalf_s *dev, uint16_t rcr); #endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev); -#else static int pwm_configure(struct pwm_lowerhalf_s *dev); -#endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#endif static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_AT32_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_AT32_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_AT32_TIM20_PWM -static int pwm_tim20interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#endif static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); @@ -417,11 +371,7 @@ static const struct pwm_ops_s g_pwmops = { .setup = pwm_setup, .shutdown = pwm_shutdown, -#ifdef CONFIG_PWM_PULSECOUNT - .start = pwm_start_pulsecount, -#else .start = pwm_start, -#endif .stop = pwm_stop, .ioctl = pwm_ioctl, }; @@ -613,9 +563,6 @@ static struct at32_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM1_TRGO) .trgo = AT32_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM1UP, #endif .base = AT32_TMR1_BASE, .pclk = TIMCLK_TIM1, @@ -712,9 +659,6 @@ static struct at32_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM2_TRGO) .trgo = AT32_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM2, #endif .base = AT32_TMR2_BASE, .pclk = TIMCLK_TIM2, @@ -811,9 +755,6 @@ static struct at32_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM3_TRGO) .trgo = AT32_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM3, #endif .base = AT32_TMR3_BASE, .pclk = TIMCLK_TIM3, @@ -910,9 +851,6 @@ static struct at32_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM4_TRGO) .trgo = AT32_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM4, #endif .base = AT32_TMR4_BASE, .pclk = TIMCLK_TIM4, @@ -1007,9 +945,6 @@ static struct at32_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM5_TRGO) .trgo = AT32_TIM5_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM5, #endif .base = AT32_TMR5_BASE, .pclk = TIMCLK_TIM5, @@ -1173,9 +1108,6 @@ static struct at32_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM8_TRGO) .trgo = AT32_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM8UP, #endif .base = AT32_TMR8_BASE, .pclk = TIMCLK_TIM8, @@ -1240,9 +1172,6 @@ static struct at32_pwmtimer_s g_pwm9dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM9 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM9, #endif .base = AT32_TMR9_BASE, .pclk = TIMCLK_TIM9, @@ -1291,9 +1220,6 @@ static struct at32_pwmtimer_s g_pwm10dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM10 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM10, #endif .base = AT32_TMR10_BASE, .pclk = TIMCLK_TIM10, @@ -1342,9 +1268,6 @@ static struct at32_pwmtimer_s g_pwm11dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM11 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM11, #endif .base = AT32_TMR11_BASE, .pclk = TIMCLK_TIM11, @@ -1409,9 +1332,6 @@ static struct at32_pwmtimer_s g_pwm12dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM12 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM12, #endif .base = AT32_TMR12_BASE, .pclk = TIMCLK_TIM12, @@ -1460,9 +1380,6 @@ static struct at32_pwmtimer_s g_pwm13dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM13 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM13, #endif .base = AT32_TMR13_BASE, .pclk = TIMCLK_TIM13, @@ -1511,9 +1428,6 @@ static struct at32_pwmtimer_s g_pwm14dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM14 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM14, #endif .base = AT32_TMR14_BASE, .pclk = TIMCLK_TIM14, @@ -1677,9 +1591,6 @@ static struct at32_pwmtimer_s g_pwm20dev = #endif #if defined(HAVE_TRGO) && defined(AT32_TIM20_TRGO) .trgo = AT32_TIM20_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = AT32_IRQ_TIM20UP, #endif .base = AT32_TMR20_BASE, .pclk = TIMCLK_TIM20, @@ -3088,256 +2999,11 @@ static int pwm_break_dt_configure(struct at32_pwmtimer_s *priv) } #endif -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_configure - * - * Description: - * Configure PWM timer in PULSECOUNT mode - * - ****************************************************************************/ - -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev) -{ - struct at32_pwmtimer_s *priv = (struct at32_pwmtimer_s *)dev; - uint16_t outputs = 0; - uint8_t j = 0; - int ret = OK; - - /* NOTE: leave timer counter disabled and all outputs disabled! */ - - /* Disable the timer until we get it configured */ - - pwm_timer_enable(dev, false); - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* REVISIT: Disable outputs */ - - ret = pwm_outputs_enable(dev, outputs, false); - if (ret < 0) - { - goto errout; - } - - /* Initial timer configuration */ - - ret = pwm_timer_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure break and deadtime register */ - - ret = pwm_break_dt_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Disable software break (enable outputs) */ - - ret = pwm_soft_break(dev, false); - if (ret < 0) - { - goto errout; - } - -#ifdef HAVE_TRGO - /* Configure TRGO/TRGO2 */ - - ret = pwm_trgo_configure(dev, priv->trgo); - if (ret < 0) - { - goto errout; - } -#endif - - /* Configure timer channels */ - - for (j = 0; j < priv->chan_num; j++) - { - /* Skip channel if not in use */ - - if (priv->channels[j].channel != 0) - { - /* Update PWM mode */ - - pwm_mode_configure(dev, priv->channels[j].channel, - priv->channels[j].mode); - - /* PWM outputs configuration */ - - pwm_output_configure(priv, &priv->channels[j]); - } - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct at32_pwmtimer_s *priv = (struct at32_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %" PRIx32 " duty: %08" PRIx32 - " count: %" PRIx32 "\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, AT32_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, AT32_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_rcr_update(dev, priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_rcr_update(dev, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, AT32_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, AT32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#endif /* CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -3611,173 +3277,6 @@ static int pwm_timer(struct pwm_lowerhalf_s *dev, return ret; } -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct at32_pwmtimer_s *priv = (struct at32_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, AT32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, AT32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the master output to stop the output as - * quickly as possible. - */ - - pwm_soft_break(dev, true); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output. - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %" PRIx32 "\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_AT32_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_AT32_TIM1_PWM */ - -#ifdef CONFIG_AT32_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_AT32_TIM8_PWM */ - -#ifdef CONFIG_AT32_TIM20_PWM -static int pwm_tim20interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm20dev); -} -#endif /* CONFIG_AT32_TIM20_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* REVISIT: RCR_REP_MAX for GTIM or ATIM ? */ - - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ - /**************************************************************************** * Name: pwm_set_apb_clock * @@ -4032,13 +3531,6 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) * counter, disabled outputs, not configured frequency and duty cycle */ -#ifdef CONFIG_PWM_PULSECOUNT - if (priv->timtype == TIMTYPE_ADVANCED) - { - ret = pwm_pulsecount_configure(dev); - } - else -#endif { ret = pwm_configure(dev); } @@ -4139,46 +3631,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct at32_pwmtimer_s *priv = (struct at32_pwmtimer_s *)dev; - - /* Generate an indefinite number of pulses */ - - if (info->channels[0].count == 0) - { - return pwm_start(dev, info); - } - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) - * REVISIT: verify if TIMTYPE_COUNTUP16_N works with it - */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %" PRIx32 "\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#endif /* CONFIG_PWM_PULSECOUNT */ - static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -4345,10 +3797,6 @@ struct pwm_lowerhalf_s *at32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4392,10 +3840,6 @@ struct pwm_lowerhalf_s *at32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4456,10 +3900,6 @@ struct pwm_lowerhalf_s *at32_pwminitialize(int timer) /* Attach but disable the TIM20 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim20interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif From 2c1d4268948158c081a8824e6a14342fdb766562 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:15:54 +0200 Subject: [PATCH 03/14] !arch/stm32: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32/CMakeLists.txt | 4 + arch/arm/src/stm32/Kconfig | 74 +- arch/arm/src/stm32/Make.defs | 4 + arch/arm/src/stm32/stm32_pulsecount.c | 1775 +++++++++++++++++++++++++ arch/arm/src/stm32/stm32_pulsecount.h | 39 + arch/arm/src/stm32/stm32_pwm.c | 554 +------- 6 files changed, 1896 insertions(+), 554 deletions(-) create mode 100644 arch/arm/src/stm32/stm32_pulsecount.c create mode 100644 arch/arm/src/stm32/stm32_pulsecount.h diff --git a/arch/arm/src/stm32/CMakeLists.txt b/arch/arm/src/stm32/CMakeLists.txt index 9727b096e3ba5..a725dcc6f908b 100644 --- a/arch/arm/src/stm32/CMakeLists.txt +++ b/arch/arm/src/stm32/CMakeLists.txt @@ -200,6 +200,10 @@ if(CONFIG_STM32_PWM) list(APPEND SRCS stm32_pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS stm32_pulsecount.c) +endif() + if(CONFIG_STM32_CAP) list(APPEND SRCS stm32_capture_lowerhalf.c) endif() diff --git a/arch/arm/src/stm32/Kconfig b/arch/arm/src/stm32/Kconfig index 97dcf898a3fe4..f6d7ff5b37e79 100644 --- a/arch/arm/src/stm32/Kconfig +++ b/arch/arm/src/stm32/Kconfig @@ -3459,7 +3459,6 @@ config STM32_TIM config STM32_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32_CAP bool @@ -6045,6 +6044,79 @@ config STM32_PWM_TRGO ---help--- Enable TRGO support for PWM driver +config STM32_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config STM32_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32_TIM1 + select STM32_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if STM32_TIM1_PULSECOUNT + +config STM32_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config STM32_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config STM32_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # STM32_TIM1_PULSECOUNT + +config STM32_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on STM32_TIM8 + select STM32_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if STM32_TIM8_PULSECOUNT + +config STM32_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config STM32_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config STM32_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # STM32_TIM8_PULSECOUNT config STM32_TIM1_ADC bool "TIM1 ADC" default n diff --git a/arch/arm/src/stm32/Make.defs b/arch/arm/src/stm32/Make.defs index 09f2cf8f3bd26..32c30a76ee74e 100644 --- a/arch/arm/src/stm32/Make.defs +++ b/arch/arm/src/stm32/Make.defs @@ -184,6 +184,10 @@ ifeq ($(CONFIG_STM32_PWM),y) CHIP_CSRCS += stm32_pwm.c endif +ifeq ($(CONFIG_STM32_PULSECOUNT),y) +CHIP_CSRCS += stm32_pulsecount.c +endif + ifeq ($(CONFIG_STM32_CAP),y) CHIP_CSRCS += stm32_capture_lowerhalf.c endif diff --git a/arch/arm/src/stm32/stm32_pulsecount.c b/arch/arm/src/stm32/stm32_pulsecount.c new file mode 100644 index 0000000000000..c8aea22c4e19e --- /dev/null +++ b/arch/arm/src/stm32/stm32_pulsecount.c @@ -0,0 +1,1775 @@ +/**************************************************************************** + * arch/arm/src/stm32/stm32_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32_pulsecount.h" +#include "stm32_rcc.h" +#include "stm32_gpio.h" +#include "stm32_tim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the pulsecount upper half driver. + * + * It implements support for both: + * 1. STM32 TIMER IP version 1 - F0, F1, F2, F37x, F4, L0, L1 + * 2. STM32 TIMER IP version 2 - F3 (no F37x), F7, H7, L4, L4+ + */ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED + +/* Advanced timer clock source, RCC EN offset, enable bit, + * RCC RST offset, reset bit to use + */ + +# define TIMCLK_TIM1 STM32_APB2_TIM1_CLKIN +# define TIMRCCEN_TIM1 STM32_RCC_APB2ENR +# define TIMEN_TIM1 RCC_APB2ENR_TIM1EN +# define TIMRCCRST_TIM1 STM32_RCC_APB2RSTR +# define TIMRST_TIM1 RCC_APB2RSTR_TIM1RST +# define TIMCLK_TIM8 STM32_APB2_TIM8_CLKIN +# define TIMRCCEN_TIM8 STM32_RCC_APB2ENR +# define TIMEN_TIM8 RCC_APB2ENR_TIM8EN +# define TIMRCCRST_TIM8 STM32_RCC_APB2RSTR +# define TIMRST_TIM8 RCC_APB2RSTR_TIM8RST + +/* Default GPIO pins state */ + +#if defined(CONFIG_STM32_STM32F10XX) +# define PINCFG_DEFAULT (GPIO_INPUT | GPIO_CNF_INFLOAT | GPIO_MODE_INPUT) +#elif defined(CONFIG_STM32_STM32F20XX) || \ + defined(CONFIG_STM32_STM32F30XX) || \ + defined(CONFIG_STM32_STM32F33XX) || \ + defined(CONFIG_STM32_STM32F37XX) || \ + defined(CONFIG_STM32_STM32F4XXX) || \ + defined(CONFIG_STM32_STM32L15XX) || \ + defined(CONFIG_STM32_STM32G4XXX) +# define PINCFG_DEFAULT (GPIO_INPUT | GPIO_FLOAT) +#else +# error "Unrecognized STM32 chip" +#endif + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) stm32_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct stm32_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct stm32_chan_s +{ + uint8_t channel; + struct stm32_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32_tim_s +{ + struct stm32_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct stm32_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct stm32_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset); +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value); +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct stm32_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_STM32_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_STM32_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32_TIM1_PULSECOUNT + +static struct stm32_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_STM32_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_STM32_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_STM32_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_STM32_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_STM32_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_STM32_TIM1_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM1UP, + .base = STM32_TIM1_BASE, + .pclk = TIMCLK_TIM1, +}; + +#endif /* CONFIG_STM32_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32_TIM8_PULSECOUNT + +static struct stm32_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_STM32_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_STM32_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_STM32_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_STM32_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_STM32_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_STM32_TIM8_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM8UP, + .base = STM32_TIM8_BASE, + .pclk = TIMCLK_TIM8, +}; + +#endif /* CONFIG_STM32_TIM8_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32_TIM1_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_STM32_TIM8_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_reg_is_32bit + ****************************************************************************/ + +static bool pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + bool ret = false; + + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == STM32_ATIM_CR2_OFFSET || + offset == STM32_ATIM_CCMR1_OFFSET || + offset == STM32_ATIM_CCMR2_OFFSET || + offset == STM32_ATIM_CCER_OFFSET || + offset == STM32_ATIM_BDTR_OFFSET) + { + ret = true; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset) +{ + uint32_t retval = 0; + + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); + } +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DIER_OFFSET)); + + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_SR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET)); + + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET)); + + if (priv->timid == 1 || priv->timid == 8) + { + _info(" RCR: %04x BDTR: %04x\n", + pulsecount_getreg(priv, STM32_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET)); + } + + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR4_OFFSET)); + + _info(" DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_DCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t offset = 0; + + /* CCR channel indices are one-based to match timer channel numbers. */ + + switch (index) + { + case 1: + { + offset = STM32_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = STM32_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = STM32_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = STM32_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32" frequency: %" PRIu32 + " TIMCLK: %" PRIu32 " " + "prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, STM32_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, STM32_GTIM_PSC_OFFSET, (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct stm32_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* Get CCMR offset */ + + switch (channel) + { + case 1: + case 2: + { + offset = STM32_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = STM32_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + ocmode |= ATIM_CCMR1_OC1PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC1M); +#endif + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + ocmode |= ATIM_CCMR1_OC2PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC2M); +#endif + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + uint8_t channel = 0; + + /* Get channel */ + + channel = chan->channel; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (chan->out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (chan->out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + } + + /* Write registers */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, STM32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum stm32_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct stm32_tim_s *priv, uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer in PULSECOUNT mode + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channels */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, &priv->channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, STM32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the master output to stop the output as + * quickly as possible. + */ + + pulsecount_moe_enable(dev, false); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output. + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_STM32_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_STM32_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_STM32_TIM8_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* Use the advanced-timer repetition counter limit. */ + + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_set_apb_clock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_set_apb_clock(struct stm32_tim_s *priv, bool on) +{ + uint32_t en_bit = 0; + uint32_t regaddr = 0; + int ret = OK; + + _info("timer %d clock enable: %d\n", priv->timid, on ? 1 : 0); + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCEN_TIM1; + en_bit = TIMEN_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCEN_TIM8; + en_bit = TIMEN_TIM8; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + _info("RCC_APBxENR base: %08" PRIx32 " bits: %04" PRIx32 "\n", + regaddr, en_bit); + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, true); + if (ret < 0) + { + goto errout; + } + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + /* Do not configure the pin if pincfg is not specified. + * This prevents overwriting the PA0 configuration if the + * channel is used internally. + */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= PINCFG_DEFAULT; + + stm32_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + irqstate_t flags = 0; + uint16_t outputs = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Stopped so frequency is zero */ + + priv->frequency = 0; + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Disable the timer and timer outputs */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + outputs = pulsecount_outputs_from_channels(priv, 0); + ret = pulsecount_outputs_enable(dev, outputs, false); + + /* Clear all channels */ + + pulsecount_putreg(priv, STM32_GTIM_CCR1_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR2_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR3_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR4_OFFSET, 0); + + leave_critical_section(flags); + + pulsecount_dumpregs(dev, "After stop"); + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + struct stm32_tim_s *priv = pulse->timer; + + /* Check if a pulsecount has been selected */ + + if (info->count > 0) + { + /* Only the advanced timers (TIM1,8 can support the pulse counting) + */ + + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + /* Save the handle */ + + priv->handle = handle; + + /* Start the time */ + + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer) +{ + struct stm32_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_STM32_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} diff --git a/arch/arm/src/stm32/stm32_pulsecount.h b/arch/arm/src/stm32/stm32_pulsecount.h new file mode 100644 index 0000000000000..20ccf3608fed1 --- /dev/null +++ b/arch/arm/src/stm32/stm32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32/stm32_pulsecount.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_STM32_STM32_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32_STM32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32_STM32_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32/stm32_pwm.c b/arch/arm/src/stm32/stm32_pwm.c index fd1b45f1647a2..4b7d1378d8240 100644 --- a/arch/arm/src/stm32/stm32_pwm.c +++ b/arch/arm/src/stm32/stm32_pwm.c @@ -284,17 +284,6 @@ # undef HAVE_ADVTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_STM32_TIM1_PWM) || defined(CONFIG_STM32_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* TRGO/TRGO2 support */ #ifdef CONFIG_STM32_PWM_TRGO @@ -387,21 +376,12 @@ struct stm32_pwmtimer_s uint8_t trgo; /* TRGO configuration: * 4 LSB = TRGO, 4 MSB = TRGO2 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ #endif uint32_t frequency; /* Current frequency setting */ uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral * clock that drives the timer module */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -462,38 +442,15 @@ static uint16_t pwm_rcr_get(struct pwm_lowerhalf_s *dev); static int pwm_rcr_update(struct pwm_lowerhalf_s *dev, uint16_t rcr); #endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev); -#else static int pwm_configure(struct pwm_lowerhalf_s *dev); -#endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#endif static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_STM32_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_STM32_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#endif static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); @@ -513,11 +470,7 @@ static const struct pwm_ops_s g_pwmops = { .setup = pwm_setup, .shutdown = pwm_shutdown, -#ifdef CONFIG_PWM_PULSECOUNT - .start = pwm_start_pulsecount, -#else .start = pwm_start, -#endif .stop = pwm_stop, .ioctl = pwm_ioctl, }; @@ -709,9 +662,6 @@ static struct stm32_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM1_TRGO) .trgo = STM32_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM1UP, #endif .base = STM32_TIM1_BASE, .pclk = TIMCLK_TIM1, @@ -808,9 +758,6 @@ static struct stm32_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM2_TRGO) .trgo = STM32_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM2, #endif .base = STM32_TIM2_BASE, .pclk = TIMCLK_TIM2, @@ -907,9 +854,6 @@ static struct stm32_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM3_TRGO) .trgo = STM32_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM3, #endif .base = STM32_TIM3_BASE, .pclk = TIMCLK_TIM3, @@ -1006,9 +950,6 @@ static struct stm32_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM4_TRGO) .trgo = STM32_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM4, #endif .base = STM32_TIM4_BASE, .pclk = TIMCLK_TIM4, @@ -1103,9 +1044,6 @@ static struct stm32_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM5_TRGO) .trgo = STM32_TIM5_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM5, #endif .base = STM32_TIM5_BASE, .pclk = TIMCLK_TIM5, @@ -1269,9 +1207,6 @@ static struct stm32_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM8_TRGO) .trgo = STM32_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM8UP, #endif .base = STM32_TIM8_BASE, .pclk = TIMCLK_TIM8, @@ -1336,9 +1271,6 @@ static struct stm32_pwmtimer_s g_pwm9dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM9 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM9, #endif .base = STM32_TIM9_BASE, .pclk = TIMCLK_TIM9, @@ -1387,9 +1319,6 @@ static struct stm32_pwmtimer_s g_pwm10dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM10 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM10, #endif .base = STM32_TIM10_BASE, .pclk = TIMCLK_TIM10, @@ -1438,9 +1367,6 @@ static struct stm32_pwmtimer_s g_pwm11dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM11 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM11, #endif .base = STM32_TIM11_BASE, .pclk = TIMCLK_TIM11, @@ -1505,9 +1431,6 @@ static struct stm32_pwmtimer_s g_pwm12dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM12 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM12, #endif .base = STM32_TIM12_BASE, .pclk = TIMCLK_TIM12, @@ -1556,9 +1479,6 @@ static struct stm32_pwmtimer_s g_pwm13dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM13 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM13, #endif .base = STM32_TIM13_BASE, .pclk = TIMCLK_TIM13, @@ -1607,9 +1527,6 @@ static struct stm32_pwmtimer_s g_pwm14dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM14 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM14, #endif .base = STM32_TIM14_BASE, .pclk = TIMCLK_TIM14, @@ -1692,9 +1609,6 @@ static struct stm32_pwmtimer_s g_pwm15dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM15_TRGO) .trgo = STM32_TIM15_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM15, #endif .base = STM32_TIM15_BASE, .pclk = TIMCLK_TIM15, @@ -1761,9 +1675,6 @@ static struct stm32_pwmtimer_s g_pwm16dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM16 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM16, #endif .base = STM32_TIM16_BASE, .pclk = TIMCLK_TIM16, @@ -1830,9 +1741,6 @@ static struct stm32_pwmtimer_s g_pwm17dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM17 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM17, #endif .base = STM32_TIM17_BASE, .pclk = TIMCLK_TIM17, @@ -3241,256 +3149,11 @@ static int pwm_break_dt_configure(struct stm32_pwmtimer_s *priv) } #endif -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_configure - * - * Description: - * Configure PWM timer in PULSECOUNT mode - * - ****************************************************************************/ - -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t outputs = 0; - uint8_t j = 0; - int ret = OK; - - /* NOTE: leave timer counter disabled and all outputs disabled! */ - - /* Disable the timer until we get it configured */ - - pwm_timer_enable(dev, false); - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* REVISIT: Disable outputs */ - - ret = pwm_outputs_enable(dev, outputs, false); - if (ret < 0) - { - goto errout; - } - - /* Initial timer configuration */ - - ret = pwm_timer_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure break and deadtime register */ - - ret = pwm_break_dt_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Disable software break (enable outputs) */ - - ret = pwm_soft_break(dev, false); - if (ret < 0) - { - goto errout; - } - -#ifdef HAVE_TRGO - /* Configure TRGO/TRGO2 */ - - ret = pwm_trgo_configure(dev, priv->trgo); - if (ret < 0) - { - goto errout; - } -#endif - - /* Configure timer channels */ - - for (j = 0; j < priv->chan_num; j++) - { - /* Skip channel if not in use */ - - if (priv->channels[j].channel != 0) - { - /* Update PWM mode */ - - pwm_mode_configure(dev, priv->channels[j].channel, - priv->channels[j].mode); - - /* PWM outputs configuration */ - - pwm_output_configure(priv, &priv->channels[j]); - } - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %" PRIx32 " duty: %08" PRIx32 - " count: %" PRIx32 "\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_rcr_update(dev, priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_rcr_update(dev, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#endif /* CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -3764,166 +3427,6 @@ static int pwm_timer(struct pwm_lowerhalf_s *dev, return ret; } -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the master output to stop the output as - * quickly as possible. - */ - - pwm_soft_break(dev, true); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output. - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %" PRIx32 "\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_STM32_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_STM32_TIM1_PWM */ - -#ifdef CONFIG_STM32_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_STM32_TIM8_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* REVISIT: RCR_REP_MAX for GTIM or ATIM ? */ - - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ - /**************************************************************************** * Name: pwm_set_apb_clock * @@ -4196,13 +3699,6 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) * counter, disabled outputs, not configured frequency and duty cycle */ -#ifdef CONFIG_PWM_PULSECOUNT - if (priv->timtype == TIMTYPE_ADVANCED) - { - ret = pwm_pulsecount_configure(dev); - } - else -#endif { ret = pwm_configure(dev); } @@ -4303,46 +3799,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - - /* Generate an indefinite number of pulses */ - - if (info->channels[0].count == 0) - { - return pwm_start(dev, info); - } - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) - * REVISIT: verify if TIMTYPE_COUNTUP16_N works with it - */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %" PRIx32 "\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#endif /* CONFIG_PWM_PULSECOUNT */ - static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -4516,10 +3972,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4563,10 +4015,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif From 8d017d7cc78a1e397b32d280246cc51e66b6505a Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:16:02 +0200 Subject: [PATCH 04/14] !arch/stm32f0l0g0: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32f0l0g0/CMakeLists.txt | 4 + arch/arm/src/stm32f0l0g0/Kconfig | 41 +- arch/arm/src/stm32f0l0g0/Make.defs | 6 + arch/arm/src/stm32f0l0g0/stm32_pulsecount.c | 1277 +++++++++++++++++++ arch/arm/src/stm32f0l0g0/stm32_pulsecount.h | 39 + arch/arm/src/stm32f0l0g0/stm32_pwm.c | 341 +---- 6 files changed, 1361 insertions(+), 347 deletions(-) create mode 100644 arch/arm/src/stm32f0l0g0/stm32_pulsecount.c create mode 100644 arch/arm/src/stm32f0l0g0/stm32_pulsecount.h diff --git a/arch/arm/src/stm32f0l0g0/CMakeLists.txt b/arch/arm/src/stm32f0l0g0/CMakeLists.txt index b2cf874c32e8f..a60ec1966a7ec 100644 --- a/arch/arm/src/stm32f0l0g0/CMakeLists.txt +++ b/arch/arm/src/stm32f0l0g0/CMakeLists.txt @@ -91,6 +91,10 @@ if(CONFIG_STM32F0L0G0_PWM) list(APPEND SRCS stm32_pwm.c) endif() +if(CONFIG_PULSECOUNT AND CONFIG_STM32F0L0G0_TIM1_PULSECOUNT) + list(APPEND SRCS stm32_pulsecount.c) +endif() + if(CONFIG_STM32F0L0G0_ADC) list(APPEND SRCS stm32_adc.c) endif() diff --git a/arch/arm/src/stm32f0l0g0/Kconfig b/arch/arm/src/stm32f0l0g0/Kconfig index ad53ee4d492c6..31cddad994e15 100644 --- a/arch/arm/src/stm32f0l0g0/Kconfig +++ b/arch/arm/src/stm32f0l0g0/Kconfig @@ -2167,7 +2167,6 @@ config STM32F0L0G0_CAN config STM32F0L0G0_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32F0L0G0_USART bool @@ -2191,17 +2190,30 @@ config STM32F0L0G0_1WIREDRIVER menu "Timer Configuration" +config STM32F0L0G0_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32F0L0G0_TIM1 + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + ---help--- + Reserve timer 1 for use by the pulse count driver. + + Timer devices may be used for different purposes. If STM32F0L0G0_TIM1 + is defined then this option may also be defined to indicate that TIM1 is + intended to generate a fixed number of output pulses. + config STM32F0L0G0_TIM1_PWM bool "TIM1 PWM" default n - depends on STM32F0L0G0_TIM1 + depends on STM32F0L0G0_TIM1 && !STM32F0L0G0_TIM1_PULSECOUNT select STM32F0L0G0_PWM ---help--- Reserve timer 1 for use by PWM Timer devices may be used for different purposes. One special purpose is to generate modulated outputs for such things as motor control. If - STM32F0L0G0_TIM1 is defined then THIS option may also be defined to + STM32F0L0G0_TIM1 is defined then this option may also be defined to indicate that the timer is intended to be used for pulsed output modulation. Valid channel modes: @@ -2243,7 +2255,7 @@ config STM32F0L0G0_TIM1_CH1MODE default 0 range 0 5 ---help--- - Specifies the channel mode. See STM32F0L0G0_TIM1_PWM description for available modes. + Specifies the channel mode. config STM32F0L0G0_TIM1_CH1OUT bool "TIM1 Channel 1 Output" @@ -2273,7 +2285,7 @@ config STM32F0L0G0_TIM1_CH2MODE default 0 range 0 5 ---help--- - Specifies the channel mode. See STM32F0L0G0_TIM1_PWM description for available modes. + Specifies the channel mode. config STM32F0L0G0_TIM1_CH2OUT bool "TIM1 Channel 2 Output" @@ -2303,7 +2315,7 @@ config STM32F0L0G0_TIM1_CH3MODE default 0 range 0 5 ---help--- - Specifies the channel mode. See STM32F0L0G0_TIM1_PWM description for available modes. + Specifies the channel mode. config STM32F0L0G0_TIM1_CH3OUT bool "TIM1 Channel 3 Output" @@ -2333,7 +2345,7 @@ config STM32F0L0G0_TIM1_CH4MODE default 0 range 0 5 ---help--- - Specifies the channel mode. See STM32F0L0G0_TIM1_PWM description for available modes. + Specifies the channel mode. config STM32F0L0G0_TIM1_CH4OUT bool "TIM1 Channel 4 Output" @@ -2352,7 +2364,7 @@ config STM32F0L0G0_TIM1_CHANNEL default 1 range 1 4 ---help--- - If TIM1 is enabled for PWM usage, you also need specifies the timer output + If TIM1 is enabled for output usage, you also need specifies the timer output channel {1,..,4} config STM32F0L0G0_TIM1_CHMODE @@ -2360,12 +2372,23 @@ config STM32F0L0G0_TIM1_CHMODE default 0 range 0 5 ---help--- - Specifies the channel mode. See STM32F0L0G0_TIM1_PWM description for available modes. + Specifies the channel mode. endif # !STM32F0L0G0_PWM_MULTICHAN endif # STM32F0L0G0_TIM1_PWM +if STM32F0L0G0_TIM1_PULSECOUNT + +config STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +endif # STM32F0L0G0_TIM1_PULSECOUNT + config STM32F0L0G0_TIM1_QE bool "TIM1 Quadrature Encoder" default n diff --git a/arch/arm/src/stm32f0l0g0/Make.defs b/arch/arm/src/stm32f0l0g0/Make.defs index df47027944caa..e4ab1438876cc 100644 --- a/arch/arm/src/stm32f0l0g0/Make.defs +++ b/arch/arm/src/stm32f0l0g0/Make.defs @@ -77,6 +77,12 @@ ifeq ($(CONFIG_STM32F0L0G0_PWM),y) CHIP_CSRCS += stm32_pwm.c endif +ifeq ($(CONFIG_PULSECOUNT),y) +ifeq ($(CONFIG_STM32F0L0G0_TIM1_PULSECOUNT),y) +CHIP_CSRCS += stm32_pulsecount.c +endif +endif + ifeq ($(CONFIG_STM32F0L0G0_ADC),y) CHIP_CSRCS += stm32_adc.c endif diff --git a/arch/arm/src/stm32f0l0g0/stm32_pulsecount.c b/arch/arm/src/stm32f0l0g0/stm32_pulsecount.c new file mode 100644 index 0000000000000..a60c576c4a59a --- /dev/null +++ b/arch/arm/src/stm32f0l0g0/stm32_pulsecount.c @@ -0,0 +1,1277 @@ +/**************************************************************************** + * arch/arm/src/stm32f0l0g0/stm32_pulsecount.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 + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32_gpio.h" +#include "stm32_pulsecount.h" +#include "stm32_rcc.h" +#include "stm32_tim.h" + +/* This module only supports pulse count on advanced timers. */ + +#ifdef CONFIG_STM32F0L0G0_TIM1_PULSECOUNT + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Pulse count is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 4 /* Advanced timers: TIM1 */ +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED + +#define HAVE_IP_TIMERS_V2 1 + +/* CCMR2 */ + +#define HAVE_CCMR2 1 + +#ifdef STM32_APB2_TIM1_CLKIN +# define PULSECOUNT_TIM1_CLKIN STM32_APB2_TIM1_CLKIN +#else +# define PULSECOUNT_TIM1_CLKIN STM32_APB1_TIM1_CLKIN +#endif + +#if CONFIG_STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL == 1 +# define PULSECOUNT_TIM1_CHCFG GPIO_TIM1_CH1OUT +#elif CONFIG_STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL == 2 +# define PULSECOUNT_TIM1_CHCFG GPIO_TIM1_CH2OUT +#elif CONFIG_STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL == 3 +# define PULSECOUNT_TIM1_CHCFG GPIO_TIM1_CH3OUT +#elif CONFIG_STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL == 4 +# define PULSECOUNT_TIM1_CHCFG GPIO_TIM1_CH4OUT +#else +# error Unsupported TIM1 pulse count channel +#endif + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) +# warning "pulsecount_dumpgpio not implemented" +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct stm32_pulsecountchan_s +{ + uint8_t channel; + uint32_t pincfg; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32_pulsecounttimer_s +{ + const struct pulsecount_ops_s *ops; + struct stm32_pulsecountchan_s channel; + uint8_t timid; + uint8_t timtype; + uint8_t irq; + uint32_t prev; + uint32_t curr; + uint32_t count; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t stm32pulsecount_getreg(struct stm32_pulsecounttimer_s *priv, + int offset); +static void stm32pulsecount_putreg(struct stm32_pulsecounttimer_s *priv, + int offset, uint32_t value); +static void stm32pulsecount_modifyreg(struct stm32_pulsecounttimer_s *priv, + uint32_t offset, uint32_t clearbits, + uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void stm32pulsecount_dumpregs(struct stm32_pulsecounttimer_s *priv, + const char *msg); +#else +# define stm32pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int +stm32pulsecount_output_configure(struct stm32_pulsecounttimer_s *priv, + uint8_t channel); +static int stm32pulsecount_timer(struct stm32_pulsecounttimer_s *priv, + const struct pulsecount_info_s *info); +static void stm32pulsecount_setapbclock( + struct stm32_pulsecounttimer_s *priv, bool on); +static int stm32pulsecount_interrupt(struct stm32_pulsecounttimer_s *priv); +static int stm32pulsecount_tim1interrupt(int irq, void *context, void *arg); +static uint32_t stm32pulsecount_pulsecount(uint32_t count); + +/* Pulsecount driver methods */ + +static int stm32pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int stm32pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int stm32pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); + +static int stm32pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int stm32pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This is the list of lower half pulsecount driver methods used by the upper + * half driver. + */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = stm32pulsecount_setup, + .shutdown = stm32pulsecount_shutdown, + .start = stm32pulsecount_start, + .stop = stm32pulsecount_stop, + .ioctl = stm32pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32F0L0G0_TIM1_PULSECOUNT +static struct stm32_pulsecounttimer_s g_pulsecount1dev = +{ + .ops = &g_pulsecountops, + .timid = 1, + .channel = + { + .channel = CONFIG_STM32F0L0G0_TIM1_PULSECOUNT_CHANNEL, + .pincfg = PULSECOUNT_TIM1_CHCFG, + }, + .timtype = TIMTYPE_TIM1, + .irq = STM32_IRQ_TIM1_BRK, + .base = STM32_TIM1_BASE, + .pclk = PULSECOUNT_TIM1_CLKIN, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32pulsecount_reg_is_32bit + * + * Description: + * Verify whether the timer register is 32bit or not. + * + * Input Parameters: + * timtype - The type of the timer. See the TIMTYPE_* definitions + * offset - The offset to the register to read + * + * Returned Value: + * Return true for 32 bits register; false otherwise. + * + ****************************************************************************/ + +static bool stm32pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == STM32_ATIM_CR2_OFFSET || + offset == STM32_ATIM_CCMR1_OFFSET || + offset == STM32_ATIM_CCMR2_OFFSET || + offset == STM32_ATIM_CCER_OFFSET || + offset == STM32_ATIM_BDTR_OFFSET || + offset == STM32_ATIM_DMAR_OFFSET || + offset == STM32_ATIM_AF1_OFFSET || + offset == STM32_ATIM_TISEL_OFFSET) + { + return true; + } + } + + return false; +} + +/**************************************************************************** + * Name: stm32pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount timer status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t stm32pulsecount_getreg( + struct stm32_pulsecounttimer_s *priv, int offset) +{ + uint32_t retval; + + if (stm32pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: stm32pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount timer status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32pulsecount_putreg(struct stm32_pulsecounttimer_s *priv, + int offset, uint32_t value) +{ + if (stm32pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: stm32pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount timer status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32pulsecount_modifyreg(struct stm32_pulsecounttimer_s *priv, + uint32_t offset, uint32_t clearbits, + uint32_t setbits) +{ + if (stm32pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, clearbits, setbits); + } +} + +/**************************************************************************** + * Name: stm32pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * priv - A reference to the pulsecount timer status + * msg - A message to be printed on the screen + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void stm32pulsecount_dumpregs(struct stm32_pulsecounttimer_s *priv, + const char *msg) +{ + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + stm32pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_SMCR_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_DIER_OFFSET)); + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + stm32pulsecount_getreg(priv, STM32_GTIM_SR_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_EGR_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET)); + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + stm32pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CNT_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_PSC_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET)); + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + stm32pulsecount_getreg(priv, STM32_GTIM_CCR1_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CCR2_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CCR3_OFFSET), + stm32pulsecount_getreg(priv, STM32_GTIM_CCR4_OFFSET)); + if (priv->timtype == TIMTYPE_ADVANCED) + { + _info(" RCR: %04x BDTR: %04x DCR: %04x DMAR: %04x\n", + stm32pulsecount_getreg(priv, STM32_ATIM_RCR_OFFSET), + stm32pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET), + stm32pulsecount_getreg(priv, STM32_ATIM_DCR_OFFSET), + stm32pulsecount_getreg(priv, STM32_ATIM_DMAR_OFFSET)); + + _info(" AF1: %04x TISEL: %04x\n", + stm32pulsecount_getreg(priv, STM32_ATIM_AF1_OFFSET), + stm32pulsecount_getreg(priv, STM32_ATIM_TISEL_OFFSET)); + } +} +#endif + +/**************************************************************************** + * Name: stm32pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + * Input Parameters: + * priv - A reference to the pulsecount timer status + * channel - Timer output channel + * + * Returned Value: + * Zero on success; + ****************************************************************************/ + +static int +stm32pulsecount_output_configure(struct stm32_pulsecounttimer_s *priv, + uint8_t channel) +{ + uint32_t cr2; + uint32_t ccer; + + /* Get current registers state */ + + cr2 = stm32pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET); + ccer = stm32pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* Reset the output polarity level of all channels (selects high + * polarity) + */ + + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + + /* Enable the output state of the selected channels */ + + ccer |= (GTIM_CCER_CC1E << ((channel - 1) * 4)); + + if (priv->timtype == TIMTYPE_ADVANCED) + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + + stm32pulsecount_modifyreg(priv, STM32_GTIM_CR2_OFFSET, 0, cr2); + stm32pulsecount_modifyreg(priv, STM32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * priv - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int stm32pulsecount_timer(struct stm32_pulsecounttimer_s *priv, + const struct pulsecount_info_s *info) +{ + /* Calculated values */ + + uint32_t prescaler; + uint32_t timclk; + uint32_t reload; + uint32_t ccr; + ub16_t duty; + uint32_t chanmode = GTIM_CCMR_MODE_PWM1; + uint8_t channel; + + /* Register contents */ + + uint32_t cr1; + uint32_t ccmr1; +#if defined(HAVE_CCMR2) + uint32_t ccmr2; + uint32_t ocmode2; +#endif + + /* New timer register bit settings */ + + uint32_t ocmode1; + + DEBUGASSERT(priv != NULL && info != NULL); + + ccmr1 = stm32pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET); + +#if defined(HAVE_CCMR2) + ccmr2 = stm32pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET); +#endif + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Disable all interrupts and DMA requests, clear all pending status */ + + stm32pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + stm32pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the + * one that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / pulsecount_frequency(info) + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / pulsecount_frequency(info); + + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32 " frequency: %" PRIu32 " " + "TIMCLK: %" PRIu32 " prescaler: %" PRIu32 + " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, pulsecount_frequency(info), timclk, + prescaler, reload); + + /* Set up the timer CR1 register: + * + * 1,8 CKD[1:0] ARPE CMS[1:0] DIR OPM URS UDIS CEN + * 2-5 CKD[1:0] ARPE CMS DIR OPM URS UDIS CEN + * 6-7 ARPE OPM URS UDIS CEN + * 9-14 CKD[1:0] ARPE URS UDIS CEN + * 15-17 CKD[1:0] ARPE OPM URS UDIS CEN + */ + + cr1 = stm32pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + + /* Disable the timer until we get it configured */ + + cr1 &= ~GTIM_CR1_CEN; + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + + cr1 |= GTIM_CR1_EDGE; + + /* Set the clock division to zero for all (but the basic timers, but there + * should be no basic timers in this context + */ + + cr1 &= ~GTIM_CR1_CKD_MASK; + stm32pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + /* Set the reload and prescaler values */ + + stm32pulsecount_putreg(priv, STM32_GTIM_ARR_OFFSET, reload); + stm32pulsecount_putreg(priv, STM32_GTIM_PSC_OFFSET, (prescaler - 1)); + + /* Set the advanced timer's repetition counter */ + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1. stm32pulsecount_start() has + * already assured us that the count value is within range. + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = stm32pulsecount_pulsecount(info->count); + stm32pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, + priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + stm32pulsecount_putreg(priv, STM32_ATIM_EGR_OFFSET, ATIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = stm32pulsecount_pulsecount(info->count + - priv->prev); + stm32pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, + priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + stm32pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + stm32pulsecount_putreg(priv, STM32_ATIM_EGR_OFFSET, ATIM_EGR_UG); + } + } + + /* Handle channel specific setup */ + + ocmode1 = 0; +#if defined(HAVE_CCMR2) + ocmode2 = 0; +#endif + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + switch (channel) + { + case 1: + ocmode1 |= (GTIM_CCMR_CCS_CCOUT << GTIM_CCMR1_CC1S_SHIFT) | + (chanmode << GTIM_CCMR1_OC1M_SHIFT) | + GTIM_CCMR1_OC1PE; + stm32pulsecount_putreg(priv, STM32_GTIM_CCR1_OFFSET, ccr); + ccmr1 &= ~(GTIM_CCMR1_CC1S_MASK | GTIM_CCMR1_OC1M_MASK | + GTIM_CCMR1_OC1PE | GTIM_CCMR1_OC1M); + break; + + case 2: + ocmode1 |= (GTIM_CCMR_CCS_CCOUT << GTIM_CCMR1_CC2S_SHIFT) | + (chanmode << GTIM_CCMR1_OC2M_SHIFT) | + GTIM_CCMR1_OC2PE; + stm32pulsecount_putreg(priv, STM32_GTIM_CCR2_OFFSET, ccr); + ccmr1 &= ~(GTIM_CCMR1_CC2S_MASK | GTIM_CCMR1_OC2M_MASK | + GTIM_CCMR1_OC2PE | GTIM_CCMR1_OC2M); + break; + + case 3: + ocmode2 |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC3S_SHIFT) | + (chanmode << ATIM_CCMR2_OC3M_SHIFT) | + ATIM_CCMR2_OC3PE; + stm32pulsecount_putreg(priv, STM32_ATIM_CCR3_OFFSET, ccr); + ccmr2 &= ~(ATIM_CCMR2_CC3S_MASK | ATIM_CCMR2_OC3M_MASK | + ATIM_CCMR2_OC3PE | ATIM_CCMR2_OC3M); + break; + + case 4: + ocmode2 |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR2_CC4S_SHIFT) | + (chanmode << ATIM_CCMR2_OC4M_SHIFT) | + ATIM_CCMR2_OC4PE; + stm32pulsecount_putreg(priv, STM32_ATIM_CCR4_OFFSET, ccr); + ccmr2 &= ~(ATIM_CCMR2_CC4S_MASK | ATIM_CCMR2_OC4M_MASK | + ATIM_CCMR2_OC4PE | ATIM_CCMR2_OC4M); + break; + + default: + _err("ERROR: No such channel: %u\n", channel); + return -EINVAL; + } + + stm32pulsecount_output_configure(priv, channel); + + ccmr1 |= ocmode1; +#if defined(HAVE_CCMR2) + ccmr2 |= ocmode2; +#endif + + if (priv->timtype == TIMTYPE_ADVANCED) + { + uint32_t bdtr; + + /* Get current register state */ + + bdtr = stm32pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET); + + bdtr &= ~(ATIM_BDTR_OSSI | ATIM_BDTR_OSSR); + bdtr |= ATIM_BDTR_MOE; + + stm32pulsecount_putreg(priv, STM32_ATIM_BDTR_OFFSET, bdtr); + } + + /* Save the modified register values */ + + putreg32(ccmr1, priv->base + STM32_GTIM_CCMR1_OFFSET); +#if defined(HAVE_CCMR2) + putreg32(ccmr2, priv->base + STM32_ATIM_CCMR2_OFFSET); +#endif + + /* Set the ARR Preload Bit */ + + cr1 = stm32pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + cr1 |= GTIM_CR1_ARPE; + stm32pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that stm32pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + stm32pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + stm32pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + cr1 |= GTIM_CR1_CEN; + stm32pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + else + { + /* Just enable the timer, leaving all interrupts disabled */ + + cr1 |= GTIM_CR1_CEN; + stm32pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + } + + stm32pulsecount_dumpregs(priv, "After starting"); + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * priv - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + ****************************************************************************/ + +static int stm32pulsecount_interrupt(struct stm32_pulsecounttimer_s *priv) +{ + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = stm32pulsecount_getreg(priv, STM32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + stm32pulsecount_putreg(priv, STM32_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the mast output to stop the output as + * quickly as possible. + */ + + regval = stm32pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET); + regval &= ~ATIM_BDTR_MOE; + stm32pulsecount_putreg(priv, STM32_ATIM_BDTR_OFFSET, regval); + + /* Disable first interrupts, stop and reset the timer */ + + stm32pulsecount_stop((struct pulsecount_lowerhalf_s *)priv); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = stm32pulsecount_pulsecount(priv->count - priv->prev); + stm32pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output + */ + + _info("Update interrupt SR: %04x prev: %" PRIu32 " curr: %" PRIu32 + " count: %" PRIu32 "\n", + regval, priv->prev, priv->curr, priv->count); + + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_tim1interrupt + * + * Description: + * Handle timer 1 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int stm32pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return stm32pulsecount_interrupt(&g_pulsecount1dev); +} + +/**************************************************************************** + * Name: stm32pulsecount_pulsecount + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint32_t stm32pulsecount_pulsecount(uint32_t count) +{ + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (ATIM_RCR_REP_MAX + 1) >> 1; + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: stm32pulsecount_setapbclock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32pulsecount_setapbclock( + struct stm32_pulsecounttimer_s *priv, bool on) +{ + uint32_t en_bit; + uint32_t regaddr; + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32F0L0G0_TIM1_PULSECOUNT + case 1: + regaddr = STM32_RCC_APB2ENR; + en_bit = RCC_APB2ENR_TIM1EN; + break; +#endif + default: + return; + } + + /* Enable/disable APB 1/2 clock for timer */ + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } +} + +/**************************************************************************** + * Name: stm32pulsecount_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int stm32pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecounttimer_s *priv = + (struct stm32_pulsecounttimer_s *)dev; + uint32_t pincfg; + + _info("TIM%u\n", priv->timid); + stm32pulsecount_dumpregs(priv, "Initially"); + + /* Enable APB1/2 clocking for timer. */ + + stm32pulsecount_setapbclock(priv, true); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + pincfg = priv->channel.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32_configgpio(pincfg); + } + + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int stm32pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecounttimer_s *priv = + (struct stm32_pulsecounttimer_s *)dev; + uint32_t pincfg; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + stm32pulsecount_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + stm32pulsecount_setapbclock(priv, false); + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= GPIO_INPUT | GPIO_FLOAT; + + stm32_configgpio(pincfg); + } + + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_start + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int stm32pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32_pulsecounttimer_s *priv = + (struct stm32_pulsecounttimer_s *)dev; + + /* Check if a pulsecount has been selected */ + + if (info->count > 0) + { + /* Only the advanced timers (TIM1,8 can support the pulse counting) */ + + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + /* Save the handle */ + + priv->handle = handle; + + /* Start the time */ + + return stm32pulsecount_timer(priv, info); +} + +/**************************************************************************** + * Name: stm32pulsecount_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int stm32pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecounttimer_s *priv = + (struct stm32_pulsecounttimer_s *)dev; + uint32_t resetbit; + uint32_t regaddr; + uint32_t regval; + irqstate_t flags; + + _info("TIM%u\n", priv->timid); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Disable further interrupts and stop the timer */ + + stm32pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + stm32pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Determine which timer to reset */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32F0L0G0_TIM1_PULSECOUNT + case 1: + regaddr = STM32_RCC_APB2RSTR; + resetbit = RCC_APB2RSTR_TIM1RST; + break; +#endif + + default: + leave_critical_section(flags); + return -EINVAL; + } + + /* Reset the timer - stopping the output and putting the timer back + * into a state where stm32pulsecount_start() can be called. + */ + + regval = getreg32(regaddr); + regval |= resetbit; + putreg32(regval, regaddr); + + regval &= ~resetbit; + putreg32(regval, regaddr); + leave_critical_section(flags); + + _info("regaddr: %08" PRIx32 " resetbit: %08" PRIx32 "\n", + regaddr, resetbit); + stm32pulsecount_dumpregs(priv, "After stop"); + return OK; +} + +/**************************************************************************** + * Name: stm32pulsecount_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int stm32pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32_pulsecounttimer_s *priv = + (struct stm32_pulsecounttimer_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_pulsecountinitialize + * + * Description: + * Initialize one timer for use with the upper-level pulsecount driver. + * + * Input Parameters: + * timer - A number identifying the timer use. The number of valid timer + * IDs varies with the STM32 MCU and MCU family. This pulsecount driver + * supports TIM1 only on STM32F0/L0/G0. + * + * Returned Value: + * On success, a pointer to the STM32 lower half pulsecount driver is + * returned. NULL is returned on any failure. + * + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer) +{ + struct stm32_pulsecounttimer_s *lower; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32F0L0G0_TIM1_PULSECOUNT + case 1: + lower = &g_pulsecount1dev; + + /* Attach but disable the TIM1 update interrupt */ + + irq_attach(lower->irq, stm32pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->irq); + break; +#endif + + default: + _err("ERROR: No such timer configured\n"); + return NULL; + } + + return (struct pulsecount_lowerhalf_s *)lower; +} + +#endif /* CONFIG_STM32F0L0G0_TIMx_PULSECOUNT */ diff --git a/arch/arm/src/stm32f0l0g0/stm32_pulsecount.h b/arch/arm/src/stm32f0l0g0/stm32_pulsecount.h new file mode 100644 index 0000000000000..8c8aca535f1a7 --- /dev/null +++ b/arch/arm/src/stm32f0l0g0/stm32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32f0l0g0/stm32_pulsecount.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_STM32F0L0G0_STM32_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32F0L0G0_STM32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32F0L0G0_STM32_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32f0l0g0/stm32_pwm.c b/arch/arm/src/stm32f0l0g0/stm32_pwm.c index 2aff3c8e1f92e..df066d05163a3 100644 --- a/arch/arm/src/stm32f0l0g0/stm32_pwm.c +++ b/arch/arm/src/stm32f0l0g0/stm32_pwm.c @@ -172,20 +172,10 @@ struct stm32_pwmtimer_s uint8_t timid; /* Timer ID {1,...,17} */ uint8_t timtype; /* See the TIMTYPE_* definitions */ enum stm32_timmode_e mode; -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ -#else uint32_t frequency; /* Current frequency setting */ -#endif uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral clock * that drives the timer module. */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -218,31 +208,13 @@ static int stm32pwm_update_duty(struct stm32_pwmtimer_s *priv, uint8_t channel, ub16_t duty); static void stm32pwm_setapbclock(struct stm32_pwmtimer_s *priv, bool on); -#if defined(CONFIG_PWM_PULSECOUNT) && \ - (defined(CONFIG_STM32F0L0G0_TIM1_PWM) || defined(CONFIG_STM32F0L0G0_TIM8_PWM)) -static int stm32pwm_interrupt(struct stm32_pwmtimer_s *priv); -# if defined(CONFIG_STM32F0L0G0_TIM1_PWM) -static int stm32pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# if defined(CONFIG_STM32F0L0G0_TIM8_PWM) -static int stm32pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t stm32pwm_pulsecount(uint32_t count); -#endif /* CONFIG_PWM_PULSECOUNT && CONFIG_STM32F0L0G0_TIM{1,8}_PWM */ - /* PWM driver methods */ static int stm32pwm_setup(struct pwm_lowerhalf_s *dev); static int stm32pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int stm32pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int stm32pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int stm32pwm_stop(struct pwm_lowerhalf_s *dev); static int stm32pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -307,9 +279,6 @@ static struct stm32_pwmtimer_s g_pwm1dev = }, .timtype = TIMTYPE_TIM1, .mode = CONFIG_STM32F0L0G0_TIM1_MODE, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM1UP, -#endif .base = STM32_TIM1_BASE, .pclk = STM32_APB2_TIM1_CLKIN, }; @@ -357,9 +326,6 @@ static struct stm32_pwmtimer_s g_pwm2dev = }, .timtype = TIMTYPE_TIM2, .mode = CONFIG_STM32F0L0G0_TIM2_MODE, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM2, -#endif .base = STM32_TIM2_BASE, .pclk = STM32_APB1_TIM2_CLKIN, }; @@ -407,9 +373,6 @@ static struct stm32_pwmtimer_s g_pwm3dev = }, .timtype = TIMTYPE_TIM3, .mode = CONFIG_STM32F0L0G0_TIM3_MODE, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM3, -#endif .base = STM32_TIM3_BASE, .pclk = STM32_APB1_TIM3_CLKIN, }; @@ -433,9 +396,6 @@ static struct stm32_pwmtimer_s g_pwm14dev = }, .timtype = TIMTYPE_TIM14, .mode = STM32_TIMMODE_COUNTUP, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM14, -#endif .base = STM32_TIM14_BASE, .pclk = STM32_APB2_TIM14_CLKIN, }; @@ -467,9 +427,6 @@ static struct stm32_pwmtimer_s g_pwm15dev = }, .timtype = TIMTYPE_TIM15, .mode = STM32_TIMMODE_COUNTUP, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM15, -#endif .base = STM32_TIM15_BASE, .pclk = STM32_APB2_TIM15_CLKIN, }; @@ -493,9 +450,6 @@ static struct stm32_pwmtimer_s g_pwm16dev = }, .timtype = TIMTYPE_TIM16, .mode = STM32_TIMMODE_COUNTUP, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM16, -#endif .base = STM32_TIM16_BASE, .pclk = STM32_APB2_TIM16_CLKIN, }; @@ -519,9 +473,6 @@ static struct stm32_pwmtimer_s g_pwm17dev = }, .timtype = TIMTYPE_TIM17, .mode = STM32_TIMMODE_COUNTUP, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM17, -#endif .base = STM32_TIM17_BASE, .pclk = STM32_APB2_TIM17_CLKIN, }; @@ -882,25 +833,13 @@ static int stm32pwm_timer(struct stm32_pwmtimer_s *priv, ccmr2 = stm32pwm_getreg(priv, STM32_GTIM_CCMR2_OFFSET); #endif -#ifdef CONFIG_PWM_PULSECOUNT - pwminfo("TIM%u channel: %u frequency: %" PRIu32 " duty: %08" PRIx32 - " count: %u\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); -#else pwminfo("TIM%u frequency: %" PRIu32 "\n", priv->timid, info->frequency); -#endif DEBUGASSERT(info->frequency > 0); /* Disable all interrupts and DMA requests, clear all pending status */ -#ifdef CONFIG_PWM_PULSECOUNT - stm32pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); - stm32pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); -#endif - /* Calculate optimal values for the timer prescaler and for the timer * reload register. If 'frequency' is the desired frequency, then * @@ -1070,41 +1009,6 @@ static int stm32pwm_timer(struct stm32_pwmtimer_s *priv, * assured us that the count value is within range). */ -#ifdef CONFIG_PWM_PULSECOUNT - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = stm32pwm_pulsecount(info->channels[0].count); - stm32pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - stm32pwm_putreg(priv, STM32_ATIM_EGR_OFFSET, ATIM_EGR_UG); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = stm32pwm_pulsecount(info->channels[0].count - - priv->prev); - stm32pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else -#endif { /* Set the repetition counter to zero */ @@ -1381,43 +1285,15 @@ static int stm32pwm_timer(struct stm32_pwmtimer_s *priv, cr1 |= GTIM_CR1_ARPE; stm32pwm_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that stm32pwm_start() has already verified: (1) that this - * is an advanced timer, and that (2) the repetition count is within - * range. - */ - -#ifdef CONFIG_PWM_PULSECOUNT - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - stm32pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - stm32pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - cr1 |= GTIM_CR1_CEN; - stm32pwm_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - else -#endif - { - /* Just enable the timer, leaving all interrupts disabled */ + /* Just enable the timer, leaving all interrupts disabled */ - cr1 |= GTIM_CR1_CEN; - stm32pwm_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); - } + cr1 |= GTIM_CR1_CEN; + stm32pwm_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); stm32pwm_dumpregs(priv, "After starting"); return OK; } -#ifndef CONFIG_PWM_PULSECOUNT /**************************************************************************** * Name: stm32pwm_update_duty * @@ -1493,174 +1369,6 @@ static int stm32pwm_update_duty(struct stm32_pwmtimer_s *priv, return OK; } -#endif - -/**************************************************************************** - * Name: stm32pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * priv - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * channel - Timer output channel - * mode - PWM mode. See stm32_chanmode_e - * - * Returned Values: - * Zero on success; a negated errno value on failure - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && (defined(CONFIG_STM32F0L0G0_TIM1_PWM) || defined(CONFIG_STM32F0L0G0_TIM8_PWM)) -static int stm32pwm_interrupt(struct stm32_pwmtimer_s *priv) -{ - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = stm32pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - stm32pwm_putreg(priv, STM32_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the mast output to stop the output as - * quickly as possible. - */ - - regval = stm32pwm_getreg(priv, STM32_ATIM_BDTR_OFFSET); - regval &= ~ATIM_BDTR_MOE; - stm32pwm_putreg(priv, STM32_ATIM_BDTR_OFFSET, regval); - - /* Disable first interrupts, stop and reset the timer */ - - stm32pwm_stop((struct pwm_lowerhalf_s *)priv); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = stm32pwm_pulsecount(priv->count - priv->prev); - stm32pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %u\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} -#endif - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_STM32F0L0G0_TIM1_PWM) -static int stm32pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return stm32pwm_interrupt(&g_pwm1dev); -} -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_STM32F0L0G0_TIM8_PWM) -static int stm32pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return stm32pwm_interrupt(&g_pwm8dev); -} -#endif - -/**************************************************************************** - * Name: stm32pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && (defined(CONFIG_STM32F0L0G0_TIM1_PWM) || defined(CONFIG_STM32F0L0G0_TIM8_PWM)) -static uint8_t stm32pwm_pulsecount(uint32_t count) -{ - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (ATIM_RCR_REP_MAX + 1) >> 1; - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return ATIM_RCR_REP_MAX; - } -} -#endif /**************************************************************************** * Name: stm32pwm_setapbclock @@ -1882,43 +1590,12 @@ static int stm32pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int stm32pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %u\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return stm32pwm_timer(priv, info); -} -#else static int stm32pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { int ret = OK; struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; -#ifndef CONFIG_PWM_PULSECOUNT /* if frequency has not changed we just update duty */ if (info->frequency == priv->frequency) @@ -1944,23 +1621,19 @@ static int stm32pwm_start(struct pwm_lowerhalf_s *dev, } } else -#endif { ret = stm32pwm_timer(priv, info); -#ifndef CONFIG_PWM_PULSECOUNT /* Save current frequency */ if (ret == OK) { priv->frequency = info->frequency; } -#endif } return ret; } -#endif /**************************************************************************** * Name: stm32pwm_stop @@ -2167,10 +1840,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, stm32pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; #endif @@ -2204,10 +1873,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, stm32pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; #endif From 243749afb6cf4fec3d671f3cc5caa6eab705afb8 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:16:09 +0200 Subject: [PATCH 05/14] !arch/stm32f7: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32f7/CMakeLists.txt | 4 + arch/arm/src/stm32f7/Kconfig | 74 +- arch/arm/src/stm32f7/Make.defs | 4 + arch/arm/src/stm32f7/stm32_pulsecount.c | 1748 +++++++++++++++++++++++ arch/arm/src/stm32f7/stm32_pulsecount.h | 39 + arch/arm/src/stm32f7/stm32_pwm.c | 543 +------ 6 files changed, 1869 insertions(+), 543 deletions(-) create mode 100644 arch/arm/src/stm32f7/stm32_pulsecount.c create mode 100644 arch/arm/src/stm32f7/stm32_pulsecount.h diff --git a/arch/arm/src/stm32f7/CMakeLists.txt b/arch/arm/src/stm32f7/CMakeLists.txt index 6d6682d90facf..89cca465803d9 100644 --- a/arch/arm/src/stm32f7/CMakeLists.txt +++ b/arch/arm/src/stm32f7/CMakeLists.txt @@ -182,6 +182,10 @@ if(CONFIG_STM32F7_PWM) list(APPEND SRCS stm32_pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS stm32_pulsecount.c) +endif() + if(CONFIG_STM32F7_FOC) list(APPEND SRCS stm32_foc.c) endif() diff --git a/arch/arm/src/stm32f7/Kconfig b/arch/arm/src/stm32f7/Kconfig index 67581eecad46a..ec6c5361ede17 100644 --- a/arch/arm/src/stm32f7/Kconfig +++ b/arch/arm/src/stm32f7/Kconfig @@ -1273,7 +1273,6 @@ config STM32F7_TIM config STM32F7_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32F7_USART bool @@ -4669,6 +4668,79 @@ config STM32F7_PWM_TRGO ---help--- Enable TRGO support for PWM driver +config STM32F7_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config STM32F7_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32F7_TIM1 + select STM32F7_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if STM32F7_TIM1_PULSECOUNT + +config STM32F7_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config STM32F7_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32F7_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config STM32F7_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # STM32F7_TIM1_PULSECOUNT + +config STM32F7_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on STM32F7_TIM8 + select STM32F7_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if STM32F7_TIM8_PULSECOUNT + +config STM32F7_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config STM32F7_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32F7_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config STM32F7_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # STM32F7_TIM8_PULSECOUNT config STM32F7_TIM1_ADC bool "TIM1 ADC" default n diff --git a/arch/arm/src/stm32f7/Make.defs b/arch/arm/src/stm32f7/Make.defs index 247a19afc0b38..04f0514666b05 100644 --- a/arch/arm/src/stm32f7/Make.defs +++ b/arch/arm/src/stm32f7/Make.defs @@ -184,6 +184,10 @@ ifeq ($(CONFIG_STM32F7_PWM),y) CHIP_CSRCS += stm32_pwm.c endif +ifeq ($(CONFIG_STM32F7_PULSECOUNT),y) +CHIP_CSRCS += stm32_pulsecount.c +endif + ifeq ($(CONFIG_STM32F7_FOC),y) CHIP_CSRCS += stm32_foc.c endif diff --git a/arch/arm/src/stm32f7/stm32_pulsecount.c b/arch/arm/src/stm32f7/stm32_pulsecount.c new file mode 100644 index 0000000000000..ed0a19ae07992 --- /dev/null +++ b/arch/arm/src/stm32f7/stm32_pulsecount.c @@ -0,0 +1,1748 @@ +/**************************************************************************** + * arch/arm/src/stm32f7/stm32_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32_pulsecount.h" +#include "stm32_rcc.h" +#include "stm32_gpio.h" +#include "stm32_tim.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED + +/* Advanced timer clock source, RCC EN offset, enable bit, + * RCC RST offset, reset bit to use + */ + +#define TIMCLK_TIM1 STM32_APB2_TIM1_CLKIN +#define TIMRCCEN_TIM1 STM32_RCC_APB2ENR +#define TIMEN_TIM1 RCC_APB2ENR_TIM1EN +#define TIMRCCRST_TIM1 STM32_RCC_APB2RSTR +#define TIMRST_TIM1 RCC_APB2RSTR_TIM1RST +#define TIMCLK_TIM8 STM32_APB2_TIM8_CLKIN +#define TIMRCCEN_TIM8 STM32_RCC_APB2ENR +#define TIMEN_TIM8 RCC_APB2ENR_TIM8EN +#define TIMRCCRST_TIM8 STM32_RCC_APB2RSTR +#define TIMRST_TIM8 RCC_APB2RSTR_TIM8RST + +/* Default GPIO pins state */ + +#define PINCFG_DEFAULT (GPIO_INPUT | GPIO_FLOAT) + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) stm32_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct stm32_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct stm32_chan_s +{ + uint8_t channel; + struct stm32_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32_tim_s +{ + struct stm32_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct stm32_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct stm32_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset); +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value); +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct stm32_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_STM32F7_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_STM32F7_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32F7_TIM1_PULSECOUNT + +static struct stm32_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_STM32F7_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_STM32F7_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_STM32F7_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_STM32F7_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_STM32F7_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_STM32F7_TIM1_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM1UP, + .base = STM32_TIM1_BASE, + .pclk = TIMCLK_TIM1, +}; + +#endif /* CONFIG_STM32F7_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32F7_TIM8_PULSECOUNT + +static struct stm32_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_STM32F7_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_STM32F7_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_STM32F7_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_STM32F7_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_STM32F7_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32F7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32F7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_STM32F7_TIM8_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM8UP, + .base = STM32_TIM8_BASE, + .pclk = TIMCLK_TIM8, +}; + +#endif /* CONFIG_STM32F7_TIM8_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32F7_TIM1_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_STM32F7_TIM8_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_reg_is_32bit + ****************************************************************************/ + +static bool pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + bool ret = false; + + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == STM32_ATIM_CR2_OFFSET || + offset == STM32_ATIM_CCMR1_OFFSET || + offset == STM32_ATIM_CCMR2_OFFSET || + offset == STM32_ATIM_CCER_OFFSET || + offset == STM32_ATIM_BDTR_OFFSET) + { + ret = true; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset) +{ + uint32_t retval = 0; + + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); + } +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DIER_OFFSET)); + + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_SR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET)); + + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET)); + + if (priv->timid == 1 || priv->timid == 8) + { + _info(" RCR: %04x BDTR: %04x\n", + pulsecount_getreg(priv, STM32_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET)); + } + + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR4_OFFSET)); + + _info(" DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_DCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t offset = 0; + + /* CCR channel indices are one-based to match timer channel numbers. */ + + switch (index) + { + case 1: + { + offset = STM32_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = STM32_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = STM32_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = STM32_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32" frequency: %" PRIu32 + " TIMCLK: %" PRIu32 " " + "prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, STM32_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, STM32_GTIM_PSC_OFFSET, (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct stm32_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* Get CCMR offset */ + + switch (channel) + { + case 1: + case 2: + { + offset = STM32_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = STM32_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE | ATIM_CCMR1_OC1M); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + ocmode |= ATIM_CCMR1_OC1PE; + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE | ATIM_CCMR1_OC2M); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + ocmode |= ATIM_CCMR1_OC2PE; + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + uint8_t channel = 0; + + /* Get channel */ + + channel = chan->channel; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (chan->out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (chan->out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + } + + /* Write registers */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, STM32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum stm32_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct stm32_tim_s *priv, uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer in PULSECOUNT mode + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channels */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, &priv->channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, STM32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the master output to stop the output as + * quickly as possible. + */ + + pulsecount_moe_enable(dev, false); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output. + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_STM32F7_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_STM32F7_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32F7_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_STM32F7_TIM8_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* Use the advanced-timer repetition counter limit. */ + + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_set_apb_clock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_set_apb_clock(struct stm32_tim_s *priv, bool on) +{ + uint32_t en_bit = 0; + uint32_t regaddr = 0; + int ret = OK; + + _info("timer %d clock enable: %d\n", priv->timid, on ? 1 : 0); + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32F7_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCEN_TIM1; + en_bit = TIMEN_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32F7_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCEN_TIM8; + en_bit = TIMEN_TIM8; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + _info("RCC_APBxENR base: %08" PRIx32 " bits: %04" PRIx32 "\n", + regaddr, en_bit); + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, true); + if (ret < 0) + { + goto errout; + } + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + /* Do not configure the pin if pincfg is not specified. + * This prevents overwriting the PA0 configuration if the + * channel is used internally. + */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= PINCFG_DEFAULT; + + stm32_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + irqstate_t flags = 0; + uint16_t outputs = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Stopped so frequency is zero */ + + priv->frequency = 0; + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Disable the timer and timer outputs */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + outputs = pulsecount_outputs_from_channels(priv, 0); + ret = pulsecount_outputs_enable(dev, outputs, false); + + /* Clear all channels */ + + pulsecount_putreg(priv, STM32_GTIM_CCR1_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR2_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR3_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR4_OFFSET, 0); + + leave_critical_section(flags); + + pulsecount_dumpregs(dev, "After stop"); + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + struct stm32_tim_s *priv = pulse->timer; + + /* Check if a pulsecount has been selected */ + + if (info->count > 0) + { + /* Only the advanced timers (TIM1,8 can support the pulse counting) */ + + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + /* Save the handle */ + + priv->handle = handle; + + /* Start the time */ + + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer) +{ + struct stm32_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32F7_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_STM32F7_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} diff --git a/arch/arm/src/stm32f7/stm32_pulsecount.h b/arch/arm/src/stm32f7/stm32_pulsecount.h new file mode 100644 index 0000000000000..7261a99c491e4 --- /dev/null +++ b/arch/arm/src/stm32f7/stm32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32f7/stm32_pulsecount.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_STM32F7_STM32_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32F7_STM32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32F7_STM32_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32f7/stm32_pwm.c b/arch/arm/src/stm32f7/stm32_pwm.c index 41725c7dfec29..66f1f90ca68bf 100644 --- a/arch/arm/src/stm32f7/stm32_pwm.c +++ b/arch/arm/src/stm32f7/stm32_pwm.c @@ -155,17 +155,6 @@ # undef HAVE_ADVTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_STM32F7_TIM1_PWM) || defined(CONFIG_STM32F7_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* TRGO/TRGO2 support */ #ifdef CONFIG_STM32F7_PWM_TRGO @@ -254,21 +243,12 @@ struct stm32_pwmtimer_s uint8_t trgo; /* TRGO configuration: * 4 LSB = TRGO, 4 MSB = TRGO2 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ #endif uint32_t frequency; /* Current frequency setting */ uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral * clock that drives the timer module */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -329,38 +309,15 @@ static uint16_t pwm_rcr_get(struct pwm_lowerhalf_s *dev); static int pwm_rcr_update(struct pwm_lowerhalf_s *dev, uint16_t rcr); #endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev); -#else static int pwm_configure(struct pwm_lowerhalf_s *dev); -#endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#endif static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_STM32F7_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_STM32F7_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#endif static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); @@ -380,11 +337,7 @@ static const struct pwm_ops_s g_pwmops = { .setup = pwm_setup, .shutdown = pwm_shutdown, -#ifdef CONFIG_PWM_PULSECOUNT - .start = pwm_start_pulsecount, -#else .start = pwm_start, -#endif .stop = pwm_stop, .ioctl = pwm_ioctl, }; @@ -576,9 +529,6 @@ static struct stm32_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM1_TRGO) .trgo = STM32_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM1UP, #endif .base = STM32_TIM1_BASE, .pclk = TIMCLK_TIM1, @@ -675,9 +625,6 @@ static struct stm32_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM2_TRGO) .trgo = STM32_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM2, #endif .base = STM32_TIM2_BASE, .pclk = TIMCLK_TIM2, @@ -774,9 +721,6 @@ static struct stm32_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM3_TRGO) .trgo = STM32_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM3, #endif .base = STM32_TIM3_BASE, .pclk = TIMCLK_TIM3, @@ -873,9 +817,6 @@ static struct stm32_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM4_TRGO) .trgo = STM32_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM4, #endif .base = STM32_TIM4_BASE, .pclk = TIMCLK_TIM4, @@ -970,9 +911,6 @@ static struct stm32_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM5_TRGO) .trgo = STM32_TIM5_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM5, #endif .base = STM32_TIM5_BASE, .pclk = TIMCLK_TIM5, @@ -1136,9 +1074,6 @@ static struct stm32_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM8_TRGO) .trgo = STM32_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM8UP, #endif .base = STM32_TIM8_BASE, .pclk = TIMCLK_TIM8, @@ -1203,9 +1138,6 @@ static struct stm32_pwmtimer_s g_pwm9dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM9 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM9, #endif .base = STM32_TIM9_BASE, .pclk = TIMCLK_TIM9, @@ -1254,9 +1186,6 @@ static struct stm32_pwmtimer_s g_pwm10dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM10 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM10, #endif .base = STM32_TIM10_BASE, .pclk = TIMCLK_TIM10, @@ -1305,9 +1234,6 @@ static struct stm32_pwmtimer_s g_pwm11dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM11 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM11, #endif .base = STM32_TIM11_BASE, .pclk = TIMCLK_TIM11, @@ -1372,9 +1298,6 @@ static struct stm32_pwmtimer_s g_pwm12dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM12 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM12, #endif .base = STM32_TIM12_BASE, .pclk = TIMCLK_TIM12, @@ -1423,9 +1346,6 @@ static struct stm32_pwmtimer_s g_pwm13dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM13 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM13, #endif .base = STM32_TIM13_BASE, .pclk = TIMCLK_TIM13, @@ -1474,9 +1394,6 @@ static struct stm32_pwmtimer_s g_pwm14dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM14 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM14, #endif .base = STM32_TIM14_BASE, .pclk = TIMCLK_TIM14, @@ -2817,256 +2734,11 @@ static int pwm_break_dt_configure(struct stm32_pwmtimer_s *priv) } #endif -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_configure - * - * Description: - * Configure PWM timer in PULSECOUNT mode - * - ****************************************************************************/ - -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t outputs = 0; - uint8_t j = 0; - int ret = OK; - - /* NOTE: leave timer counter disabled and all outputs disabled! */ - - /* Disable the timer until we get it configured */ - - pwm_timer_enable(dev, false); - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* REVISIT: Disable outputs */ - - ret = pwm_outputs_enable(dev, outputs, false); - if (ret < 0) - { - goto errout; - } - - /* Initial timer configuration */ - - ret = pwm_timer_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure break and deadtime register */ - - ret = pwm_break_dt_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Disable software break (enable outputs) */ - - ret = pwm_soft_break(dev, false); - if (ret < 0) - { - goto errout; - } - -#ifdef HAVE_TRGO - /* Configure TRGO/TRGO2 */ - - ret = pwm_trgo_configure(dev, priv->trgo); - if (ret < 0) - { - goto errout; - } -#endif - - /* Configure timer channels */ - - for (j = 0; j < priv->chan_num; j++) - { - /* Skip channel if not in use */ - - if (priv->channels[j].channel != 0) - { - /* Update PWM mode */ - - pwm_mode_configure(dev, priv->channels[j].channel, - priv->channels[j].mode); - - /* PWM outputs configuration */ - - pwm_output_configure(priv, &priv->channels[j]); - } - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %" PRIx32 " duty: %08" PRIx32 - " count: %" PRIx32 "\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_rcr_update(dev, priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_rcr_update(dev, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#endif /* CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -3337,166 +3009,6 @@ static int pwm_timer(struct pwm_lowerhalf_s *dev, return ret; } -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the master output to stop the output as - * quickly as possible. - */ - - pwm_soft_break(dev, true); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_rcr_update(dev, priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output. - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %" PRIx32 "\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_STM32F7_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_STM32F7_TIM1_PWM */ - -#ifdef CONFIG_STM32F7_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_STM32F7_TIM8_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* REVISIT: RCR_REP_MAX for GTIM or ATIM ? */ - - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ - /**************************************************************************** * Name: pwm_set_apb_clock * @@ -3742,13 +3254,6 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) * counter, disabled outputs, not configured frequency and duty cycle */ -#ifdef CONFIG_PWM_PULSECOUNT - if (priv->timtype == TIMTYPE_ADVANCED) - { - ret = pwm_pulsecount_configure(dev); - } - else -#endif { ret = pwm_configure(dev); } @@ -3849,44 +3354,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start_pulsecount(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - - /* Generate an indefinite number of pulses */ - - if (info->channels[0].count == 0) - { - return pwm_start(dev, info); - } - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %" PRIx32 "\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#endif /* CONFIG_PWM_PULSECOUNT */ - static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -4060,10 +3527,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4107,10 +3570,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif From b7a4bc81438568c9766259fa8021c5d065a69c24 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:16:15 +0200 Subject: [PATCH 06/14] !arch/stm32h5: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32h5/CMakeLists.txt | 4 + arch/arm/src/stm32h5/Kconfig | 74 +- arch/arm/src/stm32h5/Make.defs | 4 + arch/arm/src/stm32h5/stm32_pulsecount.c | 1792 +++++++++++++++++++++++ arch/arm/src/stm32h5/stm32_pulsecount.h | 39 + arch/arm/src/stm32h5/stm32_pwm.c | 540 +------ 6 files changed, 1913 insertions(+), 540 deletions(-) create mode 100644 arch/arm/src/stm32h5/stm32_pulsecount.c create mode 100644 arch/arm/src/stm32h5/stm32_pulsecount.h diff --git a/arch/arm/src/stm32h5/CMakeLists.txt b/arch/arm/src/stm32h5/CMakeLists.txt index 2387532444758..37874fe58c99d 100644 --- a/arch/arm/src/stm32h5/CMakeLists.txt +++ b/arch/arm/src/stm32h5/CMakeLists.txt @@ -116,6 +116,10 @@ if(CONFIG_STM32H5_PWM) list(APPEND SRCS stm32_pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS stm32_pulsecount.c) +endif() + # Required chip type specific files if(CONFIG_STM32H5_STM32H5XXXX) diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index 651bbeeba080c..14730b3b84ee8 100644 --- a/arch/arm/src/stm32h5/Kconfig +++ b/arch/arm/src/stm32h5/Kconfig @@ -326,7 +326,6 @@ config STM32H5_FDCAN config STM32H5_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32H5_SPI bool @@ -3377,6 +3376,79 @@ config STM32H5_PWM_MULTICHAN Specifies that the PWM driver supports multiple output channels per timer. +config STM32H5_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config STM32H5_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32H5_TIM1 + select STM32H5_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if STM32H5_TIM1_PULSECOUNT + +config STM32H5_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config STM32H5_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32H5_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config STM32H5_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # STM32H5_TIM1_PULSECOUNT + +config STM32H5_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on STM32H5_TIM8 + select STM32H5_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if STM32H5_TIM8_PULSECOUNT + +config STM32H5_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config STM32H5_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32H5_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config STM32H5_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # STM32H5_TIM8_PULSECOUNT config STM32H5_TIM1_ADC bool "TIM1 ADC" default n diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs index 55c8db2ee6069..ca7e9a796197c 100644 --- a/arch/arm/src/stm32h5/Make.defs +++ b/arch/arm/src/stm32h5/Make.defs @@ -112,6 +112,10 @@ ifeq ($(CONFIG_STM32H5_PWM),y) CHIP_CSRCS += stm32_pwm.c endif +ifeq ($(CONFIG_STM32H5_PULSECOUNT),y) +CHIP_CSRCS += stm32_pulsecount.c +endif + # Required chip type specific files ifeq ($(CONFIG_STM32H5_STM32H5XXXX),y) diff --git a/arch/arm/src/stm32h5/stm32_pulsecount.c b/arch/arm/src/stm32h5/stm32_pulsecount.c new file mode 100644 index 0000000000000..7bae9cc8cb7f2 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_pulsecount.c @@ -0,0 +1,1792 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32_pulsecount.h" +#include "stm32.h" +#include "stm32_tim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the pulsecount upper half driver. + * + * It implements support for both: + * 1. STM32 TIMER IP version 1 - F0, F1, F2, F37x, F4, L0, L1 + * 2. STM32 TIMER IP version 2 - F3 (no F37x), F7, H7, L4, L4+, H5 + */ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED + +/* Advanced timer clock source, RCC EN offset, enable bit, + * RCC RST offset, reset bit to use + */ + +#define TIMCLK_TIM1 STM32_APB2_TIM1_CLKIN +#define TIMRCCEN_TIM1 STM32_RCC_APB2ENR +#define TIMEN_TIM1 RCC_APB2ENR_TIM1EN +#define TIMRCCRST_TIM1 STM32_RCC_APB2RSTR +#define TIMRST_TIM1 RCC_APB2RSTR_TIM1RST +#define TIMCLK_TIM8 STM32_APB2_TIM8_CLKIN +#define TIMRCCEN_TIM8 STM32_RCC_APB2ENR +#define TIMEN_TIM8 RCC_APB2ENR_TIM8EN +#define TIMRCCRST_TIM8 STM32_RCC_APB2RSTR +#define TIMRST_TIM8 RCC_APB2RSTR_TIM8RST + +/* Default GPIO pins state */ + +#define PINCFG_DEFAULT (GPIO_INPUT | GPIO_FLOAT) + +/* Synchronisation support */ + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) stm32_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct stm32_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct stm32_chan_s +{ + uint8_t channel; + struct stm32_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32_tim_s +{ + struct stm32_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct stm32_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct stm32_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset); +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value); +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct stm32_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_STM32H5_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_STM32H5_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT + +static struct stm32_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_STM32H5_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_STM32H5_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_STM32H5_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_STM32H5_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_STM32H5_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_STM32H5_TIM1_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM1_UP, + .base = STM32_TIM1_BASE, + .pclk = TIMCLK_TIM1, +}; + +#endif /* CONFIG_STM32H5_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT + +static struct stm32_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_STM32H5_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_STM32H5_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_STM32H5_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_STM32H5_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_STM32H5_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H5_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H5_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_STM32H5_TIM8_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM8_UP, + .base = STM32_TIM8_BASE, + .pclk = TIMCLK_TIM8, +}; + +#endif /* CONFIG_STM32H5_TIM8_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_reg_is_32bit + ****************************************************************************/ + +static bool pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + bool ret = false; + + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == STM32_ATIM_CR2_OFFSET || + offset == STM32_ATIM_CCMR1_OFFSET || + offset == STM32_ATIM_CCMR2_OFFSET || + offset == STM32_ATIM_CCER_OFFSET || + offset == STM32_ATIM_BDTR_OFFSET) + { + ret = true; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset) +{ + uint32_t retval = 0; + + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); + } +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DIER_OFFSET)); + + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_SR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET)); + + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET)); + + if (priv->timid == 1 || priv->timid == 8) + { + _info(" RCR: %04x BDTR: %04x\n", + pulsecount_getreg(priv, STM32_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET)); + } + + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR4_OFFSET)); + + _info(" DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_DCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t offset = 0; + + switch (index) + { + case 1: + { + offset = STM32_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = STM32_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = STM32_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = STM32_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32 " frequency: %" PRIu32 + " TIMCLK: %" PRIu32 + " prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, STM32_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, STM32_GTIM_PSC_OFFSET, (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct stm32_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* Get CCMR offset */ + + switch (channel) + { + case 1: + case 2: + { + offset = STM32_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = STM32_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + ocmode |= ATIM_CCMR1_OC1PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC1M); +#endif + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + ocmode |= ATIM_CCMR1_OC2PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC2M); +#endif + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + uint8_t channel = 0; + + /* Get channel */ + + channel = chan->channel; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (chan->out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (chan->out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + } + + /* Write registers */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, STM32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum stm32_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct stm32_tim_s *priv, uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer in PULSECOUNT mode + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + UNUSED(priv); + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channels */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, &priv->channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, + (uint16_t)priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, STM32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the master output to stop the output as + * quickly as possible. + */ + + pulsecount_moe_enable(dev, false); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output. + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_STM32H5_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_STM32H5_TIM8_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* Use the advanced-timer repetition counter limit. */ + + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_set_apb_clock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_set_apb_clock(struct stm32_tim_s *priv, bool on) +{ + uint32_t en_bit = 0; + uint32_t regaddr = 0; + int ret = OK; + + _info("timer %d clock enable: %d\n", priv->timid, on ? 1 : 0); + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCEN_TIM1; + en_bit = TIMEN_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCEN_TIM8; + en_bit = TIMEN_TIM8; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + _info("RCC_APBxENR base: %08" PRIx32 " bits: %04" PRIx32 "\n", + regaddr, en_bit); + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, true); + if (ret < 0) + { + goto errout; + } + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + /* Do not configure the pin if pincfg is not specified. + * This prevents overwriting the PA0 configuration if the + * channel is used internally. + */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= PINCFG_DEFAULT; + + stm32_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + irqstate_t flags = 0; + uint32_t resetbit = 0; + uint32_t regaddr = 0; + uint32_t regval = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Determine which timer to reset */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCRST_TIM1; + resetbit = TIMRST_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCRST_TIM8; + resetbit = TIMRST_TIM8; + break; + } +#endif + + default: + { + ret = -EINVAL; + goto errout; + } + } + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Reset the timer - stopping the output and putting the timer back + * into a state where pulsecount_start() can be called. + */ + + regval = getreg32(regaddr); + regval |= resetbit; + putreg32(regval, regaddr); + + regval &= ~resetbit; + putreg32(regval, regaddr); + + /* Clear all channels */ + + pulsecount_putreg(priv, STM32_GTIM_CCR1_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR2_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR3_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR4_OFFSET, 0); + + leave_critical_section(flags); + + _info("regaddr: %08" PRIx32 " resetbit: %08" PRIx32 "\n", + regaddr, resetbit); + pulsecount_dumpregs(dev, "After stop"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + struct stm32_tim_s *priv = pulse->timer; + + if (info->count > 0) + { + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + priv->handle = handle; + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer) +{ + struct stm32_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32H5_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_STM32H5_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} diff --git a/arch/arm/src/stm32h5/stm32_pulsecount.h b/arch/arm/src/stm32h5/stm32_pulsecount.h new file mode 100644 index 0000000000000..e236340ff6265 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_pulsecount.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_STM32H5_STM32_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32H5_STM32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32H5_STM32_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32h5/stm32_pwm.c b/arch/arm/src/stm32h5/stm32_pwm.c index 26f8f92629400..eadb42917b1a6 100644 --- a/arch/arm/src/stm32h5/stm32_pwm.c +++ b/arch/arm/src/stm32h5/stm32_pwm.c @@ -167,17 +167,6 @@ # undef HAVE_ADVTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_STM32H5_TIM1_PWM) || defined(CONFIG_STM32H5_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* Synchronisation support */ #ifdef CONFIG_STM32H5_PWM_TRGO @@ -271,21 +260,11 @@ struct stm32_pwmtimer_s * 4 LSB = TRGO, 4 MSB = TRGO2 */ #endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ -#else uint32_t frequency; /* Current frequency setting */ -#endif uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral clock * that drives the timer module. */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -342,42 +321,17 @@ static int pwm_deadtime_update(struct pwm_lowerhalf_s *dev, uint8_t dt); static uint32_t pwm_ccr_get(struct pwm_lowerhalf_s *dev, uint8_t index); #endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev); -#else static int pwm_configure(struct pwm_lowerhalf_s *dev); -#endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#else static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_STM32H5_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_STM32H5_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int pwm_stop(struct pwm_lowerhalf_s *dev); static int pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -580,9 +534,6 @@ static struct stm32_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM1_TRGO) .trgo = STM32_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM1_UP, #endif .base = STM32_TIM1_BASE, .pclk = TIMCLK_TIM1, @@ -679,9 +630,6 @@ static struct stm32_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM2_TRGO) .trgo = STM32_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM2, #endif .base = STM32_TIM2_BASE, .pclk = TIMCLK_TIM2, @@ -778,9 +726,6 @@ static struct stm32_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM3_TRGO) .trgo = STM32_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM3, #endif .base = STM32_TIM3_BASE, .pclk = TIMCLK_TIM3, @@ -877,9 +822,6 @@ static struct stm32_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM4_TRGO) .trgo = STM32_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM4, #endif .base = STM32_TIM4_BASE, .pclk = TIMCLK_TIM4, @@ -974,9 +916,6 @@ static struct stm32_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM5_TRGO) .trgo = STM32_TIM5_TRGO -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM5, #endif .base = STM32_TIM5_BASE, .pclk = TIMCLK_TIM5, @@ -1140,9 +1079,6 @@ static struct stm32_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM8_TRGO) .trgo = STM32_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM8_UP, #endif .base = STM32_TIM8_BASE, .pclk = TIMCLK_TIM8, @@ -1207,9 +1143,6 @@ static struct stm32_pwmtimer_s g_pwm12dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM12 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM12, #endif .base = STM32_TIM12_BASE, .pclk = TIMCLK_TIM12, @@ -1258,9 +1191,6 @@ static struct stm32_pwmtimer_s g_pwm13dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM13 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM13, #endif .base = STM32_TIM13_BASE, .pclk = TIMCLK_TIM13, @@ -1309,9 +1239,6 @@ static struct stm32_pwmtimer_s g_pwm14dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM14 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM14, #endif .base = STM32_TIM14_BASE, .pclk = TIMCLK_TIM14, @@ -1394,9 +1321,6 @@ static struct stm32_pwmtimer_s g_pwm15dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM15_TRGO) .trgo = STM32_TIM15_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM15, #endif .base = STM32_TIM15_BASE, .pclk = TIMCLK_TIM15, @@ -1463,9 +1387,6 @@ static struct stm32_pwmtimer_s g_pwm16dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM16 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM16, #endif .base = STM32_TIM16_BASE, .pclk = TIMCLK_TIM16, @@ -1532,9 +1453,6 @@ static struct stm32_pwmtimer_s g_pwm17dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM17 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM17, #endif .base = STM32_TIM17_BASE, .pclk = TIMCLK_TIM17, @@ -2910,259 +2828,11 @@ static int pwm_break_dt_configure(struct stm32_pwmtimer_s *priv) } #endif -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_configure - * - * Description: - * Configure PWM timer in PULSECOUNT mode - * - ****************************************************************************/ - -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t outputs = 0; - uint8_t j = 0; - int ret = OK; - - UNUSED(priv); - - /* NOTE: leave timer counter disabled and all outputs disabled! */ - - /* Disable the timer until we get it configured */ - - pwm_timer_enable(dev, false); - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* REVISIT: Disable outputs */ - - ret = pwm_outputs_enable(dev, outputs, false); - if (ret < 0) - { - goto errout; - } - - /* Initial timer configuration */ - - ret = pwm_timer_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure break and deadtime register */ - - ret = pwm_break_dt_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Disable software break (enable outputs) */ - - ret = pwm_soft_break(dev, false); - if (ret < 0) - { - goto errout; - } - -#ifdef HAVE_TRGO - /* Configure TRGO/TRGO2 */ - - ret = pwm_sync_configure(priv, priv->trgo); - if (ret < 0) - { - goto errout; - } -#endif - - /* Configure timer channels */ - - for (j = 0; j < priv->chan_num; j++) - { - /* Skip channel if not in use */ - - if (priv->channels[j].channel != 0) - { - /* Update PWM mode */ - - pwm_mode_configure(dev, priv->channels[j].channel, - priv->channels[j].mode); - - /* PWM outputs configuration */ - - pwm_output_configure(priv, &priv->channels[j]); - } - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint32_t mode = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %u duty: %08x count: %u\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - mode = priv->channels[0].mode; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, (uint16_t)priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#else /* !CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -3435,167 +3105,6 @@ static int pwm_timer(struct pwm_lowerhalf_s *dev, errout: return ret; } -#endif /* CONFIG_PWM_PULSECOUNT */ - -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the master output to stop the output as - * quickly as possible. - */ - - pwm_soft_break(dev, true); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output. - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %u\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_STM32H5_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_STM32H5_TIM1_PWM */ - -#ifdef CONFIG_STM32H5_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_STM32H5_TIM8_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* REVISIT: RCR_REP_MAX for GTIM or ATIM ? */ - - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ /**************************************************************************** * Name: pwm_set_apb_clock @@ -3842,11 +3351,7 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) * counter, disabled outputs, not configured frequency and duty cycle */ -#ifdef CONFIG_PWM_PULSECOUNT - ret = pwm_pulsecount_configure(dev); -#else ret = pwm_configure(dev); -#endif if (ret < 0) { pwmerr("failed to configure PWM %d\n", priv->timid); @@ -3943,38 +3448,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) - * REVISIT: verify if TIMTYPE_COUNTUP16_N works with it - */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %u\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#else /* !CONFIG_PWM_PULSECOUNT */ static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -4019,7 +3492,6 @@ static int pwm_start(struct pwm_lowerhalf_s *dev, return ret; } -#endif /* CONFIG_PWM_PULSECOUNT */ /**************************************************************************** * Name: pwm_stop @@ -4176,11 +3648,9 @@ static int pwm_stop(struct pwm_lowerhalf_s *dev) flags = enter_critical_section(); -#ifndef CONFIG_PWM_PULSECOUNT /* Stopped so frequency is zero */ priv->frequency = 0; -#endif /* Disable further interrupts and stop the timer */ @@ -4280,10 +3750,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4327,10 +3793,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif From 09a6a6b06387efa4294b7928479e0f6576b57726 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:16:31 +0200 Subject: [PATCH 07/14] !arch/stm32l4: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32l4/CMakeLists.txt | 4 + arch/arm/src/stm32l4/Kconfig | 74 +- arch/arm/src/stm32l4/Make.defs | 4 + arch/arm/src/stm32l4/stm32l4_pulsecount.c | 1657 +++++++++++++++++++++ arch/arm/src/stm32l4/stm32l4_pulsecount.h | 39 + arch/arm/src/stm32l4/stm32l4_pwm.c | 436 +----- 6 files changed, 1778 insertions(+), 436 deletions(-) create mode 100644 arch/arm/src/stm32l4/stm32l4_pulsecount.c create mode 100644 arch/arm/src/stm32l4/stm32l4_pulsecount.h diff --git a/arch/arm/src/stm32l4/CMakeLists.txt b/arch/arm/src/stm32l4/CMakeLists.txt index ae4098bd0a29b..040c98142bcd8 100644 --- a/arch/arm/src/stm32l4/CMakeLists.txt +++ b/arch/arm/src/stm32l4/CMakeLists.txt @@ -164,6 +164,10 @@ if(CONFIG_STM32L4_PWM) list(APPEND SRCS stm32l4_pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS stm32l4_pulsecount.c) +endif() + if(CONFIG_SENSORS_QENCODER) list(APPEND SRCS stm32l4_qencoder.c) endif() diff --git a/arch/arm/src/stm32l4/Kconfig b/arch/arm/src/stm32l4/Kconfig index 0731829dd6c71..b6d2bb1fb804c 100644 --- a/arch/arm/src/stm32l4/Kconfig +++ b/arch/arm/src/stm32l4/Kconfig @@ -1219,7 +1219,6 @@ config STM32L4_SPI config STM32L4_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32L4_USART bool @@ -3713,6 +3712,79 @@ config STM32L4_PWM_MULTICHAN Specifies that the PWM driver supports multiple output channels per timer. +config STM32L4_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config STM32L4_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32L4_TIM1 + select STM32L4_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if STM32L4_TIM1_PULSECOUNT + +config STM32L4_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config STM32L4_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32L4_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config STM32L4_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # STM32L4_TIM1_PULSECOUNT + +config STM32L4_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on STM32L4_TIM8 + select STM32L4_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if STM32L4_TIM8_PULSECOUNT + +config STM32L4_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config STM32L4_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32L4_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config STM32L4_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # STM32L4_TIM8_PULSECOUNT config STM32L4_TIM1_ADC bool "TIM1 ADC" default n diff --git a/arch/arm/src/stm32l4/Make.defs b/arch/arm/src/stm32l4/Make.defs index b2a675e6d9382..3d981681ff023 100644 --- a/arch/arm/src/stm32l4/Make.defs +++ b/arch/arm/src/stm32l4/Make.defs @@ -157,6 +157,10 @@ ifeq ($(CONFIG_STM32L4_PWM),y) CHIP_CSRCS += stm32l4_pwm.c endif +ifeq ($(CONFIG_STM32L4_PULSECOUNT),y) +CHIP_CSRCS += stm32l4_pulsecount.c +endif + ifeq ($(CONFIG_SENSORS_QENCODER),y) CHIP_CSRCS += stm32l4_qencoder.c endif diff --git a/arch/arm/src/stm32l4/stm32l4_pulsecount.c b/arch/arm/src/stm32l4/stm32l4_pulsecount.c new file mode 100644 index 0000000000000..08807a2334e21 --- /dev/null +++ b/arch/arm/src/stm32l4/stm32l4_pulsecount.c @@ -0,0 +1,1657 @@ +/**************************************************************************** + * arch/arm/src/stm32l4/stm32l4_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32l4_pulsecount.h" +#include "stm32l4.h" +#include "stm32l4_tim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the pulsecount upper half driver. + */ + +#if defined(CONFIG_STM32L4_TIM1_PULSECOUNT) || defined(CONFIG_STM32L4_TIM8_PULSECOUNT) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) stm32l4_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct stm32l4_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct stm32l4_chan_s +{ + uint8_t channel; + struct stm32l4_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32l4_tim_s +{ + struct stm32l4_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct stm32l4_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct stm32l4_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint16_t pulsecount_getreg(struct stm32l4_tim_s *priv, int offset); +static void pulsecount_putreg(struct stm32l4_tim_s *priv, int offset, + uint16_t value); +static void pulsecount_modifyreg(struct stm32l4_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct stm32l4_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct stm32l4_tim_s *priv, + struct stm32l4_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_STM32L4_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_STM32L4_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT + +static struct stm32l4_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_STM32L4_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_STM32L4_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_STM32L4_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_STM32L4_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_STM32L4_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_STM32L4_TIM1_PULSECOUNT_TDTS, + .irq = STM32L4_IRQ_TIM1UP, + .base = STM32L4_TIM1_BASE, + .pclk = STM32L4_APB2_TIM1_CLKIN, +}; + +#endif /* CONFIG_STM32L4_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT + +static struct stm32l4_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_STM32L4_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_STM32L4_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_STM32L4_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_STM32L4_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_STM32L4_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32L4_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32L4_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_STM32L4_TIM8_PULSECOUNT_TDTS, + .irq = STM32L4_IRQ_TIM8UP, + .base = STM32L4_TIM8_BASE, + .pclk = STM32L4_APB2_TIM8_CLKIN, +}; + +#endif /* CONFIG_STM32L4_TIM8_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT +static struct stm32l4_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT +static struct stm32l4_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register. + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint16_t pulsecount_getreg(struct stm32l4_tim_s *priv, int offset) +{ + return getreg16(priv->base + offset); +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register. + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct stm32l4_tim_s *priv, int offset, + uint16_t value) +{ + putreg16(value, priv->base + offset); +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct stm32l4_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, STM32L4_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_DIER_OFFSET)); + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, STM32L4_GTIM_SR_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CCMR2_OFFSET)); + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, STM32L4_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_ARR_OFFSET)); + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, STM32L4_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, STM32L4_GTIM_CCR4_OFFSET)); + _info(" RCR: %04x BDTR: %04x DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, STM32L4_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, STM32L4_ATIM_BDTR_OFFSET), + pulsecount_getreg(priv, STM32L4_ATIM_DCR_OFFSET), + pulsecount_getreg(priv, STM32L4_ATIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t offset = 0; + + /* CCR channel indices are one-based to match timer channel numbers. */ + + switch (index) + { + case 1: + { + offset = STM32L4_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = STM32L4_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = STM32L4_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = STM32L4_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, STM32L4_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32 " frequency: %" PRIu32 " TIMCLK: %" PRIu32 + " prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, STM32L4_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, STM32L4_GTIM_PSC_OFFSET, + (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct stm32l4_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, STM32L4_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, STM32L4_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* output compare configuration */ + + switch (channel) + { + /* Get CCMR offset */ + + case 1: + case 2: + { + offset = STM32L4_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = STM32L4_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE | ATIM_CCMR1_OC1M); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + ocmode |= ATIM_CCMR1_OC1PE; + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE | ATIM_CCMR1_OC2M); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + ocmode |= ATIM_CCMR1_OC2PE; + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct stm32l4_tim_s *priv, + uint8_t channel) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, STM32L4_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, STM32L4_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (priv->channel.out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (priv->channel.out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + } + + /* Write registers */ + + pulsecount_modifyreg(priv, STM32L4_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, STM32L4_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum stm32l4_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, STM32L4_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + + /* NOTE: CC4N does not exist, but some docs show configuration bits for it + */ + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, STM32L4_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, STM32L4_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, STM32L4_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct stm32l4_tim_s *priv, + uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer for pulse output + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channel */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, priv->channel.channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, STM32L4_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32L4_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, STM32L4_GTIM_RCR_OFFSET, + (uint16_t)priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, STM32L4_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, STM32L4_GTIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, STM32L4_GTIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, STM32L4_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, STM32L4_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, STM32L4_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, STM32L4_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, STM32L4_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, STM32L4_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the mast output to stop the output as + * quickly as possible. + */ + + regval = pulsecount_getreg(priv, STM32L4_ATIM_BDTR_OFFSET); + regval &= ~ATIM_BDTR_MOE; + pulsecount_putreg(priv, STM32L4_ATIM_BDTR_OFFSET, regval); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, STM32L4_ATIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_STM32L4_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_STM32L4_TIM8_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_setapbclock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_setapbclock(struct stm32l4_tim_s *priv, + bool on) +{ + uint32_t en_bit; + uint32_t regaddr; + int ret = OK; + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT + case 1: + { + regaddr = STM32L4_RCC_APB2ENR; + en_bit = RCC_APB2ENR_TIM1EN; + break; + } +#endif + +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT + case 8: + { + regaddr = STM32L4_RCC_APB2ENR; + en_bit = RCC_APB2ENR_TIM8EN; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t pincfg; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + pulsecount_setapbclock(priv, true); + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + pincfg = priv->channel.out1.pincfg; + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32l4_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_setapbclock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= GPIO_INPUT | GPIO_FLOAT; + + stm32l4_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + uint32_t resetbit = 0; + uint32_t regaddr; + uint32_t regval; + irqstate_t flags; + + _info("TIM%u\n", priv->timid); + + /* Determine which timer to reset */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT + case 1: + regaddr = STM32L4_RCC_APB2RSTR; + resetbit = RCC_APB2RSTR_TIM1RST; + break; +#endif +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT + case 8: + regaddr = STM32L4_RCC_APB2RSTR; + resetbit = RCC_APB2RSTR_TIM8RST; + break; +#endif + default: + return -EINVAL; + } + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, STM32L4_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32L4_GTIM_SR_OFFSET, 0); + + /* Reset the timer - stopping the output and putting the timer back + * into a state where pulsecount_start() can be called. + */ + + regval = getreg32(regaddr); + regval |= resetbit; + putreg32(regval, regaddr); + + regval &= ~resetbit; + putreg32(regval, regaddr); + leave_critical_section(flags); + + _info("regaddr: %08" PRIx32 " resetbit: %08" PRIx32 "\n", + regaddr, resetbit); + pulsecount_dumpregs(dev, "After stop"); + return OK; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32l4_tim_s *priv = (struct stm32l4_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_pulsecount_s *pulse = (struct stm32l4_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_pulsecount_s *pulse = (struct stm32l4_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32l4_pulsecount_s *pulse = (struct stm32l4_pulsecount_s *)dev; + struct stm32l4_tim_s *priv = pulse->timer; + + if (info->count > 0) + { + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + priv->handle = handle; + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32l4_pulsecount_s *pulse = (struct stm32l4_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct stm32l4_pulsecount_s *pulse = (struct stm32l4_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32l4_pulsecountinitialize(int timer) +{ + struct stm32l4_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32L4_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_STM32L4_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} + +#endif /* CONFIG_STM32L4_TIM1_PULSECOUNT || CONFIG_STM32L4_TIM8_PULSECOUNT */ diff --git a/arch/arm/src/stm32l4/stm32l4_pulsecount.h b/arch/arm/src/stm32l4/stm32l4_pulsecount.h new file mode 100644 index 0000000000000..8d1a95c595ba9 --- /dev/null +++ b/arch/arm/src/stm32l4/stm32l4_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32l4/stm32l4_pulsecount.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_STM32L4_STM32L4_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32L4_STM32L4_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32l4_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32L4_STM32L4_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32l4/stm32l4_pwm.c b/arch/arm/src/stm32l4/stm32l4_pwm.c index 37a4ec2b6a569..46d0aff819376 100644 --- a/arch/arm/src/stm32l4/stm32l4_pwm.c +++ b/arch/arm/src/stm32l4/stm32l4_pwm.c @@ -107,17 +107,6 @@ # undef HAVE_LPTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_STM32L4_TIM1_PWM) || defined(CONFIG_STM32L4_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* Synchronisation support */ #ifdef CONFIG_STM32L4_PWM_TRGO @@ -196,20 +185,10 @@ struct stm32l4_pwmtimer_s * 4 LSB = TRGO, 4 MSB = TRGO2 */ #endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ -#else uint32_t frequency; /* Current frequency setting */ -#endif uint32_t base; /* The base address of the timer */ uint32_t pclk; /* Frequency of the peripheral clock * that drives the timer module. */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -233,17 +212,12 @@ static void pwm_dumpregs(struct pwm_lowerhalf_s *dev, /* Timer management */ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#else static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); # ifdef HAVE_LPTIM static int pwm_lptimer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); # endif -#endif static int pwm_configure(struct pwm_lowerhalf_s *dev); @@ -270,30 +244,13 @@ static int pwm_deadtime_update(struct pwm_lowerhalf_s *dev, uint8_t dt); static uint32_t pwm_ccr_get(struct pwm_lowerhalf_s *dev, uint8_t index); #endif -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_STM32L4_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_STM32L4_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif - /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int pwm_stop(struct pwm_lowerhalf_s *dev); static int pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -496,9 +453,6 @@ static struct stm32l4_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM1_TRGO) .trgo = STM32L4_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM1UP, #endif .base = STM32L4_TIM1_BASE, .pclk = STM32L4_APB2_TIM1_CLKIN, @@ -595,9 +549,6 @@ static struct stm32l4_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM2_TRGO) .trgo = STM32L4_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM2, #endif .base = STM32L4_TIM2_BASE, .pclk = STM32L4_APB1_TIM2_CLKIN, @@ -695,9 +646,6 @@ static struct stm32l4_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM3_TRGO) .trgo = STM32L4_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM3, #endif .base = STM32L4_TIM3_BASE, .pclk = STM32L4_APB1_TIM3_CLKIN, @@ -794,9 +742,6 @@ static struct stm32l4_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM4_TRGO) .trgo = STM32L4_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM4, #endif .base = STM32L4_TIM4_BASE, .pclk = STM32L4_APB1_TIM4_CLKIN, @@ -891,9 +836,6 @@ static struct stm32l4_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM5_TRGO) .trgo = STM32L4_TIM5_TRGO -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM5, #endif .base = STM32L4_TIM5_BASE, .pclk = STM32L4_APB1_TIM5_CLKIN, @@ -1057,9 +999,6 @@ static struct stm32l4_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM8_TRGO) .trgo = STM32L4_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM8UP, #endif .base = STM32L4_TIM8_BASE, .pclk = STM32L4_APB2_TIM8_CLKIN, @@ -1142,9 +1081,6 @@ static struct stm32l4_pwmtimer_s g_pwm15dev = #endif #if defined(HAVE_TRGO) && defined(STM32L4_TIM15_TRGO) .trgo = STM32L4_TIM15_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM15, #endif .base = STM32L4_TIM15_BASE, .pclk = STM32L4_APB2_TIM15_CLKIN, @@ -1211,9 +1147,6 @@ static struct stm32l4_pwmtimer_s g_pwm16dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM16 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM16, #endif .base = STM32L4_TIM16_BASE, .pclk = STM32L4_APB2_TIM16_CLKIN, @@ -1280,9 +1213,6 @@ static struct stm32l4_pwmtimer_s g_pwm17dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM17 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_TIM17, #endif .base = STM32L4_TIM17_BASE, .pclk = STM32L4_APB2_TIM17_CLKIN, @@ -1331,9 +1261,6 @@ static struct stm32l4_pwmtimer_s g_pwmlp1dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for LPTIM1 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_LPTIM1, #endif .base = STM32L4_LPTIM1_BASE, #if defined(CONFIG_STM32L4_LPTIM1_CLK_APB1) @@ -1390,9 +1317,6 @@ static struct stm32l4_pwmtimer_s g_pwmlp2dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for LPTIM2 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32L4_IRQ_LPTIM2, #endif .base = STM32L4_LPTIM2_BASE, #if defined(CONFIG_STM32L4_LPTIM2_CLK_APB1) @@ -2786,7 +2710,7 @@ static int pwm_break_dt_configure(struct stm32l4_pwmtimer_s *priv) * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -2905,160 +2829,6 @@ static int pwm_configure(struct pwm_lowerhalf_s *dev) return ret; } -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct stm32l4_pwmtimer_s *priv = (struct stm32l4_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %u duty: %08x count: %u\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, STM32L4_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, STM32L4_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_putreg(priv, STM32L4_GTIM_RCR_OFFSET, (uint16_t)priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_putreg(priv, STM32L4_GTIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_putreg(priv, STM32L4_GTIM_RCR_OFFSET, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, STM32L4_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, STM32L4_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#else /* !CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_duty_channels_update * @@ -3288,168 +3058,6 @@ static int pwm_lptimer(struct pwm_lowerhalf_s *dev, } #endif /* HAVE_LPTIM */ -#endif /* CONFIG_PWM_PULSECOUNT */ - -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct stm32l4_pwmtimer_s *priv = (struct stm32l4_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32L4_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32L4_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the mast output to stop the output as - * quickly as possible. - */ - - regval = pwm_getreg(priv, STM32L4_ATIM_BDTR_OFFSET); - regval &= ~ATIM_BDTR_MOE; - pwm_putreg(priv, STM32L4_ATIM_BDTR_OFFSET, regval); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_putreg(priv, STM32L4_ATIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %u\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_STM32L4_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_STM32L4_TIM1_PWM */ - -#ifdef CONFIG_STM32L4_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_STM32L4_TIM8_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ - /**************************************************************************** * Name: pwm_setapbclock * @@ -3837,37 +3445,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32l4_pwmtimer_s *priv = (struct stm32l4_pwmtimer_s *)dev; - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %u\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#else /* !CONFIG_PWM_PULSECOUNT */ - static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -3921,7 +3498,6 @@ static int pwm_start(struct pwm_lowerhalf_s *dev, return ret; } -#endif /* CONFIG_PWM_PULSECOUNT */ /**************************************************************************** * Name: pwm_stop @@ -4044,11 +3620,9 @@ static int pwm_stop(struct pwm_lowerhalf_s *dev) flags = enter_critical_section(); -#ifndef CONFIG_PWM_PULSECOUNT /* Stopped so frequency is zero */ priv->frequency = 0; -#endif /* Disable further interrupts and stop the timer */ @@ -4137,10 +3711,6 @@ struct pwm_lowerhalf_s *stm32l4_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; #endif @@ -4174,10 +3744,6 @@ struct pwm_lowerhalf_s *stm32l4_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; #endif From 1c93dc2d0972dcaff724ae8de79165a2e606f04e Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:16:40 +0200 Subject: [PATCH 08/14] !arch/stm32h7: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/stm32h7/CMakeLists.txt | 4 + arch/arm/src/stm32h7/Kconfig | 76 +- arch/arm/src/stm32h7/Make.defs | 4 + arch/arm/src/stm32h7/stm32_pulsecount.c | 1813 +++++++++++++++++++++++ arch/arm/src/stm32h7/stm32_pulsecount.h | 39 + arch/arm/src/stm32h7/stm32_pwm.c | 540 +------ 6 files changed, 1934 insertions(+), 542 deletions(-) create mode 100644 arch/arm/src/stm32h7/stm32_pulsecount.c create mode 100644 arch/arm/src/stm32h7/stm32_pulsecount.h diff --git a/arch/arm/src/stm32h7/CMakeLists.txt b/arch/arm/src/stm32h7/CMakeLists.txt index 85c278d67d505..eb5a4218c2203 100644 --- a/arch/arm/src/stm32h7/CMakeLists.txt +++ b/arch/arm/src/stm32h7/CMakeLists.txt @@ -181,6 +181,10 @@ if(CONFIG_STM32H7_PWM) list(APPEND SRCS stm32_pwm.c) endif() +if(CONFIG_PULSECOUNT) + list(APPEND SRCS stm32_pulsecount.c) +endif() + if(CONFIG_STM32H7_ETHMAC) list(APPEND SRCS stm32_ethernet.c) endif() diff --git a/arch/arm/src/stm32h7/Kconfig b/arch/arm/src/stm32h7/Kconfig index 3810e8f2c1df1..2a1cad61ab3ef 100644 --- a/arch/arm/src/stm32h7/Kconfig +++ b/arch/arm/src/stm32h7/Kconfig @@ -834,7 +834,6 @@ config STM32H7_PWR config STM32H7_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32H7_USART bool @@ -980,7 +979,6 @@ config STM32H7_WWDG default n select WATCHDOG - menu "STM32H7 FDCAN Selection" config STM32H7_FDCAN1 @@ -4316,6 +4314,79 @@ config STM32H7_PWM_MULTICHAN Specifies that the PWM driver supports multiple output channels per timer. +config STM32H7_PULSECOUNT + bool + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + +config STM32H7_TIM1_PULSECOUNT + bool "TIM1 pulse count" + default n + depends on STM32H7_TIM1 + select STM32H7_PULSECOUNT + ---help--- + Reserve timer 1 for pulse count output. + +if STM32H7_TIM1_PULSECOUNT + +config STM32H7_TIM1_PULSECOUNT_TDTS + int "TIM1 pulse count clock division" + default 0 + range 0 2 + +config STM32H7_TIM1_PULSECOUNT_CHANNEL + int "TIM1 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32H7_TIM1_PULSECOUNT_POL + int "TIM1 pulse count output polarity" + default 0 + range 0 1 + +config STM32H7_TIM1_PULSECOUNT_IDLE + int "TIM1 pulse count idle state" + default 0 + range 0 1 + +endif # STM32H7_TIM1_PULSECOUNT + +config STM32H7_TIM8_PULSECOUNT + bool "TIM8 pulse count" + default n + depends on STM32H7_TIM8 + select STM32H7_PULSECOUNT + ---help--- + Reserve timer 8 for pulse count output. + +if STM32H7_TIM8_PULSECOUNT + +config STM32H7_TIM8_PULSECOUNT_TDTS + int "TIM8 pulse count clock division" + default 0 + range 0 2 + +config STM32H7_TIM8_PULSECOUNT_CHANNEL + int "TIM8 pulse count channel" + default 1 + range 1 4 + ---help--- + Specifies the timer channel {1,..,4}. + +config STM32H7_TIM8_PULSECOUNT_POL + int "TIM8 pulse count output polarity" + default 0 + range 0 1 + +config STM32H7_TIM8_PULSECOUNT_IDLE + int "TIM8 pulse count idle state" + default 0 + range 0 1 + +endif # STM32H7_TIM8_PULSECOUNT config STM32H7_TIM1_ADC bool "TIM1 ADC" default n @@ -6558,7 +6629,6 @@ endchoice # FDCAN WorkQueue Selection endmenu # FDCAN Driver - menu "Progmem MTD configuration" if STM32_HAVE_OTA_PARTITION diff --git a/arch/arm/src/stm32h7/Make.defs b/arch/arm/src/stm32h7/Make.defs index 008dfc475ade9..ed5fdee000ac2 100644 --- a/arch/arm/src/stm32h7/Make.defs +++ b/arch/arm/src/stm32h7/Make.defs @@ -183,6 +183,10 @@ ifeq ($(CONFIG_STM32H7_PWM),y) CHIP_CSRCS += stm32_pwm.c endif +ifeq ($(CONFIG_STM32H7_PULSECOUNT),y) +CHIP_CSRCS += stm32_pulsecount.c +endif + ifeq ($(CONFIG_STM32H7_ETHMAC),y) CHIP_CSRCS += stm32_ethernet.c endif diff --git a/arch/arm/src/stm32h7/stm32_pulsecount.c b/arch/arm/src/stm32h7/stm32_pulsecount.c new file mode 100644 index 0000000000000..f0a5189aba06b --- /dev/null +++ b/arch/arm/src/stm32h7/stm32_pulsecount.c @@ -0,0 +1,1813 @@ +/**************************************************************************** + * arch/arm/src/stm32h7/stm32_pulsecount.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 + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "chip.h" +#include "stm32_pulsecount.h" +#include "stm32.h" +#include "stm32_tim.h" + +/* This module then only compiles if there is at least one enabled timer + * intended for use with the pulsecount upper half driver. + * + * It implements support for both: + * 1. STM32 TIMER IP version 1 - F0, F1, F2, F37x, F4, L0, L1 + * 2. STM32 TIMER IP version 2 - F3 (no F37x), F7, H7, L4, L4+ + */ + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Timer Definitions ********************************************************/ + +/* Pulsecount is supported by advanced timers only. */ + +#define TIMTYPE_ADVANCED 5 +#define TIMTYPE_TIM1 TIMTYPE_ADVANCED +#define TIMTYPE_TIM8 TIMTYPE_ADVANCED + +/* Advanced timer clock source, RCC EN offset, enable bit, + * RCC RST offset, reset bit to use + */ + +#define TIMCLK_TIM1 STM32_APB2_TIM1_CLKIN +#define TIMRCCEN_TIM1 STM32_RCC_APB2ENR +#define TIMEN_TIM1 RCC_APB2ENR_TIM1EN +#define TIMRCCRST_TIM1 STM32_RCC_APB2RSTR +#define TIMRST_TIM1 RCC_APB2RSTR_TIM1RST +#define TIMCLK_TIM8 STM32_APB2_TIM8_CLKIN +#define TIMRCCEN_TIM8 STM32_RCC_APB2ENR +#define TIMEN_TIM8 RCC_APB2ENR_TIM8EN +#define TIMRCCRST_TIM8 STM32_RCC_APB2RSTR +#define TIMRST_TIM8 RCC_APB2RSTR_TIM8RST + +/* Default GPIO pins state */ + +#define PINCFG_DEFAULT (GPIO_INPUT | GPIO_FLOAT) + +/* Advanced timer support */ + +# if defined(CONFIG_STM32H7_TIM1_PULSECOUNT) || defined(CONFIG_STM32H7_TIM8_PULSECOUNT) +# endif + +/* Synchronisation support */ + +#define PULSECOUNT_POL_NEG 1 +#define PULSECOUNT_IDLE_ACTIVE 1 + +#ifndef STM32_GTIM_RCR_OFFSET +# define STM32_GTIM_RCR_OFFSET STM32_ATIM_RCR_OFFSET +#endif + +/* Debug ********************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +# define pulsecount_dumpgpio(p,m) stm32_dumpgpio(p,m) +#else +# define pulsecount_dumpgpio(p,m) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Pulsecount output configuration */ + +struct stm32_out_s +{ + uint8_t in_use:1; + uint8_t pol:1; + uint8_t idle:1; + uint8_t _res:5; + uint32_t pincfg; +}; + +/* Pulsecount channel configuration */ + +struct stm32_chan_s +{ + uint8_t channel; + struct stm32_out_s out1; +}; + +/* This structure represents the state of one pulsecount timer */ + +struct stm32_tim_s +{ + struct stm32_chan_s channel; + uint8_t timid:5; + uint8_t timtype:3; + uint8_t t_dts:3; + uint8_t _res:5; + uint8_t irq; + uint8_t prev; + uint8_t curr; + uint32_t count; + uint32_t frequency; + uint32_t base; + uint32_t pclk; + void *handle; +}; + +struct stm32_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct stm32_tim_s *timer; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Register access */ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset); +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value); +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits); + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg); +#else +# define pulsecount_dumpregs(priv,msg) +#endif + +/* Timer management */ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr); +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty); +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency); +static int pulsecount_timer_configure(struct stm32_tim_s *priv); +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel); +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan); +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state); +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable); +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info); +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev); +# ifdef CONFIG_STM32H7_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg); +# endif +# ifdef CONFIG_STM32H7_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg); +# endif +static uint8_t pulsecount_count(uint32_t count); + +/* Pulsecount driver methods */ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev); + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT + +static struct stm32_tim_s g_pulsecount1dev = +{ + .channel = + { + .channel = CONFIG_STM32H7_TIM1_PULSECOUNT_CHANNEL, +#if CONFIG_STM32H7_TIM1_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH1OUT, + }, +#elif CONFIG_STM32H7_TIM1_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH2OUT, + }, +#elif CONFIG_STM32H7_TIM1_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH3OUT, + }, +#elif CONFIG_STM32H7_TIM1_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM1_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM1_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM1_CH4OUT, + }, +#endif + }, + .timid = 1, + .timtype = TIMTYPE_TIM1, + .t_dts = CONFIG_STM32H7_TIM1_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM1UP, + .base = STM32_TIM1_BASE, + .pclk = TIMCLK_TIM1, +}; + +#endif /* CONFIG_STM32H7_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT + +static struct stm32_tim_s g_pulsecount8dev = +{ + .channel = + { + .channel = CONFIG_STM32H7_TIM8_PULSECOUNT_CHANNEL, +#if CONFIG_STM32H7_TIM8_PULSECOUNT_CHANNEL == 1 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH1OUT, + }, +#elif CONFIG_STM32H7_TIM8_PULSECOUNT_CHANNEL == 2 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH2OUT, + }, +#elif CONFIG_STM32H7_TIM8_PULSECOUNT_CHANNEL == 3 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH3OUT, + }, +#elif CONFIG_STM32H7_TIM8_PULSECOUNT_CHANNEL == 4 + .out1 = + { + .in_use = 1, + .pol = CONFIG_STM32H7_TIM8_PULSECOUNT_POL, + .idle = CONFIG_STM32H7_TIM8_PULSECOUNT_IDLE, + .pincfg = GPIO_TIM8_CH4OUT, + }, +#endif + }, + .timid = 8, + .timtype = TIMTYPE_TIM8, + .t_dts = CONFIG_STM32H7_TIM8_PULSECOUNT_TDTS, + .irq = STM32_IRQ_TIM8UP, + .base = STM32_TIM8_BASE, + .pclk = TIMCLK_TIM8, +}; + +#endif /* CONFIG_STM32H7_TIM8_PULSECOUNT */ + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount1lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount1dev, +}; +#endif + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT +static struct stm32_pulsecount_s g_pulsecount8lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount8dev, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_reg_is_32bit + ****************************************************************************/ + +static bool pulsecount_reg_is_32bit(uint8_t timtype, uint32_t offset) +{ + bool ret = false; + + if (timtype == TIMTYPE_ADVANCED) + { + if (offset == STM32_ATIM_CR2_OFFSET || + offset == STM32_ATIM_CCMR1_OFFSET || + offset == STM32_ATIM_CCMR2_OFFSET || + offset == STM32_ATIM_CCER_OFFSET || + offset == STM32_ATIM_BDTR_OFFSET) + { + ret = true; + } + } + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_getreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * The current contents of the specified register + * + ****************************************************************************/ + +static uint32_t pulsecount_getreg(struct stm32_tim_s *priv, int offset) +{ + uint32_t retval = 0; + + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + retval = getreg32(priv->base + offset); + } + else + { + /* 16-bit register */ + + retval = getreg16(priv->base + offset); + } + + /* Return 32-bit value */ + + return retval; +} + +/**************************************************************************** + * Name: pulsecount_putreg + * + * Description: + * Read the value of an pulsecount timer register + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_putreg(struct stm32_tim_s *priv, int offset, + uint32_t value) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + putreg32(value, priv->base + offset); + } + else + { + /* 16-bit register */ + + putreg16((uint16_t)value, priv->base + offset); + } +} + +/**************************************************************************** + * Name: pulsecount_modifyreg + * + * Description: + * Modify timer register (32-bit or 16-bit) + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * offset - The offset to the register to read + * clrbits - The bits to clear + * setbits - The bits to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void pulsecount_modifyreg(struct stm32_tim_s *priv, uint32_t offset, + uint32_t clearbits, uint32_t setbits) +{ + if (pulsecount_reg_is_32bit(priv->timtype, offset) == true) + { + /* 32-bit register */ + + modifyreg32(priv->base + offset, clearbits, setbits); + } + else + { + /* 16-bit register */ + + modifyreg16(priv->base + offset, (uint16_t)clearbits, + (uint16_t)setbits); + } +} + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all timer registers. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(struct pulsecount_lowerhalf_s *dev, + const char *msg) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + _info("%s:\n", msg); + _info(" CR1: %04x CR2: %04x SMCR: %04x DIER: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_SMCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DIER_OFFSET)); + + _info(" SR: %04x EGR: %04x CCMR1: %04x CCMR2: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_SR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_EGR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCMR2_OFFSET)); + + _info(" CCER: %04x CNT: %04x PSC: %04x ARR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CNT_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_PSC_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET)); + + if (priv->timid == 1 || priv->timid == 8) + { + _info(" RCR: %04x BDTR: %04x\n", + pulsecount_getreg(priv, STM32_ATIM_RCR_OFFSET), + pulsecount_getreg(priv, STM32_ATIM_BDTR_OFFSET)); + } + + _info(" CCR1: %04x CCR2: %04x CCR3: %04x CCR4: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_CCR1_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR2_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR3_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_CCR4_OFFSET)); + + _info(" DCR: %04x DMAR: %04x\n", + pulsecount_getreg(priv, STM32_GTIM_DCR_OFFSET), + pulsecount_getreg(priv, STM32_GTIM_DMAR_OFFSET)); +} +#endif + +/**************************************************************************** + * Name: pulsecount_ccr_update + ****************************************************************************/ + +static int pulsecount_ccr_update(struct pulsecount_lowerhalf_s *dev, + uint8_t index, uint32_t ccr) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t offset = 0; + + /* CCR channel indices are one-based to match timer channel numbers. */ + + switch (index) + { + case 1: + { + offset = STM32_GTIM_CCR1_OFFSET; + break; + } + + case 2: + { + offset = STM32_GTIM_CCR2_OFFSET; + break; + } + + case 3: + { + offset = STM32_GTIM_CCR3_OFFSET; + break; + } + + case 4: + { + offset = STM32_GTIM_CCR4_OFFSET; + break; + } + + default: + { + _err("ERROR: No such CCR: %u\n", index); + return -EINVAL; + } + } + + /* Update CCR register */ + + pulsecount_putreg(priv, offset, ccr); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_duty_update + * + * Description: + * Try to change only channel duty + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * channel - Channel to by updated + * duty - New duty + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_duty_update(struct pulsecount_lowerhalf_s *dev, + uint8_t channel, ub16_t duty) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t ccr = 0; + + /* We don't want compilation warnings if no DEBUGASSERT */ + + UNUSED(priv); + + DEBUGASSERT(priv != NULL); + + _info("TIM%u channel: %u duty: %08" PRIx32 "\n", + priv->timid, channel, duty); + + /* Get the reload values */ + + reload = pulsecount_getreg(priv, STM32_GTIM_ARR_OFFSET); + + /* Duty cycle: + * + * duty cycle = ccr / reload (fractional value) + */ + + ccr = b16toi(duty * reload + b16HALF); + + _info("ccr: %" PRIu32 "\n", ccr); + + /* Write corresponding CCR register */ + + return pulsecount_ccr_update(dev, channel, ccr); +} + +/**************************************************************************** + * Name: pulsecount_frequency_update + * + * Description: + * Update a pulsecount timer frequency + * + ****************************************************************************/ + +static int pulsecount_frequency_update(struct pulsecount_lowerhalf_s *dev, + uint32_t frequency) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t reload = 0; + uint32_t timclk = 0; + uint32_t prescaler = 0; + + /* Calculate optimal values for the timer prescaler and for the timer + * reload register. If 'frequency' is the desired frequency, then + * + * reload = timclk / frequency + * timclk = pclk / presc + * + * Or, + * + * reload = pclk / presc / frequency + * + * There are many solutions to this, but the best solution will be the one + * that has the largest reload value and the smallest prescaler value. + * That is the solution that should give us the most accuracy in the timer + * control. Subject to: + * + * 0 <= presc <= 65536 + * 1 <= reload <= 65535 + * + * So presc = pclk / 65535 / frequency would be optimal. + * + * Example: + * + * pclk = 42 MHz + * frequency = 100 Hz + * + * prescaler = 42,000,000 / 65,535 / 100 + * = 6.4 (or 7 -- taking the ceiling always) + * timclk = 42,000,000 / 7 + * = 6,000,000 + * reload = 6,000,000 / 100 + * = 60,000 + */ + + prescaler = (priv->pclk / frequency + 65534) / 65535; + if (prescaler < 1) + { + prescaler = 1; + } + else if (prescaler > 65536) + { + prescaler = 65536; + } + + timclk = priv->pclk / prescaler; + + reload = timclk / frequency; + if (reload < 2) + { + reload = 1; + } + else if (reload > 65535) + { + reload = 65535; + } + else + { + reload--; + } + + _info("TIM%u PCLK: %" PRIu32 " frequency: %" PRIu32 + " TIMCLK: %" PRIu32 + " prescaler: %" PRIu32 " reload: %" PRIu32 "\n", + priv->timid, priv->pclk, frequency, timclk, prescaler, reload); + + /* Set the reload and prescaler values */ + + pulsecount_putreg(priv, STM32_GTIM_ARR_OFFSET, reload); + pulsecount_putreg(priv, STM32_GTIM_PSC_OFFSET, (uint16_t)(prescaler - 1)); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_configure + * + * Description: + * Initial configuration for pulsecount timer + * + ****************************************************************************/ + +static int pulsecount_timer_configure(struct stm32_tim_s *priv) +{ + uint16_t cr1 = 0; + + /* Set up the advanced timer CR1 register. */ + + cr1 = pulsecount_getreg(priv, STM32_GTIM_CR1_OFFSET); + + /* Pulsecount always uses edge-aligned up-counting mode. */ + + cr1 &= ~(GTIM_CR1_DIR | GTIM_CR1_CMS_MASK); + cr1 |= GTIM_CR1_EDGE; + cr1 &= ~GTIM_CR1_CKD_MASK; + cr1 |= priv->t_dts << GTIM_CR1_CKD_SHIFT; + + /* Enable ARR preload to preserve the previous pulsecount behavior. */ + + cr1 |= GTIM_CR1_ARPE; + + /* Write CR1 */ + + pulsecount_putreg(priv, STM32_GTIM_CR1_OFFSET, cr1); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_channel_configure + * + * Description: + * Configure pulsecount output compare for a channel + * + ****************************************************************************/ + +static int pulsecount_channel_configure(struct pulsecount_lowerhalf_s *dev, + uint8_t channel) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t chanmode = 0; + uint32_t ocmode = 0; + uint32_t ccmr = 0; + uint32_t offset = 0; + int ret = OK; + + /* Configure output compare mode */ + + chanmode = GTIM_CCMR_MODE_PWM1; + + /* Get CCMR offset */ + + switch (channel) + { + case 1: + case 2: + { + offset = STM32_GTIM_CCMR1_OFFSET; + break; + } + + case 3: + case 4: + { + offset = STM32_GTIM_CCMR2_OFFSET; + break; + } + + default: + { + _err("ERROR: No such channel: %u\n", channel); + ret = -EINVAL; + goto errout; + } + } + + /* Get current registers */ + + ccmr = pulsecount_getreg(priv, offset); + + /* output compare configuration. + * NOTE: The CCMRx registers are identical if the channels are outputs. + */ + + switch (channel) + { + /* Configure channel 1/3 */ + + case 1: + case 3: + { + ccmr &= ~(ATIM_CCMR1_CC1S_MASK | ATIM_CCMR1_OC1M_MASK | + ATIM_CCMR1_OC1PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC1S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC1M_SHIFT); + ocmode |= ATIM_CCMR1_OC1PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC1M); +#endif + break; + } + + /* Configure channel 2/4 */ + + case 2: + case 4: + { + ccmr &= ~(ATIM_CCMR1_CC2S_MASK | ATIM_CCMR1_OC2M_MASK | + ATIM_CCMR1_OC2PE); + ocmode |= (ATIM_CCMR_CCS_CCOUT << ATIM_CCMR1_CC2S_SHIFT); + ocmode |= (chanmode << ATIM_CCMR1_OC2M_SHIFT); + ocmode |= ATIM_CCMR1_OC2PE; +#ifdef HAVE_IP_TIMERS_V2 + ccmr &= ~(ATIM_CCMR1_OC2M); +#endif + break; + } + } + + /* Set the selected output compare configuration */ + + ccmr |= ocmode; + + /* Write CCMRx registers */ + + pulsecount_putreg(priv, offset, ccmr); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_output_configure + * + * Description: + * Configure pulsecount output for given channel + * + ****************************************************************************/ + +static int pulsecount_output_configure(struct stm32_tim_s *priv, + struct stm32_chan_s *chan) +{ + uint32_t cr2 = 0; + uint32_t ccer = 0; + uint8_t channel = 0; + + /* Get channel */ + + channel = chan->channel; + + /* Get current registers state */ + + cr2 = pulsecount_getreg(priv, STM32_GTIM_CR2_OFFSET); + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* | OISx | IDLE | advanced timers | CR2 register + * | CCxP | POL | all pulsecount timers | CCER register + */ + + /* Configure output polarity (all pulsecount timers) */ + + if (chan->out1.pol == PULSECOUNT_POL_NEG) + { + ccer |= (GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + else + { + ccer &= ~(GTIM_CCER_CC1P << ((channel - 1) * 4)); + } + + if (priv->timtype == TIMTYPE_ADVANCED) + { + /* Configure output IDLE State */ + + if (chan->out1.idle == PULSECOUNT_IDLE_ACTIVE) + { + cr2 |= (ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + else + { + cr2 &= ~(ATIM_CR2_OIS1 << ((channel - 1) * 2)); + } + } + + /* Write registers */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR2_OFFSET, 0, cr2); + pulsecount_modifyreg(priv, STM32_GTIM_CCER_OFFSET, 0, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_outputs_enable + * + * Description: + * Enable/disable given timer pulsecount outputs. + * + * NOTE: This is bulk operation - we can enable/disable many outputs + * at one time + * + * Input Parameters: + * dev - A reference to the lower half driver state structure + * outputs - outputs to set (look at enum stm32_pulsecount_chan_e) + * state - Enable/disable operation + * + ****************************************************************************/ + +static int pulsecount_outputs_enable(struct pulsecount_lowerhalf_s *dev, + uint16_t outputs, bool state) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t ccer = 0; + uint32_t regval = 0; + + /* Get current register state */ + + ccer = pulsecount_getreg(priv, STM32_GTIM_CCER_OFFSET); + + /* Get outputs configuration */ + + regval |= ((outputs & (1 << 0)) ? GTIM_CCER_CC1E : 0); + regval |= ((outputs & (1 << 2)) ? GTIM_CCER_CC2E : 0); + regval |= ((outputs & (1 << 4)) ? GTIM_CCER_CC3E : 0); + regval |= ((outputs & (1 << 6)) ? GTIM_CCER_CC4E : 0); + + if (state == true) + { + /* Enable outputs - set bits */ + + ccer |= regval; + } + else + { + /* Disable outputs - reset bits */ + + ccer &= ~regval; + } + + /* Write register */ + + pulsecount_putreg(priv, STM32_GTIM_CCER_OFFSET, ccer); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_moe_enable + ****************************************************************************/ + +static void pulsecount_moe_enable(struct pulsecount_lowerhalf_s *dev, + bool enable) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + if (enable) + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, 0, ATIM_BDTR_MOE); + } + else + { + pulsecount_modifyreg(priv, STM32_ATIM_BDTR_OFFSET, ATIM_BDTR_MOE, 0); + } +} + +/**************************************************************************** + * Name: pulsecount_outputs_from_channels + * + * Description: + * Get enabled outputs configuration from the pulsecount timer state + * + ****************************************************************************/ + +static uint16_t +pulsecount_outputs_from_channels(struct stm32_tim_s *priv, uint8_t selected) +{ + uint16_t outputs = 0; + uint8_t channel; + + channel = priv->channel.channel; + + if (channel != 0 && (selected == 0 || channel == selected) && + priv->channel.out1.in_use == 1) + { + outputs = (1 << ((channel - 1) * 2)); + } + + return outputs; +} + +/**************************************************************************** + * Name: pulsecount_configure + * + * Description: + * Configure pulsecount timer in PULSECOUNT mode + * + ****************************************************************************/ + +static int pulsecount_configure(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t outputs = 0; + int ret = OK; + + UNUSED(priv); + + /* NOTE: leave timer counter disabled and all outputs disabled! */ + + /* Disable the timer until we get it configured */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, GTIM_CR1_CEN, 0); + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, 0); + + /* Disable configured outputs before the timer is reconfigured. */ + + ret = pulsecount_outputs_enable(dev, outputs, false); + if (ret < 0) + { + goto errout; + } + + /* Initial timer configuration */ + + ret = pulsecount_timer_configure(priv); + if (ret < 0) + { + goto errout; + } + + /* Disable software break (enable outputs) */ + + pulsecount_moe_enable(dev, true); + + /* Configure timer channels */ + + if (priv->channel.channel != 0) + { + pulsecount_channel_configure(dev, priv->channel.channel); + pulsecount_output_configure(priv, &priv->channel); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * This split keeps pulsecount as the existing single-channel mode. + * + ****************************************************************************/ + +static int pulsecount_timer(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + ub16_t duty = 0; + uint8_t channel = 0; + uint16_t outputs = 0; + int ret = OK; + + /* If we got here then the timer instance supports pulsecount output. */ + + DEBUGASSERT(priv != NULL && info != NULL); + + _info("TIM%u channel: %u high: %" PRIu32 " ns low: %" PRIu32 + " ns count: %" PRIu32 "\n", + priv->timid, priv->channel.channel, info->high_ns, + info->low_ns, info->count); + + DEBUGASSERT(pulsecount_frequency(info) > 0); + + /* Channel specific setup */ + + duty = pulsecount_duty(info); + channel = priv->channel.channel; + + /* Disable all interrupts and DMA requests, clear all pending status */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Set timer frequency */ + + ret = pulsecount_frequency_update(dev, pulsecount_frequency(info)); + if (ret < 0) + { + goto errout; + } + + /* Update duty cycle */ + + ret = pulsecount_duty_update(dev, channel, duty); + if (ret < 0) + { + goto errout; + } + + /* If a non-zero repetition count has been selected, then set the + * repetition counter to the count-1 (pulsecount_start() has already + * assured us that the count value is within range). + */ + + if (info->count > 0) + { + /* Save the remaining count and the number of counts that will have + * elapsed on the first interrupt. + */ + + /* If the first interrupt occurs at the end end of the first + * repetition count, then the count will be the same as the RCR + * value. + */ + + priv->prev = pulsecount_count(info->count); + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, + (uint16_t)priv->prev - 1); + + /* Generate an update event to reload the prescaler. This should + * preload the RCR into active repetition counter. + */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + + /* Now set the value of the RCR that will be loaded on the next + * update event. + */ + + priv->count = info->count; + priv->curr = pulsecount_count(info->count - priv->prev); + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Otherwise, just clear the repetition counter */ + + else + { + /* Set the repetition counter to zero */ + + pulsecount_putreg(priv, STM32_GTIM_RCR_OFFSET, 0); + + /* Generate an update event to reload the prescaler */ + + pulsecount_putreg(priv, STM32_GTIM_EGR_OFFSET, GTIM_EGR_UG); + } + + /* Get configured outputs */ + + outputs = pulsecount_outputs_from_channels(priv, channel); + + /* Enable output */ + + ret = pulsecount_outputs_enable(dev, outputs, true); + if (ret < 0) + { + goto errout; + } + + /* Setup update interrupt. If info->count is > 0, then we can + * be assured that pulsecount_start() has already verified: (1) that + * this is an advanced timer, and that (2) the repetition count is within + * range. + */ + + if (info->count > 0) + { + /* Clear all pending interrupts and enable the update interrupt. */ + + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); + + /* Enable the timer */ + + pulsecount_modifyreg(priv, STM32_GTIM_CR1_OFFSET, 0, GTIM_CR1_CEN); + + /* And enable timer interrupts at the NVIC */ + + up_enable_irq(priv->irq); + } + + pulsecount_dumpregs(dev, "After starting"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint16_t regval; + + /* Verify that this is an update interrupt. Nothing else is expected. */ + + regval = pulsecount_getreg(priv, STM32_ATIM_SR_OFFSET); + DEBUGASSERT((regval & ATIM_SR_UIF) != 0); + + /* Clear the UIF interrupt bit */ + + pulsecount_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); + + /* Calculate the new count by subtracting the number of pulses + * since the last interrupt. + */ + + if (priv->count <= priv->prev) + { + /* We are finished. Turn off the master output to stop the output as + * quickly as possible. + */ + + pulsecount_moe_enable(dev, false); + + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_ll_stop(dev); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + priv->count = 0; + priv->prev = 0; + priv->curr = 0; + } + else + { + /* Decrement the count of pulses remaining using the number of + * pulses generated since the last interrupt. + */ + + priv->count -= priv->prev; + + /* Set up the next RCR. Set 'prev' to the value of the RCR that + * was loaded when the update occurred (just before this interrupt) + * and set 'curr' to the current value of the RCR register (which + * will bet loaded on the next update event). + */ + + priv->prev = priv->curr; + priv->curr = pulsecount_count(priv->count - priv->prev); + pulsecount_putreg(priv, STM32_ATIM_RCR_OFFSET, + (uint16_t)priv->curr - 1); + } + + /* Now all of the time critical stuff is done so we can do some debug + * output. + */ + + _info("Update interrupt SR: %04" PRIx16 " prev: %u curr: %u" + " count: %" PRIu32 "\n", + regval, (unsigned int)priv->prev, (unsigned int)priv->curr, + priv->count); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_tim1/8interrupt + * + * Description: + * Handle timer 1 and 8 interrupts. + * + * Input Parameters: + * Standard NuttX interrupt inputs + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT +static int pulsecount_tim1interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount1dev); +} +#endif /* CONFIG_STM32H7_TIM1_PULSECOUNT */ + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT +static int pulsecount_tim8interrupt(int irq, void *context, void *arg) +{ + return pulsecount_interrupt((struct pulsecount_lowerhalf_s *) + &g_pulsecount8dev); +} +#endif /* CONFIG_STM32H7_TIM8_PULSECOUNT */ + +/**************************************************************************** + * Name: pulsecount_count + * + * Description: + * Pick an optimal pulse count to program the RCR. + * + * Input Parameters: + * count - The total count remaining + * + * Returned Value: + * The recommended pulse count + * + ****************************************************************************/ + +static uint8_t pulsecount_count(uint32_t count) +{ + /* Use the advanced-timer repetition counter limit. */ + + /* The the remaining pulse count is less than or equal to the maximum, the + * just return the count. + */ + + if (count <= ATIM_RCR_REP_MAX) + { + return (uint8_t)count; + } + + /* Otherwise, we have to be careful. We do not want a small number of + * counts at the end because we might have trouble responding fast enough. + * If the remaining count is less than 150% of the maximum, then return + * half of the maximum. In this case the final sequence will be between 64 + * and 128. + */ + + else if (count < (3 * ATIM_RCR_REP_MAX / 2)) + { + return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); + } + + /* Otherwise, return the maximum. The final count will be 64 or more */ + + else + { + return (uint8_t)ATIM_RCR_REP_MAX; + } +} + +/**************************************************************************** + * Name: pulsecount_set_apb_clock + * + * Description: + * Enable or disable APB clock for the timer peripheral + * + * Input Parameters: + * priv - A reference to the pulsecount block status + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static int pulsecount_set_apb_clock(struct stm32_tim_s *priv, bool on) +{ + uint32_t en_bit = 0; + uint32_t regaddr = 0; + int ret = OK; + + _info("timer %d clock enable: %d\n", priv->timid, on ? 1 : 0); + + /* Determine which timer to configure */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCEN_TIM1; + en_bit = TIMEN_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCEN_TIM8; + en_bit = TIMEN_TIM8; + break; + } +#endif + + default: + { + _err("ERROR: No such timer configured %d\n", priv->timid); + ret = -EINVAL; + goto errout; + } + } + + /* Enable/disable APB 1/2 clock for timer */ + + _info("RCC_APBxENR base: %08" PRIx32 " bits: %04" PRIx32 "\n", + regaddr, en_bit); + + if (on) + { + modifyreg32(regaddr, 0, en_bit); + } + else + { + modifyreg32(regaddr, en_bit, 0); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * APB1 or 2 clocking for the GPIOs has already been configured by the RCC + * logic at power up. + * + ****************************************************************************/ + +static int pulsecount_ll_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Enable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, true); + if (ret < 0) + { + goto errout; + } + + pulsecount_dumpregs(dev, "Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + if (priv->channel.out1.in_use == 1) + { + /* Do not configure the pin if pincfg is not specified. + * This prevents overwriting the PA0 configuration if the + * channel is used internally. + */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + stm32_configgpio(pincfg); + pulsecount_dumpgpio(pincfg, "pulsecount setup"); + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + uint32_t pincfg = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Make sure that the output has been stopped */ + + pulsecount_ll_stop(dev); + + /* Disable APB1/2 clocking for timer. */ + + ret = pulsecount_set_apb_clock(priv, false); + if (ret < 0) + { + goto errout; + } + + /* Then put the GPIO pins back to the default state */ + + pincfg = priv->channel.out1.pincfg; + if (pincfg != 0) + { + _info("pincfg: %08" PRIx32 "\n", pincfg); + + pincfg &= (GPIO_PORT_MASK | GPIO_PIN_MASK); + pincfg |= PINCFG_DEFAULT; + + stm32_configgpio(pincfg); + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_ll_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + irqstate_t flags = 0; + uint32_t resetbit = 0; + uint32_t regaddr = 0; + uint32_t regval = 0; + int ret = OK; + + _info("TIM%u\n", priv->timid); + + /* Determine which timer to reset */ + + switch (priv->timid) + { +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT + case 1: + { + regaddr = TIMRCCRST_TIM1; + resetbit = TIMRST_TIM1; + break; + } +#endif + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT + case 8: + { + regaddr = TIMRCCRST_TIM8; + resetbit = TIMRST_TIM8; + break; + } +#endif + + default: + { + ret = -EINVAL; + goto errout; + } + } + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Disable further interrupts and stop the timer */ + + pulsecount_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_SR_OFFSET, 0); + + /* Reset the timer - stopping the output and putting the timer back + * into a state where pulsecount_start() can be called. + */ + + regval = getreg32(regaddr); + regval |= resetbit; + putreg32(regval, regaddr); + + regval &= ~resetbit; + putreg32(regval, regaddr); + + /* Clear all channels */ + + pulsecount_putreg(priv, STM32_GTIM_CCR1_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR2_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR3_OFFSET, 0); + pulsecount_putreg(priv, STM32_GTIM_CCR4_OFFSET, 0); + + leave_critical_section(flags); + + _info("regaddr: %08" PRIx32 " resetbit: %08" PRIx32 "\n", + regaddr, resetbit); + pulsecount_dumpregs(dev, "After stop"); + +errout: + return ret; +} + +/**************************************************************************** + * Name: pulsecount_ll_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_ll_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + struct stm32_tim_s *priv = (struct stm32_tim_s *)dev; + + /* There are no platform-specific ioctl commands */ + + _info("TIM%u\n", priv->timid); +#endif + return -ENOTTY; +} + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + int ret; + + ret = pulsecount_ll_setup((struct pulsecount_lowerhalf_s *)pulse->timer); + if (ret < 0) + { + return ret; + } + + return pulsecount_configure((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_shutdown((struct pulsecount_lowerhalf_s *) + pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + struct stm32_tim_s *priv = pulse->timer; + + /* Check if a pulsecount has been selected */ + + if (info->count > 0) + { + /* Only the advanced timers (TIM1,8 can support the pulse counting) + */ + + if (priv->timtype != TIMTYPE_ADVANCED) + { + _err("ERROR: TIM%u cannot support pulse count: %" PRIu32 "\n", + priv->timid, info->count); + return -EPERM; + } + } + + /* Save the handle */ + + priv->handle = handle; + + /* Start the time */ + + return pulsecount_timer((struct pulsecount_lowerhalf_s *)priv, info); +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_stop((struct pulsecount_lowerhalf_s *)pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct stm32_pulsecount_s *pulse = (struct stm32_pulsecount_s *)dev; + return pulsecount_ll_ioctl((struct pulsecount_lowerhalf_s *)pulse->timer, + cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer) +{ + struct stm32_pulsecount_s *lower = NULL; + + _info("TIM%u\n", timer); + + switch (timer) + { +#ifdef CONFIG_STM32H7_TIM1_PULSECOUNT + case 1: + { + lower = &g_pulsecount1lower; + irq_attach(lower->timer->irq, pulsecount_tim1interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + +#ifdef CONFIG_STM32H7_TIM8_PULSECOUNT + case 8: + { + lower = &g_pulsecount8lower; + irq_attach(lower->timer->irq, pulsecount_tim8interrupt, NULL); + up_disable_irq(lower->timer->irq); + break; + } +#endif + + default: + { + _err("ERROR: TIM%d does not support pulse count\n", timer); + return NULL; + } + } + + return (struct pulsecount_lowerhalf_s *)lower; +} diff --git a/arch/arm/src/stm32h7/stm32_pulsecount.h b/arch/arm/src/stm32h7/stm32_pulsecount.h new file mode 100644 index 0000000000000..efd73aa4a781d --- /dev/null +++ b/arch/arm/src/stm32h7/stm32_pulsecount.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * arch/arm/src/stm32h7/stm32_pulsecount.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_STM32H7_STM32_PULSECOUNT_H +#define __ARCH_ARM_SRC_STM32H7_STM32_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *stm32_pulsecountinitialize(int timer); + +#endif /* __ARCH_ARM_SRC_STM32H7_STM32_PULSECOUNT_H */ diff --git a/arch/arm/src/stm32h7/stm32_pwm.c b/arch/arm/src/stm32h7/stm32_pwm.c index 10bd501b18997..d2dc3b4f06e8d 100644 --- a/arch/arm/src/stm32h7/stm32_pwm.c +++ b/arch/arm/src/stm32h7/stm32_pwm.c @@ -169,17 +169,6 @@ # undef HAVE_ADVTIM #endif -/* Pulsecount support */ - -#ifdef CONFIG_PWM_PULSECOUNT -# ifndef HAVE_ADVTIM -# error "PWM_PULSECOUNT requires HAVE_ADVTIM" -# endif -# if defined(CONFIG_STM32H7_TIM1_PWM) || defined(CONFIG_STM32H7_TIM8_PWM) -# define HAVE_PWM_INTERRUPT -# endif -#endif - /* Synchronisation support */ #ifdef CONFIG_STM32H7_PWM_TRGO @@ -273,21 +262,11 @@ struct stm32_pwmtimer_s * 4 LSB = TRGO, 4 MSB = TRGO2 */ #endif -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ -#else uint32_t frequency; /* Current frequency setting */ -#endif uint32_t base; /* The base address of the timer */ uint32_t pclk; /* The frequency of the peripheral clock * that drives the timer module. */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -344,42 +323,17 @@ static int pwm_deadtime_update(struct pwm_lowerhalf_s *dev, uint8_t dt); static uint32_t pwm_ccr_get(struct pwm_lowerhalf_s *dev, uint8_t index); #endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev); -#else static int pwm_configure(struct pwm_lowerhalf_s *dev); -#endif -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info); -#else static int pwm_timer(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif -#ifdef HAVE_PWM_INTERRUPT -static int pwm_interrupt(struct pwm_lowerhalf_s *dev); -# ifdef CONFIG_STM32H7_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg); -# endif -# ifdef CONFIG_STM32H7_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg); -# endif -static uint8_t pwm_pulsecount(uint32_t count); -#endif /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int pwm_stop(struct pwm_lowerhalf_s *dev); static int pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -582,9 +536,6 @@ static struct stm32_pwmtimer_s g_pwm1dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM1_TRGO) .trgo = STM32_TIM1_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM1UP, #endif .base = STM32_TIM1_BASE, .pclk = TIMCLK_TIM1, @@ -681,9 +632,6 @@ static struct stm32_pwmtimer_s g_pwm2dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM2_TRGO) .trgo = STM32_TIM2_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM2, #endif .base = STM32_TIM2_BASE, .pclk = TIMCLK_TIM2, @@ -780,9 +728,6 @@ static struct stm32_pwmtimer_s g_pwm3dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM3_TRGO) .trgo = STM32_TIM3_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM3, #endif .base = STM32_TIM3_BASE, .pclk = TIMCLK_TIM3, @@ -879,9 +824,6 @@ static struct stm32_pwmtimer_s g_pwm4dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM4_TRGO) .trgo = STM32_TIM4_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM4, #endif .base = STM32_TIM4_BASE, .pclk = TIMCLK_TIM4, @@ -976,9 +918,6 @@ static struct stm32_pwmtimer_s g_pwm5dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM5_TRGO) .trgo = STM32_TIM5_TRGO -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM5, #endif .base = STM32_TIM5_BASE, .pclk = TIMCLK_TIM5, @@ -1142,9 +1081,6 @@ static struct stm32_pwmtimer_s g_pwm8dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM8_TRGO) .trgo = STM32_TIM8_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM8UP, #endif .base = STM32_TIM8_BASE, .pclk = TIMCLK_TIM8, @@ -1209,9 +1145,6 @@ static struct stm32_pwmtimer_s g_pwm12dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM12 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM12, #endif .base = STM32_TIM12_BASE, .pclk = TIMCLK_TIM12, @@ -1260,9 +1193,6 @@ static struct stm32_pwmtimer_s g_pwm13dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM13 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM13, #endif .base = STM32_TIM13_BASE, .pclk = TIMCLK_TIM13, @@ -1311,9 +1241,6 @@ static struct stm32_pwmtimer_s g_pwm14dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM14 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM14, #endif .base = STM32_TIM14_BASE, .pclk = TIMCLK_TIM14, @@ -1396,9 +1323,6 @@ static struct stm32_pwmtimer_s g_pwm15dev = #endif #if defined(HAVE_TRGO) && defined(STM32_TIM15_TRGO) .trgo = STM32_TIM15_TRGO, -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM15, #endif .base = STM32_TIM15_BASE, .pclk = TIMCLK_TIM15, @@ -1465,9 +1389,6 @@ static struct stm32_pwmtimer_s g_pwm16dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM16 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM16, #endif .base = STM32_TIM16_BASE, .pclk = TIMCLK_TIM16, @@ -1534,9 +1455,6 @@ static struct stm32_pwmtimer_s g_pwm17dev = #endif #if defined(HAVE_TRGO) .trgo = 0, /* TRGO not supported for TIM17 */ -#endif -#ifdef CONFIG_PWM_PULSECOUNT - .irq = STM32_IRQ_TIM17, #endif .base = STM32_TIM17_BASE, .pclk = TIMCLK_TIM17, @@ -2914,259 +2832,11 @@ static int pwm_break_dt_configure(struct stm32_pwmtimer_s *priv) } #endif -#ifdef CONFIG_PWM_PULSECOUNT - -/**************************************************************************** - * Name: pwm_pulsecount_configure - * - * Description: - * Configure PWM timer in PULSECOUNT mode - * - ****************************************************************************/ - -static int pwm_pulsecount_configure(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t outputs = 0; - uint8_t j = 0; - int ret = OK; - - UNUSED(priv); - - /* NOTE: leave timer counter disabled and all outputs disabled! */ - - /* Disable the timer until we get it configured */ - - pwm_timer_enable(dev, false); - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* REVISIT: Disable outputs */ - - ret = pwm_outputs_enable(dev, outputs, false); - if (ret < 0) - { - goto errout; - } - - /* Initial timer configuration */ - - ret = pwm_timer_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Configure break and deadtime register */ - - ret = pwm_break_dt_configure(priv); - if (ret < 0) - { - goto errout; - } - - /* Disable software break (enable outputs) */ - - ret = pwm_soft_break(dev, false); - if (ret < 0) - { - goto errout; - } - -#ifdef HAVE_TRGO - /* Configure TRGO/TRGO2 */ - - ret = pwm_sync_configure(priv, priv->trgo); - if (ret < 0) - { - goto errout; - } -#endif - - /* Configure timer channels */ - - for (j = 0; j < priv->chan_num; j++) - { - /* Skip channel if not in use */ - - if (priv->channels[j].channel != 0) - { - /* Update PWM mode */ - - pwm_mode_configure(dev, priv->channels[j].channel, - priv->channels[j].mode); - - /* PWM outputs configuration */ - - pwm_output_configure(priv, &priv->channels[j]); - } - } - -errout: - return ret; -} - -/**************************************************************************** - * Name: pwm_pulsecount_timer - * - * Description: - * (Re-)initialize the timer resources and start the pulsed output - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * info - A reference to the characteristics of the pulsed output - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - * TODO: PWM_PULSECOUNT should be configurable for each timer instance - * TODO: PULSECOUNT doesn't work with MULTICHAN at this moment - * - ****************************************************************************/ - -static int pwm_pulsecount_timer(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - ub16_t duty = 0; - uint8_t channel = 0; - uint32_t mode = 0; - uint16_t outputs = 0; - int ret = OK; - - /* If we got here it means that timer instance support pulsecount mode! */ - - DEBUGASSERT(priv != NULL && info != NULL); - - pwminfo("TIM%u channel: %u frequency: %u duty: %08x count: %u\n", - priv->timid, priv->channels[0].channel, info->frequency, - info->channels[0].duty, info->channels[0].count); - - DEBUGASSERT(info->frequency > 0); - - /* Channel specific setup */ - - duty = info->channels[0].duty; - channel = priv->channels[0].channel; - mode = priv->channels[0].mode; - - /* Disable all interrupts and DMA requests, clear all pending status */ - - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - - /* Set timer frequency */ - - ret = pwm_frequency_update(dev, info->frequency); - if (ret < 0) - { - goto errout; - } - - /* Update duty cycle */ - - ret = pwm_duty_update(dev, channel, duty); - if (ret < 0) - { - goto errout; - } - - /* If a non-zero repetition count has been selected, then set the - * repetition counter to the count-1 (pwm_pulsecount_start() has already - * assured us that the count value is within range). - */ - - if (info->channels[0].count > 0) - { - /* Save the remaining count and the number of counts that will have - * elapsed on the first interrupt. - */ - - /* If the first interrupt occurs at the end end of the first - * repetition count, then the count will be the same as the RCR - * value. - */ - - priv->prev = pwm_pulsecount(info->channels[0].count); - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, (uint16_t)priv->prev - 1); - - /* Generate an update event to reload the prescaler. This should - * preload the RCR into active repetition counter. - */ - - pwm_soft_update(dev); - - /* Now set the value of the RCR that will be loaded on the next - * update event. - */ - - priv->count = info->channels[0].count; - priv->curr = pwm_pulsecount(info->channels[0].count - priv->prev); - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Otherwise, just clear the repetition counter */ - - else - { - /* Set the repetition counter to zero */ - - pwm_putreg(priv, STM32_GTIM_RCR_OFFSET, 0); - - /* Generate an update event to reload the prescaler */ - - pwm_soft_update(dev); - } - - /* Get configured outputs */ - - outputs = pwm_outputs_from_channels(priv); - - /* Enable output */ - - ret = pwm_outputs_enable(dev, outputs, true); - if (ret < 0) - { - goto errout; - } - - /* Setup update interrupt. If info->channels[0].count is > 0, then we can - * be assured that pwm_pulsecount_start() has already verified: (1) that - * this is an advanced timer, and that (2) the repetition count is within - * range. - */ - - if (info->channels[0].count > 0) - { - /* Clear all pending interrupts and enable the update interrupt. */ - - pwm_putreg(priv, STM32_GTIM_SR_OFFSET, 0); - pwm_putreg(priv, STM32_GTIM_DIER_OFFSET, GTIM_DIER_UIE); - - /* Enable the timer */ - - pwm_timer_enable(dev, true); - - /* And enable timer interrupts at the NVIC */ - - up_enable_irq(priv->irq); - } - - pwm_dumpregs(dev, "After starting"); - -errout: - return ret; -} - -#else /* !CONFIG_PWM_PULSECOUNT */ - /**************************************************************************** * Name: pwm_configure * * Description: - * Configure PWM timer in normal mode (no PULSECOUNT) + * Configure PWM timer in standard mode * ****************************************************************************/ @@ -3439,167 +3109,6 @@ static int pwm_timer(struct pwm_lowerhalf_s *dev, errout: return ret; } -#endif /* CONFIG_PWM_PULSECOUNT */ - -#ifdef HAVE_PWM_INTERRUPT - -/**************************************************************************** - * Name: pwm_interrupt - * - * Description: - * Handle timer interrupts. - * - * Input Parameters: - * dev - A reference to the lower half PWM driver state structure - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -static int pwm_interrupt(struct pwm_lowerhalf_s *dev) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - uint16_t regval; - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32_ATIM_SR_OFFSET, (regval & ~ATIM_SR_UIF)); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the master output to stop the output as - * quickly as possible. - */ - - pwm_soft_break(dev, true); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop(dev); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, (uint16_t)priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output. - */ - - pwminfo("Update interrupt SR: %04x prev: %u curr: %u count: %u\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -} - -/**************************************************************************** - * Name: pwm_tim1/8interrupt - * - * Description: - * Handle timer 1 and 8 interrupts. - * - * Input Parameters: - * Standard NuttX interrupt inputs - * - * Returned Value: - * Zero on success; a negated errno value on failure - * - ****************************************************************************/ - -#ifdef CONFIG_STM32H7_TIM1_PWM -static int pwm_tim1interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm1dev); -} -#endif /* CONFIG_STM32H7_TIM1_PWM */ - -#ifdef CONFIG_STM32H7_TIM8_PWM -static int pwm_tim8interrupt(int irq, void *context, void *arg) -{ - return pwm_interrupt((struct pwm_lowerhalf_s *)&g_pwm8dev); -} -#endif /* CONFIG_STM32H7_TIM8_PWM */ - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* REVISIT: RCR_REP_MAX for GTIM or ATIM ? */ - - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return (uint8_t)count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (uint8_t)((ATIM_RCR_REP_MAX + 1) >> 1); - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return (uint8_t)ATIM_RCR_REP_MAX; - } -} -#endif /* HAVE_PWM_INTERRUPT */ /**************************************************************************** * Name: pwm_set_apb_clock @@ -3846,11 +3355,7 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) * counter, disabled outputs, not configured frequency and duty cycle */ -#ifdef CONFIG_PWM_PULSECOUNT - ret = pwm_pulsecount_configure(dev); -#else ret = pwm_configure(dev); -#endif if (ret < 0) { pwmerr("failed to configure PWM %d\n", priv->timid); @@ -3947,38 +3452,6 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct stm32_pwmtimer_s *priv = (struct stm32_pwmtimer_s *)dev; - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the advanced timers (TIM1,8 can support the pulse counting) - * REVISIT: verify if TIMTYPE_COUNTUP16_N works with it - */ - - if (priv->timtype != TIMTYPE_ADVANCED) - { - pwmerr("ERROR: TIM%u cannot support pulse count: %u\n", - priv->timid, info->channels[0].count); - return -EPERM; - } - } - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_pulsecount_timer(dev, info); -} -#else /* !CONFIG_PWM_PULSECOUNT */ static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -4023,7 +3496,6 @@ static int pwm_start(struct pwm_lowerhalf_s *dev, return ret; } -#endif /* CONFIG_PWM_PULSECOUNT */ /**************************************************************************** * Name: pwm_stop @@ -4180,11 +3652,9 @@ static int pwm_stop(struct pwm_lowerhalf_s *dev) flags = enter_critical_section(); -#ifndef CONFIG_PWM_PULSECOUNT /* Stopped so frequency is zero */ priv->frequency = 0; -#endif /* Disable further interrupts and stop the timer */ @@ -4284,10 +3754,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM1 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim1interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif @@ -4331,10 +3797,6 @@ struct pwm_lowerhalf_s *stm32_pwminitialize(int timer) /* Attach but disable the TIM8 update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_tim8interrupt, NULL); - up_disable_irq(lower->irq); -#endif break; } #endif From 4e303e08f2fc6881e26913cd8c3b9fc0dcf8f3c1 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:17:04 +0200 Subject: [PATCH 09/14] !arch/tlsr82: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/tlsr82/Kconfig | 11 +- arch/arm/src/tlsr82/Make.defs | 4 + arch/arm/src/tlsr82/tlsr82_pulsecount.c | 739 ++++++++++++++++++++++++ arch/arm/src/tlsr82/tlsr82_pulsecount.h | 38 ++ arch/arm/src/tlsr82/tlsr82_pwm.c | 135 ----- arch/arm/src/tlsr82/tlsr82_pwm.h | 2 + 6 files changed, 791 insertions(+), 138 deletions(-) create mode 100644 arch/arm/src/tlsr82/tlsr82_pulsecount.c create mode 100644 arch/arm/src/tlsr82/tlsr82_pulsecount.h diff --git a/arch/arm/src/tlsr82/Kconfig b/arch/arm/src/tlsr82/Kconfig index dfb8d9ddf8f42..4e517223907f4 100644 --- a/arch/arm/src/tlsr82/Kconfig +++ b/arch/arm/src/tlsr82/Kconfig @@ -258,7 +258,6 @@ menuconfig TLSR82_USB menuconfig TLSR82_PWM bool "PWM Configuration" default n - select PWM if TLSR82_PWM @@ -267,42 +266,48 @@ if TLSR82_PWM config TLSR82_PWM0 bool "TLSR82 PWM0 Enable" default n + select PWM config TLSR82_PWM0_PULSECOUNT bool "TLSR82 PWM0 PulseCount Enable" default n - depends on TLSR82_PWM0 - select PWM_PULSECOUNT + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT # PWM1 configuration config TLSR82_PWM1 bool "TLSR82 PWM1 Enable" default n + select PWM # PWM2 configuration config TLSR82_PWM2 bool "TLSR82 PWM2 Enable" default n + select PWM # PWM3 configuration config TLSR82_PWM3 bool "TLSR82 PWM3 Enable" default n + select PWM # PWM4 configuration config TLSR82_PWM4 bool "TLSR82 PWM4 Enable" default n + select PWM # PWM5 configuration config TLSR82_PWM5 bool "TLSR82 PWM5 Enable" default n + select PWM endif diff --git a/arch/arm/src/tlsr82/Make.defs b/arch/arm/src/tlsr82/Make.defs index 704c733eb0edd..cac170780ab24 100644 --- a/arch/arm/src/tlsr82/Make.defs +++ b/arch/arm/src/tlsr82/Make.defs @@ -43,6 +43,10 @@ ifeq ($(CONFIG_TLSR82_PWM),y) CHIP_CSRCS += tlsr82_pwm.c endif +ifeq ($(CONFIG_PULSECOUNT),y) + CHIP_CSRCS += tlsr82_pulsecount.c +endif + ifeq ($(CONFIG_TLSR82_ADC),y) CHIP_CSRCS += tlsr82_adc.c endif diff --git a/arch/arm/src/tlsr82/tlsr82_pulsecount.c b/arch/arm/src/tlsr82/tlsr82_pulsecount.c new file mode 100644 index 0000000000000..1e13ddec51e48 --- /dev/null +++ b/arch/arm/src/tlsr82/tlsr82_pulsecount.c @@ -0,0 +1,739 @@ +/**************************************************************************** + * arch/arm/src/tlsr82/tlsr82_pulsecount.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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "arm_internal.h" + +#include "chip.h" +#include "tlsr82_pwm.h" +#include "tlsr82_pulsecount.h" +#include "tlsr82_gpio.h" +#include "tlsr82_gpio_cfg.h" + +#include "hardware/tlsr82_pwm.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The PWM peripheral clock source is the system clock. */ + +#define PWM_SRC_CLK_HZ (CONFIG_TLSR82_CPU_CLK_MHZ * 1000000) + +/* The default clkdiv = 9, default PWM clock = PWM_SRC_CLK_HZ / 10. */ + +#define PWM_INIT_CLKDIV 9 + +/* Make sure the max frequency has at least 1% duty accuracy. */ + +#define PWM_MAX_FREQ (g_pulsecountclk / 100) +#define PWM_MIN_FREQ (g_pulsecountclk / UINT16_MAX + 1) + +/* PWM peripheral max pulse count number, bit 0 ~ 13. */ + +#define PWM_MAX_COUNT (0x3fff) + +/* PWM simple operation definition */ + +#define PWM_INT_PEND(id) BM_IS_SET(PWM_IRQ_STA_REG, 1 << ((id) + 2)) +#define PWM_INT_CLEAR(id) BM_CLR(PWM_IRQ_STA_REG, 1 << ((id) + 2)) +#define PWM_INT_ENABLE(id) BM_SET(PWM_IRQ_CTRL_REG, 1 << ((id) + 2)) +#define PWM_INT_DISABLE(id) BM_CLR(PWM_IRQ_CTRL_REG, 1 << ((id) + 2)) + +#define PWM0_PNUM_INT_PEND() BM_IS_SET(PWM_IRQ_STA_REG, 1 << 0) +#define PWM0_PNUM_INT_CLEAR() BM_CLR(PWM_IRQ_STA_REG, 1 << 0) +#define PWM0_PNUM_INT_ENABLE() BM_SET(PWM_IRQ_CTRL_REG, 1 << 0) +#define PWM0_PNUM_INT_ISENABLE() BM_IS_SET(PWM_IRQ_CTRL_REG, 1 << 0) +#define PWM0_PNUM_INT_DISABLE() BM_CLR(PWM_IRQ_CTRL_REG, 1 << 0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure represents the state of one pulsecount timer */ + +struct tlsr82_pulsecounttimer_s +{ + uint32_t pincfg; /* PWM peripheral pin config */ + uint8_t id; /* PWM peripheral channel id */ + bool invert; /* Pulsecount output is inverted or not */ + bool started; /* Pulsecount output has started or not */ + uint8_t irq; /* Timer update IRQ */ + uint32_t count; /* Remaining pulse count */ + uint32_t frequency; /* Current frequency setting */ + uint16_t max; + uint16_t cmp; + void *handle; /* Handle used for upper-half callback */ +}; + +struct tlsr82_pulsecount_s +{ + const struct pulsecount_ops_s *ops; + struct tlsr82_pulsecounttimer_s *timer; +}; + +/**************************************************************************** + * Static Function Prototypes + ****************************************************************************/ + +/* Register access */ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(const char *msg); +#else +# define pulsecount_dumpregs(msg) +#endif + +/* Timer management */ + +static void pulsecount_enable(struct tlsr82_pulsecounttimer_s *priv, + bool enable); +static int pulsecount_cfg_check(struct tlsr82_pulsecounttimer_s *priv); +static int pulsecount_config(struct tlsr82_pulsecounttimer_s *priv, + const struct pulsecount_info_s *info); + +static int pulsecount_interrupt(int irq, void *context, void *arg); + +/* Pulsecount timer methods */ + +static int pulsecount_timer_setup(struct tlsr82_pulsecounttimer_s *priv); +static int pulsecount_timer_shutdown(struct tlsr82_pulsecounttimer_s *priv); + +static int pulsecount_timer_stop(struct tlsr82_pulsecounttimer_s *priv); +static int pulsecount_timer_ioctl(struct tlsr82_pulsecounttimer_s *priv, + int cmd, unsigned long arg); + +/* Pulsecount driver methods */ + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static uint32_t g_pulsecountclk; + +static struct tlsr82_pulsecounttimer_s g_pulsecount0dev = +{ + .pincfg = BOARD_PWM0_PIN, + .id = 0, + .invert = false, + .started = false, + .irq = NR_SW_PWM_IRQ, +}; + +static const struct pulsecount_ops_s g_pulsecountops = +{ + .setup = pulsecount_setup, + .shutdown = pulsecount_shutdown, + .start = pulsecount_start, + .stop = pulsecount_stop, + .ioctl = pulsecount_ioctl, +}; + +static struct tlsr82_pulsecount_s g_pulsecount0lower = +{ + .ops = &g_pulsecountops, + .timer = &g_pulsecount0dev, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pulsecount_dumpregs + * + * Description: + * Dump all PWM peripheral registers. + * + * Input Parameters: + * msg - message. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEBUG_TIMER_INFO +static void pulsecount_dumpregs(const char *msg) +{ + int i; + _info("%s:\n", msg); + _info("PWM_CLKDIV_REG : 0x%x\n", PWM_CLKDIV_REG); + _info("PWM_ENABLE0_REG : 0x%x\n", PWM_ENABLE0_REG); + _info("PWM_MODE0_REG : 0x%x\n", PWM_MODE0_REG); + _info("PWM_ENABLE_REG : 0x%x\n", PWM_ENABLE_REG); + _info("PWM_INVERT_REG : 0x%x\n", PWM_INVERT_REG); + _info("PWM_N_INVERT_REG : 0x%x\n", PWM_N_INVERT_REG); + _info("PWM_POL_REG : 0x%x\n", PWM_POL_REG); + + for (i = 0; i < 5; i++) + { + _info("PWM_CYCLE_REG(%d): 0x%lx\n", i, PWM_CYCLE_REG(i)); + _info("PWM_CMP_REG(%d) : 0x%x\n", i, PWM_CMP_REG(i)); + _info("PWM_MAX_REG(%d) : 0x%x\n", i, PWM_MAX_REG(i)); + } + + _info("PWM_PLUSE_NUM_REG : 0x%x\n", PWM_PLUSE_NUM_REG); + _info("PWM_IRQ_CTRL_REG : 0x%x\n", PWM_IRQ_CTRL_REG); + _info("PWM_IRQ_STA_REG : 0x%x\n", PWM_IRQ_STA_REG); +} +#endif + +/**************************************************************************** + * Name: pulsecount_config + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * priv - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_config(struct tlsr82_pulsecounttimer_s *priv, + const struct pulsecount_info_s *info) +{ + /* Calculated values */ + + uint16_t max; + uint16_t cmp; + + if (priv == NULL || info == NULL) + { + _err("priv or info is null, priv=%p info=%p\n", priv, info); + return -EINVAL; + } + + _info("PWM%d invert: %d high: %" PRIu32 " ns low: %" PRIu32 " ns\n", + (int)priv->id, (int)priv->invert, info->high_ns, + info->low_ns); + + if (pulsecount_frequency(info) == 0) + { + _err("ERROR: pulsecount period is too long, high: %" PRIu32 + " ns low: %" PRIu32 " ns\n", info->high_ns, info->low_ns); + return -EINVAL; + } + + if (pulsecount_frequency(info) > PWM_MAX_FREQ || + pulsecount_frequency(info) < PWM_MIN_FREQ) + { + _err("ERROR: pulsecount frequency out of range, " + "freq: %ld, max: %ld, min: %ld\n", + pulsecount_frequency(info), PWM_MAX_FREQ, PWM_MIN_FREQ); + return -EINVAL; + } + + /* Calculate the MAX and CMP register value + * period = max_reg / g_pulsecountclk + * freq = g_pulsecountclk / max_reg + */ + + max = (uint16_t)((g_pulsecountclk / pulsecount_frequency(info))); + + cmp = (uint16_t)(((uint64_t)max * + (uint64_t)pulsecount_duty(info)) >> 16); + + _info("PWM%d PCLK: %lu freq: %lu duty: %lu max: %u cmp: %u\n", + priv->id, g_pulsecountclk, pulsecount_frequency(info), + pulsecount_duty(info), max, cmp); + + priv->max = max; + priv->cmp = cmp; + + PWM_MAX_REG(priv->id) = priv->max; + PWM_CMP_REG(priv->id) = priv->cmp; + + if (priv->count > 0) + { + PWM_MODE0_REG = PWM_MODE0_COUNT; + PWM_PLUSE_NUM_REG = priv->count; + PWM0_PNUM_INT_ENABLE(); + up_enable_irq(priv->irq); + } + else + { + PWM_MODE0_REG = PWM_MODE0_NORMAL; + } + + pulsecount_enable(priv, true); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_interrupt + * + * Description: + * Handle timer interrupts. + * + * Input Parameters: + * priv - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_interrupt(int irq, void *context, void *arg) +{ + struct tlsr82_pulsecounttimer_s *priv = + (struct tlsr82_pulsecounttimer_s *)arg; + + if (PWM0_PNUM_INT_PEND() && PWM0_PNUM_INT_ISENABLE()) + { + /* Disable first interrupts, stop and reset the timer */ + + pulsecount_enable(priv, false); + + /* Disable PWMn interrupt and clear interrupt flag */ + + PWM_INT_DISABLE(priv->id); + PWM_INT_CLEAR(priv->id); + + /* Then perform the callback into the upper half driver */ + + pulsecount_expired(priv->handle); + + priv->handle = NULL; + } + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_enable + * + * Description: + * Enable or disable pulse output. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * en - Enable clock if 'en' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static void pulsecount_enable(struct tlsr82_pulsecounttimer_s *priv, bool en) +{ + int id = (int)priv->id; + + if (en) + { + if (priv->started) + { + return; + } + + if (id == 0) + { + BM_SET(PWM_ENABLE0_REG, 1); + } + else + { + BM_SET(PWM_ENABLE_REG, 1 << id); + } + + priv->started = true; + } + else + { + if (!priv->started) + { + return; + } + + if (id == 0) + { + BM_CLR(PWM_ENABLE0_REG, 1); + } + else + { + BM_CLR(PWM_ENABLE_REG, 1 << id); + } + + priv->started = false; + } +} + +/**************************************************************************** + * Name: pulsecount_cfg_check + * + * Description: + * This method is called when the driver initialize. This function will + * Check the pincfg. If pincfg is not valid or the current pin can not be + * used as a PWM peripheral output, this function will call PANIC(). + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * + ****************************************************************************/ + +static int pulsecount_cfg_check(struct tlsr82_pulsecounttimer_s *priv) +{ + uint32_t pincfg = priv->pincfg; + + if (pincfg == GPIO_INVLD_CFG) + { + _err("pulsecount pincfg is not valid, pincfg=0x%lx\n", pincfg); + PANIC(); + return -EINVAL; + } + + if (tlsr82_gpio_cfg_check(pincfg, TLSR82_MUX_PWM) != 0) + { + _err("pincfg=0x%lx does not support pulsecount mux\n", pincfg); + PANIC(); + return -EINVAL; + } + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * should configure and initialize the device so that it is ready for use. + * It should not, however, output pulses until the start method is called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_timer_setup(struct tlsr82_pulsecounttimer_s *priv) +{ + uint32_t pincfg; + int ret = OK; + + pulsecount_dumpregs("Initially"); + + /* Configure the pulsecount output pins, but do not start the timer yet */ + + pincfg = priv->pincfg; + + _info("pincfg: %08lx\n", pincfg); + + /* Set the complementary output inversion before GPIO configuration. This + * keeps the output state stable while the GPIO is configured. + */ + + if (priv->invert) + { + BM_SET(PWM_N_INVERT_REG, 1 << priv->id); + } + + if (priv->id == 0) + { + ret = irq_attach(priv->irq, pulsecount_interrupt, priv); + if (ret < 0) + { + _err("ERROR: PWM0 pulsecount interrupt attach failed, ret=%d\n", + ret); + return ret; + } + } + + tlsr82_gpioconfig(pincfg); + + return ret; +} + +/**************************************************************************** + * Name: pulsecount_timer_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_timer_shutdown(struct tlsr82_pulsecounttimer_s *priv) +{ + /* Make sure that the output has been stopped */ + + pulsecount_timer_stop(priv); + + /* Then put the GPIO pins back to the default state */ + + tlsr82_gpiounconfig(priv->pincfg); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int pulsecount_timer_stop(struct tlsr82_pulsecounttimer_s *priv) +{ + irqstate_t flags; + + _info("PWM%u\n", priv->id); + + /* Disable interrupts momentary to stop any ongoing timer processing and + * to prevent any concurrent access to the reset register. + */ + + flags = enter_critical_section(); + + /* Disable PWMn output */ + + pulsecount_enable(priv, false); + + /* Disable PWMn interrupt and clear interrupt flag */ + + PWM_INT_DISABLE(priv->id); + PWM_INT_CLEAR(priv->id); + + /* Disable and clear PWM0 count interrupt flag */ + + if (priv->count > 0) + { + priv->count = 0; + up_disable_irq(priv->irq); + PWM0_PNUM_INT_DISABLE(); + PWM0_PNUM_INT_CLEAR(); + } + + leave_critical_section(flags); + + pulsecount_dumpregs("After stop"); + + return OK; +} + +/**************************************************************************** + * Name: pulsecount_timer_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int pulsecount_timer_ioctl(struct tlsr82_pulsecounttimer_s *priv, + int cmd, unsigned long arg) +{ +#ifdef CONFIG_DEBUG_TIMER_INFO + /* There are no platform-specific ioctl commands */ + + _info("PWM%d\n", priv->id); +#endif + return -ENOTTY; +} + +static void tlsr82_pulsecount_hwinitialize(void) +{ + static bool pulsecount_initialized = false; + + if (!pulsecount_initialized) + { + pulsecount_initialized = true; + + /* Disable all the output */ + + BM_CLR(PWM_ENABLE0_REG, 0xff); + BM_CLR(PWM_ENABLE_REG, 0xff); + + /* Configure the PWM peripheral clock. */ + + g_pulsecountclk = PWM_SRC_CLK_HZ / (PWM_INIT_CLKDIV + 1); + PWM_CLKDIV_REG = PWM_INIT_CLKDIV; + + /* Disable and clear all PWM peripheral interrupts. */ + + BM_CLR(PWM_IRQ_CTRL_REG, 0xff); + BM_SET(PWM_IRQ_STA_REG, 0xff); + + /* Enable the shared PWM peripheral interrupt. */ + + up_enable_irq(NR_SW_PWM_IRQ); + } +} + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct tlsr82_pulsecount_s *pulse = (struct tlsr82_pulsecount_s *)dev; + return pulsecount_timer_setup(pulse->timer); +} + +static int pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct tlsr82_pulsecount_s *pulse = (struct tlsr82_pulsecount_s *)dev; + return pulsecount_timer_shutdown(pulse->timer); +} + +static int pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct tlsr82_pulsecount_s *pulse = (struct tlsr82_pulsecount_s *)dev; + struct tlsr82_pulsecounttimer_s *priv = pulse->timer; + int ret; + + if (priv->id != 0) + { + _err("ERROR: PWM%d cannot support pulse count: %" PRIu32 "\n", + priv->id, info->count); + return -EPERM; + } + + if (info->count == 0 || info->count > PWM_MAX_COUNT) + { + _err("ERROR: PWM0 count out of range, count: %" PRIu32 + ", max: %d\n", info->count, PWM_MAX_COUNT); + return -EINVAL; + } + + priv->count = info->count; + priv->handle = handle; + + ret = pulsecount_config(priv, info); + if (ret == OK) + { + priv->frequency = pulsecount_frequency(info); + } + + return ret; +} + +static int pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct tlsr82_pulsecount_s *pulse = (struct tlsr82_pulsecount_s *)dev; + return pulsecount_timer_stop(pulse->timer); +} + +static int pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg) +{ + struct tlsr82_pulsecount_s *pulse = (struct tlsr82_pulsecount_s *)dev; + return pulsecount_timer_ioctl(pulse->timer, cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int tlsr82_pulsecountinitialize(const char *devpath, int minor) +{ + struct tlsr82_pulsecount_s *lower; + int ret; + + tlsr82_pulsecount_hwinitialize(); + + if (minor != 0) + { + _err("ERROR: PWM%d cannot support pulse count\n", minor); + return -EINVAL; + } + + lower = &g_pulsecount0lower; + + ret = pulsecount_cfg_check(lower->timer); + if (ret < 0) + { + return ret; + } + + ret = pulsecount_register(devpath, + (struct pulsecount_lowerhalf_s *)lower); + if (ret < 0) + { + _err("%s has existed\n", devpath); + return ret; + } + + pulsecount_dumpregs("tlsr82_pulsecountinitialize"); + return ret; +} diff --git a/arch/arm/src/tlsr82/tlsr82_pulsecount.h b/arch/arm/src/tlsr82/tlsr82_pulsecount.h new file mode 100644 index 0000000000000..18588c899b991 --- /dev/null +++ b/arch/arm/src/tlsr82/tlsr82_pulsecount.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * arch/arm/src/tlsr82/tlsr82_pulsecount.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_TLSR82_TLSR82_PULSECOUNT_H +#define __ARCH_ARM_SRC_TLSR82_TLSR82_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int tlsr82_pulsecountinitialize(const char *devpath, int minor); + +#endif /* __ARCH_ARM_SRC_TLSR82_TLSR82_PULSECOUNT_H */ diff --git a/arch/arm/src/tlsr82/tlsr82_pwm.c b/arch/arm/src/tlsr82/tlsr82_pwm.c index f4d8f9fcb07a7..e5c8afb06f0c8 100644 --- a/arch/arm/src/tlsr82/tlsr82_pwm.c +++ b/arch/arm/src/tlsr82/tlsr82_pwm.c @@ -62,23 +62,11 @@ #define PWM_MAX_FREQ (g_pwmclk / 100) #define PWM_MIN_FREQ (g_pwmclk / UINT16_MAX + 1) -/* PWM max pulse count number, bit 0 ~ 13 */ - -#define PWM_MAX_COUNT (0x3fff) - -/* PWM simple operation definition */ - #define PWM_INT_PEND(id) BM_IS_SET(PWM_IRQ_STA_REG, 1 << ((id) + 2)) #define PWM_INT_CLEAR(id) BM_CLR(PWM_IRQ_STA_REG, 1 << ((id) + 2)) #define PWM_INT_ENABLE(id) BM_SET(PWM_IRQ_CTRL_REG, 1 << ((id) + 2)) #define PWM_INT_DISABLE(id) BM_CLR(PWM_IRQ_CTRL_REG, 1 << ((id) + 2)) -#define PWM0_PNUM_INT_PEND() BM_IS_SET(PWM_IRQ_STA_REG, 1 << 0) -#define PWM0_PNUM_INT_CLEAR() BM_CLR(PWM_IRQ_STA_REG, 1 << 0) -#define PWM0_PNUM_INT_ENABLE() BM_SET(PWM_IRQ_CTRL_REG, 1 << 0) -#define PWM0_PNUM_INT_ISENABLE() BM_IS_SET(PWM_IRQ_CTRL_REG, 1 << 0) -#define PWM0_PNUM_INT_DISABLE() BM_CLR(PWM_IRQ_CTRL_REG, 1 << 0) - /**************************************************************************** * Private Types ****************************************************************************/ @@ -92,16 +80,9 @@ struct tlsr82_pwmtimer_s uint8_t id; /* PWM hardware id */ bool invert; /* PWM output is inverted or not */ bool started; /* PWM output has started or not */ -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint32_t count; /* Remaining pulse count */ -#endif uint32_t frequency; /* Current frequency setting */ uint16_t max; uint16_t cmp; -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -123,23 +104,13 @@ static int pwm_cfg_check(struct tlsr82_pwmtimer_s *priv); static int pwm_config(struct tlsr82_pwmtimer_s *priv, const struct pwm_info_s *info); -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT -static int pwm_interrupt(int irq, void *context, void *arg); -#endif - /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int pwm_stop(struct pwm_lowerhalf_s *dev); static int pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -172,9 +143,6 @@ static struct tlsr82_pwmtimer_s g_pwm0dev = .id = 0, .invert = false, .started = false, -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - .irq = NR_SW_PWM_IRQ, -#endif }; #endif @@ -343,20 +311,6 @@ static int pwm_config(struct tlsr82_pwmtimer_s *priv, PWM_MAX_REG(priv->id) = priv->max; PWM_CMP_REG(priv->id) = priv->cmp; -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - if (priv->count > 0) - { - PWM_MODE0_REG = PWM_MODE0_COUNT; - PWM_PLUSE_NUM_REG = priv->count; - PWM0_PNUM_INT_ENABLE(); - up_enable_irq(priv->irq); - } - else - { - PWM_MODE0_REG = PWM_MODE0_NORMAL; - } -#endif - pwm_enable(priv, true); return OK; @@ -376,33 +330,6 @@ static int pwm_config(struct tlsr82_pwmtimer_s *priv, * ****************************************************************************/ -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT -static int pwm_interrupt(int irq, void *context, void *arg) -{ - struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)arg; - - if (PWM0_PNUM_INT_PEND() && PWM0_PNUM_INT_ISENABLE()) - { - /* Disable first interrupts, stop and reset the timer */ - - pwm_enable(priv, false); - - /* Disable PWMn interrupt and clear interrupt flag */ - - PWM_INT_DISABLE(priv->id); - PWM_INT_CLEAR(priv->id); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - } - - return OK; -} -#endif - /**************************************************************************** * Name: pwm_enable * @@ -537,19 +464,6 @@ static int pwm_setup(struct pwm_lowerhalf_s *dev) BM_SET(PWM_N_INVERT_REG, 1 << priv->id); } -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - if (priv->id == 0) - { - ret = irq_attach(priv->irq, pwm_interrupt, dev); - if (ret < 0) - { - pwmerr("ERROR: PWM0 plusecount interrupt attach failed, ret=%d\n", - ret); - return ret; - } - } -#endif - tlsr82_gpioconfig(pincfg); return ret; @@ -601,49 +515,12 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) -#endif { int ret = OK; struct tlsr82_pwmtimer_s *priv = (struct tlsr82_pwmtimer_s *)dev; -#ifdef CONFIG_PWM_PULSECOUNT - - /* Check if a pulsecount has been selected */ - - if (info->channels[0].count > 0) - { - /* Only the PWM0 support the pulse counting */ - - if (priv->id != 0) - { - pwmerr("ERROR: PWM%d cannot support pulse count: %lu\n", - priv->id, info->channels[0].count); - return -EPERM; - } - - if (info->channels[0].count > PWM_MAX_COUNT) - { - pwmerr("ERROR: PWM0 count out of range, count: %lu, max: %d", - info->channels[0].count, PWM_MAX_COUNT); - return -EINVAL; - } - - priv->count = info->channels[0].count; - } - - /* Save the handle */ - - priv->handle = handle; - -#endif - /* Config the PWM */ ret = pwm_config(priv, info); @@ -699,18 +576,6 @@ static int pwm_stop(struct pwm_lowerhalf_s *dev) PWM_INT_DISABLE(priv->id); PWM_INT_CLEAR(priv->id); -#ifdef CONFIG_TLSR82_PWM0_PULSECOUNT - /* Disable and clear PWM0 count interrupt flag */ - - if (priv->count > 0) - { - priv->count = 0; - up_disable_irq(priv->irq); - PWM0_PNUM_INT_DISABLE(); - PWM0_PNUM_INT_CLEAR(); - } -#endif - leave_critical_section(flags); pwm_dumpregs("After stop"); diff --git a/arch/arm/src/tlsr82/tlsr82_pwm.h b/arch/arm/src/tlsr82/tlsr82_pwm.h index 9544b71214b1c..f2d3525e3e6c5 100644 --- a/arch/arm/src/tlsr82/tlsr82_pwm.h +++ b/arch/arm/src/tlsr82/tlsr82_pwm.h @@ -75,7 +75,9 @@ extern "C" * Public Function Prototypes ****************************************************************************/ +#ifdef CONFIG_PWM int tlsr82_pwminitialize(const char *devpath, int minor); +#endif #undef EXTERN #if defined(__cplusplus) From 0870a8e5cbacf8ac1cd15a20e1168e1fa3062161 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:17:10 +0200 Subject: [PATCH 10/14] !arch/tivia: separate pulse count from PWM driver BREAKING CHANGE: separate pulse count from PWM driver Pulse count handling was removed from PWM driver and moved to a separate driver. For details about this change, look at previous commit. Signed-off-by: raiden00pl --- arch/arm/src/tiva/Kconfig | 48 ++ arch/arm/src/tiva/Make.defs | 6 + arch/arm/src/tiva/common/CMakeLists.txt | 4 + arch/arm/src/tiva/common/tiva_pulsecount.c | 834 +++++++++++++++++++++ arch/arm/src/tiva/common/tiva_pwm.c | 264 ------- arch/arm/src/tiva/tiva_pulsecount.h | 38 + 6 files changed, 930 insertions(+), 264 deletions(-) create mode 100644 arch/arm/src/tiva/common/tiva_pulsecount.c create mode 100644 arch/arm/src/tiva/tiva_pulsecount.h diff --git a/arch/arm/src/tiva/Kconfig b/arch/arm/src/tiva/Kconfig index e8c89b7b11273..78ce2f833e761 100644 --- a/arch/arm/src/tiva/Kconfig +++ b/arch/arm/src/tiva/Kconfig @@ -593,6 +593,18 @@ config TIVA_TIMER bool default n +config TIVA_PWM + bool + default n + +config TIVA_PULSECOUNT + bool "PWM pulse count" + default n + select ARCH_HAVE_PULSECOUNT + select PULSECOUNT + ---help--- + Enables the Tiva PWM peripheral pulse count lower-half driver. + # Peripheral Selections config TIVA_ADC0 @@ -606,6 +618,42 @@ config TIVA_ADC1 depends on TIVA_HAVE_ADC0 select TIVA_ADC +if TIVA_PULSECOUNT + +config TIVA_PULSECOUNT0_CHAN0 + bool "Pulse count 0 channel 0" + default n + +config TIVA_PULSECOUNT0_CHAN1 + bool "Pulse count 0 channel 1" + default n + +config TIVA_PULSECOUNT0_CHAN2 + bool "Pulse count 0 channel 2" + default n + +config TIVA_PULSECOUNT0_CHAN3 + bool "Pulse count 0 channel 3" + default n + +config TIVA_PULSECOUNT0_CHAN4 + bool "Pulse count 0 channel 4" + default n + +config TIVA_PULSECOUNT0_CHAN5 + bool "Pulse count 0 channel 5" + default n + +config TIVA_PULSECOUNT0_CHAN6 + bool "Pulse count 0 channel 6" + default n + +config TIVA_PULSECOUNT0_CHAN7 + bool "Pulse count 0 channel 7" + default n + +endif # TIVA_PULSECOUNT + config TIVA_CAN0 bool "CAN0" default n diff --git a/arch/arm/src/tiva/Make.defs b/arch/arm/src/tiva/Make.defs index 914850234fa39..d8a9efa595d85 100644 --- a/arch/arm/src/tiva/Make.defs +++ b/arch/arm/src/tiva/Make.defs @@ -77,6 +77,12 @@ ifeq ($(CONFIG_TIVA_PWM),y) CHIP_CSRCS += tiva_pwm.c endif +ifeq ($(CONFIG_TIVA_PULSECOUNT),y) +ifeq ($(CONFIG_PULSECOUNT),y) + CHIP_CSRCS += tiva_pulsecount.c +endif +endif + ifeq ($(CONFIG_TIVA_QEI),y) CHIP_CSRCS += tiva_qencoder.c endif diff --git a/arch/arm/src/tiva/common/CMakeLists.txt b/arch/arm/src/tiva/common/CMakeLists.txt index 47c5790f82bcf..c7d938f9cbb6f 100644 --- a/arch/arm/src/tiva/common/CMakeLists.txt +++ b/arch/arm/src/tiva/common/CMakeLists.txt @@ -64,6 +64,10 @@ if(CONFIG_TIVA_PWM) list(APPEND SRCS tiva_pwm.c) endif() +if(CONFIG_TIVA_PULSECOUNT AND CONFIG_PULSECOUNT) + list(APPEND SRCS tiva_pulsecount.c) +endif() + if(CONFIG_TIVA_QEI) list(APPEND SRCS tiva_qencoder.c) endif() diff --git a/arch/arm/src/tiva/common/tiva_pulsecount.c b/arch/arm/src/tiva/common/tiva_pulsecount.c new file mode 100644 index 0000000000000..5ec7401b57873 --- /dev/null +++ b/arch/arm/src/tiva/common/tiva_pulsecount.c @@ -0,0 +1,834 @@ +/**************************************************************************** + * arch/arm/src/tiva/common/tiva_pulsecount.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 + +#include +#include +#include + +#include + +#include "arm_internal.h" +#include "tiva_gpio.h" +#include "tiva_pulsecount.h" +#include "tiva_enablepwr.h" +#include "tiva_enableclks.h" + +#include "hardware/tiva_pwm.h" +#include "hardware/tiva_pinmap.h" +#include "hardware/tiva_memorymap.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +uint32_t g_pulsecount_pinset[] = +{ + GPIO_M0_PWM0, + GPIO_M0_PWM1, + GPIO_M0_PWM2, + GPIO_M0_PWM3, + GPIO_M0_PWM4, + GPIO_M0_PWM5, + GPIO_M0_PWM6, + GPIO_M0_PWM7, +}; + +struct tiva_pulsecount_chan_s +{ + const struct pulsecount_ops_s *ops; + uint8_t controller_id; + uintptr_t controller_base; + uint8_t generator_id; + uintptr_t generator_base; + uint8_t channel_id; + bool inited; + uint8_t irq; + uint32_t count; + uint32_t cur_count; + void *handle; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN0 +static int tiva_pulsecount_gen0_interrupt(int irq, + void *context, void *arg); +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN2 +static int tiva_pulsecount_gen1_interrupt(int irq, + void *context, void *arg); +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN4 +static int tiva_pulsecount_gen2_interrupt(int irq, + void *context, void *arg); +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN6 +static int tiva_pulsecount_gen3_interrupt(int irq, + void *context, void *arg); +#endif + +#if defined(CONFIG_TIVA_PULSECOUNT0_CHAN0) || defined(CONFIG_TIVA_PULSECOUNT0_CHAN2) || \ + defined(CONFIG_TIVA_PULSECOUNT0_CHAN4) || defined(CONFIG_TIVA_PULSECOUNT0_CHAN6) +static int tiva_pulsecount_interrupt(struct tiva_pulsecount_chan_s *chan); +#endif + +static inline void +tiva_pulsecount_putreg(struct tiva_pulsecount_chan_s *chan, + unsigned int offset, uint32_t regval); +static inline uint32_t +tiva_pulsecount_getreg(struct tiva_pulsecount_chan_s *chan, + unsigned int offset); +static inline int tiva_pulsecount_timer(struct tiva_pulsecount_chan_s *chan, + const struct pulsecount_info_s *info); + +static int tiva_pulsecount_setup(struct pulsecount_lowerhalf_s *dev); +static int tiva_pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev); +static int tiva_pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle); +static int tiva_pulsecount_stop(struct pulsecount_lowerhalf_s *dev); +static int tiva_pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, + int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static uint32_t g_pulsecount_freq = 1875000; +static uint32_t g_pulsecount_counter = (1 << 16); + +static const struct pulsecount_ops_s g_pulsecount_ops = +{ + .setup = tiva_pulsecount_setup, + .shutdown = tiva_pulsecount_shutdown, + .start = tiva_pulsecount_start, + .stop = tiva_pulsecount_stop, + .ioctl = tiva_pulsecount_ioctl, +}; + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN0 +static struct tiva_pulsecount_chan_s g_pulsecount_chan0 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 0, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 0, + .channel_id = 0, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN0, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN1 +static struct tiva_pulsecount_chan_s g_pulsecount_chan1 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 0, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 0, + .channel_id = 1, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN0, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN2 +static struct tiva_pulsecount_chan_s g_pulsecount_chan2 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 1, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 1, + .channel_id = 2, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN1, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN3 +static struct tiva_pulsecount_chan_s g_pulsecount_chan3 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 1, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 1, + .channel_id = 3, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN1, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN4 +static struct tiva_pulsecount_chan_s g_pulsecount_chan4 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 2, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 2, + .channel_id = 4, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN2, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN5 +static struct tiva_pulsecount_chan_s g_pulsecount_chan5 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 2, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 2, + .channel_id = 5, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN2, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN6 +static struct tiva_pulsecount_chan_s g_pulsecount_chan6 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 3, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 3, + .channel_id = 6, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN3, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN7 +static struct tiva_pulsecount_chan_s g_pulsecount_chan7 = +{ + .ops = &g_pulsecount_ops, + .controller_id = 0, + .controller_base = TIVA_PWM0_BASE, + .generator_id = 3, + .generator_base = TIVA_PWM0_BASE + TIVA_PWMN_BASE + + TIVA_PWMN_INTERVAL * 3, + .channel_id = 7, + .inited = false, + .irq = TIVA_IRQ_PWM0_GEN3, + .count = 0, + .cur_count = 0, + .handle = NULL, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tiva_pulsecount_gen[n]_interrupt + * + * Description: + * Pulse count interrupt handlers for PWM[n] + * + ****************************************************************************/ + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN0 +static int tiva_pulsecount_gen0_interrupt(int irq, void *context, void *arg) +{ + return tiva_pulsecount_interrupt(&g_pulsecount_chan0); +} +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN2 +static int tiva_pulsecount_gen1_interrupt(int irq, void *context, void *arg) +{ + return tiva_pulsecount_interrupt(&g_pulsecount_chan2); +} +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN4 +static int tiva_pulsecount_gen2_interrupt(int irq, void *context, void *arg) +{ + return tiva_pulsecount_interrupt(&g_pulsecount_chan4); +} +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN6 +static int tiva_pulsecount_gen3_interrupt(int irq, void *context, void *arg) +{ + return tiva_pulsecount_interrupt(&g_pulsecount_chan6); +} +#endif + +/**************************************************************************** + * Name: tiva_pulsecount_interrupt + * + * Description: + * Common pulse count interrupt handler. + * + ****************************************************************************/ + +#if defined(CONFIG_TIVA_PULSECOUNT0_CHAN0) || defined(CONFIG_TIVA_PULSECOUNT0_CHAN2) || \ + defined(CONFIG_TIVA_PULSECOUNT0_CHAN4) || defined(CONFIG_TIVA_PULSECOUNT0_CHAN6) +static int tiva_pulsecount_interrupt(struct tiva_pulsecount_chan_s *chan) +{ + /* Clear interrupt */ + + tiva_pulsecount_putreg(chan, TIVA_PWMN_ISC_OFFSET, INT_SET << INTCMPAD); + + /* Count down current pulse count */ + + chan->cur_count--; + + /* Disable generator and reload current pulse count */ + + if (chan->cur_count == 0) + { + tiva_pulsecount_putreg(chan, TIVA_PWMN_CTL_OFFSET, + CTL_DISABLE << TIVA_PWMN_CTL_ENABLE); + chan->cur_count = chan->count; + pulsecount_expired(chan->handle); + } + + return 0; +} +#endif + +/**************************************************************************** + * Name: tiva_pulsecount_getreg + * + * Description: + * Get a 32-bit register value by offset + * + ****************************************************************************/ + +static inline uint32_t +tiva_pulsecount_getreg(struct tiva_pulsecount_chan_s *chan, + unsigned int offset) +{ + uintptr_t regaddr = chan->generator_base + offset; + return getreg32(regaddr); +} + +/**************************************************************************** + * Name: tiva_pulsecount_putreg + * + * Description: + * Put a 32-bit register value by offset + * + ****************************************************************************/ + +static inline void +tiva_pulsecount_putreg(struct tiva_pulsecount_chan_s *chan, + unsigned int offset, uint32_t regval) +{ + uintptr_t regaddr = chan->generator_base + offset; + putreg32(regval, regaddr); +} + +/**************************************************************************** + * Name: tiva_pulsecount_setup + * + * Description: + * This method is called when the driver is opened. The lower half driver + * will be configured and initialized the device so that it is ready for + * use. It will not, however, output pulses until the start method is + * called. + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int tiva_pulsecount_setup(struct pulsecount_lowerhalf_s *dev) +{ + struct tiva_pulsecount_chan_s *chan = (struct tiva_pulsecount_chan_s *)dev; + _info("setup pulsecount for channel %d\n", chan->channel_id); + + /* Enable GPIO port, GPIO pin type and GPIO alternate function (refer to + * TM4C1294NCPDT 23.4.2-4) + */ + + int ret = tiva_configgpio(g_pulsecount_pinset[chan->channel_id]); + if (ret < 0) + { + _err("ERROR: tiva_configgpio failed (%x)\n", + g_pulsecount_pinset[chan->channel_id]); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: tiva_pulsecount_shutdown + * + * Description: + * This method is called when the driver is closed. The lower half driver + * stop pulsed output, free any resources, disable the timer hardware, and + * put the system into the lowest possible power usage state + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int tiva_pulsecount_shutdown(struct pulsecount_lowerhalf_s *dev) +{ + struct tiva_pulsecount_chan_s *chan = (struct tiva_pulsecount_chan_s *)dev; + _info("shutdown pulsecount for channel %d\n", chan->channel_id); + + /* Remove unused-variable warning */ + + UNUSED(chan); + + /* Ensure the PWM channel has been stopped */ + + tiva_pulsecount_stop(dev); + + return OK; +} + +/**************************************************************************** + * Name: tiva_pulsecount_start + * + * Description: + * (Re-)initialize the timer resources and start the pulsed output + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * handle - This is the handle that was provided to the lower-half + * start() method. + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int tiva_pulsecount_start(struct pulsecount_lowerhalf_s *dev, + const struct pulsecount_info_s *info, + void *handle) +{ + struct tiva_pulsecount_chan_s *chan = (struct tiva_pulsecount_chan_s *)dev; + _info("start pulsecount for channel %d\n", chan->channel_id); + + /* Save the handle */ + + chan->handle = handle; + + /* Load pulse count and current pulse count + * + * Workaround: + * Count should be add 1 for the first time + */ + + chan->count = info->count; + chan->cur_count = info->count; + + if (!chan->inited) + { + chan->count++; + chan->cur_count++; + chan->inited = true; + } + + /* Count 0 means to generate indefinite number of pulses */ + + if (info->count == 0) + { + pulsecount_expired(chan->handle); + + /* Disable interrupt */ + + uint32_t enable = getreg32(chan->controller_base + + TIVA_PWM_INTEN_OFFSET); + enable &= ~(INT_ENABLE << chan->generator_id); + putreg32(enable, chan->controller_base + TIVA_PWM_INTEN_OFFSET); + } + else + { + /* Enable interrupt */ + + uint32_t enable = getreg32(chan->controller_base + + TIVA_PWM_INTEN_OFFSET); + enable |= (INT_ENABLE << chan->generator_id); + putreg32(enable, chan->controller_base + TIVA_PWM_INTEN_OFFSET); + } + + /* Start the timer */ + + return tiva_pulsecount_timer(chan, info); +} + +/**************************************************************************** + * Name: tiva_pulsecount_timer + * + * Description: + * Configure PWM registers and start pulsecount + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * info - A reference to the characteristics of the pulsed output + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static inline int tiva_pulsecount_timer(struct tiva_pulsecount_chan_s *chan, + const struct pulsecount_info_s *info) +{ + uint16_t duty = pulsecount_duty(info); + uint32_t frequency = pulsecount_frequency(info); + + _info("> high = %" PRIu32 " ns\n", info->high_ns); + _info("> low = %" PRIu32 " ns\n", info->low_ns); + _info("> frequency = %" PRIu32 "\n", frequency); + _info("> duty = %u\n", duty); + + /* Configure PWM countdown mode (refer to TM4C1294NCPDT 23.4.6) */ + + tiva_pulsecount_putreg(chan, TIVA_PWMN_CTL_OFFSET, 0); + + if (chan->channel_id % 2 == 0) + { + tiva_pulsecount_putreg(chan, TIVA_PWMN_GENA_OFFSET, + GENX_LOW << TIVA_PWMN_GENX_ACTCMPAD | + GENX_HIGH << TIVA_PWMN_GENX_ACTLOAD); + } + else + { + tiva_pulsecount_putreg(chan, TIVA_PWMN_GENB_OFFSET, + GENX_LOW << TIVA_PWMN_GENX_ACTCMPBD | + GENX_HIGH << TIVA_PWMN_GENX_ACTLOAD); + } + + /* Set the PWM period (refer to TM4C1294NCPDT 23.4.7) */ + + uint32_t pulsecount_min_freq = + (uint32_t)(g_pulsecount_freq / g_pulsecount_counter) + 1; + uint32_t pulsecount_max_freq = g_pulsecount_freq; + uint32_t load = (uint32_t)(g_pulsecount_freq / frequency); + + _info("> load = %u (%08x)\n", load, load); + + if (load >= g_pulsecount_counter || load < 1) + { + _err("ERROR: frequency should be in [%d, %d] Hz\n", + pulsecount_min_freq, pulsecount_max_freq); + return -ERANGE; + } + + tiva_pulsecount_putreg(chan, TIVA_PWMN_LOAD_OFFSET, load - 1); + + /* Configure PWM duty (refer to TM4C1294NCPDT 23.4.8-9) + * + * Workaround: + * When comp equals to load, the signal is never pulled down, + * so let comp equals to (comp-1) + */ + + uint32_t comp = + (uint32_t)((1 - (float)duty / g_pulsecount_counter) * load); + comp = (duty == 0) ? (comp - 1) : (comp); + _info("> comp = %u (%08x)\n", comp, comp); + + if (chan->channel_id % 2 == 0) + { + tiva_pulsecount_putreg(chan, TIVA_PWMN_CMPA_OFFSET, comp - 1); + } + else + { + tiva_pulsecount_putreg(chan, TIVA_PWMN_CMPB_OFFSET, comp - 1); + } + + /* Enable the PWM generator (refer to TM4C1294NCPDT 23.4.10) */ + + tiva_pulsecount_putreg(chan, + TIVA_PWMN_CTL_OFFSET, + CTL_ENABLE << TIVA_PWMN_CTL_ENABLE); + + /* Enable PWM channel (refer to TM4C1294NCPDT 23.4.11) */ + + uint32_t enable = getreg32(chan->controller_base + TIVA_PWM_ENABLE_OFFSET); + enable |= (1 << chan->channel_id); + putreg32(enable, chan->controller_base + TIVA_PWM_ENABLE_OFFSET); + + return OK; +} + +/**************************************************************************** + * Name: tiva_pulsecount_stop + * + * Description: + * Stop the pulsed output and reset the timer resources + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + * Assumptions: + * This function is called to stop the pulsed output at anytime. This + * method is also called from the timer interrupt handler when a repetition + * count expires... automatically stopping the timer. + * + ****************************************************************************/ + +static int tiva_pulsecount_stop(struct pulsecount_lowerhalf_s *dev) +{ + struct tiva_pulsecount_chan_s *chan = (struct tiva_pulsecount_chan_s *)dev; + _info("stop pulsecount for channel %d\n", chan->channel_id); + + /* Disable PWM channel */ + + uint32_t value = getreg32(chan->controller_base + TIVA_PWM_ENABLE_OFFSET); + value &= ~(1 << chan->channel_id); + putreg32(value, chan->controller_base + TIVA_PWM_ENABLE_OFFSET); + + return OK; +} + +/**************************************************************************** + * Name: tiva_pulsecount_ioctl + * + * Description: + * Lower-half logic may support platform-specific ioctl commands + * + * Input Parameters: + * dev - A reference to the lower half pulsecount driver state structure + * cmd - The ioctl command + * arg - The argument accompanying the ioctl command + * + * Returned Value: + * Zero on success; a negated errno value on failure + * + ****************************************************************************/ + +static int tiva_pulsecount_ioctl(struct pulsecount_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + struct tiva_pulsecount_chan_s *chan = (struct tiva_pulsecount_chan_s *)dev; + _info("ioctl pulsecount for channel %d\n", chan->channel_id); + + /* Remove unused-variable warning */ + + UNUSED(chan); + + /* There are no platform-specific ioctl commands */ + + return -ENOTTY; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: tiva_pulsecount_initialize + * + * Description: + * Initialize one channel for use with the upper-level pulsecount driver. + * + * Input Parameters: + * channel - A number identifying the pulsecount channel to use. + * + * Returned Value: + * On success, a pointer to the Tiva lower half pulsecount driver is + * returned. + * NULL is returned on any failure. + * + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *tiva_pulsecount_initialize(int channel) +{ + ASSERT(channel >= 0 && channel <= 7); + struct tiva_pulsecount_chan_s *chan; + + switch (channel) + { +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN0 + case 0: + chan = &g_pulsecount_chan0; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN1 + case 1: + chan = &g_pulsecount_chan1; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN2 + case 2: + chan = &g_pulsecount_chan2; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN3 + case 3: + chan = &g_pulsecount_chan3; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN4 + case 4: + chan = &g_pulsecount_chan4; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN5 + case 5: + chan = &g_pulsecount_chan5; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN6 + case 6: + chan = &g_pulsecount_chan6; + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN7 + case 7: + chan = &g_pulsecount_chan7; + break; +#endif + + default: + _err("ERROR: invalid channel %d\n", channel); + return NULL; + } + + _info("channel %d:\n", channel); + _info("> channel_id = %d\n", chan->channel_id); + _info("> controller_id = %d\n", chan->controller_id); + _info("> controller_base = %08x\n", chan->controller_base); + _info("> generator_id = %d\n", chan->generator_id); + _info("> generator_base = %08x\n", chan->generator_base); + + /* Enable PWM controller (refer to TM4C1294NCPDT 23.4.1) */ + + ASSERT(chan->controller_id == 0); + tiva_pwm_enablepwr(chan->controller_id); + tiva_pwm_enableclk(chan->controller_id); + + /* Configure PWM Clock Configuration (refer to TM4C1294NCPDT 23.4.5) + * + * On TM4C1294NCPDT, configure the PWM clock source as 1.875MHz (the system + * clock 120MHz divided by 64) + * + * Keep the existing fixed divider/load selection in this split. + */ + + putreg32(CC_USEPWM << TIVA_PWM_CC_USEPWM | + CC_PWMDIV_64 << TIVA_PWM_CC_PWMDIV, + chan->controller_base + TIVA_PWM_CC); + + /* Enable interrupt INTCMPAD mode */ + + tiva_pulsecount_putreg(chan, TIVA_PWMN_INTEN_OFFSET, INT_SET << INTCMPAD); + + /* Attach IRQ handler and enable interrupt */ + + switch (chan->channel_id) + { +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN0 + case 0: + irq_attach(chan->irq, tiva_pulsecount_gen0_interrupt, NULL); + up_enable_irq(chan->irq); + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN2 + case 2: + irq_attach(chan->irq, tiva_pulsecount_gen1_interrupt, NULL); + up_enable_irq(chan->irq); + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN4 + case 4: + irq_attach(chan->irq, tiva_pulsecount_gen2_interrupt, NULL); + up_enable_irq(chan->irq); + break; +#endif + +#ifdef CONFIG_TIVA_PULSECOUNT0_CHAN6 + case 6: + irq_attach(chan->irq, tiva_pulsecount_gen3_interrupt, NULL); + up_enable_irq(chan->irq); + break; +#endif + } + + return (struct pulsecount_lowerhalf_s *)chan; +} diff --git a/arch/arm/src/tiva/common/tiva_pwm.c b/arch/arm/src/tiva/common/tiva_pwm.c index 59612c58eda77..53c31600f6f18 100644 --- a/arch/arm/src/tiva/common/tiva_pwm.c +++ b/arch/arm/src/tiva/common/tiva_pwm.c @@ -83,45 +83,12 @@ struct tiva_pwm_chan_s uintptr_t generator_base; uint8_t channel_id; bool complementary; -#ifdef CONFIG_PWM_PULSECOUNT - bool inited; - uint8_t irq; - uint32_t count; - uint32_t cur_count; - void *handle; -#endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN0) -static int tiva_pwm_gen0_interrupt(int irq, - void *context, void *arg); -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN2) -static int tiva_pwm_gen1_interrupt(int irq, - void *context, void *arg); -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN4) -static int tiva_pwm_gen2_interrupt(int irq, - void *context, void *arg); -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN6) -static int tiva_pwm_gen3_interrupt(int irq, - void *context, void *arg); -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && \ - (defined(CONFIG_TIVA_PWM0_CHAN0) || defined(CONFIG_TIVA_PWM0_CHAN2) || \ - defined(CONFIG_TIVA_PWM0_CHAN4) || defined(CONFIG_TIVA_PWM0_CHAN6)) -static int tiva_pwm_interrupt(struct tiva_pwm_chan_s *chan); -#endif - static inline void tiva_pwm_putreg(struct tiva_pwm_chan_s *chan, unsigned int offset, uint32_t regval); static inline uint32_t tiva_pwm_getreg(struct tiva_pwm_chan_s *chan, @@ -131,14 +98,8 @@ static inline int tiva_pwm_timer(struct tiva_pwm_chan_s *chan, static int tiva_pwm_setup(struct pwm_lowerhalf_s *dev); static int tiva_pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int tiva_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int tiva_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int tiva_pwm_stop(struct pwm_lowerhalf_s *dev); static int tiva_pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd, unsigned long arg); @@ -174,13 +135,6 @@ static struct tiva_pwm_chan_s g_pwm_chan0 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN0, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -199,13 +153,6 @@ static struct tiva_pwm_chan_s g_pwm_chan1 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN0, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -224,13 +171,6 @@ static struct tiva_pwm_chan_s g_pwm_chan2 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN1, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -249,13 +189,6 @@ static struct tiva_pwm_chan_s g_pwm_chan3 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN1, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -274,13 +207,6 @@ static struct tiva_pwm_chan_s g_pwm_chan4 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN2, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -299,13 +225,6 @@ static struct tiva_pwm_chan_s g_pwm_chan5 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN2, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -324,13 +243,6 @@ static struct tiva_pwm_chan_s g_pwm_chan6 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN3, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -349,13 +261,6 @@ static struct tiva_pwm_chan_s g_pwm_chan7 = #else .complementary = false, #endif -#ifdef CONFIG_PWM_PULSECOUNT - .inited = false, - .irq = TIVA_IRQ_PWM0_GEN3, - .count = 0, - .cur_count = 0, - .handle = NULL, -#endif }; #endif @@ -363,77 +268,6 @@ static struct tiva_pwm_chan_s g_pwm_chan7 = * Private Functions ****************************************************************************/ -/**************************************************************************** - * Name: tiva_pwm_gen[n]_interrupt - * - * Description: - * Pulse count interrupt handlers for PWM[n] - * - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN0) -static int tiva_pwm_gen0_interrupt(int irq, void *context, void *arg) -{ - return tiva_pwm_interrupt(&g_pwm_chan0); -} -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN2) -static int tiva_pwm_gen1_interrupt(int irq, void *context, void *arg) -{ - return tiva_pwm_interrupt(&g_pwm_chan2); -} -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN4) -static int tiva_pwm_gen2_interrupt(int irq, void *context, void *arg) -{ - return tiva_pwm_interrupt(&g_pwm_chan4); -} -#endif - -#if defined(CONFIG_PWM_PULSECOUNT) && defined(CONFIG_TIVA_PWM0_CHAN6) -static int tiva_pwm_gen3_interrupt(int irq, void *context, void *arg) -{ - return tiva_pwm_interrupt(&g_pwm_chan6); -} -#endif - -/**************************************************************************** - * Name: tiva_pwm_interrupt - * - * Description: - * Common pulse count interrupt handler. - * - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && \ - (defined(CONFIG_TIVA_PWM0_CHAN0) || defined(CONFIG_TIVA_PWM0_CHAN2) || \ - defined(CONFIG_TIVA_PWM0_CHAN4) || defined(CONFIG_TIVA_PWM0_CHAN6)) -static int tiva_pwm_interrupt(struct tiva_pwm_chan_s *chan) -{ - /* Clear interrupt */ - - tiva_pwm_putreg(chan, TIVA_PWMN_ISC_OFFSET, INT_SET << INTCMPAD); - - /* Count down current pulse count */ - - chan->cur_count--; - - /* Disable PWM generator and reload current pulse count */ - - if (chan->cur_count == 0) - { - tiva_pwm_putreg(chan, TIVA_PWMN_CTL_OFFSET, - CTL_DISABLE << TIVA_PWMN_CTL_ENABLE); - chan->cur_count = chan->count; - pwm_expired(chan->handle); - } - - return 0; -} -#endif - /**************************************************************************** * Name: tiva_pwm_getreg * @@ -550,62 +384,6 @@ static int tiva_pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int tiva_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct tiva_pwm_chan_s *chan = (struct tiva_pwm_chan_s *)dev; - pwminfo("start PWM for channel %d\n", chan->channel_id); - - /* Save the handle */ - - chan->handle = handle; - - /* Load pulse count and current pulse count - * - * Workaround: - * Count should be add 1 for the first time - */ - - chan->count = info->channels[0].count; - chan->cur_count = info->channels[0].count; - - if (!chan->inited) - { - chan->count++; - chan->cur_count++; - chan->inited = true; - } - - /* Count 0 means to generate indefinite number of pulses */ - - if (info->channels[0].count == 0) - { - pwm_expired(chan->handle); - - /* Disable interrupt */ - - uint32_t enable = getreg32(chan->controller_base + - TIVA_PWM_INTEN_OFFSET); - enable &= ~(INT_ENABLE << chan->generator_id); - putreg32(enable, chan->controller_base + TIVA_PWM_INTEN_OFFSET); - } - else - { - /* Enable interrupt */ - - uint32_t enable = getreg32(chan->controller_base + - TIVA_PWM_INTEN_OFFSET); - enable |= (INT_ENABLE << chan->generator_id); - putreg32(enable, chan->controller_base + TIVA_PWM_INTEN_OFFSET); - } - - /* Start the timer */ - - return tiva_pwm_timer(chan, info); -} -#else static int tiva_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -616,7 +394,6 @@ static int tiva_pwm_start(struct pwm_lowerhalf_s *dev, return tiva_pwm_timer(chan, info); } -#endif /**************************************************************************** * Name: tiva_pwm_timer @@ -898,46 +675,5 @@ struct pwm_lowerhalf_s *tiva_pwm_initialize(int channel) CC_PWMDIV_64 << TIVA_PWM_CC_PWMDIV, chan->controller_base + TIVA_PWM_CC); -#ifdef CONFIG_PWM_PULSECOUNT - - /* Enable interrupt INTCMPAD mode */ - - tiva_pwm_putreg(chan, TIVA_PWMN_INTEN_OFFSET, INT_SET << INTCMPAD); - - /* Attach IRQ handler and enable interrupt */ - - switch (chan->channel_id) - { -#ifdef CONFIG_TIVA_PWM0_CHAN0 - case 0: - irq_attach(chan->irq, tiva_pwm_gen0_interrupt, NULL); - up_enable_irq(chan->irq); - break; -#endif - -#ifdef CONFIG_TIVA_PWM0_CHAN2 - case 2: - irq_attach(chan->irq, tiva_pwm_gen1_interrupt, NULL); - up_enable_irq(chan->irq); - break; -#endif - -#ifdef CONFIG_TIVA_PWM0_CHAN4 - case 4: - irq_attach(chan->irq, tiva_pwm_gen2_interrupt, NULL); - up_enable_irq(chan->irq); - break; -#endif - -#ifdef CONFIG_TIVA_PWM0_CHAN6 - case 6: - irq_attach(chan->irq, tiva_pwm_gen3_interrupt, NULL); - up_enable_irq(chan->irq); - break; -#endif - } - -#endif - return (struct pwm_lowerhalf_s *)chan; } diff --git a/arch/arm/src/tiva/tiva_pulsecount.h b/arch/arm/src/tiva/tiva_pulsecount.h new file mode 100644 index 0000000000000..30a660e607f43 --- /dev/null +++ b/arch/arm/src/tiva/tiva_pulsecount.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * arch/arm/src/tiva/tiva_pulsecount.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_TIVA_TIVA_PULSECOUNT_H +#define __ARCH_ARM_SRC_TIVA_TIVA_PULSECOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +struct pulsecount_lowerhalf_s *tiva_pulsecount_initialize(int channel); + +#endif /* __ARCH_ARM_SRC_TIVA_TIVA_PULSECOUNT_H */ From 873a21ecc28aa3ee7e67c3a53ea0ea34a6a47fbb Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 10:17:59 +0200 Subject: [PATCH 11/14] arch: remove PULSECOUNT references from PWM drivers remove PULSECOUNT references from PWM drivers that not implemet PWM feature. PULSECOUNT for these architectures was not implemented, so we just remove all references to it. Signed-off-by: raiden00pl --- arch/arm/src/efm32/Kconfig | 4 - arch/arm/src/efm32/efm32_pwm.c | 204 ------------------------------ arch/arm/src/gd32f4/Kconfig | 1 - arch/arm/src/nrf52/nrf52_pwm.c | 15 --- arch/arm/src/nrf53/nrf53_pwm.c | 15 --- arch/arm/src/sama5/sam_pwm.c | 6 - arch/arm/src/stm32l5/Kconfig | 6 - arch/arm/src/stm32u5/Kconfig | 1 - arch/risc-v/src/litex/litex_pwm.c | 4 - 9 files changed, 256 deletions(-) diff --git a/arch/arm/src/efm32/Kconfig b/arch/arm/src/efm32/Kconfig index f3e60230dde07..a2fec0f4e63ab 100644 --- a/arch/arm/src/efm32/Kconfig +++ b/arch/arm/src/efm32/Kconfig @@ -499,7 +499,6 @@ config EFM32_TIMER0_PWM bool "TIMER0 PWM" default n depends on EFM32_TIMER0 - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 0 for use by PWM @@ -527,7 +526,6 @@ config EFM32_TIMER1_PWM bool "TIMER1 PWM" default n depends on EFM32_TIMER1 - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 0 for use by PWM @@ -555,7 +553,6 @@ config EFM32_TIMER2_PWM bool "TIMER2 PWM" default n depends on EFM32_TIMER2 - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 0 for use by PWM @@ -583,7 +580,6 @@ config EFM32_TIMER3_PWM bool "TIMER3 PWM" default n depends on EFM32_TIMER3 - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 0 for use by PWM diff --git a/arch/arm/src/efm32/efm32_pwm.c b/arch/arm/src/efm32/efm32_pwm.c index 41ddea21f74df..96ec9616ca272 100644 --- a/arch/arm/src/efm32/efm32_pwm.c +++ b/arch/arm/src/efm32/efm32_pwm.c @@ -81,19 +81,10 @@ struct efm32_pwmtimer_s uint8_t timid; /* Timer ID {1,...,14} */ uint8_t channel; /* Timer output channel: {1,..4} */ uint8_t pinloc; /* Timer output channel pin location */ -#ifdef CONFIG_PWM_PULSECOUNT - uint8_t irq; /* Timer update IRQ */ - uint8_t prev; /* The previous value of the RCR (pre-loaded) */ - uint8_t curr; /* The current value of the RCR (pre-loaded) */ - uint32_t count; /* Remaining pulse count */ -#endif uint32_t base; /* The base address of the timer */ uint32_t pincfg; /* Output pin configuration */ uint32_t pclk; /* The frequency of the peripheral clock * that drives the timer module. */ -#ifdef CONFIG_PWM_PULSECOUNT - void *handle; /* Handle used for upper-half callback */ -#endif }; /**************************************************************************** @@ -117,29 +108,13 @@ static void pwm_dumpregs(struct efm32_pwmtimer_s *priv, const char *msg); static int pwm_timer(struct efm32_pwmtimer_s *priv, const struct pwm_info_s *info); -#if defined(CONFIG_PWM_PULSECOUNT) && (defined(CONFIG_EFM32_TIMER0_PWM) || \ - defined(CONFIG_EFM32_TIMER1_PWM) || \ - defined(CONFIG_EFM32_TIMER2_PWM) || \ - defined(CONFIG_EFM32_TIMER3_PWM) \ - ) -static int pwm_interrupt(int irq, void *context, void *arg); -static uint8_t pwm_pulsecount(uint32_t count); - -#endif - /* PWM driver methods */ static int pwm_setup(struct pwm_lowerhalf_s *dev); static int pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int pwm_stop(struct pwm_lowerhalf_s *dev); static int pwm_ioctl(struct pwm_lowerhalf_s *dev, @@ -168,9 +143,6 @@ static struct efm32_pwmtimer_s g_pwm0dev = .ops = &g_pwmops, .timid = 0, .channel = CONFIG_EFM32_TIMER0_CHANNEL, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = EFM32_IRQ_TIMER0, -#endif .base = EFM32_TIMER0_BASE, .pincfg = BOARD_PWM_TIMER0_PINCFG, .pinloc = BOARD_PWM_TIMER0_PINLOC, @@ -184,9 +156,6 @@ static struct efm32_pwmtimer_s g_pwm1dev = .ops = &g_pwmops, .timid = 0, .channel = CONFIG_EFM32_TIMER1_CHANNEL, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = EFM32_IRQ_TIMER1, -#endif .base = EFM32_TIMER1_BASE, .pincfg = BOARD_PWM_TIMER1_PINCFG, .pinloc = BOARD_PWM_TIMER1_PINLOC, @@ -200,9 +169,6 @@ static struct efm32_pwmtimer_s g_pwm2dev = .ops = &g_pwmops, .timid = 0, .channel = CONFIG_EFM32_TIMER2_CHANNEL, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = EFM32_IRQ_TIMER2, -#endif .base = EFM32_TIMER2_BASE, .pincfg = BOARD_PWM_TIMER2_PINCFG, .pinloc = BOARD_PWM_TIMER2_PINLOC, @@ -216,9 +182,6 @@ static struct efm32_pwmtimer_s g_pwm3dev = .ops = &g_pwmops, .timid = 0, .channel = CONFIG_EFM32_TIMER3_CHANNEL, -#ifdef CONFIG_PWM_PULSECOUNT - .irq = EFM32_IRQ_TIMER3, -#endif .base = EFM32_TIMER3_BASE, .pincfg = BOARD_PWM_TIMER3_PINCFG, .pinloc = BOARD_PWM_TIMER3_PINLOC, @@ -357,24 +320,14 @@ static int pwm_timer(struct efm32_pwmtimer_s *priv, DEBUGASSERT(priv != NULL && info != NULL); -#ifdef CONFIG_PWM_PULSECOUNT - pwminfo("TIMER%d channel: %d frequency: %d duty: %08x count: %d\n", - priv->timid, priv->channel, info->frequency, - info->channels[0].duty, info->channels[0].count); -#else pwminfo("TIMER%d channel: %d frequency: %d duty: %08x\n", priv->timid, priv->channel, info->frequency, info->channels[0].duty); -#endif DEBUGASSERT(info->frequency > 0 && info->channels[0].duty >= 0 && info->channels[0].duty < uitoub16(100)); efm32_timer_reset(priv->base); -#ifdef CONFIG_PWM_PULSECOUNT -#error "Not implemented ! Sorry" -#endif - if (efm32_timer_set_freq(priv->base, priv->pclk, info->frequency) < 0) { pwmerr("ERROR: Cannot set TIMER frequency %dHz from clock %dHz\n", @@ -437,141 +390,6 @@ static int pwm_timer(struct efm32_pwmtimer_s *priv, * ****************************************************************************/ -#if defined(CONFIG_PWM_PULSECOUNT) && (defined(CONFIG_EFM32_TIMER0_PWM) || \ - defined(CONFIG_EFM32_TIMER1_PWM) || \ - defined(CONFIG_EFM32_TIMER2_PWM) || \ - defined(CONFIG_EFM32_TIMER3_PWM) \ - ) -#warning "not yet implemented" -static int pwm_interrupt(int irq, void *context, void *arg) -{ - /* TODO pwm_interrupt */ -#if 0 - struct efm32_pwmtimer_s *priv = (struct efm32_pwmtimer_s *)arg; - uint32_t regval; - - DEBUGASSERT(priv != NULL); - - /* Verify that this is an update interrupt. Nothing else is expected. */ - - regval = pwm_getreg(priv, STM32_ATIM_SR_OFFSET); - DEBUGASSERT((regval & ATIM_SR_UIF) != 0); - - /* Clear the UIF interrupt bit */ - - pwm_putreg(priv, STM32_ATIM_SR_OFFSET, regval & ~ATIM_SR_UIF); - - /* Calculate the new count by subtracting the number of pulses - * since the last interrupt. - */ - - if (priv->count <= priv->prev) - { - /* We are finished. Turn off the mast output to stop the output as - * quickly as possible. - */ - - regval = pwm_getreg(priv, STM32_ATIM_BDTR_OFFSET); - regval &= ~ATIM_BDTR_MOE; - pwm_putreg(priv, STM32_ATIM_BDTR_OFFSET, regval); - - /* Disable first interrupts, stop and reset the timer */ - - pwm_stop((struct pwm_lowerhalf_s *)priv); - - /* Then perform the callback into the upper half driver */ - - pwm_expired(priv->handle); - - priv->handle = NULL; - priv->count = 0; - priv->prev = 0; - priv->curr = 0; - } - else - { - /* Decrement the count of pulses remaining using the number of - * pulses generated since the last interrupt. - */ - - priv->count -= priv->prev; - - /* Set up the next RCR. Set 'prev' to the value of the RCR that - * was loaded when the update occurred (just before this interrupt) - * and set 'curr' to the current value of the RCR register (which - * will bet loaded on the next update event). - */ - - priv->prev = priv->curr; - priv->curr = pwm_pulsecount(priv->count - priv->prev); - pwm_putreg(priv, STM32_ATIM_RCR_OFFSET, (uint32_t)priv->curr - 1); - } - - /* Now all of the time critical stuff is done so we can do some debug - * output - */ - - pwminfo("Update interrupt SR: %04x prev: %d curr: %d count: %d\n", - regval, priv->prev, priv->curr, priv->count); - - return OK; -#else - return -ENODEV; -#endif -} -#endif - -/**************************************************************************** - * Name: pwm_pulsecount - * - * Description: - * Pick an optimal pulse count to program the RCR. - * - * Input Parameters: - * count - The total count remaining - * - * Returned Value: - * The recommended pulse count - * - ****************************************************************************/ - -#if defined(CONFIG_PWM_PULSECOUNT) && (defined(CONFIG_EFM32_TIMER0_PWM) || \ - defined(CONFIG_EFM32_TIMER1_PWM) || \ - defined(CONFIG_EFM32_TIMER2_PWM) || \ - defined(CONFIG_EFM32_TIMER3_PWM) \ - ) -static uint8_t pwm_pulsecount(uint32_t count) -{ - /* The the remaining pulse count is less than or equal to the maximum, the - * just return the count. - */ - - if (count <= ATIM_RCR_REP_MAX) - { - return count; - } - - /* Otherwise, we have to be careful. We do not want a small number of - * counts at the end because we might have trouble responding fast enough. - * If the remaining count is less than 150% of the maximum, then return - * half of the maximum. In this case the final sequence will be between 64 - * and 128. - */ - - else if (count < (3 * ATIM_RCR_REP_MAX / 2)) - { - return (ATIM_RCR_REP_MAX + 1) >> 1; - } - - /* Otherwise, return the maximum. The final count will be 64 or more */ - - else - { - return ATIM_RCR_REP_MAX; - } -} -#endif - /**************************************************************************** * Name: pwm_setup * @@ -684,29 +502,12 @@ static int pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ - struct efm32_pwmtimer_s *priv = (struct efm32_pwmtimer_s *)dev; - - /* Save the handle */ - - priv->handle = handle; - - /* Start the time */ - - return pwm_timer(priv, info); -} -#else static int pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { struct efm32_pwmtimer_s *priv = (struct efm32_pwmtimer_s *)dev; return pwm_timer(priv, info); } -#endif /**************************************************************************** * Name: pwm_stop @@ -839,11 +640,6 @@ struct pwm_lowerhalf_s *efm32_pwminitialize(int timer) /* Attach but disable the timer update interrupt */ -#ifdef CONFIG_PWM_PULSECOUNT - irq_attach(lower->irq, pwm_interrupt, lower); - up_disable_irq(lower->irq); -#endif - return (struct pwm_lowerhalf_s *)lower; } diff --git a/arch/arm/src/gd32f4/Kconfig b/arch/arm/src/gd32f4/Kconfig index 9852b46a94771..c2d60e0c31122 100644 --- a/arch/arm/src/gd32f4/Kconfig +++ b/arch/arm/src/gd32f4/Kconfig @@ -782,7 +782,6 @@ config GD32F4_TIMER0_PWM bool "TIMER0 PWM" default n depends on GD32F4_TIMER0 - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 0 for use by PWM diff --git a/arch/arm/src/nrf52/nrf52_pwm.c b/arch/arm/src/nrf52/nrf52_pwm.c index 537dd161cb94f..777052128ffed 100644 --- a/arch/arm/src/nrf52/nrf52_pwm.c +++ b/arch/arm/src/nrf52/nrf52_pwm.c @@ -97,14 +97,8 @@ static int nrf52_pwm_freq(struct nrf52_pwm_s *priv, uint32_t freq); static int nrf52_pwm_setup(struct pwm_lowerhalf_s *dev); static int nrf52_pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int nrf52_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int nrf52_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int nrf52_pwm_stop(struct pwm_lowerhalf_s *dev); static int nrf52_pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd, unsigned long arg); @@ -540,14 +534,6 @@ static int nrf52_pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int nrf52_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ -#error Not supported -} -#else static int nrf52_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -600,7 +586,6 @@ static int nrf52_pwm_start(struct pwm_lowerhalf_s *dev, return ret; } -#endif /**************************************************************************** * Name: nrf52_pwm_stop diff --git a/arch/arm/src/nrf53/nrf53_pwm.c b/arch/arm/src/nrf53/nrf53_pwm.c index ba17aca3ba730..b2638faa8b849 100644 --- a/arch/arm/src/nrf53/nrf53_pwm.c +++ b/arch/arm/src/nrf53/nrf53_pwm.c @@ -97,14 +97,8 @@ static int nrf53_pwm_freq(struct nrf53_pwm_s *priv, uint32_t freq); static int nrf53_pwm_setup(struct pwm_lowerhalf_s *dev); static int nrf53_pwm_shutdown(struct pwm_lowerhalf_s *dev); -#ifdef CONFIG_PWM_PULSECOUNT -static int nrf53_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle); -#else static int nrf53_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info); -#endif static int nrf53_pwm_stop(struct pwm_lowerhalf_s *dev); static int nrf53_pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd, unsigned long arg); @@ -518,14 +512,6 @@ static int nrf53_pwm_shutdown(struct pwm_lowerhalf_s *dev) * ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -static int nrf53_pwm_start(struct pwm_lowerhalf_s *dev, - const struct pwm_info_s *info, - void *handle) -{ -#error Not supported -} -#else static int nrf53_pwm_start(struct pwm_lowerhalf_s *dev, const struct pwm_info_s *info) { @@ -578,7 +564,6 @@ static int nrf53_pwm_start(struct pwm_lowerhalf_s *dev, return ret; } -#endif /**************************************************************************** * Name: nrf53_pwm_stop diff --git a/arch/arm/src/sama5/sam_pwm.c b/arch/arm/src/sama5/sam_pwm.c index f3f23a62fc6b0..3372bbb6d5a4a 100644 --- a/arch/arm/src/sama5/sam_pwm.c +++ b/arch/arm/src/sama5/sam_pwm.c @@ -62,12 +62,6 @@ #define PWM_SINGLE 1 -/* Pulse counting is not supported by this driver */ - -#ifdef CONFIG_PWM_PULSECOUNT -# warning CONFIG_PWM_PULSECOUNT no supported by this driver. -#endif - /* Are we using CLKA? CLKB? If so, at what frequency? */ #if defined(CONFIG_SAMA5_PWM_CLKA) && !defined(CONFIG_SAMA5_PWM_CLKA_FREQUENCY) diff --git a/arch/arm/src/stm32l5/Kconfig b/arch/arm/src/stm32l5/Kconfig index 0c970b7f89cec..4a7589a116ad5 100644 --- a/arch/arm/src/stm32l5/Kconfig +++ b/arch/arm/src/stm32l5/Kconfig @@ -586,7 +586,6 @@ config STM32L5_TIM1_PWM default n depends on STM32L5_TIM1 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 1 for use by PWM @@ -747,7 +746,6 @@ config STM32L5_TIM2_PWM default n depends on STM32L5_TIM2 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 2 for use by PWM @@ -887,7 +885,6 @@ config STM32L5_TIM3_PWM default n depends on STM32L5_TIM3 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 3 for use by PWM @@ -1027,7 +1024,6 @@ config STM32L5_TIM4_PWM default n depends on STM32L5_TIM4 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 4 for use by PWM @@ -1167,7 +1163,6 @@ config STM32L5_TIM5_PWM default n depends on STM32L5_TIM5 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 5 for use by PWM @@ -1307,7 +1302,6 @@ config STM32L5_TIM8_PWM default n depends on STM32L5_TIM8 select PWM - select ARCH_HAVE_PWM_PULSECOUNT ---help--- Reserve timer 8 for use by PWM diff --git a/arch/arm/src/stm32u5/Kconfig b/arch/arm/src/stm32u5/Kconfig index c9d61b3219f42..0a6b6cff64f4f 100644 --- a/arch/arm/src/stm32u5/Kconfig +++ b/arch/arm/src/stm32u5/Kconfig @@ -215,7 +215,6 @@ config STM32U5_SPI config STM32U5_PWM bool default n - select ARCH_HAVE_PWM_PULSECOUNT config STM32U5_USART bool diff --git a/arch/risc-v/src/litex/litex_pwm.c b/arch/risc-v/src/litex/litex_pwm.c index 01460c8ab8815..cdde6966492c2 100644 --- a/arch/risc-v/src/litex/litex_pwm.c +++ b/arch/risc-v/src/litex/litex_pwm.c @@ -43,10 +43,6 @@ * Pre-processor Definitions ****************************************************************************/ -#ifdef CONFIG_PWM_PULSECOUNT -# error PWM pulsecount not supported for Litex. -#endif - #if CONFIG_PWM_NCHANNELS > 1 # error PWM multiple channels not supported for Litex. #endif From 6e57beaa55773b4a10156f6591ded176cc83dc01 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 13:02:02 +0200 Subject: [PATCH 12/14] boards/nucleo-h743zi2: add pulse count example add pulse count example for nucleo-h743zi2 Signed-off-by: raiden00pl --- .../nucleo-h743zi2/configs/jumbo/defconfig | 3 +++ .../stm32h7/nucleo-h743zi2/include/board.h | 4 +++ .../nucleo-h743zi2/src/stm32_bringup.c | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/boards/arm/stm32h7/nucleo-h743zi2/configs/jumbo/defconfig b/boards/arm/stm32h7/nucleo-h743zi2/configs/jumbo/defconfig index de5ac1f5cd3d8..23c77196423b0 100644 --- a/boards/arm/stm32h7/nucleo-h743zi2/configs/jumbo/defconfig +++ b/boards/arm/stm32h7/nucleo-h743zi2/configs/jumbo/defconfig @@ -38,6 +38,7 @@ CONFIG_EXAMPLES_BUTTONS_NAME0="B1" CONFIG_EXAMPLES_BUTTONS_NAMES=y CONFIG_EXAMPLES_BUTTONS_QTD=1 CONFIG_EXAMPLES_HIDKBD=y +CONFIG_EXAMPLES_PULSECOUNT=y CONFIG_EXAMPLES_TOUCHSCREEN=y CONFIG_EXAMPLES_TOUCHSCREEN_DEVPATH="/dev/mouse0" CONFIG_EXAMPLES_TOUCHSCREEN_MOUSE=y @@ -113,6 +114,8 @@ CONFIG_STM32H7_PHYSR_10FD=0x0014 CONFIG_STM32H7_PHYSR_10HD=0x0004 CONFIG_STM32H7_PHYSR_ALTCONFIG=y CONFIG_STM32H7_PHYSR_ALTMODE=0x001c +CONFIG_STM32H7_TIM8=y +CONFIG_STM32H7_TIM8_PULSECOUNT=y CONFIG_STM32H7_USART3=y CONFIG_SYSLOG_INTBUFFER=y CONFIG_SYSLOG_PRIORITY=y diff --git a/boards/arm/stm32h7/nucleo-h743zi2/include/board.h b/boards/arm/stm32h7/nucleo-h743zi2/include/board.h index a9c0a0cb70ed2..9f06515763291 100644 --- a/boards/arm/stm32h7/nucleo-h743zi2/include/board.h +++ b/boards/arm/stm32h7/nucleo-h743zi2/include/board.h @@ -410,6 +410,10 @@ #define GPIO_TIM4_CH1IN (GPIO_TIM4_CH1IN_2|GPIO_SPEED_50MHz) /* PD12 */ #define GPIO_TIM4_CH2IN (GPIO_TIM4_CH2IN_2|GPIO_SPEED_50MHz) /* PD13 */ +/* TIM8 */ + +#define GPIO_TIM8_CH1OUT (GPIO_TIM8_CH1OUT_1|GPIO_SPEED_50MHz) /* PC6 */ + /* FDCAN1 */ #define GPIO_CAN1_RX (GPIO_CAN1_RX_3|GPIO_SPEED_50MHz) /* PD0 */ diff --git a/boards/arm/stm32h7/nucleo-h743zi2/src/stm32_bringup.c b/boards/arm/stm32h7/nucleo-h743zi2/src/stm32_bringup.c index aa17bd7856f56..f0d0a7df159bd 100644 --- a/boards/arm/stm32h7/nucleo-h743zi2/src/stm32_bringup.c +++ b/boards/arm/stm32h7/nucleo-h743zi2/src/stm32_bringup.c @@ -43,6 +43,10 @@ #include "stm32_fdcan_sock.h" #endif +#ifdef CONFIG_PULSECOUNT +#include "stm32_pulsecount.h" +#endif + #ifdef CONFIG_SYSTEMTICK_HOOK #include #endif @@ -74,6 +78,9 @@ int stm32_bringup(void) { +#ifdef CONFIG_PULSECOUNT + struct pulsecount_lowerhalf_s *pulsecount; +#endif int ret; #ifdef CONFIG_RAMMTD uint8_t *ramstart; @@ -204,6 +211,24 @@ int stm32_bringup(void) } #endif /* CONFIG_ADC */ +#ifdef CONFIG_PULSECOUNT + /* Initialize and register the pulse count driver. */ + + pulsecount = stm32_pulsecountinitialize(8); + if (pulsecount == NULL) + { + syslog(LOG_ERR, "ERROR: stm32_pulsecountinitialize failed\n"); + return -ENODEV; + } + + ret = pulsecount_register("/dev/pulsecount0", pulsecount); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pulsecount_register failed: %d\n", ret); + return ret; + } +#endif + #ifdef CONFIG_DEV_GPIO /* Register the GPIO driver */ From 2b6c8b8addb2f4703544282b2abc528c5c4feae9 Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 13:01:11 +0200 Subject: [PATCH 13/14] boards/nucleo-f446re: add pulse count example add pulse count example for nucleo-f446re Signed-off-by: raiden00pl --- .../nucleo-f446re/configs/jumbo/defconfig | 54 +++++++++++++++++++ .../arm/stm32/nucleo-f446re/include/board.h | 4 ++ .../stm32/nucleo-f446re/src/stm32_bringup.c | 25 +++++++++ 3 files changed, 83 insertions(+) create mode 100644 boards/arm/stm32/nucleo-f446re/configs/jumbo/defconfig diff --git a/boards/arm/stm32/nucleo-f446re/configs/jumbo/defconfig b/boards/arm/stm32/nucleo-f446re/configs/jumbo/defconfig new file mode 100644 index 0000000000000..e553a334ca160 --- /dev/null +++ b/boards/arm/stm32/nucleo-f446re/configs/jumbo/defconfig @@ -0,0 +1,54 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_FPU is not set +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +# CONFIG_NSH_DISABLE_IFCONFIG is not set +# CONFIG_NSH_DISABLE_PS is not set +# CONFIG_STM32_FLASH_PREFETCH is not set +CONFIG_ARCH="arm" +CONFIG_ARCH_BOARD="nucleo-f446re" +CONFIG_ARCH_BOARD_NUCLEO_F446RE=y +CONFIG_ARCH_BUTTONS=y +CONFIG_ARCH_CHIP="stm32" +CONFIG_ARCH_CHIP_STM32=y +CONFIG_ARCH_CHIP_STM32F446R=y +CONFIG_ARCH_INTERRUPTSTACK=2048 +CONFIG_ARCH_STACKDUMP=y +CONFIG_BOARD_LOOPSPERMSEC=8499 +CONFIG_BUILTIN=y +CONFIG_EXAMPLES_PULSECOUNT=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INTELHEX_BINARY=y +CONFIG_LINE_MAX=64 +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=131072 +CONFIG_RAM_START=0x20000000 +CONFIG_RAW_BINARY=y +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_WAITPID=y +CONFIG_SPI=y +CONFIG_START_DAY=14 +CONFIG_START_MONTH=10 +CONFIG_START_YEAR=2014 +CONFIG_STM32_CRC=y +CONFIG_STM32_JTAG_SW_ENABLE=y +CONFIG_STM32_OTGFS=y +CONFIG_STM32_PWR=y +CONFIG_STM32_TIM8=y +CONFIG_STM32_TIM8_PULSECOUNT=y +CONFIG_STM32_USART2=y +CONFIG_SYSTEM_NSH=y +CONFIG_TASK_NAME_SIZE=0 +CONFIG_TESTING_OSTEST=y +CONFIG_USART2_SERIAL_CONSOLE=y diff --git a/boards/arm/stm32/nucleo-f446re/include/board.h b/boards/arm/stm32/nucleo-f446re/include/board.h index 46870b34da890..3e4b1ed17570c 100644 --- a/boards/arm/stm32/nucleo-f446re/include/board.h +++ b/boards/arm/stm32/nucleo-f446re/include/board.h @@ -391,6 +391,10 @@ #define GPIO_TIM3_CH1OUT (GPIO_TIM3_CH1OUT_1|GPIO_SPEED_50MHz) +/* TIM8 configuration *******************************************************/ + +#define GPIO_TIM8_CH1OUT (GPIO_TIM8_CH1OUT_1|GPIO_SPEED_50MHz) /* PC6 */ + #ifdef CONFIG_BOARD_STM32_IHM08M1 /* Configuration specific to the X-NUCLEO-IHM08M1 expansion board with diff --git a/boards/arm/stm32/nucleo-f446re/src/stm32_bringup.c b/boards/arm/stm32/nucleo-f446re/src/stm32_bringup.c index 84d62e89f0b9b..893fe82510275 100644 --- a/boards/arm/stm32/nucleo-f446re/src/stm32_bringup.c +++ b/boards/arm/stm32/nucleo-f446re/src/stm32_bringup.c @@ -40,6 +40,10 @@ #include +#ifdef CONFIG_PULSECOUNT +# include "stm32_pulsecount.h" +#endif + #ifdef CONFIG_INPUT_BUTTONS # include #endif @@ -80,6 +84,9 @@ int stm32_bringup(void) { +#ifdef CONFIG_PULSECOUNT + struct pulsecount_lowerhalf_s *pulsecount; +#endif int ret = OK; #ifdef CONFIG_FS_PROCFS @@ -163,6 +170,24 @@ int stm32_bringup(void) } #endif +#ifdef CONFIG_PULSECOUNT + /* Initialize and register the pulse count driver. */ + + pulsecount = stm32_pulsecountinitialize(8); + if (pulsecount == NULL) + { + syslog(LOG_ERR, "ERROR: stm32_pulsecountinitialize failed\n"); + return -ENODEV; + } + + ret = pulsecount_register("/dev/pulsecount0", pulsecount); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pulsecount_register failed: %d\n", ret); + return ret; + } +#endif + #ifdef CONFIG_STM32_CAN_CHARDRIVER /* Initialize CAN and register the CAN driver. */ From 04a116d2ec3c3372b7ebf763ee6157596baee5fe Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Wed, 20 May 2026 13:01:37 +0200 Subject: [PATCH 14/14] boards/nucleo-c092rc: add pulse count example add pulse count example for nucleo-c092rc Signed-off-by: raiden00pl --- .../nucleo-c092rc/configs/jumbo/defconfig | 3 +++ .../stm32f0l0g0/nucleo-c092rc/include/board.h | 6 +++++ .../nucleo-c092rc/src/stm32_bringup.c | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/boards/arm/stm32f0l0g0/nucleo-c092rc/configs/jumbo/defconfig b/boards/arm/stm32f0l0g0/nucleo-c092rc/configs/jumbo/defconfig index e3b8fb25c66e4..c3a1f4f8b40f5 100644 --- a/boards/arm/stm32f0l0g0/nucleo-c092rc/configs/jumbo/defconfig +++ b/boards/arm/stm32f0l0g0/nucleo-c092rc/configs/jumbo/defconfig @@ -36,6 +36,7 @@ CONFIG_EXAMPLES_ADC_GROUPSIZE=2 CONFIG_EXAMPLES_ADC_SWTRIG=y CONFIG_EXAMPLES_BUTTONS=y CONFIG_EXAMPLES_HELLO=y +CONFIG_EXAMPLES_PULSECOUNT=y CONFIG_EXAMPLES_PWM=y CONFIG_EXAMPLES_PWM_DEVPATH="/dev/pwm13" CONFIG_EXAMPLES_QENCODER=y @@ -77,6 +78,8 @@ CONFIG_STM32F0L0G0_TIM14=y CONFIG_STM32F0L0G0_TIM14_CH1OUT=y CONFIG_STM32F0L0G0_TIM14_CHANNEL1=y CONFIG_STM32F0L0G0_TIM14_PWM=y +CONFIG_STM32F0L0G0_TIM1=y +CONFIG_STM32F0L0G0_TIM1_PULSECOUNT=y CONFIG_STM32F0L0G0_TIM3=y CONFIG_STM32F0L0G0_TIM3_QE=y CONFIG_STM32F0L0G0_USART2=y diff --git a/boards/arm/stm32f0l0g0/nucleo-c092rc/include/board.h b/boards/arm/stm32f0l0g0/nucleo-c092rc/include/board.h index 153327b730ce3..f85f0b12044b3 100644 --- a/boards/arm/stm32f0l0g0/nucleo-c092rc/include/board.h +++ b/boards/arm/stm32f0l0g0/nucleo-c092rc/include/board.h @@ -187,6 +187,12 @@ #define GPIO_TIM14_CH1OUT (GPIO_TIM14_CH1OUT_2|GPIO_SPEED_HIGH) +/* Pulse count on TIM1: + * TIM1_CH1OUT - PA8 (D7) + */ + +#define GPIO_TIM1_CH1OUT (GPIO_TIM1_CH1OUT_3|GPIO_SPEED_HIGH) + /* DMA channels *************************************************************/ /* ADC */ diff --git a/boards/arm/stm32f0l0g0/nucleo-c092rc/src/stm32_bringup.c b/boards/arm/stm32f0l0g0/nucleo-c092rc/src/stm32_bringup.c index bab34624d47c9..ba1c4751664b2 100644 --- a/boards/arm/stm32f0l0g0/nucleo-c092rc/src/stm32_bringup.c +++ b/boards/arm/stm32f0l0g0/nucleo-c092rc/src/stm32_bringup.c @@ -28,6 +28,7 @@ #include +#include #include #ifdef CONFIG_INPUT_BUTTONS @@ -42,6 +43,10 @@ # include #endif +#ifdef CONFIG_PULSECOUNT +# include "stm32_pulsecount.h" +#endif + #ifdef CONFIG_SENSORS_QENCODER # include "board_qencoder.h" #endif @@ -75,6 +80,9 @@ int stm32_bringup(void) { +#ifdef CONFIG_PULSECOUNT + struct pulsecount_lowerhalf_s *pulsecount; +#endif int ret; #ifdef CONFIG_STM32F0L0G0_IWDG @@ -83,6 +91,24 @@ int stm32_bringup(void) stm32_iwdginitialize("/dev/watchdog0", STM32_LSI_FREQUENCY); #endif +#ifdef CONFIG_PULSECOUNT + /* Initialize and register the pulse count driver. */ + + pulsecount = stm32_pulsecountinitialize(1); + if (pulsecount == NULL) + { + syslog(LOG_ERR, "ERROR: stm32_pulsecountinitialize failed\n"); + return -ENODEV; + } + + ret = pulsecount_register("/dev/pulsecount0", pulsecount); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: pulsecount_register failed: %d\n", ret); + return ret; + } +#endif + #if !defined(CONFIG_ARCH_LEDS) && defined(CONFIG_USERLED_LOWER) /* Register the LED driver */