2008-03-01 22:17:33 +01:00
|
|
|
/***************************************************************************
|
2008-03-03 23:50:40 +01:00
|
|
|
* Copyright (C) 03/2008 by Olaf Rempel *
|
2008-03-01 22:17:33 +01:00
|
|
|
* 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"
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
#include "at91_twi.h"
|
2008-03-01 22:17:33 +01:00
|
|
|
#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;
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
uint32_t fmr = *AT91C_MC_FSR;
|
2008-03-01 22:17:33 +01:00
|
|
|
|
|
|
|
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 {
|
2008-03-03 22:32:38 +01:00
|
|
|
dfu_state = DFU_STATE_dfuDNBUSY;
|
2008-03-01 22:17:33 +01:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
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];
|
2008-03-01 22:17:33 +01:00
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
static void handle_dnload_flash(void)
|
2008-03-01 22:17:33 +01:00
|
|
|
{
|
2008-03-03 22:32:38 +01:00
|
|
|
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;
|
|
|
|
|
2008-03-01 22:17:33 +01:00
|
|
|
ep_transfer_send(0, NULL, 0, NULL);
|
|
|
|
}
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
static void handle_dnload_eeprom(void)
|
2008-03-01 22:17:33 +01:00
|
|
|
{
|
2008-03-03 22:32:38 +01:00
|
|
|
/*
|
|
|
|
* TODO: write buf to onboard eeprom @ address
|
|
|
|
* inc. address
|
|
|
|
*/
|
|
|
|
ep_transfer_send(0, NULL, 0, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_dnload_blctrl(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* TODO: write buf to blctrl#iface @ address
|
|
|
|
* inc. address
|
|
|
|
*/
|
|
|
|
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);
|
2008-03-01 22:17:33 +01:00
|
|
|
|
|
|
|
if (length == 0) {
|
|
|
|
dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
|
|
|
|
return RET_ZLP;
|
|
|
|
}
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
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);
|
2008-03-01 22:17:33 +01:00
|
|
|
return RET_NOTHING;
|
|
|
|
}
|
|
|
|
|
2008-03-03 22:32:38 +01:00
|
|
|
static void handle_upload(uint16_t iface, uint16_t block, uint16_t length)
|
2008-03-01 22:17:33 +01:00
|
|
|
{
|
2008-03-03 22:32:38 +01:00
|
|
|
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);
|
2008-03-01 22:17:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ep0_handle_dfu(struct usb_ctrlrequest *req)
|
|
|
|
{
|
2008-03-03 22:32:38 +01:00
|
|
|
uint32_t ret = RET_NOTHING;
|
2008-03-01 22:17:33 +01:00
|
|
|
|
|
|
|
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 */
|
2008-03-03 22:32:38 +01:00
|
|
|
handle_upload(req->wIndex, req->wValue, req->wLength);
|
2008-03-01 22:17:33 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|