linux-stable/sound/soc/generic/simple-card.c

534 lines
14 KiB
C
Raw Normal View History

/*
* ASoC simple sound card support
*
* Copyright (C) 2012 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <sound/jack.h>
#include <sound/simple_card.h>
#include <sound/soc-dai.h>
#include <sound/soc.h>
struct asoc_simple_jack {
struct snd_soc_jack jack;
struct snd_soc_jack_pin pin;
struct snd_soc_jack_gpio gpio;
};
struct simple_card_data {
struct snd_soc_card snd_card;
struct simple_dai_props {
struct asoc_simple_dai cpu_dai;
struct asoc_simple_dai codec_dai;
unsigned int mclk_fs;
} *dai_props;
unsigned int mclk_fs;
struct asoc_simple_jack hp_jack;
struct asoc_simple_jack mic_jack;
struct snd_soc_dai_link *dai_link;
};
#define simple_priv_to_card(priv) (&(priv)->snd_card)
#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev)
#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i))
#define DAI "sound-dai"
#define CELL "#sound-dai-cells"
#define PREFIX "simple-audio-card,"
#define asoc_simple_card_init_hp(card, sjack, prefix)\
asoc_simple_card_init_jack(card, sjack, 1, prefix)
#define asoc_simple_card_init_mic(card, sjack, prefix)\
asoc_simple_card_init_jack(card, sjack, 0, prefix)
static int asoc_simple_card_init_jack(struct snd_soc_card *card,
struct asoc_simple_jack *sjack,
int is_hp, char *prefix)
{
struct device *dev = card->dev;
enum of_gpio_flags flags;
char prop[128];
char *pin_name;
char *gpio_name;
int mask;
int det;
sjack->gpio.gpio = -ENOENT;
if (is_hp) {
snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix);
pin_name = "Headphones";
gpio_name = "Headphone detection";
mask = SND_JACK_HEADPHONE;
} else {
snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix);
pin_name = "Mic Jack";
gpio_name = "Mic detection";
mask = SND_JACK_MICROPHONE;
}
det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags);
if (det == -EPROBE_DEFER)
return -EPROBE_DEFER;
if (gpio_is_valid(det)) {
sjack->pin.pin = pin_name;
sjack->pin.mask = mask;
sjack->gpio.name = gpio_name;
sjack->gpio.report = mask;
sjack->gpio.gpio = det;
sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW);
sjack->gpio.debounce_time = 150;
snd_soc_card_jack_new(card, pin_name, mask,
&sjack->jack,
&sjack->pin, 1);
snd_soc_jack_add_gpios(&sjack->jack, 1,
&sjack->gpio);
}
return 0;
}
static void asoc_simple_card_remove_jack(struct asoc_simple_jack *sjack)
{
if (gpio_is_valid(sjack->gpio.gpio))
snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio);
}
static int asoc_simple_card_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
simple_priv_to_props(priv, rtd->num);
int ret;
ret = asoc_simple_card_clk_enable(&dai_props->cpu_dai);
if (ret)
return ret;
ret = asoc_simple_card_clk_enable(&dai_props->codec_dai);
if (ret)
asoc_simple_card_clk_disable(&dai_props->cpu_dai);
return ret;
}
static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
simple_priv_to_props(priv, rtd->num);
asoc_simple_card_clk_disable(&dai_props->cpu_dai);
asoc_simple_card_clk_disable(&dai_props->codec_dai);
}
static int asoc_simple_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct simple_dai_props *dai_props =
simple_priv_to_props(priv, rtd->num);
unsigned int mclk, mclk_fs = 0;
int ret = 0;
if (priv->mclk_fs)
mclk_fs = priv->mclk_fs;
else if (dai_props->mclk_fs)
mclk_fs = dai_props->mclk_fs;
if (mclk_fs) {
mclk = params_rate(params) * mclk_fs;
ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
SND_SOC_CLOCK_IN);
if (ret && ret != -ENOTSUPP)
goto err;
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
SND_SOC_CLOCK_OUT);
if (ret && ret != -ENOTSUPP)
goto err;
}
return 0;
err:
return ret;
}
ASoC: constify snd_soc_ops structures Check for snd_soc_ops structures that are only stored in the ops field of a snd_soc_dai_link structure. This field is declared const, so snd_soc_ops structures that have this property can be declared as const also. The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // <smpl> @r disable optional_qualifier@ identifier i; position p; @@ static struct snd_soc_ops i@p = { ... }; @ok1@ identifier r.i; struct snd_soc_dai_link e; position p; @@ e.ops = &i@p; @ok2@ identifier r.i, e; position p; @@ struct snd_soc_dai_link e[] = { ..., { .ops = &i@p, }, ..., }; @bad@ position p != {r.p,ok1.p,ok2.p}; identifier r.i; struct snd_soc_ops e; @@ e@i@p @depends on !bad disable optional_qualifier@ identifier r.i; @@ static +const struct snd_soc_ops i = { ... }; // </smpl> The effect on the layout of the .o files is shown by the following output of the size command, first before then after the transformation: text data bss dec hex filename 4500 696 0 5196 144c sound/soc/generic/simple-card.o 4564 632 0 5196 144c sound/soc/generic/simple-card.o text data bss dec hex filename 3018 608 0 3626 e2a sound/soc/generic/simple-scu-card.o 3074 544 0 3618 e22 sound/soc/generic/simple-scu-card.o text data bss dec hex filename 4148 2448 768 7364 1cc4 sound/soc/intel/boards/bdw-rt5677.o 4212 2384 768 7364 1cc4 sound/soc/intel/boards/bdw-rt5677.o text data bss dec hex filename 5403 4628 384 10415 28af sound/soc/intel/boards/bxt_da7219_max98357a.o 5531 4516 384 10431 28bf sound/soc/intel/boards/bxt_da7219_max98357a.o text data bss dec hex filename 5275 4496 384 10155 27ab sound/soc/intel/boards/bxt_rt298.o 5403 4368 384 10155 27ab sound/soc/intel/boards/bxt_rt298.o text data bss dec hex filename 10017 2344 48 12409 3079 sound/soc/intel/boards/bytcr_rt5640.o 10145 2232 48 12425 3089 sound/soc/intel/boards/bytcr_rt5640.o text data bss dec hex filename 3719 2356 0 6075 17bb sound/soc/intel/boards/bytcr_rt5651.o 3847 2244 0 6091 17cb sound/soc/intel/boards/bytcr_rt5651.o text data bss dec hex filename 3598 2392 0 5990 1766 sound/soc/intel/boards/cht_bsw_max98090_ti.o 3726 2280 0 6006 1776 sound/soc/intel/boards/cht_bsw_max98090_ti.o text data bss dec hex filename 5343 3624 16 8983 2317 sound/soc/intel/boards/cht_bsw_rt5645.o 5471 3496 16 8983 2317 sound/soc/intel/boards/cht_bsw_rt5645.o text data bss dec hex filename 4662 2592 384 7638 1dd6 sound/soc/intel/boards/cht_bsw_rt5672.o 4790 2464 384 7638 1dd6 sound/soc/intel/boards/cht_bsw_rt5672.o text data bss dec hex filename 1595 2528 0 4123 101b sound/soc/intel/boards/haswell.o 1659 2472 0 4131 1023 sound/soc/intel/boards/haswell.o text data bss dec hex filename 6272 4760 416 11448 2cb8 sound/soc/intel/boards/skl_nau88l25_max98357a.o 6464 4568 416 11448 2cb8 sound/soc/intel/boards/skl_nau88l25_max98357a.o text data bss dec hex filename 7075 4888 416 12379 305b sound/soc/intel/boards/skl_nau88l25_ssm4567.o 7267 4696 416 12379 305b sound/soc/intel/boards/skl_nau88l25_ssm4567.o text data bss dec hex filename 5659 4496 384 10539 292b sound/soc/intel/boards/skl_rt286.o 5787 4368 384 10539 292b sound/soc/intel/boards/skl_rt286.o text data bss dec hex filename 1721 2048 0 3769 eb9 sound/soc/kirkwood/armada-370-db.o 1769 1976 0 3745 ea1 sound/soc/kirkwood/armada-370-db.o text data bss dec hex filename 1363 1792 0 3155 c53 sound/soc/mxs/mxs-sgtl5000.o 1427 1728 0 3155 c53 sound/soc/mxs/mxs-sgtl5000.o Signed-off-by: Julia Lawall <Julia.Lawall@lip6.fr> Signed-off-by: Mark Brown <broonie@kernel.org>
2016-10-15 14:55:49 +00:00
static const struct snd_soc_ops asoc_simple_card_ops = {
.startup = asoc_simple_card_startup,
.shutdown = asoc_simple_card_shutdown,
.hw_params = asoc_simple_card_hw_params,
};
static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd)
{
struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_dai *codec = rtd->codec_dai;
struct snd_soc_dai *cpu = rtd->cpu_dai;
struct simple_dai_props *dai_props =
simple_priv_to_props(priv, rtd->num);
2015-11-18 07:34:11 +00:00
int ret;
ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai);
if (ret < 0)
return ret;
ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai);
if (ret < 0)
return ret;
ret = asoc_simple_card_init_hp(rtd->card, &priv->hp_jack, PREFIX);
if (ret < 0)
return ret;
ret = asoc_simple_card_init_mic(rtd->card, &priv->hp_jack, PREFIX);
if (ret < 0)
return ret;
return 0;
}
static int asoc_simple_card_dai_link_of(struct device_node *node,
struct simple_card_data *priv,
int idx,
bool is_top_level_node)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx);
struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx);
struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai;
struct asoc_simple_dai *codec_dai = &dai_props->codec_dai;
struct device_node *cpu = NULL;
struct device_node *plat = NULL;
struct device_node *codec = NULL;
char prop[128];
char *prefix = "";
int ret, single_cpu;
/* For single DAI link & old style of DT node */
if (is_top_level_node)
prefix = PREFIX;
snprintf(prop, sizeof(prop), "%scpu", prefix);
cpu = of_get_child_by_name(node, prop);
if (!cpu) {
ret = -EINVAL;
dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
goto dai_link_of_err;
}
snprintf(prop, sizeof(prop), "%splat", prefix);
plat = of_get_child_by_name(node, prop);
snprintf(prop, sizeof(prop), "%scodec", prefix);
codec = of_get_child_by_name(node, prop);
if (!codec) {
ret = -EINVAL;
dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop);
goto dai_link_of_err;
}
ret = asoc_simple_card_parse_daifmt(dev, node, codec,
prefix, &dai_link->dai_fmt);
if (ret < 0)
goto dai_link_of_err;
of_property_read_u32(node, "mclk-fs", &dai_props->mclk_fs);
ret = asoc_simple_card_parse_cpu(cpu, dai_link,
DAI, CELL, &single_cpu);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_parse_codec(codec, dai_link, DAI, CELL);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_parse_platform(plat, dai_link, DAI, CELL);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_of_parse_tdm(cpu, cpu_dai);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_of_parse_tdm(codec, codec_dai);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_parse_clk_cpu(dev, cpu, dai_link, cpu_dai);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_parse_clk_codec(dev, codec, dai_link, codec_dai);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_canonicalize_dailink(dai_link);
if (ret < 0)
goto dai_link_of_err;
ret = asoc_simple_card_set_dailink_name(dev, dai_link,
"%s-%s",
dai_link->cpu_dai_name,
dai_link->codec_dai_name);
if (ret < 0)
goto dai_link_of_err;
dai_link->ops = &asoc_simple_card_ops;
dai_link->init = asoc_simple_card_dai_init;
asoc_simple_card_canonicalize_cpu(dai_link, single_cpu);
dai_link_of_err:
of_node_put(cpu);
of_node_put(codec);
return ret;
}
static int asoc_simple_card_parse_aux_devs(struct device_node *node,
struct simple_card_data *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct device_node *aux_node;
struct snd_soc_card *card = simple_priv_to_card(priv);
int i, n, len;
if (!of_find_property(node, PREFIX "aux-devs", &len))
return 0; /* Ok to have no aux-devs */
n = len / sizeof(__be32);
if (n <= 0)
return -EINVAL;
card->aux_dev = devm_kzalloc(dev,
n * sizeof(*card->aux_dev), GFP_KERNEL);
if (!card->aux_dev)
return -ENOMEM;
for (i = 0; i < n; i++) {
aux_node = of_parse_phandle(node, PREFIX "aux-devs", i);
if (!aux_node)
return -EINVAL;
card->aux_dev[i].codec_of_node = aux_node;
}
card->num_aux_devs = n;
return 0;
}
static int asoc_simple_card_parse_of(struct simple_card_data *priv)
{
struct device *dev = simple_priv_to_dev(priv);
struct snd_soc_card *card = simple_priv_to_card(priv);
struct device_node *dai_link;
struct device_node *node = dev->of_node;
int ret;
if (!node)
return -EINVAL;
dai_link = of_get_child_by_name(node, PREFIX "dai-link");
/* The off-codec widgets */
if (of_property_read_bool(node, PREFIX "widgets")) {
ret = snd_soc_of_parse_audio_simple_widgets(card,
PREFIX "widgets");
if (ret)
goto card_parse_end;
}
/* DAPM routes */
if (of_property_read_bool(node, PREFIX "routing")) {
ret = snd_soc_of_parse_audio_routing(card,
PREFIX "routing");
if (ret)
goto card_parse_end;
}
/* Factor to mclk, used in hw_params() */
of_property_read_u32(node, PREFIX "mclk-fs", &priv->mclk_fs);
/* Single/Muti DAI link(s) & New style of DT node */
if (dai_link) {
struct device_node *np = NULL;
int i = 0;
for_each_child_of_node(node, np) {
dev_dbg(dev, "\tlink %d:\n", i);
ret = asoc_simple_card_dai_link_of(np, priv,
i, false);
if (ret < 0) {
of_node_put(np);
goto card_parse_end;
}
i++;
}
} else {
/* For single DAI link & old style of DT node */
ret = asoc_simple_card_dai_link_of(node, priv, 0, true);
if (ret < 0)
goto card_parse_end;
}
ret = asoc_simple_card_parse_card_name(card, PREFIX);
if (ret < 0)
goto card_parse_end;
ret = asoc_simple_card_parse_aux_devs(node, priv);
card_parse_end:
of_node_put(dai_link);
return ret;
}
static int asoc_simple_card_probe(struct platform_device *pdev)
{
struct simple_card_data *priv;
struct snd_soc_dai_link *dai_link;
struct simple_dai_props *dai_props;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct snd_soc_card *card;
int num, ret;
/* Get the number of DAI links */
if (np && of_get_child_by_name(np, PREFIX "dai-link"))
num = of_get_child_count(np);
else
num = 1;
/* Allocate the private data and the DAI link array */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL);
dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL);
if (!dai_props || !dai_link)
return -ENOMEM;
priv->dai_props = dai_props;
priv->dai_link = dai_link;
/* Init snd_soc_card */
card = simple_priv_to_card(priv);
card->owner = THIS_MODULE;
card->dev = dev;
card->dai_link = priv->dai_link;
card->num_links = num;
if (np && of_device_is_available(np)) {
ret = asoc_simple_card_parse_of(priv);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
dev_err(dev, "parse error %d\n", ret);
goto err;
}
} else {
struct asoc_simple_card_info *cinfo;
cinfo = dev->platform_data;
if (!cinfo) {
dev_err(dev, "no info for asoc-simple-card\n");
return -EINVAL;
}
if (!cinfo->name ||
!cinfo->codec_dai.name ||
!cinfo->codec ||
!cinfo->platform ||
!cinfo->cpu_dai.name) {
dev_err(dev, "insufficient asoc_simple_card_info settings\n");
return -EINVAL;
}
card->name = (cinfo->card) ? cinfo->card : cinfo->name;
dai_link->name = cinfo->name;
dai_link->stream_name = cinfo->name;
dai_link->platform_name = cinfo->platform;
dai_link->codec_name = cinfo->codec;
dai_link->cpu_dai_name = cinfo->cpu_dai.name;
dai_link->codec_dai_name = cinfo->codec_dai.name;
dai_link->dai_fmt = cinfo->daifmt;
dai_link->init = asoc_simple_card_dai_init;
memcpy(&priv->dai_props->cpu_dai, &cinfo->cpu_dai,
sizeof(priv->dai_props->cpu_dai));
memcpy(&priv->dai_props->codec_dai, &cinfo->codec_dai,
sizeof(priv->dai_props->codec_dai));
}
snd_soc_card_set_drvdata(card, priv);
ret = devm_snd_soc_register_card(dev, card);
if (ret < 0)
goto err;
return 0;
err:
asoc_simple_card_clean_reference(card);
return ret;
}
static int asoc_simple_card_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct simple_card_data *priv = snd_soc_card_get_drvdata(card);
asoc_simple_card_remove_jack(&priv->hp_jack);
asoc_simple_card_remove_jack(&priv->mic_jack);
return asoc_simple_card_clean_reference(card);
}
static const struct of_device_id asoc_simple_of_match[] = {
{ .compatible = "simple-audio-card", },
{},
};
MODULE_DEVICE_TABLE(of, asoc_simple_of_match);
static struct platform_driver asoc_simple_card = {
.driver = {
.name = "asoc-simple-card",
.pm = &snd_soc_pm_ops,
.of_match_table = asoc_simple_of_match,
},
.probe = asoc_simple_card_probe,
.remove = asoc_simple_card_remove,
};
module_platform_driver(asoc_simple_card);
MODULE_ALIAS("platform:asoc-simple-card");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ASoC Simple Sound Card");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");