I have previously written an audio-driven codec analysis. At that time, I ignored the most basic concepts. To understand something, you must first understand what it serves, and then better analyze its workflow. So here we mention:
Codec: audio chip control, such as mute, turn on (off) ADC (DAC), set ADC (DAC) gain, and earphone mode detection.
I2S: digital audio interface, used to transmit raw data of digital audio streams between the CPU and codec. When there is a playback or record operation, snd_soc_dai_ops.prepare () will be called to start the I2S bus.
PCM: I don't know why this module name is used. It actually defines DMA operations and is used to transmit audio data to the I2S controller FIFO through DMA.
Audio Data Flow Direction:
| DMA | I2S/PCM/ac97 |
Ram --------> i2scontrollerfifo -----------------> codec ----> SPK/headset
PCM module initialization
struct snd_soc_platform s3c_soc_platform = { .name = "s3c-pcm-audio", .pcm_ops = &s3c_pcm_ops, .pcm_new = s3c_pcm_new, .pcm_free = s3c_pcm_free_dma_buffers, .suspend = s3c_pcm_suspend, .resume = s3c_pcm_resume,};
Call snd_soc_register_platform () to register an snd_soc_platform struct with ALSA core.
The member pcm_new needs to call dma_alloc_writecombine () to allocate a write-combining memory space to the DMA, and save the information about the buffer to substream-> dma_buffer, which is equivalent to the constructor. Pcm_free is the opposite. These member functions are fairly simple. You can understand the process by looking at the code.
Snd_pcm_ops
Next, let's take a look at the snd_pcm_ops struct. The implementation of the operator function set of this struct is the main body of this module.
struct snd_pcm_ops { int (*open)(struct snd_pcm_substream *substream); int (*close)(struct snd_pcm_substream *substream); int (*ioctl)(struct snd_pcm_substream * substream, unsigned int cmd, void *arg); int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); int (*hw_free)(struct snd_pcm_substream *substream); int (*prepare)(struct snd_pcm_substream *substream); int (*trigger)(struct snd_pcm_substream *substream, int cmd); snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream); int (*copy)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void __user *buf, snd_pcm_uframes_t count); int (*silence)(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count); struct page *(*page)(struct snd_pcm_substream *substream, unsigned long offset); int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma); int (*ack)(struct snd_pcm_substream *substream);};
We mainly implement open, close, hw_params, hw_free, prepare, and trigger interfaces.
Open
The open function sets the supported transmission mode, data format, number of channels, period, and other parameters for the PCM module, and allocates the corresponding DMA channel for playback/capture stream. The general implementation is as follows:
Static int initi_pcm_open (struct snd_pcm_substream * substream) {struct snd_soc_pcm_runtime * RTD = substream-> private_data; struct snd_soc_dai * cpu_dai = RTD-> Dai-> cpu_dai; struct snd_pcm_runtime * runtime = substream-> runtime; struct audio_stream_a * s = runtime-> private_data; int ret; If (! Cpu_dai-> active) {audio_dma_request (& S [0], audio_dma_callback); // allocate DMA audio_dma_request (& S [1], audio_dma_callback) to the playback stream ); // assign DMA to the capture stream} // set the runtime hardware parameter snd_soc_set_runtime_hwparams (substream, & amp; S _ pcm_hardware ); /* Ensure that buffer size is a multiple of period size */ret = snd_pcm_hw_constraint_integer (runtime, sndrv_pcm_hw_param_periods); return ret ;}
The hardware parameters should be defined according to the Data Manual of the chip, for example:
static const struct snd_pcm_hardware s3c_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 128*1024, .period_bytes_min = PAGE_SIZE, .period_bytes_max = PAGE_SIZE*2, .periods_min = 2, .periods_max = 128, .fifo_size = 32,};
The concept of peroid is described as follows: the "period" is a term that corresponds to a fragment in the OSS world. the period defines the size at which a PCM interrupt is generated. the concept of peroid is very important. You are advised to go To the Alsa official website to find details.
Upper ALSA lib can obtain these parameters through interfaces, such as snd_pcm_hw_params_get_buffer_size_max () to obtain buffer_bytes_max.
DMA interrupt handling
Pay attention to the audio_dma_request (& S [0], audio_dma_callback); audio_dma_callback in the open function. This is the DMA interrupt function, which exists in the form of callback, in fact, the bottom layer of DMA is still in the form of static irqreturn_t dma_irq_handler (int irq, void * dev_id), which calls callback in dma_irq_handler () of DMA interrupt processing. These are related to the DMA implementation of a specific hardware platform. If there is no similar mechanism, the interruption should be implemented in the PCM module.
/** This is called when dma irq occurs at the end of each transmited block */static void audio_dma_callback (void * Data) {struct audio_stream_a * s = data; /** if we are getting a callback for an active stream then we inform * The PCM middle layer we 've finished a period */If (S-> active) snd_pcm_period_elapsed (S-> stream); spin_lock (& S-> dma_lock); If (S-> periods> 0) S-> periods --; audio_process_dma (s ); // start the spin_unlock (& S-> dma_lock) using DMA );}
Hw_params
The hw_params function is substream (each time a playback or capture is opened, ALSA core generates a corresponding substream) to set the source (destination) Address of the DMA and the size of the DMA buffer.
static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params){ struct snd_pcm_runtime *runtime = substream->runtime; int err = 0; snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); return err;}
Hw_free is the opposite operation of hw_params. Call snd_pcm_set_runtime_buffer (substream, null.
Note: The dma_buffer in the Code is a DMA buffer, which is defined by four fields: dma_area, dma_addr, dma_bytes, and dma_private. Dma_area indicates the Logical Address of the buffer, dma_addr indicates the physical address of the buffer, dma_bytes indicates the size of the buffer, and dma_private indicates the DMA Management of ALSA. Dma_buffer is initialized in pcm_new (); of course, you can also put the work of allocating the DMA buffer in this part for implementation, but considering the reduction of fragments, we still use the maximum size (buffer_bytes_max) in pcm_new).
Prepare
This function is called when PCM is "ready. The DMA transmission parameters are set here based on channels, buffer_bytes, and so on, which is related to the specific hardware platform. Note: each time you call snd_pcm_prepare (), the prepare function is called.
Trigger
The trigger function is called when PCM is started, stopped, or paused.
Static int initi_pcm_trigger (struct snd_pcm_substream * substream, int cmd) {struct runtime_data * prtd = substream-> runtime-> private_data; int ret = 0; spin_lock (& prtd-> lock ); switch (CMD) {Case sndrv_pcm_trigger_start: Case sndrv_pcm_trigger_resume: case when: prtd-> state | = st_running; dma_ctrl (prtd-> Params-> channel, dmaop_start ); // enable break for DMA; Case sndrv_pcm_trigger_sto P: Case sndrv_pcm_trigger_suspend: Case sndrv_pcm_trigger_pause_push: prtd-> State & = ~ St_running; dma_ctrl (prtd-> Params-> channel, dmaop_stop); // DMA stop break; default: ret =-einval; break;} spin_unlock (& prtd-> lock ); return ret ;}
The operations in the trigger function should be atomic. Do not sleep when calling these operations. The trigger function should be as small as possible, or even trigger DMA.
Pointer
Static snd_pcm_uframes_t wmt_pcm_pointer (struct snd_pcm_substream * substream)
The PCM intermediate layer calls this function to obtain the buffer location. Generally, snd_pcm_period_elapsed () is called in the interrupt function or called when the PCM intermediate layer updates the buffer. Then, the PCM middle layer updates the pointer position and computing buffer available space to wake up the waiting threads. This function is also atomic.
Snd_pcm_runtime
We will notice that all OPS member functions need to obtain an snd_pcm_runtime struct pointer, which can be obtained through substream-> runtime. Snd_pcm_runtime is the information of PCM runtime. When a PCM sub-stream is opened, the instance will be allocated to the sub-stream during the PCM operation. It has a lot of information: hw_params and sw_params configuration copy, buffer pointer, MMAP record, spin lock, etc. Snd_pcm_runtime is read-only for driver operator set functions. Only the PCM middle layer can change or update the information.