blob: 9ab3451e167465eb308efe09e00b15fa598b4a35 [file] [log] [blame]
/*
* Copyright (C) 2016 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/vmalloc.h>
#include "nanohub.h"
#include "main.h"
#include "bl_st.h"
#define MAX_BUFFER_SIZE 1024
#define MAX_FLASH_BANKS 16
#define READ_ACK_TIMEOUT 100000
static uint8_t write_len(struct nanohub_data *data, int len)
{
uint8_t buffer[sizeof(uint8_t) + 1];
buffer[0] = len - 1;
return data->bl.write_data(data, buffer, sizeof(uint8_t));
}
static uint8_t write_cnt(struct nanohub_data *data, uint16_t cnt)
{
uint8_t buffer[sizeof(uint16_t) + 1];
buffer[0] = (cnt >> 8) & 0xFF;
buffer[1] = (cnt >> 0) & 0xFF;
return data->bl.write_data(data, buffer, sizeof(uint16_t));
}
static uint8_t write_addr(struct nanohub_data *data, uint32_t addr)
{
uint8_t buffer[sizeof(uint32_t) + 1];
buffer[0] = (addr >> 24) & 0xFF;
buffer[1] = (addr >> 16) & 0xFF;
buffer[2] = (addr >> 8) & 0xFF;
buffer[3] = addr & 0xFF;
return data->bl.write_data(data, buffer, sizeof(uint32_t));
}
/* write length followed by the data */
static uint8_t write_len_data(struct nanohub_data *data, int len,
const uint8_t *buf)
{
uint8_t buffer[sizeof(uint8_t) + 256 + sizeof(uint8_t)];
buffer[0] = len - 1;
memcpy(&buffer[1], buf, len);
return data->bl.write_data(data, buffer, sizeof(uint8_t) + len);
}
/* keep checking for ack until we receive a ack or nack */
static uint8_t read_ack_loop(struct nanohub_data *data)
{
uint8_t ret;
int32_t timeout = READ_ACK_TIMEOUT;
do {
ret = data->bl.read_ack(data);
if (ret != CMD_ACK && ret != CMD_NACK)
schedule();
} while (ret != CMD_ACK && ret != CMD_NACK && timeout-- > 0);
return ret;
}
uint8_t nanohub_bl_sync(struct nanohub_data *data)
{
return data->bl.sync(data);
}
int nanohub_bl_open(struct nanohub_data *data)
{
int ret = -1;
data->bl.tx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
if (!data->bl.tx_buffer)
goto out;
data->bl.rx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
if (!data->bl.rx_buffer)
goto free_tx;
ret = data->bl.open(data);
if (!ret)
goto out;
kfree(data->bl.rx_buffer);
free_tx:
kfree(data->bl.tx_buffer);
out:
return ret;
}
void nanohub_bl_close(struct nanohub_data *data)
{
data->bl.close(data);
kfree(data->bl.tx_buffer);
kfree(data->bl.rx_buffer);
}
static uint8_t write_bank(struct nanohub_data *data, int bank, uint32_t addr,
const uint8_t *buf, size_t length)
{
const struct nanohub_platform_data *pdata = data->pdata;
uint8_t status = CMD_ACK;
uint32_t offset;
if (addr <= pdata->flash_banks[bank].address) {
offset = pdata->flash_banks[bank].address - addr;
if (addr + length >
pdata->flash_banks[bank].address +
pdata->flash_banks[bank].length)
status =
nanohub_bl_write_memory(data,
pdata->flash_banks[bank].
address,
pdata->flash_banks[bank].
length, buf + offset);
else
status =
nanohub_bl_write_memory(data,
pdata->flash_banks[bank].
address, length - offset,
buf + offset);
} else {
if (addr + length >
pdata->flash_banks[bank].address +
pdata->flash_banks[bank].length)
status =
nanohub_bl_write_memory(data, addr,
pdata->flash_banks[bank].
address +
pdata->flash_banks[bank].
length - addr, buf);
else
status =
nanohub_bl_write_memory(data, addr, length, buf);
}
return status;
}
uint8_t nanohub_bl_download(struct nanohub_data *data, uint32_t addr,
const uint8_t *image, size_t length)
{
const struct nanohub_platform_data *pdata = data->pdata;
uint8_t *ptr;
int i, j;
uint8_t status;
uint32_t offset;
uint8_t erase_mask[MAX_FLASH_BANKS] = { 0 };
uint8_t erase_write_mask[MAX_FLASH_BANKS] = { 0 };
uint8_t write_mask[MAX_FLASH_BANKS] = { 0 };
if (pdata->num_flash_banks > MAX_FLASH_BANKS) {
status = CMD_NACK;
goto out;
}
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_download: sync=%02x\n", status);
goto out;
}
ptr = vmalloc(length);
if (!ptr) {
status = CMD_NACK;
goto out;
}
status = nanohub_bl_read_memory(data, addr, length, ptr);
pr_info(
"nanohub: nanohub_bl_read_memory: status=%02x, addr=%08x, length=%zd\n",
status, addr, length);
for (i = 0; i < pdata->num_flash_banks; i++) {
if (addr >= pdata->flash_banks[i].address &&
addr <
pdata->flash_banks[i].address +
pdata->flash_banks[i].length) {
break;
}
}
offset = (uint32_t) (addr - pdata->flash_banks[i].address);
j = 0;
while (j < length && i < pdata->num_flash_banks) {
if (image[j] != 0xFF)
erase_write_mask[i] = true;
if ((ptr[j] & image[j]) != image[j]) {
erase_mask[i] = true;
if (erase_write_mask[i]) {
j += pdata->flash_banks[i].length - offset;
offset = pdata->flash_banks[i].length;
} else {
j++;
offset++;
}
} else {
if (ptr[j] != image[j])
write_mask[i] = true;
j++;
offset++;
}
if (offset == pdata->flash_banks[i].length) {
i++;
offset = 0;
if (i < pdata->num_flash_banks)
j += (pdata->flash_banks[i].address -
pdata->flash_banks[i - 1].address -
pdata->flash_banks[i - 1].length);
else
j = length;
}
}
for (i = 0; status == CMD_ACK && i < pdata->num_flash_banks; i++) {
pr_info("nanohub: i=%d, erase=%d, erase_write=%d, write=%d\n",
i, erase_mask[i], erase_write_mask[i], write_mask[i]);
if (erase_mask[i]) {
status =
nanohub_bl_erase_sector(data,
pdata->flash_banks[i].bank);
if (status == CMD_ACK && erase_write_mask[i])
status =
write_bank(data, i, addr, image, length);
} else if (write_mask[i]) {
status = write_bank(data, i, addr, image, length);
}
}
vfree(ptr);
out:
return status;
}
uint8_t nanohub_bl_erase_shared(struct nanohub_data *data)
{
const struct nanohub_platform_data *pdata = data->pdata;
int i;
uint8_t status;
if (pdata->num_shared_flash_banks > MAX_FLASH_BANKS) {
status = CMD_NACK;
goto out;
}
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_erase_shared: sync=%02x\n", status);
goto out;
}
for (i = 0;
status == CMD_ACK && i < pdata->num_shared_flash_banks;
i++) {
status = nanohub_bl_erase_sector(data,
pdata->shared_flash_banks[i].bank);
}
out:
return status;
}
uint8_t nanohub_bl_erase_shared_bl(struct nanohub_data *data)
{
uint8_t status;
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_erase_shared_bl: sync=%02x\n", status);
goto out;
}
status = nanohub_bl_erase_special(data, 0xFFF0);
out:
return status;
}
/* erase a single sector */
uint8_t nanohub_bl_erase_sector(struct nanohub_data *data, uint16_t sector)
{
uint8_t ret;
data->bl.write_cmd(data, data->bl.cmd_erase);
ret = data->bl.read_ack(data);
if (ret == CMD_ACK)
ret = write_cnt(data, 0x0000);
if (ret != CMD_NACK)
ret = read_ack_loop(data);
if (ret == CMD_ACK)
ret = write_cnt(data, sector);
if (ret != CMD_NACK)
ret = read_ack_loop(data);
return ret;
}
/* erase special */
uint8_t nanohub_bl_erase_special(struct nanohub_data *data, uint16_t special)
{
uint8_t ret;
data->bl.write_cmd(data, data->bl.cmd_erase);
ret = data->bl.read_ack(data);
if (ret == CMD_ACK)
ret = write_cnt(data, special);
if (ret != CMD_NACK)
ret = read_ack_loop(data);
return ret;
}
/* read memory - this will chop the request into 256 byte reads */
uint8_t nanohub_bl_read_memory(struct nanohub_data *data, uint32_t addr,
uint32_t length, uint8_t *buffer)
{
uint8_t ret = CMD_ACK;
uint32_t offset = 0;
while (ret == CMD_ACK && length > offset) {
data->bl.write_cmd(data, data->bl.cmd_read_memory);
ret = data->bl.read_ack(data);
if (ret == CMD_ACK) {
write_addr(data, addr + offset);
ret = read_ack_loop(data);
if (ret == CMD_ACK) {
if (length - offset >= 256) {
write_len(data, 256);
ret = read_ack_loop(data);
if (ret == CMD_ACK) {
data->bl.read_data(data,
&buffer
[offset],
256);
offset += 256;
}
} else {
write_len(data, length - offset);
ret = read_ack_loop(data);
if (ret == CMD_ACK) {
data->bl.read_data(data,
&buffer
[offset],
length -
offset);
offset = length;
}
}
}
}
}
return ret;
}
/* write memory - this will chop the request into 256 byte writes */
uint8_t nanohub_bl_write_memory(struct nanohub_data *data, uint32_t addr,
uint32_t length, const uint8_t *buffer)
{
uint8_t ret = CMD_ACK;
uint32_t offset = 0;
while (ret == CMD_ACK && length > offset) {
data->bl.write_cmd(data, data->bl.cmd_write_memory);
ret = data->bl.read_ack(data);
if (ret == CMD_ACK) {
write_addr(data, addr + offset);
ret = read_ack_loop(data);
if (ret == CMD_ACK) {
if (length - offset >= 256) {
write_len_data(data, 256,
&buffer[offset]);
offset += 256;
} else {
write_len_data(data, length - offset,
&buffer[offset]);
offset = length;
}
ret = read_ack_loop(data);
}
}
}
return ret;
}
uint8_t nanohub_bl_get_version(struct nanohub_data *data, uint8_t *version)
{
uint8_t status;
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_get_version: sync=%02x\n", status);
goto out;
}
data->bl.write_cmd(data, data->bl.cmd_get_version);
status = data->bl.read_ack(data);
if (status == CMD_ACK)
data->bl.read_data(data, version, 1);
status = data->bl.read_ack(data);
out:
return status;
}
uint8_t nanohub_bl_get_id(struct nanohub_data *data, uint16_t *id)
{
uint8_t status;
uint8_t len;
uint8_t buffer[256];
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_get_id: sync=%02x\n", status);
goto out;
}
data->bl.write_cmd(data, data->bl.cmd_get_id);
status = data->bl.read_ack(data);
if (status == CMD_ACK) {
data->bl.read_data(data, &len, 1);
data->bl.read_data(data, buffer, len+1);
*id = (buffer[0] << 8) | buffer[1];
}
status = data->bl.read_ack(data);
out:
return status;
}
uint8_t nanohub_bl_lock(struct nanohub_data *data)
{
uint8_t status;
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_lock: sync=%02x\n", status);
goto out;
}
data->bl.write_cmd(data, data->bl.cmd_readout_protect);
status = data->bl.read_ack(data);
if (status == CMD_ACK)
status = read_ack_loop(data);
out:
return status;
}
uint8_t nanohub_bl_unlock(struct nanohub_data *data)
{
uint8_t status;
status = nanohub_bl_sync(data);
if (status != CMD_ACK) {
pr_err("nanohub_bl_lock: sync=%02x\n", status);
goto out;
}
data->bl.write_cmd(data, data->bl.cmd_readout_unprotect);
status = data->bl.read_ack(data);
if (status == CMD_ACK)
status = read_ack_loop(data);
out:
return status;
}
uint8_t nanohub_bl_update_finished(struct nanohub_data *data)
{
uint8_t ret;
data->bl.write_cmd(data, data->bl.cmd_update_finished);
ret = read_ack_loop(data);
return ret;
}