diff --git a/package/kernel/ath10k-ct/patches/970-ath10k-calibration-base64.patch b/package/kernel/ath10k-ct/patches/970-ath10k-calibration-base64.patch
new file mode 100644
index 000000000000..739bdee9ccf4
--- /dev/null
+++ b/package/kernel/ath10k-ct/patches/970-ath10k-calibration-base64.patch
@@ -0,0 +1,249 @@
+Adapted from:
+https://chromium-review.googlesource.com/307062
+
+This "hack" remained in the production kernel, and so the production firmware
+for Google OnHub still only knows how to patch the *-base64 Device Tree
+property.
+
+CHROMIUM: HACK: ath10k: read calibration data in base64 format from DT
+
+ Chrome OS firmware doesn't support binary format in VPD currently.
+ As a workaround, the firmware stores the calibration data in base64
+ format in the same node with the different name:
+
+   qcom,ath10k-calibration-data-base64 = [ 01 02 03 ... ];
+
+   Since the original property "qcom,ath10k-calibration-data" is always
+   looked for first, it should have an invalid size (e.g. 1).
+
+   BUG=chrome-os-partner:35262
+   TEST=build/boot on storm suceeded. Setup Storm board as AP using
+   hostapd and
+   connected to the board using another device. Device was able to
+   connect to
+   the internet and load multiple websites.
+
+   Change-Id: I95675a803fad3b94977ecd0977bd9980779ad7e9
+   Signed-off-by: Toshi Kikuchi <toshik@chromium.org>
+   Reviewed-on: https://chromium-review.googlesource.com/243115
+   Reviewed-by: Grant Grundler <grundler@chromium.org>
+
+Change-Id: I17874f0ed03e28d279b08fe70aca70af57c90bda
+Signed-off-by: Anilkumar Kolli <akolli@codeaurora.org>
+Reviewed-on: https://chromium-review.googlesource.com/307062
+Commit-Ready: Grant Grundler <grundler@chromium.org>
+Tested-by: Grant Grundler <grundler@chromium.org>
+Reviewed-by: Srinivasa duvvuri <sduvvuri@chromium.org>
+Reviewed-by: Grant Grundler <grundler@chromium.org>
+--- a/ath10k-5.15/Makefile
++++ b/ath10k-5.15/Makefile
+@@ -4,6 +4,7 @@ ath10k_core-y += mac.o \
+ 		 debug.o \
+ 		 core.o \
+ 		 coredump.o \
++		 decode64.o \
+ 		 htc.o \
+ 		 htt.o \
+ 		 htt_rx.o \
+--- a/ath10k-5.15/core.c
++++ b/ath10k-5.15/core.c
+@@ -18,6 +18,7 @@
+ #include <linux/ctype.h>
+ 
+ #include "core.h"
++#include "decode64.h"
+ #include "mac.h"
+ #include "htc.h"
+ #include "hif.h"
+@@ -2167,6 +2168,73 @@ static int ath10k_download_cal_file(stru
+ 	return 0;
+ }
+ 
++static int ath10k_download_cal_dt_base64(struct ath10k *ar)
++{
++	struct device_node *node;
++	int data_len;
++	void *data;
++	int ret;
++
++	node = ar->dev->of_node;
++	if (!node)
++		/* Device Tree is optional, don't print any warnings if
++		 * there's no node for ath10k.
++		 */
++		return -ENOENT;
++
++	if (!of_get_property(node, "qcom,ath10k-calibration-data-base64",
++			     &data_len)) {
++		/* The calibration data node is optional */
++		return -ENOENT;
++	}
++
++	data = kmalloc(data_len, GFP_KERNEL);
++	if (!data) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	ret = of_property_read_u8_array(node,
++					"qcom,ath10k-calibration-data-base64",
++					data, data_len);
++	if (ret) {
++		ath10k_warn(ar,
++		    "failed to read calibration data (base64) from DT: %d\n",
++			    ret);
++		goto out_free;
++	}
++
++	data_len = strip_nl(data, data + data_len, data);
++	data_len = decode64(data, data + data_len, data);
++	if (data_len < 0) {
++		ath10k_warn(ar,
++			    "base64 decoder found invalid input\n");
++		ret = -EINVAL;
++		goto out_free;
++	}
++
++	if (data_len != ar->hw_params.cal_data_len) {
++		ath10k_warn(ar, "invalid calibration data length in DT: %d\n",
++			    data_len);
++		ret = -EMSGSIZE;
++		goto out_free;
++	}
++
++	ret = ath10k_download_board_data(ar, data, data_len);
++	if (ret) {
++		ath10k_warn(ar, "failed to download base64 calibration data from Device Tree: %d\n",
++			    ret);
++		goto out_free;
++	}
++
++	ret = 0;
++out_free:
++	kfree(data);
++
++out:
++	return ret;
++}
++
+ static int ath10k_download_cal_dt(struct ath10k *ar, const char *dt_name)
+ {
+ 	struct device_node *node;
+@@ -2787,6 +2855,12 @@ static int ath10k_download_cal_data(stru
+ 		   "boot did not find target EEPROM entry, try OTP next: %d\n",
+ 		   ret);
+ 
++	ret = ath10k_download_cal_dt_base64(ar);
++	if (ret == 0) {
++		ar->cal_mode = ATH10K_CAL_MODE_DT;
++		goto done;
++	}
++
+ 	ret = ath10k_download_and_run_otp(ar);
+ 	if (ret) {
+ 		ath10k_err(ar, "failed to run otp: %d (download-cal-data)\n", ret);
+--- /dev/null
++++ b/ath10k-5.15/decode64.c
+@@ -0,0 +1,89 @@
++/*
++ * Copyright (c) 2013 The Linux Foundation. All rights reserved.
++ * Copyright (c) 2014, The Chromium OS Authors
++ *
++ * 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 "decode64.h"
++
++static char revkey[128] = {
++	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
++	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
++	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
++	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
++	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
++	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
++	-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
++	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
++};
++
++int
++decode64(unsigned char *src, unsigned char *src_end, unsigned char *dst)
++{
++	unsigned char *dst_end = dst;
++
++	while (&src[3] < src_end) {
++		int x;
++		int t[4];
++		int i;
++
++		if (src[0] == '=' || src[1] == '=' ||
++		    (src[2] == '=' && src[3] != '=')) {
++			return -1;
++		}
++
++		for (i = 0; i < 4; i++) {
++			if (src[i] == '=') {
++				t[i] = 0;
++			} else {
++				if (src[i] >= 128 ||
++				    ((t[i] = revkey[src[i]]) < 0)) {
++					return -1;
++				}
++			}
++		}
++
++		x = (t[0] << 18) + (t[1] << 12) + (t[2] << 6) + t[3];
++
++		*dst_end++ = (x >> 16) & 0xff;
++		if (src[2] != '=')
++			*dst_end++ = (x >> 8) & 0xff;
++		if (src[3] != '=')
++			*dst_end++ = x & 0xff;
++
++		src += 4;
++	}
++
++	if (src != src_end)
++		return -1;
++
++	return dst_end - dst;
++}
++
++int
++strip_nl(unsigned char *src, unsigned char *src_end, unsigned char *dst)
++{
++	unsigned char *dst_end = dst;
++
++	while (src < src_end) {
++		if (*src != '\n')
++			*dst_end++ = *src;
++		src++;
++	}
++
++	return dst_end - dst;
++}
+--- /dev/null
++++ b/ath10k-5.15/decode64.h
+@@ -0,0 +1,10 @@
++#ifndef _DECODE64_H_
++#define _DECODE64_H_
++
++int decode64(unsigned char *src, unsigned char *src_end,
++	     unsigned char *dst);
++
++int strip_nl(unsigned char *src, unsigned char *src_end,
++	     unsigned char *dst);
++
++#endif /* _DECODE64_H_ */
