/*************************************************************************** * 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 #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; } }