| /* | 
 |  * ALSA SoC codec for HDMI encoder drivers | 
 |  * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ | 
 |  * Author: Jyri Sarha <jsarha@ti.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. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, but | 
 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU | 
 |  * General Public License for more details. | 
 |  */ | 
 | #include <linux/module.h> | 
 | #include <linux/string.h> | 
 | #include <sound/core.h> | 
 | #include <sound/pcm.h> | 
 | #include <sound/pcm_params.h> | 
 | #include <sound/soc.h> | 
 | #include <sound/pcm_drm_eld.h> | 
 | #include <sound/hdmi-codec.h> | 
 | #include <sound/pcm_iec958.h> | 
 |  | 
 | #include <drm/drm_crtc.h> /* This is only to get MAX_ELD_BYTES */ | 
 |  | 
 | struct hdmi_device { | 
 | 	struct device *dev; | 
 | 	struct list_head list; | 
 | 	int cnt; | 
 | }; | 
 | #define pos_to_hdmi_device(pos)	container_of((pos), struct hdmi_device, list) | 
 | LIST_HEAD(hdmi_device_list); | 
 |  | 
 | #define DAI_NAME_SIZE 16 | 
 | struct hdmi_codec_priv { | 
 | 	struct hdmi_codec_pdata hcd; | 
 | 	struct snd_soc_dai_driver *daidrv; | 
 | 	struct hdmi_codec_daifmt daifmt[2]; | 
 | 	struct mutex current_stream_lock; | 
 | 	struct snd_pcm_substream *current_stream; | 
 | 	struct snd_pcm_hw_constraint_list ratec; | 
 | 	uint8_t eld[MAX_ELD_BYTES]; | 
 | }; | 
 |  | 
 | static const struct snd_soc_dapm_widget hdmi_widgets[] = { | 
 | 	SND_SOC_DAPM_OUTPUT("TX"), | 
 | }; | 
 |  | 
 | static const struct snd_soc_dapm_route hdmi_routes[] = { | 
 | 	{ "TX", NULL, "Playback" }, | 
 | }; | 
 |  | 
 | enum { | 
 | 	DAI_ID_I2S = 0, | 
 | 	DAI_ID_SPDIF, | 
 | }; | 
 |  | 
 | static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol, | 
 | 			     struct snd_ctl_elem_info *uinfo) | 
 | { | 
 | 	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); | 
 |  | 
 | 	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; | 
 | 	uinfo->count = sizeof(hcp->eld); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, | 
 | 			    struct snd_ctl_elem_value *ucontrol) | 
 | { | 
 | 	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); | 
 |  | 
 | 	memcpy(ucontrol->value.bytes.data, hcp->eld, sizeof(hcp->eld)); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct snd_kcontrol_new hdmi_controls[] = { | 
 | 	{ | 
 | 		.access = SNDRV_CTL_ELEM_ACCESS_READ | | 
 | 			  SNDRV_CTL_ELEM_ACCESS_VOLATILE, | 
 | 		.iface = SNDRV_CTL_ELEM_IFACE_PCM, | 
 | 		.name = "ELD", | 
 | 		.info = hdmi_eld_ctl_info, | 
 | 		.get = hdmi_eld_ctl_get, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int hdmi_codec_new_stream(struct snd_pcm_substream *substream, | 
 | 				 struct snd_soc_dai *dai) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 | 	int ret = 0; | 
 |  | 
 | 	mutex_lock(&hcp->current_stream_lock); | 
 | 	if (!hcp->current_stream) { | 
 | 		hcp->current_stream = substream; | 
 | 	} else if (hcp->current_stream != substream) { | 
 | 		dev_err(dai->dev, "Only one simultaneous stream supported!\n"); | 
 | 		ret = -EINVAL; | 
 | 	} | 
 | 	mutex_unlock(&hcp->current_stream_lock); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int hdmi_codec_startup(struct snd_pcm_substream *substream, | 
 | 			      struct snd_soc_dai *dai) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_dbg(dai->dev, "%s()\n", __func__); | 
 |  | 
 | 	ret = hdmi_codec_new_stream(substream, dai); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (hcp->hcd.ops->audio_startup) { | 
 | 		ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); | 
 | 		if (ret) { | 
 | 			mutex_lock(&hcp->current_stream_lock); | 
 | 			hcp->current_stream = NULL; | 
 | 			mutex_unlock(&hcp->current_stream_lock); | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (hcp->hcd.ops->get_eld) { | 
 | 		ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, | 
 | 					    hcp->eld, sizeof(hcp->eld)); | 
 |  | 
 | 		if (!ret) { | 
 | 			ret = snd_pcm_hw_constraint_eld(substream->runtime, | 
 | 							hcp->eld); | 
 | 			if (ret) | 
 | 				return ret; | 
 | 		} | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, | 
 | 				struct snd_soc_dai *dai) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 |  | 
 | 	dev_dbg(dai->dev, "%s()\n", __func__); | 
 |  | 
 | 	WARN_ON(hcp->current_stream != substream); | 
 |  | 
 | 	hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); | 
 |  | 
 | 	mutex_lock(&hcp->current_stream_lock); | 
 | 	hcp->current_stream = NULL; | 
 | 	mutex_unlock(&hcp->current_stream_lock); | 
 | } | 
 |  | 
 | static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, | 
 | 				struct snd_pcm_hw_params *params, | 
 | 				struct snd_soc_dai *dai) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 | 	struct hdmi_codec_params hp = { | 
 | 		.iec = { | 
 | 			.status = { 0 }, | 
 | 			.subcode = { 0 }, | 
 | 			.pad = 0, | 
 | 			.dig_subframe = { 0 }, | 
 | 		} | 
 | 	}; | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(dai->dev, "%s() width %d rate %d channels %d\n", __func__, | 
 | 		params_width(params), params_rate(params), | 
 | 		params_channels(params)); | 
 |  | 
 | 	if (params_width(params) > 24) | 
 | 		params->msbits = 24; | 
 |  | 
 | 	ret = snd_pcm_create_iec958_consumer_hw_params(params, hp.iec.status, | 
 | 						       sizeof(hp.iec.status)); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dai->dev, "Creating IEC958 channel status failed %d\n", | 
 | 			ret); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	ret = hdmi_codec_new_stream(substream, dai); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	hdmi_audio_infoframe_init(&hp.cea); | 
 | 	hp.cea.channels = params_channels(params); | 
 | 	hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; | 
 | 	hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; | 
 | 	hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; | 
 |  | 
 | 	hp.sample_width = params_width(params); | 
 | 	hp.sample_rate = params_rate(params); | 
 | 	hp.channels = params_channels(params); | 
 |  | 
 | 	return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data, | 
 | 				       &hcp->daifmt[dai->id], &hp); | 
 | } | 
 |  | 
 | static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, | 
 | 			      unsigned int fmt) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 | 	struct hdmi_codec_daifmt cf = { 0 }; | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_dbg(dai->dev, "%s()\n", __func__); | 
 |  | 
 | 	if (dai->id == DAI_ID_SPDIF) { | 
 | 		cf.fmt = HDMI_SPDIF; | 
 | 	} else { | 
 | 		switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | 
 | 		case SND_SOC_DAIFMT_CBM_CFM: | 
 | 			cf.bit_clk_master = 1; | 
 | 			cf.frame_clk_master = 1; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_CBS_CFM: | 
 | 			cf.frame_clk_master = 1; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_CBM_CFS: | 
 | 			cf.bit_clk_master = 1; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_CBS_CFS: | 
 | 			break; | 
 | 		default: | 
 | 			return -EINVAL; | 
 | 		} | 
 |  | 
 | 		switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | 
 | 		case SND_SOC_DAIFMT_NB_NF: | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_NB_IF: | 
 | 			cf.frame_clk_inv = 1; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_IB_NF: | 
 | 			cf.bit_clk_inv = 1; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_IB_IF: | 
 | 			cf.frame_clk_inv = 1; | 
 | 			cf.bit_clk_inv = 1; | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | 
 | 		case SND_SOC_DAIFMT_I2S: | 
 | 			cf.fmt = HDMI_I2S; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_DSP_A: | 
 | 			cf.fmt = HDMI_DSP_A; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_DSP_B: | 
 | 			cf.fmt = HDMI_DSP_B; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_RIGHT_J: | 
 | 			cf.fmt = HDMI_RIGHT_J; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_LEFT_J: | 
 | 			cf.fmt = HDMI_LEFT_J; | 
 | 			break; | 
 | 		case SND_SOC_DAIFMT_AC97: | 
 | 			cf.fmt = HDMI_AC97; | 
 | 			break; | 
 | 		default: | 
 | 			dev_err(dai->dev, "Invalid DAI interface format\n"); | 
 | 			return -EINVAL; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	hcp->daifmt[dai->id] = cf; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) | 
 | { | 
 | 	struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | 
 |  | 
 | 	dev_dbg(dai->dev, "%s()\n", __func__); | 
 |  | 
 | 	if (hcp->hcd.ops->digital_mute) | 
 | 		return hcp->hcd.ops->digital_mute(dai->dev->parent, | 
 | 						  hcp->hcd.data, mute); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct snd_soc_dai_ops hdmi_dai_ops = { | 
 | 	.startup	= hdmi_codec_startup, | 
 | 	.shutdown	= hdmi_codec_shutdown, | 
 | 	.hw_params	= hdmi_codec_hw_params, | 
 | 	.set_fmt	= hdmi_codec_set_fmt, | 
 | 	.digital_mute	= hdmi_codec_digital_mute, | 
 | }; | 
 |  | 
 |  | 
 | #define HDMI_RATES	(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ | 
 | 			 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ | 
 | 			 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ | 
 | 			 SNDRV_PCM_RATE_192000) | 
 |  | 
 | #define SPDIF_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) | 
 |  | 
 | /* | 
 |  * This list is only for formats allowed on the I2S bus. So there is | 
 |  * some formats listed that are not supported by HDMI interface. For | 
 |  * instance allowing the 32-bit formats enables 24-precision with CPU | 
 |  * DAIs that do not support 24-bit formats. If the extra formats cause | 
 |  * problems, we should add the video side driver an option to disable | 
 |  * them. | 
 |  */ | 
 | #define I2S_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\ | 
 | 			 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) | 
 |  | 
 | static struct snd_soc_dai_driver hdmi_i2s_dai = { | 
 | 	.id = DAI_ID_I2S, | 
 | 	.playback = { | 
 | 		.stream_name = "Playback", | 
 | 		.channels_min = 2, | 
 | 		.channels_max = 8, | 
 | 		.rates = HDMI_RATES, | 
 | 		.formats = I2S_FORMATS, | 
 | 		.sig_bits = 24, | 
 | 	}, | 
 | 	.ops = &hdmi_dai_ops, | 
 | }; | 
 |  | 
 | static const struct snd_soc_dai_driver hdmi_spdif_dai = { | 
 | 	.id = DAI_ID_SPDIF, | 
 | 	.playback = { | 
 | 		.stream_name = "Playback", | 
 | 		.channels_min = 2, | 
 | 		.channels_max = 2, | 
 | 		.rates = HDMI_RATES, | 
 | 		.formats = SPDIF_FORMATS, | 
 | 	}, | 
 | 	.ops = &hdmi_dai_ops, | 
 | }; | 
 |  | 
 | static char hdmi_dai_name[][DAI_NAME_SIZE] = { | 
 | 	"hdmi-hifi.0", | 
 | 	"hdmi-hifi.1", | 
 | 	"hdmi-hifi.2", | 
 | 	"hdmi-hifi.3", | 
 | }; | 
 |  | 
 | static int hdmi_of_xlate_dai_name(struct snd_soc_component *component, | 
 | 				  struct of_phandle_args *args, | 
 | 				  const char **dai_name) | 
 | { | 
 | 	int id; | 
 |  | 
 | 	if (args->args_count) | 
 | 		id = args->args[0]; | 
 | 	else | 
 | 		id = 0; | 
 |  | 
 | 	if (id < ARRAY_SIZE(hdmi_dai_name)) { | 
 | 		*dai_name = hdmi_dai_name[id]; | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	return -EAGAIN; | 
 | } | 
 |  | 
 | static struct snd_soc_codec_driver hdmi_codec = { | 
 | 	.component_driver = { | 
 | 		.controls		= hdmi_controls, | 
 | 		.num_controls		= ARRAY_SIZE(hdmi_controls), | 
 | 		.dapm_widgets		= hdmi_widgets, | 
 | 		.num_dapm_widgets	= ARRAY_SIZE(hdmi_widgets), | 
 | 		.dapm_routes		= hdmi_routes, | 
 | 		.num_dapm_routes	= ARRAY_SIZE(hdmi_routes), | 
 | 		.of_xlate_dai_name	= hdmi_of_xlate_dai_name, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int hdmi_codec_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct hdmi_codec_pdata *hcd = pdev->dev.platform_data; | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct hdmi_codec_priv *hcp; | 
 | 	struct hdmi_device *hd; | 
 | 	struct list_head *pos; | 
 | 	int dai_count, i = 0; | 
 | 	int ret; | 
 |  | 
 | 	dev_dbg(dev, "%s()\n", __func__); | 
 |  | 
 | 	if (!hcd) { | 
 | 		dev_err(dev, "%s: No plalform data\n", __func__); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	dai_count = hcd->i2s + hcd->spdif; | 
 | 	if (dai_count < 1 || !hcd->ops || !hcd->ops->hw_params || | 
 | 	    !hcd->ops->audio_shutdown) { | 
 | 		dev_err(dev, "%s: Invalid parameters\n", __func__); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	hcp = devm_kzalloc(dev, sizeof(*hcp), GFP_KERNEL); | 
 | 	if (!hcp) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	hd = NULL; | 
 | 	list_for_each(pos, &hdmi_device_list) { | 
 | 		struct hdmi_device *tmp = pos_to_hdmi_device(pos); | 
 |  | 
 | 		if (tmp->dev == dev->parent) { | 
 | 			hd = tmp; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!hd) { | 
 | 		hd = devm_kzalloc(dev, sizeof(*hd), GFP_KERNEL); | 
 | 		if (!hd) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		hd->dev = dev->parent; | 
 |  | 
 | 		list_add_tail(&hd->list, &hdmi_device_list); | 
 | 	} | 
 |  | 
 | 	if (hd->cnt >= ARRAY_SIZE(hdmi_dai_name)) { | 
 | 		dev_err(dev, "too many hdmi codec are deteced\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	hcp->hcd = *hcd; | 
 | 	mutex_init(&hcp->current_stream_lock); | 
 |  | 
 | 	hcp->daidrv = devm_kzalloc(dev, dai_count * sizeof(*hcp->daidrv), | 
 | 				   GFP_KERNEL); | 
 | 	if (!hcp->daidrv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	if (hcd->i2s) { | 
 | 		hcp->daidrv[i] = hdmi_i2s_dai; | 
 | 		hcp->daidrv[i].playback.channels_max = | 
 | 			hcd->max_i2s_channels; | 
 | 		hcp->daidrv[i].name = hdmi_dai_name[hd->cnt++]; | 
 | 		i++; | 
 | 	} | 
 |  | 
 | 	if (hcd->spdif) { | 
 | 		hcp->daidrv[i] = hdmi_spdif_dai; | 
 | 		hcp->daidrv[i].name = hdmi_dai_name[hd->cnt++]; | 
 | 	} | 
 |  | 
 | 	ret = snd_soc_register_codec(dev, &hdmi_codec, hcp->daidrv, | 
 | 				     dai_count); | 
 | 	if (ret) { | 
 | 		dev_err(dev, "%s: snd_soc_register_codec() failed (%d)\n", | 
 | 			__func__, ret); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	dev_set_drvdata(dev, hcp); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int hdmi_codec_remove(struct platform_device *pdev) | 
 | { | 
 | 	snd_soc_unregister_codec(&pdev->dev); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct platform_driver hdmi_codec_driver = { | 
 | 	.driver = { | 
 | 		.name = HDMI_CODEC_DRV_NAME, | 
 | 	}, | 
 | 	.probe = hdmi_codec_probe, | 
 | 	.remove = hdmi_codec_remove, | 
 | }; | 
 |  | 
 | module_platform_driver(hdmi_codec_driver); | 
 |  | 
 | MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); | 
 | MODULE_DESCRIPTION("HDMI Audio Codec Driver"); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_ALIAS("platform:" HDMI_CODEC_DRV_NAME); |