1. Introduction
(1) When an application uses a sound card, the data flow is as follows: the application sends data to the driver, the driver sends the data to the hardware sound card, and the sound card converts the data into sound data and plays it out.
(2) There are two ways to send data
The first method: the app sends data, and then sends the next segment after the driver has processed it (sending the next segment after processing will cause the sound to be intermittent)
The second method: the application continuously sends data, the driver continuously retrieves data and continuously sends it to the hardware. This solves the problem of intermittent sound, but requires the creation of a very large buffer (requested in the driver, called a buffer)
The data of a sampling point includes left channel data and right channel data
Here hw_ptr is a pointer (update means the pointer moves backward)
2. How to write a driver (s3c2440_dma.c (platform))
(1) The DMA file in the platform part is responsible for data transmission. Modify s3c2440_dma.c
3. Allocate/release buffer
When creating a sound card, the s3c2440_dma_new function is called, and the dma buffer is allocated in the function. When destroying the sound card, the dma buffer is released
(1) Allocate DMA BUFFER
static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
int ret = 0;
/* 1. Allocate DMA BUFFER */
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev ->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { //Playback function
playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card->dev, s3c2440_dma_hardware.buffer_bytes_max,
&playback_dma_info.phy_addr, GFP_KERNEL); //Allocate DMA BUFFER //virt_addr is the virtual address of buffer, buffer_bytes_max is the size of the allocated buffer, phy_addr is the physical address of the allocated buffer
if (!playback_dma_info.virt_addr)//If the virtual address is empty, return an error
{
return -ENOMEM; // Returns an error flag indicating that there is no memory
}
playback_dma_info.buf_max_size = s3c2440_dma_hardware.buffer_bytes_max; //Allocation successful, record the size of the allocated buffer. Although 128K is allocated, the amount used is determined by the app.
//Tell the alsa core layer the buffer information
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev .dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = playback_dma_info.virt_addr; //Structure of playback-related dma buffer information
buf->bytes = playback_dma_info.buf_max_size; // The size of buf
}//
return ret;
}
(2) Release DMA BUFFER
static void s3c2440_dma_free(struct snd_pcm *pcm)
{
dma_free_writecombine(pcm->card->dev, playback_dma_info.buf_max_size,
(void *)playback_dma_info.virt_addr, playback_dma_info.phy_addr); //Release the dma buffer of the playback part (buffer size, buffer virtual address, buffer physical address)
}
4. request_irq
(1) open function
static int s3c2440_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
/* Register interrupt*/
ret = request_irq(IRQ_DMA2,s3c2440_dma2_irq, IRQF_DISABLED, "myalsa for playback",substream);
if (ret) //IRQ_DMA2 is the interrupt number, s3c2440_dma2_irq is the interrupt processing function, IRQF_DISABLED is the flag (when an interrupt occurs, the interrupt remains masked during the interrupt processing), "myalsa for playback" is the interrupt name, substream is the device ID
{
printk("request_irq error!n");
return -EIO;
}
return 0;
}
(2) Interrupt request function (update hw_ptr and other information after successful data transmission)
static irqreturn_t s3c2440_dma2_irq(int irq, void *devid) //The left parameter is the interrupt number, the right is the device ID
{
struct snd_pcm_substream *substream = devid; //The parameter substream given to the interrupt processing function s3c2440_dma2_irq in the request interrupt function called by the open function request_irq
/* Update status information*/ When a DMA is transmitted, an interrupt occurs
playback_dma_info.dma_ofs += playback_dma_info.period_size; //The offset address moves back one period
if (playback_dma_info.dma_ofs >= playback_dma_info.buffer_size) //If the offset address exceeds the buffer range
playback_dma_info.dma_ofs = 0; //Point to the beginning of the buffer
/* Update hw_ptr and other information, (driver part)
* And judge: if there is no data in the buffer, call trigger to stop DMA
*/
After transferring a DMA,
snd_pcm_period_elapsed(substream);
if (playback_dma_info.be_running)
{
/* If there is still data, be_running is 1 to indicate it is running
* 1. Load the next period
* 2. Start DMA transfer again
*/
load_dma_period();
s3c2440_dma_start();
}
return IRQ_HANDLED;
}
5. s3c2440_dma_hw_params (prepare parameters for DMA transfer)
static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long totbytes = params_buffer_bytes(params);//The buffer size used is determined by the parameter params passed in by the application./*
Set DMA according to params */
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
/* s3c2440_dma_new (inside the driver) allocates a large DMA BUFFER. How much the application will use is determined by the application. The parameter params
* params passed in determines how much to use
*/
runtime->dma_bytes = totbytes;//Record the size used, which will be used later. The application will return when it writes to the tail. Where the tail is is determined by the tail.
playback_dma_info.buffer_size = totbytes; //buffer_size
playback_dma_info.period_size = params_period_bytes(params); //period_size, in buffer, one DMA transfer is transferred in period
return 0;
}
6. prepare function (prepare data for DMA transfer)
static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)
{
/* Prepare for DMA transfer */
/* Reset various status information (clear) */
playback_dma_info.dma_ofs = 0; //The offset value is set to 0, and the DMA transfer will start from the beginning
playback_dma_info.be_running = 0; //Indicates that DMA has not been started and is not running yet
/* Load the first period */
load_dma_period();
return 0;
}
return ret;
}
7. Load the data to be transferred
/* Data transfer: source, destination, length*/
static void load_dma_period(void)
{
/* Tell DMA the source, destination and length */
dma_regs->disrc = playback_dma_info.phy_addr + playback_dma_info.dma_ofs; /* Physical address of source*/physical address + offset address
dma_regs->disrcc = (0<<1) | (0<<0); /* Source is on AHB bus, source address increments*/
dma_regs->didst = 0x55000010; /* Physical address of destination*/IIC controller
dma_regs->didstc = (0<<2) | (1<<1) | (1<<0); /* Destination is on APB bus, destination address remains unchanged*/
/* bit22: 1-noreload *Indicates that after a piece of data is transmitted, some values will be reloaded and DMA will be started by itself. In our interrupt program, DMA is started by itself, so it is not allowed to download automatically. Therefore, it is set to 1. If it is automatically loaded, data will be automatically transmitted before the interrupt function is processed. /Solve the problem of noise in playback
dma_regs->dcon = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<22)|(1<<20)|(playback_dma_info.period_size/2); /* Enable interrupt, single transmission, hardware trigger*/period_size length, each transmission is not the entire buffer, but a period in it, period_size size. buffer_size is the size of the buffer used by the application, buf_max_size is the size of the buffer allocated by the driver, and the application can only use part of it. The reason for dividing by 2 is that (1<<20) is 2 bytes, so it is divided by 2 because it is in bytes. That is, how many times it is transmitted, 2 bytes each time.
}
8. dma_trigger function (trigger DMA transfer, after the transfer is completed, an interrupt is generated and the interrupt processing function s3c2440_dma2_irq is entered)
static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
/* Start or stop DMA transfer according to cmd*/
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* Start DMA transfer*/
playback_dma_info.be_running = 1;//There is also data status information
s3c2440_dma_start();
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
/* Stop DMA transfer*/
playback_dma_info.be_running = 0; //Status information after data transmission
s3c2440_dma_stop();
break;
default:
ret = -EINVAL;
break;
}
9. dma_pointer function (indicates the location of the next DMA transfer (each DMA transfer is a period), the hw_ptr pointer is determined according to the DMA position)
Previous article:ARM stack method
Next article:u-boot-2011.06 transplant
Recommended ReadingLatest update time:2024-11-15 01:57
- Popular Resources
- Popular amplifiers
- Learn ARM development(16)
- Learn ARM development(17)
- Learn ARM development(18)
- Embedded system debugging simulation tool
- A small question that has been bothering me recently has finally been solved~~
- Learn ARM development (1)
- Learn ARM development (2)
- Learn ARM development (4)
- Learn ARM development (6)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- CGD and Qorvo to jointly revolutionize motor control solutions
- CGD and Qorvo to jointly revolutionize motor control solutions
- Keysight Technologies FieldFox handheld analyzer with VDI spread spectrum module to achieve millimeter wave analysis function
- Infineon's PASCO2V15 XENSIV PAS CO2 5V Sensor Now Available at Mouser for Accurate CO2 Level Measurement
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- A new chapter in Great Wall Motors R&D: solid-state battery technology leads the future
- Naxin Micro provides full-scenario GaN driver IC solutions
- Interpreting Huawei’s new solid-state battery patent, will it challenge CATL in 2030?
- Are pure electric/plug-in hybrid vehicles going crazy? A Chinese company has launched the world's first -40℃ dischargeable hybrid battery that is not afraid of cold
- Keyence vision camera communicates with Siemens 1500 PLC to achieve grasping correction with the robot
- LSM6DSO(X) sensor driver key code analysis: read_data_polling routine (Part 2)
- Xunwei STM32MP157 development board Linux + MCU learning materials and tutorials summary (manual + video)
- Microchip FAQ | TA100-VAO secure boot and message authentication for CAN FD in ADAS and IVI systems
- C2000 Piccolo MCU F28027F LaunchPad Development Kit
- [MPS Mall Big Offer Experience Season] Unboxing
- [ESP32-Korvo Review] Part 4: Text-to-Speech TTS
- [Silicon Labs Development Kit Review] +PDM Stereo Microphone SPK0641HT4H-1
- Embedded Qt-Realize switching between two windows
- Some unused boards