| /* |
| * Copyright 2014 Google Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * 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; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, |
| * MA 02111-1307 USA |
| */ |
| |
| #include <libpayload.h> |
| |
| #include "board.h" |
| |
| /* |
| * This file provides functions retrieving WiFi calibration data from CBMEM |
| * and placing it in the kernel device tree. |
| * |
| * Per interface calibration data is stored in a CBMEM entry as a header |
| * followed by a few adjacent blobs, as described by the following structures. |
| */ |
| |
| /* A single calibration data blob */ |
| struct calibration_blob { |
| uint32_t blob_size; /* Total size. rounded up to fall on a 4 byte |
| boundary. */ |
| uint32_t key_size; /* Size of the name of this entry, \0 included. */ |
| uint32_t value_size; /* Size of the value of this entry */ |
| /* Zero terminated name(key) goes here, immediately followed by value */ |
| }; |
| |
| /* |
| * This is the structure of the CBMEM entry containing WiFi calibration blobs. |
| * It starts with the total size (header size included) followed by an |
| * arbitrary number of concatenated 4 byte aligned calibration blobs. |
| */ |
| struct calibration_entry { |
| uint32_t size; |
| struct calibration_blob entries[0]; /* A varialble size container. */ |
| }; |
| |
| /* |
| * The keys' values match one of two templates followed by a single digit, the |
| * interface number. |
| */ |
| static const char * const templates[] = { |
| "wifi_base64_calibration", |
| "wifi_calibration" |
| }; |
| |
| /* |
| * The string below represents the location of the blobs in the device tree |
| * passed to the kernel. The %d is replaced by a number derived from the blob |
| * index. In case the calibration data is stored in base64 form (as defined by |
| * the key), the last node name needs to be extended by "-base64". |
| */ |
| static const char *dt_path = "soc/pci@%8.8x/pcie@0/ath10k@0,0"; |
| |
| static const char *base64_suffix = "-base64"; |
| |
| /* Mapping of interface numbers into the wifi device address on the PCI bus. */ |
| static const uint32_t if_to_address[] = { 0x1b500000, 0x1b700000, 0x1b900000 }; |
| |
| static int blob_is_valid(struct calibration_entry *cal_entry, |
| struct calibration_blob *cal_blob) |
| { |
| uintptr_t entry_base, blob_base; |
| uintptr_t blob_size; |
| |
| entry_base = (uintptr_t)cal_entry; |
| blob_base = (uintptr_t)cal_blob; |
| |
| if ((blob_base <= entry_base) || |
| (blob_base > (entry_base + cal_entry->size)) || |
| (blob_base & 3)) |
| return 0; |
| |
| blob_size = sizeof(struct calibration_blob) + |
| cal_blob->key_size + |
| cal_blob->value_size; |
| |
| if (cal_blob->blob_size < blob_size) |
| return 0; |
| |
| if ((blob_base + blob_size) > (entry_base + cal_entry->size)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void process_blob(DeviceTree *tree, |
| struct calibration_blob *cal_blob, |
| const char *templ, |
| int base64) |
| { |
| void *value; |
| char *key = (char *)(cal_blob + 1); |
| int templ_len = strlen(templ); |
| int if_index; |
| int string_size; |
| char path_str[100]; /* Should be enough for a longest path. */ |
| DeviceTreeNode *node; |
| |
| if (((templ_len + 2) != cal_blob->key_size) || |
| strncmp(key, templ, templ_len)) |
| return; /* Key does not match. */ |
| |
| if_index = key[templ_len] - '0'; |
| |
| if ((if_index < 0) || (if_index >= ARRAY_SIZE(if_to_address))) |
| return; |
| |
| string_size = snprintf(path_str, sizeof(path_str), |
| dt_path, if_to_address[if_index]); |
| if (string_size >= sizeof(path_str)) |
| return; |
| |
| if (base64) { |
| if ((string_size + strlen(base64_suffix)) >= |
| (sizeof(path_str) -1)) |
| return; |
| strcpy(path_str + string_size, base64_suffix); |
| } |
| |
| /* |
| * Binary nodes are supposed to be present in the device tree, the |
| * base64 nodes need to be created. |
| */ |
| node = dt_find_node_by_path(tree->root, path_str, NULL, NULL, base64); |
| if (!node) |
| return; |
| |
| /* The value data starts right above the key. */ |
| value = key + cal_blob->key_size; |
| dt_add_bin_prop(node, base64 ? |
| "qcom,ath10k-calibration-data-base64" : |
| "qcom,ath10k-calibration-data", |
| value, cal_blob->value_size); |
| } |
| |
| int set_wifi_calibration(DeviceTree *tree) |
| { |
| int i; |
| struct calibration_entry *cal_entry; |
| struct calibration_blob *cal_blob; |
| |
| cal_entry = lib_sysinfo.wifi_calibration; |
| |
| if (!cal_entry) |
| return 0; /* No calibration data was found. */ |
| |
| /* The first blob starts right above the header */ |
| cal_blob = cal_entry->entries; |
| |
| while(blob_is_valid(cal_entry, cal_blob)) { |
| |
| for (i = 0; i < ARRAY_SIZE(templates); i++) |
| process_blob(tree, cal_blob, templates[i], |
| strstr(templates[i], "_base64") != NULL); |
| |
| cal_blob = (struct calibration_blob *) |
| ((uintptr_t)cal_blob + cal_blob->blob_size); |
| } |
| |
| return 0; |
| } |
| |