Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,43 @@ PAR/M0AR register pair:
| `memory_to_peripheral` | memory buffer | peripheral register |
| `memory_to_memory` | memory buffer | memory buffer |

### `dmdma_stream_options_t` (optional, `dmdma_lease_start_ex()`)

```c
typedef struct
{
dmdma_flow_controller_t flow_controller; /* dma (default) | peripheral */
dmdma_fifo_threshold_t fifo_threshold; /* direct (default) | quarter | half | three_quarters | full */
dmdma_burst_t source_burst; /* single (default) | 4 | 8 | 16 beats */
dmdma_burst_t destination_burst;
} dmdma_stream_options_t;
```

A zero-initialized structure (or `NULL`) is exactly the behavior of
`dmdma_lease_start()`. Rules checked by the core before the port is touched
(`-EINVAL` otherwise):

- bursts need FIFO mode; direct mode also needs equal source/destination widths,
- the memory-side burst (beats x memory element width) must fit the FIFO
threshold (4/8/12/16 bytes) an integral number of times,
- `dmdma_flow_controller_peripheral` is only valid for memory<->peripheral,
non-circular transfers; the hardware then ignores `element_count` and the
peripheral ends the transfer (e.g. SDIO/SDMMC).

In FIFO mode the FIFO-error flag is not reported as `dmdma_event_error`
(the FIFO error interrupt is not enabled); in direct mode it still is.

## Lease API (`dmdma_lease.h`)

| Function | Description |
|----------|-------------|
| `dmdma_lease_acquire(controller, stream)` | Reserve a stream (shared pool with `/dev/dmdmaN/M`) |
| `dmdma_lease_release(lease)` | Abort, disable the IRQ, return the stream |
| `dmdma_lease_start(lease, config)` | Validate and start a transfer (direct mode, single transfers) |
| `dmdma_lease_start_ex(lease, config, options)` | Same, with `dmdma_stream_options_t` |
| `dmdma_lease_abort(lease)` / `_is_busy()` / `_get_remaining()` | Transfer control |
| `dmdma_lease_set_callback(lease, callback, user_ptr)` | Completion/error/timeout/abort callback |

### `dmdma_event_t`

```c
Expand Down
11 changes: 11 additions & 0 deletions docs/port-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ completion/half-complete/error reporting are added automatically. Returns
`element_count` is `0` or exceeds the hardware counter width) or
`controller`/`stream` is out of range.

```c
int dmdma_port_stream_start_ex(dmdma_controller_t controller, dmdma_stream_t stream,
const dmdma_transfer_config_t *config,
const dmdma_stream_options_t *options);
```
Same as `_stream_start()` plus FIFO mode/threshold, burst lengths and the
flow controller (`options` already validated by the core; `NULL` means the
`_stream_start()` defaults). On STM32F4/F7: `DMA_SxFCR.DMDIS`/`FTH`,
`DMA_SxCR.PBURST`/`MBURST` (mapped from source/destination by direction)
and `DMA_SxCR.PFCTRL`. `_stream_start()` is `_stream_start_ex(..., NULL)`.

```c
void dmdma_port_stream_stop(dmdma_controller_t controller, dmdma_stream_t stream);
```
Expand Down
12 changes: 12 additions & 0 deletions include/dmdma_lease.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ dmod_dmdma_api(1.0, void, _lease_release, ( dmdma_lease_t lease ) );
*/
dmod_dmdma_api(1.0, int, _lease_start, ( dmdma_lease_t lease, const dmdma_transfer_config_t *config ) );

/**
* Same as dmdma_lease_start() with explicit stream options - FIFO mode and
* threshold, burst lengths and the flow controller (see
* dmdma_stream_options_t). options == NULL behaves exactly like
* dmdma_lease_start(). Invalid combinations (burst without FIFO, a memory
* burst that does not fit the FIFO threshold, peripheral flow control for a
* memory-to-memory or circular transfer, ...) are rejected with -EINVAL
* before the port is touched.
*/
dmod_dmdma_api(1.0, int, _lease_start_ex, ( dmdma_lease_t lease, const dmdma_transfer_config_t *config,
const dmdma_stream_options_t *options ) );

/**
* Abort whatever transfer is in flight; safe to call when idle. Fires the
* lease's callback with dmdma_event_aborted, but only if a transfer was
Expand Down
5 changes: 5 additions & 0 deletions include/dmdma_port.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ dmod_dmdma_port_api(1.0, void, _stream_release, ( dmdma_controller_t controller,

dmod_dmdma_port_api(1.0, int, _stream_start, ( dmdma_controller_t controller, dmdma_stream_t stream,
const dmdma_transfer_config_t *config ) );
/* Like _stream_start(), with the stream options already validated by the
* core (options == NULL means the _stream_start() defaults). */
dmod_dmdma_port_api(1.0, int, _stream_start_ex, ( dmdma_controller_t controller, dmdma_stream_t stream,
const dmdma_transfer_config_t *config,
const dmdma_stream_options_t *options ) );
dmod_dmdma_port_api(1.0, void, _stream_stop, ( dmdma_controller_t controller, dmdma_stream_t stream ) );
dmod_dmdma_port_api(1.0, bool, _stream_is_busy, ( dmdma_controller_t controller, dmdma_stream_t stream ) );
dmod_dmdma_port_api(1.0, size_t, _stream_get_remaining, ( dmdma_controller_t controller, dmdma_stream_t stream ) );
Expand Down
62 changes: 62 additions & 0 deletions include/dmdma_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,68 @@ typedef struct
* not reset it) rather than a per-element deadline. */
} dmdma_transfer_config_t;

/**
* @brief Who decides when a peripheral transfer ends
*
* With dmdma_flow_controller_peripheral the peripheral signals the last
* data item itself (e.g. SDIO/SDMMC, whose data length is programmed in the
* peripheral) and element_count is ignored by the hardware (STM32: NDTR is
* forced to 0xFFFF, RM0090/RM0385 "Flow controller"). Only valid for
* memory<->peripheral transfers and never together with circular mode.
*/
typedef enum
{
dmdma_flow_controller_dma = 0, /**< DMA counts element_count (default) */
dmdma_flow_controller_peripheral, /**< The peripheral ends the transfer */
} dmdma_flow_controller_t;

/**
* @brief Burst length (beats per request) on one side of a transfer
*
* Anything but single requires FIFO mode (fifo_threshold != direct).
*/
typedef enum
{
dmdma_burst_single = 0, /**< One element per request (default) */
dmdma_burst_4, /**< Incremental burst of 4 beats */
dmdma_burst_8, /**< Incremental burst of 8 beats */
dmdma_burst_16, /**< Incremental burst of 16 beats */
} dmdma_burst_t;

/**
* @brief FIFO usage of a stream
*
* dmdma_fifo_direct disables the FIFO (direct mode, source and destination
* widths must match). The other values enable the 16-byte FIFO and set the
* level that triggers a memory access: the memory-side burst (beats x
* memory element width) must fit the threshold an integral number of times
* (RM0090/RM0385 "FIFO threshold configurations").
*/
typedef enum
{
dmdma_fifo_direct = 0, /**< Direct mode, no FIFO (default) */
dmdma_fifo_quarter, /**< FIFO threshold 1/4 (4 bytes) */
dmdma_fifo_half, /**< FIFO threshold 1/2 (8 bytes) */
dmdma_fifo_three_quarters, /**< FIFO threshold 3/4 (12 bytes) */
dmdma_fifo_full, /**< FIFO threshold full (16 bytes) */
} dmdma_fifo_threshold_t;

/**
* @brief Optional stream options, see dmdma_lease_start_ex()
*
* A zero-initialized structure (or passing NULL) selects the behavior of
* dmdma_lease_start(): direct mode, single transfers, DMA flow control.
* Burst lengths are given per side of the transfer (source/destination),
* the port maps them to its peripheral/memory registers by direction.
*/
typedef struct
{
dmdma_flow_controller_t flow_controller; /**< Who ends a peripheral transfer */
dmdma_fifo_threshold_t fifo_threshold; /**< Direct mode or FIFO threshold */
dmdma_burst_t source_burst; /**< Burst length at the source */
dmdma_burst_t destination_burst; /**< Burst length at the destination */
} dmdma_stream_options_t;

/*
* Which address is "the peripheral one" depends on direction, exactly as it
* does in the underlying hardware register pair (PAR/M0AR on STM32):
Expand Down
79 changes: 76 additions & 3 deletions src/dmdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,63 @@ static int validate_transfer_config(dmdma_controller_t controller, const dmdma_t
return 0;
}

static uint32_t burst_beats(dmdma_burst_t burst)
{
switch (burst)
{
case dmdma_burst_4: return 4U;
case dmdma_burst_8: return 8U;
case dmdma_burst_16: return 16U;
default: return 1U;
}
}

/* FIFO bytes that trigger a memory access, 0 for direct mode. */
static uint32_t fifo_threshold_bytes(dmdma_fifo_threshold_t threshold)
{
return (threshold == dmdma_fifo_direct) ? 0U : 4U * (uint32_t)threshold;
}

/* RM0090/RM0385 "FIFO threshold configurations": the memory-side burst has
* to fit the threshold level an integral number of times. */
static int validate_memory_burst(uint32_t threshold, dmdma_burst_t burst, dmdma_data_width_t width)
{
uint32_t bytes = burst_beats(burst) * (uint32_t)width;
if (burst == dmdma_burst_single)
{
return 0;
}
return (bytes <= threshold && (threshold % bytes) == 0U) ? 0 : -EINVAL;
}

/* Checks dmdma_stream_options_t on top of validate_transfer_config(). */
static int validate_stream_options(const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options)
{
if (options->flow_controller > dmdma_flow_controller_peripheral ||
options->fifo_threshold > dmdma_fifo_full ||
options->source_burst > dmdma_burst_16 || options->destination_burst > dmdma_burst_16)
{
return -EINVAL;
}
if (options->flow_controller == dmdma_flow_controller_peripheral &&
(config->direction == dmdma_direction_memory_to_memory || config->circular))
{
return -EINVAL;
}
uint32_t threshold = fifo_threshold_bytes(options->fifo_threshold);
if (threshold == 0U)
{
/* Direct mode: no bursts, both sides move the same element size. */
bool bursts = options->source_burst != dmdma_burst_single ||
options->destination_burst != dmdma_burst_single;
return (bursts || config->source_width != config->destination_width) ? -EINVAL : 0;
}
bool memory_is_source = (config->direction == dmdma_direction_memory_to_peripheral);
return memory_is_source
? validate_memory_burst(threshold, options->source_burst, config->source_width)
: validate_memory_burst(threshold, options->destination_burst, config->destination_width);
}

/* [dmdma] is the fast path; anything else falls back to whichever section
* comes first in the file, so e.g. [dma1]/[dma2]-named sections work too. */
static const char *detect_config_section(dmini_context_t config)
Expand Down Expand Up @@ -764,8 +821,8 @@ dmod_dmdma_api_declaration(1.0, void, _lease_release, ( dmdma_lease_t lease ))
slot->cb.lease.user_ptr = NULL;
}

dmod_dmdma_api_declaration(1.0, int, _lease_start,
( dmdma_lease_t lease, const dmdma_transfer_config_t *config ))
static int lease_start(dmdma_lease_t lease, const dmdma_transfer_config_t *config,
const dmdma_stream_options_t *options)
{
dmdma_stream_slot_t *slot = slot_from_lease(lease);
if (slot == NULL)
Expand All @@ -777,21 +834,37 @@ dmod_dmdma_api_declaration(1.0, int, _lease_start,
dmdma_controller_t controller = context->config.controller;

int rc = validate_transfer_config(controller, config);
if (rc == 0 && options != NULL)
{
rc = validate_stream_options(config, options);
}
if (rc != 0)
{
DMOD_LOG_ERROR("dmdma: rejecting lease transfer config on controller %u (rc=%d)\n", (unsigned)controller, rc);
return rc;
}

slot->circular = config->circular;
rc = dmdma_port_stream_start(controller, slot->stream, config);
rc = dmdma_port_stream_start_ex(controller, slot->stream, config, options);
if (rc == 0)
{
rc = arm_timeout(slot, config->timeout_ms);
}
return rc;
}

dmod_dmdma_api_declaration(1.0, int, _lease_start,
( dmdma_lease_t lease, const dmdma_transfer_config_t *config ))
{
return lease_start(lease, config, NULL);
}

dmod_dmdma_api_declaration(1.0, int, _lease_start_ex,
( dmdma_lease_t lease, const dmdma_transfer_config_t *config, const dmdma_stream_options_t *options ))
{
return lease_start(lease, config, options);
}

dmod_dmdma_api_declaration(1.0, void, _lease_abort, ( dmdma_lease_t lease ))
{
dmdma_stream_slot_t *slot = slot_from_lease(lease);
Expand Down
Loading
Loading