357 lines · 9.9 KB · C
RawDownload
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)//// This file is provided under a dual BSD/GPLv2 license.  When using or// redistributing this file, you may do so under either license.//// Copyright(c) 2018 Intel Corporation//// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>//	    Ranjani Sridharan <ranjani.sridharan@linux.intel.com>//	    Rander Wang <rander.wang@intel.com>//          Keyon Jie <yang.jie@linux.intel.com>// /* * Hardware interface for generic Intel audio DSP HDA IP */ #include <linux/module.h>#include <sound/hdaudio_ext.h>#include <sound/hda_register.h>#include <sound/hda_component.h>#include <sound/hda-mlink.h>#include "../ops.h"#include "hda.h" /* * HDA Operations. */ int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset){	unsigned long timeout;	u32 gctl = 0;	u32 val; 	/* 0 to enter reset and 1 to exit reset */	val = reset ? 0 : SOF_HDA_GCTL_RESET; 	/* enter/exit HDA controller reset */	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL,				SOF_HDA_GCTL_RESET, val); 	/* wait to enter/exit reset */	timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT);	while (time_before(jiffies, timeout)) {		gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);		if ((gctl & SOF_HDA_GCTL_RESET) == val)			return 0;		usleep_range(500, 1000);	} 	/* enter/exit reset failed */	dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n",		reset ? "reset" : "ready", gctl);	return -EIO;} int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev){	struct hdac_bus *bus = sof_to_bus(sdev);	u32 cap, offset, feature;	int count = 0;	int ret; 	/*	 * On some devices, one reset cycle is necessary before reading	 * capabilities	 */	ret = hda_dsp_ctrl_link_reset(sdev, true);	if (ret < 0)		return ret;	ret = hda_dsp_ctrl_link_reset(sdev, false);	if (ret < 0)		return ret; 	offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH); 	do {		dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n",			offset & SOF_HDA_CAP_NEXT_MASK); 		cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset); 		if (cap == -1) {			dev_dbg(bus->dev, "Invalid capability reg read\n");			break;		} 		feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF; 		switch (feature) {		case SOF_HDA_PP_CAP_ID:			dev_dbg(sdev->dev, "found DSP capability at 0x%x\n",				offset);			bus->ppcap = bus->remap_addr + offset;			sdev->bar[HDA_DSP_PP_BAR] = bus->ppcap;			break;		case SOF_HDA_SPIB_CAP_ID:			dev_dbg(sdev->dev, "found SPIB capability at 0x%x\n",				offset);			bus->spbcap = bus->remap_addr + offset;			sdev->bar[HDA_DSP_SPIB_BAR] = bus->spbcap;			break;		case SOF_HDA_DRSM_CAP_ID:			dev_dbg(sdev->dev, "found DRSM capability at 0x%x\n",				offset);			bus->drsmcap = bus->remap_addr + offset;			sdev->bar[HDA_DSP_DRSM_BAR] = bus->drsmcap;			break;		case SOF_HDA_GTS_CAP_ID:			dev_dbg(sdev->dev, "found GTS capability at 0x%x\n",				offset);			bus->gtscap = bus->remap_addr + offset;			break;		case SOF_HDA_ML_CAP_ID:			dev_dbg(sdev->dev, "found ML capability at 0x%x\n",				offset);			bus->mlcap = bus->remap_addr + offset;			break;		default:			dev_dbg(sdev->dev, "found capability %d at 0x%x\n",				feature, offset);			break;		} 		offset = cap & SOF_HDA_CAP_NEXT_MASK;	} while (count++ <= SOF_HDA_MAX_CAPS && offset); 	return 0;}EXPORT_SYMBOL_NS(hda_dsp_ctrl_get_caps, "SND_SOC_SOF_INTEL_HDA_COMMON"); void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable){	u32 val = enable ? SOF_HDA_PPCTL_GPROCEN : 0; 	snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,				SOF_HDA_PPCTL_GPROCEN, val);}EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_enable, "SND_SOC_SOF_INTEL_HDA_COMMON"); void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable){	u32 val	= enable ? SOF_HDA_PPCTL_PIE : 0; 	snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,				SOF_HDA_PPCTL_PIE, val);}EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_int_enable, "SND_SOC_SOF_INTEL_HDA_COMMON"); void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable){	u32 val = enable ? PCI_CGCTL_MISCBDCGE_MASK : 0; 	snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val);} /* * enable/disable audio dsp clock gating and power gating bits. * This allows the HW to opportunistically power and clock gate * the audio dsp when it is idle */int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable){	struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;	u32 val; 	/* enable/disable audio dsp clock gating */	val = enable ? PCI_CGCTL_ADSPDCGE : 0;	snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); 	/* disable the DMI link when requested. But enable only if it wasn't disabled previously */	val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0;	if (!enable || !hda->l1_disabled)		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,					HDA_VS_INTEL_EM2_L1SEN, val); 	/* enable/disable audio dsp power gating */	val = enable ? 0 : PCI_PGCTL_ADSPPGD;	snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val); 	return 0;}EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON"); int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec){	struct hdac_bus *bus = sof_to_bus(sdev);	struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);	struct hdac_stream *stream;	int sd_offset, ret = 0;	u32 gctl; 	if (bus->chip_init)		return 0; 	/*	 * The controller reset clears the ACE2+ link DMA stream allocation	 * constraints; reset the masks to reflect this.	 */	memset(sof_hda->link_dma_active_sdw_mask, 0,	       sizeof(sof_hda->link_dma_active_sdw_mask));	memset(sof_hda->link_dma_active_multi_mask, 0,	       sizeof(sof_hda->link_dma_active_multi_mask));	sof_hda->link_dma_out_hda_used_mask = 0; 	hda_codec_set_codec_wakeup(sdev, true); 	hda_dsp_ctrl_misc_clock_gating(sdev, false); 	/* clear WAKE_STS if not in reset */	gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);	if (gctl & SOF_HDA_GCTL_RESET)		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,				  SOF_HDA_WAKESTS, SOF_HDA_WAKESTS_INT_MASK); 	/* reset HDA controller */	ret = hda_dsp_ctrl_link_reset(sdev, true);	if (ret < 0) {		dev_err(sdev->dev, "error: failed to reset HDA controller\n");		goto err;	} 	usleep_range(500, 1000); 	/* exit HDA controller reset */	ret = hda_dsp_ctrl_link_reset(sdev, false);	if (ret < 0) {		dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");		goto err;	}	usleep_range(1000, 1200); 	/* Accept unsolicited responses */	snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); 	/* Perform a one-time enumeration of the Multi-Link capability */	ret = hda_bus_ml_init(bus);	if (ret < 0) {		dev_err(sdev->dev, "%s: failed to enumerate multi-links\n",			__func__);		goto err;	} 	if (detect_codec)		hda_codec_detect_mask(sdev); 	/* clear stream status */	list_for_each_entry(stream, &bus->stream_list, list) {		sd_offset = SOF_STREAM_SD_OFFSET(stream);		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,				  sd_offset + SOF_HDA_ADSP_REG_SD_STS,				  SOF_HDA_CL_DMA_SD_INT_MASK);	} 	/* clear WAKESTS */	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,			  bus->codec_mask); 	hda_codec_rirb_status_clear(sdev); 	/* clear interrupt status register */	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,			  SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM); 	hda_codec_init_cmd_io(sdev); 	/* enable CIE and GIE interrupts */	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,				SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,				SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN); 	/* program the position buffer */	if (bus->use_posbuf && bus->posbuf.addr) {		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE,				  (u32)bus->posbuf.addr);		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE,				  upper_32_bits(bus->posbuf.addr));	} 	hda_bus_ml_reset_losidv(bus); 	bus->chip_init = true; err:	hda_dsp_ctrl_misc_clock_gating(sdev, true); 	hda_codec_set_codec_wakeup(sdev, false); 	return ret;}EXPORT_SYMBOL_NS(hda_dsp_ctrl_init_chip, "SND_SOC_SOF_INTEL_HDA_COMMON"); void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev){	struct hdac_bus *bus = sof_to_bus(sdev);	struct hdac_stream *stream;	int sd_offset; 	if (!bus->chip_init)		return; 	/* disable interrupts in stream descriptor */	list_for_each_entry(stream, &bus->stream_list, list) {		sd_offset = SOF_STREAM_SD_OFFSET(stream);		snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,					sd_offset +					SOF_HDA_ADSP_REG_SD_CTL,					SOF_HDA_CL_DMA_SD_INT_MASK,					0);	} 	/* disable SIE for all streams */	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,				SOF_HDA_INT_ALL_STREAM,	0); 	/* disable controller CIE and GIE */	snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,				SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,				0); 	/* clear stream status */	list_for_each_entry(stream, &bus->stream_list, list) {		sd_offset = SOF_STREAM_SD_OFFSET(stream);		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,				  sd_offset + SOF_HDA_ADSP_REG_SD_STS,				  SOF_HDA_CL_DMA_SD_INT_MASK);	} 	/* clear WAKESTS */	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,			  SOF_HDA_WAKESTS_INT_MASK); 	hda_codec_rirb_status_clear(sdev); 	/* clear interrupt status register */	snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,			  SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM); 	hda_codec_stop_cmd_io(sdev); 	/* disable position buffer */	if (bus->use_posbuf && bus->posbuf.addr) {		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,				  SOF_HDA_ADSP_DPLBASE, 0);		snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,				  SOF_HDA_ADSP_DPUBASE, 0);	} 	bus->chip_init = false;} MODULE_LICENSE("Dual BSD/GPL");MODULE_DESCRIPTION("SOF helpers for HDaudio platforms");MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC");MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC_I915");