Linux Audio Driver II: Call of Control Interface

Posted by phpCCore Brad on Wed, 08 May 2019 22:06:02 +0200

This paper is a learning note based on Linux version number of mini2440 development board linux-2.6.32.2

I. control Interface Description

Control interface mainly allows user space applications (alsa-lib) to access and control audio codec chip multiplexers, sliding controls and so on.

2. open of control interface

According to the article "One of Linux Audio Drivers: Audio Driver Registration Process", the control device calls the snd_ctl_dev_register function registration.

static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

The incoming f_ops is snd_ctl_f_ops, as shown below.

static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};

When the control device device is opened, the snd_ctl_open function above is called.
Enter snd_ctl_open function analysis.

  • Get snd_card data.
card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);
    mreg = snd_minors[minor];
    private_data = mreg->private_data;

iminor(inode) = MINOR(inode->i_rdev)
Inode - > i_rdev is the device number of the file corresponding device, and MINOR (inode - > i_rdev) is the secondary device number.
When we register the device as control device, we look for an unused element in the snd_minors array and save fops, snd_card, type and other information. Subscript minor of this element is used as secondary device number.
Main equipment number 116

#define CONFIG_SND_MAJOR	116

dev_t = 116 << 20 | minor
Finally, dev_t is registered with the kernel.
Now open finds snd_minors[minor] according to the secondary device number and gets snd_card data.

  • Call the snd_card_file_add function
    This function saves the open file into the file_list list list of snd_card. The purpose is to track the connection status and avoid hot plugging releasing busy resources.
int snd_card_file_add(struct snd_card *card, struct file *file)
{
    struct snd_monitor_file *mfile;
    mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
    if (mfile == NULL)
        return -ENOMEM;
    mfile->file = file;
    mfile->disconnected_f_op = NULL;
    spin_lock(&card->files_lock);
    if (card->shutdown) 
    {
        spin_unlock(&card->files_lock);
        kfree(mfile);
        return -ENODEV;
    }
    list_add(&mfile->list, &card->files_list);
    spin_unlock(&card->files_lock);
    return 0;
}
  • Apply for a snd_ctl_file structure, save snd_card and other information, and assign it to file - > private_data, then through file - > private_data can access snd_card and other information.
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
ctl->card = card;
ctl->prefer_pcm_subdevice = -1;
ctl->prefer_rawmidi_subdevice = -1;
ctl->pid = current->pid;
file->private_data = ctl;
  • Add snd_ctl_file to the ctl_files list of snd_card.
list_add_tail(&ctl->list, &card->ctl_files);
3. write operation of control interface

The write operation of the control interface passes through the snd_ctl_ioctl. Enter the snd_ctl_ioctl function.
Snd_ctl_file is obtained by file - > private_data, and snd_card is obtained by snd_ctl_file.

ctl = file->private_data;
card = ctl->card;

The interface that the write operation calls is snd_ctl_elem_write_user.
Let's go into the snd_ctl_elem_write_user function.

  • Copy data from user space to the kernel. The data type is snd_ctl_elem_value
control = memdup_user(_control, sizeof(*control));
  • Write snd_ctl_elem_value data.
result = snd_ctl_elem_write(card, file, control);

Enter the snd_ctl_elem_write function.

  • The corresponding snd_kcontrol is found according to the ID of snd_ctl_elem_value passed in by the user.
    When the sound card is initialized, the bottom layer has 20 control lists registered from snd_kcontrol to snd_card.
kctl = snd_ctl_find_id(card, &control->id);
  • Call the kctl - > put function.
result = kctl->put(kctl, control);

The kctl - > put function is assigned before snd_kcontrol is registered. The function is snd_soc_put_volsw.

4. snd_soc_put_volsw function

Take volume control as an example.

uda1341_snd_controls[0] = 
{
    .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
    .name = "Master Playback Volume",
    .info = snd_soc_info_volsw,
    .get = snd_soc_get_volsw,
    .put = snd_soc_put_volsw,
    .private_value =
    {
        soc_mixer_control.reg = UDA134X_DATA000,
        soc_mixer_control.shift = 0,
        soc_mixer_control.rshift = 0,
        soc_mixer_control.max = 0x3F,
        soc_mixer_control.invert = 1,
    },
}
  • Get soc_mixer_control and snd_soc_codec from snd_kcontrol
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  • Calculation part
    unsigned int reg = mc->reg;
    unsigned int shift = mc->shift;
    unsigned int rshift = mc->rshift;
    int max = mc->max;
    unsigned int mask = (1 << fls(max)) - 1;
    unsigned int invert = mc->invert;
    unsigned int val, val2, val_mask;

    val = (ucontrol->value.integer.value[0] & mask);
    if (invert)
        val = max - val;
    val_mask = mask << shift;
    val = val << shift;
    if (shift != rshift) 
    {
        val2 = (ucontrol->value.integer.value[1] & mask);
        if (invert)
            val2 = max - val2;
        val_mask |= mask << rshift;
        val |= val2 << rshift;
    }

reg = UDA134X_DATA000;
shift = 0;
rshift = 0;
max = 0x3f;
fls(max), the highest position of max, fls(max) = 6, mask = 0x3f;
Whether invert is inverted, invert = 1, needs to be inverted;
val, the value given by user space, if reversed, needs to be subtracted by the maximum value. The larger the value, the larger the volume, the smaller the value that needs to be set to the register.
val_mask = 0x3f << 0;
val = val << 0;
In the case of shift!= rshit, guess is that the left and right channels need to be set separately, we don't need to set them here.

  • Call the snd_soc_update_bits function
snd_soc_update_bits(codec, reg, val_mask, val);
    snd_soc_write(codec, reg, new);
        codec->write(codec, reg, val);

codec->write = uda134x_write
The uda134x_write function is analyzed in "Linux Audio Driver V: UDA1341 Chip Operating Interface". It is not analyzed here, but the value of the register is written.

Reference Blog:
https://blog.csdn.net/sunweizhong1024/article/details/7697363
https://blog.csdn.net/droidphone/article/details/6409983

Topics: codec Linux