sam7fc/src/dfu.c

473 lines
9.7 KiB
C

/***************************************************************************
* Copyright (C) 03/2008 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* 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; version 2 of the License *
* *
* 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. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <stdio.h>
#include "AT91SAM7S256.h"
#include "board.h"
#include "at91_twi.h"
#include "at91_udp.h"
#include "usb_ch9.h"
#include "usb_dfu.h"
static uint8_t dfu_state = DFU_STATE_dfuIDLE;
static uint8_t dfu_status = DFU_STATUS_OK;
#define RET_NOTHING 0
#define RET_STALL 1
#define RET_ZLP 2
static void handle_getstatus(void)
{
struct dfu_status dstat;
uint32_t fmr = *AT91C_MC_FSR;
switch (dfu_state) {
case DFU_STATE_dfuDNLOAD_SYNC:
case DFU_STATE_dfuDNBUSY:
if (fmr & AT91C_MC_PROGE) {
dfu_status = DFU_STATUS_errPROG;
dfu_state = DFU_STATE_dfuERROR;
} else if (fmr & AT91C_MC_LOCKE) {
dfu_status = DFU_STATUS_errWRITE;
dfu_state = DFU_STATE_dfuERROR;
} else if (fmr & AT91C_MC_FRDY) {
dfu_state = DFU_STATE_dfuDNLOAD_IDLE;
} else {
dfu_state = DFU_STATE_dfuDNBUSY;
}
break;
}
/* send status response */
dstat.bStatus = dfu_status;
dstat.bState = dfu_state;
dstat.iString = 0;
/* FIXME: set dstat.bwPollTimeout */
ep_transfer_send(0, (char *)&dstat, sizeof(dstat), NULL);
}
static void handle_getstate(void)
{
ep_transfer_send(0, (char *)&dfu_state, sizeof(dfu_state), NULL);
}
static uint16_t prog_iface;
static uint16_t prog_block;
static uint16_t prog_length;
static uint32_t prog_buf[AT91C_IFLASH_PAGE_SIZE /4];
static void handle_dnload_flash(void)
{
uint32_t *ptr = (uint32_t *)((uint8_t *)0x100000 + (prog_block * 0x100));
uint32_t i;
prog_length += 3;
for (i = 0; i < prog_length /4; i++)
*ptr++ = prog_buf[i];
if (!(*AT91C_MC_FSR & AT91C_MC_FRDY)) {
printf("flash not ready!\n\r");
return;
}
*AT91C_MC_FCR = (AT91C_MC_KEY & (0x5A << 24)) |
(((uint32_t)ptr - 0x100004) & AT91C_MC_PAGEN) |
AT91C_MC_FCMD_START_PROG;
ep_transfer_send(0, NULL, 0, NULL);
}
static void handle_dnload_eeprom(void)
{
/*
* TODO: write buf to onboard eeprom @ address
* inc. address
*/
ep_transfer_send(0, NULL, 0, NULL);
}
static void handle_dnload_blctrl(void)
{
uint32_t i2c_dev = TWI_ADDR_BL1 + (prog_iface -2);
uint8_t buf[66] = { 0x47, 0x11 };
uint32_t i;
for (i = 0; i < prog_length; i += 0x40) {
uint32_t j;
for (j = 0; j < 0x40; j++)
buf[j +2] = ((i + j) < prog_length) ? ((uint8_t *)prog_buf)[i + j] : 0xFF;
struct blmc_cmd cmd = {
.cmd = (CMD_WRITE_FLASH << 16) | (prog_block * 0x100 + i),
.mode = BLMC_CMD_WRITE | BLMC_CMD_2_ARG,
.size = sizeof(buf),
.data = buf,
};
printf("cmd:0x%08lx\n\r", cmd.cmd);
if (twi_cmd(i2c_dev, &cmd) != 0) {
printf("flash write failed\n\r");
dfu_state = DFU_STATE_dfuERROR;
dfu_status = DFU_STATUS_errUNKNOWN;
}
}
ep_transfer_send(0, NULL, 0, NULL);
}
static uint32_t handle_dnload(uint16_t iface, uint16_t block, uint16_t length)
{
printf("down:%x-%x-%x\n\r", iface, block, length);
if (length == 0) {
dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
return RET_ZLP;
}
prog_iface = iface;
prog_block = block;
prog_length = length;
void *callback = NULL;
switch (iface) {
case 0: /* internal flash */
if (block >= 0x3C0) {
dfu_state = DFU_STATE_dfuERROR;
dfu_status = DFU_STATUS_errADDRESS;
return RET_STALL;
}
callback = handle_dnload_flash;
break;
case 1: /* onboard i2c-eeprom */
callback = handle_dnload_eeprom;
break;
case 2:
case 3:
case 4:
case 5: /* blctrl 1-4 */
callback = handle_dnload_blctrl;
break;
}
ep_transfer_receive(0, (char *)&prog_buf, length, callback);
return RET_NOTHING;
}
static void handle_upload(uint16_t iface, uint16_t block, uint16_t length)
{
printf("up:%x-%x-%x\n\r", iface, block, length);
char *ptr = (char *)&prog_buf;
switch (iface) {
case 0: /* internal flash */
if (block >= 0x400) {
length = 0;
break;
}
ptr = (char *)0x100000 + (block * 0x100);
break;
case 1: /* onboard i2c-eeprom */
if (block >= 0x80) {
length = 0;
break;
}
/* TODO */
break;
case 2:
case 3:
case 4:
case 5: /* blctrl 1-4 */
if (block >= 0x20) {
length = 0;
dfu_state = DFU_STATE_dfuIDLE;
break;
}
uint32_t i2c_dev = TWI_ADDR_BL1 + (iface -2);
struct blmc_cmd cmd = {
.cmd = (CMD_READ_FLASH << 16) | (block * 0x100),
.mode = BLMC_CMD_READ | BLMC_CMD_2_ARG,
.size = length,
.data = (uint8_t *)&prog_buf,
};
if (twi_cmd(i2c_dev, &cmd) != 0) {
length = 0;
dfu_state = DFU_STATE_dfuERROR;
dfu_status = DFU_STATUS_errUNKNOWN;
}
break;
}
ep_transfer_send(0, ptr, length, NULL);
}
void ep0_handle_dfu(struct usb_ctrlrequest *req)
{
uint32_t ret = RET_NOTHING;
switch (dfu_state) {
case DFU_STATE_appIDLE:
switch (req->bRequest) {
case USB_REQ_DFU_DETACH:
dfu_state = DFU_STATE_appDETACH;
ret = RET_ZLP;
break;
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
default:
ret = RET_STALL;
break;
}
break;
case DFU_STATE_appDETACH:
switch (req->bRequest) {
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
default:
dfu_state = DFU_STATE_appIDLE;
ret = RET_STALL;
break;
}
/* FIXME: implement timer to return to appIDLE */
break;
case DFU_STATE_dfuIDLE:
switch (req->bRequest) {
case USB_REQ_DFU_DNLOAD:
if (req->wLength == 0) {
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
ret = handle_dnload(req->wIndex, req->wValue, req->wLength);
break;
case USB_REQ_DFU_UPLOAD:
dfu_state = DFU_STATE_dfuUPLOAD_IDLE;
handle_upload(req->wIndex, req->wValue, req->wLength);
break;
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
case USB_REQ_DFU_ABORT:
/* no zlp? */
ret = RET_ZLP;
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuDNLOAD_SYNC:
switch (req->bRequest) {
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
/* FIXME: state transition depending on block completeness */
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuDNBUSY:
switch (req->bRequest) {
case USB_REQ_DFU_GETSTATUS:
/* FIXME: only accept getstatus if bwPollTimeout
* has elapsed */
handle_getstatus();
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuDNLOAD_IDLE:
switch (req->bRequest) {
case USB_REQ_DFU_DNLOAD:
dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
ret = handle_dnload(req->wIndex, req->wValue, req->wLength);
break;
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
case USB_REQ_DFU_ABORT:
dfu_state = DFU_STATE_dfuIDLE;
ret = RET_ZLP;
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuMANIFEST_SYNC:
switch (req->bRequest) {
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
dfu_state = DFU_STATE_dfuIDLE;
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuMANIFEST:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
case DFU_STATE_dfuMANIFEST_WAIT_RST:
/* we should never go here */
break;
case DFU_STATE_dfuUPLOAD_IDLE:
switch (req->bRequest) {
case USB_REQ_DFU_UPLOAD:
/* state transition if less data then requested */
handle_upload(req->wIndex, req->wValue, req->wLength);
break;
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
case USB_REQ_DFU_ABORT:
dfu_state = DFU_STATE_dfuIDLE;
/* no zlp? */
ret = RET_ZLP;
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
case DFU_STATE_dfuERROR:
switch (req->bRequest) {
case USB_REQ_DFU_GETSTATUS:
handle_getstatus();
break;
case USB_REQ_DFU_CLRSTATUS:
dfu_state = DFU_STATE_dfuIDLE;
dfu_status = DFU_STATUS_OK;
/* no zlp? */
ret = RET_ZLP;
break;
case USB_REQ_DFU_GETSTATE:
handle_getstate();
break;
default:
dfu_state = DFU_STATE_dfuERROR;
ret = RET_STALL;
break;
}
break;
}
switch (ret) {
case RET_NOTHING:
break;
case RET_ZLP:
ep_transfer_send(0, NULL, 0, NULL);
break;
case RET_STALL:
ep_send_stall(0);
break;
}
}