summaryrefslogtreecommitdiffstats
path: root/sound/hda/intel-nhlt.c
blob: 6ed80a4cba01a6187370211e6e442372e3eb190f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2015-2019 Intel Corporation

#include <linux/acpi.h>
#include <sound/intel-nhlt.h>

#define NHLT_ACPI_HEADER_SIG	"NHLT"

/* Unique identification for getting NHLT blobs */
static guid_t osc_guid =
	GUID_INIT(0xA69F886E, 0x6CEB, 0x4594,
		  0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53);

struct nhlt_acpi_table *intel_nhlt_init(struct device *dev)
{
	acpi_handle handle;
	union acpi_object *obj;
	struct nhlt_resource_desc *nhlt_ptr;
	struct nhlt_acpi_table *nhlt_table = NULL;

	handle = ACPI_HANDLE(dev);
	if (!handle) {
		dev_err(dev, "Didn't find ACPI_HANDLE\n");
		return NULL;
	}

	obj = acpi_evaluate_dsm(handle, &osc_guid, 1, 1, NULL);

	if (!obj)
		return NULL;

	if (obj->type != ACPI_TYPE_BUFFER) {
		dev_dbg(dev, "No NHLT table found\n");
		ACPI_FREE(obj);
		return NULL;
	}

	nhlt_ptr = (struct nhlt_resource_desc  *)obj->buffer.pointer;
	if (nhlt_ptr->length)
		nhlt_table = (struct nhlt_acpi_table *)
			memremap(nhlt_ptr->min_addr, nhlt_ptr->length,
				 MEMREMAP_WB);
	ACPI_FREE(obj);
	if (nhlt_table &&
	    (strncmp(nhlt_table->header.signature,
		     NHLT_ACPI_HEADER_SIG,
		     strlen(NHLT_ACPI_HEADER_SIG)) != 0)) {
		memunmap(nhlt_table);
		dev_err(dev, "NHLT ACPI header signature incorrect\n");
		return NULL;
	}
	return nhlt_table;
}
EXPORT_SYMBOL_GPL(intel_nhlt_init);

void intel_nhlt_free(struct nhlt_acpi_table *nhlt)
{
	memunmap((void *)nhlt);
}
EXPORT_SYMBOL_GPL(intel_nhlt_free);

int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt)
{
	struct nhlt_endpoint *epnt;
	struct nhlt_dmic_array_config *cfg;
	struct nhlt_vendor_dmic_array_config *cfg_vendor;
	struct nhlt_fmt *fmt_configs;
	unsigned int dmic_geo = 0;
	u16 max_ch = 0;
	u8 i, j;

	if (!nhlt)
		return 0;

	if (nhlt->header.length <= sizeof(struct acpi_table_header)) {
		dev_warn(dev, "Invalid DMIC description table\n");
		return 0;
	}

	for (j = 0, epnt = nhlt->desc; j < nhlt->endpoint_count; j++,
	     epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length)) {

		if (epnt->linktype != NHLT_LINK_DMIC)
			continue;

		cfg = (struct nhlt_dmic_array_config  *)(epnt->config.caps);
		fmt_configs = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);

		/* find max number of channels based on format_configuration */
		if (fmt_configs->fmt_count) {
			dev_dbg(dev, "%s: found %d format definitions\n",
				__func__, fmt_configs->fmt_count);

			for (i = 0; i < fmt_configs->fmt_count; i++) {
				struct wav_fmt_ext *fmt_ext;

				fmt_ext = &fmt_configs->fmt_config[i].fmt_ext;

				if (fmt_ext->fmt.channels > max_ch)
					max_ch = fmt_ext->fmt.channels;
			}
			dev_dbg(dev, "%s: max channels found %d\n", __func__, max_ch);
		} else {
			dev_dbg(dev, "%s: No format information found\n", __func__);
		}

		if (cfg->device_config.config_type != NHLT_CONFIG_TYPE_MIC_ARRAY) {
			dmic_geo = max_ch;
		} else {
			switch (cfg->array_type) {
			case NHLT_MIC_ARRAY_2CH_SMALL:
			case NHLT_MIC_ARRAY_2CH_BIG:
				dmic_geo = MIC_ARRAY_2CH;
				break;

			case NHLT_MIC_ARRAY_4CH_1ST_GEOM:
			case NHLT_MIC_ARRAY_4CH_L_SHAPED:
			case NHLT_MIC_ARRAY_4CH_2ND_GEOM:
				dmic_geo = MIC_ARRAY_4CH;
				break;
			case NHLT_MIC_ARRAY_VENDOR_DEFINED:
				cfg_vendor = (struct nhlt_vendor_dmic_array_config *)cfg;
				dmic_geo = cfg_vendor->nb_mics;
				break;
			default:
				dev_warn(dev, "%s: undefined DMIC array_type 0x%0x\n",
					 __func__, cfg->array_type);
			}

			if (dmic_geo > 0) {
				dev_dbg(dev, "%s: Array with %d dmics\n", __func__, dmic_geo);
			}
			if (max_ch > dmic_geo) {
				dev_dbg(dev, "%s: max channels %d exceed dmic number %d\n",
					__func__, max_ch, dmic_geo);
			}
		}
	}

	dev_dbg(dev, "%s: dmic number %d max_ch %d\n",
		__func__, dmic_geo, max_ch);

	return dmic_geo;
}
EXPORT_SYMBOL_GPL(intel_nhlt_get_dmic_geo);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel NHLT driver");