The HW supports up to 4095 bytes transfers via SPI. The SPI read & write operations do not handle larger transfers, causing the HW to stall in such cases. Fix this by fragmenting large transfers into smaller chunks, and transferring each one separately. Signed-off-by: Ido Yariv <ido@xxxxxxxxxx> Tested-by: Juuso Oikarinen <juuso.oikarinen@xxxxxxxxx> --- drivers/net/wireless/wl12xx/wl1271_spi.c | 132 ++++++++++++++++++----------- 1 files changed, 82 insertions(+), 50 deletions(-) diff --git a/drivers/net/wireless/wl12xx/wl1271_spi.c b/drivers/net/wireless/wl12xx/wl1271_spi.c index 75cbf36..71336ef 100644 --- a/drivers/net/wireless/wl12xx/wl1271_spi.c +++ b/drivers/net/wireless/wl12xx/wl1271_spi.c @@ -63,6 +63,11 @@ ((WL1271_BUSY_WORD_LEN - 4) / sizeof(u32)) #define HW_ACCESS_WSPI_INIT_CMD_MASK 0 +/* HW limitation: maximum possible chunk size is 4095 bytes */ +#define WSPI_MAX_CHUNK_SIZE 4092 + +#define WSPI_MAX_NUM_OF_CHUNKS (WL1271_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) + static inline struct spi_device *wl_to_spi(struct wl1271 *wl) { return wl->if_priv; @@ -202,53 +207,64 @@ static int wl1271_spi_read_busy(struct wl1271 *wl) static void wl1271_spi_raw_read(struct wl1271 *wl, int addr, void *buf, size_t len, bool fixed) { - struct spi_transfer t[3]; + struct spi_transfer t[2]; struct spi_message m; u32 *busy_buf; u32 *cmd; + u32 chunk_len; - cmd = &wl->buffer_cmd; - busy_buf = wl->buffer_busyword; + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); - *cmd = 0; - *cmd |= WSPI_CMD_READ; - *cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH; - *cmd |= addr & WSPI_CMD_BYTE_ADDR; + cmd = &wl->buffer_cmd; + busy_buf = wl->buffer_busyword; - if (fixed) - *cmd |= WSPI_CMD_FIXED; + *cmd = 0; + *cmd |= WSPI_CMD_READ; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; - spi_message_init(&m); - memset(t, 0, sizeof(t)); + if (fixed) + *cmd |= WSPI_CMD_FIXED; - t[0].tx_buf = cmd; - t[0].len = 4; - t[0].cs_change = true; - spi_message_add_tail(&t[0], &m); + spi_message_init(&m); + memset(t, 0, sizeof(t)); - /* Busy and non busy words read */ - t[1].rx_buf = busy_buf; - t[1].len = WL1271_BUSY_WORD_LEN; - t[1].cs_change = true; - spi_message_add_tail(&t[1], &m); + t[0].tx_buf = cmd; + t[0].len = 4; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); - spi_sync(wl_to_spi(wl), &m); + /* Busy and non busy words read */ + t[1].rx_buf = busy_buf; + t[1].len = WL1271_BUSY_WORD_LEN; + t[1].cs_change = true; + spi_message_add_tail(&t[1], &m); - if (!(busy_buf[WL1271_BUSY_WORD_CNT - 1] & 0x1) && - wl1271_spi_read_busy(wl)) { - memset(buf, 0, len); - return; - } + spi_sync(wl_to_spi(wl), &m); - spi_message_init(&m); - memset(t, 0, sizeof(t)); + if (!(busy_buf[WL1271_BUSY_WORD_CNT - 1] & 0x1) && + wl1271_spi_read_busy(wl)) { + memset(buf, 0, chunk_len); + return; + } - t[0].rx_buf = buf; - t[0].len = len; - t[0].cs_change = true; - spi_message_add_tail(&t[0], &m); + spi_message_init(&m); + memset(t, 0, sizeof(t)); - spi_sync(wl_to_spi(wl), &m); + t[0].rx_buf = buf; + t[0].len = chunk_len; + t[0].cs_change = true; + spi_message_add_tail(&t[0], &m); + + spi_sync(wl_to_spi(wl), &m); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + } wl1271_dump(DEBUG_SPI, "spi_read cmd -> ", cmd, sizeof(*cmd)); wl1271_dump(DEBUG_SPI, "spi_read buf <- ", buf, len); @@ -257,30 +273,46 @@ static void wl1271_spi_raw_read(struct wl1271 *wl, int addr, void *buf, static void wl1271_spi_raw_write(struct wl1271 *wl, int addr, void *buf, size_t len, bool fixed) { - struct spi_transfer t[2]; + struct spi_transfer t[2 * WSPI_MAX_NUM_OF_CHUNKS]; struct spi_message m; + u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; u32 *cmd; + u32 chunk_len; + int i; - cmd = &wl->buffer_cmd; - - *cmd = 0; - *cmd |= WSPI_CMD_WRITE; - *cmd |= (len << WSPI_CMD_BYTE_LENGTH_OFFSET) & WSPI_CMD_BYTE_LENGTH; - *cmd |= addr & WSPI_CMD_BYTE_ADDR; - - if (fixed) - *cmd |= WSPI_CMD_FIXED; + WARN_ON(len > WL1271_AGGR_BUFFER_SIZE); spi_message_init(&m); memset(t, 0, sizeof(t)); - t[0].tx_buf = cmd; - t[0].len = sizeof(*cmd); - spi_message_add_tail(&t[0], &m); - - t[1].tx_buf = buf; - t[1].len = len; - spi_message_add_tail(&t[1], &m); + cmd = &commands[0]; + i = 0; + while (len > 0) { + chunk_len = min((size_t)WSPI_MAX_CHUNK_SIZE, len); + + *cmd = 0; + *cmd |= WSPI_CMD_WRITE; + *cmd |= (chunk_len << WSPI_CMD_BYTE_LENGTH_OFFSET) & + WSPI_CMD_BYTE_LENGTH; + *cmd |= addr & WSPI_CMD_BYTE_ADDR; + + if (fixed) + *cmd |= WSPI_CMD_FIXED; + + t[i].tx_buf = cmd; + t[i].len = sizeof(*cmd); + spi_message_add_tail(&t[i++], &m); + + t[i].tx_buf = buf; + t[i].len = chunk_len; + spi_message_add_tail(&t[i++], &m); + + if (!fixed) + addr += chunk_len; + buf += chunk_len; + len -= chunk_len; + cmd++; + } spi_sync(wl_to_spi(wl), &m); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html