1. the role of the platform driver in ASOC has been described in the previous chapters. ASOC is divided into three parts: machine, platform, and CODEC. The main role of the platform driver is to manage audio data, finally, the audio data is transmitted to codec through the digital audio interface (DAI) of the CPU for processing, and the audio signal of the headset or speaker is output by codec. In specific implementation, ASOC divides the platform driver into two parts: snd_soc_platform_driver and snd_soc_dai_driver. The platform_driver manages audio data and transfers audio data to the CPU through DMA or other operations.
In Dai, dai_driver mainly configures the Dai parameter on the CPU side, and also interacts with snd_soc_platform_driver with necessary DMA parameters.
/*************************************** **************************************** **********************/
Statement: the content of this blog is created at http://blog.csdn.net/droidphone. please refer to it for help. Thank you!
/*************************************** **************************************** * *********************/2. generally, ASOC registers snd_soc_platform_driver as a system's platform_driver. Do not be confused by the two similar terms. The former is only for the ASOC subsystem, the latter is a device driver model from Linux. What we need to do is:
- Defines an instance with the snd_soc_platform_driver structure;
- In the probe callback of platform_driver, use the asoc api: snd_soc_register_platform () to register the instance defined above;
- Implement various callback functions in snd_soc_platform_driver;
Take/sound/soc/Samsung/DMA. c In kernel3.3 as an example:
static struct snd_soc_platform_driver samsung_asoc_platform = {.ops= &dma_ops,.pcm_new= dma_new,.pcm_free= dma_free_dma_buffers,};static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev){return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);}static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev){snd_soc_unregister_platform(&pdev->dev);return 0;}static struct platform_driver asoc_dma_driver = {.driver = {.name = "samsung-audio",.owner = THIS_MODULE,},.probe = samsung_asoc_platform_probe,.remove = __devexit_p(samsung_asoc_platform_remove),};module_platform_driver(asoc_dma_driver);
Snd_soc_register_platform ()This function is used to register an snd_soc_platform. It can be used by the machine driver only after registration. Its Code clearly expresses its implementation process:
- Apply for memory for the snd_soc_platform instance;
- Obtain its name from platform_device for matching the machine driver;
- Initialize the snd_soc_platform field;
- Connect the snd_soc_platform instance to the global linked list platform_list;
- Call snd_soc_instantiate_cards to trigger matching of the sound card machine, platform, codec, and Dai;
3. the registration of the snd_soc_dai driver Driver of the CPU usually corresponds to one or several I2S/PCM interfaces of the CPU. Like snd_soc_platform, the Dai driver is also implemented as a platform driver, to implement a Dai driver, perform the following steps:
- Defines an instance with the snd_soc_dai_driver structure;
- Register the snd_soc_dai instance through API: snd_soc_register_dai or snd_soc_register_dais in the probe callback of the corresponding platform_driver;
- Implement probe, suspend, and other callbacks in the snd_soc_dai_driver structure;
- Implement the callback function in the snd_soc_dai_driver field in the snd_soc_dai_ops structure;
Snd_soc_register_daiThis function has been introduced in the previous article about the codec driver. For details, refer to: Linux
ALSA sound card driver 7: Codec in the ASOC architecture.
Snd_soc_daiThis structure is obtained through dynamic memory application in the snd_soc_register_dai function. Several important fields are briefly introduced:
- The driver points to the associated snd_soc_dai_driver structure, which is passed in by parameters during registration;
- Playback_dma_data is used to save the DMA information of the stream played by the Dai, such as the target address of the DMA, the size of the DMA transfer unit, and the channel number;
- Capture_dma_data is the same as above, used for recording stream;
- Platform points to the associated snd_soc_platform structure;
Snd_soc_dai_driverThis structure needs to be defined based on different SOC chips. The keyword segment is described as follows:
- The probe and remove callback functions are called when the sound card is loaded and detached;
- Suspend and resume power management callback functions;
- Ops points to the snd_soc_dai_ops structure, which is used to configure and control the Dai;
- Playback snd_soc_pcm_stream structure, which indicates the number of channels, bit rate, and data format supported by the Dai;
- The capture snd_soc_pcm_stream structure is used to indicate the number of channels, bit rates, and data formats supported by the Dai;
4. ops field in snd_soc_dai_driver
The OPS field points to a snd_soc_dai_ops structure, which is actually a collection of callback functions. The configuration and control of DAI are almost implemented through these callback functions, these callback functions can basically be divided into three categories, and the driver can implement one of them according to the actual situation:
The clock configuration function is usually called by the machine DRIVER:
- Set_sysclk: set the main clock of Dai;
- Set_pll: Set the PLL parameters;
- Set_clkdiv: set the frequency division coefficient;
- The format configuration function of Dai is usually called by the machine DRIVER:
- Set_fmt: sets the Dai format;
- Set_tdm_slot if Dai supports time-division multiplexing, it is used to set the slot of time-division multiplexing;
- Set_channel_map time division multiplexing ing settings;
- Set_tristate: sets the status of the Dai pin. This callback is required when the same pin is used in parallel with other Dai;
The standard snd_soc_ops callback is usually called by SoC-core during PCM operations:
- Startup
- Shutdown
- Hw_params
- Hw_free
- Prepare
- Trigger
Anti-Pop: The pop sound is called by SoC-core:
The following APIs are usually used by the machine driver. The machine driver uses these Apis in the hw_params callback in the snd_pcm_ops field:
- Snd_soc_dai_set_fmt () will actually call the set_fmt callback in snd_soc_dai_ops or codec driver;
- Snd_soc_dai_set_pll () will actually call the set_pll callback in snd_soc_dai_ops or codec driver;
- Snd_soc_dai_set_sysclk () will actually call the set_sysclk callback in snd_soc_dai_ops or codec driver;
- Snd_soc_dai_set_clkdiv () actually calls the set_clkdiv callback in snd_soc_dai_ops or codec driver;
The second FMT parameter of snd_soc_dai_set_fmt (struct snd_soc_dai * DAI, unsigned int FMT) is described here. Currently, ASOC only uses its 16-bit low, we have also defined some macros for it to facilitate our use:
Bit 0-3 is used to set the Interface format:
#define SND_SOC_DAIFMT_I2S1 /* I2S mode */#define SND_SOC_DAIFMT_RIGHT_J2 /* Right Justified mode */#define SND_SOC_DAIFMT_LEFT_J3 /* Left Justified mode */#define SND_SOC_DAIFMT_DSP_A4 /* L data MSB after FRM LRC */#define SND_SOC_DAIFMT_DSP_B5 /* L data MSB during FRM LRC */#define SND_SOC_DAIFMT_AC976 /* AC97 */#define SND_SOC_DAIFMT_PDM7 /* Pulse density modulation */
Bit 4-7 is used to set the switch feature of the interface clock:
#define SND_SOC_DAIFMT_CONT(1 << 4) /* continuous clock */#define SND_SOC_DAIFMT_GATED(2 << 4) /* clock is gated */
Bit 8-11 is used to set the phase of the interface clock:
#define SND_SOC_DAIFMT_NB_NF(1 << 8) /* normal bit clock + frame */#define SND_SOC_DAIFMT_NB_IF(2 << 8) /* normal BCLK + inv FRM */#define SND_SOC_DAIFMT_IB_NF(3 << 8) /* invert BCLK + nor FRM */#define SND_SOC_DAIFMT_IB_IF(4 << 8) /* invert BCLK + FRM */
Bit 12-15 is used to set the interface master/Slave format:
#define SND_SOC_DAIFMT_CBM_CFM(1 << 12) /* codec clk & FRM master */#define SND_SOC_DAIFMT_CBS_CFM(2 << 12) /* codec clk slave & FRM master */#define SND_SOC_DAIFMT_CBM_CFS(3 << 12) /* codec clk master & frame slave */#define SND_SOC_DAIFMT_CBS_CFS(4 << 12) /* codec clk & FRM slave */
5. ops field in snd_soc_platform_driver
This ops field is a snd_pcm_ops structure. The implementation of each callback function in this structure is the main work of the SOC platform driver. They basically involve DMA operations and DMA buffer management. The following describes several important callback functions:
Ops. Open
When an application opens a PCM device, this function is called. Generally, this function uses snd_soc_set_runtime_hwparams () to set the hw_params fields in the snd_pcm_runtime structure of substream, apply for a private structure for the private_data field of snd_pcm_runtime to save the DMA parameters of the platform.
Ops. hw_params
In the hw_params phase of the driver, this function will be called. Generally, this function obtains the corresponding DMA parameter of Dai through the snd_soc_dai_get_dma_data function. The obtained parameters are generally stored in the private_data field of the snd_pcm_runtime structure. Then, use the snd_pcm_set_runtime_buffer function to set parameters such as the address and size of the DMA buffer in the snd_pcm_runtime structure. It should be noted that the callback may be called multiple times. Be careful when implementing the callback.
Ops. Prepare
This function is called before data transfer is officially started. This function usually completes necessary preparations for DMA operations.
Ops. Trigger
This function is called when data transfer starts, stops, recovers, and stops.
Ops. pointer
This function returns the current location of the transmitted data.
6. DMA Operation of audio data
The main function of the SOC-platform driver is to transmit audio data. In most cases, audio data is transmitted through DMA.
6.1. Apply for DMA Buffer
Due to the special nature of DMA, DMA buffer is a special memory. For example, some platforms stipulate that only memory within a certain address range can be used for DMA operations, most embedded platforms require that the physical address of the DMA memory be continuous to facilitate access to the memory by the DMA controller. In the ASOC architecture, the DMA buffer information is stored in the snd_dma_buffer * Buf field of the snd_pcm_substream structure. Its definition is as follows:
struct snd_dma_buffer {struct snd_dma_device dev;/* device type */unsigned char *area;/* virtual pointer */dma_addr_t addr;/* physical address */size_t bytes;/* buffer size in bytes */void *private_data;/* private for allocator; don't touch */};
So where has the snd_dam_buffer structure been initialized and assigned? The answer is in the pcm_new callback function of snd_soc_platform_driver. Take/sound/soc/Samsung/DMA. C as an example:
static struct snd_soc_platform_driver samsung_asoc_platform = {.ops= &dma_ops,.pcm_new= dma_new,.pcm_free= dma_free_dma_buffers,};static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev){return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);}
The pcm_new field points to the dma_new function. The dma_new function calls the preallocate_dma_buffer function for playback and capture respectively. Let's look at the implementation of the preallocate_dma_buffer function:
static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream){struct snd_pcm_substream *substream = pcm->streams[stream].substream;struct snd_dma_buffer *buf = &substream->dma_buffer;size_t size = dma_hardware.buffer_bytes_max;pr_debug("Entered %s\n", __func__);buf->dev.type = SNDRV_DMA_TYPE_DEV;buf->dev.dev = pcm->card->dev;buf->private_data = NULL;buf->area = dma_alloc_writecombine(pcm->card->dev, size, &buf->addr, GFP_KERNEL);if (!buf->area)return -ENOMEM;buf->bytes = size;return 0;}
This function first obtains the buffer size defined in advance, allocates the DMA memory through the dma_alloc_weitecombine function, and then completes the initialization assignment of substream-> dma_buffer. The above pcm_new callback will be called at the sound card establishment stage. For details about the call process, refer to figure 3.1 in the machine in the ASOC architecture of Linux alsas sound card driver 6.
In the hw_params stage of the Sound Card, OPS> hw_params in the snd_soc_platform_driver structure will be called. In this callback, Apis: snd_pcm_set_runtime_buffer () are usually used () copy the value of substream-> dma_buffer to the related field of substream-> Runtime (. dma_area ,. dma_addr ,. dma_bytes), so that the address and size information can be obtained through substream-> runtime.
After the DMA buffer is obtained, the source address of the DMA Operation is obtained. Where is the destination address? In fact, the destination address is of course in DAI, that is, In the playback_dma_data and capture_dma_data fields of the snd_soc_dai structure described earlier. The values of these two fields are also in the hw_params stage, the OPS-> hw_params callback in the snd_soc_dai_driver structure is set using API: snd_soc_dai_set_dma_data. Next, the OPS-> hw_params callback in the snd_soc_platform_driver structure uses API: snd_soc_dai_get_dma_data to obtain the DMA information of the Dai, including the target address of the DMA. The DMA information is usually stored in substream-> runtime-> private_data, so that the information can be obtained at any time throughout the substream lifecycle, to complete the DMA configuration and operations.
6.2 When the DMA buffer manages playback, the application continuously writes the audio data into the DMA buffer, and the DMA Operation of the corresponding platform keeps pulling data from the buffer, sent to codec via Dai. The opposite is true during recording. codec continuously sends the audio data converted by A/D to the DMA buffer through Dai, and the application continuously reads the audio data from the buffer. Figure 6.2.1 The Ring buffer ring buffer is suitable for buffer management in this scenario. Ideally, the buffer with a count size has a read pointer and a write pointer, we expect them to be able to move in a closed loop, but the actual situation is true: the buffer is usually a continuous address, which has two boundaries: start and end, A judgment must be made before each movement. When the pointer moves to the end, it must be artificially set to the starting position. In practical applications, we usually virtualize the buffer with the size of count into a logical buffer with the size of N * count, which is equivalent to after the circle in the ideal state is round n circles, then draw the total distance to a straight line, and each circle corresponds to a section in the straight line, because N is relatively large, therefore, in most cases, there will be no transposition of the read and write pointers (if the buffer is not extended, when the pointer reaches the end and returns to the start end, the two pointers will be swapped before and after ). The extended logical buffer zone is relatively easy to determine when the remaining space is computed. ALSA
The driver also uses this method to manage the DMA Buffer: Figure 6.2.2 ALSA Driver Buffer Management in the snd_pcm_runtime structure, four related fields are used to manage the logical Buffer:
- The base address of each ring in the snd_pcm_runtime.hw_ptr_base ring buffer. When the read/write pointer crosses a circle, it moves by buffer size;
- The logical position of snd_pcm_runtime.status-> hw_ptr is equivalent to a read pointer during playback and a write pointer during recording;
- Snd_pcm_runtime.control-> appl_ptr: the logical position of the application. It is equivalent to writing a pointer during playback and reading a pointer during recording;
- The size of the logical buffer after snd_pcm_runtime.boundary expansion, usually (2 ^ N) * size;
Through these fields, we can easily obtain the valid data and remaining space in the buffer, and easily map the current logical location to the real DMA buffer. For example, to obtain the free space of the playback Buffer:
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime){snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;if (avail < 0)avail += runtime->boundary;else if ((snd_pcm_uframes_t) avail >= runtime->boundary)avail -= runtime->boundary;return avail;}
To map to the real buffer location, Just subtract runtime-> hw_ptr_base. The following API is used to update the current positions of these pointers:
int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
Therefore, before using functions such as snd_pcm_playback_avail to obtain the correct information, you must call this API to update the pointer position.
Taking playback as an example, I now know that there are at least three ways to write the DMA Buffer:
- The application calls the snd_pcm_writei and snd_pcm_writen functions of ALSA-lib;
- The application uses IOCTL: sndrv_pcm_ioctl_writei_frames or sndrv_pcm_ioctl_writen_frames;
- The application uses snd_pcm_mmap_begin/snd_pcm_mmap_commit of ALSA-lib;
The above methods write data into the DMA buffer, and then modify the value of runtime-> Control-> appl_ptr.
During playback, a DMA interrupt is usually generated by configuring each period size. The most important task of the interrupt processing function is:
- Update the current location of the DMA hardware. This value is usually stored in runtime-> private_data;
- Call the snd_pcm_period_elapsed function, which will further call the snd_pcm_update_hw_ptr0 function to update the four buffer management fields mentioned above, and then wake up the corresponding waiting process;
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream){struct snd_pcm_runtime *runtime;unsigned long flags;if (PCM_RUNTIME_CHECK(substream))return;runtime = substream->runtime;if (runtime->transfer_ack_begin)runtime->transfer_ack_begin(substream);snd_pcm_stream_lock_irqsave(substream, flags);if (!snd_pcm_running(substream) || snd_pcm_update_hw_ptr0(substream, 1) < 0)goto _end;if (substream->timer_running)snd_timer_interrupt(substream->timer, 1); _end:snd_pcm_stream_unlock_irqrestore(substream, flags);if (runtime->transfer_ack_end)runtime->transfer_ack_end(substream);kill_fasync(&runtime->fasync, SIGIO, POLL_IN);}
If the transfer_ack_begin and transfer_ack_end Callbacks are set, snd_pcm_period_elapsed will also call these two callback functions.
7. Graphic code
Finally, the picture is drawn. both the good and the bad are uploaded for reference. The following figure shows the relationship between several important data structures driven by platform in ASOC:
Figure 7.1 ASOC platform driver
A bunch of private_data is very important, but it is also easy to mix up. The following figure does not help you:
Figure 7.2 private_data