/* * hdac_hdmi.c - ASoc HDA-HDMI codec driver for Intel platforms * * Copyright (C) 2014-2015 Intel Corp * Author: Samreen Nilofer * Subhransu S. Prusty * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 #include #include #include #include #include #include #include "../../hda/local.h" #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 { hda_nid_t nid; struct hdac_hdmi_cvt_params params; }; struct hdac_hdmi_pin { 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 { hda_nid_t pin_nid[3]; hda_nid_t cvt_nid[3]; struct hdac_hdmi_dai_pin_map dai_map[3]; }; 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 int hdac_hdmi_query_pin_connlist(struct hdac_ext_device *hdac, struct hdac_hdmi_pin *pin) { if (!(get_wcaps(&hdac->hdac, pin->nid) & AC_WCAP_CONN_LIST)) { dev_warn(&hdac->hdac.dev, "HDMI: pin %d wcaps %#x does not support connection list\n", pin->nid, get_wcaps(&hdac->hdac, pin->nid)); return -EINVAL; } pin->num_mux_nids = snd_hdac_get_connections(&hdac->hdac, pin->nid, pin->mux_nids, HDA_MAX_CONNECTIONS); if (pin->num_mux_nids == 0) { dev_err(&hdac->hdac.dev, "No connections found\n"); return -ENODEV; } return pin->num_mux_nids; } 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_dai_pin_map *dai_map, hda_nid_t pin_nid, hda_nid_t cvt_nid, int dai_id) { int ret; dai_map->dai_id = dai_id; dai_map->pin.nid = pin_nid; ret = hdac_hdmi_query_pin_connlist(edev, &dai_map->pin); if (ret < 0) { dev_err(&edev->hdac.dev, "Error querying connection list: %d\n", ret); return ret; } dai_map->cvt.nid = cvt_nid; /* 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 hdac_hdmi_query_cvt_params(&edev->hdac, &dai_map->cvt); } /* * 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; struct hdac_device *hdac = &edev->hdac; struct hdac_hdmi_priv *hdmi = edev->private_data; int cvt_nid = 0, pin_nid = 0; hdac->num_nodes = snd_hdac_get_sub_nodes(hdac, hdac->afg, &nid); if (!nid || hdac->num_nodes < 0) { dev_warn(&hdac->dev, "HDMI: failed to get afg sub nodes\n"); return -EINVAL; } 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: hdmi->cvt_nid[cvt_nid] = nid; cvt_nid++; break; case AC_WID_PIN: hdmi->pin_nid[pin_nid] = nid; pin_nid++; break; } } hdac->end_nid = nid; if (!pin_nid || !cvt_nid) return -EIO; /* * 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 */ return hdac_hdmi_init_dai_map(edev, &hdmi->dai_map[0], hdmi->pin_nid[0], hdmi->cvt_nid[0], 0); } 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; return 0; } static struct snd_soc_codec_driver hdmi_hda_codec = { .probe = hdmi_codec_probe, .idle_bias_off = true, }; 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, }, }, }; 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); 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) { snd_soc_unregister_codec(&edev->hdac.dev); return 0; } 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", }, .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"); MODULE_AUTHOR("Subhransu S. Prusty");