diff --git a/drivers/cyw43/README.md b/drivers/cyw43/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5af6f655806681f5ba3cc0feb03c18e5819389ff
--- /dev/null
+++ b/drivers/cyw43/README.md
@@ -0,0 +1,17 @@
+CYW43xx WiFi SoC driver
+=======================
+
+This is a driver for the CYW43xx WiFi SoC.
+
+There are four layers to the driver:
+
+1. SDIO bus interface, provided by the host device/system.
+
+2. Low-level CYW43xx interface, managing the bus, control messages, Ethernet
+   frames and asynchronous events.  Includes download of SoC firmware.  The
+   header file `cyw43_ll.h` defines the interface to this layer.
+
+3. Mid-level CYW43xx control, to control and set WiFi parameters and manage
+   events.  See `cyw43_ctrl.c`.
+
+4. TCP/IP bindings to lwIP.  See `cyw43_lwip.c`.
diff --git a/drivers/cyw43/cyw43.h b/drivers/cyw43/cyw43.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7f08cb5dfeed21d5015ab3e083e0c71f230ab23
--- /dev/null
+++ b/drivers/cyw43/cyw43.h
@@ -0,0 +1,135 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2019 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_STM32_CYW43_H
+#define MICROPY_INCLUDED_STM32_CYW43_H
+
+#include "lwip/netif.h"
+#include "lwip/dhcp.h"
+#include "lib/netutils/dhcpserver.h"
+#include "drivers/cyw43/cyw43_ll.h"
+
+// For trace_flags
+#define CYW43_TRACE_ASYNC_EV    (0x0001)
+#define CYW43_TRACE_ETH_TX      (0x0002)
+#define CYW43_TRACE_ETH_RX      (0x0004)
+#define CYW43_TRACE_ETH_FULL    (0x0008)
+#define CYW43_TRACE_MAC         (0x0010)
+
+// Return value of cyw43_wifi_link_status
+#define CYW43_LINK_DOWN         (0)
+#define CYW43_LINK_JOIN         (1)
+#define CYW43_LINK_NOIP         (2)
+#define CYW43_LINK_UP           (3)
+#define CYW43_LINK_FAIL         (-1)
+#define CYW43_LINK_NONET        (-2)
+#define CYW43_LINK_BADAUTH      (-3)
+
+typedef struct _cyw43_t {
+    cyw43_ll_t cyw43_ll;
+
+    uint8_t itf_state;
+    uint32_t trace_flags;
+
+    // State for async events
+    volatile uint32_t wifi_scan_state;
+    uint32_t wifi_join_state;
+    void *wifi_scan_env;
+    int (*wifi_scan_cb)(void*, const cyw43_ev_scan_result_t*);
+
+    // Pending things to do
+    bool pend_disassoc;
+    bool pend_rejoin;
+    bool pend_rejoin_wpa;
+
+    // AP settings
+    uint8_t ap_channel;
+    uint8_t ap_auth;
+    uint8_t ap_ssid_len;
+    uint8_t ap_key_len;
+    uint8_t ap_ssid[32];
+    uint8_t ap_key[64];
+
+    // lwIP data
+    struct netif netif[2];
+    struct dhcp dhcp_client;
+    dhcp_server_t dhcp_server;
+} cyw43_t;
+
+extern cyw43_t cyw43_state;
+extern void (*cyw43_poll)(void);
+extern uint32_t cyw43_sleep;
+
+void cyw43_init(cyw43_t *self);
+void cyw43_deinit(cyw43_t *self);
+
+int cyw43_ioctl(cyw43_t *self, uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface);
+int cyw43_send_ethernet(cyw43_t *self, int itf, size_t len, const void *buf, bool is_pbuf);
+
+int cyw43_wifi_pm(cyw43_t *self, uint32_t pm);
+int cyw43_wifi_link_status(cyw43_t *self, int itf);
+void cyw43_wifi_set_up(cyw43_t *self, int itf, bool up);
+int cyw43_wifi_get_mac(cyw43_t *self, int itf, uint8_t mac[6]);
+int cyw43_wifi_scan(cyw43_t *self, cyw43_wifi_scan_options_t *opts, void *env, int (*result_cb)(void*, const cyw43_ev_scan_result_t*));
+
+static inline bool cyw43_wifi_scan_active(cyw43_t *self) {
+    return self->wifi_scan_state == 1;
+}
+
+int cyw43_wifi_join(cyw43_t *self, size_t ssid_len, const uint8_t *ssid, size_t key_len, const uint8_t *key, uint32_t auth_type, const uint8_t *bssid, uint32_t channel);
+int cyw43_wifi_leave(cyw43_t *self, int itf);
+
+static inline void cyw43_wifi_ap_get_ssid(cyw43_t *self, size_t *len, const uint8_t **buf) {
+    *len = self->ap_ssid_len;
+    *buf = self->ap_ssid;
+}
+
+static inline void cyw43_wifi_ap_set_channel(cyw43_t *self, uint32_t channel) {
+    self->ap_channel = channel;
+}
+
+static inline void cyw43_wifi_ap_set_ssid(cyw43_t *self, size_t len, const uint8_t *buf) {
+    self->ap_ssid_len = MIN(len, sizeof(self->ap_ssid));
+    memcpy(self->ap_ssid, buf, self->ap_ssid_len);
+}
+
+static inline void cyw43_wifi_ap_set_password(cyw43_t *self, size_t len, const uint8_t *buf) {
+    self->ap_key_len = MIN(len, sizeof(self->ap_key));
+    memcpy(self->ap_key, buf, self->ap_key_len);
+}
+
+static inline void cyw43_wifi_ap_set_auth(cyw43_t *self, uint32_t auth) {
+    self->ap_auth = auth;
+}
+
+void cyw43_wifi_ap_get_stas(cyw43_t *self, int *num_stas, uint8_t *macs);
+
+void cyw43_tcpip_init(cyw43_t *self, int itf);
+void cyw43_tcpip_deinit(cyw43_t *self, int itf);
+void cyw43_tcpip_set_link_up(cyw43_t *self, int itf);
+void cyw43_tcpip_set_link_down(cyw43_t *self, int itf);
+int cyw43_tcpip_link_status(cyw43_t *self, int itf);
+
+#endif // MICROPY_INCLUDED_STM32_CYW43_H
diff --git a/drivers/cyw43/cyw43_ctrl.c b/drivers/cyw43/cyw43_ctrl.c
new file mode 100644
index 0000000000000000000000000000000000000000..3a8bbf8646f40532824f41cb17f0dd4ea49988c8
--- /dev/null
+++ b/drivers/cyw43/cyw43_ctrl.c
@@ -0,0 +1,588 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2019 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "py/mphal.h"
+#include "drivers/cyw43/cyw43.h"
+#include "pendsv.h"
+#include "sdio.h"
+
+#define CYW_ENTER MICROPY_PY_LWIP_ENTER
+#define CYW_EXIT MICROPY_PY_LWIP_EXIT
+
+#ifdef pyb_pin_WL_HOST_WAKE
+#define USE_SDIOIT (0)
+#else
+#define USE_SDIOIT (1)
+#endif
+
+#define CYW43_SLEEP_MAX (50)
+
+#define WIFI_JOIN_STATE_ACTIVE  (0x0001)
+#define WIFI_JOIN_STATE_FAIL    (0x0002)
+#define WIFI_JOIN_STATE_NONET   (0x0003)
+#define WIFI_JOIN_STATE_BADAUTH (0x0004)
+#define WIFI_JOIN_STATE_AUTH    (0x0200)
+#define WIFI_JOIN_STATE_LINK    (0x0400)
+#define WIFI_JOIN_STATE_KEYED   (0x0800)
+#define WIFI_JOIN_STATE_ALL     (0x0e01)
+
+cyw43_t cyw43_state;
+void (*cyw43_poll)(void);
+uint32_t cyw43_sleep;
+
+STATIC void cyw43_poll_func(void);
+STATIC void cyw43_wifi_ap_init(cyw43_t *self);
+STATIC void cyw43_wifi_ap_set_up(cyw43_t *self, bool up);
+
+static inline uint32_t cyw43_get_be16(const uint8_t *buf) {
+    return buf[0] << 8 | buf[1];
+}
+
+static inline uint32_t cyw43_get_be32(const uint8_t *buf) {
+    return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
+}
+
+static inline void cyw43_delay_ms(uint32_t ms) {
+    mp_hal_delay_ms(ms);
+}
+
+/*******************************************************************************/
+// Initialisation and polling
+
+void cyw43_init(cyw43_t *self) {
+    #ifdef pyb_pin_WL_HOST_WAKE
+    mp_hal_pin_config(pyb_pin_WL_HOST_WAKE, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_NONE, 0);
+    #endif
+    mp_hal_pin_config(pyb_pin_WL_REG_ON, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0);
+    mp_hal_pin_low(pyb_pin_WL_REG_ON);
+    #ifdef pyb_pin_WL_RFSW_VDD
+    mp_hal_pin_config(pyb_pin_WL_RFSW_VDD, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power
+    mp_hal_pin_low(pyb_pin_WL_RFSW_VDD);
+    #endif
+
+    cyw43_ll_init(&self->cyw43_ll, self);
+
+    self->itf_state = 0;
+    self->wifi_scan_state = 0;
+    self->wifi_join_state = 0;
+    self->pend_disassoc = false;
+    self->pend_rejoin= false;
+    self->pend_rejoin_wpa = false;
+    self->ap_channel = 3;
+    self->ap_ssid_len = 0;
+    self->ap_key_len = 0;
+
+    cyw43_poll = NULL;
+}
+
+void cyw43_deinit(cyw43_t *self) {
+    CYW_ENTER
+
+    cyw43_ll_bus_sleep(&self->cyw43_ll, true);
+    cyw43_delay_ms(2);
+    cyw43_tcpip_deinit(self, 0);
+    cyw43_tcpip_deinit(self, 1);
+
+    self->itf_state = 0;
+
+    // Disable async polling
+    SDMMC1->MASK &= ~SDMMC_MASK_SDIOITIE;
+    cyw43_poll = NULL;
+
+    #ifdef pyb_pin_WL_RFSW_VDD
+    // Turn the RF-switch off
+    mp_hal_pin_low(pyb_pin_WL_RFSW_VDD);
+    #endif
+
+    // Power down the WL chip and the SDIO bus
+    mp_hal_pin_low(pyb_pin_WL_REG_ON);
+    sdio_deinit();
+
+    CYW_EXIT
+}
+
+STATIC int cyw43_ensure_up(cyw43_t *self) {
+    if (cyw43_poll != NULL) {
+        cyw43_ll_bus_sleep(&self->cyw43_ll, false);
+        return 0;
+    }
+
+    CYW_ENTER
+
+    // Disable the netif if it was previously up
+    cyw43_tcpip_deinit(self, CYW43_ITF_STA);
+    cyw43_tcpip_deinit(self, CYW43_ITF_AP);
+    self->itf_state = 0;
+
+    // Reset and power up the WL chip
+    mp_hal_pin_low(pyb_pin_WL_REG_ON);
+    cyw43_delay_ms(20);
+    mp_hal_pin_high(pyb_pin_WL_REG_ON);
+    cyw43_delay_ms(50);
+
+    // Initialise SDIO bus
+    // IRQ priority only needs to be higher than CYW_ENTER/EXIT protection (PENDSV)
+    sdio_init(NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 14, 0));
+
+    // Initialise the low-level driver
+    uint8_t mac[6];
+    mp_hal_get_mac(MP_HAL_MAC_WLAN0, mac);
+    int ret = cyw43_ll_bus_init(&self->cyw43_ll, mac);
+
+    if (ret != 0) {
+        CYW_EXIT
+        return ret;
+    }
+
+    // Enable async events from low-level driver
+    cyw43_sleep = CYW43_SLEEP_MAX;
+    cyw43_poll = cyw43_poll_func;
+    #if USE_SDIOIT
+    SDMMC1->MASK |= SDMMC_MASK_SDIOITIE;
+    #else
+    extern void extint_set(const pin_obj_t *pin, uint32_t mode);
+    extint_set(pyb_pin_WL_HOST_WAKE, GPIO_MODE_IT_FALLING);
+    #endif
+
+    CYW_EXIT
+
+    return ret;
+}
+
+// This function must always be executed at the level where CYW_ENTER is effectively active
+STATIC void cyw43_poll_func(void) {
+    if (cyw43_poll == NULL) {
+        // Poll scheduled during deinit, just ignore it
+        return;
+    }
+
+    cyw43_t *self = &cyw43_state;
+    cyw43_ll_process_packets(&self->cyw43_ll);
+
+    if (self->pend_disassoc) {
+        self->pend_disassoc = false;
+        cyw43_ll_ioctl(&self->cyw43_ll, CYW43_IOCTL_SET_DISASSOC, 0, NULL, CYW43_ITF_STA);
+    }
+
+    if (self->pend_rejoin_wpa) {
+        self->pend_rejoin_wpa = false;
+        cyw43_ll_wifi_set_wpa_auth(&self->cyw43_ll);
+    }
+
+    if (self->pend_rejoin) {
+        self->pend_rejoin = false;
+        cyw43_ll_wifi_rejoin(&self->cyw43_ll);
+        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;
+    }
+
+    if (cyw43_sleep == 0) {
+        cyw43_ll_bus_sleep(&self->cyw43_ll, true);
+        #if !USE_SDIOIT
+        sdio_deinit(); // save power while WLAN bus sleeps
+        #endif
+    }
+
+    #if USE_SDIOIT
+    SDMMC1->MASK |= SDMMC_MASK_SDIOITIE;
+    #endif
+}
+
+/*******************************************************************************/
+// Callback interface to low-level driver
+
+int cyw43_cb_read_host_interrupt_pin(void *cb_data) {
+    #ifdef pyb_pin_WL_HOST_WAKE
+    return mp_hal_pin_read(pyb_pin_WL_HOST_WAKE);
+    #else
+    return mp_hal_pin_read(pyb_pin_WL_SDIO_1);
+    #endif
+}
+
+void cyw43_cb_ensure_awake(void *cb_data) {
+    cyw43_sleep = CYW43_SLEEP_MAX;
+    #if !USE_SDIOIT
+    if (__HAL_RCC_SDMMC1_IS_CLK_DISABLED()) {
+        __HAL_RCC_SDMMC1_CLK_ENABLE(); // enable SDIO peripheral
+        sdio_enable_high_speed_4bit();
+    }
+    #endif
+}
+
+STATIC const char *cyw43_async_event_name_table[89] = {
+    [0 ... 88] = NULL,
+    [CYW43_EV_SET_SSID] = "SET_SSID",
+    [CYW43_EV_JOIN] = "JOIN",
+    [CYW43_EV_AUTH] = "AUTH",
+    [CYW43_EV_DEAUTH_IND] = "DEAUTH_IND",
+    [CYW43_EV_ASSOC] = "ASSOC",
+    [CYW43_EV_DISASSOC] = "DISASSOC",
+    [CYW43_EV_DISASSOC_IND] = "DISASSOC_IND",
+    [CYW43_EV_LINK] = "LINK",
+    [CYW43_EV_PSK_SUP] = "PSK_SUP",
+    [CYW43_EV_ESCAN_RESULT] = "ESCAN_RESULT",
+    [CYW43_EV_CSA_COMPLETE_IND] = "CSA_COMPLETE_IND",
+    [CYW43_EV_ASSOC_REQ_IE] = "ASSOC_REQ_IE",
+    [CYW43_EV_ASSOC_RESP_IE] = "ASSOC_RESP_IE",
+};
+
+STATIC void cyw43_dump_async_event(const cyw43_async_event_t *ev) {
+    printf("[% 8d] ASYNC(%04x,",
+        mp_hal_ticks_ms(),
+        (unsigned int)ev->flags
+    );
+    if (ev->event_type < MP_ARRAY_SIZE(cyw43_async_event_name_table)
+        && cyw43_async_event_name_table[ev->event_type] != NULL) {
+        printf("%s", cyw43_async_event_name_table[ev->event_type]);
+    } else {
+        printf("%u", (unsigned int)ev->event_type);
+    }
+    printf(",%u,%u,%u)\n",
+        (unsigned int)ev->status,
+        (unsigned int)ev->reason,
+        (unsigned int)ev->interface
+    );
+}
+
+void cyw43_cb_process_async_event(void *cb_data, const cyw43_async_event_t *ev) {
+    cyw43_t *self = cb_data;
+
+    if (self->trace_flags & CYW43_TRACE_ASYNC_EV) {
+        cyw43_dump_async_event(ev);
+    }
+
+    if (ev->event_type == CYW43_EV_ESCAN_RESULT && self->wifi_scan_state == 1) {
+        // Escan result event
+        if (ev->status == 8) {
+            // Partial result
+            int ret = self->wifi_scan_cb(self->wifi_scan_env, &ev->u.scan_result);
+            if (ret != 0) {
+                // TODO need to abort scan, or just ignore any more results
+            }
+        } else if (ev->status == 0) {
+            // Scan complete
+            self->wifi_scan_state = 2;
+        }
+
+    } else if (ev->event_type == CYW43_EV_DISASSOC) {
+        cyw43_tcpip_set_link_down(self, CYW43_ITF_STA);
+        self->wifi_join_state = 0x0000;
+
+    /*
+    } else if (ev->event_type == CYW43_EV_DISASSOC_IND) {
+        if (ev->interface == CYW43_ITF_AP) {
+            // Station disassociated with our AP, let DHCP server know so it can free the IP address
+            dhcp_server_disassoc(&self->dhcp_server, buf + 24);
+        }
+    */
+
+    // WiFi join events
+    } else if (ev->event_type == CYW43_EV_PRUNE) {
+        if (ev->status == 0 && ev->reason == 8) {
+            // RSN mismatch, retry join with WPA auth
+            self->pend_rejoin = true;
+            self->pend_rejoin_wpa = true;
+            pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll_func);
+        }
+    } else if (ev->event_type == CYW43_EV_SET_SSID) {
+        if (ev->status == 0) {
+            // Success setting SSID
+        } else if (ev->status == 3 && ev->reason == 0) {
+            self->wifi_join_state = WIFI_JOIN_STATE_NONET;
+            // No matching SSID found (could be out of range, or down)
+        } else {
+            // Other failure setting SSID
+            self->wifi_join_state = WIFI_JOIN_STATE_FAIL;
+        }
+    } else if (ev->event_type == CYW43_EV_AUTH) {
+        if (ev->status == 0) {
+            self->wifi_join_state |= WIFI_JOIN_STATE_AUTH;
+        } else if (ev->status == 6) {
+            // Unsolicited auth packet, ignore it
+        } else {
+            // Cannot authenticate
+            self->wifi_join_state = WIFI_JOIN_STATE_BADAUTH;
+        }
+    } else if (ev->event_type == CYW43_EV_DEAUTH_IND) {
+        if (ev->status == 0 && ev->reason == 2) {
+            // Deauth, probably because password was wrong; disassociate
+            self->pend_disassoc = true;
+            pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll_func);
+        }
+    } else if (ev->event_type == CYW43_EV_LINK) {
+        if (ev->status == 0) {
+            if (ev->flags & 1) {
+                // Link is up
+                if (ev->interface == CYW43_ITF_STA) {
+                    self->wifi_join_state |= WIFI_JOIN_STATE_LINK;
+                } else {
+                    cyw43_tcpip_set_link_up(self, ev->interface);
+                }
+            } else {
+                // Link is down
+                cyw43_tcpip_set_link_down(self, ev->interface);
+            }
+        }
+    } else if (ev->event_type == CYW43_EV_PSK_SUP) {
+        if (ev->status == 6) { // WLC_SUP_KEYED
+            self->wifi_join_state |= WIFI_JOIN_STATE_KEYED;
+        } else if ((ev->status == 4 || ev->status == 8 || ev->status == 11) && ev->reason == 15) {
+            // Timeout waiting for key exchange M1/M3/G1
+            // Probably at edge of the cell, retry
+            self->pend_rejoin = true;
+            pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll_func);
+        } else {
+            // PSK_SUP failure
+            self->wifi_join_state = WIFI_JOIN_STATE_BADAUTH;
+        }
+    }
+
+    if (self->wifi_join_state == WIFI_JOIN_STATE_ALL) {
+        // STA connected
+        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;
+        cyw43_tcpip_set_link_up(self, CYW43_ITF_STA);
+    }
+}
+
+/*******************************************************************************/
+// Ioctl and Ethernet interface
+
+int cyw43_ioctl(cyw43_t *self, uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return ret;
+    }
+
+    CYW_ENTER
+    ret = cyw43_ll_ioctl(&self->cyw43_ll, cmd, len, buf, iface);
+    CYW_EXIT
+
+    return ret;
+}
+
+int cyw43_send_ethernet(cyw43_t *self, int itf, size_t len, const void *buf, bool is_pbuf) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return ret;
+    }
+
+    CYW_ENTER
+    ret = cyw43_ll_send_ethernet(&self->cyw43_ll, itf, len, buf, is_pbuf);
+    CYW_EXIT
+
+    return ret;
+}
+
+/*******************************************************************************/
+// WiFi control
+
+STATIC int cyw43_wifi_on(cyw43_t *self, uint32_t country) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return ret;
+    }
+
+    #ifdef pyb_pin_WL_RFSW_VDD
+    // Turn the RF-switch on
+    mp_hal_pin_high(pyb_pin_WL_RFSW_VDD);
+    #endif
+
+    CYW_ENTER
+    ret = cyw43_ll_wifi_on(&self->cyw43_ll, country);
+    CYW_EXIT
+
+    return ret;
+}
+
+int cyw43_wifi_pm(cyw43_t *self, uint32_t pm_in) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return ret;
+    }
+
+    // pm_in: 0x00adbrrm
+    uint32_t pm = pm_in & 0xf;
+    uint32_t pm_sleep_ret = (pm_in >> 4) & 0xff;
+    uint32_t li_bcn = (pm_in >> 12) & 0xf;
+    uint32_t li_dtim = (pm_in >> 16) & 0xf;
+    uint32_t li_assoc = (pm_in >> 20) & 0xf;
+
+    CYW_ENTER
+    ret = cyw43_ll_wifi_pm(&self->cyw43_ll, pm, pm_sleep_ret, li_bcn, li_dtim, li_assoc);
+    CYW_EXIT
+
+    return ret;
+}
+
+int cyw43_wifi_get_mac(cyw43_t *self, int itf, uint8_t mac[6]) {
+    mp_hal_get_mac(MP_HAL_MAC_WLAN0, &mac[0]);
+    return 0;
+}
+
+#define MAKE_COUNTRY(a, b, rev) ((a) | (b) << 8 | (rev) << 16)
+
+void cyw43_wifi_set_up(cyw43_t *self, int itf, bool up) {
+    if (up) {
+        if (self->itf_state == 0) {
+            uint32_t country;
+            extern char pyb_country_code[2];
+            if (pyb_country_code[0] == '\0' || pyb_country_code[1] == '\0') {
+                country = MAKE_COUNTRY('X', 'X', 17); // default to world-wide (passive ch 12-14)
+            } else {
+                country = MAKE_COUNTRY(pyb_country_code[0], pyb_country_code[1], 0);
+            }
+            cyw43_wifi_on(self, country);
+            cyw43_wifi_pm(self, 10 << 20 | 1 << 16 | 1 << 12 | 20 << 4 | 2);
+        }
+        if (itf == CYW43_ITF_AP) {
+            cyw43_wifi_ap_init(self);
+            cyw43_wifi_ap_set_up(self, true);
+        }
+        if ((self->itf_state & (1 << itf)) == 0) {
+            CYW_ENTER
+            cyw43_tcpip_deinit(self, itf);
+            cyw43_tcpip_init(self, itf);
+            self->itf_state |= 1 << itf;
+            CYW_EXIT
+        }
+    } else {
+        if (itf == CYW43_ITF_AP) {
+            cyw43_wifi_ap_set_up(self, false);
+        }
+    }
+}
+
+int cyw43_wifi_scan(cyw43_t *self, cyw43_wifi_scan_options_t *opts, void *env, int (*result_cb)(void*, const cyw43_ev_scan_result_t*)) {
+    if (self->itf_state == 0) {
+        return -1;
+    }
+
+    cyw43_ensure_up(self);
+
+    CYW_ENTER
+
+    // Set state and callback data
+    self->wifi_scan_state = 1;
+    self->wifi_scan_env = env;
+    self->wifi_scan_cb = result_cb;
+
+    // Start the scan
+    int ret = cyw43_ll_wifi_scan(&self->cyw43_ll, opts);
+
+    CYW_EXIT
+
+    return ret;
+}
+
+int cyw43_wifi_link_status(cyw43_t *self, int itf) {
+    if (itf == CYW43_ITF_STA) {
+        int s = self->wifi_join_state & 0xf;
+        if (s == WIFI_JOIN_STATE_ACTIVE) {
+            return CYW43_LINK_JOIN;
+        } else if (s == WIFI_JOIN_STATE_FAIL) {
+            return CYW43_LINK_FAIL;
+        } else if (s == WIFI_JOIN_STATE_NONET) {
+            return CYW43_LINK_NONET;
+        } else if (s == WIFI_JOIN_STATE_BADAUTH) {
+            return CYW43_LINK_BADAUTH;
+        } else {
+            return CYW43_LINK_DOWN;
+        }
+    } else {
+        return CYW43_LINK_DOWN;
+    }
+}
+
+/*******************************************************************************/
+// WiFi STA
+
+int cyw43_wifi_join(cyw43_t *self, size_t ssid_len, const uint8_t *ssid, size_t key_len, const uint8_t *key, uint32_t auth_type, const uint8_t *bssid, uint32_t channel) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return ret;
+    }
+
+    CYW_ENTER
+
+    ret = cyw43_ll_wifi_join(&self->cyw43_ll, ssid_len, ssid, key_len, key, auth_type, bssid, channel);
+
+    if (ret == 0) {
+        // Wait for responses: EV_AUTH, EV_LINK, EV_SET_SSID, EV_PSK_SUP
+        // Will get EV_DEAUTH_IND if password is invalid
+        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;
+
+        if (auth_type == 0) {
+            // For open security we don't need EV_PSK_SUP, so set that flag indicator now
+            self->wifi_join_state |= WIFI_JOIN_STATE_KEYED;
+        }
+    }
+
+    CYW_EXIT
+
+    return ret;
+}
+
+int cyw43_wifi_leave(cyw43_t *self, int itf) {
+    // Disassociate with SSID
+    return cyw43_ioctl(self, CYW43_IOCTL_SET_DISASSOC, 0, NULL, itf);
+}
+
+/*******************************************************************************/
+// WiFi AP
+
+STATIC void cyw43_wifi_ap_init(cyw43_t *self) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return;
+    }
+
+    CYW_ENTER
+    cyw43_ll_wifi_ap_init(&self->cyw43_ll, self->ap_ssid_len, self->ap_ssid, self->ap_auth, self->ap_key_len, self->ap_key, self->ap_channel);
+    CYW_EXIT
+}
+
+STATIC void cyw43_wifi_ap_set_up(cyw43_t *self, bool up) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return;
+    }
+
+    CYW_ENTER
+    cyw43_ll_wifi_ap_set_up(&self->cyw43_ll, up);
+    CYW_EXIT
+}
+
+void cyw43_wifi_ap_get_stas(cyw43_t *self, int *num_stas, uint8_t *macs) {
+    int ret = cyw43_ensure_up(self);
+    if (ret) {
+        return;
+    }
+
+    CYW_ENTER
+    cyw43_ll_wifi_ap_get_stas(&self->cyw43_ll, num_stas, macs);
+    CYW_EXIT
+}
diff --git a/drivers/cyw43/cyw43_ll.h b/drivers/cyw43/cyw43_ll.h
new file mode 100644
index 0000000000000000000000000000000000000000..879367a2edd9e85318673300069f4cf771895fd2
--- /dev/null
+++ b/drivers/cyw43/cyw43_ll.h
@@ -0,0 +1,135 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2019 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef MICROPY_INCLUDED_STM32_CYW43_LL_H
+#define MICROPY_INCLUDED_STM32_CYW43_LL_H
+
+// IOCTL commands
+#define CYW43_IOCTL_GET_SSID            (0x32)
+#define CYW43_IOCTL_GET_CHANNEL         (0x3a)
+#define CYW43_IOCTL_SET_DISASSOC        (0x69)
+#define CYW43_IOCTL_GET_ANTDIV          (0x7e)
+#define CYW43_IOCTL_SET_ANTDIV          (0x81)
+#define CYW43_IOCTL_SET_MONITOR         (0xd9)
+#define CYW43_IOCTL_GET_VAR             (0x20c)
+#define CYW43_IOCTL_SET_VAR             (0x20f)
+
+// Async events, event_type field
+#define CYW43_EV_SET_SSID               (0)
+#define CYW43_EV_JOIN                   (1)
+#define CYW43_EV_AUTH                   (3)
+#define CYW43_EV_DEAUTH_IND             (6)
+#define CYW43_EV_ASSOC                  (7)
+#define CYW43_EV_DISASSOC               (11)
+#define CYW43_EV_DISASSOC_IND           (12)
+#define CYW43_EV_LINK                   (16)
+#define CYW43_EV_PRUNE                  (23)
+#define CYW43_EV_PSK_SUP                (46)
+#define CYW43_EV_ESCAN_RESULT           (69)
+#define CYW43_EV_CSA_COMPLETE_IND       (80)
+#define CYW43_EV_ASSOC_REQ_IE           (87)
+#define CYW43_EV_ASSOC_RESP_IE          (88)
+
+enum {
+    CYW43_ITF_STA,
+    CYW43_ITF_AP,
+};
+
+typedef struct _cyw43_ev_scan_result_t {
+    uint32_t _0[5];
+    uint8_t bssid[6];
+    uint16_t _1[2];
+    uint8_t ssid_len;
+    uint8_t ssid[32];
+    uint32_t _2[5];
+    uint16_t channel;
+    uint16_t _3;
+    uint8_t auth_mode;
+    int16_t rssi;
+} cyw43_ev_scan_result_t;
+
+typedef struct _cyw43_async_event_t {
+    uint16_t _0;
+    uint16_t flags;
+    uint32_t event_type;
+    uint32_t status;
+    uint32_t reason;
+    uint8_t _1[30];
+    uint8_t interface;
+    uint8_t _2;
+    union {
+        cyw43_ev_scan_result_t scan_result;
+    } u;
+} cyw43_async_event_t;
+
+typedef struct _cyw43_wifi_scan_options_t {
+    uint32_t version;
+    uint16_t action;
+    uint16_t _;
+    uint32_t ssid_len; // 0 to select all
+    uint8_t ssid[32];
+    uint8_t bssid[6];
+    int8_t bss_type; // fill with 0xff to select all
+    int8_t scan_type; // 0=active, 1=passive
+    int32_t nprobes;
+    int32_t active_time;
+    int32_t passive_time;
+    int32_t home_time;
+    int32_t channel_num;
+    uint16_t channel_list[1];
+} cyw43_wifi_scan_options_t;
+
+typedef struct _cyw43_ll_t {
+    uint32_t opaque[528];
+} cyw43_ll_t;
+
+void cyw43_ll_init(cyw43_ll_t *self, void *cb_data);
+void cyw43_ll_deinit(cyw43_ll_t *self);
+
+int cyw43_ll_bus_init(cyw43_ll_t *self, const uint8_t *mac);
+void cyw43_ll_bus_sleep(cyw43_ll_t *self, bool can_sleep);
+void cyw43_ll_process_packets(cyw43_ll_t *self);
+int cyw43_ll_ioctl(cyw43_ll_t *self, uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface);
+int cyw43_ll_send_ethernet(cyw43_ll_t *self, int itf, size_t len, const void *buf, bool is_pbuf);
+
+int cyw43_ll_wifi_on(cyw43_ll_t *self, uint32_t country);
+int cyw43_ll_wifi_pm(cyw43_ll_t *self, uint32_t pm, uint32_t pm_sleep_ret, uint32_t li_bcn, uint32_t li_dtim, uint32_t li_assoc);
+int cyw43_ll_wifi_scan(cyw43_ll_t *self, cyw43_wifi_scan_options_t *opts);
+
+int cyw43_ll_wifi_join(cyw43_ll_t *self, size_t ssid_len, const uint8_t *ssid, size_t key_len, const uint8_t *key, uint32_t auth_type, const uint8_t *bssid, uint32_t channel);
+void cyw43_ll_wifi_set_wpa_auth(cyw43_ll_t *self);
+void cyw43_ll_wifi_rejoin(cyw43_ll_t *self);
+
+int cyw43_ll_wifi_ap_init(cyw43_ll_t *self, size_t ssid_len, const uint8_t *ssid, uint32_t auth, size_t key_len, const uint8_t *key, uint32_t channel);
+int cyw43_ll_wifi_ap_set_up(cyw43_ll_t *self, bool up);
+int cyw43_ll_wifi_ap_get_stas(cyw43_ll_t *self, int *num_stas, uint8_t *macs);
+
+// Callbacks to be provided by mid-level interface
+int cyw43_cb_read_host_interrupt_pin(void *cb_data);
+void cyw43_cb_ensure_awake(void *cb_data);
+void cyw43_cb_process_async_event(void *cb_data, const cyw43_async_event_t *ev);
+void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf);
+
+#endif // MICROPY_INCLUDED_STM32_CYW43_LL_H
diff --git a/drivers/cyw43/cyw43_lwip.c b/drivers/cyw43/cyw43_lwip.c
new file mode 100644
index 0000000000000000000000000000000000000000..8f4223029edc77fe009fd6468c561df585f81156
--- /dev/null
+++ b/drivers/cyw43/cyw43_lwip.c
@@ -0,0 +1,197 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2019 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "py/mphal.h"
+#include "lib/netutils/netutils.h"
+#include "lwip/etharp.h"
+#include "lwip/dns.h"
+#include "lwip/apps/mdns.h"
+#include "drivers/cyw43/cyw43.h"
+
+STATIC void cyw43_ethernet_trace(cyw43_t *self, struct netif *netif, size_t len, const void *data, unsigned int flags) {
+    bool is_tx = flags & NETUTILS_TRACE_IS_TX;
+    if ((is_tx && (self->trace_flags & CYW43_TRACE_ETH_TX))
+        || (!is_tx && (self->trace_flags & CYW43_TRACE_ETH_RX))) {
+        const uint8_t *buf;
+        if (len == (size_t)-1) {
+            // data is a pbuf
+            const struct pbuf *pbuf = data;
+            buf = pbuf->payload;
+            len = pbuf->len; // restricted to print only the first chunk of the pbuf
+        } else {
+            // data is actual data buffer
+            buf = data;
+        }
+
+        if (self->trace_flags & CYW43_TRACE_MAC) {
+            printf("[% 8d] ETH%cX itf=%c%c len=%u", mp_hal_ticks_ms(), is_tx ? 'T' : 'R', netif->name[0], netif->name[1], len);
+            printf(" MAC type=%d subtype=%d data=", buf[0] >> 2 & 3, buf[0] >> 4);
+            for (size_t i = 0; i < len; ++i) {
+                printf(" %02x", buf[i]);
+            }
+            printf("\n");
+            return;
+        }
+
+        if (self->trace_flags & CYW43_TRACE_ETH_FULL) {
+            flags |= NETUTILS_TRACE_PAYLOAD;
+        }
+        netutils_ethernet_trace(MP_PYTHON_PRINTER, len, buf, flags);
+    }
+}
+
+STATIC err_t cyw43_netif_output(struct netif *netif, struct pbuf *p) {
+    cyw43_t *self = netif->state;
+    if (self->trace_flags != 0) {
+        cyw43_ethernet_trace(self, netif, (size_t)-1, p, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE);
+    }
+    int itf = netif->name[1] - '0';
+    int ret = cyw43_send_ethernet(self, itf, p->tot_len, (void*)p, true);
+    if (ret) {
+        printf("[CYW43] send_ethernet failed: %d\n", ret);
+        return ERR_IF;
+    }
+    return ERR_OK;
+}
+
+STATIC err_t cyw43_netif_init(struct netif *netif) {
+    netif->linkoutput = cyw43_netif_output;
+    netif->output = etharp_output;
+    netif->mtu = 1500;
+    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP;
+    cyw43_wifi_get_mac(netif->state, netif->name[1] - '0', netif->hwaddr);
+    netif->hwaddr_len = sizeof(netif->hwaddr);
+    return ERR_OK;
+}
+
+void cyw43_tcpip_init(cyw43_t *self, int itf) {
+    ip_addr_t ipconfig[4];
+    #if LWIP_IPV6
+    #define IP(x) ((x).u_addr.ip4)
+    #else
+    #define IP(x) (x)
+    #endif
+    if (itf == 0) {
+        // need to zero out to get isconnected() working
+        IP4_ADDR(&IP(ipconfig[0]), 0, 0, 0, 0);
+        IP4_ADDR(&IP(ipconfig[2]), 192, 168, 0, 1);
+    } else {
+        IP4_ADDR(&IP(ipconfig[0]), 192, 168, 4, 1);
+        IP4_ADDR(&IP(ipconfig[2]), 192, 168, 4, 1);
+    }
+    IP4_ADDR(&IP(ipconfig[1]), 255, 255, 255, 0);
+    IP4_ADDR(&IP(ipconfig[3]), 8, 8, 8, 8);
+    #undef IP
+
+    struct netif *n = &self->netif[itf];
+    n->name[0] = 'w';
+    n->name[1] = '0' + itf;
+    #if LWIP_IPV6
+    netif_add(n, &ipconfig[0].u_addr.ip4, &ipconfig[1].u_addr.ip4, &ipconfig[2].u_addr.ip4, self, cyw43_netif_init, ethernet_input);
+    #else
+    netif_add(n, &ipconfig[0], &ipconfig[1], &ipconfig[2], self, cyw43_netif_init, netif_input);
+    #endif
+    netif_set_hostname(n, "PYBD");
+    netif_set_default(n);
+    netif_set_up(n);
+
+    if (itf == CYW43_ITF_STA) {
+        dns_setserver(0, &ipconfig[3]);
+        dhcp_set_struct(n, &self->dhcp_client);
+        dhcp_start(n);
+    } else {
+        dhcp_server_init(&self->dhcp_server, &ipconfig[0], &ipconfig[1]);
+    }
+
+    #if LWIP_MDNS_RESPONDER
+    // TODO better to call after IP address is set
+    char mdns_hostname[9];
+    memcpy(&mdns_hostname[0], "PYBD", 4);
+    mp_hal_get_mac_ascii(MP_HAL_MAC_WLAN0, 8, 4, &mdns_hostname[4]);
+    mdns_hostname[8] = '\0';
+    mdns_resp_add_netif(n, mdns_hostname, 60);
+    #endif
+}
+
+void cyw43_tcpip_deinit(cyw43_t *self, int itf) {
+    struct netif *n = &self->netif[itf];
+    if (itf == CYW43_ITF_STA) {
+        dhcp_stop(n);
+    } else {
+        dhcp_server_deinit(&self->dhcp_server);
+    }
+    #if LWIP_MDNS_RESPONDER
+    mdns_resp_remove_netif(n);
+    #endif
+    for (struct netif *netif = netif_list; netif != NULL; netif = netif->next) {
+        if (netif == n) {
+            netif_remove(netif);
+            netif->ip_addr.addr = 0;
+            netif->flags = 0;
+        }
+    }
+}
+
+void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf) {
+    cyw43_t *self = cb_data;
+    struct netif *netif = &self->netif[itf];
+    if (self->trace_flags) {
+        cyw43_ethernet_trace(self, netif, len, buf, NETUTILS_TRACE_NEWLINE);
+    }
+    if (netif->flags & NETIF_FLAG_LINK_UP) {
+        struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
+        if (p != NULL) {
+            pbuf_take(p, buf, len);
+            if (netif->input(p, netif) != ERR_OK) {
+                pbuf_free(p);
+            }
+        }
+    }
+}
+
+void cyw43_tcpip_set_link_up(cyw43_t *self, int itf) {
+    netif_set_link_up(&self->netif[itf]);
+}
+
+void cyw43_tcpip_set_link_down(cyw43_t *self, int itf) {
+    netif_set_link_down(&self->netif[itf]);
+}
+
+int cyw43_tcpip_link_status(cyw43_t *self, int itf) {
+    struct netif *netif = &self->netif[itf];
+    if ((netif->flags & (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP))
+        == (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP)) {
+        if (netif->ip_addr.addr != 0) {
+            return CYW43_LINK_UP;
+        } else {
+            return CYW43_LINK_NOIP;
+        }
+    } else {
+        return cyw43_wifi_link_status(self, itf);
+    }
+}
diff --git a/drivers/cyw43/libcyw43.a b/drivers/cyw43/libcyw43.a
new file mode 100644
index 0000000000000000000000000000000000000000..7d0ff93dcbe686e225efd2e119694d13e36ece5b
Binary files /dev/null and b/drivers/cyw43/libcyw43.a differ