cAVS HD/A DMA Driver

Probing

A basic initialization of basic data structures is performed. No piece of HD/A HW is touched at this point.

Configuration

HD/A DMA works with a single continuous circular buffer only. Therefore, the SGLs are verified if they are of the same size and point to a continuous memory space.

The total buffer size (period size x number of periods) must be a multiple of HD/A DMA burst size (32 bytes).

The initial DMA HW buffer setup takes place.

Setting up Callback

A client (a host/dai component for instance) registers a callback by calling dma_set_cb() which is notified upon completion of the transfer of each period of data.

The size of the period is specified by SGL elements passed to dma_set_config().

Starting the Device

The device is registered in the PM platform driver to ensure that the DMI L1 is handled properly.

Note

It is not required when the PM call is made by the device driver, but when the call is moved to the systick handler, the PM platform driver must know if any active DMA devices are registered.

box "Host" #ffffff
    participant "Host\nDriver" as drv
end box
box "HD/A HW"
	participant hda_sw
	participant hda_fw
end box
box "DSP" #ffffff
    participant host
    participant hda_dma
end box

note over hda_sw, hda_fw: "DMA in RESET state"

-> drv : Create Stream
    drv -> host : new()
        host -> hda_dma : dma_get(dmac_id)
            host <-- hda_dma : dmac

        drv <-- host
    <-- drv

-> drv : Stream Params
    drv -> hda_sw : setup DMA (format & BDL)

    drv -> host : params()
        host -> hda_dma : dma_channel_get(dmac, params.stream_tag)
            host <-- hda_dma : chan

        host -> hda_dma : dma_set_config(dmac, chan, config)
            hda_fw <- hda_dma : DGBBA, DGBS
            hda_fw <- hda_dma : DGMBS := buffer size [host dma]
            hda_fw <- hda_dma : GEN := 0
            hda_fw <- hda_dma : SCS := 1 [bit_depth != 32]
            hda_fw <- hda_dma : FWCB : = 1
            hda_fw <- hda_dma : FIFORDY := 0
            host <-- hda_dma

        drv <-- host
    <-- drv

-> drv : RUN
    drv -> hda_sw : RUN := 1

    drv -> host : trigger(COMP_TRIGGER_START)
        host -> hda_dma : dma_start()
            hda_fw <- hda_dma : GEN := 1
            hda_fw <- hda_dma : FIFORDY := 1
            host <-- hda_dma
        drv <-- host
    <-- drv

Figure 26 HD/A DMA Device Start Flow

Stopping the Device

HW reset is programmed by setting GEN to 0. DSP confirms GBUSY is 0; otherwise, an exception is reported to the host.

box "Host" #ffffff
    participant "Host\nDriver" as drv
end box
box "HD/A HW"
	participant hda_sw
	participant hda_fw
end box
box "DSP" #ffffff
    participant host
    participant hda_dma
end box

note over hda_sw, hda_fw: "DMA in RUNNING state"

-> drv : Stop?

    drv -> host : COMP_TRIGGER_PAUSE ?
            note right : No more FPI touching at this point
        drv <-- host

    drv -> hda_sw : RUN := 0

    drv -> host : COMP_TRIGGER_STOP
        host -> hda_dma : dma_stop()
            hda_fw <- hda_dma : GEN := 0
            hda_fw <- hda_dma : FIFORDY := 0
            host <-- hda_dma
        drv <-- host

    drv -> hda_sw : Flush DMA,\nset SRST (stream reset)

Figure 27 HD/A DMA Device Stop Flow

Transferring Data

Transmission is started on the DSP side after the dma_start() is called as GEN is set to 1 there.

Interrupts

Segment completion interrupts are unavailable; therefore, the DSP has to calculate the amount of space/data available in the buffer manually by reading HD/A register values.

Any blocking polling must be done for as short a time as possible to release the CPU for other tasks. The HD/A driver uses the system work queue API to check for IO completion in the context of timer callbacks deferred to a point in time when the IO operation is expected to finish.

Power Management

The driver prevents the DMI from entering L1 at the end of each data copy request for a host HD/A DMA to secure the transfer operation.

Cyclic vs. Non-cyclic Mode of Work

Four types of HD/A DMAs exist:

  • Host Output DMA - host memory to DSP memory

  • Host Input DMA - DSP memory to host memory

  • Link Output DMA - DSP memory to peripheral device memory

  • Link Input DMA - peripheral device memory to DSP memory

Host DMAs work in a non-cyclic mode in SOF, i.e. the transfer of a full period is scheduled on demand each time and completes very quickly.

Link DMAs work in cyclic mode. In the case of HD/A DMA, DMA pointers are updated in real-time with a small step.

Host Output DMA (On Demand Mode)

Host Output DMA provides input data for the DSP on a playback path. In the beginning, once the DMA is started, the host fills up the entire buffer with data (the buffer size is typically set to two periods of data). Subsequent transfers are requested by the DSP by advancing its read pointer, making space for the next transfer available to the host side. It takes some time for the initial transfer to complete (buffer full is signaled), so the DSP should not expect the data to be available “instantly” after the DMA is started. It should not wait in blocking mode for “buffer full” either. However, the second copy operation run by the pre-loader task presents a good opportunity to eventually “complete” the first transfer and reliably commit the data for further processing by the pipeline.

actor drv as "Host\nDriver"
participant ppl as "pipeline"
participant host as "host\ncomponent"
participant hda_dma
participant hda_hw as "HD/A HW"

== Start Trigger ==

drv -> ppl : <<IPC>> trigger (START)
   activate ppl

   ppl -> host : trigger (START)
      activate host

      host -> hda_dma : dma_start()
         activate hda_dma
      host <-- hda_dma
      deactivate hda_dma

      host -> hda_dma : copy(flags = PRELOAD)
         activate hda_dma
         note right: Do not expect Buffer Full yet.
         hda_dma -> hda_dma : state += PRELOAD
         hda_dma -> hda_dma : preload()
            activate hda_dma
            note right : First non-blocking BF test
         deactivate hda_dma
         hda_dma -> hda_dma : state += BF_WAIT
      host <-- hda_dma
      deactivate hda_dma
   ppl <-- host
   deactivate host

   ppl -> ppl : schedule_copy_idle()
      activate ppl
      note right: Scheduler got a ppl task (pre-loader)\n to run in idle
   deactivate ppl
drv <-- ppl
deactivate ppl

== Pre-load ==

-> ppl : pipeline_task
   activate ppl
   ppl -> ppl : pipeline_copy
      activate ppl
      ppl -> host : copy()
         activate host
         host -> hda_dma : copy()
            activate hda_dma
            hda_dma -> hda_dma : preload()
               activate hda_dma
               note right : Blocking BF wait this time
               host <- hda_dma : callback() /for each period/
                  activate host
                  host -> host : comp_update_buffer_produce()
               host --> hda_dma
               deactivate host
               hda_dma -> hda_dma : clear state flags
note right: Switching to normal on demand mode.\n\
Rptr (FPI) advanced on the next copy()\n\
Once the first period is processed.
            deactivate hda_dma
         host <-- hda_dma
         deactivate hda_dma
      ppl <-- host
      deactivate host

Figure 28 Host output startup sequence

Limitations

Passthrough pipelines (host-dai) with a period size unaligned to HD/A DMA burst size (32 bytes) cannot work with 2-periods shared buffer configured. If the DSP moves the read pointer by unaligned size of the period, the tail (period % burst size) is not transferred until the next pointer move.