Merge remote-tracking branch 'asoc/topic/hdmi' into asoc-next
authorMark Brown <broonie@kernel.org>
Fri, 13 May 2016 13:27:16 +0000 (14:27 +0100)
committerMark Brown <broonie@kernel.org>
Fri, 13 May 2016 13:27:16 +0000 (14:27 +0100)
13 files changed:
include/sound/hdmi-codec.h [new file with mode: 0644]
include/sound/pcm_iec958.h
sound/core/pcm_iec958.c
sound/hda/local.h
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/hdac_hdmi.c
sound/soc/codecs/hdmi-codec.c [new file with mode: 0644]
sound/soc/intel/boards/skl_nau88l25_max98357a.c
sound/soc/intel/boards/skl_nau88l25_ssm4567.c
sound/soc/intel/boards/skl_rt286.c
sound/soc/intel/skylake/skl-pcm.c
sound/soc/intel/skylake/skl-topology.c

diff --git a/include/sound/hdmi-codec.h b/include/sound/hdmi-codec.h
new file mode 100644 (file)
index 0000000..fc3a481
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * hdmi-codec.h - HDMI Codec driver API
+ *
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef __HDMI_CODEC_H__
+#define __HDMI_CODEC_H__
+
+#include <linux/hdmi.h>
+#include <drm/drm_edid.h>
+#include <sound/asoundef.h>
+#include <uapi/sound/asound.h>
+
+/*
+ * Protocol between ASoC cpu-dai and HDMI-encoder
+ */
+struct hdmi_codec_daifmt {
+       enum {
+               HDMI_I2S,
+               HDMI_RIGHT_J,
+               HDMI_LEFT_J,
+               HDMI_DSP_A,
+               HDMI_DSP_B,
+               HDMI_AC97,
+               HDMI_SPDIF,
+       } fmt;
+       int bit_clk_inv:1;
+       int frame_clk_inv:1;
+       int bit_clk_master:1;
+       int frame_clk_master:1;
+};
+
+/*
+ * HDMI audio parameters
+ */
+struct hdmi_codec_params {
+       struct hdmi_audio_infoframe cea;
+       struct snd_aes_iec958 iec;
+       int sample_rate;
+       int sample_width;
+       int channels;
+};
+
+struct hdmi_codec_ops {
+       /*
+        * Called when ASoC starts an audio stream setup.
+        * Optional
+        */
+       int (*audio_startup)(struct device *dev);
+
+       /*
+        * Configures HDMI-encoder for audio stream.
+        * Mandatory
+        */
+       int (*hw_params)(struct device *dev,
+                        struct hdmi_codec_daifmt *fmt,
+                        struct hdmi_codec_params *hparms);
+
+       /*
+        * Shuts down the audio stream.
+        * Mandatory
+        */
+       void (*audio_shutdown)(struct device *dev);
+
+       /*
+        * Mute/unmute HDMI audio stream.
+        * Optional
+        */
+       int (*digital_mute)(struct device *dev, bool enable);
+
+       /*
+        * Provides EDID-Like-Data from connected HDMI device.
+        * Optional
+        */
+       int (*get_eld)(struct device *dev, uint8_t *buf, size_t len);
+};
+
+/* HDMI codec initalization data */
+struct hdmi_codec_pdata {
+       const struct hdmi_codec_ops *ops;
+       uint i2s:1;
+       uint spdif:1;
+       int max_i2s_channels;
+};
+
+#define HDMI_CODEC_DRV_NAME "hdmi-audio-codec"
+
+#endif /* __HDMI_CODEC_H__ */
index 0eed397..36f023a 100644 (file)
@@ -6,4 +6,6 @@
 int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
        size_t len);
 
+int snd_pcm_create_iec958_consumer_hw_params(struct snd_pcm_hw_params *params,
+                                            u8 *cs, size_t len);
 #endif
index 36b2d7a..5e6aed6 100644 (file)
@@ -9,30 +9,18 @@
 #include <linux/types.h>
 #include <sound/asoundef.h>
 #include <sound/pcm.h>
+#include <sound/pcm_params.h>
 #include <sound/pcm_iec958.h>
 
-/**
- * snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status
- * @runtime: pcm runtime structure with ->rate filled in
- * @cs: channel status buffer, at least four bytes
- * @len: length of channel status buffer
- *
- * Create the consumer format channel status data in @cs of maximum size
- * @len corresponding to the parameters of the PCM runtime @runtime.
- *
- * Drivers may wish to tweak the contents of the buffer after creation.
- *
- * Returns: length of buffer, or negative error code if something failed.
- */
-int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
-       size_t len)
+static int create_iec958_consumer(uint rate, uint sample_width,
+                                 u8 *cs, size_t len)
 {
        unsigned int fs, ws;
 
        if (len < 4)
                return -EINVAL;
 
-       switch (runtime->rate) {
+       switch (rate) {
        case 32000:
                fs = IEC958_AES3_CON_FS_32000;
                break;
@@ -59,7 +47,7 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
        }
 
        if (len > 4) {
-               switch (snd_pcm_format_width(runtime->format)) {
+               switch (sample_width) {
                case 16:
                        ws = IEC958_AES4_CON_WORDLEN_20_16;
                        break;
@@ -71,6 +59,7 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
                             IEC958_AES4_CON_MAX_WORDLEN_24;
                        break;
                case 24:
+               case 32: /* Assume 24-bit width for 32-bit samples. */
                        ws = IEC958_AES4_CON_WORDLEN_24_20 |
                             IEC958_AES4_CON_MAX_WORDLEN_24;
                        break;
@@ -92,4 +81,46 @@ int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
 
        return len;
 }
+
+/**
+ * snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status
+ * @runtime: pcm runtime structure with ->rate filled in
+ * @cs: channel status buffer, at least four bytes
+ * @len: length of channel status buffer
+ *
+ * Create the consumer format channel status data in @cs of maximum size
+ * @len corresponding to the parameters of the PCM runtime @runtime.
+ *
+ * Drivers may wish to tweak the contents of the buffer after creation.
+ *
+ * Returns: length of buffer, or negative error code if something failed.
+ */
+int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs,
+       size_t len)
+{
+       return create_iec958_consumer(runtime->rate,
+                                     snd_pcm_format_width(runtime->format),
+                                     cs, len);
+}
 EXPORT_SYMBOL(snd_pcm_create_iec958_consumer);
+
+/**
+ * snd_pcm_create_iec958_consumer_hw_params - create IEC958 channel status
+ * @hw_params: the hw_params instance for extracting rate and sample format
+ * @cs: channel status buffer, at least four bytes
+ * @len: length of channel status buffer
+ *
+ * Create the consumer format channel status data in @cs of maximum size
+ * @len corresponding to the parameters of the PCM runtime @runtime.
+ *
+ * Drivers may wish to tweak the contents of the buffer after creation.
+ *
+ * Returns: length of buffer, or negative error code if something failed.
+ */
+int snd_pcm_create_iec958_consumer_hw_params(struct snd_pcm_hw_params *params,
+                                            u8 *cs, size_t len)
+{
+       return create_iec958_consumer(params_rate(params), params_width(params),
+                                     cs, len);
+}
+EXPORT_SYMBOL(snd_pcm_create_iec958_consumer_hw_params);
index d692f41..0d5bb15 100644 (file)
@@ -16,6 +16,16 @@ static inline int get_wcaps_type(unsigned int wcaps)
        return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
 }
 
+static inline unsigned int get_wcaps_channels(u32 wcaps)
+{
+       unsigned int chans;
+
+       chans = (wcaps & AC_WCAP_CHAN_CNT_EXT) >> 13;
+       chans = (chans + 1) * 2;
+
+       return chans;
+}
+
 extern const struct attribute_group *hdac_dev_attr_groups[];
 int hda_widget_sysfs_init(struct hdac_device *codec);
 void hda_widget_sysfs_exit(struct hdac_device *codec);
index 26ae0b5..b3afae9 100644 (file)
@@ -88,6 +88,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_MC13783 if MFD_MC13XXX
        select SND_SOC_ML26124 if I2C
        select SND_SOC_NAU8825 if I2C
+       select SND_SOC_HDMI_CODEC
        select SND_SOC_PCM1681 if I2C
        select SND_SOC_PCM179X_I2C if I2C
        select SND_SOC_PCM179X_SPI if SPI_MASTER
@@ -478,6 +479,11 @@ config SND_SOC_BT_SCO
 config SND_SOC_DMIC
        tristate
 
+config SND_SOC_HDMI_CODEC
+       tristate
+       select SND_PCM_ELD
+       select SND_PCM_IEC958
+
 config SND_SOC_ES8328
        tristate "Everest Semi ES8328 CODEC"
 
index 4532a74..b7b9941 100644 (file)
@@ -81,6 +81,7 @@ snd-soc-max9850-objs := max9850.o
 snd-soc-mc13783-objs := mc13783.o
 snd-soc-ml26124-objs := ml26124.o
 snd-soc-nau8825-objs := nau8825.o
+snd-soc-hdmi-codec-objs := hdmi-codec.o
 snd-soc-pcm1681-objs := pcm1681.o
 snd-soc-pcm179x-codec-objs := pcm179x.o
 snd-soc-pcm179x-i2c-objs := pcm179x-i2c.o
@@ -291,6 +292,7 @@ obj-$(CONFIG_SND_SOC_MAX9850)       += snd-soc-max9850.o
 obj-$(CONFIG_SND_SOC_MC13783)  += snd-soc-mc13783.o
 obj-$(CONFIG_SND_SOC_ML26124)  += snd-soc-ml26124.o
 obj-$(CONFIG_SND_SOC_NAU8825)   += snd-soc-nau8825.o
+obj-$(CONFIG_SND_SOC_HDMI_CODEC)       += snd-soc-hdmi-codec.o
 obj-$(CONFIG_SND_SOC_PCM1681)  += snd-soc-pcm1681.o
 obj-$(CONFIG_SND_SOC_PCM179X)  += snd-soc-pcm179x-codec.o
 obj-$(CONFIG_SND_SOC_PCM179X_I2C)      += snd-soc-pcm179x-i2c.o
index 13002f3..3733297 100644 (file)
@@ -29,6 +29,7 @@
 #include <sound/hdaudio_ext.h>
 #include <sound/hda_i915.h>
 #include <sound/pcm_drm_eld.h>
+#include <sound/hda_chmap.h>
 #include "../../hda/local.h"
 #include "hdac_hdmi.h"
 
@@ -60,11 +61,17 @@ struct hdac_hdmi_cvt {
        struct hdac_hdmi_cvt_params params;
 };
 
+/* Currently only spk_alloc, more to be added */
+struct hdac_hdmi_parsed_eld {
+       u8 spk_alloc;
+};
+
 struct hdac_hdmi_eld {
        bool    monitor_present;
        bool    eld_valid;
        int     eld_size;
        char    eld_buffer[ELD_MAX_SIZE];
+       struct  hdac_hdmi_parsed_eld info;
 };
 
 struct hdac_hdmi_pin {
@@ -76,6 +83,10 @@ struct hdac_hdmi_pin {
        struct hdac_ext_device *edev;
        int repoll_count;
        struct delayed_work work;
+       struct mutex lock;
+       bool chmap_set;
+       unsigned char chmap[8]; /* ALSA API channel-map */
+       int channels; /* current number of channels */
 };
 
 struct hdac_hdmi_pcm {
@@ -100,8 +111,22 @@ struct hdac_hdmi_priv {
        int num_pin;
        int num_cvt;
        struct mutex pin_mutex;
+       struct hdac_chmap chmap;
 };
 
+static struct hdac_hdmi_pcm *get_hdmi_pcm_from_id(struct hdac_hdmi_priv *hdmi,
+                                               int pcm_idx)
+{
+       struct hdac_hdmi_pcm *pcm;
+
+       list_for_each_entry(pcm, &hdmi->pcm_list, head) {
+               if (pcm->pcm_id == pcm_idx)
+                       return pcm;
+       }
+
+       return NULL;
+}
+
 static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
 {
        struct hdac_device *hdac = dev_to_hdac_dev(dev);
@@ -278,26 +303,31 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
        int i;
        const u8 *eld_buf;
        u8 conn_type;
-       int channels = 2;
+       int channels, ca;
 
        list_for_each_entry(pin, &hdmi->pin_list, head) {
                if (pin->nid == pin_nid)
                        break;
        }
 
+       ca = snd_hdac_channel_allocation(&hdac->hdac, pin->eld.info.spk_alloc,
+                       pin->channels, pin->chmap_set, true, pin->chmap);
+
+       channels = snd_hdac_get_active_channels(ca);
+       hdmi->chmap.ops.set_channel_count(&hdac->hdac, cvt_nid, channels);
+
+       snd_hdac_setup_channel_mapping(&hdmi->chmap, pin->nid, false, ca,
+                               pin->channels, pin->chmap, pin->chmap_set);
+
        eld_buf = pin->eld.eld_buffer;
        conn_type = drm_eld_get_conn_type(eld_buf);
 
-       /* setup channel count */
-       snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0,
-                           AC_VERB_SET_CVT_CHAN_COUNT, channels - 1);
-
        switch (conn_type) {
        case DRM_ELD_CONN_TYPE_HDMI:
                hdmi_audio_infoframe_init(&frame);
 
-               /* Default stereo for now */
                frame.channels = channels;
+               frame.channel_allocation = ca;
 
                ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
                if (ret < 0)
@@ -311,7 +341,7 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
                dp_ai.len       = 0x1b;
                dp_ai.ver       = 0x11 << 2;
                dp_ai.CC02_CT47 = channels - 1;
-               dp_ai.CA        = 0;
+               dp_ai.CA        = ca;
 
                dip = (u8 *)&dp_ai;
                break;
@@ -370,17 +400,23 @@ static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
        struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
        struct hdac_hdmi_priv *hdmi = hdac->private_data;
        struct hdac_hdmi_dai_pin_map *dai_map;
+       struct hdac_hdmi_pin *pin;
        struct hdac_ext_dma_params *dd;
        int ret;
 
        dai_map = &hdmi->dai_map[dai->id];
+       pin = dai_map->pin;
 
        dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream);
        dev_dbg(&hdac->hdac.dev, "stream tag from cpu dai %d format in cvt 0x%x\n",
                        dd->stream_tag, dd->format);
 
+       mutex_lock(&pin->lock);
+       pin->channels = substream->runtime->channels;
+
        ret = hdac_hdmi_setup_audio_infoframe(hdac, dai_map->cvt->nid,
                                                dai_map->pin->nid);
+       mutex_unlock(&pin->lock);
        if (ret < 0)
                return ret;
 
@@ -640,6 +676,12 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
                snd_hdac_codec_write(&hdac->hdac, dai_map->pin->nid, 0,
                        AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
 
+               mutex_lock(&dai_map->pin->lock);
+               dai_map->pin->chmap_set = false;
+               memset(dai_map->pin->chmap, 0, sizeof(dai_map->pin->chmap));
+               dai_map->pin->channels = 0;
+               mutex_unlock(&dai_map->pin->lock);
+
                dai_map->pin = NULL;
        }
 }
@@ -647,10 +689,19 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
 static int
 hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt)
 {
+       unsigned int chans;
+       struct hdac_ext_device *edev = to_ehdac_device(hdac);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
        int err;
 
-       /* Only stereo supported as of now */
-       cvt->params.channels_min = cvt->params.channels_max = 2;
+       chans = get_wcaps(hdac, cvt->nid);
+       chans = get_wcaps_channels(chans);
+
+       cvt->params.channels_min = 2;
+
+       cvt->params.channels_max = chans;
+       if (chans > hdmi->chmap.channels_max)
+               hdmi->chmap.channels_max = chans;
 
        err = snd_hdac_query_supported_pcm(hdac, cvt->nid,
                        &cvt->params.rates,
@@ -1008,6 +1059,12 @@ static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
        return hdac_hdmi_query_cvt_params(&edev->hdac, cvt);
 }
 
+static void hdac_hdmi_parse_eld(struct hdac_ext_device *edev,
+                       struct hdac_hdmi_pin *pin)
+{
+       pin->eld.info.spk_alloc = pin->eld.eld_buffer[DRM_ELD_SPEAKER];
+}
+
 static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
 {
        struct hdac_ext_device *edev = pin->edev;
@@ -1065,6 +1122,7 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
 
                                snd_jack_report(pcm->jack, SND_JACK_AVOUT);
                        }
+                       hdac_hdmi_parse_eld(edev, pin);
 
                        print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET,
                                        pin->eld.eld_buffer, pin->eld.eld_size);
@@ -1123,6 +1181,7 @@ static int hdac_hdmi_add_pin(struct hdac_ext_device *edev, hda_nid_t nid)
        hdmi->num_pin++;
 
        pin->edev = edev;
+       mutex_init(&pin->lock);
        INIT_DELAYED_WORK(&pin->work, hdac_hdmi_repoll_eld);
 
        return 0;
@@ -1342,6 +1401,19 @@ static struct i915_audio_component_audio_ops aops = {
        .pin_eld_notify = hdac_hdmi_eld_notify_cb,
 };
 
+static struct snd_pcm *hdac_hdmi_get_pcm_from_id(struct snd_soc_card *card,
+                                               int device)
+{
+       struct snd_soc_pcm_runtime *rtd;
+
+       list_for_each_entry(rtd, &card->rtd_list, list) {
+               if (rtd->pcm && (rtd->pcm->device == device))
+                       return rtd->pcm;
+       }
+
+       return NULL;
+}
+
 int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
 {
        char jack_name[NAME_SIZE];
@@ -1351,6 +1423,8 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
                snd_soc_component_get_dapm(&codec->component);
        struct hdac_hdmi_priv *hdmi = edev->private_data;
        struct hdac_hdmi_pcm *pcm;
+       struct snd_pcm *snd_pcm;
+       int err;
 
        /*
         * this is a new PCM device, create new pcm and
@@ -1362,6 +1436,18 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
        pcm->pcm_id = device;
        pcm->cvt = hdmi->dai_map[dai->id].cvt;
 
+       snd_pcm = hdac_hdmi_get_pcm_from_id(dai->component->card, device);
+       if (snd_pcm) {
+               err = snd_hdac_add_chmap_ctls(snd_pcm, device, &hdmi->chmap);
+               if (err < 0) {
+                       dev_err(&edev->hdac.dev,
+                               "chmap control add failed with err: %d for pcm: %d\n",
+                               err, device);
+                       kfree(pcm);
+                       return err;
+               }
+       }
+
        list_add_tail(&pcm->head, &hdmi->pcm_list);
 
        sprintf(jack_name, "HDMI/DP, pcm=%d Jack", device);
@@ -1483,6 +1569,60 @@ static struct snd_soc_codec_driver hdmi_hda_codec = {
        .idle_bias_off  = true,
 };
 
+static void hdac_hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
+                                       unsigned char *chmap)
+{
+       struct hdac_ext_device *edev = to_ehdac_device(hdac);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
+       struct hdac_hdmi_pin *pin = pcm->pin;
+
+       /* chmap is already set to 0 in caller */
+       if (!pin)
+               return;
+
+       memcpy(chmap, pin->chmap, ARRAY_SIZE(pin->chmap));
+}
+
+static void hdac_hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx,
+                               unsigned char *chmap, int prepared)
+{
+       struct hdac_ext_device *edev = to_ehdac_device(hdac);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
+       struct hdac_hdmi_pin *pin = pcm->pin;
+
+       mutex_lock(&pin->lock);
+       pin->chmap_set = true;
+       memcpy(pin->chmap, chmap, ARRAY_SIZE(pin->chmap));
+       if (prepared)
+               hdac_hdmi_setup_audio_infoframe(edev, pcm->cvt->nid, pin->nid);
+       mutex_unlock(&pin->lock);
+}
+
+static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdac, int pcm_idx)
+{
+       struct hdac_ext_device *edev = to_ehdac_device(hdac);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
+       struct hdac_hdmi_pin *pin = pcm->pin;
+
+       return pin ? true:false;
+}
+
+static int hdac_hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx)
+{
+       struct hdac_ext_device *edev = to_ehdac_device(hdac);
+       struct hdac_hdmi_priv *hdmi = edev->private_data;
+       struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
+       struct hdac_hdmi_pin *pin = pcm->pin;
+
+       if (!pin || !pin->eld.eld_valid)
+               return 0;
+
+       return pin->eld.info.spk_alloc;
+}
+
 static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
 {
        struct hdac_device *codec = &edev->hdac;
@@ -1501,6 +1641,11 @@ static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
                return -ENOMEM;
 
        edev->private_data = hdmi_priv;
+       snd_hdac_register_chmap_ops(codec, &hdmi_priv->chmap);
+       hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap;
+       hdmi_priv->chmap.ops.set_chmap = hdac_hdmi_set_chmap;
+       hdmi_priv->chmap.ops.is_pcm_attached = is_hdac_hdmi_pcm_attached;
+       hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc;
 
        dev_set_drvdata(&codec->dev, edev);
 
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
new file mode 100644 (file)
index 0000000..8e36e88
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * 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_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);
+               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->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);
+
+       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->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, 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 = {
+       .name = "i2s-hifi",
+       .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 = {
+       .name = "spdif-hifi",
+       .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 struct snd_soc_codec_driver hdmi_codec = {
+       .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),
+};
+
+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;
+       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;
+
+       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;
+               i++;
+       }
+
+       if (hcd->spdif)
+               hcp->daidrv[i] = hdmi_spdif_dai;
+
+       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);
index 7cc7251..d280865 100644 (file)
 static struct snd_soc_jack skylake_headset;
 static struct snd_soc_card skylake_audio_card;
 
+struct skl_hdmi_pcm {
+       struct list_head head;
+       struct snd_soc_dai *codec_dai;
+       int device;
+};
+
+struct skl_nau8825_private {
+       struct list_head hdmi_pcm_list;
+};
+
 enum {
        SKL_DPCM_AUDIO_PB = 0,
        SKL_DPCM_AUDIO_CP,
@@ -192,23 +202,56 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
 
 static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
+
+       pcm->device = SKL_DPCM_AUDIO_HDMI1_PB;
+       pcm->codec_dai = dai;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI1_PB);
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI2_PB);
+       pcm->device = SKL_DPCM_AUDIO_HDMI2_PB;
+       pcm->codec_dai = dai;
+
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI3_PB);
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
+
+       pcm->device = SKL_DPCM_AUDIO_HDMI3_PB;
+       pcm->codec_dai = dai;
+
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
@@ -533,6 +576,21 @@ static struct snd_soc_dai_link skylake_dais[] = {
        },
 };
 
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+       struct skl_nau8825_private *ctx = snd_soc_card_get_drvdata(card);
+       struct skl_hdmi_pcm *pcm;
+       int err;
+
+       list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+               err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 /* skylake audio machine driver for SPT + NAU88L25 */
 static struct snd_soc_card skylake_audio_card = {
        .name = "sklnau8825max",
@@ -546,11 +604,21 @@ static struct snd_soc_card skylake_audio_card = {
        .dapm_routes = skylake_map,
        .num_dapm_routes = ARRAY_SIZE(skylake_map),
        .fully_routed = true,
+       .late_probe = skylake_card_late_probe,
 };
 
 static int skylake_audio_probe(struct platform_device *pdev)
 {
+       struct skl_nau8825_private *ctx;
+
+       ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC);
+       if (!ctx)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
        skylake_audio_card.dev = &pdev->dev;
+       snd_soc_card_set_drvdata(&skylake_audio_card, ctx);
 
        return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
 }
index 73cbddb..e19aa99 100644 (file)
 static struct snd_soc_jack skylake_headset;
 static struct snd_soc_card skylake_audio_card;
 
+struct skl_hdmi_pcm {
+       struct list_head head;
+       struct snd_soc_dai *codec_dai;
+       int device;
+};
+
+struct skl_nau88125_private {
+       struct list_head hdmi_pcm_list;
+};
 enum {
        SKL_DPCM_AUDIO_PB = 0,
        SKL_DPCM_AUDIO_CP,
@@ -222,24 +231,57 @@ static int skylake_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd)
 
 static int skylake_hdmi1_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
+
+       pcm->device = SKL_DPCM_AUDIO_HDMI1_PB;
+       pcm->codec_dai = dai;
+
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI1_PB);
+       return 0;
 }
 
 static int skylake_hdmi2_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
+
+       pcm->device = SKL_DPCM_AUDIO_HDMI2_PB;
+       pcm->codec_dai = dai;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI2_PB);
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 
 static int skylake_hdmi3_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI3_PB);
+       pcm->device = SKL_DPCM_AUDIO_HDMI3_PB;
+       pcm->codec_dai = dai;
+
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 static int skylake_nau8825_fe_init(struct snd_soc_pcm_runtime *rtd)
@@ -584,6 +626,21 @@ static struct snd_soc_dai_link skylake_dais[] = {
        },
 };
 
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+       struct skl_nau88125_private *ctx = snd_soc_card_get_drvdata(card);
+       struct skl_hdmi_pcm *pcm;
+       int err;
+
+       list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+               err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 /* skylake audio machine driver for SPT + NAU88L25 */
 static struct snd_soc_card skylake_audio_card = {
        .name = "sklnau8825adi",
@@ -599,11 +656,21 @@ static struct snd_soc_card skylake_audio_card = {
        .codec_conf = ssm4567_codec_conf,
        .num_configs = ARRAY_SIZE(ssm4567_codec_conf),
        .fully_routed = true,
+       .late_probe = skylake_card_late_probe,
 };
 
 static int skylake_audio_probe(struct platform_device *pdev)
 {
+       struct skl_nau88125_private *ctx;
+
+       ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC);
+       if (!ctx)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
        skylake_audio_card.dev = &pdev->dev;
+       snd_soc_card_set_drvdata(&skylake_audio_card, ctx);
 
        return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card);
 }
index d4cdb94..426b482 100644 (file)
 
 static struct snd_soc_jack skylake_headset;
 
+struct skl_hdmi_pcm {
+       struct list_head head;
+       struct snd_soc_dai *codec_dai;
+       int device;
+};
+
+struct skl_rt286_private {
+       struct list_head hdmi_pcm_list;
+};
+
 enum {
        SKL_DPCM_AUDIO_PB = 0,
        SKL_DPCM_AUDIO_CP,
@@ -142,9 +152,20 @@ static int skylake_rt286_codec_init(struct snd_soc_pcm_runtime *rtd)
 
 static int skylake_hdmi_init(struct snd_soc_pcm_runtime *rtd)
 {
+       struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(rtd->card);
        struct snd_soc_dai *dai = rtd->codec_dai;
+       struct skl_hdmi_pcm *pcm;
+
+       pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
+       if (!pcm)
+               return -ENOMEM;
 
-       return hdac_hdmi_jack_init(dai, SKL_DPCM_AUDIO_HDMI1_PB + dai->id);
+       pcm->device = SKL_DPCM_AUDIO_HDMI1_PB + dai->id;
+       pcm->codec_dai = dai;
+
+       list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
+
+       return 0;
 }
 
 static unsigned int rates[] = {
@@ -437,6 +458,21 @@ static struct snd_soc_dai_link skylake_rt286_dais[] = {
        },
 };
 
+static int skylake_card_late_probe(struct snd_soc_card *card)
+{
+       struct skl_rt286_private *ctx = snd_soc_card_get_drvdata(card);
+       struct skl_hdmi_pcm *pcm;
+       int err;
+
+       list_for_each_entry(pcm, &ctx->hdmi_pcm_list, head) {
+               err = hdac_hdmi_jack_init(pcm->codec_dai, pcm->device);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 /* skylake audio machine driver for SPT + RT286S */
 static struct snd_soc_card skylake_rt286 = {
        .name = "skylake-rt286",
@@ -450,11 +486,21 @@ static struct snd_soc_card skylake_rt286 = {
        .dapm_routes = skylake_rt286_map,
        .num_dapm_routes = ARRAY_SIZE(skylake_rt286_map),
        .fully_routed = true,
+       .late_probe = skylake_card_late_probe,
 };
 
 static int skylake_audio_probe(struct platform_device *pdev)
 {
+       struct skl_rt286_private *ctx;
+
+       ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC);
+       if (!ctx)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
+
        skylake_rt286.dev = &pdev->dev;
+       snd_soc_card_set_drvdata(&skylake_rt286, ctx);
 
        return devm_snd_soc_register_card(&pdev->dev, &skylake_rt286);
 }
index 1548023..7c81b31 100644 (file)
@@ -51,7 +51,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
        .rate_min =             8000,
        .rate_max =             48000,
        .channels_min =         1,
-       .channels_max =         HDA_QUAD,
+       .channels_max =         8,
        .buffer_bytes_max =     AZX_MAX_BUF_SIZE,
        .period_bytes_min =     128,
        .period_bytes_max =     AZX_MAX_BUF_SIZE / 2,
@@ -691,7 +691,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "HDMI1 Playback",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .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 |
@@ -706,7 +706,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "HDMI2 Playback",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .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 |
@@ -721,7 +721,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "HDMI3 Playback",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .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 |
@@ -846,7 +846,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "iDisp1 Tx",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .channels_max = 8,
                .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000,
                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
                        SNDRV_PCM_FMTBIT_S24_LE,
@@ -858,7 +858,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "iDisp2 Tx",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .channels_max = 8,
                .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|
                        SNDRV_PCM_RATE_48000,
                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
@@ -871,7 +871,7 @@ static struct snd_soc_dai_driver skl_platform_dai[] = {
        .playback = {
                .stream_name = "iDisp3 Tx",
                .channels_min = HDA_STEREO,
-               .channels_max = HDA_STEREO,
+               .channels_max = 8,
                .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|
                        SNDRV_PCM_RATE_48000,
                .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE |
index a1f4478..3e036b0 100644 (file)
@@ -154,13 +154,32 @@ static void skl_dump_mconfig(struct skl_sst *ctx,
        dev_dbg(ctx->dev, "ch_cfg = %d\n", mcfg->out_fmt[0].ch_cfg);
 }
 
+static void skl_tplg_update_chmap(struct skl_module_fmt *fmt, int chs)
+{
+       int slot_map = 0xFFFFFFFF;
+       int start_slot = 0;
+       int i;
+
+       for (i = 0; i < chs; i++) {
+               /*
+                * For 2 channels with starting slot as 0, slot map will
+                * look like 0xFFFFFF10.
+                */
+               slot_map &= (~(0xF << (4 * i)) | (start_slot << (4 * i)));
+               start_slot++;
+       }
+       fmt->ch_map = slot_map;
+}
+
 static void skl_tplg_update_params(struct skl_module_fmt *fmt,
                        struct skl_pipe_params *params, int fixup)
 {
        if (fixup & SKL_RATE_FIXUP_MASK)
                fmt->s_freq = params->s_freq;
-       if (fixup & SKL_CH_FIXUP_MASK)
+       if (fixup & SKL_CH_FIXUP_MASK) {
                fmt->channels = params->ch;
+               skl_tplg_update_chmap(fmt, fmt->channels);
+       }
        if (fixup & SKL_FMT_FIXUP_MASK) {
                fmt->valid_bit_depth = skl_get_bit_depth(params->s_fmt);