Linux驱动开发之ALSA驱动mach

 

概述

ALSA驱动machine

背景

基于正点原子的i.MX6ULL开发板,Linux内核版本 4.1.15

源文件

sound/soc/fsl/imx-wm8960.c

static struct snd_soc_dai_link imx_wm8960_dai[] = {
    {
        .name = "HiFi",
        .stream_name = "HiFi",
        .codec_dai_name = "wm8960-hifi",
        .ops = &imx_hifi_ops,
    },
    {
        .name = "HiFi-ASRC-FE",
        .stream_name = "HiFi-ASRC-FE",
        .codec_name = "snd-soc-dummy",
        .codec_dai_name = "snd-soc-dummy-dai",
        .dynamic = 1,
        .ignore_pmdown_time = 1,
        .dpcm_playback = 1,
        .dpcm_capture = 1,
    },
    {
        .name = "HiFi-ASRC-BE",
        .stream_name = "HiFi-ASRC-BE",
        .codec_dai_name = "wm8960-hifi",
        .platform_name = "snd-soc-dummy",
        .no_pcm = 1,
        .ignore_pmdown_time = 1,
        .dpcm_playback = 1,
        .dpcm_capture = 1,
        .ops = &imx_hifi_ops,
        .be_hw_params_fixup = be_hw_params_fixup,
    },
};

static int imx_wm8960_probe(struct platform_device *pdev)
{
    struct device_node *cpu_np, *codec_np = NULL;
    struct platform_device *cpu_pdev;
    struct imx_priv *priv = &card_priv;
    struct i2c_client *codec_dev;
    struct imx_wm8960_data *data;
    struct platform_device *asrc_pdev = NULL;
    struct device_node *asrc_np;
    struct of_phandle_args args;
    u32 width;
    int ret;

    priv->pdev = pdev;
    dev_info(&pdev->dev, "%s\n", __func__);
    dev_dbg(&pdev->dev, "%s\n", __func__);

    cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
    if (!cpu_np) {
        dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
        ret = -EINVAL;
        goto fail;
    }

    codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
    if (!codec_np) {
        dev_err(&pdev->dev, "phandle missing or invalid\n");
        ret = -EINVAL;
        goto fail;
    }

    cpu_pdev = of_find_device_by_node(cpu_np);
    if (!cpu_pdev) {
        dev_err(&pdev->dev, "failed to find SAI platform device\n");
        ret = -EINVAL;
        goto fail;
    }

    codec_dev = of_find_i2c_device_by_node(codec_np);
    if (!codec_dev || !codec_dev->dev.driver) {
        dev_err(&pdev->dev, "failed to find codec platform device\n");
        ret = -EINVAL;
        goto fail;
    }

    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data) {
        ret = -ENOMEM;
        goto fail;
    }

    if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
        data->is_codec_master = true;

    data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
    if (IS_ERR(data->codec_clk)) {
        ret = PTR_ERR(data->codec_clk);
        dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
        goto fail;
    }

    ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, "gpr", 3,
                0, &args);
    if (ret) {
        dev_err(&pdev->dev, "failed to get gpr property\n");
        goto fail;
    } else {
        data->gpr = syscon_node_to_regmap(args.np);
        if (IS_ERR(data->gpr)) {
            ret = PTR_ERR(data->gpr);
            dev_err(&pdev->dev, "failed to get gpr regmap\n");
            goto fail;
        }
        regmap_update_bits(data->gpr, args.args[0], args.args[1], args.args[2]);
    }

    of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2);

    asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
    if (asrc_np) {
        asrc_pdev = of_find_device_by_node(asrc_np);
        priv->asrc_pdev = asrc_pdev;
    }

    data->card.dai_link = imx_wm8960_dai;

    imx_wm8960_dai[0].codec_of_node	= codec_np;
    imx_wm8960_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
    imx_wm8960_dai[0].platform_of_node = cpu_np;

    if (!asrc_pdev) {
        data->card.num_links = 1;
    } else {
        imx_wm8960_dai[1].cpu_of_node = asrc_np;
        imx_wm8960_dai[1].platform_of_node = asrc_np;
        imx_wm8960_dai[2].codec_of_node	= codec_np;
        imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
        data->card.num_links = 3;

        ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
                &data->asrc_rate);
        if (ret) {
            dev_err(&pdev->dev, "failed to get output rate\n");
            ret = -EINVAL;
            goto fail;
        }

        ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
        if (ret) {
            dev_err(&pdev->dev, "failed to get output rate\n");
            ret = -EINVAL;
            goto fail;
        }

        if (width == 24)
            data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
        else
            data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
    }

    data->card.dev = &pdev->dev;
    data->card.owner = THIS_MODULE;
    ret = snd_soc_of_parse_card_name(&data->card, "model");
    if (ret)
        goto fail;
    data->card.dapm_widgets = imx_wm8960_dapm_widgets;
    data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);

    ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
    if (ret)
        goto fail;

    data->card.late_probe = imx_wm8960_late_probe;

    platform_set_drvdata(pdev, &data->card);
    snd_soc_card_set_drvdata(&data->card, data);
    ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
    if (ret) {
        dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
        goto fail;
    }

    priv->snd_card = data->card.snd_card;

    imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
            "hp-det-gpios", 0, &priv->hp_active_low);

    imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
            "mic-det-gpios", 0, &priv->mic_active_low);

    if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
        gpio_is_valid(imx_mic_jack_gpio.gpio) &&
        imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
        priv->is_headset_jack = true;

    if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
        priv->headphone_kctl = snd_kctl_jack_new("Headphone", 0, NULL);
        ret = snd_ctl_add(priv->snd_card, priv->headphone_kctl);
        if (ret)
            dev_warn(&pdev->dev, "failed to create headphone jack kctl\n");

        if (priv->is_headset_jack) {
            imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
            imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
        }
        imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
        imx_hp_jack_gpio.data = &imx_hp_jack;
        ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
                       &imx_hp_jack_pin, &imx_hp_jack_gpio);
        if (ret) {
            dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
            goto out;
        }

        ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
        if (ret)
            dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
    }

    if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
        if (!priv->is_headset_jack) {
            imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
            imx_mic_jack_gpio.data = &imx_mic_jack;
            ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
                    &imx_mic_jack_pins, &imx_mic_jack_gpio);
            if (ret) {
                dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
                goto out;
            }
        }
        ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
        if (ret)
            dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
    }

out:
    ret = 0;
fail:
    if (cpu_np)
        of_node_put(cpu_np);
    if (codec_np)
        of_node_put(codec_np);

    return ret;
}
devm_snd_soc_register_card(&pdev->dev, &data->card);
--> devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
--> snd_soc_register_card(card);
----> snd_soc_init_multicodec(card, link);
----> snd_soc_initialize_card_lists(card);
----> snd_soc_instantiate_card(card);
------> soc_bind_dai_link(card, i);
--------> rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
----------> list_for_each_entry(component, &component_list, list) {/*匹配*/}
--------> codec_dais[i] = snd_soc_find_dai(&codecs[i]);
----------> list_for_each_entry(component, &component_list, list) {/*匹配*/}
------> snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner, 0, &card->snd_card);
------> soc_init_card_debugfs(card);
------> snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
------> snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets, card->num_dapm_widgets);
------> snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets, card->num_of_dapm_widgets);
------> card->probe(card);
------> soc_probe_link_components(card, i, order);
------> soc_probe_link_dais(card, i, order);
------> soc_probe_aux_dev(card, i);
------> snd_soc_dapm_link_dai_widgets(card);
------> snd_soc_dapm_connect_dai_link_widgets(card);
------> snd_soc_add_card_controls(card, card->controls, card->num_controls);
------> snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,card->num_dapm_routes);
------> snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,card->num_of_dapm_routes);
------> card->late_probe(card);
------> snd_soc_dapm_new_widgets(card);
------> snd_card_register(card->snd_card);
------> snd_soc_dapm_sync(&card->dapm);
--> devres_add;