From e1df068bb505bad57f24edc9b3f099e0f4e62e9a Mon Sep 17 00:00:00 2001 From: Shreyas Shankar Date: Mon, 24 Nov 2025 10:51:47 +0530 Subject: [PATCH 01/22] samples: dma: Add uart_dma loopback example Add UART_DMA loopback example to test M2P/P2M on eDMA. This uses UART3 (pins J1-3 Rx and J1-4 Tx on LP). Signed-off-by: Shreyas Shankar --- .../uart_dma_loopback_test/CMakeLists.txt | 8 ++ .../uart_dma_loopback_test/README.rst | 107 ++++++++++++++++++ .../boards/am261x_lp_am2612_r5f0_0.overlay | 41 +++++++ .../boards/am261x_lp_am2612_r5f0_1.overlay | 41 +++++++ .../am261x_lp/uart_dma_loopback_test/prj.conf | 4 + .../uart_dma_loopback_test/sample.yaml | 25 ++++ .../uart_dma_loopback_test/src/main.c | 61 ++++++++++ 7 files changed, 287 insertions(+) create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/CMakeLists.txt create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/README.rst create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_0.overlay create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_1.overlay create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/prj.conf create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/sample.yaml create mode 100644 samples/boards/ti/am261x_lp/uart_dma_loopback_test/src/main.c diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/CMakeLists.txt b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/CMakeLists.txt new file mode 100644 index 000000000000..d823e017fef9 --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(uart_dma_loopback_test) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/README.rst b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/README.rst new file mode 100644 index 000000000000..1f2e34ff48ba --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/README.rst @@ -0,0 +1,107 @@ +.. zephyr:code-sample:: uart-dma-loopback-ti + :name: UART DMA Loopback Test + :relevant-api: uart_interface dma_interface + + Test UART with DMA using hardware loopback. + +Overview +******** + +This sample demonstrates UART communication with DMA transfer on the TI AM261x LaunchPad. +It uses DMA to handle UART transmission and implements a loopback test by connecting the +UART TX pin to the UART RX pin with a physical jumper wire. +The sample transmits test data via UART with DMA, receives it back through the loopback, +and verifies data integrity. + +Hardware Setup +************** + +**This sample requires a physical jumper wire connection.** + +Connect the UART TX pin to the UART RX pin with a jumper wire to create a hardware loopback. + +Refer to your board's overlay file (``boards/.overlay``) to identify the specific +pin assignments for UART TX and RX that need to be connected. + +Key Features +************ + +- **DMA-based UART**: Uses DMA controller for UART transmission +- **Async UART API**: Demonstrates asynchronous UART operations with callbacks +- **Cache Management**: Proper cache line alignment and invalidation for cache-coherent data access +- **Data Verification**: Compares transmitted and received buffers to validate loopback functionality +- **Error Handling**: Includes return codes indicating test success or failure + +Building and Running +******************** + +Build for the AM261x LaunchPad: + +.. code-block:: bash + + west build -b am261x_lp/am2612/r5f0_0 samples/boards/ti/am261x_lp/uart_dma_loopback_test + +Flash to the board: + +.. code-block:: bash + + west flash + +Sample Output +************* + +With the jumper wire properly connected between UART TX and RX, the console output should display: + +.. code-block:: console + + Hi from UART. This is an intentionally long message, my friend! Lorem Ipsum! + UART DMA loopback test passed! + +Without the jumper wire, or if the connection is incorrect: + +.. code-block:: console + + (received data does not match) + UART DMA loopback test failed! + +Technical Details +***************** + +UART Configuration +================== + +The sample uses the ``uart_dut`` alias defined in the board overlay to access the UART device. +The UART is configured for asynchronous operation with DMA channels allocated for both +transmission and reception. + +Cache Coherency +=============== + +Buffers used for DMA transfers are aligned to ``CONFIG_DCACHE_LINE_SIZE`` to avoid cache +coherency issues: + +.. code-block:: c + + char tx_buf[96] __aligned(CONFIG_DCACHE_LINE_SIZE) = "..."; + char rx_buf[96] __aligned(CONFIG_DCACHE_LINE_SIZE); + +Before DMA transmission, the transmit buffer cache is flushed to ensure the DMA controller +reads fresh data from main memory: + +.. code-block:: c + + sys_cache_data_flush_range((void *)tx_buf, sizeof(tx_buf)); + +After DMA reception completes, the receive buffer cache is invalidated to ensure the CPU +reads the freshly transferred data from main memory: + +.. code-block:: c + + sys_cache_data_invd_range((void *)rx_buf, sizeof(rx_buf)); + +Return Values +============= + +- ``0``: Test passed - transmitted and received data match +- ``-1``: Test failed - data mismatch detected +- Error code: UART transmission error encountered diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_0.overlay b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_0.overlay new file mode 100644 index 000000000000..1c32a3792fea --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_0.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Hardware Setup: Connect UART TX and RX pins with a jumper wire. + * See pinctrl configuration below for pin assignments. + */ + +#include + +/ { + aliases { + uart-dut = &uart3; + }; +}; + +&edma0 { + status = "okay"; +}; + +&pinctrl { + uart3_rx: uart3_rx_default { + pinmux = ; + }; + + uart3_tx: uart3_tx_default { + pinmux = ; + }; +}; + +&uart3 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart3_tx &uart3_rx>; + pinctrl-names = "default"; +}; diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_1.overlay b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_1.overlay new file mode 100644 index 000000000000..1c32a3792fea --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/boards/am261x_lp_am2612_r5f0_1.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Hardware Setup: Connect UART TX and RX pins with a jumper wire. + * See pinctrl configuration below for pin assignments. + */ + +#include + +/ { + aliases { + uart-dut = &uart3; + }; +}; + +&edma0 { + status = "okay"; +}; + +&pinctrl { + uart3_rx: uart3_rx_default { + pinmux = ; + }; + + uart3_tx: uart3_tx_default { + pinmux = ; + }; +}; + +&uart3 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart3_tx &uart3_rx>; + pinctrl-names = "default"; +}; diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/prj.conf b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/prj.conf new file mode 100644 index 000000000000..392cda1911cd --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/prj.conf @@ -0,0 +1,4 @@ +CONFIG_PRINTK=y + +CONFIG_DMA=y +CONFIG_UART_ASYNC_API=y diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/sample.yaml b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/sample.yaml new file mode 100644 index 000000000000..5afdca5380f3 --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/sample.yaml @@ -0,0 +1,25 @@ +sample: + name: AM261x UART DMA Loopback Test + description: > + Demonstrates UART with DMA loopback operations on TI AM261x LaunchPad. + Transmits data via UART with DMA, enables loopback mode to receive the same data, + and verifies data integrity. + NOTE: This test uses cache APIs + +common: + tags: + - sample + - dma + - uart + integration_platforms: + - am261x_lp/am2612/r5f0_0 + harness: console + harness_config: + type: one_line + regex: + - "UART DMA loopback test passed!" + +tests: + sample.boards.ti.am261x_lp.uart_dma_loopback_test.normal: + tags: tests + filter: CONFIG_CACHE_MANAGEMENT and CONFIG_UART_ASYNC_API and dt_alias_exists("uart-dut") diff --git a/samples/boards/ti/am261x_lp/uart_dma_loopback_test/src/main.c b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/src/main.c new file mode 100644 index 000000000000..736744759db6 --- /dev/null +++ b/samples/boards/ti/am261x_lp/uart_dma_loopback_test/src/main.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include + +/* + * Hardware Setup Required: + * Connect UART TX to UART RX with a jumper wire for hardware loopback. + * Refer to your board's overlay file for specific pin assignments. + */ + +/* + * Get UART device from the devicetree uart_dut alias. This is mandatory. + */ +#define UART_DUT_NODE DT_ALIAS(uart_dut) +#if !DT_NODE_HAS_STATUS_OKAY(UART_DUT_NODE) +#error "Unsupported board: uart_dut devicetree alias is not defined" +#endif + +int main(void) +{ + const struct device *uart_dev = DEVICE_DT_GET(UART_DUT_NODE); + char rx_buf[96] __aligned(CONFIG_DCACHE_LINE_SIZE); + char tx_buf[96] __aligned(CONFIG_DCACHE_LINE_SIZE) = + "Hi from UART. This is an intentionally long message, my friend! Lorem Ipsum!"; + int err; + + sys_cache_data_flush_range((void *)tx_buf, sizeof(tx_buf)); + uart_rx_enable(uart_dev, rx_buf, sizeof(rx_buf), SYS_FOREVER_US); + + err = uart_tx(uart_dev, tx_buf, sizeof(tx_buf), SYS_FOREVER_US); + if (err) { + printk("Unexpected error encountered with error code %d\n", err); + return err; + } + + k_sleep(K_MSEC(100)); + + uart_rx_disable(uart_dev); + + sys_cache_data_invd_range((void *)rx_buf, sizeof(rx_buf)); + printk("%s\n", rx_buf); + + if (memcmp(tx_buf, rx_buf, sizeof(tx_buf))) { + printk("UART DMA loopback test failed!\n"); + return -1; + } + + printk("UART DMA loopback test passed!\n"); + return 0; +} From 22e33bed96a37ff3493ed98ffe4f952b22fb69ff Mon Sep 17 00:00:00 2001 From: Shreyas Shankar Date: Tue, 11 Nov 2025 13:55:28 +0530 Subject: [PATCH 02/22] samples: dma: Add simple m2m sample Add m2m DMA example. Unlike the testcase dma/chan_blen_transfer, this test also uses cache APIs. Signed-off-by: Shreyas Shankar --- .../dma_transfer_test/CMakeLists.txt | 8 ++ .../ti/am261x_lp/dma_transfer_test/Kconfig | 8 ++ .../ti/am261x_lp/dma_transfer_test/README.rst | 86 ++++++++++++ .../boards/am261x_lp_am2612_r5f0_0.overlay | 15 +++ .../boards/am261x_lp_am2612_r5f0_1.conf | 1 + .../boards/am261x_lp_am2612_r5f0_1.overlay | 15 +++ .../ti/am261x_lp/dma_transfer_test/prj.conf | 7 + .../am261x_lp/dma_transfer_test/sample.yaml | 24 ++++ .../ti/am261x_lp/dma_transfer_test/src/main.c | 127 ++++++++++++++++++ 9 files changed, 291 insertions(+) create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/CMakeLists.txt create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/Kconfig create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/README.rst create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.conf create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/prj.conf create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/sample.yaml create mode 100644 samples/boards/ti/am261x_lp/dma_transfer_test/src/main.c diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/CMakeLists.txt b/samples/boards/ti/am261x_lp/dma_transfer_test/CMakeLists.txt new file mode 100644 index 000000000000..06fce7b71893 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(dma_transfer_test) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/Kconfig b/samples/boards/ti/am261x_lp/dma_transfer_test/Kconfig new file mode 100644 index 000000000000..24dfc4c1d94e --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +source "Kconfig.zephyr" + +config DMA_TRANSFER_CHANNEL + int "DMA channel to use for M2M transfer" + default 0 diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/README.rst b/samples/boards/ti/am261x_lp/dma_transfer_test/README.rst new file mode 100644 index 000000000000..2f8f013b144d --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/README.rst @@ -0,0 +1,86 @@ +AM261x DMA Transfer Test Sample +################################ + +This sample demonstrates Direct Memory Access (DMA) memory-to-memory transfer operations on the TI AM261x LaunchPad board. It showcases how to configure and execute a DMA transfer, handle completion callbacks, and use cache management APIs to ensure data coherency. + +Overview +******** + +The sample transfers a test string from one memory location to another using DMA, then verifies that the data was transferred correctly. +This sample also includes cache invalidation operations to demonstrate proper cache handling on systems with data cache. + +Key Features +************ + +- **DMA Configuration**: Sets up memory-to-memory transfer with burst length of 8 bytes +- **Interrupt-driven**: Uses DMA completion callbacks with semaphore synchronization +- **Cache Management**: Demonstrates cache line alignment and cache invalidation after DMA transfers +- **Data Verification**: Compares source and destination buffers to ensure transfer integrity +- **Error Handling**: Includes timeout protection and error checking at each step + +Building and Running +******************** + +Build for the AM261x LaunchPad: + +.. code-block:: bash + + west build -b am261x_lp/am2612/r5f0_0 samples/boards/ti/am261x_lp/dma_transfer_test + +Flash to the board: + +.. code-block:: bash + + west flash + +Expected Output +*************** + +.. code-block:: console + + Starting DMA memory-to-memory transfer + Source data: Hello, testing DMA memory-to-memory transfer! + DMA transfer completed successfully! + Received data: Hello, testing DMA memory-to-memory transfer! + Memory-to-memory DMA transfer verified successfully! + Test passed! + +Technical Details +***************** + +DMA Transfer Configuration +========================== + +The DMA channel is configured with: + +- **Channel Direction**: Memory-to-memory (MEMORY_TO_MEMORY) +- **Data Size**: 1 byte per transfer +- **Burst Length**: 8 bytes +- **Block Size**: 64 bytes (size of tx_data buffer) +- **Callback**: Enabled for completion notification + +Cache Considerations +==================== + +Buffers are aligned to ``CONFIG_DCACHE_LINE_SIZE`` to avoid cache line conflicts: + +.. code-block:: c + + static char tx_data[64] __aligned(CONFIG_DCACHE_LINE_SIZE) = ... + static char rx_data[64] __aligned(CONFIG_DCACHE_LINE_SIZE) = ... + +After the DMA transfer completes, the destination buffer cache is invalidated to ensure the CPU reads the freshly transferred data from main memory: + +.. code-block:: c + + sys_cache_data_invd_range((void *)rx_data, sizeof(rx_data)); + +Synchronization +=============== + +The sample uses a kernel semaphore to synchronize the main thread with the DMA completion interrupt: + +1. Main thread configures and starts DMA transfer +2. DMA completion callback signals the semaphore +3. Main thread waits on semaphore with 5-second timeout +4. After transfer, cache is invalidated and data is verified diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay new file mode 100644 index 000000000000..b7a90558f722 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + dma-dut = &edma0; + }; +}; + +&edma0 { + status = "okay"; +}; diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.conf b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.conf new file mode 100644 index 000000000000..0e5ab5343c47 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.conf @@ -0,0 +1 @@ +CONFIG_DMA_TRANSFER_CHANNEL=32 diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay new file mode 100644 index 000000000000..b7a90558f722 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + dma-dut = &edma0; + }; +}; + +&edma0 { + status = "okay"; +}; diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/prj.conf b/samples/boards/ti/am261x_lp/dma_transfer_test/prj.conf new file mode 100644 index 000000000000..a43d93ff3aa2 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/prj.conf @@ -0,0 +1,7 @@ +CONFIG_PRINTK=y + +CONFIG_DMA=y + +CONFIG_CACHE_MANAGEMENT=y +CONFIG_DCACHE=y +CONFIG_ICACHE=y diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/sample.yaml b/samples/boards/ti/am261x_lp/dma_transfer_test/sample.yaml new file mode 100644 index 000000000000..4fd8fa2b7131 --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/sample.yaml @@ -0,0 +1,24 @@ +sample: + name: AM261x DMA Transfer Test + description: > + Demonstrates DMA memory-to-memory transfer operations on TI AM261x LaunchPad. + Writes data from one memory location to another via DMA, reads it back, and verifies integrity. + NOTE: This test uses cache APIs + +common: + tags: + - sample + - dma + integration_platforms: + - am261x_lp/am2612/r5f0_0 + - am261x_lp/am2612/r5f0_1 + harness: console + harness_config: + type: one_line + regex: + - "Test passed!" + +tests: + sample.boards.ti.am261x_lp.dma_transfer_test.normal: + tags: tests + filter: CONFIG_CACHE_MANAGEMENT and CONFIG_DMA diff --git a/samples/boards/ti/am261x_lp/dma_transfer_test/src/main.c b/samples/boards/ti/am261x_lp/dma_transfer_test/src/main.c new file mode 100644 index 000000000000..e5c6e5c6021c --- /dev/null +++ b/samples/boards/ti/am261x_lp/dma_transfer_test/src/main.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include + +#define DMA_DUT_NODE DT_ALIAS(dma_dut) +#if !DT_NODE_HAS_STATUS_OKAY(DMA_DUT_NODE) +#error "Unsupported board: dma_dut devicetree alias is not defined" +#endif + +/* Define source and destination buffers */ +static char tx_data[64] __aligned(CONFIG_DCACHE_LINE_SIZE) = + "Hello, testing DMA memory-to-memory transfer!"; +static char rx_data[64] __aligned(CONFIG_DCACHE_LINE_SIZE) = {0}; + +/* DMA transfer completion callback */ +static void dma_transfer_complete(const struct device *dma_dev, void *user_data, uint32_t channel, + int status) +{ + if (status >= 0) { + printk("DMA transfer completed successfully!\n"); + } else { + printk("DMA transfer failed with status %d\n", status); + } + + /* Signal the main thread that transfer is complete */ + struct k_sem *transfer_sem = (struct k_sem *)user_data; + + k_sem_give(transfer_sem); +} + +int main(void) +{ + /* Semaphore to synchronize with DMA completion */ + struct k_sem transfer_sem; + int ret; + + k_sem_init(&transfer_sem, 0, 1); + + const struct device *dma_dev = DEVICE_DT_GET(DMA_DUT_NODE); + + if (!device_is_ready(dma_dev)) { + printk("DMA device not ready\n"); + return -1; + } + + /* Configure DMA channel */ + struct dma_config dma_cfg = {0}; + struct dma_block_config dma_block_cfg = {0}; + + /* Define the DMA channel to use */ + uint32_t dma_channel = CONFIG_DMA_TRANSFER_CHANNEL; + + /* Setup DMA configuration */ + dma_cfg.channel_direction = MEMORY_TO_MEMORY; + dma_cfg.source_data_size = 1; /* 1 byte */ + dma_cfg.dest_data_size = 1; /* 1 byte */ + dma_cfg.source_burst_length = 8; /* Burst length of 8 bytes */ + dma_cfg.dest_burst_length = 8; /* Burst length of 8 bytes */ + dma_cfg.dma_callback = dma_transfer_complete; + dma_cfg.user_data = &transfer_sem; + dma_cfg.complete_callback_en = 0; + dma_cfg.error_callback_dis = 0; + dma_cfg.block_count = 1; + dma_cfg.head_block = &dma_block_cfg; + + /* Setup block configuration */ + dma_block_cfg.block_size = sizeof(tx_data); +#ifdef CONFIG_DMA_64BIT + dma_block_cfg.source_address = (uint64_t)tx_data; + dma_block_cfg.dest_address = (uint64_t)rx_data; +#else + dma_block_cfg.source_address = (uint32_t)tx_data; + dma_block_cfg.dest_address = (uint32_t)rx_data; +#endif + + /* Clear the destination buffer */ + memset(rx_data, 0, sizeof(rx_data)); + + printk("Starting DMA memory-to-memory transfer\n"); + printk("Source data: %s\n", tx_data); + + /* Configure DMA */ + ret = dma_config(dma_dev, dma_channel, &dma_cfg); + if (ret != 0) { + printk("Error: DMA configuration failed with error code %d\n", ret); + return ret; + } + + /* Start DMA transfer */ + ret = dma_start(dma_dev, dma_channel); + if (ret != 0) { + printk("Error: DMA transfer failed to start with error code %d\n", ret); + return ret; + } + + /* Wait for transfer completion */ + if (k_sem_take(&transfer_sem, K_MSEC(5000)) != 0) { + printk("Error: DMA transfer timed out\n"); + dma_stop(dma_dev, dma_channel); + return -1; + } + + sys_cache_data_invd_range((void *)rx_data, sizeof(rx_data)); + + printk("Received data: %s\n", rx_data); + + /* Verify the transfer */ + if (memcmp(tx_data, rx_data, sizeof(tx_data)) == 0) { + printk("Memory-to-memory DMA transfer verified successfully!\n"); + printk("Test passed!\n"); + } else { + printk("Error: Data verification failed\n"); + return -1; + } + + return 0; +} From 6f1110b52ad42c29567ec26f8df9f24532dd0877 Mon Sep 17 00:00:00 2001 From: Shreyas Shankar Date: Tue, 23 Dec 2025 15:42:53 +0530 Subject: [PATCH 03/22] samples: ti: Adding spi_transfer_test Add example to perform spi_transceive_cb. This tests SPI Async Operation using McSPI and eDMA. This example spawns multiple threads for concurrent access of a single SPI instance. This tests SPI driver's concurrent access arbitration capability. Signed-off-by: Shreyas Shankar --- .../spi_transfer_test/CMakeLists.txt | 7 + .../ti/am261x_lp/spi_transfer_test/README.md | 57 ++++ .../boards/am261x_lp_am2612_r5f0_0.overlay | 15 + .../boards/am261x_lp_am2612_r5f0_1.overlay | 15 + .../ti/am261x_lp/spi_transfer_test/prj.conf | 13 + .../am261x_lp/spi_transfer_test/sample.yaml | 16 + .../ti/am261x_lp/spi_transfer_test/src/main.c | 286 ++++++++++++++++++ 7 files changed, 409 insertions(+) create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/CMakeLists.txt create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/README.md create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/prj.conf create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/sample.yaml create mode 100644 samples/boards/ti/am261x_lp/spi_transfer_test/src/main.c diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/CMakeLists.txt b/samples/boards/ti/am261x_lp/spi_transfer_test/CMakeLists.txt new file mode 100644 index 000000000000..6fb979c8ffce --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/CMakeLists.txt @@ -0,0 +1,7 @@ +# CMakeLists.txt +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(spi_transfer_test) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/README.md b/samples/boards/ti/am261x_lp/spi_transfer_test/README.md new file mode 100644 index 000000000000..7db904ebb364 --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/README.md @@ -0,0 +1,57 @@ +# SPI Transfer Test + +## Overview + +This sample demonstrates SPI loopback testing with concurrent thread execution to verify: +1. SPI controller functionality in loopback mode (MOSI connected to MISO) +2. SPI driver's arbitration capabilities under concurrent access +3. DMA transfer correctness with cache maintenance operations + +## Test Description + +The test creates **4 concurrent threads**, each executing identical test logic: +- Threads perform multiple iterations of SPI transceive operations +- Each iteration transmits a unique pattern and verifies loopback reception +- Uses proper cache maintenance (flush/invalidate) for DMA coherence + +### Test Patterns +Four distinct 8-bit patterns are used for verification: +1. `{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}` - Sequential increment +2. `{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}` - High byte sequential +3. `{0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A}` - Alternating nibble pattern +4. `{0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}` - Zero/FF alternating + +## Build and Run + +```bash +# Build for AM261x LP board +west build -b am261x_lp samples/boards/ti/am261x_lp/spi_transfer_test + +# Flash to hardware +west flash + +# Monitor output via UART console +``` + +## Expected Output + +Each thread reports: +``` +[INF] SPI Loopback Test Started (Thread X) +[INF] Running test pattern Y +[INF] Thread Z: Testing pattern with first byte: 0xNN + Test Callback activated. SPI transfer completed successfully! +[INF] Thread Z:Pattern test PASSED - Loopback successful +[INF] Thread Z: Transmitted/received data (first 8 bytes): +0xNN 0xNN 0xNN 0xNN 0xNN 0xNN 0xNN 0xNN +``` + +After all threads complete: +``` +======================================== +FINAL TEST SUMMARY +======================================== +Threads passed: 4 / 4 +RESULT: ALL TESTS PASSED +======================================== +``` diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay b/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay new file mode 100644 index 000000000000..1de80b38614e --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_0.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + spi-dut = &mcspi2; + }; +}; + +&mcspi2 { + status="okay"; +}; diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay b/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay new file mode 100644 index 000000000000..1de80b38614e --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/boards/am261x_lp_am2612_r5f0_1.overlay @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + spi-dut = &mcspi2; + }; +}; + +&mcspi2 { + status="okay"; +}; diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/prj.conf b/samples/boards/ti/am261x_lp/spi_transfer_test/prj.conf new file mode 100644 index 000000000000..8f9eec5f3844 --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/prj.conf @@ -0,0 +1,13 @@ +CONFIG_SPI=y +CONFIG_SPI_ASYNC=y +CONFIG_SPI_DMA=y + +# Enable logging with timestamps for better debugging +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y # Immediate logging (no buffering) +CONFIG_LOG_TIMESTAMP_64BIT=y # 64-bit timestamps for accurate timing + +# Console configuration +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_PRINTK=y diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/sample.yaml b/samples/boards/ti/am261x_lp/spi_transfer_test/sample.yaml new file mode 100644 index 000000000000..b666d56667c8 --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/sample.yaml @@ -0,0 +1,16 @@ +sample: + name: SPI Transfer Test + +tests: + sample.boards.ti.am261x_lp.spi_transfer_test: + tags: + - sample + - spi + - dma + - tests + filter: CONFIG_CACHE_MANAGEMENT and CONFIG_SPI_DMA and dt_alias_exists("spi-dut") + harness: console + harness_config: + type: one_line + regex: + - "RESULT: ALL TESTS PASSED" diff --git a/samples/boards/ti/am261x_lp/spi_transfer_test/src/main.c b/samples/boards/ti/am261x_lp/spi_transfer_test/src/main.c new file mode 100644 index 000000000000..6c7e12503d3b --- /dev/null +++ b/samples/boards/ti/am261x_lp/spi_transfer_test/src/main.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2025 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(spi_loopback, LOG_LEVEL_INF); + +#define SPI_DEV_NODE DT_ALIAS(spi_dut) +#if !DT_NODE_HAS_STATUS_OKAY(SPI_DEV_NODE) +#error "Unsupported board: spi_dut devicetree alias is not defined" +#endif +#define BUFFER_SIZE 32 + +/* Test patterns for loopback verification */ +static uint8_t test_patterns[][8] + __aligned(CONFIG_DCACHE_LINE_SIZE) = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + {0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8}, + {0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A}, + {0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}}; + +static uint8_t __aligned(CONFIG_DCACHE_LINE_SIZE) tx_buf[4][BUFFER_SIZE]; +static uint8_t __aligned(CONFIG_DCACHE_LINE_SIZE) rx_buf[4][BUFFER_SIZE]; + +/** + * @brief Perform a single SPI loopback test with a specific pattern + * + * @param spi_dev Pointer to the SPI device + * @param pattern Pointer to test pattern array + * @param pattern_len Length of the test pattern + * @param cb Callback function to be called upon transfer completion + * @param sem Pointer to semaphore for synchronization with callback + * @param thread_num Identifier of the calling thread (for logging) + * @return 0 on success (pattern verified), negative on error, positive on pattern mismatch + */ +static int spi_loopback_test_pattern(const struct device *spi_dev, const uint8_t *pattern, + size_t pattern_len, + void (*cb)(const struct device *, int, void *), + struct k_sem *sem, int thread_num) +{ + k_sem_reset(sem); + + uint8_t *tx_buffer = tx_buf[thread_num]; + uint8_t *rx_buffer = rx_buf[thread_num]; + int ret; + + /* Basic SPI configuration for loopback test - no CS needed */ + struct spi_config spi_cfg = { + .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB | SPI_MODE_LOOP, + .frequency = 25000000, /* 25 MHz */ + }; + + /* Fill transmit buffer with the pattern (possibly repeating) */ + for (int i = 0; i < BUFFER_SIZE; i++) { + tx_buffer[i] = pattern[i % pattern_len]; + } + sys_cache_data_flush_range((void *)tx_buffer, sizeof(tx_buffer)); + + /* Clear receive buffer */ + memset(rx_buffer, 0, BUFFER_SIZE); + sys_cache_data_flush_range((void *)rx_buffer, sizeof(rx_buffer)); + + /* Set up SPI buffers */ + struct spi_buf tx_buf = { + .buf = tx_buffer, + .len = BUFFER_SIZE, + }; + + struct spi_buf rx_buf = { + .buf = rx_buffer, + .len = BUFFER_SIZE, + }; + + struct spi_buf_set tx_bufs = { + .buffers = &tx_buf, + .count = 1, + }; + + struct spi_buf_set rx_bufs = { + .buffers = &rx_buf, + .count = 1, + }; + + /* Perform transceive operation */ + LOG_INF("Thread %d: Testing pattern with first byte: 0x%02X", thread_num, pattern[0]); + + ret = spi_transceive_cb(spi_dev, &spi_cfg, &tx_bufs, &rx_bufs, cb, (void *)sem); + k_sem_take(sem, K_FOREVER); + + sys_cache_data_invd_range((void *)tx_buffer, sizeof(tx_buffer)); + sys_cache_data_invd_range((void *)rx_buffer, sizeof(rx_buffer)); + + if (ret) { + LOG_ERR("Thread %d: SPI transceive failed: %d", thread_num, ret); + return ret; + } + + /* Verify received data matches transmitted data */ + bool pattern_match = true; + + for (int i = 0; i < BUFFER_SIZE; i++) { + if (rx_buffer[i] != tx_buffer[i]) { + pattern_match = false; + LOG_ERR("Thread %d: Data mismatch at index %d: TX=0x%02X, RX=0x%02X", + thread_num, i, tx_buffer[i], rx_buffer[i]); + /* Continue checking to show all mismatches */ + } + } + + if (pattern_match) { + LOG_INF("Thread %d:Pattern test PASSED - Loopback successful", thread_num); + + LOG_INF("Thread %d: Transmitted/received data (first 8 bytes):", thread_num); + for (int i = 0; i < 8 && i < BUFFER_SIZE; i++) { + printk("0x%02X ", rx_buffer[i]); + } + printk("\n"); + return 0; + } + LOG_ERR("Thread %d: Pattern test FAILED - Loopback not functioning correctly", thread_num); + return 1; +} + +/* Threads based test */ +#define THREAD_STACK_SIZE 1024 + +struct k_thread task1, task2, task3, task4; +K_THREAD_STACK_DEFINE(task1_stack, THREAD_STACK_SIZE); +K_THREAD_STACK_DEFINE(task2_stack, THREAD_STACK_SIZE); +K_THREAD_STACK_DEFINE(task3_stack, THREAD_STACK_SIZE); +K_THREAD_STACK_DEFINE(task4_stack, THREAD_STACK_SIZE); + +K_SEM_DEFINE(sem1, 0, 1); +K_SEM_DEFINE(sem2, 0, 1); +K_SEM_DEFINE(sem3, 0, 1); +K_SEM_DEFINE(sem4, 0, 1); + +/* Thread completion tracking */ +/* -1 = not run, 0 = fail, 1 = pass */ +static int thread_test_results[4] = {-1, -1, -1, -1}; + +void call_back_task1(const struct device *spi_dev, int status, void *user_data) +{ + if (status >= 0) { + printk("Test Callback activated. SPI transfer completed successfully!\n"); + } else { + printk("Test Callback activated. SPI transfer failed with status %d\n", status); + } + k_sem_give(&sem1); +} +void call_back_task2(const struct device *spi_dev, int status, void *user_data) +{ + if (status >= 0) { + printk("Test Callback activated. SPI transfer completed successfully!\n"); + } else { + printk("Test Callback activated. SPI transfer failed with status %d\n", status); + } + k_sem_give(&sem2); +} +void call_back_task3(const struct device *spi_dev, int status, void *user_data) +{ + if (status >= 0) { + printk("Test Callback activated. SPI transfer completed successfully!\n"); + } else { + printk("Test Callback activated. SPI transfer failed with status %d\n", status); + } + k_sem_give(&sem3); +} +void call_back_task4(const struct device *spi_dev, int status, void *user_data) +{ + if (status >= 0) { + printk("Test Callback activated. SPI transfer completed successfully!\n"); + } else { + printk("Test Callback activated. SPI transfer failed with status %d\n", status); + } + k_sem_give(&sem4); +} + +static void main_function(void *sem, void *cb, void *thread_id_ptr) +{ + const struct device *spi_dev; + int test_result = 0; + int thread_id = (int)thread_id_ptr; /* Each thread has unique ID 0-3 */ + + /* Get SPI device */ + spi_dev = DEVICE_DT_GET(SPI_DEV_NODE); + if (!device_is_ready(spi_dev)) { + LOG_ERR("Thread %d: SPI device not ready", thread_id); + return; + } + + LOG_INF("SPI Loopback Test Started (Thread %d)", thread_id); + + /* Each thread tests ONE specific pattern using its own dedicated buffer */ + printk("\n"); + LOG_INF("Running test pattern %d", thread_id + 1); + + /* Run multiple iterations to stress test */ + int iterations = 4; + int passed = 0; + + for (int iter = 0; iter < iterations; iter++) { + test_result = spi_loopback_test_pattern(spi_dev, test_patterns[thread_id], + ARRAY_SIZE(test_patterns[thread_id]), cb, + (struct k_sem *)sem, thread_id); + + if (test_result == 0) { + passed++; + } + + /* Add delay between iterations */ + k_sleep(K_MSEC(500)); + } + + /* Print overall results */ + printk("\n"); + LOG_INF("SPI Loopback Test Summary (Thread %d):", thread_id); + LOG_INF("%d of %d iterations passed for pattern %d", passed, iterations, thread_id + 1); + + if (passed == iterations) { + LOG_INF("OVERALL RESULT: PASS - Loopback functioning correctly"); + thread_test_results[thread_id] = 1; /* Pass */ + } else { + LOG_INF("OVERALL RESULT: FAIL - Loopback issues detected"); + thread_test_results[thread_id] = 0; /* Fail */ + } + + LOG_INF("Thread %d: Test complete - Exiting", thread_id); +} + +int main(void) +{ + k_thread_create(&task1, task1_stack, THREAD_STACK_SIZE, main_function, &sem1, + &call_back_task1, (void *)0, /* Thread 0 tests pattern 0 */ + -1, K_USER, K_MSEC(3)); + + k_thread_create(&task2, task2_stack, THREAD_STACK_SIZE, main_function, &sem2, + &call_back_task2, (void *)1, /* Thread 1 tests pattern 1 */ + -1, K_USER, K_MSEC(2)); + + k_thread_create(&task3, task3_stack, THREAD_STACK_SIZE, main_function, &sem3, + &call_back_task3, (void *)2, /* Thread 2 tests pattern 2 */ + -1, K_USER, K_MSEC(1)); + + k_thread_create(&task4, task4_stack, THREAD_STACK_SIZE, main_function, &sem4, + &call_back_task4, (void *)3, /* Thread 3 tests pattern 3 */ + -1, K_USER, K_MSEC(4)); + + /* Wait for all threads to complete and exit */ + k_thread_join(&task1, K_FOREVER); + k_thread_join(&task2, K_FOREVER); + k_thread_join(&task3, K_FOREVER); + k_thread_join(&task4, K_FOREVER); + + /* Collect results and print final summary */ + int total_passed = 0; + + for (int i = 0; i < 4; i++) { + if (thread_test_results[i] == 1) { + total_passed++; + } + } + + printk("\n"); + printk("========================================\n"); + printk("FINAL TEST SUMMARY\n"); + printk("========================================\n"); + printk("Threads passed: %d / 4\n", total_passed); + + if (total_passed == 4) { + printk("RESULT: ALL TESTS PASSED\n"); + } else { + printk("RESULT: SOME TESTS FAILED\n"); + } + printk("========================================\n"); + + return 0; +} From 402a054921027576b39487ef49af3c489be9b8f3 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Thu, 9 Apr 2026 18:36:37 +0530 Subject: [PATCH 04/22] drivers: pinctrl: pinctrl_msp: pass iomux config Update pinctrl_msp driver to configure both iomux and pin function in case of AM13E SoC Series. Fixes: ASMZEP-94 Signed-off-by: Sameer Srivastava --- drivers/pinctrl/pinctrl_msp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/pinctrl_msp.c b/drivers/pinctrl/pinctrl_msp.c index b012622d49b6..94978c66ffe6 100644 --- a/drivers/pinctrl/pinctrl_msp.c +++ b/drivers/pinctrl/pinctrl_msp.c @@ -63,10 +63,10 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp DL_GPIO_initPeripheralAnalogFunction(pin_cm); } else if ((iomux >> AM13E_GPIO_INPUT_ENABLE) & 0x1) { /* Pin has input-enable property, configure as peripheral input */ - DL_GPIO_initPeripheralInputFunction(pin_cm, pin_function); + DL_GPIO_initPeripheralInputFunction(pin_cm, (iomux | pin_function)); } else { /* Configure as peripheral output */ - DL_GPIO_initPeripheralOutputFunction(pin_cm, pin_function); + DL_GPIO_initPeripheralOutputFunction(pin_cm, (iomux | pin_function)); } #endif } From 8128fabb222fe93681f7ac2c592fa53f38a26eb4 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Mon, 13 Apr 2026 13:35:08 +0530 Subject: [PATCH 05/22] include: dt-bindings: i2c: header with FIFO trigger levels Create header with Unicomm I2C controller FIFO trigger levels that can be set for the rxfifo-threshold and txfifo-threshold properties in devicetree. Signed-off-by: Sameer Srivastava --- .../zephyr/dt-bindings/i2c/ti-unicomm-i2c.h | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 include/zephyr/dt-bindings/i2c/ti-unicomm-i2c.h diff --git a/include/zephyr/dt-bindings/i2c/ti-unicomm-i2c.h b/include/zephyr/dt-bindings/i2c/ti-unicomm-i2c.h new file mode 100644 index 000000000000..d1baa4192107 --- /dev/null +++ b/include/zephyr/dt-bindings/i2c/ti-unicomm-i2c.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_TI_UNICOMM_I2C_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_TI_UNICOMM_I2C_H_ + +/* I2C Controller Mode FIFO trigger levels */ + +#define I2CC_TX_FIFO_LEVEL_3_4 0x00000001U /* TX FIFO <= 3/4 empty */ +#define I2CC_TX_FIFO_LEVEL_1_2 0x00000002U /* TX FIFO <= 1/2 empty (default) */ +#define I2CC_TX_FIFO_LEVEL_1_4 0x00000003U /* TX FIFO <= 1/4 empty */ +#define I2CC_TX_FIFO_LEVEL_NOT_FULL 0x00000004U /* Opposite of full */ +#define I2CC_TX_FIFO_LEVEL_EMPTY 0x00000005U /* TX FIFO is empty */ +#define I2CC_TX_FIFO_LEVEL_ALMOST_EMPTY 0x00000006U /* TX FIFO <= 1 */ +#define I2CC_TX_FIFO_LEVEL_ALMOST_FULL 0x00000007U /* TX_FIFO >= (MAX_FIFO_LEN-1) */ + +/* I2C Target Mode FIFO trigger levels */ + +#define I2CT_RX_FIFO_LEVEL_1_4 0x00000010U /* RX FIFO >= 1/4 full */ +#define I2CT_RX_FIFO_LEVEL_1_2 0x00000020U /* RX FIFO >= 1/2 full (default) */ +#define I2CT_RX_FIFO_LEVEL_3_4 0x00000030U /* RX FIFO >= 3/4 full */ +#define I2CT_RX_FIFO_LEVEL_NOT_EMPTY 0x00000040U /* Opposite of empty */ +#define I2CT_RX_FIFO_LEVEL_FULL 0x00000050U /* RX FIFO is full */ +#define I2CT_RX_FIFO_LEVEL_ALMOST_FULL 0x00000060U /* RX_FIFO >= (MAX_FIFO_LEN-1) */ +#define I2CT_RX_FIFO_LEVEL_ALMOST_EMPTY 0x00000070U /* RX_FIFO <= 1 */ + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_TI_UNICOMM_I2C_H_ */ From a6f977917d6c53d302c9afcf17fb4f192ff04cc4 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Mon, 23 Mar 2026 17:13:12 +0530 Subject: [PATCH 06/22] dts: bindings: i2c: TI UNICOMM I2C Compatible Define compatible for TI Unicomm peripheral's I2C functionality Signed-off-by: Sameer Srivastava --- dts/bindings/i2c/ti,unicomm-i2c.yaml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 dts/bindings/i2c/ti,unicomm-i2c.yaml diff --git a/dts/bindings/i2c/ti,unicomm-i2c.yaml b/dts/bindings/i2c/ti,unicomm-i2c.yaml new file mode 100644 index 000000000000..03d519e7f5a2 --- /dev/null +++ b/dts/bindings/i2c/ti,unicomm-i2c.yaml @@ -0,0 +1,44 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +description: TI UNICOMM I2C Controller + +compatible: "ti,unicomm-i2c" + +include: [i2c-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + rxfifo-threshold: + type: int + description: | + Trigger level for the RX FIFO. Default is FIFO length, i.e. 16 bytes. + + txfifo-threshold: + type: int + description: | + Trigger level for the TX FIFO. Default is FIFO length, i.e. 16 bytes. + + unicomm-clock-freq: + type: int + default: 100000000 + description: | + BUSCLK input frequency in Hz fed into the I2CC module before + the internal clock divider (CLKDIV). Defaults to 100 MHz. + + unicomm-advanced-i2c: + type: boolean + description: | + Whether or not the UNICOMM instance supports automatic + STOP generation for I2C among other things (refer to TRM). From 43ab4acc08e8389ffe2cfd2951cf6053ad9a08ee Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Mon, 23 Mar 2026 17:14:15 +0530 Subject: [PATCH 07/22] drivers: i2c: i2c_ti_unicomm: I2C Driver for TI Unicomm Peripheral - Define I2C_TI_UNICOMM Kconfig - Create I2C Driver supporting both advanced and non-advanced Unicomms - Add support for configuration of target RX and TX FIFO trigger levels - Add support for standard, fast and fast-plus I2C bus speeds - Add i2cc and i2ct child nodes for unicomm nodes 0 to 5 - Mark unicomm2 and 5 controllers as advanced Unicomms Signed-off-by: Sameer Srivastava --- drivers/i2c/CMakeLists.txt | 1 + drivers/i2c/Kconfig | 1 + drivers/i2c/Kconfig.ti_unicomm | 9 + drivers/i2c/i2c_ti_unicomm.c | 689 +++++++++++++++++++++++++++++++++ drivers/i2c/i2c_ti_unicomm.h | 192 +++++++++ dts/arm/ti/am13/am13e230x.dtsi | 110 ++++++ 6 files changed, 1002 insertions(+) create mode 100644 drivers/i2c/Kconfig.ti_unicomm create mode 100644 drivers/i2c/i2c_ti_unicomm.c create mode 100644 drivers/i2c/i2c_ti_unicomm.h diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index f8c1c31d7167..5bd251e35aa0 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -71,6 +71,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_SMARTBOND i2c_smartbond.c) zephyr_library_sources_ifdef(CONFIG_I2C_SY1XX i2c_sy1xx.c) zephyr_library_sources_ifdef(CONFIG_I2C_TCA954X i2c_tca954x.c) zephyr_library_sources_ifdef(CONFIG_I2C_TELINK_B91 i2c_b91.c) +zephyr_library_sources_ifdef(CONFIG_I2C_TI_UNICOMM i2c_ti_unicomm.c) zephyr_library_sources_ifdef(CONFIG_I2C_WCH i2c_wch.c) zephyr_library_sources_ifdef(CONFIG_I2C_XEC i2c_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_I2C_XEC_V2 i2c_mchp_xec_v2.c) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index c25954fc7d6b..2e6609a82584 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -159,6 +159,7 @@ source "drivers/i2c/Kconfig.stm32" source "drivers/i2c/Kconfig.sy1xx" source "drivers/i2c/Kconfig.tca954x" source "drivers/i2c/Kconfig.test" +source "drivers/i2c/Kconfig.ti_unicomm" source "drivers/i2c/Kconfig.wch" source "drivers/i2c/Kconfig.xec" source "drivers/i2c/Kconfig.xilinx_axi" diff --git a/drivers/i2c/Kconfig.ti_unicomm b/drivers/i2c/Kconfig.ti_unicomm new file mode 100644 index 000000000000..c73dd321836c --- /dev/null +++ b/drivers/i2c/Kconfig.ti_unicomm @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +config I2C_TI_UNICOMM + bool "TI UNICOMM I2C Driver" + default y + depends on DT_HAS_TI_UNICOMM_I2C_ENABLED + help + Enable I2C on TI devices with the UNICOMM peripheral. diff --git a/drivers/i2c/i2c_ti_unicomm.c b/drivers/i2c/i2c_ti_unicomm.c new file mode 100644 index 000000000000..f58cd69857d6 --- /dev/null +++ b/drivers/i2c/i2c_ti_unicomm.c @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_unicomm_i2c + +#include +LOG_MODULE_REGISTER(i2c_ti_unicomm, CONFIG_I2C_LOG_LEVEL); + +#include +#include +#include + +#include "i2c_ti_unicomm.h" + +/* + * Convenience Macros + */ + +#define I2CC_IS_TX_FIFO_FULL(i2cc_base) \ + ((sys_read32(i2cc_base + UNICOMM_I2CC_STATUS) & UNICOMM_I2CC_STATUS_TXFIFOFULL_BIT) != 0) + +#define I2CT_IS_RX_FIFO_EMPTY(i2ct_base) \ + ((sys_read32(i2ct_base + UNICOMM_I2CT_STATUS) & I2CT_STATUS_RXFIFOEMPTY_BIT) != 0) + +#define I2CC_IS_BUSY(i2cc_base) \ + ((sys_read32(i2cc_base + UNICOMM_I2CC_STATUS) & UNICOMM_I2CC_STATUS_BUSY) != 0) + +#define I2CC_IS_RX_FIFO_EMPTY(i2cc_base) \ + ((sys_read32(i2cc_base + UNICOMM_I2CC_STATUS) & 0x00000800U) != 0) + +#define UPDATE_REG(reg_offset, value, mask) \ + { \ + uint32_t tmp = sys_read32(reg_offset); \ + tmp = tmp & ~(mask); \ + sys_write32(tmp | ((value) & (mask)), reg_offset); \ + } + +/* + * Config and Data structs + */ + +enum i2c_ti_unicomm_state { + UC_I2CT_STARTED, + UC_I2CT_TX_BUSY, + UC_I2CT_RX_BUSY, + UC_I2CT_PREEMPTED, + UC_I2C_TIMEOUT, + UC_I2C_ERROR +}; + +struct i2c_ti_unicomm_config { + const struct pinctrl_dev_config *pcfg; + + uint32_t unicomm_inst_base; + uint32_t unicomm_i2cc_base; + uint32_t unicomm_i2ct_base; + + bool unicomm_is_advanced; + + uint8_t clkdiv; /* Clock divide ratio. Register value: 0=div1, 1=div2, ... 7=div8 */ + + uint32_t busclk_hz; /* BUSCLK input frequency in Hz (before clkdiv) */ + + uint32_t tx_fifo_threshold; + uint32_t rx_fifo_threshold; + + uint32_t timer_period; + + void (*irq_config_func)(const struct device *dev); +}; + +struct i2c_ti_unicomm_data { + volatile enum i2c_ti_unicomm_state state; + +#if defined(CONFIG_I2C_TARGET) + struct i2c_target_config *target_cfg; + + bool is_target; +#endif +}; + +/* + * Helper functions + */ + +/* Reset unicomm instance */ +static inline void unicomm_reset(uint32_t base) +{ + sys_write32(RESET_CTL_KEY_UNLOCK | RESET_CTL_STICKY_BIT_CLEAR | RESET_CTL_ASSERT_RESET, + base + UNICOMM_RESET_CTL); +} + +/* Enable power for UNICOMM instance */ +static inline void unicomm_enable_power(uint32_t base) +{ + sys_write32(PWREN_KEY | PWREN_ENABLE, base + UNICOMM_POWER_EN); + k_sleep(K_CYC(20)); +} + +static int i2cc_fill_tx_fifo(uint32_t base, uint8_t *buf, uint32_t len) +{ + int n_filled_bytes = 0; + + for (; n_filled_bytes < len; n_filled_bytes++) { + if (!(I2CC_IS_TX_FIFO_FULL(base))) { + sys_write32(buf[n_filled_bytes], base + UNICOMM_I2CC_TXDATA); + } else { + break; + } + } + + return n_filled_bytes; +} + +static void i2cc_set_flags(const struct device *dev, uint32_t flags) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + + UPDATE_REG(cfg->unicomm_i2cc_base + UNICOMM_I2CC_CONTROL, flags, + UNICOMM_I2CC_CONTROL_START_MASK | UNICOMM_I2CC_CONTROL_STOP_MASK | + UNICOMM_I2CC_CONTROL_ACK_MASK | UNICOMM_I2CC_CONTROL_FRAME_START_MASK); +} + +static inline void i2cc_set_target_address(uint32_t base, uint16_t addr, bool dir_transmit) +{ + UPDATE_REG(base + UNICOMM_I2CC_TARGET_ADDRESS, + (addr << I2CC_TARGET_ADDRESS_OFFSET) | + ((dir_transmit) ? I2CC_TARGET_ADDRESS_DIRECTION_TRANSMIT + : I2CC_TARGET_ADDRESS_DIRECTION_RECEIVE), + UNICOMM_I2CC_TARGET_ADDRESS_ADDR_MASK | + UNICOMM_I2CC_TARGET_ADDRESS_DIRECTION_MASK); +} + +static inline void i2cc_send_frame(uint32_t base) +{ + UPDATE_REG(base + UNICOMM_I2CC_CONTROL, I2CC_CONTROL_START_FRAME, + UNICOMM_I2CC_CONTROL_FRAME_START_MASK); +} + +static inline void i2cc_wait_not_busy(uint32_t base) +{ + while (I2CC_IS_BUSY(base)) { + k_sleep(K_CYC(20)); + } +} + +static inline void i2cc_set_blen(uint32_t base, uint32_t len) +{ + UPDATE_REG(base + UNICOMM_I2CC_CONTROL, (len << I2CC_CONTROL_BLEN_OFFSET), + UNICOMM_I2CC_CONTROL_BLEN_MASK); +} + +static inline void i2cc_set_enable(uint32_t base, bool enable) +{ + uint32_t tmp = sys_read32(base + UNICOMM_I2CC_CONFIG); + + if (enable) { + tmp |= I2CC_CONFIG_ENABLE; + } else { + tmp &= ~I2CC_CONFIG_ENABLE; + } + sys_write32(tmp, base + UNICOMM_I2CC_CONFIG); +} + +#if defined(CONFIG_I2C_TARGET) +static inline void i2ct_clear_tx_fifo(uint32_t base) +{ + sys_write32(sys_read32(base + UNICOMM_I2CT_IFLS) | I2CT_IFLS_TXCLR_MASK, + base + UNICOMM_I2CT_IFLS); + while ((sys_read32(base + UNICOMM_I2CT_STATUS) & I2CT_STATUS_TXFE_BIT) == 0) { + } + sys_write32(sys_read32(base + UNICOMM_I2CT_IFLS) & ~I2CT_IFLS_TXCLR_MASK, + base + UNICOMM_I2CT_IFLS); +} + +static inline void i2ct_send_nack(uint32_t base) +{ + UPDATE_REG(base + UNICOMM_I2CT_ACKCTL, I2CT_ACKCTL_ACKOEN_ENABLE | I2CT_ACKCTL_ACKOVAL_NACK, + I2CT_ACKCTL_ACKOEN_MASK | I2CT_ACKCTL_ACKOVAL_MASK); +} +#endif /* CONFIG_I2C_TARGET */ + +static int i2c_ti_unicomm_init(const struct device *dev) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + struct i2c_ti_unicomm_data *data = dev->data; + int err; + + unicomm_reset(cfg->unicomm_inst_base); + + /* Reset Peripheral */ + sys_write32(0, cfg->unicomm_i2cc_base + UNICOMM_I2CC_CONTROL); + + unicomm_enable_power(cfg->unicomm_inst_base); + + /* Set instance mode to I2CC */ + sys_write32(IPMODE_CONTROLLER, cfg->unicomm_inst_base + UNICOMM_MODE); + + /* Configure clock divide ratio and select BUSCLK as clock source */ + sys_write32(cfg->clkdiv, cfg->unicomm_i2cc_base + UNICOMM_I2CC_CLKDIV); + sys_write32(I2CC_CLKSEL_BUSCLK_ENABLE, cfg->unicomm_i2cc_base + UNICOMM_I2CC_CLKSEL); + + /* Apply pinctrl config */ + err = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (err < 0) { + return err; + } + + /* Set timer period */ + sys_write32(cfg->timer_period, cfg->unicomm_i2cc_base + UNICOMM_I2CC_TPR); + + /* Enable module with clock stretching */ + sys_write32(sys_read32(cfg->unicomm_i2cc_base + UNICOMM_I2CC_CONFIG) | I2CC_CONFIG_ENABLE | + I2CC_CONFIG_CLKSTRETCH_ENABLE, + cfg->unicomm_i2cc_base + UNICOMM_I2CC_CONFIG); + +#if defined(CONFIG_I2C_TARGET) + data->is_target = false; +#endif + + LOG_INF("I2C Controller init done for 0x%08x", cfg->unicomm_i2cc_base); + + return 0; +} + +static int i2c_ti_unicomm_configure(const struct device *dev, uint32_t dev_config) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + struct i2c_ti_unicomm_data *data = dev->data; + uint32_t speed_hz; + uint32_t functional_clk_hz; + uint32_t tpr; + + /* Reset a controller device */ + if ((dev_config & I2C_MODE_CONTROLLER)) { + /* Changing controller/target mode at runtime is not supported */ + if (data->is_target == true) { + return -ENOTSUP; + } + + int ret = i2c_ti_unicomm_init(dev); + if (ret < 0) { + return ret; + } + } else { + return -ENOTSUP; + } + + switch (I2C_SPEED_GET(dev_config)) { + case I2C_SPEED_STANDARD: + speed_hz = 100000U; + break; + case I2C_SPEED_FAST: + speed_hz = 400000U; + break; + case I2C_SPEED_FAST_PLUS: + speed_hz = 1000000U; + break; + default: + return -ENOTSUP; + } + + /* Functional clock = BUSCLK / (clkdiv_reg_value + 1) */ + functional_clk_hz = cfg->busclk_hz / ((uint32_t)cfg->clkdiv + 1U); + + /* TPR = functional_clk_hz / (SCL_LP_HP * speed_hz) - 1 */ + tpr = functional_clk_hz / (I2CC_SCL_LP_HP * speed_hz); + if (tpr == 0U) { + return -EINVAL; + } + tpr -= 1U; + + if (tpr < 1U || tpr > 127U) { + return -EINVAL; + } + + /* Disable module, update TPR, re-enable */ + i2cc_set_enable(cfg->unicomm_i2cc_base, false); + + sys_write32(tpr, cfg->unicomm_i2cc_base + UNICOMM_I2CC_TPR); + + i2cc_set_enable(cfg->unicomm_i2cc_base, true); + + return 0; +} + +static int i2c_ti_unicomm_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, + uint16_t addr) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + + /* For each entry of msgs, loop over bytes of msgs[i].buf, filling TX FIFO and + * starting a transfer */ + for (int i = 0; i < num_msgs; i++) { + struct i2c_msg msg = msgs[i]; + + char *buf_ptr = msg.buf; + int bytes_sent = 0; + + if ((msg.flags & (uint32_t)I2C_MSG_READ) != I2C_MSG_READ) { + i2cc_set_target_address(cfg->unicomm_i2cc_base, addr, DIRECTION_WRITE); + + if (cfg->unicomm_is_advanced) { + /* Advanced: set BLEN, pre-fill FIFO, trigger once, then keep + * filling FIFO until all bytes are queued. + */ + int filled = + i2cc_fill_tx_fifo(cfg->unicomm_i2cc_base, buf_ptr, msg.len); + + buf_ptr += filled; + bytes_sent += filled; + + i2cc_set_flags(dev, I2CC_CONTROL_START_ENABLE | + I2CC_CONTROL_STOP_ENABLE | + I2CC_CONTROL_ACK_ENABLE); + + i2cc_set_blen(cfg->unicomm_i2cc_base, msg.len); + + i2cc_send_frame(cfg->unicomm_i2cc_base); + + /* Polling Write */ + while (bytes_sent < msg.len) { + if (!I2CC_IS_TX_FIFO_FULL(cfg->unicomm_i2cc_base)) { + filled = i2cc_fill_tx_fifo(cfg->unicomm_i2cc_base, + buf_ptr, + msg.len - bytes_sent); + + buf_ptr += filled; + bytes_sent += filled; + } else if (!I2CC_IS_BUSY(cfg->unicomm_i2cc_base)) { + /* HW aborted (e.g. NACK): FIFO not draining */ + return -EIO; + } + } + + i2cc_wait_not_busy(cfg->unicomm_i2cc_base); + } else { + /* Non-advanced: one byte per FRM_START trigger. + * START_ENABLE only on the first byte to generate START+address. + * Subsequent bytes use START_DISABLE to continue the same + * transaction (no RESTART, no repeated address phase). + * STOP after the last byte. + */ + + for (int byte_idx = 0; byte_idx < msg.len; byte_idx++) { + bool is_first_byte = (byte_idx == 0); + bool is_last_byte = (byte_idx == msg.len - 1); + + sys_write32(msg.buf[byte_idx], + cfg->unicomm_i2cc_base + UNICOMM_I2CC_TXDATA); + + i2cc_set_flags( + dev, + (is_first_byte ? I2CC_CONTROL_START_ENABLE + : I2CC_CONTROL_START_DISABLE) | + (is_last_byte ? I2CC_CONTROL_STOP_ENABLE + : I2CC_CONTROL_STOP_DISABLE) | + I2CC_CONTROL_ACK_ENABLE); + + i2cc_send_frame(cfg->unicomm_i2cc_base); + + i2cc_wait_not_busy(cfg->unicomm_i2cc_base); + } + + /* Send STOP condition */ + i2cc_set_flags(dev, I2CC_CONTROL_START_DISABLE | + I2CC_CONTROL_STOP_ENABLE | + I2CC_CONTROL_ACK_DISABLE); + + i2cc_send_frame(cfg->unicomm_i2cc_base); + } + } else if ((msg.flags & (uint32_t)I2C_MSG_READ) == I2C_MSG_READ) { + i2cc_set_target_address(cfg->unicomm_i2cc_base, addr, DIRECTION_READ); + + if (cfg->unicomm_is_advanced) { + /* Advanced: BLEN tells the hardware how many bytes to receive in + * one transaction; it handles ACK/NACK/STOP automatically. + */ + i2cc_set_flags(dev, I2CC_CONTROL_START_ENABLE | + I2CC_CONTROL_STOP_ENABLE | + I2CC_CONTROL_ACK_DISABLE); + + i2cc_set_blen(cfg->unicomm_i2cc_base, msg.len); + + i2cc_send_frame(cfg->unicomm_i2cc_base); + + int bytes_received = 0; + + /* Polling Read */ + while (bytes_received < msg.len) { + /* Wait until a byte arrives or the transfer ends */ + while (I2CC_IS_RX_FIFO_EMPTY(cfg->unicomm_i2cc_base)) { + k_sleep(K_CYC(20)); + } + + while (!I2CC_IS_RX_FIFO_EMPTY(cfg->unicomm_i2cc_base) && + bytes_received < msg.len) { + msg.buf[bytes_received++] = + sys_read32(cfg->unicomm_i2cc_base + + UNICOMM_I2CC_RXDATA) & + BYTE_MASK; + } + } + } else { + /* Non-advanced: one byte per FRM_START trigger. + * START=ENABLE only on the first byte (generates START+address+R). + * ACK=ENABLE for all bytes except the last. + * Last byte: ACK=DISABLE (NACK) + STOP. + */ + for (int b = 0; b < msg.len; b++) { + bool is_first_byte = (b == 0); + bool is_last_byte = (b == msg.len - 1); + + i2cc_set_flags( + dev, + (is_first_byte ? I2CC_CONTROL_START_ENABLE + : I2CC_CONTROL_START_DISABLE) | + (is_last_byte ? I2CC_CONTROL_STOP_ENABLE + : I2CC_CONTROL_STOP_DISABLE) | + I2CC_CONTROL_ACK_ENABLE); + + i2cc_send_frame(cfg->unicomm_i2cc_base); + + /* Wait for the received byte to arrive in the RX FIFO. + * Polling RX FIFO empty is more reliable than polling BUSY + * for reads: BUSY may clear briefly between FRM_STARTs as + * the bus idles between bytes, causing while(!BUSY) to spin + * forever if the pulse is missed. + */ + while (I2CC_IS_RX_FIFO_EMPTY(cfg->unicomm_i2cc_base)) { + k_sleep(K_CYC(20)); + } + + msg.buf[b] = sys_read32(cfg->unicomm_i2cc_base + + UNICOMM_I2CC_RXDATA) & + 0xFF; + } + } + } + } + + return 0; +} + +#if defined(CONFIG_I2C_TARGET) +static inline int i2c_ti_unicomm_target_register(const struct device *dev, + struct i2c_target_config *target_cfg) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + struct i2c_ti_unicomm_data *data = dev->data; + + unicomm_reset(cfg->unicomm_inst_base); + unicomm_enable_power(cfg->unicomm_inst_base); + + /* Set I2C Target mode */ + sys_write32(IPMODE_TARGET, cfg->unicomm_inst_base + UNICOMM_MODE); + k_sleep(K_CYC(20)); + + /* Clock config */ + sys_write32(cfg->clkdiv, cfg->unicomm_i2ct_base + UNICOMM_I2CT_CLKDIV); + sys_write32(I2CC_CLKSEL_BUSCLK_ENABLE, cfg->unicomm_i2ct_base + UNICOMM_I2CT_CLKSEL); + + /* Set target address */ + if (target_cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) { + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_OAR, I2CT_OAR_MODE_10BIT, + I2CT_OAR_MODE_MASK); + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_OAR, target_cfg->address, + I2CT_10BIT_ADDR_MASK); + } else { + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_OAR, I2CT_OAR_MODE_7BIT, + I2CT_OAR_MODE_MASK); + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_OAR, target_cfg->address, + I2CT_7BIT_ADDR_MASK); + } + + /* Enable Own Address */ + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_OAR, I2CT_OAR_ENABLE, + I2CT_OAR_ENABLE_MASK); + + /* Enabled interrupts */ + uint32_t triggers_mask = IMASK_I2CT_RXDONE | IMASK_I2CT_TXDONE | IMASK_I2CT_TXEMPTY | + IMASK_I2CT_TXUNFL | IMASK_I2CT_RXOVFL | IMASK_I2CT_START | + IMASK_I2CT_STOP; + + /* Set RX FIFO Threshold */ + if (cfg->rx_fifo_threshold != 0) { + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_IFLS, cfg->rx_fifo_threshold, + I2CT_RX_FIFO_LEVEL_MASK); + triggers_mask |= IMASK_I2CT_RXTRG; /* enable RXFIFO trigger */ + } + + /* Set TX FIFO Threshold */ + if (cfg->tx_fifo_threshold != 0) { + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_IFLS, cfg->tx_fifo_threshold, + I2CT_TX_FIFO_LEVEL_MASK); + triggers_mask |= IMASK_I2CT_TXTRG; /* enable TXFIFO trigger */ + } + + /* Set a default RXFIFO trigger if none configured by user */ + if (cfg->rx_fifo_threshold == 0) { + UPDATE_REG(cfg->unicomm_i2ct_base + UNICOMM_I2CT_IFLS, I2CT_RX_FIFO_LEVEL_NOT_EMPTY, + I2CT_RX_FIFO_LEVEL_MASK); + triggers_mask |= IMASK_I2CT_RXTRG; /* enable RXFIFO trigger */ + } + + /* Clock stretching would deadlock when controller and target share a single + * CPU with a blocking polling transfer. Target can't drain its RX FIFO + * while the controller is busy-waiting for SCL to be released. */ + sys_write32(sys_read32(cfg->unicomm_i2ct_base + UNICOMM_I2CT_CONTROL) | + I2CT_CONTROL_CLKSTRETCH_MASK | I2CT_CONTROL_TXEMPTY_ON_TREQ | + I2CT_CONTROL_TXWAIT_STALE_TXFIFO, + cfg->unicomm_i2ct_base + UNICOMM_I2CT_CONTROL); + + /* Enable module */ + sys_write32(sys_read32(cfg->unicomm_i2ct_base + UNICOMM_I2CT_CONTROL) | I2CT_CONTROL_ENABLE, + cfg->unicomm_i2ct_base + UNICOMM_I2CT_CONTROL); + + /* Enable I2CT Interrupts */ + sys_write32(IMASK_ALL, cfg->unicomm_i2ct_base + UNICOMM_I2CT_CPU_INT_ICLR); + sys_write32(triggers_mask, cfg->unicomm_i2ct_base + UNICOMM_I2CT_CPU_INT_IMASK); + + cfg->irq_config_func(dev); + + data->is_target = true; + data->target_cfg = target_cfg; + + LOG_INF("I2C Target init done for 0x%08x", cfg->unicomm_i2ct_base); + + return 0; +} + +static void i2c_ti_unicomm_target_isr(const struct device *dev) +{ + const struct i2c_ti_unicomm_config *cfg = dev->config; + struct i2c_ti_unicomm_data *data = dev->data; + + uint32_t pending_interrupt; + + int ret; + + do { + pending_interrupt = sys_read32(cfg->unicomm_i2ct_base + UNICOMM_I2CT_CPU_INT_IIDX); + + switch (pending_interrupt) { + case I2CT_IIDX_TARGET_START: + data->state = UC_I2CT_STARTED; + i2ct_clear_tx_fifo(cfg->unicomm_i2ct_base); + break; + + case I2CT_IIDX_TARGET_TXFIFO_EMPTY: { + uint8_t byte = 0; + if (data->state == UC_I2CT_STARTED) { + /* First byte of a read transaction */ + int rr_ret = 0; + + if (data->target_cfg->callbacks->read_requested != NULL) { + rr_ret = data->target_cfg->callbacks->read_requested( + data->target_cfg, &byte); + } + if (rr_ret < 0) { + byte = 0; + data->state = UC_I2C_ERROR; + } else { + data->state = UC_I2CT_TX_BUSY; + } + } else if (data->state == UC_I2CT_TX_BUSY) { + /* Subsequent bytes */ + if (data->target_cfg->callbacks->read_processed != NULL) { + data->target_cfg->callbacks->read_processed( + data->target_cfg, &byte); + } + } + sys_write32((uint32_t)byte & 0xFFU, + cfg->unicomm_i2ct_base + UNICOMM_I2CT_TXDATA); + break; + } + + case I2CT_IIDX_TARGET_TX_DONE: + break; + + case I2CT_IIDX_TARGET_RX_DONE: + if (data->state == UC_I2CT_STARTED) { + data->state = UC_I2CT_RX_BUSY; + if (data->target_cfg->callbacks->write_requested != NULL) { + ret = data->target_cfg->callbacks->write_requested( + data->target_cfg); + if (ret < 0) { + i2ct_send_nack(cfg->unicomm_i2ct_base); + } + } + } + break; + + case I2CT_IIDX_TARGET_RXFIFO_TRIGGER: + if (data->state == UC_I2CT_RX_BUSY) { + if (data->target_cfg->callbacks->write_received != NULL) { + while (!I2CT_IS_RX_FIFO_EMPTY(cfg->unicomm_i2ct_base)) { + ret = data->target_cfg->callbacks->write_received( + data->target_cfg, + sys_read32(cfg->unicomm_i2ct_base + + UNICOMM_I2CT_RXDATA)); + if (ret < 0) { + i2ct_send_nack(cfg->unicomm_i2ct_base); + break; + } + } + } + } + break; + + case I2CT_IIDX_TARGET_STOP: + sys_write32(I2CT_ACKCTL_ACKOEN_DISABLE, + cfg->unicomm_i2ct_base + UNICOMM_I2CT_ACKCTL); + if (data->target_cfg->callbacks->stop != NULL) { + data->target_cfg->callbacks->stop(data->target_cfg); + } + break; + + case I2CT_IIDX_TARGET_TXFIFO_UNDERFLOW: + break; + + default: + if (pending_interrupt != 0) { + LOG_DBG("TARGET ISR: pending interrupts = 0x%08x\n", + pending_interrupt); + } + break; + } + } while (pending_interrupt != I2CT_IIDX_NO_INTR); +} +#endif /* CONFIG_I2C_TARGET */ + +static void i2c_ti_unicomm_isr(const struct device *dev) +{ + struct i2c_ti_unicomm_data *data = dev->data; + +#if defined(CONFIG_I2C_TARGET) + if (data->is_target == true) { + i2c_ti_unicomm_target_isr(dev); + return; + } +#endif /* CONFIG_I2C_TARGET */ +} + +static DEVICE_API(i2c, i2c_ti_unicomm_api) = {.configure = i2c_ti_unicomm_configure, + .transfer = i2c_ti_unicomm_transfer, +#if defined(CONFIG_I2C_TARGET) + .target_register = i2c_ti_unicomm_target_register +#endif +}; + +#define I2C_TI_UNICOMM_IRQ_FUNC(index) \ + static void i2c_ti_unicomm_irq_config_func_##index(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), i2c_ti_unicomm_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + } + +#define I2C_TI_UNICOMM_INIT(index) \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + I2C_TI_UNICOMM_IRQ_FUNC(index); \ + \ + static const struct i2c_ti_unicomm_config i2c_config_##index = { \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ + .unicomm_inst_base = \ + (uint32_t)(DT_REG_ADDR_BY_IDX(DT_PARENT(DT_DRV_INST(index)), 0)), \ + .unicomm_i2cc_base = (uint32_t)(DT_INST_REG_ADDR(index)), \ + .unicomm_i2ct_base = (uint32_t)(DT_INST_REG_ADDR(index)), \ + .unicomm_is_advanced = DT_INST_PROP(index, unicomm_advanced_i2c), \ + .clkdiv = CLKDIV_DIVIDE_BY_1, \ + .busclk_hz = DT_INST_PROP_OR(index, unicomm_clock_freq, 100000000U), \ + .timer_period = 99, \ + .irq_config_func = i2c_ti_unicomm_irq_config_func_##index, \ + .rx_fifo_threshold = DT_INST_PROP_OR(index, rxfifo_threshold, 0), \ + .tx_fifo_threshold = DT_INST_PROP_OR(index, txfifo_threshold, 0), \ + }; \ + \ + static struct i2c_ti_unicomm_data i2c_data_##index; \ + \ + I2C_DEVICE_DT_INST_DEFINE(index, i2c_ti_unicomm_init, NULL, &i2c_data_##index, \ + &i2c_config_##index, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \ + &i2c_ti_unicomm_api); + +DT_INST_FOREACH_STATUS_OKAY(I2C_TI_UNICOMM_INIT) diff --git a/drivers/i2c/i2c_ti_unicomm.h b/drivers/i2c/i2c_ti_unicomm.h new file mode 100644 index 000000000000..4d94305d9669 --- /dev/null +++ b/drivers/i2c/i2c_ti_unicomm.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_I2C_TI_UNICOMM_H_ +#define ZEPHYR_DRIVERS_I2C_TI_UNICOMM_H_ + +/* + * UNICOMM Register Offsets + */ + +#define UNICOMM_POWER_EN 0x00000800U +#define UNICOMM_RESET_CTL 0x00000804U +#define UNICOMM_CLOCK_CFG 0x00000808U +#define UNICOMM_STATUS 0x00000814U +#define UNICOMM_MODE 0x00001100U + +/* + * UNICOMM I2CC Register Offsets + */ + +#define UNICOMM_I2CC_CLKDIV 0x00000000U +#define UNICOMM_I2CC_CLKSEL 0x00000008U +/* SKIPPED - cpu int, dma trig0,1 */ +#define UNICOMM_I2CC_INTCTL 0x000000E4U +#define UNICOMM_I2CC_CONTROL 0x00000100U +#define UNICOMM_I2CC_CONFIG 0x00000104U +#define UNICOMM_I2CC_STATUS 0x00000108U +#define UNICOMM_I2CC_FIFO_LEVEL 0x0000010CU +#define UNICOMM_I2CC_TPR 0x00000110U +/* SKIPPED - glitch filter control, bus monitor, */ +#define UNICOMM_I2CC_TXDATA 0x00000120U +#define UNICOMM_I2CC_RXDATA 0x00000124U +/* SKIPPED - PEC status */ +#define UNICOMM_I2CC_TARGET_ADDRESS 0x0000014CU +#define UNICOMM_I2CC_TIMEOUT_COUNT 0x00000150U +#define UNICOMM_I2CC_TIMEOUT_CONTROL 0x00000154U +/* SKIPPED - PEC control */ + +/* + * UNICOMM I2CT Register Offsets + */ + +#define UNICOMM_I2CT_CLKDIV UNICOMM_I2CC_CLKDIV +#define UNICOMM_I2CT_CLKSEL UNICOMM_I2CC_CLKSEL +#define UNICOMM_I2CT_STATUS UNICOMM_I2CC_STATUS +#define UNICOMM_I2CT_ACKCTL 0x00000104U +#define UNICOMM_I2CT_CPU_INT_IIDX 0x00000020U +#define UNICOMM_I2CT_CPU_INT_IMASK 0x00000028U +#define UNICOMM_I2CT_CPU_INT_ICLR 0x00000048U +/* SKIPPED - dma trig0,1 */ +#define UNICOMM_I2CT_INTCTL UNICOMM_I2CC_INTCTL +#define UNICOMM_I2CT_CONTROL UNICOMM_I2CC_CONTROL +#define UNICOMM_I2CT_OAR 0x0000014CU +#define UNICOMM_I2CT_IFLS 0x0000010CU +#define UNICOMM_I2CT_TXDATA 0x00000120U +#define UNICOMM_I2CT_RXDATA 0x00000124U + +/* + * Peripheral Configuration Values + */ + +#define PWREN_ENABLE 0x00000001U +#define PWREN_DISABLE 0x00000000U +#define PWREN_KEY 0x26000000U +#define IPMODE_CONTROLLER 0x00000002U +#define IPMODE_TARGET 0x00000003U + +#define RESET_CTL_KEY_UNLOCK 0xB1000000U +#define RESET_CTL_STICKY_BIT_CLEAR 0x00000002U +#define RESET_CTL_ASSERT_RESET 0x00000001U + +/* + * I2CC Configuration Values + */ + +#define I2CC_CLKSEL_BUSCLK_ENABLE 0x00000008U + +#define I2CC_CONFIG_ENABLE 0x00000001U +#define I2CC_CONFIG_CLKSTRETCH_ENABLE 0x00000004U + +#define I2CC_TARGET_ADDRESS_OFFSET 0x1 +#define I2CC_TARGET_ADDRESS_DIRECTION_TRANSMIT 0x00000000U +#define I2CC_TARGET_ADDRESS_DIRECTION_RECEIVE 0x00000001U + +#define I2CC_CONTROL_BLEN_OFFSET 0x00000010U +#define I2CC_CONTROL_START_ENABLE 0x00000002U +#define I2CC_CONTROL_START_DISABLE 0x00000000U +#define I2CC_CONTROL_STOP_ENABLE 0x00000004U +#define I2CC_CONTROL_STOP_DISABLE 0x00000000U +#define I2CC_CONTROL_ACK_ENABLE 0x00000008U +#define I2CC_CONTROL_ACK_DISABLE 0x00000000U +#define I2CC_CONTROL_START_FRAME 0x00000001U + +#define I2CC_RX_FIFO_LEVEL_NOT_EMPTY 0x00000040U + +/* SCL period = (1 + TPR) * (SCL_LP + SCL_HP) * CLK_PRD, where SCL_LP=6, SCL_HP=4 */ +#define I2CC_SCL_LP_HP 10U + +/* + * I2CT Configuration Values + */ +#define I2CT_RX_FIFO_LEVEL_NOT_EMPTY 0x00000040U + +#define I2CT_CONTROL_ENABLE 0x00000001U +#define I2CT_CONTROL_TXEMPTY_ON_TREQ 0x00000008U +#define I2CT_CONTROL_TXWAIT_STALE_TXFIFO 0x00000020U + +#define I2CT_OAR_MODE_7BIT 0x00000000U +#define I2CT_OAR_MODE_10BIT 0x00008000U +#define I2CT_OAR_ENABLE 0x00004000U + +/* + * I2CT Interrupt Index Values (read from UNICOMM_I2CT_CPU_INT_IIDX) + */ + +#define I2CT_IIDX_NO_INTR 0x00000000U +#define I2CT_IIDX_TARGET_RX_DONE 0x00000001U +#define I2CT_IIDX_TARGET_TX_DONE 0x00000002U +#define I2CT_IIDX_TARGET_RXFIFO_TRIGGER 0x00000003U +#define I2CT_IIDX_TARGET_RXFIFO_FULL 0x00000005U +#define I2CT_IIDX_TARGET_TXFIFO_EMPTY 0x00000006U +#define I2CT_IIDX_TARGET_TXFIFO_UNDERFLOW 0x00000007U +#define I2CT_IIDX_TARGET_START 0x0000000AU +#define I2CT_IIDX_TARGET_STOP 0x0000000BU + +/* + * Masks + */ + +#define UNICOMM_I2CC_STATUS_TXFIFOFULL_BIT 0x00004000U +#define UNICOMM_I2CC_STATUS_BUSY 0x00000001U +#define UNICOMM_I2CC_STATUS_ERR 0x00000002U /* NACK received */ + +#define UNICOMM_I2CC_FIFO_LEVEL_TXCLR 0x00000008U /* flush TX FIFO */ + +#define UNICOMM_I2CC_TARGET_ADDRESS_DIRECTION_MASK 0x00000001U +#define UNICOMM_I2CC_TARGET_ADDRESS_ADDR_MASK 0x000007FEU + +#define UNICOMM_I2CC_CONTROL_BLEN_MASK 0x0FFF0000U +#define UNICOMM_I2CC_CONTROL_START_MASK 0x00000002U +#define UNICOMM_I2CC_CONTROL_STOP_MASK 0x00000004U +#define UNICOMM_I2CC_CONTROL_ACK_MASK 0x00000008U +#define UNICOMM_I2CC_CONTROL_FRAME_START_MASK 0x00000001U + +#define I2CT_CONTROL_CLKSTRETCH_MASK 0x00100000U + +#define I2CT_OAR_MODE_MASK 0x00008000U +#define I2CT_OAR_ADDR_MASK 0x000003FFU +#define I2CT_OAR_ENABLE_MASK 0x00000400U + +#define I2CT_7BIT_ADDR_MASK 0x0000007F +#define I2CT_10BIT_ADDR_MASK 0x000003FF + +#define I2CT_RX_FIFO_LEVEL_MASK 0x00000070U +#define I2CT_TX_FIFO_LEVEL_MASK 0x00000007U + +#define I2CT_STATUS_RXFIFOEMPTY_BIT 0x00000800U + +#define I2CT_IFLS_TXCLR_MASK 0x00000008U /* bit 3: set to flush TX FIFO */ +#define I2CT_STATUS_TXFE_BIT 0x00002000U /* bit 13: TX FIFO empty */ + +#define I2CT_ACKCTL_ACKOEN_MASK 0x00000001U /* bit 0: ACK override enable */ +#define I2CT_ACKCTL_ACKOVAL_MASK 0x00000002U /* bit 1: ACK / NACK setting */ +#define I2CT_ACKCTL_ACKOEN_ENABLE 0x00000001U +#define I2CT_ACKCTL_ACKOEN_DISABLE 0x00000000U +#define I2CT_ACKCTL_ACKOVAL_MASK 0x00000002U /* bit 1: 0=ACK, 1=NACK */ +#define I2CT_ACKCTL_ACKOVAL_NACK 0x00000002U +#define I2CT_ACKCTL_ACKOVAL_ACK 0x00000000U + +/* Miscellaneous */ +#define IMASK_ALL 0xFFFFFFFF + +#define IMASK_I2CT_RXDONE 0x00000001U +#define IMASK_I2CT_TXDONE 0x00000002U +#define IMASK_I2CT_RXTRG 0x00000004U +#define IMASK_I2CT_TXTRG 0x00000008U +#define IMASK_I2CT_RXFULL 0x00000010U +#define IMASK_I2CT_TXEMPTY 0x00000020U +#define IMASK_I2CT_TXUNFL 0x00000040U +#define IMASK_I2CT_RXOVFL 0x00000080U +#define IMASK_I2CT_START 0x00000200U +#define IMASK_I2CT_STOP 0x00000400U + +#define BYTE_MASK 0xFF +#define DIRECTION_WRITE 1 +#define DIRECTION_READ 0 +#define CLKDIV_DIVIDE_BY_1 0 + +#endif /* ZEPHYR_DRIVERS_I2C_TI_UNICOMM_H_ */ diff --git a/dts/arm/ti/am13/am13e230x.dtsi b/dts/arm/ti/am13/am13e230x.dtsi index 585860313e1b..2e016682b0f5 100644 --- a/dts/arm/ti/am13/am13e230x.dtsi +++ b/dts/arm/ti/am13/am13e230x.dtsi @@ -93,6 +93,24 @@ reg = <0x40600000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm0_i2cc: unicomm0_i2cc@40608000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <34 0>; + reg = <0x40608000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm0_i2ct: unicomm0_i2ct@40610000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <34 0>; + reg = <0x40610000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm1: unicomm@40632000 { @@ -107,6 +125,24 @@ reg = <0x40601000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm1_i2cc: unicomm1_i2cc@40609000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <35 0>; + reg = <0x40609000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm1_i2ct: unicomm1_i2ct@40611000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <35 0>; + reg = <0x40611000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm2: unicomm@40634000 { @@ -121,6 +157,25 @@ reg = <0x40602000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm2_i2cc: unicomm2_i2cc@4060A000 { + compatible = "ti,unicomm-i2c"; + unicomm-advanced-i2c; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <36 0>; + reg = <0x4060A000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm2_i2ct: unicomm2_i2ct@40612000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <36 0>; + reg = <0x40612000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm3: unicomm@40670000 { @@ -135,6 +190,24 @@ reg = <0x40640000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm3_i2cc: unicomm3_i2cc@40648000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <37 0>; + reg = <0x40648000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm3_i2ct: unicomm3_i2ct@40650000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <37 0>; + reg = <0x40650000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm4: unicomm@40672000 { @@ -149,6 +222,24 @@ reg = <0x40641000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm4_i2cc: unicomm4_i2cc@40649000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <38 0>; + reg = <0x40649000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm4_i2ct: unicomm4_i2ct@40651000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <38 0>; + reg = <0x40651000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm5: unicomm@40674000 { @@ -163,6 +254,25 @@ reg = <0x40642000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm5_i2cc: unicomm5_i2cc@4064A000 { + compatible = "ti,unicomm-i2c"; + unicomm-advanced-i2c; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <39 0>; + reg = <0x4064A000 DT_SIZE_K(4)>; + status = "disabled"; + }; + + unicomm5_i2ct: unicomm5_i2ct@40652000 { + compatible = "ti,unicomm-i2c"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <39 0>; + reg = <0x40652000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; }; }; From 43664ad2de405410ab221f791cde61b084d6c8c8 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Mon, 20 Apr 2026 16:50:46 +0530 Subject: [PATCH 08/22] TI: samples: i2c_loopback_test: test application for UNICOMM I2C driver Test application for UNICOMM I2C native Zephyr driver. Signed-off-by: Sameer Srivastava --- samples/i2c_loopback_test/CMakeLists.txt | 8 + .../boards/am13e230x_lp_e2.overlay | 119 +++++++++++++ samples/i2c_loopback_test/prj.conf | 11 ++ samples/i2c_loopback_test/src/main.c | 156 ++++++++++++++++++ samples/i2c_loopback_test/src/target.c | 136 +++++++++++++++ 5 files changed, 430 insertions(+) create mode 100644 samples/i2c_loopback_test/CMakeLists.txt create mode 100644 samples/i2c_loopback_test/boards/am13e230x_lp_e2.overlay create mode 100644 samples/i2c_loopback_test/prj.conf create mode 100644 samples/i2c_loopback_test/src/main.c create mode 100644 samples/i2c_loopback_test/src/target.c diff --git a/samples/i2c_loopback_test/CMakeLists.txt b/samples/i2c_loopback_test/CMakeLists.txt new file mode 100644 index 000000000000..0616783ed092 --- /dev/null +++ b/samples/i2c_loopback_test/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(i2c_loopback_test) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/i2c_loopback_test/boards/am13e230x_lp_e2.overlay b/samples/i2c_loopback_test/boards/am13e230x_lp_e2.overlay new file mode 100644 index 000000000000..68df3e8fee8f --- /dev/null +++ b/samples/i2c_loopback_test/boards/am13e230x_lp_e2.overlay @@ -0,0 +1,119 @@ +#include + +&pinctrl { + pa11_uc2_tx_sda: pa11_uc2_tx_sda { + pinmux = ; + bias-pull-up; + drive-open-drain; /* Matches DL_GPIO_enableHiZ */ + input-enable; /* Required for bidirectional I2C */ + }; + + pa23_uc2_rx_sclk: pa23_uc2_rx_sclk { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + /* Apply similar logic to your Target pins */ + pa10_uc1_tx_sda_pico: pa10_uc1_tx_sda_pico { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + pa9_uc1_rx_scl_sclk: pa9_uc1_rx_scl_sclk { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + /* non-advanced controller */ + pa22_uc3_tx_sda_pico: pa22_uc3_tx_sda_pico { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + pa24_uc3_rx_scl_sclk: pa24_uc3_rx_scl_sclk { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + pb10_uc3_rx_scl_sclk: pb10_uc3_rx_scl_sclk { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + pb11_uc3_tx_sda_pico: pb11_uc3_tx_sda_pico { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + /* advanced target */ + pa22_uc5_tx_sda: pa22_uc5_tx_sda { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; + + pb6_uc5_rx_scl: pb6_uc5_rx_scl { + pinmux = ; + bias-pull-up; + drive-open-drain; + input-enable; + }; +}; + +/ { + aliases { + i2c-controller = &unicomm2_i2cc; + i2c-target = &unicomm1_i2ct; + }; +}; + +/* advanced target */ +&unicomm5_i2ct { + status = "okay"; + + pinctrl-0 = <&pa22_uc5_tx_sda &pb6_uc5_rx_scl>; + pinctrl-names = "default"; + + rxfifo-threshold = ; +}; + +/* target */ +&unicomm1_i2ct { + status = "okay"; + + pinctrl-0 = <&pa10_uc1_tx_sda_pico &pa9_uc1_rx_scl_sclk>; + pinctrl-names = "default"; + + //rxfifo-threshold = ; +}; + +/* advanced controller */ +&unicomm2_i2cc { + status = "okay"; + + pinctrl-0 = <&pa11_uc2_tx_sda &pa23_uc2_rx_sclk>; + pinctrl-names = "default"; +}; + +/* controller */ +&unicomm3_i2cc { + status = "okay"; + + pinctrl-0 = <&pb11_uc3_tx_sda_pico &pb10_uc3_rx_scl_sclk>; + pinctrl-names = "default"; +}; diff --git a/samples/i2c_loopback_test/prj.conf b/samples/i2c_loopback_test/prj.conf new file mode 100644 index 000000000000..59b9f63c7964 --- /dev/null +++ b/samples/i2c_loopback_test/prj.conf @@ -0,0 +1,11 @@ +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +CONFIG_I2C=y +CONFIG_I2C_TARGET=y + +CONFIG_COMPILER_SAVE_TEMPS=y + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_UART=y diff --git a/samples/i2c_loopback_test/src/main.c b/samples/i2c_loopback_test/src/main.c new file mode 100644 index 000000000000..f88950541cf3 --- /dev/null +++ b/samples/i2c_loopback_test/src/main.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define TARGET_ADDR 0x50 + +static char i2c_buf[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +static const struct device *i2c_controller = DEVICE_DT_GET(DT_ALIAS(i2c_controller)); +static const struct device *i2c_target = DEVICE_DT_GET(DT_ALIAS(i2c_target)); + +static char target_data[] = "01234567898765432123456789"; +static int target_data_ptr = 0; + +#define TARGET_RX_BUFLEN 40 +static char target_rx_data[TARGET_RX_BUFLEN]; +static int target_rx_data_len = 0; + +static bool write_requested_send_nack = true; +static bool read_requested_send_nack = true; + +int my_stop(struct i2c_target_config *config) { + if (write_requested_send_nack) { + write_requested_send_nack = false; // only do it once + target_rx_data_len = 0; + } + + if (read_requested_send_nack) { + target_data_ptr = 0; + read_requested_send_nack = false; + } + printk("\nmy_stop reached\n"); +} + +int my_write_requested(struct i2c_target_config *config) { + if (write_requested_send_nack) { + return -1; /* "An error return shall cause the controller to NACK the next byte received" */ + } + return 0; +} + +int my_write_received(struct i2c_target_config *config, uint8_t data) { + if (target_rx_data_len == TARGET_RX_BUFLEN) target_rx_data_len = 0; + + target_rx_data[target_rx_data_len++] = data; + + return 0; +} + +int my_read_requested(struct i2c_target_config *config, uint8_t *data) { + if (read_requested_send_nack) { + return -1; + } + + if (target_data_ptr < sizeof(target_data)) { + *data = target_data[target_data_ptr++]; + } else { + *data = 0; + } + + return 0; +} + +int my_read_processed(struct i2c_target_config *config, uint8_t *data) { + if (target_data_ptr < sizeof(target_data)) { + *data = target_data[target_data_ptr++]; + } else { + *data = 0; + } + + return 0; +} + +static const struct i2c_target_callbacks target_callbacks = { + .write_requested = my_write_requested, + .write_received = my_write_received, + .read_requested = my_read_requested, + .read_processed = my_read_processed, + .stop = my_stop, +}; + +static struct i2c_target_config target_cfg = { + .address = TARGET_ADDR, + .callbacks = &target_callbacks, + //.flags = I2C_TARGET_FLAGS_ADDR_10_BITS, +}; + +int main(void) +{ + //dl_target_init(); + + struct i2c_msg tx = { + .buf = i2c_buf, + .len = sizeof(i2c_buf), + .flags = I2C_MSG_WRITE | I2C_MSG_STOP, + //.flags = I2C_MSG_READ | I2C_MSG_STOP, + }; + + if (i2c_target_register(i2c_target, &target_cfg) != 0) { + printk("Error registering target\n"); + } + + if (i2c_configure(i2c_controller, I2C_MODE_CONTROLLER | I2C_SPEED_SET(I2C_SPEED_STANDARD)) != 0) { + printk("Error configuring i2c speed\n"); + } + + int ret = i2c_transfer(i2c_controller, &tx, 1, TARGET_ADDR); + + if (tx.flags & I2C_MSG_READ) { + printk("received bytes from target: "); + for (int i=0; i +#include + +/* Defines for APP_I2C_CONTROLLER */ +#define APP_I2C_CONTROLLER_INST UC2_INST_PTR +#define APP_I2C_CONTROLLER_BUS_SPEED_HZ 100000 +#define GPIO_APP_I2C_CONTROLLER_SDA_PORT GPIO0 +#define GPIO_APP_I2C_CONTROLLER_SDA_PIN DL_GPIO_PIN_11 +#define GPIO_APP_I2C_CONTROLLER_IOMUX_SDA (IOMUX_PINCM_PA11) +#define GPIO_APP_I2C_CONTROLLER_IOMUX_SDA_FUNC IOMUX_PA11_UC2_TX_SDA +#define GPIO_APP_I2C_CONTROLLER_SCL_PORT GPIO0 +#define GPIO_APP_I2C_CONTROLLER_SCL_PIN DL_GPIO_PIN_23 +#define GPIO_APP_I2C_CONTROLLER_IOMUX_SCL (IOMUX_PINCM_PA23) +#define GPIO_APP_I2C_CONTROLLER_IOMUX_SCL_FUNC IOMUX_PA23_UC2_RX_SCL + +/* Defines for APP_I2C_TARGET */ +#define APP_I2C_TARGET_INST UC1_INST_PTR +#define APP_I2C_TARGET_TARGET_OWN_ADDR 0x50 +#define GPIO_APP_I2C_TARGET_SDA_PORT GPIO0 +#define GPIO_APP_I2C_TARGET_SDA_PIN DL_GPIO_PIN_10 +#define GPIO_APP_I2C_TARGET_IOMUX_SDA (IOMUX_PINCM_PA10) +#define GPIO_APP_I2C_TARGET_IOMUX_SDA_FUNC IOMUX_PA10_UC1_TX_SDA_PICO +#define GPIO_APP_I2C_TARGET_SCL_PORT GPIO0 +#define GPIO_APP_I2C_TARGET_SCL_PIN DL_GPIO_PIN_9 +#define GPIO_APP_I2C_TARGET_IOMUX_SCL (IOMUX_PINCM_PA9) +#define GPIO_APP_I2C_TARGET_IOMUX_SCL_FUNC IOMUX_PA9_UC1_RX_SCL_SCLK + +void SYSCFG_DL_initPower(void) +{ + DL_GPIO_reset(GPIO0); + DL_GPIO_reset(GPIO1); + DL_GPIO_reset(GPIO2); + DL_GPIO_reset(GPIO3); + + DL_I2CT_reset(APP_I2C_TARGET_INST); + + DL_GPIO_enablePower(GPIO0); + DL_GPIO_enablePower(GPIO1); + DL_GPIO_enablePower(GPIO2); + DL_GPIO_enablePower(GPIO3); + + DL_I2CT_enablePower(APP_I2C_TARGET_INST); +} + +void SYSCFG_DL_Pinmux_init(void) +{ + DL_GPIO_initPeripheralAnalogFunction(IOMUX_PINCM_PC16_X1); + DL_GPIO_initPeripheralAnalogFunction(IOMUX_PINCM_PC17_X2); + + DL_GPIO_initPeripheralInputFunctionFeatures( + GPIO_APP_I2C_CONTROLLER_IOMUX_SDA, GPIO_APP_I2C_CONTROLLER_IOMUX_SDA_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures( + GPIO_APP_I2C_CONTROLLER_IOMUX_SCL, GPIO_APP_I2C_CONTROLLER_IOMUX_SCL_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_APP_I2C_CONTROLLER_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_APP_I2C_CONTROLLER_IOMUX_SCL); + + DL_GPIO_initPeripheralInputFunctionFeatures( + GPIO_APP_I2C_TARGET_IOMUX_SDA, GPIO_APP_I2C_TARGET_IOMUX_SDA_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures( + GPIO_APP_I2C_TARGET_IOMUX_SCL, GPIO_APP_I2C_TARGET_IOMUX_SCL_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_APP_I2C_TARGET_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_APP_I2C_TARGET_IOMUX_SCL); +} + +static const DL_I2CT_ClockConfig gAPP_I2C_TARGETClockConfig = { + .clockSel = DL_I2CT_CLOCK_BUSCLK, + .divideRatio = DL_I2CT_CLOCK_DIVIDE_1, +}; + +void SYSCFG_DL_APP_I2C_TARGET_init(void) { + DL_I2CT_setClockConfig(APP_I2C_TARGET_INST, + (DL_I2CT_ClockConfig *) &gAPP_I2C_TARGETClockConfig); + + /* Configure Target Mode */ + DL_I2CT_setOwnAddress(APP_I2C_TARGET_INST, APP_I2C_TARGET_TARGET_OWN_ADDR); + DL_I2CT_setTXFIFOThreshold(APP_I2C_TARGET_INST, DL_I2CT_TX_FIFO_LEVEL_1_2_EMPTY); + DL_I2CT_setRXFIFOThreshold(APP_I2C_TARGET_INST, DL_I2CT_RX_FIFO_LEVEL_NOT_EMPTY); + + /* Clock stretching would deadlock when controller and target share a single + * CPU with a blocking polling transfer — target can't drain its RX FIFO + * while the controller is busy-waiting for SCL to be released. */ + DL_I2CT_disableClockStretching(APP_I2C_TARGET_INST); + + /* Enable module */ + DL_I2CT_enable(APP_I2C_TARGET_INST); +} + +void dl_target_init(void) +{ + SYSCFG_DL_initPower(); + SYSCFG_DL_Pinmux_init(); + //SYSCFG_DL_APP_I2C_TARGET_init(); +} + +/* target I2C buffer */ +static volatile char rx_buf[128]; + +static volatile char *rx_buf_ptr = rx_buf; +static volatile int rx_remaining_bytes = 26; + +static volatile bool is_rx_complete = false; + +void target_receive_bytes(void) +{ + while (!DL_I2CT_isRXFIFOEmpty(APP_I2C_TARGET_INST) && rx_remaining_bytes > 0U) + { + *rx_buf_ptr = DL_I2CT_receiveData(APP_I2C_TARGET_INST); + rx_buf_ptr++; + rx_remaining_bytes--; + } + + /* Check for STOP or completion */ + + bool isStopDetected = DL_I2CT_getRawInterruptStatus(APP_I2C_TARGET_INST, DL_I2CT_INTERRUPT_STOP); + if (isStopDetected) + { + DL_I2CT_clearInterruptStatus(APP_I2C_TARGET_INST, DL_I2CT_INTERRUPT_STOP); + } + + /* Transfer is complete if stop is detected or all bytes received */ + + is_rx_complete = (isStopDetected && rx_remaining_bytes == 0U) || (rx_remaining_bytes == 0U); +} From d6f252145803193e9ef7e525589e497a585eaea9 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Thu, 16 Apr 2026 14:35:13 +0530 Subject: [PATCH 09/22] drivers: dma: Kconfig.ti_am13: add DMA_TI_AM13 Kconfig Add Kconfig option to enable DMA on TI AM13x MCUs Signed-off-by: Sameer Srivastava --- drivers/dma/Kconfig | 1 + drivers/dma/Kconfig.ti_am13 | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 drivers/dma/Kconfig.ti_am13 diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index b554832a3267..0c64d70c6c59 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -98,5 +98,6 @@ source "drivers/dma/Kconfig.wch" source "drivers/dma/Kconfig.ti_cc23x0" source "drivers/dma/Kconfig.ti_edma" +source "drivers/dma/Kconfig.ti_am13" endif # DMA diff --git a/drivers/dma/Kconfig.ti_am13 b/drivers/dma/Kconfig.ti_am13 new file mode 100644 index 000000000000..036a8afcfcd4 --- /dev/null +++ b/drivers/dma/Kconfig.ti_am13 @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +config DMA_TI_AM13 + bool "TI AM13x DMA Driver" + default y + depends on DT_HAS_TI_AM13_DMA_ENABLED + help + DMA driver for TI AM13x. From 93d19e9cef56a311c7009dd1beca7b61ecc292a5 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Thu, 16 Apr 2026 14:38:18 +0530 Subject: [PATCH 10/22] drivers: dma: AM13x DMA driver AM13x DMA driver with memory-to-memory transfer working, dma_start currently polling the control register. Signed-off-by: Sameer Srivastava drivers: dma: dma_ti_am13: interrupt-driven DMA transfer Add logic for memory-to-memory DMA in interrupt-mode. Signed-off-by: Sameer Srivastava --- drivers/dma/CMakeLists.txt | 1 + drivers/dma/dma_ti_am13.c | 308 +++++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 drivers/dma/dma_ti_am13.c diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index c77961d6bf10..0673d5d24a4d 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -54,3 +54,4 @@ zephyr_library_sources_ifdef(CONFIG_DMA_NXP_SDMA dma_nxp_sdma.c) zephyr_library_sources_ifdef(CONFIG_DMA_WCH dma_wch.c) zephyr_library_sources_ifdef(CONFIG_DMA_TI_CC23X0 dma_ti_cc23x0.c) zephyr_library_sources_ifdef(CONFIG_DMA_TI_EDMA dma_ti_edma.c) +zephyr_library_sources_ifdef(CONFIG_DMA_TI_AM13 dma_ti_am13.c) diff --git a/drivers/dma/dma_ti_am13.c b/drivers/dma/dma_ti_am13.c new file mode 100644 index 000000000000..ee8e74223e03 --- /dev/null +++ b/drivers/dma/dma_ti_am13.c @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dma_ti_am13, CONFIG_DMA_LOG_LEVEL); + +#define DT_DRV_COMPAT ti_am13_dma + +/* Register offsets from peripheral base */ +#define DMA_IIDX_OFS 0x1020U +#define DMA_IMASK_OFS 0x1028U +#define DMA_ICLR_OFS 0x1048U +#define DMA_DMATCTL_OFS(ch) (0x1110U + 4U * (ch)) +#define DMA_DMACTL_OFS(ch) (0x1200U + 16U * (ch)) +#define DMA_DMASA_OFS(ch) (0x1204U + 16U * (ch)) +#define DMA_DMADA_OFS(ch) (0x1208U + 16U * (ch)) +#define DMA_DMASZ_OFS(ch) (0x120CU + 16U * (ch)) + +/* DMACTL bits */ +#define DMACTL_DMAREQ BIT(0) /* software trigger (self-clearing) */ +#define DMACTL_DMAEN BIT(1) /* channel enable; HW clears on block done */ +#define DMACTL_DMASRCWDTH_MSK (0x7U << 8) +#define DMACTL_DMADSTWDTH_MSK (0x7U << 12) +#define DMACTL_DMASRCINCR_MSK (0xFU << 16) +#define DMACTL_DMADSTINCR_MSK (0xFU << 20) +#define DMACTL_DMAEM_MSK (0x3U << 24) +#define DMACTL_DMATM_MSK (0x3U << 28) +#define DMACTL_DMATM_BLOCK (0x1U << 28) + +/* Source width (bits [10:8]) */ +#define SRCWDTH_BYTE (0x0U << 8) +#define SRCWDTH_HALF (0x1U << 8) +#define SRCWDTH_WORD (0x2U << 8) +#define SRCWDTH_LONG (0x3U << 8) + +/* Destination width (bits [14:12]) */ +#define DSTWDTH_BYTE (0x0U << 12) +#define DSTWDTH_HALF (0x1U << 12) +#define DSTWDTH_WORD (0x2U << 12) +#define DSTWDTH_LONG (0x3U << 12) + +/* Source increment (bits [19:16]): 0=unchanged, 2=decrement, 3=increment */ +#define SRCINCR_UNCHANGED (0x0U << 16) +#define SRCINCR_DECREMENT (0x2U << 16) +#define SRCINCR_INCREMENT (0x3U << 16) + +/* Destination increment (bits [23:20]) */ +#define DSTINCR_UNCHANGED (0x0U << 20) +#define DSTINCR_DECREMENT (0x2U << 20) +#define DSTINCR_INCREMENT (0x3U << 20) + +/* DMATCTL = 0: trigger source 0 (DMAREQ), external → software trigger */ +#define DMATCTL_SW_TRIGGER 0U + +/* IIDX values: channel N done = N + 1 */ +#define IIDX_CHAN_DONE(n) ((n) + 1U) +#define IIDX_ADDR_ERR 0x19U + +struct ti_am13_dma_chan { + dma_callback_t callback; + void *user_data; +}; + +struct ti_am13_dma_config { + uintptr_t base; + uint8_t num_channels; + void (*irq_config)(const struct device *dev); +}; + +struct ti_am13_dma_data { + struct ti_am13_dma_chan chan[12]; +}; + +static inline uint32_t dma_rd(uintptr_t base, uint32_t ofs) +{ + return sys_read32(base + ofs); +} + +static inline void dma_wr(uintptr_t base, uint32_t ofs, uint32_t val) +{ + sys_write32(val, base + ofs); +} + +static uint32_t data_size_to_src_width(uint32_t bytes) +{ + switch (bytes) { + case 2: return SRCWDTH_HALF; + case 4: return SRCWDTH_WORD; + case 8: return SRCWDTH_LONG; + default: return SRCWDTH_BYTE; + } +} + +static uint32_t data_size_to_dst_width(uint32_t bytes) +{ + switch (bytes) { + case 2: return DSTWDTH_HALF; + case 4: return DSTWDTH_WORD; + case 8: return DSTWDTH_LONG; + default: return DSTWDTH_BYTE; + } +} + +static uint32_t addr_adj_to_src_incr(uint16_t adj) +{ + switch (adj) { + case DMA_ADDR_ADJ_DECREMENT: return SRCINCR_DECREMENT; + case DMA_ADDR_ADJ_NO_CHANGE: return SRCINCR_UNCHANGED; + default: return SRCINCR_INCREMENT; + } +} + +static uint32_t addr_adj_to_dst_incr(uint16_t adj) +{ + switch (adj) { + case DMA_ADDR_ADJ_DECREMENT: return DSTINCR_DECREMENT; + case DMA_ADDR_ADJ_NO_CHANGE: return DSTINCR_UNCHANGED; + default: return DSTINCR_INCREMENT; + } +} + +static void ti_am13_dma_isr(const struct device *dev) +{ + const struct ti_am13_dma_config *cfg = dev->config; + struct ti_am13_dma_data *data = dev->data; + uint32_t iidx; + + /* IIDX returns the highest-priority pending interrupt index and + * auto-advances; loop until all pending interrupts are serviced. */ + while ((iidx = dma_rd(cfg->base, DMA_IIDX_OFS)) != 0) { + if (iidx >= 1U && iidx <= (uint32_t)cfg->num_channels) { + uint32_t ch = iidx - 1U; + + dma_wr(cfg->base, DMA_ICLR_OFS, BIT(ch)); + + if (data->chan[ch].callback) { + data->chan[ch].callback(dev, + data->chan[ch].user_data, + ch, 0); + } + } else if (iidx == IIDX_ADDR_ERR) { + LOG_ERR("DMA address error"); + dma_wr(cfg->base, DMA_ICLR_OFS, BIT(IIDX_ADDR_ERR - 1U)); + } + } +} + +static int ti_am13_dma_configure(const struct device *dev, uint32_t channel, + struct dma_config *config) +{ + const struct ti_am13_dma_config *cfg = dev->config; + struct ti_am13_dma_data *data = dev->data; + struct dma_block_config *block = config->head_block; + uint32_t dmactl; + uint32_t xfer_count; + + if (channel >= cfg->num_channels) { + LOG_ERR("channel %u out of range", channel); + return -EINVAL; + } + + if (config->block_count != 1) { + LOG_ERR("only single-block transfers supported"); + return -ENOTSUP; + } + + if (config->channel_direction != MEMORY_TO_MEMORY) { + LOG_ERR("only memory-to-memory supported"); + return -ENOTSUP; + } + + if (config->source_data_size != config->dest_data_size) { + LOG_ERR("source and dest data sizes must match"); + return -EINVAL; + } + + xfer_count = block->block_size / config->source_data_size; + if (xfer_count == 0 || xfer_count > 0xFFFFU) { + LOG_ERR("invalid transfer count %u", xfer_count); + return -EINVAL; + } + + data->chan[channel].callback = config->dma_callback; + data->chan[channel].user_data = config->user_data; + + dmactl = DMACTL_DMATM_BLOCK + | data_size_to_src_width(config->source_data_size) + | data_size_to_dst_width(config->dest_data_size) + | addr_adj_to_src_incr(block->source_addr_adj) + | addr_adj_to_dst_incr(block->dest_addr_adj); + + /* Software trigger: trigger source 0 (DMAREQ), external */ + dma_wr(cfg->base, DMA_DMATCTL_OFS(channel), DMATCTL_SW_TRIGGER); + dma_wr(cfg->base, DMA_DMACTL_OFS(channel), dmactl); + dma_wr(cfg->base, DMA_DMASA_OFS(channel), block->source_address); + dma_wr(cfg->base, DMA_DMADA_OFS(channel), block->dest_address); + dma_wr(cfg->base, DMA_DMASZ_OFS(channel), xfer_count); + + /* Enable interrupt for this channel */ + uint32_t imask = dma_rd(cfg->base, DMA_IMASK_OFS); + + imask |= BIT(channel); + dma_wr(cfg->base, DMA_IMASK_OFS, imask); + + return 0; +} + +static int ti_am13_dma_start(const struct device *dev, uint32_t channel) +{ + const struct ti_am13_dma_config *cfg = dev->config; + uint32_t dmactl; + + if (channel >= cfg->num_channels) { + return -EINVAL; + } + + /* Enable channel and trigger the block transfer; ISR handles completion */ + dmactl = dma_rd(cfg->base, DMA_DMACTL_OFS(channel)); + dmactl |= DMACTL_DMAEN | DMACTL_DMAREQ; + dma_wr(cfg->base, DMA_DMACTL_OFS(channel), dmactl); + + return 0; +} + +static int ti_am13_dma_stop(const struct device *dev, uint32_t channel) +{ + const struct ti_am13_dma_config *cfg = dev->config; + uint32_t dmactl; + + if (channel >= cfg->num_channels) { + return -EINVAL; + } + + /* Disable channel and its interrupt */ + dmactl = dma_rd(cfg->base, DMA_DMACTL_OFS(channel)); + dmactl &= ~DMACTL_DMAEN; + dma_wr(cfg->base, DMA_DMACTL_OFS(channel), dmactl); + + uint32_t imask = dma_rd(cfg->base, DMA_IMASK_OFS); + + imask &= ~BIT(channel); + dma_wr(cfg->base, DMA_IMASK_OFS, imask); + + return 0; +} + +static int ti_am13_dma_get_status(const struct device *dev, uint32_t channel, + struct dma_status *status) +{ + const struct ti_am13_dma_config *cfg = dev->config; + uint32_t dmactl, dmasz; + + if (channel >= cfg->num_channels) { + return -EINVAL; + } + + dmactl = dma_rd(cfg->base, DMA_DMACTL_OFS(channel)); + dmasz = dma_rd(cfg->base, DMA_DMASZ_OFS(channel)); + + status->busy = !!(dmactl & DMACTL_DMAEN); + status->dir = MEMORY_TO_MEMORY; + status->pending_length = dmasz & 0xFFFFU; + + return 0; +} + +static int ti_am13_dma_init(const struct device *dev) +{ + const struct ti_am13_dma_config *cfg = dev->config; + + cfg->irq_config(dev); + return 0; +} + +static DEVICE_API(dma, ti_am13_dma_driver_api) = { + .config = ti_am13_dma_configure, + .start = ti_am13_dma_start, + .stop = ti_am13_dma_stop, + .get_status = ti_am13_dma_get_status, +}; + +#define TI_AM13_DMA_INIT(inst) \ + static void ti_am13_dma_irq_config_##inst(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(inst), \ + DT_INST_IRQ(inst, priority), \ + ti_am13_dma_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + irq_enable(DT_INST_IRQN(inst)); \ + } \ + static struct ti_am13_dma_config dma_config_##inst = { \ + .base = DT_INST_REG_ADDR(inst), \ + .num_channels = DT_INST_PROP(inst, dma_channels), \ + .irq_config = ti_am13_dma_irq_config_##inst, \ + }; \ + static struct ti_am13_dma_data dma_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, ti_am13_dma_init, NULL, &dma_data_##inst, \ + &dma_config_##inst, PRE_KERNEL_1, \ + CONFIG_DMA_INIT_PRIORITY, &ti_am13_dma_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TI_AM13_DMA_INIT); From a440fc448ec42301e35e872ef2a31a5216168bf0 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Thu, 16 Apr 2026 14:36:16 +0530 Subject: [PATCH 11/22] dts: bindings: dma: add binding for TI AM13 DMA Add a devicetree binding for TI AM13 DMA Signed-off-by: Sameer Srivastava --- dts/bindings/dma/ti,am13-dma.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 dts/bindings/dma/ti,am13-dma.yaml diff --git a/dts/bindings/dma/ti,am13-dma.yaml b/dts/bindings/dma/ti,am13-dma.yaml new file mode 100644 index 000000000000..10446d8d2a49 --- /dev/null +++ b/dts/bindings/dma/ti,am13-dma.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2025, Texas Instruments +# SPDX-License-Identifier: Apache-2.0 + +description: | + TI AM13x DMA Controller + +compatible: "ti,am13-dma" + +include: dma-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + dma-channels: + required: true + + "#dma-cells": + const: 1 + description: No specific input is needed from dts to use eDMA From 1a5be9c91ca755a26e6a42259942d83eabab8f7a Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Thu, 16 Apr 2026 14:37:00 +0530 Subject: [PATCH 12/22] dts: arm: ti: am13: am13e230x: add dma0 node Add DMA0 node to AM13E230x dtsi Signed-off-by: Sameer Srivastava --- dts/arm/ti/am13/am13e230x.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dts/arm/ti/am13/am13e230x.dtsi b/dts/arm/ti/am13/am13e230x.dtsi index 2e016682b0f5..d619fbbebe0b 100644 --- a/dts/arm/ti/am13/am13e230x.dtsi +++ b/dts/arm/ti/am13/am13e230x.dtsi @@ -274,6 +274,16 @@ status = "disabled"; }; }; + + dma0: dma@40020000 { + compatible = "ti,am13-dma"; + reg = <0x40020000 DT_SIZE_K(32)>; + interrupts = <40 0>; + status = "disabled"; + #dma-cells = <1>; + + dma-channels = <12>; + }; }; }; From 637942792db16e15874312a6de2d8a3dc94b44a7 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:29:32 +0530 Subject: [PATCH 13/22] drivers: spi: add TI UNICOMM SPI driver Add SPI driver for TI UNICOMM peripheral, including CMakeLists wiring and full driver implementation with spi_context, configure, and transceive support. Signed-off-by: Sameer Srivastava --- drivers/spi/CMakeLists.txt | 1 + drivers/spi/spi_ti_unicomm.c | 370 +++++++++++++++++++++++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 drivers/spi/spi_ti_unicomm.c diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 883444202dc9..50b63afd194a 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -67,6 +67,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_SMARTBOND spi_smartbond.c) zephyr_library_sources_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c) zephyr_library_sources_ifdef(CONFIG_SPI_TELINK_B91 spi_b91.c) zephyr_library_sources_ifdef(CONFIG_SPI_TEST spi_test.c) +zephyr_library_sources_ifdef(CONFIG_SPI_TI_UNICOMM spi_ti_unicomm.c) zephyr_library_sources_ifdef(CONFIG_SPI_WCH spi_wch.c) zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI_LDMA spi_xec_qmspi_ldma.c) diff --git a/drivers/spi/spi_ti_unicomm.c b/drivers/spi/spi_ti_unicomm.c new file mode 100644 index 000000000000..89e32982dd69 --- /dev/null +++ b/drivers/spi/spi_ti_unicomm.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_unicomm_spi + +#include +LOG_MODULE_REGISTER(spi_ti_unicomm, CONFIG_SPI_LOG_LEVEL); + +#include +#include +#include +#include + +#include "spi_context.h" + +/* + * UNICOMM Register Offsets + */ + +#define UNICOMM_POWER_EN 0x00000800U +#define UNICOMM_RESET_CTL 0x00000804U +#define UNICOMM_CLOCK_CFG 0x00000808U +#define UNICOMM_STATUS 0x00000814U +#define UNICOMM_MODE 0x00001100U + +/* + * Peripheral Configuration Values + */ + +#define PWREN_ENABLE 0x00000001U +#define PWREN_DISABLE 0x00000000U +#define PWREN_KEY 0x26000000U +#define IPMODE_SPI 0x00000001U + +#define RESET_CTL_KEY_UNLOCK 0xB1000000U +#define RESET_CTL_STICKY_BIT_CLEAR 0x00000002U +#define RESET_CTL_ASSERT_RESET 0x00000001U + +/* UNICOMM SPI Regs */ +#define UNICOMM_SPI_CLKDIV 0x0 +#define UNICOMM_SPI_CLKSEL 0x8 +#define UNICOMM_SPI_CTL0 0x100 /* frame format, data size */ +#define UNICOMM_SPI_CTL1 0x14C /* parity, bit order, mode */ +#define UNICOMM_SPI_CLKCTL 0x110 + +#define UNICOMMSPI_CTL0_DSS_MASK ((uint32_t)0x0000001FU) /* !< Data Size Select. */ +#define UNICOMMSPI_CTL0_FRF_MASK ((uint32_t)0x00000060U) /* !< Frame format Select */ +#define UNICOMMSPI_CTL0_SPO_MASK ((uint32_t)0x00000100U) /* !< CLKOUT polarity */ +#define UNICOMMSPI_CTL0_SPH_MASK ((uint32_t)0x00000200U) /* !< CLKOUT phase */ +#define UNICOMMSPI_CTL0_CSCLR_MASK ((uint32_t)0x00004000U) /* !< Clear on CS inactive */ + +#define UNICOMMSPI_CTL1_LBM_MASK ((uint32_t)0x00000002U) /* !< Loop back mode enable */ +#define UNICOMMSPI_CTL1_CP_MASK ((uint32_t)0x00000004U) /* !< Controller or peripheral mode */ +#define UNICOMMSPI_CTL1_POD_MASK ((uint32_t)0x00000008U) /* !< Peripheral-mode: Data output */ +#define UNICOMMSPI_CTL1_MSB_MASK ((uint32_t)0x00000010U) /* !< MSB first select */ +#define UNICOMMSPI_CTL1_PREN_MASK ((uint32_t)0x00000020U) /* !< Parity Receive Enable */ +#define UNICOMMSPI_CTL1_PES_MASK ((uint32_t)0x00000040U) /* !< Even Parity Select */ +#define UNICOMMSPI_CTL1_ENABLE_MASK ((uint32_t)0x00000001U) /* !< SPI enable */ +#define UNICOMMSPI_CTL1_PTEN_MASK ((uint32_t)0x00000100U) /* !< Parity Transmit Enable */ +#define UNICOMMSPI_CTL1_SUSPEND_MASK \ + ((uint32_t)0x00000200U) /* !< Suspend external communication \ + */ + +#define UNICOMMSPI_CLKCTL_SCR_MASK ((uint32_t)0x000003FFU) /* !< Serial clock divider */ + +#define UNICOMM_SPI_STAT 0x108 +#define UNICOMM_SPI_TXDATA 0x120 +#define UNICOMM_SPI_RXDATA 0x124 + +#define UNICOMMSPI_STAT_TXFF_MASK ((uint32_t)0x00000040U) /* TX FIFO full */ +#define UNICOMMSPI_STAT_RXFE_MASK ((uint32_t)0x00000004U) /* RX FIFO empty */ +#define UNICOMMSPI_STAT_BUSY_MASK ((uint32_t)0x00000100U) /* SPI busy */ + +/* Configuration Values */ +#define SPI_CLKDIV_DIVIDE_BY_1 0 +#define SPI_CLKSEL_BUSCLK_ENABLE 0x00000008U + +#define SPI_CTL1_ENABLE 0x00000001U +#define SPI_CTL1_DISABLE 0x00000000U + +#define SPI_CTL0_FRF_MOTOROLA_3WIRE 0x00000000U +#define SPI_CTL0_FRF_MOTOROLA_4WIRE 0x00000020U +#define SPI_CTL0_FRF_TI 0x00000040U +#define SPI_CTL0_SPO_CPOL 0x00000100U +#define SPI_CTL0_SPH_CPHA 0x00000200U +#define SPI_CTL0_DSS_8BIT 0x00000007U + +/* Helper macros */ +#define UPDATE_REG(reg_offset, value, mask) \ + { \ + uint32_t tmp = sys_read32(reg_offset); \ + tmp = tmp & ~(mask); \ + sys_write32(tmp | ((value) & (mask)), reg_offset); \ + } + +struct spi_ti_unicomm_config { + const struct pinctrl_dev_config *pcfg; + + uint32_t unicomm_inst_base; + uint32_t unicomm_spi_base; + + uint8_t clkdiv; /* Clock divide ratio. Register value: 0=div1, 1=div2, ... 7=div8 */ + uint32_t busclk_hz; /* BUSCLK input frequency in Hz (before clkdiv) */ + + uint32_t tx_fifo_threshold; + uint32_t rx_fifo_threshold; + + void (*irq_config_func)(const struct device *dev); +}; + +struct spi_ti_unicomm_data { + struct spi_context ctx; +}; + +/* + * Helper functions + */ + +/* Reset unicomm instance */ +static inline void unicomm_reset(uint32_t base) +{ + sys_write32(RESET_CTL_KEY_UNLOCK | RESET_CTL_STICKY_BIT_CLEAR | RESET_CTL_ASSERT_RESET, + base + UNICOMM_RESET_CTL); +} + +/* Enable power for UNICOMM instance */ +static inline void unicomm_enable_power(uint32_t base) +{ + sys_write32(PWREN_KEY | PWREN_ENABLE, base + UNICOMM_POWER_EN); + k_sleep(K_CYC(20)); +} + +static int spi_ti_unicomm_configure(const struct device *dev, const struct spi_config *config) +{ + const struct spi_ti_unicomm_config *cfg = dev->config; + struct spi_ti_unicomm_data *data = dev->data; + + uint32_t frame_format; + + uint32_t ctl0 = 0; + uint32_t ctl1 = 0; + + if (spi_context_configured(&data->ctx, config)) { + /* Nothing to do */ + return 0; + } + + /* Only master mode is supported */ + if (SPI_OP_MODE_GET(config->operation) != SPI_OP_MODE_MASTER) { + return -ENOTSUP; + } + + /* Only single line mode is supported */ + if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) && + (config->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { + return -EINVAL; + } + + /* Set Frame Format*/ + if (config->operation & SPI_FRAME_FORMAT_TI) { + ctl0 = SPI_CTL0_FRF_TI; + + /* Half duplex mode is not supported by TI Frame Format */ + if (config->operation & SPI_HALF_DUPLEX) { + return -ENOTSUP; + } + } else { + ctl0 = SPI_CTL0_FRF_MOTOROLA_4WIRE; + + if (config->operation & SPI_HALF_DUPLEX) { + ctl0 = SPI_CTL0_FRF_MOTOROLA_3WIRE; + } + + if (config->operation & SPI_MODE_CPOL) { + ctl0 |= SPI_CTL0_SPO_CPOL; + } + + if (config->operation & SPI_MODE_CPHA) { + ctl0 |= SPI_CTL0_SPH_CPHA; + } + } + + /* Data size */ + if (SPI_WORD_SIZE_GET(config->operation) != 8) { + return -ENOTSUP; + } + ctl0 |= SPI_CTL0_DSS_8BIT; + + /* MSB bit = 1 means LSB first; leave clear for MSB first */ + if (!(config->operation & SPI_TRANSFER_MSB)) { + ctl1 |= UNICOMMSPI_CTL1_MSB_MASK; + } + + if (config->operation & SPI_MODE_LOOP) { + ctl1 |= UNICOMMSPI_CTL1_LBM_MASK; + } + + /* Set controller mode */ + ctl1 |= UNICOMMSPI_CTL1_CP_MASK; + + /* Disable peripheral, apply settings and enable again */ + unicomm_reset(cfg->unicomm_inst_base); + unicomm_enable_power(cfg->unicomm_inst_base); + + /* Set instance mode to SPI */ + sys_write32(IPMODE_SPI, cfg->unicomm_inst_base + UNICOMM_MODE); + + /* Configure clock divide ratio and select BUSCLK as clock source */ + sys_write32(cfg->clkdiv, cfg->unicomm_spi_base + UNICOMM_SPI_CLKDIV); + sys_write32(SPI_CLKSEL_BUSCLK_ENABLE, cfg->unicomm_spi_base + UNICOMM_SPI_CLKSEL); + + /* Set CTL0 and CTL1 */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL0, ctl0, + UNICOMMSPI_CTL0_FRF_MASK | UNICOMMSPI_CTL0_SPO_MASK | UNICOMMSPI_CTL0_SPH_MASK | + UNICOMMSPI_CTL0_DSS_MASK); + + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL1, ctl1, + UNICOMMSPI_CTL1_PES_MASK | UNICOMMSPI_CTL1_PREN_MASK | + UNICOMMSPI_CTL1_PTEN_MASK | UNICOMMSPI_CTL1_MSB_MASK | + UNICOMMSPI_CTL1_CP_MASK | UNICOMMSPI_CTL1_LBM_MASK); + + /* Set SPI bitrate Serial Clock Divider (SCR) */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CLKCTL, 99, UNICOMMSPI_CLKCTL_SCR_MASK); + + /* Enable SPI */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL1, SPI_CTL1_ENABLE, + UNICOMMSPI_CTL1_ENABLE_MASK); + + /* Cache SPI config for reuse, required by spi_context owner */ + data->ctx.config = config; + + return 0; +} + +static int spi_ti_unicomm_init(const struct device *dev) +{ + const struct spi_ti_unicomm_config *cfg = dev->config; + struct spi_ti_unicomm_data *data = dev->data; + int ret = 0; + + unicomm_reset(cfg->unicomm_inst_base); + unicomm_enable_power(cfg->unicomm_inst_base); + + /* Set instance mode to SPI */ + sys_write32(IPMODE_SPI, cfg->unicomm_inst_base + UNICOMM_MODE); + + /* Configure clock divide ratio and select BUSCLK as clock source */ + sys_write32(cfg->clkdiv, cfg->unicomm_spi_base + UNICOMM_SPI_CLKDIV); + sys_write32(SPI_CLKSEL_BUSCLK_ENABLE, cfg->unicomm_spi_base + UNICOMM_SPI_CLKSEL); + + /* + * CTL0: Motorola 4-wire (FRF=0x20), CPOL=0/CPHA=1 (SPH=0x200), 8-bit (DSS=0x7) + * CTL1: controller mode (CP=0x4), MSB first (MSB bit=0, acts as LSB-first enable), no + * parity + */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL0, + (uint32_t)0x00000020U | (uint32_t)0x00000200U | (uint32_t)0x00000007U, + UNICOMMSPI_CTL0_FRF_MASK | UNICOMMSPI_CTL0_SPO_MASK | UNICOMMSPI_CTL0_SPH_MASK | + UNICOMMSPI_CTL0_DSS_MASK); + + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL1, UNICOMMSPI_CTL1_CP_MASK, + UNICOMMSPI_CTL1_PES_MASK | UNICOMMSPI_CTL1_PREN_MASK | + UNICOMMSPI_CTL1_PTEN_MASK | UNICOMMSPI_CTL1_MSB_MASK | + UNICOMMSPI_CTL1_CP_MASK | UNICOMMSPI_CTL1_LBM_MASK); + + /* Set SPI bitrate Serial Clock Divider (SCR) */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CLKCTL, 99, UNICOMMSPI_CLKCTL_SCR_MASK); + + /* Enable SPI */ + UPDATE_REG(cfg->unicomm_spi_base + UNICOMM_SPI_CTL1, SPI_CTL1_ENABLE, + UNICOMMSPI_CTL1_ENABLE_MASK); + + /* Apply pinctrl config */ + ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static int spi_ti_unicomm_transceive(const struct device *dev, const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + const struct spi_ti_unicomm_config *cfg = dev->config; + struct spi_ti_unicomm_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + uint32_t base = cfg->unicomm_spi_base; + int ret = 0; + + spi_context_lock(ctx, false, NULL, NULL, config); + + ret = spi_ti_unicomm_configure(dev, config); + if (ret != 0) { + spi_context_release(&data->ctx, ret); + return ret; + } + + spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1); + + while (spi_context_tx_buf_on(ctx) || spi_context_rx_buf_on(ctx)) { + uint8_t tx_byte = spi_context_tx_buf_on(ctx) ? *(uint8_t *)ctx->tx_buf : 0; + + /* Wait TX FIFO not full */ + while (sys_read32(base + UNICOMM_SPI_STAT) & UNICOMMSPI_STAT_TXFF_MASK) { + } + + sys_write32(tx_byte, base + UNICOMM_SPI_TXDATA); + + /* Wait RX FIFO not empty */ + uint32_t timeout = 100000U; + + while ((sys_read32(base + UNICOMM_SPI_STAT) & UNICOMMSPI_STAT_RXFE_MASK) && + --timeout) { + } + if (!timeout) { + ret = -ETIMEDOUT; + goto done; + } + + uint8_t rx_byte = (uint8_t)sys_read32(base + UNICOMM_SPI_RXDATA); + + if (spi_context_rx_buf_on(ctx)) { + *(uint8_t *)ctx->rx_buf = rx_byte; + } + + spi_context_update_tx(ctx, 1, 1); + spi_context_update_rx(ctx, 1, 1); + } + + /* Wait for SPI to finish */ + while (sys_read32(base + UNICOMM_SPI_STAT) & UNICOMMSPI_STAT_BUSY_MASK) { + } + +done: + spi_context_complete(ctx, dev, ret); + spi_context_release(ctx, ret); + return ret; +} + +static DEVICE_API(spi, spi_ti_unicomm_api) = {.transceive = spi_ti_unicomm_transceive}; + +#define SPI_TI_UNICOMM_INIT(index) \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + static const struct spi_ti_unicomm_config spi_config_##index = { \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ + .unicomm_inst_base = \ + (uint32_t)(DT_REG_ADDR_BY_IDX(DT_PARENT(DT_DRV_INST(index)), 0)), \ + .unicomm_spi_base = (uint32_t)(DT_INST_REG_ADDR(index)), \ + .clkdiv = SPI_CLKDIV_DIVIDE_BY_1, \ + .busclk_hz = DT_INST_PROP_OR(index, unicomm_clock_freq, 100000000U), \ + }; \ + \ + static struct spi_ti_unicomm_data spi_data_##index = { \ + SPI_CONTEXT_INIT_LOCK(spi_data_##index, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_data_##index, ctx), \ + }; \ + \ + SPI_DEVICE_DT_INST_DEFINE(index, spi_ti_unicomm_init, NULL, &spi_data_##index, \ + &spi_config_##index, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ + &spi_ti_unicomm_api); + +DT_INST_FOREACH_STATUS_OKAY(SPI_TI_UNICOMM_INIT) From 64bd21b5367a04a326bd8366bcd57e402566b759 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:29:38 +0530 Subject: [PATCH 14/22] drivers: spi: add TI UNICOMM SPI Kconfig Add Kconfig entry and config options for TI UNICOMM SPI driver. Signed-off-by: Sameer Srivastava --- drivers/spi/Kconfig | 1 + drivers/spi/Kconfig.ti_unicomm | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 drivers/spi/Kconfig.ti_unicomm diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b7866a6aa40a..f4ec0e115dda 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -145,6 +145,7 @@ source "drivers/spi/Kconfig.smartbond" source "drivers/spi/Kconfig.spi_emul" source "drivers/spi/Kconfig.stm32" source "drivers/spi/Kconfig.test" +source "drivers/spi/Kconfig.ti_unicomm" source "drivers/spi/Kconfig.wch" source "drivers/spi/Kconfig.xec_qmspi" source "drivers/spi/Kconfig.xlnx" diff --git a/drivers/spi/Kconfig.ti_unicomm b/drivers/spi/Kconfig.ti_unicomm new file mode 100644 index 000000000000..ca78f8667d09 --- /dev/null +++ b/drivers/spi/Kconfig.ti_unicomm @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +config SPI_TI_UNICOMM + bool "TI Unicomm SPI Driver" + default y + depends on DT_HAS_TI_UNICOMM_SPI_ENABLED + help + Enable SPI on TI devices with the Unicomm peripheral. From b5dc869f51a7faa89cd5d3e43f9def1502606c92 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:29:44 +0530 Subject: [PATCH 15/22] dts: bindings: spi: add TI UNICOMM SPI binding Add YAML DT binding for TI UNICOMM SPI node. Signed-off-by: Sameer Srivastava --- dts/bindings/spi/ti,unicomm-spi.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 dts/bindings/spi/ti,unicomm-spi.yaml diff --git a/dts/bindings/spi/ti,unicomm-spi.yaml b/dts/bindings/spi/ti,unicomm-spi.yaml new file mode 100644 index 000000000000..c76c3a0ca24b --- /dev/null +++ b/dts/bindings/spi/ti,unicomm-spi.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +description: TI Unicomm SPI controller + +compatible: "ti,unicomm-spi" + +include: [spi-controller.yaml, pinctrl-device.yaml, base.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true + + pinctrl-names: + required: true From c1f27a65b82a1e8155de6904eb77e49630f0437a Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:29:48 +0530 Subject: [PATCH 16/22] dts: arm: ti: am13: add SPI nodes to am13e230x DTSI Add SPI peripheral nodes to am13e230x device tree. Signed-off-by: Sameer Srivastava --- dts/arm/ti/am13/am13e230x.dtsi | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/dts/arm/ti/am13/am13e230x.dtsi b/dts/arm/ti/am13/am13e230x.dtsi index d619fbbebe0b..44a3e7df65b6 100644 --- a/dts/arm/ti/am13/am13e230x.dtsi +++ b/dts/arm/ti/am13/am13e230x.dtsi @@ -111,6 +111,15 @@ reg = <0x40610000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm0_spi: unicomm0_spi@40618000 { + compatible = "ti,unicomm-spi"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <34 0>; + reg = <0x40618000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm1: unicomm@40632000 { @@ -143,6 +152,15 @@ reg = <0x40611000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm1_spi: unicomm1_spi@40619000 { + compatible = "ti,unicomm-spi"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <35 0>; + reg = <0x40619000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm2: unicomm@40634000 { @@ -208,6 +226,15 @@ reg = <0x40650000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm3_spi: unicomm3_spi@40658000 { + compatible = "ti,unicomm-spi"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <37 0>; + reg = <0x40658000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm4: unicomm@40672000 { @@ -240,6 +267,15 @@ reg = <0x40651000 DT_SIZE_K(4)>; status = "disabled"; }; + + unicomm4_spi: unicomm4_spi@40659000 { + compatible = "ti,unicomm-spi"; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <38 0>; + reg = <0x40659000 DT_SIZE_K(4)>; + status = "disabled"; + }; }; unicomm5: unicomm@40674000 { From 9b91eb143c733b801f79f660645d816084ef2c44 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Mon, 22 Jun 2026 12:59:17 +0530 Subject: [PATCH 17/22] TI: samples: drivers: spi: UNICOMM loopback Add a test application for the spi_ti_unicomm driver to test loopback transfer on a unicomm_spi instance. Signed-off-by: Sameer Srivastava --- .../spi_unicomm_loopback/CMakeLists.txt | 7 ++ .../boards/am13e230x_lp.overlay | 39 +++++++++ samples/drivers/spi_unicomm_loopback/prj.conf | 9 ++ .../drivers/spi_unicomm_loopback/src/main.c | 84 +++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 samples/drivers/spi_unicomm_loopback/CMakeLists.txt create mode 100644 samples/drivers/spi_unicomm_loopback/boards/am13e230x_lp.overlay create mode 100644 samples/drivers/spi_unicomm_loopback/prj.conf create mode 100644 samples/drivers/spi_unicomm_loopback/src/main.c diff --git a/samples/drivers/spi_unicomm_loopback/CMakeLists.txt b/samples/drivers/spi_unicomm_loopback/CMakeLists.txt new file mode 100644 index 000000000000..1e24161e5c4a --- /dev/null +++ b/samples/drivers/spi_unicomm_loopback/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(spi_loopback_test) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/spi_unicomm_loopback/boards/am13e230x_lp.overlay b/samples/drivers/spi_unicomm_loopback/boards/am13e230x_lp.overlay new file mode 100644 index 000000000000..dd949c9b9337 --- /dev/null +++ b/samples/drivers/spi_unicomm_loopback/boards/am13e230x_lp.overlay @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + test-unicomm = &unicomm3_spi; + }; +}; + +&pinctrl { + status = "okay"; + + pb10_uc3_spi_pico: pb10_uc3_spi_pico { + pinmux = ; + }; + + pa20_uc3_spi_poci: pa20_uc3_spi_poci { + pinmux = ; + input-enable; + }; + + pb5_uc3_spi_sclk: pb5_uc3_spi_sclk { + pinmux = ; + }; + + pa21_uc3_spi_cs0: pa21_uc3_spi_cs0 { + pinmux = ; + }; +}; + +&unicomm3_spi { + status = "okay"; + + pinctrl-0 = <&pb10_uc3_spi_pico &pa20_uc3_spi_poci &pb5_uc3_spi_sclk &pa21_uc3_spi_cs0>; + pinctrl-names = "default"; +}; diff --git a/samples/drivers/spi_unicomm_loopback/prj.conf b/samples/drivers/spi_unicomm_loopback/prj.conf new file mode 100644 index 000000000000..ad4880dcd01d --- /dev/null +++ b/samples/drivers/spi_unicomm_loopback/prj.conf @@ -0,0 +1,9 @@ +CONFIG_GPIO=y + +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y + +CONFIG_COMPILER_SAVE_TEMPS=y + +CONFIG_SPI=y diff --git a/samples/drivers/spi_unicomm_loopback/src/main.c b/samples/drivers/spi_unicomm_loopback/src/main.c new file mode 100644 index 000000000000..20a1e00e124c --- /dev/null +++ b/samples/drivers/spi_unicomm_loopback/src/main.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* SPI config */ +#define SPI_NODE DT_ALIAS(test_unicomm) + +static const struct device *spi_dev = DEVICE_DT_GET(SPI_NODE); +static const struct spi_config spi_cfg = { + .frequency = 1000000, + .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB | SPI_MODE_CPHA, + .slave = 0, +}; + +#define ALPHABET_LEN 26 + +int main(void) +{ + int ret; + + uint8_t tx_buffer[ALPHABET_LEN]; + uint8_t rx_buffer[ALPHABET_LEN]; + + printk("Starting SPI Alphabet Transfer Sample\n"); + + /* Verify that the SPI device is ready */ + if (!device_is_ready(spi_dev)) { + printk("SPI device not ready\n"); + return -ENODEV; + } + + /* Initialize the transmit buffer with the uppercase alphabet */ + for (int i = 0; i < ALPHABET_LEN; i++) { + tx_buffer[i] = 'A' + i; + } + + /* Set up the SPI buffer structures */ + const struct spi_buf tx_buf = { + .buf = tx_buffer, + .len = sizeof(tx_buffer) + }; + const struct spi_buf_set tx_bufs = { + .buffers = &tx_buf, + .count = 1 + }; + + struct spi_buf rx_buf = { + .buf = rx_buffer, + .len = sizeof(rx_buffer) + }; + const struct spi_buf_set rx_bufs = { + .buffers = &rx_buf, + .count = 1 + }; + + while (1) { + printk("Transmitting alphabet ('A'-'Z')... "); + + /* Perform synchronous full-duplex SPI transfer */ + ret = spi_transceive(spi_dev, &spi_cfg, &tx_bufs, &rx_bufs); + if (ret < 0) { + printk("SPI transceive failed with error: %d", ret); + } else { + printk("Transfer complete.\n"); + /* If MISO is tied to MOSI (loopback), rx_buffer will hold the alphabet */ + printk("Received data: "); + for (int j=0; j Date: Wed, 17 Jun 2026 18:08:55 +0530 Subject: [PATCH 18/22] drivers: clock_control: add AM13E clock control driver Add clock control driver for AM13E SoC, including CMakeLists wiring, driver source, and associated header/dt-binding includes. Signed-off-by: Sameer Srivastava --- drivers/clock_control/CMakeLists.txt | 1 + drivers/clock_control/clock_control_am13e.c | 447 ++++++++++++++++++ .../clock_control/am13e_clock_control.h | 30 ++ .../zephyr/dt-bindings/clock/am13e_clock.h | 22 + 4 files changed, 500 insertions(+) create mode 100644 drivers/clock_control/clock_control_am13e.c create mode 100644 include/zephyr/drivers/clock_control/am13e_clock_control.h create mode 100644 include/zephyr/dt-bindings/clock/am13e_clock.h diff --git a/drivers/clock_control/CMakeLists.txt b/drivers/clock_control/CMakeLists.txt index 18b6025df6c7..e3693f09e81a 100644 --- a/drivers/clock_control/CMakeLists.txt +++ b/drivers/clock_control/CMakeLists.txt @@ -19,6 +19,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_SCG clock_cont zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_SCG_K4 clock_control_mcux_scg_k4.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_SIM clock_control_mcux_sim.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MCUX_SYSCON clock_control_mcux_syscon.c) +zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AM13E clock_control_am13e.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_MSPM0 clock_control_mspm0.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NPCM clock_control_npcm.c) zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NPCX clock_control_npcx.c) diff --git a/drivers/clock_control/clock_control_am13e.c b/drivers/clock_control/clock_control_am13e.c new file mode 100644 index 000000000000..504588332015 --- /dev/null +++ b/drivers/clock_control/clock_control_am13e.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define DT_DRV_COMPAT ti_am13e_clk + +/* + * SYSCTL register base address and offsets + */ +BUILD_ASSERT(DT_NODE_HAS_PROP(DT_NODELABEL(ckm), ti_sysctl_base), + "ti,sysctl-base property is missing from ckm devicetree node!"); + +#define AM13E_SYSCTL_BASE DT_PROP(DT_NODELABEL(ckm), ti_sysctl_base) + +#define AM13E_SYSCTL_MCLKCFG 0x1104U +#define AM13E_SYSCTL_HSCLKEN 0x1108U +#define AM13E_SYSCTL_HSCLKCFG 0x110CU +#define AM13E_SYSCTL_HFCLKCLKCFG 0x1110U +#define AM13E_SYSCTL_SYSPLLCFG0 0x1120U +#define AM13E_SYSCTL_SYSPLLCFG1 0x1124U +#define AM13E_SYSCTL_SYSPLLPARAM0 0x1128U +#define AM13E_SYSCTL_SYSPLLPARAM1 0x112CU +#define AM13E_SYSCTL_GENCLKEN 0x113CU +#define AM13E_SYSCTL_GENCLKCFG 0x1140U +#define AM13E_SYSCTL_CLKSTATUS 0x1204U +#define AM13E_SYSCTL_XTALCR 0x1474U + +/* MCLKCFG */ +#define AM13E_MCLKCFG_USEHSCLK_EN 0x00010000U +#define AM13E_MCLKCFG_MCLKDIVCFG_MASK 0x07000000U + +/* HSCLKEN */ +#define AM13E_HSCLKEN_SYSPLLEN_MASK 0x00000100U +#define AM13E_HSCLKEN_SYSPLLEN_EN 0x00000100U + +/* HSCLKCFG */ +#define AM13E_HSCLKCFG_HSCLKSEL_MASK 0x00000001U +#define AM13E_HSCLKCFG_HSCLKSEL_SYSPLL 0x00000000U + +/* HFCLKCLKCFG */ +#define AM13E_HFCLKCLKCFG_XTALTIME_MASK 0x000000FFU +#define AM13E_HFCLKCLKCFG_FLTCHK_EN 0x10000000U + +/* SYSPLLCFG0 */ +#define AM13E_SYSPLLCFG0_SYSPLLREF_MASK 0x00000001U +#define AM13E_SYSPLLCFG0_SYSPLLREF_HFCLK 0x00000001U +#define AM13E_SYSPLLCFG0_ENABLECLK0_MASK 0x00000010U +#define AM13E_SYSPLLCFG0_ENABLECLK0_EN 0x00000010U +#define AM13E_SYSPLLCFG0_ENABLECLK1_MASK 0x00000020U +#define AM13E_SYSPLLCFG0_ENABLECLK1_EN 0x00000020U +#define AM13E_SYSPLLCFG0_RDIVCLK0_OFS 8U +#define AM13E_SYSPLLCFG0_RDIVCLK0_MASK 0x00000F00U +#define AM13E_SYSPLLCFG0_RDIVCLK1_OFS 12U +#define AM13E_SYSPLLCFG0_RDIVCLK1_MASK 0x0000F000U + +/* SYSPLLCFG1 */ +#define AM13E_SYSPLLCFG1_PDIV_MASK 0x00000003U +#define AM13E_SYSPLLCFG1_QDIV_OFS 8U +#define AM13E_SYSPLLCFG1_QDIV_MASK 0x00007F00U + +/* GENCLKEN */ +#define AM13E_GENCLKEN_EXTDIVMCLK_MASK 0x00000700U +#define AM13E_GENCLKEN_EXTDIVMCLK_DIV4 0x00000100U +#define AM13E_GENCLKEN_EXTDIVMCLK_DIV2 0x00000000U +#define AM13E_GENCLKEN_MCLKEXTDIVEN_EN 0x00000800U +#define AM13E_GENCLKEN_EXTDIVCAN_MASK 0x00007000U +#define AM13E_GENCLKEN_CANEXTDIVEN_MASK 0x00008000U +#define AM13E_GENCLKEN_CANEXTDIVEN_EN 0x00008000U + +/* GENCLKCFG */ +#define AM13E_GENCLKCFG_CANCLKSRC_MASK 0x00000100U +#define AM13E_GENCLKCFG_CANCLKSRC_HFCLK 0x00000000U +#define AM13E_GENCLKCFG_CANCLKSRC_SYSPLLOUT 0x00000100U + +/* CLKSTATUS */ +#define AM13E_CLKSTATUS_HFCLKGOOD_MASK 0x00000100U +#define AM13E_CLKSTATUS_SYSPLLGOOD_MASK 0x00000200U +#define AM13E_CLKSTATUS_HSCLKMUX_MASK 0x00000010U +#define AM13E_CLKSTATUS_SYSPLLOFF_MASK 0x00004000U +#define AM13E_CLKSTATUS_HSCLKGOOD_MASK 0x00200000U + +/* XTALCR */ +#define AM13E_XTALCR_OSCOFF_MASK 0x00000001U + +/* Factory region: PLL startup parameter base addresses (per input freq range) */ +#define AM13E_FACTORY_PLLPARAM0_4_8MHZ 0x60111020UL +#define AM13E_FACTORY_PLLPARAM0_8_16MHZ 0x60111028UL +#define AM13E_FACTORY_PLLPARAM0_16_32MHZ 0x60111030UL +#define AM13E_FACTORY_PLLPARAM0_32_48MHZ 0x60111038UL + +/* + * MMIO helpers + */ +#define SYSCTL_RD(off) (*(volatile uint32_t *)(AM13E_SYSCTL_BASE + (off))) +#define SYSCTL_WR(off, val) (*(volatile uint32_t *)(AM13E_SYSCTL_BASE + (off)) = (uint32_t)(val)) +#define SYSCTL_SET(off, mask) SYSCTL_WR(off, SYSCTL_RD(off) | (uint32_t)(mask)) +#define SYSCTL_CLR(off, mask) SYSCTL_WR(off, SYSCTL_RD(off) & ~(uint32_t)(mask)) +#define SYSCTL_RMW(off, mask, val) \ + SYSCTL_WR(off, (SYSCTL_RD(off) & ~(uint32_t)(mask)) | ((uint32_t)(val) & (uint32_t)(mask))) + +/* + * Compile-time presence checks + */ +#if DT_NODE_HAS_STATUS(DT_NODELABEL(syspll), okay) +#define AM13E_PLL_ENABLED 1 +#endif + +#if DT_NODE_HAS_STATUS(DT_NODELABEL(hfxt), okay) +#define AM13E_HFXT_ENABLED 1 +#endif + +/* + * Build-time validation + */ +#ifdef AM13E_PLL_ENABLED +#if !DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk0_div) && \ + !DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk1_div) +#error "syspll: at least one of ti,clk0-div or ti,clk1-div must be set" +#endif +#endif + +/* + * Compile-time clock tree values derived from device tree + */ +#define AM13E_MCLK_HZ DT_PROP(DT_NODELABEL(mclk), clock_frequency) +#define AM13E_MCLK2_HZ DT_PROP(DT_NODELABEL(mclk2), clock_frequency) +#define AM13E_MCLK4_HZ DT_PROP(DT_NODELABEL(mclk4), clock_frequency) + +/* + * MCLKDIVCFG raw field value (written directly to MCLKCFG[26:24]). + * Encoding: RATIO_1_1_1=0x0, RATIO_1_1_2=0x1, RATIO_1_1_4=0x3, + * RATIO_1_2_2=0x5, RATIO_1_2_4=0x7 (all shifted to bit 24). + */ +#define AM13E_MCLK_DIV_VAL \ + ((AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ) ? 0x00000000UL \ + : (AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 2) ? 0x01000000UL \ + : (AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 4) ? 0x03000000UL \ + : (AM13E_MCLK2_HZ == AM13E_MCLK_HZ / 2 && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 2) \ + ? 0x05000000UL \ + : 0x07000000UL) + +BUILD_ASSERT((AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ) || + (AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 2) || + (AM13E_MCLK2_HZ == AM13E_MCLK_HZ && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 4) || + (AM13E_MCLK2_HZ == AM13E_MCLK_HZ / 2 && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 2) || + (AM13E_MCLK2_HZ == AM13E_MCLK_HZ / 2 && AM13E_MCLK4_HZ == AM13E_MCLK_HZ / 4), + "mclk2,mclk4: unsupported MCLK divider ratio combination"); + +#ifdef AM13E_PLL_ENABLED + +/* Frequency of the PLL reference clock (parent of syspll) */ +#define AM13E_PLL_REF_HZ DT_PROP(DT_CLOCKS_CTLR(DT_NODELABEL(syspll)), clock_frequency) + +/* PLL input frequency after pre-divider: F_in = REF / p-div */ +#define AM13E_PLL_INPUT_HZ (AM13E_PLL_REF_HZ / DT_PROP(DT_NODELABEL(syspll), ti_p_div)) + +BUILD_ASSERT(DT_PROP(DT_NODELABEL(syspll), ti_p_div) == 1 || + DT_PROP(DT_NODELABEL(syspll), ti_p_div) == 2 || + DT_PROP(DT_NODELABEL(syspll), ti_p_div) == 4 || + DT_PROP(DT_NODELABEL(syspll), ti_p_div) == 8, + "syspll: ti,p-div must be 1, 2, 4, or 8"); + +BUILD_ASSERT(AM13E_PLL_INPUT_HZ >= 4000000 && AM13E_PLL_INPUT_HZ <= 48000000, + "syspll: PLL input frequency (REF/p-div) must be in range 4..48 MHz"); + +#if DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk0_div) +BUILD_ASSERT(DT_PROP(DT_NODELABEL(syspll), ti_clk0_div) >= 2 && + DT_PROP(DT_NODELABEL(syspll), ti_clk0_div) <= 32 && + (DT_PROP(DT_NODELABEL(syspll), ti_clk0_div) % 2) == 0, + "syspll: ti,clk0-div must be an even value in range 2..32"); +#endif + +#if DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk1_div) +BUILD_ASSERT(DT_PROP(DT_NODELABEL(syspll), ti_clk1_div) >= 2 && + DT_PROP(DT_NODELABEL(syspll), ti_clk1_div) <= 32 && + (DT_PROP(DT_NODELABEL(syspll), ti_clk1_div) % 2) == 0, + "syspll: ti,clk1-div must be an even value in range 2..32"); +#endif + +/* TODO + * Factory region address of PLLPARAM0 for the applicable input-frequency band. + * PLLPARAM1 is at +4 bytes from the same base. + */ +#define AM13E_FACTORY_PARAM0_ADDR \ + ((AM13E_PLL_INPUT_HZ >= 4000000 && AM13E_PLL_INPUT_HZ < 8000000) \ + ? AM13E_FACTORY_PLLPARAM0_4_8MHZ \ + : (AM13E_PLL_INPUT_HZ >= 8000000 && AM13E_PLL_INPUT_HZ < 16000000) \ + ? AM13E_FACTORY_PLLPARAM0_8_16MHZ \ + : (AM13E_PLL_INPUT_HZ >= 16000000 && AM13E_PLL_INPUT_HZ < 32000000) \ + ? AM13E_FACTORY_PLLPARAM0_16_32MHZ \ + : AM13E_FACTORY_PLLPARAM0_32_48MHZ) + +/* + * Map pDiv integer (1, 2, 4, 8) to SYSPLLCFG1[PDIV] 2-bit encoded value. + * Hardware: 0=÷1, 1=÷2, 2=÷4, 3=÷8. + */ +#define AM13E_PDIV_BITS(p) ((p) == 1U ? 0U : (p) == 2U ? 1U : (p) == 4U ? 2U : 3U) + +/* + * Map output-divider (2, 4, 6, ..., 32) to RDIVCLK field value. + * Hardware: field=0 → ÷2, field=1 → ÷4, etc. (field = d/2 − 1) + */ +#define AM13E_RDIV(d) (((d) / 2U) - 1U) + +/* SYSPLL reference bit: 1 if parent is hfclk, 0 for sysosc */ +#if DT_SAME_NODE(DT_CLOCKS_CTLR(DT_NODELABEL(syspll)), DT_NODELABEL(hfclk)) +#define AM13E_PLL_REF_BIT AM13E_SYSPLLCFG0_SYSPLLREF_HFCLK +#else +#define AM13E_PLL_REF_BIT 0U +#endif + +#endif /* AM13E_PLL_ENABLED */ + +/* + * Conservative settling delay after an MCLK source switch or GENCLKEN + * divider change. No minimum is specified in the TRM. + */ +#define AM13E_MCLK_SETTLE_CYCLES 20U + +/* + * Short cycle-count delay (replaces DL_Common_delayCycles) + */ +__maybe_unused static void am13e_delay(uint32_t n) +{ + for (uint32_t i = 0; i < n; i++) { + __asm__ volatile("nop"); + } +} + +/* + * Clock control API callbacks + */ + +static int clock_am13e_on(const struct device *dev, clock_control_subsys_t sys) +{ + ARG_UNUSED(dev); + ARG_UNUSED(sys); + return -ENOTSUP; +} + +static int clock_am13e_off(const struct device *dev, clock_control_subsys_t sys) +{ + ARG_UNUSED(dev); + ARG_UNUSED(sys); + return -ENOTSUP; +} + +static int clock_am13e_get_rate(const struct device *dev, clock_control_subsys_t sys, + uint32_t *rate) +{ + const struct am13e_sys_clock *clk = (const struct am13e_sys_clock *)sys; + + switch (clk->clk) { + case AM13E_CLOCK_MCLK: + *rate = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC; + break; + + case AM13E_CLOCK_MCLK2: + *rate = AM13E_MCLK2_HZ; + break; + + case AM13E_CLOCK_MCLK4: + *rate = AM13E_MCLK4_HZ; + break; + + case AM13E_CLOCK_HFCLK: + *rate = DT_PROP(DT_NODELABEL(hfclk), clock_frequency); + break; + + case AM13E_CLOCK_SYSPLL0: +#ifdef AM13E_PLL_ENABLED + *rate = DT_PROP(DT_NODELABEL(syspll), clock_frequency); +#else + return -ENOTSUP; +#endif + break; + + case AM13E_CLOCK_SYSPLL1: +#if defined(AM13E_PLL_ENABLED) && DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk1_div) + *rate = (uint32_t)(((uint64_t)AM13E_PLL_REF_HZ * + (DT_PROP(DT_NODELABEL(syspll), ti_q_div) + 1)) / + DT_PROP(DT_NODELABEL(syspll), ti_p_div) / + DT_PROP(DT_NODELABEL(syspll), ti_clk1_div)); +#else + return -ENOTSUP; +#endif + break; + + case AM13E_CLOCK_HSCLK: + *rate = DT_PROP(DT_NODELABEL(hsclk), clock_frequency); + break; + + case AM13E_CLOCK_CANCLK: + return -ENOTSUP; + + case AM13E_CLOCK_SYSOSC: + *rate = DT_PROP(DT_NODELABEL(sysosc), clock_frequency); + break; + + default: + return -ENOTSUP; + } + + return 0; +} + +/* + * Hardware initialization + * + * NOTE: Flash wait states must already be configured before this runs. + * soc_early_init_hook() calls FLASH_init() prior to any device init. + */ + +static int clock_am13e_init(const struct device *dev) +{ + ARG_UNUSED(dev); + +#ifdef AM13E_HFXT_ENABLED + /* Reset XTAL to a known state before reconfiguring */ + SYSCTL_SET(AM13E_SYSCTL_XTALCR, AM13E_XTALCR_OSCOFF_MASK); + + /* Program XTAL startup time (register unit = 64 us) */ + uint32_t xtaltime = DT_PROP_OR(DT_NODELABEL(hfxt), ti_xtal_startup_delay, 0U); + SYSCTL_RMW(AM13E_SYSCTL_HFCLKCLKCFG, AM13E_HFCLKCLKCFG_XTALTIME_MASK, xtaltime); + + /* Power up XTAL (clear OSCOFF) */ + SYSCTL_CLR(AM13E_SYSCTL_XTALCR, AM13E_XTALCR_OSCOFF_MASK); + + /* Enable startup monitor and wait for HFCLK to be valid */ + SYSCTL_SET(AM13E_SYSCTL_HFCLKCLKCFG, AM13E_HFCLKCLKCFG_FLTCHK_EN); + while (!(SYSCTL_RD(AM13E_SYSCTL_CLKSTATUS) & AM13E_CLKSTATUS_HFCLKGOOD_MASK)) { + } /* TODO: busy-wait loops need ETIMEDOUT logic */ +#endif /* AM13E_HFXT_ENABLED */ + +#ifdef AM13E_PLL_ENABLED + /* Disable SYSPLL (retained across lower resets; ensure clean state) */ + SYSCTL_CLR(AM13E_SYSCTL_HSCLKEN, AM13E_HSCLKEN_SYSPLLEN_MASK); + while (!(SYSCTL_RD(AM13E_SYSCTL_CLKSTATUS) & AM13E_CLKSTATUS_SYSPLLOFF_MASK)) { + } + + /* SYSPLLCFG0: reference source, output enables, output dividers */ + { + uint32_t cfg0 = AM13E_PLL_REF_BIT; + uint32_t mask = AM13E_SYSPLLCFG0_SYSPLLREF_MASK | AM13E_SYSPLLCFG0_ENABLECLK0_MASK | + AM13E_SYSPLLCFG0_ENABLECLK1_MASK | AM13E_SYSPLLCFG0_RDIVCLK0_MASK | + AM13E_SYSPLLCFG0_RDIVCLK1_MASK; + +#if DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk0_div) + cfg0 |= AM13E_SYSPLLCFG0_ENABLECLK0_EN; + cfg0 |= AM13E_RDIV(DT_PROP(DT_NODELABEL(syspll), ti_clk0_div)) + << AM13E_SYSPLLCFG0_RDIVCLK0_OFS; +#endif +#if DT_NODE_HAS_PROP(DT_NODELABEL(syspll), ti_clk1_div) + cfg0 |= AM13E_SYSPLLCFG0_ENABLECLK1_EN; + cfg0 |= AM13E_RDIV(DT_PROP(DT_NODELABEL(syspll), ti_clk1_div)) + << AM13E_SYSPLLCFG0_RDIVCLK1_OFS; +#endif + SYSCTL_RMW(AM13E_SYSCTL_SYSPLLCFG0, mask, cfg0); + } + + /* SYSPLLCFG1: pre-divider (pDiv) and feedback divider (qDiv) */ + { + uint32_t pdiv = AM13E_PDIV_BITS(DT_PROP(DT_NODELABEL(syspll), ti_p_div)); + uint32_t qdiv = + (DT_PROP(DT_NODELABEL(syspll), ti_q_div) << AM13E_SYSPLLCFG1_QDIV_OFS) & + AM13E_SYSPLLCFG1_QDIV_MASK; + + SYSCTL_RMW(AM13E_SYSCTL_SYSPLLCFG1, + AM13E_SYSPLLCFG1_PDIV_MASK | AM13E_SYSPLLCFG1_QDIV_MASK, pdiv | qdiv); + } + + /* Load factory-calibrated PLL startup parameters */ + *(volatile uint32_t *)(AM13E_SYSCTL_BASE + AM13E_SYSCTL_SYSPLLPARAM0) = + *(volatile uint32_t *)(AM13E_FACTORY_PARAM0_ADDR); + *(volatile uint32_t *)(AM13E_SYSCTL_BASE + AM13E_SYSCTL_SYSPLLPARAM1) = + *(volatile uint32_t *)(AM13E_FACTORY_PARAM0_ADDR + 4U); + + /* Enable SYSPLL and wait for lock */ + SYSCTL_SET(AM13E_SYSCTL_HSCLKEN, AM13E_HSCLKEN_SYSPLLEN_EN); + while (!(SYSCTL_RD(AM13E_SYSCTL_CLKSTATUS) & AM13E_CLKSTATUS_SYSPLLGOOD_MASK)) { + } + + /* Select SYSPLL as the HSCLK source and wait for HSCLK valid */ + SYSCTL_WR(AM13E_SYSCTL_HSCLKCFG, AM13E_HSCLKCFG_HSCLKSEL_SYSPLL); + while (!(SYSCTL_RD(AM13E_SYSCTL_CLKSTATUS) & AM13E_CLKSTATUS_HSCLKGOOD_MASK)) { + } + + /* + * Ramp MCLK to full PLL speed in three steps to stay within safe + * operating limits during the transition. The intermediate dividers + * are fixed by hardware requirements; the final speed is HSCLK as + * configured in the device tree. + * + * Step 1 – engage /4 divider, switch MCLK source from SYSOSC to HSCLK. + * MCLK = HSCLK / 4. + * Step 2 – step up to /2 divider. + * MCLK = HSCLK / 2. + * Step 3 – remove divider. + * MCLK = HSCLK. + */ + + /* Step 1: /4 divider + switch to HSCLK */ + SYSCTL_RMW(AM13E_SYSCTL_GENCLKEN, + AM13E_GENCLKEN_EXTDIVMCLK_MASK | AM13E_GENCLKEN_MCLKEXTDIVEN_EN, + AM13E_GENCLKEN_EXTDIVMCLK_DIV4 | AM13E_GENCLKEN_MCLKEXTDIVEN_EN); + + SYSCTL_SET(AM13E_SYSCTL_MCLKCFG, AM13E_MCLKCFG_USEHSCLK_EN); + am13e_delay(AM13E_MCLK_SETTLE_CYCLES); + while (!(SYSCTL_RD(AM13E_SYSCTL_CLKSTATUS) & AM13E_CLKSTATUS_HSCLKMUX_MASK)) { + } + + /* Step 2: /2 divider */ + SYSCTL_RMW(AM13E_SYSCTL_GENCLKEN, + AM13E_GENCLKEN_EXTDIVMCLK_MASK | AM13E_GENCLKEN_MCLKEXTDIVEN_EN, + AM13E_GENCLKEN_EXTDIVMCLK_DIV2 | AM13E_GENCLKEN_MCLKEXTDIVEN_EN); + + am13e_delay(AM13E_MCLK_SETTLE_CYCLES); + + /* Step 3: no divider */ + SYSCTL_CLR(AM13E_SYSCTL_GENCLKEN, AM13E_GENCLKEN_MCLKEXTDIVEN_EN); + am13e_delay(AM13E_MCLK_SETTLE_CYCLES); +#endif /* AM13E_PLL_ENABLED */ + + /* Configure MCLK2 / MCLK4 sub-dividers */ + SYSCTL_RMW(AM13E_SYSCTL_MCLKCFG, AM13E_MCLKCFG_MCLKDIVCFG_MASK, AM13E_MCLK_DIV_VAL); + + return 0; +} + +/* + * Device registration + */ + +static DEVICE_API(clock_control, clock_am13e_driver_api) = { + .on = clock_am13e_on, + .off = clock_am13e_off, + .get_rate = clock_am13e_get_rate, +}; + +DEVICE_DT_DEFINE(DT_NODELABEL(ckm), &clock_am13e_init, NULL, NULL, NULL, PRE_KERNEL_1, + CONFIG_CLOCK_CONTROL_INIT_PRIORITY, &clock_am13e_driver_api); diff --git a/include/zephyr/drivers/clock_control/am13e_clock_control.h b/include/zephyr/drivers/clock_control/am13e_clock_control.h new file mode 100644 index 000000000000..5eb743980a98 --- /dev/null +++ b/include/zephyr/drivers/clock_control/am13e_clock_control.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_AM13E_CLOCK_CONTROL_H +#define ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_AM13E_CLOCK_CONTROL_H + +#include + +/** + * @brief Clock subsystem descriptor passed to clock_control_get_rate(). + * + * Peripheral drivers embed this in their config and pass a pointer to it + * as the clock_control_subsys_t argument: + * + * static const struct am13e_sys_clock my_clk = AM13E_CLOCK_SUBSYS(AM13E_CLOCK_MCLK4); + * clock_control_get_rate(ckm_dev, (clock_control_subsys_t)&my_clk, &rate); + */ +struct am13e_sys_clock { + uint32_t clk; +}; + +#define AM13E_CLOCK_SUBSYS(clock_id) {.clk = (clock_id)} + +/** Convenience macro for use in peripheral driver config structs. */ +#define AM13E_CLOCK_SUBSYS_INST(index) {.clk = DT_INST_CLOCKS_CELL(index, clk)} + +#endif /* ZEPHYR_INCLUDE_DRIVERS_CLOCK_CONTROL_AM13E_CLOCK_CONTROL_H */ diff --git a/include/zephyr/dt-bindings/clock/am13e_clock.h b/include/zephyr/dt-bindings/clock/am13e_clock.h new file mode 100644 index 000000000000..129722177f64 --- /dev/null +++ b/include/zephyr/dt-bindings/clock/am13e_clock.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_AM13E_CLOCK_H +#define ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_AM13E_CLOCK_H + +/* Clock reference IDs used in DTS: clocks = <&ckm AM13E_CLOCK_xxx> */ +#define AM13E_CLOCK_SYSOSC 0 +#define AM13E_CLOCK_HFCLK 1 +#define AM13E_CLOCK_SYSPLL0 2 +#define AM13E_CLOCK_SYSPLL1 3 +#define AM13E_CLOCK_HSCLK 4 +#define AM13E_CLOCK_MCLK 5 +#define AM13E_CLOCK_MCLK2 6 +#define AM13E_CLOCK_MCLK4 7 +#define AM13E_CLOCK_CANCLK 8 +#define AM13E_CLOCK_LFOSC 9 + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_CLOCK_AM13E_CLOCK_H */ From a4da3ed3790f00a9c3d939585fd535386dfdf9c8 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:08:59 +0530 Subject: [PATCH 19/22] drivers: clock_control: add AM13E Kconfig Add Kconfig entry and defconfig for AM13E clock control driver. Signed-off-by: Sameer Srivastava --- drivers/clock_control/Kconfig | 2 ++ drivers/clock_control/Kconfig.am13e | 14 ++++++++++++++ soc/ti/am13/am13e/Kconfig.defconfig | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 drivers/clock_control/Kconfig.am13e diff --git a/drivers/clock_control/Kconfig b/drivers/clock_control/Kconfig index 05a2a7fb8fa6..5e34619ec0fe 100644 --- a/drivers/clock_control/Kconfig +++ b/drivers/clock_control/Kconfig @@ -52,6 +52,8 @@ source "drivers/clock_control/Kconfig.mcux_sim" source "drivers/clock_control/Kconfig.mcux_syscon" +source "drivers/clock_control/Kconfig.am13e" + source "drivers/clock_control/Kconfig.mspm0" source "drivers/clock_control/Kconfig.npcm" diff --git a/drivers/clock_control/Kconfig.am13e b/drivers/clock_control/Kconfig.am13e new file mode 100644 index 000000000000..10cdfc8d816e --- /dev/null +++ b/drivers/clock_control/Kconfig.am13e @@ -0,0 +1,14 @@ +# TI AM13E Family +# +# Copyright (c) 2026, Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +config CLOCK_CONTROL_AM13E + bool "TI AM13E clock control" + default y + depends on DT_HAS_TI_AM13E_CLK_ENABLED + help + Enable clock control driver for the TI AM13E SoC family. + Provides clock frequency queries (get_rate) and configures the + HFCLK, SYSPLL, MCLK, MCLK2, MCLK4 and HSCLK tree from + device tree properties at PRE_KERNEL_1. diff --git a/soc/ti/am13/am13e/Kconfig.defconfig b/soc/ti/am13/am13e/Kconfig.defconfig index b01f4c2ee6f1..95e733b18b11 100644 --- a/soc/ti/am13/am13e/Kconfig.defconfig +++ b/soc/ti/am13/am13e/Kconfig.defconfig @@ -6,6 +6,9 @@ if SOC_SERIES_AM13E +config CLOCK_CONTROL + default y + config NUM_IRQS default 48 From 9914ad7f8ef913fb141c7814c23f6475a468db22 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:09:04 +0530 Subject: [PATCH 20/22] dts: bindings: clock: add AM13E clock bindings Add YAML DT bindings for AM13E clock, oscillator, and PLL nodes. Signed-off-by: Sameer Srivastava --- dts/bindings/clock/ti,am13e-clk.yaml | 27 +++++++++++++ dts/bindings/clock/ti,am13e-osc.yaml | 19 +++++++++ dts/bindings/clock/ti,am13e-pll.yaml | 59 ++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 dts/bindings/clock/ti,am13e-clk.yaml create mode 100644 dts/bindings/clock/ti,am13e-osc.yaml create mode 100644 dts/bindings/clock/ti,am13e-pll.yaml diff --git a/dts/bindings/clock/ti,am13e-clk.yaml b/dts/bindings/clock/ti,am13e-clk.yaml new file mode 100644 index 000000000000..e29d1a689f40 --- /dev/null +++ b/dts/bindings/clock/ti,am13e-clk.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +description: TI AM13E clock node + +compatible: "ti,am13e-clk" + +include: [clock-controller.yaml, base.yaml] + +properties: + "#clock-cells": + const: 1 + + clocks: + description: Parent clock source reference + + clock-frequency: + type: int + description: Output clock frequency in Hz + + ti,sysctl-base: + type: int + description: | + SYSCTL Registers base. + +clock-cells: + - clk diff --git a/dts/bindings/clock/ti,am13e-osc.yaml b/dts/bindings/clock/ti,am13e-osc.yaml new file mode 100644 index 000000000000..1ab510d8372e --- /dev/null +++ b/dts/bindings/clock/ti,am13e-osc.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +description: TI AM13E oscillator (XTAL or internal) + +compatible: "ti,am13e-osc" + +include: [fixed-clock.yaml, base.yaml] + +properties: + "#clock-cells": + const: 0 + + ti,xtal-startup-delay: + type: int + description: | + Crystal oscillator startup time, stored in units of 64 us + (value = delay_us / 64). Applies only to the HFXT (external + crystal) oscillator. diff --git a/dts/bindings/clock/ti,am13e-pll.yaml b/dts/bindings/clock/ti,am13e-pll.yaml new file mode 100644 index 000000000000..ba6777fab1ee --- /dev/null +++ b/dts/bindings/clock/ti,am13e-pll.yaml @@ -0,0 +1,59 @@ +# Copyright (c) 2026 Texas Instruments Incorporated +# SPDX-License-Identifier: Apache-2.0 + +description: | + TI AM13E SYSPLL node. + + The SYSPLL output frequency formula: + F_VCO = (F_REF / p-div) * (q-div + 1) + F_CLK0 = F_VCO / clk0-div + F_CLK1 = F_VCO / clk1-div + + Example for 200 MHz output with a 25 MHz HFXTAL: + p-div = 2 -> F_input = 12.5 MHz (in 8-16 MHz range) + q-div = 31 -> F_VCO = 400 MHz + clk0-div = 2 -> CLK0 = 200 MHz + clk1-div = 2 -> CLK1 = 200 MHz + +compatible: "ti,am13e-pll" + +include: [clock-controller.yaml, base.yaml] + +properties: + "#clock-cells": + const: 1 + + clocks: + required: true + description: Reference clock source (hfclk or sysosc) + + clock-frequency: + type: int + required: true + description: CLK0 output frequency in Hz (used for clock-frequency reporting) + + ti,p-div: + type: int + required: true + description: | + PLL reference pre-divider. Valid values: 1, 2, 4, 8. + + ti,q-div: + type: int + required: true + description: | + PLL feedback divider. Effective multiplier = q-div + 1. + Valid range: 1 to 126 (register value 0 to 125). + + ti,clk0-div: + type: int + description: | + CLK0 output divider. Even values 2..32. Omit to disable CLK0. + + ti,clk1-div: + type: int + description: | + CLK1 output divider. Even values 2..32. Omit to disable CLK1. + +clock-cells: + - clk From d4c1f6d65295dc5fdff9e72fc90778418f44c8a1 Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:09:08 +0530 Subject: [PATCH 21/22] dts: arm: ti: am13: add clock nodes Add oscillator, PLL, and clock nodes to AM13E DTSI. Signed-off-by: Sameer Srivastava --- dts/arm/ti/am13/am13e.dtsi | 118 ++++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/dts/arm/ti/am13/am13e.dtsi b/dts/arm/ti/am13/am13e.dtsi index 3203ad4c23fa..cc24ff86c082 100644 --- a/dts/arm/ti/am13/am13e.dtsi +++ b/dts/arm/ti/am13/am13e.dtsi @@ -7,6 +7,7 @@ #include #include #include +#include /{ cpus { @@ -27,4 +28,119 @@ reg = <0xe000ed90 0x40>; }; }; -}; \ No newline at end of file + + /* + * Clock module: single clock controller device for AM13E230X. + * Peripheral drivers reference this with: + * clocks = <&ckm AM13E_CLOCK_MCLK4>; + */ + ckm: clock-module { + compatible = "ti,am13e-clk"; + #clock-cells = <1>; + ti,sysctl-base = <0x400AF000UL>; + }; + + /* + * Clock tree data nodes. No separate Zephyr device is created for + * these; the ckm driver reads their properties at init time. + */ + clocks { + /* + * Internal System Oscillator (SYSOSC). + */ + sysosc: sysosc { + compatible = "fixed-clock"; + clock-frequency = ; + #clock-cells = <0>; + }; + + /* + * External high-frequency crystal oscillator (HFXT). + * + * Startup delay has to be set in units of 64us, with the + * upper limit of 255, i.e. for a 25MHz crystal, + * maximum startup delay = 255 * 64 us = 16320 us + */ + hfxt: hfxt { + compatible = "ti,am13e-osc"; + clock-frequency = ; + #clock-cells = <0>; + ti,xtal-startup-delay = <255>; + status = "okay"; + }; + + /* + * HFCLK: driven from the external XTAL (hfxt). + * clock-frequency must equal hfxt clock-frequency. + */ + hfclk: hfclk { + compatible = "ti,am13e-clk"; + clock-frequency = ; + clocks = <&hfxt>; + #clock-cells = <1>; + status = "okay"; + }; + + /* + * SYSPLL configuration: + * F_input = HFCLK / p-div = 25 / 2 = 12.5 MHz (8-16 MHz range) + * F_VCO = F_input * (q-div + 1) = 12.5 * 32 = 400 MHz + * CLK0 = F_VCO / clk0-div = 400 / 2 = 200 MHz + * CLK1 = F_VCO / clk1-div = 400 / 2 = 200 MHz + * + * clock-frequency reports the CLK0 output frequency. + */ + syspll: syspll { + compatible = "ti,am13e-pll"; + clock-frequency = ; + clocks = <&hfclk AM13E_CLOCK_HFCLK>; + #clock-cells = <1>; + ti,p-div = <2>; + ti,q-div = <31>; + ti,clk0-div = <2>; + ti,clk1-div = <2>; + status = "okay"; + }; + + /* + * HSCLK: high-speed clock, fed from SYSPLL CLK0 (200 MHz). + */ + hsclk: hsclk { + compatible = "ti,am13e-clk"; + clock-frequency = ; + clocks = <&syspll AM13E_CLOCK_SYSPLL0>; + #clock-cells = <1>; + }; + + /* + * MCLK: main CPU clock sourced from HSCLK. + * clock-frequency must match CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC. + */ + mclk: mclk { + compatible = "ti,am13e-clk"; + clock-frequency = ; + clocks = <&hsclk AM13E_CLOCK_HSCLK>; + #clock-cells = <1>; + }; + + /* + * MCLK2: MCLK / 2 = 100 MHz. + * Peripheral bus clock for high-speed peripherals. + */ + mclk2: mclk2 { + compatible = "ti,am13e-clk"; + clock-frequency = ; + #clock-cells = <1>; + }; + + /* + * MCLK4: MCLK / 4 = 50 MHz. + * Default peripheral clock for UNICOMM (UART, I2C, SPI). + */ + mclk4: mclk4 { + compatible = "ti,am13e-clk"; + clock-frequency = ; + #clock-cells = <1>; + }; + }; +}; From d3f1e0dc5147330e58aa7286b42e507213cabb2d Mon Sep 17 00:00:00 2001 From: Sameer Srivastava Date: Wed, 17 Jun 2026 18:09:17 +0530 Subject: [PATCH 22/22] soc: ti: am13: use clock control driver for init Replace hardcoded clock init in soc.c with calls to the AM13E clock control driver. Depends on the clock driver being present. Signed-off-by: Sameer Srivastava --- soc/ti/am13/am13e/soc.c | 51 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/soc/ti/am13/am13e/soc.c b/soc/ti/am13/am13e/soc.c index 867154f6d515..31913fc0e12f 100644 --- a/soc/ti/am13/am13e/soc.c +++ b/soc/ti/am13/am13e/soc.c @@ -3,60 +3,31 @@ * * SPDX-License-Identifier: Apache-2.0 */ - -#include #include -static const DL_SYSCTL_SYSPLLConfig gSYSPLLConfig = { - .sysPLLRef = DL_SYSCTL_SYSPLL_REF_HFCLK, - .inputFreq = DL_SYSCTL_SYSPLL_INPUT_FREQ_8_16_MHZ, - .pDiv = DL_SYSCTL_SYSPLL_PDIV_2, - .qDiv = 31, - .enableCLK1 = DL_SYSCTL_SYSPLL_CLK1_ENABLE, - .enableCLK0 = DL_SYSCTL_SYSPLL_CLK0_ENABLE, - .rDivClk1 = DL_SYSCTL_SYSPLL_RDIVCLK1_DIV2, - .rDivClk0 = DL_SYSCTL_SYSPLL_RDIVCLK0_DIV2, -}; - -void SysPLL_Init(void) -{ - DL_SYSCTL_setHFCLKSourceXTAL(255, true); - DL_SYSCTL_configSYSPLL((DL_SYSCTL_SYSPLLConfig *)&gSYSPLLConfig); - - /* Before switching to PLL output, step down to a lower frequency and gradually increase */ - DL_SYSCTL_enablePLLDivider(DL_SYSCTL_PLL_DIVIDER_DIV4); - DL_SYSCTL_switchMCLKfromSYSOSCtoHSCLK(DL_SYSCTL_HSCLK_SOURCE_SYSPLL); - DL_SYSCTL_enablePLLDivider(DL_SYSCTL_PLL_DIVIDER_DIV2); - DL_Common_delayCycles(20); - while ((DL_SYSCTL_getClockStatus() & SYSCTL_CLKSTATUS_HSCLKMUX_MASK) != - DL_SYSCTL_CLK_STATUS_MCLK_SOURCE_HSCLK) - ; - DL_SYSCTL_disablePLLDivider(); - DL_Common_delayCycles(20); - while ((DL_SYSCTL_getClockStatus() & SYSCTL_CLKSTATUS_HSCLKMUX_MASK) != - DL_SYSCTL_CLK_STATUS_MCLK_SOURCE_HSCLK) - ; - - DL_SYSCTL_setMCLKDivider(DL_SYSCTL_MCLK_DIV_2_DIV_4); - DL_SYSCTL_setCANCLKSource(DL_SYSCTL_CANCLK_SOURCE_SYSPLL_DIV2); -} - -void FLASH_init(void) +static void FLASH_init(void) { /* Disable cache before changing wait states */ DL_FRI_disableDLB(); DL_FRI_disableCache(); - /* Set the flash wait states */ + /* Set flash wait states appropriate for 200 MHz operation */ DL_FRI_setReadWaitStates(0x3); - /* Enable cache to improve performance of code executed from flash */ + /* Re-enable cache for improved code-execution performance */ DL_FRI_enableDLB(); DL_FRI_enableCache(); } +/* + * soc_early_init_hook runs before any device initialization, including the + * clock control driver. Configure flash wait states here so the CPU can + * safely execute at 200 MHz once the clock driver switches to the SYSPLL. + * + * Clock tree configuration (HFXT, SYSPLL, MCLK switch) is handled by the + * clock_control_am13e driver at PRE_KERNEL_1 priority. + */ void soc_early_init_hook(void) { FLASH_init(); - SysPLL_Init(); }