storm: add WiFi calibration blobs to the device tree

This patch finds WiFi calibration data in CBMEM, verifies its sanity
and places the calibration blobs in the kernel device tree at the
appropriate locations, mapping VPD key names into device tree paths.

Calibration data in the CBMEM comes from the VPD and could be in one
of two formats, binary or base64. The key for the WiFi calibration
entries in the VPD is

wifi_calibration<if_number> or wifi_base64_calibration<if_number>

depending on the calibration data storage format. CBMEM WiFi
calibration data table uses key names directly from the VPD.

The device tree path of the calibration blobs is as follows:

/soc/pci@<pci_base>/pcie@0/ath10k@0,0/qcom,ath10k-calibration-data<base64_suffix>

where <pci_base> is determined by the interface number (the last
character of the VPD key) and base64_suffix is '-base64', it is added
to the path in case calibration data is stored in the VPD in base64
format.

The standard whirlwind device tree comes with node stubs for binary
format calibration data for three interfaces. These stubs are supposed
to be filled by firmware. If calibration data format for a certain
interface is base64, the firmware is supposed to create a new device
tree node next to the stub. The kernel driver will figure out which of
the two to use.

CQ-DEPEND=CL:225506
BRANCH=storm
BUG=chrome-os-partner:32611
TEST=created three entries in the VPD with one of the keys indicating
     base 64 format, and others - binary format. Booted the new
     firmware of Whirlwind, observed proper device tree entries
     populated.

Change-Id: I028863283c79738117aa72aabcb83debf0ffb022
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/225517
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
diff --git a/src/board/storm/Makefile.inc b/src/board/storm/Makefile.inc
index f45aade..4533e78 100644
--- a/src/board/storm/Makefile.inc
+++ b/src/board/storm/Makefile.inc
@@ -16,5 +16,4 @@
 ##
 
 depthcharge-y += board.c
-#TODO Furquan: Add power_ops.c and power_ops.h when required
-
+depthcharge-y += wifi_calibration.c
diff --git a/src/board/storm/board.c b/src/board/storm/board.c
index b5b8746..cd052f7 100644
--- a/src/board/storm/board.c
+++ b/src/board/storm/board.c
@@ -25,7 +25,6 @@
 #include <sysinfo.h>
 #include <stdio.h>
 
-#include "base/device_tree.h"
 #include "base/init_funcs.h"
 #include "boot/fit.h"
 #include "drivers/bus/i2c/ipq806x_gsbi.h"
@@ -41,6 +40,8 @@
 #include "drivers/gpio/ipq806x.h"
 #include "drivers/storage/ipq806x_mmc.h"
 
+#include "board.h"
+
 #define GPIO_SDCC_FUNC_VAL      2
 
 #define MSM_SDC1_BASE		0x12400000
@@ -50,7 +51,7 @@
  * required. Just two need to be set, at the particular paths in the device
  * tree listed in the array below.
  */
-static int set_mac_addresses(DeviceTreeFixup *fixup, DeviceTree *tree)
+static int set_mac_addresses(DeviceTree *tree)
 {
 	static const char *mac_addr_paths[][3] = {
 		{ "soc", "ethernet@37000000", NULL },
@@ -77,8 +78,18 @@
 	return 0;
 }
 
+static int fix_device_tree(DeviceTreeFixup *fixup, DeviceTree *tree)
+{
+	int rv;
+
+	rv = set_mac_addresses(tree);
+	rv |= set_wifi_calibration(tree);
+
+	return rv;
+}
+
 static DeviceTreeFixup ipq_enet_fixup = {
-	.fixup = set_mac_addresses
+	.fixup = fix_device_tree
 };
 
 /* MMC bus GPIO assignments. */
diff --git a/src/board/storm/board.h b/src/board/storm/board.h
new file mode 100644
index 0000000..374576f
--- /dev/null
+++ b/src/board/storm/board.h
@@ -0,0 +1,29 @@
+/*
+ * 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
+ */
+#ifndef __BOARD_STORM_BOARD_H__
+#define __BOARD_STORM_BOARD_H__
+
+#include "base/device_tree.h"
+
+int set_wifi_calibration(DeviceTree *tree);
+
+#endif
diff --git a/src/board/storm/wifi_calibration.c b/src/board/storm/wifi_calibration.c
new file mode 100644
index 0000000..5489ff5
--- /dev/null
+++ b/src/board/storm/wifi_calibration.c
@@ -0,0 +1,179 @@
+/*
+ * 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;
+}
+