1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* |
3 | * Copyright (C) 2005-2014, 2020-2021 Intel Corporation |
4 | * Copyright (C) 2016 Intel Deutschland GmbH |
5 | */ |
6 | #include <linux/slab.h> |
7 | #include <linux/string.h> |
8 | #include <linux/export.h> |
9 | |
10 | #include "iwl-drv.h" |
11 | #include "iwl-phy-db.h" |
12 | #include "iwl-debug.h" |
13 | #include "iwl-op-mode.h" |
14 | #include "iwl-trans.h" |
15 | |
16 | struct iwl_phy_db_entry { |
17 | u16 size; |
18 | u8 *data; |
19 | }; |
20 | |
21 | /** |
22 | * struct iwl_phy_db - stores phy configuration and calibration data. |
23 | * |
24 | * @cfg: phy configuration. |
25 | * @calib_nch: non channel specific calibration data. |
26 | * @n_group_papd: number of entries in papd channel group. |
27 | * @calib_ch_group_papd: calibration data related to papd channel group. |
28 | * @n_group_txp: number of entries in tx power channel group. |
29 | * @calib_ch_group_txp: calibration data related to tx power chanel group. |
30 | * @trans: transport layer |
31 | */ |
32 | struct iwl_phy_db { |
33 | struct iwl_phy_db_entry cfg; |
34 | struct iwl_phy_db_entry calib_nch; |
35 | int n_group_papd; |
36 | struct iwl_phy_db_entry *calib_ch_group_papd; |
37 | int n_group_txp; |
38 | struct iwl_phy_db_entry *calib_ch_group_txp; |
39 | |
40 | struct iwl_trans *trans; |
41 | }; |
42 | |
43 | enum iwl_phy_db_section_type { |
44 | IWL_PHY_DB_CFG = 1, |
45 | IWL_PHY_DB_CALIB_NCH, |
46 | IWL_PHY_DB_UNUSED, |
47 | IWL_PHY_DB_CALIB_CHG_PAPD, |
48 | IWL_PHY_DB_CALIB_CHG_TXP, |
49 | IWL_PHY_DB_MAX |
50 | }; |
51 | |
52 | #define PHY_DB_CMD 0x6c |
53 | |
54 | /* for parsing of tx power channel group data that comes from the firmware*/ |
55 | struct iwl_phy_db_chg_txp { |
56 | __le32 space; |
57 | __le16 max_channel_idx; |
58 | } __packed; |
59 | |
60 | struct iwl_phy_db *iwl_phy_db_init(struct iwl_trans *trans) |
61 | { |
62 | struct iwl_phy_db *phy_db = kzalloc(size: sizeof(struct iwl_phy_db), |
63 | GFP_KERNEL); |
64 | |
65 | if (!phy_db) |
66 | return phy_db; |
67 | |
68 | phy_db->trans = trans; |
69 | |
70 | phy_db->n_group_txp = -1; |
71 | phy_db->n_group_papd = -1; |
72 | |
73 | /* TODO: add default values of the phy db. */ |
74 | return phy_db; |
75 | } |
76 | IWL_EXPORT_SYMBOL(iwl_phy_db_init); |
77 | |
78 | /* |
79 | * get phy db section: returns a pointer to a phy db section specified by |
80 | * type and channel group id. |
81 | */ |
82 | static struct iwl_phy_db_entry * |
83 | iwl_phy_db_get_section(struct iwl_phy_db *phy_db, |
84 | enum iwl_phy_db_section_type type, |
85 | u16 chg_id) |
86 | { |
87 | if (!phy_db || type >= IWL_PHY_DB_MAX) |
88 | return NULL; |
89 | |
90 | switch (type) { |
91 | case IWL_PHY_DB_CFG: |
92 | return &phy_db->cfg; |
93 | case IWL_PHY_DB_CALIB_NCH: |
94 | return &phy_db->calib_nch; |
95 | case IWL_PHY_DB_CALIB_CHG_PAPD: |
96 | if (chg_id >= phy_db->n_group_papd) |
97 | return NULL; |
98 | return &phy_db->calib_ch_group_papd[chg_id]; |
99 | case IWL_PHY_DB_CALIB_CHG_TXP: |
100 | if (chg_id >= phy_db->n_group_txp) |
101 | return NULL; |
102 | return &phy_db->calib_ch_group_txp[chg_id]; |
103 | default: |
104 | return NULL; |
105 | } |
106 | return NULL; |
107 | } |
108 | |
109 | static void iwl_phy_db_free_section(struct iwl_phy_db *phy_db, |
110 | enum iwl_phy_db_section_type type, |
111 | u16 chg_id) |
112 | { |
113 | struct iwl_phy_db_entry *entry = |
114 | iwl_phy_db_get_section(phy_db, type, chg_id); |
115 | if (!entry) |
116 | return; |
117 | |
118 | kfree(objp: entry->data); |
119 | entry->data = NULL; |
120 | entry->size = 0; |
121 | } |
122 | |
123 | void iwl_phy_db_free(struct iwl_phy_db *phy_db) |
124 | { |
125 | int i; |
126 | |
127 | if (!phy_db) |
128 | return; |
129 | |
130 | iwl_phy_db_free_section(phy_db, type: IWL_PHY_DB_CFG, chg_id: 0); |
131 | iwl_phy_db_free_section(phy_db, type: IWL_PHY_DB_CALIB_NCH, chg_id: 0); |
132 | |
133 | for (i = 0; i < phy_db->n_group_papd; i++) |
134 | iwl_phy_db_free_section(phy_db, type: IWL_PHY_DB_CALIB_CHG_PAPD, chg_id: i); |
135 | kfree(objp: phy_db->calib_ch_group_papd); |
136 | |
137 | for (i = 0; i < phy_db->n_group_txp; i++) |
138 | iwl_phy_db_free_section(phy_db, type: IWL_PHY_DB_CALIB_CHG_TXP, chg_id: i); |
139 | kfree(objp: phy_db->calib_ch_group_txp); |
140 | |
141 | kfree(objp: phy_db); |
142 | } |
143 | IWL_EXPORT_SYMBOL(iwl_phy_db_free); |
144 | |
145 | int iwl_phy_db_set_section(struct iwl_phy_db *phy_db, |
146 | struct iwl_rx_packet *pkt) |
147 | { |
148 | unsigned int pkt_len = iwl_rx_packet_payload_len(pkt); |
149 | struct iwl_calib_res_notif_phy_db *phy_db_notif = |
150 | (struct iwl_calib_res_notif_phy_db *)pkt->data; |
151 | enum iwl_phy_db_section_type type; |
152 | u16 size; |
153 | struct iwl_phy_db_entry *entry; |
154 | u16 chg_id = 0; |
155 | |
156 | if (pkt_len < sizeof(*phy_db_notif)) |
157 | return -EINVAL; |
158 | |
159 | type = le16_to_cpu(phy_db_notif->type); |
160 | size = le16_to_cpu(phy_db_notif->length); |
161 | |
162 | if (pkt_len < sizeof(*phy_db_notif) + size) |
163 | return -EINVAL; |
164 | |
165 | if (!phy_db) |
166 | return -EINVAL; |
167 | |
168 | if (type == IWL_PHY_DB_CALIB_CHG_PAPD) { |
169 | chg_id = le16_to_cpup(p: (__le16 *)phy_db_notif->data); |
170 | if (phy_db && !phy_db->calib_ch_group_papd) { |
171 | /* |
172 | * Firmware sends the largest index first, so we can use |
173 | * it to know how much we should allocate. |
174 | */ |
175 | phy_db->calib_ch_group_papd = kcalloc(n: chg_id + 1, |
176 | size: sizeof(struct iwl_phy_db_entry), |
177 | GFP_ATOMIC); |
178 | if (!phy_db->calib_ch_group_papd) |
179 | return -ENOMEM; |
180 | phy_db->n_group_papd = chg_id + 1; |
181 | } |
182 | } else if (type == IWL_PHY_DB_CALIB_CHG_TXP) { |
183 | chg_id = le16_to_cpup(p: (__le16 *)phy_db_notif->data); |
184 | if (phy_db && !phy_db->calib_ch_group_txp) { |
185 | /* |
186 | * Firmware sends the largest index first, so we can use |
187 | * it to know how much we should allocate. |
188 | */ |
189 | phy_db->calib_ch_group_txp = kcalloc(n: chg_id + 1, |
190 | size: sizeof(struct iwl_phy_db_entry), |
191 | GFP_ATOMIC); |
192 | if (!phy_db->calib_ch_group_txp) |
193 | return -ENOMEM; |
194 | phy_db->n_group_txp = chg_id + 1; |
195 | } |
196 | } |
197 | |
198 | entry = iwl_phy_db_get_section(phy_db, type, chg_id); |
199 | if (!entry) |
200 | return -EINVAL; |
201 | |
202 | kfree(objp: entry->data); |
203 | entry->data = kmemdup(p: phy_db_notif->data, size, GFP_ATOMIC); |
204 | if (!entry->data) { |
205 | entry->size = 0; |
206 | return -ENOMEM; |
207 | } |
208 | |
209 | entry->size = size; |
210 | |
211 | IWL_DEBUG_INFO(phy_db->trans, |
212 | "%s(%d): [PHYDB]SET: Type %d , Size: %d\n" , |
213 | __func__, __LINE__, type, size); |
214 | |
215 | return 0; |
216 | } |
217 | IWL_EXPORT_SYMBOL(iwl_phy_db_set_section); |
218 | |
219 | static int is_valid_channel(u16 ch_id) |
220 | { |
221 | if (ch_id <= 14 || |
222 | (36 <= ch_id && ch_id <= 64 && ch_id % 4 == 0) || |
223 | (100 <= ch_id && ch_id <= 140 && ch_id % 4 == 0) || |
224 | (145 <= ch_id && ch_id <= 165 && ch_id % 4 == 1)) |
225 | return 1; |
226 | return 0; |
227 | } |
228 | |
229 | static u8 ch_id_to_ch_index(u16 ch_id) |
230 | { |
231 | if (WARN_ON(!is_valid_channel(ch_id))) |
232 | return 0xff; |
233 | |
234 | if (ch_id <= 14) |
235 | return ch_id - 1; |
236 | if (ch_id <= 64) |
237 | return (ch_id + 20) / 4; |
238 | if (ch_id <= 140) |
239 | return (ch_id - 12) / 4; |
240 | return (ch_id - 13) / 4; |
241 | } |
242 | |
243 | |
244 | static u16 channel_id_to_papd(u16 ch_id) |
245 | { |
246 | if (WARN_ON(!is_valid_channel(ch_id))) |
247 | return 0xff; |
248 | |
249 | if (1 <= ch_id && ch_id <= 14) |
250 | return 0; |
251 | if (36 <= ch_id && ch_id <= 64) |
252 | return 1; |
253 | if (100 <= ch_id && ch_id <= 140) |
254 | return 2; |
255 | return 3; |
256 | } |
257 | |
258 | static u16 channel_id_to_txp(struct iwl_phy_db *phy_db, u16 ch_id) |
259 | { |
260 | struct iwl_phy_db_chg_txp *txp_chg; |
261 | int i; |
262 | u8 ch_index = ch_id_to_ch_index(ch_id); |
263 | if (ch_index == 0xff) |
264 | return 0xff; |
265 | |
266 | for (i = 0; i < phy_db->n_group_txp; i++) { |
267 | txp_chg = (void *)phy_db->calib_ch_group_txp[i].data; |
268 | if (!txp_chg) |
269 | return 0xff; |
270 | /* |
271 | * Looking for the first channel group that its max channel is |
272 | * higher then wanted channel. |
273 | */ |
274 | if (le16_to_cpu(txp_chg->max_channel_idx) >= ch_index) |
275 | return i; |
276 | } |
277 | return 0xff; |
278 | } |
279 | static |
280 | int iwl_phy_db_get_section_data(struct iwl_phy_db *phy_db, |
281 | u32 type, u8 **data, u16 *size, u16 ch_id) |
282 | { |
283 | struct iwl_phy_db_entry *entry; |
284 | u16 ch_group_id = 0; |
285 | |
286 | if (!phy_db) |
287 | return -EINVAL; |
288 | |
289 | /* find wanted channel group */ |
290 | if (type == IWL_PHY_DB_CALIB_CHG_PAPD) |
291 | ch_group_id = channel_id_to_papd(ch_id); |
292 | else if (type == IWL_PHY_DB_CALIB_CHG_TXP) |
293 | ch_group_id = channel_id_to_txp(phy_db, ch_id); |
294 | |
295 | entry = iwl_phy_db_get_section(phy_db, type, chg_id: ch_group_id); |
296 | if (!entry) |
297 | return -EINVAL; |
298 | |
299 | *data = entry->data; |
300 | *size = entry->size; |
301 | |
302 | IWL_DEBUG_INFO(phy_db->trans, |
303 | "%s(%d): [PHYDB] GET: Type %d , Size: %d\n" , |
304 | __func__, __LINE__, type, *size); |
305 | |
306 | return 0; |
307 | } |
308 | |
309 | static int iwl_send_phy_db_cmd(struct iwl_phy_db *phy_db, u16 type, |
310 | u16 length, void *data) |
311 | { |
312 | struct iwl_phy_db_cmd phy_db_cmd; |
313 | struct iwl_host_cmd cmd = { |
314 | .id = PHY_DB_CMD, |
315 | }; |
316 | |
317 | IWL_DEBUG_INFO(phy_db->trans, |
318 | "Sending PHY-DB hcmd of type %d, of length %d\n" , |
319 | type, length); |
320 | |
321 | /* Set phy db cmd variables */ |
322 | phy_db_cmd.type = cpu_to_le16(type); |
323 | phy_db_cmd.length = cpu_to_le16(length); |
324 | |
325 | /* Set hcmd variables */ |
326 | cmd.data[0] = &phy_db_cmd; |
327 | cmd.len[0] = sizeof(struct iwl_phy_db_cmd); |
328 | cmd.data[1] = data; |
329 | cmd.len[1] = length; |
330 | cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY; |
331 | |
332 | return iwl_trans_send_cmd(trans: phy_db->trans, cmd: &cmd); |
333 | } |
334 | |
335 | static int iwl_phy_db_send_all_channel_groups( |
336 | struct iwl_phy_db *phy_db, |
337 | enum iwl_phy_db_section_type type, |
338 | u8 max_ch_groups) |
339 | { |
340 | u16 i; |
341 | int err; |
342 | struct iwl_phy_db_entry *entry; |
343 | |
344 | /* Send all the channel specific groups to operational fw */ |
345 | for (i = 0; i < max_ch_groups; i++) { |
346 | entry = iwl_phy_db_get_section(phy_db, |
347 | type, |
348 | chg_id: i); |
349 | if (!entry) |
350 | return -EINVAL; |
351 | |
352 | if (!entry->size) |
353 | continue; |
354 | |
355 | /* Send the requested PHY DB section */ |
356 | err = iwl_send_phy_db_cmd(phy_db, |
357 | type, |
358 | length: entry->size, |
359 | data: entry->data); |
360 | if (err) { |
361 | IWL_ERR(phy_db->trans, |
362 | "Can't SEND phy_db section %d (%d), err %d\n" , |
363 | type, i, err); |
364 | return err; |
365 | } |
366 | |
367 | IWL_DEBUG_INFO(phy_db->trans, |
368 | "Sent PHY_DB HCMD, type = %d num = %d\n" , |
369 | type, i); |
370 | } |
371 | |
372 | return 0; |
373 | } |
374 | |
375 | int iwl_send_phy_db_data(struct iwl_phy_db *phy_db) |
376 | { |
377 | u8 *data = NULL; |
378 | u16 size = 0; |
379 | int err; |
380 | |
381 | IWL_DEBUG_INFO(phy_db->trans, |
382 | "Sending phy db data and configuration to runtime image\n" ); |
383 | |
384 | /* Send PHY DB CFG section */ |
385 | err = iwl_phy_db_get_section_data(phy_db, type: IWL_PHY_DB_CFG, |
386 | data: &data, size: &size, ch_id: 0); |
387 | if (err) { |
388 | IWL_ERR(phy_db->trans, "Cannot get Phy DB cfg section\n" ); |
389 | return err; |
390 | } |
391 | |
392 | err = iwl_send_phy_db_cmd(phy_db, type: IWL_PHY_DB_CFG, length: size, data); |
393 | if (err) { |
394 | IWL_ERR(phy_db->trans, |
395 | "Cannot send HCMD of Phy DB cfg section\n" ); |
396 | return err; |
397 | } |
398 | |
399 | err = iwl_phy_db_get_section_data(phy_db, type: IWL_PHY_DB_CALIB_NCH, |
400 | data: &data, size: &size, ch_id: 0); |
401 | if (err) { |
402 | IWL_ERR(phy_db->trans, |
403 | "Cannot get Phy DB non specific channel section\n" ); |
404 | return err; |
405 | } |
406 | |
407 | err = iwl_send_phy_db_cmd(phy_db, type: IWL_PHY_DB_CALIB_NCH, length: size, data); |
408 | if (err) { |
409 | IWL_ERR(phy_db->trans, |
410 | "Cannot send HCMD of Phy DB non specific channel section\n" ); |
411 | return err; |
412 | } |
413 | |
414 | /* Send all the TXP channel specific data */ |
415 | err = iwl_phy_db_send_all_channel_groups(phy_db, |
416 | type: IWL_PHY_DB_CALIB_CHG_PAPD, |
417 | max_ch_groups: phy_db->n_group_papd); |
418 | if (err) { |
419 | IWL_ERR(phy_db->trans, |
420 | "Cannot send channel specific PAPD groups\n" ); |
421 | return err; |
422 | } |
423 | |
424 | /* Send all the TXP channel specific data */ |
425 | err = iwl_phy_db_send_all_channel_groups(phy_db, |
426 | type: IWL_PHY_DB_CALIB_CHG_TXP, |
427 | max_ch_groups: phy_db->n_group_txp); |
428 | if (err) { |
429 | IWL_ERR(phy_db->trans, |
430 | "Cannot send channel specific TX power groups\n" ); |
431 | return err; |
432 | } |
433 | |
434 | IWL_DEBUG_INFO(phy_db->trans, |
435 | "Finished sending phy db non channel data\n" ); |
436 | return 0; |
437 | } |
438 | IWL_EXPORT_SYMBOL(iwl_send_phy_db_data); |
439 | |