From 1b06403a192a26deaa82bb1348c57f0e6ad50cc0 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Thu, 17 Apr 2025 18:58:49 -0300 Subject: [PATCH] Add support for SDIO Add support for SDIO in CYW driver Add Portenta H7 example --- mongoose.c | 642 ++++++++++++++---- mongoose.h | 45 +- src/drivers/cyw.c | 465 +++++++++---- src/drivers/cyw.h | 6 +- src/drivers/sdio.c | 169 +++++ src/drivers/sdio.h | 39 ++ src/wifi_dummy.c | 4 +- .../Makefile | 43 ++ .../README.md | 3 + .../portenta-h7-make-baremetal-builtin/hal.c | 243 +++++++ .../portenta-h7-make-baremetal-builtin/hal.h | 446 ++++++++++++ .../link.ld | 33 + .../portenta-h7-make-baremetal-builtin/main.c | 176 +++++ .../mongoose.c | 1 + .../mongoose.h | 1 + .../mongoose_config.h | 13 + 16 files changed, 2053 insertions(+), 276 deletions(-) create mode 100644 src/drivers/sdio.c create mode 100644 src/drivers/sdio.h create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/Makefile create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/README.md create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.c create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.h create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/link.ld create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/main.c create mode 120000 tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.c create mode 120000 tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.h create mode 100644 tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose_config.h diff --git a/mongoose.c b/mongoose.c index bef564f7..399acf40 100644 --- a/mongoose.c +++ b/mongoose.c @@ -19887,7 +19887,9 @@ void mg_delayms(unsigned int ms) { #if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ - (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) + (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) && \ + (!defined(MG_ENABLE_DRIVER_CYW_SDIO) || !MG_ENABLE_DRIVER_CYW_SDIO) + bool mg_wifi_scan(void) { MG_ERROR(("No Wi-Fi driver enabled")); @@ -20356,8 +20358,16 @@ static size_t cmsis_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) { -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW +#if MG_ENABLE_TCPIP && \ + ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) +#ifndef MG_ENABLE_DRIVER_CYW +#define MG_ENABLE_DRIVER_CYW 0 +#endif +#ifndef MG_ENABLE_DRIVER_CYW_SDIO +#define MG_ENABLE_DRIVER_CYW_SDIO 0 +#endif static struct mg_tcpip_if *s_ifp; static bool s_link, s_auth, s_join; @@ -20423,10 +20433,12 @@ struct mg_tcpip_driver mg_tcpip_driver_cyw = {mg_tcpip_driver_cyw_init, // SPI | SDIO <-- padded to 32-bit | 64-bytes // // - SDPCM has 3 channels (control, data, and asynchronous data) -// - SPI (and SDIO) has 4 "functions", F0 to F3, to access different -// blocks in the chip, like the SPI/SDIO controller, chip backplane, and 2 DMA -// I/Os; these are usually handled by SDPCM but we need to explicitly access -// the I/O controller and chip backplane during initialization +// - SPI has 4 "functions", F0 to F3, to access different blocks in the chip, +// like the SPI/SDIO controller, chip backplane, and 2 DMA I/Os; these are +// usually handled by SDPCM but we need to explicitly access the I/O controller +// and chip backplane during initialization +// - SDIO has 3 functions (proper SDIO terminology), F0 to F2, coincident with +// those for SPI, accessed through standard SDIO practices. There is no F3. // Processor core firmware is loaded to TCM RAM, along with module-dependent // (hardware design) NVRAM data, via the chip backplane access through the bus @@ -20476,10 +20488,15 @@ struct data_hdr { struct bdc_hdr bdc; }; -// gSPI, DS 4.2.1 Fig.12, 2-bit field -#define CYW_SD_FUNC_BUS 0 // F0 -#define CYW_SD_FUNC_CHIP 1 // F1 -#define CYW_SD_FUNC_WLAN 2 // F2 +// gSPI, CYW43439 DS 4.2.1 Fig.12, 2-bit field +#define CYW_SPID_FUNC_BUS 0 // F0 +#define CYW_SPID_FUNC_CHIP 1 // F1 +#define CYW_SPID_FUNC_WLAN 2 // F2 + +// SDIO functions, 3-bit field; CYW4343W and CYW43439 DS 4.1 +#define CYW_SDIO_FUNC_BUS 0 // F0 +#define CYW_SDIO_FUNC_CHIP 1 // F1 +#define CYW_SDIO_FUNC_WLAN 2 // F2 #define CYW_SDPCM_CTRL_HDR 0 #define CYW_SDPCM_ASYNC_HDR 1 @@ -20494,7 +20511,7 @@ static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len); static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len); static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len); -static size_t cyw_spi_poll(uint8_t *dest); +static size_t cyw_bus_specific_poll(uint32_t *dest); static void cyw_update_hash_table(void); // High-level comm stuff @@ -20507,7 +20524,7 @@ static void cyw_poll(void) { cyw_update_hash_table(); s_ifp->update_mac_hash_table = false; } - if (cyw_spi_poll((uint8_t *) resp) == 0) return; // BUS DEPENDENCY + if (cyw_bus_specific_poll(resp) == 0) return; if ((sdpcm->len ^ sdpcm->_len) != 0xffff || sdpcm->len < sizeof(*sdpcm) || sdpcm->len > 2048 - sizeof(*sdpcm)) return; @@ -20540,7 +20557,7 @@ static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len) { mg_tcpip_qwrite(payload, len - (payload - (uint8_t *) bdc), s_ifp); } -static size_t cyw_bus_tx(uint32_t *data, uint16_t len); +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len); // WLAN frame transmission static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { @@ -20549,15 +20566,15 @@ static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { memset(txdata, 0, sizeof(*hdr)); memcpy((uint8_t *) txdata + sizeof(*hdr), data, len); // TODO(): hdr->bdc.priority = map IP to TOS if supporting QoS/ToS - hdr->bdc.flags = 2 << 4; // BDC version 2 - hdr->bdc.flags2 = ifc; // 0 -> STA, 1 -> AP + hdr->bdc.flags = 2 << 4; // BDC version 2 + hdr->bdc.flags2 = (uint8_t) ifc; // 0 -> STA, 1 -> AP // hdr->bdc.data_offset = 0; // actually zeroed above hdr->sdpcm.len = txlen; hdr->sdpcm._len = (uint16_t) ~txlen; hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_DATA_HDR, hdr->sdpcm.sw_hdr.header_length = offsetof(struct data_hdr, bdc); - return cyw_bus_tx(txdata, txlen); + return cyw_bus_specific_tx(txdata, txlen); } // WLAN event handling @@ -20605,6 +20622,7 @@ struct scan_result; static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, size_t len); +// Do not call any IOCTL functions here, otherwise revise cyw_ioctl_wait() static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len) { struct evnt_msg *msg = (struct evnt_msg *) &bdc[bdc->data_offset + 1]; MG_VERBOSE(("%u bytes event", len)); @@ -20668,7 +20686,7 @@ static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, size_t len); // clang-format off // convenience: ioctl funcs on default ifc (0), as only AP needs ifc 1 -static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } +__attribute__((unused)) static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } static bool cyw_ioctl_set(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_set_(0, cmd, data, len); } static bool cyw_ioctl_iovar_get(char *var, void *data, size_t len) { return cyw_ioctl_iovar_get_(0, var, data, len); } static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ioctl_iovar_set_(0, var, data, len); } @@ -20679,9 +20697,10 @@ static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ // clang-format off static bool cyw_wifi_connect(char *ssid, char *pass) { uint32_t sup_wpa[2] = {0, 1}; // bss index 0 = STA, not open - static const uint32_t const eapver[2] = {0, (uint32_t) -1}, // accept AP version + static const uint32_t eapver[2] = {0, (uint32_t) -1}, // accept AP version tmo[2] = {0, 2500}; uint32_t data[64/4 + 1]; // max pass length: 64 for WPA, 128 for WPA3 SAE + uint16_t *da = (uint16_t *) data; unsigned int len; uint32_t val; val = 4; // security type: 0 for none, 2 for WPA, 4 for WPA2/WPA3, 6 for mixed WPA/WPA2 @@ -20695,8 +20714,8 @@ static bool cyw_wifi_connect(char *ssid, char *pass) { // skip if not using auth memset(data, 0, sizeof(data)); len = strlen(pass); - ((uint16_t *)data)[0] = (uint16_t) len; - ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + da[0] = (uint16_t) len; + da[1] = 1; // indicates wireless security key, skip for WPA3 SAE memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE if (!cyw_ioctl_set(268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA // for WPA3 SAE: memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); cyw_ioctl_iovar_set("sae_password", data, sizeof(data)); @@ -20723,6 +20742,7 @@ static bool cyw_wifi_disconnect(void) { static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { uint32_t data[64/4 + 2]; // max pass length: 64 for WPA, 128 for WPA3 SAE + uint16_t *da = (uint16_t *) data; unsigned int len; uint32_t val; // CHIP DEPENDENCY @@ -20749,8 +20769,8 @@ static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { // NOTE(): WHD does not set SAE password for shared WPA2/WPA3, same do we memset(data, 0, sizeof(data)); len = strlen(pass); - ((uint16_t *)data)[0] = (uint16_t) len; // skip for WPA3 SAE - ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + da[0] = (uint16_t) len; // skip for WPA3 SAE (43430 does NOT support WPA3 in AP) + da[1] = 1; // indicates wireless security key, skip for WPA3 SAE memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE if (!cyw_ioctl_set_(1, 268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA /* for WPA3 SAE: @@ -20894,8 +20914,8 @@ static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, si if (sbss->length > len - offsetof(struct scan_result, bss) || sbss->SSID_len > sizeof(sbss->SSID) || sbss->ie_offset < sizeof(*sbss) || sbss->ie_offset > (sizeof(*sbss) + sbss->ie_length) || sbss->ie_offset + sbss->ie_length > sbss->length) return; // silently discard malformed data if (!(sbss->flags & MG_BIT(2))) return; // RSSI_ONCHANNEL, ignore off-channel results - bss.SSID = mg_str_n(sbss->SSID, sbss->SSID_len); - bss.BSSID = sbss->BSSID; + bss.SSID = mg_str_n((char *)sbss->SSID, sbss->SSID_len); + bss.BSSID = (char *)sbss->BSSID; bss.RSSI = (int8_t)sbss->RSSI; bss.has_n = sbss->n_cap != 0; bss.channel = bss.has_n ? sbss->ctl_ch : (uint8_t)(sbss->chanspec & 0xff); // n 40MHz vs a/b/g and 20MHz @@ -20930,7 +20950,7 @@ static uint8_t *s_ioctl_resp; static bool s_ioctl_err; static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { - uint8_t *resp = (uint8_t *) cdc + sizeof(*cdc); + uint8_t *r = (uint8_t *) cdc + sizeof(*cdc); MG_VERBOSE(("%u bytes CDC frame", len)); if ((cdc->flags >> 16) != s_ioctl_reqid) return; if (cdc->flags & 1) { @@ -20939,9 +20959,8 @@ static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { return; } if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) cdc, len); - MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", resp[0], resp[1], resp[2], - resp[3])); - s_ioctl_resp = resp; + MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", r[0], r[1], r[2], r[3])); + s_ioctl_resp = r; } // NOTE(): alt no loop handler dispatching IOCTL response to current handler: // static void *s_ioctl_hnd; *s_ioctl_hnd(ioctl, len); @@ -20973,7 +20992,7 @@ static void cyw_ioctl_send_cmd(unsigned int ifc, unsigned int cmd, bool set, hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_CTRL_HDR; hdr->sdpcm.sw_hdr.header_length = offsetof(struct ctrl_hdr, cdc); - cyw_bus_tx(txdata, txlen); + cyw_bus_specific_tx(txdata, txlen); } // just send respective commands, response handled via CDC handler @@ -21007,20 +21026,29 @@ static void cyw_ioctl_send_iovar_set2(unsigned int ifc, char *var, void *data1, cyw_ioctl_send_cmd(ifc, 263, true, txlen); // cmd = SET IOVAR } -static void cyw_ioctl_send_iovar_set(unsigned int ifc, char *var, void *data, - size_t len) { +__attribute__((unused)) static void cyw_ioctl_send_iovar_set(unsigned int ifc, + char *var, + void *data, + size_t len) { cyw_ioctl_send_iovar_set2(ifc, var, data, len, NULL, 0); } +static inline bool delayms(unsigned int ms) { + mg_delayms(ms); + return true; +} + // wait for a response, meanwhile delivering received frames and events static bool cyw_ioctl_wait(void) { - unsigned int times = 6000; + unsigned int times = 100; s_ioctl_resp = NULL; s_ioctl_err = false; - while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0) - cyw_poll(); // TODO(scaprile): review wait/sleep strategy (this loop is executed only when initializing/acting on the chip) - MG_DEBUG(("resp: %lp, err: %c, times: %d", s_ioctl_resp, - s_ioctl_err ? '1' : '0', (int) times)); + do { // IOCTL response processing does not call any other IOCTL function + cyw_poll(); // otherwise we can't allow them to pile up here + // network frames will be pushed to the queue so that is safe + } while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0 && delayms(1)); + MG_VERBOSE(("resp: %lp, err: %c, times: %d", s_ioctl_resp, + s_ioctl_err ? '1' : '0', (int) times)); return s_ioctl_resp != NULL; } @@ -21078,15 +21106,22 @@ struct clm_hdr { #pragma pack(pop) -// worlwide rev0, try rev 17 for 4343W +// worlwide rev0, TODO(): try rev 17 for 4343W static const uint32_t country_code = 'X' + ('X' << 8) + (0 << 16); -static bool cyw_spi_init(); +static bool cyw_bus_specific_init(); +static bool cyw_load_clmll(void *data, size_t len); + +static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); +} // clang-format off static bool cyw_init(uint8_t *mac) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; uint32_t val = 0; - if (!cyw_spi_init()) return false; // BUS DEPENDENCY + if (!cyw_bus_specific_init()) return false; + if (!cyw_load_clm(d->fw)) return false; // Load CLM blob // BT-ENABLED DEPENDENCY // set Wi-Fi up val = 0 /* disable */; cyw_ioctl_iovar_set("bus:txglom", (uint8_t *)&val, sizeof(val)); @@ -21114,7 +21149,7 @@ static bool cyw_init(uint8_t *mac) { unsigned int times = 100; while (times --) if (cyw_ioctl_iovar_set("bsscfg:event_msgs", (uint8_t *)data, sizeof(data))) break; - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; } val = 0; if (!cyw_ioctl_set(64 /* SET_ANTDIV */, (uint8_t *)&val, sizeof(val))) return false; if (!cyw_ioctl_set(2 /* UP, interface up */, NULL, 0)) return false; @@ -21173,20 +21208,18 @@ static bool cyw_load_clmll(void *data, size_t len) { break; sent += bytes; offset += bytes; - hdr.flag &= ~MG_BIT(1); // DL_BEGIN + hdr.flag &= (uint16_t)~MG_BIT(1); // DL_BEGIN } return sent >= len; } // clang-format on -static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { - return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); -} - static void cyw_update_hash_table(void) { // TODO(): read database, rebuild hash table uint32_t val = 0; - val = 1; cyw_ioctl_iovar_set2_(0, "mcast_list", (uint8_t *)&val, sizeof(val), (uint8_t *)mcast_addr, sizeof(mcast_addr)); + val = 1; + cyw_ioctl_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), + (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); } @@ -21203,8 +21236,16 @@ static void cyw_update_hash_table(void) { #define CYW_CHIP_BCKPLN_ADDRMSK 0x7fff #define CYW_CHIP_BCKPLN_ACCSS4B MG_BIT(15) #define CYW_CHIP_BCKPLN_WRAPPOFF 0x100000 +// BUS DEPENDENCY: max bus to backplane transfer size, bus function id #define CYW_CHIP_BCKPLN_SPIMAX 64 #define CYW_CHIP_BCKPLN_SDIOMAX 1536 +#if MG_ENABLE_DRIVER_CYW_SDIO +#define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SDIOMAX +#define CYW_BUS_FUNC_CHIP CYW_SDIO_FUNC_CHIP +#else +#define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SPIMAX +#define CYW_BUS_FUNC_CHIP CYW_SPID_FUNC_CHIP +#endif // CHIP DEPENDENCY #define CYW_CHIP_ARMCORE_BASE (CYW_CHIP_CHIPCOMMON + 0x3000) @@ -21229,9 +21270,9 @@ static void cyw_update_hash_table(void) { #define CYW_CHIP_AI_IOCTRL 0x408 #define CYW_CHIP_AI_RESETCTRL 0x800 -static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, uint16_t len); -static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, uint16_t len); // clang-format off @@ -21239,9 +21280,9 @@ static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, static void cyw_set_backplane_window(uint32_t addr) { uint32_t val; addr &= ~CYW_CHIP_BCKPLN_ADDRMSK; - val = (addr >> 24) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); - val = (addr >> 16) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); - val = (addr >> 8) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); + val = (addr >> 24) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); + val = (addr >> 16) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); + val = (addr >> 8) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); } static bool cyw_core_reset(uint32_t core_base, bool check) { @@ -21249,21 +21290,21 @@ static bool cyw_core_reset(uint32_t core_base, bool check) { // core disabled after chip reset cyw_set_backplane_window(core_base); // set backplane window for requested area; we do know offsets fall within that window // possible CHIP DEPENDENCY: AI_RESETSTATUS check and wait (instead of these cool reads) to ensure backplane operations end - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end - val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = 0x00; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset mg_delayms(1); - val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end mg_delayms(1); if (check) { // Verify only clock is enabled - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); if ((val & (MG_BIT(1) | MG_BIT(0)) /* SICF_FGC | SICF_CLOCK_EN) */) != MG_BIT(0)) return false; // Verify it is not in reset state - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); if (val & MG_BIT(0)) return false; // AIRC_RESET } return true; @@ -21271,29 +21312,28 @@ static bool cyw_core_reset(uint32_t core_base, bool check) { static void cyw_socram_init(void) { uint32_t val; - // CHIP DEPENDENCY: disable remap for SRAM_3 + // CHIP DEPENDENCY: disable remap for SRAM_3: 43430 and 43439 only cyw_set_backplane_window(CYW_CHIP_SOCSRAM_BASE); // set backplane window for requested area; we do know offsets fall within that window - val = 0x03; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + val = 0x03; cyw_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK), &val, sizeof(val)); + val = 0x00; cyw_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK), &val, sizeof(val)); } // transfer is fractioned in bus-to-backplane-size units within backplane windows static void cyw_load_data(uint32_t dest, void *data, size_t len) { size_t sent = 0, offset = 0; - uint32_t last_addr = ~0; + uint32_t last_addr = (uint32_t) ~0; while (sent < len) { size_t bytes = len - sent, avail; uint32_t addr = dest + offset; - if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == ~0) { + if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == (uint32_t) ~0) { cyw_set_backplane_window(addr); // set backplane window for requested area last_addr = addr & ~CYW_CHIP_BCKPLN_ADDRMSK; } addr &= CYW_CHIP_BCKPLN_ADDRMSK; - avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; + avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; // internal backplane limit if (bytes > avail) bytes = avail; - // BUS DEPENDENCY: max bus to backplane transfer size - if (bytes > CYW_CHIP_BCKPLN_SPIMAX) bytes = CYW_CHIP_BCKPLN_SPIMAX; - cyw_spi_write(CYW_SD_FUNC_CHIP, addr | CYW_CHIP_BCKPLN_ACCSS4B, (uint8_t *)data + offset, bytes); + if (bytes > CYW_CHIP_BCKPLN_BUSMAX) bytes = CYW_CHIP_BCKPLN_BUSMAX; // bus to backplane transfer limit + cyw_bus_write(CYW_BUS_FUNC_CHIP, addr, (uint8_t *)data + offset, (uint16_t) bytes); sent += bytes; offset += bytes; } @@ -21305,13 +21345,13 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv cyw_core_reset(CYW_CHIP_SOCSRAM, false); // cores were disabled at chip reset cyw_socram_init(); cyw_load_data(CYW_CHIP_ATCMRAM_BASE, fwdata, fwlen); - mg_delayms(5); // ************ CHECK IF THIS IS ACTUALLY NEEDED + mg_delayms(5); // TODO(scaprile): CHECK IF THIS IS ACTUALLY NEEDED // Load NVRAM and place 'length ~length' at the end; end of chip RAM { const uint32_t start = CYW_CHIP_RAM_SIZE - 4 - nvramlen; cyw_load_data(start, nvramdata, nvramlen); // nvramlen must be a multiple of 4 // RAM_SIZE is a multiple of WINSZ, so the place for len ~len will be at the end of the window - cyw_spi_write(CYW_SD_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + cyw_bus_write(CYW_BUS_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4), &val, sizeof(val)); } // Reset ARM core and check it starts if (!cyw_core_reset(CYW_CHIP_ARMCORE, true)) return false; @@ -21319,6 +21359,8 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv } // clang-format on +#if !MG_ENABLE_DRIVER_CYW_SDIO + // CYW43 SPI bus specifics #define CYW_BUS_SPI_BUSCTRL 0x00 // 4 regs, 0 to 3 @@ -21330,29 +21372,33 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv #define CYW_BUS_STS_LEN(x) ((x >> 9) & 0x7ff) +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len); +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len); + // clang-format off static size_t cyw_spi_poll(uint8_t *response) { size_t len; uint32_t res; // SPI poll - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); - if (res == ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); + if (res == (uint32_t) ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; len = CYW_BUS_STS_LEN(res); if (len == 0) { // just ack IRQ uint16_t val = 1; - cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); return 0; } - cyw_spi_read(CYW_SD_FUNC_WLAN, 0, response, len); + cyw_spi_read(CYW_SPID_FUNC_WLAN, 0, response, len); return len; } -// BUS DEPENDENCY: name is generic but function is bus dependent -static size_t cyw_bus_tx(uint32_t *data, uint16_t len) { - while (len & 3) data[len++] = 0; // SPI 32-bit padding (SDIO->64-byte) - return cyw_spi_write(CYW_SD_FUNC_WLAN, 0, data, len) ? len: 0; +static size_t cyw_spi_tx(uint32_t *data, uint16_t len) { + while (len & 3) data[len++] = 0; // SPI 32-bit padding + return cyw_spi_write(CYW_SPID_FUNC_WLAN, 0, data, len) ? len: 0; } // this can be integrated in lowest level SPI read/write _driver_ functions @@ -21362,20 +21408,19 @@ uint32_t sw16_2(uint32_t data) { } // DS 4.2.2 Table 6: signal we're working in 16-bit mode -#define CYW_SD_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space +#define CYW_SPI_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space static bool cyw_spi_init() { - struct mg_tcpip_driver_cyw_data *d = - (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; uint32_t val = 0; // DS 4.2.3 Boot-Up Sequence; WHD: other chips might require more effort unsigned int times = 51; while (times--) { - cyw_spi_read(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (sw16_2(val) == 0xFEEDBEAD) break; mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // DS 4.2.3 Table 6. Chip starts in 16-bit little-endian mode. // Configure SPI and switch to 32-bit big-endian mode: // - High-speed mode: d->hs true @@ -21383,33 +21428,34 @@ static bool cyw_spi_init() { // - SPI RESPONSE DELAY 4 bytes time [not in DS] TODO(scaprile): logic ana // - Status not sent after command, IRQ with status val = sw16_2(0x000204a3 | (d->hs ? MG_BIT(4) : 0)); // 4 reg content - cyw_spi_write(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); mg_tcpip_call(s_ifp, MG_TCPIP_EV_DRIVER, NULL); - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (val != 0xFEEDBEAD) return false; - val = 4; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); + val = 4; cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); val = 0x99; // clear error bits DATA_UNAVAILABLE, COMMAND_ERROR, DATA_ERROR, F1_OVERFLOW - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); val = 0x00be; // Enable IRQs F2_F3_FIFO_RD_UNDERFLOW, F2_F3_FIFO_WR_OVERFLOW, COMMAND_ERROR, DATA_ERROR, F2_PACKET_AVAILABLE, F1_OVERFLOW // BT-ENABLED DEPENDENCY: add F1_INTR (bit 13) - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); // chip backplane is ready, initialize it // request ALP (Active Low Power) clock - val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // BT-ENABLED DEPENDENCY times = 10; while (times--) { - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(6)) break; // ALP_AVAIL mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // clear request - val = 0; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = 0; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area - cyw_spi_read(CYW_SD_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); - MG_INFO(("WLAN chip is CYW%u", *((uint16_t *)&val))); + cyw_spi_read(CYW_SPID_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + if (val == 43430) val = 4343; + MG_INFO(("WLAN chip is CYW%u%c", val), val == 4343 ? 'W' : ' ')); // Load firmware (code and NVRAM) if (!cyw_load_firmware(d->fw)) return false; @@ -21417,77 +21463,74 @@ static bool cyw_spi_init() { // Wait for High Throughput (HT) clock ready times = 50; while (times--) { - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(7)) break; // HT_AVAIL mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // Wait for backplane ready times = 1000; while (times--) { - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); if (val & MG_BIT(5)) break; // F2_RX_READY mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // CHIP DEPENDENCY // Enable save / restore // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); - val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); #if 0 // Set BRCM_CARDCAP to CMD_NODEC. NOTE(): This is probably only necessary for SDIO, not SPI - val = MG_BIT(3); cyw_spi_write(CYW_SD_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); + val = MG_BIT(3); cyw_spi_write(CYW_SPID_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); #endif // Force HT request to chip backplane - val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // Enable Keep SDIO On (KSO) - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); if (!(val & MG_BIT(0))) { - val |= MG_BIT(0); cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + val |= MG_BIT(0); cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); } // The SPI bus can be configured for sleep (KSO controls wlan block sleep) - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); - val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); // Set SPI bus sleep - val = 0x0f; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + val = 0x0f; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear pullups. NOTE(): ? - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + val = 0x00; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear possible data unavailable error - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); - if (val & MG_BIT(0)) cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); - - // Load CLM blob - if (!cyw_load_clm(d->fw)) return false; + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + if (val & MG_BIT(0)) cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); return true; } // clang-format on -// gSPI, DS 4.2.1 Fig.12 -#define CYW_SD_LEN(x) ((x) &0x7FF) // bits 0-10 -#define CYW_SD_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, -#define CYW_SD_FUNC(x) (((x) &3) << 28) // bits 28-29 -#define CYW_SD_INC MG_BIT(30) -#define CYW_SD_WR MG_BIT(31) +// gSPI, CYW43439 DS 4.2.1 Fig.12 +#define CYW_SPID_LEN(x) ((x) &0x7FF) // bits 0-10 +#define CYW_SPID_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, +#define CYW_SPID_FUNC(x) (((x) &3) << 28) // bits 28-29 +#define CYW_SPID_INC MG_BIT(30) +#define CYW_SPID_WR MG_BIT(31) static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, uint16_t len) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; - struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; - uint32_t hdr = CYW_SD_WR | CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | - CYW_SD_LEN(len); // gSPI header + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->bus; + uint32_t hdr = CYW_SPID_WR | CYW_SPID_INC | CYW_SPID_FUNC(f) | + CYW_SPID_ADDR(addr) | CYW_SPID_LEN(len); // gSPI header // TODO(scaprile): check spin in between and timeout values, return false - if (f == CYW_SD_FUNC_WLAN) { + if (f == CYW_SPID_FUNC_WLAN) { uint32_t val = 0; while ((val & MG_BIT(5)) != MG_BIT(5)) // F2 rx ready (FIFO ready) - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); } - if (f & CYW_SD_16bMODE) + if (f & CYW_SPI_16bMODE) hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode s->begin(NULL); @@ -21503,26 +21546,26 @@ static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, return true; } -// will write 32-bit aligned quantities to data if f == CYW_SD_FUNC_WLAN +// will write 32-bit aligned quantities to data if f == CYW_SPID_FUNC_WLAN static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, uint16_t len) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; - struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->bus; uint32_t padding = - f == CYW_SD_FUNC_CHIP + f == CYW_SPID_FUNC_CHIP ? 4 : 0; // add padding to chip backplane reads as a response delay - uint32_t hdr = CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | - CYW_SD_LEN(len + padding); // gSPI header - if (f == CYW_SD_FUNC_WLAN && (len & 3)) + uint32_t hdr = CYW_SPID_INC | CYW_SPID_FUNC(f) | CYW_SPID_ADDR(addr) | + CYW_SPID_LEN(len + padding); // gSPI header + if (f == CYW_SPID_FUNC_WLAN && (len & 3)) len = (len + 4) & ~3; // align WLAN transfers to 32-bit - if (f & CYW_SD_16bMODE) + if (f & CYW_SPI_16bMODE) hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode s->begin(NULL); s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); - if (f == CYW_SD_FUNC_CHIP) { + if (f == CYW_SPID_FUNC_CHIP) { uint32_t pad; s->txn(NULL, NULL, (uint8_t *) &pad, 4); // read padding back and discard } @@ -21530,6 +21573,160 @@ static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, s->end(NULL); } +static bool cyw_bus_specific_init(void) { + return cyw_spi_init(); +} +static size_t cyw_bus_specific_poll(uint32_t *response) { + return cyw_spi_poll((uint8_t *) response); +} +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { + return cyw_spi_tx(data, len); +} +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + if (f == CYW_SPID_FUNC_CHIP && len >= 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; + return cyw_spi_write(f, addr, data, len); +} +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + cyw_spi_read(f, addr, data, len); + return true; +} + +#else // MG_ENABLE_DRIVER_CYW_SDIO + + + +// CYW43 SDIO bus specifics + +// CYW4343W and CYW43439 DS 4.1 SDIO v2.0: +//- F0: max block size is 32 bytes +//- F1: max block size is 64 bytes +//- F2: max block size is 512 bytes + +// clang-format off +static bool cyw_sdio_transfer(bool write, unsigned int f, uint32_t addr, void *data, uint32_t len) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; + uint32_t *ptr = (uint32_t *) data; // assume 32-bit aligned data (all except firmware) + if (write && (size_t) data & 3) { // missed, source data is not 32-bit aligned + memcpy(txdata, data, len); // copy to an aligned buffer, we know it fits + ptr = txdata; + } // all possible read destinations are 32-bit aligned + // mg_sdio_transfer requires 32-bit alignment for > 1 byte transfers + return mg_sdio_transfer(s, write, f, addr, ptr, len); +} + +static size_t cyw_sdio_poll(uint32_t *response) { + uint32_t res; + uint16_t *len = (uint16_t *)&res; + // WHD: internal docs, "tag" hinting a possible packet. + // This is actually the len / ~len field of a possible struct sdpcm_hdr, if there is a packet available, or 0 if there is none. + cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, &res, sizeof(res)); // read "the tag" + if ((len[0] | len[1]) == 0 || (len[0] ^ len[1]) != 0xffff || *len <= 4) return 0; + response[0] = res; // copy what we already read, then read the rest + cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, response + 1, *len - sizeof(res)); + return (size_t)*len; +} + +static size_t cyw_sdio_tx(uint32_t *data, uint16_t len) { + return cyw_sdio_transfer(true, CYW_SDIO_FUNC_WLAN, 0, data, len) ? len: 0; +} + +static bool cyw_sdio_init() { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; + uint32_t val = 0; + if (!mg_sdio_init(s)) return false; + // no block transfers on F0. if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_BUS, 32)) return false; + if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_CHIP, 64)) return false; + if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_WLAN, 64)) return false; + // TODO(scaprile): we don't handle SDIO interrupts, study CCCR INTEN and SDIO support (SDIO 6.3, 8) + // Enable chip backplane (F1) + if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_CHIP)) return false; + // Wait for F1 to be ready + if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_CHIP)) return false; + // chip backplane is ready, initialize it + // request ALP (Active Low Power) clock + val = MG_BIT(5) | MG_BIT(3) | MG_BIT(0); // HW_CLKREQ_OFF ALP_REQ FORCE_ALP + cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // BT-ENABLED DEPENDENCY + unsigned int times = 10; + while (times--) { + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + if (val & MG_BIT(6)) break; // ALP_AVAIL + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + // clear request + val = 0; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // Enable WLAN (F2) + if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_WLAN)) return false; + // Clear pullups. NOTE(): ? + val = 0x00; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + // we don't handle wake nor OOB interrupts; SEP_INT_CTL is a vendor specific SDIO register + // SDIO interrupts: enable F2 interrupt only + + cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area + cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + if (val == 43430) val = 4343; + MG_INFO(("WLAN chip is CYW%u%c", val, val == 4343 ? 'W' : ' ')); + // Load firmware (code and NVRAM) + if (!cyw_load_firmware(d->fw)) return false; + + // Wait for High Throughput (HT) clock ready + times = 50; + while (times--) { + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + if (val & MG_BIT(7)) break; // HT_AVAIL + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + // Wait for WLAN ready + if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_WLAN)) return false; + + // CHIP DEPENDENCY + // Enable save / restore + // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1)) return false; + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); +#if 0 // TODO(scaprile): Check if this is actually necessary + // Set BRCM_CARDCAP to CMD_NODEC. This is a vendor specific SDIO register + val = MG_BIT(3); cyw_sdio_transfer(true, CYW_SDIO_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); +#endif + // Force HT request to chip backplane + val = MG_BIT(1) /* FORCE_HT */; if(!cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + // Enable Keep SDIO On (KSO) + cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + if (!(val & MG_BIT(0))) { + val |= MG_BIT(0); cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + } + return true; +} + +// clang-format on + +static bool cyw_bus_specific_init(void) { + return cyw_sdio_init(); +} +static size_t cyw_bus_specific_poll(uint32_t *response) { + return cyw_sdio_poll(response); +} +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { + return cyw_sdio_tx(data, len); +} +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + if (f == CYW_SDIO_FUNC_CHIP && len == 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; + return cyw_sdio_transfer(true, f, addr, data, (uint32_t) len); +} +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + return cyw_sdio_transfer(false, f, addr, data, (uint32_t) len); +} + +#endif + // Mongoose Wi-Fi API functions bool mg_wifi_scan(void) { @@ -23130,6 +23327,179 @@ struct mg_tcpip_driver mg_tcpip_driver_same54 = { mg_tcpip_driver_same54_poll}; #endif +#ifdef MG_ENABLE_LINES +#line 1 "src/drivers/sdio.c" +#endif + + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// SDIO 6.9 Table 6-1 CCCR (Common Card Control Registers) +#define MG_SDIO_CCCR_SDIOREV 0x000 +#define MG_SDIO_CCCR_SDREV 0x001 +#define MG_SDIO_CCCR_IOEN 0x002 +#define MG_SDIO_CCCR_IORDY 0x003 +#define MG_SDIO_CCCR_INTEN 0x004 +#define MG_SDIO_CCCR_BIC 0x007 +#define MG_SDIO_CCCR_CCAP 0x008 +#define MG_SDIO_CCCR_CCIS 0x009 // 3 registers +#define MG_SDIO_CCCR_F0BLKSZ 0x010 // 2 registers +#define MG_SDIO_CCCR_HISPD 0x013 +// SDIO 6.10 Table 6-3 FBR (Function Basic Registers) +#define MG_SDIO_FBR_FnBLKSZ(n) (((n) &7) * 0x100 + 0x10) // 2 registers + +// SDIO 5.1 IO_RW_DIRECT Command (CMD52) +#define MG_SDIO_DATA(x) ((x) &0xFF) // bits 0-7 +#define MG_SDIO_ADDR(x) (((x) &0x1FFFF) << 9) // bits 9-25 +#define MG_SDIO_FUNC(x) (((x) &3) << 28) // bits 28-30 (30 unused here) +#define MG_SDIO_WR MG_BIT(31) + +// SDIO 5.3 IO_RW_EXTENDED Command (CMD53) +#define MG_SDIO_LEN(x) ((x) &0x1FF) // bits 0-8 +#define MG_SDIO_OPINC MG_BIT(26) +#define MG_SDIO_BLKMODE MG_BIT(27) + +// - Drivers set blocksize, drivers request transfers. Requesting a read +// transfer > blocksize means block transfer will be used. +// - To simplify the use of DMA transfers and avoid intermediate buffers, +// drivers must have room to accomodate a whole block transfer, e.g.: blocksize +// = 64, read 65 => 2 blocks = 128 bytes +// - Transfers of more than 1 byte assume (uint32_t *) data. 1-byte transfers +// use (uint8_t *) data +// - 'len' is the number of _bytes_ to transfer +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len) { + uint32_t arg, val = 0; + unsigned int blksz = 64; // TODO(): mg_sdio_set_blksz() stores in an array, + // index on f, skip if 0 + if (len == 1) { + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_FUNC(f) | MG_SDIO_ADDR(addr) | + (write ? MG_SDIO_DATA(*(uint8_t *) data) : 0); + bool res = sdio->txn(sdio, 52, arg, &val); // IO_RW_DIRECT + if (!write) *(uint8_t *) data = (uint8_t) val; + return res; + } + // IO_RW_EXTENDED + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_OPINC | MG_SDIO_FUNC(f) | + MG_SDIO_ADDR(addr); + if (len > 512 || (blksz != 0 && len > blksz)) { // SDIO 5.3 512 -> len=0 + unsigned int blkcnt; + if (blksz == 0) return false; // > 512 requires block size set + blkcnt = (len + blksz - 1) / blksz; + if (blkcnt > 511) return false; // we don't support "infinite" blocks + arg |= MG_SDIO_BLKMODE | MG_SDIO_LEN(blkcnt); // block transfer + len = blksz * blkcnt; + } else { + arg |= MG_SDIO_LEN(len); // multi-byte transfer + } + return sdio->xfr(sdio, write, arg, + (arg & MG_SDIO_BLKMODE) ? (uint16_t) blksz : 0, + (uint32_t *) data, len, &val); +} + +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz) { + uint32_t val = blksz & 0xff; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f), &val, 1)) + return false; + val = (blksz >> 8) & 0x0f; // SDIO 6.10 Table 6-4, max 2048 + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f) + 1, &val, 1)) + return false; + // TODO(): store in an array, index on f. Static 8-element array + MG_VERBOSE(("F%c block size set", (f & 7) + '0')); + return true; +} + +// Enable Fx +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7), bits; + uint32_t val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + bits = (uint8_t) val | bit; + unsigned int times = 501; + while (times--) { + val = bits; /* IOEf */ + ; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + mg_delayms(1); + val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + if (val & bit) break; + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c enabled", (f & 7) + '0')); + return true; +} + +// Wait for Fx to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7); + unsigned int times = 501; + while (times--) { + uint32_t val; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IORDY, &val, 1)) + return false; + if (val & bit) break; // IORf + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c ready", (f & 7) + '0')); + return true; +} + +// SDIO 6.14 Bus State Diagram +bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { + uint32_t val = 0; + if (!sdio->txn(sdio, 0, 0, NULL)) return false; // GO_IDLE_STATE + sdio->txn(sdio, 5, 0, &val); // IO_SEND_OP_COND, no CRC + MG_VERBOSE(("IO Functions: %u, Memory: %c", 1 + ((val >> 28) & 7), + (val & MG_BIT(27)) ? 'Y' : 'N')); + if (!sdio->txn(sdio, 3, 0, &val)) return false; // SEND_RELATIVE_ADDR + val = ((uint32_t) val) >> 16; // RCA + if (!sdio->txn(sdio, 7, val << 16, &val)) + return false; // SELECT/DESELECT_CARD + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDIOREV, &val, 1); + MG_DEBUG(("CCCR: %u.%u, SDIO: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3, + 1 + ((val >> 6) & 3), (val >> 4) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDREV, &val, 1); + MG_VERBOSE(("SD: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_BIC, &val, 1); + MG_SET_BITS(val, 3, + MG_BIT(7) | MG_BIT(1)); // SDIO 6.9 Tables 6-1 6-2, 4-bit bus + mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_BIC, &val, 1); + // All Full-Speed SDIO cards support a 4-bit bus. Skip for Low-Speed SDIO + // cards, we don't provide separate low-level functions for width and speed + sdio->cfg(sdio, 0); // set DS; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + if (val & MG_BIT(0) /* SHS */) { + val = MG_BIT(1); /* EHS */ + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + sdio->cfg(sdio, 1); // set HS; + MG_VERBOSE(("Bus set to 4-bit @50MHz")); + } else { + MG_VERBOSE(("Bus set to 4-bit @25MHz")); + } + return true; +} + +// - 6.11 Card Information Structure (CIS): 0x0001000-0x017FF; for card common +// and all functions +// - 16.5 SDIO Card Metaformat +// - 16.7.2 CISTPL_FUNCE (0x22): Function Extension Tuple, provides standard +// information about the card (common) and each individual function. One +// CISTPL_FUNCE in each function’s CIS, immediately following the CISTPL_FUNCID +// tuple +// - 16.7.3 CISTPL_FUNCE Tuple for Function 0 (common) +// - 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 + +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/drivers/stm32f.c" #endif diff --git a/mongoose.h b/mongoose.h index 28d69528..8628ac24 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3009,7 +3009,9 @@ struct mg_profitem { #endif -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW +#if MG_ENABLE_TCPIP && \ + ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) struct mg_tcpip_spi_ { void *spi; // Opaque SPI bus descriptor @@ -3029,7 +3031,7 @@ struct mg_tcpip_driver_cyw_firmware { }; struct mg_tcpip_driver_cyw_data { - struct mg_tcpip_spi_ *spi; + void *bus; struct mg_tcpip_driver_cyw_firmware *fw; char *ssid; char *pass; @@ -3279,6 +3281,45 @@ struct mg_tcpip_driver_same54_data { #endif +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// Specific chip/card driver --> SDIO driver --> HAL --> SDIO hw controller + +// API with HAL for hardware controller +// - Provide a function to init the controller (external) +// - Provide these functions: +struct mg_tcpip_sdio { + void *sdio; // Opaque SDIO bus descriptor + void (*cfg)(void *, uint8_t); // select operating parameters + // SDIO transaction: send cmd with a possible 1-byte read or write + bool (*txn)(void *, uint8_t cmd, uint32_t arg, uint32_t *r); + // SDIO extended transaction: write or read len bytes, using blksz blocks + bool (*xfr)(void *, bool write, uint32_t arg, uint16_t blksz, uint32_t *, + uint32_t len, uint32_t *r); +}; + +// API with driver (e.g.: cyw.c) +// Once the hardware controller has been initialized: +// - Init card: selects the card, sets F0 block size, sets bus width and speed +bool mg_sdio_init(struct mg_tcpip_sdio *sdio); +// - Enable other possible functions (F1 to F7) +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Wait for them to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Set their transfer block length +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz); +// - Transfer data to/from a function (abstracts from transaction type) +// - Requesting a read transfer > blocksize means block transfer will be used. +// - Drivers must have room to accomodate a whole block transfer, see sdio.c +// - Transfers of > 1 byte --> (uint32_t *) data. 1-byte --> (uint8_t *) data +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len); + +#endif + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32F) && \ MG_ENABLE_DRIVER_STM32F diff --git a/src/drivers/cyw.c b/src/drivers/cyw.c index 8fd7caf1..e493bc87 100644 --- a/src/drivers/cyw.c +++ b/src/drivers/cyw.c @@ -1,8 +1,16 @@ #include "cyw.h" #include "net.h" -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW +#if MG_ENABLE_TCPIP && \ + ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) +#ifndef MG_ENABLE_DRIVER_CYW +#define MG_ENABLE_DRIVER_CYW 0 +#endif +#ifndef MG_ENABLE_DRIVER_CYW_SDIO +#define MG_ENABLE_DRIVER_CYW_SDIO 0 +#endif static struct mg_tcpip_if *s_ifp; static bool s_link, s_auth, s_join; @@ -68,10 +76,12 @@ struct mg_tcpip_driver mg_tcpip_driver_cyw = {mg_tcpip_driver_cyw_init, // SPI | SDIO <-- padded to 32-bit | 64-bytes // // - SDPCM has 3 channels (control, data, and asynchronous data) -// - SPI (and SDIO) has 4 "functions", F0 to F3, to access different -// blocks in the chip, like the SPI/SDIO controller, chip backplane, and 2 DMA -// I/Os; these are usually handled by SDPCM but we need to explicitly access -// the I/O controller and chip backplane during initialization +// - SPI has 4 "functions", F0 to F3, to access different blocks in the chip, +// like the SPI/SDIO controller, chip backplane, and 2 DMA I/Os; these are +// usually handled by SDPCM but we need to explicitly access the I/O controller +// and chip backplane during initialization +// - SDIO has 3 functions (proper SDIO terminology), F0 to F2, coincident with +// those for SPI, accessed through standard SDIO practices. There is no F3. // Processor core firmware is loaded to TCM RAM, along with module-dependent // (hardware design) NVRAM data, via the chip backplane access through the bus @@ -121,10 +131,15 @@ struct data_hdr { struct bdc_hdr bdc; }; -// gSPI, DS 4.2.1 Fig.12, 2-bit field -#define CYW_SD_FUNC_BUS 0 // F0 -#define CYW_SD_FUNC_CHIP 1 // F1 -#define CYW_SD_FUNC_WLAN 2 // F2 +// gSPI, CYW43439 DS 4.2.1 Fig.12, 2-bit field +#define CYW_SPID_FUNC_BUS 0 // F0 +#define CYW_SPID_FUNC_CHIP 1 // F1 +#define CYW_SPID_FUNC_WLAN 2 // F2 + +// SDIO functions, 3-bit field; CYW4343W and CYW43439 DS 4.1 +#define CYW_SDIO_FUNC_BUS 0 // F0 +#define CYW_SDIO_FUNC_CHIP 1 // F1 +#define CYW_SDIO_FUNC_WLAN 2 // F2 #define CYW_SDPCM_CTRL_HDR 0 #define CYW_SDPCM_ASYNC_HDR 1 @@ -139,7 +154,7 @@ static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len); static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len); static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len); -static size_t cyw_spi_poll(uint8_t *dest); +static size_t cyw_bus_specific_poll(uint32_t *dest); static void cyw_update_hash_table(void); // High-level comm stuff @@ -152,7 +167,7 @@ static void cyw_poll(void) { cyw_update_hash_table(); s_ifp->update_mac_hash_table = false; } - if (cyw_spi_poll((uint8_t *) resp) == 0) return; // BUS DEPENDENCY + if (cyw_bus_specific_poll(resp) == 0) return; if ((sdpcm->len ^ sdpcm->_len) != 0xffff || sdpcm->len < sizeof(*sdpcm) || sdpcm->len > 2048 - sizeof(*sdpcm)) return; @@ -185,7 +200,7 @@ static void cyw_handle_bdc(struct bdc_hdr *bdc, size_t len) { mg_tcpip_qwrite(payload, len - (payload - (uint8_t *) bdc), s_ifp); } -static size_t cyw_bus_tx(uint32_t *data, uint16_t len); +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len); // WLAN frame transmission static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { @@ -194,15 +209,15 @@ static size_t mg_cyw_tx(unsigned int ifc, void *data, size_t len) { memset(txdata, 0, sizeof(*hdr)); memcpy((uint8_t *) txdata + sizeof(*hdr), data, len); // TODO(): hdr->bdc.priority = map IP to TOS if supporting QoS/ToS - hdr->bdc.flags = 2 << 4; // BDC version 2 - hdr->bdc.flags2 = ifc; // 0 -> STA, 1 -> AP + hdr->bdc.flags = 2 << 4; // BDC version 2 + hdr->bdc.flags2 = (uint8_t) ifc; // 0 -> STA, 1 -> AP // hdr->bdc.data_offset = 0; // actually zeroed above hdr->sdpcm.len = txlen; hdr->sdpcm._len = (uint16_t) ~txlen; hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_DATA_HDR, hdr->sdpcm.sw_hdr.header_length = offsetof(struct data_hdr, bdc); - return cyw_bus_tx(txdata, txlen); + return cyw_bus_specific_tx(txdata, txlen); } // WLAN event handling @@ -250,6 +265,7 @@ struct scan_result; static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, size_t len); +// Do not call any IOCTL functions here, otherwise revise cyw_ioctl_wait() static void cyw_handle_bdc_evnt(struct bdc_hdr *bdc, size_t len) { struct evnt_msg *msg = (struct evnt_msg *) &bdc[bdc->data_offset + 1]; MG_VERBOSE(("%u bytes event", len)); @@ -313,7 +329,7 @@ static bool cyw_ioctl_iovar_set_(unsigned int ifc, char *var, void *data, size_t len); // clang-format off // convenience: ioctl funcs on default ifc (0), as only AP needs ifc 1 -static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } +__attribute__((unused)) static bool cyw_ioctl_get(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_get_(0, cmd, data, len); } static bool cyw_ioctl_set(unsigned int cmd, void *data, size_t len) { return cyw_ioctl_set_(0, cmd, data, len); } static bool cyw_ioctl_iovar_get(char *var, void *data, size_t len) { return cyw_ioctl_iovar_get_(0, var, data, len); } static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ioctl_iovar_set_(0, var, data, len); } @@ -324,9 +340,10 @@ static bool cyw_ioctl_iovar_set(char *var, void *data, size_t len) { return cyw_ // clang-format off static bool cyw_wifi_connect(char *ssid, char *pass) { uint32_t sup_wpa[2] = {0, 1}; // bss index 0 = STA, not open - static const uint32_t const eapver[2] = {0, (uint32_t) -1}, // accept AP version + static const uint32_t eapver[2] = {0, (uint32_t) -1}, // accept AP version tmo[2] = {0, 2500}; uint32_t data[64/4 + 1]; // max pass length: 64 for WPA, 128 for WPA3 SAE + uint16_t *da = (uint16_t *) data; unsigned int len; uint32_t val; val = 4; // security type: 0 for none, 2 for WPA, 4 for WPA2/WPA3, 6 for mixed WPA/WPA2 @@ -340,8 +357,8 @@ static bool cyw_wifi_connect(char *ssid, char *pass) { // skip if not using auth memset(data, 0, sizeof(data)); len = strlen(pass); - ((uint16_t *)data)[0] = (uint16_t) len; - ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + da[0] = (uint16_t) len; + da[1] = 1; // indicates wireless security key, skip for WPA3 SAE memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE if (!cyw_ioctl_set(268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA // for WPA3 SAE: memcpy((uint8_t *)data + sizeof(uint16_t), pass, len); cyw_ioctl_iovar_set("sae_password", data, sizeof(data)); @@ -368,6 +385,7 @@ static bool cyw_wifi_disconnect(void) { static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { uint32_t data[64/4 + 2]; // max pass length: 64 for WPA, 128 for WPA3 SAE + uint16_t *da = (uint16_t *) data; unsigned int len; uint32_t val; // CHIP DEPENDENCY @@ -394,8 +412,8 @@ static bool cyw_wifi_ap_start(char *ssid, char *pass, unsigned int channel) { // NOTE(): WHD does not set SAE password for shared WPA2/WPA3, same do we memset(data, 0, sizeof(data)); len = strlen(pass); - ((uint16_t *)data)[0] = (uint16_t) len; // skip for WPA3 SAE - ((uint16_t *)data)[1] = 1; // indicates wireless security key, skip for WPA3 SAE + da[0] = (uint16_t) len; // skip for WPA3 SAE (43430 does NOT support WPA3 in AP) + da[1] = 1; // indicates wireless security key, skip for WPA3 SAE memcpy((uint8_t *)data + 2 * sizeof(uint16_t), pass, len); // skip for WPA3 SAE if (!cyw_ioctl_set_(1, 268 /* SET_WSEC_PMK */, data, sizeof(data))) return false; // skip for WPA3 SAE, sizeof/2 if supporting SAE but using WPA /* for WPA3 SAE: @@ -539,8 +557,8 @@ static void cyw_handle_scan_result(uint32_t status, struct scan_result *data, si if (sbss->length > len - offsetof(struct scan_result, bss) || sbss->SSID_len > sizeof(sbss->SSID) || sbss->ie_offset < sizeof(*sbss) || sbss->ie_offset > (sizeof(*sbss) + sbss->ie_length) || sbss->ie_offset + sbss->ie_length > sbss->length) return; // silently discard malformed data if (!(sbss->flags & MG_BIT(2))) return; // RSSI_ONCHANNEL, ignore off-channel results - bss.SSID = mg_str_n(sbss->SSID, sbss->SSID_len); - bss.BSSID = sbss->BSSID; + bss.SSID = mg_str_n((char *)sbss->SSID, sbss->SSID_len); + bss.BSSID = (char *)sbss->BSSID; bss.RSSI = (int8_t)sbss->RSSI; bss.has_n = sbss->n_cap != 0; bss.channel = bss.has_n ? sbss->ctl_ch : (uint8_t)(sbss->chanspec & 0xff); // n 40MHz vs a/b/g and 20MHz @@ -575,7 +593,7 @@ static uint8_t *s_ioctl_resp; static bool s_ioctl_err; static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { - uint8_t *resp = (uint8_t *) cdc + sizeof(*cdc); + uint8_t *r = (uint8_t *) cdc + sizeof(*cdc); MG_VERBOSE(("%u bytes CDC frame", len)); if ((cdc->flags >> 16) != s_ioctl_reqid) return; if (cdc->flags & 1) { @@ -584,9 +602,8 @@ static void cyw_handle_cdc(struct cdc_hdr *cdc, size_t len) { return; } if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump((void *) cdc, len); - MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", resp[0], resp[1], resp[2], - resp[3])); - s_ioctl_resp = resp; + MG_DEBUG(("IOCTL result: %02x %02x %02x %02x ...", r[0], r[1], r[2], r[3])); + s_ioctl_resp = r; } // NOTE(): alt no loop handler dispatching IOCTL response to current handler: // static void *s_ioctl_hnd; *s_ioctl_hnd(ioctl, len); @@ -618,7 +635,7 @@ static void cyw_ioctl_send_cmd(unsigned int ifc, unsigned int cmd, bool set, hdr->sdpcm.sw_hdr.sequence = ++s_tx_seqno; hdr->sdpcm.sw_hdr.channel_and_flags = CYW_SDPCM_CTRL_HDR; hdr->sdpcm.sw_hdr.header_length = offsetof(struct ctrl_hdr, cdc); - cyw_bus_tx(txdata, txlen); + cyw_bus_specific_tx(txdata, txlen); } // just send respective commands, response handled via CDC handler @@ -652,20 +669,29 @@ static void cyw_ioctl_send_iovar_set2(unsigned int ifc, char *var, void *data1, cyw_ioctl_send_cmd(ifc, 263, true, txlen); // cmd = SET IOVAR } -static void cyw_ioctl_send_iovar_set(unsigned int ifc, char *var, void *data, - size_t len) { +__attribute__((unused)) static void cyw_ioctl_send_iovar_set(unsigned int ifc, + char *var, + void *data, + size_t len) { cyw_ioctl_send_iovar_set2(ifc, var, data, len, NULL, 0); } +static inline bool delayms(unsigned int ms) { + mg_delayms(ms); + return true; +} + // wait for a response, meanwhile delivering received frames and events static bool cyw_ioctl_wait(void) { - unsigned int times = 6000; + unsigned int times = 100; s_ioctl_resp = NULL; s_ioctl_err = false; - while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0) - cyw_poll(); // TODO(scaprile): review wait/sleep strategy (this loop is executed only when initializing/acting on the chip) - MG_DEBUG(("resp: %lp, err: %c, times: %d", s_ioctl_resp, - s_ioctl_err ? '1' : '0', (int) times)); + do { // IOCTL response processing does not call any other IOCTL function + cyw_poll(); // otherwise we can't allow them to pile up here + // network frames will be pushed to the queue so that is safe + } while (s_ioctl_resp == NULL && !s_ioctl_err && times-- > 0 && delayms(1)); + MG_VERBOSE(("resp: %lp, err: %c, times: %d", s_ioctl_resp, + s_ioctl_err ? '1' : '0', (int) times)); return s_ioctl_resp != NULL; } @@ -723,15 +749,22 @@ struct clm_hdr { #pragma pack(pop) -// worlwide rev0, try rev 17 for 4343W +// worlwide rev0, TODO(): try rev 17 for 4343W static const uint32_t country_code = 'X' + ('X' << 8) + (0 << 16); -static bool cyw_spi_init(); +static bool cyw_bus_specific_init(); +static bool cyw_load_clmll(void *data, size_t len); + +static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { + return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); +} // clang-format off static bool cyw_init(uint8_t *mac) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; uint32_t val = 0; - if (!cyw_spi_init()) return false; // BUS DEPENDENCY + if (!cyw_bus_specific_init()) return false; + if (!cyw_load_clm(d->fw)) return false; // Load CLM blob // BT-ENABLED DEPENDENCY // set Wi-Fi up val = 0 /* disable */; cyw_ioctl_iovar_set("bus:txglom", (uint8_t *)&val, sizeof(val)); @@ -759,7 +792,7 @@ static bool cyw_init(uint8_t *mac) { unsigned int times = 100; while (times --) if (cyw_ioctl_iovar_set("bsscfg:event_msgs", (uint8_t *)data, sizeof(data))) break; - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; } val = 0; if (!cyw_ioctl_set(64 /* SET_ANTDIV */, (uint8_t *)&val, sizeof(val))) return false; if (!cyw_ioctl_set(2 /* UP, interface up */, NULL, 0)) return false; @@ -818,20 +851,18 @@ static bool cyw_load_clmll(void *data, size_t len) { break; sent += bytes; offset += bytes; - hdr.flag &= ~MG_BIT(1); // DL_BEGIN + hdr.flag &= (uint16_t)~MG_BIT(1); // DL_BEGIN } return sent >= len; } // clang-format on -static bool cyw_load_clm(struct mg_tcpip_driver_cyw_firmware *fw) { - return cyw_load_clmll((void *) fw->clm_addr, fw->clm_len); -} - static void cyw_update_hash_table(void) { // TODO(): read database, rebuild hash table uint32_t val = 0; - val = 1; cyw_ioctl_iovar_set2_(0, "mcast_list", (uint8_t *)&val, sizeof(val), (uint8_t *)mcast_addr, sizeof(mcast_addr)); + val = 1; + cyw_ioctl_iovar_set2_(0, "mcast_list", (uint8_t *) &val, sizeof(val), + (uint8_t *) mcast_addr, sizeof(mcast_addr)); mg_delayms(50); } @@ -848,8 +879,16 @@ static void cyw_update_hash_table(void) { #define CYW_CHIP_BCKPLN_ADDRMSK 0x7fff #define CYW_CHIP_BCKPLN_ACCSS4B MG_BIT(15) #define CYW_CHIP_BCKPLN_WRAPPOFF 0x100000 +// BUS DEPENDENCY: max bus to backplane transfer size, bus function id #define CYW_CHIP_BCKPLN_SPIMAX 64 #define CYW_CHIP_BCKPLN_SDIOMAX 1536 +#if MG_ENABLE_DRIVER_CYW_SDIO +#define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SDIOMAX +#define CYW_BUS_FUNC_CHIP CYW_SDIO_FUNC_CHIP +#else +#define CYW_CHIP_BCKPLN_BUSMAX CYW_CHIP_BCKPLN_SPIMAX +#define CYW_BUS_FUNC_CHIP CYW_SPID_FUNC_CHIP +#endif // CHIP DEPENDENCY #define CYW_CHIP_ARMCORE_BASE (CYW_CHIP_CHIPCOMMON + 0x3000) @@ -874,9 +913,9 @@ static void cyw_update_hash_table(void) { #define CYW_CHIP_AI_IOCTRL 0x408 #define CYW_CHIP_AI_RESETCTRL 0x800 -static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, uint16_t len); -static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, uint16_t len); // clang-format off @@ -884,9 +923,9 @@ static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, static void cyw_set_backplane_window(uint32_t addr) { uint32_t val; addr &= ~CYW_CHIP_BCKPLN_ADDRMSK; - val = (addr >> 24) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); - val = (addr >> 16) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); - val = (addr >> 8) & 0xff; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); + val = (addr >> 24) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRHIGH, &val, 1); + val = (addr >> 16) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRMID, &val, 1); + val = (addr >> 8) & 0xff; cyw_bus_write(CYW_BUS_FUNC_CHIP, CYW_CHIP_ADDRLOW, &val, 1); } static bool cyw_core_reset(uint32_t core_base, bool check) { @@ -894,21 +933,21 @@ static bool cyw_core_reset(uint32_t core_base, bool check) { // core disabled after chip reset cyw_set_backplane_window(core_base); // set backplane window for requested area; we do know offsets fall within that window // possible CHIP DEPENDENCY: AI_RESETSTATUS check and wait (instead of these cool reads) to ensure backplane operations end - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end - val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(1) | MG_BIT(0) /* SICF_FGC | SICF_CLOCK_EN */; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // reset + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = 0x00; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // release reset mg_delayms(1); - val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_spi_write(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end + val = MG_BIT(0) /* SICF_CLOCK_EN */; cyw_bus_write(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); // ensure backplane operations end mg_delayms(1); if (check) { // Verify only clock is enabled - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_IOCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); if ((val & (MG_BIT(1) | MG_BIT(0)) /* SICF_FGC | SICF_CLOCK_EN) */) != MG_BIT(0)) return false; // Verify it is not in reset state - cyw_spi_read(CYW_SD_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); + cyw_bus_read(CYW_BUS_FUNC_CHIP, (core_base + CYW_CHIP_AI_RESETCTRL) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 1); if (val & MG_BIT(0)) return false; // AIRC_RESET } return true; @@ -916,29 +955,28 @@ static bool cyw_core_reset(uint32_t core_base, bool check) { static void cyw_socram_init(void) { uint32_t val; - // CHIP DEPENDENCY: disable remap for SRAM_3 + // CHIP DEPENDENCY: disable remap for SRAM_3: 43430 and 43439 only cyw_set_backplane_window(CYW_CHIP_SOCSRAM_BASE); // set backplane window for requested area; we do know offsets fall within that window - val = 0x03; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + val = 0x03; cyw_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXIDX) & CYW_CHIP_BCKPLN_ADDRMSK), &val, sizeof(val)); + val = 0x00; cyw_bus_write(CYW_BUS_FUNC_CHIP, ((CYW_CHIP_SOCSRAM_BASE + CYW_CHIP_SOCSRAM_BANKXPDA) & CYW_CHIP_BCKPLN_ADDRMSK), &val, sizeof(val)); } // transfer is fractioned in bus-to-backplane-size units within backplane windows static void cyw_load_data(uint32_t dest, void *data, size_t len) { size_t sent = 0, offset = 0; - uint32_t last_addr = ~0; + uint32_t last_addr = (uint32_t) ~0; while (sent < len) { size_t bytes = len - sent, avail; uint32_t addr = dest + offset; - if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == ~0) { + if (addr - last_addr >= CYW_CHIP_BCKPLN_WINSZ || last_addr == (uint32_t) ~0) { cyw_set_backplane_window(addr); // set backplane window for requested area last_addr = addr & ~CYW_CHIP_BCKPLN_ADDRMSK; } addr &= CYW_CHIP_BCKPLN_ADDRMSK; - avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; + avail = CYW_CHIP_BCKPLN_WINSZ - (unsigned int) addr; // internal backplane limit if (bytes > avail) bytes = avail; - // BUS DEPENDENCY: max bus to backplane transfer size - if (bytes > CYW_CHIP_BCKPLN_SPIMAX) bytes = CYW_CHIP_BCKPLN_SPIMAX; - cyw_spi_write(CYW_SD_FUNC_CHIP, addr | CYW_CHIP_BCKPLN_ACCSS4B, (uint8_t *)data + offset, bytes); + if (bytes > CYW_CHIP_BCKPLN_BUSMAX) bytes = CYW_CHIP_BCKPLN_BUSMAX; // bus to backplane transfer limit + cyw_bus_write(CYW_BUS_FUNC_CHIP, addr, (uint8_t *)data + offset, (uint16_t) bytes); sent += bytes; offset += bytes; } @@ -950,13 +988,13 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv cyw_core_reset(CYW_CHIP_SOCSRAM, false); // cores were disabled at chip reset cyw_socram_init(); cyw_load_data(CYW_CHIP_ATCMRAM_BASE, fwdata, fwlen); - mg_delayms(5); // ************ CHECK IF THIS IS ACTUALLY NEEDED + mg_delayms(5); // TODO(scaprile): CHECK IF THIS IS ACTUALLY NEEDED // Load NVRAM and place 'length ~length' at the end; end of chip RAM { const uint32_t start = CYW_CHIP_RAM_SIZE - 4 - nvramlen; cyw_load_data(start, nvramdata, nvramlen); // nvramlen must be a multiple of 4 // RAM_SIZE is a multiple of WINSZ, so the place for len ~len will be at the end of the window - cyw_spi_write(CYW_SD_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4) | CYW_CHIP_BCKPLN_ACCSS4B, &val, sizeof(val)); + cyw_bus_write(CYW_BUS_FUNC_CHIP, (CYW_CHIP_BCKPLN_WINSZ - 4), &val, sizeof(val)); } // Reset ARM core and check it starts if (!cyw_core_reset(CYW_CHIP_ARMCORE, true)) return false; @@ -964,6 +1002,8 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv } // clang-format on +#if !MG_ENABLE_DRIVER_CYW_SDIO + // CYW43 SPI bus specifics #define CYW_BUS_SPI_BUSCTRL 0x00 // 4 regs, 0 to 3 @@ -975,29 +1015,33 @@ static bool cyw_load_fwll(void *fwdata, size_t fwlen, void *nvramdata, size_t nv #define CYW_BUS_STS_LEN(x) ((x >> 9) & 0x7ff) +static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, + uint16_t len); +static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, + uint16_t len); + // clang-format off static size_t cyw_spi_poll(uint8_t *response) { size_t len; uint32_t res; // SPI poll - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); - if (res == ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &res, sizeof(res)); + if (res == (uint32_t) ~0 || !(res & MG_BIT(8) /* packet available */ )) return 0; len = CYW_BUS_STS_LEN(res); if (len == 0) { // just ack IRQ uint16_t val = 1; - cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_SPIFRCTRL, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, sizeof(val)); return 0; } - cyw_spi_read(CYW_SD_FUNC_WLAN, 0, response, len); + cyw_spi_read(CYW_SPID_FUNC_WLAN, 0, response, len); return len; } -// BUS DEPENDENCY: name is generic but function is bus dependent -static size_t cyw_bus_tx(uint32_t *data, uint16_t len) { - while (len & 3) data[len++] = 0; // SPI 32-bit padding (SDIO->64-byte) - return cyw_spi_write(CYW_SD_FUNC_WLAN, 0, data, len) ? len: 0; +static size_t cyw_spi_tx(uint32_t *data, uint16_t len) { + while (len & 3) data[len++] = 0; // SPI 32-bit padding + return cyw_spi_write(CYW_SPID_FUNC_WLAN, 0, data, len) ? len: 0; } // this can be integrated in lowest level SPI read/write _driver_ functions @@ -1007,20 +1051,19 @@ uint32_t sw16_2(uint32_t data) { } // DS 4.2.2 Table 6: signal we're working in 16-bit mode -#define CYW_SD_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space +#define CYW_SPI_16bMODE MG_BIT(2) // arbitrary bit out of the FUNC space static bool cyw_spi_init() { - struct mg_tcpip_driver_cyw_data *d = - (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; uint32_t val = 0; // DS 4.2.3 Boot-Up Sequence; WHD: other chips might require more effort unsigned int times = 51; while (times--) { - cyw_spi_read(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (sw16_2(val) == 0xFEEDBEAD) break; mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // DS 4.2.3 Table 6. Chip starts in 16-bit little-endian mode. // Configure SPI and switch to 32-bit big-endian mode: // - High-speed mode: d->hs true @@ -1028,33 +1071,34 @@ static bool cyw_spi_init() { // - SPI RESPONSE DELAY 4 bytes time [not in DS] TODO(scaprile): logic ana // - Status not sent after command, IRQ with status val = sw16_2(0x000204a3 | (d->hs ? MG_BIT(4) : 0)); // 4 reg content - cyw_spi_write(CYW_SD_FUNC_BUS | CYW_SD_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_write(CYW_SPID_FUNC_BUS | CYW_SPI_16bMODE, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); mg_tcpip_call(s_ifp, MG_TCPIP_EV_DRIVER, NULL); - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_TEST, &val, sizeof(val)); if (val != 0xFEEDBEAD) return false; - val = 4; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); + val = 4; cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_RESPDLY_F1, &val, 1); val = 0x99; // clear error bits DATA_UNAVAILABLE, COMMAND_ERROR, DATA_ERROR, F1_OVERFLOW - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INT, &val, 1); val = 0x00be; // Enable IRQs F2_F3_FIFO_RD_UNDERFLOW, F2_F3_FIFO_WR_OVERFLOW, COMMAND_ERROR, DATA_ERROR, F2_PACKET_AVAILABLE, F1_OVERFLOW // BT-ENABLED DEPENDENCY: add F1_INTR (bit 13) - cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); // chip backplane is ready, initialize it // request ALP (Active Low Power) clock - val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = MG_BIT(3) /* ALP_REQ */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // BT-ENABLED DEPENDENCY times = 10; while (times--) { - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(6)) break; // ALP_AVAIL mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // clear request - val = 0; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = 0; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area - cyw_spi_read(CYW_SD_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); - MG_INFO(("WLAN chip is CYW%u", *((uint16_t *)&val))); + cyw_spi_read(CYW_SPID_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + if (val == 43430) val = 4343; + MG_INFO(("WLAN chip is CYW%u%c", val), val == 4343 ? 'W' : ' ')); // Load firmware (code and NVRAM) if (!cyw_load_firmware(d->fw)) return false; @@ -1062,77 +1106,74 @@ static bool cyw_spi_init() { // Wait for High Throughput (HT) clock ready times = 50; while (times--) { - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); if (val & MG_BIT(7)) break; // HT_AVAIL mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // Wait for backplane ready times = 1000; while (times--) { - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); if (val & MG_BIT(5)) break; // F2_RX_READY mg_delayms(1); } - if (times == ~0) return false; + if (times == (unsigned int) ~0) return false; // CHIP DEPENDENCY // Enable save / restore // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); - val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); #if 0 // Set BRCM_CARDCAP to CMD_NODEC. NOTE(): This is probably only necessary for SDIO, not SPI - val = MG_BIT(3); cyw_spi_write(CYW_SD_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); + val = MG_BIT(3); cyw_spi_write(CYW_SPID_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); #endif // Force HT request to chip backplane - val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + val = MG_BIT(1) /* FORCE_HT */; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); // Enable Keep SDIO On (KSO) - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); if (!(val & MG_BIT(0))) { - val |= MG_BIT(0); cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + val |= MG_BIT(0); cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); } // The SPI bus can be configured for sleep (KSO controls wlan block sleep) - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); - val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); + val &= ~MG_BIT(7) /* WAKE_UP */; cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_BUSCTRL, &val, sizeof(val)); // Set SPI bus sleep - val = 0x0f; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + val = 0x0f; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear pullups. NOTE(): ? - val = 0x00; cyw_spi_write(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); - cyw_spi_read(CYW_SD_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + val = 0x00; cyw_spi_write(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + cyw_spi_read(CYW_SPID_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); // Clear possible data unavailable error - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); - if (val & MG_BIT(0)) cyw_spi_write(CYW_SD_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); - - // Load CLM blob - if (!cyw_load_clm(d->fw)) return false; + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); + if (val & MG_BIT(0)) cyw_spi_write(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_INTEN, &val, sizeof(uint16_t)); return true; } // clang-format on -// gSPI, DS 4.2.1 Fig.12 -#define CYW_SD_LEN(x) ((x) &0x7FF) // bits 0-10 -#define CYW_SD_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, -#define CYW_SD_FUNC(x) (((x) &3) << 28) // bits 28-29 -#define CYW_SD_INC MG_BIT(30) -#define CYW_SD_WR MG_BIT(31) +// gSPI, CYW43439 DS 4.2.1 Fig.12 +#define CYW_SPID_LEN(x) ((x) &0x7FF) // bits 0-10 +#define CYW_SPID_ADDR(x) (((x) &0x1FFFF) << 11) // bits 11-27, +#define CYW_SPID_FUNC(x) (((x) &3) << 28) // bits 28-29 +#define CYW_SPID_INC MG_BIT(30) +#define CYW_SPID_WR MG_BIT(31) static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, uint16_t len) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; - struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; - uint32_t hdr = CYW_SD_WR | CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | - CYW_SD_LEN(len); // gSPI header + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->bus; + uint32_t hdr = CYW_SPID_WR | CYW_SPID_INC | CYW_SPID_FUNC(f) | + CYW_SPID_ADDR(addr) | CYW_SPID_LEN(len); // gSPI header // TODO(scaprile): check spin in between and timeout values, return false - if (f == CYW_SD_FUNC_WLAN) { + if (f == CYW_SPID_FUNC_WLAN) { uint32_t val = 0; while ((val & MG_BIT(5)) != MG_BIT(5)) // F2 rx ready (FIFO ready) - cyw_spi_read(CYW_SD_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); + cyw_spi_read(CYW_SPID_FUNC_BUS, CYW_BUS_SPI_STATUS, &val, sizeof(val)); } - if (f & CYW_SD_16bMODE) + if (f & CYW_SPI_16bMODE) hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode s->begin(NULL); @@ -1148,26 +1189,26 @@ static bool cyw_spi_write(unsigned int f, uint32_t addr, void *data, return true; } -// will write 32-bit aligned quantities to data if f == CYW_SD_FUNC_WLAN +// will write 32-bit aligned quantities to data if f == CYW_SPID_FUNC_WLAN static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, uint16_t len) { struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; - struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->spi; + struct mg_tcpip_spi_ *s = (struct mg_tcpip_spi_ *) d->bus; uint32_t padding = - f == CYW_SD_FUNC_CHIP + f == CYW_SPID_FUNC_CHIP ? 4 : 0; // add padding to chip backplane reads as a response delay - uint32_t hdr = CYW_SD_INC | CYW_SD_FUNC(f) | CYW_SD_ADDR(addr) | - CYW_SD_LEN(len + padding); // gSPI header - if (f == CYW_SD_FUNC_WLAN && (len & 3)) + uint32_t hdr = CYW_SPID_INC | CYW_SPID_FUNC(f) | CYW_SPID_ADDR(addr) | + CYW_SPID_LEN(len + padding); // gSPI header + if (f == CYW_SPID_FUNC_WLAN && (len & 3)) len = (len + 4) & ~3; // align WLAN transfers to 32-bit - if (f & CYW_SD_16bMODE) + if (f & CYW_SPI_16bMODE) hdr = sw16_2(hdr); // swap half-words in 16-bit little-endian mode s->begin(NULL); s->txn(NULL, (uint8_t *) &hdr, NULL, sizeof(hdr)); - if (f == CYW_SD_FUNC_CHIP) { + if (f == CYW_SPID_FUNC_CHIP) { uint32_t pad; s->txn(NULL, NULL, (uint8_t *) &pad, 4); // read padding back and discard } @@ -1175,6 +1216,160 @@ static void cyw_spi_read(unsigned int f, uint32_t addr, void *data, s->end(NULL); } +static bool cyw_bus_specific_init(void) { + return cyw_spi_init(); +} +static size_t cyw_bus_specific_poll(uint32_t *response) { + return cyw_spi_poll((uint8_t *) response); +} +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { + return cyw_spi_tx(data, len); +} +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + if (f == CYW_SPID_FUNC_CHIP && len >= 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; + return cyw_spi_write(f, addr, data, len); +} +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + cyw_spi_read(f, addr, data, len); + return true; +} + +#else // MG_ENABLE_DRIVER_CYW_SDIO + +#include "sdio.h" + +// CYW43 SDIO bus specifics + +// CYW4343W and CYW43439 DS 4.1 SDIO v2.0: +//- F0: max block size is 32 bytes +//- F1: max block size is 64 bytes +//- F2: max block size is 512 bytes + +// clang-format off +static bool cyw_sdio_transfer(bool write, unsigned int f, uint32_t addr, void *data, uint32_t len) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; + uint32_t *ptr = (uint32_t *) data; // assume 32-bit aligned data (all except firmware) + if (write && (size_t) data & 3) { // missed, source data is not 32-bit aligned + memcpy(txdata, data, len); // copy to an aligned buffer, we know it fits + ptr = txdata; + } // all possible read destinations are 32-bit aligned + // mg_sdio_transfer requires 32-bit alignment for > 1 byte transfers + return mg_sdio_transfer(s, write, f, addr, ptr, len); +} + +static size_t cyw_sdio_poll(uint32_t *response) { + uint32_t res; + uint16_t *len = (uint16_t *)&res; + // WHD: internal docs, "tag" hinting a possible packet. + // This is actually the len / ~len field of a possible struct sdpcm_hdr, if there is a packet available, or 0 if there is none. + cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, &res, sizeof(res)); // read "the tag" + if ((len[0] | len[1]) == 0 || (len[0] ^ len[1]) != 0xffff || *len <= 4) return 0; + response[0] = res; // copy what we already read, then read the rest + cyw_sdio_transfer(false, CYW_SDIO_FUNC_WLAN, 0, response + 1, *len - sizeof(res)); + return (size_t)*len; +} + +static size_t cyw_sdio_tx(uint32_t *data, uint16_t len) { + return cyw_sdio_transfer(true, CYW_SDIO_FUNC_WLAN, 0, data, len) ? len: 0; +} + +static bool cyw_sdio_init() { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) s_ifp->driver_data; + struct mg_tcpip_sdio *s = (struct mg_tcpip_sdio *) d->bus; + uint32_t val = 0; + if (!mg_sdio_init(s)) return false; + // no block transfers on F0. if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_BUS, 32)) return false; + if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_CHIP, 64)) return false; + if (!mg_sdio_set_blksz(s, CYW_SDIO_FUNC_WLAN, 64)) return false; + // TODO(scaprile): we don't handle SDIO interrupts, study CCCR INTEN and SDIO support (SDIO 6.3, 8) + // Enable chip backplane (F1) + if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_CHIP)) return false; + // Wait for F1 to be ready + if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_CHIP)) return false; + // chip backplane is ready, initialize it + // request ALP (Active Low Power) clock + val = MG_BIT(5) | MG_BIT(3) | MG_BIT(0); // HW_CLKREQ_OFF ALP_REQ FORCE_ALP + cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // BT-ENABLED DEPENDENCY + unsigned int times = 10; + while (times--) { + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + if (val & MG_BIT(6)) break; // ALP_AVAIL + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + // clear request + val = 0; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1); + // Enable WLAN (F2) + if (!mg_sdio_enable_f(s, CYW_SDIO_FUNC_WLAN)) return false; + // Clear pullups. NOTE(): ? + val = 0x00; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_PULLUP, &val, 1); + // we don't handle wake nor OOB interrupts; SEP_INT_CTL is a vendor specific SDIO register + // SDIO interrupts: enable F2 interrupt only + + cyw_set_backplane_window(CYW_CHIP_CHIPCOMMON); // set backplane window to start of CHIPCOMMON area + cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, (CYW_CHIP_CHIPCOMMON + 0x00) & CYW_CHIP_BCKPLN_ADDRMSK, &val, 2); + if (val == 43430) val = 4343; + MG_INFO(("WLAN chip is CYW%u%c", val, val == 4343 ? 'W' : ' ')); + // Load firmware (code and NVRAM) + if (!cyw_load_firmware(d->fw)) return false; + + // Wait for High Throughput (HT) clock ready + times = 50; + while (times--) { + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + if (val & MG_BIT(7)) break; // HT_AVAIL + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + // Wait for WLAN ready + if (!mg_sdio_waitready_f(s, CYW_SDIO_FUNC_WLAN)) return false; + + // CHIP DEPENDENCY + // Enable save / restore + // Configure WakeupCtrl, set HT_AVAIL in CLOCK_CSR + if(!cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1)) return false; + val |= MG_BIT(1) /* WAKE_TILL_HT_AVAIL */; cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_WAKEUPCTL, &val, 1); +#if 0 // TODO(scaprile): Check if this is actually necessary + // Set BRCM_CARDCAP to CMD_NODEC. This is a vendor specific SDIO register + val = MG_BIT(3); cyw_sdio_transfer(true, CYW_SDIO_FUNC_BUS, 0xf0 /* SDIOD_CCCR_BRCM_CARDCAP */, &val, 1); +#endif + // Force HT request to chip backplane + val = MG_BIT(1) /* FORCE_HT */; if(!cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_CLOCKCSR, &val, 1)) return false; + // Enable Keep SDIO On (KSO) + cyw_sdio_transfer(false, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + if (!(val & MG_BIT(0))) { + val |= MG_BIT(0); cyw_sdio_transfer(true, CYW_SDIO_FUNC_CHIP, CYW_CHIP_SLEEPCSR, &val, 1); + } + return true; +} + +// clang-format on + +static bool cyw_bus_specific_init(void) { + return cyw_sdio_init(); +} +static size_t cyw_bus_specific_poll(uint32_t *response) { + return cyw_sdio_poll(response); +} +static size_t cyw_bus_specific_tx(uint32_t *data, uint16_t len) { + return cyw_sdio_tx(data, len); +} +static bool cyw_bus_write(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + if (f == CYW_SDIO_FUNC_CHIP && len == 4) addr |= CYW_CHIP_BCKPLN_ACCSS4B; + return cyw_sdio_transfer(true, f, addr, data, (uint32_t) len); +} +static bool cyw_bus_read(unsigned int f, uint32_t addr, void *data, + uint16_t len) { + return cyw_sdio_transfer(false, f, addr, data, (uint32_t) len); +} + +#endif + // Mongoose Wi-Fi API functions bool mg_wifi_scan(void) { diff --git a/src/drivers/cyw.h b/src/drivers/cyw.h index 20d7a430..e2160997 100644 --- a/src/drivers/cyw.h +++ b/src/drivers/cyw.h @@ -1,6 +1,8 @@ #pragma once -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW +#if MG_ENABLE_TCPIP && \ + ((defined(MG_ENABLE_DRIVER_CYW) && MG_ENABLE_DRIVER_CYW) || \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO)) struct mg_tcpip_spi_ { void *spi; // Opaque SPI bus descriptor @@ -20,7 +22,7 @@ struct mg_tcpip_driver_cyw_firmware { }; struct mg_tcpip_driver_cyw_data { - struct mg_tcpip_spi_ *spi; + void *bus; struct mg_tcpip_driver_cyw_firmware *fw; char *ssid; char *pass; diff --git a/src/drivers/sdio.c b/src/drivers/sdio.c new file mode 100644 index 00000000..4a35475e --- /dev/null +++ b/src/drivers/sdio.c @@ -0,0 +1,169 @@ +#include "sdio.h" + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// SDIO 6.9 Table 6-1 CCCR (Common Card Control Registers) +#define MG_SDIO_CCCR_SDIOREV 0x000 +#define MG_SDIO_CCCR_SDREV 0x001 +#define MG_SDIO_CCCR_IOEN 0x002 +#define MG_SDIO_CCCR_IORDY 0x003 +#define MG_SDIO_CCCR_INTEN 0x004 +#define MG_SDIO_CCCR_BIC 0x007 +#define MG_SDIO_CCCR_CCAP 0x008 +#define MG_SDIO_CCCR_CCIS 0x009 // 3 registers +#define MG_SDIO_CCCR_F0BLKSZ 0x010 // 2 registers +#define MG_SDIO_CCCR_HISPD 0x013 +// SDIO 6.10 Table 6-3 FBR (Function Basic Registers) +#define MG_SDIO_FBR_FnBLKSZ(n) (((n) &7) * 0x100 + 0x10) // 2 registers + +// SDIO 5.1 IO_RW_DIRECT Command (CMD52) +#define MG_SDIO_DATA(x) ((x) &0xFF) // bits 0-7 +#define MG_SDIO_ADDR(x) (((x) &0x1FFFF) << 9) // bits 9-25 +#define MG_SDIO_FUNC(x) (((x) &3) << 28) // bits 28-30 (30 unused here) +#define MG_SDIO_WR MG_BIT(31) + +// SDIO 5.3 IO_RW_EXTENDED Command (CMD53) +#define MG_SDIO_LEN(x) ((x) &0x1FF) // bits 0-8 +#define MG_SDIO_OPINC MG_BIT(26) +#define MG_SDIO_BLKMODE MG_BIT(27) + +// - Drivers set blocksize, drivers request transfers. Requesting a read +// transfer > blocksize means block transfer will be used. +// - To simplify the use of DMA transfers and avoid intermediate buffers, +// drivers must have room to accomodate a whole block transfer, e.g.: blocksize +// = 64, read 65 => 2 blocks = 128 bytes +// - Transfers of more than 1 byte assume (uint32_t *) data. 1-byte transfers +// use (uint8_t *) data +// - 'len' is the number of _bytes_ to transfer +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len) { + uint32_t arg, val = 0; + unsigned int blksz = 64; // TODO(): mg_sdio_set_blksz() stores in an array, + // index on f, skip if 0 + if (len == 1) { + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_FUNC(f) | MG_SDIO_ADDR(addr) | + (write ? MG_SDIO_DATA(*(uint8_t *) data) : 0); + bool res = sdio->txn(sdio, 52, arg, &val); // IO_RW_DIRECT + if (!write) *(uint8_t *) data = (uint8_t) val; + return res; + } + // IO_RW_EXTENDED + arg = (write ? MG_SDIO_WR : 0) | MG_SDIO_OPINC | MG_SDIO_FUNC(f) | + MG_SDIO_ADDR(addr); + if (len > 512 || (blksz != 0 && len > blksz)) { // SDIO 5.3 512 -> len=0 + unsigned int blkcnt; + if (blksz == 0) return false; // > 512 requires block size set + blkcnt = (len + blksz - 1) / blksz; + if (blkcnt > 511) return false; // we don't support "infinite" blocks + arg |= MG_SDIO_BLKMODE | MG_SDIO_LEN(blkcnt); // block transfer + len = blksz * blkcnt; + } else { + arg |= MG_SDIO_LEN(len); // multi-byte transfer + } + return sdio->xfr(sdio, write, arg, + (arg & MG_SDIO_BLKMODE) ? (uint16_t) blksz : 0, + (uint32_t *) data, len, &val); +} + +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz) { + uint32_t val = blksz & 0xff; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f), &val, 1)) + return false; + val = (blksz >> 8) & 0x0f; // SDIO 6.10 Table 6-4, max 2048 + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_FBR_FnBLKSZ(f) + 1, &val, 1)) + return false; + // TODO(): store in an array, index on f. Static 8-element array + MG_VERBOSE(("F%c block size set", (f & 7) + '0')); + return true; +} + +// Enable Fx +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7), bits; + uint32_t val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + bits = (uint8_t) val | bit; + unsigned int times = 501; + while (times--) { + val = bits; /* IOEf */ + ; + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + mg_delayms(1); + val = 0; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IOEN, &val, 1)) + return false; + if (val & bit) break; + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c enabled", (f & 7) + '0')); + return true; +} + +// Wait for Fx to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f) { + uint8_t bit = 1U << (f & 7); + unsigned int times = 501; + while (times--) { + uint32_t val; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_IORDY, &val, 1)) + return false; + if (val & bit) break; // IORf + mg_delayms(1); + } + if (times == (unsigned int) ~0) return false; + MG_VERBOSE(("F%c ready", (f & 7) + '0')); + return true; +} + +// SDIO 6.14 Bus State Diagram +bool mg_sdio_init(struct mg_tcpip_sdio *sdio) { + uint32_t val = 0; + if (!sdio->txn(sdio, 0, 0, NULL)) return false; // GO_IDLE_STATE + sdio->txn(sdio, 5, 0, &val); // IO_SEND_OP_COND, no CRC + MG_VERBOSE(("IO Functions: %u, Memory: %c", 1 + ((val >> 28) & 7), + (val & MG_BIT(27)) ? 'Y' : 'N')); + if (!sdio->txn(sdio, 3, 0, &val)) return false; // SEND_RELATIVE_ADDR + val = ((uint32_t) val) >> 16; // RCA + if (!sdio->txn(sdio, 7, val << 16, &val)) + return false; // SELECT/DESELECT_CARD + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDIOREV, &val, 1); + MG_DEBUG(("CCCR: %u.%u, SDIO: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3, + 1 + ((val >> 6) & 3), (val >> 4) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_SDREV, &val, 1); + MG_VERBOSE(("SD: %u.%u", 1 + ((val >> 2) & 3), (val >> 0) & 3)); + mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_BIC, &val, 1); + MG_SET_BITS(val, 3, + MG_BIT(7) | MG_BIT(1)); // SDIO 6.9 Tables 6-1 6-2, 4-bit bus + mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_BIC, &val, 1); + // All Full-Speed SDIO cards support a 4-bit bus. Skip for Low-Speed SDIO + // cards, we don't provide separate low-level functions for width and speed + sdio->cfg(sdio, 0); // set DS; + if (!mg_sdio_transfer(sdio, false, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + if (val & MG_BIT(0) /* SHS */) { + val = MG_BIT(1); /* EHS */ + if (!mg_sdio_transfer(sdio, true, 0, MG_SDIO_CCCR_HISPD, &val, 1)) + return false; + sdio->cfg(sdio, 1); // set HS; + MG_VERBOSE(("Bus set to 4-bit @50MHz")); + } else { + MG_VERBOSE(("Bus set to 4-bit @25MHz")); + } + return true; +} + +// - 6.11 Card Information Structure (CIS): 0x0001000-0x017FF; for card common +// and all functions +// - 16.5 SDIO Card Metaformat +// - 16.7.2 CISTPL_FUNCE (0x22): Function Extension Tuple, provides standard +// information about the card (common) and each individual function. One +// CISTPL_FUNCE in each function’s CIS, immediately following the CISTPL_FUNCID +// tuple +// - 16.7.3 CISTPL_FUNCE Tuple for Function 0 (common) +// - 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 + +#endif diff --git a/src/drivers/sdio.h b/src/drivers/sdio.h new file mode 100644 index 00000000..151e97cb --- /dev/null +++ b/src/drivers/sdio.h @@ -0,0 +1,39 @@ +#pragma once + +#if MG_ENABLE_TCPIP && \ + (defined(MG_ENABLE_DRIVER_CYW_SDIO) && MG_ENABLE_DRIVER_CYW_SDIO) + +// Specific chip/card driver --> SDIO driver --> HAL --> SDIO hw controller + +// API with HAL for hardware controller +// - Provide a function to init the controller (external) +// - Provide these functions: +struct mg_tcpip_sdio { + void *sdio; // Opaque SDIO bus descriptor + void (*cfg)(void *, uint8_t); // select operating parameters + // SDIO transaction: send cmd with a possible 1-byte read or write + bool (*txn)(void *, uint8_t cmd, uint32_t arg, uint32_t *r); + // SDIO extended transaction: write or read len bytes, using blksz blocks + bool (*xfr)(void *, bool write, uint32_t arg, uint16_t blksz, uint32_t *, + uint32_t len, uint32_t *r); +}; + +// API with driver (e.g.: cyw.c) +// Once the hardware controller has been initialized: +// - Init card: selects the card, sets F0 block size, sets bus width and speed +bool mg_sdio_init(struct mg_tcpip_sdio *sdio); +// - Enable other possible functions (F1 to F7) +bool mg_sdio_enable_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Wait for them to be ready +bool mg_sdio_waitready_f(struct mg_tcpip_sdio *sdio, unsigned int f); +// - Set their transfer block length +bool mg_sdio_set_blksz(struct mg_tcpip_sdio *sdio, unsigned int f, + uint16_t blksz); +// - Transfer data to/from a function (abstracts from transaction type) +// - Requesting a read transfer > blocksize means block transfer will be used. +// - Drivers must have room to accomodate a whole block transfer, see sdio.c +// - Transfers of > 1 byte --> (uint32_t *) data. 1-byte --> (uint8_t *) data +bool mg_sdio_transfer(struct mg_tcpip_sdio *sdio, bool write, unsigned int f, + uint32_t addr, void *data, uint32_t len); + +#endif diff --git a/src/wifi_dummy.c b/src/wifi_dummy.c index 3a56d627..84886509 100644 --- a/src/wifi_dummy.c +++ b/src/wifi_dummy.c @@ -1,7 +1,9 @@ #include "wifi.h" #if (!defined(MG_ENABLE_DRIVER_PICO_W) || !MG_ENABLE_DRIVER_PICO_W) && \ - (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) + (!defined(MG_ENABLE_DRIVER_CYW) || !MG_ENABLE_DRIVER_CYW) && \ + (!defined(MG_ENABLE_DRIVER_CYW_SDIO) || !MG_ENABLE_DRIVER_CYW_SDIO) + bool mg_wifi_scan(void) { MG_ERROR(("No Wi-Fi driver enabled")); diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/Makefile b/tutorials/stm32/portenta-h7-make-baremetal-builtin/Makefile new file mode 100644 index 00000000..2d803b69 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/Makefile @@ -0,0 +1,43 @@ +# Environment setup: https://mongoose.ws/documentation/tutorials/tools/ + +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -Wno-cast-function-type +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_mcu/Include +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA) -DCORE_CM7 +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +# Wi-Fi chip firmware stuff +CFLAGS += -Imbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD # nvram header includes a text file + +CFLAGS += -DMG_HAL_DISABLE_ETHERNET + +SOURCES = main.c hal.c mongoose.c +SOURCES += cmsis_mcu/Source/Templates/gcc/startup_stm32h747xx.s + +all build: firmware.bin + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + arm-none-eabi-size --format=berkeley $< + +firmware.elf: cmsis_core cmsis_mcu mbed $(SOURCES) $(wildcard *.h) link.ld Makefile + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -w $< 0x8040000 -hardRst + +cmsis_core: # ARM CMSIS core headers + git clone --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ +cmsis_mcu: # Keil CMSIS headers and drivers for STM32F7 series (CMSIS-pack) + git clone --depth 1 -b v1.10.4 https://github.com/STMicroelectronics/cmsis_device_h7 $@ + +# Wi-Fi chip firmware +mbed: + git clone --depth 1 --no-checkout -b extrapatches-6.17.0 https://github.com/arduino/mbed-os $@ && cd $@ && git sparse-checkout set targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources && git checkout + touch wiced_resource.h + +clean: + rm -rf firmware.* cmsis_core cmsis_mcu mbed + diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/README.md b/tutorials/stm32/portenta-h7-make-baremetal-builtin/README.md new file mode 100644 index 00000000..93e4f4b9 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/README.md @@ -0,0 +1,3 @@ +# Arduino Portenta H7 + +Use Mongoose bare metal on the Portenta H7, with only CMSIS and no other overhead diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.c b/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.c new file mode 100644 index 00000000..a8c7a6ed --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.c @@ -0,0 +1,243 @@ +// Copyright (c) 2024 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +#define MG_HAL_SYSTICK_NONE 1 +#define MG_HAL_SYSTICK_FREERTOS 2 +#ifndef MG_HAL_SYSTICK +#define MG_HAL_SYSTICK 0 +#endif + +#if MG_HAL_SYSTICK == MG_HAL_SYSTICK_FREERTOS +#include +#include +extern void xPortSysTickHandler(void); +void SysTick_Handler(void) { + if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) + xPortSysTickHandler(); +} +#elif MG_HAL_SYSTICK != MG_HAL_SYSTICK_NONE +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} +#endif + +#ifndef MG_HAL_DISABLE_RANDOM +#ifdef MG_HAL_ENABLE_PRNG +static void rng_init(void); +static uint32_t rng_read(void); +#endif +bool mg_random(void *buf, size_t len) { // Use on-board RNG or custom PRNG + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; // TODO(): change rng_read() sig: check chip RNG, return false +} +#endif + +#ifndef MG_HAL_DISABLE_MILLIS +uint64_t mg_millis(void) { // Return number of milliseconds since boot + // handle architectures with non-atomic 64-bit access, see #3041 + uint64_t ticks = s_ticks; // first read + if (ticks == s_ticks) return ticks; // second read, same value => OK + return s_ticks; // changed while reading, read again +} +#endif + +#ifndef MG_HAL_DISABLE_SYSTEM_INIT +uint32_t SystemCoreClock; +void SystemInit(void) { // Called automatically by startup code + system_init(); +} +#endif + +#ifdef MG_HAL_ARCH_ARMCGT +#include + +int _write(int fd, const char *ptr, unsigned int len) { + (void) fd, (void) ptr, (void) len; + if (fd == 1) uart_write_buf(UART_DEBUG, ptr, (size_t) len); + return len; +} +#endif + +void hal_init(void) { +#ifndef MG_HAL_DISABLE_CLOCK + clock_init(); // Set system clock to SYS_FREQUENCY + SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var +#endif +#if MG_HAL_SYSTICK != MG_HAL_SYSTICK_NONE + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +#endif +#ifndef MG_HAL_DISABLE_RANDOM + rng_init(); // Initialise random number generator +#endif + +#ifndef MG_HAL_DISABLE_UART + uart_init(UART_DEBUG, 115200); // Initialise UART +#ifdef MG_HAL_ARCH_ARMCGT + _stream[1].dfd = 1; + _stream[1].dev->WRITE = _write; // redirect stream to _write syscall +#endif +#endif + leds_init(); +#ifndef MG_HAL_DISABLE_ETHERNET + ethernet_init(); // Initialise Ethernet pins +#endif +} + +#ifdef MG_HAL_ENABLE_GENMAC +void hal_genmac(unsigned char *mac) { + ethernet_genmac(mac); +} +#endif + +#ifdef MG_HAL_ENABLE_PRNG +static uint32_t s_hal_rng_seed; +static void rng_init(void) { + s_hal_rng_seed = entropy_init(); +} + +static uint32_t rng_read() { + uint32_t seed = s_hal_rng_seed; + uint32_t ent; + if (entropy_get(&ent)) { + // Factor both previous seed and current ent value into a new seed + ent *= 0x85ebca6b; + ent ^= (ent << 16); + ent ^= (ent >> 5); + seed ^= ent; + seed *= 0x5bd1e995; + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed += ent; + seed ^= seed >> 16; + } else { + // apply complex bit permutations / arithmetical ops for better randomness + seed ^= seed >> 3; + seed *= 2654435761U; + seed ^= seed << 13; + seed *= 0x85ebca6b; + seed ^= seed >> 16; + seed *= 0xc2b2ae35; + seed ^= seed >> 13; + } + s_hal_rng_seed = seed; + return seed; +} +#endif + + +#if defined(__ARMCC_VERSION) +// Keil specific - implement IO printf redirection +int fputc(int c, FILE *stream) { + if (stream == stdout || stream == stderr) uart_write_byte(UART_DEBUG, c); + return c; +} +#elif defined(__GNUC__) && !defined(MG_HAL_DISABLE_NEWLIB) +// ARM GCC specific. ARM GCC is shipped with Newlib C library. +// Implement newlib syscalls: +// _sbrk() for malloc +// _write() for printf redirection +// the rest are just stubs +#include // For _fstat() + +int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +#ifndef MG_HAL_SBRK_NONE +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; +#ifndef MG_HAL_SBRK_SIMPLEST + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); +#endif + prev_heap = s_current_heap_end; +#ifndef MG_HAL_SBRK_SIMPLEST + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; +#endif + s_current_heap_end += incr; + return prev_heap; +} +#endif + +int _open(const char *path) { + (void) path; + return -1; +} + +int _close(int fd) { + (void) fd; + return -1; +} + +int _isatty(int fd) { + (void) fd; + return 1; +} + +int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +int _getpid(void) { + return -1; +} + +int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + if (fd == 1) uart_write_buf(UART_DEBUG, ptr, (size_t) len); + return -1; +} + +int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +int _unlink(const char *a) { + (void) a; + return -1; +} + +int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +void _init(void) { +} +#endif // __GNUC__ diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.h b/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.h new file mode 100644 index 00000000..e36d008f --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/hal.h @@ -0,0 +1,446 @@ +// Copyright (c) 2022-2023 Cesanta Software Limited +// All rights reserved +// +// RM0399 +// https://www.st.com/resource/en/reference_manual/rm0399-stm32h745755-and-stm32h747757-advanced-armbased-32bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/datasheet/stm32h747xi.pdf +// https://docs.arduino.cc/hardware/portenta-h7/ + +#pragma once + +#define LED1 PIN('K', 5) // On-board LED pin (red) +#define LED2 PIN('K', 6) // On-board LED pin (green) +#define LED3 PIN('K', 7) // On-board LED pin (blue) +#define LED LED2 // Use green LED for blinking + +#ifndef UART_DEBUG +#define UART_DEBUG USART1 +#endif + +#include + +#include +#include +#include +#include + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); + +// System clock (2.1, Fig 1; 8.5, Fig 45; 8.5.5, Fig 47; 8.5.6, Fig 49) +// - This board defaults to work with an SMPS regulator, so: +// - SYS_FREQUENCY <= 400 MHz; hclk = SYS_FREQUENCY / HPRE ; hclk <= 200 MHz; +// APB clocks <= 100 MHz. +// - D1 domain bus matrix (and so flash) runs at hclk frequency. Configure flash +// latency (WS) in accordance to hclk freq (4.3.8, Table 17) +// - The Ethernet controller is in D2 domain and runs at hclk frequency +enum { + D1CPRE = 1, // actual divisor value + HPRE = 2, // actual divisor value + D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2 + D2PPRE1 = 4, + D2PPRE2 = 4, + D3PPRE = 4 +}; +// PLL1_P: odd division factors are not allowed (8.7.13) (according to Cube, '1' +// is also an "odd division factor"). +// Make sure your chip is revision 'V', otherwise set PLL1_N = 400 +enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 400, PLL1_P = 2 , PLL1_Q = 4 }; +#define SYS_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / D1CPRE) * 1000000) +// #define SYS_FREQUENCY ((PLL1_HSI / D1CPRE) * 1000000) +// #define SYS_FREQUENCY 64000000 +#define AHB_FREQUENCY (SYS_FREQUENCY / HPRE) +#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(D2PPRE2 - 3))) +#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(D2PPRE1 - 3))) +#define PLL1_Q_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_Q) * 1000000) + +static inline void spin(volatile uint32_t n) { + while (n--) (void) 0; +} + +enum { GPIO_MODE_INPUT, GPIO_MODE_OUTPUT, GPIO_MODE_AF, GPIO_MODE_ANALOG }; +enum { GPIO_OTYPE_PUSH_PULL, GPIO_OTYPE_OPEN_DRAIN }; +enum { GPIO_SPEED_LOW, GPIO_SPEED_MEDIUM, GPIO_SPEED_HIGH, GPIO_SPEED_INSANE }; +enum { GPIO_PULL_NONE, GPIO_PULL_UP, GPIO_PULL_DOWN }; + +#define GPIO(N) ((GPIO_TypeDef *) (0x40000000 + 0x18020000UL + 0x400 * (N))) + +static GPIO_TypeDef *gpio_bank(uint16_t pin) { + return GPIO(PINBANK(pin)); +} +static inline void gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int gpio_read(uint16_t pin) { + return gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void gpio_input(uint16_t pin) { + gpio_init(pin, GPIO_MODE_INPUT, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, + GPIO_PULL_NONE, 0); +} +static inline void gpio_output(uint16_t pin) { + gpio_init(pin, GPIO_MODE_OUTPUT, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, + GPIO_PULL_NONE, 0); +} + +static inline void leds_init(void) { + gpio_output(LED1); // Initialise LED1 + gpio_output(LED2); // Initialise LED2 + gpio_output(LED3); // Initialise LED3 + gpio_write(LED1, true); // OFF + gpio_write(LED2, true); + gpio_write(LED3, true); +} + +// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 +// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks +// must be enabled for CPU access, as the kernel clock drives the BRR, not the +// APB bus interface +static inline void uart_init(USART_TypeDef *uart, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4); + if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(17); + if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18); + + if (uart == USART1) tx = PIN('A', 9), rx = PIN('A', 10); + if (uart == USART2) tx = PIN('A', 2), rx = PIN('A', 3); + if (uart == USART3) tx = PIN('D', 8), rx = PIN('D', 9); + +#if 1 // CONSTANT BAUD RATE FOR DEBUGGING + CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1 + freq = 64000000; +#endif + + gpio_init(tx, GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, 0, af); + gpio_init(rx, GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) spin(1); +} +static inline void uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->RDR & 255); +} + +static inline void rng_init(void) { + RCC->D2CCIP2R |= RCC_D2CCIP2R_RNGSEL_0; // RNG clock source pll1_q_ck + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG clock + RNG->CR = RNG_CR_RNGEN; // Enable RNG +} + +static inline uint32_t rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0; + return RNG->DR; +} + +// NO Hw pull-ups on PHY RXD0,1,DV; set them to enable autonegotiation +static inline void ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see schematics + uint16_t pins1[] = {PIN('A', 7), PIN('C', 4), PIN('C', 5)}; + for (size_t i = 0; i < sizeof(pins1) / sizeof(pins1[0]); i++) { + gpio_init(pins1[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE, + GPIO_PULL_UP, 11); // 11 is the Ethernet function + } + uint16_t pins2[] = {PIN('A', 1), PIN('A', 2), PIN('C', 1), + PIN('G', 11), PIN('G', 12), PIN('G', 13)}; + for (size_t i = 0; i < sizeof(pins2) / sizeof(pins2[0]); i++) { + gpio_init(pins2[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE, + GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (13.3.1) + RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks +} + +static inline char chiprev(void) { + uint16_t rev = (uint16_t) (((uint32_t) DBGMCU->IDCODE) >> 16); + if (rev == 0x1003) return 'Y'; + if (rev == 0x2003) return 'V'; + return '?'; +} + +static inline unsigned int div2prescval(unsigned int div) { + // 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512 + if (div == 1) return 0; + if (div > 16) div /= 2; + unsigned int val = 7; + while (div >>= 1) ++val; + return val; +} + +static inline unsigned int pllrge(unsigned int f) { + unsigned int val = 0; + while (f >>= 1) ++val; + return val - 1; +} + +static inline void clock_init(void) { + // Set flash latency. RM section 4.9.1, section 4.3.8 table 16 + CLRSET(FLASH->ACR, (FLASH_ACR_WRHIGHFREQ_Msk | FLASH_ACR_LATENCY_Msk), + FLASH_ACR_LATENCY_2WS | FLASH_ACR_WRHIGHFREQ_1); + CLRSET(PWR->CR3, BIT(1), BIT(2)); // select SMPS + while ((PWR->CSR1 & BIT(13)) == 0) spin(1); // ACTVOSRDY + PWR->D3CR |= BIT(15) | BIT(14); // Select VOS1 + uint32_t f = PWR->D3CR; // fake read to wait for bus clocking + while ((PWR->CSR1 & BIT(13)) == 0) spin(1); // ACTVOSRDY +#if 0 + SYSCFG->PWRCR |= BIT(0); // ODEN for LDO to go 480MHz + f = SYSCFG->PWRCR; + while ((PWR->CSR1 & BIT(13)) == 0) spin(1); // ACTVOSRDY +#endif + (void) f; + RCC->CFGR &= ~(3 << 0); // Set clock source to HSI (bootloader...) + RCC->CR &= ~BIT(24); // Disable PLL1 + CLRSET( + RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0), + (div2prescval(D1CPRE) << 8) | (D1PPRE << 4) | (div2prescval(HPRE) << 0)); + RCC->D2CFGR = (D2PPRE2 << 8) | (D2PPRE1 << 4); + RCC->D3CFGR = (D3PPRE << 4); + // DIVQ1EN DIVP1EN PLL1RGE !PLL1VCOSEL !PLL1FRACEN + CLRSET(RCC->PLLCFGR, (3 << 2) | BIT(1) | BIT(0), + BIT(17) | BIT(16) | pllrge(PLL1_HSI / PLL1_M) << 2); + CLRSET(RCC->PLL1DIVR, (0x7F << 16) | (0x7F << 9) | (0x1FF << 0), + ((PLL1_Q - 1) << 16) | ((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); + CLRSET(RCC->PLLCKSELR, (0x3F << 4) | (3 << 0), PLL1_M << 4); // PLL1: HSI/M + RCC->CR |= BIT(24); // Enable PLL1 + while ((RCC->CR & BIT(25)) == 0) spin(1); // Wait until done + RCC->CFGR |= (3 << 0); // Set clock source to PLL1 + while ((RCC->CFGR & (7 << 3)) != (3 << 3)) spin(1); // Wait until done + RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG +} + +static inline void system_init(void) { // Called automatically by startup code + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + SCB_DisableDCache(); // The bootloader enables caches, disable them + SCB_DisableICache(); + __DSB(); + __ISB(); +} + +#define PWRPIN PIN('J', 1) + +// - 58.4 Table 473 +// - 58.5.8 AHB bw >= 3x SDMMC bus bw; Table 492; SD HS => AHB >= ~20MHz +// - SDIO 2.0 section 3.1 +static inline void sdmmc_set_ds(void) { // NO FLOW CONTROL ??? REALLY ??? ******************************* + CLRSET(SDMMC1->CLKCR, (1 << 17) | 0x3ff, (1 << 14) | ((PLL1_Q_FREQUENCY / (2 * 25000000)) & 0x3ff)); // 4-bit bus, 25MHz +} +static inline void sdmmc_set_hs(void) { // mbed enables flow control... AIROC does not + CLRSET(SDMMC1->CLKCR, 0x3ff, (1 << 17) | (1 << 14) | ((PLL1_Q_FREQUENCY / (2 * 50000000)) & 0x3ff)); // hw flow control, 4-bit bus, 50MHz +} +static inline void sdmmc_init(void) { + RCC->AHB3RSTR |= BIT(16); // Reset SDIO block + RCC->AHB3RSTR &= ~BIT(16); + RCC->D1CCIPR &= ~BIT(16); // 9.7.18, use PLL1Q + RCC->AHB3ENR |= BIT(16); // Enable SDIO clock + // Initialise SDIO pins, see schematics + uint16_t pins[] = {PIN('C', 8), PIN('C', 9), PIN('C', 10), + PIN('C', 11), PIN('C', 12), PIN('D', 2)}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + // Portenta H7 doesn't pull-up the SDIO bus + gpio_init(pins[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE, + GPIO_PULL_UP, 12); // 12 is the SDMMC1 (SDIO) function + } + gpio_input(PIN('J', 5)); // wake, ignored + // 1-bit bus, rising edge, clock always on, no flow control, 400KHz + SDMMC1->CLKCR = (PLL1_Q_FREQUENCY / (2 * 400000)) & 0x3ff; + SDMMC1->POWER |= 3 << 0; // SDMMC_POWER_PWRCTRL + SDMMC1->DCTRL = BIT(10); // Read Wait control by stopping SDMMCCLK + // wait 74 clocks (~200us) before accessing the "card" +} + +static inline bool sdmmc_transact(uint8_t cmd, uint32_t arg, uint32_t *resp) { + unsigned int wresp = (resp != NULL) ? 1 : 0; // 58.10.4: 2-bit; cmds used + uint32_t cmdr = BIT(12) | (wresp << 8) | ((cmd & 0x3f) << 0); // CPSMEN + SDMMC1->ARG = arg; + CLRSET(SDMMC1->CMD, 0x00011f3f /* CMDSUSPEND CPSMEN WAITPEND WAITINT WAITRESP CMD */, cmdr); + if (wresp == 0) { + while ((SDMMC1->STA & SDMMC_STA_CMDSENT) == 0) spin(1); + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + return true; + } + //while ((SDMMC1->STA & 0x00200045 /*BUSYD0END CMDREND CTIMEOUT CCRCFAIL*/) == 0 || SDMMC1->STA & SDMMC_STA_CPSMACT) spin(1); + while (SDMMC1->STA & SDMMC_STA_CPSMACT) spin(1); + bool res = (SDMMC1->STA & 0x00200005 /*BUSYD0END CTIMEOUT CCRCFAIL*/) == 0; + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + *resp = SDMMC1->RESP1; + if (!res || SDMMC1->RESPCMD != cmd) return false; + return true; +} + +static inline unsigned int _log2(uint16_t x) { + unsigned int i = 0; + while (x != 0) x = ((uint16_t)x) >> 1, ++i; + return i - 1; +} + +static inline bool sdmmc_transfer(bool write, uint32_t arg, uint16_t blksz, uint32_t *data, uint32_t length, uint32_t *resp) { + uint32_t cmdr = BIT(12) | (1 << 8) | (53 << 0); // CPSMEN WRESP CMD=53 + bool bmode = (blksz != 0); + uint8_t dblksz = bmode ? (uint8_t)_log2(blksz) : 0; + uint32_t dctrlr = (dblksz << 4) | (bmode ? (0 << 2) : (1 << 2)) | (write ? 0 : BIT(1)); // DBLOCKSIZE=f(blksz) DTMODE=f(bmode) DTDIR=!write !DTEN + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + SDMMC1->IDMACTRL = SDMMC_IDMA_IDMAEN; // single buffer + SDMMC1->IDMABASE0 = (uint32_t)data; + SDMMC1->DTIMER = (uint32_t) 50000000; // 58.10.7, set for 1 sec @50MHz (2 secs @25MHz) + SDMMC1->DLEN = length; // 58.10.8 call with a multiple of blksz (we do) + CLRSET(SDMMC1->DCTRL, 0xff /* DBLOCKSIZE DTMODE DTDIR DTEN */, dctrlr); + SDMMC1->CMD = SDMMC_CMD_CMDTRANS; // CPSM will perform a data transfer + SDMMC1->ARG = arg; + SDMMC1->CMD |= cmdr; // other flags cleared above + while ((SDMMC1->STA & 0x0020003f) == 0 && SDMMC1->STA & SDMMC_STA_DPSMACT) + spin(1); // incorrect "card" usage will stall here + bool res = (SDMMC1->STA & 0x0020003f /* BUSYD0END RXOVERR TXUNDERR DTIMEOUT CTIMEOUT DCRCFAIL CCRCFAIL */) == 0; + SDMMC1->CMD = 0; + SDMMC1->DLEN = 0; + SDMMC1->IDMACTRL = 0; + SDMMC1->ICR = (uint32_t) ~0; // clear all flags + *resp = SDMMC1->RESP1; + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + if (!res || SDMMC1->RESPCMD != 53) return false; + return true; +} + + + + + +// Bit-banged SPI, pin wiring does not allow using a hw SPI peripheral +#define CYWSPI NULL +#define BBSPI_SPIN 1 + +static inline void spi_init(void *spi, uint8_t div) { + // Initialise bit-banged SPI, see: schematics; CYW DS 4.1.1 Table 5, 13.7 + // Table 22 + gpio_input(PIN('C', 8)); // DO -> MISO + gpio_input(PIN('C', 9)); // IRQ -> ignore + gpio_output(PIN('C', 10)); // D2 <-- strap for SPI mode + gpio_output(PIN('C', 11)); // CS + gpio_output(PIN('C', 12)); // SCLK + gpio_output(PIN('D', 2)); // DI -> MOSI + // select SPI mode at power-on/reset time (not WL_REG_ON, actual POR) + // "Sampling occurs a few milliseconds after an internal POR + // or deassertion of the external POR + gpio_write(PIN('C', 10), 0); + gpio_write(PIN('D', 2), 0); // idle values + gpio_write(PIN('C', 12), 0); + gpio_write(PIN('C', 11), 0); // no power at startup + (void) spi; + (void) div; +} + +static inline void spi_write_byte(void *spi, uint8_t byte) { + unsigned int n = 8; + while (n--) { + gpio_write(PIN('D', 2), byte & 0x80); + spin(BBSPI_SPIN); + gpio_write(PIN('C', 12), 1); + byte <<= 1; + spin(BBSPI_SPIN); + gpio_write(PIN('C', 12), 0); + } + gpio_write(PIN('D', 2), 0); + (void) spi; +} + +static inline uint8_t spi_read_byte(void *spi) { + uint8_t byte = 0; + unsigned int n = 8; + while (n--) { + byte <<= 1; + spin(BBSPI_SPIN); + byte |= gpio_read(PIN('C', 8)) ? 1 : 0; + gpio_write(PIN('C', 12), 1); + spin(BBSPI_SPIN); + gpio_write(PIN('C', 12), 0); + } + (void) spi; + return byte; +} + +static inline void spi_select(void *spi, bool s) { + gpio_write(PIN('C', 11), !s); + (void) spi; +} + +// Current bootloader sets all outputs in the PMIC, no need to use I2C. +// The bootloader inits I2C1 and chips in there, but we change MCU clock +// DS 4 Table 12, schematic PB6,7 +static inline void i2c_init(I2C_TypeDef *i2c) { + CLRSET(RCC->D2CCIP2R, 3 << 12, 2 << 12); // use HSI for I2C1 (64MHz) + RCC->APB1LENR |= BIT(21); // enable I2C clock + gpio_init(PIN('B', 6), GPIO_MODE_AF, GPIO_OTYPE_OPEN_DRAIN, GPIO_SPEED_INSANE, + GPIO_PULL_NONE, 4); + gpio_init(PIN('B', 7), GPIO_MODE_AF, GPIO_OTYPE_OPEN_DRAIN, GPIO_SPEED_INSANE, + GPIO_PULL_NONE, 4); + i2c->CR1 = 0; // disable before any config change + i2c->CR1 = BIT(12); // 50.7 disable filters + i2c->TIMINGR = 10U << 28 | 3 << 20 | 2 << 16 | 3 << 8 | 9 << 0; // < 400KHz + i2c->CR1 |= BIT(0); // enable after fully configured +} + +static inline void i2c_write_reg(I2C_TypeDef *i2c, uint8_t addr, uint8_t reg, + uint8_t byte) { + i2c->CR2 = BIT(25) | 2 << 16 | + addr << 1; // write byte to reg in chip at addr, AUTOEND + i2c->CR2 |= BIT(13); // START + while ((i2c->ISR & BIT(0)) == 0) spin(1); // TXE + i2c->TXDR = reg; + while ((i2c->ISR & BIT(0)) == 0) spin(1); // TXE + i2c->TXDR = byte; + while ((i2c->ISR & BIT(5)) == 0) spin(1); // STOPF + i2c->ICR |= BIT(5); // STOPCF, clear STOPF +} + +static inline uint8_t i2c_read_reg(I2C_TypeDef *i2c, uint8_t addr, + uint8_t reg) { + i2c->CR2 = 1 << 16 | addr << 1; // write reg addr to chip at addr + i2c->CR2 |= BIT(13); // START + while ((i2c->ISR & BIT(0)) == 0) spin(1); // TXE + i2c->TXDR = reg; + while ((i2c->ISR & BIT(6)) == 0) spin(1); // TC + i2c->CR2 = BIT(13) | BIT(10) | 1 << 16 | addr << 1; // read 1 byte, AUTOEND + i2c->CR2 |= BIT(25); // START + while ((i2c->ISR & BIT(2)) == 0) spin(1); // RXNE + uint8_t byte = (uint8_t) i2c->RXDR; + while ((i2c->ISR & BIT(5)) == 0) spin(1); // STOPF + i2c->ICR |= BIT(5); // STOPCF, clear STOPF + return byte; +} diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/link.ld b/tutorials/stm32/portenta-h7-make-baremetal-builtin/link.ld new file mode 100644 index 00000000..a88f6770 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/link.ld @@ -0,0 +1,33 @@ +/* DTCMRAM (rwx) : ORIGIN = 0x20000400, LENGTH = 128k - 1k */ /* bootloader starts at 0x20000000 */ + +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08040000, LENGTH = 2048k - 0x40000 /* bootloader starts at 0x08000000 */ + sram(rwx) : ORIGIN = 0x24000000, LENGTH = 512k +} +_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; + *(.first_data) + *(.data SORT(.data.*)) + *(.iram .iram* .iram.*) + _edata = .; + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; + *(.bss SORT(.bss.*) COMMON) + _ebss = .; + } > sram + + . = ALIGN(8); + _end = .; +} + diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/main.c b/tutorials/stm32/portenta-h7-make-baremetal-builtin/main.c new file mode 100644 index 00000000..025fc6d5 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/main.c @@ -0,0 +1,176 @@ +// Copyright (c) 2025 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + + +#define WIFI_SSID "YOUR_WIFI_NETWORK_NAME" // SET THIS! +#define WIFI_PASS "YOUR_WIFI_PASSWORD" // SET THIS! + + +static void hwspecific_sdio_init(void) { + gpio_output(PWRPIN); + gpio_write(PWRPIN, 0); + mg_delayms(100); + sdmmc_init(); + mg_delayms(1); + gpio_write(PWRPIN, 1); + mg_delayms(50); +} + +void hwspecific_sdio_config(void *sdio, uint8_t cfg) { + (void) sdio; + if (cfg == 0) return sdmmc_set_ds(); + if (cfg == 1) return sdmmc_set_hs(); +} +bool hwspecific_sdio_txn(void *sdio, uint8_t cmd, uint32_t arg, uint32_t *resp) { + (void) sdio; + return sdmmc_transact(cmd, arg, resp); +} +bool hwspecific_sdio_xfr(void *sdio, bool write, uint32_t arg, uint16_t blksz, uint32_t *data, uint32_t len, uint32_t *resp){ + (void) sdio; + return sdmmc_transfer(write, arg, blksz, data, len, resp); +} + +static const struct mg_tcpip_sdio sdio = {NULL, hwspecific_sdio_config, hwspecific_sdio_txn, hwspecific_sdio_xfr}; + +// clang-format off +typedef enum {RESOURCE_IN_MEMORY,RESOURCE_IN_EXTERNAL_STORAGE} resource_location_t; +typedef struct {resource_location_t l;unsigned long sz;union{struct {unsigned long o;const char *f;}fs;struct {const char *m;}mem;}val;} resource_hnd_t; +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/firmware/COMPONENT_4343W_FS/4343WA1_bin.c" +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/firmware/COMPONENT_4343W_FS/4343WA1_clm_blob.c" +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/nvram/wifi_nvram_image.h" +// clang-format on +static const struct mg_tcpip_driver_cyw_firmware fw = { + (const uint8_t *)wifi_firmware_image_data, (size_t)sizeof(wifi_firmware_image_data), + (const uint8_t *)wifi_nvram_image, (size_t)sizeof(wifi_nvram_image), + (const uint8_t *)wifi_firmware_clm_blob_image_data, (size_t)sizeof(wifi_firmware_clm_blob_image_data)}; + +// mif user states +enum {AP, SCANNING, STOPPING_AP, CONNECTING, READY}; +static unsigned int state; +static uint32_t s_ip, s_mask; + + +static void mif_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) { + // TODO(): should we include this inside ifp ? add an fn_data ? + if (ev == MG_TCPIP_EV_ST_CHG) { + MG_INFO(("State change: %u", *(uint8_t *) ev_data)); + } + switch(state) { + case AP: // we are in AP mode, wait for a user connection to trigger a scan or a connection to a network + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_UP) { + MG_INFO(("Access Point started")); + s_ip = ifp->ip, ifp->ip = MG_IPV4(192, 168, 169, 1); + s_mask = ifp->mask, ifp->mask = MG_IPV4(255, 255, 255, 0); + ifp->enable_dhcp_client = false; + ifp->enable_dhcp_server = true; + } else if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_READY) { + MG_INFO(("Access Point READY !")); + + // simulate user request to scan for networks + bool res = mg_wifi_scan(); + MG_INFO(("Starting scan: %s", res ? "OK":"FAIL")); + if (res) state = SCANNING; + } + break; + case SCANNING: + if (ev == MG_TCPIP_EV_WIFI_SCAN_RESULT) { + struct mg_wifi_scan_bss_data *bss = (struct mg_wifi_scan_bss_data *) ev_data; + MG_INFO(("BSS: %.*s (%u) (%M) %d dBm %u", bss->SSID.len, bss->SSID.buf, bss->channel, mg_print_mac, bss->BSSID, (int) bss->RSSI, bss->security)); + } else if (ev == MG_TCPIP_EV_WIFI_SCAN_END) { + // struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + MG_INFO(("Wi-Fi scan finished")); + + // simulate user selection of a network (1/2: stop AP) + bool res = mg_wifi_ap_stop(); + MG_INFO(("Manually stopping AP: %s", res ? "OK":"FAIL")); + if (res) state = STOPPING_AP; + // else we have a hw/fw problem + } + break; + case STOPPING_AP: + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + d->apmode = false; + + // simulate user selection of a network (2/2: actual connect) + bool res = mg_wifi_connect(d->ssid, d->pass); + MG_INFO(("Manually connecting: %s", res ? "OK":"FAIL")); + if (res) { + state = CONNECTING; + ifp->ip = s_ip; + ifp->mask = s_mask; + if (ifp->ip == 0) ifp->enable_dhcp_client = true; + ifp->enable_dhcp_server = false; + } // else manually start AP as below + } + break; + case CONNECTING: + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_READY) { + MG_INFO(("READY!")); + state = READY; + + // simulate user code disconnection and go back to AP mode (1/2: disconnect) + bool res = mg_wifi_disconnect(); + MG_INFO(("Manually disconnecting: %s", res ? "OK":"FAIL")); + } else if (ev == MG_TCPIP_EV_WIFI_CONNECT_ERR) { + MG_ERROR(("Wi-Fi connect failed")); + // manually start AP as below + } + break; + case READY: + // go back to AP mode after a disconnection (simulation 2/2), you could retry + if (ev == MG_TCPIP_EV_ST_CHG && *(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) ifp->driver_data; + bool res = mg_wifi_ap_start(d->apssid, d->appass, d->apchannel); + MG_INFO(("Disconnected")); + MG_INFO(("Manually starting AP: %s", res ? "OK":"FAIL")); + if (res) { + state = AP; + d->apmode = true; + } + } + break; + } +} + + +static struct mg_tcpip_driver_cyw_data d = { + (void *)&sdio, (struct mg_tcpip_driver_cyw_firmware *)&fw, WIFI_SSID, WIFI_PASS, "mongoose", "mongoose", 0, 0, 10, true, false}; + +int main(void) { + hal_init(); + + hwspecific_sdio_init(); + + state = d.apmode ? AP : CONNECTING; + + struct mg_mgr mgr; // Initialise Mongoose event manager + mg_mgr_init(&mgr); // and attach it to the interface + mg_log_set(MG_LL_DEBUG); // Set log level + + // Initialise Mongoose network stack + // Either set use_dhcp or enter a static config. + // For static configuration, specify IP/mask/GW in network byte order + struct mg_tcpip_if mif = { + .ip = 0, + .driver = (struct mg_tcpip_driver *)&mg_tcpip_driver_cyw, + .driver_data = (struct mg_tcpip_driver_cyw_data*)&d, + .fn = mif_fn, +// .recv_queue.size = 8192 + }; + + mg_tcpip_init(&mgr, &mif); + MG_INFO(("Init done, starting main loop")); + + MG_INFO(("Initialising application...")); + + MG_INFO(("Starting event loop")); + for (;;) { + mg_mgr_poll(&mgr, 0); + } + + return 0; +} diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.c b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.c new file mode 120000 index 00000000..5e522bbc --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.c @@ -0,0 +1 @@ +../../../mongoose.c \ No newline at end of file diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.h b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.h new file mode 120000 index 00000000..ee4ac823 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose.h @@ -0,0 +1 @@ +../../../mongoose.h \ No newline at end of file diff --git a/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose_config.h b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose_config.h new file mode 100644 index 00000000..3eccc063 --- /dev/null +++ b/tutorials/stm32/portenta-h7-make-baremetal-builtin/mongoose_config.h @@ -0,0 +1,13 @@ +#pragma once + +// See https://mongoose.ws/documentation/#build-options +#define MG_ARCH MG_ARCH_NEWLIB +#define MG_TLS MG_TLS_BUILTIN +#define MG_OTA MG_OTA_STM32H7_DUAL_CORE + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_PACKED_FS 1 +#define MG_ENABLE_DRIVER_CYW_SDIO 1 +#define MG_ENABLE_TCPIP_DRIVER_INIT 0