From patchwork Fri Oct 3 08:22:36 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Francois Moine X-Patchwork-Id: 5021561 Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 6BBA4C11AB for ; Fri, 3 Oct 2014 09:00:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 67273201F5 for ; Fri, 3 Oct 2014 09:00:56 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 0DEE12021A for ; Fri, 3 Oct 2014 09:00:55 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 4017F26045C; Fri, 3 Oct 2014 11:00:54 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,FREEMAIL_FROM, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id 9597226042F; Fri, 3 Oct 2014 11:00:15 +0200 (CEST) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 7F515260427; Fri, 3 Oct 2014 11:00:14 +0200 (CEST) Received: from smtp4-g21.free.fr (smtp4-g21.free.fr [212.27.42.4]) by alsa0.perex.cz (Postfix) with ESMTP id BF298260427 for ; Fri, 3 Oct 2014 11:00:12 +0200 (CEST) Received: from localhost (unknown [IPv6:2a01:e35:2f5c:9de0:212:bfff:fe1e:9ce4]) by smtp4-g21.free.fr (Postfix) with ESMTP id EC16A4C801A; Fri, 3 Oct 2014 10:59:23 +0200 (CEST) X-Mailbox-Line: From 6a7fb623a46853b36fe6be008e816181678fa193 Mon Sep 17 00:00:00 2001 Message-Id: <6a7fb623a46853b36fe6be008e816181678fa193.1412325047.git.moinejf@free.fr> In-Reply-To: References: From: Jean-Francois Moine Date: Fri, 3 Oct 2014 10:22:36 +0200 To: Mark Brown , Russell King - ARM Linux Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org, Andrew Jackson , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Jyri Sarha , Dave Airlie Subject: [alsa-devel] [PATCH v7 1/2] ASoC: codecs: Add a transmitter interface to the HDMI CODEC X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP The audio constraints of the HDMI interface are defined by the EDID which is sent by the connected device. The HDMI transmitters may have one or many audio sources. This patch adds two functions to the HDMI CODEC: - it updates the audio constraints from the EDID, - it gives the audio source type to the HDMI transmitter on start of audio streaming. Signed-off-by: Jean-Francois Moine --- include/sound/hdmi.h | 20 ++++++ sound/soc/codecs/hdmi.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 include/sound/hdmi.h diff --git a/include/sound/hdmi.h b/include/sound/hdmi.h new file mode 100644 index 0000000..49062c7 --- /dev/null +++ b/include/sound/hdmi.h @@ -0,0 +1,20 @@ +#ifndef SND_HDMI_H +#define SND_HDMI_H + +#include + +/* platform_data */ +struct hdmi_data { + int (*get_audio)(struct device *dev, + int *max_channels, + int *rate_mask, + int *fmt); + void (*audio_switch)(struct device *dev, + int port_index, + unsigned sample_rate, + int sample_format); + int ndais; + struct snd_soc_dai_driver *dais; + struct snd_soc_codec_driver *driver; +}; +#endif diff --git a/sound/soc/codecs/hdmi.c b/sound/soc/codecs/hdmi.c index 1087fd5..0309d98 100644 --- a/sound/soc/codecs/hdmi.c +++ b/sound/soc/codecs/hdmi.c @@ -22,9 +22,148 @@ #include #include #include +#include +#include #define DRV_NAME "hdmi-audio-codec" +struct hdmi_priv { + struct hdmi_data hdmi_data; + struct snd_pcm_hw_constraint_list rate_constraints; +}; + +static int hdmi_dev_match(struct device *dev, void *data) +{ + return !strcmp(dev_name(dev), (char *) data); +} + +/* get the codec device */ +static struct device *hdmi_get_cdev(struct device *dev) +{ + struct device *cdev; + + cdev = device_find_child(dev, + DRV_NAME, + hdmi_dev_match); + if (!cdev) + dev_err(dev, "Cannot get codec device"); + else + put_device(cdev); + return cdev; +} + +static int hdmi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *cdev; + struct hdmi_priv *priv; + struct snd_pcm_hw_constraint_list *rate_constraints; + int ret, max_channels, rate_mask, fmt; + u64 formats; + static const u32 hdmi_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + cdev = hdmi_get_cdev(dai->dev); + if (!cdev) + return -ENODEV; + priv = dev_get_drvdata(cdev); + + /* get the EDID values and the rate constraints buffer */ + ret = priv->hdmi_data.get_audio(dai->dev, + &max_channels, &rate_mask, &fmt); + if (ret < 0) + return ret; /* no screen */ + + /* convert the EDID values to audio constraints */ + rate_constraints = &priv->rate_constraints; + rate_constraints->list = hdmi_rates; + rate_constraints->count = ARRAY_SIZE(hdmi_rates); + rate_constraints->mask = rate_mask; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rate_constraints); + + formats = 0; + if (fmt & 1) + formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (fmt & 2) + formats |= SNDRV_PCM_FMTBIT_S20_3LE; + if (fmt & 4) + formats |= SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + formats); + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, max_channels); + return 0; +} + +static int hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *cdev; + struct hdmi_priv *priv; + + cdev = hdmi_get_cdev(dai->dev); + if (!cdev) + return -ENODEV; + priv = dev_get_drvdata(cdev); + + priv->hdmi_data.audio_switch(dai->dev, dai->id, + params_rate(params), + params_format(params)); + return 0; +} + +static void hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct device *cdev; + struct hdmi_priv *priv; + + cdev = hdmi_get_cdev(dai->dev); + if (!cdev) + return; + priv = dev_get_drvdata(cdev); + + priv->hdmi_data.audio_switch(dai->dev, -1, 0, 0); /* stop */ +} + +static const struct snd_soc_dai_ops hdmi_ops = { + .startup = hdmi_startup, + .hw_params = hdmi_hw_params, + .shutdown = hdmi_shutdown, +}; + +static int hdmi_codec_probe(struct snd_soc_codec *codec) +{ + struct hdmi_priv *priv; + struct device *dev = codec->dev; /* encoder device */ + struct device *cdev; /* codec device */ + + cdev = hdmi_get_cdev(dev); + if (!cdev) + return -ENODEV; + + /* allocate some memory to store + * the encoder callback functions and the rate constraints */ + priv = devm_kzalloc(cdev, sizeof *priv, GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(cdev, priv); + + memcpy(&priv->hdmi_data, cdev->platform_data, + sizeof priv->hdmi_data); + return 0; +} + static const struct snd_soc_dapm_widget hdmi_widgets[] = { SND_SOC_DAPM_INPUT("RX"), SND_SOC_DAPM_OUTPUT("TX"), @@ -77,13 +216,38 @@ static struct snd_soc_codec_driver hdmi_codec = { .num_dapm_routes = ARRAY_SIZE(hdmi_routes), }; -static int hdmi_codec_probe(struct platform_device *pdev) +static int hdmi_codec_dev_probe(struct platform_device *pdev) { - return snd_soc_register_codec(&pdev->dev, &hdmi_codec, - &hdmi_codec_dai, 1); + struct hdmi_data *pdata = pdev->dev.platform_data; + struct snd_soc_dai_driver *dais; + struct snd_soc_codec_driver *driver; + int i, ret; + + if (!pdata) + return snd_soc_register_codec(&pdev->dev, &hdmi_codec, + &hdmi_codec_dai, 1); + + /* creation from a video encoder as a child device */ + dais = devm_kmemdup(&pdev->dev, + pdata->dais, + sizeof *pdata->dais * pdata->ndais, + GFP_KERNEL); + for (i = 0; i < pdata->ndais; i++) + dais[i].ops = &hdmi_ops; + + driver = devm_kmemdup(&pdev->dev, + pdata->driver, + sizeof *pdata->driver, + GFP_KERNEL); + driver->probe = hdmi_codec_probe; + + /* register the codec on the video encoder */ + ret = snd_soc_register_codec(pdev->dev.parent, driver, + dais, pdata->ndais); + return ret; } -static int hdmi_codec_remove(struct platform_device *pdev) +static int hdmi_codec_dev_remove(struct platform_device *pdev) { snd_soc_unregister_codec(&pdev->dev); return 0; @@ -96,8 +260,8 @@ static struct platform_driver hdmi_codec_driver = { .of_match_table = of_match_ptr(hdmi_audio_codec_ids), }, - .probe = hdmi_codec_probe, - .remove = hdmi_codec_remove, + .probe = hdmi_codec_dev_probe, + .remove = hdmi_codec_dev_remove, }; module_platform_driver(hdmi_codec_driver);