static int wl1271_boot_upload_firmware_chunk(struct wl1271 *wl, void *buf, size_t fw_data_len, u32 dest) { struct wlcore_partition_set partition; int addr, chunk_num, partition_limit; u8 *p, *chunk; int ret; /* whal_FwCtrl_LoadFwImageSm() */ wl1271_debug(DEBUG_BOOT, "starting firmware upload"); wl1271_debug(DEBUG_BOOT, "fw_data_len %zd chunk_size %d", fw_data_len, CHUNK_SIZE); if ((fw_data_len % 4) != 0) { wl1271_error("firmware length not multiple of four"); return -EIO; } chunk = kmalloc(CHUNK_SIZE, GFP_KERNEL); if (!chunk) { wl1271_error("allocation for firmware upload chunk failed"); return -ENOMEM; } memcpy(&partition, &wl->ptable[PART_DOWN], sizeof(partition)); partition.mem.start = dest; ret = wlcore_set_partition(wl, &partition); if (ret < 0) goto out; /* 10.1 set partition limit and chunk num */ chunk_num = 0; partition_limit = wl->ptable[PART_DOWN].mem.size; while (chunk_num < fw_data_len / CHUNK_SIZE) { /* 10.2 update partition, if needed */ addr = dest + (chunk_num + 2) * CHUNK_SIZE; if (addr > partition_limit) { addr = dest + chunk_num * CHUNK_SIZE; partition_limit = chunk_num * CHUNK_SIZE + wl->ptable[PART_DOWN].mem.size; partition.mem.start = addr; ret = wlcore_set_partition(wl, &partition); if (ret < 0) goto out; } /* 10.3 upload the chunk */ addr = dest + chunk_num * CHUNK_SIZE; p = buf + chunk_num * CHUNK_SIZE; memcpy(chunk, p, CHUNK_SIZE); wl1271_debug(DEBUG_BOOT, "uploading fw chunk 0x%p to 0x%x", p, addr); ret = wlcore_write(wl, addr, chunk, CHUNK_SIZE, false); if (ret < 0) goto out; chunk_num++; } /* 10.4 upload the last chunk */ addr = dest + chunk_num * CHUNK_SIZE; p = buf + chunk_num * CHUNK_SIZE; memcpy(chunk, p, fw_data_len % CHUNK_SIZE); wl1271_debug(DEBUG_BOOT, "uploading fw last chunk (%zd B) 0x%p to 0x%x", fw_data_len % CHUNK_SIZE, p, addr); ret = wlcore_write(wl, addr, chunk, fw_data_len % CHUNK_SIZE, false); out: kfree(chunk); return ret; }
/* * send command to firmware * * @wl: wl struct * @id: command id * @buf: buffer containing the command, must work with dma * @len: length of the buffer * return the cmd status code on success. */ static int __wlcore_cmd_send(struct wl1271 *wl, u16 id, void *buf, size_t len, size_t res_len) { struct wl1271_cmd_header *cmd; unsigned long timeout; u32 intr; int ret; u16 status; u16 poll_count = 0; if (unlikely(wl->state == WLCORE_STATE_RESTARTING && id != CMD_STOP_FWLOGGER)) return -EIO; if (WARN_ON_ONCE(len < sizeof(*cmd))) return -EIO; cmd = buf; cmd->id = cpu_to_le16(id); cmd->status = 0; WARN_ON(len % 4 != 0); WARN_ON(test_bit(WL1271_FLAG_IN_ELP, &wl->flags)); ret = wlcore_write(wl, wl->cmd_box_addr, buf, len, false); if (ret < 0) return ret; /* * TODO: we just need this because one bit is in a different * place. Is there any better way? */ ret = wl->ops->trigger_cmd(wl, wl->cmd_box_addr, buf, len); if (ret < 0) return ret; timeout = jiffies + msecs_to_jiffies(WL1271_COMMAND_TIMEOUT); ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &intr); if (ret < 0) return ret; while (!(intr & WL1271_ACX_INTR_CMD_COMPLETE)) { if (time_after(jiffies, timeout)) { wl1271_error("command complete timeout"); return -ETIMEDOUT; } poll_count++; if (poll_count < WL1271_CMD_FAST_POLL_COUNT) udelay(10); else msleep(1); ret = wlcore_read_reg(wl, REG_INTERRUPT_NO_CLEAR, &intr); if (ret < 0) return ret; } /* read back the status code of the command */ if (res_len == 0) res_len = sizeof(struct wl1271_cmd_header); ret = wlcore_read(wl, wl->cmd_box_addr, cmd, res_len, false); if (ret < 0) return ret; status = le16_to_cpu(cmd->status); ret = wlcore_write_reg(wl, REG_INTERRUPT_ACK, WL1271_ACX_INTR_CMD_COMPLETE); if (ret < 0) return ret; return status; }