--- /dev/null
+/*
+ * drivers/net/wireless/bcmdhd/dhd_custom_net_perf_tegra.c
+ *
+ * NVIDIA Tegra Network Performance Boost for BCMDHD driver
+ *
+ * Copyright (C) 2015 NVIDIA Corporation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include "dhd_custom_net_perf_tegra.h"
+
+static DEFINE_SEMAPHORE(wifi_sclk_lock);
+static struct clk *wifi_sclk;
+static int wifi_sclk_count;
+
+static void wifi_sclk_enable(void)
+{
+ if (!wifi_sclk)
+ return;
+
+ down(&wifi_sclk_lock);
+ if (++wifi_sclk_count == 1) {
+ pr_debug("%s\n", __func__);
+ clk_enable(wifi_sclk);
+ }
+ up(&wifi_sclk_lock);
+}
+
+static void wifi_sclk_disable(void)
+{
+ if (!wifi_sclk)
+ return;
+
+ down(&wifi_sclk_lock);
+ if (--wifi_sclk_count == 0) {
+ pr_debug("%s\n", __func__);
+ clk_disable(wifi_sclk);
+ }
+ up(&wifi_sclk_lock);
+}
+
+/* network performance policy worker function */
+static void tegra_net_perf_policy_worker(struct work_struct *work)
+{
+ struct delayed_work *dwork
+ = container_of(work, struct delayed_work, work);
+ struct tegra_net_perf_policy *policy
+ = container_of(dwork, struct tegra_net_perf_policy, dwork);
+ unsigned long now, timeout, flags;
+
+ /* lock net perf policy */
+ spin_lock_irqsave(&policy->lock, flags);
+
+ /* get boost timeout */
+ now = jiffies;
+ timeout = policy->jiffies_boost_timeout;
+
+ /* check boost timeout */
+ if (time_before(now, timeout)) {
+ /* boost freq */
+ if (!policy->freq_boost_flag) {
+ pr_debug("%s: begin freq boost (policy %p)...\n",
+ __func__, policy);
+ /* set freq boost flag */
+ policy->freq_boost_flag = 1;
+ /* reschedule later to restore freq */
+ schedule_delayed_work(dwork, timeout - now);
+ /* unlock net perf policy */
+ spin_unlock_irqrestore(&policy->lock, flags);
+ /* boost freq (by enabling wifi sclk) */
+ wifi_sclk_enable();
+ return;
+ }
+ } else {
+ /* restore freq */
+ if (policy->freq_boost_flag) {
+ pr_debug("%s: end freq boost... (policy %p)\n",
+ __func__, policy);
+ /* clear freq boost flag */
+ policy->freq_boost_flag = 0;
+ /* unlock net perf policy */
+ spin_unlock_irqrestore(&policy->lock, flags);
+ /* restore freq (by disabling wifi sclk) */
+ wifi_sclk_disable();
+ return;
+ }
+ }
+
+ /* unlock net perf policy */
+ spin_unlock_irqrestore(&policy->lock, flags);
+}
+
+/* network performance policy */
+static struct tegra_net_perf_policy rx_net_perf_policy = {
+ .bps_boost_threshold = TEGRA_NET_PERF_RX_THRESHOLD,
+ .boost_timeout_ms = TEGRA_NET_PERF_RX_TIMEOUT,
+};
+
+static struct tegra_net_perf_policy tx_net_perf_policy = {
+ .bps_boost_threshold = TEGRA_NET_PERF_TX_THRESHOLD,
+ .boost_timeout_ms = TEGRA_NET_PERF_TX_TIMEOUT,
+};
+
+static void calc_network_rate(struct tegra_net_perf_policy *policy,
+ unsigned int bits)
+{
+ unsigned long now = jiffies;
+ unsigned long flags;
+ unsigned long delta;
+
+ /* check if bps exceeds threshold for boosting freq */
+ spin_lock_irqsave(&policy->lock, flags);
+ if (!policy->jiffies_prev)
+ policy->jiffies_prev = now;
+ if (ULONG_MAX - bits < policy->bits) {
+ policy->jiffies_prev = 0;
+ policy->bits = bits;
+ goto unlock;
+ }
+ policy->bits += bits;
+ delta = now - policy->jiffies_prev;
+ if (delta < msecs_to_jiffies(TEGRA_NET_PERF_MIN_SAMPLE_WINDOW))
+ goto unlock;
+ if ((delta > msecs_to_jiffies(TEGRA_NET_PERF_MAX_SAMPLE_WINDOW)) ||
+ (policy->bits / (delta + 1) > ULONG_MAX / HZ)) {
+ policy->jiffies_prev = 0;
+ policy->bits = bits;
+ goto unlock;
+ }
+ policy->bps = policy->bits / (delta + 1) * HZ;
+ if (policy->bps < policy->bps_boost_threshold)
+ goto unlock;
+ policy->jiffies_boost_timeout = now +
+ msecs_to_jiffies(policy->boost_timeout_ms);
+
+ /* boost freq */
+ if (!policy->freq_boost_flag)
+ schedule_delayed_work(&policy->dwork, msecs_to_jiffies(0));
+ else {
+ cancel_delayed_work(&policy->dwork);
+ schedule_delayed_work(&policy->dwork,
+ msecs_to_jiffies(policy->boost_timeout_ms));
+ }
+
+unlock:
+ spin_unlock_irqrestore(&policy->lock, flags);
+}
+
+void tegra_net_perf_init(void)
+{
+ /* initialize static variable(s) */
+ wifi_sclk = clk_get_sys("tegra-wifi", "sclk");
+ if (IS_ERR(wifi_sclk)) {
+ pr_err("%s: cannot get wifi sclk\n", __func__);
+ wifi_sclk = NULL;
+ }
+
+ /* initialize wifi sclk rate (will not take effect until clk enable) */
+ if (wifi_sclk)
+ if (clk_set_rate(wifi_sclk, TEGRA_NET_PERF_WIFI_SCLK_FREQ) < 0)
+ pr_err("%s: cannot set wifi sclk rate %ld\n",
+ __func__, TEGRA_NET_PERF_WIFI_SCLK_FREQ);
+
+ /* sanity-check configuration values */
+ if (rx_net_perf_policy.boost_timeout_ms <
+ TEGRA_NET_PERF_MIN_SAMPLE_WINDOW)
+ rx_net_perf_policy.boost_timeout_ms =
+ TEGRA_NET_PERF_MIN_SAMPLE_WINDOW;
+ if (tx_net_perf_policy.boost_timeout_ms <
+ TEGRA_NET_PERF_MIN_SAMPLE_WINDOW)
+ tx_net_perf_policy.boost_timeout_ms =
+ TEGRA_NET_PERF_MIN_SAMPLE_WINDOW;
+
+ /* initialize network performance policy(s) */
+ INIT_DELAYED_WORK(&rx_net_perf_policy.dwork,
+ tegra_net_perf_policy_worker);
+ INIT_DELAYED_WORK(&tx_net_perf_policy.dwork,
+ tegra_net_perf_policy_worker);
+ spin_lock_init(&rx_net_perf_policy.lock);
+ spin_lock_init(&tx_net_perf_policy.lock);
+}
+
+void tegra_net_perf_exit(void)
+{
+ /* uninitialize network performance policy(s) */
+ cancel_delayed_work_sync(&tx_net_perf_policy.dwork);
+ tx_net_perf_policy.freq_boost_flag = 0;
+ cancel_delayed_work_sync(&rx_net_perf_policy.dwork);
+ rx_net_perf_policy.freq_boost_flag = 0;
+
+ /* uninitialize static variable(s) */
+ if (wifi_sclk) {
+ if (wifi_sclk_count > 0) {
+ pr_debug("%s: wifi sclk disable\n",
+ __func__);
+ wifi_sclk_count = 0;
+ clk_disable(wifi_sclk);
+ }
+ clk_put(wifi_sclk);
+ wifi_sclk = NULL;
+ }
+}
+
+void tegra_net_perf_rx(struct sk_buff *skb)
+{
+ /* calc rx data rate (and boost freq if threshold exceeded) */
+ calc_network_rate(&rx_net_perf_policy, skb->len * 8);
+}
+
+void tegra_net_perf_tx(struct sk_buff *skb)
+{
+ /* calc tx data rate (and boost freq if threshold exceeded) */
+ calc_network_rate(&tx_net_perf_policy, skb->len * 8);
+}
#endif
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+#include "dhd_custom_net_perf_tegra.h"
+#endif
+
#ifdef WLMEDIA_HTSF
#include <linux/time.h>
#include <htsf.h>
DHD_TRACE(("%s: Enter\n", __FUNCTION__));
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_tx(skb);
+#endif
+
DHD_OS_WAKE_LOCK(&dhd->pub);
DHD_PERIM_LOCK_TRY(DHD_FWDER_UNIT(dhd), TRUE);
}
if (in_interrupt()) {
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_rx(skb);
+#endif
netif_rx(skb);
} else {
if (dhd->rxthread_enabled) {
* by netif_rx_ni(), but in earlier kernels, we need
* to do it manually.
*/
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_rx(skb);
+#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
netif_rx_ni(skb);
#else
void *skbnext = PKTNEXT(pub->osh, skb);
PKTSETNEXT(pub->osh, skb, NULL);
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_rx(skb);
+#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
netif_rx_ni(skb);
#else
while (skbp) {
void *skbnext = PKTNEXT(dhdp->osh, skbp);
PKTSETNEXT(dhdp->osh, skbp, NULL);
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_rx(skbp);
+#endif
netif_rx_ni(skbp);
skbp = skbnext;
}
static void __exit
dhd_module_exit(void)
{
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_exit();
+#endif
dhd_module_cleanup();
unregister_reboot_notifier(&dhd_reboot_notifier);
}
DHD_ERROR(("%s in\n", __FUNCTION__));
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_init();
+#endif
+
DHD_PERIM_RADIO_INIT();
if (firmware_path[0] != '\0') {
skb_pull(skb, ETH_HLEN);
/* Send the packet */
+#ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA
+ tegra_net_perf_rx(skb);
+#endif
if (in_interrupt()) {
netif_rx(skb);
} else {
--- /dev/null
+/*
+ * drivers/net/wireless/bcmdhd/include/dhd_custom_net_perf_tegra.h
+ *
+ * NVIDIA Tegra Network Performance Boost for BCMDHD driver
+ *
+ * Copyright (C) 2015 NVIDIA Corporation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef _dhd_custom_net_perf_tegra_h_
+#define _dhd_custom_net_perf_tegra_h_
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/skbuff.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+/* measure network data rate for this time period before boosting freq */
+#ifndef TEGRA_NET_PERF_MIN_SAMPLE_WINDOW
+#define TEGRA_NET_PERF_MIN_SAMPLE_WINDOW 30 /* ms */
+#endif /* TEGRA_NET_PERF_MIN_SAMPLE_WINDOW */
+
+#ifndef TEGRA_NET_PERF_MAX_SAMPLE_WINDOW
+#define TEGRA_NET_PERF_MAX_SAMPLE_WINDOW 3000 /* ms */
+#endif /* TEGRA_NET_PERF_MAX_SAMPLE_WINDOW */
+
+/* network data rate threshold for boosting freq */
+#ifndef TEGRA_NET_PERF_RX_THRESHOLD
+#define TEGRA_NET_PERF_RX_THRESHOLD 200000000 /* bps */
+#endif /* TEGRA_NET_PERF_RX_THRESHOLD */
+
+#ifndef TEGRA_NET_PERF_TX_THRESHOLD
+#define TEGRA_NET_PERF_TX_THRESHOLD 200000000 /* bps */
+#endif /* TEGRA_NET_PERF_TX_THRESHOLD */
+
+/* how long to keep boosting freq after data rate threshold reached */
+#ifndef TEGRA_NET_PERF_RX_TIMEOUT
+#define TEGRA_NET_PERF_RX_TIMEOUT 50 /* ms */
+#endif /* TEGRA_NET_PERF_RX_TIMEOUT */
+
+#ifndef TEGRA_NET_PERF_TX_TIMEOUT
+#define TEGRA_NET_PERF_TX_TIMEOUT 50 /* ms */
+#endif /* TEGRA_NET_PERF_TX_TIMEOUT */
+
+/*
+ * how much frequency boost for wifi sclk
+ * - the wifi.sclk pushes up the APB PCLK, which in turn pushes up HCLK and
+ * SCLK to 2xPCLK level
+ * - so to set SCLK to X, need to specify (X/2) for the wifi.sclk frequency
+ */
+#ifndef TEGRA_NET_PERF_WIFI_SCLK_FREQ
+#define TEGRA_NET_PERF_WIFI_SCLK_FREQ (265600000UL / 2) /* Hz */
+#endif /* TEGRA_NET_PERF_WIFI_SCLK_FREQ */
+
+/**
+ * struct tegra_net_perf_policy - network performance policy
+ * @dwork: delayed work object for boosting / unboosting
+ * frequencies
+ * @lock: locks this structure against concurrent access
+ * by network i/o functions and the work object
+ * @freq_boost_flag: set to non-zero if frequency is boosted (and
+ * needs to be unboosted later)
+ * @jiffies_prev: when calculating the data throughput, the time
+ * period is current time (jiffies) minus this
+ * value
+ * @jiffies_boost_timeout: the jiffies timestamp at which frequency boost
+ * is supposed to expire
+ * @bits: the number of bits (it will be divided by the
+ * elapsed time when calculating the bits per sec
+ * value)
+ * @bps_boost_threshold: once bits per sec exceeds this value, the
+ * frequency will be boosted
+ * @jiffies_boost_timeout: duration to keep frequency boosted after data
+ * throughput drops below threshold (if data
+ * throughput constantly stays above threshold,
+ * the the frequency will get boosted
+ * indefinitely)
+ *
+ * This structure maintains all the variables required to calculate network
+ * throughput, and to boost frequencies for the duration of high network
+ * traffic.
+ */
+struct tegra_net_perf_policy {
+ struct delayed_work dwork;
+ spinlock_t lock;
+ int freq_boost_flag;
+ unsigned long jiffies_prev;
+ unsigned long jiffies_boost_timeout;
+ unsigned long bits;
+ unsigned long bps;
+ unsigned long bps_boost_threshold;
+ unsigned int boost_timeout_ms;
+};
+
+/* initialization */
+void tegra_net_perf_init(void);
+
+void tegra_net_perf_exit(void);
+
+/* network packet rx/tx notification */
+void tegra_net_perf_rx(struct sk_buff *skb);
+
+void tegra_net_perf_tx(struct sk_buff *skb);
+
+#endif /* _dhd_custom_net_perf_tegra_h_ */