@@ -512,3 +512,6 @@ config SND_SOC_ML26124
config SND_SOC_TPA6130A2
tristate
+
+config SND_SOC_EXYNOS_HDMI_CODEC
+ tristate
@@ -115,6 +115,7 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-exynos-hdmi-audio-objs := exynos_hdmi_audio.o
# Amp
snd-soc-max9877-objs := max9877.o
@@ -236,6 +237,7 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
+obj-$(CONFIG_SND_SOC_EXYNOS_HDMI_CODEC) += snd-soc-exynos-hdmi-audio.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
new file mode 100644
@@ -0,0 +1,424 @@
+/*
+ * ALSA SoC codec driver for HDMI audio on Samsung Exynos processors.
+ * Copyright (C) 2013 Samsung corp.
+ * Author: Rahul Sharma <rahul.sharma@samsung.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/delay.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include <video/display.h>
+#include <video/exynos_hdmi.h>
+
+#define get_ctx(dev) ((struct hdmi_audio_context *)platform_get_drvdata\
+ (to_platform_device((struct device *)dev)))
+
+/* platform device pointer for eynos hdmi audio codec device. */
+static struct platform_device *exynos_hdmi_codec_pdev;
+
+struct hdmi_audio_context {
+ struct platform_device *pdev;
+ atomic_t plugged;
+ atomic_t enabled;
+ struct workqueue_struct *event_wq;
+ struct delayed_work hotplug_work;
+ struct display_entity_audio_params audio_params;
+ struct display_entity *entity;
+ struct display_entity_notifier notf;
+ struct display_event_subscriber subscriber;
+ struct snd_soc_jack hdmi_jack;
+};
+
+static int exynos_hdmi_audio_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct hdmi_audio_context *ctx = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ dev_dbg(codec->dev, "[%d] %s\n", __LINE__, __func__);
+
+ /* report failure if hdmi sink is unplugged. */
+ if (!atomic_read(&ctx->plugged))
+ return -ENODEV;
+
+ ctx->audio_params.type = DISPLAY_ENTITY_AUDIO_I2S;
+
+ switch (params_channels(params)) {
+ case 6:
+ case 4:
+ case 2:
+ case 1:
+ ctx->audio_params.channels = params_channels(params);
+ break;
+ default:
+ dev_err(codec->dev, "%d channels not supported\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ ctx->audio_params.bits_per_sample = 8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ ctx->audio_params.bits_per_sample = 12;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ ctx->audio_params.bits_per_sample = 16;
+ break;
+ default:
+ dev_err(codec->dev, "Format(%d) not supported\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ switch (params_rate(params)) {
+ case 32000:
+ case 44100:
+ case 88200:
+ case 176400:
+ case 48000:
+ case 96000:
+ case 192000:
+ ctx->audio_params.sf = params_rate(params);
+ break;
+ default:
+ dev_err(codec->dev, "%d Rate supported\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ ret =
+ display_entity_hdmi_init_audio(ctx->entity, &ctx->audio_params);
+ if (ret)
+ dev_err(codec->dev, "initaudio failed ret %d\n", ret);
+ return ret;
+}
+
+static int exynos_hdmi_audio_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct hdmi_audio_context *ctx = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ dev_dbg(codec->dev, "[%d] %s\n", __LINE__, __func__);
+
+ /* report failure if hdmi sink is unplugged. */
+ if (!atomic_read(&ctx->plugged))
+ return -EINVAL;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* Don't start if hdmi audio is disabled. return Success. */
+ if (!atomic_read(&ctx->enabled))
+ return 0;
+
+ ret = display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_ON);
+ if (ret) {
+ dev_err(codec->dev, "audio enable failed.\n");
+ return -EINVAL;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_OFF);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops exynos_hdmi_audio_dai_ops = {
+ .hw_params = exynos_hdmi_audio_hw_params,
+ .trigger = exynos_hdmi_audio_trigger,
+};
+
+static struct snd_soc_dai_driver hdmi_codec_dai = {
+ .name = "exynos-hdmi-audio-dai",
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .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,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &exynos_hdmi_audio_dai_ops,
+};
+
+void hdmi_audio_event_notify(struct display_entity *entity,
+ enum display_entity_event_type type,
+ unsigned int value, void *context)
+{
+ struct hdmi_audio_context *ctx = (struct hdmi_audio_context *)context;
+
+ if (type == DISPLAY_ENTITY_HDMI_HOTPLUG) {
+ dev_dbg(&ctx->pdev->dev, "[%d][%s] hpd(%d)\n", __LINE__,
+ __func__, value);
+ atomic_set(&ctx->plugged, !!value);
+
+ /* should set audio regs after ip, phy got stable. 5ms suff */
+ queue_delayed_work(ctx->event_wq, &ctx->hotplug_work,
+ msecs_to_jiffies(5));
+ }
+}
+
+static void hotplug_event_handler(struct work_struct *work)
+{
+ struct hdmi_audio_context *ctx = container_of(work,
+ struct hdmi_audio_context, hotplug_work.work);
+
+ if (atomic_read(&ctx->plugged)) {
+ display_entity_hdmi_init_audio(ctx->entity,
+ &ctx->audio_params);
+
+ if (atomic_read(&ctx->enabled))
+ display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_ON);
+ else
+ display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_OFF);
+ }
+
+ snd_soc_jack_report(&ctx->hdmi_jack,
+ atomic_read(&ctx->plugged) ? SND_JACK_AVOUT
+ : 0, SND_JACK_AVOUT);
+}
+
+int exynos_hdmi_audio_notification(struct display_entity_notifier *notf,
+ struct display_entity *entity, int status)
+{
+ struct hdmi_audio_context *ctx = container_of(notf,
+ struct hdmi_audio_context, notf);
+ struct exynos_hdmi_control_ops *exynos_ops =
+ (struct exynos_hdmi_control_ops *)entity->private;
+ int hpd;
+
+ if (status != DISPLAY_ENTITY_NOTIFIER_CONNECT && entity)
+ return -EINVAL;
+
+ dev_dbg(&ctx->pdev->dev, "[%d][%s]\n", __LINE__, __func__);
+
+ ctx->entity = entity;
+
+ ctx->subscriber.context = ctx;
+ ctx->subscriber.notify = hdmi_audio_event_notify;
+
+ display_entity_subscribe_event(entity, &ctx->subscriber);
+
+ exynos_ops->get_hpdstate(entity, &hpd);
+ atomic_set(&ctx->plugged, !!hpd);
+
+ return 0;
+}
+
+static int get_hdmi(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hdmi_audio_context *ctx = get_ctx(kcontrol->private_value);
+
+ if (!ctx) {
+ dev_err(&ctx->pdev->dev, "invalid context.\n");
+ return 0;
+ }
+
+ ucontrol->value.integer.value[0] = atomic_read(&ctx->enabled);
+ return 1;
+}
+
+static int put_hdmi(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct hdmi_audio_context *ctx = get_ctx(kcontrol->private_value);
+ int enable = (int)ucontrol->value.integer.value[0];
+
+ if (!ctx) {
+ dev_err(&ctx->pdev->dev, "invalid context.\n");
+ return 0;
+ }
+
+ atomic_set(&ctx->enabled, !!enable);
+
+ if (!atomic_read(&ctx->plugged))
+ return 1;
+ else if (enable)
+ display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_ON);
+ else
+ display_entity_hdmi_set_audiostate(ctx->entity,
+ DISPLAY_ENTITY_AUDIOSTATE_OFF);
+
+ return 1;
+}
+
+static struct snd_kcontrol_new hdmi_dapm_controls[] = {
+ SOC_SINGLE_BOOL_EXT("HDMI Playback Switch", 0, get_hdmi, put_hdmi),
+};
+
+static int hdmi_codec_init(struct snd_soc_codec *codec)
+{
+ struct hdmi_audio_context *ctx = get_ctx(codec->dev);
+ int ret;
+
+ ret = snd_soc_jack_new(codec, "HDMI Jack",
+ SND_JACK_AVOUT, &ctx->hdmi_jack);
+ if (ret) {
+ dev_err(codec->dev, "audio enable failed.\n");
+ return ret;
+ }
+
+ hdmi_dapm_controls[0].private_value = (unsigned long)codec->dev;
+ return 0;
+}
+
+static struct snd_soc_codec_driver hdmi_codec = {
+ .probe = hdmi_codec_init,
+ .controls = hdmi_dapm_controls,
+ .num_controls = ARRAY_SIZE(hdmi_dapm_controls),
+};
+
+static int hdmi_codec_probe(struct platform_device *pdev)
+{
+ struct hdmi_audio_context *ctx;
+ struct device_node *dev_node;
+ struct platform_device *disp_pdev;
+ int ret;
+
+ dev_dbg(&pdev->dev, "[%d][%s]\n", __LINE__, __func__);
+
+ ret = snd_soc_register_codec(&pdev->dev, &hdmi_codec,
+ &hdmi_codec_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "register_codec failed (%d)\n", ret);
+ return ret;
+ }
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(struct hdmi_audio_context),
+ GFP_KERNEL);
+ if (ctx == NULL)
+ return -ENOMEM;
+
+ ctx->pdev = pdev;
+ atomic_set(&ctx->enabled, 1);
+ platform_set_drvdata(pdev, ctx);
+
+ /* create workqueue and hotplug work */
+ ctx->event_wq = alloc_workqueue("hdmi-audio-event",
+ WQ_UNBOUND | WQ_NON_REENTRANT, 1);
+ if (ctx->event_wq == NULL) {
+ dev_err(&pdev->dev, "failed to create workqueue\n");
+ ret = -ENOMEM;
+ }
+ INIT_DELAYED_WORK(&ctx->hotplug_work, hotplug_event_handler);
+
+ dev_node = of_find_compatible_node(NULL, NULL,
+ "samsung,exynos5-hdmi");
+ if (!dev_node) {
+ dev_err(&pdev->dev, "[%d][%s] dt node not found.\n",
+ __LINE__, __func__);
+ return -EINVAL;
+ }
+
+ disp_pdev = of_find_device_by_node(dev_node);
+ if (!disp_pdev) {
+ dev_err(&pdev->dev, "[ERROR][%d][%s] No pdev\n",
+ __LINE__, __func__);
+ return -EINVAL;
+ }
+
+ ctx->notf.dev = &disp_pdev->dev;
+ ctx->notf.notify = exynos_hdmi_audio_notification;
+
+ ret = display_entity_register_notifier(&ctx->notf);
+ if (ret) {
+ dev_err(&pdev->dev, "[%d][%s] entity registe failed.\n",
+ __LINE__, __func__);
+ ret = -EINVAL;
+ goto err_workq;
+ }
+
+ return 0;
+
+err_workq:
+ destroy_workqueue(ctx->event_wq);
+ return ret;
+}
+
+static int hdmi_codec_remove(struct platform_device *pdev)
+{
+ struct hdmi_audio_context *ctx = get_ctx(&pdev->dev);
+ dev_dbg(&pdev->dev, "[%d][%s]\n", __LINE__, __func__);
+ mdelay(1000);
+
+ display_entity_register_notifier(&ctx->notf);
+ snd_soc_unregister_codec(&pdev->dev);
+ destroy_workqueue(ctx->event_wq);
+ return 0;
+}
+
+static struct platform_driver hdmi_codec_driver = {
+ .driver = {
+ .name = "exynos-hdmi-audio-codec",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = hdmi_codec_probe,
+ .remove = hdmi_codec_remove,
+};
+
+static int __init hdmi_audio_codec_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&hdmi_codec_driver);
+ if (ret < 0)
+ return -EINVAL;
+
+ exynos_hdmi_codec_pdev = platform_device_register_simple
+ ("exynos-hdmi-audio-codec", -1, NULL, 0);
+ if (IS_ERR_OR_NULL(exynos_hdmi_codec_pdev)) {
+ ret = PTR_ERR(exynos_hdmi_codec_pdev);
+ platform_driver_unregister(&hdmi_codec_driver);
+ return ret;
+ }
+
+ return 0;
+}
+static void __exit hdmi_audio_codec_exit(void)
+{
+ platform_driver_unregister(&hdmi_codec_driver);
+ platform_device_unregister(exynos_hdmi_codec_pdev);
+}
+
+module_init(hdmi_audio_codec_init);
+module_exit(hdmi_audio_codec_exit);
+
+MODULE_AUTHOR("Rahul Sharma <rahul.sharma@samsung.com>");
+MODULE_DESCRIPTION("ASoC EXYNOS HDMI codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:exynos-hdmi-codec");
V2: - DAPM and JACK control to hdmi codec. This patch registers hdmi-audio codec to the ALSA framework. This is the second client to the hdmi panel. Once notified by the CDF Core it proceeds towards audio setting and audio control. It also subscribes for hpd notification to implement hpd related audio requirements. Signed-off-by: Rahul Sharma <rahul.sharma@samsung.com> --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/exynos_hdmi_audio.c | 424 +++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 sound/soc/codecs/exynos_hdmi_audio.c