/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2021 by Tomasz Moń * Ported from Linux libertas driver * Copyright 2008 Analog Devices Inc. * Authors: * Andrey Yurovsky * Colin McCabe * Inspired by if_sdio.c, Copyright 2007-2008 Pierre Ossman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" /*#define LOGF_ENABLE*/ #include "logf.h" #include "errno.h" #include "file.h" #include "panic.h" #include "system.h" #include "tick.h" #include #include "if_spi.h" #include "if_spi_drv.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define DIV_ROUND_UP(n,d) (((n) + (d) - 1)/(d)) struct if_spi_card { /* The card ID and card revision, as reported by the hardware. */ uint16_t card_id; uint8_t card_rev; unsigned long spu_port_delay; unsigned long spu_reg_delay; uint8_t cmd_buffer[IF_SPI_CMD_BUF_SIZE]; }; #define MODEL_8686 0x0b static const struct { uint16_t model; const char *helper; const char *main; } fw_table[] = { { MODEL_8686, ROCKBOX_DIR"/libertas/gspi8686_v9_helper.bin", ROCKBOX_DIR"/libertas/gspi8686_v9.bin" }, { 0, NULL, NULL } }; /* * SPI Interface Unit Routines * * The SPU sits between the host and the WLAN module. * All communication with the firmware is through SPU transactions. * * First we have to put a SPU register name on the bus. Then we can * either read from or write to that register. * */ static void spu_transaction_init(struct if_spi_card *card) { (void)card; /* Linux delays 400 ns if spu_transaction_finish() was called * within the same jiffy. As we don't have jiffy counter nor * nanosecond delays, simply delay for 1 us. This currently * does not really matter as this driver simply loads firmware. */ udelay(1); libertas_spi_cs(0); /* assert CS */ } static void spu_transaction_finish(struct if_spi_card *card) { (void)card; libertas_spi_cs(1); /* drop CS */ } /* * Write out a byte buffer to an SPI register, * using a series of 16-bit transfers. */ static int spu_write(struct if_spi_card *card, uint16_t reg, const uint8_t *buf, int len) { int err = 0; uint8_t reg_out[2]; /* You must give an even number of bytes to the SPU, even if it * doesn't care about the last one. */ if (len & 0x1) panicf("Odd length in spu_write()"); reg |= IF_SPI_WRITE_OPERATION_MASK; reg_out[0] = (reg & 0x00FF); reg_out[1] = (reg & 0xFF00) >> 8; spu_transaction_init(card); libertas_spi_tx(reg_out, sizeof(reg_out)); libertas_spi_tx(buf, len); spu_transaction_finish(card); return err; } static inline int spu_write_u16(struct if_spi_card *card, uint16_t reg, uint16_t val) { uint8_t buf[2]; buf[0] = (val & 0x00FF); buf[1] = (val & 0xFF00) >> 8; return spu_write(card, reg, buf, sizeof(buf)); } static inline int spu_reg_is_port_reg(uint16_t reg) { switch (reg) { case IF_SPI_IO_RDWRPORT_REG: case IF_SPI_CMD_RDWRPORT_REG: case IF_SPI_DATA_RDWRPORT_REG: return 1; default: return 0; } } static int spu_read(struct if_spi_card *card, uint16_t reg, uint8_t *buf, int len) { unsigned int delay; int err = 0; uint8_t reg_out[2]; /* * You must take an even number of bytes from the SPU, even if you * don't care about the last one. */ if (len & 0x1) panicf("Odd length in spu_read()"); reg |= IF_SPI_READ_OPERATION_MASK; reg_out[0] = (reg & 0x00FF); reg_out[1] = (reg & 0xFF00) >> 8; spu_transaction_init(card); libertas_spi_tx(reg_out, sizeof(reg_out)); delay = spu_reg_is_port_reg(reg) ? card->spu_port_delay : card->spu_reg_delay; /* Busy-wait while the SPU fills the FIFO */ delay = DIV_ROUND_UP((100 + (delay * 10)), 1000); if (delay < 1000) udelay(delay); else mdelay(DIV_ROUND_UP(delay, 1000)); libertas_spi_rx(buf, len); spu_transaction_finish(card); return err; } /* Read 16 bits from an SPI register */ static inline int spu_read_u16(struct if_spi_card *card, uint16_t reg, uint16_t *val) { uint8_t buf[2]; int ret; ret = spu_read(card, reg, buf, sizeof(buf)); if (ret == 0) *val = buf[0] | (buf[1] << 8); return ret; } /* * Read 32 bits from an SPI register. * The low 16 bits are read first. */ static int spu_read_u32(struct if_spi_card *card, uint16_t reg, uint32_t *val) { uint8_t buf[4]; int err; err = spu_read(card, reg, buf, sizeof(buf)); if (!err) *val = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); return err; } /* * Keep reading 16 bits from an SPI register until you get the correct result. * * If mask = 0, the correct result is any non-zero number. * If mask != 0, the correct result is any number where * number & target_mask == target * * Returns -ETIMEDOUT if a five seconds passes without the correct result. */ static int spu_wait_for_u16(struct if_spi_card *card, uint16_t reg, uint16_t target_mask, uint16_t target) { int err; unsigned long timeout = current_tick + 5*HZ; while (1) { uint16_t val; err = spu_read_u16(card, reg, &val); if (err) return err; if (target_mask) { if ((val & target_mask) == target) return 0; } else { if (val) return 0; } udelay(100); if (TIME_AFTER(current_tick, timeout)) { logf("%s: timeout with val=%02x, target_mask=%02x, target=%02x", __func__, val, target_mask, target); return -ETIMEDOUT; } } } /* * Read 16 bits from an SPI register until you receive a specific value. * Returns -ETIMEDOUT if a 4 tries pass without success. */ static int spu_wait_for_u32(struct if_spi_card *card, uint32_t reg, uint32_t target) { int err, try; for (try = 0; try < 4; ++try) { uint32_t val = 0; err = spu_read_u32(card, reg, &val); if (err) return err; if (val == target) return 0; mdelay(100); } return -ETIMEDOUT; } static int spu_set_interrupt_mode(struct if_spi_card *card, int suppress_host_int, int auto_int) { int err = 0; /* * We can suppress a host interrupt by clearing the appropriate * bit in the "host interrupt status mask" register */ if (suppress_host_int) { err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0); if (err) return err; } else { err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, IF_SPI_HISM_TX_DOWNLOAD_RDY | IF_SPI_HISM_RX_UPLOAD_RDY | IF_SPI_HISM_CMD_DOWNLOAD_RDY | IF_SPI_HISM_CARDEVENT | IF_SPI_HISM_CMD_UPLOAD_RDY); if (err) return err; } /* * If auto-interrupts are on, the completion of certain transactions * will trigger an interrupt automatically. If auto-interrupts * are off, we need to set the "Card Interrupt Cause" register to * trigger a card interrupt. */ if (auto_int) { err = spu_write_u16(card, IF_SPI_HOST_INT_CTRL_REG, IF_SPI_HICT_TX_DOWNLOAD_OVER_AUTO | IF_SPI_HICT_RX_UPLOAD_OVER_AUTO | IF_SPI_HICT_CMD_DOWNLOAD_OVER_AUTO | IF_SPI_HICT_CMD_UPLOAD_OVER_AUTO); if (err) return err; } else { err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_MASK_REG, 0); if (err) return err; } return err; } static int spu_get_chip_revision(struct if_spi_card *card, uint16_t *card_id, uint8_t *card_rev) { int err = 0; uint32_t dev_ctrl; err = spu_read_u32(card, IF_SPI_DEVICEID_CTRL_REG, &dev_ctrl); if (err) return err; *card_id = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_ID(dev_ctrl); *card_rev = IF_SPI_DEVICEID_CTRL_REG_TO_CARD_REV(dev_ctrl); return err; } static int spu_set_bus_mode(struct if_spi_card *card, uint16_t mode) { int err = 0; uint16_t rval; /* set bus mode */ err = spu_write_u16(card, IF_SPI_SPU_BUS_MODE_REG, mode); if (err) return err; /* Check that we were able to read back what we just wrote. */ err = spu_read_u16(card, IF_SPI_SPU_BUS_MODE_REG, &rval); if (err) return err; if ((rval & 0xF) != mode) { logf("Can't read bus mode register"); return -EIO; } return 0; } static int spu_init(struct if_spi_card *card) { int err = 0; uint32_t delay; err = spu_set_bus_mode(card, IF_SPI_BUS_MODE_SPI_CLOCK_PHASE_RISING | IF_SPI_BUS_MODE_DELAY_METHOD_TIMED | IF_SPI_BUS_MODE_16_BIT_ADDRESS_16_BIT_DATA); if (err) return err; card->spu_port_delay = 1000; card->spu_reg_delay = 1000; err = spu_read_u32(card, IF_SPI_DELAY_READ_REG, &delay); if (err) return err; card->spu_port_delay = delay & 0x0000ffff; card->spu_reg_delay = (delay & 0xffff0000) >> 16; logf("Initialized SPU unit. " "spu_port_delay=0x%04lx, spu_reg_delay=0x%04lx", card->spu_port_delay, card->spu_reg_delay); return err; } /* * Firmware Loading */ static int if_spi_prog_helper_firmware(struct if_spi_card *card, int fd) { int err = 0; int bytes_read; uint8_t *temp = card->cmd_buffer; err = spu_set_interrupt_mode(card, 1, 0); if (err) goto out; /* Load helper firmware image */ while ((bytes_read = read(fd, temp, HELPER_FW_LOAD_CHUNK_SZ)) > 0) { /* * Scratch pad 1 should contain the number of bytes we * want to download to the firmware */ err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG, HELPER_FW_LOAD_CHUNK_SZ); if (err) goto out; err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG, IF_SPI_HIST_CMD_DOWNLOAD_RDY, IF_SPI_HIST_CMD_DOWNLOAD_RDY); if (err) goto out; /* * Feed the data into the command read/write port reg * in chunks of 64 bytes */ memset(temp + bytes_read, 0, HELPER_FW_LOAD_CHUNK_SZ - bytes_read); mdelay(10); err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG, temp, HELPER_FW_LOAD_CHUNK_SZ); if (err) goto out; /* Interrupt the boot code */ err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); if (err) goto out; err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, IF_SPI_CIC_CMD_DOWNLOAD_OVER); if (err) goto out; } /* * Once the helper / single stage firmware download is complete, * write 0 to scratch pad 1 and interrupt the * bootloader. This completes the helper download. */ err = spu_write_u16(card, IF_SPI_SCRATCH_1_REG, FIRMWARE_DNLD_OK); if (err) goto out; err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); if (err) goto out; err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, IF_SPI_CIC_CMD_DOWNLOAD_OVER); out: if (err) logf("failed to load helper firmware (err=%d)", err); return err; } /* * Returns the length of the next packet the firmware expects us to send. * Sets crc_err if the previous transfer had a CRC error. */ static int if_spi_prog_main_firmware_check_len(struct if_spi_card *card, int *crc_err) { uint16_t len; int err = 0; /* * wait until the host interrupt status register indicates * that we are ready to download */ err = spu_wait_for_u16(card, IF_SPI_HOST_INT_STATUS_REG, IF_SPI_HIST_CMD_DOWNLOAD_RDY, IF_SPI_HIST_CMD_DOWNLOAD_RDY); if (err) { logf("timed out waiting for host_int_status"); return err; } /* Ask the device how many bytes of firmware it wants. */ err = spu_read_u16(card, IF_SPI_SCRATCH_1_REG, &len); if (err) return err; if (len > IF_SPI_CMD_BUF_SIZE) { logf("firmware load device requested a larger transfer than we are prepared to handle (len = %d)", len); return -EIO; } if (len & 0x1) { logf("%s: crc error", __func__); len &= ~0x1; *crc_err = 1; } else *crc_err = 0; return len; } static int if_spi_prog_main_firmware(struct if_spi_card *card, int fd) { int len; int bytes_read = 0, crc_err = 0, err = 0; uint16_t num_crc_errs; err = spu_set_interrupt_mode(card, 1, 0); if (err) goto out; err = spu_wait_for_u16(card, IF_SPI_SCRATCH_1_REG, 0, 0); if (err) { logf("%s: timed out waiting for initial scratch reg = 0", __func__); goto out; } num_crc_errs = 0; while ((len = if_spi_prog_main_firmware_check_len(card, &crc_err))) { if (len < 0) { err = len; goto out; } if (crc_err) { /* Previous transfer failed. */ if (++num_crc_errs > MAX_MAIN_FW_LOAD_CRC_ERR) { logf("Too many CRC errors encountered in firmware load."); err = -EIO; goto out; } /* Rewind so we read back the data from previous transfer */ lseek(fd, -bytes_read, SEEK_CUR); } bytes_read = read(fd, card->cmd_buffer, len); if (bytes_read < 0) { /* * If there are no more bytes left, we would normally * expect to have terminated with len = 0 */ logf("Firmware load wants more bytes than we have to offer."); break; } else if (bytes_read < len) { memset(card->cmd_buffer + bytes_read, 0, len - bytes_read); } err = spu_write_u16(card, IF_SPI_HOST_INT_STATUS_REG, 0); if (err) goto out; err = spu_write(card, IF_SPI_CMD_RDWRPORT_REG, card->cmd_buffer, len); if (err) goto out; err = spu_write_u16(card, IF_SPI_CARD_INT_CAUSE_REG, IF_SPI_CIC_CMD_DOWNLOAD_OVER); if (err) goto out; } if (read(fd, card->cmd_buffer, IF_SPI_CMD_BUF_SIZE) > 0) { logf("firmware load wants fewer bytes than we have to offer"); } /* Confirm firmware download */ err = spu_wait_for_u32(card, IF_SPI_SCRATCH_4_REG, SUCCESSFUL_FW_DOWNLOAD_MAGIC); if (err) { logf("failed to confirm the firmware download"); goto out; } out: if (err) logf("failed to load firmware (err=%d)", err); return err; } static int if_spi_init_card(struct if_spi_card *card) { int err; size_t i; uint32_t scratch; int fd; err = spu_init(card); if (err) goto out; err = spu_get_chip_revision(card, &card->card_id, &card->card_rev); if (err) goto out; err = spu_read_u32(card, IF_SPI_SCRATCH_4_REG, &scratch); if (err) goto out; if (scratch == SUCCESSFUL_FW_DOWNLOAD_MAGIC) logf("Firmware is already loaded for Marvell WLAN 802.11 adapter"); else { /* Check if we support this card */ for (i = 0; i < ARRAY_SIZE(fw_table); i++) { if (card->card_id == fw_table[i].model) break; } if (i == ARRAY_SIZE(fw_table)) { logf("Unsupported chip_id: 0x%02x", card->card_id); err = -ENODEV; goto out; } logf("Initializing FW for Marvell WLAN 802.11 adapter " "(chip_id = 0x%04x, chip_rev = 0x%02x)", card->card_id, card->card_rev); fd = open(fw_table[i].helper, O_RDONLY); if (fd >= 0) { err = if_spi_prog_helper_firmware(card, fd); close(fd); if (err) goto out; } else { logf("failed to find firmware helper (%s)", fw_table[i].helper); err = -ENOENT; goto out; } fd = open(fw_table[i].main, O_RDONLY); if (fd >= 0) { err = if_spi_prog_main_firmware(card, fd); close(fd); if (err) goto out; } else { logf("failed to find firmware (%s)", fw_table[i].main); err = -ENOENT; goto out; } logf("loaded FW for Marvell WLAN 802.11 adapter"); } err = spu_set_interrupt_mode(card, 0, 1); if (err) goto out; out: return err; } void wifi_init(void) INIT_ATTR { #if 0 static struct if_spi_card card; libertas_spi_init(); libertas_spi_pd(1); libertas_spi_reset(1); mdelay(100); if (!if_spi_init_card(&card)) { /* TODO: Configure card and enter deep sleep */ } else #else libertas_spi_init(); (void)if_spi_init_card; #endif { /* Keep the lines in lowest power configuration */ libertas_spi_pd(0); libertas_spi_reset(1); libertas_spi_cs(1); } }