From 93ce941adf0f74e6704335c994629db072da3045 Mon Sep 17 00:00:00 2001
From: ashish kumar <ashish4.kumar@infineon.com>
Date: Fri, 10 Mar 2023 04:51:34 -0600
Subject: [PATCH 210/296] brcmfmac: Support idleclock feature for 43012/43022
 chip

Modified and taken from: http://gerrit-iot.aus.cypress.com:8081/#/c/14941
This supports switching OFF SD clock during Sleep state to avoid
back powering of chip for VDDIO current measurement
Wakeup - sleep Sequence:

Set SD CLOCK EN bit 2 in CLOCK CONTROL Register 0x2C in HOST Controller
Set KSO Bit in CSR Register
Set 4 bit mode in BUS INTERFACE CONTROL and HOST Control register
Process the interrupt or packet
Set 1 bit mode in BUS INTERFACE CONTROL and HOST Control register
KSO CLEAR
Clear SD CLOCK EN bit 2 in CLOCK CONTROL Register 0x2C in HOST
Controller
NOTE: 1 bit mode is set during sleep for supporting interrupt processing
without SD clock
Old Jira ref: 
Current Jira:
Usage:

insmod brcmfmac.ko sdio_idleclk_disable=1 ==> Enables this feature
insmod brcmfmac.ko   ==> disables this feature by default.
Related mmc:
 https://gitlab.intra.infineon.com/linux/brcm80211/-/merge_requests/258/

Signed-off-by: Ashish Kumar <ashish4.kumar@infineon.com>
Signed-off-by: Shelley Yang <shelley.yang@infineon.com>
---
 .../broadcom/brcm80211/brcmfmac/common.c      |  7 ++
 .../broadcom/brcm80211/brcmfmac/common.h      | 18 ++++
 .../broadcom/brcm80211/brcmfmac/sdio.c        | 86 +++++++++++++++++++
 3 files changed, 111 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
index 98fc6500c048..7225978788f3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
@@ -132,6 +132,10 @@ static int brcmf_bt_over_sdio;
 module_param_named(bt_over_sdio, brcmf_bt_over_sdio, int, 0);
 MODULE_PARM_DESC(bt_over_sdio, "Enable BT over SDIO");
 
+static int brcmf_sdio_idleclk_disable = BRCMFMAC_AUTO;
+module_param_named(sdio_idleclk_disable, brcmf_sdio_idleclk_disable, int, 0644);
+MODULE_PARM_DESC(sdio_idleclk_disable, "Disable SDIO idle clock");
+
 static struct brcmfmac_platform_data *brcmfmac_pdata;
 struct brcmf_mp_global_t brcmf_mp_global;
 
@@ -818,6 +822,9 @@ struct brcmf_mp_device *brcmf_get_module_param(struct device *dev,
 	settings->bt_over_sdio = !!brcmf_bt_over_sdio;
 	brcmf_dbg(INFO, "bt_over_sdio: %d\n", settings->bt_over_sdio);
 
+	settings->idleclk_disable = brcmf_sdio_idleclk_disable;
+	brcmf_dbg(INFO, "idleclk_disable: %d\n", settings->idleclk_disable);
+
 	if (bus_type == BRCMF_BUSTYPE_SDIO) {
 		settings->bus.sdio.txglomsz = brcmf_sdiod_txglomsz;
 		brcmf_dbg(INFO, "txglomsz: %d\n", settings->bus.sdio.txglomsz);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h
index c67cf760175f..eab720625ef8 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.h
@@ -11,6 +11,22 @@
 
 #define BRCMF_FW_ALTPATH_LEN			256
 
+#define BRCMFMAC_DISABLE	0
+#define BRCMFMAC_ENABLE		1
+#define BRCMFMAC_AUTO		2
+
+/* Keeping these macro definition here because these are defined in mmc drivers.
+ * So for 3rd party mmc, fmac build should not fail due to build error.
+ */
+
+/* SDIO IDLECLOCK Support - reusing pm_caps */
+#ifndef SDIO_IDLECLOCK_DIS
+#define SDIO_IDLECLOCK_DIS	BIT(2)	/* Start SDClock */
+#define SDIO_IDLECLOCK_EN	BIT(3)	/* Stop SDClock */
+#define SDIO_SDMODE_1BIT	BIT(4)	/* Set 1-bit Bus mode */
+#define SDIO_SDMODE_4BIT	BIT(5)	/* Set 4-bit Bus mode */
+#endif /* !SDIO_IDLECLOCK_DIS */
+
 /* Definitions for the module global and device specific settings are defined
  * here. Two structs are used for them. brcmf_mp_global_t and brcmf_mp_device.
  * The mp_global is instantiated once in a global struct and gets initialized
@@ -49,6 +65,7 @@ extern struct brcmf_mp_global_t brcmf_mp_global;
  * @country_codes: If available, pointer to struct for translating country codes
  * @bus: Bus specific platform data. Only SDIO at the mmoment.
  * @pkt_prio: Support customer dscp to WMM up mapping.
+ * @idleclk_disable: SDIO bus clock output disable when bus is idle.
  */
 struct brcmf_mp_device {
 	bool		p2p_enable;
@@ -77,6 +94,7 @@ struct brcmf_mp_device {
 		struct brcmfmac_sdio_pd sdio;
 	} bus;
 	bool		pkt_prio;
+	int			idleclk_disable;
 };
 
 /**
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
index e84ae4e8c378..fcaf8eadecce 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
@@ -18,6 +18,7 @@
 #include <linux/mmc/sdio_func.h>
 #include <linux/mmc/card.h>
 #include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
 #include <linux/semaphore.h>
 #include <linux/firmware.h>
 #include <linux/module.h>
@@ -340,6 +341,7 @@ struct rte_console {
 #define BRCMF_IDLE_ACTIVE	0	/* Do not request any SD clock change
 					 * when idle
 					 */
+#define BRCMF_IDLE_STOP		(-1)	/* Request SD clock be stopped */
 #define BRCMF_IDLE_INTERVAL	1
 
 #define KSO_WAIT_US 50
@@ -986,11 +988,64 @@ static int brcmf_sdio_htclk(struct brcmf_sdio *bus, bool on, bool pendok)
 	return 0;
 }
 
+/**
+ *	brcmf_sdio_set_sdbus_clk_width - set SD clock enable/disable and sd_mode
+ *	@func: SDIO function attached to host
+ *	@flags: reusing existing mmc->pm_flags to pass idle clk disable/enable or
+ *		change sdbus width through mmc.
+ */
+int brcmf_sdio_set_sdbus_clk_width(struct brcmf_sdio *bus, unsigned int flags)
+{
+	struct mmc_host *host;
+	u8 ctrl;
+	int ret = 0;
+
+	if (WARN_ON(!bus))
+		return -EINVAL;
+
+	host = bus->sdiodev->func1->card->host;
+
+	brcmf_dbg(SDIO, "Enter\n");
+
+	if (flags == SDIO_IDLECLOCK_DIS || flags == SDIO_IDLECLOCK_EN) {
+		/* Switch OFF/ON SD CLOCK in sdio Host Controller */
+		host->pm_caps |= flags;
+		/* Call SDHCI interface function from ops */
+		host->ops->set_ios(host, &host->ios);
+	} else if (flags == SDIO_SDMODE_1BIT || flags == SDIO_SDMODE_4BIT) {
+		ctrl = brcmf_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IF, &ret);
+		/* Check for Error */
+		if (ret)
+			return ret;
+
+		/* Clear first two bits
+		 * 00 - 1 bit wide
+		 * 10 - 4 bit wide
+		 */
+		ctrl &= ~SDIO_BUS_WIDTH_MASK;
+		/* set as 4-bit bus width */
+		if (flags == SDIO_SDMODE_4BIT)
+			ctrl |= SDIO_BUS_WIDTH_4BIT;
+
+		brcmf_sdiod_func0_wb(bus->sdiodev, SDIO_CCCR_IF, ctrl, &ret);
+		/* Update HOST CTRL register with 1 bit or 4 bit mode */
+		host->pm_caps |= flags;
+		/* Call SDHCI interface function from ops */
+		host->ops->set_ios(host, &host->ios);
+	}
+	return ret;
+}
+
 /* Change idle/active SD state */
 static int brcmf_sdio_sdclk(struct brcmf_sdio *bus, bool on)
 {
 	brcmf_dbg(SDIO, "Enter\n");
 
+	if (bus->idleclock == BRCMF_IDLE_STOP)
+		brcmf_sdio_set_sdbus_clk_width(bus, (on ?
+									   SDIO_IDLECLOCK_DIS :
+									   SDIO_IDLECLOCK_EN));
+
 	if (on)
 		bus->clkstate = CLK_SDONLY;
 	else
@@ -1124,9 +1179,24 @@ brcmf_sdio_bus_sleep(struct brcmf_sdio *bus, bool sleep, bool pendok)
 						   SBSDIO_FUNC1_CHIPCLKCSR,
 						   SBSDIO_ALP_AVAIL_REQ, &err);
 			}
+
+			if (bus->idleclock == BRCMF_IDLE_STOP)
+				brcmf_sdio_set_sdbus_clk_width(bus,
+							       SDIO_SDMODE_1BIT);
+
 			err = brcmf_sdio_kso_control(bus, false);
+
+			if (bus->idleclock == BRCMF_IDLE_STOP)
+				brcmf_sdio_sdclk(bus, false);
 		} else {
+			if (bus->idleclock == BRCMF_IDLE_STOP && bus->clkstate == CLK_NONE)
+				brcmf_sdio_clkctl(bus, CLK_SDONLY, false);
+
 			err = brcmf_sdio_kso_control(bus, true);
+
+			if (bus->idleclock == BRCMF_IDLE_STOP)
+				brcmf_sdio_set_sdbus_clk_width(bus,
+							       SDIO_SDMODE_4BIT);
 		}
 		if (err) {
 			brcmf_err("error while changing bus sleep state %d\n",
@@ -5191,6 +5261,22 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err,
 			bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_IDLE;
 	}
 
+	if (sdiod->settings->idleclk_disable == BRCMFMAC_DISABLE) {
+		bus->idleclock = BRCMF_IDLE_ACTIVE;
+	} else if (sdiod->settings->idleclk_disable == BRCMFMAC_ENABLE) {
+		bus->idleclock = BRCMF_IDLE_STOP;
+	} else if (sdiod->settings->idleclk_disable == BRCMFMAC_AUTO) {
+		if (sdiod->func1->device == SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012 ||
+		    sdiod->func1->device == SDIO_DEVICE_ID_BROADCOM_CYPRESS_43022 ||
+		    sdiod->func1->device == SDIO_DEVICE_ID_CYPRESS_43022) {
+			bus->idleclock = BRCMF_IDLE_STOP;
+		} else {
+			bus->idleclock = BRCMF_IDLE_ACTIVE;
+		}
+	} else {
+		brcmf_err("unexpected idleclk_disable%d\n", sdiod->settings->idleclk_disable);
+	}
+
 	/* ready */
 	return;
 
-- 
2.25.1

