@@ -46,3 +46,6 @@ config WILC1000_HW_OOB_INTR
mechanism for SDIO host controllers that don't support SDIO interrupt.
Select this option If the SDIO host controller in your platform
doesn't support SDIO time division interrupt.
+
+config WILC_BT
+ bool
@@ -9,6 +9,7 @@ wilc1000-objs := \
wlan.o \
wlan_cfg.o
+wilc1000-$(CONFIG_WILC_BT) += bt.o
obj-$(CONFIG_WILC1000_SDIO) += wilc1000-sdio.o
wilc1000-sdio-objs += sdio.o
new file mode 100644
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dev_printk.h>
+#include <linux/mutex.h>
+#include <linux/firmware.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <net/wilc.h>
+#include "netdev.h"
+#include "wlan_if.h"
+#include "wlan.h"
+
+#define FW_WILC3000_BLE "mchp/wilc3000_ble_firmware.bin"
+
+static int wilc_bt_power_down(struct wilc *wilc)
+{
+ int ret;
+
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, GLOBAL_MODE_CONTROL, BIT(1), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable BT mode\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ return ret;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, COE_AUTO_PS_ON_NULL_PKT,
+ BIT(30), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable awake coexistence null frames\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ return ret;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, COE_AUTO_PS_OFF_NULL_PKT,
+ BIT(30), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable doze coexistence null frames\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ return ret;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, PWR_SEQ_MISC_CTRL, BIT(29), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable bluetooth wake-up\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ return ret;
+ }
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+
+ if (!wilc->initialized) {
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_ONLY);
+ ret = wilc->hif_func->hif_deinit(wilc);
+ release_bus(wilc, WILC_BUS_RELEASE_ONLY);
+ }
+
+ return 0;
+}
+
+static int wilc_bt_power_up(struct wilc *wilc)
+{
+ int ret;
+
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
+ if (!wilc->initialized) {
+ ret = wilc->hif_func->hif_rmw_reg(wilc, COE_AUTO_PS_ON_NULL_PKT,
+ BIT(30), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable awake coexistence null frames\n");
+ goto fail;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc,
+ COE_AUTO_PS_OFF_NULL_PKT, BIT(30), 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable awake coexistence null frames\n");
+ goto fail;
+ }
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, PWR_SEQ_MISC_CTRL, BIT(29),
+ BIT(29));
+ if (ret) {
+ dev_err(wilc->dev, "Failed to enable bluetooth wake-up\n");
+ goto fail;
+ }
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+
+ return 0;
+
+fail:
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ wilc_bt_power_down(wilc);
+ return ret;
+}
+
+static int wilc_bt_firmware_download(struct wilc *wilc)
+{
+ const struct firmware *wilc_bt_firmware;
+ u32 addr, size, size2, blksz;
+ size_t buffer_size;
+ const u8 *buffer;
+ u8 *dma_buffer;
+ u32 offset;
+ int ret = 0;
+
+ dev_info(wilc->dev, "Bluetooth firmware: %s\n", FW_WILC3000_BLE);
+ ret = request_firmware(&wilc_bt_firmware, FW_WILC3000_BLE, wilc->dev);
+ if (ret) {
+ dev_err(wilc->dev, "%s - firmware not available. Skip!\n",
+ FW_WILC3000_BLE);
+ return ret;
+ }
+
+ buffer = wilc_bt_firmware->data;
+ buffer_size = (size_t)wilc_bt_firmware->size;
+ if (buffer_size <= 0) {
+ dev_err(wilc->dev, "Firmware size = 0!\n");
+ ret = -EINVAL;
+ goto out_release_firmware;
+ }
+
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
+
+ ret = wilc->hif_func->hif_write_reg(wilc, WILC_BT_BOOTROM_CONFIGURATION,
+ WILC_BT_BOOTROM_DISABLE);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable BT bootrom\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ goto out_release_firmware;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, WILC_BT_RESET_MUX,
+ WILC_BT_ENABLE_GLOBAL_RESET,
+ WILC_BT_ENABLE_GLOBAL_RESET);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to configure reset for BT CPU\n");
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+ goto out_release_firmware;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, WILC_BT_CPU_CONFIGURATION,
+ WILC_BT_CPU_ENABLE,
+ WILC_BT_CPU_ENABLE);
+ if (!ret)
+ ret = wilc->hif_func->hif_rmw_reg(wilc,
+ WILC_BT_CPU_CONFIGURATION,
+ WILC_BT_CPU_ENABLE, 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable BT CPU\n");
+ goto out_release_firmware;
+ }
+
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+
+ /* blocks of sizes > 512 causes the wifi to hang! */
+ blksz = (1ul << 9);
+ /* Allocate a DMA coherent buffer. */
+ dma_buffer = kmalloc(blksz, GFP_KERNEL);
+ if (!dma_buffer) {
+ ret = -ENOMEM;
+ dev_err(wilc->dev,
+ "Can't allocate buffer for BT firmware download\n");
+ goto out_free_buffer;
+ }
+ dev_info(wilc->dev, "Downloading BT firmware size = %zu ...\n",
+ buffer_size);
+
+ offset = 0;
+ addr = WILC_BT_IRAM;
+ size = buffer_size;
+ offset = 0;
+
+ while (((int)size) && (offset < buffer_size)) {
+ if (size <= blksz)
+ size2 = size;
+ else
+ size2 = blksz;
+
+ /* Copy firmware into a DMA coherent buffer */
+ memcpy(dma_buffer, &buffer[offset], size2);
+
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
+
+ ret = wilc->hif_func->hif_block_tx(wilc, addr, dma_buffer,
+ size2);
+
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+
+ if (ret)
+ break;
+
+ addr += size2;
+ offset += size2;
+ size -= size2;
+ }
+
+ if (ret) {
+ dev_err(wilc->dev, "Failed to download BT firmware\n");
+ goto out_free_buffer;
+ }
+ dev_info(wilc->dev, "Finished downloading firmware\n");
+
+out_free_buffer:
+ kfree(dma_buffer);
+out_release_firmware:
+ release_firmware(wilc_bt_firmware);
+return ret;
+}
+
+static int wilc_bt_start(struct wilc *wilc)
+{
+ int ret;
+
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
+
+ dev_info(wilc->dev, "Starting BT firmware\n");
+ /*
+ * Write the firmware download complete magic at
+ * location 0xFFFF000C (Cortus map) or C000C (AHB map).
+ * This will let the boot-rom code execute from RAM.
+ */
+ ret = wilc->hif_func->hif_write_reg(wilc, WILC_BT_FW_MAGIC_LOC,
+ WILC_BT_FW_MAGIC);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to write BT firmware magic\n");
+ return ret;
+ }
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, WILC_BT_CPU_CONFIGURATION,
+ WILC_BT_CPU_BOOT, 0);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to disable BT CPU");
+ return ret;
+ }
+
+ msleep(100);
+
+ ret = wilc->hif_func->hif_rmw_reg(wilc, WILC_BT_CPU_CONFIGURATION,
+ WILC_BT_CPU_BOOT, WILC_BT_CPU_BOOT);
+ if (ret) {
+ dev_err(wilc->dev, "Failed to enable BT CPU");
+ return ret;
+ }
+ /* An additional wait to give BT firmware time to do
+ * CPLL update as the time measured since the start of
+ * BT FW till the end of function "rf_nmi_init_tuner"
+ * was 71.2 ms
+ */
+ msleep(100);
+
+ dev_info(wilc->dev, "BT Start Succeeded\n");
+
+ release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
+
+ return 0;
+}
+
+int wilc_bt_init(void *wilc_wl_priv)
+{
+ struct wilc *wilc = (struct wilc *)wilc_wl_priv;
+ int ret;
+
+ if (!wilc->hif_func->hif_is_init(wilc)) {
+ dev_info(wilc->dev, "Initializing bus before starting BT");
+ acquire_bus(wilc, WILC_BUS_ACQUIRE_ONLY);
+ ret = wilc->hif_func->hif_init(wilc, false);
+ release_bus(wilc, WILC_BUS_RELEASE_ONLY);
+ if (ret)
+ return ret;
+ }
+
+ mutex_lock(&wilc->radio_fw_start);
+ ret = wilc_bt_power_up(wilc);
+ if (ret) {
+ dev_err(wilc->dev, "Error powering up bluetooth chip\n");
+ goto hif_deinit;
+ }
+ ret = wilc_bt_firmware_download(wilc);
+ if (ret) {
+ dev_err(wilc->dev, "Error downloading firmware\n");
+ goto power_down;
+ }
+ ret = wilc_bt_start(wilc);
+ if (ret) {
+ dev_err(wilc->dev, "Error starting bluetooth firmware\n");
+ goto power_down;
+ }
+ mutex_unlock(&wilc->radio_fw_start);
+ return 0;
+
+power_down:
+ wilc_bt_power_down(wilc);
+hif_deinit:
+ mutex_unlock(&wilc->radio_fw_start);
+ if (!wilc->initialized)
+ wilc->hif_func->hif_deinit(wilc);
+ return ret;
+}
+EXPORT_SYMBOL(wilc_bt_init);
+
+int wilc_bt_shutdown(void *wilc_wl_priv)
+{
+ struct wilc *wilc = (struct wilc *)wilc_wl_priv;
+ int ret;
+
+ mutex_lock(&wilc->radio_fw_start);
+ ret = wilc->hif_func->hif_rmw_reg(wilc, WILC_BT_CPU_CONFIGURATION,
+ WILC_BT_CPU_ENABLE, 0);
+ if (ret)
+ dev_warn(wilc->dev, "Failed to disable BT CPU\n");
+ if (wilc_bt_power_down(wilc))
+ dev_warn(wilc->dev, "Failed to power down BT CPU\n");
+ if (!wilc->initialized)
+ wilc->hif_func->hif_deinit(wilc);
+ mutex_unlock(&wilc->radio_fw_start);
+
+ return 0;
+}
+EXPORT_SYMBOL(wilc_bt_shutdown);
@@ -10,6 +10,7 @@
#include <linux/firmware.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
+#include <net/wilc.h>
#include "cfg80211.h"
#include "wlan_cfg.h"
@@ -1024,6 +1025,14 @@ struct wilc_vif *wilc_netdev_ifc_init(struct wilc *wl, const char *name,
}
EXPORT_SYMBOL_GPL(wilc_netdev_ifc_init);
+void wilc_put(void *wilc_wl_priv)
+{
+ struct wilc *wilc = (struct wilc *)wilc_wl_priv;
+
+ put_device(wilc->dev);
+}
+EXPORT_SYMBOL_GPL(wilc_put);
+
MODULE_DESCRIPTION("Atmel WILC1000 core wireless driver");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(WILC1000_FW(WILC1000_API_VER));
@@ -5,11 +5,16 @@
*/
#include <linux/clk.h>
+#include "linux/device.h"
+#include "linux/device/driver.h"
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio.h>
#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <net/wilc.h>
#include "netdev.h"
#include "cfg80211.h"
@@ -1072,5 +1077,33 @@ static struct sdio_driver wilc_sdio_driver = {
};
module_sdio_driver(wilc_sdio_driver);
+static int find_wilc_device(struct device *dev, const void *data)
+{
+ struct device_node *target_node = (struct device_node *)data;
+ struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+
+ return func->card->dev.of_node == target_node ? 1 : 0;
+}
+
+void *wilc_sdio_get_byphandle(struct device_node *wlan_node)
+{
+ struct wilc *wilc;
+ struct device *wilc_dev;
+
+ /* Search in devices bound to the driver if any has a device_node
+ * matching the targeted one
+ */
+ wilc_dev = driver_find_device(&wilc_sdio_driver.drv, NULL,
+ (void *)wlan_node, find_wilc_device);
+ if (!wilc_dev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ get_device(wilc_dev);
+ wilc = (struct wilc *)dev_get_drvdata(wilc_dev);
+
+ return wilc;
+}
+EXPORT_SYMBOL(wilc_sdio_get_byphandle);
+
MODULE_DESCRIPTION("Atmel WILC1000 SDIO wireless driver");
MODULE_LICENSE("GPL");
@@ -4,11 +4,16 @@
* All rights reserved.
*/
+#include "linux/device/driver.h"
+#include "linux/of.h"
+#include "linux/of_platform.h"
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <linux/crc7.h>
#include <linux/crc-itu-t.h>
#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <net/wilc.h>
#include "netdev.h"
#include "cfg80211.h"
@@ -1390,3 +1395,23 @@ static const struct wilc_hif_func wilc_hif_spi = {
.hif_is_init = wilc_spi_is_init,
.hif_rmw_reg = wilc_spi_rmw_reg
};
+
+void *wilc_spi_get_byphandle(struct device_node *wlan_node)
+{
+ struct wilc *wilc;
+ struct device *wilc_dev;
+
+ /* Search in devices bound to the driver if any has a device_node
+ * matching the targeted one
+ */
+ wilc_dev = driver_find_device_by_of_node(&wilc_spi_driver.driver,
+ wlan_node);
+ if (!wilc_dev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ get_device(wilc_dev);
+ wilc = (struct wilc *)dev_get_drvdata(wilc_dev);
+
+ return wilc;
+}
+EXPORT_SYMBOL(wilc_spi_get_byphandle);
@@ -116,6 +116,21 @@
#define WILC_SPI_CLOCKLESS_ADDR_LIMIT 0x30
+#define WILC_BT_RESET_MUX 0x3b0090
+#define WILC_BT_ENABLE_GLOBAL_RESET BIT(0)
+
+#define WILC_BT_CPU_CONFIGURATION 0x3b0400
+#define WILC_BT_CPU_ENABLE BIT(2)
+#define WILC_BT_CPU_BOOT (BIT(3) | WILC_BT_CPU_ENABLE)
+
+#define WILC_BT_IRAM 0x400000
+
+#define WILC_BT_BOOTROM_CONFIGURATION 0x4f0000
+#define WILC_BT_BOOTROM_DISABLE 0x71
+
+#define WILC_BT_FW_MAGIC_LOC 0x4f000c
+#define WILC_BT_FW_MAGIC 0x10add09e
+
/* Functions IO enables bits */
#define WILC_SDIO_CCCR_IO_EN_FUNC1 BIT(1)
@@ -174,6 +189,8 @@
#define GLOBAL_MODE_CONTROL 0x1614
#define PWR_SEQ_MISC_CTRL 0x3008
+#define COE_AUTO_PS_ON_NULL_PKT 0x160468
+#define COE_AUTO_PS_OFF_NULL_PKT 0x16046C
#define WILC_GLOBAL_MODE_ENABLE_WIFI BIT(0)
#define WILC_PWR_SEQ_ENABLE_WIFI_SLEEP BIT(28)
new file mode 100644
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __WILC_HEADER_H
+#define __WILC_HEADER_H
+
+#include <linux/of.h>
+
+#if defined(CONFIG_WILC1000_SDIO) || defined(CONFIG_WILC1000_SDIO_MODULE)
+void *wilc_sdio_get_byphandle(struct device_node *wlan_node);
+#endif
+#if defined(CONFIG_WILC1000_SPI) || defined(CONFIG_WILC1000_SPI_MODULE)
+void *wilc_spi_get_byphandle(struct device_node *wlan_node);
+#endif
+void wilc_put(void *wilc_wl_priv);
+
+int wilc_bt_init(void *wilc_wl_priv);
+int wilc_bt_shutdown(void *wilc_wl_priv);
+
+#endif
Despite using a dedicated uart bus as main interface for bluetooth operations, WILC3000 needs some initialization to be done over the wlan bus (either SPI or SDIO) before being able to process HCI commands. Those operations mostly consists in: - some internal registers configuration - a bluetooth firmware download - bluetooth firmware start Some of those operations also need to consider whether wlan is running or not (eg: some coex registers need to be explicitly written if wlan is not already running) Add two set of APIs to fulfill those needs: - two getters (one for sdio, one for spi) to allow retrieving an opaque handle on wilc structure - some init/shutdown APIs, consuming this opaque wilc struct and actually performing the needed bluetooth initialization steps The new code in charge of bluetooth initialization is driven by a dedicated Kconfig, which will be selected by the new bluetooth driver Kconfig coming in the next commits. The wilc driver then exposes a few new functions to allow the future bluetooth driver to request the chip initialization: - wilc_<sdio|spi>_get_by_phandle : returns an opaque handle on the wilc structure - wilc_bt_init: use the handle returned previously and request bluetooth initialization - wilc_put: once done, give back the handle Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com> --- The bluetooth init sequence is heavily inspired from Microchip downstream kernel, see [1]. While the downstream kernel relies on user to trigger the bluetooth init sequence (through a chardev), this version is autonomous and supposed to be triggered by the actual bluetooth driver in the next commits. [1] https://github.com/linux4microchip/linux/blob/linux-6.6-mchp/drivers/net/wireless/microchip/wilc1000/bt.c --- drivers/net/wireless/microchip/wilc1000/Kconfig | 3 + drivers/net/wireless/microchip/wilc1000/Makefile | 1 + drivers/net/wireless/microchip/wilc1000/bt.c | 322 +++++++++++++++++++++++ drivers/net/wireless/microchip/wilc1000/netdev.c | 9 + drivers/net/wireless/microchip/wilc1000/sdio.c | 33 +++ drivers/net/wireless/microchip/wilc1000/spi.c | 25 ++ drivers/net/wireless/microchip/wilc1000/wlan.h | 17 ++ include/net/wilc.h | 19 ++ 8 files changed, 429 insertions(+)