diff mbox series

[v3,14/20] staging: wfx: setup initial chip configuration

Message ID 20190919142527.31797-15-Jerome.Pouiller@silabs.com
State Not Applicable
Delegated to: David Miller
Headers show
Series Add support for Silicon Labs WiFi chip WF200 and further | expand

Commit Message

Jérôme Pouiller Sept. 19, 2019, 2:25 p.m. UTC
From: Jérôme Pouiller <jerome.pouiller@silabs.com>

A few tasks remain to be done in order to finish chip initial
configuration:
   - configure chip to use multi-tx confirmation (speed up data
     transfer)
   - configure chip to use wake-up feature (save power consumption
     during runtime)
   - set hardware configuration (clocks, RF, pinout, etc...) using a
     Platform Data Set (PDS) file

On release, driver completely shutdown the chip to save power
consumption.

Documentation about PDS and PDS data for sample boards are available
here[1]. One day, PDS data may find a place in device tree but,
currently, PDS is too much linked with firmware to allowing that.

This patch also add "send_pds" file in debugfs to be able to dynamically
change PDS (only for debug, of course).

[1]: https://github.com/SiliconLabs/wfx-firmware/tree/master/PDS

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/bus_sdio.c |  1 +
 drivers/staging/wfx/bus_spi.c  |  1 +
 drivers/staging/wfx/debug.c    | 29 +++++++++++
 drivers/staging/wfx/hif_rx.c   | 11 ++++
 drivers/staging/wfx/main.c     | 94 ++++++++++++++++++++++++++++++++++
 drivers/staging/wfx/main.h     |  2 +
 6 files changed, 138 insertions(+)
diff mbox series

Patch

diff --git a/drivers/staging/wfx/bus_sdio.c b/drivers/staging/wfx/bus_sdio.c
index c0c063c3cfc9..05f02c278782 100644
--- a/drivers/staging/wfx/bus_sdio.c
+++ b/drivers/staging/wfx/bus_sdio.c
@@ -19,6 +19,7 @@ 
 
 static const struct wfx_platform_data wfx_sdio_pdata = {
 	.file_fw = "wfm_wf200",
+	.file_pds = "wf200.pds",
 };
 
 struct wfx_sdio_priv {
diff --git a/drivers/staging/wfx/bus_spi.c b/drivers/staging/wfx/bus_spi.c
index b7cd82b4e5e7..f65f7d75e731 100644
--- a/drivers/staging/wfx/bus_spi.c
+++ b/drivers/staging/wfx/bus_spi.c
@@ -29,6 +29,7 @@  MODULE_PARM_DESC(gpio_reset, "gpio number for reset. -1 for none.");
 
 static const struct wfx_platform_data wfx_spi_pdata = {
 	.file_fw = "wfm_wf200",
+	.file_pds = "wf200.pds",
 	.use_rising_clk = true,
 };
 
diff --git a/drivers/staging/wfx/debug.c b/drivers/staging/wfx/debug.c
index f79693a4be7f..0619c7d1cf79 100644
--- a/drivers/staging/wfx/debug.c
+++ b/drivers/staging/wfx/debug.c
@@ -10,6 +10,7 @@ 
 
 #include "debug.h"
 #include "wfx.h"
+#include "main.h"
 
 #define CREATE_TRACE_POINTS
 #include "traces.h"
@@ -54,6 +55,33 @@  const char *get_reg_name(unsigned long id)
 	return get_symbol(id, wfx_reg_print_map);
 }
 
+static ssize_t wfx_send_pds_write(struct file *file, const char __user *user_buf,
+			     size_t count, loff_t *ppos)
+{
+	struct wfx_dev *wdev = file->private_data;
+	char *buf;
+	int ret;
+
+	if (*ppos != 0) {
+		dev_dbg(wdev->dev, "PDS data must be written in one transaction");
+		return -EBUSY;
+	}
+	buf = memdup_user(user_buf, count);
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+	*ppos = *ppos + count;
+	ret = wfx_send_pds(wdev, buf, count);
+	kfree(buf);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static const struct file_operations wfx_send_pds_fops = {
+	.open = simple_open,
+	.write = wfx_send_pds_write,
+};
+
 static ssize_t wfx_burn_slk_key_write(struct file *file,
 				      const char __user *user_buf,
 				      size_t count, loff_t *ppos)
@@ -162,6 +190,7 @@  int wfx_debug_init(struct wfx_dev *wdev)
 	struct dentry *d;
 
 	d = debugfs_create_dir("wfx", wdev->hw->wiphy->debugfsdir);
+	debugfs_create_file("send_pds", 0200, d, wdev, &wfx_send_pds_fops);
 	debugfs_create_file("burn_slk_key", 0200, d, wdev, &wfx_burn_slk_key_fops);
 	debugfs_create_file("send_hif_msg", 0600, d, wdev, &wfx_send_hif_msg_fops);
 
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c
index dd5f1dea4e85..6b9683d69a3f 100644
--- a/drivers/staging/wfx/hif_rx.c
+++ b/drivers/staging/wfx/hif_rx.c
@@ -71,6 +71,16 @@  static int hif_startup_indication(struct wfx_dev *wdev, struct hif_msg *hif, voi
 	return 0;
 }
 
+static int hif_wakeup_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
+{
+	if (!wdev->pdata.gpio_wakeup
+	    || !gpiod_get_value(wdev->pdata.gpio_wakeup)) {
+		dev_warn(wdev->dev, "unexpected wake-up indication\n");
+		return -EIO;
+	}
+	return 0;
+}
+
 static int hif_keys_indication(struct wfx_dev *wdev, struct hif_msg *hif, void *buf)
 {
 	struct hif_ind_sl_exchange_pub_keys *body = buf;
@@ -89,6 +99,7 @@  static const struct {
 	int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf);
 } hif_handlers[] = {
 	{ HIF_IND_ID_STARTUP,              hif_startup_indication },
+	{ HIF_IND_ID_WAKEUP,               hif_wakeup_indication },
 	{ HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication },
 };
 
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index 0cfd6b2ec8d1..5b04ea5f4353 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -18,6 +18,7 @@ 
 #include <linux/mmc/sdio_func.h>
 #include <linux/spi/spi.h>
 #include <linux/etherdevice.h>
+#include <linux/firmware.h>
 
 #include "main.h"
 #include "wfx.h"
@@ -28,9 +29,12 @@ 
 #include "sta.h"
 #include "debug.h"
 #include "secure_link.h"
+#include "hif_tx_mib.h"
 #include "hif_api_cmd.h"
 #include "wfx_version.h"
 
+#define WFX_PDS_MAX_SIZE 1500
+
 MODULE_DESCRIPTION("Silicon Labs 802.11 Wireless LAN driver for WFx");
 MODULE_AUTHOR("Jérôme Pouiller <jerome.pouiller@silabs.com>");
 MODULE_LICENSE("GPL");
@@ -112,6 +116,69 @@  static void wfx_fill_sl_key(struct device *dev, struct wfx_platform_data *pdata)
 	dev_err(dev, "secure link is not supported by this driver, ignoring provided key\n");
 }
 
+/* NOTE: wfx_send_pds() destroy buf */
+int wfx_send_pds(struct wfx_dev *wdev, unsigned char *buf, size_t len)
+{
+	int ret;
+	int start, brace_level, i;
+
+	start = 0;
+	brace_level = 0;
+	if (buf[0] != '{') {
+		dev_err(wdev->dev, "valid PDS start with '{'. Did you forget to compress it?\n");
+		return -EINVAL;
+	}
+	for (i = 1; i < len - 1; i++) {
+		if (buf[i] == '{')
+			brace_level++;
+		if (buf[i] == '}')
+			brace_level--;
+		if (buf[i] == '}' && !brace_level) {
+			i++;
+			if (i - start + 1 > WFX_PDS_MAX_SIZE)
+				return -EFBIG;
+			buf[start] = '{';
+			buf[i] = 0;
+			dev_dbg(wdev->dev, "send PDS '%s}'\n", buf + start);
+			buf[i] = '}';
+			ret = hif_configuration(wdev, buf + start, i - start + 1);
+			if (ret == HIF_STATUS_FAILURE) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: invalid data (unsupported options?)\n", start, i);
+				return -EINVAL;
+			}
+			if (ret == -ETIMEDOUT) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: chip didn't reply (corrupted file?)\n", start, i);
+				return ret;
+			}
+			if (ret) {
+				dev_err(wdev->dev, "PDS bytes %d to %d: chip returned an unknown error\n", start, i);
+				return -EIO;
+			}
+			buf[i] = ',';
+			start = i;
+		}
+	}
+	return 0;
+}
+
+static int wfx_send_pdata_pds(struct wfx_dev *wdev)
+{
+	int ret = 0;
+	const struct firmware *pds;
+	unsigned char *tmp_buf;
+
+	ret = request_firmware(&pds, wdev->pdata.file_pds, wdev->dev);
+	if (ret) {
+		dev_err(wdev->dev, "can't load PDS file %s\n", wdev->pdata.file_pds);
+		return ret;
+	}
+	tmp_buf = kmemdup(pds->data, pds->size, GFP_KERNEL);
+	ret = wfx_send_pds(wdev, tmp_buf, pds->size);
+	kfree(tmp_buf);
+	release_firmware(pds);
+	return ret;
+}
+
 struct wfx_dev *wfx_init_common(struct device *dev,
 				const struct wfx_platform_data *pdata,
 				const struct hwbus_ops *hwbus_ops,
@@ -141,6 +208,8 @@  struct wfx_dev *wfx_init_common(struct device *dev,
 	wdev->hwbus_ops = hwbus_ops;
 	wdev->hwbus_priv = hwbus_priv;
 	memcpy(&wdev->pdata, pdata, sizeof(*pdata));
+	of_property_read_string(dev->of_node, "config-file", &wdev->pdata.file_pds);
+	wdev->pdata.gpio_wakeup = wfx_get_gpio(dev, gpio_wakeup, "wakeup");
 	wfx_fill_sl_key(dev, &wdev->pdata);
 
 	init_completion(&wdev->firmware_ready);
@@ -159,6 +228,12 @@  int wfx_probe(struct wfx_dev *wdev)
 	int i;
 	int err;
 	const void *macaddr;
+	struct gpio_desc *gpio_saved;
+
+	// During first part of boot, gpio_wakeup cannot yet been used. So
+	// prevent bh() to touch it.
+	gpio_saved = wdev->pdata.gpio_wakeup;
+	wdev->pdata.gpio_wakeup = NULL;
 
 	wfx_bh_register(wdev);
 
@@ -202,6 +277,24 @@  int wfx_probe(struct wfx_dev *wdev)
 		goto err1;
 	}
 
+	dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds);
+	err = wfx_send_pdata_pds(wdev);
+	if (err < 0)
+		goto err1;
+
+	wdev->pdata.gpio_wakeup = gpio_saved;
+	if (wdev->pdata.gpio_wakeup) {
+		dev_dbg(wdev->dev, "enable 'quiescent' power mode with gpio %d and PDS file %s\n",
+			desc_to_gpio(wdev->pdata.gpio_wakeup), wdev->pdata.file_pds);
+		gpiod_set_value(wdev->pdata.gpio_wakeup, 1);
+		control_reg_write(wdev, 0);
+		hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_QUIESCENT);
+	} else {
+		hif_set_operational_mode(wdev, HIF_OP_POWER_MODE_DOZE);
+	}
+
+	hif_use_multi_tx_conf(wdev, true);
+
 	for (i = 0; i < ARRAY_SIZE(wdev->addresses); i++) {
 		eth_zero_addr(wdev->addresses[i].addr);
 		macaddr = of_get_mac_address(wdev->dev->of_node);
@@ -232,6 +325,7 @@  int wfx_probe(struct wfx_dev *wdev)
 
 void wfx_release(struct wfx_dev *wdev)
 {
+	hif_shutdown(wdev);
 	wfx_bh_unregister(wdev);
 	wfx_sl_deinit(wdev);
 }
diff --git a/drivers/staging/wfx/main.h b/drivers/staging/wfx/main.h
index 2c9c215455ce..f2b07ed1627c 100644
--- a/drivers/staging/wfx/main.h
+++ b/drivers/staging/wfx/main.h
@@ -21,6 +21,7 @@  struct wfx_dev;
 struct wfx_platform_data {
 	/* Keyset and ".sec" extention will appended to this string */
 	const char *file_fw;
+	const char *file_pds;
 	unsigned char slk_key[API_KEY_VALUE_SIZE];
 	struct gpio_desc *gpio_wakeup;
 	/*
@@ -42,5 +43,6 @@  void wfx_release(struct wfx_dev *wdev);
 struct gpio_desc *wfx_get_gpio(struct device *dev, int override,
 			       const char *label);
 bool wfx_api_older_than(struct wfx_dev *wdev, int major, int minor);
+int wfx_send_pds(struct wfx_dev *wdev, unsigned char *buf, size_t len);
 
 #endif