codec相关spec和函数介绍

一、codec command and control
这是一个controller和codec之间发送和接受command的机制。分别是CORB和RIRB,软件上分别通过两个control 寄存器来配置。

1.1、command outband ring buffer
controller 通过CORB来向codec发送commands。这个CORB是一个circular buffer,位于系统内存中,用来从software pass命令到和HDA link链接的codec上面。controller 使用DMA来从CORB中抓取数据,然后command放置在每一个link frame 开始的Command/Control位。
CORB的大小一般被设置为256 entries(1kb)

这里会涉及到两个指针:
WP(write pointer): used by sofetware to indicate to the hardware the last valid command in the CORB, 最后一条已发送的有效指令
RP(write pointer): the hardware uses RP to indicate to the sofeware the last command that has been fetched,硬件那边已经接收到的最后一条有效指令

WP & RP both measure the offset into the buffer in terms of commands
因为command都是4 byte长,所以在CORB buffer中的字节偏移都是WP/RP*4。往CORB中添加commands的时候,就是往offset (WP+1)*4byte处写入command,然后更新WP的值,WP+1。当software结束往里面写command的时候,它更新WP的值等于buffer中最后一条有效command的offset的值。刚初始化的时候,WP=0,所以第一条command被放置在offset(0+1)*4=4byte处,WP的值更新为1。

hardware sequence for pointer usage
Loop:
If (RP != WP) && ('run' bit is set) && ('link is running')
	RP++
	Get DWORD from buffer offset RP*4bytes
	Send DWORD (at beginning of next new frame)
goto Loop

当CORB RUN位被设置以后,controller里面的DMA会持续的比较RP和WP的值,看是否有新的命令可以使用。当两个pointer不相等的时候,MDA engine就会run到他们相等,DMA engine从CORB中读取commands并通过link将他们send到codec。
在这里插入图片描述
1.1.1、CORB buffer allocation
内存中的CORBbuffer是一个128字节大小开始的空间,大小由软件分配,并将location写道controller的CORB address Upper base和 Lower base register中,低7位为0来强制使用128byte的分配需求。

1.1.2、CORB entry format
从controller到codec的verbs为32 bit长度

1.1.3、 初始化CORB
首先得确认在CORBCTL register中的CORBRUN bit的值为0,正确的寄存器大小是使用corb size寄存器确定的,corb内存是从适当的堆和内存类型分配的。CORBBASE寄存器被定义为分配内存的基址,并使用CORBRPRST位将读取指针重置为0。软件必须向写入指针写入0h才能清除写入指针。如果需要,可以通过设CMEIE位来启用corb错误报告。最后,将CORBRUN位设置为1以启用corb操作。
在这里插入图片描述
1.1.4 Transmitting commands via the CORB
通过CORB进行的commands传输,首先是要确认CORB上有足够的空间。CORBWP & CORBRP的差别可以用来确定CORB中的可用空间。当第一条command被放置到CORB buffer中,因为command为32位占据4bytes,所以CORBWP+1表示CORB中4bytes的offset。

CORBWP然后被software更新为指向最后一条被写进CORB的command的index。Hardware然后就开始通过link transfer command,每从memory中抓取一条command就更新CORBRP的值。当RP=WP的时候,所有的command都被sent完,这个时候controller就会停止transfer。
在这里插入图片描述
在传送的过程中,software需要确认最新被添加进来的commands不会overwrite the buffer。

1.1.5 notice in corb program
如果一次有大量的command被sent到codec,这时候softwore需要不时的往这些commands中插入breaks,不然会造成拥堵

1.2 Response inbound ring buffer

Codec通过RIRB机制往controller发送responses。RIRB也是一个位于系统memeory中的用于存储codec发送来的responses的ring buffer。responses可以是请求(solicited)(响应来自控制器的命令)或主动请求(unsolicited)(由编解码器发送以发出事件信号)。

RIRB的分配和CORB差不多。

任何一个codec都可以往controller发送Response,controller中的DMA engine会往RIRB中来写这些responses。请求的响应由单个codec在随后的帧中返回,与此同时将提示命令(prompting commonds)发送到该codec。codec可以在不存在请求响应的任何帧中注入(injected)非请求响应。controller将把这个响应流写入rirb缓冲区。软件负责将响应与单个codec 分离,并将未经请求的响应与请求的响应分离。

同样在RIRB中也有WP & RP:
RP: sofrware控制,用来记录software从response buffer中读取的最后一条response,RP没有硬件的表示
WP: controller在hardware中使用WP来表示最后一个被写入到 response buffer中的response的offset。

不管是什么样的请求来了,controller都会往RIRB中写,与CORB一样,wp指示响应缓冲区单元中的偏移量。因为每一个response是8byte,所以buffer中的字节偏移值为:WP*8bytes

Example hardware sequence for pointer usage:
Loop:
	If (‘new response received’) && (‘run’ bit is set) && (‘Valid bit’ is set)
		WP++
		Write response + flags (QWORD) into buffer offset WP*8bytes
	Goto loop

controller有两种方法去提醒software RIRB已经被读取了:
1、中断
2、poll(计数)
在这里插入图片描述
1.2.1 RIRB entry format
codec发过来的response是32位的,然后controller在RIRB中为每个response加了一个32位的信息,这个信息包括两个:一个是表示是具体哪个codec发过来的,还有一个位表示这个response是un or solicited的。

1.2.2 RIRB初始化
rirb初始化与corb初始化非常相似。必须根据rirbsize寄存器和适合系统基础结构的堆正确分配内存。然后适当地更新rirbubase、rirblbase和中断生成控制寄存器。
在这里插入图片描述

二、codec相关函数分析

/* create codec instances */
err = azx_codec_create(chip, model[dev],
		       azx_max_codecs[chip->driver_type],
		       power_save_addr);

这里涉及到几个重要的结构体:
hda_bus、hda_bus_ops
hda_codec、hda_codec_ops
azx_rb
下面是codec的初始化工作

/* Codec initialization */
{
    
    
	struct hda_bus_template bus_temp;
	int c, codecs, err;

	bus_temp.ops.command = azx_send_cmd;
	bus_temp.ops.get_response = azx_get_response;
	bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
	bus_temp.ops.bus_reset = azx_bus_reset;

azx_send_cmd();azx_get_response();两个函数的内容,应该就是上面所介绍的CORB\RIRB的具体实现。下面来具体分析:

2.1 azx_send_cmd()
这里主要调用函数 azx_corb_send_cmd() 来send a command

unsigned int addr = azx_command_addr(val);

这一句,通过查看azx_command_addr函数可知,应该是用来获得codec的位置信息,也就是具体往哪一个codec上发送commands

	/* add command to corb */
	wp = azx_readw(chip, CORBWP);
	if (wp == 0xffff) {
    
    
		/* something wrong, controller likely turned to D3 */
		spin_unlock_irq(&chip->reg_lock);
		return -EIO;
	}
	wp++;
	wp %= ICH6_MAX_CORB_ENTRIES;

CORBWP在HDA spec上有说明,位于offset48h的位置,一个16位的寄存器。表示就是CORB write pointer的值。chip就是具体的声卡芯片,基地址,然后加偏移,这就是azx_readw()的操作。
ICH6_MAX_CORB_ENTRIES == 256,在spec中有说CORBWP支持最多256 CORB entries(256 x 4B = 1KB),所以这里取余是获得wp的具体指向的command的位置数

	rp = azx_readw(chip, CORBRP);
	if (wp == rp) {
    
    
		/* oops, it's full */
		spin_unlock_irq(&chip->reg_lock);
		return -EAGAIN;
	}

当wp=rp的时候,DMA就不从CORB中fetch command了

	chip->rirb.cmds[addr]++;
	chip->corb.buf[wp] = cpu_to_le32(val);
	azx_writew(chip, CORBWP, wp);

wp++以后,RIRB中待处理的cmd就得++,然后将这个wp写入到chip上的CORBWP中去

2.2 azx_get_response()

static unsigned int azx_rirb_get_response(struct hda_bus *bus,
					  unsigned int addr)
{
    
    
	struct azx *chip = bus->private_data;
	unsigned long timeout;
	unsigned long loopcounter;
	int do_poll = 0;

 again:
	timeout = jiffies + msecs_to_jiffies(1000);

	for (loopcounter = 0;; loopcounter++) {
    
    
		if (chip->polling_mode || do_poll) {
    
    
			spin_lock_irq(&chip->reg_lock);
			azx_update_rirb(chip);
			spin_unlock_irq(&chip->reg_lock);
		}

这里使用的是poll模式来更新这个RIRB
在这个函数中主要调用 azx_rirb_get_response() 函数,用于receive a response,然后在这里面首先会=调用下面这个函数来更新RIRB

/* retrieve RIRB entry - called from interrupt handler */
static void azx_update_rirb(struct azx *chip)
	unsigned int rp, wp;
	unsigned int addr;
	u32 res, res_ex;

上面这个addr就是表示codec的具体哪个,res表示codec返回的32位的response,res_ex表示controller为这个res添加的4位的info信息,所以在buffer中要多加一个4byte来表示。

	while (chip->rirb.rp != wp) {
    
    
		chip->rirb.rp++;
		chip->rirb.rp %= ICH6_MAX_RIRB_ENTRIES;

		rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */
		res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]);
		res = le32_to_cpu(chip->rirb.buf[rp]);
		addr = res_ex & 0xf;

首先要明确几点:因为RIRB中的每个entry是8byte,但在azx_rb中定义的CORB/RIRB buffer为*u32 buf,是4byte的形式存储,所以RIRB buffer中的一个entry的存储占两个位置。
将rp的值取余256可以得到rp在buffer中具体指向的entry(因为rp一直是++的)这时的rp是指向每一条entry的,也就是每隔8byte指向一个;
然后将rp的值左移一位,相当于x2,就是rp的位置上向下挪了一位,指向了低4byte,也就是这个entry的response部分,rp+1指向的就是这个entry的expend部分。
addr就是通过和0x1111与一下就可以得到具体是哪一个codec,因为最多15个。

		else if (chip->rirb.cmds[addr]) {
    
    
			chip->rirb.res[addr] = res;
			smp_wmb();
			chip->rirb.cmds[addr]--;

这里就表示如果有待处理的command,就赋值写到RIRB中,相应的标志位–。

然后继续回到函数 azx_rirb_get_response() 中;
后面就是一些错误处理,最后释放掉cmd_io,然后查看了一下,发现cmd_io 的初始化在 azx_init_chip()函数中就调用了

	if (!chip->single_cmd)
		azx_init_cmd_io(chip);

在这里面是CORB\RIRB的一些初始化工作,包括一些关键位和buffer的分配。

2.3 azx_attach_pcm_stream()

这里涉及到三个关于pcm的结构体:
struct snd_pcm pcm
struct azx_pcm apcm
struct hda_pcm cpcm

struct hda_pcm {
    
    
	char *name;
	struct hda_pcm_stream stream[2];
	unsigned int pcm_type;	/* HDA_PCM_TYPE_XXX */
	int device;		/* device number to assign */
	struct snd_pcm *pcm;	/* assigned PCM instance */
	bool own_chmap;		/* codec driver provides own channel maps */
};

struct azx_pcm {
    
    
	struct azx *chip;
	struct snd_pcm *pcm;
	struct hda_codec *codec;
	struct hda_pcm_stream *hinfo[2];
	struct list_head list;
};

struct snd_pcm {
    
    
	struct snd_card *card;
	struct list_head list;
	int device; /* device number */
	unsigned int info_flags;
	unsigned short dev_class;
	unsigned short dev_subclass;
	char id[64];
	char name[80];
	struct snd_pcm_str streams[2];
	struct mutex open_mutex;
	wait_queue_head_t open_wait;
	void *private_data;
	void (*private_free) (struct snd_pcm *pcm);
	struct device *dev; /* actual hw device this belongs to */
	bool internal; /* pcm is for internal use only */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
	struct snd_pcm_oss oss;
#endif
};

然后创建一个PCM 实例:

err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
			  cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
			  cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
			  &pcm);

上面的最后一个参数 &pcm , 在函数原型上的传参是:struct snd_pcm **rpcm ,rpcm是一个用来存储pcm instace的指针。

apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);

然后为apcm分配一块内存

	cpcm->pcm = pcm;
	for (s = 0; s < 2; s++) {
    
    
		apcm->hinfo[s] = &cpcm->stream[s];
		if (cpcm->stream[s].substreams)
			snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
	}

这时为stream设置azx_pcm_ops;
然后接下来就是给buffer pre-allocation, 这里的buffer分配是为给定pcm下的所有的stream pre-allocation一个连续的内存空间,最大是#define MAX_PREALLOC_SIZE (32 * 1024 * 1024),DMA类型是SG

	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
					      chip->card->dev,
					      size, MAX_PREALLOC_SIZE);

然后每个stream的具体的buffer分配是通过下面ALSA 内核函数

/**
 * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
 * @substream: the pcm substream instance
 * @type: DMA type (SNDRV_DMA_TYPE_*)
 * @data: DMA type dependent data
 * @size: the requested pre-allocation size in bytes
 * @max: the max. allowed pre-allocation size
 *
 * Do pre-allocation for the given DMA buffer type.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
				  int type, struct device *data,
				  size_t size, size_t max)
{
    
    
	substream->dma_buffer.dev.type = type;
	substream->dma_buffer.dev.dev = data;
	return snd_pcm_lib_preallocate_pages1(substream, size, max);
}


最后将这个pcm dev 链接到codec上,这样就完成了将pcm和这个codec相关联的过程。

	/* link to codec */
	pcm->dev = &codec->dev;

2.4、azx_bus_reset()

	struct azx *chip = bus->private_data;

	bus->in_reset = 1;
	azx_stop_chip(chip);
	azx_init_chip(chip, true);

首先在这个 stop 、init chip两个函数中,初始化了controller、interrupts、codec command I/O、和 position buffer。

		snd_hda_suspend(chip->bus);
		snd_hda_resume(chip->bus);

挂起,然后重启 the codecs

err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus);

创建一个HDA bus,

err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);

给bus分配内存

err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);

这里把bus也当作一个device来创建

2.5、Create codec instance

int snd_hda_codec_new(struct hda_bus *bus,
				unsigned int codec_addr,
				struct hda_codec **codecp)
{
    
    
	struct hda_codec *codec;
	char component[31];
	hda_nid_t fg;

每个线程都有一个nid。

codec = kzalloc(sizeof(*codec), GFP_KERNEL);

为codec这个设备文件分配一块内存,
:
:
:
:
太多了中间,省略
最后就是把codec当作一个dev来创建

err = snd_device_new(bus->card, SNDRV_DEV_CODEC, codec, &dev_ops);

猜你喜欢

转载自blog.csdn.net/qq_38350702/article/details/111996649
今日推荐