diff options
author | Mark Brown <broonie@kernel.org> | 2016-01-11 16:54:28 +0300 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2016-01-11 16:54:28 +0300 |
commit | 7b2f32cc81e14149e2fe515f69f35f9e11e8ba5a (patch) | |
tree | dac5980859c5af6566c7f9be2a12c7b68651eea9 /sound/soc/codecs | |
parent | b7f08d29c9ef76fb4bc95385e3f90b51e0494a3b (diff) | |
parent | fe09dd8eb2310ec658f49a5431df2259f11cbe9e (diff) | |
download | linux-7b2f32cc81e14149e2fe515f69f35f9e11e8ba5a.tar.xz |
Merge remote-tracking branch 'asoc/topic/intel' into asoc-next
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r-- | sound/soc/codecs/Kconfig | 6 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/codecs/hdac_hdmi.c | 697 | ||||
-rw-r--r-- | sound/soc/codecs/rt5640.c | 103 | ||||
-rw-r--r-- | sound/soc/codecs/rt5640.h | 17 |
5 files changed, 825 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cfdafc4c11ea..8bba374b8860 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -66,6 +66,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C select SND_SOC_GTM601 + select SND_SOC_HDAC_HDMI select SND_SOC_ICS43432 select SND_SOC_ISABELLE if I2C select SND_SOC_JZ4740_CODEC @@ -468,6 +469,11 @@ config SND_SOC_ES8328_SPI config SND_SOC_GTM601 tristate 'GTM601 UMTS modem audio codec' +config SND_SOC_HDAC_HDMI + tristate + select SND_HDA_EXT_CORE + select HDMI + config SND_SOC_ICS43432 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f632fc42f59f..bcd5ad6b6fb0 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -59,6 +59,7 @@ snd-soc-es8328-objs := es8328.o snd-soc-es8328-i2c-objs := es8328-i2c.o snd-soc-es8328-spi-objs := es8328-spi.o snd-soc-gtm601-objs := gtm601.o +snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-ics43432-objs := ics43432.o snd-soc-isabelle-objs := isabelle.o snd-soc-jz4740-codec-objs := jz4740.o @@ -254,6 +255,7 @@ obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o +obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c new file mode 100644 index 000000000000..5a1ec0f7a1a6 --- /dev/null +++ b/sound/soc/codecs/hdac_hdmi.c @@ -0,0 +1,697 @@ +/* + * hdac_hdmi.c - ASoc HDA-HDMI codec driver for Intel platforms + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Samreen Nilofer <samreen.nilofer@intel.com> + * Subhransu S. Prusty <subhransu.s.prusty@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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/init.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/hdmi.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_i915.h> +#include "../../hda/local.h" + +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define PIN_OUT (AC_PINCTL_OUT_EN) + +#define HDA_MAX_CONNECTIONS 32 + +struct hdac_hdmi_cvt_params { + unsigned int channels_min; + unsigned int channels_max; + u32 rates; + u64 formats; + unsigned int maxbps; +}; + +struct hdac_hdmi_cvt { + struct list_head head; + hda_nid_t nid; + struct hdac_hdmi_cvt_params params; +}; + +struct hdac_hdmi_pin { + struct list_head head; + hda_nid_t nid; + int num_mux_nids; + hda_nid_t mux_nids[HDA_MAX_CONNECTIONS]; +}; + +struct hdac_hdmi_dai_pin_map { + int dai_id; + struct hdac_hdmi_pin *pin; + struct hdac_hdmi_cvt *cvt; +}; + +struct hdac_hdmi_priv { + struct hdac_hdmi_dai_pin_map dai_map[3]; + struct list_head pin_list; + struct list_head cvt_list; + int num_pin; + int num_cvt; +}; + +static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev) +{ + struct hdac_device *hdac = dev_to_hdac_dev(dev); + + return to_ehdac_device(hdac); +} + +static int hdac_hdmi_setup_stream(struct hdac_ext_device *hdac, + hda_nid_t cvt_nid, hda_nid_t pin_nid, + u32 stream_tag, int format) +{ + unsigned int val; + + dev_dbg(&hdac->hdac.dev, "cvt nid %d pnid %d stream %d format 0x%x\n", + cvt_nid, pin_nid, stream_tag, format); + + val = (stream_tag << 4); + + snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, val); + snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0, + AC_VERB_SET_STREAM_FORMAT, format); + + return 0; +} + +static void +hdac_hdmi_set_dip_index(struct hdac_ext_device *hdac, hda_nid_t pin_nid, + int packet_index, int byte_index) +{ + int val; + + val = (packet_index << 5) | (byte_index & 0x1f); + + snd_hdac_codec_write(&hdac->hdac, pin_nid, 0, + AC_VERB_SET_HDMI_DIP_INDEX, val); +} + +static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac, + hda_nid_t cvt_nid, hda_nid_t pin_nid) +{ + uint8_t buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AUDIO_INFOFRAME_SIZE]; + struct hdmi_audio_infoframe frame; + u8 *dip = (u8 *)&frame; + int ret; + int i; + + hdmi_audio_infoframe_init(&frame); + + /* Default stereo for now */ + frame.channels = 2; + + /* setup channel count */ + snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0, + AC_VERB_SET_CVT_CHAN_COUNT, frame.channels - 1); + + ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + /* stop infoframe transmission */ + hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0); + snd_hdac_codec_write(&hdac->hdac, pin_nid, 0, + AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_DISABLE); + + + /* Fill infoframe. Index auto-incremented */ + hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0); + for (i = 0; i < sizeof(frame); i++) + snd_hdac_codec_write(&hdac->hdac, pin_nid, 0, + AC_VERB_SET_HDMI_DIP_DATA, dip[i]); + + /* Start infoframe */ + hdac_hdmi_set_dip_index(hdac, pin_nid, 0x0, 0x0); + snd_hdac_codec_write(&hdac->hdac, pin_nid, 0, + AC_VERB_SET_HDMI_DIP_XMIT, AC_DIPXMIT_BEST); + + return 0; +} + +static void hdac_hdmi_set_power_state(struct hdac_ext_device *edev, + struct hdac_hdmi_dai_pin_map *dai_map, unsigned int pwr_state) +{ + /* Power up pin widget */ + if (!snd_hdac_check_power_state(&edev->hdac, dai_map->pin->nid, + pwr_state)) + snd_hdac_codec_write(&edev->hdac, dai_map->pin->nid, 0, + AC_VERB_SET_POWER_STATE, pwr_state); + + /* Power up converter */ + if (!snd_hdac_check_power_state(&edev->hdac, dai_map->cvt->nid, + pwr_state)) + snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_SET_POWER_STATE, pwr_state); +} + +static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + 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_ext_dma_params *dd; + int ret; + + if (dai->id > 0) { + dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n"); + return -ENODEV; + } + + dai_map = &hdmi->dai_map[dai->id]; + + 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); + + ret = hdac_hdmi_setup_audio_infoframe(hdac, dai_map->cvt->nid, + dai_map->pin->nid); + if (ret < 0) + return ret; + + return hdac_hdmi_setup_stream(hdac, dai_map->cvt->nid, + dai_map->pin->nid, dd->stream_tag, dd->format); +} + +static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hparams, struct snd_soc_dai *dai) +{ + struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai); + struct hdac_ext_dma_params *dd; + + if (dai->id > 0) { + dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n"); + return -ENODEV; + } + + dd = kzalloc(sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + dd->format = snd_hdac_calc_stream_format(params_rate(hparams), + params_channels(hparams), params_format(hparams), + 24, 0); + + snd_soc_dai_set_dma_data(dai, substream, (void *)dd); + + return 0; +} + +static int hdac_hdmi_playback_cleanup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdac_ext_device *edev = snd_soc_dai_get_drvdata(dai); + struct hdac_ext_dma_params *dd; + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct hdac_hdmi_dai_pin_map *dai_map; + + dai_map = &hdmi->dai_map[dai->id]; + + snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, 0); + snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0, + AC_VERB_SET_STREAM_FORMAT, 0); + + dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + + kfree(dd); + + return 0; +} + +static int hdac_hdmi_pcm_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + 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; + int val; + + if (dai->id > 0) { + dev_err(&hdac->hdac.dev, "Only one dai supported as of now\n"); + return -ENODEV; + } + + dai_map = &hdmi->dai_map[dai->id]; + + val = snd_hdac_codec_read(&hdac->hdac, dai_map->pin->nid, 0, + AC_VERB_GET_PIN_SENSE, 0); + dev_info(&hdac->hdac.dev, "Val for AC_VERB_GET_PIN_SENSE: %x\n", val); + + if ((!(val & AC_PINSENSE_PRESENCE)) || (!(val & AC_PINSENSE_ELDV))) { + dev_err(&hdac->hdac.dev, "Monitor presence invalid with val: %x\n", val); + return -ENODEV; + } + + hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D0); + + snd_hdac_codec_write(&hdac->hdac, dai_map->pin->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, 2); + + return 0; +} + +static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + 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; + + dai_map = &hdmi->dai_map[dai->id]; + + hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D3); + + snd_hdac_codec_write(&hdac->hdac, dai_map->pin->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); +} + +static int +hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt) +{ + int err; + + /* Only stereo supported as of now */ + cvt->params.channels_min = cvt->params.channels_max = 2; + + err = snd_hdac_query_supported_pcm(hdac, cvt->nid, + &cvt->params.rates, + &cvt->params.formats, + &cvt->params.maxbps); + if (err < 0) + dev_err(&hdac->dev, + "Failed to query pcm params for nid %d: %d\n", + cvt->nid, err); + + return err; +} + +static void hdac_hdmi_fill_widget_info(struct snd_soc_dapm_widget *w, + enum snd_soc_dapm_type id, + const char *wname, const char *stream) +{ + w->id = id; + w->name = wname; + w->sname = stream; + w->reg = SND_SOC_NOPM; + w->shift = 0; + w->kcontrol_news = NULL; + w->num_kcontrols = 0; + w->priv = NULL; +} + +static void hdac_hdmi_fill_route(struct snd_soc_dapm_route *route, + const char *sink, const char *control, const char *src) +{ + route->sink = sink; + route->source = src; + route->control = control; + route->connected = NULL; +} + +static void create_fill_widget_route_map(struct snd_soc_dapm_context *dapm, + struct hdac_hdmi_dai_pin_map *dai_map) +{ + struct snd_soc_dapm_route route[1]; + struct snd_soc_dapm_widget widgets[2] = { {0} }; + + memset(&route, 0, sizeof(route)); + + hdac_hdmi_fill_widget_info(&widgets[0], snd_soc_dapm_output, + "hif1 Output", NULL); + hdac_hdmi_fill_widget_info(&widgets[1], snd_soc_dapm_aif_in, + "Coverter 1", "hif1"); + + hdac_hdmi_fill_route(&route[0], "hif1 Output", NULL, "Coverter 1"); + + snd_soc_dapm_new_controls(dapm, widgets, ARRAY_SIZE(widgets)); + snd_soc_dapm_add_routes(dapm, route, ARRAY_SIZE(route)); +} + +static int hdac_hdmi_init_dai_map(struct hdac_ext_device *edev) +{ + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct hdac_hdmi_dai_pin_map *dai_map = &hdmi->dai_map[0]; + struct hdac_hdmi_cvt *cvt; + struct hdac_hdmi_pin *pin; + + if (list_empty(&hdmi->cvt_list) || list_empty(&hdmi->pin_list)) + return -EINVAL; + + /* + * Currently on board only 1 pin and 1 converter is enabled for + * simplification, more will be added eventually + * So using fixed map for dai_id:pin:cvt + */ + cvt = list_first_entry(&hdmi->cvt_list, struct hdac_hdmi_cvt, head); + pin = list_first_entry(&hdmi->pin_list, struct hdac_hdmi_pin, head); + + dai_map->dai_id = 0; + dai_map->pin = pin; + + dai_map->cvt = cvt; + + /* Enable out path for this pin widget */ + snd_hdac_codec_write(&edev->hdac, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + + /* Enable transmission */ + snd_hdac_codec_write(&edev->hdac, cvt->nid, 0, + AC_VERB_SET_DIGI_CONVERT_1, 1); + + /* Category Code (CC) to zero */ + snd_hdac_codec_write(&edev->hdac, cvt->nid, 0, + AC_VERB_SET_DIGI_CONVERT_2, 0); + + snd_hdac_codec_write(&edev->hdac, pin->nid, 0, + AC_VERB_SET_CONNECT_SEL, 0); + + return 0; +} + +static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid) +{ + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct hdac_hdmi_cvt *cvt; + + cvt = kzalloc(sizeof(*cvt), GFP_KERNEL); + if (!cvt) + return -ENOMEM; + + cvt->nid = nid; + + list_add_tail(&cvt->head, &hdmi->cvt_list); + hdmi->num_cvt++; + + return hdac_hdmi_query_cvt_params(&edev->hdac, cvt); +} + +static int hdac_hdmi_add_pin(struct hdac_ext_device *edev, hda_nid_t nid) +{ + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct hdac_hdmi_pin *pin; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return -ENOMEM; + + pin->nid = nid; + + list_add_tail(&pin->head, &hdmi->pin_list); + hdmi->num_pin++; + + return 0; +} + +/* + * Parse all nodes and store the cvt/pin nids in array + * Add one time initialization for pin and cvt widgets + */ +static int hdac_hdmi_parse_and_map_nid(struct hdac_ext_device *edev) +{ + hda_nid_t nid; + int i, num_nodes; + struct hdac_device *hdac = &edev->hdac; + struct hdac_hdmi_priv *hdmi = edev->private_data; + int ret; + + num_nodes = snd_hdac_get_sub_nodes(hdac, hdac->afg, &nid); + if (!nid || num_nodes <= 0) { + dev_warn(&hdac->dev, "HDMI: failed to get afg sub nodes\n"); + return -EINVAL; + } + + hdac->num_nodes = num_nodes; + hdac->start_nid = nid; + + for (i = 0; i < hdac->num_nodes; i++, nid++) { + unsigned int caps; + unsigned int type; + + caps = get_wcaps(hdac, nid); + type = get_wcaps_type(caps); + + if (!(caps & AC_WCAP_DIGITAL)) + continue; + + switch (type) { + + case AC_WID_AUD_OUT: + ret = hdac_hdmi_add_cvt(edev, nid); + if (ret < 0) + return ret; + break; + + case AC_WID_PIN: + ret = hdac_hdmi_add_pin(edev, nid); + if (ret < 0) + return ret; + break; + } + } + + hdac->end_nid = nid; + + if (!hdmi->num_pin || !hdmi->num_cvt) + return -EIO; + + return hdac_hdmi_init_dai_map(edev); +} + +static int hdmi_codec_probe(struct snd_soc_codec *codec) +{ + struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(&codec->component); + + edev->scodec = codec; + + create_fill_widget_route_map(dapm, &hdmi->dai_map[0]); + + /* Imp: Store the card pointer in hda_codec */ + edev->card = dapm->card->snd_card; + + /* + * hdac_device core already sets the state to active and calls + * get_noresume. So enable runtime and set the device to suspend. + */ + pm_runtime_enable(&edev->hdac.dev); + pm_runtime_put(&edev->hdac.dev); + pm_runtime_suspend(&edev->hdac.dev); + + return 0; +} + +static int hdmi_codec_remove(struct snd_soc_codec *codec) +{ + struct hdac_ext_device *edev = snd_soc_codec_get_drvdata(codec); + + pm_runtime_disable(&edev->hdac.dev); + return 0; +} + +static struct snd_soc_codec_driver hdmi_hda_codec = { + .probe = hdmi_codec_probe, + .remove = hdmi_codec_remove, + .idle_bias_off = true, +}; + +static struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdac_hdmi_pcm_open, + .shutdown = hdac_hdmi_pcm_close, + .hw_params = hdac_hdmi_set_hw_params, + .prepare = hdac_hdmi_playback_prepare, + .hw_free = hdac_hdmi_playback_cleanup, +}; + +static struct snd_soc_dai_driver hdmi_dais[] = { + { .name = "intel-hdmi-hif1", + .playback = { + .stream_name = "hif1", + .channels_min = 2, + .channels_max = 2, + .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_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + }, + .ops = &hdmi_dai_ops, + }, +}; + +static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev) +{ + struct hdac_device *codec = &edev->hdac; + struct hdac_hdmi_priv *hdmi_priv; + int ret = 0; + + hdmi_priv = devm_kzalloc(&codec->dev, sizeof(*hdmi_priv), GFP_KERNEL); + if (hdmi_priv == NULL) + return -ENOMEM; + + edev->private_data = hdmi_priv; + + dev_set_drvdata(&codec->dev, edev); + + INIT_LIST_HEAD(&hdmi_priv->pin_list); + INIT_LIST_HEAD(&hdmi_priv->cvt_list); + + ret = hdac_hdmi_parse_and_map_nid(edev); + if (ret < 0) + return ret; + + /* ASoC specific initialization */ + return snd_soc_register_codec(&codec->dev, &hdmi_hda_codec, + hdmi_dais, ARRAY_SIZE(hdmi_dais)); +} + +static int hdac_hdmi_dev_remove(struct hdac_ext_device *edev) +{ + struct hdac_hdmi_priv *hdmi = edev->private_data; + struct hdac_hdmi_pin *pin, *pin_next; + struct hdac_hdmi_cvt *cvt, *cvt_next; + + snd_soc_unregister_codec(&edev->hdac.dev); + + list_for_each_entry_safe(cvt, cvt_next, &hdmi->cvt_list, head) { + list_del(&cvt->head); + kfree(cvt); + } + + list_for_each_entry_safe(pin, pin_next, &hdmi->pin_list, head) { + list_del(&pin->head); + kfree(pin); + } + + return 0; +} + +#ifdef CONFIG_PM +static int hdac_hdmi_runtime_suspend(struct device *dev) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dev); + struct hdac_device *hdac = &edev->hdac; + struct hdac_bus *bus = hdac->bus; + int err; + + dev_dbg(dev, "Enter: %s\n", __func__); + + /* controller may not have been initialized for the first time */ + if (!bus) + return 0; + + /* Power down afg */ + if (!snd_hdac_check_power_state(hdac, hdac->afg, AC_PWRST_D3)) + snd_hdac_codec_write(hdac, hdac->afg, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + + err = snd_hdac_display_power(bus, false); + if (err < 0) { + dev_err(bus->dev, "Cannot turn on display power on i915\n"); + return err; + } + + return 0; +} + +static int hdac_hdmi_runtime_resume(struct device *dev) +{ + struct hdac_ext_device *edev = to_hda_ext_device(dev); + struct hdac_device *hdac = &edev->hdac; + struct hdac_bus *bus = hdac->bus; + int err; + + dev_dbg(dev, "Enter: %s\n", __func__); + + /* controller may not have been initialized for the first time */ + if (!bus) + return 0; + + err = snd_hdac_display_power(bus, true); + if (err < 0) { + dev_err(bus->dev, "Cannot turn on display power on i915\n"); + return err; + } + + /* Power up afg */ + if (!snd_hdac_check_power_state(hdac, hdac->afg, AC_PWRST_D0)) + snd_hdac_codec_write(hdac, hdac->afg, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D0); + + return 0; +} +#else +#define hdac_hdmi_runtime_suspend NULL +#define hdac_hdmi_runtime_resume NULL +#endif + +static const struct dev_pm_ops hdac_hdmi_pm = { + SET_RUNTIME_PM_OPS(hdac_hdmi_runtime_suspend, hdac_hdmi_runtime_resume, NULL) +}; + +static const struct hda_device_id hdmi_list[] = { + HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0), + {} +}; + +MODULE_DEVICE_TABLE(hdaudio, hdmi_list); + +static struct hdac_ext_driver hdmi_driver = { + . hdac = { + .driver = { + .name = "HDMI HDA Codec", + .pm = &hdac_hdmi_pm, + }, + .id_table = hdmi_list, + }, + .probe = hdac_hdmi_dev_probe, + .remove = hdac_hdmi_dev_remove, +}; + +static int __init hdmi_init(void) +{ + return snd_hda_ext_driver_register(&hdmi_driver); +} + +static void __exit hdmi_exit(void) +{ + snd_hda_ext_driver_unregister(&hdmi_driver); +} + +module_init(hdmi_init); +module_exit(hdmi_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("HDMI HD codec"); +MODULE_AUTHOR("Samreen Nilofer<samreen.nilofer@intel.com>"); +MODULE_AUTHOR("Subhransu S. Prusty<subhransu.s.prusty@intel.com>"); diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index f2beb1aa5763..11d032cdc658 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -488,6 +488,18 @@ static int is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, return 0; } +static int is_using_asrc(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(source->dapm); + struct rt5640_priv *rt5640 = snd_soc_codec_get_drvdata(codec); + + if (!rt5640->asrc_en) + return 0; + + return 1; +} + /* Digital Mixer */ static const struct snd_kcontrol_new rt5640_sto_adc_l_mix[] = { SOC_DAPM_SINGLE("ADC1 Switch", RT5640_STO_ADC_MIXER, @@ -1059,6 +1071,20 @@ static int rt5640_hp_post_event(struct snd_soc_dapm_widget *w, static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("PLL1", RT5640_PWR_ANLG2, RT5640_PWR_PLL_BIT, 0, NULL, 0), + + /* ASRC */ + SND_SOC_DAPM_SUPPLY_S("Stereo Filter ASRC", 1, RT5640_ASRC_1, + 15, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 Filter ASRC", 1, RT5640_ASRC_1, + 12, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("I2S2 ASRC", 1, RT5640_ASRC_1, + 11, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC1 ASRC", 1, RT5640_ASRC_1, + 9, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DMIC2 ASRC", 1, RT5640_ASRC_1, + 8, 0, NULL, 0), + + /* Input Side */ /* micbias */ SND_SOC_DAPM_SUPPLY("LDO2", RT5640_PWR_ANLG1, @@ -1319,6 +1345,12 @@ static const struct snd_soc_dapm_widget rt5639_specific_dapm_widgets[] = { }; static const struct snd_soc_dapm_route rt5640_dapm_routes[] = { + { "I2S1", NULL, "Stereo Filter ASRC", is_using_asrc }, + { "I2S2", NULL, "I2S2 ASRC", is_using_asrc }, + { "I2S2", NULL, "I2S2 Filter ASRC", is_using_asrc }, + { "DMIC1", NULL, "DMIC1 ASRC", is_using_asrc }, + { "DMIC2", NULL, "DMIC2 ASRC", is_using_asrc }, + {"IN1P", NULL, "LDO2"}, {"IN2P", NULL, "LDO2"}, {"IN3P", NULL, "LDO2"}, @@ -1981,6 +2013,76 @@ int rt5640_dmic_enable(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(rt5640_dmic_enable); +int rt5640_sel_asrc_clk_src(struct snd_soc_codec *codec, + unsigned int filter_mask, unsigned int clk_src) +{ + struct rt5640_priv *rt5640 = snd_soc_codec_get_drvdata(codec); + unsigned int asrc2_mask = 0; + unsigned int asrc2_value = 0; + + switch (clk_src) { + case RT5640_CLK_SEL_SYS: + case RT5640_CLK_SEL_ASRC: + break; + + default: + return -EINVAL; + } + + if (!filter_mask) + return -EINVAL; + + if (filter_mask & RT5640_DA_STEREO_FILTER) { + asrc2_mask |= RT5640_STO_DAC_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_STO_DAC_M_MASK) + | (clk_src << RT5640_STO_DAC_M_SFT); + } + + if (filter_mask & RT5640_DA_MONO_L_FILTER) { + asrc2_mask |= RT5640_MDA_L_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MDA_L_M_MASK) + | (clk_src << RT5640_MDA_L_M_SFT); + } + + if (filter_mask & RT5640_DA_MONO_R_FILTER) { + asrc2_mask |= RT5640_MDA_R_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MDA_R_M_MASK) + | (clk_src << RT5640_MDA_R_M_SFT); + } + + if (filter_mask & RT5640_AD_STEREO_FILTER) { + asrc2_mask |= RT5640_ADC_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_ADC_M_MASK) + | (clk_src << RT5640_ADC_M_SFT); + } + + if (filter_mask & RT5640_AD_MONO_L_FILTER) { + asrc2_mask |= RT5640_MAD_L_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MAD_L_M_MASK) + | (clk_src << RT5640_MAD_L_M_SFT); + } + + if (filter_mask & RT5640_AD_MONO_R_FILTER) { + asrc2_mask |= RT5640_MAD_R_M_MASK; + asrc2_value = (asrc2_value & ~RT5640_MAD_R_M_MASK) + | (clk_src << RT5640_MAD_R_M_SFT); + } + + snd_soc_update_bits(codec, RT5640_ASRC_2, + asrc2_mask, asrc2_value); + + if (snd_soc_read(codec, RT5640_ASRC_2)) { + rt5640->asrc_en = true; + snd_soc_update_bits(codec, RT5640_JD_CTRL, 0x3, 0x3); + } else { + rt5640->asrc_en = false; + snd_soc_update_bits(codec, RT5640_JD_CTRL, 0x3, 0x0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(rt5640_sel_asrc_clk_src); + static int rt5640_probe(struct snd_soc_codec *codec) { struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); @@ -2175,6 +2277,7 @@ static const struct acpi_device_id rt5640_acpi_match[] = { { "INT33CA", 0 }, { "10EC5640", 0 }, { "10EC5642", 0 }, + { "INTCCFFD", 0 }, { }, }; MODULE_DEVICE_TABLE(acpi, rt5640_acpi_match); diff --git a/sound/soc/codecs/rt5640.h b/sound/soc/codecs/rt5640.h index 3deb8babeabb..83a7150ddc24 100644 --- a/sound/soc/codecs/rt5640.h +++ b/sound/soc/codecs/rt5640.h @@ -1033,6 +1033,10 @@ #define RT5640_DMIC_2_M_NOR (0x0 << 8) #define RT5640_DMIC_2_M_ASYN (0x1 << 8) +/* ASRC clock source selection (0x84) */ +#define RT5640_CLK_SEL_SYS (0x0) +#define RT5640_CLK_SEL_ASRC (0x1) + /* ASRC Control 2 (0x84) */ #define RT5640_MDA_L_M_MASK (0x1 << 15) #define RT5640_MDA_L_M_SFT 15 @@ -2079,6 +2083,16 @@ enum { RT5640_DMIC2, }; +/* filter mask */ +enum { + RT5640_DA_STEREO_FILTER = 0x1, + RT5640_DA_MONO_L_FILTER = (0x1 << 1), + RT5640_DA_MONO_R_FILTER = (0x1 << 2), + RT5640_AD_STEREO_FILTER = (0x1 << 3), + RT5640_AD_MONO_L_FILTER = (0x1 << 4), + RT5640_AD_MONO_R_FILTER = (0x1 << 5), +}; + struct rt5640_priv { struct snd_soc_codec *codec; struct rt5640_platform_data pdata; @@ -2095,9 +2109,12 @@ struct rt5640_priv { int pll_out; bool hp_mute; + bool asrc_en; }; int rt5640_dmic_enable(struct snd_soc_codec *codec, bool dmic1_data_pin, bool dmic2_data_pin); +int rt5640_sel_asrc_clk_src(struct snd_soc_codec *codec, + unsigned int filter_mask, unsigned int clk_src); #endif |