diff mbox series

[02/20] staging: wfx: add support for I/O access

Message ID 20190919105153.15285-3-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, 10:52 a.m. UTC
From: Jérôme Pouiller <jerome.pouiller@silabs.com>

Introduce bus level communication layer. At this level, 7 registers can
be addressed.

Notice that SPI driver is able to manage chip reset. SDIO mode relies
on an external driver (`mmc-pwrseq`) to reset chip.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
---
 drivers/staging/wfx/bus.h      |  17 +++
 drivers/staging/wfx/bus_sdio.c | 189 ++++++++++++++++++++++-
 drivers/staging/wfx/bus_spi.c  | 271 ++++++++++++++++++++++++++++++++-
 drivers/staging/wfx/hwio.h     |  48 ++++++
 drivers/staging/wfx/main.c     |  53 +++++++
 drivers/staging/wfx/main.h     |  32 ++++
 drivers/staging/wfx/wfx.h      |  24 +++
 7 files changed, 632 insertions(+), 2 deletions(-)
 create mode 100644 drivers/staging/wfx/hwio.h
 create mode 100644 drivers/staging/wfx/main.h
 create mode 100644 drivers/staging/wfx/wfx.h

Comments

Andrew Lunn Sept. 19, 2019, 4:34 p.m. UTC | #1
On Thu, Sep 19, 2019 at 10:52:35AM +0000, Jerome Pouiller wrote:
> +static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
> +				 void *dst, size_t count)
> +{
> +	struct wfx_sdio_priv *bus = priv;
> +	unsigned int sdio_addr = reg_id << 2;
> +	int ret;
> +
> +	BUG_ON(reg_id > 7);

Hi Jerome

BUG_ON should only be used when the system is corrupted, and there is
no alternative than to stop the machine, so it does not further
corrupt itself. Accessing a register which does not exist is not a
reason the kill the machine. A WARN() and a return of -EINVAL would be
better.

	Andrew
Greg KH Sept. 19, 2019, 4:40 p.m. UTC | #2
On Thu, Sep 19, 2019 at 06:34:29PM +0200, Andrew Lunn wrote:
> On Thu, Sep 19, 2019 at 10:52:35AM +0000, Jerome Pouiller wrote:
> > +static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
> > +				 void *dst, size_t count)
> > +{
> > +	struct wfx_sdio_priv *bus = priv;
> > +	unsigned int sdio_addr = reg_id << 2;
> > +	int ret;
> > +
> > +	BUG_ON(reg_id > 7);
> 
> Hi Jerome
> 
> BUG_ON should only be used when the system is corrupted, and there is
> no alternative than to stop the machine, so it does not further
> corrupt itself. Accessing a register which does not exist is not a
> reason the kill the machine. A WARN() and a return of -EINVAL would be
> better.

dev_warn() is even better.

But that's one reason this is going into staging I guess :)

thanks,

greg k-h
Jérôme Pouiller Oct. 2, 2019, 4:29 p.m. UTC | #3
On Thursday 19 September 2019 18:34:48 CEST Andrew Lunn wrote:
> On Thu, Sep 19, 2019 at 10:52:35AM +0000, Jerome Pouiller wrote:
> > +static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
> > +                              void *dst, size_t count)
> > +{
> > +     struct wfx_sdio_priv *bus = priv;
> > +     unsigned int sdio_addr = reg_id << 2;
> > +     int ret;
> > +
> > +     BUG_ON(reg_id > 7);
> 
> Hi Jerome
> 
> BUG_ON should only be used when the system is corrupted, and there is
> no alternative than to stop the machine, so it does not further
> corrupt itself. Accessing a register which does not exist is not a
> reason the kill the machine. A WARN() and a return of -EINVAL would be
> better.

Hi Andrew,

I did not forget your suggestion. However, if everyone is agree with that, I'd 
prefer to address it in a next pull request. Indeed, I'd prefer to keep this 
version in sync with version 2.3.1 published on github.
Greg KH Oct. 2, 2019, 4:42 p.m. UTC | #4
On Wed, Oct 02, 2019 at 04:29:09PM +0000, Jerome Pouiller wrote:
> On Thursday 19 September 2019 18:34:48 CEST Andrew Lunn wrote:
> > On Thu, Sep 19, 2019 at 10:52:35AM +0000, Jerome Pouiller wrote:
> > > +static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
> > > +                              void *dst, size_t count)
> > > +{
> > > +     struct wfx_sdio_priv *bus = priv;
> > > +     unsigned int sdio_addr = reg_id << 2;
> > > +     int ret;
> > > +
> > > +     BUG_ON(reg_id > 7);
> > 
> > Hi Jerome
> > 
> > BUG_ON should only be used when the system is corrupted, and there is
> > no alternative than to stop the machine, so it does not further
> > corrupt itself. Accessing a register which does not exist is not a
> > reason the kill the machine. A WARN() and a return of -EINVAL would be
> > better.
> 
> Hi Andrew,
> 
> I did not forget your suggestion. However, if everyone is agree with that, I'd 
> prefer to address it in a next pull request. Indeed, I'd prefer to keep this 
> version in sync with version 2.3.1 published on github.

Ugh, you aren't doing development outside of the kernel tree and
expecting things to stay in sync somehow are you?  That way lies madness
and a sure way to get me to just delete the staging driver.  Just work
on it in-tree please.

thanks,

greg k-h
Jérôme Pouiller Oct. 2, 2019, 4:51 p.m. UTC | #5
On Wednesday 2 October 2019 18:42:14 CEST Greg Kroah-Hartman wrote:
> On Wed, Oct 02, 2019 at 04:29:09PM +0000, Jerome Pouiller wrote:
[...]
> >
> > Hi Andrew,
> >
> > I did not forget your suggestion. However, if everyone is agree with that, I'd
> > prefer to address it in a next pull request. Indeed, I'd prefer to keep this
> > version in sync with version 2.3.1 published on github.
> 
> Ugh, you aren't doing development outside of the kernel tree and
> expecting things to stay in sync somehow are you?  That way lies madness
> and a sure way to get me to just delete the staging driver.  Just work
> on it in-tree please.

Sure, I will just work in-tree. But, I think that if someone want to
follow history, it is easier if it exists a version exactly identical
in kernel and on github.
diff mbox series

Patch

diff --git a/drivers/staging/wfx/bus.h b/drivers/staging/wfx/bus.h
index 8ce871a8a9ff..eb77abc09ec2 100644
--- a/drivers/staging/wfx/bus.h
+++ b/drivers/staging/wfx/bus.h
@@ -11,6 +11,23 @@ 
 #include <linux/mmc/sdio_func.h>
 #include <linux/spi/spi.h>
 
+#define WFX_REG_CONFIG        0x0
+#define WFX_REG_CONTROL       0x1
+#define WFX_REG_IN_OUT_QUEUE  0x2
+#define WFX_REG_AHB_DPORT     0x3
+#define WFX_REG_BASE_ADDR     0x4
+#define WFX_REG_SRAM_DPORT    0x5
+#define WFX_REG_SET_GEN_R_W   0x6
+#define WFX_REG_FRAME_OUT     0x7
+
+struct hwbus_ops {
+	int (*copy_from_io)(void *bus_priv, unsigned int addr, void *dst, size_t count);
+	int (*copy_to_io)(void *bus_priv, unsigned int addr, const void *src, size_t count);
+	void (*lock)(void *bus_priv);
+	void (*unlock)(void *bus_priv);
+	size_t (*align_size)(void *bus_priv, size_t size);
+};
+
 extern struct sdio_driver wfx_sdio_driver;
 extern struct spi_driver wfx_spi_driver;
 
diff --git a/drivers/staging/wfx/bus_sdio.c b/drivers/staging/wfx/bus_sdio.c
index 4b26c994f43c..35bcca7ec5dc 100644
--- a/drivers/staging/wfx/bus_sdio.c
+++ b/drivers/staging/wfx/bus_sdio.c
@@ -8,36 +8,223 @@ 
 #include <linux/module.h>
 #include <linux/mmc/sdio_func.h>
 #include <linux/mmc/card.h>
+#include <linux/interrupt.h>
 #include <linux/of_irq.h>
 
 #include "bus.h"
+#include "wfx.h"
+#include "hwio.h"
+#include "main.h"
+
+static const struct wfx_platform_data wfx_sdio_pdata = {
+};
+
+struct wfx_sdio_priv {
+	struct sdio_func *func;
+	struct wfx_dev *core;
+	u8 buf_id_tx;
+	u8 buf_id_rx;
+	int of_irq;
+};
+
+static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
+				 void *dst, size_t count)
+{
+	struct wfx_sdio_priv *bus = priv;
+	unsigned int sdio_addr = reg_id << 2;
+	int ret;
+
+	BUG_ON(reg_id > 7);
+	WARN(((uintptr_t) dst) & 3, "unaligned buffer size");
+	WARN(count & 3, "unaligned buffer address");
+
+	/* Use queue mode buffers */
+	if (reg_id == WFX_REG_IN_OUT_QUEUE)
+		sdio_addr |= (bus->buf_id_rx + 1) << 7;
+	ret = sdio_memcpy_fromio(bus->func, dst, sdio_addr, count);
+	if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
+		bus->buf_id_rx = (bus->buf_id_rx + 1) % 4;
+
+	return ret;
+}
+
+static int wfx_sdio_copy_to_io(void *priv, unsigned int reg_id,
+			       const void *src, size_t count)
+{
+	struct wfx_sdio_priv *bus = priv;
+	unsigned int sdio_addr = reg_id << 2;
+	int ret;
+
+	BUG_ON(reg_id > 7);
+	WARN(((uintptr_t) src) & 3, "unaligned buffer size");
+	WARN(count & 3, "unaligned buffer address");
+
+	/* Use queue mode buffers */
+	if (reg_id == WFX_REG_IN_OUT_QUEUE)
+		sdio_addr |= bus->buf_id_tx << 7;
+	// FIXME: discards 'const' qualifier for src
+	ret = sdio_memcpy_toio(bus->func, sdio_addr, (void *) src, count);
+	if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
+		bus->buf_id_tx = (bus->buf_id_tx + 1) % 32;
+
+	return ret;
+}
+
+static void wfx_sdio_lock(void *priv)
+{
+	struct wfx_sdio_priv *bus = priv;
+
+	sdio_claim_host(bus->func);
+}
+
+static void wfx_sdio_unlock(void *priv)
+{
+	struct wfx_sdio_priv *bus = priv;
+
+	sdio_release_host(bus->func);
+}
+
+static void wfx_sdio_irq_handler(struct sdio_func *func)
+{
+	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
+
+	if (bus->core)
+		/* empty */;
+	else
+		WARN(!bus->core, "race condition in driver init/deinit");
+}
+
+static irqreturn_t wfx_sdio_irq_handler_ext(int irq, void *priv)
+{
+	struct wfx_sdio_priv *bus = priv;
+
+	if (!bus->core) {
+		WARN(!bus->core, "race condition in driver init/deinit");
+		return IRQ_NONE;
+	}
+	sdio_claim_host(bus->func);
+	sdio_release_host(bus->func);
+	return IRQ_HANDLED;
+}
+
+static int wfx_sdio_irq_subscribe(struct wfx_sdio_priv *bus)
+{
+	int ret;
+
+	if (bus->of_irq) {
+		ret = request_irq(bus->of_irq, wfx_sdio_irq_handler_ext,
+				  IRQF_TRIGGER_RISING, "wfx", bus);
+	} else {
+		sdio_claim_host(bus->func);
+		ret = sdio_claim_irq(bus->func, wfx_sdio_irq_handler);
+		sdio_release_host(bus->func);
+	}
+	return ret;
+}
+
+static int wfx_sdio_irq_unsubscribe(struct wfx_sdio_priv *bus)
+{
+	int ret;
+
+	if (bus->of_irq) {
+		free_irq(bus->of_irq, bus);
+		ret = 0;
+	} else {
+		sdio_claim_host(bus->func);
+		ret = sdio_release_irq(bus->func);
+		sdio_release_host(bus->func);
+	}
+	return ret;
+}
+
+static size_t wfx_sdio_align_size(void *priv, size_t size)
+{
+	struct wfx_sdio_priv *bus = priv;
+
+	return sdio_align_size(bus->func, size);
+}
+
+static const struct hwbus_ops wfx_sdio_hwbus_ops = {
+	.copy_from_io = wfx_sdio_copy_from_io,
+	.copy_to_io = wfx_sdio_copy_to_io,
+	.lock			= wfx_sdio_lock,
+	.unlock			= wfx_sdio_unlock,
+	.align_size		= wfx_sdio_align_size,
+};
 
 static const struct of_device_id wfx_sdio_of_match[];
 static int wfx_sdio_probe(struct sdio_func *func,
 			  const struct sdio_device_id *id)
 {
 	struct device_node *np = func->dev.of_node;
+	struct wfx_sdio_priv *bus;
+	int ret;
 
 	if (func->num != 1) {
 		dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n", func->num);
 		return -ENODEV;
 	}
 
+	bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+
 	if (np) {
 		if (!of_match_node(wfx_sdio_of_match, np)) {
 			dev_warn(&func->dev, "no compatible device found in DT\n");
 			return -ENODEV;
 		}
+		bus->of_irq = irq_of_parse_and_map(np, 0);
 	} else {
 		dev_warn(&func->dev, "device is not declared in DT, features will be limited\n");
 		// FIXME: ignore VID/PID and only rely on device tree
 		// return -ENODEV;
 	}
-	return -EIO; // FIXME: not yet supported
+
+	bus->func = func;
+	sdio_set_drvdata(func, bus);
+	func->card->quirks |= MMC_QUIRK_LENIENT_FN0 | MMC_QUIRK_BLKSZ_FOR_BYTE_MODE | MMC_QUIRK_BROKEN_BYTE_MODE_512;
+
+	sdio_claim_host(func);
+	ret = sdio_enable_func(func);
+	// Block of 64 bytes is more efficient than 512B for frame sizes < 4k
+	sdio_set_block_size(func, 64);
+	sdio_release_host(func);
+	if (ret)
+		goto err0;
+
+	ret = wfx_sdio_irq_subscribe(bus);
+	if (ret)
+		goto err1;
+
+	bus->core = wfx_init_common(&func->dev, &wfx_sdio_pdata,
+				    &wfx_sdio_hwbus_ops, bus);
+	if (!bus->core) {
+		ret = -EIO;
+		goto err2;
+	}
+
+	return 0;
+
+err2:
+	wfx_sdio_irq_unsubscribe(bus);
+err1:
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	sdio_release_host(func);
+err0:
+	return ret;
 }
 
 static void wfx_sdio_remove(struct sdio_func *func)
 {
+	struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
+
+	wfx_free_common(bus->core);
+	wfx_sdio_irq_unsubscribe(bus);
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	sdio_release_host(func);
 }
 
 #define SDIO_VENDOR_ID_SILABS        0x0000
diff --git a/drivers/staging/wfx/bus_spi.c b/drivers/staging/wfx/bus_spi.c
index 574b60f513e9..b311ff72cf80 100644
--- a/drivers/staging/wfx/bus_spi.c
+++ b/drivers/staging/wfx/bus_spi.c
@@ -7,19 +7,288 @@ 
  * Copyright (c) 2010, ST-Ericsson
  */
 #include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
 #include <linux/spi/spi.h>
+#include <linux/interrupt.h>
 #include <linux/of.h>
 
 #include "bus.h"
+#include "wfx.h"
+#include "hwio.h"
+#include "main.h"
+
+#define DETECT_INVALID_CTRL_ACCESS
+
+static int gpio_reset = -2;
+module_param(gpio_reset, int, 0644);
+MODULE_PARM_DESC(gpio_reset, "gpio number for reset. -1 for none.");
+
+#define SET_WRITE 0x7FFF        /* usage: and operation */
+#define SET_READ 0x8000         /* usage: or operation */
+
+static const struct wfx_platform_data wfx_spi_pdata = {
+};
+
+struct wfx_spi_priv {
+	struct spi_device *func;
+	struct wfx_dev *core;
+	struct gpio_desc *gpio_reset;
+	struct work_struct request_rx;
+	bool need_swab;
+};
+
+/*
+ * Read of control register need a particular attention because it should be
+ * done only after an IRQ raise. We can detect if this event happens by reading
+ * control register twice (it is safe to read twice since we can garantee that
+ * no data acess was done since IRQ raising). In add, this function optimize it
+ * by doing only one SPI request.
+ */
+#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE)
+static int wfx_spi_read_ctrl_reg(struct wfx_spi_priv *bus, u16 *dst)
+{
+	int i, ret = 0;
+	u16 tx_buf[4] = { };
+	u16 rx_buf[4] = { };
+	u16 tmp[2] = { };
+	struct spi_message m;
+	struct spi_transfer t = {
+		.rx_buf = rx_buf,
+		.tx_buf = tx_buf,
+		.len = sizeof(tx_buf),
+	};
+	u16 regaddr = (WFX_REG_CONTROL << 12) | (sizeof(u16) / 2) | SET_READ;
+
+	cpu_to_le16s(regaddr);
+	if (bus->need_swab)
+		swab16s(&regaddr);
+
+	tx_buf[0] = tx_buf[2] = regaddr;
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+	for (i = 0, tmp[0] = tmp[1] + 1; tmp[0] != tmp[1] && i < 3; i++) {
+		ret = spi_sync(bus->func, &m);
+		// Changes of gpio-wakeup can occur during control register
+		// access. In this case, CTRL_WLAN_READY may differs.
+		tmp[0] = rx_buf[1] & cpu_to_le16(~CTRL_WLAN_READY);
+		tmp[1] = rx_buf[3] & cpu_to_le16(~CTRL_WLAN_READY);
+	}
+	if (tmp[0] != tmp[1])
+		ret = -ETIMEDOUT;
+	else if (i > 1)
+		dev_info(bus->core->dev, "success read after %d failures\n", i - 1);
+
+	*dst = rx_buf[1];
+	return ret;
+}
+#endif
+
+/*
+ * WFx chip read data 16bits at time and place them directly into (little
+ * endian) CPU register. So, chip expect byte order like "B1 B0 B3 B2" (while
+ * LE is "B0 B1 B2 B3" and BE is "B3 B2 B1 B0")
+ *
+ * A little endian host with bits_per_word == 16 should do the right job
+ * natively. The code below to support big endian host and commonly used SPI
+ * 8bits.
+ */
+static int wfx_spi_copy_from_io(void *priv, unsigned int addr,
+				void *dst, size_t count)
+{
+	struct wfx_spi_priv *bus = priv;
+	u16 regaddr = (addr << 12) | (count / 2) | SET_READ;
+	struct spi_message      m;
+	struct spi_transfer     t_addr = {
+		.tx_buf         = &regaddr,
+		.len            = sizeof(regaddr),
+	};
+	struct spi_transfer     t_msg = {
+		.rx_buf         = dst,
+		.len            = count,
+	};
+	u16 *dst16 = dst;
+#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE)
+	u8 *dst8 = dst;
+#endif
+	int ret, i;
+
+	WARN(count % 2, "buffer size must be a multiple of 2");
+
+#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE)
+	/* Some SPI driver (and especially Raspberry one) have race conditions
+	 * during SPI transfers. It impact last byte of transfer. Work around
+	 * bellow try to detect and solve them.
+	 * See https://github.com/raspberrypi/linux/issues/2200
+	 */
+	if (addr == WFX_REG_IN_OUT_QUEUE)
+		dst8[count - 1] = 0xFF;
+#endif
+
+	cpu_to_le16s(&regaddr);
+	if (bus->need_swab)
+		swab16s(&regaddr);
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t_addr, &m);
+	spi_message_add_tail(&t_msg, &m);
+	ret = spi_sync(bus->func, &m);
+
+#if (KERNEL_VERSION(4, 19, 14) > LINUX_VERSION_CODE)
+	/* If last byte has not been overwritten, read ctrl_reg manually
+	 */
+	if (addr == WFX_REG_IN_OUT_QUEUE && !ret && dst8[count - 1] == 0xFF) {
+		dev_warn(bus->core->dev, "SPI DMA error detected (and resolved)\n");
+		ret = wfx_spi_read_ctrl_reg(bus, (u16 *) (dst8 + count - 2));
+	}
+#endif
+
+	if (bus->need_swab && addr == WFX_REG_CONFIG)
+		for (i = 0; i < count / 2; i++)
+			swab16s(&dst16[i]);
+	return ret;
+}
+
+static int wfx_spi_copy_to_io(void *priv, unsigned int addr,
+			      const void *src, size_t count)
+{
+	struct wfx_spi_priv *bus = priv;
+	u16 regaddr = (addr << 12) | (count / 2);
+	// FIXME: use a bounce buffer
+	u16 *src16 = (void *) src;
+	int ret, i;
+	struct spi_message      m;
+	struct spi_transfer     t_addr = {
+		.tx_buf         = &regaddr,
+		.len            = sizeof(regaddr),
+	};
+	struct spi_transfer     t_msg = {
+		.tx_buf         = src,
+		.len            = count,
+	};
+
+	WARN(count % 2, "buffer size must be a multiple of 2");
+	WARN(regaddr & SET_READ, "bad addr or size overflow");
+
+	cpu_to_le16s(&regaddr);
+
+	if (bus->need_swab)
+		swab16s(&regaddr);
+	if (bus->need_swab && addr == WFX_REG_CONFIG)
+		for (i = 0; i < count / 2; i++)
+			swab16s(&src16[i]);
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t_addr, &m);
+	spi_message_add_tail(&t_msg, &m);
+	ret = spi_sync(bus->func, &m);
+
+	if (bus->need_swab && addr == WFX_REG_CONFIG)
+		for (i = 0; i < count / 2; i++)
+			swab16s(&src16[i]);
+	return ret;
+}
+
+static void wfx_spi_lock(void *priv)
+{
+}
+
+static void wfx_spi_unlock(void *priv)
+{
+}
+
+static irqreturn_t wfx_spi_irq_handler(int irq, void *priv)
+{
+	struct wfx_spi_priv *bus = priv;
+
+	if (!bus->core) {
+		WARN(!bus->core, "race condition in driver init/deinit");
+		return IRQ_NONE;
+	}
+	queue_work(system_highpri_wq, &bus->request_rx);
+	return IRQ_HANDLED;
+}
+
+static void wfx_spi_request_rx(struct work_struct *work)
+{
+}
+
+static size_t wfx_spi_align_size(void *priv, size_t size)
+{
+	// Most of SPI controllers avoid DMA if buffer size is not 32bit aligned
+	return ALIGN(size, 4);
+}
+
+static const struct hwbus_ops wfx_spi_hwbus_ops = {
+	.copy_from_io = wfx_spi_copy_from_io,
+	.copy_to_io = wfx_spi_copy_to_io,
+	.lock			= wfx_spi_lock,
+	.unlock			= wfx_spi_unlock,
+	.align_size		= wfx_spi_align_size,
+};
 
 static int wfx_spi_probe(struct spi_device *func)
 {
-	return -EIO;
+	struct wfx_spi_priv *bus;
+	int ret;
+
+	if (!func->bits_per_word)
+		func->bits_per_word = 16;
+	ret = spi_setup(func);
+	if (ret)
+		return ret;
+	// Trace below is also displayed by spi_setup() if compiled with DEBUG
+	dev_dbg(&func->dev, "SPI params: CS=%d, mode=%d bits/word=%d speed=%d\n",
+		func->chip_select, func->mode, func->bits_per_word, func->max_speed_hz);
+	if (func->bits_per_word != 16 && func->bits_per_word != 8)
+		dev_warn(&func->dev, "unusual bits/word value: %d\n", func->bits_per_word);
+	if (func->max_speed_hz > 49000000)
+		dev_warn(&func->dev, "%dHz is a very high speed\n", func->max_speed_hz);
+
+	bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
+	if (!bus)
+		return -ENOMEM;
+	bus->func = func;
+	if (func->bits_per_word == 8 || IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+		bus->need_swab = true;
+	spi_set_drvdata(func, bus);
+
+	bus->gpio_reset = wfx_get_gpio(&func->dev, gpio_reset, "reset");
+	if (!bus->gpio_reset) {
+		dev_warn(&func->dev, "try to load firmware anyway\n");
+	} else {
+		gpiod_set_value(bus->gpio_reset, 0);
+		udelay(100);
+		gpiod_set_value(bus->gpio_reset, 1);
+		udelay(2000);
+	}
+
+	ret = devm_request_irq(&func->dev, func->irq, wfx_spi_irq_handler,
+			       IRQF_TRIGGER_RISING, "wfx", bus);
+	if (ret)
+		return ret;
+
+	INIT_WORK(&bus->request_rx, wfx_spi_request_rx);
+	bus->core = wfx_init_common(&func->dev, &wfx_spi_pdata,
+				    &wfx_spi_hwbus_ops, bus);
+	if (!bus->core)
+		return -EIO;
+
+	return ret;
 }
 
 /* Disconnect Function to be called by SPI stack when device is disconnected */
 static int wfx_spi_disconnect(struct spi_device *func)
 {
+	struct wfx_spi_priv *bus = spi_get_drvdata(func);
+
+	wfx_free_common(bus->core);
+	// A few IRQ will be sent during device release. Hopefully, no IRQ
+	// should happen after wdev/wvif are released.
+	devm_free_irq(&func->dev, func->irq, bus);
+	flush_work(&bus->request_rx);
 	return 0;
 }
 
diff --git a/drivers/staging/wfx/hwio.h b/drivers/staging/wfx/hwio.h
new file mode 100644
index 000000000000..c490014c1df8
--- /dev/null
+++ b/drivers/staging/wfx/hwio.h
@@ -0,0 +1,48 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Low-level API.
+ *
+ * Copyright (c) 2017-2018, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ */
+#ifndef WFX_HWIO_H
+#define WFX_HWIO_H
+
+#define CFG_ERR_SPI_FRAME          0x00000001 // only with SPI
+#define CFG_ERR_SDIO_BUF_MISMATCH  0x00000001 // only with SDIO
+#define CFG_ERR_BUF_UNDERRUN       0x00000002
+#define CFG_ERR_DATA_IN_TOO_LARGE  0x00000004
+#define CFG_ERR_HOST_NO_OUT_QUEUE  0x00000008
+#define CFG_ERR_BUF_OVERRUN        0x00000010
+#define CFG_ERR_DATA_OUT_TOO_LARGE 0x00000020
+#define CFG_ERR_HOST_NO_IN_QUEUE   0x00000040
+#define CFG_ERR_HOST_CRC_MISS      0x00000080 // only with SDIO
+#define CFG_SPI_IGNORE_CS          0x00000080 // only with SPI
+#define CFG_WORD_MODE_MASK         0x00000300 // Bytes ordering (only writable in SPI):
+#define     CFG_WORD_MODE0         0x00000000 //   B1,B0,B3,B2 (In SPI, register address and CONFIG data always use this mode)
+#define     CFG_WORD_MODE1         0x00000100 //   B3,B2,B1,B0
+#define     CFG_WORD_MODE2         0x00000200 //   B0,B1,B2,B3 (SDIO)
+#define CFG_DIRECT_ACCESS_MODE     0x00000400 // Direct or queue access mode
+#define CFG_PREFETCH_AHB           0x00000800
+#define CFG_DISABLE_CPU_CLK        0x00001000
+#define CFG_PREFETCH_SRAM          0x00002000
+#define CFG_CPU_RESET              0x00004000
+#define CFG_SDIO_DISABLE_IRQ       0x00008000 // only with SDIO
+#define CFG_IRQ_ENABLE_DATA        0x00010000
+#define CFG_IRQ_ENABLE_WRDY        0x00020000
+#define CFG_CLK_RISE_EDGE          0x00040000
+#define CFG_SDIO_DISABLE_CRC_CHK   0x00080000 // only with SDIO
+#define CFG_RESERVED               0x00F00000
+#define CFG_DEVICE_ID_MAJOR        0x07000000
+#define CFG_DEVICE_ID_RESERVED     0x78000000
+#define CFG_DEVICE_ID_TYPE         0x80000000
+
+#define CTRL_NEXT_LEN_MASK   0x00000FFF
+#define CTRL_WLAN_WAKEUP     0x00001000
+#define CTRL_WLAN_READY      0x00002000
+
+#define IGPR_RW          0x80000000
+#define IGPR_INDEX       0x7F000000
+#define IGPR_VALUE       0x00FFFFFF
+
+#endif /* WFX_HWIO_H */
diff --git a/drivers/staging/wfx/main.c b/drivers/staging/wfx/main.c
index cd69f955f531..744445ef597c 100644
--- a/drivers/staging/wfx/main.c
+++ b/drivers/staging/wfx/main.c
@@ -11,10 +11,15 @@ 
  * Copyright (c) 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
  */
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
 #include <linux/mmc/sdio_func.h>
 #include <linux/spi/spi.h>
 #include <linux/etherdevice.h>
 
+#include "main.h"
+#include "wfx.h"
 #include "bus.h"
 #include "wfx_version.h"
 
@@ -23,6 +28,54 @@  MODULE_AUTHOR("Jérôme Pouiller <jerome.pouiller@silabs.com>");
 MODULE_LICENSE("GPL");
 MODULE_VERSION(WFX_LABEL);
 
+struct gpio_desc *wfx_get_gpio(struct device *dev, int override, const char *label)
+{
+	struct gpio_desc *ret;
+	char label_buf[256];
+
+	if (override >= 0) {
+		snprintf(label_buf, sizeof(label_buf), "wfx_%s", label);
+		ret = ERR_PTR(devm_gpio_request_one(dev, override, GPIOF_OUT_INIT_LOW, label_buf));
+		if (!ret)
+			ret = gpio_to_desc(override);
+	} else if (override == -1) {
+		ret = NULL;
+	} else {
+		ret = devm_gpiod_get(dev, label, GPIOD_OUT_LOW);
+	}
+	if (IS_ERR(ret) || !ret) {
+		if (!ret || PTR_ERR(ret) == -ENOENT)
+			dev_warn(dev, "gpio %s is not defined\n", label);
+		else
+			dev_warn(dev, "error while requesting gpio %s\n", label);
+		ret = NULL;
+	} else {
+		dev_dbg(dev, "using gpio %d for %s\n", desc_to_gpio(ret), label);
+	}
+	return ret;
+}
+
+struct wfx_dev *wfx_init_common(struct device *dev,
+				const struct wfx_platform_data *pdata,
+				const struct hwbus_ops *hwbus_ops,
+				void *hwbus_priv)
+{
+	struct wfx_dev *wdev;
+
+	wdev = devm_kmalloc(dev, sizeof(*wdev), GFP_KERNEL);
+	if (!wdev)
+		return NULL;
+	wdev->dev = dev;
+	wdev->hwbus_ops = hwbus_ops;
+	wdev->hwbus_priv = hwbus_priv;
+	memcpy(&wdev->pdata, pdata, sizeof(*pdata));
+	return wdev;
+}
+
+void wfx_free_common(struct wfx_dev *wdev)
+{
+}
+
 static int __init wfx_core_init(void)
 {
 	int ret = 0;
diff --git a/drivers/staging/wfx/main.h b/drivers/staging/wfx/main.h
new file mode 100644
index 000000000000..82222edf998b
--- /dev/null
+++ b/drivers/staging/wfx/main.h
@@ -0,0 +1,32 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Device probe and register.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ */
+#ifndef WFX_MAIN_H
+#define WFX_MAIN_H
+
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+
+#include "bus.h"
+
+struct wfx_dev;
+
+struct wfx_platform_data {
+};
+
+struct wfx_dev *wfx_init_common(struct device *dev,
+				const struct wfx_platform_data *pdata,
+				const struct hwbus_ops *hwbus_ops,
+				void *hwbus_priv);
+void wfx_free_common(struct wfx_dev *wdev);
+
+struct gpio_desc *wfx_get_gpio(struct device *dev, int override,
+			       const char *label);
+
+#endif
diff --git a/drivers/staging/wfx/wfx.h b/drivers/staging/wfx/wfx.h
new file mode 100644
index 000000000000..9716acc981df
--- /dev/null
+++ b/drivers/staging/wfx/wfx.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Common private data for Silicon Labs WFx chips.
+ *
+ * Copyright (c) 2017-2019, Silicon Laboratories, Inc.
+ * Copyright (c) 2010, ST-Ericsson
+ * Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
+ * Copyright 2004-2006 Jean-Baptiste Note <jbnote@gmail.com>, et al.
+ */
+#ifndef WFX_H
+#define WFX_H
+
+#include "main.h"
+
+struct hwbus_ops;
+
+struct wfx_dev {
+	struct wfx_platform_data pdata;
+	struct device		*dev;
+	const struct hwbus_ops	*hwbus_ops;
+	void			*hwbus_priv;
+};
+
+#endif /* WFX_H */