Path: blob/master/sound/soc/generic/simple-card-utils.c
26424 views
// SPDX-License-Identifier: GPL-2.01//2// simple-card-utils.c3//4// Copyright (c) 2016 Kuninori Morimoto <[email protected]>56#include <dt-bindings/sound/audio-graph.h>7#include <linux/cleanup.h>8#include <linux/clk.h>9#include <linux/gpio/consumer.h>10#include <linux/module.h>11#include <linux/of.h>12#include <linux/of_graph.h>13#include <sound/jack.h>14#include <sound/pcm_params.h>15#include <sound/simple_card_utils.h>1617#define simple_ret(priv, ret) _simple_ret(priv, __func__, ret)18static inline int _simple_ret(struct simple_util_priv *priv,19const char *func, int ret)20{21return snd_soc_ret(simple_priv_to_dev(priv), ret, "at %s()\n", func);22}2324int simple_util_get_sample_fmt(struct simple_util_data *data)25{26int i;27int val = -EINVAL;2829struct {30char *fmt;31u32 val;32} of_sample_fmt_table[] = {33{ "s8", SNDRV_PCM_FORMAT_S8},34{ "s16_le", SNDRV_PCM_FORMAT_S16_LE},35{ "s24_le", SNDRV_PCM_FORMAT_S24_LE},36{ "s24_3le", SNDRV_PCM_FORMAT_S24_3LE},37{ "s32_le", SNDRV_PCM_FORMAT_S32_LE},38};3940for (i = 0; i < ARRAY_SIZE(of_sample_fmt_table); i++) {41if (!strcmp(data->convert_sample_format,42of_sample_fmt_table[i].fmt)) {43val = of_sample_fmt_table[i].val;44break;45}46}47return val;48}49EXPORT_SYMBOL_GPL(simple_util_get_sample_fmt);5051static void simple_fixup_sample_fmt(struct simple_util_data *data,52struct snd_pcm_hw_params *params)53{54int val;55struct snd_mask *mask = hw_param_mask(params,56SNDRV_PCM_HW_PARAM_FORMAT);5758val = simple_util_get_sample_fmt(data);59if (val >= 0) {60snd_mask_none(mask);61snd_mask_set(mask, val);62}63}6465void simple_util_parse_convert(struct device_node *np,66char *prefix,67struct simple_util_data *data)68{69char prop[128];7071if (!np)72return;7374if (!prefix)75prefix = "";7677/* sampling rate convert */78snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-rate");79of_property_read_u32(np, prop, &data->convert_rate);8081/* channels transfer */82snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-channels");83of_property_read_u32(np, prop, &data->convert_channels);8485/* convert sample format */86snprintf(prop, sizeof(prop), "%s%s", prefix, "convert-sample-format");87of_property_read_string(np, prop, &data->convert_sample_format);88}89EXPORT_SYMBOL_GPL(simple_util_parse_convert);9091/**92* simple_util_is_convert_required() - Query if HW param conversion was requested93* @data: Link data.94*95* Returns true if any HW param conversion was requested for this DAI link with96* any "convert-xxx" properties.97*/98bool simple_util_is_convert_required(const struct simple_util_data *data)99{100return data->convert_rate ||101data->convert_channels ||102data->convert_sample_format;103}104EXPORT_SYMBOL_GPL(simple_util_is_convert_required);105106int simple_util_parse_daifmt(struct device *dev,107struct device_node *node,108struct device_node *codec,109char *prefix,110unsigned int *retfmt)111{112struct device_node *bitclkmaster = NULL;113struct device_node *framemaster = NULL;114unsigned int daifmt;115116daifmt = snd_soc_daifmt_parse_format(node, prefix);117118snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster);119if (!bitclkmaster && !framemaster) {120/*121* No dai-link level and master setting was not found from122* sound node level, revert back to legacy DT parsing and123* take the settings from codec node.124*/125dev_dbg(dev, "Revert to legacy daifmt parsing\n");126127daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);128} else {129daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(130((codec == bitclkmaster) << 4) | (codec == framemaster));131}132133of_node_put(bitclkmaster);134of_node_put(framemaster);135136*retfmt = daifmt;137138return 0;139}140EXPORT_SYMBOL_GPL(simple_util_parse_daifmt);141142int simple_util_parse_tdm_width_map(struct simple_util_priv *priv, struct device_node *np,143struct simple_util_dai *dai)144{145struct device *dev = simple_priv_to_dev(priv);146int n, i, ret;147u32 *p;148149/*150* NOTE151*152* Clang doesn't allow to use "goto end" before calling __free(),153* because it bypasses the initialization. Use simple_ret() directly.154*/155156n = of_property_count_elems_of_size(np, "dai-tdm-slot-width-map", sizeof(u32));157if (n <= 0)158return 0;159160if (n % 3) {161dev_err(dev, "Invalid number of cells for dai-tdm-slot-width-map\n");162return simple_ret(priv, -EINVAL); /* see NOTE */163}164165ret = -ENOMEM;166dai->tdm_width_map = devm_kcalloc(dev, n, sizeof(*dai->tdm_width_map), GFP_KERNEL);167if (!dai->tdm_width_map)168return simple_ret(priv, ret); /* see NOTE */169170u32 *array_values __free(kfree) = kcalloc(n, sizeof(*array_values), GFP_KERNEL);171if (!array_values)172goto end;173174ret = of_property_read_u32_array(np, "dai-tdm-slot-width-map", array_values, n);175if (ret < 0) {176dev_err(dev, "Could not read dai-tdm-slot-width-map: %d\n", ret);177goto end;178}179180p = array_values;181for (i = 0; i < n / 3; ++i) {182dai->tdm_width_map[i].sample_bits = *p++;183dai->tdm_width_map[i].slot_width = *p++;184dai->tdm_width_map[i].slot_count = *p++;185}186187dai->n_tdm_widths = i;188ret = 0;189end:190return simple_ret(priv, ret);191}192EXPORT_SYMBOL_GPL(simple_util_parse_tdm_width_map);193194int simple_util_set_dailink_name(struct simple_util_priv *priv,195struct snd_soc_dai_link *dai_link,196const char *fmt, ...)197{198struct device *dev = simple_priv_to_dev(priv);199va_list ap;200char *name = NULL;201int ret = -ENOMEM;202203va_start(ap, fmt);204name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);205va_end(ap);206207if (name) {208ret = 0;209210dai_link->name = name;211dai_link->stream_name = name;212}213214return simple_ret(priv, ret);215}216EXPORT_SYMBOL_GPL(simple_util_set_dailink_name);217218int simple_util_parse_card_name(struct simple_util_priv *priv,219char *prefix)220{221struct snd_soc_card *card = simple_priv_to_card(priv);222int ret;223224if (!prefix)225prefix = "";226227/* Parse the card name from DT */228ret = snd_soc_of_parse_card_name(card, "label");229if (ret < 0 || !card->name) {230char prop[128];231232snprintf(prop, sizeof(prop), "%sname", prefix);233ret = snd_soc_of_parse_card_name(card, prop);234if (ret < 0)235goto end;236}237238if (!card->name && card->dai_link)239card->name = card->dai_link->name;240end:241return simple_ret(priv, ret);242}243EXPORT_SYMBOL_GPL(simple_util_parse_card_name);244245static int simple_clk_enable(struct simple_util_dai *dai)246{247if (dai)248return clk_prepare_enable(dai->clk);249250return 0;251}252253static void simple_clk_disable(struct simple_util_dai *dai)254{255if (dai)256clk_disable_unprepare(dai->clk);257}258259int simple_util_parse_clk(struct device *dev,260struct device_node *node,261struct simple_util_dai *simple_dai,262struct snd_soc_dai_link_component *dlc)263{264struct clk *clk;265u32 val;266267/*268* Parse dai->sysclk come from "clocks = <&xxx>"269* (if system has common clock)270* or "system-clock-frequency = <xxx>"271* or device's module clock.272*/273clk = devm_get_clk_from_child(dev, node, NULL);274simple_dai->clk_fixed = of_property_read_bool(275node, "system-clock-fixed");276if (!IS_ERR(clk)) {277simple_dai->sysclk = clk_get_rate(clk);278279simple_dai->clk = clk;280} else if (!of_property_read_u32(node, "system-clock-frequency", &val)) {281simple_dai->sysclk = val;282simple_dai->clk_fixed = true;283} else {284clk = devm_get_clk_from_child(dev, dlc->of_node, NULL);285if (!IS_ERR(clk))286simple_dai->sysclk = clk_get_rate(clk);287}288289if (of_property_read_bool(node, "system-clock-direction-out"))290simple_dai->clk_direction = SND_SOC_CLOCK_OUT;291292return 0;293}294EXPORT_SYMBOL_GPL(simple_util_parse_clk);295296static int simple_check_fixed_sysclk(struct device *dev,297struct simple_util_dai *dai,298unsigned int *fixed_sysclk)299{300if (dai->clk_fixed) {301if (*fixed_sysclk && *fixed_sysclk != dai->sysclk) {302dev_err(dev, "inconsistent fixed sysclk rates (%u vs %u)\n",303*fixed_sysclk, dai->sysclk);304return -EINVAL;305}306*fixed_sysclk = dai->sysclk;307}308309return 0;310}311312int simple_util_startup(struct snd_pcm_substream *substream)313{314struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);315struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);316struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);317struct simple_util_dai *dai;318unsigned int fixed_sysclk = 0;319int i1, i2, i;320int ret;321322for_each_prop_dai_cpu(props, i1, dai) {323ret = simple_clk_enable(dai);324if (ret)325goto cpu_err;326ret = simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);327if (ret)328goto cpu_err;329}330331for_each_prop_dai_codec(props, i2, dai) {332ret = simple_clk_enable(dai);333if (ret)334goto codec_err;335ret = simple_check_fixed_sysclk(rtd->dev, dai, &fixed_sysclk);336if (ret)337goto codec_err;338}339340if (fixed_sysclk && props->mclk_fs) {341unsigned int fixed_rate = fixed_sysclk / props->mclk_fs;342343if (fixed_sysclk % props->mclk_fs) {344dev_err(rtd->dev, "fixed sysclk %u not divisible by mclk_fs %u\n",345fixed_sysclk, props->mclk_fs);346ret = -EINVAL;347goto codec_err;348}349ret = snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_RATE,350fixed_rate, fixed_rate);351if (ret < 0)352goto codec_err;353}354355return 0;356357codec_err:358for_each_prop_dai_codec(props, i, dai) {359if (i >= i2)360break;361simple_clk_disable(dai);362}363cpu_err:364for_each_prop_dai_cpu(props, i, dai) {365if (i >= i1)366break;367simple_clk_disable(dai);368}369370return simple_ret(priv, ret);371}372EXPORT_SYMBOL_GPL(simple_util_startup);373374void simple_util_shutdown(struct snd_pcm_substream *substream)375{376struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);377struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);378struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);379struct simple_util_dai *dai;380int i;381382for_each_prop_dai_cpu(props, i, dai) {383struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, i);384385if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(cpu_dai))386snd_soc_dai_set_sysclk(cpu_dai, 0, 0, dai->clk_direction);387388simple_clk_disable(dai);389}390for_each_prop_dai_codec(props, i, dai) {391struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, i);392393if (props->mclk_fs && !dai->clk_fixed && !snd_soc_dai_active(codec_dai))394snd_soc_dai_set_sysclk(codec_dai, 0, 0, dai->clk_direction);395396simple_clk_disable(dai);397}398}399EXPORT_SYMBOL_GPL(simple_util_shutdown);400401static int simple_set_clk_rate(struct simple_util_priv *priv,402struct simple_util_dai *simple_dai,403unsigned long rate)404{405struct device *dev = simple_priv_to_dev(priv);406int ret = -EINVAL;407408if (!simple_dai)409return 0;410411if (simple_dai->clk_fixed && rate != simple_dai->sysclk) {412dev_err(dev, "dai %s invalid clock rate %lu\n", simple_dai->name, rate);413goto end;414}415416if (!simple_dai->clk)417return 0;418419if (clk_get_rate(simple_dai->clk) == rate)420return 0;421422ret = clk_set_rate(simple_dai->clk, rate);423end:424return simple_ret(priv, ret);425}426427static int simple_set_tdm(struct simple_util_priv *priv,428struct snd_soc_dai *dai,429struct simple_util_dai *simple_dai,430struct snd_pcm_hw_params *params)431{432int sample_bits = params_width(params);433int slot_width, slot_count;434int i, ret;435436if (!simple_dai || !simple_dai->tdm_width_map)437return 0;438439slot_width = simple_dai->slot_width;440slot_count = simple_dai->slots;441442if (slot_width == 0)443slot_width = sample_bits;444445for (i = 0; i < simple_dai->n_tdm_widths; ++i) {446if (simple_dai->tdm_width_map[i].sample_bits == sample_bits) {447slot_width = simple_dai->tdm_width_map[i].slot_width;448slot_count = simple_dai->tdm_width_map[i].slot_count;449break;450}451}452453ret = snd_soc_dai_set_tdm_slot(dai,454simple_dai->tx_slot_mask,455simple_dai->rx_slot_mask,456slot_count,457slot_width);458459return simple_ret(priv, ret);460}461462int simple_util_hw_params(struct snd_pcm_substream *substream,463struct snd_pcm_hw_params *params)464{465struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);466struct simple_util_dai *pdai;467struct snd_soc_dai *sdai;468struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);469struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);470unsigned int mclk, mclk_fs = 0;471int i, ret;472473if (props->mclk_fs)474mclk_fs = props->mclk_fs;475476if (mclk_fs) {477struct snd_soc_component *component;478mclk = params_rate(params) * mclk_fs;479480for_each_prop_dai_codec(props, i, pdai) {481ret = simple_set_clk_rate(priv, pdai, mclk);482if (ret < 0)483goto end;484}485486for_each_prop_dai_cpu(props, i, pdai) {487ret = simple_set_clk_rate(priv, pdai, mclk);488if (ret < 0)489goto end;490}491492/* Ensure sysclk is set on all components in case any493* (such as platform components) are missed by calls to494* snd_soc_dai_set_sysclk.495*/496for_each_rtd_components(rtd, i, component) {497ret = snd_soc_component_set_sysclk(component, 0, 0,498mclk, SND_SOC_CLOCK_IN);499if (ret && ret != -ENOTSUPP)500goto end;501}502503for_each_rtd_codec_dais(rtd, i, sdai) {504pdai = simple_props_to_dai_codec(props, i);505ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);506if (ret && ret != -ENOTSUPP)507goto end;508}509510for_each_rtd_cpu_dais(rtd, i, sdai) {511pdai = simple_props_to_dai_cpu(props, i);512ret = snd_soc_dai_set_sysclk(sdai, 0, mclk, pdai->clk_direction);513if (ret && ret != -ENOTSUPP)514goto end;515}516}517518for_each_prop_dai_codec(props, i, pdai) {519sdai = snd_soc_rtd_to_codec(rtd, i);520ret = simple_set_tdm(priv, sdai, pdai, params);521if (ret < 0)522goto end;523}524525for_each_prop_dai_cpu(props, i, pdai) {526sdai = snd_soc_rtd_to_cpu(rtd, i);527ret = simple_set_tdm(priv, sdai, pdai, params);528if (ret < 0)529goto end;530}531ret = 0;532end:533return simple_ret(priv, ret);534}535EXPORT_SYMBOL_GPL(simple_util_hw_params);536537int simple_util_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,538struct snd_pcm_hw_params *params)539{540struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);541struct simple_dai_props *dai_props = runtime_simple_priv_to_props(priv, rtd);542struct simple_util_data *data = &dai_props->adata;543struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);544struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);545546if (data->convert_rate)547rate->min =548rate->max = data->convert_rate;549550if (data->convert_channels)551channels->min =552channels->max = data->convert_channels;553554if (data->convert_sample_format)555simple_fixup_sample_fmt(data, params);556557return 0;558}559EXPORT_SYMBOL_GPL(simple_util_be_hw_params_fixup);560561static int simple_init_dai(struct simple_util_priv *priv,562struct snd_soc_dai *dai, struct simple_util_dai *simple_dai)563{564int ret;565566if (!simple_dai)567return 0;568569if (simple_dai->sysclk) {570ret = snd_soc_dai_set_sysclk(dai, 0, simple_dai->sysclk,571simple_dai->clk_direction);572if (ret && ret != -ENOTSUPP) {573dev_err(dai->dev, "simple-card: set_sysclk error\n");574goto end;575}576}577578if (simple_dai->slots) {579ret = snd_soc_dai_set_tdm_slot(dai,580simple_dai->tx_slot_mask,581simple_dai->rx_slot_mask,582simple_dai->slots,583simple_dai->slot_width);584if (ret && ret != -ENOTSUPP) {585dev_err(dai->dev, "simple-card: set_tdm_slot error\n");586goto end;587}588}589ret = 0;590end:591return simple_ret(priv, ret);592}593594static inline int simple_component_is_codec(struct snd_soc_component *component)595{596return component->driver->endianness;597}598599static int simple_init_for_codec2codec(struct simple_util_priv *priv,600struct snd_soc_pcm_runtime *rtd,601struct simple_dai_props *dai_props)602{603struct snd_soc_dai_link *dai_link = rtd->dai_link;604struct snd_soc_component *component;605struct snd_soc_pcm_stream *c2c_params;606struct snd_pcm_hardware hw;607int i, ret, stream;608609/* Do nothing if it already has Codec2Codec settings */610if (dai_link->c2c_params)611return 0;612613/* Do nothing if it was DPCM :: BE */614if (dai_link->no_pcm)615return 0;616617/* Only Codecs */618for_each_rtd_components(rtd, i, component) {619if (!simple_component_is_codec(component))620return 0;621}622623/* Assumes the capabilities are the same for all supported streams */624for_each_pcm_streams(stream) {625ret = snd_soc_runtime_calc_hw(rtd, &hw, stream);626if (ret == 0)627break;628}629630if (ret < 0) {631dev_err(rtd->dev, "simple-card: no valid dai_link params\n");632goto end;633}634635ret = -ENOMEM;636c2c_params = devm_kzalloc(rtd->dev, sizeof(*c2c_params), GFP_KERNEL);637if (!c2c_params)638goto end;639640c2c_params->formats = hw.formats;641c2c_params->rates = hw.rates;642c2c_params->rate_min = hw.rate_min;643c2c_params->rate_max = hw.rate_max;644c2c_params->channels_min = hw.channels_min;645c2c_params->channels_max = hw.channels_max;646647dai_link->c2c_params = c2c_params;648dai_link->num_c2c_params = 1;649650ret = 0;651end:652return simple_ret(priv, ret);653}654655int simple_util_dai_init(struct snd_soc_pcm_runtime *rtd)656{657struct simple_util_priv *priv = snd_soc_card_get_drvdata(rtd->card);658struct simple_dai_props *props = runtime_simple_priv_to_props(priv, rtd);659struct simple_util_dai *dai;660int i, ret;661662for_each_prop_dai_codec(props, i, dai) {663ret = simple_init_dai(priv, snd_soc_rtd_to_codec(rtd, i), dai);664if (ret < 0)665goto end;666}667for_each_prop_dai_cpu(props, i, dai) {668ret = simple_init_dai(priv, snd_soc_rtd_to_cpu(rtd, i), dai);669if (ret < 0)670goto end;671}672673ret = simple_init_for_codec2codec(priv, rtd, props);674end:675return simple_ret(priv, ret);676}677EXPORT_SYMBOL_GPL(simple_util_dai_init);678679void simple_util_canonicalize_platform(struct snd_soc_dai_link_component *platforms,680struct snd_soc_dai_link_component *cpus)681{682/*683* Assumes Platform == CPU684*685* Some CPU might be using soc-generic-dmaengine-pcm. This means CPU and Platform686* are different Component, but are sharing same component->dev.687*688* Let's assume Platform is same as CPU if it doesn't identify Platform on DT.689* see690* simple-card.c :: simple_count_noml()691*/692if (!platforms->of_node)693snd_soc_dlc_use_cpu_as_platform(platforms, cpus);694}695EXPORT_SYMBOL_GPL(simple_util_canonicalize_platform);696697void simple_util_canonicalize_cpu(struct snd_soc_dai_link_component *cpus,698int is_single_links)699{700/*701* In soc_bind_dai_link() will check cpu name after702* of_node matching if dai_link has cpu_dai_name.703* but, it will never match if name was created by704* fmt_single_name() remove cpu_dai_name if cpu_args705* was 0. See:706* fmt_single_name()707* fmt_multiple_name()708*/709if (is_single_links)710cpus->dai_name = NULL;711}712EXPORT_SYMBOL_GPL(simple_util_canonicalize_cpu);713714void simple_util_clean_reference(struct snd_soc_card *card)715{716struct snd_soc_dai_link *dai_link;717struct snd_soc_dai_link_component *cpu;718struct snd_soc_dai_link_component *codec;719int i, j;720721for_each_card_prelinks(card, i, dai_link) {722for_each_link_cpus(dai_link, j, cpu)723of_node_put(cpu->of_node);724for_each_link_codecs(dai_link, j, codec)725of_node_put(codec->of_node);726}727}728EXPORT_SYMBOL_GPL(simple_util_clean_reference);729730int simple_util_parse_routing(struct snd_soc_card *card,731char *prefix)732{733struct device_node *node = card->dev->of_node;734char prop[128];735736if (!prefix)737prefix = "";738739snprintf(prop, sizeof(prop), "%s%s", prefix, "routing");740741if (!of_property_present(node, prop))742return 0;743744return snd_soc_of_parse_audio_routing(card, prop);745}746EXPORT_SYMBOL_GPL(simple_util_parse_routing);747748int simple_util_parse_widgets(struct snd_soc_card *card,749char *prefix)750{751struct device_node *node = card->dev->of_node;752char prop[128];753754if (!prefix)755prefix = "";756757snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets");758759if (of_property_present(node, prop))760return snd_soc_of_parse_audio_simple_widgets(card, prop);761762/* no widgets is not error */763return 0;764}765EXPORT_SYMBOL_GPL(simple_util_parse_widgets);766767int simple_util_parse_pin_switches(struct snd_soc_card *card,768char *prefix)769{770char prop[128];771772if (!prefix)773prefix = "";774775snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches");776777return snd_soc_of_parse_pin_switches(card, prop);778}779EXPORT_SYMBOL_GPL(simple_util_parse_pin_switches);780781int simple_util_init_jack(struct snd_soc_card *card,782struct simple_util_jack *sjack,783int is_hp, char *prefix,784char *pin)785{786struct device *dev = card->dev;787struct gpio_desc *desc;788char prop[128];789char *pin_name;790char *gpio_name;791int mask;792int error;793794if (!prefix)795prefix = "";796797if (is_hp) {798snprintf(prop, sizeof(prop), "%shp-det", prefix);799pin_name = pin ? pin : "Headphones";800gpio_name = "Headphone detection";801mask = SND_JACK_HEADPHONE;802} else {803snprintf(prop, sizeof(prop), "%smic-det", prefix);804pin_name = pin ? pin : "Mic Jack";805gpio_name = "Mic detection";806mask = SND_JACK_MICROPHONE;807}808809desc = gpiod_get_optional(dev, prop, GPIOD_IN);810error = PTR_ERR_OR_ZERO(desc);811if (error)812return error;813814if (desc) {815error = gpiod_set_consumer_name(desc, gpio_name);816if (error)817return error;818819sjack->pin.pin = pin_name;820sjack->pin.mask = mask;821822sjack->gpio.name = gpio_name;823sjack->gpio.report = mask;824sjack->gpio.desc = desc;825sjack->gpio.debounce_time = 150;826827snd_soc_card_jack_new_pins(card, pin_name, mask, &sjack->jack,828&sjack->pin, 1);829830snd_soc_jack_add_gpios(&sjack->jack, 1, &sjack->gpio);831}832833return 0;834}835EXPORT_SYMBOL_GPL(simple_util_init_jack);836837int simple_util_init_aux_jacks(struct simple_util_priv *priv, char *prefix)838{839struct snd_soc_card *card = simple_priv_to_card(priv);840struct snd_soc_component *component;841int found_jack_index = 0;842int type = 0;843int num = 0;844int ret;845846if (priv->aux_jacks)847return 0;848849for_each_card_auxs(card, component) {850type = snd_soc_component_get_jack_type(component);851if (type > 0)852num++;853}854if (num < 1)855return 0;856857priv->aux_jacks = devm_kcalloc(card->dev, num,858sizeof(struct snd_soc_jack), GFP_KERNEL);859if (!priv->aux_jacks)860return simple_ret(priv, -ENOMEM);861862for_each_card_auxs(card, component) {863char id[128];864struct snd_soc_jack *jack;865866if (found_jack_index >= num)867break;868869type = snd_soc_component_get_jack_type(component);870if (type <= 0)871continue;872873/* create jack */874jack = &(priv->aux_jacks[found_jack_index++]);875snprintf(id, sizeof(id), "%s-jack", component->name);876ret = snd_soc_card_jack_new(card, id, type, jack);877if (ret)878continue;879880(void)snd_soc_component_set_jack(component, jack, NULL);881}882return 0;883}884EXPORT_SYMBOL_GPL(simple_util_init_aux_jacks);885886static struct simple_util_dai dummy_util_dais = {887.name = "dummy_util_dais",888};889890int simple_util_init_priv(struct simple_util_priv *priv,891struct link_info *li)892{893struct snd_soc_card *card = simple_priv_to_card(priv);894struct device *dev = simple_priv_to_dev(priv);895struct snd_soc_dai_link *dai_link;896struct simple_dai_props *dai_props;897struct simple_util_dai *dais;898struct snd_soc_dai_link_component *dlcs;899struct snd_soc_codec_conf *cconf = NULL;900int i, dai_num = 0, dlc_num = 0, cnf_num = 0;901902dai_props = devm_kcalloc(dev, li->link, sizeof(*dai_props), GFP_KERNEL);903dai_link = devm_kcalloc(dev, li->link, sizeof(*dai_link), GFP_KERNEL);904if (!dai_props || !dai_link)905return -ENOMEM;906907/*908* dais (= CPU+Codec)909* dlcs (= CPU+Codec+Platform)910*/911for (i = 0; i < li->link; i++) {912int cc = li->num[i].cpus + li->num[i].codecs;913914dai_num += cc;915dlc_num += cc + li->num[i].platforms;916917if (!li->num[i].cpus)918cnf_num += li->num[i].codecs;919}920921dais = devm_kcalloc(dev, dai_num, sizeof(*dais), GFP_KERNEL);922dlcs = devm_kcalloc(dev, dlc_num, sizeof(*dlcs), GFP_KERNEL);923if (!dais || !dlcs)924return -ENOMEM;925926if (cnf_num) {927cconf = devm_kcalloc(dev, cnf_num, sizeof(*cconf), GFP_KERNEL);928if (!cconf)929return -ENOMEM;930}931932dev_dbg(dev, "link %d, dais %d, ccnf %d\n",933li->link, dai_num, cnf_num);934935priv->dai_props = dai_props;936priv->dai_link = dai_link;937priv->dais = dais;938priv->dlcs = dlcs;939priv->codec_conf = cconf;940941card->dai_link = priv->dai_link;942card->num_links = li->link;943card->codec_conf = cconf;944card->num_configs = cnf_num;945946for (i = 0; i < li->link; i++) {947if (li->num[i].cpus) {948/* Normal CPU */949dai_link[i].cpus = dlcs;950dai_props[i].num.cpus =951dai_link[i].num_cpus = li->num[i].cpus;952dai_props[i].cpu_dai = dais;953954dlcs += li->num[i].cpus;955dais += li->num[i].cpus;956} else {957/* DPCM Be's CPU = dummy */958dai_link[i].cpus = &snd_soc_dummy_dlc;959dai_props[i].num.cpus =960dai_link[i].num_cpus = 1;961dai_props[i].cpu_dai = &dummy_util_dais;962}963964if (li->num[i].codecs) {965/* Normal Codec */966dai_link[i].codecs = dlcs;967dai_props[i].num.codecs =968dai_link[i].num_codecs = li->num[i].codecs;969dai_props[i].codec_dai = dais;970971dlcs += li->num[i].codecs;972dais += li->num[i].codecs;973974if (!li->num[i].cpus) {975/* DPCM Be's Codec */976dai_props[i].codec_conf = cconf;977cconf += li->num[i].codecs;978}979} else {980/* DPCM Fe's Codec = dummy */981dai_link[i].codecs = &snd_soc_dummy_dlc;982dai_props[i].num.codecs =983dai_link[i].num_codecs = 1;984dai_props[i].codec_dai = &dummy_util_dais;985}986987if (li->num[i].platforms) {988/* Have Platform */989dai_link[i].platforms = dlcs;990dai_props[i].num.platforms =991dai_link[i].num_platforms = li->num[i].platforms;992993dlcs += li->num[i].platforms;994} else {995/* Doesn't have Platform */996dai_link[i].platforms = NULL;997dai_props[i].num.platforms =998dai_link[i].num_platforms = 0;999}1000}10011002return 0;1003}1004EXPORT_SYMBOL_GPL(simple_util_init_priv);10051006void simple_util_remove(struct platform_device *pdev)1007{1008struct snd_soc_card *card = platform_get_drvdata(pdev);10091010simple_util_clean_reference(card);1011}1012EXPORT_SYMBOL_GPL(simple_util_remove);10131014int graph_util_card_probe(struct snd_soc_card *card)1015{1016struct simple_util_priv *priv = snd_soc_card_get_drvdata(card);1017int ret;10181019ret = simple_util_init_hp(card, &priv->hp_jack, NULL);1020if (ret < 0)1021goto end;10221023ret = simple_util_init_mic(card, &priv->mic_jack, NULL);1024end:1025return simple_ret(priv, ret);1026}1027EXPORT_SYMBOL_GPL(graph_util_card_probe);10281029int graph_util_is_ports0(struct device_node *np)1030{1031struct device_node *parent __free(device_node) = of_get_parent(np);1032struct device_node *port;10331034/* np is "endpoint" or "port" */1035if (of_node_name_eq(np, "endpoint"))1036port = parent;1037else1038port = np;10391040struct device_node *ports __free(device_node) = of_get_parent(port);1041struct device_node *top __free(device_node) = of_get_parent(ports);1042struct device_node *ports0 __free(device_node) = of_get_child_by_name(top, "ports");10431044return ports0 == ports;1045}1046EXPORT_SYMBOL_GPL(graph_util_is_ports0);10471048static int graph_get_dai_id(struct device_node *ep)1049{1050struct device_node *node __free(device_node) = of_graph_get_port_parent(ep);1051struct device_node *port __free(device_node) = of_get_parent(ep);1052struct of_endpoint info;1053int i, id;1054int ret;10551056/* use driver specified DAI ID if exist */1057ret = snd_soc_get_dai_id(ep);1058if (ret != -ENOTSUPP)1059return ret;10601061/* use endpoint/port reg if exist */1062ret = of_graph_parse_endpoint(ep, &info);1063if (ret == 0) {1064/*1065* Because it will count port/endpoint if it doesn't have "reg".1066* But, we can't judge whether it has "no reg", or "reg = <0>"1067* only of_graph_parse_endpoint().1068* We need to check "reg" property1069*/10701071/* check port first */1072ret = of_property_present(port, "reg");1073if (ret)1074return info.port;10751076/* check endpoint 2nd as backup */1077if (of_property_present(ep, "reg"))1078return info.id;1079}10801081/*1082* Non HDMI sound case, counting port/endpoint on its DT1083* is enough. Let's count it.1084*/1085i = 0;1086id = -1;1087for_each_of_graph_port(node, p) {1088if (port == p) {1089id = i;1090break;1091}1092i++;1093}10941095if (id < 0)1096return -ENODEV;10971098return id;1099}11001101int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep,1102struct snd_soc_dai_link_component *dlc, int *is_single_link)1103{1104struct device *dev = simple_priv_to_dev(priv);1105struct device_node *node;1106struct of_phandle_args args = {};1107struct snd_soc_dai *dai;1108int ret;11091110if (!ep)1111return 0;11121113node = of_graph_get_port_parent(ep);11141115/*1116* Try to find from DAI node1117*/1118args.np = ep;1119dai = snd_soc_get_dai_via_args(&args);1120if (dai) {1121const char *dai_name = snd_soc_dai_name_get(dai);1122const struct of_phandle_args *dai_args = snd_soc_copy_dai_args(dev, &args);11231124ret = -ENOMEM;1125if (!dai_args)1126goto err;11271128dlc->of_node = node;1129dlc->dai_name = dai_name;1130dlc->dai_args = dai_args;11311132goto parse_dai_end;1133}11341135/* Get dai->name */1136args.np = node;1137args.args[0] = graph_get_dai_id(ep);1138args.args_count = (of_graph_get_endpoint_count(node) > 1);11391140/*1141* FIXME1142*1143* Here, dlc->dai_name is pointer to CPU/Codec DAI name.1144* If user unbinded CPU or Codec driver, but not for Sound Card,1145* dlc->dai_name is keeping unbinded CPU or Codec1146* driver's pointer.1147*1148* If user re-bind CPU or Codec driver again, ALSA SoC will try1149* to rebind Card via snd_soc_try_rebind_card(), but because of1150* above reason, it might can't bind Sound Card.1151* Because Sound Card is pointing to released dai_name pointer.1152*1153* To avoid this rebind Card issue,1154* 1) It needs to alloc memory to keep dai_name eventhough1155* CPU or Codec driver was unbinded, or1156* 2) user need to rebind Sound Card everytime1157* if he unbinded CPU or Codec.1158*/1159ret = snd_soc_get_dlc(&args, dlc);1160if (ret < 0)1161goto err;11621163parse_dai_end:1164if (is_single_link)1165*is_single_link = of_graph_get_endpoint_count(node) == 1;1166ret = 0;1167err:1168if (ret < 0)1169of_node_put(node);11701171return simple_ret(priv, ret);1172}1173EXPORT_SYMBOL_GPL(graph_util_parse_dai);11741175void graph_util_parse_link_direction(struct device_node *np,1176bool *playback_only, bool *capture_only)1177{1178bool is_playback_only = of_property_read_bool(np, "playback-only");1179bool is_capture_only = of_property_read_bool(np, "capture-only");11801181if (playback_only)1182*playback_only = is_playback_only;1183if (capture_only)1184*capture_only = is_capture_only;1185}1186EXPORT_SYMBOL_GPL(graph_util_parse_link_direction);11871188static enum snd_soc_trigger_order1189__graph_util_parse_trigger_order(struct simple_util_priv *priv,1190struct device_node *np,1191const char *prop)1192{1193u32 val[SND_SOC_TRIGGER_SIZE];1194int ret;11951196ret = of_property_read_u32_array(np, prop, val, SND_SOC_TRIGGER_SIZE);1197if (ret == 0) {1198struct device *dev = simple_priv_to_dev(priv);1199u32 order = (val[0] << 8) +1200(val[1] << 4) +1201(val[2]);12021203switch (order) {1204case (SND_SOC_TRIGGER_LINK << 8) +1205(SND_SOC_TRIGGER_COMPONENT << 4) +1206(SND_SOC_TRIGGER_DAI):1207return SND_SOC_TRIGGER_ORDER_DEFAULT;12081209case (SND_SOC_TRIGGER_LINK << 8) +1210(SND_SOC_TRIGGER_DAI << 4) +1211(SND_SOC_TRIGGER_COMPONENT):1212return SND_SOC_TRIGGER_ORDER_LDC;12131214default:1215dev_err(dev, "unsupported trigger order [0x%x]\n", order);1216}1217}12181219/* SND_SOC_TRIGGER_ORDER_MAX means error */1220return SND_SOC_TRIGGER_ORDER_MAX;1221}12221223void graph_util_parse_trigger_order(struct simple_util_priv *priv,1224struct device_node *np,1225enum snd_soc_trigger_order *trigger_start,1226enum snd_soc_trigger_order *trigger_stop)1227{1228static enum snd_soc_trigger_order order;12291230/*1231* We can use it like below1232*1233* #include <dt-bindings/sound/audio-graph.h>1234*1235* link-trigger-order = <SND_SOC_TRIGGER_LINK1236* SND_SOC_TRIGGER_COMPONENT1237* SND_SOC_TRIGGER_DAI>;1238*/12391240order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order");1241if (order < SND_SOC_TRIGGER_ORDER_MAX) {1242*trigger_start = order;1243*trigger_stop = order;1244}12451246order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order-start");1247if (order < SND_SOC_TRIGGER_ORDER_MAX)1248*trigger_start = order;12491250order = __graph_util_parse_trigger_order(priv, np, "link-trigger-order-stop");1251if (order < SND_SOC_TRIGGER_ORDER_MAX)1252*trigger_stop = order;12531254return;1255}1256EXPORT_SYMBOL_GPL(graph_util_parse_trigger_order);12571258/* Module information */1259MODULE_AUTHOR("Kuninori Morimoto <[email protected]>");1260MODULE_DESCRIPTION("ALSA SoC Simple Card Utils");1261MODULE_LICENSE("GPL v2");126212631264