1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 /* 3 * Copyright (C) 2017 Intel Deutschland GmbH 4 * Copyright (C) 2019-2020 Intel Corporation 5 */ 6 #include <linux/uuid.h> 7 #include "iwl-drv.h" 8 #include "iwl-debug.h" 9 #include "acpi.h" 10 #include "fw/runtime.h" 11 12 static const guid_t intel_wifi_guid = GUID_INIT(0xF21202BF, 0x8F78, 0x4DC6, 13 0xA5, 0xB3, 0x1F, 0x73, 14 0x8E, 0x28, 0x5A, 0xDE); 15 16 static int iwl_acpi_get_handle(struct device *dev, acpi_string method, 17 acpi_handle *ret_handle) 18 { 19 acpi_handle root_handle; 20 acpi_status status; 21 22 root_handle = ACPI_HANDLE(dev); 23 if (!root_handle) { 24 IWL_DEBUG_DEV_RADIO(dev, 25 "ACPI: Could not retrieve root port handle\n"); 26 return -ENOENT; 27 } 28 29 status = acpi_get_handle(root_handle, method, ret_handle); 30 if (ACPI_FAILURE(status)) { 31 IWL_DEBUG_DEV_RADIO(dev, 32 "ACPI: %s method not found\n", method); 33 return -ENOENT; 34 } 35 return 0; 36 } 37 38 void *iwl_acpi_get_object(struct device *dev, acpi_string method) 39 { 40 struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; 41 acpi_handle handle; 42 acpi_status status; 43 int ret; 44 45 ret = iwl_acpi_get_handle(dev, method, &handle); 46 if (ret) 47 return ERR_PTR(-ENOENT); 48 49 /* Call the method with no arguments */ 50 status = acpi_evaluate_object(handle, NULL, NULL, &buf); 51 if (ACPI_FAILURE(status)) { 52 IWL_DEBUG_DEV_RADIO(dev, 53 "ACPI: %s method invocation failed (status: 0x%x)\n", 54 method, status); 55 return ERR_PTR(-ENOENT); 56 } 57 return buf.pointer; 58 } 59 IWL_EXPORT_SYMBOL(iwl_acpi_get_object); 60 61 /* 62 * Generic function for evaluating a method defined in the device specific 63 * method (DSM) interface. The returned acpi object must be freed by calling 64 * function. 65 */ 66 static void *iwl_acpi_get_dsm_object(struct device *dev, int rev, int func, 67 union acpi_object *args) 68 { 69 union acpi_object *obj; 70 71 obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), &intel_wifi_guid, rev, func, 72 args); 73 if (!obj) { 74 IWL_DEBUG_DEV_RADIO(dev, 75 "ACPI: DSM method invocation failed (rev: %d, func:%d)\n", 76 rev, func); 77 return ERR_PTR(-ENOENT); 78 } 79 return obj; 80 } 81 82 /* 83 * Generic function to evaluate a DSM with no arguments 84 * and an integer return value, 85 * (as an integer object or inside a buffer object), 86 * verify and assign the value in the "value" parameter. 87 * return 0 in success and the appropriate errno otherwise. 88 */ 89 static int iwl_acpi_get_dsm_integer(struct device *dev, int rev, int func, 90 u64 *value, size_t expected_size) 91 { 92 union acpi_object *obj; 93 int ret = 0; 94 95 obj = iwl_acpi_get_dsm_object(dev, rev, func, NULL); 96 if (IS_ERR(obj)) { 97 IWL_DEBUG_DEV_RADIO(dev, 98 "Failed to get DSM object. func= %d\n", 99 func); 100 return -ENOENT; 101 } 102 103 if (obj->type == ACPI_TYPE_INTEGER) { 104 *value = obj->integer.value; 105 } else if (obj->type == ACPI_TYPE_BUFFER) { 106 __le64 le_value = 0; 107 108 if (WARN_ON_ONCE(expected_size > sizeof(le_value))) 109 return -EINVAL; 110 111 /* if the buffer size doesn't match the expected size */ 112 if (obj->buffer.length != expected_size) 113 IWL_DEBUG_DEV_RADIO(dev, 114 "ACPI: DSM invalid buffer size, padding or truncating (%d)\n", 115 obj->buffer.length); 116 117 /* assuming LE from Intel BIOS spec */ 118 memcpy(&le_value, obj->buffer.pointer, 119 min_t(size_t, expected_size, (size_t)obj->buffer.length)); 120 *value = le64_to_cpu(le_value); 121 } else { 122 IWL_DEBUG_DEV_RADIO(dev, 123 "ACPI: DSM method did not return a valid object, type=%d\n", 124 obj->type); 125 ret = -EINVAL; 126 goto out; 127 } 128 129 IWL_DEBUG_DEV_RADIO(dev, 130 "ACPI: DSM method evaluated: func=%d, ret=%d\n", 131 func, ret); 132 out: 133 ACPI_FREE(obj); 134 return ret; 135 } 136 137 /* 138 * Evaluate a DSM with no arguments and a u8 return value, 139 */ 140 int iwl_acpi_get_dsm_u8(struct device *dev, int rev, int func, u8 *value) 141 { 142 int ret; 143 u64 val; 144 145 ret = iwl_acpi_get_dsm_integer(dev, rev, func, &val, sizeof(u8)); 146 147 if (ret < 0) 148 return ret; 149 150 /* cast val (u64) to be u8 */ 151 *value = (u8)val; 152 return 0; 153 } 154 IWL_EXPORT_SYMBOL(iwl_acpi_get_dsm_u8); 155 156 union acpi_object *iwl_acpi_get_wifi_pkg(struct device *dev, 157 union acpi_object *data, 158 int data_size, int *tbl_rev) 159 { 160 int i; 161 union acpi_object *wifi_pkg; 162 163 /* 164 * We need at least one entry in the wifi package that 165 * describes the domain, and one more entry, otherwise there's 166 * no point in reading it. 167 */ 168 if (WARN_ON_ONCE(data_size < 2)) 169 return ERR_PTR(-EINVAL); 170 171 /* 172 * We need at least two packages, one for the revision and one 173 * for the data itself. Also check that the revision is valid 174 * (i.e. it is an integer smaller than 2, as we currently support only 175 * 2 revisions). 176 */ 177 if (data->type != ACPI_TYPE_PACKAGE || 178 data->package.count < 2 || 179 data->package.elements[0].type != ACPI_TYPE_INTEGER || 180 data->package.elements[0].integer.value > 1) { 181 IWL_DEBUG_DEV_RADIO(dev, "Unsupported packages structure\n"); 182 return ERR_PTR(-EINVAL); 183 } 184 185 *tbl_rev = data->package.elements[0].integer.value; 186 187 /* loop through all the packages to find the one for WiFi */ 188 for (i = 1; i < data->package.count; i++) { 189 union acpi_object *domain; 190 191 wifi_pkg = &data->package.elements[i]; 192 193 /* skip entries that are not a package with the right size */ 194 if (wifi_pkg->type != ACPI_TYPE_PACKAGE || 195 wifi_pkg->package.count != data_size) 196 continue; 197 198 domain = &wifi_pkg->package.elements[0]; 199 if (domain->type == ACPI_TYPE_INTEGER && 200 domain->integer.value == ACPI_WIFI_DOMAIN) 201 goto found; 202 } 203 204 return ERR_PTR(-ENOENT); 205 206 found: 207 return wifi_pkg; 208 } 209 IWL_EXPORT_SYMBOL(iwl_acpi_get_wifi_pkg); 210 211 int iwl_acpi_get_tas(struct iwl_fw_runtime *fwrt, 212 __le32 *block_list_array, 213 int *block_list_size) 214 { 215 union acpi_object *wifi_pkg, *data; 216 int ret, tbl_rev, i; 217 bool enabled; 218 219 data = iwl_acpi_get_object(fwrt->dev, ACPI_WTAS_METHOD); 220 if (IS_ERR(data)) 221 return PTR_ERR(data); 222 223 wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, 224 ACPI_WTAS_WIFI_DATA_SIZE, 225 &tbl_rev); 226 if (IS_ERR(wifi_pkg)) { 227 ret = PTR_ERR(wifi_pkg); 228 goto out_free; 229 } 230 231 if (wifi_pkg->package.elements[0].type != ACPI_TYPE_INTEGER || 232 tbl_rev != 0) { 233 ret = -EINVAL; 234 goto out_free; 235 } 236 237 enabled = !!wifi_pkg->package.elements[0].integer.value; 238 239 if (!enabled) { 240 *block_list_size = -1; 241 IWL_DEBUG_RADIO(fwrt, "TAS not enabled\n"); 242 ret = 0; 243 goto out_free; 244 } 245 246 if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || 247 wifi_pkg->package.elements[1].integer.value > 248 APCI_WTAS_BLACK_LIST_MAX) { 249 IWL_DEBUG_RADIO(fwrt, "TAS invalid array size %llu\n", 250 wifi_pkg->package.elements[1].integer.value); 251 ret = -EINVAL; 252 goto out_free; 253 } 254 *block_list_size = wifi_pkg->package.elements[1].integer.value; 255 256 IWL_DEBUG_RADIO(fwrt, "TAS array size %d\n", *block_list_size); 257 if (*block_list_size > APCI_WTAS_BLACK_LIST_MAX) { 258 IWL_DEBUG_RADIO(fwrt, "TAS invalid array size value %u\n", 259 *block_list_size); 260 ret = -EINVAL; 261 goto out_free; 262 } 263 264 for (i = 0; i < *block_list_size; i++) { 265 u32 country; 266 267 if (wifi_pkg->package.elements[2 + i].type != 268 ACPI_TYPE_INTEGER) { 269 IWL_DEBUG_RADIO(fwrt, 270 "TAS invalid array elem %d\n", 2 + i); 271 ret = -EINVAL; 272 goto out_free; 273 } 274 275 country = wifi_pkg->package.elements[2 + i].integer.value; 276 block_list_array[i] = cpu_to_le32(country); 277 IWL_DEBUG_RADIO(fwrt, "TAS block list country %d\n", country); 278 } 279 280 ret = 0; 281 out_free: 282 kfree(data); 283 return ret; 284 } 285 IWL_EXPORT_SYMBOL(iwl_acpi_get_tas); 286 287 int iwl_acpi_get_mcc(struct device *dev, char *mcc) 288 { 289 union acpi_object *wifi_pkg, *data; 290 u32 mcc_val; 291 int ret, tbl_rev; 292 293 data = iwl_acpi_get_object(dev, ACPI_WRDD_METHOD); 294 if (IS_ERR(data)) 295 return PTR_ERR(data); 296 297 wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, ACPI_WRDD_WIFI_DATA_SIZE, 298 &tbl_rev); 299 if (IS_ERR(wifi_pkg)) { 300 ret = PTR_ERR(wifi_pkg); 301 goto out_free; 302 } 303 304 if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || 305 tbl_rev != 0) { 306 ret = -EINVAL; 307 goto out_free; 308 } 309 310 mcc_val = wifi_pkg->package.elements[1].integer.value; 311 312 mcc[0] = (mcc_val >> 8) & 0xff; 313 mcc[1] = mcc_val & 0xff; 314 mcc[2] = '\0'; 315 316 ret = 0; 317 out_free: 318 kfree(data); 319 return ret; 320 } 321 IWL_EXPORT_SYMBOL(iwl_acpi_get_mcc); 322 323 u64 iwl_acpi_get_pwr_limit(struct device *dev) 324 { 325 union acpi_object *data, *wifi_pkg; 326 u64 dflt_pwr_limit; 327 int tbl_rev; 328 329 data = iwl_acpi_get_object(dev, ACPI_SPLC_METHOD); 330 if (IS_ERR(data)) { 331 dflt_pwr_limit = 0; 332 goto out; 333 } 334 335 wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, 336 ACPI_SPLC_WIFI_DATA_SIZE, &tbl_rev); 337 if (IS_ERR(wifi_pkg) || tbl_rev != 0 || 338 wifi_pkg->package.elements[1].integer.value != ACPI_TYPE_INTEGER) { 339 dflt_pwr_limit = 0; 340 goto out_free; 341 } 342 343 dflt_pwr_limit = wifi_pkg->package.elements[1].integer.value; 344 out_free: 345 kfree(data); 346 out: 347 return dflt_pwr_limit; 348 } 349 IWL_EXPORT_SYMBOL(iwl_acpi_get_pwr_limit); 350 351 int iwl_acpi_get_eckv(struct device *dev, u32 *extl_clk) 352 { 353 union acpi_object *wifi_pkg, *data; 354 int ret, tbl_rev; 355 356 data = iwl_acpi_get_object(dev, ACPI_ECKV_METHOD); 357 if (IS_ERR(data)) 358 return PTR_ERR(data); 359 360 wifi_pkg = iwl_acpi_get_wifi_pkg(dev, data, ACPI_ECKV_WIFI_DATA_SIZE, 361 &tbl_rev); 362 if (IS_ERR(wifi_pkg)) { 363 ret = PTR_ERR(wifi_pkg); 364 goto out_free; 365 } 366 367 if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || 368 tbl_rev != 0) { 369 ret = -EINVAL; 370 goto out_free; 371 } 372 373 *extl_clk = wifi_pkg->package.elements[1].integer.value; 374 375 ret = 0; 376 377 out_free: 378 kfree(data); 379 return ret; 380 } 381 IWL_EXPORT_SYMBOL(iwl_acpi_get_eckv); 382 383 static int iwl_sar_set_profile(union acpi_object *table, 384 struct iwl_sar_profile *profile, 385 bool enabled) 386 { 387 int i; 388 389 profile->enabled = enabled; 390 391 for (i = 0; i < ACPI_SAR_TABLE_SIZE; i++) { 392 if (table[i].type != ACPI_TYPE_INTEGER || 393 table[i].integer.value > U8_MAX) 394 return -EINVAL; 395 396 profile->table[i] = table[i].integer.value; 397 } 398 399 return 0; 400 } 401 402 static int iwl_sar_fill_table(struct iwl_fw_runtime *fwrt, 403 __le16 *per_chain, u32 n_subbands, 404 int prof_a, int prof_b) 405 { 406 int profs[ACPI_SAR_NUM_CHAIN_LIMITS] = { prof_a, prof_b }; 407 int i, j, idx; 408 409 for (i = 0; i < ACPI_SAR_NUM_CHAIN_LIMITS; i++) { 410 struct iwl_sar_profile *prof; 411 412 /* don't allow SAR to be disabled (profile 0 means disable) */ 413 if (profs[i] == 0) 414 return -EPERM; 415 416 /* we are off by one, so allow up to ACPI_SAR_PROFILE_NUM */ 417 if (profs[i] > ACPI_SAR_PROFILE_NUM) 418 return -EINVAL; 419 420 /* profiles go from 1 to 4, so decrement to access the array */ 421 prof = &fwrt->sar_profiles[profs[i] - 1]; 422 423 /* if the profile is disabled, do nothing */ 424 if (!prof->enabled) { 425 IWL_DEBUG_RADIO(fwrt, "SAR profile %d is disabled.\n", 426 profs[i]); 427 /* 428 * if one of the profiles is disabled, we 429 * ignore all of them and return 1 to 430 * differentiate disabled from other failures. 431 */ 432 return 1; 433 } 434 435 IWL_DEBUG_INFO(fwrt, 436 "SAR EWRD: chain %d profile index %d\n", 437 i, profs[i]); 438 IWL_DEBUG_RADIO(fwrt, " Chain[%d]:\n", i); 439 for (j = 0; j < n_subbands; j++) { 440 idx = i * ACPI_SAR_NUM_SUB_BANDS + j; 441 per_chain[i * n_subbands + j] = 442 cpu_to_le16(prof->table[idx]); 443 IWL_DEBUG_RADIO(fwrt, " Band[%d] = %d * .125dBm\n", 444 j, prof->table[idx]); 445 } 446 } 447 448 return 0; 449 } 450 451 int iwl_sar_select_profile(struct iwl_fw_runtime *fwrt, 452 __le16 *per_chain, u32 n_tables, u32 n_subbands, 453 int prof_a, int prof_b) 454 { 455 int i, ret = 0; 456 457 for (i = 0; i < n_tables; i++) { 458 ret = iwl_sar_fill_table(fwrt, 459 &per_chain[i * n_subbands * ACPI_SAR_NUM_CHAIN_LIMITS], 460 n_subbands, prof_a, prof_b); 461 if (ret) 462 break; 463 } 464 465 return ret; 466 } 467 IWL_EXPORT_SYMBOL(iwl_sar_select_profile); 468 469 int iwl_sar_get_wrds_table(struct iwl_fw_runtime *fwrt) 470 { 471 union acpi_object *wifi_pkg, *table, *data; 472 bool enabled; 473 int ret, tbl_rev; 474 475 data = iwl_acpi_get_object(fwrt->dev, ACPI_WRDS_METHOD); 476 if (IS_ERR(data)) 477 return PTR_ERR(data); 478 479 wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, 480 ACPI_WRDS_WIFI_DATA_SIZE, &tbl_rev); 481 if (IS_ERR(wifi_pkg) || tbl_rev != 0) { 482 ret = PTR_ERR(wifi_pkg); 483 goto out_free; 484 } 485 486 if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER) { 487 ret = -EINVAL; 488 goto out_free; 489 } 490 491 enabled = !!(wifi_pkg->package.elements[1].integer.value); 492 493 /* position of the actual table */ 494 table = &wifi_pkg->package.elements[2]; 495 496 /* The profile from WRDS is officially profile 1, but goes 497 * into sar_profiles[0] (because we don't have a profile 0). 498 */ 499 ret = iwl_sar_set_profile(table, &fwrt->sar_profiles[0], enabled); 500 out_free: 501 kfree(data); 502 return ret; 503 } 504 IWL_EXPORT_SYMBOL(iwl_sar_get_wrds_table); 505 506 int iwl_sar_get_ewrd_table(struct iwl_fw_runtime *fwrt) 507 { 508 union acpi_object *wifi_pkg, *data; 509 bool enabled; 510 int i, n_profiles, tbl_rev, pos; 511 int ret = 0; 512 513 data = iwl_acpi_get_object(fwrt->dev, ACPI_EWRD_METHOD); 514 if (IS_ERR(data)) 515 return PTR_ERR(data); 516 517 wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, 518 ACPI_EWRD_WIFI_DATA_SIZE, &tbl_rev); 519 if (IS_ERR(wifi_pkg) || tbl_rev != 0) { 520 ret = PTR_ERR(wifi_pkg); 521 goto out_free; 522 } 523 524 if (wifi_pkg->package.elements[1].type != ACPI_TYPE_INTEGER || 525 wifi_pkg->package.elements[2].type != ACPI_TYPE_INTEGER) { 526 ret = -EINVAL; 527 goto out_free; 528 } 529 530 enabled = !!(wifi_pkg->package.elements[1].integer.value); 531 n_profiles = wifi_pkg->package.elements[2].integer.value; 532 533 /* 534 * Check the validity of n_profiles. The EWRD profiles start 535 * from index 1, so the maximum value allowed here is 536 * ACPI_SAR_PROFILES_NUM - 1. 537 */ 538 if (n_profiles <= 0 || n_profiles >= ACPI_SAR_PROFILE_NUM) { 539 ret = -EINVAL; 540 goto out_free; 541 } 542 543 /* the tables start at element 3 */ 544 pos = 3; 545 546 for (i = 0; i < n_profiles; i++) { 547 /* The EWRD profiles officially go from 2 to 4, but we 548 * save them in sar_profiles[1-3] (because we don't 549 * have profile 0). So in the array we start from 1. 550 */ 551 ret = iwl_sar_set_profile(&wifi_pkg->package.elements[pos], 552 &fwrt->sar_profiles[i + 1], 553 enabled); 554 if (ret < 0) 555 break; 556 557 /* go to the next table */ 558 pos += ACPI_SAR_TABLE_SIZE; 559 } 560 561 out_free: 562 kfree(data); 563 return ret; 564 } 565 IWL_EXPORT_SYMBOL(iwl_sar_get_ewrd_table); 566 567 int iwl_sar_get_wgds_table(struct iwl_fw_runtime *fwrt) 568 { 569 union acpi_object *wifi_pkg, *data; 570 int i, j, ret, tbl_rev; 571 int idx = 1; 572 573 data = iwl_acpi_get_object(fwrt->dev, ACPI_WGDS_METHOD); 574 if (IS_ERR(data)) 575 return PTR_ERR(data); 576 577 wifi_pkg = iwl_acpi_get_wifi_pkg(fwrt->dev, data, 578 ACPI_WGDS_WIFI_DATA_SIZE, &tbl_rev); 579 if (IS_ERR(wifi_pkg) || tbl_rev > 1) { 580 ret = PTR_ERR(wifi_pkg); 581 goto out_free; 582 } 583 584 fwrt->geo_rev = tbl_rev; 585 for (i = 0; i < ACPI_NUM_GEO_PROFILES; i++) { 586 for (j = 0; j < ACPI_GEO_TABLE_SIZE; j++) { 587 union acpi_object *entry; 588 589 entry = &wifi_pkg->package.elements[idx++]; 590 if (entry->type != ACPI_TYPE_INTEGER || 591 entry->integer.value > U8_MAX) { 592 ret = -EINVAL; 593 goto out_free; 594 } 595 596 fwrt->geo_profiles[i].values[j] = entry->integer.value; 597 } 598 } 599 ret = 0; 600 out_free: 601 kfree(data); 602 return ret; 603 } 604 IWL_EXPORT_SYMBOL(iwl_sar_get_wgds_table); 605 606 bool iwl_sar_geo_support(struct iwl_fw_runtime *fwrt) 607 { 608 /* 609 * The GEO_TX_POWER_LIMIT command is not supported on earlier 610 * firmware versions. Unfortunately, we don't have a TLV API 611 * flag to rely on, so rely on the major version which is in 612 * the first byte of ucode_ver. This was implemented 613 * initially on version 38 and then backported to 17. It was 614 * also backported to 29, but only for 7265D devices. The 615 * intention was to have it in 36 as well, but not all 8000 616 * family got this feature enabled. The 8000 family is the 617 * only one using version 36, so skip this version entirely. 618 */ 619 return IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) >= 38 || 620 IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 17 || 621 (IWL_UCODE_SERIAL(fwrt->fw->ucode_ver) == 29 && 622 ((fwrt->trans->hw_rev & CSR_HW_REV_TYPE_MSK) == 623 CSR_HW_REV_TYPE_7265D)); 624 } 625 IWL_EXPORT_SYMBOL(iwl_sar_geo_support); 626 627 int iwl_sar_geo_init(struct iwl_fw_runtime *fwrt, 628 struct iwl_per_chain_offset *table, u32 n_bands) 629 { 630 int ret, i, j; 631 632 if (!iwl_sar_geo_support(fwrt)) 633 return -EOPNOTSUPP; 634 635 ret = iwl_sar_get_wgds_table(fwrt); 636 if (ret < 0) { 637 IWL_DEBUG_RADIO(fwrt, 638 "Geo SAR BIOS table invalid or unavailable. (%d)\n", 639 ret); 640 /* we don't fail if the table is not available */ 641 return -ENOENT; 642 } 643 644 for (i = 0; i < ACPI_NUM_GEO_PROFILES; i++) { 645 for (j = 0; j < n_bands; j++) { 646 struct iwl_per_chain_offset *chain = 647 &table[i * n_bands + j]; 648 u8 *value; 649 650 if (j * ACPI_GEO_PER_CHAIN_SIZE >= 651 ARRAY_SIZE(fwrt->geo_profiles[0].values)) 652 /* 653 * Currently we only store lb an hb values, and 654 * don't have any special ones for uhb. So leave 655 * those empty for the time being 656 */ 657 break; 658 659 value = &fwrt->geo_profiles[i].values[j * 660 ACPI_GEO_PER_CHAIN_SIZE]; 661 chain->max_tx_power = cpu_to_le16(value[0]); 662 chain->chain_a = value[1]; 663 chain->chain_b = value[2]; 664 IWL_DEBUG_RADIO(fwrt, 665 "SAR geographic profile[%d] Band[%d]: chain A = %d chain B = %d max_tx_power = %d\n", 666 i, j, value[1], value[2], value[0]); 667 } 668 } 669 670 return 0; 671 } 672 IWL_EXPORT_SYMBOL(iwl_sar_geo_init); 673