diff options
-rw-r--r-- | src/Kconfig | 6 | ||||
-rw-r--r-- | src/include/nhlt.h | 284 | ||||
-rw-r--r-- | src/lib/Makefile.inc | 1 | ||||
-rw-r--r-- | src/lib/nhlt.c | 421 |
4 files changed, 712 insertions, 0 deletions
diff --git a/src/Kconfig b/src/Kconfig index f6702eadaf..6a0209dad3 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -613,6 +613,12 @@ config COMMON_FADT bool default n +config ACPI_NHLT + bool + default n + help + Build support for NHLT (non HD Audio) ACPI table generation. + #These Options are here to avoid "undefined" warnings. #The actual selection and help texts are in the following menu. diff --git a/src/include/nhlt.h b/src/include/nhlt.h new file mode 100644 index 0000000000..ca1669381c --- /dev/null +++ b/src/include/nhlt.h @@ -0,0 +1,284 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2015 Google, Inc. + * + * 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. + */ + +#ifndef _NHLT_H_ +#define _NHLT_H_ + +#include <stdint.h> +#include <stddef.h> + +struct nhlt; +struct nhlt_endpoint; +struct nhlt_format; +struct nhlt_format_config; + +/* + * Non HD Audio ACPI support. This table is typically used for Intel Smart + * Sound Technology DSP. It provides a way to encode opaque settings in + * the ACPI tables. + * + * While the structure fields of the NHLT structs are exposed below + * the SoC/chipset code should be the only other user manipulating the + * fields directly aside from the library itself. + * + * The NHLT table consists of endpoints which in turn contain different + * supporting stream formats. Each endpoint may contain a device specific + * configuration payload as well as each stream format. + * + * Most code should use the SoC variants of the functions because + * there is required logic needed to be performed by the SoC. The SoC + * code should be abstracting the inner details of these functions that + * specically apply to NHLT objects for that SoC. + * + * An example sequence: + * + * nhlt = nhlt_init() + * ep = nhlt_soc_add_endpoint() + * nhlt_endpoint_append_config(ep) + * nhlt_endpoint_add_formats(ep) + * nhlt_soc_serialize() + */ + +/* Obtain an nhlt object for adding endpoints. Returns NULL on error. */ +struct nhlt *nhlt_init(void); + +/* Return the size of the NHLT table including APCI header. */ +size_t nhlt_current_size(struct nhlt *nhlt); + +/* + * Add endpoint to NHLT object. Returns NULL on error. + * + * Note that the SoC variant uses SoC-specifc types for the hardware interface + * and device types. This is to allow the SoC code to validate its particular + * device support for specific hardware interfaces. + * + * The more generic nhlt_add_endpoint() is called by the SoC code to provide + * the specific assumptions/uses for NHLT for that platform. All fields + * are the NHLT enumerations found within this header file. + */ +struct nhlt_endpoint *nhlt_add_endpoint(struct nhlt *nhlt, int link_type, + int device_type, int dir, + uint16_t vid, uint16_t did); +struct nhlt_endpoint *nhlt_soc_add_endpoint(struct nhlt *nhlt, int soc_hwintf, + int soc_devtype, int dir); + +/* + * Append blob of configuration to the endpoint proper. Returns 0 on + * success, < 0 on error. A copy of the configuration is made so any + * resources pointed to by config can be freed after the call. + */ +int nhlt_endpoint_append_config(struct nhlt_endpoint *endpoint, + const void *config, size_t config_sz); + +/* Add a format type to the provided endpoint. Returns NULL on error. */ +struct nhlt_format *nhlt_add_format(struct nhlt_endpoint *endpoint, + int num_channels, + int sample_freq_khz, + int container_bits_per_sample, + int valid_bits_per_sample, + uint32_t speaker_mask); + +/* + * Append blob of configuration to the format proper. Returns 0 on + * success, < 0 on error. A copy of the configuration is made so any + * resources pointed to by config can be freed after the call. + */ +int nhlt_format_append_config(struct nhlt_format *format, const void *config, + size_t config_sz); + +/* + * Add num_formats described by formats to the endpoint. This function + * effectively wraps nhlt_add_format() and nhlt_format_config() using the + * data found in each nhlt_format_config object. Returns 0 on success, < 0 + * on error. + */ +int nhlt_endpoint_add_formats(struct nhlt_endpoint *endpoint, + const struct nhlt_format_config *formats, + size_t num_formats); + +/* + * Increment the instance id for a given link type. This function is + * used for marking a device being completely added to the NHLT object. + * Subsequent endpoints added to the nhlt object with the same link type + * will use incremented instance id. + */ +void nhlt_next_instance(struct nhlt *nhlt, int link_type); + +/* + * Serialize NHLT object to ACPI table. Take in the beginning address of where + * the table will reside and return the address of the next ACPI table. On + * error 0 will be returned. The NHLT object is no longer valid after this + * function is called. + */ +uintptr_t nhlt_serialize(struct nhlt *nhlt, uintptr_t acpi_addr); + +/* + * While very similar to nhlt_serialize() the SoC specific function allows + * the chipset to perform any needed accounting work such as updating ACPI + * field references for the serialized structure. + */ +uintptr_t nhlt_soc_serialize(struct nhlt *nhlt, uintptr_t acpi_addr); + +/* Link and device types. */ +enum { + NHLT_LINK_HDA, + NHLT_LINK_DSP, + NHLT_LINK_PDM, + NHLT_LINK_SSP, + NHLT_MAX_LINK_TYPES, +}; + +enum { + NHLT_SSP_DEV_BT, /* Bluetooth */ + NHLT_SSP_DEV_MODEM, + NHLT_SSP_DEV_FM, + NHLT_SSP_DEV_RESERVED, + NHLT_SSP_DEV_I2S = 4, +}; + +enum { + NHLT_PDM_DEV, +}; + +/* Endpoint direction. */ +enum { + NHLT_DIR_RENDER, + NHLT_DIR_CAPTURE, + NHLT_DIR_BIDIRECTIONAL, +}; + +/* Channel Mask for an endpoint. While they are prefixed with 'SPEAKER' the + * channel masks are also used for capture devices. */ +enum { + SPEAKER_FRONT_LEFT = 1 << 0, + SPEAKER_FRONT_RIGHT = 1 << 1, + SPEAKER_FRONT_CENTER = 1 << 2, + SPEAKER_LOW_FREQUENCY = 1 << 3, + SPEAKER_BACK_LEFT = 1 << 4, + SPEAKER_BACK_RIGHT = 1 << 5, + SPEAKER_FRONT_LEFT_OF_CENTER = 1 << 6, + SPEAKER_FRONT_RIGHT_OF_CENTER = 1 << 7, + SPEAKER_BACK_CENTER = 1 << 8, + SPEAKER_SIDE_LEFT = 1 << 9, + SPEAKER_SIDE_RIGHT = 1 << 10, + SPEAKER_TOP_CENTER = 1 << 11, + SPEAKER_TOP_FRONT_LEFT = 1 << 12, + SPEAKER_TOP_FRONT_CENTER = 1 << 13, + SPEAKER_TOP_FRONT_RIGHT = 1 << 14, + SPEAKER_TOP_BACK_LEFT = 1 << 15, + SPEAKER_TOP_BACK_CENTER = 1 << 16, + SPEAKER_TOP_BACK_RIGHT = 1 << 17, +}; + + +/* Supporting structures. Only SoC/chipset and the library code directly should + * be manipulating these structures. */ +struct sub_format { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +}; + +struct nhlt_specific_config { + uint32_t size; + void *capabilities; +}; + +struct nhlt_waveform { + uint16_t tag; + uint16_t num_channels; + uint32_t samples_per_second; + uint32_t bytes_per_second; + uint16_t block_align; + uint16_t bits_per_sample; + uint16_t extra_size; + uint16_t valid_bits_per_sample; + uint32_t channel_mask; + struct sub_format sub_format; +}; + +struct nhlt_format { + struct nhlt_waveform waveform; + struct nhlt_specific_config config; +}; + +/* + * This struct is used by nhlt_endpoint_add_formats() for easily adding + * waveform formats with associated settings file. + */ +struct nhlt_format_config { + int num_channels; + int sample_freq_khz; + int container_bits_per_sample; + int valid_bits_per_sample; + uint32_t speaker_mask; + const char *settings_file; +}; + +/* Arbitrary max number of formats per endpoint. */ +#define MAX_FORMATS 2 +struct nhlt_endpoint { + uint32_t length; + uint8_t link_type; + uint8_t instance_id; + uint16_t vendor_id; + uint16_t device_id; + uint16_t revision_id; + uint32_t subsystem_id; + uint8_t device_type; + uint8_t direction; + uint8_t virtual_bus_id; + struct nhlt_specific_config config; + uint8_t num_formats; + struct nhlt_format formats[MAX_FORMATS]; +}; + +#define MAX_ENDPOINTS 8 +struct nhlt { + uint8_t num_endpoints; + struct nhlt_endpoint endpoints[MAX_ENDPOINTS]; + uint8_t current_instance_id[NHLT_MAX_LINK_TYPES]; +}; + +struct nhlt_tdm_config { + uint8_t virtual_slot; + uint8_t config_type; +}; + +enum { + NHLT_TDM_BASIC, + NHLT_TDM_MIC_ARRAY, +}; + +struct nhlt_dmic_array_config { + struct nhlt_tdm_config tdm_config; + uint8_t array_type; +}; + +/* + * Microphone array definitions may be found here: + * https://msdn.microsoft.com/en-us/library/windows/hardware/dn613960%28v=vs.85%29.aspx + */ +enum { + NHLT_MIC_ARRAY_2CH_SMALL = 0xa, + NHLT_MIC_ARRAY_2CH_BIG = 0xb, + NHLT_MIC_ARRAY_4CH_1ST_GEOM = 0xc, + NHLT_MIC_ARRAY_4CH_L_SHAPED = 0xd, + NHLT_MIC_ARRAY_4CH_2ND_GEOM = 0xe, + NHLT_MIC_ARRAY_VENDOR_DEFINED = 0xf, +}; + +#endif diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 724391fb21..11db6550ea 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -125,6 +125,7 @@ ramstage-$(CONFIG_TIMER_QUEUE) += timer_queue.c ramstage-$(CONFIG_GENERIC_GPIO_LIB) += gpio.c ramstage-$(CONFIG_GENERIC_UDELAY) += timer.c ramstage-y += b64_decode.c +ramstage-$(CONFIG_ACPI_NHLT) += nhlt.c romstage-y += cbmem_common.c romstage-y += imd_cbmem.c diff --git a/src/lib/nhlt.c b/src/lib/nhlt.c new file mode 100644 index 0000000000..4fa4a0c2c1 --- /dev/null +++ b/src/lib/nhlt.c @@ -0,0 +1,421 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2015 Google, Inc. + * + * 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 <arch/acpi.h> +#include <cbfs.h> +#include <commonlib/endian.h> +#include <console/console.h> +#include <nhlt.h> +#include <stdlib.h> +#include <string.h> + +#define NHLT_RID 1 +#define NHLT_SSID 1 +#define WAVEFORMAT_TAG 0xfffe + +static const struct sub_format pcm_subformat = { + .data1 = 0x00000001, + .data2 = 0x0000, + .data3 = 0x0010, + .data4 = { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }, +}; + +struct nhlt *nhlt_init(void) +{ + struct nhlt *nhlt; + + nhlt = malloc(sizeof(*nhlt)); + + if (nhlt == NULL) + return NULL; + + memset(nhlt, 0, sizeof(*nhlt)); + + return nhlt; +} + +struct nhlt_endpoint *nhlt_add_endpoint(struct nhlt *nhlt, int link_type, + int device_type, int dir, + uint16_t vid, uint16_t did) +{ + struct nhlt_endpoint *endp; + + if (link_type < NHLT_LINK_HDA || link_type >= NHLT_MAX_LINK_TYPES) + return NULL; + + if (nhlt->num_endpoints >= MAX_ENDPOINTS) + return NULL; + + endp = &nhlt->endpoints[nhlt->num_endpoints]; + + endp->link_type = link_type; + endp->instance_id = nhlt->current_instance_id[link_type]; + endp->vendor_id = vid; + endp->device_id = did; + endp->revision_id = NHLT_RID; + endp->subsystem_id = NHLT_SSID; + endp->device_type = device_type; + endp->direction = dir; + endp->virtual_bus_id = 0; + + nhlt->num_endpoints++; + + return endp; +} + +static int append_specific_config(struct nhlt_specific_config *spec_cfg, + const void *config, size_t config_sz) +{ + size_t new_sz; + void *new_cfg; + + if (config == NULL || config_sz == 0) + return 0; + + new_sz = spec_cfg->size + config_sz; + + new_cfg = malloc(new_sz); + + if (new_cfg == NULL) + return -1; + + /* Append new config. */ + memcpy(new_cfg, spec_cfg->capabilities, spec_cfg->size); + memcpy(new_cfg + spec_cfg->size, config, config_sz); + + free(spec_cfg->capabilities); + + /* Update with new config data. */ + spec_cfg->size = new_sz; + spec_cfg->capabilities = new_cfg; + + return 0; +} + +int nhlt_endpoint_append_config(struct nhlt_endpoint *endp, const void *config, + size_t config_sz) +{ + return append_specific_config(&endp->config, config, config_sz); +} + +struct nhlt_format *nhlt_add_format(struct nhlt_endpoint *endp, + int num_channels, + int sample_freq_khz, + int container_bits_per_sample, + int valid_bits_per_sample, + uint32_t speaker_mask) +{ + struct nhlt_format *fmt; + struct nhlt_waveform *wave; + + if (endp->num_formats >= MAX_FORMATS) + return NULL; + + fmt = &endp->formats[endp->num_formats]; + wave = &fmt->waveform; + + wave->tag = WAVEFORMAT_TAG; + wave->num_channels = num_channels; + wave->samples_per_second = sample_freq_khz * KHz; + wave->bits_per_sample = container_bits_per_sample; + wave->extra_size = sizeof(wave->valid_bits_per_sample); + wave->extra_size += sizeof(wave->channel_mask); + wave->extra_size += sizeof(wave->sub_format); + wave->valid_bits_per_sample = valid_bits_per_sample; + wave->channel_mask = speaker_mask; + memcpy(&wave->sub_format, &pcm_subformat, sizeof(wave->sub_format)); + + /* Calculate the dervied fields. */ + wave->block_align = wave->num_channels * wave->bits_per_sample / 8; + wave->bytes_per_second = wave->block_align * wave->samples_per_second; + + endp->num_formats++; + + return fmt; +} + +int nhlt_format_append_config(struct nhlt_format *fmt, const void *config, + size_t config_sz) +{ + return append_specific_config(&fmt->config, config, config_sz); +} + +int nhlt_endpoint_add_formats(struct nhlt_endpoint *endp, + const struct nhlt_format_config *formats, + size_t num_formats) +{ + size_t i; + + for (i = 0; i < num_formats; i++) { + struct nhlt_format *fmt; + struct cbfsf file; + struct region_device settings; + void *settings_data; + const struct nhlt_format_config *cfg = &formats[i]; + + fmt = nhlt_add_format(endp, cfg->num_channels, + cfg->sample_freq_khz, + cfg->container_bits_per_sample, + cfg->valid_bits_per_sample, + cfg->speaker_mask); + + if (fmt == NULL) + return -1; + + if (cfg->settings_file == NULL) + continue; + + /* Find the settings file in CBFS and place it in format. */ + if (cbfs_boot_locate(&file, cfg->settings_file, NULL)) + return -1; + + cbfs_file_data(&settings, &file); + + settings_data = rdev_mmap_full(&settings); + + if (settings_data == NULL) + return -1; + + if (nhlt_format_append_config(fmt, settings_data, + region_device_sz(&settings))) { + rdev_munmap(&settings, settings_data); + return -1; + } + + rdev_munmap(&settings, settings_data); + } + + return 0; +} + +void nhlt_next_instance(struct nhlt *nhlt, int link_type) +{ + if (link_type < NHLT_LINK_HDA || link_type >= NHLT_MAX_LINK_TYPES) + return; + + nhlt->current_instance_id[link_type]++; +} + +static size_t calc_specific_config_size(struct nhlt_specific_config *cfg) +{ + return sizeof(cfg->size) + cfg->size; +} + +static size_t calc_format_size(struct nhlt_format *fmt) +{ + size_t sz = 0; + + /* Wave format first. */ + sz += sizeof(fmt->waveform.tag); + sz += sizeof(fmt->waveform.num_channels); + sz += sizeof(fmt->waveform.samples_per_second); + sz += sizeof(fmt->waveform.bytes_per_second); + sz += sizeof(fmt->waveform.block_align); + sz += sizeof(fmt->waveform.bits_per_sample); + sz += sizeof(fmt->waveform.extra_size); + sz += sizeof(fmt->waveform.valid_bits_per_sample); + sz += sizeof(fmt->waveform.channel_mask); + sz += sizeof(fmt->waveform.sub_format); + + sz += calc_specific_config_size(&fmt->config); + + return sz; +} + +static size_t calc_endpoint_size(struct nhlt_endpoint *endp) +{ + int i; + size_t sz = 0; + + sz += sizeof(endp->length) + sizeof(endp->link_type); + sz += sizeof(endp->instance_id) + sizeof(endp->vendor_id); + sz += sizeof(endp->device_id) + sizeof(endp->revision_id); + sz += sizeof(endp->subsystem_id) + sizeof(endp->device_type); + sz += sizeof(endp->direction) + sizeof(endp->virtual_bus_id); + sz += calc_specific_config_size(&endp->config); + sz += sizeof(endp->num_formats); + + for (i = 0; i < endp->num_formats; i++) + sz += calc_format_size(&endp->formats[i]); + + /* Adjust endpoint length to reflect current configuration. */ + endp->length = sz; + + return sz; +} + +static size_t calc_endpoints_size(struct nhlt *nhlt) +{ + int i; + size_t sz = 0; + + for (i = 0; i < nhlt->num_endpoints; i++) + sz += calc_endpoint_size(&nhlt->endpoints[i]); + + return sz; +} + +static size_t calc_size(struct nhlt *nhlt) +{ + return sizeof(nhlt->num_endpoints) + calc_endpoints_size(nhlt); +} + +size_t nhlt_current_size(struct nhlt *nhlt) +{ + return calc_size(nhlt) + sizeof(acpi_header_t); +} + +static void nhlt_free_resources(struct nhlt *nhlt) +{ + int i; + int j; + + /* Free all specific configs. */ + for (i = 0; i < nhlt->num_endpoints; i++) { + struct nhlt_endpoint *endp = &nhlt->endpoints[i]; + + free(endp->config.capabilities); + for (j = 0; j < endp->num_formats; j++) { + struct nhlt_format *fmt = &endp->formats[j]; + + free(fmt->config.capabilities); + } + } + + /* Free nhlt object proper. */ + free(nhlt); +} + +struct cursor { + uint8_t *buf; +}; + +static void ser8(struct cursor *cur, uint8_t val) +{ + write_le8(cur->buf, val); + cur->buf += sizeof(val); +} + +static void ser16(struct cursor *cur, uint16_t val) +{ + write_le16(cur->buf, val); + cur->buf += sizeof(val); +} + +static void ser32(struct cursor *cur, uint32_t val) +{ + write_le32(cur->buf, val); + cur->buf += sizeof(val); +} + +static void serblob(struct cursor *cur, void *from, size_t sz) +{ + memcpy(cur->buf, from, sz); + cur->buf += sz; +} + +static void serialize_specific_config(struct nhlt_specific_config *cfg, + struct cursor *cur) +{ + ser32(cur, cfg->size); + serblob(cur, cfg->capabilities, cfg->size); +} + +static void serialize_waveform(struct nhlt_waveform *wave, struct cursor *cur) +{ + ser16(cur, wave->tag); + ser16(cur, wave->num_channels); + ser32(cur, wave->samples_per_second); + ser32(cur, wave->bytes_per_second); + ser16(cur, wave->block_align); + ser16(cur, wave->bits_per_sample); + ser16(cur, wave->extra_size); + ser16(cur, wave->valid_bits_per_sample); + ser32(cur, wave->channel_mask); + ser32(cur, wave->sub_format.data1); + ser16(cur, wave->sub_format.data2); + ser16(cur, wave->sub_format.data3); + serblob(cur, wave->sub_format.data4, sizeof(wave->sub_format.data4)); +} + +static void serialize_format(struct nhlt_format *fmt, struct cursor *cur) +{ + serialize_waveform(&fmt->waveform, cur); + serialize_specific_config(&fmt->config, cur); +} + +static void serialize_endpoint(struct nhlt_endpoint *endp, struct cursor *cur) +{ + int i; + + ser32(cur, endp->length); + ser8(cur, endp->link_type); + ser8(cur, endp->instance_id); + ser16(cur, endp->vendor_id); + ser16(cur, endp->device_id); + ser16(cur, endp->revision_id); + ser32(cur, endp->subsystem_id); + ser8(cur, endp->device_type); + ser8(cur, endp->direction); + ser8(cur, endp->virtual_bus_id); + serialize_specific_config(&endp->config, cur); + ser8(cur, endp->num_formats); + + for (i = 0; i < endp->num_formats; i++) + serialize_format(&endp->formats[i], cur); +} + +static void nhlt_serialize_endpoints(struct nhlt *nhlt, struct cursor *cur) +{ + int i; + + ser8(cur, nhlt->num_endpoints); + + for (i = 0; i < nhlt->num_endpoints; i++) + serialize_endpoint(&nhlt->endpoints[i], cur); +} + +uintptr_t nhlt_serialize(struct nhlt *nhlt, uintptr_t acpi_addr) +{ + struct cursor cur; + acpi_header_t *header; + size_t sz; + + printk(BIOS_DEBUG, "ACPI: * NHLT\n"); + + sz = nhlt_current_size(nhlt); + + /* Create header */ + header = (void *)acpi_addr; + memset(header, 0, sizeof(acpi_header_t)); + memcpy(header->signature, "NHLT", 4); + write_le32(&header->length, sz); + write_le8(&header->revision, 5); + memcpy(header->oem_id, OEM_ID, 6); + memcpy(header->oem_table_id, ACPI_TABLE_CREATOR, 8); + memcpy(header->asl_compiler_id, ASLC, 4); + + cur.buf = (void *)(acpi_addr + sizeof(acpi_header_t)); + nhlt_serialize_endpoints(nhlt, &cur); + + write_le8(&header->checksum, acpi_checksum((void *)header, sz)); + + nhlt_free_resources(nhlt); + + acpi_addr += sz; + acpi_addr = ALIGN_UP(acpi_addr, 16); + + return acpi_addr; +} |