From 101ff2c2bb230f28f5ad9068c4b7a15bf2a9f406 Mon Sep 17 00:00:00 2001 From: Jukka Laitinen Date: Tue, 17 Mar 2026 09:37:43 +0200 Subject: [PATCH] arch/imx9: Add bidirectional dshot driver using i.MX9 FlexIO Create a bidirectional dshot lower-half driver, using the flexio block. This driver enables use of DShot protocol for Electronic Speed Controllers (ESCs) typically used in drone motor control. Eight channels of DShot per flexio block are supported in: - Normal mode in all DShot speeds, with telemetry request (uart response needs to be handled outside of this driver) - Bidirectional mode in all DShot speeds, with telemetry request & response - Configurable to use either independent TX timers for each channel, or to use a common TX timer for all channels to synchronize the output - For bidirectional RX, each channel always uses an own timer. In normal mode, the driver operates without any interrupts. In bidirectional mode, the driver reconfigures the FlexIO timers (as needed) and shifters from TX to RX in the interrupt handler, and reads out the raw response packet. Signed-off-by: Jukka Laitinen --- arch/arm64/src/imx9/CMakeLists.txt | 4 + arch/arm64/src/imx9/Kconfig | 87 ++ arch/arm64/src/imx9/Make.defs | 4 + arch/arm64/src/imx9/hardware/imx9_flexio.h | 16 + arch/arm64/src/imx9/imx9_flexio_dshot.c | 1137 ++++++++++++++++++++ arch/arm64/src/imx9/imx9_flexio_dshot.h | 107 ++ 6 files changed, 1355 insertions(+) create mode 100644 arch/arm64/src/imx9/imx9_flexio_dshot.c create mode 100644 arch/arm64/src/imx9/imx9_flexio_dshot.h diff --git a/arch/arm64/src/imx9/CMakeLists.txt b/arch/arm64/src/imx9/CMakeLists.txt index 71a34221e1c..8ed03549fc4 100644 --- a/arch/arm64/src/imx9/CMakeLists.txt +++ b/arch/arm64/src/imx9/CMakeLists.txt @@ -45,6 +45,10 @@ if(CONFIG_IMX9_FLEXIO_PWM) list(APPEND SRCS imx9_flexio_pwm.c) endif() +if(CONFIG_IMX9_FLEXIO_DSHOT) + list(APPEND SRCS imx9_flexio_dshot.c) +endif() + if(CONFIG_IMX9_TPM_PWM) list(APPEND SRCS imx9_tpm_pwm.c) endif() diff --git a/arch/arm64/src/imx9/Kconfig b/arch/arm64/src/imx9/Kconfig index 3abc85092dc..415748b957e 100644 --- a/arch/arm64/src/imx9/Kconfig +++ b/arch/arm64/src/imx9/Kconfig @@ -356,6 +356,93 @@ config IMX9_FLEXIO2_PWM_CHANNEL_PINS hex "FlexIO outputs used for FLEXIO2 timers" default 0x0000000000000000 +config IMX9_FLEXIO_DSHOT + bool + default n + select DSHOT + +choice + prompt "DShot timestamp source" + default IMX9_FLEXIO_DSHOT_TS_SYSTIME + depends on IMX9_FLEXIO_DSHOT + +config IMX9_FLEXIO_DSHOT_TS_SYSTIME + bool "Use clock_systime" + ---help--- + Use clock_systime for dshot response timestamping + +config IMX9_FLEXIO_DSHOT_TS_BOARDTIME + bool "Use board provided timestamping function" + ---help--- + Board-specific sources provide "board_systime_timespec" + function, which is called to get timestamp. This can be used + e.g. if some special time base is required for the timestamping. + +config IMX9_FLEXIO_DSHOT_TS_NONE + bool "Timestamping disabled" + ---help--- + No timestamping, timestamps stay at 0 + +endchoice # DShot timestamp source + +config IMX9_FLEXIO1_DSHOT + bool "Enable FLEXIO1 based DShot" + default n + select IMX9_FLEXIO_DSHOT + +config IMX9_FLEXIO1_DSHOT_NCHANNELS + int "Number of channels for FLEXIO1 DShot" + depends on IMX9_FLEXIO1_DSHOT + default 4 + range 1 8 + +config IMX9_FLEXIO1_DSHOT_TX_TIMER + int "FlexIO timer to use for TX on all channels" + depends on IMX9_FLEXIO1_DSHOT + default 6 + range -1 7 + ---help--- + If set to -1, each DSHOT channel uses it's own individual timer. + When set to timer 0-7, all the DSHOT TX channels use this common + timer to clock out the TX messages. This reduces interrupt load + and synchronizes all the channels to send out messages simultaneously. + +config IMX9_FLEXIO1_DSHOT_CHANNEL_PINS + hex "FlexIO outputs used for FLEXIO1 DShot channels" + depends on IMX9_FLEXIO1_DSHOT + default 0x0000000007060504 + ---help--- + Packed pin-index array: one byte per channel, byte 0 is channel 0. + +config IMX9_FLEXIO2_DSHOT + bool "Enable FLEXIO2 based DShot" + default n + select IMX9_FLEXIO_DSHOT + +config IMX9_FLEXIO2_DSHOT_NCHANNELS + int "Number of channels for FLEXIO2 DShot" + depends on IMX9_FLEXIO2_DSHOT + default 8 + range 1 8 + +config IMX9_FLEXIO2_DSHOT_CHANNEL_PINS + hex "FlexIO outputs used for FLEXIO2 DShot channels" + depends on IMX9_FLEXIO2_DSHOT + default 0x0000000007060504 + ---help--- + Packed pin-index array: one byte per channel, byte 0 is channel 0. + +config IMX9_FLEXIO2_DSHOT_TX_TIMER + int "FlexIO timer to use for TX on all channels" + depends on IMX9_FLEXIO2_DSHOT + default 6 + range -1 7 + ---help--- + If set to -1, each DSHOT channel uses it's own individual timer. + When set to timer 0-7, all the DSHOT TX channels use this common + timer to clock out the TX messages. This reduces interrupt load + and synchronizes all the channels to send out messages simultaneously. + config IMX9_TPM_PWM bool select PWM_MULTICHAN diff --git a/arch/arm64/src/imx9/Make.defs b/arch/arm64/src/imx9/Make.defs index d684576de79..9f95aada779 100644 --- a/arch/arm64/src/imx9/Make.defs +++ b/arch/arm64/src/imx9/Make.defs @@ -56,6 +56,10 @@ ifeq ($(CONFIG_IMX9_FLEXIO_PWM),y) CHIP_CSRCS += imx9_flexio_pwm.c endif +ifeq ($(CONFIG_IMX9_FLEXIO_DSHOT),y) + CHIP_CSRCS += imx9_flexio_dshot.c +endif + ifeq ($(CONFIG_IMX9_TPM_PWM),y) CHIP_CSRCS += imx9_tpm_pwm.c endif diff --git a/arch/arm64/src/imx9/hardware/imx9_flexio.h b/arch/arm64/src/imx9/hardware/imx9_flexio.h index 0bec3605b9b..7ea071c990c 100644 --- a/arch/arm64/src/imx9/hardware/imx9_flexio.h +++ b/arch/arm64/src/imx9/hardware/imx9_flexio.h @@ -66,6 +66,7 @@ #define IMX9_FLEXIO_SHIFTCTL5_OFFSET 0x0094 #define IMX9_FLEXIO_SHIFTCTL6_OFFSET 0x0098 #define IMX9_FLEXIO_SHIFTCTL7_OFFSET 0x009c +#define IMX9_FLEXIO_SHIFTCTL_OFFSET(x) (IMX9_FLEXIO_SHIFTCTL0_OFFSET + (x) * 4) #define IMX9_FLEXIO_SHIFTCFG0_OFFSET 0x0100 /* Shifter Configuration N Register, array offset: 0x100, array step: 0x4 */ #define IMX9_FLEXIO_SHIFTCFG1_OFFSET 0x0104 #define IMX9_FLEXIO_SHIFTCFG2_OFFSET 0x0108 @@ -74,6 +75,7 @@ #define IMX9_FLEXIO_SHIFTCFG5_OFFSET 0x0114 #define IMX9_FLEXIO_SHIFTCFG6_OFFSET 0x0118 #define IMX9_FLEXIO_SHIFTCFG7_OFFSET 0x011c +#define IMX9_FLEXIO_SHIFTCFG_OFFSET(x) (IMX9_FLEXIO_SHIFTCFG0_OFFSET + (x) * 4) #define IMX9_FLEXIO_SHIFTBUF0_OFFSET 0x0200 /* Shifter Buffer N Register, array offset: 0x200, array step: 0x4 */ #define IMX9_FLEXIO_SHIFTBUF1_OFFSET 0x0204 #define IMX9_FLEXIO_SHIFTBUF2_OFFSET 0x0208 @@ -82,6 +84,7 @@ #define IMX9_FLEXIO_SHIFTBUF5_OFFSET 0x0214 #define IMX9_FLEXIO_SHIFTBUF6_OFFSET 0x0218 #define IMX9_FLEXIO_SHIFTBUF7_OFFSET 0x021c +#define IMX9_FLEXIO_SHIFTBUF_OFFSET(x) (IMX9_FLEXIO_SHIFTBUF0_OFFSET + (x) * 4) #define IMX9_FLEXIO_SHIFTBUFBIS0_OFFSET 0x0280 /* Shifter Buffer N Bit Swapped Register, array offset: 0x280, array step: 0x4 */ #define IMX9_FLEXIO_SHIFTBUFBIS1_OFFSET 0x0284 #define IMX9_FLEXIO_SHIFTBUFBIS2_OFFSET 0x0288 @@ -90,6 +93,7 @@ #define IMX9_FLEXIO_SHIFTBUFBIS5_OFFSET 0x0294 #define IMX9_FLEXIO_SHIFTBUFBIS6_OFFSET 0x0298 #define IMX9_FLEXIO_SHIFTBUFBIS7_OFFSET 0x029c +#define IMX9_FLEXIO_SHIFTBUFBIS_OFFSET(x) (IMX9_FLEXIO_SHIFTBUFBIS0_OFFSET + (x) * 4) #define IMX9_FLEXIO_SHIFTBUFBYS0_OFFSET 0x0300 /* Shifter Buffer N Byte Swapped Register, array offset: 0x300, array step: 0x4 */ #define IMX9_FLEXIO_SHIFTBUFBYS1_OFFSET 0x0304 #define IMX9_FLEXIO_SHIFTBUFBYS2_OFFSET 0x0308 @@ -98,6 +102,7 @@ #define IMX9_FLEXIO_SHIFTBUFBYS5_OFFSET 0x0314 #define IMX9_FLEXIO_SHIFTBUFBYS6_OFFSET 0x0318 #define IMX9_FLEXIO_SHIFTBUFBYS7_OFFSET 0x031c +#define IMX9_FLEXIO_SHIFTBUFBYS_OFFSET(x) (IMX9_FLEXIO_SHIFTBUFBYS0_OFFSET + (x) * 4) #define IMX9_FLEXIO_SHIFTBUFBBS0_OFFSET 0x0380 /* Shifter Buffer N Bit Byte Swapped Register, array offset: 0x380, array step: 0x4 */ #define IMX9_FLEXIO_SHIFTBUFBBS1_OFFSET 0x0384 #define IMX9_FLEXIO_SHIFTBUFBBS2_OFFSET 0x0388 @@ -106,6 +111,7 @@ #define IMX9_FLEXIO_SHIFTBUFBBS5_OFFSET 0x0394 #define IMX9_FLEXIO_SHIFTBUFBBS6_OFFSET 0x0398 #define IMX9_FLEXIO_SHIFTBUFBBS7_OFFSET 0x039c +#define IMX9_FLEXIO_SHIFTBUFBBS_OFFSET(x) (IMX9_FLEXIO_SHIFTBUFBBS0_OFFSET + (x) * 4) #define IMX9_FLEXIO_TIMCTL0_OFFSET 0x0400 /* Timer Control N Register, array offset: 0x400, array step: 0x4 */ #define IMX9_FLEXIO_TIMCTL1_OFFSET 0x0404 #define IMX9_FLEXIO_TIMCTL2_OFFSET 0x0408 @@ -603,6 +609,16 @@ #define FLEXIO_TIMCTL_TIMOD(x) ((((uint32_t)(x)) << FLEXIO_TIMCTL_TIMOD_SHIFT) & FLEXIO_TIMCTL_TIMOD_MASK) +#define FLEXIO_TIMCTL_ONETIM_MASK (0x20u) +#define FLEXIO_TIMCTL_ONETIM_SHIFT (5u) + +/* ONETIM - Timer One Time Operation + * 0b0..Event is generated as normal + * 0b1..Event is blocked unless timer status flag is clear + */ + +#define FLEXIO_TIMCTL_ONETIM(x) ((((uint32_t)(x)) << FLEXIO_TIMCTL_ONETIM_SHIFT) & FLEXIO_TIMCTL_ONETIM_MASK) + #define FLEXIO_TIMCTL_PINPOL_MASK (0x80u) #define FLEXIO_TIMCTL_PINPOL_SHIFT (7u) diff --git a/arch/arm64/src/imx9/imx9_flexio_dshot.c b/arch/arm64/src/imx9/imx9_flexio_dshot.c new file mode 100644 index 00000000000..917004febee --- /dev/null +++ b/arch/arm64/src/imx9/imx9_flexio_dshot.c @@ -0,0 +1,1137 @@ +/**************************************************************************** + * arch/arm64/src/imx9/imx9_flexio_dshot.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 "arm64_internal.h" +#include "imx9_ccm.h" +#include "imx9_clockconfig.h" +#include "imx9_flexio_dshot.h" +#include "imx9_gpio.h" +#include "imx9_iomuxc.h" +#include "hardware/imx9_ccm.h" +#include "hardware/imx9_pinmux.h" +#include "hardware/imx9_flexio.h" + +#ifdef CONFIG_IMX9_FLEXIO_DSHOT + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Per-bit 3-chip symbols for DShot TX */ + +#define DSHOT_SYMBOL_0 0b100 +#define DSHOT_SYMBOL_1 0b110 + +/* Telemetry request bit in a TX packet */ + +#define TELEM_BIT 4 + +/* Extract the FlexIO output index for channel 'ch' from the packed + * CONFIG_IMX9_FLEXIOx_DSHOT_CHANNEL_PINS value (one byte per channel). + */ + +#define PIN_FOR_CHANNEL(priv, ch) ((uint8_t)((priv)->pins >> ((ch) * 8))) + +/* Maximum flexio root clock frequency acc. to the reference manual */ + +#define FLEXIO_MAX_ROOT_FREQ 80000000 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct imx9_dshot_channel_s +{ + int8_t shifter; + int8_t timer; + int8_t pin; + + uint32_t tx_timctl; + uint32_t rx_timctl; + uint32_t tx_shftctl; + uint32_t rx_shftctl; + + volatile uint32_t curr_timctl; + volatile struct timespec ts_raw; + volatile uint32_t rx_raw; + volatile int state; +}; + +struct imx9_flexio_dshot_s +{ + const struct dshot_lowerhalf_s lh; + + const flexio_dshot_id_t id; + const uintptr_t base; + const int irq; + const uint64_t pins; + const int tx_timer; + + uint32_t tx_timcmp; + uint32_t rx_timcmp; + + bool initialized; + bool bidir_enabled; + int nchannels; + + uint32_t active_ch_mask; + + volatile uint32_t timien; + volatile uint32_t shiftsien; + + spinlock_t spinlock; + + struct imx9_dshot_channel_s chan[DSHOT_MAX_CHANNELS]; +}; + +enum imx9_dshot_ch_state_e +{ + CH_TX_SEG1 = 0, + CH_TX_SEG2 = 1, + CH_RX = 2, + CH_DONE = 3 +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int imx9_dshot_setup(struct dshot_lowerhalf_s *dev); +static int imx9_dshot_shutdown(struct dshot_lowerhalf_s *dev); +static int imx9_dshot_configure(struct dshot_lowerhalf_s *dev, + const struct dshot_config_s *cfg); +static int imx9_dshot_send_command(struct dshot_lowerhalf_s *dev, + const uint16_t *packets, + uint16_t ch_mask); +static int imx9_dshot_get_raw_telemetry(struct dshot_lowerhalf_s *dev, + struct dshot_raw_telemetry_s *raw, + uint16_t ch_mask); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct dshot_ops_s g_imx9_dshot_ops = +{ + .setup = imx9_dshot_setup, + .shutdown = imx9_dshot_shutdown, + .configure = imx9_dshot_configure, + .send_command = imx9_dshot_send_command, + .get_raw_telemetry = imx9_dshot_get_raw_telemetry, + .ioctl = NULL, +}; + +static struct imx9_flexio_dshot_s g_dshot_dev[] = +{ +#ifdef CONFIG_IMX9_FLEXIO1_DSHOT + { + .lh.ops = &g_imx9_dshot_ops, + .id = DSHOT_FLEXIO1, + .base = IMX9_FLEXIO1_BASE, + .irq = IMX9_IRQ_FLEXIO1, + .nchannels = CONFIG_IMX9_FLEXIO1_DSHOT_NCHANNELS, + .pins = CONFIG_IMX9_FLEXIO1_DSHOT_CHANNEL_PINS, + .tx_timer = CONFIG_IMX9_FLEXIO1_DSHOT_TX_TIMER, + .spinlock = SP_UNLOCKED, + }, +#endif +#ifdef CONFIG_IMX9_FLEXIO2_DSHOT + { + .lh.ops = &g_imx9_dshot_ops, + .id = DSHOT_FLEXIO2, + .base = IMX9_FLEXIO2_BASE, + .irq = IMX9_IRQ_FLEXIO2, + .nchannels = CONFIG_IMX9_FLEXIO2_DSHOT_NCHANNELS, + .pins = CONFIG_IMX9_FLEXIO2_DSHOT_CHANNEL_PINS, + .tx_timer = CONFIG_IMX9_FLEXIO2_DSHOT_TX_TIMER, + .spinlock = SP_UNLOCKED, + }, +#endif +}; + +/* Constant register values for the FlexIO SHIFTCFG, TIMCFG and TIMCTL */ + +/* Timer control, same for TX and RX apart from trigger control */ + +static const uint32_t timctl = (FLEXIO_TIMCTL_TRGPOL(1) | + FLEXIO_TIMCTL_TRGSRC(1) | + FLEXIO_TIMCTL_PINCFG(0) | + FLEXIO_TIMCTL_PINSEL(0) | + FLEXIO_TIMCTL_PINPOL(0)); + +/* These are the same for TX and RX */ + +static const uint32_t shftcfg = (FLEXIO_SHIFTCFG_PWIDTH(0) | + FLEXIO_SHIFTCFG_INSRC(0) | + FLEXIO_SHIFTCFG_SSTOP(1) | + FLEXIO_SHIFTCFG_SSTART(0)); + +static const uint32_t timcfg = (FLEXIO_TIMCFG_TIMOUT(0) | + FLEXIO_TIMCFG_TIMDEC(0) | + FLEXIO_TIMCFG_TIMRST(0) | + FLEXIO_TIMCFG_TIMDIS(2) | + FLEXIO_TIMCFG_TIMENA(2) | + FLEXIO_TIMCFG_TSTOP(2) | + FLEXIO_TIMCFG_TSTART(0)); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline uint32_t imx9_flexio_getreg(struct imx9_flexio_dshot_s *priv, + int offset) +{ + return getreg32(priv->base + offset); +} + +static inline void imx9_flexio_putreg(struct imx9_flexio_dshot_s *priv, + int offset, + uint32_t value) +{ + putreg32(value, priv->base + offset); +} + +static inline void imx9_flexio_modifyreg(struct imx9_flexio_dshot_s *priv, + int offset, uint32_t clear, + uint32_t set) +{ + modifyreg32(priv->base + offset, clear, set); +} + +static inline void imx9_dshot_get_timestamp(struct timespec *time) +{ +#if defined (CONFIG_IMX9_FLEXIO_DSHOT_TS_SYSTIME) + clock_systime_timespec(time); +#elif defined (CONFIG_IMX9_FLEXIO_DSHOT_TS_BOARDTIME) + board_systime_timespec(time); +#else /* CONFIG_IMX9_FLEXIO_DSHOT_TS_NONE */ + time->tv_sec = 0; + time->tv_nsec = 0; +#endif +} + +/* Expand each packet bit to 3 bit symbols on the wire */ + +static uint64_t imx9_dshot_expand_data(uint16_t packet) +{ + uint32_t mask; + uint64_t expanded = 0; + + for (mask = 0x8000; mask != 0; mask >>= 1) + { + expanded <<= 3; + expanded |= (packet & mask) ? DSHOT_SYMBOL_1 : DSHOT_SYMBOL_0; + } + + return expanded; +} + +static int imx9_dshot_select_clock(struct imx9_flexio_dshot_s *priv, + uint32_t freq, uint32_t telem_freq) +{ + uint32_t div; + uint32_t clk_hz; + uint32_t baud_half_tx; + uint32_t baud_half_rx; + int clkroot = CCM_CR_FLEXIO1 + priv->id; + int ret; + + ret = imx9_get_clock(SYS_PLL1PFD0DIV2, &clk_hz); + if (ret < 0) + { + return ret; + } + + /* Start with minimum divider for the clock that doesn't exceed the maximum + * allowed root clock frequency + */ + + div = clk_hz / FLEXIO_MAX_ROOT_FREQ; + if (div == 0) + { + /* Set to minimum 1, in case clk_hz < FLEXIO_MAX_ROOT_FREQ */ + + div = 1; + } + else if (clk_hz / div > FLEXIO_MAX_ROOT_FREQ) + { + /* Don't exceed the MAX_ROOT_FREQ due to rounding error */ + + div++; + } + + /* Search for the smallest divider that keeps baud_half in [1, 256]. */ + + for (; div <= 255; div++) + { + ret = imx9_ccm_configure_root_clock(clkroot, SYS_PLL1PFD0DIV2, div); + if (ret < 0) + { + return ret; + } + + ret = imx9_get_rootclock(clkroot, &clk_hz); + if (ret < 0) + { + return ret; + } + + /* Calculate rounded baud rate halves (the result is + * register write * 2 + 1). + */ + + baud_half_tx = clk_hz / (freq * 3); + baud_half_tx += baud_half_tx & 1; + baud_half_tx /= 2; + + baud_half_rx = clk_hz / telem_freq; + baud_half_rx += baud_half_rx & 1; + baud_half_rx /= 2; + + if (baud_half_tx > 0 && baud_half_tx <= 256 && + baud_half_rx > 0 && baud_half_rx <= 256) + { + /* TX uses 8-bit baud mode + * Configure rx_timcmp to count for 21 bits. The actual receive + * frame is only 20 bits, but the first transition from high to low + * is just a start of the frame and needs to be skipped. + * + * Configure tx_timcmp to count for 48 / 2. + * The TX is done in 2 23 bit parts + stop bit (0) for both. + */ + + priv->tx_timcmp = ((23 * 2 - 1) << 8) | + (baud_half_tx - 1); + priv->rx_timcmp = ((21 * 2 - 1) << 8) | (baud_half_rx - 1); + imx9_ccm_gate_on(CCM_LPCG_FLEXIO1 + priv->id, true); + return OK; + } + } + + return -ERANGE; +} + +static void imx9_dshot_apply_pinmux(flexio_dshot_id_t id, int channel) +{ + if (id == DSHOT_FLEXIO1) + { + switch (channel) + { +#ifdef FLEXIO1_DSHOT0_MUX + case 0: + imx9_iomux_configure(FLEXIO1_DSHOT0_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT1_MUX + case 1: + imx9_iomux_configure(FLEXIO1_DSHOT1_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT2_MUX + case 2: + imx9_iomux_configure(FLEXIO1_DSHOT2_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT3_MUX + case 3: + imx9_iomux_configure(FLEXIO1_DSHOT3_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT4_MUX + case 4: + imx9_iomux_configure(FLEXIO1_DSHOT4_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT5_MUX + case 5: + imx9_iomux_configure(FLEXIO1_DSHOT5_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT6_MUX + case 6: + imx9_iomux_configure(FLEXIO1_DSHOT6_MUX); + break; +#endif +#ifdef FLEXIO1_DSHOT7_MUX + case 7: + imx9_iomux_configure(FLEXIO1_DSHOT7_MUX); + break; +#endif + default: + break; + } + } + else + { + switch (channel) + { +#ifdef FLEXIO2_DSHOT0_MUX + case 0: + imx9_iomux_configure(FLEXIO2_DSHOT0_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT1_MUX + case 1: + imx9_iomux_configure(FLEXIO2_DSHOT1_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT2_MUX + case 2: + imx9_iomux_configure(FLEXIO2_DSHOT2_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT3_MUX + case 3: + imx9_iomux_configure(FLEXIO2_DSHOT3_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT4_MUX + case 4: + imx9_iomux_configure(FLEXIO2_DSHOT4_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT5_MUX + case 5: + imx9_iomux_configure(FLEXIO2_DSHOT5_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT6_MUX + case 6: + imx9_iomux_configure(FLEXIO2_DSHOT6_MUX); + break; +#endif +#ifdef FLEXIO2_DSHOT7_MUX + case 7: + imx9_iomux_configure(FLEXIO2_DSHOT7_MUX); + break; +#endif + default: + break; + } + } +} + +/* Switch channel configuration between RX and TX as needed */ + +static void imx9_dshot_switch_channel(struct imx9_flexio_dshot_s *priv, + struct imx9_dshot_channel_s *ch, + bool rx) +{ + uint32_t new_shftctl = rx ? ch->rx_shftctl : ch->tx_shftctl; + uint32_t new_timctl; + uint32_t new_timcmp; + + /* If using common tx_timer for all channels, only switch between + * rx and tx for that one. Keep all the other timers in rx setting + * so that no extra time is spent in register writes. + */ + + if ((priv->tx_timer >= 0 && priv->tx_timer != ch->timer) || rx) + { + new_timctl = ch->rx_timctl; + new_timcmp = priv->rx_timcmp; + } + else + { + new_timctl = ch->tx_timctl; + new_timcmp = priv->tx_timcmp; + } + + /* Disable timer if re-configuration is needed */ + + if (ch->curr_timctl != new_timctl) + { + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(ch->timer), + ch->curr_timctl & (~FLEXIO_TIMCTL_TIMOD_MASK)); + } + + /* Shifter needs to write a temporary "2" to PINCFG to avoid + * spurious transition to low according to the TRM. + */ + + imx9_flexio_putreg(priv, + IMX9_FLEXIO_SHIFTCTL_OFFSET(ch->shifter), + (ch->tx_shftctl & + (~(FLEXIO_SHIFTCTL_SMOD_MASK | + FLEXIO_SHIFTCTL_PINCFG_MASK))) | + FLEXIO_SHIFTCTL_PINCFG(2)); + + /* Reconfigure shifter */ + + imx9_flexio_putreg(priv, + IMX9_FLEXIO_SHIFTCTL_OFFSET(ch->shifter), + new_shftctl); + + /* Reconfigure the timer if needed */ + + if (ch->curr_timctl != new_timctl) + { + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMCMP_OFFSET(ch->timer), + new_timcmp); + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(ch->timer), + new_timctl); + ch->curr_timctl = new_timctl; + } +} + +static void imx9_dshot_config_channel(struct imx9_flexio_dshot_s *priv, + struct imx9_dshot_channel_s *ch) +{ + bool common_tx = priv->tx_timer >= 0; + + /* Polarity is inverted for Bidirectional DShot */ + + uint32_t pinpol = priv->bidir_enabled ? 1 : 0; + + /* If all channels use the same timer for TX, configure that to shiftctl */ + + int tx_timer = common_tx ? priv->tx_timer : ch->timer; + + /* Channel configuration for TX, trigger timer on shift buffer + * not empty + */ + + ch->tx_shftctl = (FLEXIO_SHIFTCTL_TIMSEL(tx_timer) | + FLEXIO_SHIFTCTL_TIMPOL(0) | + FLEXIO_SHIFTCTL_PINCFG(3) | + FLEXIO_SHIFTCTL_PINSEL(ch->pin) | + FLEXIO_SHIFTCTL_PINPOL(pinpol) | + FLEXIO_SHIFTCTL_SMOD(2)); + + ch->tx_timctl = timctl | FLEXIO_TIMCTL_TRGSEL(4 * ch->shifter + 1); + + /* When using a common TX timer, it is enabled separately in send_command + * to avoid spurious triggers while shifters are being switched from RX + * to TX. + */ + + if (!common_tx) + { + ch->tx_timctl |= FLEXIO_TIMCTL_TIMOD(1); + } + + /* Channel configuration for RX, Enable timer on the first bit + * seen in the shifter input (falling edge on the pin) + */ + + ch->rx_shftctl = (FLEXIO_SHIFTCTL_TIMSEL(ch->timer) | + FLEXIO_SHIFTCTL_TIMPOL(1) | + FLEXIO_SHIFTCTL_PINCFG(0) | + FLEXIO_SHIFTCTL_PINSEL(ch->pin) | + FLEXIO_SHIFTCTL_PINPOL(0) | + FLEXIO_SHIFTCTL_SMOD(1)); + + ch->rx_timctl = timctl | FLEXIO_TIMCTL_TRGSEL(2 * ch->pin) | + FLEXIO_TIMCTL_ONETIM(1) | + FLEXIO_TIMCTL_TIMOD(1); +} + +static int imx9_dshot_configure(struct dshot_lowerhalf_s *dev, + const struct dshot_config_s *cfg) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)dev; + int i; + int ret = 0; + + /* Verify the inputs; frequencies must be non-zero, active mask + * must not contain channels which don't exist + */ + + if (cfg == NULL || cfg->freq == 0 || cfg->telem_freq == 0 || + (cfg->active_mask & (~((1 << priv->nchannels) - 1))) != 0) + { + return -EINVAL; + } + + ret = imx9_dshot_select_clock(priv, cfg->freq, cfg->telem_freq); + if (ret < 0) + { + return ret; + } + + priv->bidir_enabled = (cfg->bidir != 0); + + /* Reset and enable flexio, poll for enabled */ + + imx9_flexio_putreg(priv, IMX9_FLEXIO_CTRL_OFFSET, FLEXIO_CTRL_SWRST(1)); + imx9_flexio_putreg(priv, IMX9_FLEXIO_CTRL_OFFSET, FLEXIO_CTRL_FLEXEN(1)); + + while (imx9_flexio_getreg(priv, IMX9_FLEXIO_CTRL_OFFSET) != + FLEXIO_CTRL_FLEXEN_MASK) + { + } + + /* Disable all shifter and timer interrupts and clear status */ + + imx9_flexio_putreg(priv, IMX9_FLEXIO_SHIFTSIEN_OFFSET, 0); + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMIEN_OFFSET, 0); + imx9_flexio_putreg(priv, IMX9_FLEXIO_SHIFTSTAT_OFFSET, cfg->active_mask); + priv->timien = 0; + priv->shiftsien = 0; + + /* Set channel specific configurations */ + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + struct imx9_dshot_channel_s *ch = &priv->chan[i]; + + ch->state = CH_DONE; + ch->curr_timctl = 0; + + if ((cfg->active_mask & (1 << (i))) != 0) + { + /* Allocate shifters and timers for channels, and initialize + * state + */ + + ch->shifter = i; + ch->timer = i; + ch->pin = PIN_FOR_CHANNEL(priv, i); + + /* Mux the pin */ + + imx9_dshot_apply_pinmux(priv->id, i); + + /* Calculate channel specific configurations for shifters and + * timers + */ + + imx9_dshot_config_channel(priv, ch); + + /* Set the constant configuration values */ + + imx9_flexio_putreg(priv, + IMX9_FLEXIO_SHIFTCFG_OFFSET(ch->shifter), + shftcfg); + imx9_flexio_putreg(priv, + IMX9_FLEXIO_TIMCFG_OFFSET(ch->timer), + timcfg); + + /* Set the configuration to TX */ + + ch->curr_timctl = 0; + imx9_dshot_switch_channel(priv, ch, false); + } + else + { + ch->shifter = -1; + ch->timer = -1; + ch->pin = -1; + } + } + + if (priv->tx_timer >= 0) + { + /* In addition, in case the tx timer is not one of active channels' + * timers, configure that as well + */ + + imx9_flexio_putreg(priv, + IMX9_FLEXIO_TIMCMP_OFFSET(priv->tx_timer), + priv->tx_timcmp); + imx9_flexio_putreg(priv, + IMX9_FLEXIO_TIMCFG_OFFSET(priv->tx_timer), + timcfg); + } + + priv->active_ch_mask = cfg->active_mask; + + return OK; +} + +static int imx9_dshot_send_command(struct dshot_lowerhalf_s *dev, + const uint16_t *packets, + uint16_t ch_mask) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)dev; + struct imx9_dshot_channel_s *ch = NULL; + uint32_t stat; + uint32_t tx_seg1[DSHOT_MAX_CHANNELS]; + uint32_t tx_seg2[DSHOT_MAX_CHANNELS]; + irqstate_t flags; + int i; + uint32_t sft_mask = 0; + uint32_t tlm_timer_mask = 0; + uint32_t common_tx_timctl = 0; + + /* Filter out all channels which are not configured */ + + ch_mask &= priv->active_ch_mask; + + /* If request contains no active channel data (or there are no active + * channels), just return + */ + + if (ch_mask == 0) + { + return OK; + } + + /* Loop through the channels, switch each channel to transmit if needed + * and build the TX packets + */ + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + bool telemetry; + uint64_t expanded; + + if ((ch_mask & (1 << i)) == 0) + { + continue; + } + + ch = &priv->chan[i]; + telemetry = (packets[i] & (1 << TELEM_BIT)) != 0; + + /* Collect the needed shifters for the requested channels + * and a bitmap of the timer interrupt enables needed for telemetry + */ + + sft_mask |= 1 << ch->shifter; + + if (priv->bidir_enabled && telemetry) + { + tlm_timer_mask |= 1 << ch->timer; + } + + /* Expand each bit in packet to 3-bit symbols and split it + * into two 23-bit shift buffer write values. The timer is configured + * to send 2x23 bits, the last bit is always dropped here, as it is + * always 0 / STOP. + */ + + expanded = imx9_dshot_expand_data(packets[i]); + tx_seg1[i] = (uint32_t)((expanded & 0xffffff000000) >> 16); + tx_seg2[i] = (uint32_t)((expanded & 0xffffff) << 8); + } + + if (priv->tx_timer >= 0) + { + /* If bidirectional telemetry is requested, and we are using a common + * TX timer, only request interrupts from the single TX channel + */ + + if (tlm_timer_mask) + { + tlm_timer_mask = 1 << priv->tx_timer; + } + + /* Disable the common TX timer just in case; it might get triggered + * early or it may already be running in some error case. Also set it + * to trigger on the last TX channel, so that it triggers when all the + * shifters have been filled in below. Note: ch is left pointing to + * the last channel after the previous loop. + */ + + DEBUGASSERT(ch != NULL); + common_tx_timctl = timctl | FLEXIO_TIMCTL_TRGSEL(4 * ch->shifter + 1); + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(priv->tx_timer), + common_tx_timctl); + } + + flags = spin_lock_irqsave(&priv->spinlock); + + if (priv->bidir_enabled) + { + /* Disable shifter interrupts, which might be left on */ + + priv->shiftsien &= ~sft_mask; + imx9_flexio_putreg(priv, IMX9_FLEXIO_SHIFTSIEN_OFFSET, + priv->shiftsien); + + /* If there was bidirectional telemetry read previously, restore + * shifter and timer configuration for TX. + */ + + for (i = DSHOT_MAX_CHANNELS - 1; i >= 0; i--) + { + if ((ch_mask & (1 << i)) != 0) + { + ch = &priv->chan[i]; + imx9_dshot_switch_channel(priv, ch, false); + } + } + } + + if (priv->tx_timer >= 0) + { + /* Re-enable the common TX timer. + */ + + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMCTL_OFFSET(priv->tx_timer), + common_tx_timctl | FLEXIO_TIMCTL_TIMOD(1)); + } + + /* Send out the segment1 for every channel. Don't do anything else + * in this loop, so that channels start as close to each other + * as possible timewise. This helps with interrupt load, where interrupts + * from multiple channels occur at the same time. + */ + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + if ((ch_mask & (1 << i)) != 0) + { + bool telemetry = (packets[i] & (1 << TELEM_BIT)) != 0; + ch = &priv->chan[i]; + + /* If telemetry is requested from this channel, set the state to + * the beginning of the state machine. Otherwise, set it directly + * to "done". + */ + + ch->state = priv->bidir_enabled && telemetry ? CH_TX_SEG1 + : CH_DONE; + + /* Load seg1 to shifter buffer */ + + imx9_flexio_putreg(priv, + IMX9_FLEXIO_SHIFTBUFBIS_OFFSET(ch->shifter), + tx_seg1[i]); + } + } + + /* Poll for the shift buffers to become empty (seg1 starts shifting) */ + + do + { + stat = imx9_flexio_getreg(priv, IMX9_FLEXIO_SHIFTSTAT_OFFSET); + } + while ((stat & sft_mask) != sft_mask); + + /* Segment 1 is now shifting for all channels, shifter buffers are empty */ + + if (tlm_timer_mask) + { + /* Clear timer status for the timers generating tx interrupts */ + + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMSTAT_OFFSET, tlm_timer_mask); + + /* Enable TX timer interrupt(s) if bidirectional DShot telemetry is + * requested. + */ + + priv->timien |= tlm_timer_mask; + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMIEN_OFFSET, priv->timien); + } + + /* Load segment 2 */ + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + if ((ch_mask & (1 << i)) != 0) + { + ch = &priv->chan[i]; + imx9_flexio_putreg(priv, + IMX9_FLEXIO_SHIFTBUFBIS_OFFSET(ch->shifter), + tx_seg2[i]); + } + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + + return OK; +} + +/* For bi-directional DShot, there are 3 interrupts to be handled: + * 1. timer interrupt in the middle of TX (state CH_TX_SEG1) + * 2. timer interrupt in the end of TX (state CH_TX_SEG2) + * 3. shifter interrupt in the end of RX (state CH_RX) + * + * First interrupt is useless, but can't really be avoided. On the second + * one, the shifter/timer pair is configured for RX. On the third one, + * the RX data is read from the shifter. + */ + +static int imx9_dshot_irq_handler(int irq, void *context, void *arg) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)arg; + + uint32_t timstat_clear = 0; + uint32_t shiftsien_enable = 0; + uint32_t shiftsien_disable = 0; + uint32_t timien_disable = 0; + int i; + + irqstate_t flags = spin_lock_irqsave(&priv->spinlock); + uint32_t timstat = imx9_flexio_getreg(priv, IMX9_FLEXIO_TIMSTAT_OFFSET) + & priv->timien; + uint32_t shiftstat = imx9_flexio_getreg(priv, IMX9_FLEXIO_SHIFTSTAT_OFFSET) + & priv->shiftsien; + struct timespec ts_raw; + bool common_tx = priv->tx_timer >= 0; + + /* Get timestamp in case we have shifter interrupts (data is ready) */ + + if (shiftstat != 0) + { + imx9_dshot_get_timestamp(&ts_raw); + } + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + struct imx9_dshot_channel_s *ch = &priv->chan[i]; + uint32_t tim_irq; + uint32_t shift_irq; + + if ((priv->active_ch_mask & (1 << i)) == 0 || ch->state >= CH_DONE) + { + /* Channel not active (ch->timer, ch->shifter not valid) or + * no telemetry requested + */ + + continue; + } + + tim_irq = common_tx ? timstat & (1 << priv->tx_timer) + : timstat & (1 << ch->timer); + shift_irq = (shiftstat & (1 << ch->shifter)) != 0; + + if (!tim_irq && !shift_irq) + { + /* No interrupt for this channel */ + + continue; + } + + if (tim_irq && ch->state == CH_TX_SEG1) + { + /* First timer interrupt. Just clear it and wait for the next */ + + timstat_clear |= tim_irq; + ch->state = CH_TX_SEG2; + } + else if (tim_irq && ch->state == CH_TX_SEG2) + { + /* Second timer interrupt, configure for RX */ + + /* If the rx timer and tx timer are not the same, but rx timer is + * already running, force re-configure it (stop & re-start) + */ + + if (common_tx && priv->tx_timer != ch->timer && + (timstat & (1 << ch->timer)) == 0) + { + ch->curr_timctl = 0; + } + + /* Set this channel to RX */ + + imx9_dshot_switch_channel(priv, ch, true); + + /* Enable shifter interrupt and disable timer interrupt for this + * channel + */ + + shiftsien_enable |= 1 << ch->shifter; + timien_disable |= 1 << ch->timer; + + /* Clear the status for the RX timer to enable the trigger */ + + timstat_clear |= 1 << ch->timer; + ch->state = CH_RX; + } + else if (shift_irq && ch->state == CH_RX) + { + /* Shifter interrupt, data is ready in bits 19:0 */ + + ch->rx_raw = imx9_flexio_getreg( + priv, IMX9_FLEXIO_SHIFTBUFBIS_OFFSET(ch->shifter)); + ch->rx_raw &= 0xfffff; + ch->ts_raw = ts_raw; + shiftsien_disable |= 1 << ch->shifter; + ch->state = CH_DONE; + } + } + + if (timien_disable != 0) + { + /* TX complete */ + + /* If there is just one timer used for all channels, modify the + * disable mask to only contain that timer + */ + + if (common_tx) + { + timien_disable = 1 << priv->tx_timer; + } + + /* Clear shifter status for RX channels */ + + imx9_flexio_putreg(priv, IMX9_FLEXIO_SHIFTSTAT_OFFSET, + shiftsien_enable); + + /* Disable the handled timer interrupts */ + + priv->timien &= ~timien_disable; + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMIEN_OFFSET, + priv->timien); + } + + if (shiftsien_disable != 0 || shiftsien_enable != 0) + { + /* TX or RX complete */ + + /* Enable shifter irqs for channels just configured for RX, disable + * for channels which already received data + */ + + priv->shiftsien |= shiftsien_enable; + priv->shiftsien &= ~shiftsien_disable; + imx9_flexio_putreg(priv, IMX9_FLEXIO_SHIFTSIEN_OFFSET, + priv->shiftsien); + } + + /* Clear status of the timer interrupts */ + + if (timstat_clear != 0) + { + imx9_flexio_putreg(priv, IMX9_FLEXIO_TIMSTAT_OFFSET, timstat_clear); + } + + spin_unlock_irqrestore(&priv->spinlock, flags); + + return OK; +} + +static int imx9_dshot_setup(struct dshot_lowerhalf_s *dev) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)dev; + int ret; + + if (priv->initialized) + { + return OK; + } + + ret = irq_attach(priv->irq, imx9_dshot_irq_handler, priv); + if (ret == OK) + { + up_enable_irq(priv->irq); + priv->initialized = true; + } + + return ret; +} + +static int imx9_dshot_shutdown(struct dshot_lowerhalf_s *dev) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)dev; + + if (!priv->initialized) + { + return OK; + } + + up_disable_irq(priv->irq); + irq_detach(priv->irq); + + priv->initialized = false; + priv->active_ch_mask = 0; + + return OK; +} + +static int imx9_dshot_get_raw_telemetry(struct dshot_lowerhalf_s *dev, + struct dshot_raw_telemetry_s *raw, + uint16_t ch_mask) +{ + struct imx9_flexio_dshot_s *priv = + (struct imx9_flexio_dshot_s *)dev; + int i; + + if (raw == NULL) + { + return -EINVAL; + } + + for (i = 0; i < DSHOT_MAX_CHANNELS; i++) + { + if ((ch_mask & (1u << i)) != 0) + { + irqstate_t flags = spin_lock_irqsave(&priv->spinlock); + raw[i].raw = priv->chan[i].rx_raw; + raw[i].timestamp = priv->chan[i].ts_raw; + spin_unlock_irqrestore(&priv->spinlock, flags); + } + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct dshot_lowerhalf_s *imx9_flexio_dshot_init(flexio_dshot_id_t id) +{ + struct imx9_flexio_dshot_s *priv = NULL; + int i; + + for (i = 0; i < sizeof(g_dshot_dev) / sizeof(g_dshot_dev[0]); i++) + { + if (g_dshot_dev[i].id == id) + { + priv = &g_dshot_dev[i]; + break; + } + } + + if (priv == NULL) + { + return NULL; + } + + return (struct dshot_lowerhalf_s *)&priv->lh; +} + +void imx9_flexio_dshot_deinit(flexio_dshot_id_t id) +{ +} + +#endif /* CONFIG_IMX9_FLEXIO_DSHOT */ diff --git a/arch/arm64/src/imx9/imx9_flexio_dshot.h b/arch/arm64/src/imx9/imx9_flexio_dshot.h new file mode 100644 index 00000000000..9e59821abcd --- /dev/null +++ b/arch/arm64/src/imx9/imx9_flexio_dshot.h @@ -0,0 +1,107 @@ +/**************************************************************************** + * arch/arm64/src/imx9/imx9_flexio_dshot.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_ARM64_SRC_IMX9_IMX9_FLEXIO_DSHOT_H +#define __ARCH_ARM64_SRC_IMX9_IMX9_FLEXIO_DSHOT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include + +#ifdef CONFIG_IMX9_FLEXIO_DSHOT + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* There are 8 shifters and timers per one FlexIO IP */ + +#if CONFIG_DSHOT_NCHANNELS > 8 +# error Driver supports max 8 channels per flexio (CONFIG_DSHOT_NCHANNELS) +#endif + +#define DSHOT_MAX_CHANNELS CONFIG_DSHOT_NCHANNELS + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Which FlexIO instance backs this DShot device */ + +typedef enum +{ + DSHOT_FLEXIO1 = 0, + DSHOT_FLEXIO2 = 1, +} flexio_dshot_id_t; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: imx9_flexio_dshot_init + * + * Description: + * Initialise a FlexIO-backed DShot lower-half instance. + * + * Channel count and pin mapping are taken from Kconfig + * (CONFIG_IMX9_FLEXIOx_DSHOT_NCHANNELS, + * CONFIG_IMX9_FLEXIOx_DSHOT_CHANNEL_PINS). These use the same FlexIO + * output numbers as the FlexIO PWM driver. The corresponding board + * IOMUXC macros must be provided, and the driver applies the required + * pinmux configuration during setup. + * + * Input Parameters: + * id - Which FlexIO instance to use (DSHOT_FLEXIO1 / DSHOT_FLEXIO2) + * + * Returned Value: + * A valid pointer to struct dshot_lowerhalf_s on success; NULL on failure. + * + ****************************************************************************/ + +struct dshot_lowerhalf_s *imx9_flexio_dshot_init(flexio_dshot_id_t id); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* CONFIG_IMX9_FLEXIO_DSHOT */ +#endif /* __ARCH_ARM64_SRC_IMX9_IMX9_FLEXIO_DSHOT_H */