diff options
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/sound/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/sound/Makefile | 1 | ||||
| -rw-r--r-- | drivers/sound/hda_codec.c | 556 | 
3 files changed, 566 insertions, 0 deletions
| diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig index 40f4f7598aa..0dca6ffe88c 100644 --- a/drivers/sound/Kconfig +++ b/drivers/sound/Kconfig @@ -40,6 +40,15 @@ config I2S_SAMSUNG  	  option provides an implementation for sound_init() and  	  sound_play(). +config SOUND_INTEL_HDA +	bool "Intel HDA audio codec" +	depends on SOUND +	help +	  Most Intel chips have an HDA (High-definition audio) codec which can +	  be used by U-Boot to play simple beeps. This is also sometimes called +	  Azalia which was the development code-name. It requires setup +	  information in the device tree (see intel-hda.txt). +  config SOUND_MAX98088  	bool "Support Maxim max98088 audio codec"  	depends on I2S diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 170e06ad538..2e21a0d0674 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_SOUND_WM8994)	+= wm8994.o  obj-$(CONFIG_SOUND_MAX98088)	+= max98088.o maxim_codec.o  obj-$(CONFIG_SOUND_MAX98090)	+= max98090.o maxim_codec.o  obj-$(CONFIG_SOUND_MAX98095)	+= max98095.o maxim_codec.o +obj-$(CONFIG_SOUND_INTEL_HDA)	+= hda_codec.o diff --git a/drivers/sound/hda_codec.c b/drivers/sound/hda_codec.c new file mode 100644 index 00000000000..572ef693cc0 --- /dev/null +++ b/drivers/sound/hda_codec.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of per-board codec beeping + * Copyright (c) 2011 The Chromium OS Authors. + * Copyright 2018 Google LLC + */ + +#define LOG_CATEGORY	UCLASS_SOUND + +#include <common.h> +#include <dm.h> +#include <hda_codec.h> +#include <pci.h> +#include <sound.h> +#include <asm/io.h> +#include <dt-bindings/sound/azalia.h> + +/** + * struct hda_regs - HDA registers + * + * https://wiki.osdev.org/Intel_High_Definition_Audio + * https://www.intel.com/content/www/us/en/standards/high-definition-audio-specification.html + */ +struct hda_regs { +	u16 gcap; +	u8 vmin; +	u8 vmaj; +	u16 outpay; +	u16 inpay; +	u32 gctl; +	u16 wakeen; +	u16 statests; +	u8 reserved[0x50]; +	u32 cmd;		/* 0x60 */ +	u32 resp; +	u32 icii; +}; + +enum { +	HDA_ICII_BUSY			= BIT(0), +	HDA_ICII_VALID			= BIT(1), + +	/* Common node IDs */ +	HDA_ROOT_NODE			= 0x00, + +	/* HDA verbs fields */ +	HDA_VERB_NID_S			= 20, +	HDA_VERB_VERB_S			= 8, +	HDA_VERB_PARAM_S		= 0, + +	HDA_VERB_GET_PARAMS		= 0xf00, +	HDA_VERB_SET_BEEP		= 0x70a, + +	/* GET_PARAMS parameter IDs */ +	GET_PARAMS_NODE_COUNT		= 0x04, +	GET_PARAMS_AUDIO_GROUP_CAPS	= 0x08, +	GET_PARAMS_AUDIO_WIDGET_CAPS	= 0x09, + +	/* Sub-node fields */ +	NUM_SUB_NODES_S			= 0, +	NUM_SUB_NODES_M			= 0xff << NUM_SUB_NODES_S, +	FIRST_SUB_NODE_S		= 16, +	FIRST_SUB_NODE_M		= 0xff << FIRST_SUB_NODE_S, + +	/* Get Audio Function Group Capabilities fields */ +	AUDIO_GROUP_CAPS_BEEP_GEN	= 0x10000, + +	/* Get Audio Widget Capabilities fields */ +	AUDIO_WIDGET_TYPE_BEEP		= 0x7, +	AUDIO_WIDGET_TYPE_S		= 20, +	AUDIO_WIDGET_TYPE_M		= 0xf << AUDIO_WIDGET_TYPE_S, + +	BEEP_FREQ_BASE			= 12000, +}; + +static inline uint hda_verb(uint nid, uint verb, uint param) +{ +	return nid << HDA_VERB_NID_S | verb << HDA_VERB_VERB_S | +		param << HDA_VERB_PARAM_S; +} + +int hda_wait_for_ready(struct hda_regs *regs) +{ +	int timeout = 1000;	/* Use a 1msec timeout */ + +	while (timeout--) { +		u32 reg32 = readl(®s->icii); + +		if (!(reg32 & HDA_ICII_BUSY)) +			return 0; +		udelay(1); +	} + +	return -ETIMEDOUT; +} + +static int wait_for_response(struct hda_regs *regs, uint *response) +{ +	int timeout = 1000; +	u32 reg32; + +	/* Send the verb to the codec */ +	setbits_le32(®s->icii, HDA_ICII_BUSY | HDA_ICII_VALID); + +	/* Use a 1msec timeout */ +	while (timeout--) { +		reg32 = readl(®s->icii); +		if ((reg32 & (HDA_ICII_VALID | HDA_ICII_BUSY)) == +			HDA_ICII_VALID) { +			if (response) +				*response = readl(®s->resp); +			return 0; +		} +		udelay(1); +	} + +	return -ETIMEDOUT; +} + +int hda_wait_for_valid(struct hda_regs *regs) +{ +	return wait_for_response(regs, NULL); +} + +static int set_bits(void *port, u32 mask, u32 val) +{ +	u32 reg32; +	int count; + +	/* Write (val & mask) to port */ +	clrsetbits_le32(port, mask, val); + +	/* Wait for readback of register to match what was just written to it */ +	count = 50; +	do { +		/* Wait 1ms based on BKDG wait time */ +		mdelay(1); +		reg32 = readl(port) & mask; +	} while (reg32 != val && --count); + +	/* Timeout occurred */ +	if (!count) +		return -ETIMEDOUT; + +	return 0; +} + +int hda_codec_detect(struct hda_regs *regs) +{ +	uint reg8; + +	/* Set Bit 0 to 1 to exit reset state (BAR + 0x8)[0] */ +	if (set_bits(®s->gctl, 1, 1)) +		goto no_codec; + +	/* Write back the value once reset bit is set */ +	writew(readw(®s->gcap), ®s->gcap); + +	/* Read in Codec location */ +	reg8 = readb(®s->statests) & 0xf; +	if (!reg8) +		goto no_codec; + +	return reg8; + +no_codec: +	/* Codec Not found - put HDA back in reset */ +	set_bits(®s->gctl, 1, 0); +	log_debug("No codec\n"); + +	return 0; +} + +static int find_verb_data(struct udevice *dev, uint id, ofnode *nodep) +{ +	ofnode parent = dev_read_subnode(dev, "codecs"); +	ofnode node; +	u32 vendor_id, device_id; + +	ofnode_for_each_subnode(node, parent) { +		if (ofnode_read_u32(node, "vendor-id", &vendor_id) || +		    ofnode_read_u32(node, "device-id", &device_id)) { +			log_debug("Cannot get IDs for '%s'\n", +				  ofnode_get_name(node)); +			return -EINVAL; +		} +		if (id != (vendor_id << 16 | device_id)) { +			log_debug("Skip codec node '%s' for %08x\n", +				  ofnode_get_name(node), id); +			continue; +		} + +		log_debug("Found codec node '%s' for %08x\n", +			  ofnode_get_name(node), id); +		*nodep = node; +		return 0; +	} + +	return -ENOENT; +} + +static int send_verbs(ofnode node, const char *prop_name, struct hda_regs *regs) +{ +	int ret, verb_size, i; +	const u32 *verb; + +	verb = ofnode_get_property(node, prop_name, &verb_size); +	if (verb_size < 0) { +		log_debug("No verb data\n"); +		return -EINVAL; +	} +	log_debug("verb_size: %d\n", verb_size); + +	for (i = 0; i < verb_size / sizeof(*verb); i++) { +		ret = hda_wait_for_ready(regs); +		if (ret) { +			log_debug("  codec ready timeout\n"); +			return ret; +		} + +		writel(fdt32_to_cpu(verb[i]), ®s->cmd); + +		ret = hda_wait_for_valid(regs); +		if (ret) { +			log_debug("  codec valid timeout\n"); +			return ret; +		} +	} + +	return 0; +} + +static int codec_init(struct udevice *dev, struct hda_regs *regs, uint addr) +{ +	ofnode node; +	uint id; +	int ret; + +	log_debug("Initializing codec #%d\n", addr); +	ret = hda_wait_for_ready(regs); +	if (ret) { +		log_debug("  codec not ready\n"); +		return ret; +	} + +	/* Read the codec's vendor ID */ +	writel(addr << AZALIA_CODEC_SHIFT | +	       AZALIA_OPCODE_READ_PARAM << AZALIA_VERB_SHIFT | +	       AZALIA_PARAM_VENDOR_ID, ®s->cmd); +	ret = hda_wait_for_valid(regs); +	if (ret) { +		log_debug("  codec not valid\n"); +		return ret; +	} + +	id = readl(®s->resp); +	log_debug("codec vid/did: %08x\n", id); +	ret = find_verb_data(dev, id, &node); +	if (ret) { +		log_debug("No verb (err=%d)\n", ret); +		return ret; +	} +	ret = send_verbs(node, "verbs", regs); +	if (ret) { +		log_debug("failed to send verbs (err=%d)\n", ret); +		return ret; +	} +	log_debug("verb loaded\n"); + +	return 0; +} + +int hda_codecs_init(struct udevice *dev, struct hda_regs *regs, u32 codec_mask) +{ +	int ret; +	int i; + +	for (i = 3; i >= 0; i--) { +		if (codec_mask & (1 << i)) { +			ret = codec_init(dev, regs, i); +			if (ret) +				return ret; +		} +	} + +	ret = send_verbs(dev_ofnode(dev), "beep-verbs", regs); +	if (ret) { +		log_debug("failed to send beep verbs (err=%d)\n", ret); +		return ret; +	} +	log_debug("beep verbs loaded\n"); + +	return 0; +} + +/** + * exec_verb() - Write a verb to the codec + * + * @regs: HDA registers + * @val: Command to write + * @response: Set to response from codec + * @return 0 if OK, -ve on error + */ +static int exec_verb(struct hda_regs *regs, uint val, uint *response) +{ +	int ret; + +	ret = hda_wait_for_ready(regs); +	if (ret) +		return ret; + +	writel(val, ®s->cmd); + +	return wait_for_response(regs, response); +} + +/** + * get_subnode_info() - Get subnode information + * + * @regs: HDA registers + * @nid: Parent node ID to check + * @num_sub_nodesp: Returns number of subnodes + * @start_sub_node_nidp: Returns start subnode number + * @return 0 if OK, -ve on error + */ +static int get_subnode_info(struct hda_regs *regs, uint nid, +			    uint *num_sub_nodesp, uint *start_sub_node_nidp) +{ +	uint response; +	int ret; + +	ret = exec_verb(regs, hda_verb(nid, HDA_VERB_GET_PARAMS, +				       GET_PARAMS_NODE_COUNT), +			&response); +	if (ret < 0) { +		printf("Audio: Error reading sub-node info %d\n", nid); +		return ret; +	} + +	*num_sub_nodesp = (response & NUM_SUB_NODES_M) >> NUM_SUB_NODES_S; +	*start_sub_node_nidp = (response & FIRST_SUB_NODE_M) >> +			 FIRST_SUB_NODE_S; + +	return 0; +} + +/** + * find_beep_node_in_group() - Finds the beeping node + * + * Searches the audio group for a node that supports beeping + * + * @regs: HDA registers + * @group_nid: Group node ID to check + * @return 0 if OK, -ve on error + */ +static uint find_beep_node_in_group(struct hda_regs *regs, uint group_nid) +{ +	uint node_count = 0; +	uint current_nid = 0; +	uint response; +	uint end_nid; +	int ret; + +	ret = get_subnode_info(regs, group_nid, &node_count, ¤t_nid); +	if (ret < 0) +		return 0; + +	end_nid = current_nid + node_count; +	while (current_nid < end_nid) { +		ret = exec_verb(regs, +				hda_verb(current_nid, HDA_VERB_GET_PARAMS, +					 GET_PARAMS_AUDIO_WIDGET_CAPS), +				&response); +		if (ret < 0) { +			printf("Audio: Error reading widget caps\n"); +			return 0; +		} + +		if ((response & AUDIO_WIDGET_TYPE_M) >> AUDIO_WIDGET_TYPE_S == +		    AUDIO_WIDGET_TYPE_BEEP) +			return current_nid; + +		current_nid++; +	} + +	return 0; /* no beep node found */ +} + +/** + * audio_group_has_beep_node() - Check if group has a beep node + * + * Checks if the given audio group contains a beep generator + * @regs: HDA registers + * @nid: Node ID to check + * @return 0 if OK, -ve on error + */ +static int audio_group_has_beep_node(struct hda_regs *regs, uint nid) +{ +	uint response; +	int ret; + +	ret = exec_verb(regs, hda_verb(nid, HDA_VERB_GET_PARAMS, +				       GET_PARAMS_AUDIO_GROUP_CAPS), +			&response); +	if (ret < 0) { +		printf("Audio: Error reading audio group caps %d\n", nid); +		return 0; +	} + +	return !!(response & AUDIO_GROUP_CAPS_BEEP_GEN); +} + +/** + * get_hda_beep_nid() - Finds the node ID of the beep node + * + * Finds the nid of the beep node if it exists. Starts at the root node, for + * each sub-node checks if the group contains a beep node.  If the group + * contains a beep node, polls each node in the group until it is found. + * + * If the device has a intel,beep-nid property, the value of that is used + * instead. + * + * @dev: Sound device + * @return Node ID >0 if found, -ve error code otherwise + */ +static int get_hda_beep_nid(struct udevice *dev) +{ +	struct hda_codec_priv *priv = dev_get_priv(dev); +	uint current_nid = 0; +	uint node_count = 0; +	uint end_nid; +	int ret; + +	/* If the field exists, use the beep nid set in the fdt */ +	ret = dev_read_u32(dev, "intel,beep-nid", ¤t_nid); +	if (!ret) +		return current_nid; + +	ret = get_subnode_info(priv->regs, HDA_ROOT_NODE, &node_count, +			       ¤t_nid); +	if (ret < 0) +		return ret; + +	end_nid = current_nid + node_count; +	while (current_nid < end_nid) { +		if (audio_group_has_beep_node(priv->regs, current_nid)) +			return find_beep_node_in_group(priv->regs, +						       current_nid); +		current_nid++; +	} +	 /* no beep node found */ + +	return -ENOENT; +} + +/** + * set_beep_divisor() - Sets the beep divisor to set the pitch + * + * @priv: Device's private data + * @divider: Divider value (0 to disable the beep) + * @return 0 if OK, -ve on error + */ +static int set_beep_divisor(struct hda_codec_priv *priv, uint divider) +{ +	return exec_verb(priv->regs, +			 hda_verb(priv->beep_nid, HDA_VERB_SET_BEEP, divider), +			 NULL); +} + +int hda_codec_init(struct udevice *dev) +{ +	struct hda_codec_priv *priv = dev_get_priv(dev); +	ulong base_addr; + +	base_addr = dm_pci_read_bar32(dev, 0); +	log_debug("base = %08lx\n", base_addr); +	if (!base_addr) +		return -EINVAL; + +	priv->regs = (struct hda_regs *)base_addr; + +	return 0; +} + +int hda_codec_finish_init(struct udevice *dev) +{ +	struct hda_codec_priv *priv = dev_get_priv(dev); +	int ret; + +	ret = get_hda_beep_nid(dev); +	if (ret <= 0) { +		log_warning("Could not find beep NID (err=%d)\n", ret); +		return ret ? ret : -ENOENT; +	} +	priv->beep_nid = ret; + +	return 0; +} + +int hda_codec_start_beep(struct udevice *dev, int frequency_hz) +{ +	struct hda_codec_priv *priv = dev_get_priv(dev); +	uint divider_val; + +	if (!priv->beep_nid) { +		log_err("Failed to find a beep-capable node\n"); +		return -ENOENT; +	} + +	if (!frequency_hz) +		divider_val = 0;	/* off */ +	else if (frequency_hz > BEEP_FREQ_BASE) +		divider_val = 1; +	else if (frequency_hz < BEEP_FREQ_BASE / 0xff) +		divider_val = 0xff; +	else +		divider_val = 0xff & (BEEP_FREQ_BASE / frequency_hz); + +	return set_beep_divisor(priv, divider_val); +} + +int hda_codec_stop_beep(struct udevice *dev) +{ +	struct hda_codec_priv *priv = dev_get_priv(dev); + +	return set_beep_divisor(priv, 0); +} + +static const struct sound_ops hda_codec_ops = { +	.setup		= hda_codec_finish_init, +	.start_beep	= hda_codec_start_beep, +	.stop_beep	= hda_codec_stop_beep, +}; + +U_BOOT_DRIVER(hda_codec) = { +	.name		= "hda_codec", +	.id		= UCLASS_SOUND, +	.ops		= &hda_codec_ops, +	.priv_auto_alloc_size	= sizeof(struct hda_codec_priv), +	.probe		= hda_codec_init, +}; + +static struct pci_device_id hda_supported[] = { +	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_COUGARPOINT_HDA}, +	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_PANTHERPOINT_HDA}, +	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, +		PCI_DEVICE_ID_INTEL_WILDCATPOINT_HDA) }, + +	/* +	 * Note this driver is not necessarily generic, but it attempts to +	 * support any codec in the hd-audio class +	 */ +	{ PCI_DEVICE_CLASS(PCI_CLASS_MULTIMEDIA_HD_AUDIO, 0xffffff) }, +}; + +U_BOOT_PCI_DEVICE(hda_codec, hda_supported); | 
