Commit | Line | Data |
---|---|---|
82b8acc0 CR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright(c) 2023-2024 Intel Corporation | |
4 | * | |
5 | * Authors: Cezary Rojewski <cezary.rojewski@intel.com> | |
6 | * Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> | |
7 | */ | |
8 | ||
9 | #define pr_fmt(fmt) "ACPI: NHLT: " fmt | |
10 | ||
11 | #include <linux/acpi.h> | |
12 | #include <linux/errno.h> | |
13 | #include <linux/export.h> | |
14 | #include <linux/minmax.h> | |
15 | #include <linux/printk.h> | |
16 | #include <linux/types.h> | |
17 | #include <acpi/nhlt.h> | |
18 | ||
a640acab | 19 | static struct acpi_table_nhlt *acpi_gbl_nhlt; |
82b8acc0 | 20 | |
a640acab | 21 | static struct acpi_table_nhlt empty_nhlt = { |
82b8acc0 CR |
22 | .header = { |
23 | .signature = ACPI_SIG_NHLT, | |
24 | }, | |
25 | }; | |
26 | ||
27 | /** | |
28 | * acpi_nhlt_get_gbl_table - Retrieve a pointer to the first NHLT table. | |
29 | * | |
30 | * If there is no NHLT in the system, acpi_gbl_nhlt will instead point to an | |
31 | * empty table. | |
32 | * | |
33 | * Return: ACPI status code of the operation. | |
34 | */ | |
35 | acpi_status acpi_nhlt_get_gbl_table(void) | |
36 | { | |
37 | acpi_status status; | |
38 | ||
39 | status = acpi_get_table(ACPI_SIG_NHLT, 0, (struct acpi_table_header **)(&acpi_gbl_nhlt)); | |
40 | if (!acpi_gbl_nhlt) | |
41 | acpi_gbl_nhlt = &empty_nhlt; | |
42 | return status; | |
43 | } | |
44 | EXPORT_SYMBOL_GPL(acpi_nhlt_get_gbl_table); | |
45 | ||
46 | /** | |
47 | * acpi_nhlt_put_gbl_table - Release the global NHLT table. | |
48 | */ | |
49 | void acpi_nhlt_put_gbl_table(void) | |
50 | { | |
51 | acpi_put_table((struct acpi_table_header *)acpi_gbl_nhlt); | |
52 | } | |
53 | EXPORT_SYMBOL_GPL(acpi_nhlt_put_gbl_table); | |
54 | ||
55 | /** | |
56 | * acpi_nhlt_endpoint_match - Verify if an endpoint matches criteria. | |
57 | * @ep: the endpoint to check. | |
58 | * @link_type: the hardware link type, e.g.: PDM or SSP. | |
59 | * @dev_type: the device type. | |
60 | * @dir: stream direction. | |
61 | * @bus_id: the ID of virtual bus hosting the endpoint. | |
62 | * | |
63 | * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative | |
64 | * value to ignore the parameter when matching. | |
65 | * | |
66 | * Return: %true if endpoint matches specified criteria or %false otherwise. | |
67 | */ | |
a640acab | 68 | bool acpi_nhlt_endpoint_match(const struct acpi_nhlt_endpoint *ep, |
82b8acc0 CR |
69 | int link_type, int dev_type, int dir, int bus_id) |
70 | { | |
71 | return ep && | |
72 | (link_type < 0 || ep->link_type == link_type) && | |
73 | (dev_type < 0 || ep->device_type == dev_type) && | |
74 | (bus_id < 0 || ep->virtual_bus_id == bus_id) && | |
75 | (dir < 0 || ep->direction == dir); | |
76 | } | |
77 | EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_match); | |
78 | ||
79 | /** | |
80 | * acpi_nhlt_tb_find_endpoint - Search a NHLT table for an endpoint. | |
81 | * @tb: the table to search. | |
82 | * @link_type: the hardware link type, e.g.: PDM or SSP. | |
83 | * @dev_type: the device type. | |
84 | * @dir: stream direction. | |
85 | * @bus_id: the ID of virtual bus hosting the endpoint. | |
86 | * | |
87 | * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative | |
88 | * value to ignore the parameter during the search. | |
89 | * | |
90 | * Return: A pointer to endpoint matching the criteria, %NULL if not found or | |
91 | * an ERR_PTR() otherwise. | |
92 | */ | |
a640acab CR |
93 | struct acpi_nhlt_endpoint * |
94 | acpi_nhlt_tb_find_endpoint(const struct acpi_table_nhlt *tb, | |
82b8acc0 CR |
95 | int link_type, int dev_type, int dir, int bus_id) |
96 | { | |
a640acab | 97 | struct acpi_nhlt_endpoint *ep; |
82b8acc0 CR |
98 | |
99 | for_each_nhlt_endpoint(tb, ep) | |
100 | if (acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id)) | |
101 | return ep; | |
102 | return NULL; | |
103 | } | |
104 | EXPORT_SYMBOL_GPL(acpi_nhlt_tb_find_endpoint); | |
105 | ||
106 | /** | |
107 | * acpi_nhlt_find_endpoint - Search all NHLT tables for an endpoint. | |
108 | * @link_type: the hardware link type, e.g.: PDM or SSP. | |
109 | * @dev_type: the device type. | |
110 | * @dir: stream direction. | |
111 | * @bus_id: the ID of virtual bus hosting the endpoint. | |
112 | * | |
113 | * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative | |
114 | * value to ignore the parameter during the search. | |
115 | * | |
116 | * Return: A pointer to endpoint matching the criteria, %NULL if not found or | |
117 | * an ERR_PTR() otherwise. | |
118 | */ | |
a640acab | 119 | struct acpi_nhlt_endpoint * |
82b8acc0 CR |
120 | acpi_nhlt_find_endpoint(int link_type, int dev_type, int dir, int bus_id) |
121 | { | |
122 | /* TODO: Currently limited to table of index 0. */ | |
123 | return acpi_nhlt_tb_find_endpoint(acpi_gbl_nhlt, link_type, dev_type, dir, bus_id); | |
124 | } | |
125 | EXPORT_SYMBOL_GPL(acpi_nhlt_find_endpoint); | |
126 | ||
127 | /** | |
128 | * acpi_nhlt_endpoint_find_fmtcfg - Search endpoint's formats configuration space | |
129 | * for a specific format. | |
130 | * @ep: the endpoint to search. | |
131 | * @ch: number of channels. | |
132 | * @rate: samples per second. | |
133 | * @vbps: valid bits per sample. | |
134 | * @bps: bits per sample. | |
135 | * | |
136 | * Return: A pointer to format matching the criteria, %NULL if not found or | |
137 | * an ERR_PTR() otherwise. | |
138 | */ | |
a640acab CR |
139 | struct acpi_nhlt_format_config * |
140 | acpi_nhlt_endpoint_find_fmtcfg(const struct acpi_nhlt_endpoint *ep, | |
82b8acc0 CR |
141 | u16 ch, u32 rate, u16 vbps, u16 bps) |
142 | { | |
a640acab CR |
143 | struct acpi_nhlt_wave_formatext *wav; |
144 | struct acpi_nhlt_format_config *fmt; | |
82b8acc0 CR |
145 | |
146 | for_each_nhlt_endpoint_fmtcfg(ep, fmt) { | |
147 | wav = &fmt->format; | |
148 | ||
149 | if (wav->valid_bits_per_sample == vbps && | |
150 | wav->samples_per_sec == rate && | |
151 | wav->bits_per_sample == bps && | |
152 | wav->channel_count == ch) | |
153 | return fmt; | |
154 | } | |
155 | ||
156 | return NULL; | |
157 | } | |
158 | EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_find_fmtcfg); | |
159 | ||
160 | /** | |
161 | * acpi_nhlt_tb_find_fmtcfg - Search a NHLT table for a specific format. | |
162 | * @tb: the table to search. | |
163 | * @link_type: the hardware link type, e.g.: PDM or SSP. | |
164 | * @dev_type: the device type. | |
165 | * @dir: stream direction. | |
166 | * @bus_id: the ID of virtual bus hosting the endpoint. | |
167 | * | |
168 | * @ch: number of channels. | |
169 | * @rate: samples per second. | |
170 | * @vbps: valid bits per sample. | |
171 | * @bps: bits per sample. | |
172 | * | |
173 | * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative | |
174 | * value to ignore the parameter during the search. | |
175 | * | |
176 | * Return: A pointer to format matching the criteria, %NULL if not found or | |
177 | * an ERR_PTR() otherwise. | |
178 | */ | |
a640acab CR |
179 | struct acpi_nhlt_format_config * |
180 | acpi_nhlt_tb_find_fmtcfg(const struct acpi_table_nhlt *tb, | |
82b8acc0 CR |
181 | int link_type, int dev_type, int dir, int bus_id, |
182 | u16 ch, u32 rate, u16 vbps, u16 bps) | |
183 | { | |
a640acab CR |
184 | struct acpi_nhlt_format_config *fmt; |
185 | struct acpi_nhlt_endpoint *ep; | |
82b8acc0 CR |
186 | |
187 | for_each_nhlt_endpoint(tb, ep) { | |
188 | if (!acpi_nhlt_endpoint_match(ep, link_type, dev_type, dir, bus_id)) | |
189 | continue; | |
190 | ||
191 | fmt = acpi_nhlt_endpoint_find_fmtcfg(ep, ch, rate, vbps, bps); | |
192 | if (fmt) | |
193 | return fmt; | |
194 | } | |
195 | ||
196 | return NULL; | |
197 | } | |
198 | EXPORT_SYMBOL_GPL(acpi_nhlt_tb_find_fmtcfg); | |
199 | ||
200 | /** | |
201 | * acpi_nhlt_find_fmtcfg - Search all NHLT tables for a specific format. | |
202 | * @link_type: the hardware link type, e.g.: PDM or SSP. | |
203 | * @dev_type: the device type. | |
204 | * @dir: stream direction. | |
205 | * @bus_id: the ID of virtual bus hosting the endpoint. | |
206 | * | |
207 | * @ch: number of channels. | |
208 | * @rate: samples per second. | |
209 | * @vbps: valid bits per sample. | |
210 | * @bps: bits per sample. | |
211 | * | |
212 | * Either of @link_type, @dev_type, @dir or @bus_id may be set to a negative | |
213 | * value to ignore the parameter during the search. | |
214 | * | |
215 | * Return: A pointer to format matching the criteria, %NULL if not found or | |
216 | * an ERR_PTR() otherwise. | |
217 | */ | |
a640acab | 218 | struct acpi_nhlt_format_config * |
82b8acc0 CR |
219 | acpi_nhlt_find_fmtcfg(int link_type, int dev_type, int dir, int bus_id, |
220 | u16 ch, u32 rate, u16 vbps, u16 bps) | |
221 | { | |
222 | /* TODO: Currently limited to table of index 0. */ | |
223 | return acpi_nhlt_tb_find_fmtcfg(acpi_gbl_nhlt, link_type, dev_type, dir, bus_id, | |
224 | ch, rate, vbps, bps); | |
225 | } | |
226 | EXPORT_SYMBOL_GPL(acpi_nhlt_find_fmtcfg); | |
227 | ||
228 | static bool acpi_nhlt_config_is_micdevice(struct acpi_nhlt_config *cfg) | |
229 | { | |
230 | return cfg->capabilities_size >= sizeof(struct acpi_nhlt_micdevice_config); | |
231 | } | |
232 | ||
233 | static bool acpi_nhlt_config_is_vendor_micdevice(struct acpi_nhlt_config *cfg) | |
234 | { | |
235 | struct acpi_nhlt_vendor_micdevice_config *devcfg = __acpi_nhlt_config_caps(cfg); | |
236 | ||
237 | return cfg->capabilities_size >= sizeof(*devcfg) && | |
238 | cfg->capabilities_size == struct_size(devcfg, mics, devcfg->mics_count); | |
239 | } | |
240 | ||
241 | /** | |
242 | * acpi_nhlt_endpoint_mic_count - Retrieve number of digital microphones for a PDM endpoint. | |
243 | * @ep: the endpoint to return microphones count for. | |
244 | * | |
245 | * Return: A number of microphones or an error code if an invalid endpoint is provided. | |
246 | */ | |
a640acab | 247 | int acpi_nhlt_endpoint_mic_count(const struct acpi_nhlt_endpoint *ep) |
82b8acc0 CR |
248 | { |
249 | union acpi_nhlt_device_config *devcfg; | |
a640acab | 250 | struct acpi_nhlt_format_config *fmt; |
82b8acc0 CR |
251 | struct acpi_nhlt_config *cfg; |
252 | u16 max_ch = 0; | |
253 | ||
254 | if (!ep || ep->link_type != ACPI_NHLT_LINKTYPE_PDM) | |
255 | return -EINVAL; | |
256 | ||
257 | /* Find max number of channels based on formats configuration. */ | |
258 | for_each_nhlt_endpoint_fmtcfg(ep, fmt) | |
259 | max_ch = max(fmt->format.channel_count, max_ch); | |
260 | ||
261 | cfg = __acpi_nhlt_endpoint_config(ep); | |
262 | devcfg = __acpi_nhlt_config_caps(cfg); | |
263 | ||
264 | /* If @ep is not a mic array, fallback to channels count. */ | |
265 | if (!acpi_nhlt_config_is_micdevice(cfg) || | |
266 | devcfg->gen.config_type != ACPI_NHLT_CONFIGTYPE_MICARRAY) | |
267 | return max_ch; | |
268 | ||
269 | switch (devcfg->mic.array_type) { | |
270 | case ACPI_NHLT_ARRAYTYPE_LINEAR2_SMALL: | |
271 | case ACPI_NHLT_ARRAYTYPE_LINEAR2_BIG: | |
272 | return 2; | |
273 | ||
274 | case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO1: | |
275 | case ACPI_NHLT_ARRAYTYPE_PLANAR4_LSHAPED: | |
276 | case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO2: | |
277 | return 4; | |
278 | ||
279 | case ACPI_NHLT_ARRAYTYPE_VENDOR: | |
280 | if (!acpi_nhlt_config_is_vendor_micdevice(cfg)) | |
281 | return -EINVAL; | |
282 | return devcfg->vendor_mic.mics_count; | |
283 | ||
284 | default: | |
285 | pr_warn("undefined mic array type: %#x\n", devcfg->mic.array_type); | |
286 | return max_ch; | |
287 | } | |
288 | } | |
289 | EXPORT_SYMBOL_GPL(acpi_nhlt_endpoint_mic_count); |