Initial Check in to manually control Frequency ( Auto Voltage ) from User Space

Change-Id: I0e8b80d1ce661fe48ebf977bf55f6e9e0b2f4ef3
Signed-off-by: wthomas <wthomas@codeaurora.org>
Reviewed-by: Bob Amstadt <ramstadt@codeaurora.org>
diff --git a/nss_api_if.h b/nss_api_if.h
index 9f2070e..78b5633 100755
--- a/nss_api_if.h
+++ b/nss_api_if.h
@@ -1029,5 +1029,15 @@
  */
 extern void nss_unregister_tun6rd_if(uint32_t if_num);
 
+/*
+ * @brief NSS Frequency Change
+ * @ param ctx NSS context
+ * @ param eng Frequency Value in Hz
+ * @ param start_or_end Start or End of Freq Change
+ *
+ * @return nss_tx_status_t Tx Status
+ */
+nss_tx_status_t nss_freq_change(void *ctx, uint32_t eng, uint32_t start_or_end);
+
 /**@}*/
 #endif /** __NSS_API_IF_H */
diff --git a/nss_core.h b/nss_core.h
index 254bcac..d0f5b92 100755
--- a/nss_core.h
+++ b/nss_core.h
@@ -553,6 +553,17 @@
 #endif
 
 /*
+ * NSS Statistics and Data for User Space
+ */
+struct nss_cmd_buffer {
+	int32_t current_freq;
+	int32_t auto_scale;
+	int32_t max_freq;
+	uint32_t register_addr;
+	uint32_t register_data;
+};
+
+/*
  * APIs provided by nss_core.c
  */
 extern int nss_core_handle_napi(struct napi_struct *napi, int budget);
diff --git a/nss_hal/ipq806x/nss_hal_pvt.c b/nss_hal/ipq806x/nss_hal_pvt.c
index d40bb6a..2f58ed0 100755
--- a/nss_hal/ipq806x/nss_hal_pvt.c
+++ b/nss_hal/ipq806x/nss_hal_pvt.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/delay.h>
+#include <linux/err.h>
 #include "nss_hal_pvt.h"
 #include "nss_clocks.h"
 
@@ -27,17 +28,205 @@
 	return readl(addr);
 }
 
-#if defined(NSS_ENABLE_CLK)
+/*
+ * nss_hal_pvt_pll_change
+ *	Change the Pll between 11(400mhz) or 18(1066 or 1466)
+ */
+void nss_hal_pvt_pll_change(uint32_t pll)
+{
+	uint32_t ns_reg0;
+	uint32_t ns_reg1;
+
+	uint32_t pll11_mask = 0x3;
+	uint32_t pll18_mask = 0x1;
+
+	uint32_t pll_cl_mask = 0x7;
+
+
+	printk("Picking PLL%d\n", pll);
+
+	if (pll == 11) {
+		ns_reg0 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(0));
+		ns_reg1 = clk_reg_read_32(UBI32_COREn_CLK_SRC1_NS(1));
+
+		ns_reg0 &= ~pll_cl_mask;
+		ns_reg1 &= ~pll_cl_mask;
+
+		ns_reg0 |= pll11_mask;
+		ns_reg1 |= pll11_mask;
+
+		clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(0), ns_reg0);
+		clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(1), ns_reg1);
+		
+
+	} else if (pll == 18) {
+		ns_reg0 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(0));
+		ns_reg1 = clk_reg_read_32(UBI32_COREn_CLK_SRC1_NS(1));
+
+		ns_reg0 &= ~pll_cl_mask;
+		ns_reg1 &= ~pll_cl_mask;
+
+		ns_reg0 |= pll18_mask;
+		ns_reg1 |= pll18_mask;
+
+		clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(0), ns_reg0);
+		clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(1), ns_reg1);
+
+	}
+
+	return;
+}
+
+/*
+ * nss_hal_pvt_divide_pll
+ *	Divide PLL by int val
+ */
+uint32_t nss_hal_pvt_divide_pll(uint32_t core_id, uint32_t pll, uint32_t divider)
+{
+	uint32_t ns_mask 	= 0x00ff01ff;
+	uint32_t ns_mask_1	= 0x00ff0001;
+	uint32_t ns_mask_2	= 0x00fe0141;
+	uint32_t ns_mask_5 	= 0x00fb0141;
+	uint32_t ns_reg0;
+	uint32_t ns_reg1;
+
+	uint32_t md_mask 	= 0x00ff00ff;
+	uint32_t md_mask_2	= 0x000100fd;
+	uint32_t md_mask_5 	= 0x000100fa;
+	uint32_t md_reg0;
+	uint32_t md_reg1;
+
+#if (NSS_DEBUG_LEVEL > 0)
+	printk("NSSFB0_CLK_SRC_CTL  : %x\n", clk_reg_read_32(NSSFB0_CLK_SRC_CTL));
+	printk("NSSFB1_CLK_SRC_CTL  : %x\n", clk_reg_read_32(NSSFB1_CLK_SRC_CTL));
+	printk("NSSFB0_CLK_SRC0_NS  : %x\n", clk_reg_read_32(NSSFB0_CLK_SRC0_NS));
+	printk("NSSFB0_CLK_SRC1_NS  : %x\n", clk_reg_read_32(NSSFB0_CLK_SRC1_NS));
+	printk("NSSFB1_CLK_SRC0_NS  : %x\n", clk_reg_read_32(NSSFB1_CLK_SRC0_NS));
+	printk("NSSFB1_CLK_SRC1_NS  : %x\n", clk_reg_read_32(NSSFB1_CLK_SRC1_NS));
+	printk("PLL_ENA_NSS	    : %x\n", clk_reg_read_32(PLL_ENA_NSS));
+	printk("\n");
+	printk("PLL18_L_VAL  : %x\n", clk_reg_read_32(PLL18_L_VAL));
+	printk("PLL18_M_VAL  : %x\n", clk_reg_read_32(PLL18_M_VAL));
+	printk("PLL18_N_VAL  : %x\n", clk_reg_read_32(PLL18_N_VAL));
+	printk("PLL18_CONFIG : %x\n", clk_reg_read_32(PLL18_CONFIG));
+	printk("PLL18_TEST_CTL: %x\n", clk_reg_read_32(PLL18_TEST_CTL));
+	printk("\n");
+	printk("UBI32_COREn_CLK_SRC0_CTL Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC_CTL(0)));
+	printk("UBI32_COREn_CLK_SRC0_CTL Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC_CTL(1)));
+	printk("UBI32_COREn_CLK_SRC0_NS Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(0)));
+	printk("UBI32_COREn_CLK_SRC0_NS Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(1)));
+	printk("UBI32_COREn_CLK_SRC0_MD Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(0)));
+	printk("UBI32_COREn_CLK_SRC0_MD Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(1)));
+	printk("\n\n\n");
+#endif
+
+	md_reg0 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(0));
+	md_reg1 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(1));
+	ns_reg0 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(0));
+	ns_reg1 = clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(1));
+
+	/*
+	 * Bypass
+	 */
+	if (divider == 1) {
+		printk("Bypass PLL Output\n");
+
+		/*
+		 * Clear M and D ( Not2*D ) and Set Bits
+		 */
+
+		md_reg0 &= ~md_mask;
+		md_reg1 &= ~md_mask;
+
+		/*
+		 * PLL Source/ Pre Divide/ Counter Mode/ Counter Reset/ Counter Enable/ N Value
+		 */
+
+		ns_reg0 &= ~ns_mask;
+		ns_reg1 &= ~ns_mask;
+
+		ns_reg0 |= ns_mask_1;
+		ns_reg1 |= ns_mask_1;
+	} else if (divider == 2) {
+
+		printk("Divide PLL Output by 2\n");
+
+		/*
+		 * Clear M and D ( Not2*D ) and Set Bits
+		 */
+
+		md_reg0 &= ~md_mask;
+		md_reg1 &= ~md_mask;
+
+		md_reg0 |= md_mask_2;
+		md_reg1 |= md_mask_2;
+
+		/*
+		 * PLL Source/ Pre Divide/ Counter Mode/ Counter Reset/ Counter Enable/ N Value
+		 */
+
+		ns_reg0 &= ~ns_mask;
+		ns_reg1 &= ~ns_mask;
+
+		ns_reg0 |= ns_mask_2;
+		ns_reg1 |= ns_mask_2;
+	} else if (divider == 5) {
+
+		printk("Divide PLL Output by 5\n");
+
+		/*
+		 * Clear M and D ( Not2*D ) and Set Bits
+		 */
+
+		md_reg0 &= ~md_mask;
+		md_reg1 &= ~md_mask;
+
+		md_reg0 |= md_mask_5;
+		md_reg1 |= md_mask_5;
+
+		/*
+		 * PLL Source/ Pre Divide/ Counter Mode/ Counter Reset/ Counter Enable/ N Value
+		 */
+
+		ns_reg0 &= ~ns_mask;
+		ns_reg1 &= ~ns_mask;
+
+		ns_reg0 |= ns_mask_5;
+		ns_reg1 |= ns_mask_5;
+	}
+
+	nss_hal_pvt_pll_change(pll);
+
+	clk_reg_write_32(UBI32_COREn_CLK_SRC0_MD(0), md_reg0);
+	clk_reg_write_32(UBI32_COREn_CLK_SRC0_MD(1), md_reg1);
+	clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(0), ns_reg0);
+	clk_reg_write_32(UBI32_COREn_CLK_SRC0_NS(1), ns_reg1);
+
+#if (NSS_DEBUG_LEVEL > 0)
+	printk("UBI32_COREn_CLK_SRC0_CTL Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC_CTL(0)));
+	printk("UBI32_COREn_CLK_SRC0_CTL Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC_CTL(1)));
+	printk("UBI32_COREn_CLK_SRC0_NS Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(0)));
+	printk("UBI32_COREn_CLK_SRC0_NS Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_NS(1)));
+	printk("UBI32_COREn_CLK_SRC0_MD Core 0: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(0)));
+	printk("UBI32_COREn_CLK_SRC0_MD Core 1: %x\n", clk_reg_read_32(UBI32_COREn_CLK_SRC0_MD(1)));
+#endif
+
+	return 1;
+}
 
 /*
  * nss_hal_pvt_enable_pll18()
  *	Enable PLL18
  */
-static uint32_t nss_hal_pvt_enable_pll18(void)
+uint32_t nss_hal_pvt_enable_pll18(uint32_t speed)
 {
 	uint32_t wait_cycles = 100;
+
+	/*
+	 * Prevent Compiler from commenting out the loop.
+	 */
 	volatile uint32_t value;
-	volatile uint32_t mask = (1 << 18);
+	volatile uint32_t mask = (1 << 2);
 
 	/*
 	 * Start with clean slate
@@ -45,18 +234,33 @@
 	clk_reg_write_32(PLL18_MODE, 0);
 
 	/*
-	 * Set L = 44, M = 0, N = 1
-	 * Effective VCO Frequency = 1100 MHz
+	 * Effective VCO Frequency = 1100 MHz Post Divide 2
 	 */
-	clk_reg_write_32(PLL18_L_VAL, 0x4000042C);
-	clk_reg_write_32(PLL18_M_VAL, 0x0);
-	clk_reg_write_32(PLL18_N_VAL, 0x1);
+	if (speed == 1100) {
+		clk_reg_write_32(PLL18_L_VAL, 0x4000042C);
+		clk_reg_write_32(PLL18_M_VAL, 0x0);
+		clk_reg_write_32(PLL18_N_VAL, 0x1);
 
-	/*
-	 * PLL configuration (as provided by HW team)
-	 */
-	clk_reg_write_32(PLL18_CONFIG, 0x01495625);
-	clk_reg_write_32(PLL18_TEST_CTL, 0x00003080);
+		/*
+		 * PLL configuration (as provided by HW team)
+		 */
+		clk_reg_write_32(PLL18_CONFIG, 0x01495625);
+		clk_reg_write_32(PLL18_TEST_CTL, 0x00003080);
+	} else if (speed == 1466) {
+		/*
+		 * Effective VCO Frequency = 1466 MHz Post Divide 2
+		 */
+
+		clk_reg_write_32(PLL18_L_VAL, 0x4000043A);
+		clk_reg_write_32(PLL18_M_VAL, 0x10);
+		clk_reg_write_32(PLL18_N_VAL, 0x19);
+
+		/*
+		 * PLL configuration (as provided by HW team)
+		 */
+		clk_reg_write_32(PLL18_CONFIG, 0x014B5625);
+		clk_reg_write_32(PLL18_TEST_CTL, 0x00003080);
+	}
 
 	/*
 	 * Enable PLL18 output (sequence provided by HW team)
@@ -82,7 +286,6 @@
 	return PLL_NOT_LOCKED;
 }
 
-#endif
 
 /*
  * __nss_hal_common_reset
@@ -91,9 +294,10 @@
 void __nss_hal_common_reset(uint32_t *clk_src)
 {
 	uint32_t i;
-#if defined(NSS_ENABLE_CLK)
 	uint32_t pll18_status;
 
+#if defined(NSS_ENABLE_CLK)
+
 	/*
 	 * NSS FPB CLOCK
 	 */
@@ -202,6 +406,18 @@
 	 */
 	*clk_src = NSS_REGS_CLK_SRC_DEFAULT;
 #endif
+	pll18_status = nss_hal_pvt_enable_pll18(1100);
+
+	if (!pll18_status) {
+		/*
+		 * Select alternate good source (Src1/pll0)
+		 */
+		printk("Enable PLL18 Failed, Using Alternate");
+		*clk_src = NSS_REGS_CLK_SRC_ALTERNATE;
+	} else {
+		nss_hal_pvt_divide_pll(0, 18, 1);
+	}
+
 	/*
 	 * Attach debug interface to TLMM
 	 */
diff --git a/nss_hal/ipq806x/nss_hal_pvt.h b/nss_hal/ipq806x/nss_hal_pvt.h
index 51c2905..c4a092b 100755
--- a/nss_hal/ipq806x/nss_hal_pvt.h
+++ b/nss_hal/ipq806x/nss_hal_pvt.h
@@ -58,5 +58,8 @@
 
 extern void __nss_hal_core_reset(uint32_t core_id, uint32_t map, uint32_t addr, uint32_t clk_src);
 extern void __nss_hal_common_reset(uint32_t *clk_src);
+extern uint32_t nss_hal_pvt_divide_pll(uint32_t core_id, uint32_t pll, uint32_t divider);
+extern void nss_hal_pvt_pll_change(uint32_t pll);
+extern uint32_t nss_hal_pvt_enable_pll18(uint32_t speed);
 
 #endif /* __NSS_HAL_PVT_H */
diff --git a/nss_hlos_if.h b/nss_hlos_if.h
index e865043..db2aa51 100755
--- a/nss_hlos_if.h
+++ b/nss_hlos_if.h
@@ -257,6 +257,14 @@
 };
 
 /*
+ * The NSS freq start or stop strcture
+ */
+struct nss_freq_change {
+	uint32_t frequency;
+	uint32_t start_or_end;
+};
+
+/*
  * Types of TX metadata.
  */
 enum nss_tx_metadata_types {
@@ -280,8 +288,12 @@
 	NSS_TX_METADATA_TYPE_MSS_SET,
 	NSS_TX_METADATA_TYPE_C2C_TX_MAP,
 	NSS_TX_METADATA_TYPE_IPSEC_RULE,
+	NSS_TX_METADATA_TYPE_IPSEC_TX_RULE_DESTROY,
+	NSS_TX_METADATA_TYPE_IPSEC_RX_RULE_CREATE,
+	NSS_TX_METADATA_TYPE_IPSEC_RX_RULE_DESTROY,
 	NSS_TX_METADATA_TYPE_PROFILER_TX,
 	NSS_TX_METADATA_TYPE_GENERIC_IF_PARAMS,
+	NSS_TX_METADATA_TYPE_NSS_FREQ_CHANGE,
 };
 
 /*
@@ -310,9 +322,18 @@
 		struct nss_ipsec_rule ipsec_rule;
 		struct nss_profiler_tx profiler_tx;
 		struct nss_generic_if_params generic_if_params;
+		struct nss_freq_change freq_change;
 	} sub;
 };
 
+/*
+ * The NSS freq ack structure
+ */
+struct nss_freq_ack {
+	uint32_t freq_current;
+	int32_t ack_status;
+};
+
 struct nss_port_info {
 	uint8_t num_phys_ports;
 };
@@ -764,6 +785,7 @@
 	NSS_RX_METADATA_TYPE_NSS_STATS_SYNC,
 	NSS_RX_METADATA_TYPE_PPPOE_STATS_SYNC,
 	NSS_RX_METADATA_TYPE_PROFILER_SYNC,
+	NSS_RX_METADATA_TYPE_FREQ_ACK,
 };
 
 /*
@@ -783,6 +805,7 @@
 		struct nss_nss_stats_sync nss_stats_sync;
 		struct nss_pppoe_exception_stats_sync pppoe_exception_stats_sync;
 		struct nss_profiler_sync profiler_sync;
+		struct nss_freq_ack freq_ack;
 	} sub;
 };
 
diff --git a/nss_init.c b/nss_init.c
index a5161d8..46ad2b1 100755
--- a/nss_init.c
+++ b/nss_init.c
@@ -11,8 +11,13 @@
 
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/device.h>
 #include <mach/msm_nss.h>
 
+#include <linux/sysctl.h>
+#include <linux/regulator/consumer.h>
+
 /*
  * Declare module parameters
  */
@@ -45,10 +50,23 @@
  * Global declarations
  */
 
+
+/*
+ * Define for RPM Nominal and Turbo for NSS
+ */
+#define NSS_NOM_VCC  1050000
+#define NSS_TURB_VCC 1150000
+
+/*
+ * Global Handlers for the nss regulators
+ */
+struct regulator *nss0_vreg;
+
 /*
  * Top level nss context structure
  */
 struct nss_top_instance nss_top_main;
+struct nss_cmd_buffer nss_cmd_buf;
 
 /*
  * File local/Static variables/functions
@@ -101,6 +119,40 @@
 	nss_ctx->nss_top = nss_top;
 	nss_ctx->id = nss_dev->id;
 
+	nss_info("%p: NSS_DEV_ID %s \n", nss_ctx, dev_name(&nss_dev->dev));
+
+	/*
+	 * Both NSS cores controlled by same regulator, Hook only Once
+	 */
+	if (!nss_dev->id) {
+		nss0_vreg = devm_regulator_get(&nss_dev->dev, "VDD_UBI0");
+
+		if (IS_ERR(nss0_vreg)) {
+
+			err = PTR_ERR(nss0_vreg);
+			nss_info("%p: Regulator %s get failed, err=%d\n", nss_ctx, dev_name(&nss_dev->dev), err);
+			return err;
+
+		} else {
+
+			nss_info("%p: Regulator %s get success\n", nss_ctx, dev_name(&nss_dev->dev));
+
+			err = regulator_enable(nss0_vreg);
+			if (err) {
+				nss_info("%p: Regulator %s enable voltage failed, err=%d\n", nss_ctx, dev_name(&nss_dev->dev), err);
+				return err;
+			}
+
+			err = regulator_set_voltage(nss0_vreg, NSS_NOM_VCC, NSS_NOM_VCC);
+			if (err) {
+				nss_info("%p: Regulator %s set voltage failed, err=%d\n", nss_ctx, dev_name(&nss_dev->dev), err);
+				return err;
+			}
+
+
+		}
+	}
+
 	/*
 	 * Get virtual and physical memory addresses for nss logical/hardware address maps
 	 */
@@ -365,6 +417,176 @@
 };
 
 /*
+ * nss_current_freq_handler()
+ *	Handle Userspace Frequency Change Requests
+ */
+static int nss_current_freq_handler (ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	void *ubicom_na_nss_context = NULL;
+	int ret;
+	int vret;
+
+	ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+
+	if (write) {
+
+		ubicom_na_nss_context = nss_register_ipv4_mgr(NULL);
+
+		nss_info("Frequency Set to %d\n", nss_cmd_buf.current_freq);
+
+		/* If support NSS freq is in the table send the new frequency request to NSS */
+
+		if (nss_cmd_buf.current_freq == 110000000) {
+
+			nss_freq_change(ubicom_na_nss_context, 533000000, 0);
+			nss_hal_pvt_divide_pll(0, 11, 1);
+			nss_hal_pvt_enable_pll18(1100);
+			nss_freq_change(ubicom_na_nss_context, nss_cmd_buf.current_freq, 0);
+			nss_hal_pvt_divide_pll(0, 18, 5);
+
+			vret = regulator_set_voltage(nss0_vreg, NSS_NOM_VCC, NSS_NOM_VCC);
+			if (vret) {
+				nss_info("Regulator set voltage failed, err=%d\n", vret);
+				return ret;
+			}
+
+		} else if (nss_cmd_buf.current_freq == 225000000) {
+
+			nss_freq_change(ubicom_na_nss_context, 533000000, 0);
+			nss_hal_pvt_divide_pll(0, 11, 1);
+			nss_hal_pvt_enable_pll18(1100);
+			nss_freq_change(ubicom_na_nss_context, nss_cmd_buf.current_freq, 0);
+			nss_hal_pvt_divide_pll(0, 18, 2);
+
+			vret = regulator_set_voltage(nss0_vreg, NSS_NOM_VCC, NSS_NOM_VCC);
+			if (vret) {
+				nss_info("Regulator set voltage failed, err=%d\n", vret);
+				return ret;
+			}
+
+		} else if (nss_cmd_buf.current_freq == 550000000) {
+
+			nss_freq_change(ubicom_na_nss_context, 533000000, 0);
+			nss_hal_pvt_divide_pll(0, 11, 1);
+			nss_hal_pvt_enable_pll18(1100);
+			nss_freq_change(ubicom_na_nss_context, nss_cmd_buf.current_freq, 0);
+			nss_hal_pvt_divide_pll(0, 18, 1);
+
+			vret = regulator_set_voltage(nss0_vreg, NSS_NOM_VCC, NSS_NOM_VCC);
+			if (vret) {
+				nss_info("Regulator set voltage failed, err=%d\n", vret);
+				return ret;
+			}
+
+		} else if (nss_cmd_buf.current_freq == 733000000) {
+			vret = regulator_set_voltage(nss0_vreg, NSS_TURB_VCC, NSS_TURB_VCC);
+			if (vret) {
+				nss_info("Regulator set voltage failed, err=%d\n", vret);
+				return ret;
+			}
+
+			nss_freq_change(ubicom_na_nss_context, 533000000, 0);
+			nss_hal_pvt_divide_pll(0, 11, 1);
+			nss_hal_pvt_enable_pll18(1466);
+			nss_freq_change(ubicom_na_nss_context, nss_cmd_buf.current_freq, 0);
+			nss_hal_pvt_divide_pll(0, 18, 1);
+
+		} else {
+			nss_info("Frequency not found. Please check Frequency Table\n");
+		}
+	}
+	return ret;
+}
+
+/*
+ * nss_auto_scale_handler()
+ *	Enables or Disable Auto Scaling
+ */
+static int nss_auto_scale_handler (ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	int ret;
+
+	ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+
+	printk("Not Supported\n");
+
+	return ret;
+}
+
+/*
+ * nss_get_freq_table_handler()
+ *	Display Support Freq and Ex how to Change.
+ */
+static int nss_get_freq_table_handler (ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	int ret;
+
+	ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+
+	printk("Frequency Supported - 110Mhz 225Mhz 550Mhz 733Mhz \n");
+	printk("Ex. To Change Frequency - echo 110000000 > current_freq \n");
+
+	return ret;
+}
+
+/*
+ * sysctl-tuning infrastructure.
+ */
+static ctl_table nss_freq_table[] = {
+	{
+		.procname		= "current_freq",
+		.data			= &nss_cmd_buf.current_freq,
+		.maxlen			= sizeof(int),
+		.mode			= 0644,
+		.proc_handler	= &nss_current_freq_handler,
+	},
+	{
+		.procname		= "freq_table",
+		.data			= &nss_cmd_buf.max_freq,
+		.maxlen			= sizeof(int),
+		.mode			= 0644,
+		.proc_handler	= &nss_get_freq_table_handler,
+	},
+	{
+		.procname		= "auto_scale",
+		.data			= &nss_cmd_buf.auto_scale,
+		.maxlen			= sizeof(int),
+		.mode			= 0644,
+		.proc_handler	= &nss_auto_scale_handler,
+	},
+	{ }
+};
+
+static ctl_table nss_clock_dir[] = {
+	{
+		.procname		= "clock",
+		.mode			= 0555,
+		.child			= nss_freq_table,
+	},
+	{ }
+};
+
+static ctl_table nss_root_dir[] = {
+	{
+		.procname		= "nss",
+		.mode			= 0555,
+		.child			= nss_clock_dir,
+	},
+	{ }
+};
+
+static ctl_table nss_root[] = {
+	{
+		.procname		= "dev",
+		.mode			= 0555,
+		.child			= nss_root_dir,
+	},
+	{ }
+};
+
+static struct ctl_table_header *nss_dev_header;
+
+/*
  * nss_init()
  *	Registers nss driver
  */
@@ -395,6 +617,11 @@
 	nss_top_main.nss[1].load = (uint32_t)load1;
 
 	/*
+	 * Register sysctl table.
+	 */
+	nss_dev_header = register_sysctl_table(nss_root);
+
+	/*
 	 * Register platform_driver
 	 */
 	return platform_driver_register(&nss_driver);
@@ -407,6 +634,10 @@
 static void __exit nss_cleanup(void)
 {
 	nss_info("Exit NSS driver");
+
+	if (nss_dev_header)
+		unregister_sysctl_table(nss_dev_header);
+
 	platform_driver_unregister(&nss_driver);
 }
 
diff --git a/nss_tx_rx.c b/nss_tx_rx.c
index ef4370e..afc769b 100755
--- a/nss_tx_rx.c
+++ b/nss_tx_rx.c
@@ -13,6 +13,10 @@
  * Global variables/extern declarations
  */
 extern struct nss_top_instance nss_top_main;
+extern struct nss_frequency_statistics nss_freq_stat;
+
+#define NSS_ACK_STARTED 0
+#define NSS_ACK_FINISHED 1
 
 #if (NSS_DEBUG_LEVEL > 0)
 #define NSS_VERIFY_CTX_MAGIC(x) nss_verify_ctx_magic(x)
@@ -37,6 +41,37 @@
 #endif
 
 /*
+ * nss_rx_metadata_nss_freq_ack()
+ *     Handle the nss ack of frequency change.
+ */
+static void nss_rx_metadata_nss_freq_ack(struct nss_ctx_instance *nss_ctx, struct nss_freq_ack *nfa)
+{
+	void *ubicom_na_nss_context = NULL;
+	ubicom_na_nss_context = nss_register_ipv4_mgr(NULL);
+
+	if (nfa->ack_status == NSS_ACK_STARTED) {
+
+		/*
+		 * NSS finished start noficiation - HW change clocks and send end notification
+		 */
+
+		nss_info("%p: NSS ACK Received: %d - Change HW CLK/Send Finish to NSS\n", nss_ctx, nfa->ack);
+		nss_freq_change(ubicom_na_nss_context, nfa->freq_current, 1);
+
+	} else if (nfa->ack_status == NSS_ACK_FINISHED) {
+
+		/*
+		 * NSS finished end notification - Done
+		 */
+
+		nss_info("%p: NSS Finish End Notification ACK: %d - Running: %dmhz\n", nss_ctx, nfa->ack, nfa->freq_current);
+
+	} else {
+		nss_info("%p: NSS had an error - Running: %dmhz\n", nss_ctx, nfa->freq_current);
+	}
+}
+
+/*
  * nss_rx_metadata_ipv4_rule_establish()
  *	Handle the establishment of an IPv4 rule.
  */
@@ -231,6 +266,52 @@
 }
 
 /*
+ * nss_freq_change()
+ *     NSS frequency change API
+ */
+nss_tx_status_t nss_freq_change(void *ctx, uint32_t eng, uint32_t start_or_end)
+{
+	struct nss_ctx_instance *nss_ctx = (struct nss_ctx_instance *) ctx;
+	struct sk_buff *nbuf;
+	int32_t status;
+	struct nss_tx_metadata_object *ntmo;
+	struct nss_freq_change *nfc;
+
+	nss_info("%p: Frequency Changing to: %d\n", nss_ctx, eng);
+
+	NSS_VERIFY_CTX_MAGIC(nss_ctx);
+	if (unlikely(nss_ctx->state != NSS_CORE_STATE_INITIALIZED)) {
+		return NSS_TX_FAILURE_NOT_READY;
+	}
+
+	nbuf = __dev_alloc_skb(NSS_NBUF_PAYLOAD_SIZE, GFP_ATOMIC | __GFP_NOWARN);
+	if (unlikely(!nbuf)) {
+		spin_lock_bh(&nss_ctx->nss_top->stats_lock);
+		nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_NBUF_ALLOC_FAILS]++;
+		spin_unlock_bh(&nss_ctx->nss_top->stats_lock);
+		return NSS_TX_FAILURE;
+	}
+
+	ntmo = (struct nss_tx_metadata_object *)skb_put(nbuf, sizeof(struct nss_tx_metadata_object));
+	ntmo->type = NSS_TX_METADATA_TYPE_NSS_FREQ_CHANGE;
+
+	nfc = &ntmo->sub.freq_change;
+	nfc->frequency = eng;
+	nfc->start_or_end = start_or_end;
+
+	status = nss_core_send_buffer(nss_ctx, 0, nbuf, NSS_IF_CMD_QUEUE, H2N_BUFFER_CTRL, 0);
+	if (status != NSS_CORE_STATUS_SUCCESS) {
+		dev_kfree_skb_any(nbuf);
+		nss_info("%p: unable to enqueue 'nss frequency change' - marked as stopped\n", nss_ctx);
+		return NSS_TX_FAILURE;
+	}
+
+	nss_hal_send_interrupt(nss_ctx->nmap, nss_ctx->h2n_desc_rings[NSS_IF_CMD_QUEUE].desc_ring.int_bit, NSS_REGS_H2N_INTR_STATUS_DATA_COMMAND_QUEUE);
+
+	return NSS_TX_SUCCESS;
+}
+
+/*
  * nss_rx_metadata_gmac_stats_sync()
  *	Handle the syncing of GMAC stats.
  */
@@ -464,6 +545,10 @@
 		nss_rx_metadata_profiler_sync(nss_ctx, &nrmo->sub.profiler_sync);
 		break;
 
+	case NSS_RX_METADATA_TYPE_FREQ_ACK:
+		nss_rx_metadata_nss_freq_ack(nss_ctx, &nrmo->sub.freq_ack);
+		break;
+
 	default:
 		/*
 		 * WARN: Unknown metadata type