rockbox/firmware/drivers/libertas/if_spi.c

675 lines
18 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 by Tomasz Moń
* Ported from Linux libertas driver
* Copyright 2008 Analog Devices Inc.
* Authors:
* Andrey Yurovsky <andrey@cozybit.com>
* Colin McCabe <colin@cozybit.com>
* 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 <stddef.h>
#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);
}
}