Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: core: Support transparent CODEC<->CODEC DAI links

Rather than having the user half start a stream but avoid any DMA to
trigger data flow on links which don't pass through the CPU create a
DAPM route between the two DAI widgets using a hw_params configuration
provided by the machine driver with the new 'params' member of the
dai_link struct. If no configuration is provided in the dai_link then
use the old style even for CODEC<->CODEC links to avoid breaking
systems.

This greatly simplifies the userspace usage of such links, making them
as simple as analogue connections with the stream configuration being
completely transparent to them.

This is achieved by defining a new dai_link widget type which is created
when CODECs are linked and triggering the configuration of the link via
the normal PCM operations from there. It is expected that the bias
level callbacks will be used for clock configuration.

Currently only the DAI format, rate and channel count can be configured
and currently the only DAI operations which can be called are hw_params
and digital_mute(). This corresponds well to the majority of CODEC
drivers which only use other callbacks for constraint setting but there
is obviously much room for extension here. We can't simply call
hw_params() on startup as things like the system clocking configuration
may change at runtime and in future it will be desirable to offer some
configurability of the link parameters.

At present we are also restricted to a single DAPM link for the entire
DAI. Once we have better support for channel mapping it would also be
desirable to extend this feature so that we can propagate per-channel
power state over the link.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Liam Girdwood <lrg@ti.com>

+196 -10
+6
include/sound/soc-dapm.h
··· 356 356 int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, 357 357 struct snd_soc_dai *dai); 358 358 int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card); 359 + int snd_soc_dapm_new_pcm(struct snd_soc_card *card, 360 + const struct snd_soc_pcm_stream *params, 361 + struct snd_soc_dapm_widget *source, 362 + struct snd_soc_dapm_widget *sink); 359 363 360 364 /* dapm path setup */ 361 365 int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm); ··· 431 427 snd_soc_dapm_aif_out, /* audio interface output */ 432 428 snd_soc_dapm_siggen, /* signal generator */ 433 429 snd_soc_dapm_dai, /* link to DAI structure */ 430 + snd_soc_dapm_dai_link, /* link between two DAI structures */ 434 431 }; 435 432 436 433 enum snd_soc_dapm_subclass { ··· 490 485 491 486 void *priv; /* widget specific data */ 492 487 struct regulator *regulator; /* attached regulator */ 488 + const struct snd_soc_pcm_stream *params; /* params for dai links */ 493 489 494 490 /* dapm control */ 495 491 int reg; /* negative reg = no direct dapm */
+2
include/sound/soc.h
··· 761 761 const struct device_node *cpu_dai_of_node; 762 762 const char *codec_dai_name; 763 763 764 + const struct snd_soc_pcm_stream *params; 765 + 764 766 unsigned int dai_fmt; /* format to set on init */ 765 767 766 768 /* Keep DAI active over suspend */
+36 -7
sound/soc/soc-core.c
··· 1189 1189 struct snd_soc_pcm_runtime *rtd = &card->rtd[num]; 1190 1190 struct snd_soc_codec *codec = rtd->codec; 1191 1191 struct snd_soc_platform *platform = rtd->platform; 1192 - struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai; 1192 + struct snd_soc_dai *codec_dai = rtd->codec_dai; 1193 + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; 1194 + struct snd_soc_dapm_widget *play_w, *capture_w; 1193 1195 int ret; 1194 1196 1195 1197 dev_dbg(card->dev, "probe %s dai link %d late %d\n", ··· 1272 1270 if (ret < 0) 1273 1271 pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret); 1274 1272 1275 - /* create the pcm */ 1276 - ret = soc_new_pcm(rtd, num); 1277 - if (ret < 0) { 1278 - pr_err("asoc: can't create pcm %s :%d\n", 1279 - dai_link->stream_name, ret); 1280 - return ret; 1273 + if (!dai_link->params) { 1274 + /* create the pcm */ 1275 + ret = soc_new_pcm(rtd, num); 1276 + if (ret < 0) { 1277 + pr_err("asoc: can't create pcm %s :%d\n", 1278 + dai_link->stream_name, ret); 1279 + return ret; 1280 + } 1281 + } else { 1282 + /* link the DAI widgets */ 1283 + play_w = codec_dai->playback_widget; 1284 + capture_w = cpu_dai->capture_widget; 1285 + if (play_w && capture_w) { 1286 + ret = snd_soc_dapm_new_pcm(card, dai_link->params, 1287 + capture_w, play_w); 1288 + if (ret != 0) { 1289 + dev_err(card->dev, "Can't link %s to %s: %d\n", 1290 + play_w->name, capture_w->name, ret); 1291 + return ret; 1292 + } 1293 + } 1294 + 1295 + play_w = cpu_dai->playback_widget; 1296 + capture_w = codec_dai->capture_widget; 1297 + if (play_w && capture_w) { 1298 + ret = snd_soc_dapm_new_pcm(card, dai_link->params, 1299 + capture_w, play_w); 1300 + if (ret != 0) { 1301 + dev_err(card->dev, "Can't link %s to %s: %d\n", 1302 + play_w->name, capture_w->name, ret); 1303 + return ret; 1304 + } 1305 + } 1281 1306 } 1282 1307 1283 1308 /* add platform data for AC97 devices */
+152 -3
sound/soc/soc-dapm.c
··· 52 52 [snd_soc_dapm_supply] = 1, 53 53 [snd_soc_dapm_regulator_supply] = 1, 54 54 [snd_soc_dapm_micbias] = 2, 55 + [snd_soc_dapm_dai_link] = 2, 55 56 [snd_soc_dapm_dai] = 3, 56 57 [snd_soc_dapm_aif_in] = 3, 57 58 [snd_soc_dapm_aif_out] = 3, ··· 89 88 [snd_soc_dapm_aif_in] = 10, 90 89 [snd_soc_dapm_aif_out] = 10, 91 90 [snd_soc_dapm_dai] = 10, 92 - [snd_soc_dapm_regulator_supply] = 11, 93 - [snd_soc_dapm_supply] = 11, 94 - [snd_soc_dapm_post] = 12, 91 + [snd_soc_dapm_dai_link] = 11, 92 + [snd_soc_dapm_regulator_supply] = 12, 93 + [snd_soc_dapm_supply] = 12, 94 + [snd_soc_dapm_post] = 13, 95 95 }; 96 96 97 97 static void pop_wait(u32 pop_time) ··· 396 394 case snd_soc_dapm_mic: 397 395 case snd_soc_dapm_spk: 398 396 case snd_soc_dapm_line: 397 + case snd_soc_dapm_dai_link: 399 398 p->connect = 1; 400 399 break; 401 400 /* does affect routing - dynamically connected */ ··· 2082 2079 case snd_soc_dapm_aif_in: 2083 2080 case snd_soc_dapm_aif_out: 2084 2081 case snd_soc_dapm_dai: 2082 + case snd_soc_dapm_dai_link: 2085 2083 list_add(&path->list, &dapm->card->paths); 2086 2084 list_add(&path->list_sink, &wsink->sources); 2087 2085 list_add(&path->list_source, &wsource->sinks); ··· 2811 2807 case snd_soc_dapm_hp: 2812 2808 case snd_soc_dapm_mic: 2813 2809 case snd_soc_dapm_line: 2810 + case snd_soc_dapm_dai_link: 2814 2811 w->power_check = dapm_generic_check_power; 2815 2812 break; 2816 2813 case snd_soc_dapm_supply: ··· 2875 2870 return ret; 2876 2871 } 2877 2872 EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls); 2873 + 2874 + static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, 2875 + struct snd_kcontrol *kcontrol, int event) 2876 + { 2877 + struct snd_soc_dapm_path *source_p, *sink_p; 2878 + struct snd_soc_dai *source, *sink; 2879 + const struct snd_soc_pcm_stream *config = w->params; 2880 + struct snd_pcm_substream substream; 2881 + struct snd_pcm_hw_params params; 2882 + u64 fmt; 2883 + int ret; 2884 + 2885 + BUG_ON(!config); 2886 + BUG_ON(list_empty(&w->sources) || list_empty(&w->sinks)); 2887 + 2888 + /* We only support a single source and sink, pick the first */ 2889 + source_p = list_first_entry(&w->sources, struct snd_soc_dapm_path, 2890 + list_sink); 2891 + sink_p = list_first_entry(&w->sinks, struct snd_soc_dapm_path, 2892 + list_source); 2893 + 2894 + BUG_ON(!source_p || !sink_p); 2895 + BUG_ON(!sink_p->source || !source_p->sink); 2896 + BUG_ON(!source_p->source || !sink_p->sink); 2897 + 2898 + source = source_p->source->priv; 2899 + sink = sink_p->sink->priv; 2900 + 2901 + /* Be a little careful as we don't want to overflow the mask array */ 2902 + if (config->formats) { 2903 + fmt = ffs(config->formats) - 1; 2904 + } else { 2905 + dev_warn(w->dapm->dev, "Invalid format %lx specified\n", 2906 + config->formats); 2907 + fmt = 0; 2908 + } 2909 + 2910 + /* Currently very limited parameter selection */ 2911 + memset(&params, 0, sizeof(params)); 2912 + snd_mask_set(hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT), fmt); 2913 + 2914 + hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->min = 2915 + config->rate_min; 2916 + hw_param_interval(&params, SNDRV_PCM_HW_PARAM_RATE)->max = 2917 + config->rate_max; 2918 + 2919 + hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->min 2920 + = config->channels_min; 2921 + hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS)->max 2922 + = config->channels_max; 2923 + 2924 + memset(&substream, 0, sizeof(substream)); 2925 + 2926 + switch (event) { 2927 + case SND_SOC_DAPM_PRE_PMU: 2928 + if (source->driver->ops && source->driver->ops->hw_params) { 2929 + substream.stream = SNDRV_PCM_STREAM_CAPTURE; 2930 + ret = source->driver->ops->hw_params(&substream, 2931 + &params, source); 2932 + if (ret != 0) { 2933 + dev_err(source->dev, 2934 + "hw_params() failed: %d\n", ret); 2935 + return ret; 2936 + } 2937 + } 2938 + 2939 + if (sink->driver->ops && sink->driver->ops->hw_params) { 2940 + substream.stream = SNDRV_PCM_STREAM_PLAYBACK; 2941 + ret = sink->driver->ops->hw_params(&substream, &params, 2942 + sink); 2943 + if (ret != 0) { 2944 + dev_err(sink->dev, 2945 + "hw_params() failed: %d\n", ret); 2946 + return ret; 2947 + } 2948 + } 2949 + break; 2950 + 2951 + case SND_SOC_DAPM_POST_PMU: 2952 + ret = snd_soc_dai_digital_mute(sink, 0); 2953 + if (ret != 0 && ret != -ENOTSUPP) 2954 + dev_warn(sink->dev, "Failed to unmute: %d\n", ret); 2955 + break; 2956 + 2957 + case SND_SOC_DAPM_PRE_PMD: 2958 + ret = snd_soc_dai_digital_mute(sink, 1); 2959 + if (ret != 0 && ret != -ENOTSUPP) 2960 + dev_warn(sink->dev, "Failed to mute: %d\n", ret); 2961 + break; 2962 + 2963 + default: 2964 + BUG(); 2965 + return -EINVAL; 2966 + } 2967 + 2968 + return 0; 2969 + } 2970 + 2971 + int snd_soc_dapm_new_pcm(struct snd_soc_card *card, 2972 + const struct snd_soc_pcm_stream *params, 2973 + struct snd_soc_dapm_widget *source, 2974 + struct snd_soc_dapm_widget *sink) 2975 + { 2976 + struct snd_soc_dapm_route routes[2]; 2977 + struct snd_soc_dapm_widget template; 2978 + struct snd_soc_dapm_widget *w; 2979 + size_t len; 2980 + char *link_name; 2981 + 2982 + len = strlen(source->name) + strlen(sink->name) + 2; 2983 + link_name = devm_kzalloc(card->dev, len, GFP_KERNEL); 2984 + if (!link_name) 2985 + return -ENOMEM; 2986 + snprintf(link_name, len, "%s-%s", source->name, sink->name); 2987 + 2988 + memset(&template, 0, sizeof(template)); 2989 + template.reg = SND_SOC_NOPM; 2990 + template.id = snd_soc_dapm_dai_link; 2991 + template.name = link_name; 2992 + template.event = snd_soc_dai_link_event; 2993 + template.event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | 2994 + SND_SOC_DAPM_PRE_PMD; 2995 + 2996 + dev_dbg(card->dev, "adding %s widget\n", link_name); 2997 + 2998 + w = snd_soc_dapm_new_control(&card->dapm, &template); 2999 + if (!w) { 3000 + dev_err(card->dev, "Failed to create %s widget\n", 3001 + link_name); 3002 + return -ENOMEM; 3003 + } 3004 + 3005 + w->params = params; 3006 + 3007 + memset(&routes, 0, sizeof(routes)); 3008 + 3009 + routes[0].source = source->name; 3010 + routes[0].sink = link_name; 3011 + routes[1].source = link_name; 3012 + routes[1].sink = sink->name; 3013 + 3014 + return snd_soc_dapm_add_routes(&card->dapm, routes, 3015 + ARRAY_SIZE(routes)); 3016 + } 2878 3017 2879 3018 int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm, 2880 3019 struct snd_soc_dai *dai)