Message ID | 20180213165837.1620-15-srinivas.kandagatla@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote: > From: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> > > This patch adds support to q6afe backend dais driver. > > Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> > --- > sound/soc/qcom/qdsp6/Makefile | 2 +- > sound/soc/qcom/qdsp6/q6afe-dai.c | 280 +++++++++++++++++++++++++++++++++++++++ > sound/soc/qcom/qdsp6/q6afe.h | 3 + > 3 files changed, 284 insertions(+), 1 deletion(-) > create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c > > diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile > index 660afcab98fd..c7833842b878 100644 > --- a/sound/soc/qcom/qdsp6/Makefile > +++ b/sound/soc/qcom/qdsp6/Makefile > @@ -1,5 +1,5 @@ > obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o > -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o > +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o depmod: ERROR: Cycle detected: q6afe -> q6afe_dai -> q6afe need to use like: +qdsp6_afe-objs := q6afe.o q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += qdsp6_afe.o similarly for asm and adm > obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o > obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o > obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o > diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c > new file mode 100644 > index 000000000000..f6a618e9db9e > --- /dev/null > +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c > @@ -0,0 +1,280 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (c) 2011-2016, The Linux Foundation > + * Copyright (c) 2018, Linaro Limited > + */ > + > +#include <linux/err.h> > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/device.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <sound/pcm.h> > +#include <sound/soc.h> > +#include <sound/pcm_params.h> > +#include "q6afe.h" > + > +struct q6afe_dai_data { > + struct q6afe_port *port[AFE_PORT_MAX]; > + struct q6afe_port_config port_config[AFE_PORT_MAX]; > + bool is_port_started[AFE_PORT_MAX]; > +}; > + > +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + struct q6afe_dai_data *dai_data = kcontrol->private_data; > + int value = ucontrol->value.integer.value[0]; > + > + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value; > + > + return 0; > +} > + > +static int q6hdmi_format_get(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + > + struct q6afe_dai_data *dai_data = kcontrol->private_data; > + > + ucontrol->value.integer.value[0] = > + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype; > + > + return 0; > +} > + > +static const char * const hdmi_format[] = { > + "LPCM", > + "Compr" > +}; > + > +static const struct soc_enum hdmi_config_enum[] = { > + SOC_ENUM_SINGLE_EXT(2, hdmi_format), > +}; > + > +static const struct snd_kcontrol_new q6afe_config_controls[] = { > + SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0], > + q6hdmi_format_get, > + q6hdmi_format_put), > +}; > + > +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + int channels = params_channels(params); > + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; > + > + hdmi->sample_rate = params_rate(params); > + switch (params_format(params)) { > + case SNDRV_PCM_FORMAT_S16_LE: > + hdmi->bit_width = 16; > + break; > + case SNDRV_PCM_FORMAT_S24_LE: > + hdmi->bit_width = 24; > + break; > + } > + > + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ > + switch (channels) { > + case 2: > + hdmi->channel_allocation = 0; > + break; > + case 3: > + hdmi->channel_allocation = 0x02; > + break; > + case 4: > + hdmi->channel_allocation = 0x06; > + break; > + case 5: > + hdmi->channel_allocation = 0x0A; > + break; > + case 6: > + hdmi->channel_allocation = 0x0B; > + break; > + case 7: > + hdmi->channel_allocation = 0x12; > + break; > + case 8: > + hdmi->channel_allocation = 0x13; > + break; > + default: > + dev_err(dai->dev, "invalid Channels = %u\n", channels); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int q6afe_dai_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + > + dai_data->is_port_started[dai->id] = false; > + > + return 0; > +} > + > +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + int rc; > + > + rc = q6afe_port_stop(dai_data->port[dai->id]); > + if (rc < 0) > + dev_err(dai->dev, "fail to close AFE port\n"); > + > + dai_data->is_port_started[dai->id] = false; > + > +} > + > +static int q6afe_dai_prepare(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + int rc; > + > + if (dai_data->is_port_started[dai->id]) { > + /* stop the port and restart with new port config */ > + rc = q6afe_port_stop(dai_data->port[dai->id]); > + if (rc < 0) { > + dev_err(dai->dev, "fail to close AFE port\n"); > + return rc; > + } > + } > + > + if (dai->id == AFE_PORT_HDMI_RX) > + q6afe_hdmi_port_prepare(dai_data->port[dai->id], > + &dai_data->port_config[dai->id].hdmi); > + > + rc = q6afe_port_start(dai_data->port[dai->id]); > + if (rc < 0) { > + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); > + return rc; > + } > + dai_data->is_port_started[dai->id] = true; > + > + return 0; > +} > + > +static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { > + {"HDMI Playback", NULL, "HDMI_RX"}, > +}; > + > +static struct snd_soc_dai_ops q6hdmi_ops = { > + .prepare = q6afe_dai_prepare, > + .hw_params = q6hdmi_hw_params, > + .shutdown = q6afe_dai_shutdown, > + .startup = q6afe_dai_startup, > +}; > + > +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + struct snd_soc_dapm_context *dapm; > + struct q6afe_port *port; > + > + dapm = snd_soc_component_get_dapm(dai->component); > + > + port = q6afe_port_get_from_id(dai->dev, dai->id); > + if (IS_ERR(port)) { > + dev_err(dai->dev, "Unable to get afe port\n"); > + return -EINVAL; > + } > + dai_data->port[dai->id] = port; > + > + return 0; > +} > + > +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + > + q6afe_port_put(dai_data->port[dai->id]); > + > + return 0; > +} > + > +static struct snd_soc_dai_driver q6afe_dais[] = { > + { > + .playback = { > + .stream_name = "HDMI Playback", > + .rates = SNDRV_PCM_RATE_48000 | > + SNDRV_PCM_RATE_96000 | > + SNDRV_PCM_RATE_192000, > + .formats = SNDRV_PCM_FMTBIT_S16_LE | > + SNDRV_PCM_FMTBIT_S24_LE, > + .channels_min = 2, > + .channels_max = 8, > + .rate_max = 192000, > + .rate_min = 48000, > + }, > + .ops = &q6hdmi_ops, > + .id = AFE_PORT_HDMI_RX, > + .name = "HDMI", > + .probe = msm_dai_q6_dai_probe, > + .remove = msm_dai_q6_dai_remove, > + }, > +}; > + > +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, > + struct of_phandle_args *args, > + const char **dai_name) > +{ > + int id = args->args[0]; > + int i, ret = -EINVAL; > + > + for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { > + if (q6afe_dais[i].id == id) { > + *dai_name = q6afe_dais[i].name; > + ret = 0; > + break; > + } > + } > + > + return ret; > +} > + > +static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { > + SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0), > +}; > + > +static const struct snd_soc_component_driver q6afe_dai_component = { > + .name = "q6afe-dai-component", > + .dapm_widgets = q6afe_dai_widgets, > + .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), > + .controls = q6afe_config_controls, > + .num_controls = ARRAY_SIZE(q6afe_config_controls), > + .dapm_routes = q6afe_dapm_routes, > + .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), > + .of_xlate_dai_name = q6afe_of_xlate_dai_name, > + > +}; > + > +int q6afe_dai_dev_probe(struct device *dev) > +{ > + int rc = 0; > + struct q6afe_dai_data *dai_data; > + > + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); > + if (!dai_data) > + rc = -ENOMEM; > + > + q6afe_set_dai_data(dev, dai_data); > + > + return devm_snd_soc_register_component(dev, &q6afe_dai_component, > + q6afe_dais, ARRAY_SIZE(q6afe_dais)); > +} > +EXPORT_SYMBOL_GPL(q6afe_dai_dev_probe); > + > +int q6afe_dai_dev_remove(struct device *dev) > +{ > + return 0; > +} > +EXPORT_SYMBOL_GPL(q6afe_dai_dev_remove); > +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); > +MODULE_LICENSE("GPL v2"); > diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h > index 43df524f01bb..647ed2d15545 100644 > --- a/sound/soc/qcom/qdsp6/q6afe.h > +++ b/sound/soc/qcom/qdsp6/q6afe.h > @@ -26,6 +26,9 @@ struct q6afe_port; > void q6afe_set_dai_data(struct device *dev, void *data); > void *q6afe_get_dai_data(struct device *dev); > > +int q6afe_dai_dev_probe(struct device *dev); > +int q6afe_dai_dev_remove(struct device *dev); > + > struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); > int q6afe_port_start(struct q6afe_port *port); > int q6afe_port_stop(struct q6afe_port *port);
Thanks for testing this out, On 19/02/18 10:32, Rohit Kumar wrote: >> >> diff --git a/sound/soc/qcom/qdsp6/Makefile >> b/sound/soc/qcom/qdsp6/Makefile >> index 660afcab98fd..c7833842b878 100644 >> --- a/sound/soc/qcom/qdsp6/Makefile >> +++ b/sound/soc/qcom/qdsp6/Makefile >> @@ -1,5 +1,5 @@ >> obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o >> -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o >> +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o > depmod: ERROR: Cycle detected: q6afe -> q6afe_dai -> q6afe > need to use like: > +qdsp6_afe-objs := q6afe.o q6afe-dai.o > +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += qdsp6_afe.o > similarly for asm and adm Yep, will fix this in next version, thanks, srini >> obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o >> obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o >> obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o >> diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c >> b/sound/soc/qcom/qdsp6/q6afe-dai.c >> new file mode 100644 >> index 000000000000..f6a618e9db9e
On Tue, Feb 13, 2018 at 04:58:26PM +0000, srinivas.kandagatla@linaro.org wrote: > +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *ucontrol) > +{ > + struct q6afe_dai_data *dai_data = kcontrol->private_data; > + int value = ucontrol->value.integer.value[0]; > + > + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value; > + > + return 0; > +} No validation, and do we not need to tell a currently running stream if the format changed on it (or block such changes if they're not going to work, which seems more likely)? > +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + int channels = params_channels(params); > + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; > + > + hdmi->sample_rate = params_rate(params); > + switch (params_format(params)) { > + case SNDRV_PCM_FORMAT_S16_LE: > + hdmi->bit_width = 16; > + break; > + case SNDRV_PCM_FORMAT_S24_LE: > + hdmi->bit_width = 24; > + break; > + } This silently accepts invalid values. > + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ Coding style, spaces around the /* */. > +static int q6afe_dai_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + > + dai_data->is_port_started[dai->id] = false; > + > + return 0; > +} If this is needed it makes me a bit worried that we've got some kind of bug with not shutting things down properly somewhere - what's going on here? > +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); > + int rc; > + > + rc = q6afe_port_stop(dai_data->port[dai->id]); > + if (rc < 0) > + dev_err(dai->dev, "fail to close AFE port\n"); Better to print the error code so users have more information to debug the problem. > + .stream_name = "HDMI Playback", > + .rates = SNDRV_PCM_RATE_48000 | > + SNDRV_PCM_RATE_96000 | > + SNDRV_PCM_RATE_192000, Indentation again. > +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, > + struct of_phandle_args *args, > + const char **dai_name) > +{ > + int id = args->args[0]; > + int i, ret = -EINVAL; Coding style, don't mix initialization in with other variable declarations on the same line like this. > +int q6afe_dai_dev_remove(struct device *dev) > +{ > + return 0; > +} Remove empty functions, if they can't be removed it's probably not OK for them to be empty either.
Thanks for the review comments, On 02/03/18 12:50, Mark Brown wrote: > On Tue, Feb 13, 2018 at 04:58:26PM +0000, srinivas.kandagatla@linaro.org wrote: > >> +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, >> + struct snd_ctl_elem_value *ucontrol) >> +{ >> + struct q6afe_dai_data *dai_data = kcontrol->private_data; >> + int value = ucontrol->value.integer.value[0]; >> + >> + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value; >> + >> + return 0; >> +} > > No validation, and do we not need to tell a currently running stream if > the format changed on it (or block such changes if they're not going to > work, which seems more likely)? Yes, It would not work if the stream is running. This mixer has to be setup before the stream/port is prepared/started. TBH, I have no means to test Compr format, I should probably remove this control until am able to test this format. > >> +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, >> + struct snd_pcm_hw_params *params, >> + struct snd_soc_dai *dai) >> +{ >> + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); >> + int channels = params_channels(params); >> + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; >> + >> + hdmi->sample_rate = params_rate(params); >> + switch (params_format(params)) { >> + case SNDRV_PCM_FORMAT_S16_LE: >> + hdmi->bit_width = 16; >> + break; >> + case SNDRV_PCM_FORMAT_S24_LE: >> + hdmi->bit_width = 24; >> + break; >> + } > > This silently accepts invalid values. > Yep, I will fix this in next version. >> + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ > > Coding style, spaces around the /* */. Agreed! Will fix it in next version. > >> +static int q6afe_dai_startup(struct snd_pcm_substream *substream, >> + struct snd_soc_dai *dai) >> +{ >> + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); >> + >> + dai_data->is_port_started[dai->id] = false; >> + >> + return 0; >> +} > > If this is needed it makes me a bit worried that we've got some kind of > bug with not shutting things down properly somewhere - what's going on > here? This looks over done, we do not need to set this flag in startup, as it would be properly reset in shutdown. Will remove this function totally as its not required. > >> +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, >> + struct snd_soc_dai *dai) >> +{ >> + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); >> + int rc; >> + >> + rc = q6afe_port_stop(dai_data->port[dai->id]); >> + if (rc < 0) >> + dev_err(dai->dev, "fail to close AFE port\n"); > > Better to print the error code so users have more information to debug > the problem. Yep. > >> + .stream_name = "HDMI Playback", >> + .rates = SNDRV_PCM_RATE_48000 | >> + SNDRV_PCM_RATE_96000 | >> + SNDRV_PCM_RATE_192000, > > Indentation again. Will sort it out in next version. > >> +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, >> + struct of_phandle_args *args, >> + const char **dai_name) >> +{ >> + int id = args->args[0]; >> + int i, ret = -EINVAL; > > Coding style, don't mix initialization in with other variable > declarations on the same line like this. Will fix all such instances in next version. > >> +int q6afe_dai_dev_remove(struct device *dev) >> +{ >> + return 0; >> +} > > Remove empty functions, if they can't be removed it's probably not OK > for them to be empty either. Sure will do that. > thanks, srini
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 660afcab98fd..c7833842b878 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000000..f6a618e9db9e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2016, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "q6afe.h" + +struct q6afe_dai_data { + struct q6afe_port *port[AFE_PORT_MAX]; + struct q6afe_port_config port_config[AFE_PORT_MAX]; + bool is_port_started[AFE_PORT_MAX]; +}; + +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct q6afe_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value; + + return 0; +} + +static int q6hdmi_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct q6afe_dai_data *dai_data = kcontrol->private_data; + + ucontrol->value.integer.value[0] = + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype; + + return 0; +} + +static const char * const hdmi_format[] = { + "LPCM", + "Compr" +}; + +static const struct soc_enum hdmi_config_enum[] = { + SOC_ENUM_SINGLE_EXT(2, hdmi_format), +}; + +static const struct snd_kcontrol_new q6afe_config_controls[] = { + SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0], + q6hdmi_format_get, + q6hdmi_format_put), +}; + +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int channels = params_channels(params); + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; + + hdmi->sample_rate = params_rate(params); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + hdmi->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hdmi->bit_width = 24; + break; + } + + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ + switch (channels) { + case 2: + hdmi->channel_allocation = 0; + break; + case 3: + hdmi->channel_allocation = 0x02; + break; + case 4: + hdmi->channel_allocation = 0x06; + break; + case 5: + hdmi->channel_allocation = 0x0A; + break; + case 6: + hdmi->channel_allocation = 0x0B; + break; + case 7: + hdmi->channel_allocation = 0x12; + break; + case 8: + hdmi->channel_allocation = 0x13; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", channels); + return -EINVAL; + } + + return 0; +} + +static int q6afe_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + + dai_data->is_port_started[dai->id] = false; + + return 0; +} + +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int rc; + + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) + dev_err(dai->dev, "fail to close AFE port\n"); + + dai_data->is_port_started[dai->id] = false; + +} + +static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int rc; + + if (dai_data->is_port_started[dai->id]) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port\n"); + return rc; + } + } + + if (dai->id == AFE_PORT_HDMI_RX) + q6afe_hdmi_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].hdmi); + + rc = q6afe_port_start(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + +static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + {"HDMI Playback", NULL, "HDMI_RX"}, +}; + +static struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6hdmi_hw_params, + .shutdown = q6afe_dai_shutdown, + .startup = q6afe_dai_startup, +}; + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct snd_soc_dapm_context *dapm; + struct q6afe_port *port; + + dapm = snd_soc_component_get_dapm(dai->component); + + port = q6afe_port_get_from_id(dai->dev, dai->id); + if (IS_ERR(port)) { + dev_err(dai->dev, "Unable to get afe port\n"); + return -EINVAL; + } + dai_data->port[dai->id] = port; + + return 0; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + + q6afe_port_put(dai_data->port[dai->id]); + + return 0; +} + +static struct snd_soc_dai_driver q6afe_dais[] = { + { + .playback = { + .stream_name = "HDMI Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .ops = &q6hdmi_ops, + .id = AFE_PORT_HDMI_RX, + .name = "HDMI", + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, +}; + +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { + if (q6afe_dais[i].id == id) { + *dai_name = q6afe_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0), +}; + +static const struct snd_soc_component_driver q6afe_dai_component = { + .name = "q6afe-dai-component", + .dapm_widgets = q6afe_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), + .controls = q6afe_config_controls, + .num_controls = ARRAY_SIZE(q6afe_config_controls), + .dapm_routes = q6afe_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), + .of_xlate_dai_name = q6afe_of_xlate_dai_name, + +}; + +int q6afe_dai_dev_probe(struct device *dev) +{ + int rc = 0; + struct q6afe_dai_data *dai_data; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + rc = -ENOMEM; + + q6afe_set_dai_data(dev, dai_data); + + return devm_snd_soc_register_component(dev, &q6afe_dai_component, + q6afe_dais, ARRAY_SIZE(q6afe_dais)); +} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_probe); + +int q6afe_dai_dev_remove(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_remove); +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 43df524f01bb..647ed2d15545 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -26,6 +26,9 @@ struct q6afe_port; void q6afe_set_dai_data(struct device *dev, void *data); void *q6afe_get_dai_data(struct device *dev); +int q6afe_dai_dev_probe(struct device *dev); +int q6afe_dai_dev_remove(struct device *dev); + struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); int q6afe_port_start(struct q6afe_port *port); int q6afe_port_stop(struct q6afe_port *port);