/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id: $ * * Copyright (C) 2021 by Tomasz Moń * Ported from Sansa Connect TNETV105 UDC Linux driver * Copyright (c) 2005,2006 Zermatt Systems, Inc. * Written by: Ben Bostwick * Linux driver was modeled strongly after the pxa usb driver. * * 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 "system.h" #include "kernel.h" #include "panic.h" #include "logf.h" #include "usb.h" #include "usb_drv.h" #include "usb_core.h" #include #include "tnetv105_usb_drv.h" #include "tnetv105_cppi.h" #ifdef SANSA_CONNECT #define SDRAM_SIZE 0x04000000 static void set_tnetv_reset(bool high) { if (high) { IO_GIO_BITSET0 = (1 << 7); } else { IO_GIO_BITCLR0 = (1 << 7); } } static bool is_tnetv_reset_high(void) { return (IO_GIO_BITSET0 & (1 << 7)) ? true : false; } #endif static bool setup_is_set_address; static cppi_info cppi; static struct ep_runtime_t { int max_packet_size; bool in_allocated; bool out_allocated; uint8_t *rx_buf; /* OUT */ int rx_remaining; int rx_size; uint8_t *tx_buf; /* IN */ int tx_remaining; int tx_size; volatile bool block; /* flag indicating that transfer is blocking */ struct semaphore complete; /* semaphore for blocking transfers */ } ep_runtime[USB_NUM_ENDPOINTS]; static const struct { int type; int hs_max_packet_size; /* Not sure what xyoff[1] is for. Presumably it is double buffer, but how * the double buffering works is not so clear from the Sansa Connect Linux * kernel patch. As TNETV105 datasheet is not available, the values are * simply taken from the Linux patch as potential constraints are unknown. * * Linux kernel has 9 endpoints: * * 0: ep0 * * 1: ep1in-bulk * * 2: ep2out-bulk * * 3: ep3in-int * * 4: ep4in-int * * 5: ep1out-bulk * * 6: ep2in-bulk * * 7: ep3out-int * * 8: ep4out-int */ uint16_t xyoff_in[2]; uint16_t xyoff_out[2]; } ep_const_data[USB_NUM_ENDPOINTS] = { { .type = USB_ENDPOINT_XFER_CONTROL, .hs_max_packet_size = EP0_MAX_PACKET_SIZE, /* Do not set xyoff as it likely does not apply here. * Linux simply hardcodes the offsets when needed. */ }, { .type = USB_ENDPOINT_XFER_BULK, .hs_max_packet_size = EP1_MAX_PACKET_SIZE, .xyoff_in = {EP1_XBUFFER_ADDRESS, EP1_YBUFFER_ADDRESS}, .xyoff_out = {EP5_XBUFFER_ADDRESS, EP5_YBUFFER_ADDRESS}, }, { .type = USB_ENDPOINT_XFER_BULK, .hs_max_packet_size = EP2_MAX_PACKET_SIZE, .xyoff_in = {EP6_XBUFFER_ADDRESS, EP6_YBUFFER_ADDRESS}, .xyoff_out = {EP2_XBUFFER_ADDRESS, EP2_YBUFFER_ADDRESS}, }, { .type = USB_ENDPOINT_XFER_INT, .hs_max_packet_size = EP3_MAX_PACKET_SIZE, .xyoff_in = {EP3_XBUFFER_ADDRESS, EP3_YBUFFER_ADDRESS}, .xyoff_out = {EP7_XBUFFER_ADDRESS, EP7_YBUFFER_ADDRESS}, }, { .type = USB_ENDPOINT_XFER_INT, .hs_max_packet_size = EP4_MAX_PACKET_SIZE, .xyoff_in = {EP4_XBUFFER_ADDRESS, EP4_YBUFFER_ADDRESS}, .xyoff_out = {EP8_XBUFFER_ADDRESS, EP8_YBUFFER_ADDRESS}, }, }; #define VLYNQ_CTL_RESET_MASK 0x0001 #define VLYNQ_CTL_CLKDIR_MASK 0x8000 #define VLYNQ_STS_LINK_MASK 0x0001 #define DM320_VLYNQ_CTRL_RESET (1 << 0) #define DM320_VLYNQ_CTRL_LOOP (1 << 1) #define DM320_VLYNQ_CTRL_ADR_OPT (1 << 2) #define DM320_VLYNQ_CTRL_INT_CFG (1 << 7) #define DM320_VLYNQ_CTRL_INT_VEC_MASK (0x00001F00) #define DM320_VLYNQ_CTRL_INT_EN (1 << 13) #define DM320_VLYNQ_CTRL_INT_LOC (1 << 14) #define DM320_VLYNQ_CTRL_CLKDIR (1 << 15) #define DM320_VLYNQ_CTRL_CLKDIV_MASK (0x00070000) #define DM320_VLYNQ_CTRL_PWR_MAN (1 << 31) #define DM320_VLYNQ_STAT_LINK (1 << 0) #define DM320_VLYNQ_STAT_MST_PEND (1 << 1) #define DM320_VLYNQ_STAT_SLV_PEND (1 << 2) #define DM320_VLYNQ_STAT_F0_NE (1 << 3) #define DM320_VLYNQ_STAT_F1_NE (1 << 4) #define DM320_VLYNQ_STAT_F2_NE (1 << 5) #define DM320_VLYNQ_STAT_F3_NE (1 << 6) #define DM320_VLYNQ_STAT_LOC_ERR (1 << 7) #define DM320_VLYNQ_STAT_REM_ERR (1 << 8) #define DM320_VLYNQ_STAT_FC_OUT (1 << 9) #define DM320_VLYNQ_STAT_FC_IN (1 << 10) #define MAX_PACKET(epn, speed) ((((speed) == USB_SPEED_HIGH) && (((epn) == 1) || ((epn) == 2))) ? USB_HIGH_SPEED_MAXPACKET : USB_FULL_SPEED_MAXPACKET) #define VLYNQ_INTR_USB20 (1 << 0) #define VLYNQ_INTR_CPPI (1 << 1) static inline void set_vlynq_clock(bool enable) { if (enable) { IO_CLK_MOD2 |= (1 << 13); } else { IO_CLK_MOD2 &= ~(1 << 13); } } static inline void set_vlynq_irq(bool enabled) { if (enabled) { /* Enable VLYNQ interrupt */ IO_INTC_EINT1 |= (1 << 0); } else { IO_INTC_EINT1 &= ~(1 << 0); } } static int tnetv_hw_reset(void) { int timeout; /* hold down the reset pin on the USB chip */ set_tnetv_reset(false); /* Turn on VLYNQ clock. */ set_vlynq_clock(true); /* now reset the VLYNQ module */ VL_CTRL |= (VLYNQ_CTL_CLKDIR_MASK | DM320_VLYNQ_CTRL_PWR_MAN); VL_CTRL |= VLYNQ_CTL_RESET_MASK; mdelay(10); /* pull up the reset pin */ set_tnetv_reset(true); /* take the VLYNQ out of reset */ VL_CTRL &= ~VLYNQ_CTL_RESET_MASK; timeout = 0; while (!(VL_STAT & VLYNQ_STS_LINK_MASK) && timeout++ < 50); { mdelay(40); } if (!(VL_STAT & VLYNQ_STS_LINK_MASK)) { logf("ERROR: VLYNQ not initialized!\n"); return -1; } /* set up vlynq local map */ VL_TXMAP = DM320_VLYNQ_PADDR; VL_RXMAPOF1 = CONFIG_SDRAM_START; VL_RXMAPSZ1 = SDRAM_SIZE; /* set up vlynq remote map for tnetv105 */ VL_TXMAP_R = 0x00000000; VL_RXMAPOF1_R = 0x0C000000; VL_RXMAPSZ1_R = 0x00030000; /* clear TNETV gpio state */ tnetv_usb_reg_write(TNETV_V2USB_GPIO_FS, 0); /* set USB_CHARGE_EN pin (gpio 1) - output, disable pullup */ tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, 0); tnetv_usb_reg_write(TNETV_V2USB_GPIO_DIR, 0xffff); return 0; } static int tnetv_xcvr_on(void) { return tnetv_hw_reset(); } static void tnetv_xcvr_off(void) { /* turn off vlynq module clock */ set_vlynq_clock(false); /* hold down the reset pin on the USB chip */ set_tnetv_reset(false); } /* Copy data from the usb data memory. The memory reads should be done 32 bits at a time. * We do not assume that the dst data is aligned. */ static void tnetv_copy_from_data_mem(void *dst, const volatile uint32_t *sp, int size) { uint8_t *dp = (uint8_t *) dst; uint32_t value; while (size >= 4) { value = *sp++; dp[0] = value; dp[1] = value >> 8; dp[2] = value >> 16; dp[3] = value >> 24; dp += 4; size -= 4; } if (size) { value = sp[0]; switch (size) { case 3: dp[2] = value >> 16; case 2: dp[1] = value >> 8; case 1: dp[0] = value; break; } } } /* Copy data into the usb data memory. The memory writes must be done 32 bits at a time. * We do not assume that the src data is aligned. */ static void tnetv_copy_to_data_mem(volatile uint32_t *dp, const void *src, int size) { const uint8_t *sp = (const uint8_t *) src; uint32_t value; while (size >= 4) { value = sp[0] | (sp[1] << 8) | (sp[2] << 16) | (sp[3] << 24); *dp++ = value; sp += 4; size -= 4; } switch (size) { case 3: value = sp[0] | (sp[1] << 8) | (sp[2] << 16); *dp = value; break; case 2: value = sp[0] | (sp[1] << 8); *dp = value; break; case 1: value = sp[0]; *dp = value; break; } } static void tnetv_init_endpoints(void) { UsbEp0CtrlType ep0Cfg; UsbEp0ByteCntType ep0Cnt; UsbEpCfgCtrlType epCfg; UsbEpStartAddrType epStartAddr; int ch, wd, epn; ep0Cnt.val = 0; ep0Cnt.f.out_ybuf_nak = 1; ep0Cnt.f.out_xbuf_nak = 1; ep0Cnt.f.in_ybuf_nak = 1; ep0Cnt.f.in_xbuf_nak = 1; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); /* Setup endpoint zero */ ep0Cfg.val = 0; ep0Cfg.f.buf_size = EP0_BUF_SIZE_64; /* must be 64 bytes for USB 2.0 */ ep0Cfg.f.dbl_buf = 0; ep0Cfg.f.in_en = 1; ep0Cfg.f.in_int_en = 1; ep0Cfg.f.out_en = 1; ep0Cfg.f.out_int_en = 1; tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Cfg.val); /* disable cell dma */ tnetv_usb_reg_write(TNETV_USB_CELL_DMA_EN, 0); /* turn off dma engines */ tnetv_usb_reg_write(TNETV_USB_TX_CTL, 0); tnetv_usb_reg_write(TNETV_USB_RX_CTL, 0); /* clear out DMA registers */ for (ch = 0; ch < TNETV_DMA_NUM_CHANNELS; ch++) { for (wd = 0; wd < TNETV_DMA_TX_NUM_WORDS; wd++) { tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, wd), 0); } for (wd = 0; wd < TNETV_DMA_RX_NUM_WORDS; wd++) { tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, wd), 0); } /* flush the free buf count */ while (tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)) != 0) { tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), 0xFFFF); } } for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++) { tnetv_usb_reg_write(TNETV_USB_EPx_ADR(epn),0); tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), 0); tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); } /* Setup the other endpoints */ for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++) { epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn)); epStartAddr.val = tnetv_usb_reg_read(TNETV_USB_EPx_ADR(epn)); /* Linux kernel enables dbl buf for both IN and OUT. * For IN this is problematic when tnetv_cppi_send() is called * to send single ZLP, it will actually send two ZLPs. * Disable the dbl buf here as datasheet is not available and * this results in working mass storage on Windows 10. */ epCfg.f.in_dbl_buf = 0; epCfg.f.in_toggle_rst = 1; epCfg.f.in_ack_int = 0; epCfg.f.in_stall = 0; epCfg.f.in_nak_int = 0; epCfg.f.out_dbl_buf = 0; epCfg.f.out_toggle_rst = 1; epCfg.f.out_ack_int = 0; epCfg.f.out_stall = 0; epCfg.f.out_nak_int = 0; /* buf_size is specified "in increments of 8 bytes" */ epCfg.f.in_buf_size = ep_const_data[epn].hs_max_packet_size >> 3; epCfg.f.out_buf_size = ep_const_data[epn].hs_max_packet_size >> 3; epStartAddr.f.xBuffStartAddrIn = ep_const_data[epn].xyoff_in[0] >> 4; epStartAddr.f.yBuffStartAddrIn = ep_const_data[epn].xyoff_in[1] >> 4; epStartAddr.f.xBuffStartAddrOut = ep_const_data[epn].xyoff_out[0] >> 4; epStartAddr.f.yBuffStartAddrOut = ep_const_data[epn].xyoff_out[1] >> 4; /* allocate memory for DMA */ tnetv_cppi_init_rcb(&cppi, (epn - 1)); /* set up DMA queue */ tnetv_cppi_init_tcb(&cppi, (epn - 1)); /* now write out the config to the TNETV (write enable bits last) */ tnetv_usb_reg_write(TNETV_USB_EPx_ADR(epn), epStartAddr.val); tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val); tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); } /* turn on dma engines */ tnetv_usb_reg_write(TNETV_USB_TX_CTL, 1); tnetv_usb_reg_write(TNETV_USB_RX_CTL, 1); /* enable cell dma */ tnetv_usb_reg_write(TNETV_USB_CELL_DMA_EN, (TNETV_USB_CELL_DMA_EN_RX | TNETV_USB_CELL_DMA_EN_TX)); } static void tnetv_udc_enable_interrupts(void) { UsbCtrlType usb_ctl; uint8_t tx_int_en, rx_int_en; int ep, chan; /* set up the system interrupts */ usb_ctl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usb_ctl.f.vbus_int_en = 1; usb_ctl.f.reset_int_en = 1; usb_ctl.f.suspend_int_en = 1; usb_ctl.f.resume_int_en = 1; usb_ctl.f.ep0_in_int_en = 1; usb_ctl.f.ep0_out_int_en = 1; usb_ctl.f.setup_int_en = 1; usb_ctl.f.setupow_int_en = 1; tnetv_usb_reg_write(TNETV_USB_CTRL, usb_ctl.val); /* Enable the DMA endpoint interrupts */ tx_int_en = 0; rx_int_en = 0; for (ep = 1; ep < USB_NUM_ENDPOINTS; ep++) { chan = ep - 1; rx_int_en |= (1 << chan); /* OUT */ tx_int_en |= (1 << chan); /* IN */ } /* enable rx interrupts */ tnetv_usb_reg_write(TNETV_USB_RX_INT_EN, rx_int_en); /* enable tx interrupts */ tnetv_usb_reg_write(TNETV_USB_TX_INT_EN, tx_int_en); set_vlynq_irq(true); } static void tnetv_udc_disable_interrupts(void) { UsbCtrlType usb_ctl; /* disable interrupts from linux */ set_vlynq_irq(false); /* Disable Endpoint Interrupts */ tnetv_usb_reg_write(TNETV_USB_RX_INT_DIS, 0x3); tnetv_usb_reg_write(TNETV_USB_TX_INT_DIS, 0x3); /* Disable USB system interrupts */ usb_ctl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usb_ctl.f.vbus_int_en = 0; usb_ctl.f.reset_int_en = 0; usb_ctl.f.suspend_int_en = 0; usb_ctl.f.resume_int_en = 0; usb_ctl.f.ep0_in_int_en = 0; usb_ctl.f.ep0_out_int_en = 0; usb_ctl.f.setup_int_en = 0; usb_ctl.f.setupow_int_en = 0; tnetv_usb_reg_write(TNETV_USB_CTRL, usb_ctl.val); } static void tnetv_ep_halt(int epn, bool in) { if (in) { tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); } else { tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); } } /* Reset the TNETV usb2.0 controller and configure it to run in function mode */ static void tnetv_usb_reset(void) { uint32_t timeout = 0; int wd; int ch; /* configure function clock */ tnetv_usb_reg_write(TNETV_V2USB_CLK_CFG, 0x80); /* Reset the USB 2.0 function module */ tnetv_usb_reg_write(TNETV_V2USB_RESET, 0x01); /* now poll the module ready register until the 2.0 controller finishes resetting */ while (!(tnetv_usb_reg_read(TNETV_USB_RESET_CMPL) & 0x1) && (timeout < 1000000)) { timeout++; } if (!(tnetv_usb_reg_read(TNETV_USB_RESET_CMPL) & 0x1)) { logf("tnetv105_udc: VLYNQ USB module reset failed!\n"); return; } /* turn off external clock */ tnetv_usb_reg_write(TNETV_V2USB_CLK_PERF, 0); /* clear out USB data memory */ for (wd = 0; wd < TNETV_EP_DATA_SIZE; wd += 4) { tnetv_usb_reg_write(TNETV_EP_DATA_ADDR(wd), 0); } /* clear out DMA memory */ for (ch = 0; ch < TNETV_DMA_NUM_CHANNELS; ch++) { for (wd = 0; wd < TNETV_DMA_TX_NUM_WORDS; wd++) { tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, wd), 0); } for (wd = 0; wd < TNETV_DMA_RX_NUM_WORDS; wd++) { tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, wd), 0); } } /* point VLYNQ interrupts at the pending register */ VL_INTPTR = DM320_VLYNQ_INTPND_PHY; /* point VLYNQ remote interrupts at the pending register */ VL_INTPTR_R = 0; /* clear out interrupt register */ VL_INTST |= 0xFFFFFFFF; /* enable interrupts on remote device */ VL_CTRL_R |= (DM320_VLYNQ_CTRL_INT_EN); VL_INTVEC30_R = 0x8180; /* enable VLYNQ interrupts & set interrupts to trigger VLYNQ int */ VL_CTRL |= (DM320_VLYNQ_CTRL_INT_LOC | DM320_VLYNQ_CTRL_INT_CFG); } static int tnetv_ep_start_xmit(int epn, void *buf, int size) { UsbEp0ByteCntType ep0Cnt; if (epn == 0) { /* Write the Control Data packet to the EP0 IN memory area */ tnetv_copy_to_data_mem(TNETV_EP_DATA_ADDR(EP0_INPKT_ADDRESS), buf, size); /* start xmitting */ ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT); ep0Cnt.f.in_xbuf_cnt = size; ep0Cnt.f.in_xbuf_nak = 0; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); } else { dma_addr_t buffer = (dma_addr_t)buf; int send_zlp = 0; if (size == 0) { /* Any address in SDRAM will do, contents do not matter */ buffer = CONFIG_SDRAM_START; size = 1; send_zlp = 1; } else { commit_discard_dcache_range(buf, size); } if ((buffer >= CONFIG_SDRAM_START) && (buffer + size < CONFIG_SDRAM_START + SDRAM_SIZE)) { if (tnetv_cppi_send(&cppi, (epn - 1), buffer, size, send_zlp)) { panicf("tnetv_cppi_send() failed"); } } else { panicf("USB xmit buf outside SDRAM %p", buf); } } return 0; } static void tnetv_gadget_req_nuke(int epn, bool in) { struct ep_runtime_t *ep = &ep_runtime[epn]; uint32_t old_rx_int = 0; uint32_t old_tx_int = 0; int ch; int flags; /* don't nuke control ep */ if (epn == 0) { return; } flags = disable_irq_save(); /* save and disable interrupts before nuking request */ old_rx_int = tnetv_usb_reg_read(TNETV_USB_RX_INT_EN); old_tx_int = tnetv_usb_reg_read(TNETV_USB_TX_INT_EN); tnetv_usb_reg_write(TNETV_USB_RX_INT_DIS, 0x3); tnetv_usb_reg_write(TNETV_USB_TX_INT_DIS, 0x3); ch = epn - 1; if (in) { tnetv_cppi_flush_tx_queue(&cppi, ch); tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); if (ep->tx_remaining > 0) { usb_core_transfer_complete(epn, USB_DIR_IN, -1, 0); } ep->tx_buf = NULL; ep->tx_remaining = 0; ep->tx_size = 0; if (ep->block) { semaphore_release(&ep->complete); ep->block = false; } } else { tnetv_cppi_flush_rx_queue(&cppi, ch); tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); if (ep->rx_remaining > 0) { usb_core_transfer_complete(epn, USB_DIR_OUT, -1, 0); } ep->rx_buf = NULL; ep->rx_remaining = 0; ep->rx_size = 0; } /* reenable any interrupts */ tnetv_usb_reg_write(TNETV_USB_RX_INT_EN, old_rx_int); tnetv_usb_reg_write(TNETV_USB_TX_INT_EN, old_tx_int); restore_irq(flags); } static int tnetv_gadget_ep_enable(int epn, bool in) { UsbEpCfgCtrlType epCfg; int flags; enum usb_device_speed speed; if (epn == 0 || epn >= USB_NUM_ENDPOINTS) { return 0; } flags = disable_irq_save(); /* set the maxpacket for this endpoint based on the current speed */ speed = usb_drv_port_speed() ? USB_SPEED_HIGH : USB_SPEED_FULL; ep_runtime[epn].max_packet_size = MAX_PACKET(epn, speed); /* Enable the endpoint */ epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn)); if (in) { epCfg.f.in_en = 1; epCfg.f.in_stall = 0; epCfg.f.in_toggle_rst = 1; epCfg.f.in_buf_size = ep_runtime[epn].max_packet_size >> 3; tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); } else { epCfg.f.out_en = 1; epCfg.f.out_stall = 0; epCfg.f.out_toggle_rst = 1; epCfg.f.out_buf_size = ep_runtime[epn].max_packet_size >> 3; tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); } tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val); restore_irq(flags); return 0; } static int tnetv_gadget_ep_disable(int epn, bool in) { UsbEpCfgCtrlType epCfg; int flags; if (epn == 0 || epn >= USB_NUM_ENDPOINTS) { return 0; } flags = disable_irq_save(); /* Disable the endpoint */ epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn)); if (in) { epCfg.f.in_en = 0; } else { epCfg.f.out_en = 0; } tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val); /* Turn off the endpoint and unready it */ tnetv_ep_halt(epn, in); restore_irq(flags); /* Clear out all the pending requests */ tnetv_gadget_req_nuke(epn, in); return 0; } /* TNETV udc goo * Power up and enable the udc. This includes resetting the hardware, turn on the appropriate clocks * and initializing things so that the first setup packet can be received. */ static void tnetv_udc_enable(void) { /* Enable M48XI crystal resonator */ IO_CLK_LPCTL1 &= ~(0x01); /* Set GIO33 as CLKOUT1B */ IO_GIO_FSEL3 |= 0x0003; if (tnetv_xcvr_on()) return; tnetv_usb_reset(); /* BEN - RNDIS mode is assuming zlps after packets that are multiples of buffer endpoints * zlps are not required by the spec and many controllers don't send them. * set DMA to RNDIS mode (packet concatenation, less interrupts) * tnetv_usb_reg_write(TNETV_USB_RNDIS_MODE, 0xFF); */ tnetv_usb_reg_write(TNETV_USB_RNDIS_MODE, 0); tnetv_init_endpoints(); tnetv_udc_enable_interrupts(); } static void tnetv_udc_disable(void) { tnetv_udc_disable_interrupts(); tnetv_hw_reset(); tnetv_xcvr_off(); /* Set GIO33 as normal output, drive it low */ IO_GIO_FSEL3 &= ~(0x0003); IO_GIO_BITCLR2 = (1 << 1); /* Disable M48XI crystal resonator */ IO_CLK_LPCTL1 |= 0x01; } static void tnetv_udc_handle_reset(void) { UsbCtrlType usbCtrl; /* disable USB interrupts */ tnetv_udc_disable_interrupts(); usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usbCtrl.f.func_addr = 0; tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); /* Reset endpoints */ tnetv_init_endpoints(); /* Re-enable interrupts */ tnetv_udc_enable_interrupts(); } static void ep_write(int epn) { struct ep_runtime_t *ep = &ep_runtime[epn]; int tx_size; if (epn == 0) { tx_size = MIN(ep->max_packet_size, ep->tx_remaining); } else { /* DMA takes care of splitting the buffer into packets, * but only up to CPPI_MAX_FRAG. After the data is sent * a single interrupt is generated. There appears to be * splitting code in the tnetv_cppi_send() function but * it is somewhat suspicious (it doesn't seem like it * will work with requests larger than 2*CPPI_MAX_FRAG). * Also, if tnetv_cppi_send() does the splitting, we will * get an interrupt after CPPI_MAX_FRAG but before the * full request is sent. * * CPPI_MAX_FRAG is multiple of both 64 and 512 so we * don't have to worry about this split prematurely ending * the transfer. */ tx_size = MIN(CPPI_MAX_FRAG, ep->tx_remaining); } tnetv_ep_start_xmit(epn, ep->tx_buf, tx_size); ep->tx_remaining -= tx_size; ep->tx_buf += tx_size; } static void in_interrupt(int epn) { struct ep_runtime_t *ep = &ep_runtime[epn]; if (ep->tx_remaining <= 0) { usb_core_transfer_complete(epn, USB_DIR_IN, 0, ep->tx_size); /* release semaphore for blocking transfer */ if (ep->block) { semaphore_release(&ep->complete); ep->tx_buf = NULL; ep->tx_size = 0; ep->tx_remaining = 0; ep->block = false; } } else if (ep->tx_buf) { ep_write(epn); } } static void ep_read(int epn) { if (epn == 0) { UsbEp0ByteCntType ep0Cnt; ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT); ep0Cnt.f.out_xbuf_nak = 0; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); } else { struct ep_runtime_t *ep = &ep_runtime[epn]; tnetv_cppi_rx_queue_add(&cppi, (epn - 1), 0, ep->rx_remaining); } } static void out_interrupt(int epn) { struct ep_runtime_t *ep = &ep_runtime[epn]; int is_short; int rcv_len; if (epn == 0) { UsbEp0ByteCntType ep0Cnt; /* get the length of the received data */ ep0Cnt.val = tnetv_usb_reg_read(TNETV_USB_EP0_CNT); rcv_len = ep0Cnt.f.out_xbuf_cnt; if (rcv_len > ep->rx_remaining) { rcv_len = ep->rx_remaining; } tnetv_copy_from_data_mem(ep->rx_buf, TNETV_EP_DATA_ADDR(EP0_OUTPKT_ADDRESS), rcv_len); ep->rx_buf += rcv_len; ep->rx_remaining -= rcv_len; /* See if we are done */ is_short = rcv_len && (rcv_len < ep->max_packet_size); if (is_short || (ep->rx_remaining == 0)) { usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); ep->rx_remaining = 0; ep->rx_size = 0; ep->rx_buf = 0; return; } /* make sure nak is cleared only if we expect more data */ ep0Cnt.f.out_xbuf_nak = 0; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); ep_read(epn); } else if (ep->rx_remaining > 0) { int ret, bytes_rcvd; /* copy the data from the DMA buffers */ bytes_rcvd = ep->rx_remaining; ret = tnetv_cppi_rx_int_recv(&cppi, (epn - 1), &bytes_rcvd, ep->rx_buf, ep->max_packet_size); if (ret == 0 || ret == -EAGAIN) { ep->rx_buf += bytes_rcvd; ep->rx_remaining -= bytes_rcvd; } /* complete the request if we got a short packet or an error * make sure we don't complete a request with zero bytes. */ if ((ret == 0) && (ep->rx_remaining != ep->rx_size)) { usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); ep->rx_remaining = 0; ep->rx_size = 0; ep->rx_buf = 0; } } } static bool tnetv_handle_cppi(void) { int ret; int ch; uint32_t tx_intstatus; uint32_t rx_intstatus; uint32_t status; int rcv_sched = 0; rx_intstatus = tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS); tx_intstatus = tnetv_usb_reg_read(TNETV_USB_TX_INT_STATUS); /* handle any transmit interrupts */ status = tx_intstatus; for (ch = 0; ch < CPPI_NUM_CHANNELS && status; ch++) { if (status & 0x1) { ret = tnetv_cppi_tx_int(&cppi, ch); if (ret >= 0) { in_interrupt(ch + 1); } } status = status >> 1; } rcv_sched = 0; status = rx_intstatus; for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) { if (status & 0x1 || tnetv_cppi_rx_int_recv_check(&cppi, ch)) { ret = tnetv_cppi_rx_int(&cppi, ch); if (ret < 0) { /* only an error if interrupt bit is set */ logf("CPPI Rx: failed to ACK int!\n"); } else { if (tnetv_cppi_rx_int_recv_check(&cppi, ch)) { out_interrupt(ch + 1); } } } if (tnetv_cppi_rx_int_recv_check(&cppi, ch)) { rcv_sched = 1; } status = status >> 1; } rx_intstatus = tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS); tx_intstatus = tnetv_usb_reg_read(TNETV_USB_TX_INT_STATUS); if (rx_intstatus || tx_intstatus || rcv_sched) { /* Request calling again after short delay * Needed when for example when OUT endpoint has pending data * but the USB task did not call usb_drv_recv_nonblocking() yet. */ return true; } return false; } static int cppi_timeout_cb(struct timeout *tmo) { (void)tmo; int flags = disable_irq_save(); bool requeue = tnetv_handle_cppi(); restore_irq(flags); return requeue ? 1 : 0; } void VLYNQ(void) __attribute__ ((section(".icode"))); void VLYNQ(void) { UsbStatusType sysIntrStatus; UsbStatusType sysIntClear; UsbCtrlType usbCtrl; volatile uint32_t *reg; uint32_t vlynq_intr; /* Clear interrupt */ IO_INTC_IRQ1 = (1 << 0); /* clear out VLYNQ interrupt register */ vlynq_intr = VL_INTST; if (vlynq_intr & VLYNQ_INTR_USB20) { VL_INTST = VLYNQ_INTR_USB20; /* Examine system interrupt status */ sysIntrStatus.val = tnetv_usb_reg_read(TNETV_USB_STATUS); if (sysIntrStatus.f.reset) { sysIntClear.val = 0; sysIntClear.f.reset = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); tnetv_udc_handle_reset(); usb_core_bus_reset(); } if (sysIntrStatus.f.suspend) { sysIntClear.val = 0; sysIntClear.f.suspend = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); } if (sysIntrStatus.f.resume) { sysIntClear.val = 0; sysIntClear.f.resume = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); } if (sysIntrStatus.f.vbus) { sysIntClear.val = 0; sysIntClear.f.vbus = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); if (*((uint32_t *) TNETV_USB_IF_STATUS) & 0x40) { /* write out connect bit */ reg = (volatile uint32_t *) TNETV_USB_CTRL; *reg |= 0x80; /* write to wakeup bit in clock config */ reg = (volatile uint32_t *) TNETV_V2USB_CLK_WKUP; *reg |= TNETV_V2USB_CLK_WKUP_VBUS; } else { /* clear out connect bit */ reg = (volatile uint32_t *) TNETV_USB_CTRL; *reg &= ~0x80; } } if (sysIntrStatus.f.setup_ow) { sysIntrStatus.f.setup_ow = 0; sysIntClear.val = 0; sysIntClear.f.setup_ow = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); } if (sysIntrStatus.f.setup) { UsbEp0ByteCntType ep0Cnt; static struct usb_ctrlrequest setup; sysIntrStatus.f.setup = 0; /* Copy setup packet into buffer */ tnetv_copy_from_data_mem(&setup, TNETV_EP_DATA_ADDR(EP0_OUTPKT_ADDRESS), sizeof(setup)); /* Determine next stage of the control message */ if (setup.bRequestType & USB_DIR_IN) { /* This is a control-read. Switch directions to send the response. * set the dir bit before clearing the interrupt */ usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usbCtrl.f.dir = 1; tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); } else { /* This is a control-write. Remain using USB_DIR_OUT to receive the rest of the data. * set the NAK bits according to supplement doc */ ep0Cnt.val = 0; ep0Cnt.f.in_xbuf_nak = 1; ep0Cnt.f.out_xbuf_nak = 1; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); /* clear the dir bit before clearing the interrupt */ usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usbCtrl.f.dir = 0; tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); } /* Clear interrupt */ sysIntClear.val = 0; sysIntClear.f.setup = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); if (((setup.bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) && (setup.bRequest == USB_REQ_SET_ADDRESS)) { /* Rockbox USB core works according to USB specification, i.e. * it first acknowledges the control transfer and then sets * the address. However, Linux TNETV105 driver first sets the * address and then acknowledges the transfer. At first, * it seemed that Linux driver was wrong, but it seems that * TNETV105 simply requires such order. It might be documented * in the datasheet and thus there is no comment in the Linux * driver about this. */ setup_is_set_address = true; } else { setup_is_set_address = false; } /* Process control packet */ usb_core_legacy_control_request(&setup); } if (sysIntrStatus.f.ep0_in_ack) { sysIntClear.val = 0; sysIntClear.f.ep0_in_ack = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); in_interrupt(0); } if (sysIntrStatus.f.ep0_out_ack) { sysIntClear.val = 0; sysIntClear.f.ep0_out_ack = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); out_interrupt(0); } } if (vlynq_intr & VLYNQ_INTR_CPPI) { static struct timeout cppi_timeout; VL_INTST = VLYNQ_INTR_CPPI; if (tnetv_handle_cppi()) { timeout_register(&cppi_timeout, cppi_timeout_cb, 1, 0); } } } void usb_charging_maxcurrent_change(int maxcurrent) { uint32_t wreg; if (!is_tnetv_reset_high()) { /* TNETV105 is in reset, it is not getting more than 100 mA */ return; } wreg = tnetv_usb_reg_read(TNETV_V2USB_GPIO_DOUT); if (maxcurrent < 500) { /* set tnetv into low power mode */ tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, (wreg & ~0x2)); } else { /* set tnetv into high power mode */ tnetv_usb_reg_write(TNETV_V2USB_GPIO_DOUT, (wreg | 0x2)); } } void usb_drv_init(void) { int epn; memset(ep_runtime, 0, sizeof(ep_runtime)); ep_runtime[0].max_packet_size = EP0_MAX_PACKET_SIZE; ep_runtime[0].in_allocated = true; ep_runtime[0].out_allocated = true; for (epn = 0; epn < USB_NUM_ENDPOINTS; epn++) { semaphore_init(&ep_runtime[epn].complete, 1, 0); } tnetv_cppi_init(&cppi); tnetv_udc_enable(); } void usb_drv_exit(void) { tnetv_udc_disable(); tnetv_cppi_cleanup(&cppi); } void usb_drv_stall(int endpoint, bool stall, bool in) { int epn = EP_NUM(endpoint); if (epn == 0) { UsbEp0CtrlType ep0Ctrl; ep0Ctrl.val = tnetv_usb_reg_read(TNETV_USB_EP0_CFG); if (in) { ep0Ctrl.f.in_stall = stall ? 1 : 0; } else { ep0Ctrl.f.out_stall = stall ? 1 : 0; } tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Ctrl.val); } else { UsbEpCfgCtrlType epCfg; epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn)); if (in) { epCfg.f.in_stall = stall ? 1 : 0; } else { epCfg.f.out_stall = stall ? 1 : 0; } tnetv_usb_reg_write(TNETV_USB_EPx_CFG(epn), epCfg.val); } } bool usb_drv_stalled(int endpoint, bool in) { int epn = EP_NUM(endpoint); if (epn == 0) { UsbEp0CtrlType ep0Ctrl; ep0Ctrl.val = tnetv_usb_reg_read(TNETV_USB_EP0_CFG); if (in) { return ep0Ctrl.f.in_stall; } else { return ep0Ctrl.f.out_stall; } } else { UsbEpCfgCtrlType epCfg; epCfg.val = tnetv_usb_reg_read(TNETV_USB_EPx_CFG(epn)); if (in) { return epCfg.f.in_stall; } else { return epCfg.f.out_stall; } } } static int _usb_drv_send(int endpoint, void *ptr, int length, bool block) { int epn = EP_NUM(endpoint); struct ep_runtime_t *ep; int flags; ep = &ep_runtime[epn]; flags = disable_irq_save(); ep->tx_buf = ptr; ep->tx_remaining = ep->tx_size = length; ep->block = block; ep_write(epn); restore_irq(flags); /* wait for transfer to end */ if (block) { semaphore_wait(&ep->complete, TIMEOUT_BLOCK); } return 0; } int usb_drv_send(int endpoint, void* ptr, int length) { if ((EP_NUM(endpoint) == 0) && (length == 0)) { if (setup_is_set_address) { /* usb_drv_set_address() will call us later */ return 0; } /* HACK: Do not wait for status stage ZLP * This seems to be the only way to get through SET ADDRESS * and retain ability to receive SETUP packets. */ return _usb_drv_send(endpoint, ptr, length, false); } return _usb_drv_send(endpoint, ptr, length, false); } int usb_drv_send_nonblocking(int endpoint, void* ptr, int length) { return _usb_drv_send(endpoint, ptr, length, false); } int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length) { int epn = EP_NUM(endpoint); struct ep_runtime_t *ep; int flags; ep = &ep_runtime[epn]; flags = disable_irq_save(); ep->rx_buf = ptr; ep->rx_remaining = ep->rx_size = length; ep_read(epn); restore_irq(flags); return 0; } void usb_drv_set_address(int address) { UsbCtrlType usbCtrl; usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usbCtrl.f.func_addr = address; tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); /* This seems to be the only working order */ setup_is_set_address = false; usb_drv_send(EP_CONTROL, NULL, 0); usb_drv_cancel_all_transfers(); } /* return port speed FS=0, HS=1 */ int usb_drv_port_speed(void) { UsbCtrlType usbCtrl; usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); return usbCtrl.f.speed ? 1 : 0; } void usb_drv_cancel_all_transfers(void) { int epn; if (setup_is_set_address) { return; } for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++) { tnetv_gadget_req_nuke(epn, false); tnetv_gadget_req_nuke(epn, true); } } static const uint8_t TestPacket[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E }; void usb_drv_set_test_mode(int mode) { UsbCtrlType usbCtrl; if (mode == 4) { volatile uint32_t *reg; UsbEp0ByteCntType ep0Cnt; UsbEp0CtrlType ep0Cfg; uint8_t *addr; size_t i; /* set up the xnak for ep0 */ reg = (volatile uint32_t *) TNETV_USB_EP0_CNT; *reg &= ~0xFF; /* Setup endpoint zero */ ep0Cfg.val = 0; ep0Cfg.f.buf_size = EP0_BUF_SIZE_64; /* must be 64 bytes for USB 2.0 */ ep0Cfg.f.dbl_buf = 0; ep0Cfg.f.in_en = 1; ep0Cfg.f.in_int_en = 0; ep0Cfg.f.out_en = 0; ep0Cfg.f.out_int_en = 0; tnetv_usb_reg_write(TNETV_USB_EP0_CFG, ep0Cfg.val); addr = (uint8_t *) TNETV_EP_DATA_ADDR(EP0_INPKT_ADDRESS); for (i = 0; i < sizeof(TestPacket); i++) { *addr++ = TestPacket[i]; } /* start xmitting (only 53 bytes are used) */ ep0Cnt.val = 0; ep0Cnt.f.in_xbuf_cnt = 53; ep0Cnt.f.in_xbuf_nak = 0; tnetv_usb_reg_write(TNETV_USB_EP0_CNT, ep0Cnt.val); } /* write the config */ usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); usbCtrl.f.hs_test_mode = mode; usbCtrl.f.dir = 1; tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); } int usb_drv_request_endpoint(int type, int dir) { int epn; for (epn = 1; epn < USB_NUM_ENDPOINTS; epn++) { if (type == ep_const_data[epn].type) { if ((dir == USB_DIR_IN) && (!ep_runtime[epn].in_allocated)) { ep_runtime[epn].in_allocated = true; tnetv_gadget_ep_enable(epn, true); return epn | USB_DIR_IN; } if ((dir == USB_DIR_OUT) && (!ep_runtime[epn].out_allocated)) { ep_runtime[epn].out_allocated = true; tnetv_gadget_ep_enable(epn, false); return epn | USB_DIR_OUT; } } } return -1; } void usb_drv_release_endpoint(int ep) { int epn = EP_NUM(ep); if (EP_DIR(ep) == DIR_IN) { ep_runtime[epn].in_allocated = false; tnetv_gadget_ep_disable(epn, true); } else { ep_runtime[epn].out_allocated = false; tnetv_gadget_ep_disable(epn, false); } }