Adding McHCK DFU Bootloader
- Heavily modified, not compatible with McHCK images - Uses 4k of Flash (rather than 3k) - LED turns on when in firmware flash mode - Changed the USB IDs - Added CMake build system - Updated linker script to be closer to what is used for the rest of the projects - Removed a lot of unnecessary code - Added a license header to each file (using the same license as the McHCK had) - Updated the USB Vendor, Product and Serial strings - Using the Kiibohd initialization sequence rather than the McHCK one - Using Kiibohd interrupt vector table and other misc mk20dx setup
This commit is contained in:
parent
eabb1c546a
commit
4102512579
20 changed files with 3742 additions and 0 deletions
326
Bootloader/kinetis.c
Normal file
326
Bootloader/kinetis.c
Normal file
|
@ -0,0 +1,326 @@
|
|||
/* Copyright (c) 2011,2012 Simon Schubert <2@0x2c.org>.
|
||||
* Modifications by Jacob Alexander 2014 <haata@kiibohd.com>
|
||||
*
|
||||
* 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ----- Defines -----
|
||||
|
||||
#define usb_xfer_info USB_STAT_t
|
||||
|
||||
|
||||
|
||||
// ----- Local Includes -----
|
||||
|
||||
#include "mchck.h"
|
||||
#include "usb-internal.h"
|
||||
|
||||
|
||||
|
||||
// ----- Functions -----
|
||||
|
||||
/**
|
||||
* Kinetis USB driver notes:
|
||||
* We need to manually maintain the DATA0/1 toggling for the SIE.
|
||||
* SETUP transactions always start with a DATA0.
|
||||
*
|
||||
* The SIE internally uses pingpong (double) buffering, which is
|
||||
* easily confused with DATA0/DATA1 toggling, and I think even the
|
||||
* Freescale docs confuse the two notions. When BD->DTS is set,
|
||||
* BD->DATA01 will be used to verify/discard incoming DATAx and it
|
||||
* will set the DATAx PID for outgoing tokens. This is not described
|
||||
* as such in the Freescale Kinetis docs, but the Microchip PIC32 OTG
|
||||
* docs are more clear on this; it seems that both Freescale and
|
||||
* Microchip use different versions of the same USB OTG IP core.
|
||||
*
|
||||
* http://ww1.microchip.com/downloads/en/DeviceDoc/61126F.pdf
|
||||
*
|
||||
* Clear CTL->TOKEN_BUSY after SETUP tokens.
|
||||
*/
|
||||
|
||||
|
||||
static struct USB_BD_t bdt[USB_MAX_EP * 2 *2] __attribute__((section(".usbdescriptortable")));
|
||||
|
||||
static struct USB_BD_t *
|
||||
usb_get_bd(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
return (&bdt[(s->ep_num << 2) | (s->ep_dir << 1) | s->pingpong]);
|
||||
}
|
||||
|
||||
static struct USB_BD_t *
|
||||
usb_get_bd_stat(struct USB_STAT_t *stat)
|
||||
{
|
||||
return (((void *)(uintptr_t)bdt + (stat->raw << 1)));
|
||||
}
|
||||
|
||||
void *usb_get_xfer_data(struct usb_xfer_info *i)
|
||||
{
|
||||
return (usb_get_bd_stat(i)->addr);
|
||||
}
|
||||
|
||||
enum usb_tok_pid usb_get_xfer_pid(struct usb_xfer_info *i)
|
||||
{
|
||||
return (usb_get_bd_stat(i)->tok_pid);
|
||||
}
|
||||
|
||||
int usb_get_xfer_ep(struct usb_xfer_info *i)
|
||||
{
|
||||
return (i->ep);
|
||||
}
|
||||
|
||||
enum usb_ep_dir usb_get_xfer_dir(struct usb_xfer_info *i)
|
||||
{
|
||||
return (i->dir);
|
||||
}
|
||||
|
||||
void usb_enable_xfers(void)
|
||||
{
|
||||
USB0.ctl.raw = ((struct USB_CTL_t){
|
||||
.txd_suspend = 0,
|
||||
.usben = 1
|
||||
}).raw;
|
||||
}
|
||||
|
||||
void usb_set_addr(int addr)
|
||||
{
|
||||
USB0.addr.raw = addr;
|
||||
}
|
||||
|
||||
|
||||
void usb_pipe_stall(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
volatile struct USB_BD_t *bd = usb_get_bd(s);
|
||||
bd->raw = ((struct USB_BD_BITS_t){
|
||||
.stall = 1,
|
||||
.own = 1
|
||||
}).raw;
|
||||
}
|
||||
|
||||
void usb_pipe_unstall(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
volatile struct USB_BD_t *bd = usb_get_bd(s);
|
||||
struct USB_BD_BITS_t state = { .raw = bd->raw };
|
||||
|
||||
if (state.own && state.stall)
|
||||
bd->raw = 0;
|
||||
}
|
||||
|
||||
void usb_pipe_enable(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
USB0.endpt[s->ep_num].raw |= ((struct USB_ENDPT_t){
|
||||
.eptxen = s->ep_dir == USB_EP_TX,
|
||||
.eprxen = s->ep_dir == USB_EP_RX,
|
||||
.ephshk = 1, /* XXX ISO */
|
||||
.epctldis = s->ep_num != 0
|
||||
}).raw;
|
||||
}
|
||||
|
||||
void usb_pipe_disable(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
USB0.endpt[s->ep_num].raw &= ~((struct USB_ENDPT_t){
|
||||
.eptxen = s->ep_dir == USB_EP_TX,
|
||||
.eprxen = s->ep_dir == USB_EP_RX,
|
||||
.epctldis = 1
|
||||
}).raw;
|
||||
}
|
||||
|
||||
size_t usb_ep_get_transfer_size(struct usbd_ep_pipe_state_t *s)
|
||||
{
|
||||
struct USB_BD_t *bd = usb_get_bd(s);
|
||||
return (bd->bc);
|
||||
}
|
||||
|
||||
void usb_queue_next(struct usbd_ep_pipe_state_t *s, void *addr, size_t len)
|
||||
{
|
||||
volatile struct USB_BD_t *bd = usb_get_bd(s);
|
||||
|
||||
bd->addr = addr;
|
||||
/* damn you bitfield problems */
|
||||
bd->raw = ((struct USB_BD_BITS_t){
|
||||
.dts = 1,
|
||||
.own = 1,
|
||||
.data01 = s->data01,
|
||||
.bc = len,
|
||||
}).raw;
|
||||
}
|
||||
|
||||
static void usb_reset(void)
|
||||
{
|
||||
/* reset pingpong state */
|
||||
/* For some obscure reason, we need to use or here. */
|
||||
USB0.ctl.raw |= ((struct USB_CTL_t){
|
||||
.txd_suspend = 1,
|
||||
.oddrst = 1,
|
||||
}).raw;
|
||||
|
||||
/* clear all interrupt bits - not sure if needed */
|
||||
USB0.istat.raw = 0xff;
|
||||
USB0.errstat.raw = 0xff;
|
||||
USB0.otgistat.raw = 0xff;
|
||||
|
||||
/* zap also BDT pingpong & queued transactions */
|
||||
memset(bdt, 0, sizeof(bdt));
|
||||
USB0.addr.raw = 0;
|
||||
|
||||
usb_restart();
|
||||
|
||||
USB0.ctl.raw = ((struct USB_CTL_t){
|
||||
.txd_suspend = 0,
|
||||
.usben = 1
|
||||
}).raw;
|
||||
|
||||
/* we're only interested in reset and transfers */
|
||||
USB0.inten.raw = ((struct USB_ISTAT_t){
|
||||
.tokdne = 1,
|
||||
.usbrst = 1,
|
||||
.stall = 1,
|
||||
.sleep = 1,
|
||||
}).raw;
|
||||
|
||||
USB0.usbtrc0.usbresmen = 0;
|
||||
USB0.usbctrl.susp = 0;
|
||||
}
|
||||
|
||||
void usb_enable(void)
|
||||
{
|
||||
SIM.sopt2.usbsrc = 1; /* usb from mcg */
|
||||
SIM.scgc4.usbotg = 1; /* enable usb clock */
|
||||
|
||||
/* reset module - not sure if needed */
|
||||
USB0.usbtrc0.raw = ((struct USB_USBTRC0_t){
|
||||
.usbreset = 1,
|
||||
.usbresmen = 1
|
||||
}).raw;
|
||||
while (USB0.usbtrc0.usbreset)
|
||||
/* NOTHING */;
|
||||
|
||||
USB0.bdtpage1 = (uintptr_t)bdt >> 8;
|
||||
USB0.bdtpage2 = (uintptr_t)bdt >> 16;
|
||||
USB0.bdtpage3 = (uintptr_t)bdt >> 24;
|
||||
|
||||
USB0.control.raw = ((struct USB_CONTROL_t){
|
||||
.dppullupnonotg = 1 /* enable pullup */
|
||||
}).raw;
|
||||
|
||||
USB0.usbctrl.raw = 0; /* resume peripheral & disable pulldowns */
|
||||
usb_reset(); /* this will start usb processing */
|
||||
|
||||
/* really only one thing we want */
|
||||
USB0.inten.raw = ((struct USB_ISTAT_t){
|
||||
.usbrst = 1,
|
||||
}).raw;
|
||||
|
||||
/**
|
||||
* Suspend transceiver now - we'll wake up at reset again.
|
||||
*/
|
||||
// TODO - Possible removal
|
||||
USB0.usbctrl.susp = 1;
|
||||
USB0.usbtrc0.usbresmen = 1;
|
||||
}
|
||||
|
||||
void USB0_Handler(void)
|
||||
{
|
||||
struct USB_ISTAT_t stat = {.raw = USB0.istat.raw };
|
||||
|
||||
if (stat.usbrst) {
|
||||
usb_reset();
|
||||
return;
|
||||
}
|
||||
if (stat.stall) {
|
||||
/* XXX need more work for non-0 ep */
|
||||
volatile struct USB_BD_t *bd = usb_get_bd(&usb.ep_state[0].rx);
|
||||
if (bd->stall)
|
||||
usb_setup_control();
|
||||
}
|
||||
if (stat.tokdne) {
|
||||
struct usb_xfer_info stat = USB0.stat;
|
||||
usb_handle_transaction(&stat);
|
||||
}
|
||||
if (stat.sleep) {
|
||||
USB0.inten.sleep = 0;
|
||||
USB0.inten.resume = 1;
|
||||
USB0.usbctrl.susp = 1;
|
||||
USB0.usbtrc0.usbresmen = 1;
|
||||
|
||||
/**
|
||||
* Clear interrupts now so that we can detect a fresh
|
||||
* resume later on.
|
||||
*/
|
||||
USB0.istat.raw = stat.raw;
|
||||
|
||||
const struct usbd_config *c = usb_get_config_data(-1);
|
||||
if (c && c->suspend)
|
||||
c->suspend();
|
||||
}
|
||||
/**
|
||||
* XXX it is unclear whether we will receive a synchronous
|
||||
* resume interrupt if we were in sleep. This code assumes we
|
||||
* do.
|
||||
*/
|
||||
if (stat.resume || USB0.usbtrc0.usb_resume_int) {
|
||||
USB0.inten.resume = 0;
|
||||
USB0.inten.sleep = 1;
|
||||
USB0.usbtrc0.usbresmen = 0;
|
||||
USB0.usbctrl.susp = 0;
|
||||
|
||||
const struct usbd_config *c = usb_get_config_data(-1);
|
||||
if (c && c->resume)
|
||||
c->resume();
|
||||
|
||||
stat.resume = 1; /* always clear bit */
|
||||
}
|
||||
USB0.istat.raw = stat.raw;
|
||||
}
|
||||
|
||||
void usb_poll(void)
|
||||
{
|
||||
USB0_Handler();
|
||||
}
|
||||
|
||||
int usb_tx_serialno(size_t reqlen)
|
||||
{
|
||||
struct usb_desc_string_t *d;
|
||||
const size_t nregs = 3;
|
||||
/**
|
||||
* actually 4, but UIDH is 0xffffffff. Also our output buffer
|
||||
* is only 64 bytes, and 128 bit + desc header exceeds this by
|
||||
* 2 bytes.
|
||||
*/
|
||||
const size_t len = nregs * 4 * 2 * 2 + sizeof(*d);
|
||||
|
||||
d = usb_ep0_tx_inplace_prepare(len);
|
||||
|
||||
if (d == NULL)
|
||||
return (-1);
|
||||
|
||||
d->bLength = len;
|
||||
d->bDescriptorType = USB_DESC_STRING;
|
||||
|
||||
size_t bufpos = 0;
|
||||
for (size_t reg = 0; reg < nregs; ++reg) {
|
||||
/* registers run MSW first */
|
||||
uint32_t val = (&SIM.uidmh)[reg];
|
||||
|
||||
for (size_t bits = 32; bits > 0; bits -= 4, val <<= 4) {
|
||||
int nibble = val >> 28;
|
||||
|
||||
if (nibble > 9)
|
||||
nibble += 'a' - '9' - 1;
|
||||
((char16_t *)d->bString)[bufpos++] = nibble + '0';
|
||||
}
|
||||
}
|
||||
usb_ep0_tx(d, len, reqlen, NULL, NULL);
|
||||
return (0);
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue