From patchwork Sun Jan 26 18:45: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: 3556691 Return-Path: X-Original-To: patchwork-linux-arm@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 5D101C02DC for ; Thu, 30 Jan 2014 12:05:58 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 7BA24201E4 for ; Thu, 30 Jan 2014 12:05:53 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 0869C201D5 for ; Thu, 30 Jan 2014 12:05:52 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8qMq-0000nf-87; Thu, 30 Jan 2014 12:05:12 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8qMi-0003FS-0j; Thu, 30 Jan 2014 12:05:04 +0000 Received: from smtp1-g21.free.fr ([2a01:e0c:1:1599::10]) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1W8qMT-0003B8-83 for linux-arm-kernel@lists.infradead.org; Thu, 30 Jan 2014 12:04:50 +0000 Received: from localhost (unknown [IPv6:2a01:e35:2f5c:9de0:212:bfff:fe1e:9ce4]) by smtp1-g21.free.fr (Postfix) with ESMTP id 9864E9401A1; Thu, 30 Jan 2014 13:04:19 +0100 (CET) X-Mailbox-Line: From c638b6622f4e059987f075091716a2305217ee49 Mon Sep 17 00:00:00 2001 Message-Id: In-Reply-To: References: From: Jean-Francois Moine Date: Sun, 26 Jan 2014 19:45:36 +0100 Subject: [PATCH v2 2/5] ASoC: tda998x: add a codec driver for TDA998x To: alsa-devel@alsa-project.org X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140130_070450_049129_FFCA0179 X-CRM114-Status: GOOD ( 22.87 ) X-Spam-Score: -1.9 (-) Cc: Russell King - ARM Linux , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Rob Clark , broonie@kernel.org, Dave Airlie , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.6 required=5.0 tests=BAYES_00,FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This patch adds a CODEC driver for the NXP TDA998x HDMI transmitter. The CODEC handles both I2S and S/PDIF input and does dynamic input switch in the TDA998x I2C driver on audio streaming start/stop. Signed-off-by: Jean-Francois Moine --- sound/soc/codecs/Kconfig | 6 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tda998x.c | 237 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 sound/soc/codecs/tda998x.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b33b45d..747e387 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -352,6 +352,12 @@ config SND_SOC_STAC9766 config SND_SOC_TAS5086 tristate +config SND_SOC_TDA998X + tristate + depends on OF + default y if DRM_I2C_NXP_TDA998X=y + default m if DRM_I2C_NXP_TDA998X=m + config SND_SOC_TLV320AIC23 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bc12676..a53d09e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -62,6 +62,7 @@ snd-soc-sta32x-objs := sta32x.o snd-soc-sta529-objs := sta529.o snd-soc-stac9766-objs := stac9766.o snd-soc-tas5086-objs := tas5086.o +snd-soc-tda998x-objs := tda998x.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o @@ -192,6 +193,7 @@ obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o +obj-$(CONFIG_SND_SOC_TDA998X) += snd-soc-tda998x.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/tda998x.c b/sound/soc/codecs/tda998x.c new file mode 100644 index 0000000..585cdb6 --- /dev/null +++ b/sound/soc/codecs/tda998x.c @@ -0,0 +1,237 @@ +/* + * ALSA SoC TDA998X driver + * + * This driver is used by the NXP TDA998x HDMI transmitter. + * + * Copyright (C) 2014 Jean-Francois Moine + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct tda_priv { + struct i2c_client *i2c_client; + struct snd_soc_codec *codec; + u8 ports[2]; + int dai_id; + u8 *eld; +}; + +static void tda_get_encoder(struct tda_priv *priv) +{ + struct snd_soc_codec *codec = priv->codec; + struct device_node *np; + struct i2c_client *i2c_client; + static const struct of_device_id tda_dt[] = { + { .compatible = "nxp,tda998x" }, + { }, + }; + + /* search the tda998x device */ + np = of_find_matching_node_and_match(NULL, tda_dt, NULL); + if (!np || !of_device_is_available(np)) { + dev_err(codec->dev, "No tda998x in DT\n"); + return; + } + i2c_client = of_find_i2c_device_by_node(np); + of_node_put(np); + if (!i2c_client) { + dev_err(codec->dev, "no tda998x i2c client\n"); + return; + } + if (!i2c_get_clientdata(i2c_client)) { + dev_err(codec->dev, "tda998x not initialized\n"); + return; + } + + priv->i2c_client = i2c_client; +} + +static int tda_start_stop(struct tda_priv *priv) +{ + int port; + + if (!priv->i2c_client) { + tda_get_encoder(priv); + if (!priv->i2c_client) + return -EINVAL; + } + + /* give the audio parameters to the HDMI encoder */ + if (priv->dai_id == AFMT_I2S) + port = priv->ports[0]; + else + port = priv->ports[1]; + tda998x_audio_update(priv->i2c_client, priv->dai_id, port); + return 0; +} + +static int tda_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + /* memorize the used DAI */ + priv->dai_id = dai->id; + + /* start the TDA998x audio */ + return tda_start_stop(priv); +} + +static void tda_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + priv->dai_id = 0; /* streaming stop */ + tda_start_stop(priv); +} + +static const struct snd_soc_dai_ops tda_ops = { + .startup = tda_startup, + .shutdown = tda_shutdown, +}; + +static const struct snd_soc_dai_driver tda998x_dai[] = { + { + .name = "i2s-hifi", + .id = AFMT_I2S, + .playback = { + .stream_name = "HDMI I2S Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + + }, + .ops = &tda_ops, + }, + { + .name = "spdif-hifi", + .id = AFMT_SPDIF, + .playback = { + .stream_name = "HDMI SPDIF Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 22050, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, +}; + +static const struct snd_soc_dapm_widget tda_widgets[] = { + SND_SOC_DAPM_OUTPUT("hdmi-out"), +}; +static const struct snd_soc_dapm_route tda_routes[] = { + { "hdmi-out", NULL, "HDMI I2S Playback" }, + { "hdmi-out", NULL, "HDMI SPDIF Playback" }, +}; + +static int tda_probe(struct snd_soc_codec *codec) +{ + struct tda_priv *priv; + struct device_node *np; + int i, j, ret; + const char *p; + + priv = devm_kzalloc(codec->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, priv); + priv->codec = codec; + + /* get the audio input ports (I2s and S/PDIF) */ + np = codec->dev->of_node; + for (i = 0; i < 2; i++) { + u32 port; + + ret = of_property_read_u32_index(np, "audio-ports", i, &port); + if (ret) { + if (i == 0) + dev_err(codec->dev, + "bad or missing audio-ports\n"); + break; + } + ret = of_property_read_string_index(np, "audio-port-names", + i, &p); + if (ret) { + dev_err(codec->dev, + "missing audio-port-names[%d]\n", i); + break; + } + if (strcmp(p, "i2s") == 0) { + j = 0; + } else if (strcmp(p, "spdif") == 0) { + j = 1; + } else { + dev_err(codec->dev, + "bad audio-port-names '%s'\n", p); + break; + } + priv->ports[j] = port; + } + + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_tda998x = { + .probe = tda_probe, + .dapm_widgets = tda_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda_widgets), + .dapm_routes = tda_routes, + .num_dapm_routes = ARRAY_SIZE(tda_routes), +}; + +static int tda998x_dev_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &soc_codec_tda998x, + tda998x_dai, ARRAY_SIZE(tda998x_dai)); +} + +static int tda998x_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static const struct of_device_id tda998x_codec_ids[] = { + { .compatible = "nxp,tda998x-codec", }, + { } +}; +MODULE_DEVICE_TABLE(of, tda998x_codec_ids); + +static struct platform_driver tda998x_driver = { + .probe = tda998x_dev_probe, + .remove = tda998x_dev_remove, + .driver = { + .name = "tda998x-codec", + .owner = THIS_MODULE, + .of_match_table = tda998x_codec_ids, + }, +}; + +module_platform_driver(tda998x_driver); + +MODULE_AUTHOR("Jean-Francois Moine"); +MODULE_DESCRIPTION("TDA998x codec driver"); +MODULE_LICENSE("GPL");