| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/debug.h> |
| |
| #include "dwc2.h" |
| |
| #define CLEAR_IN_EP_INTR(__epnum, __intr) \ |
| do { \ |
| dwc_diepint_t diepint = {0}; \ |
| diepint.__intr = 1; \ |
| regs->depin[__epnum].diepint = diepint; \ |
| } while (0) |
| |
| #define CLEAR_OUT_EP_INTR(__epnum, __intr) \ |
| do { \ |
| dwc_doepint_t doepint = {0}; \ |
| doepint.__intr = 1; \ |
| regs->depout[__epnum].doepint = doepint; \ |
| } while (0) |
| |
| static void dwc_ep_read_packet(dwc_regs_t* regs, void* buffer, uint32_t length, uint32_t ep_num) { |
| uint32_t count = (length + 3) >> 2; |
| uint32_t* dest = (uint32_t*)buffer; |
| volatile uint32_t* fifo = DWC_REG_DATA_FIFO(regs, ep_num); |
| |
| for (uint32_t i = 0; i < count; i++) { |
| *dest++ = *fifo; |
| zxlogf(LSPEW, "read %08x\n", dest[-1]); |
| } |
| } |
| |
| static void dwc_set_address(dwc_usb_t* dwc, uint8_t address) { |
| dwc_regs_t* regs = dwc->regs; |
| zxlogf(LINFO, "dwc_set_address %u\n", address); |
| regs->dcfg.devaddr = address; |
| } |
| |
| static void dwc2_ep0_out_start(dwc_usb_t* dwc) { |
| // zxlogf(LINFO, "dwc2_ep0_out_start\n"); |
| |
| dwc_regs_t* regs = dwc->regs; |
| |
| dwc_deptsiz0_t doeptsize0 = {0}; |
| dwc_depctl_t doepctl = {0}; |
| |
| doeptsize0.supcnt = 3; |
| doeptsize0.pktcnt = 1; |
| doeptsize0.xfersize = 8 * 3; |
| regs->depout[0].doeptsiz.val = doeptsize0.val; |
| |
| //?? dwc->ep0_state = EP0_STATE_IDLE; |
| |
| doepctl.epena = 1; |
| regs->depout[0].doepctl = doepctl; |
| } |
| |
| static void do_setup_status_phase(dwc_usb_t* dwc, bool is_in) { |
| //zxlogf(LINFO, "do_setup_status_phase is_in: %d\n", is_in); |
| // dwc_endpoint_t* ep = &dwc->eps[0]; |
| |
| dwc->ep0_state = EP0_STATE_STATUS; |
| |
| dwc_ep_start_transfer(dwc, 0, is_in, 0); |
| |
| /* Prepare for more SETUP Packets */ |
| dwc2_ep0_out_start(dwc); |
| } |
| |
| static void dwc_ep0_complete_request(dwc_usb_t* dwc) { |
| dwc_endpoint_t* ep = &dwc->eps[0]; |
| |
| if (dwc->ep0_state == EP0_STATE_STATUS) { |
| //zxlogf(LINFO, "dwc_ep0_complete_request EP0_STATE_STATUS\n"); |
| ep->req_offset = 0; |
| ep->req_length = 0; |
| // this interferes with zero length OUT |
| // } else if ( ep->req_length == 0) { |
| //zxlogf(LINFO, "dwc_ep0_complete_request ep->req_length == 0\n"); |
| // dwc_otg_ep_start_transfer(ep); |
| } else if (dwc->ep0_state == EP0_STATE_DATA_IN) { |
| //zxlogf(LINFO, "dwc_ep0_complete_request EP0_STATE_DATA_IN\n"); |
| if (ep->req_offset >= ep->req_length) { |
| do_setup_status_phase(dwc, false); |
| } |
| } else { |
| //zxlogf(LINFO, "dwc_ep0_complete_request ep0-OUT\n"); |
| do_setup_status_phase(dwc, true); |
| } |
| |
| #if 0 |
| deptsiz0_data_t deptsiz; |
| dwc_ep_t* ep = &pcd->dwc_eps[0].dwc_ep; |
| int ret = 0; |
| |
| if (EP0_STATUS == pcd->ep0state) { |
| ep->start_xfer_buff = 0; |
| ep->xfer_buff = 0; |
| ep->xfer_len = 0; |
| ep->num = 0; |
| ret = 1; |
| } else if (0 == ep->xfer_len) { |
| ep->xfer_len = 0; |
| ep->xfer_count = 0; |
| ep->sent_zlp = 1; |
| ep->num = 0; |
| dwc_otg_ep_start_transfer(ep); |
| ret = 1; |
| } else if (ep->is_in) { |
| deptsiz.d32 = dwc_read_reg32(DWC_REG_IN_EP_TSIZE(0)); |
| if (0 == deptsiz.b.xfersize) { |
| /* Is a Zero Len Packet needed? */ |
| do_setup_status_phase(pcd, 0); |
| } |
| } else { |
| /* ep0-OUT */ |
| do_setup_status_phase(pcd, 1); |
| } |
| |
| #endif |
| } |
| |
| static zx_status_t dwc_handle_setup(dwc_usb_t* dwc, usb_setup_t* setup, void* buffer, size_t length, |
| size_t* out_actual) { |
| //zxlogf(LINFO, "dwc_handle_setup\n"); |
| zx_status_t status; |
| dwc_endpoint_t* ep = &dwc->eps[0]; |
| |
| if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) { |
| // handle some special setup requests in this driver |
| switch (setup->bRequest) { |
| case USB_REQ_SET_ADDRESS: |
| zxlogf(INFO, "SET_ADDRESS %d\n", setup->wValue); |
| dwc_set_address(dwc, setup->wValue); |
| *out_actual = 0; |
| return ZX_OK; |
| case USB_REQ_SET_CONFIGURATION: |
| zxlogf(INFO, "SET_CONFIGURATION %d\n", setup->wValue); |
| dwc_reset_configuration(dwc); |
| dwc->configured = true; |
| status = usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual); |
| if (status == ZX_OK && setup->wValue) { |
| dwc_start_eps(dwc); |
| } else { |
| dwc->configured = false; |
| } |
| return status; |
| default: |
| // fall through to usb_dci_control() |
| break; |
| } |
| } else if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) && |
| setup->bRequest == USB_REQ_SET_INTERFACE) { |
| zxlogf(INFO, "SET_INTERFACE %d\n", setup->wValue); |
| dwc_reset_configuration(dwc); |
| dwc->configured = true; |
| status = usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual); |
| if (status == ZX_OK) { |
| dwc_start_eps(dwc); |
| } else { |
| dwc->configured = false; |
| } |
| return status; |
| } |
| |
| status = usb_dci_control(&dwc->dci_intf, setup, buffer, length, out_actual); |
| if (status == ZX_OK) { |
| ep->req_offset = 0; |
| ep->req_length = *out_actual; |
| } |
| return status; |
| } |
| |
| static void pcd_setup(dwc_usb_t* dwc) { |
| usb_setup_t* setup = &dwc->cur_setup; |
| |
| if (!dwc->got_setup) { |
| //zxlogf(LINFO, "no setup\n"); |
| return; |
| } |
| dwc->got_setup = false; |
| // _pcd->status = 0; |
| |
| |
| if (setup->bmRequestType & USB_DIR_IN) { |
| //zxlogf(LINFO, "pcd_setup set EP0_STATE_DATA_IN\n"); |
| dwc->ep0_state = EP0_STATE_DATA_IN; |
| } else { |
| //zxlogf(LINFO, "pcd_setup set EP0_STATE_DATA_OUT\n"); |
| dwc->ep0_state = EP0_STATE_DATA_OUT; |
| } |
| |
| if (setup->wLength > 0 && dwc->ep0_state == EP0_STATE_DATA_OUT) { |
| //zxlogf(LINFO, "queue read\n"); |
| // queue a read for the data phase |
| dwc->ep0_state = EP0_STATE_DATA_OUT; |
| dwc_ep_start_transfer(dwc, 0, false, setup->wLength); |
| } else { |
| size_t actual; |
| __UNUSED zx_status_t status = dwc_handle_setup(dwc, setup, dwc->ep0_buffer, |
| sizeof(dwc->ep0_buffer), &actual); |
| //zxlogf(INFO, "dwc_handle_setup returned %d actual %zu\n", status, actual); |
| // if (status != ZX_OK) { |
| // dwc3_cmd_ep_set_stall(dwc, EP0_OUT); |
| // dwc3_queue_setup_locked(dwc); |
| // break; |
| // } |
| |
| if (dwc->ep0_state == EP0_STATE_DATA_IN && setup->wLength > 0) { |
| // zxlogf(LINFO, "queue a write for the data phase\n"); |
| dwc->ep0_state = EP0_STATE_DATA_IN; |
| dwc_ep_start_transfer(dwc, 0, true, actual); |
| } else { |
| dwc_ep0_complete_request(dwc); |
| } |
| } |
| } |
| |
| |
| static void dwc_handle_ep0(dwc_usb_t* dwc) { |
| // zxlogf(LINFO, "dwc_handle_ep0\n"); |
| |
| switch (dwc->ep0_state) { |
| case EP0_STATE_IDLE: { |
| //zxlogf(LINFO, "dwc_handle_ep0 EP0_STATE_IDLE\n"); |
| // req_flag->request_config = 0; |
| pcd_setup(dwc); |
| break; |
| } |
| case EP0_STATE_DATA_IN: |
| // zxlogf(LINFO, "dwc_handle_ep0 EP0_STATE_DATA_IN\n"); |
| // if (ep0->xfer_count < ep0->total_len) |
| // zxlogf(LINFO, "FIX ME!! dwc_otg_ep0_continue_transfer!\n"); |
| // else |
| dwc_ep0_complete_request(dwc); |
| break; |
| case EP0_STATE_DATA_OUT: |
| // zxlogf(LINFO, "dwc_handle_ep0 EP0_STATE_DATA_OUT\n"); |
| dwc_ep0_complete_request(dwc); |
| break; |
| case EP0_STATE_STATUS: |
| // zxlogf(LINFO, "dwc_handle_ep0 EP0_STATE_STATUS\n"); |
| dwc_ep0_complete_request(dwc); |
| /* OUT for next SETUP */ |
| dwc->ep0_state = EP0_STATE_IDLE; |
| // ep0->stopped = 1; |
| // ep0->is_in = 0; |
| break; |
| |
| case EP0_STATE_STALL: |
| default: |
| zxlogf(LINFO, "EP0 state is %d, should not get here pcd_setup()\n", dwc->ep0_state); |
| break; |
| } |
| } |
| |
| void dwc_flush_fifo(dwc_usb_t* dwc, const int num) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| dwc_grstctl_t grstctl = {0}; |
| |
| grstctl.txfflsh = 1; |
| grstctl.txfnum = num; |
| regs->grstctl = grstctl; |
| |
| uint32_t count = 0; |
| do { |
| grstctl = regs->grstctl; |
| if (++count > 10000) |
| break; |
| } while (grstctl.txfflsh == 1); |
| |
| zx_nanosleep(zx_deadline_after(ZX_USEC(1))); |
| |
| if (num == 0) { |
| return; |
| } |
| |
| grstctl.val = 0; |
| grstctl.rxfflsh = 1; |
| regs->grstctl = grstctl; |
| |
| count = 0; |
| do { |
| grstctl = regs->grstctl; |
| if (++count > 10000) |
| break; |
| } while (grstctl.rxfflsh == 1); |
| |
| zx_nanosleep(zx_deadline_after(ZX_USEC(1))); |
| } |
| |
| static void dwc_handle_reset_irq(dwc_usb_t* dwc) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| zxlogf(LINFO, "\nUSB RESET\n"); |
| |
| dwc->ep0_state = EP0_STATE_DISCONNECTED; |
| |
| /* Clear the Remote Wakeup Signalling */ |
| regs->dctl.rmtwkupsig = 1; |
| |
| for (int i = 0; i < MAX_EPS_CHANNELS; i++) { |
| dwc_depctl_t diepctl = regs->depin[i].diepctl; |
| |
| if (diepctl.epena) { |
| // disable all active IN EPs |
| diepctl.snak = 1; |
| diepctl.epdis = 1; |
| regs->depin[i].diepctl = diepctl; |
| } |
| |
| regs->depout[i].doepctl.snak = 1; |
| } |
| |
| /* Flush the NP Tx FIFO */ |
| dwc_flush_fifo(dwc, 0); |
| |
| /* Flush the Learning Queue */ |
| regs->grstctl.intknqflsh = 1; |
| |
| // EPO IN and OUT |
| regs->daintmsk = (1 < DWC_EP_IN_SHIFT) | (1 < DWC_EP_OUT_SHIFT); |
| |
| dwc_doepint_t doepmsk = {0}; |
| doepmsk.setup = 1; |
| doepmsk.xfercompl = 1; |
| doepmsk.ahberr = 1; |
| doepmsk.epdisabled = 1; |
| regs->doepmsk = doepmsk; |
| |
| dwc_diepint_t diepmsk = {0}; |
| diepmsk.xfercompl = 1; |
| diepmsk.timeout = 1; |
| diepmsk.epdisabled = 1; |
| diepmsk.ahberr = 1; |
| regs->diepmsk = diepmsk; |
| |
| /* Reset Device Address */ |
| regs->dcfg.devaddr = 0; |
| |
| /* setup EP0 to receive SETUP packets */ |
| dwc2_ep0_out_start(dwc); |
| |
| // TODO how to detect disconnect? |
| usb_dci_set_connected(&dwc->dci_intf, true); |
| } |
| |
| static void dwc_handle_enumdone_irq(dwc_usb_t* dwc) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| zxlogf(INFO, "dwc_handle_enumdone_irq\n"); |
| |
| |
| if (dwc->astro_usb.ops) { |
| astro_usb_do_usb_tuning(&dwc->astro_usb, false, false); |
| } |
| |
| dwc->ep0_state = EP0_STATE_IDLE; |
| |
| dwc->eps[0].max_packet_size = 64; |
| |
| regs->depin[0].diepctl.mps = DWC_DEP0CTL_MPS_64; |
| regs->depout[0].doepctl.epena = 1; |
| |
| #if 0 // astro future use |
| depctl.d32 = dwc_read_reg32(DWC_REG_IN_EP_REG(1)); |
| if (!depctl.b.usbactep) { |
| depctl.b.mps = BULK_EP_MPS; |
| depctl.b.eptype = 2;//BULK_STYLE |
| depctl.b.setd0pid = 1; |
| depctl.b.txfnum = 0; //Non-Periodic TxFIFO |
| depctl.b.usbactep = 1; |
| dwc_write_reg32(DWC_REG_IN_EP_REG(1), depctl.d32); |
| } |
| |
| depctl.d32 = dwc_read_reg32(DWC_REG_OUT_EP_REG(2)); |
| if (!depctl.b.usbactep) { |
| depctl.b.mps = BULK_EP_MPS; |
| depctl.b.eptype = 2;//BULK_STYLE |
| depctl.b.setd0pid = 1; |
| depctl.b.txfnum = 0; //Non-Periodic TxFIFO |
| depctl.b.usbactep = 1; |
| dwc_write_reg32(DWC_REG_OUT_EP_REG(2), depctl.d32); |
| } |
| #endif |
| |
| regs->dctl.cgnpinnak = 1; |
| |
| /* high speed */ |
| #if 1 // astro |
| regs->gusbcfg.usbtrdtim = 9; |
| #else |
| regs->gusbcfg.usbtrdtim = 5; |
| #endif |
| |
| usb_dci_set_speed(&dwc->dci_intf, USB_SPEED_HIGH); |
| } |
| |
| static void dwc_handle_rxstsqlvl_irq(dwc_usb_t* dwc) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| //why? regs->gintmsk.rxstsqlvl = 0; |
| |
| /* Get the Status from the top of the FIFO */ |
| dwc_grxstsp_t grxstsp = regs->grxstsp; |
| zxlogf(LINFO, "dwc_handle_rxstsqlvl_irq epnum: %u bcnt: %u pktsts: %u\n", grxstsp.epnum, grxstsp.bcnt, grxstsp.pktsts); |
| |
| int ep_num = grxstsp.epnum; |
| if (ep_num > 0) { |
| ep_num += 16; |
| } |
| dwc_endpoint_t* ep = &dwc->eps[ep_num]; |
| |
| switch (grxstsp.pktsts) { |
| case DWC_STS_DATA_UPDT: { |
| uint32_t fifo_count = grxstsp.bcnt; |
| zxlogf(LINFO, "DWC_STS_DATA_UPDT grxstsp.bcnt: %u\n", grxstsp.bcnt); |
| if (fifo_count > ep->req_length - ep->req_offset) { |
| zxlogf(LINFO, "fifo_count %u > %u\n", fifo_count, ep->req_length - ep->req_offset); |
| fifo_count = ep->req_length - ep->req_offset; |
| } |
| if (fifo_count > 0) { |
| dwc_ep_read_packet(regs, ep->req_buffer + ep->req_offset, fifo_count, ep_num); |
| ep->req_offset += fifo_count; |
| } |
| break; |
| } |
| |
| case DWC_DSTS_SETUP_UPDT: { |
| //zxlogf(LINFO, "DWC_DSTS_SETUP_UPDT\n"); |
| volatile uint32_t* fifo = (uint32_t *)((uint8_t *)regs + 0x1000); |
| uint32_t* dest = (uint32_t*)&dwc->cur_setup; |
| dest[0] = *fifo; |
| dest[1] = *fifo; |
| zxlogf(LINFO, "SETUP bmRequestType: 0x%02x bRequest: %u wValue: %u wIndex: %u wLength: %u\n", |
| dwc->cur_setup.bmRequestType, dwc->cur_setup.bRequest, dwc->cur_setup.wValue, |
| dwc->cur_setup.wIndex, dwc->cur_setup.wLength); |
| dwc->got_setup = true; |
| break; |
| } |
| |
| case DWC_DSTS_GOUT_NAK: |
| zxlogf(LINFO, "DWC_DSTS_GOUT_NAK\n"); |
| break; |
| case DWC_STS_XFER_COMP: |
| //zxlogf(LINFO, "DWC_STS_XFER_COMP\n"); |
| break; |
| case DWC_DSTS_SETUP_COMP: |
| //zxlogf(LINFO, "DWC_DSTS_SETUP_COMP\n"); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void dwc_handle_inepintr_irq(dwc_usb_t* dwc) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| printf("dwc_handle_inepintr_irq\n"); |
| for (uint32_t ep_num = 0; ep_num < MAX_EPS_CHANNELS; ep_num++) { |
| uint32_t bit = 1 << ep_num; |
| uint32_t daint = regs->daint; |
| if ((daint & bit) == 0) { |
| continue; |
| } |
| regs->daint |= bit; |
| |
| dwc_diepint_t diepint = regs->depin[ep_num].diepint; |
| |
| /* Transfer complete */ |
| if (diepint.xfercompl) { |
| if (ep_num > 0) zxlogf(LINFO, "dwc_handle_inepintr_irq xfercompl ep_num %u\n", ep_num); |
| CLEAR_IN_EP_INTR(ep_num, xfercompl); |
| // regs->depin[ep_num].diepint.xfercompl = 1; |
| /* Complete the transfer */ |
| if (0 == ep_num) { |
| dwc_handle_ep0(dwc); |
| } else { |
| dwc_complete_ep(dwc, ep_num); |
| if (diepint.nak) { |
| printf("diepint.nak ep_num %u\n", ep_num); |
| CLEAR_IN_EP_INTR(ep_num, nak); |
| } |
| } |
| } |
| /* Endpoint disable */ |
| if (diepint.epdisabled) { |
| /* Clear the bit in DIEPINTn for this interrupt */ |
| CLEAR_IN_EP_INTR(ep_num, epdisabled); |
| } |
| /* AHB Error */ |
| if (diepint.ahberr) { |
| /* Clear the bit in DIEPINTn for this interrupt */ |
| CLEAR_IN_EP_INTR(ep_num, ahberr); |
| } |
| /* TimeOUT Handshake (non-ISOC IN EPs) */ |
| if (diepint.timeout) { |
| // handle_in_ep_timeout_intr(ep_num); |
| zxlogf(LINFO, "TODO handle_in_ep_timeout_intr\n"); |
| CLEAR_IN_EP_INTR(ep_num, timeout); |
| } |
| /** IN Token received with TxF Empty */ |
| if (diepint.intktxfemp) { |
| CLEAR_IN_EP_INTR(ep_num, intktxfemp); |
| } |
| /** IN Token Received with EP mismatch */ |
| if (diepint.intknepmis) { |
| CLEAR_IN_EP_INTR(ep_num, intknepmis); |
| } |
| /** IN Endpoint NAK Effective */ |
| if (diepint.inepnakeff) { |
| printf("diepint.inepnakeff ep_num %u\n", ep_num); |
| CLEAR_IN_EP_INTR(ep_num, inepnakeff); |
| } |
| } |
| } |
| |
| static void dwc_handle_outepintr_irq(dwc_usb_t* dwc) { |
| dwc_regs_t* regs = dwc->regs; |
| |
| //zxlogf(LINFO, "dwc_handle_outepintr_irq\n"); |
| |
| uint32_t epnum = 0; |
| |
| /* Read in the device interrupt bits */ |
| uint32_t ep_intr = regs->daint & DWC_EP_OUT_MASK; |
| ep_intr >>= DWC_EP_OUT_SHIFT; |
| |
| /* Clear the interrupt */ |
| regs->daint = DWC_EP_OUT_MASK; |
| |
| while (ep_intr) { |
| if (ep_intr & 1) { |
| dwc_doepint_t doepint = regs->depout[epnum].doepint; |
| doepint.val &= regs->doepmsk.val; |
| if (epnum > 0) zxlogf(LINFO, "dwc_handle_outepintr_irq doepint.val %08x\n", doepint.val); |
| |
| /* Transfer complete */ |
| if (doepint.xfercompl) { |
| if (epnum > 0) zxlogf(LINFO, "dwc_handle_outepintr_irq xfercompl\n"); |
| /* Clear the bit in DOEPINTn for this interrupt */ |
| CLEAR_OUT_EP_INTR(epnum, xfercompl); |
| |
| if (epnum == 0) { |
| if (doepint.setup) { // astro |
| CLEAR_OUT_EP_INTR(epnum, setup); |
| } |
| dwc_handle_ep0(dwc); |
| } else { |
| dwc_complete_ep(dwc, epnum); |
| } |
| } |
| /* Endpoint disable */ |
| if (doepint.epdisabled) { |
| zxlogf(LINFO, "dwc_handle_outepintr_irq epdisabled\n"); |
| /* Clear the bit in DOEPINTn for this interrupt */ |
| CLEAR_OUT_EP_INTR(epnum, epdisabled); |
| } |
| /* AHB Error */ |
| if (doepint.ahberr) { |
| zxlogf(LINFO, "dwc_handle_outepintr_irq ahberr\n"); |
| CLEAR_OUT_EP_INTR(epnum, ahberr); |
| } |
| /* Setup Phase Done (contr0l EPs) */ |
| if (doepint.setup) { |
| if (1) { // astro |
| dwc_handle_ep0(dwc); |
| } |
| CLEAR_OUT_EP_INTR(epnum, setup); |
| } |
| } |
| epnum++; |
| ep_intr >>= 1; |
| } |
| } |
| |
| static void dwc_handle_nptxfempty_irq(dwc_usb_t* dwc) { |
| bool need_more = false; |
| dwc_regs_t* regs = dwc->regs; |
| for (uint32_t ep_num = 0; ep_num < MAX_EPS_CHANNELS; ep_num++) { |
| if (regs->daintmsk & (1 << ep_num)) { |
| if (dwc_ep_write_packet(dwc, ep_num)) { |
| need_more = true; |
| } |
| } |
| } |
| if (!need_more) { |
| zxlogf(LINFO, "turn off nptxfempty\n"); |
| regs->gintmsk.nptxfempty = 0; |
| } |
| } |
| |
| static void dwc_handle_usbsuspend_irq(dwc_usb_t* dwc) { |
| zxlogf(LINFO, "dwc_handle_usbsuspend_irq\n"); |
| } |
| |
| |
| // Thread to handle interrupts. |
| static int dwc_irq_thread(void* arg) { |
| dwc_usb_t* dwc = (dwc_usb_t*)arg; |
| dwc_regs_t* regs = dwc->regs; |
| |
| while (1) { |
| zx_status_t wait_res = zx_interrupt_wait(dwc->irq_handle, NULL); |
| if (wait_res != ZX_OK) { |
| zxlogf(ERROR, "dwc_usb: irq wait failed, retcode = %d\n", wait_res); |
| } |
| |
| //?? is while loop necessary? |
| while (1) { |
| dwc_interrupts_t interrupts = regs->gintsts; |
| dwc_interrupts_t mask = regs->gintmsk; |
| interrupts.val &= mask.val; |
| |
| if (!interrupts.val) { |
| break; |
| } |
| |
| // acknowledge |
| regs->gintsts = interrupts; |
| |
| zxlogf(LINFO, "dwc_handle_irq:"); |
| if (interrupts.modemismatch) zxlogf(LINFO, " modemismatch"); |
| if (interrupts.otgintr) zxlogf(LINFO, " otgintr"); |
| if (interrupts.sof_intr) zxlogf(LINFO, " sof_intr"); |
| if (interrupts.rxstsqlvl) zxlogf(LINFO, " rxstsqlvl"); |
| if (interrupts.nptxfempty) zxlogf(LINFO, " nptxfempty"); |
| if (interrupts.ginnakeff) zxlogf(LINFO, " ginnakeff"); |
| if (interrupts.goutnakeff) zxlogf(LINFO, " goutnakeff"); |
| if (interrupts.ulpickint) zxlogf(LINFO, " ulpickint"); |
| if (interrupts.i2cintr) zxlogf(LINFO, " i2cintr"); |
| if (interrupts.erlysuspend) zxlogf(LINFO, " erlysuspend"); |
| if (interrupts.usbsuspend) zxlogf(LINFO, " usbsuspend"); |
| if (interrupts.usbreset) zxlogf(LINFO, " usbreset"); |
| if (interrupts.enumdone) zxlogf(LINFO, " enumdone"); |
| if (interrupts.isooutdrop) zxlogf(LINFO, " isooutdrop"); |
| if (interrupts.eopframe) zxlogf(LINFO, " eopframe"); |
| if (interrupts.restoredone) zxlogf(LINFO, " restoredone"); |
| if (interrupts.epmismatch) zxlogf(LINFO, " epmismatch"); |
| if (interrupts.inepintr) zxlogf(LINFO, " inepintr"); |
| if (interrupts.outepintr) zxlogf(LINFO, " outepintr"); |
| if (interrupts.incomplisoin) zxlogf(LINFO, " incomplisoin"); |
| if (interrupts.incomplisoout) zxlogf(LINFO, " incomplisoout"); |
| if (interrupts.fetsusp) zxlogf(LINFO, " fetsusp"); |
| if (interrupts.resetdet) zxlogf(LINFO, " resetdet"); |
| if (interrupts.port_intr) zxlogf(LINFO, " port_intr"); |
| if (interrupts.host_channel_intr) zxlogf(LINFO, " host_channel_intr"); |
| if (interrupts.ptxfempty) zxlogf(LINFO, " ptxfempty"); |
| if (interrupts.lpmtranrcvd) zxlogf(LINFO, " lpmtranrcvd"); |
| if (interrupts.conidstschng) zxlogf(LINFO, " conidstschng"); |
| if (interrupts.disconnect) zxlogf(LINFO, " disconnect"); |
| if (interrupts.sessreqintr) zxlogf(LINFO, " sessreqintr"); |
| if (interrupts.wkupintr) zxlogf(LINFO, " wkupintr"); |
| zxlogf(LINFO, "\n"); |
| |
| if (interrupts.usbreset) { |
| dwc_handle_reset_irq(dwc); |
| } |
| if (interrupts.usbsuspend) { |
| dwc_handle_usbsuspend_irq(dwc); |
| } |
| if (interrupts.enumdone) { |
| dwc_handle_enumdone_irq(dwc); |
| } |
| if (interrupts.rxstsqlvl) { |
| dwc_handle_rxstsqlvl_irq(dwc); |
| } |
| if (interrupts.inepintr) { |
| dwc_handle_inepintr_irq(dwc); |
| } |
| if (interrupts.outepintr) { |
| dwc_handle_outepintr_irq(dwc); |
| } |
| if (interrupts.nptxfempty) { |
| dwc_handle_nptxfempty_irq(dwc); |
| } |
| } |
| } |
| |
| zxlogf(INFO, "dwc_usb: irq thread finished\n"); |
| return 0; |
| } |
| |
| zx_status_t dwc_irq_start(dwc_usb_t* dwc) { |
| zx_status_t status = pdev_map_interrupt(&dwc->pdev, IRQ_INDEX, &dwc->irq_handle); |
| if (status != ZX_OK) { |
| return status; |
| } |
| thrd_create_with_name(&dwc->irq_thread, dwc_irq_thread, dwc, "dwc_irq_thread"); |
| return ZX_OK; |
| } |
| |
| void dwc_irq_stop(dwc_usb_t* dwc) { |
| zx_interrupt_destroy(dwc->irq_handle); |
| thrd_join(dwc->irq_thread, NULL); |
| zx_handle_close(dwc->irq_handle); |
| dwc->irq_handle = ZX_HANDLE_INVALID; |
| } |
| |