/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2008 by Vitja Makarov * * 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 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" #include "usb.h" #include "usb-tcc.h" #include "cpu.h" #include "system.h" #include "kernel.h" #include "panic.h" #ifdef HAVE_USBSTACK #include "usb_ch9.h" #include "usb_core.h" #define TCC7xx_USB_EPIF_IRQ_MASK 0xf static int dbg_level = 0x00; static int global_ep_irq_mask = 0x1; #define DEBUG(level, fmt, args...) do { if (dbg_level & (level)) printf(fmt, ## args); } while (0) #include #include "sprintf.h" #include "power.h" #ifndef BOOTLOADER #define printf(...) do {} while (0) #define panicf_my panicf #else int printf(const char *fmt, ...); #define panicf_my(fmt, args...) { \ int flags = disable_irq_save(); \ printf("*** PANIC ***"); \ printf(fmt, ## args); \ printf("*** PANIC ***"); \ while (usb_detect() == USB_INSERTED) \ ; \ power_off(); \ while(1); \ restore_irq(flags); \ } #endif struct tcc_ep { unsigned char dir; /* endpoint direction */ volatile uint16_t *ep; /* hw ep buffer */ int id; /* Endpoint id */ int mask; /* Endpoint bit mask */ char *buf; /* user buffer to store data */ int max_len; /* how match data will fit */ int count; /* actual data count */ bool busy; } ; static struct tcc_ep tcc_endpoints[] = { /* control */ { .dir = -1, .ep = &TCC7xx_USB_EP0_BUF, }, { /* bulk */ .dir = -1, .ep = &TCC7xx_USB_EP1_BUF, }, { /* bulk */ .dir = -1, .ep = &TCC7xx_USB_EP2_BUF, }, { /* interrupt */ .dir = -1, .ep = &TCC7xx_USB_EP3_BUF, }, } ; static bool usb_drv_write_ep(struct tcc_ep *ep); static void usb_set_speed(int); int usb_drv_request_endpoint(int type, int dir) { int flags = disable_irq_save(); size_t ep; int ret = 0; if (type != USB_ENDPOINT_XFER_BULK) return -1; if (dir == USB_DIR_IN) ep = 1; else ep = 2; if (!tcc_endpoints[ep].busy) { tcc_endpoints[ep].busy = true; tcc_endpoints[ep].dir = dir; ret = ep | dir; } else { ret = -1; } restore_irq(flags); return ret; } void usb_drv_release_endpoint(int ep) { int flags; ep = ep & 0x7f; if (ep < 1 || ep > USB_NUM_ENDPOINTS) return ; flags = disable_irq_save(); tcc_endpoints[ep].busy = false; tcc_endpoints[ep].dir = -1; restore_irq(flags); } static inline void pullup_on(void) { TCC7xx_USB_PHY_CFG = 0x000c; } static inline void pullup_off(void) { TCC7xx_USB_PHY_CFG = 0x3e4c; } #if 0 static char *dump_data(char *data, int count) { static char buf[1024]; char *dump = buf; int i; for (i = 0; i < count; i++) dump += snprintf(dump, sizeof(buf) - (dump - buf), "%02x", data[i]); return buf; } #endif static void handle_control(void) { /* control are always 8 bytes len */ static unsigned char ep_control[8]; struct usb_ctrlrequest *req = (struct usb_ctrlrequest *) ep_control; unsigned short stat; unsigned short count = 0; int i; int type; /* select control endpoint */ TCC7xx_USB_INDEX = 0x00; stat = TCC7xx_USB_EP0_STAT; if (stat & 0x10) { DEBUG(2, "stall"); TCC7xx_USB_EP0_STAT = 0x10; } if (TCC7xx_USB_EP0_STAT & 0x01) { /* RX */ uint16_t *ptr = (uint16_t *) ep_control; count = TCC7xx_USB_EP_BRCR; if (TCC7xx_USB_EP0_STAT & 0x2) TCC7xx_USB_EP0_STAT = 0x02; if (count != 4) { /* bad control? */ unsigned short dummy; while (count--) dummy = TCC7xx_USB_EP0_BUF; DEBUG(1, "WTF: count = %d", count); } else { /* simply read control packet */ for (i = 0; i < count; i++) ptr[i] = TCC7xx_USB_EP0_BUF; } count *= 2; TCC7xx_USB_EP0_STAT = 0x01; DEBUG(1, "CTRL: len = %d %04x", count, stat); } else if (TCC7xx_USB_EP0_STAT & 0x02) { /* TX */ TCC7xx_USB_EP0_STAT = 0x02; DEBUG(2, "TX Done\n"); } else { DEBUG(1, "stat: %04x", stat); } TCC7xx_USB_EPIF = 1; if (0 == (stat & 0x1) || count != 8) return ; #if 1 /* TODO: remove me someday */ { int i; uint16_t *ptr = (uint16_t *) ep_control; for (i = 1; i < (count>>1); i++) { if (ptr[i] != ptr[0]) break; } if (i == (count>>1)) { /*DEBUG(2, */panicf_my("sanity failed"); return ; } } #endif type = req->bRequestType; /* TODO: don't pass some kinds of requests to upper level */ switch (req->bRequest) { case USB_REQ_CLEAR_FEATURE: DEBUG(2, "USB_REQ_CLEAR_FEATURE"); DEBUG(2, "...%04x %04x", req->wValue, req->wIndex); break; case USB_REQ_SET_ADDRESS: //DEBUG(2, "USB_REQ_SET_ADDRESS, %d %d", req->wValue, TCC7xx_USB_FUNC); /* seems we don't have to set it manually TCC7xx_USB_FUNC = req->wValue; */ break; case USB_REQ_GET_DESCRIPTOR: DEBUG(2, "gd, %02x %02x", req->wValue, req->wIndex); break; case USB_REQ_GET_CONFIGURATION: DEBUG(2, "USB_REQ_GET_CONFIGURATION"); break; default: DEBUG(2, "req: %02x %02d", req->bRequestType, req->bRequest); } usb_core_control_request(req); } static void handle_ep_in(struct tcc_ep *tcc_ep, uint16_t stat) { uint8_t *buf = tcc_ep->buf; uint16_t *wbuf = (uint16_t *) buf; int wcount; int count; int i; if (tcc_ep->dir != USB_DIR_OUT) { panicf_my("ep%d: is input only", tcc_ep->id); } wcount = TCC7xx_USB_EP_BRCR; DEBUG(2, "ep%d: %04x %04x", tcc_ep->id, stat, wcount); /* read data */ count = wcount * 2; if (stat & TCC7xx_USP_EP_STAT_LWO) { count--; wcount--; } if (buf == NULL) panicf_my("ep%d: Unexpected packet! %d %x", tcc_ep->id, count, TCC7xx_USB_EP_CTRL); if (tcc_ep->max_len < count) panicf_my("Too big packet: %d excepted %d %x", count, tcc_ep->max_len, TCC7xx_USB_EP_CTRL); for (i = 0; i < wcount; i++) wbuf[i] = *tcc_ep->ep; if (count & 1) { /* lwo */ uint16_t tmp = *tcc_ep->ep; buf[count - 1] = tmp & 0xff; } tcc_ep->buf = NULL; TCC7xx_USB_EP_STAT = TCC7xx_USB_EP_STAT; TCC7xx_USB_EPIF = tcc_ep->mask; TCC7xx_USB_EPIE &= ~tcc_ep->mask; /* TODO: use INGLD? */ global_ep_irq_mask &= ~tcc_ep->mask; if (TCC7xx_USB_EP_STAT & 0x1) panicf_my("One more packet?"); TCC7xx_USB_EP_CTRL |= TCC7xx_USB_EP_CTRL_OUTHD; usb_core_transfer_complete(tcc_ep->id, USB_DIR_OUT, 0, count); } static void handle_ep_out(struct tcc_ep *tcc_ep, uint16_t stat) { bool done; (void) stat; if (tcc_ep->dir != USB_DIR_IN) { panicf_my("ep%d: is out only", tcc_ep->id); } // if (tcc_ep->buf == NULL) { // panicf_my("%s:%d", __FILE__, __LINE__); // } done = usb_drv_write_ep(tcc_ep); // TCC7xx_USB_EP_STAT = 0x2; /* Clear TX stat */ TCC7xx_USB_EPIF = tcc_ep->mask; if (done) { // tcc_ep->buf == NULL) { TCC7xx_USB_EPIE &= ~tcc_ep->mask; global_ep_irq_mask &= ~tcc_ep->mask; // usb_core_transfer_complete(tcc_ep->id, USB_DIR_IN, 0, tcc_ep->count); } } static void handle_ep(unsigned short ep_irq) { if (ep_irq & 0x1) { handle_control(); } if (ep_irq & 0xe) { int endpoint; for (endpoint = 1; endpoint < 4; endpoint++) { struct tcc_ep *tcc_ep = &tcc_endpoints[endpoint]; uint16_t stat; if (0 == (ep_irq & (1 << endpoint))) continue; if (!tcc_ep->busy) panicf_my("ep%d: wasn't requested", endpoint); TCC7xx_USB_INDEX = endpoint; stat = TCC7xx_USB_EP_STAT; DEBUG(1, "ep%d: %04x", endpoint, stat); if (stat & 0x1) handle_ep_in(tcc_ep, stat); else if (stat & 0x2) handle_ep_out(tcc_ep, stat); else /* TODO: remove me? */ panicf_my("Unhandled ep%d state: %x, %d", endpoint, TCC7xx_USB_EP_STAT, TCC7xx_USB_INDEX); } } } static void usb_set_speed(int high_speed) { TCC7xx_USB_EP_DIR = 0x0000; /* control endpoint */ TCC7xx_USB_INDEX = 0; TCC7xx_USB_EP0_CTRL = 0x0000; TCC7xx_USB_EP_MAXP = 64; TCC7xx_USB_EP_CTRL = TCC7xx_USB_EP_CTRL_CDP | TCC7xx_USB_EP_CTRL_FLUSH; /* ep1: bulk-in, to host */ TCC7xx_USB_INDEX = 1; TCC7xx_USB_EP_DIR |= (1 << 1); TCC7xx_USB_EP_CTRL = TCC7xx_USB_EP_CTRL_CDP; if (high_speed) TCC7xx_USB_EP_MAXP = 512; else TCC7xx_USB_EP_MAXP = 64; TCC7xx_USB_EP_DMA_CTRL = 0x0; /* ep2: bulk-out, from host */ TCC7xx_USB_INDEX = 2; TCC7xx_USB_EP_DIR &= ~(1 << 2); TCC7xx_USB_EP_CTRL = TCC7xx_USB_EP_CTRL_CDP; if (high_speed) TCC7xx_USB_EP_MAXP = 512; else TCC7xx_USB_EP_MAXP = 64; TCC7xx_USB_EP_DMA_CTRL = 0x0; /* ep3: interrupt in */ TCC7xx_USB_INDEX = 3; TCC7xx_USB_EP_DIR &= ~(1 << 3); TCC7xx_USB_EP_CTRL = TCC7xx_USB_EP_CTRL_CDP; TCC7xx_USB_EP_MAXP = 64; TCC7xx_USB_EP_DMA_CTRL = 0x0; } /* Reset TCC7xx usb device */ static void usb_reset(void) { pullup_on(); TCC7xx_USB_DELAY_CTRL |= 0x81; TCC7xx_USB_SYS_CTRL = 0xa000 | TCC7xx_USB_SYS_CTRL_RESET | TCC7xx_USB_SYS_CTRL_RFRE | TCC7xx_USB_SYS_CTRL_SPDEN | TCC7xx_USB_SYS_CTRL_VBONE | TCC7xx_USB_SYS_CTRL_VBOFE; usb_set_speed(1); pullup_on(); TCC7xx_USB_EPIF = TCC7xx_USB_EPIF_IRQ_MASK; global_ep_irq_mask = 0x1; TCC7xx_USB_EPIE = global_ep_irq_mask; usb_core_bus_reset(); } /* IRQ handler */ void USB_DEVICE(void) { unsigned short sys_stat; unsigned short ep_irq; unsigned short index_save; sys_stat = TCC7xx_USB_SYS_STAT; if (sys_stat & TCC7xx_USB_SYS_STAT_RESET) { TCC7xx_USB_SYS_STAT = TCC7xx_USB_SYS_STAT_RESET; usb_reset(); TCC7xx_USB_SYS_CTRL |= TCC7xx_USB_SYS_CTRL_SUSPEND; DEBUG(2, "reset"); } if (sys_stat & TCC7xx_USB_SYS_STAT_RESUME) { TCC7xx_USB_SYS_STAT = TCC7xx_USB_SYS_STAT_RESUME; usb_reset(); TCC7xx_USB_SYS_CTRL |= TCC7xx_USB_SYS_CTRL_SUSPEND; DEBUG(2, "resume"); } if (sys_stat & TCC7xx_USB_SYS_STAT_SPD_END) { usb_set_speed(1); TCC7xx_USB_SYS_STAT = TCC7xx_USB_SYS_STAT_SPD_END; DEBUG(2, "spd end"); } if (sys_stat & TCC7xx_USB_SYS_STAT_ERRORS) { DEBUG(2, "errors: %4x", sys_stat & TCC7xx_USB_SYS_STAT_ERRORS); TCC7xx_USB_SYS_STAT = sys_stat & TCC7xx_USB_SYS_STAT_ERRORS; } // TCC7xx_USB_SYS_STAT = sys_stat; index_save = TCC7xx_USB_INDEX; ep_irq = TCC7xx_USB_EPIF & global_ep_irq_mask; while (ep_irq & TCC7xx_USB_EPIF_IRQ_MASK) { handle_ep(ep_irq); /* is that really needed, btw not a problem for rockbox */ udelay(50); ep_irq = TCC7xx_USB_EPIF & global_ep_irq_mask; } TCC7xx_USB_INDEX = index_save; } void usb_drv_set_address(int address) { (void) address; DEBUG(2, "setting address %d %d", address, TCC7xx_USB_FUNC); } int usb_drv_port_speed(void) { return (TCC7xx_USB_SYS_STAT & 0x10) ? 1 : 0; } static int usb_drv_write_packet(volatile unsigned short *buf, unsigned char *data, int len, int max) { uint16_t *wbuf = (uint16_t *) data; int count, i; len = MIN(len, max); count = (len + 1) / 2; TCC7xx_USB_EP_BWCR = len; for (i = 0; i < count; i++) *buf = *wbuf++; return len; } static bool usb_drv_write_ep(struct tcc_ep *ep) { int count; if (ep->max_len == 0) return true; count = usb_drv_write_packet(ep->ep, ep->buf, ep->max_len, 512); TCC7xx_USB_EP_STAT = 0x2; /* Clear TX stat */ ep->buf += count; ep->count += count; ep->max_len -= count; if (ep->max_len == 0) { usb_core_transfer_complete(ep->id, USB_DIR_IN, 0, ep->count); ep->buf = NULL; // return true; } return false; } int usb_drv_send(int endpoint, void *ptr, int length) { int flags = disable_irq_save(); int rc = 0; char *data = (unsigned char*) ptr; DEBUG(2, "%s(%d,%d)" , __func__, endpoint, length); if (endpoint != 0) panicf_my("%s(%d,%d)", __func__, endpoint, length); TCC7xx_USB_INDEX = 0; while (length > 0) { int ret; ret = usb_drv_write_packet(&TCC7xx_USB_EP0_BUF, data, length, 64); length -= ret; data += ret; while (0 == (TCC7xx_USB_EP0_STAT & 0x2)) ; TCC7xx_USB_EP0_STAT = 0x2; } restore_irq(flags); return rc; } int usb_drv_send_nonblocking(int endpoint, void *ptr, int length) { int flags; int rc = 0, count = length; char *data = (unsigned char*) ptr; struct tcc_ep *ep = &tcc_endpoints[endpoint & 0x7f]; if (ep->dir != USB_DIR_IN || length == 0) panicf_my("%s(%d,%d): Not supported", __func__, endpoint, length); DEBUG(2, "%s(%d,%d):", __func__, endpoint, length); flags = disable_irq_save(); if(ep->buf != NULL) { panicf_my("%s: ep is already busy", __func__); } ep->buf = data; ep->max_len = length; ep->count = count; TCC7xx_USB_INDEX = ep->id; #if 1 TCC7xx_USB_EP_STAT = 0x2; /* TODO: use interrupts instead */ while (!usb_drv_write_ep(ep)) { while (0==(TCC7xx_USB_EP_STAT & 0x2)) ; } #else if (!usb_drv_write_ep(ep)) { TCC7xx_USB_EPIE |= ep->mask; global_ep_irq_mask |= ep->mask; } #endif restore_irq(flags); DEBUG(2, "%s end", __func__); return rc; } int usb_drv_recv(int endpoint, void* ptr, int length) { volatile struct tcc_ep *tcc_ep = &tcc_endpoints[endpoint & 0x7f]; int flags; if (length == 0) { if (endpoint != 0) panicf_my("%s(%d,%d) zero length?", __func__, endpoint, length); return 0; } // TODO: check ep if (tcc_ep->dir != USB_DIR_OUT) panicf_my("%s(%d,%d)", __func__, endpoint, length); DEBUG(2, "%s(%d,%d)", __func__, endpoint, length); flags = disable_irq_save(); if (tcc_ep->buf) { panicf_my("%s: overrun: %x %x", __func__, (unsigned int)tcc_ep->buf, (unsigned int)tcc_ep); } tcc_ep->buf = ptr; tcc_ep->max_len = length; tcc_ep->count = 0; TCC7xx_USB_INDEX = tcc_ep->id; TCC7xx_USB_EP_CTRL &= ~TCC7xx_USB_EP_CTRL_OUTHD; TCC7xx_USB_EPIE |= tcc_ep->mask; global_ep_irq_mask |= tcc_ep->mask; restore_irq(flags); return 0; } void usb_drv_cancel_all_transfers(void) { int endpoint; int flags; DEBUG(2, "%s", __func__); flags = disable_irq_save(); for (endpoint = 0; endpoint < 4; endpoint++) { if (tcc_endpoints[endpoint].buf) { /* usb_core_transfer_complete(tcc_endpoints[endpoint].id, tcc_endpoints[endpoint].dir, -1, 0); */ tcc_endpoints[endpoint].buf = NULL; } } global_ep_irq_mask = 1; TCC7xx_USB_EPIE = global_ep_irq_mask; TCC7xx_USB_EPIF = TCC7xx_USB_EPIF_IRQ_MASK; restore_irq(flags); } void usb_drv_set_test_mode(int mode) { panicf_my("%s(%d)", __func__, mode); } bool usb_drv_stalled(int endpoint, bool in) { panicf_my("%s(%d,%d)", __func__, endpoint, in); return false; } void usb_drv_stall(int endpoint, bool stall,bool in) { (void) endpoint; (void) stall; (void) in; printf("%s(%d,%d,%d)", __func__, endpoint, stall, in); } void usb_drv_init(void) { size_t i; DEBUG(2, "%s", __func__); for (i = 0; i < sizeof(tcc_endpoints)/sizeof(struct tcc_ep); i++) { tcc_endpoints[i].id = i; tcc_endpoints[i].mask = 1 << i; tcc_endpoints[i].buf = NULL; tcc_endpoints[i].busy = false; tcc_endpoints[i].dir = -1; } /* Enable USB clock */ BCLKCTR |= DEV_USBD; /* switch USB to host and then reset */ TCC7xx_USB_PHY_CFG = 0x3e4c; SWRESET |= DEV_USBD; udelay(50); SWRESET &= ~DEV_USBD; usb_reset(); /* unmask irq */ CREQ = USBD_IRQ_MASK; IRQSEL |= USBD_IRQ_MASK; TMODE &= ~USBD_IRQ_MASK; IEN |= USBD_IRQ_MASK; } void usb_drv_exit(void) { TCC7xx_USB_EPIE = 0; BCLKCTR &= ~DEV_USBD; SWRESET |= DEV_USBD; udelay(50); SWRESET &= ~DEV_USBD; pullup_off(); } void usb_init_device(void) { } void usb_enable(bool on) { if (on) usb_core_init(); else usb_core_exit(); } void usb_attach(void) { usb_enable(true); } int usb_detect(void) { /* TODO: not correct for all targets, we should poll VBUS signal on USB bus. */ if (charger_inserted()) return USB_INSERTED; return USB_EXTRACTED; } #ifdef BOOTLOADER #include "ata.h" void usb_test(void) { int rc; printf("ATA"); rc = ata_init(); if(rc) { panicf("ata_init failed"); } usb_init(); usb_start_monitoring(); usb_acknowledge(SYS_USB_CONNECTED_ACK); while (1) { sleep(HZ); // usb_serial_send("Hello\r\n", 7); } } #endif #else void usb_init_device(void) { /* simply switch USB off for now */ BCLKCTR |= DEV_USBD; TCC7xx_USB_PHY_CFG = 0x3e4c; BCLKCTR &= ~DEV_USBD; } void usb_enable(bool on) { (void)on; } /* Always return false for now */ int usb_detect(void) { return USB_EXTRACTED; } #endif