| // SPDX-License-Identifier: GPL-2.0 | 
 | // | 
 | // soc-link.c | 
 | // | 
 | // Copyright (C) 2019 Renesas Electronics Corp. | 
 | // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | 
 | // | 
 | #include <sound/soc.h> | 
 | #include <sound/soc-link.h> | 
 |  | 
 | #define soc_link_ret(rtd, ret) _soc_link_ret(rtd, __func__, ret) | 
 | static inline int _soc_link_ret(struct snd_soc_pcm_runtime *rtd, | 
 | 				const char *func, int ret) | 
 | { | 
 | 	/* Positive, Zero values are not errors */ | 
 | 	if (ret >= 0) | 
 | 		return ret; | 
 |  | 
 | 	/* Negative values might be errors */ | 
 | 	switch (ret) { | 
 | 	case -EPROBE_DEFER: | 
 | 	case -ENOTSUPP: | 
 | 		break; | 
 | 	default: | 
 | 		dev_err(rtd->dev, | 
 | 			"ASoC: error at %s on %s: %d\n", | 
 | 			func, rtd->dai_link->name, ret); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* | 
 |  * We might want to check substream by using list. | 
 |  * In such case, we can update these macros. | 
 |  */ | 
 | #define soc_link_mark_push(rtd, substream, tgt)		((rtd)->mark_##tgt = substream) | 
 | #define soc_link_mark_pop(rtd, substream, tgt)		((rtd)->mark_##tgt = NULL) | 
 | #define soc_link_mark_match(rtd, substream, tgt)	((rtd)->mark_##tgt == substream) | 
 |  | 
 | int snd_soc_link_init(struct snd_soc_pcm_runtime *rtd) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->init) | 
 | 		ret = rtd->dai_link->init(rtd); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | void snd_soc_link_exit(struct snd_soc_pcm_runtime *rtd) | 
 | { | 
 | 	if (rtd->dai_link->exit) | 
 | 		rtd->dai_link->exit(rtd); | 
 | } | 
 |  | 
 | int snd_soc_link_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, | 
 | 				    struct snd_pcm_hw_params *params) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->be_hw_params_fixup) | 
 | 		ret = rtd->dai_link->be_hw_params_fixup(rtd, params); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | int snd_soc_link_startup(struct snd_pcm_substream *substream) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->startup) | 
 | 		ret = rtd->dai_link->ops->startup(substream); | 
 |  | 
 | 	/* mark substream if succeeded */ | 
 | 	if (ret == 0) | 
 | 		soc_link_mark_push(rtd, substream, startup); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | void snd_soc_link_shutdown(struct snd_pcm_substream *substream, | 
 | 			   int rollback) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 |  | 
 | 	if (rollback && !soc_link_mark_match(rtd, substream, startup)) | 
 | 		return; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->shutdown) | 
 | 		rtd->dai_link->ops->shutdown(substream); | 
 |  | 
 | 	/* remove marked substream */ | 
 | 	soc_link_mark_pop(rtd, substream, startup); | 
 | } | 
 |  | 
 | int snd_soc_link_prepare(struct snd_pcm_substream *substream) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->prepare) | 
 | 		ret = rtd->dai_link->ops->prepare(substream); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | int snd_soc_link_hw_params(struct snd_pcm_substream *substream, | 
 | 			   struct snd_pcm_hw_params *params) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->hw_params) | 
 | 		ret = rtd->dai_link->ops->hw_params(substream, params); | 
 |  | 
 | 	/* mark substream if succeeded */ | 
 | 	if (ret == 0) | 
 | 		soc_link_mark_push(rtd, substream, hw_params); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | void snd_soc_link_hw_free(struct snd_pcm_substream *substream, int rollback) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 |  | 
 | 	if (rollback && !soc_link_mark_match(rtd, substream, hw_params)) | 
 | 		return; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->hw_free) | 
 | 		rtd->dai_link->ops->hw_free(substream); | 
 |  | 
 | 	/* remove marked substream */ | 
 | 	soc_link_mark_pop(rtd, substream, hw_params); | 
 | } | 
 |  | 
 | static int soc_link_trigger(struct snd_pcm_substream *substream, int cmd) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->ops && | 
 | 	    rtd->dai_link->ops->trigger) | 
 | 		ret = rtd->dai_link->ops->trigger(substream, cmd); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 |  | 
 | int snd_soc_link_trigger(struct snd_pcm_substream *substream, int cmd, | 
 | 			 int rollback) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); | 
 | 	int ret = 0; | 
 |  | 
 | 	switch (cmd) { | 
 | 	case SNDRV_PCM_TRIGGER_START: | 
 | 	case SNDRV_PCM_TRIGGER_RESUME: | 
 | 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | 
 | 		ret = soc_link_trigger(substream, cmd); | 
 | 		if (ret < 0) | 
 | 			break; | 
 | 		soc_link_mark_push(rtd, substream, trigger); | 
 | 		break; | 
 | 	case SNDRV_PCM_TRIGGER_STOP: | 
 | 	case SNDRV_PCM_TRIGGER_SUSPEND: | 
 | 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | 
 | 		if (rollback && !soc_link_mark_match(rtd, substream, trigger)) | 
 | 			break; | 
 |  | 
 | 		ret = soc_link_trigger(substream, cmd); | 
 | 		soc_link_mark_pop(rtd, substream, startup); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | int snd_soc_link_compr_startup(struct snd_compr_stream *cstream) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = cstream->private_data; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->compr_ops && | 
 | 	    rtd->dai_link->compr_ops->startup) | 
 | 		ret = rtd->dai_link->compr_ops->startup(cstream); | 
 |  | 
 | 	if (ret == 0) | 
 | 		soc_link_mark_push(rtd, cstream, compr_startup); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 | EXPORT_SYMBOL_GPL(snd_soc_link_compr_startup); | 
 |  | 
 | void snd_soc_link_compr_shutdown(struct snd_compr_stream *cstream, | 
 | 				 int rollback) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = cstream->private_data; | 
 |  | 
 | 	if (rollback && !soc_link_mark_match(rtd, cstream, compr_startup)) | 
 | 		return; | 
 |  | 
 | 	if (rtd->dai_link->compr_ops && | 
 | 	    rtd->dai_link->compr_ops->shutdown) | 
 | 		rtd->dai_link->compr_ops->shutdown(cstream); | 
 |  | 
 | 	soc_link_mark_pop(rtd, cstream, compr_startup); | 
 | } | 
 | EXPORT_SYMBOL_GPL(snd_soc_link_compr_shutdown); | 
 |  | 
 | int snd_soc_link_compr_set_params(struct snd_compr_stream *cstream) | 
 | { | 
 | 	struct snd_soc_pcm_runtime *rtd = cstream->private_data; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (rtd->dai_link->compr_ops && | 
 | 	    rtd->dai_link->compr_ops->set_params) | 
 | 		ret = rtd->dai_link->compr_ops->set_params(cstream); | 
 |  | 
 | 	return soc_link_ret(rtd, ret); | 
 | } | 
 | EXPORT_SYMBOL_GPL(snd_soc_link_compr_set_params); |