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 52 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 53 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 54 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.