| /**************************************************************************** |
| * arch/arm/src/lc823450/lc823450_usbdev.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <stdio.h> |
| #include <stddef.h> |
| #ifdef CONFIG_SYSTEM_PROPERTY |
| # include <system_property.h> |
| #endif |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/queue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbdev.h> |
| #include <nuttx/usb/usbdev_trace.h> |
| #ifdef CONFIG_BATTERY |
| # include <nuttx/power/battery.h> |
| #endif |
| #include <nuttx/power/pm.h> |
| #ifdef CONFIG_OFFDEEPSLEEP |
| # include <nuttx/power/offdeepsleep.h> |
| #endif |
| #ifdef CONFIG_WAKELOCK |
| # include <nuttx/wakelock.h> |
| #endif |
| #include <nuttx/wqueue.h> |
| |
| #include <arch/irq.h> |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| #include "lc823450_usbdev.h" |
| #include "lc823450_dma.h" |
| #include "lc823450_syscontrol.h" |
| |
| #if defined(CONFIG_HOTPLUG) && defined(CONFIG_HOTPLUG_USB) |
| # include <arch/board/hotplug.h> |
| #endif |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #if 0 |
| # define DPRINTF(fmt, args...) uinfo(fmt, ##args) |
| #else |
| # define DPRINTF(fmt, args...) do {} while (0) |
| #endif |
| |
| #ifndef container_of |
| # define container_of(ptr, type, member) \ |
| ((type *)((void *)(ptr) - offsetof(type, member))) |
| #endif |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct lc823450_ep_s; |
| |
| struct lc823450_req_s |
| { |
| struct usbdev_req_s req; /* Standard USB request */ |
| sq_entry_t q_ent; |
| struct lc823450_ep_s *ep; |
| }; |
| |
| struct lc823450_ep_s |
| { |
| struct usbdev_ep_s ep; /* Standard endpoint structure */ |
| struct lc823450_usbdev_s *dev; /* Reference to private driver data */ |
| uint32_t epcmd; |
| uint8_t epphy; /* Physical EP address */ |
| sq_queue_t req_q; |
| uint8_t *inbuf; |
| uint8_t *outbuf; |
| uint8_t stalled:1; /* 1: Endpoint is stalled */ |
| #ifdef CONFIG_USBMSC_IGNORE_CLEAR_STALL |
| uint8_t ignore_clear_stall:1; |
| #endif /* CONFIG_USBMSC_IGNORE_CLEAR_STALL */ |
| uint8_t in : 1; /* in = 1, out = 0 */ |
| uint8_t type : 2; /* 0:cont, 1:iso, 2:bulk, 3:int */ |
| uint8_t disable : 1; |
| }; |
| |
| struct lc823450_usbdev_s |
| { |
| struct usbdev_s usbdev; |
| struct usbdevclass_driver_s *driver; |
| struct lc823450_ep_s eplist[LC823450_NPHYSENDPOINTS]; |
| int bufoffset; |
| uint8_t used; /* used phyep */ |
| #ifdef CONFIG_WAKELOCK |
| struct wake_lock wlock; |
| #endif /* CONFIG_WAKELOCK */ |
| uint8_t suspended:1; |
| #ifdef CONFIG_USBDEV_CHARGER |
| uint8_t charger:1; /* 1: charger detected */ |
| #endif /* CONFIG_USBDEV_CHARGER */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static void lc823450_epack(int epnum, bool ack); |
| static int epbuf_write(int epnum, void *buf, size_t len); |
| static void epcmd_write(int epnum, uint32_t val); |
| static int lc823450_epconfigure(struct usbdev_ep_s *ep, |
| const struct usb_epdesc_s *desc, bool last); |
| static int lc823450_epclearreq(struct usbdev_ep_s *ep); |
| static int lc823450_epdisable(struct usbdev_ep_s *ep); |
| static struct usbdev_req_s *lc823450_epallocreq(struct usbdev_ep_s *ep); |
| static void lc823450_epfreereq(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req); |
| #ifdef CONFIG_USBDEV_DMA |
| static void *lc823450_epallocbuffer(struct usbdev_ep_s *ep, uint16_t bytes); |
| static void lc823450_epfreebuffer(struct usbdev_ep_s *ep, void *buf); |
| #endif /* CONFIG_USBDEV_DMA */ |
| static int lc823450_epsubmit(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req); |
| static int lc823450_epcancel(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req); |
| static int lc823450_epstall(struct usbdev_ep_s *ep, bool resume); |
| static struct usbdev_ep_s *lc823450_allocep(struct usbdev_s *dev, |
| uint8_t eplog, bool in, uint8_t eptype); |
| static void lc823450_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep); |
| static int lc823450_getframe(struct usbdev_s *dev); |
| static int lc823450_wakeup(struct usbdev_s *dev); |
| static int lc823450_selfpowered(struct usbdev_s *dev, bool selfpowered); |
| int lc823450_usbpullup(struct usbdev_s *dev, bool enable); |
| static void subintr_usbdev(void); |
| static void subintr_ep0(void); |
| static void subintr_ep(uint8_t epnum); |
| static int lc823450_usbinterrupt(int irq, void *context, void *arg); |
| #if defined(CONFIG_BATTERY) && defined(CONFIG_USBDEV_CHARGER) |
| static void usb_reset_work_func(void *arg); |
| #endif |
| |
| /**************************************************************************** |
| * Public Function Prototypes |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_DVFS |
| #define DVFS_BOOST_TIMEOUT (200) /* 200ms */ |
| extern int lc823450_dvfs_boost(int timeout); |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct lc823450_usbdev_s g_usbdev; |
| |
| static DMA_HANDLE g_hdma; |
| static sem_t dma_wait = SEM_INITIALIZER(0); |
| |
| #ifdef CONFIG_USBMSC_OPT |
| static struct lc823450_dma_llist g_dma_list[16]; |
| #endif |
| |
| #ifdef CONFIG_PM |
| static void usbdev_pmnotify(struct pm_callback_s *cb, |
| enum pm_state_e pmstate); |
| static struct pm_callback_s g_pm_cb = |
| { |
| .notify = usbdev_pmnotify, |
| }; |
| #endif |
| |
| static const struct usbdev_epops_s g_epops = |
| { |
| .configure = lc823450_epconfigure, |
| .disable = lc823450_epdisable, |
| .allocreq = lc823450_epallocreq, |
| .freereq = lc823450_epfreereq, |
| #ifdef CONFIG_USBDEV_DMA |
| .allocbuffer = lc823450_epallocbuffer, |
| .freebuffer = lc823450_epfreebuffer, |
| #endif |
| .submit = lc823450_epsubmit, |
| .cancel = lc823450_epcancel, |
| .stall = lc823450_epstall, |
| }; |
| |
| static const struct usbdev_ops_s g_devops = |
| { |
| .allocep = lc823450_allocep, |
| .freeep = lc823450_freeep, |
| .getframe = lc823450_getframe, |
| .wakeup = lc823450_wakeup, |
| .selfpowered = lc823450_selfpowered, |
| .pullup = lc823450_usbpullup, |
| }; |
| |
| #if defined(CONFIG_BATTERY) && defined(CONFIG_USBDEV_CHARGER) |
| static struct work_s g_reset_work; |
| #endif |
| |
| #ifdef CONFIG_WAKELOCK |
| static struct work_s g_suspend_work; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: lc823450_epack |
| * |
| * Description: |
| * ACK or NAK and endpoint |
| * |
| ****************************************************************************/ |
| |
| static void lc823450_epack(int epnum, bool ack) |
| { |
| struct lc823450_ep_s *privep; |
| |
| privep = &g_usbdev.eplist[epnum]; |
| |
| if (ack) |
| { |
| privep->epcmd &= ~USB_EPCMD_NAK; |
| } |
| else |
| { |
| privep->epcmd |= USB_EPCMD_NAK; |
| } |
| |
| epcmd_write(epnum, (privep->epcmd)); |
| } |
| |
| /**************************************************************************** |
| * Name: epbuf_read |
| * |
| * Description: |
| * read from RX Endpoint Buffer |
| * |
| ****************************************************************************/ |
| |
| int epbuf_read(int epnum, void *buf, size_t len) |
| { |
| size_t fifolen; |
| struct lc823450_ep_s *privep; |
| int bufidx; |
| |
| bufidx = (epnum == 0 ? 1 : epnum * 2); |
| |
| fifolen = (getreg32(USB_EPCOUNT(bufidx)) & USB_EPCOUNT_PHYCNT_MASK) >> |
| USB_EPCOUNT_PHYCNT_SHIFT; |
| |
| privep = &g_usbdev.eplist[epnum]; |
| |
| len = MIN(len, fifolen); |
| |
| memcpy(buf, privep->outbuf, len); |
| |
| epcmd_write(epnum, USB_EPCMD_BUFRD); |
| |
| return len; |
| } |
| |
| /**************************************************************************** |
| * Name: epbuf_write |
| * |
| * Description: |
| * Write to TX Endpoint Buffer |
| * |
| ****************************************************************************/ |
| |
| static int epbuf_write(int epnum, void *buf, size_t len) |
| { |
| struct lc823450_ep_s *privep; |
| int bufidx; |
| int txn; |
| int total; |
| int tout = 0; |
| |
| total = len; |
| bufidx = (epnum == 0 ? 0 : epnum * 2); |
| |
| privep = &g_usbdev.eplist[epnum]; |
| |
| cont: |
| if (epnum == 0) |
| { |
| while (!(getreg32(USB_EPCTRL(epnum)) & USB_EPCTRL_EMPTYI) && |
| !privep->disable && tout++ < 1000000) |
| { |
| up_udelay(1); |
| } |
| } |
| else |
| { |
| while (!(getreg32(USB_EPCTRL(epnum)) & USB_EPCTRL_EMPTY) && |
| !privep->disable && tout++ < 1000000) |
| { |
| up_udelay(1); |
| } |
| } |
| |
| txn = MIN(len, privep->ep.maxpacket); |
| |
| memcpy(privep->inbuf, buf, txn); |
| putreg32(txn << USB_EPCOUNT_APPCNT_SHIFT, USB_EPCOUNT(bufidx)); |
| |
| epcmd_write(epnum, USB_EPCMD_BUFWR); |
| |
| lc823450_epack(epnum, true); |
| epcmd_write(epnum, USB_EPCMD_NACK_CLR); |
| |
| len -= txn; |
| buf += txn; |
| if (len > 0) |
| { |
| goto cont; |
| } |
| |
| return total; |
| } |
| |
| /**************************************************************************** |
| * Name: epcmd_write |
| * |
| * Description: |
| * Write to EP command register |
| * |
| ****************************************************************************/ |
| |
| static void epcmd_write(int epnum, uint32_t val) |
| { |
| while (getreg32(USB_EPCMD(epnum)) & USB_EPCMD_BUSY); |
| |
| putreg32(val, USB_EPCMD(epnum)); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epconfigure |
| * |
| * Description: |
| * Configure endpoint, making it usable |
| * |
| * Input Parameters: |
| * ep - the struct usbdev_ep_s instance obtained from allocep() |
| * desc - A struct usb_epdesc_s instance describing the endpoint |
| * last - true if this is the last endpoint to be configured. Some |
| * hardware needs to take special action when all of the endpoints |
| * have been configured. |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epconfigure(struct usbdev_ep_s *ep, |
| const struct usb_epdesc_s *desc, |
| bool last) |
| { |
| int epnum; |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| struct lc823450_usbdev_s *priv = privep->dev; |
| |
| epnum = privep->epphy; |
| if (desc) |
| { |
| priv->eplist[epnum].ep.maxpacket = GETUINT16(desc->mxpacketsize); |
| } |
| |
| DPRINTF("epnum = %d, ep = %p, max = %d, speed = 0x%x, in = 0x%x\n", |
| epnum, ep, priv->eplist[epnum].ep.maxpacket, |
| priv->usbdev.speed, priv->eplist[epnum].in); |
| |
| epcmd_write(epnum, USB_EPCMD_INIT); |
| |
| /* Buffer Index assign |
| * IDX0: EP0IN |
| * IDX1: EP0OUT |
| * IDX2: EP1 |
| * IDX4: EP2 |
| * IDX6: EP3 |
| * ... |
| */ |
| |
| if (epnum == 0) |
| { |
| putreg32(0 << USB_EPCONF_CIDX_SHIFT | |
| 64 << USB_EPCONF_SIZE_SHIFT | |
| (0x100 >> 2) << USB_EPCONF_BASE_SHIFT, |
| USB_EPCONF(0)); |
| priv->eplist[0].inbuf = (uint8_t *)USB_EPBUF + 0x100; |
| priv->eplist[0].outbuf = (uint8_t *)USB_EPBUF + 0x140; |
| } |
| else |
| { |
| putreg32((epnum * 2) << USB_EPCONF_CIDX_SHIFT | |
| priv->eplist[epnum].ep.maxpacket << USB_EPCONF_SIZE_SHIFT | |
| (priv->bufoffset >> 2) << USB_EPCONF_BASE_SHIFT, |
| USB_EPCONF(epnum)); |
| priv->eplist[epnum].inbuf = (uint8_t *)USB_EPBUF + priv->bufoffset; |
| priv->eplist[epnum].outbuf = (uint8_t *)USB_EPBUF + priv->bufoffset; |
| priv->bufoffset += priv->eplist[epnum].ep.maxpacket; |
| } |
| |
| priv->eplist[epnum].epcmd = |
| priv->eplist[epnum].type << USB_EPCMD_ET_SHIFT | |
| (priv->eplist[epnum].in ? 1 : 0) << USB_EPCMD_DIR_SHIFT | |
| USB_EPCMD_WRITE_EN | USB_EPCMD_START; |
| |
| #ifdef CONFIG_USBDEV_NULLRESP_EPNUM |
| if (epnum == CONFIG_USBDEV_NULLRESP_EPNUM) |
| { |
| priv->eplist[epnum].epcmd |= USB_EPCMD_NULL; |
| } |
| #endif /* CONFIG_USBDEV_NULLRESP_EPNUM */ |
| |
| if (epnum != 0) |
| { |
| if (priv->eplist[epnum].in) |
| { |
| priv->eplist[epnum].epcmd |= USB_EPCMD_EMPTY_EN; |
| } |
| else |
| { |
| priv->eplist[epnum].epcmd |= USB_EPCMD_READY_EN; |
| } |
| } |
| |
| epcmd_write(epnum, priv->eplist[epnum].epcmd | USB_EPCMD_TGL_CLR | |
| USB_EPCMD_STALL_CLR); |
| |
| privep->disable = 0; |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epdisable |
| * |
| * Description: |
| * The endpoint will no longer be used |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epclearreq(struct usbdev_ep_s *ep) |
| { |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| irqstate_t flags; |
| |
| flags = spin_lock_irqsave(NULL); |
| while (privep->req_q.tail) |
| { |
| struct usbdev_req_s *req; |
| sq_entry_t *q_ent; |
| |
| /* Dequeue from Reqbuf poll */ |
| |
| q_ent = sq_remlast(&privep->req_q); |
| req = &container_of(q_ent, struct lc823450_req_s, q_ent)->req; |
| |
| /* return reqbuf to function driver */ |
| |
| req->result = -ESHUTDOWN; |
| req->callback(ep, req); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epdisable |
| * |
| * Description: |
| * The endpoint will no longer be used |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epdisable(struct usbdev_ep_s *ep) |
| { |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| uint8_t epnum; |
| |
| epnum = privep->epphy; |
| privep->epcmd = 0; |
| epcmd_write(epnum, privep->epcmd | USB_EPCMD_STOP); |
| |
| if (epnum == 0) |
| { |
| putreg32(0x500, USB_EPCTRL(0)); |
| } |
| else |
| { |
| putreg32(0x400, USB_EPCTRL(epnum)); |
| } |
| |
| putreg32(0, USB_EPCONF(epnum)); |
| putreg32(0, USB_EPCOUNT(epnum * 2)); |
| putreg32(0, USB_EPCOUNT(epnum * 2 + 1)); |
| |
| g_usbdev.bufoffset = 0x180; |
| privep->disable = 1; |
| lc823450_dmastop(g_hdma); |
| nxsem_post(&dma_wait); |
| return lc823450_epclearreq(ep); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epallocreq |
| * |
| * Description: |
| * Allocate an I/O request |
| * |
| ****************************************************************************/ |
| |
| static struct usbdev_req_s *lc823450_epallocreq(struct usbdev_ep_s *ep) |
| { |
| struct lc823450_req_s *privreq; |
| |
| #ifdef CONFIG_DEBUG |
| if (!ep) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_INVALIDPARMS), 0); |
| return NULL; |
| } |
| #endif |
| |
| usbtrace(TRACE_EPALLOCREQ, ((struct lc823450_ep_s *)ep)->epphy); |
| |
| privreq = (struct lc823450_req_s *) |
| kmm_zalloc(sizeof(struct lc823450_req_s)); |
| |
| if (!privreq) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_ALLOCFAIL), 0); |
| return NULL; |
| } |
| |
| return &privreq->req; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epfreereq |
| * |
| * Description: |
| * Free an I/O request |
| * |
| ****************************************************************************/ |
| |
| static void lc823450_epfreereq(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct lc823450_req_s *privreq = (struct lc823450_req_s *)req; |
| |
| #ifdef CONFIG_DEBUG |
| if (!ep || !req) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_INVALIDPARMS), 0); |
| return; |
| } |
| #endif |
| |
| usbtrace(TRACE_EPFREEREQ, ((struct lc823450_ep_s *)ep)->epphy); |
| kmm_free(privreq); |
| } |
| |
| #ifdef CONFIG_USBDEV_DMA |
| /**************************************************************************** |
| * Name: lc823450_epallocbuffer |
| * |
| * Description: |
| * Allocate an I/O buffer |
| * |
| ****************************************************************************/ |
| |
| static void *lc823450_epallocbuffer(struct usbdev_ep_s *ep, uint16_t bytes) |
| { |
| usbtrace(TRACE_EPALLOCBUFFER, privep->epphy); |
| |
| # ifdef CONFIG_USBDEV_DMAMEMORY |
| return usbdev_dma_alloc(bytes); |
| # else |
| return kmm_malloc(bytes); |
| # endif |
| } |
| #endif |
| |
| #ifdef CONFIG_USBDEV_DMA |
| /**************************************************************************** |
| * Name: lc823450_epfreebuffer |
| * |
| * Description: |
| * Free an I/O buffer |
| * |
| ****************************************************************************/ |
| |
| static void lc823450_epfreebuffer(struct usbdev_ep_s *ep, void *buf) |
| { |
| usbtrace(TRACE_EPFREEBUFFER, privep->epphy); |
| |
| # ifdef CONFIG_USBDEV_DMAMEMORY |
| usbdev_dma_free(buf); |
| # else |
| kmm_free(buf); |
| # endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: lc823450_epsubmit |
| * |
| * Description: |
| * Submit an I/O request to the endpoint |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epsubmit(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct lc823450_req_s *privreq = (struct lc823450_req_s *)req; |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| irqstate_t flags; |
| |
| req->result = 0; |
| if (privep->disable) |
| { |
| return -EBUSY; |
| } |
| |
| if (privep->epphy == 0) |
| { |
| flags = spin_lock_irqsave(NULL); |
| req->xfrd = epbuf_write(privep->epphy, req->buf, req->len); |
| spin_unlock_irqrestore(NULL, flags); |
| req->callback(ep, req); |
| } |
| else if (privep->in) |
| { |
| /* Send packet request from function driver */ |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| if ((getreg32(USB_EPCOUNT(privep->epphy * 2)) & |
| USB_EPCOUNT_PHYCNT_MASK) >> USB_EPCOUNT_PHYCNT_SHIFT || |
| privep->req_q.tail) |
| { |
| sq_addfirst(&privreq->q_ent, &privep->req_q); /* non block */ |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| else |
| { |
| spin_unlock_irqrestore(NULL, flags); |
| req->xfrd = epbuf_write(privep->epphy, req->buf, req->len); |
| req->callback(ep, req); |
| } |
| } |
| else |
| { |
| /* receive packet buffer from function driver */ |
| |
| flags = spin_lock_irqsave(NULL); |
| sq_addfirst(&privreq->q_ent, &privep->req_q); /* non block */ |
| spin_unlock_irqrestore(NULL, flags); |
| lc823450_epack(privep->epphy, 1); |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epcancel |
| * |
| * Description: |
| * Cancel an I/O request previously sent to an endpoint |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epcancel(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct lc823450_req_s *privreq = (struct lc823450_req_s *)req; |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| irqstate_t flags; |
| |
| /* Remove request from req_queue */ |
| |
| flags = spin_lock_irqsave(NULL); |
| sq_remafter(&privreq->q_ent, &privep->req_q); |
| spin_unlock_irqrestore(NULL, flags); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_epstall |
| * |
| * Description: |
| * Stall or resume and endpoint |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_epstall(struct usbdev_ep_s *ep, bool resume) |
| { |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| irqstate_t flags; |
| |
| /* STALL or RESUME the endpoint */ |
| |
| flags = spin_lock_irqsave(NULL); |
| usbtrace(resume ? TRACE_EPRESUME : TRACE_EPSTALL, privep->epphy); |
| |
| if (resume) |
| { |
| privep->stalled = false; |
| epcmd_write(privep->epphy, USB_EPCMD_STALL_CLR | USB_EPCMD_TGL_CLR); |
| } |
| else |
| { |
| privep->stalled = true; |
| epcmd_write(privep->epphy, USB_EPCMD_STALL_SET | USB_EPCMD_TGL_SET); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| return OK; |
| } |
| |
| #ifdef CONFIG_USBMSC_IGNORE_CLEAR_STALL |
| void up_epignore_clear_stall(struct usbdev_ep_s *ep, bool ignore) |
| { |
| struct lc823450_ep_s *privep = (struct lc823450_ep_s *)ep; |
| irqstate_t flags; |
| flags = spin_lock_irqsave(NULL); |
| |
| privep->ignore_clear_stall = ignore; |
| |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| #endif /* CONFIG_USBMSC_IGNORE_CLEAR_STALL */ |
| |
| /**************************************************************************** |
| * Name: lc823450_allocep |
| * |
| * Description: |
| * Allocate an endpoint matching the parameters. |
| * |
| * Input Parameters: |
| * eplog - 7-bit logical endpoint number (direction bit ignored). Zero |
| * means that any endpoint matching the other requirements will |
| * suffice. The assigned endpoint can be found in the eplog field. |
| * in - true: IN (device-to-host) endpoint requested |
| * eptype - Endpoint type. One of {USB_EP_ATTR_XFER_ISOC, |
| * USB_EP_ATTR_XFER_BULK, USB_EP_ATTR_XFER_INT} |
| * |
| ****************************************************************************/ |
| |
| static struct usbdev_ep_s *lc823450_allocep(struct usbdev_s *dev, |
| uint8_t eplog, bool in, |
| uint8_t eptype) |
| { |
| struct lc823450_usbdev_s *priv = (struct lc823450_usbdev_s *)dev; |
| struct lc823450_ep_s *privep; |
| int epphy; |
| |
| usbtrace(TRACE_DEVALLOCEP, (uint16_t)eplog); |
| |
| /* Ignore any direction bits in the logical address */ |
| |
| epphy = USB_EPNO(eplog); |
| |
| if (priv->used & 1 << epphy) |
| { |
| uinfo("ep is still used\n"); |
| return NULL; |
| } |
| |
| priv->used |= 1 << epphy; |
| |
| privep = &priv->eplist[epphy]; |
| privep->in = in; |
| privep->type = eptype; |
| privep->epphy = epphy; |
| privep->ep.eplog = epphy; |
| |
| return &privep->ep; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_freeep |
| * |
| * Description: |
| * Free the previously allocated endpoint |
| * |
| ****************************************************************************/ |
| |
| static void lc823450_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep) |
| { |
| struct lc823450_usbdev_s *priv = (struct lc823450_usbdev_s *)dev; |
| struct lc823450_ep_s *privep; |
| |
| privep = (struct lc823450_ep_s *)ep; |
| |
| priv->used &= ~(1 << privep->epphy); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_getframe |
| * |
| * Description: |
| * Returns the current frame number |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_getframe(struct usbdev_s *dev) |
| { |
| usbtrace(TRACE_DEVGETFRAME, 0); |
| |
| return (int)(getreg32(USB_TSTAMP)); |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_wakeup |
| * |
| * Description: |
| * Tries to wake up the host connected to this device |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_wakeup(struct usbdev_s *dev) |
| { |
| usbtrace(TRACE_DEVWAKEUP, 0); |
| |
| modifyreg32(USB_DEVC, 0, USB_DEVC_RESUME); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_selfpowered |
| * |
| * Description: |
| * Sets/clears the device selfpowered feature |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_selfpowered(struct usbdev_s *dev, bool selfpowered) |
| { |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_usbpullup |
| * |
| * Description: |
| * Software-controlled connect to/disconnect from USB host |
| * |
| ****************************************************************************/ |
| |
| int lc823450_usbpullup(struct usbdev_s *dev, bool enable) |
| { |
| if (enable) |
| { |
| modifyreg32(USB_DEVC, USB_DEVC_DISCON, 0); |
| } |
| else |
| { |
| modifyreg32(USB_DEVC, 0 , USB_DEVC_DISCON); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_WAKELOCK |
| /**************************************************************************** |
| * Name: subintr_usbdev |
| * |
| * Description: |
| * USB dev interrupt sub handler |
| * |
| ****************************************************************************/ |
| |
| static void usb_suspend_work_func(void *arg) |
| { |
| struct lc823450_usbdev_s *priv = arg; |
| irqstate_t flags; |
| DPRINTF("SUSPENDB MAIN\n"); |
| |
| #ifdef CONFIG_CDCACM_AS_ADB |
| if (nxproperty_adb_get()) |
| { |
| return; |
| } |
| #endif |
| |
| flags = spin_lock_irqsave(NULL); |
| if (getreg32(USB_DEVS) & USB_DEVS_SUSPEND) |
| { |
| uinfo("USB BUS SUSPEND\n"); |
| #if defined(CONFIG_BATTERY) && !defined(CONFIG_BATTERY_DISABLE_CHARGE) |
| battery_sendevent(BATTERY_USBEV_SUSP); |
| #endif |
| |
| #ifdef CONFIG_OFFDEEPSLEEP |
| g_offdeepsleep = 1; |
| #endif |
| g_usbsuspend = 1; |
| wake_unlock(&priv->wlock); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| #endif |
| |
| #if defined(CONFIG_BATTERY) && defined(CONFIG_USBDEV_CHARGER) |
| /**************************************************************************** |
| * Name: usb_reset_work_func |
| ****************************************************************************/ |
| |
| static void usb_reset_work_func(void *arg) |
| { |
| if (g_usbdev.charger) |
| { |
| /* Disconnect Charger */ |
| |
| # if defined(CONFIG_HOTPLUG) && defined(CONFIG_HOTPLUG_USB) |
| hotplug_start_usbemu(false); |
| # endif |
| |
| g_usbdev.charger = 0; |
| work_queue(HPWORK, &g_reset_work, usb_reset_work_func, NULL, |
| MSEC2TICK(100)); |
| } |
| else |
| { |
| /* Connect Host */ |
| |
| # if defined(CONFIG_HOTPLUG) && defined(CONFIG_HOTPLUG_USB) |
| hotplug_start_usbemu(true); |
| # endif |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: subintr_usbdev |
| ****************************************************************************/ |
| |
| static void subintr_usbdev(void) |
| { |
| uint32_t devs; |
| struct lc823450_usbdev_s *priv = &g_usbdev; |
| |
| devs = getreg32(USB_DEVS); |
| |
| #ifdef CONFIG_BATTERY |
| #ifdef CONFIG_USBDEV_CHARGER |
| if (g_usbdev.charger) |
| { |
| work_cancel(HPWORK, &g_reset_work); |
| work_queue(HPWORK, &g_reset_work, usb_reset_work_func, NULL, 0); |
| |
| /* Disable interrupts */ |
| |
| up_disable_irq(LC823450_IRQ_USBDEV); |
| putreg32(~devs, USB_DEVS); |
| return; |
| } |
| #endif |
| #endif |
| |
| if (devs & USB_DEVC_USBRSTE) |
| { |
| DPRINTF("BUSRESET\n"); |
| putreg32(~USB_DEVC_USBRSTE, USB_DEVS); |
| |
| #if defined(CONFIG_BATTERY) && !defined(CONFIG_BATTERY_DISABLE_CHARGE) |
| battery_sendevent(BATTERY_USBEV_BUSRST); |
| #endif |
| |
| CLASS_DISCONNECT(priv->driver, &priv->usbdev); |
| if (devs & USB_DEVS_CRTSPEED) |
| { |
| priv->usbdev.speed = USB_SPEED_FULL; |
| } |
| else |
| { |
| priv->usbdev.speed = USB_SPEED_HIGH; |
| } |
| } |
| |
| if (devs & USB_DEVC_SETUP) |
| { |
| DPRINTF("SETUP\n"); |
| putreg32(~USB_DEVC_SETUP, USB_DEVS); |
| subintr_ep0(); |
| } |
| |
| if (devs & USB_DEVC_SUSPENDB) |
| { |
| DPRINTF("SUSPENDB\n"); |
| putreg32(~USB_DEVC_SUSPENDB, USB_DEVS); |
| |
| #ifdef CONFIG_WAKELOCK |
| g_deepsleep_cancel = 0; |
| |
| work_cancel(HPWORK, &g_suspend_work); |
| work_queue(HPWORK, &g_suspend_work, usb_suspend_work_func, priv, |
| MSEC2TICK(1000)); |
| #endif |
| } |
| |
| if (devs & USB_DEVC_SUSPENDE) |
| { |
| DPRINTF("SUSPENDE\n"); |
| putreg32(~USB_DEVC_SUSPENDE, USB_DEVS); |
| #ifdef CONFIG_WAKELOCK |
| g_deepsleep_cancel = 1; |
| #endif |
| #if defined(CONFIG_BATTERY) && !defined(CONFIG_BATTERY_DISABLE_CHARGE) |
| battery_sendevent(BATTERY_USBEV_RES); |
| #endif |
| #ifdef CONFIG_WAKELOCK |
| wake_lock(&priv->wlock); |
| work_cancel(HPWORK, &g_suspend_work); |
| #endif |
| |
| #if defined(CONFIG_BATTERY) && defined(CONFIG_USBDEV_CHARGER) |
| if ((getreg32(USBSTAT) & USBSTAT_LINESTE_MASK) == |
| (USBSTAT_LINESTE_0 | USBSTAT_LINESTE_1)) |
| { |
| g_usbdev.charger = 1; |
| work_cancel(HPWORK, &g_reset_work); |
| work_queue(HPWORK, &g_reset_work, usb_reset_work_func, NULL, 0); |
| } |
| #endif |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: subintr_ep0 |
| * |
| * Description: |
| * Endpoint0 interrupt sub handler |
| * |
| ****************************************************************************/ |
| |
| static void subintr_ep0(void) |
| { |
| uint32_t epctrl; |
| struct lc823450_usbdev_s *priv = &g_usbdev; |
| int len; |
| int handled = 0; |
| char resp[2]; |
| uint8_t epnum; |
| |
| epctrl = getreg32(USB_EPCTRL(0)); |
| DPRINTF("epctrl = 0x%x\n", epctrl); |
| if (epctrl & USB_EPCTRL_READOI) |
| { |
| struct usb_ctrlreq_s ctrl; |
| memset(&ctrl, 0, sizeof(ctrl)); |
| len = epbuf_read(0, &ctrl, sizeof(ctrl)); |
| epcmd_write(0, USB_EPCMD_READYO_CLR); |
| |
| /* NULL RESP */ |
| |
| if (!len) |
| { |
| return; |
| } |
| |
| DPRINTF("CTRL: type 0x%x, req 0x%x, val 0x%x, 0x%x, " |
| "idx 0x%x, %x, len 0x%x, 0x%x\n", |
| ctrl.type, ctrl.req, ctrl.value[0], ctrl.value[1], |
| ctrl.index[0], ctrl.index[1], ctrl.len[0], ctrl.len[1]); |
| |
| if ((ctrl.type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) |
| { |
| switch (ctrl.req) |
| { |
| case USB_REQ_GETSTATUS: |
| resp[1] = 0; |
| switch (ctrl.type & USB_REQ_RECIPIENT_MASK) |
| { |
| case USB_REQ_RECIPIENT_ENDPOINT: |
| epnum = USB_EPNO(ctrl.index[0]); |
| if (epnum < LC823450_NPHYSENDPOINTS && |
| priv->eplist[epnum].stalled == 0) |
| { |
| resp[0] = 0; /* bit0: halt */ |
| } |
| else |
| { |
| resp[0] = 1; /* bit0: halt */ |
| } |
| break; |
| |
| case USB_REQ_RECIPIENT_DEVICE: |
| resp[0] = 0; /* bit0: selfpowerd, bit1: remote wakeup */ |
| break; |
| |
| case USB_REQ_RECIPIENT_INTERFACE: |
| resp[0] = 0; /* reserved */ |
| break; |
| } |
| |
| epbuf_write(0, &resp, 2); |
| handled = 1; |
| break; |
| |
| case USB_REQ_SETADDRESS: |
| modifyreg32(USB_FADDR, USB_FADDR_ADDR_MASK, ctrl.value[0]); |
| epbuf_write(0, &resp, 0); |
| handled = 1; |
| break; |
| |
| case USB_REQ_SETCONFIGURATION: |
| modifyreg32(USB_FADDR, 0, USB_FADDR_CONFD); |
| #ifdef CONFIG_WAKELOCK |
| wake_lock(&priv->wlock); |
| #endif |
| #if defined(CONFIG_BATTERY) && !defined(CONFIG_BATTERY_DISABLE_CHARGE) |
| battery_sendevent(BATTERY_USBEV_CHG); |
| #endif |
| break; |
| |
| case USB_REQ_SETFEATURE: |
| if (ctrl.value[0] == USB_FEATURE_TESTMODE) |
| { |
| epbuf_write(0, &resp, 0); |
| up_udelay(1000); |
| |
| if (ctrl.index[1] == 0x4) |
| { |
| /* TestPacket */ |
| |
| putreg32(1 << 0 | USB_TESTC_FORCE_HS, USB_TESTC); |
| } |
| else |
| { |
| putreg32(1 << ctrl.index[1] | USB_TESTC_FORCE_HS, |
| USB_TESTC); |
| } |
| |
| handled = 1; |
| } |
| else if (ctrl.value[0] == USB_FEATURE_ENDPOINTHALT) |
| { |
| epnum = USB_EPNO(ctrl.index[0]); |
| if (epnum < LC823450_NPHYSENDPOINTS) |
| { |
| lc823450_epstall(&priv->eplist[epnum].ep, false); |
| epbuf_write(0, &resp, 0); |
| handled = 1; |
| } |
| } |
| break; |
| |
| case USB_REQ_CLEARFEATURE: |
| if (ctrl.value[0] == USB_FEATURE_ENDPOINTHALT) |
| { |
| epnum = USB_EPNO(ctrl.index[0]); |
| if (epnum < LC823450_NPHYSENDPOINTS) |
| { |
| epbuf_write(0, &resp, 0); |
| #ifdef CONFIG_USBMSC_IGNORE_CLEAR_STALL |
| if (!priv->eplist[epnum].ignore_clear_stall) |
| #endif |
| { |
| lc823450_epstall(&priv->eplist[epnum].ep, true); |
| } |
| |
| handled = 1; |
| } |
| } |
| break; |
| } |
| } |
| |
| if (!handled) |
| { |
| uint8_t ctrldat[16]; |
| int ctrldat_len; |
| |
| ctrldat_len = MIN(GETUINT16(ctrl.len), sizeof(ctrldat)); |
| if (ctrldat_len) |
| { |
| int tout = 1000; |
| do |
| { |
| if (getreg32(USB_EPCMD(0)) & USB_EPCMD_READYO_CLR) |
| { |
| break; |
| } |
| |
| up_udelay(10); |
| } |
| while (tout--); |
| |
| if (tout) |
| { |
| ctrldat_len = epbuf_read(0, &ctrldat, ctrldat_len); |
| } |
| } |
| |
| if (CLASS_SETUP(priv->driver, &priv->usbdev, &ctrl, |
| (uint8_t *)&ctrldat, ctrldat_len) < 0) |
| { |
| lc823450_epstall(&priv->eplist[0].ep, false); |
| } |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: subintr_epin |
| * |
| * Description: |
| * Endpoint interrupt sub handler |
| * |
| ****************************************************************************/ |
| |
| static void subintr_epin(uint8_t epnum, struct lc823450_ep_s *privep) |
| { |
| /* Send packet done */ |
| |
| irqstate_t flags; |
| flags = spin_lock_irqsave(NULL); |
| |
| if (privep->req_q.tail) |
| { |
| struct usbdev_req_s *req; |
| sq_entry_t *q_ent; |
| |
| /* Dequeue from TXQ */ |
| |
| q_ent = sq_remlast(&privep->req_q); |
| |
| spin_unlock_irqrestore(NULL, flags); |
| |
| req = &container_of(q_ent, struct lc823450_req_s, q_ent)->req; |
| |
| /* Write to TX FIFO */ |
| |
| /* int clear!! before epbuf write */ |
| |
| epcmd_write(epnum, USB_EPCMD_EMPTY_CLR); |
| req->xfrd = epbuf_write(epnum, req->buf, req->len); |
| req->callback(&privep->ep, req); |
| |
| /* int clear */ |
| } |
| else |
| { |
| spin_unlock_irqrestore(NULL, flags); |
| epcmd_write(epnum, USB_EPCMD_EMPTY_CLR); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: subintr_epout |
| * |
| * Description: |
| * Endpoint interrupt sub handler |
| * |
| ****************************************************************************/ |
| |
| static void subintr_epout(uint8_t epnum, struct lc823450_ep_s *privep) |
| { |
| /* Packet receive from host */ |
| |
| irqstate_t flags; |
| flags = spin_lock_irqsave(NULL); |
| |
| if (privep->req_q.tail) |
| { |
| struct usbdev_req_s *req; |
| sq_entry_t *q_ent; |
| |
| /* Dequeue from Reqbuf poll */ |
| |
| q_ent = sq_remlast(&privep->req_q); |
| |
| req = &container_of(q_ent, struct lc823450_req_s, q_ent)->req; |
| if (privep->req_q.tail == NULL) |
| { |
| /* receive buffer exhaust */ |
| |
| lc823450_epack(epnum, 0); |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| |
| /* PIO */ |
| |
| epcmd_write(epnum, USB_EPCMD_READY_CLR); |
| |
| /* int clear!! before epbuf read */ |
| |
| req->xfrd = epbuf_read(epnum, req->buf, req->len); |
| req->callback(&privep->ep, req); |
| } |
| else |
| { |
| spin_unlock_irqrestore(NULL, flags); |
| uinfo("REQ Buffer Exhault\n"); |
| epcmd_write(epnum, USB_EPCMD_READY_CLR); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: subintr_ep |
| * |
| * Description: |
| * Endpoint interrupt sub handler |
| * |
| ****************************************************************************/ |
| |
| static void subintr_ep(uint8_t epnum) |
| { |
| struct lc823450_usbdev_s *priv = &g_usbdev; |
| struct lc823450_ep_s *privep; |
| uint32_t epctrl; |
| |
| epctrl = getreg32(USB_EPCTRL(epnum)); |
| |
| privep = &priv->eplist[epnum]; |
| |
| if ((epctrl & USB_EPCTRL_READI) && !privep->in) |
| { |
| subintr_epout(epnum, privep); |
| } |
| else if ((epctrl & USB_EPCTRL_EMPTI) && privep->in) |
| { |
| subintr_epin(epnum, privep); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: lc823450_usbinterrupt |
| * |
| * Description: |
| * USB interrupt handler |
| * |
| ****************************************************************************/ |
| |
| static int lc823450_usbinterrupt(int irq, void *context, void *arg) |
| { |
| uint32_t disr; |
| int i; |
| |
| if (g_usbdev.suspended) |
| { |
| modifyreg32(USBCNT, USBCNT_RSM_CONT, 0); |
| } |
| |
| disr = getreg32(USB_INTS); |
| if (disr & USB_INT_DEV) |
| { |
| subintr_usbdev(); |
| } |
| |
| if (disr & USB_INT_EP0) |
| { |
| subintr_ep0(); |
| } |
| |
| for (i = USB_INT_EP0_SHIFT + 1; i <= USB_INT_EP15_SHIFT; i++) |
| { |
| if (disr & (1 << i)) |
| { |
| subintr_ep(i - USB_INT_EP0_SHIFT); |
| } |
| } |
| |
| putreg32(~disr, USB_INTS); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: arm_usbinitialize |
| * |
| * Description: |
| * Initialize the USB driver |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void arm_usbinitialize(void) |
| { |
| int i; |
| struct lc823450_usbdev_s *priv = &g_usbdev; |
| |
| memset(&g_usbdev, 0, sizeof(g_usbdev)); |
| g_usbdev.usbdev.ops = &g_devops; |
| g_usbdev.usbdev.ep0 = &g_usbdev.eplist[0].ep; |
| |
| for (i = 0; i < LC823450_NPHYSENDPOINTS; i++) |
| { |
| g_usbdev.eplist[i].ep.ops = &g_epops; |
| g_usbdev.eplist[i].ep.maxpacket = 64; |
| g_usbdev.eplist[i].dev = &g_usbdev; |
| g_usbdev.eplist[i].epphy = i; |
| sq_init(&g_usbdev.eplist[i].req_q); |
| priv->eplist[i].ep.eplog = i; |
| } |
| |
| if (irq_attach(LC823450_IRQ_USBDEV, lc823450_usbinterrupt, NULL) != 0) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_IRQREGISTRATION), |
| (uint16_t)LC823450_IRQ_USBDEV); |
| return; |
| } |
| |
| g_hdma = lc823450_dmachannel(DMA_CHANNEL_USBDEV); |
| lc823450_dmarequest(g_hdma, DMA_REQUEST_USBDEV); |
| |
| #ifdef CONFIG_WAKELOCK |
| wake_lock_init(&g_usbdev.wlock, "usb"); |
| #endif /* CONFIG_WAKELOCK */ |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_register |
| ****************************************************************************/ |
| |
| int usbdev_register(struct usbdevclass_driver_s *driver) |
| { |
| int ret = -1; |
| int i; |
| |
| #ifdef CONFIG_DVFS |
| lc823450_dvfs_boost(DVFS_BOOST_TIMEOUT * 20); |
| #endif |
| |
| #ifdef CONFIG_LC823450_LSISTBY |
| /* enable USB */ |
| |
| mod_stby_regs(LSISTBY_STBYE, 0); |
| #endif |
| |
| g_usbdev.driver = driver; |
| |
| /* Clock & Power & Reset */ |
| |
| modifyreg32(MCLKCNTBASIC, 0, MCLKCNTBASIC_USBDEV_CLKEN); |
| modifyreg32(USBCNT, USBCNT_ANPD, 0); |
| |
| /* USB PLL REFCLOCK */ |
| |
| if (XT1OSC_CLK == (24 * 1000 * 1000)) |
| { |
| modifyreg32(USBCNT, USBCNT_CLK_MASK, USBCNT_CLK24MHZ); |
| modifyreg32(USBCNT, USBCNT_CRYCNTSW_MASK, USBCNT_CRYCNTSW24MHZ); |
| } |
| else if (XT1OSC_CLK == (20 * 1000 * 1000)) |
| { |
| modifyreg32(USBCNT, USBCNT_CLK_MASK, USBCNT_CLK20MHZ); |
| modifyreg32(USBCNT, USBCNT_CRYCNTSW_MASK, USBCNT_CRYCNTSW20MHZ); |
| } |
| else |
| { |
| uinfo("not support\n"); |
| } |
| |
| modifyreg32(MRSTCNTBASIC, 0, MRSTCNTBASIC_USBDEV_RSTB); |
| |
| modifyreg32(USBCNT, 0, USBCNT_VBUS_VALID); |
| |
| /* SoftReset */ |
| |
| modifyreg32(USB_CONF, 0, USB_CONF_SOFT_RESET); |
| while (getreg32(USB_CONF) & USB_CONF_SOFT_RESET); |
| |
| putreg32(0, USB_CONF); |
| |
| /* RAM area init */ |
| |
| for (i = 0; i < 16; i++) |
| { |
| if (i == 0) |
| { |
| putreg32(0x500, USB_EPCTRL(i)); |
| } |
| else |
| { |
| putreg32(0x400, USB_EPCTRL(i)); |
| } |
| |
| putreg32(0, USB_EPCONF(i)); |
| putreg32(0, USB_EPCOUNT(i * 2)); |
| putreg32(0, USB_EPCOUNT(i * 2 + 1)); |
| } |
| |
| g_usbdev.bufoffset = 0x180; |
| |
| /* Device mode enable */ |
| |
| modifyreg32(USB_MODE, 0, USB_MODE_DEV_EN); |
| |
| /* auto address load mode */ |
| |
| modifyreg32(USB_MODE, 0, USB_MODE_ADDR_LDMOD); |
| modifyreg32(USB_MODE, 0, USB_MODE_DEV_INTMOD); |
| |
| ret = CLASS_BIND(driver, &g_usbdev.usbdev); |
| |
| if (ret) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_BINDFAILED), (uint16_t)-ret); |
| g_usbdev.driver = NULL; |
| return ret; |
| } |
| else |
| { |
| /* Bus Reset End interrupt */ |
| |
| modifyreg32(USB_DEVC, 0, USB_DEVC_USBRSTE); |
| |
| /* Setup start interrupt */ |
| |
| modifyreg32(USB_DEVC, 0, USB_DEVC_SETUP); |
| |
| /* Setup sus/res interrupt */ |
| |
| modifyreg32(USB_DEVC, 0, USB_DEVC_SUSPENDB | USB_DEVC_SUSPENDE); |
| |
| /* Enable USB controller interrupts */ |
| |
| putreg32(0xffff0002, USB_INTEN); |
| |
| g_usbdev.eplist[0].type = 0; |
| g_usbdev.eplist[0].in = 0; |
| lc823450_epconfigure(&g_usbdev.eplist[0].ep, NULL, 0); |
| |
| lc823450_usbpullup(&g_usbdev.usbdev, true); |
| |
| /* Detect AC-Charger */ |
| |
| /* clear sof intr */ |
| |
| putreg32(~USB_DEVS_SOF, USB_DEVS); |
| |
| nxsig_usleep(100000); |
| |
| /* SOF is not arrived & D+/D- is HIGH */ |
| |
| if (!(getreg32(USB_DEVS) & USB_DEVS_SOF) && |
| (getreg32(USBSTAT) & USBSTAT_LINESTE_MASK) == |
| (USBSTAT_LINESTE_0 | USBSTAT_LINESTE_1)) |
| { |
| #ifdef CONFIG_USBDEV_CHARGER |
| g_usbdev.charger = 1; |
| #endif /* CONFIG_USBDEV_CHARGER */ |
| uinfo("charger detect\n"); |
| #if defined(CONFIG_BATTERY) && !defined(CONFIG_BATTERY_DISABLE_CHARGE) |
| battery_sendevent(BATTERY_USBEV_CHG); |
| #endif |
| } |
| |
| #ifdef CONFIG_PM |
| pm_register(&g_pm_cb); |
| #endif /* CONFIG_PM */ |
| up_enable_irq(LC823450_IRQ_USBDEV); |
| } |
| |
| #if defined(CONFIG_LC823450_USBDEV_CUSTOM_HSDSEL_5) |
| modifyreg32(USB_CUSTOMC, USB_CUSTOMC_HSDSEL_MASK, USB_CUSTOMC_HSDSEL_5); |
| #elif defined(CONFIG_LC823450_USBDEV_CUSTOM_HSDSEL_10) |
| modifyreg32(USB_CUSTOMC, USB_CUSTOMC_HSDSEL_MASK, USB_CUSTOMC_HSDSEL_10); |
| #endif |
| |
| ret = 0; |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_unregister |
| * |
| * Description: |
| * Un-register usbdev class driver. If the USB device is connected to a |
| * USB host, it will first disconnect(). The driver is also requested to |
| * unbind() and clean up any device state, before this procedure finally |
| * returns. |
| * |
| ****************************************************************************/ |
| |
| int usbdev_unregister(struct usbdevclass_driver_s *driver) |
| { |
| /* At present, there is only a single USB device support. Hence it is |
| * pre-allocated as g_otgfsdev. However, in most code, the private data |
| * structure will be referenced using the 'priv' pointer (rather than the |
| * global data) in order to simplify any future support for multiple |
| * devices. |
| */ |
| |
| struct lc823450_usbdev_s *priv = &g_usbdev; |
| irqstate_t flags; |
| |
| usbtrace(TRACE_DEVUNREGISTER, 0); |
| |
| #ifdef CONFIG_DEBUG |
| if (driver != priv->driver) |
| { |
| usbtrace(TRACE_DEVERROR(LC823450_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Reset the hardware and cancel all requests. All requests must be |
| * canceled while the class driver is still bound. |
| */ |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| #ifdef CONFIG_WAKELOCK |
| /* cancel USB suspend work */ |
| |
| work_cancel(HPWORK, &g_suspend_work); |
| #endif |
| |
| /* Unbind the class driver */ |
| |
| CLASS_UNBIND(driver, &priv->usbdev); |
| |
| /* Disable interrupts */ |
| |
| up_disable_irq(LC823450_IRQ_USBDEV); |
| |
| /* Disconnect device */ |
| |
| lc823450_usbpullup(&priv->usbdev, false); |
| |
| /* Unhook the driver */ |
| |
| modifyreg32(MCLKCNTBASIC, MCLKCNTBASIC_USBDEV_CLKEN, 0); |
| modifyreg32(USBCNT, 0, USBCNT_ANPD); |
| modifyreg32(MRSTCNTBASIC, MRSTCNTBASIC_USBDEV_RSTB, 0); |
| |
| #ifdef CONFIG_USBDEV_CHARGER |
| priv->charger = 0; |
| #endif /* CONFIG_USBDEV_CHARGER */ |
| |
| priv->driver = NULL; |
| |
| #ifdef CONFIG_PM |
| pm_unregister(&g_pm_cb); |
| #endif /* CONFIG_PM */ |
| |
| spin_unlock_irqrestore(NULL, flags); |
| |
| #ifdef CONFIG_LC823450_LSISTBY |
| /* disable USB */ |
| |
| mod_stby_regs(0, LSISTBY_STBYE); |
| #endif |
| |
| #ifdef CONFIG_WAKELOCK |
| wake_unlock(&priv->wlock); |
| #endif /* CONFIG_WAKELOCK */ |
| |
| return OK; |
| } |
| |
| /* FOR USBMSC optimization */ |
| |
| #ifdef CONFIG_USBMSC_OPT |
| /**************************************************************************** |
| * Name: usbdev_msc_read_enter |
| ****************************************************************************/ |
| |
| void usbdev_msc_read_enter(void) |
| { |
| struct lc823450_ep_s *privep; |
| # ifdef CONFIG_DVFS |
| lc823450_dvfs_boost(DVFS_BOOST_TIMEOUT); |
| # endif |
| |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKIN]; |
| privep->epcmd &= ~USB_EPCMD_EMPTY_EN; |
| epcmd_write(CONFIG_USBMSC_EPBULKIN, (privep->epcmd)); |
| lc823450_dmareauest_dir(g_hdma, DMA_REQUEST_USBDEV, 1); |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_read_exit |
| ****************************************************************************/ |
| |
| void usbdev_msc_read_exit(void) |
| { |
| struct lc823450_ep_s *privep; |
| |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKIN]; |
| privep->epcmd |= USB_EPCMD_EMPTY_EN; |
| epcmd_write(CONFIG_USBMSC_EPBULKIN, (privep->epcmd)); |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_dma_callback |
| ****************************************************************************/ |
| |
| static void usbdev_dma_callback(DMA_HANDLE hd, void *arg, int result) |
| { |
| sem_t *waitsem = (sem_t *)arg; |
| nxsem_post(waitsem); |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_epwrite |
| ****************************************************************************/ |
| |
| int usbdev_msc_epwrite(void *buf, int len) |
| { |
| int i; |
| struct lc823450_ep_s *privep; |
| uint32_t ctrl; |
| uint32_t pksz; |
| |
| pksz = g_usbdev.eplist[CONFIG_USBMSC_EPBULKIN].ep.maxpacket; |
| |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKIN]; |
| |
| ctrl = LC823450_DMA_SRCWIDTH_BYTE | |
| LC823450_DMA_DSTWIDTH_BYTE | |
| LC823450_DMA_SRCINC | LC823450_DMA_DSTINC | |
| LC823450_DMA_BS_64 << LC823450_DMA_SBS_SHIFT | |
| LC823450_DMA_BS_64 << LC823450_DMA_DBS_SHIFT; |
| |
| /* create dma link list */ |
| |
| for (i = 1; i < len / pksz; i++) |
| { |
| g_dma_list[i - 1].srcaddr = (uint32_t)buf + i * pksz; |
| g_dma_list[i - 1].dstaddr = (uint32_t)privep->inbuf + 0x8000; |
| if (i == (len / pksz) - 1) |
| { |
| /* last link */ |
| |
| g_dma_list[i - 1].nextlli = 0; |
| g_dma_list[i - 1].ctrl = ctrl | LC823450_DMA_ITC | pksz; |
| } |
| else |
| { |
| g_dma_list[i - 1].nextlli = (uint32_t)&g_dma_list[i]; |
| g_dma_list[i - 1].ctrl = ctrl | pksz; |
| } |
| } |
| |
| lc823450_dmallsetup(g_hdma, |
| ctrl, |
| (uint32_t)buf, |
| (uint32_t)privep->inbuf + 0x8000, |
| pksz, |
| len <= pksz ? 0 : (uint32_t)g_dma_list); |
| |
| lc823450_dmastart(g_hdma, usbdev_dma_callback, &dma_wait); |
| |
| putreg32(len, USB_DMATCI1); |
| |
| putreg32(64 << USB_DMAC_BSIZE_SHIFT | |
| CONFIG_USBMSC_EPBULKIN << USB_DMAC_DMAEP_SHIFT | |
| USB_DMAC_START, |
| USB_DMAC1); |
| |
| nxsem_wait(&dma_wait); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_write_enter0 |
| ****************************************************************************/ |
| |
| void usbdev_msc_write_enter0(void) |
| { |
| struct lc823450_ep_s *privep; |
| |
| # ifdef CONFIG_DVFS |
| lc823450_dvfs_boost(DVFS_BOOST_TIMEOUT); |
| # endif |
| |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKOUT]; |
| privep->epcmd &= ~USB_EPCMD_READY_EN; |
| epcmd_write(CONFIG_USBMSC_EPBULKOUT, (privep->epcmd)); |
| lc823450_dmareauest_dir(g_hdma, DMA_REQUEST_USBDEV, 0); |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_write_enter |
| ****************************************************************************/ |
| |
| void usbdev_msc_write_enter(void) |
| { |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_write_exit |
| ****************************************************************************/ |
| |
| void usbdev_msc_write_exit(void) |
| { |
| struct lc823450_ep_s *privep; |
| |
| epcmd_write(CONFIG_USBMSC_EPBULKOUT, USB_EPCMD_READY_CLR); |
| |
| /* Discard garbage packet: for USBCV MSCTEST11 */ |
| |
| epcmd_write(CONFIG_USBMSC_EPBULKOUT, USB_EPCMD_BUFRD); |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKOUT]; |
| privep->epcmd |= USB_EPCMD_READY_EN; |
| epcmd_write(CONFIG_USBMSC_EPBULKOUT, (privep->epcmd)); |
| } |
| |
| /**************************************************************************** |
| * Name: usbdev_msc_epread |
| ****************************************************************************/ |
| |
| int usbdev_msc_epread(void *buf, int len) |
| { |
| int i; |
| struct lc823450_ep_s *privep; |
| uint32_t ctrl; |
| uint32_t pksz; |
| |
| pksz = g_usbdev.eplist[CONFIG_USBMSC_EPBULKOUT].ep.maxpacket; |
| |
| privep = &g_usbdev.eplist[CONFIG_USBMSC_EPBULKOUT]; |
| |
| ctrl = LC823450_DMA_SRCWIDTH_BYTE | |
| LC823450_DMA_DSTWIDTH_BYTE | |
| LC823450_DMA_SRCINC | LC823450_DMA_DSTINC | |
| LC823450_DMA_BS_64 << LC823450_DMA_SBS_SHIFT | |
| LC823450_DMA_BS_64 << LC823450_DMA_DBS_SHIFT; |
| |
| /* create dma link list */ |
| |
| for (i = 1; i < len / pksz; i++) |
| { |
| g_dma_list[i - 1].srcaddr = (uint32_t)privep->outbuf + 0x8000; |
| g_dma_list[i - 1].dstaddr = (uint32_t)buf + i * pksz; |
| if (i == (len / pksz) - 1) |
| { |
| /* last link */ |
| |
| g_dma_list[i - 1].nextlli = 0; |
| g_dma_list[i - 1].ctrl = ctrl | LC823450_DMA_ITC | pksz; |
| } |
| else |
| { |
| g_dma_list[i - 1].nextlli = (uint32_t)&g_dma_list[i]; |
| g_dma_list[i - 1].ctrl = ctrl | pksz; |
| } |
| } |
| |
| lc823450_dmallsetup(g_hdma, |
| ctrl, |
| (uint32_t)privep->outbuf + 0x8000, |
| (uint32_t)buf, |
| pksz, |
| len <= pksz ? 0 : (uint32_t)g_dma_list); |
| |
| lc823450_dmastart(g_hdma, usbdev_dma_callback, &dma_wait); |
| |
| putreg32(len, USB_DMATCI1); |
| putreg32(64 << USB_DMAC_BSIZE_SHIFT | |
| CONFIG_USBMSC_EPBULKOUT << USB_DMAC_DMAEP_SHIFT | |
| USB_DMAC_START, |
| USB_DMAC1); |
| nxsem_wait(&dma_wait); |
| |
| return 0; |
| } |
| |
| void usbdev_msc_stop(void) |
| { |
| lc823450_dmastop(g_hdma); |
| nxsem_post(&dma_wait); |
| } |
| #endif /* CONFIG_USBMSC */ |
| |
| #ifdef CONFIG_USBDEV_CHARGER |
| /**************************************************************************** |
| * Name: usbdev_is_usbcharger |
| * |
| * return value : 0 : charger was not detected. |
| * !0 : charger was detected. |
| ****************************************************************************/ |
| |
| int usbdev_is_usbcharger(void) |
| { |
| return g_usbdev.charger; |
| } |
| #endif |
| |
| #ifdef CONFIG_PM |
| static void usbdev_pmnotify(struct pm_callback_s *cb, |
| enum pm_state_e pmstate) |
| { |
| irqstate_t flags; |
| |
| flags = spin_lock_irqsave(NULL); |
| |
| switch (pmstate) |
| { |
| case PM_SLEEP: |
| modifyreg32(USBCNT, 0, USBCNT_RSM_CONT); |
| g_usbdev.suspended = 1; |
| break; |
| |
| case PM_NORMAL: |
| modifyreg32(USBCNT, USBCNT_RSM_CONT, 0); |
| g_usbdev.suspended = 0; |
| if (g_usbdev.driver) |
| { |
| CLASS_RESUME(g_usbdev.driver, &g_usbdev.usbdev); |
| } |
| |
| break; |
| |
| default: |
| break; |
| } |
| |
| spin_unlock_irqrestore(NULL, flags); |
| } |
| #endif |