port->is_connect = is_connect; if (is_connect) { /* * Report Jack connect event when a device is connected * for the first time where same PCM is attached to multiple * ports.
*/ if (pcm->jack_event == 0) {
dev_dbg(&hdev->dev, "jack report for pcm=%d\n",
pcm->pcm_id);
snd_soc_jack_report(pcm->jack, SND_JACK_AVOUT,
SND_JACK_AVOUT);
}
pcm->jack_event++;
} else { /* * Report Jack disconnect event when a device is disconnected * is the only last connected device when same PCM is attached * to multiple ports.
*/ if (pcm->jack_event == 1)
snd_soc_jack_report(pcm->jack, 0, SND_JACK_AVOUT); if (pcm->jack_event > 0)
pcm->jack_event--;
}
}
/* MST supported verbs */ /* * Get the no devices that can be connected to a port on the Pin widget.
*/ staticint hdac_hdmi_get_port_len(struct hdac_device *hdev, hda_nid_t nid)
{ unsignedint caps; unsignedint type, param;
caps = snd_hdac_get_wcaps(hdev, nid);
type = snd_hdac_get_wcaps_type(caps);
if (!(caps & AC_WCAP_DIGITAL) || (type != AC_WID_PIN)) return 0;
/* * Get the port entry select on the pin. Return the port entry * id selected on the pin. Return 0 means the first port entry * is selected or MST is not supported.
*/ staticint hdac_hdmi_port_select_get(struct hdac_device *hdev, struct hdac_hdmi_port *port)
{ return snd_hdac_codec_read(hdev, port->pin->nid,
0, AC_VERB_GET_DEVICE_SEL, 0);
}
/* * Sets the selected port entry for the configuring Pin widget verb. * returns error if port set is not equal to port get otherwise success
*/ staticint hdac_hdmi_port_select_set(struct hdac_device *hdev, struct hdac_hdmi_port *port)
{ int num_ports;
if (!port->pin->mst_capable) return 0;
/* AC_PAR_DEVLIST_LEN is 0 based. */
num_ports = hdac_hdmi_get_port_len(hdev, port->pin->nid); if (num_ports < 0) return -EIO; /* * Device List Length is a 0 based integer value indicating the * number of sink device that a MST Pin Widget can support.
*/ if (num_ports + 1 < port->id) return 0;
sad = drm_eld_sad(eld_buf); if (!sad) goto format_constraint;
for (i = drm_eld_sad_count(eld_buf); i > 0; i--, sad += 3) { if (sad_format(sad) == 1) { /* AUDIO_CODING_TYPE_LPCM */
/* * the controller support 20 and 24 bits in 32 bit * container so we set S32
*/ if (sad_sample_bits_lpcm(sad) & 0x6)
formats |= SNDRV_PCM_FMTBIT_S32;
}
}
/* Fill infoframe. Index auto-incremented */
hdac_hdmi_set_dip_index(hdev, pin->nid, 0x0, 0x0); if (conn_type == DRM_ELD_CONN_TYPE_HDMI) { for (i = 0; i < sizeof(buffer); i++)
snd_hdac_codec_write(hdev, pin->nid, 0,
AC_VERB_SET_HDMI_DIP_DATA, buffer[i]);
} else { for (i = 0; i < sizeof(dp_ai); i++)
snd_hdac_codec_write(hdev, pin->nid, 0,
AC_VERB_SET_HDMI_DIP_DATA, dip[i]);
}
staticint hdac_hdmi_query_port_connlist(struct hdac_device *hdev, struct hdac_hdmi_pin *pin, struct hdac_hdmi_port *port)
{ if (!(snd_hdac_get_wcaps(hdev, pin->nid) & AC_WCAP_CONN_LIST)) {
dev_warn(&hdev->dev, "HDMI: pin %d wcaps %#x does not support connection list\n",
pin->nid, snd_hdac_get_wcaps(hdev, pin->nid)); return -EINVAL;
}
if (hdac_hdmi_port_select_set(hdev, port) < 0) return -EIO;
port->num_mux_nids = snd_hdac_get_connections(hdev, pin->nid,
port->mux_nids, HDA_MAX_CONNECTIONS); if (port->num_mux_nids == 0)
dev_warn(&hdev->dev, "No connections found for pin:port %d:%d\n",
pin->nid, port->id);
dev_dbg(&hdev->dev, "num_mux_nids %d for pin:port %d:%d\n",
port->num_mux_nids, pin->nid, port->id);
return port->num_mux_nids;
}
/* * Query pcm list and return port to which stream is routed. * * Also query connection list of the pin, to validate the cvt to port map. * * Same stream rendering to multiple ports simultaneously can be done * possibly, but not supported for now in driver. So return the first port * connected.
*/ staticstruct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt( struct hdac_device *hdev, struct hdac_hdmi_priv *hdmi, struct hdac_hdmi_cvt *cvt)
{ struct hdac_hdmi_pcm *pcm; struct hdac_hdmi_port *port; int ret, i;
list_for_each_entry(pcm, &hdmi->pcm_list, head) { if (pcm->cvt == cvt) { if (list_empty(&pcm->port_list)) continue;
list_for_each_entry(port, &pcm->port_list, head) {
mutex_lock(&pcm->lock);
ret = hdac_hdmi_query_port_connlist(hdev,
port->pin, port);
mutex_unlock(&pcm->lock); if (ret < 0) continue;
for (i = 0; i < port->num_mux_nids; i++) { if (port->mux_nids[i] == cvt->nid &&
port->eld.monitor_present &&
port->eld.eld_valid) return port;
}
}
}
}
return NULL;
}
/* * Go through all converters and ensure connection is set to * the correct pin as set via kcontrols.
*/ staticvoid hdac_hdmi_verify_connect_sel_all_pins(struct hdac_device *hdev)
{ struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); struct hdac_hdmi_port *port; struct hdac_hdmi_cvt *cvt; int cvt_idx = 0;
/* * This tries to get a valid pin and set the HW constraints based on the * ELD. Even if a valid pin is not found return success so that device open * doesn't fail.
*/ staticint hdac_hdmi_pcm_open(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{ struct hdac_hdmi_priv *hdmi = snd_soc_dai_get_drvdata(dai); struct hdac_device *hdev = hdmi->hdev; struct hdac_hdmi_dai_port_map *dai_map; struct hdac_hdmi_cvt *cvt; struct hdac_hdmi_port *port; int ret;
dai_map = &hdmi->dai_map[dai->id];
cvt = dai_map->cvt;
port = hdac_hdmi_get_port_from_cvt(hdev, hdmi, cvt);
/* * To make PA and other userland happy. * userland scans devices so returning error does not help.
*/ if (!port) return 0; if ((!port->eld.monitor_present) ||
(!port->eld.eld_valid)) {
/* * The connection indices are shared by all converters and * may interfere with each other. Ensure correct * routing for all converters at stream start.
*/
hdac_hdmi_verify_connect_sel_all_pins(hdev);
/* * Jack status is not reported during device probe as the * PCMs are not registered by then. So report it here.
*/
list_for_each_entry(pcm, &hdmi->pcm_list, head) { if (!strcmp(cvt_name, pcm->cvt->name)) {
list_add_tail(&port->head, &pcm->port_list); if (port->eld.monitor_present && port->eld.eld_valid) {
hdac_hdmi_jack_report_sync(pcm, port, true);
mutex_unlock(&hdmi->pin_mutex); return ret;
}
}
}
mutex_unlock(&hdmi->pin_mutex);
return ret;
}
/* * Ideally the Mux inputs should be based on the num_muxs enumerated, but * the display driver seem to be programming the connection list for the pin * widget runtime. * * So programming all the possible inputs for the mux, the user has to take * care of selecting the right one and leaving all other inputs selected to * "NONE"
*/ staticint hdac_hdmi_create_pin_port_muxs(struct hdac_device *hdev, struct hdac_hdmi_port *port, struct snd_soc_dapm_widget *widget, constchar *widget_name)
{ struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); struct hdac_hdmi_pin *pin = port->pin; struct snd_kcontrol_new *kc; struct hdac_hdmi_cvt *cvt; struct soc_enum *se; char kc_name[NAME_SIZE]; char mux_items[NAME_SIZE]; /* To hold inputs to the Pin mux */ char *items[HDA_MAX_CONNECTIONS]; int i = 0; int num_items = hdmi->num_cvt + 1;
kc = devm_kzalloc(&hdev->dev, sizeof(*kc), GFP_KERNEL); if (!kc) return -ENOMEM;
se = devm_kzalloc(&hdev->dev, sizeof(*se), GFP_KERNEL); if (!se) return -ENOMEM;
snprintf(kc_name, NAME_SIZE, "Pin %d port %d Input",
pin->nid, port->id);
kc->name = devm_kstrdup(&hdev->dev, kc_name, GFP_KERNEL); if (!kc->name) return -ENOMEM;
if (!port->eld.monitor_present || !port->eld.eld_valid) {
dev_dbg(&hdev->dev, "%s: disconnect for pin:port %d:%d\n",
__func__, pin->nid, port->id);
/* * PCMs are not registered during device probe, so don't * report jack here. It will be done in usermode mux * control select.
*/ if (pcm) {
hdac_hdmi_jack_report(pcm, port, false);
schedule_work(&port->dapm_work);
}
mutex_unlock(&hdmi->pin_mutex); return;
}
if (port->eld.monitor_present && port->eld.eld_valid) { if (pcm) {
hdac_hdmi_jack_report(pcm, port, true);
schedule_work(&port->dapm_work);
}
/* * Each converter can support a stream independently. So a dai is created * based on the number of converter queried.
*/ staticint hdac_hdmi_create_dais(struct hdac_device *hdev, struct snd_soc_dai_driver **dais, struct hdac_hdmi_priv *hdmi, int num_dais)
{ struct snd_soc_dai_driver *hdmi_dais; struct hdac_hdmi_cvt *cvt; char name[NAME_SIZE], dai_name[NAME_SIZE]; int i = 0;
u32 rates, bps; unsignedint rate_max = 384000, rate_min = 8000;
u64 formats; int ret;
/* * Set caps based on capability queried from the converter. * It will be constrained runtime based on ELD queried.
*/
hdmi_dais[i].playback.formats = formats;
hdmi_dais[i].playback.rates = rates;
hdmi_dais[i].playback.rate_max = rate_max;
hdmi_dais[i].playback.rate_min = rate_min;
hdmi_dais[i].playback.channels_min = 2;
hdmi_dais[i].playback.channels_max = 2;
hdmi_dais[i].playback.sig_bits = bps;
hdmi_dais[i].ops = &hdmi_dai_ops;
i++;
}
*dais = hdmi_dais;
hdmi->dai_drv = hdmi_dais;
return 0;
}
/* * Parse all nodes and store the cvt/pin nids in array * Add one time initialization for pin and cvt widgets
*/ staticint hdac_hdmi_parse_and_map_nid(struct hdac_device *hdev, struct snd_soc_dai_driver **dais, int *num_dais)
{
hda_nid_t nid; int i, num_nodes; struct hdac_hdmi_priv *hdmi = hdev_to_hdmi_priv(hdev); int ret;
num_nodes = snd_hdac_get_sub_nodes(hdev, hdev->afg, &nid); if (!nid || num_nodes <= 0) {
dev_warn(&hdev->dev, "HDMI: failed to get afg sub nodes\n"); return -EINVAL;
}
for (i = 0; i < num_nodes; i++, nid++) { unsignedint caps; unsignedint type;
caps = snd_hdac_get_wcaps(hdev, nid);
type = snd_hdac_get_wcaps_type(caps);
if (!(caps & AC_WCAP_DIGITAL)) continue;
switch (type) {
case AC_WID_AUD_OUT:
ret = hdac_hdmi_add_cvt(hdev, nid); if (ret < 0) return ret; break;
case AC_WID_PIN:
ret = hdac_hdmi_add_pin(hdev, nid); if (ret < 0) return ret; break;
}
}
if (!hdmi->num_pin || !hdmi->num_cvt) {
ret = -EIO;
dev_err(&hdev->dev, "Bad pin/cvt setup in %s\n", __func__); return ret;
}
ret = hdac_hdmi_create_dais(hdev, dais, hdmi, hdmi->num_cvt); if (ret) {
dev_err(&hdev->dev, "Failed to create dais with err: %d\n",
ret); return ret;
}
*num_dais = hdmi->num_cvt;
ret = hdac_hdmi_init_dai_map(hdev); if (ret < 0)
dev_err(&hdev->dev, "Failed to init DAI map with err: %d\n",
ret); return ret;
}
staticint hdac_hdmi_pin2port(void *aptr, int pin)
{ return pin - 4; /* map NID 0x05 -> port #1 */
}
/* Don't know how this mapping is derived */
hda_nid_t pin_nid = port + 0x04;
dev_dbg(&hdev->dev, "%s: for pin:%d port=%d\n", __func__,
pin_nid, pipe);
/* * skip notification during system suspend (but not in runtime PM); * the state will be updated at resume. Also since the ELD and * connection states are updated in anyway at the end of the resume, * we can skip it when received during PM process.
*/ if (snd_power_get_state(component->card->snd_card) !=
SNDRV_CTL_POWER_D0) return;
if (atomic_read(&hdev->in_pm)) return;
list_for_each_entry(pin, &hdmi->pin_list, head) { if (pin->nid != pin_nid) continue;
/* In case of non MST pin, pipe is -1 */ if (pipe == -1) {
pin->mst_capable = false; /* if not MST, default is port[0] */
hport = &pin->ports[0];
} else { for (i = 0; i < pin->num_ports; i++) {
pin->mst_capable = true; if (pin->ports[i].id == pipe) {
hport = &pin->ports[i]; break;
}
}
}
/* * hold the ref while we probe, also no need to drop the ref on * exit, we call pm_runtime_suspend() so that will do for us
*/
hlink = snd_hdac_ext_bus_get_hlink_by_name(hdev->bus, dev_name(&hdev->dev)); if (!hlink) {
dev_err(&hdev->dev, "hdac link not found\n"); return -EIO;
}
snd_hdac_ext_bus_link_get(hdev->bus, hlink);
ret = create_fill_widget_route_map(dapm); if (ret < 0) return ret;
hdac_hdmi_present_sense_all_pins(hdev, hdmi, true); /* Imp: Store the card pointer in hda_codec */
hdmi->card = dapm->card->snd_card;
/* * Setup a device_link between card device and HDMI codec device. * The card device is the consumer and the HDMI codec device is * the supplier. With this setting, we can make sure that the audio * domain in display power will be always turned on before operating * on the HDMI audio codec registers. * Let's use the flag DL_FLAG_AUTOREMOVE_CONSUMER. This can make * sure the device link is freed when the machine driver is removed.
*/
device_link_add(component->card->dev, &hdev->dev, DL_FLAG_RPM_ACTIVE |
DL_FLAG_AUTOREMOVE_CONSUMER); /* * 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(&hdev->dev);
pm_runtime_put(&hdev->dev);
pm_runtime_suspend(&hdev->dev);
ret = pm_runtime_force_resume(dev); if (ret < 0) return ret; /* * As the ELD notify callback request is not entertained while the * device is in suspend state. Need to manually check detection of * all pins here. pin capablity change is not support, so use the * already set pin caps. * * NOTE: this is safe to call even if the codec doesn't actually resume. * The pin check involves only with DRM audio component hooks, so it * works even if the HD-audio side is still dreaming peacefully.
*/
hdac_hdmi_present_sense_all_pins(hdev, hdmi, false); return 0;
}
/* hold the ref while we probe */
hlink = snd_hdac_ext_bus_get_hlink_by_name(hdev->bus, dev_name(&hdev->dev)); if (!hlink) {
dev_err(&hdev->dev, "hdac link not found\n"); return -EIO;
}
/* * Turned off in the runtime_suspend during the first explicit * pm_runtime_suspend call.
*/
snd_hdac_display_power(hdev->bus, hdev->addr, true);
ret = hdac_hdmi_parse_and_map_nid(hdev, &hdmi_dais, &num_dais); if (ret < 0) {
dev_err(&hdev->dev, "Failed in parse and map nid with err: %d\n", ret); return ret;
}
snd_hdac_refresh_widgets(hdev);
/* ASoC specific initialization */
ret = devm_snd_soc_register_component(&hdev->dev, &hdmi_hda_codec,
hdmi_dais, num_dais);
/* controller may not have been initialized for the first time */ if (!bus) return 0;
/* * Power down afg. * codec_read is preferred over codec_write to set the power state. * This way verb is send to set the power state and response * is received. So setting power state is ensured without using loop * to read the state.
*/
snd_hdac_codec_read(hdev, hdev->afg, 0, AC_VERB_SET_POWER_STATE,
AC_PWRST_D3);
hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, dev_name(dev)); if (!hlink) {
dev_err(dev, "hdac link not found\n"); return -EIO;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.