Although ac97 is currently debugging audio, the idea is the same. Transfer to another person's article record
Original article address:
Http://chxxxyg.blog.163.com/blog/static/150281193201033105123937/
# Include <Linux/module. h>
# Include <Linux/init. h>
# Include <Linux/IO. h>
# Include <Linux/platform_device.h>
# Include <Linux/slab. h>
# Include <Linux/dma-mapping.h>
# Include <sound/CORE. h>
# Include <sound/PCM. h>
# Include <sound/pcm_params.h>
# Include <sound/soc. h>
# Include <ASM/DMA. h>
# Include <Mach/hardware. h>
# Include <Mach/DMA. h>
# Include <Mach/audio. h>
# Include "s3c24xx-pcm.h"
# Define s3c24xx_pcm_debug 0
# If s3c24xx_pcm_debug
# Define dbg (X...) printk (kern_debug "s3c24xx-pcm:" X)
# Else
# Define dbg (X ...)
# Endif
// Defines some buffer information. It is used in the function s3c24xx_pcm_open to initialize the struct substream-> runtime-> HW
Static const struct snd_pcm_hardware s3c24xx_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,
. Required o_size = 32,
};
// The Cache Management struct is mounted to runtime> private_data in the function s3c24xx_pcm_open.
// The struct is initialized in the function s3c24xx_pcm_hw_params.
// The managed cache region is allocated in the function s3c24xx_pcm_preallocate_dma_buffer.
Struct s3c24xx_runtime_data {
Spinlock_t lock;
Int state;
Unsigned int dma_loaded; // number of segments inserted into the DMA backup storage chain
Unsigned int dma_limit; // The maximum number of the cache segment.
Unsigned int dma_period; // maximum amount of data stored in each segment of Cache
Dma_addr_t dma_start; // The start address of the cache area.
Dma_addr_t dma_pos; // The first segment address that is not inserted with the DMA standby cache chain
Dma_addr_t dma_end; // The end address of the cache area.
// Structure Params is initialized in the function s3c24xx_pcm_hw_params prtd-> Params = DMA;
Struct s3c24xx_pcm_dma_params * Params;
};
/* S3c24xx_pcm_enqueue
*
* Place a DMA buffer onto the queue for the DMA system
* To handle.
*/
Static void s3c24xx_pcm_enqueue (struct snd_pcm_substream * substream)
{
Struct s3c24xx_runtime_data * prtd = substream-> runtime-> private_data;
Dma_addr_t Pos = prtd-> dma_pos;
Int ret;
Dbg ("entered % s/n", _ FUNC __);
While (prtd-> dma_loaded <prtd-> dma_limit ){
Unsigned long Len = prtd-> dma_period;
Dbg ("dma_loaded: % d/N", prtd-> dma_loaded );
If (Pos + Len)> prtd-> dma_end ){
Len = prtd-> dma_end-Pos;
Dbg (kern_debug "% s: Corrected DMA Len % LD/N ",
_ FUNC __, Len );
}
// Insert the cache into the DMA standby cache chain. The POS must be a physical address, which will be written to the DMA initialization destination or source register.
Ret = s3c2410_dma_enqueue (prtd-> Params-> channel,
Substream, POs, Len );
If (ret = 0 ){
Prtd-> dma_loaded ++;
Pos + = prtd-> dma_period;
If (Pos> = prtd-> dma_end)
Pos = prtd-> dma_start;
} Else
Break;
}
Prtd-> dma_pos = Pos;
}
// When a cache is used up, the function will be called in the interrupt handler.
Static void s3c24xx_audio_buffdone (struct s3c2410_dma_chan * channel,
Void * dev_id, int size,
Enum s3c2410_dma_buffresult result)
{
Struct snd_pcm_substream * substream = dev_id;
Struct s3c24xx_runtime_data * prtd;
Dbg ("entered % s/n", _ FUNC __);
If (result = s3c2410_res_abort | result = s3c2410_res_err)
Return;
Prtd = substream-> runtime-> private_data;
If (substream)
Snd_pcm_period_elapsed (substream );
Spin_lock (& prtd-> lock );
If (prtd-> State & st_running ){
Prtd-> dma_loaded --; // when a cache is used up, insert the cache to the end of the DMA alternate linked list. The entire cache area is a circular cache area.
S3c24xx_pcm_enqueue (substream );
}
Spin_unlock (& prtd-> lock );
}
Static int s3c24xx_pcm_hw_params (struct snd_pcm_substream * substream,
Struct snd_pcm_hw_params * Params)
{
Struct snd_pcm_runtime * runtime = substream-> runtime;
Struct s3c24xx_runtime_data * prtd = runtime-> private_data;
Struct snd_soc_pcm_runtime * RTD = substream-> private_data;
Struct s3c24xx_pcm_dma_params * DMA = RTD-> Dai-> cpu_dai-> dma_data;
// Obtain the buffer size from the structure Params-> intervals
Unsigned long totbytes = params_buffer_bytes (Params );
Int ret = 0;
Dbg ("entered % s/n", _ FUNC __);
/* Return if this is a bufferless transfer e.g.
* Codec <--> BT codec or GSM Modem -- LG fixme */
If (! DMA)
Return 0;
/* This may get called several times by OSS emulation
* With Different Params-HW */
If (prtd-> Params = NULL ){
/* Prepare DMA */
// The structure DMA is s3c24xx_i2s_pcm_stereo_out or s3c24xx_i2s_pcm_stereo_in.
// Define in file s3c24xx-i2s.c
// The structure Params is s3c24xx_runtime_data.
Prtd-> Params = DMA;
Dbg ("Params % P, client % P, channel % d/N", prtd-> Params,
Prtd-> Params-> client, prtd-> Params-> channel );
// Apply for a DMA Channel
Ret = s3c2410_dma_request (prtd-> Params-> channel,
Prtd-> Params-> client, null );
If (Ret <0 ){
Dbg (kern_err "failed to get DMA channel/N ");
Return ret;
}
}
// This function mainly implements Chan-> callback_fn = RTN; that is, it directs Chan-> callback_fn to the function s3c24xx_audio_buffdone.
S3c2410_dma_set_buffdone_fn (prtd-> Params-> channel,
S3c24xx_audio_buffdone );
// Use the allocated cache substream-> dma_buffer to initialize some variables of substream-> Runtime
Snd_pcm_set_runtime_buffer (substream, & substream-> dma_buffer );
Runtime-> dma_bytes = totbytes;
Spin_lock_irq (& prtd-> lock );
Prtd-> dma_loaded = 0;
Prtd-> dma_limit = runtime-> HW. periods_min;
Prtd-> dma_period = params_period_bytes (Params); // obtain the cache size.
Prtd-> dma_start = runtime-> dma_addr;
Prtd-> dma_pos = prtd-> dma_start;
Prtd-> dma_end = prtd-> dma_start + totbytes;
Spin_unlock_irq (& prtd-> lock );
Return 0;
}
Static int s3c24xx_pcm_hw_free (struct snd_pcm_substream * substream)
{
Struct s3c24xx_runtime_data * prtd = substream-> runtime-> private_data;
Dbg ("entered % s/n", _ FUNC __);
/* Todo-Do We Need To ensure DMA flushed */
Snd_pcm_set_runtime_buffer (substream, null );
If (prtd-> Params ){
S3c2410_dma_free (prtd-> Params-> channel, prtd-> Params-> client );
Prtd-> Params = NULL;
}
Return 0;
}
Static int s3c24xx_pcm_prepare (struct snd_pcm_substream * substream)
{
Struct s3c24xx_runtime_data * prtd = substream-> runtime-> private_data;
Int ret = 0;
Dbg ("entered % s/n", _ FUNC __);
/* Return if this is a bufferless transfer e.g.
* Codec <--> BT codec or GSM Modem -- LG fixme */
If (! Prtd-> Params)
Return 0;
/* Channel needs locking ing for mem => device, increment memory ADDR,
* Sync to pclk, half-word transfers to the IIS-FIFO .*/
If (substream-> stream = sndrv_pcm_stream_playback) {// if it is playing
S3c2410_dma_devconfig (prtd-> Params-> channel, // configure the initialization destination register and corresponding control register
S3c2410_dmasrc_mem, s3c2410_disrcc_inc |
S3c2410_disrcc_apb, prtd-> Params-> dma_addr );
S3c2410_dma_config (prtd-> Params-> channel,
Prtd-> Params-> dma_size, // configure the DMA control register and save the configuration value to Chan-> dcon = dcon;
S3c2410_dcon_sync_pclk |
S3c2410_dcon_handshake );
} Else {// recording,
S3c2410_dma_config (prtd-> Params-> channel,
Prtd-> Params-> dma_size, // configure the initialization source register and corresponding control register
S3c2410_dcon_handshake |
S3c2410_dcon_sync_pclk );
S3c2410_dma_devconfig (prtd-> Params-> channel,
S3c2410_dmasrc_hw, 0x3,
Prtd-> Params-> dma_addr );
}
/* Flush the DMA channel */
S3c2410_dma_ctrl (prtd-> Params-> channel, s3c2410_dmaop_flush );
Prtd-> dma_loaded = 0;
Prtd-> dma_pos = prtd-> dma_start;
/* Enqueue DMA buffers */
S3c24xx_pcm_enqueue (substream); // Insert the cache chain into the DMA standby cache chain
Return ret;
}
Static int s3c24xx_pcm_trigger (struct snd_pcm_substream * substream, int cmd)
{
Struct s3c24xx_runtime_data * prtd = substream-> runtime-> private_data;
Int ret = 0;
Dbg ("entered % s/n", _ FUNC __);
Spin_lock (& prtd-> lock );
Switch (CMD ){
Case sndrv_pcm_trigger_start:
Case sndrv_pcm_trigger_resume:
Case sndrv_pcm_trigger_pause_release:
Prtd-> state | = st_running; // load the DMA cache to start DMA data transmission
S3c2410_dma_ctrl (prtd-> Params-> channel, s3c2410_dmaop_start );
S3c2410_dma_ctrl (prtd-> Params-> channel, s3c2410_dmaop_started );
Break;
Case sndrv_pcm_trigger_stop:
Case sndrv_pcm_trigger_suspend:
Case sndrv_pcm_trigger_pause_push:
Prtd-> State & = ~ St_running; // stop DMA Transmission
S3c2410_dma_ctrl (prtd-> Params-> channel, s3c2410_dmaop_stop );
Break;
Default:
Ret =-einval;
Break;
}
Spin_unlock (& prtd-> lock );
Return ret;
}
Static snd_pcm_uframes_t
S3c24xx_pcm_pointer (struct snd_pcm_substream * substream)
{
Struct snd_pcm_runtime * runtime = substream-> runtime;
Struct s3c24xx_runtime_data * prtd = runtime-> private_data;
Unsigned long res;
Dma_addr_t SRC, DST;
Dbg ("entered % s/n", _ FUNC __);
Spin_lock (& prtd-> lock); // read the current destination register and current source register of DMA.
S3c2410_dma_getposition (prtd-> Params-> channel, & SRC, & DST );
If (substream-> stream = sndrv_pcm_stream_capture)
Res = DST-prtd-> dma_start; // calculate the transmitted data
Else
Res = Src-prtd-> dma_start;
Spin_unlock (& prtd-> lock );
Dbg ("pointer % x/N", SRC, DST );
/* We seem to be getting the odd error from the PCM library due
* To out-of-bounds pointers. This is maybe due to the DMA Engine
* Not Having loaded the new values for the channel before being
* Callled... (todo-fix)
*/
If (RES> = snd_pcm_lib_buffer_bytes (substream )){
If (RES = snd_pcm_lib_buffer_bytes (substream ))
Res = 0;
}
Return bytes_to_frames (substream-> runtime, Res); // converts cached data into frames.
}
Static int s3c24xx_pcm_open (struct snd_pcm_substream * substream)
{
Struct snd_pcm_runtime * runtime = substream-> runtime;
Struct s3c24xx_runtime_data * prtd;
Dbg ("entered % s/n", _ FUNC __);
// Use s3c24xx_pcm_hardware to initialize the struct substream-> runtime-> HW
Snd_soc_set_runtime_hwparams (substream, & s3c24xx_pcm_hardware );
// Allocate memory for the structure s3c24xx_runtime_data
Prtd = kzarloc (sizeof (struct s3c24xx_runtime_data), gfp_kernel );
If (prtd = NULL)
Return-enomem;
Spin_lock_init (& prtd-> lock );
Runtime-> private_data = prtd ;//
Return 0;
}
Static int s3c24xx_pcm_close (struct snd_pcm_substream * substream)
{
Struct snd_pcm_runtime * runtime = substream-> runtime;
Struct s3c24xx_runtime_data * prtd = runtime-> private_data;
Dbg ("entered % s/n", _ FUNC __);
If (! Prtd)
Dbg ("s3c24xx_pcm_close called with prtd = NULL/N ");
Kfree (prtd );
Return 0;
}
Static int s3c24xx_pcm_mmap (struct snd_pcm_substream * substream,
Struct vm_area_struct * VMA)
{
Struct snd_pcm_runtime * runtime = substream-> runtime;
Dbg ("entered % s/n", _ FUNC __);
// Associate some user space addresses to the device memory. No matter when the program reads or writes within the specified range, it is actually accessing the device
Return dma_mmap_writecombine (substream-> PCM-> card-> Dev, VMA,
Runtime-> dma_area,
Runtime-> dma_addr,
Runtime-> dma_bytes );
}
// Stream operation function for playing and recording
Static struct snd_pcm_ops s3c24xx_pcm_ops = {
. Open = s3c24xx_pcm_open,
. Close = s3c24xx_pcm_close,
. IOCTL = snd_pcm_lib_ioctl,
. Hw_params = s3c24xx_pcm_hw_params,
. Hw_free = s3c24xx_pcm_hw_free,
. Prepare = s3c24xx_pcm_prepare,
. Trigger = s3c24xx_pcm_trigger,
. Pointer = s3c24xx_pcm_pointer,
. MMAP = s3c24xx_pcm_mmap,
};
// Allocate a memory segment for the data cache area
Static int s3c24xx_pcm_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 = s3c24xx_pcm_hardware.buffer_bytes_max;
Dbg ("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;
}
Static void s3c24xx_pcm_free_dma_buffers (struct snd_pcm * PCM)
{
Struct snd_pcm_substream * substream;
Struct snd_dma_buffer * Buf;
Int stream;
Dbg ("entered % s/n", _ FUNC __);
For (Stream = 0; stream <2; stream ++ ){
Substream = PCM-> streams [stream]. substream;
If (! Substream)
Continue;
Buf = & substream-> dma_buffer;
If (! Buf-> Area)
Continue;
Dma_free_writecombine (PCM-> card-> Dev, Buf-> bytes,
Buf-> area, Buf-> ADDR );
Buf-> area = NULL;
}
}
Static u64 s3c24xx_pcm_dmamask = dma_32bit_mask;
// Allocate the playback and recording cache Areas
Static int s3c24xx_pcm_new (struct snd_card * card,
Struct snd_soc_dai * DAI, struct snd_pcm * PCM)
{
Int ret = 0;
Dbg ("entered % s/n", _ FUNC __);
If (! Card-> Dev-> dma_mask)
Card-> Dev-> dma_mask = & s3c24xx_pcm_dmamask;
If (! Card-> Dev-> coherent_dma_mask)
Card-> Dev-> coherent_dma_mask = 0 xffffffff;
If (Dai-> playback. channels_min ){
Ret = s3c24xx_pcm_preallocate_dma_buffer (PCM,
Sndrv_pcm_stream_playback );
If (RET)
Goto out;
}
If (Dai-> capture. channels_min ){
Ret = s3c24xx_pcm_preallocate_dma_buffer (PCM,
Sndrv_pcm_stream_capture );
If (RET)
Goto out;
}
Out:
Return ret;
}
// PCM instance initialization function and data flow operation function
Struct snd_soc_platform s3c24xx_soc_platform = {
. Name = "s3c24xx-audio ",
. Pcm_ops = & s3c24xx_pcm_ops,
. Pcm_new = s3c24xx_pcm_new,
. Pcm_free = s3c24xx_pcm_free_dma_buffers,
};
Export_symbol_gpl (s3c24xx_soc_platform );
Static int _ init s3c24xx_soc_platform_init (void)
{// Mount s3c24xx_soc_platform to the linked list platform_list, and call the snd_soc_instantiate_card function,
// If the sound card is not initialized, remove the corresponding struct from the linked list and call the initialization function of each struct,
Return snd_soc_register_platform (& s3c24xx_soc_platform );
}
Module_init (s3c24xx_soc_platform_init );
Static void _ exit s3c24xx_soc_platform_exit (void)
{
Snd_soc_unregister_platform (& s3c24xx_soc_platform );
}
Module_exit (s3c24xx_soc_platform_exit );
Module_author ("Ben dooks, <ben@simtec.co.uk> ");
Module_description ("Samsung s3c24xx pcm dma module ");
Module_license ("GPL ");