Merge branch 'for-gadget/next' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next
* 'for-gadget/next' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb: (24 commits) usb: dwc3: gadget: add support for SG lists usb: dwc3: gadget: don't force 'LST' always usb: dwc3: gadget: don't return anything on prepare trbs usb: dwc3: gadget: re-factor dwc3_prepare_trbs() usb: gadget: introduce support for sg lists usb: renesas: pipe: convert a long if into a XOR operation usb: gadget: remove useless depends on Kconfig usb: gadget: s3c-hsudc: remove the_controller global usb: gadget: s3c-hsudc: use release_mem_region instead of release_resource usb: gadget: s3c-hsudc: Add regulator handling usb: gadget: s3c-hsudc: use udc_start and udc_stop functions usb: gadget: s3c-hsudc: move device registration to probe usb: gadget: s3c-hsudc: add missing otg_put_transceiver in probe usb: gadget: s3c-hsudc: add __devinit to probe function usb: gadget: s3c-hsudc: move platform_data struct to global header USB: EHCI: Add Marvell Host Controller driver USB: OTG: add Marvell usb OTG driver support usb: gadget: mv_udc: drop ARCH dependency usb: gadget: mv_udc: fix bug in ep_dequeue usb: gadget: enlarge maxburst bit width. ...
This commit is contained in:
commit
ee0db58ade
|
@ -50,6 +50,7 @@
|
|||
#include <plat/nand.h>
|
||||
#include <plat/sdhci.h>
|
||||
#include <plat/udc.h>
|
||||
#include <linux/platform_data/s3c-hsudc.h>
|
||||
|
||||
#include <plat/regs-fb-v4.h>
|
||||
#include <plat/fb.h>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_data/s3c-hsudc.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm/pmu.h>
|
||||
|
|
|
@ -37,20 +37,7 @@ struct s3c2410_udc_mach_info {
|
|||
|
||||
extern void __init s3c24xx_udc_set_platdata(struct s3c2410_udc_mach_info *);
|
||||
|
||||
/**
|
||||
* s3c24xx_hsudc_platdata - Platform data for USB High-Speed gadget controller.
|
||||
* @epnum: Number of endpoints to be instantiated by the controller driver.
|
||||
* @gpio_init: Platform specific USB related GPIO initialization.
|
||||
* @gpio_uninit: Platform specific USB releted GPIO uninitialzation.
|
||||
*
|
||||
* Representation of platform data for the S3C24XX USB 2.0 High Speed gadget
|
||||
* controllers.
|
||||
*/
|
||||
struct s3c24xx_hsudc_platdata {
|
||||
unsigned int epnum;
|
||||
void (*gpio_init)(void);
|
||||
void (*gpio_uninit)(void);
|
||||
};
|
||||
struct s3c24xx_hsudc_platdata;
|
||||
|
||||
extern void __init s3c24xx_hsudc_set_platdata(struct s3c24xx_hsudc_platdata *pd);
|
||||
|
||||
|
|
|
@ -65,6 +65,22 @@ void dwc3_map_buffer_to_dma(struct dwc3_request *req)
|
|||
return;
|
||||
}
|
||||
|
||||
if (req->request.num_sgs) {
|
||||
int mapped;
|
||||
|
||||
mapped = dma_map_sg(dwc->dev, req->request.sg,
|
||||
req->request.num_sgs,
|
||||
req->direction ? DMA_TO_DEVICE
|
||||
: DMA_FROM_DEVICE);
|
||||
if (mapped < 0) {
|
||||
dev_err(dwc->dev, "failed to map SGs\n");
|
||||
return;
|
||||
}
|
||||
|
||||
req->request.num_mapped_sgs = mapped;
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->request.dma == DMA_ADDR_INVALID) {
|
||||
req->request.dma = dma_map_single(dwc->dev, req->request.buf,
|
||||
req->request.length, req->direction
|
||||
|
@ -82,6 +98,17 @@ void dwc3_unmap_buffer_from_dma(struct dwc3_request *req)
|
|||
return;
|
||||
}
|
||||
|
||||
if (req->request.num_mapped_sgs) {
|
||||
req->request.dma = DMA_ADDR_INVALID;
|
||||
dma_unmap_sg(dwc->dev, req->request.sg,
|
||||
req->request.num_sgs,
|
||||
req->direction ? DMA_TO_DEVICE
|
||||
: DMA_FROM_DEVICE);
|
||||
|
||||
req->request.num_mapped_sgs = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->mapped) {
|
||||
dma_unmap_single(dwc->dev, req->request.dma,
|
||||
req->request.length, req->direction
|
||||
|
@ -97,7 +124,11 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
|
|||
struct dwc3 *dwc = dep->dwc;
|
||||
|
||||
if (req->queued) {
|
||||
dep->busy_slot++;
|
||||
if (req->request.num_mapped_sgs)
|
||||
dep->busy_slot += req->request.num_mapped_sgs;
|
||||
else
|
||||
dep->busy_slot++;
|
||||
|
||||
/*
|
||||
* Skip LINK TRB. We can't use req->trb and check for
|
||||
* DWC3_TRBCTL_LINK_TRB because it points the TRB we just
|
||||
|
@ -108,6 +139,7 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
|
|||
dep->busy_slot++;
|
||||
}
|
||||
list_del(&req->list);
|
||||
req->trb = NULL;
|
||||
|
||||
if (req->request.status == -EINPROGRESS)
|
||||
req->request.status = status;
|
||||
|
@ -544,6 +576,85 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep,
|
|||
kfree(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* dwc3_prepare_one_trb - setup one TRB from one request
|
||||
* @dep: endpoint for which this request is prepared
|
||||
* @req: dwc3_request pointer
|
||||
*/
|
||||
static void dwc3_prepare_one_trb(struct dwc3_ep *dep,
|
||||
struct dwc3_request *req, dma_addr_t dma,
|
||||
unsigned length, unsigned last, unsigned chain)
|
||||
{
|
||||
struct dwc3 *dwc = dep->dwc;
|
||||
struct dwc3_trb_hw *trb_hw;
|
||||
struct dwc3_trb trb;
|
||||
|
||||
unsigned int cur_slot;
|
||||
|
||||
dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n",
|
||||
dep->name, req, (unsigned long long) dma,
|
||||
length, last ? " last" : "",
|
||||
chain ? " chain" : "");
|
||||
|
||||
trb_hw = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK];
|
||||
cur_slot = dep->free_slot;
|
||||
dep->free_slot++;
|
||||
|
||||
/* Skip the LINK-TRB on ISOC */
|
||||
if (((cur_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) &&
|
||||
usb_endpoint_xfer_isoc(dep->desc))
|
||||
return;
|
||||
|
||||
memset(&trb, 0, sizeof(trb));
|
||||
if (!req->trb) {
|
||||
dwc3_gadget_move_request_queued(req);
|
||||
req->trb = trb_hw;
|
||||
req->trb_dma = dwc3_trb_dma_offset(dep, trb_hw);
|
||||
}
|
||||
|
||||
if (usb_endpoint_xfer_isoc(dep->desc)) {
|
||||
trb.isp_imi = true;
|
||||
trb.csp = true;
|
||||
} else {
|
||||
trb.chn = chain;
|
||||
trb.lst = last;
|
||||
}
|
||||
|
||||
if (usb_endpoint_xfer_bulk(dep->desc) && dep->stream_capable)
|
||||
trb.sid_sofn = req->request.stream_id;
|
||||
|
||||
switch (usb_endpoint_type(dep->desc)) {
|
||||
case USB_ENDPOINT_XFER_CONTROL:
|
||||
trb.trbctl = DWC3_TRBCTL_CONTROL_SETUP;
|
||||
break;
|
||||
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
trb.trbctl = DWC3_TRBCTL_ISOCHRONOUS_FIRST;
|
||||
|
||||
/* IOC every DWC3_TRB_NUM / 4 so we can refill */
|
||||
if (!(cur_slot % (DWC3_TRB_NUM / 4)))
|
||||
trb.ioc = last;
|
||||
break;
|
||||
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
trb.trbctl = DWC3_TRBCTL_NORMAL;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* This is only possible with faulty memory because we
|
||||
* checked it already :)
|
||||
*/
|
||||
BUG();
|
||||
}
|
||||
|
||||
trb.length = length;
|
||||
trb.bplh = dma;
|
||||
trb.hwo = true;
|
||||
|
||||
dwc3_trb_to_hw(&trb, trb_hw);
|
||||
}
|
||||
|
||||
/*
|
||||
* dwc3_prepare_trbs - setup TRBs from requests
|
||||
* @dep: endpoint for which requests are being prepared
|
||||
|
@ -553,18 +664,17 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep,
|
|||
* transfers. The functions returns once there are not more TRBs available or
|
||||
* it run out of requests.
|
||||
*/
|
||||
static struct dwc3_request *dwc3_prepare_trbs(struct dwc3_ep *dep,
|
||||
bool starting)
|
||||
static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting)
|
||||
{
|
||||
struct dwc3_request *req, *n, *ret = NULL;
|
||||
struct dwc3_trb_hw *trb_hw;
|
||||
struct dwc3_trb trb;
|
||||
struct dwc3_request *req, *n;
|
||||
u32 trbs_left;
|
||||
unsigned int last_one = 0;
|
||||
|
||||
BUILD_BUG_ON_NOT_POWER_OF_2(DWC3_TRB_NUM);
|
||||
|
||||
/* the first request must not be queued */
|
||||
trbs_left = (dep->busy_slot - dep->free_slot) & DWC3_TRB_MASK;
|
||||
|
||||
/*
|
||||
* if busy & slot are equal than it is either full or empty. If we are
|
||||
* starting to proceed requests then we are empty. Otherwise we ar
|
||||
|
@ -572,7 +682,7 @@ static struct dwc3_request *dwc3_prepare_trbs(struct dwc3_ep *dep,
|
|||
*/
|
||||
if (!trbs_left) {
|
||||
if (!starting)
|
||||
return NULL;
|
||||
return;
|
||||
trbs_left = DWC3_TRB_NUM;
|
||||
/*
|
||||
* In case we start from scratch, we queue the ISOC requests
|
||||
|
@ -596,94 +706,62 @@ static struct dwc3_request *dwc3_prepare_trbs(struct dwc3_ep *dep,
|
|||
|
||||
/* The last TRB is a link TRB, not used for xfer */
|
||||
if ((trbs_left <= 1) && usb_endpoint_xfer_isoc(dep->desc))
|
||||
return NULL;
|
||||
return;
|
||||
|
||||
list_for_each_entry_safe(req, n, &dep->request_list, list) {
|
||||
unsigned int last_one = 0;
|
||||
unsigned int cur_slot;
|
||||
unsigned length;
|
||||
dma_addr_t dma;
|
||||
|
||||
trb_hw = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK];
|
||||
cur_slot = dep->free_slot;
|
||||
dep->free_slot++;
|
||||
if (req->request.num_mapped_sgs > 0) {
|
||||
struct usb_request *request = &req->request;
|
||||
struct scatterlist *sg = request->sg;
|
||||
struct scatterlist *s;
|
||||
int i;
|
||||
|
||||
/* Skip the LINK-TRB on ISOC */
|
||||
if (((cur_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) &&
|
||||
usb_endpoint_xfer_isoc(dep->desc))
|
||||
continue;
|
||||
for_each_sg(sg, s, request->num_mapped_sgs, i) {
|
||||
unsigned chain = true;
|
||||
|
||||
dwc3_gadget_move_request_queued(req);
|
||||
memset(&trb, 0, sizeof(trb));
|
||||
trbs_left--;
|
||||
length = sg_dma_len(s);
|
||||
dma = sg_dma_address(s);
|
||||
|
||||
/* Is our TRB pool empty? */
|
||||
if (!trbs_left)
|
||||
last_one = 1;
|
||||
/* Is this the last request? */
|
||||
if (list_empty(&dep->request_list))
|
||||
last_one = 1;
|
||||
if (i == (request->num_mapped_sgs - 1)
|
||||
|| sg_is_last(s)) {
|
||||
last_one = true;
|
||||
chain = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME we shouldn't need to set LST bit always but we are
|
||||
* facing some weird problem with the Hardware where it doesn't
|
||||
* complete even though it has been previously started.
|
||||
*
|
||||
* While we're debugging the problem, as a workaround to
|
||||
* multiple TRBs handling, use only one TRB at a time.
|
||||
*/
|
||||
last_one = 1;
|
||||
trbs_left--;
|
||||
if (!trbs_left)
|
||||
last_one = true;
|
||||
|
||||
req->trb = trb_hw;
|
||||
if (!ret)
|
||||
ret = req;
|
||||
if (last_one)
|
||||
chain = false;
|
||||
|
||||
trb.bplh = req->request.dma;
|
||||
dwc3_prepare_one_trb(dep, req, dma, length,
|
||||
last_one, chain);
|
||||
|
||||
if (usb_endpoint_xfer_isoc(dep->desc)) {
|
||||
trb.isp_imi = true;
|
||||
trb.csp = true;
|
||||
if (last_one)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
trb.lst = last_one;
|
||||
dma = req->request.dma;
|
||||
length = req->request.length;
|
||||
trbs_left--;
|
||||
|
||||
if (!trbs_left)
|
||||
last_one = 1;
|
||||
|
||||
/* Is this the last request? */
|
||||
if (list_is_last(&req->list, &dep->request_list))
|
||||
last_one = 1;
|
||||
|
||||
dwc3_prepare_one_trb(dep, req, dma, length,
|
||||
last_one, false);
|
||||
|
||||
if (last_one)
|
||||
break;
|
||||
}
|
||||
|
||||
if (usb_endpoint_xfer_bulk(dep->desc) && dep->stream_capable)
|
||||
trb.sid_sofn = req->request.stream_id;
|
||||
|
||||
switch (usb_endpoint_type(dep->desc)) {
|
||||
case USB_ENDPOINT_XFER_CONTROL:
|
||||
trb.trbctl = DWC3_TRBCTL_CONTROL_SETUP;
|
||||
break;
|
||||
|
||||
case USB_ENDPOINT_XFER_ISOC:
|
||||
trb.trbctl = DWC3_TRBCTL_ISOCHRONOUS_FIRST;
|
||||
|
||||
/* IOC every DWC3_TRB_NUM / 4 so we can refill */
|
||||
if (!(cur_slot % (DWC3_TRB_NUM / 4)))
|
||||
trb.ioc = last_one;
|
||||
break;
|
||||
|
||||
case USB_ENDPOINT_XFER_BULK:
|
||||
case USB_ENDPOINT_XFER_INT:
|
||||
trb.trbctl = DWC3_TRBCTL_NORMAL;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* This is only possible with faulty memory because we
|
||||
* checked it already :)
|
||||
*/
|
||||
BUG();
|
||||
}
|
||||
|
||||
trb.length = req->request.length;
|
||||
trb.hwo = true;
|
||||
|
||||
dwc3_trb_to_hw(&trb, trb_hw);
|
||||
req->trb_dma = dwc3_trb_dma_offset(dep, trb_hw);
|
||||
|
||||
if (last_one)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param,
|
||||
|
@ -712,11 +790,13 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param,
|
|||
/* req points to the first request which will be sent */
|
||||
req = next_request(&dep->req_queued);
|
||||
} else {
|
||||
dwc3_prepare_trbs(dep, start_new);
|
||||
|
||||
/*
|
||||
* req points to the first request where HWO changed
|
||||
* from 0 to 1
|
||||
*/
|
||||
req = dwc3_prepare_trbs(dep, start_new);
|
||||
req = next_request(&dep->req_queued);
|
||||
}
|
||||
if (!req) {
|
||||
dep->flags |= DWC3_EP_PENDING_REQUEST;
|
||||
|
@ -2093,6 +2173,7 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc)
|
|||
dwc->gadget.max_speed = USB_SPEED_SUPER;
|
||||
dwc->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
dwc->gadget.dev.parent = dwc->dev;
|
||||
dwc->gadget.sg_supported = true;
|
||||
|
||||
dma_set_coherent_mask(&dwc->gadget.dev, dwc->dev->coherent_dma_mask);
|
||||
|
||||
|
|
|
@ -125,7 +125,6 @@ config USB_GADGET_STORAGE_NUM_BUFFERS
|
|||
#
|
||||
choice
|
||||
prompt "USB Peripheral Controller"
|
||||
depends on USB_GADGET
|
||||
help
|
||||
A USB device uses a controller to talk to its host.
|
||||
Systems should have only one such upstream link.
|
||||
|
@ -310,13 +309,13 @@ config USB_S3C_HSUDC
|
|||
|
||||
This driver has been tested on S3C2416 and S3C2450 processors.
|
||||
|
||||
config USB_PXA_U2O
|
||||
tristate "PXA9xx Processor USB2.0 controller"
|
||||
depends on ARCH_MMP
|
||||
config USB_MV_UDC
|
||||
tristate "Marvell USB2.0 Device Controller"
|
||||
select USB_GADGET_DUALSPEED
|
||||
help
|
||||
PXA9xx Processor series include a high speed USB2.0 device
|
||||
controller, which support high speed and full speed USB peripheral.
|
||||
Marvell Socs (including PXA and MMP series) include a high speed
|
||||
USB2.0 OTG controller, which can be configured as high speed or
|
||||
full speed USB peripheral.
|
||||
|
||||
#
|
||||
# Controllers available in both integrated and discrete versions
|
||||
|
@ -532,12 +531,10 @@ endchoice
|
|||
# Selected by UDC drivers that support high-speed operation.
|
||||
config USB_GADGET_DUALSPEED
|
||||
bool
|
||||
depends on USB_GADGET
|
||||
|
||||
# Selected by UDC drivers that support super-speed opperation
|
||||
config USB_GADGET_SUPERSPEED
|
||||
bool
|
||||
depends on USB_GADGET
|
||||
depends on USB_GADGET_DUALSPEED
|
||||
|
||||
#
|
||||
|
@ -545,7 +542,6 @@ config USB_GADGET_SUPERSPEED
|
|||
#
|
||||
choice
|
||||
tristate "USB Gadget Drivers"
|
||||
depends on USB_GADGET
|
||||
default USB_ETH
|
||||
help
|
||||
A Linux "Gadget Driver" talks to the USB Peripheral Controller
|
||||
|
|
|
@ -27,7 +27,7 @@ obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o
|
|||
obj-$(CONFIG_USB_S3C_HSUDC) += s3c-hsudc.o
|
||||
obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o
|
||||
obj-$(CONFIG_USB_EG20T) += pch_udc.o
|
||||
obj-$(CONFIG_USB_PXA_U2O) += mv_udc.o
|
||||
obj-$(CONFIG_USB_MV_UDC) += mv_udc.o
|
||||
mv_udc-y := mv_udc_core.o
|
||||
obj-$(CONFIG_USB_CI13XXX_MSM) += ci13xxx_msm.o
|
||||
obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o
|
||||
|
|
|
@ -180,7 +180,7 @@ struct mv_udc {
|
|||
|
||||
struct mv_cap_regs __iomem *cap_regs;
|
||||
struct mv_op_regs __iomem *op_regs;
|
||||
unsigned int phy_regs;
|
||||
void __iomem *phy_regs;
|
||||
unsigned int max_eps;
|
||||
struct mv_dqh *ep_dqh;
|
||||
size_t ep_dqh_size;
|
||||
|
|
|
@ -276,11 +276,12 @@ static void done(struct mv_ep *ep, struct mv_req *req, int status)
|
|||
|
||||
static int queue_dtd(struct mv_ep *ep, struct mv_req *req)
|
||||
{
|
||||
u32 tmp, epstatus, bit_pos, direction;
|
||||
struct mv_udc *udc;
|
||||
struct mv_dqh *dqh;
|
||||
u32 bit_pos, direction;
|
||||
u32 usbcmd, epstatus;
|
||||
unsigned int loops;
|
||||
int readsafe, retval = 0;
|
||||
int retval = 0;
|
||||
|
||||
udc = ep->udc;
|
||||
direction = ep_dir(ep);
|
||||
|
@ -293,30 +294,18 @@ static int queue_dtd(struct mv_ep *ep, struct mv_req *req)
|
|||
lastreq = list_entry(ep->queue.prev, struct mv_req, queue);
|
||||
lastreq->tail->dtd_next =
|
||||
req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK;
|
||||
if (readl(&udc->op_regs->epprime) & bit_pos) {
|
||||
loops = LOOPS(PRIME_TIMEOUT);
|
||||
while (readl(&udc->op_regs->epprime) & bit_pos) {
|
||||
if (loops == 0) {
|
||||
retval = -ETIME;
|
||||
goto done;
|
||||
}
|
||||
udelay(LOOPS_USEC);
|
||||
loops--;
|
||||
}
|
||||
if (readl(&udc->op_regs->epstatus) & bit_pos)
|
||||
goto done;
|
||||
}
|
||||
readsafe = 0;
|
||||
|
||||
wmb();
|
||||
|
||||
if (readl(&udc->op_regs->epprime) & bit_pos)
|
||||
goto done;
|
||||
|
||||
loops = LOOPS(READSAFE_TIMEOUT);
|
||||
while (readsafe == 0) {
|
||||
if (loops == 0) {
|
||||
retval = -ETIME;
|
||||
goto done;
|
||||
}
|
||||
while (1) {
|
||||
/* start with setting the semaphores */
|
||||
tmp = readl(&udc->op_regs->usbcmd);
|
||||
tmp |= USBCMD_ATDTW_TRIPWIRE_SET;
|
||||
writel(tmp, &udc->op_regs->usbcmd);
|
||||
usbcmd = readl(&udc->op_regs->usbcmd);
|
||||
usbcmd |= USBCMD_ATDTW_TRIPWIRE_SET;
|
||||
writel(usbcmd, &udc->op_regs->usbcmd);
|
||||
|
||||
/* read the endpoint status */
|
||||
epstatus = readl(&udc->op_regs->epstatus) & bit_pos;
|
||||
|
@ -329,98 +318,46 @@ static int queue_dtd(struct mv_ep *ep, struct mv_req *req)
|
|||
* primed.
|
||||
*/
|
||||
if (readl(&udc->op_regs->usbcmd)
|
||||
& USBCMD_ATDTW_TRIPWIRE_SET) {
|
||||
readsafe = 1;
|
||||
}
|
||||
& USBCMD_ATDTW_TRIPWIRE_SET)
|
||||
break;
|
||||
|
||||
loops--;
|
||||
if (loops == 0) {
|
||||
dev_err(&udc->dev->dev,
|
||||
"Timeout for ATDTW_TRIPWIRE...\n");
|
||||
retval = -ETIME;
|
||||
goto done;
|
||||
}
|
||||
udelay(LOOPS_USEC);
|
||||
}
|
||||
|
||||
/* Clear the semaphore */
|
||||
tmp = readl(&udc->op_regs->usbcmd);
|
||||
tmp &= USBCMD_ATDTW_TRIPWIRE_CLEAR;
|
||||
writel(tmp, &udc->op_regs->usbcmd);
|
||||
usbcmd = readl(&udc->op_regs->usbcmd);
|
||||
usbcmd &= USBCMD_ATDTW_TRIPWIRE_CLEAR;
|
||||
writel(usbcmd, &udc->op_regs->usbcmd);
|
||||
|
||||
/* If endpoint is not active, we activate it now. */
|
||||
if (!epstatus) {
|
||||
if (direction == EP_DIR_IN) {
|
||||
struct mv_dtd *curr_dtd = dma_to_virt(
|
||||
&udc->dev->dev, dqh->curr_dtd_ptr);
|
||||
|
||||
loops = LOOPS(DTD_TIMEOUT);
|
||||
while (curr_dtd->size_ioc_sts
|
||||
& DTD_STATUS_ACTIVE) {
|
||||
if (loops == 0) {
|
||||
retval = -ETIME;
|
||||
goto done;
|
||||
}
|
||||
loops--;
|
||||
udelay(LOOPS_USEC);
|
||||
}
|
||||
}
|
||||
/* No other transfers on the queue */
|
||||
|
||||
/* Write dQH next pointer and terminate bit to 0 */
|
||||
dqh->next_dtd_ptr = req->head->td_dma
|
||||
& EP_QUEUE_HEAD_NEXT_POINTER_MASK;
|
||||
dqh->size_ioc_int_sts = 0;
|
||||
|
||||
/*
|
||||
* Ensure that updates to the QH will
|
||||
* occur before priming.
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* Prime the Endpoint */
|
||||
writel(bit_pos, &udc->op_regs->epprime);
|
||||
}
|
||||
} else {
|
||||
/* Write dQH next pointer and terminate bit to 0 */
|
||||
dqh->next_dtd_ptr = req->head->td_dma
|
||||
& EP_QUEUE_HEAD_NEXT_POINTER_MASK;
|
||||
dqh->size_ioc_int_sts = 0;
|
||||
|
||||
/* Ensure that updates to the QH will occur before priming. */
|
||||
wmb();
|
||||
|
||||
/* Prime the Endpoint */
|
||||
writel(bit_pos, &udc->op_regs->epprime);
|
||||
|
||||
if (direction == EP_DIR_IN) {
|
||||
/* FIXME add status check after prime the IN ep */
|
||||
int prime_again;
|
||||
u32 curr_dtd_ptr = dqh->curr_dtd_ptr;
|
||||
|
||||
loops = LOOPS(DTD_TIMEOUT);
|
||||
prime_again = 0;
|
||||
while ((curr_dtd_ptr != req->head->td_dma)) {
|
||||
curr_dtd_ptr = dqh->curr_dtd_ptr;
|
||||
if (loops == 0) {
|
||||
dev_err(&udc->dev->dev,
|
||||
"failed to prime %s\n",
|
||||
ep->name);
|
||||
retval = -ETIME;
|
||||
goto done;
|
||||
}
|
||||
loops--;
|
||||
udelay(LOOPS_USEC);
|
||||
|
||||
if (loops == (LOOPS(DTD_TIMEOUT) >> 2)) {
|
||||
if (prime_again)
|
||||
goto done;
|
||||
dev_info(&udc->dev->dev,
|
||||
"prime again\n");
|
||||
writel(bit_pos,
|
||||
&udc->op_regs->epprime);
|
||||
prime_again = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (epstatus)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Write dQH next pointer and terminate bit to 0 */
|
||||
dqh->next_dtd_ptr = req->head->td_dma
|
||||
& EP_QUEUE_HEAD_NEXT_POINTER_MASK;
|
||||
|
||||
/* clear active and halt bit, in case set from a previous error */
|
||||
dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED);
|
||||
|
||||
/* Ensure that updates to the QH will occure before priming. */
|
||||
wmb();
|
||||
|
||||
/* Prime the Endpoint */
|
||||
writel(bit_pos, &udc->op_regs->epprime);
|
||||
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static struct mv_dtd *build_dtd(struct mv_req *req, unsigned *length,
|
||||
dma_addr_t *dma, int *is_last)
|
||||
{
|
||||
|
@ -841,6 +778,27 @@ mv_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void mv_prime_ep(struct mv_ep *ep, struct mv_req *req)
|
||||
{
|
||||
struct mv_dqh *dqh = ep->dqh;
|
||||
u32 bit_pos;
|
||||
|
||||
/* Write dQH next pointer and terminate bit to 0 */
|
||||
dqh->next_dtd_ptr = req->head->td_dma
|
||||
& EP_QUEUE_HEAD_NEXT_POINTER_MASK;
|
||||
|
||||
/* clear active and halt bit, in case set from a previous error */
|
||||
dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED);
|
||||
|
||||
/* Ensure that updates to the QH will occure before priming. */
|
||||
wmb();
|
||||
|
||||
bit_pos = 1 << (((ep_dir(ep) == EP_DIR_OUT) ? 0 : 16) + ep->ep_num);
|
||||
|
||||
/* Prime the Endpoint */
|
||||
writel(bit_pos, &ep->udc->op_regs->epprime);
|
||||
}
|
||||
|
||||
/* dequeues (cancels, unlinks) an I/O request from an endpoint */
|
||||
static int mv_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
||||
{
|
||||
|
@ -883,15 +841,13 @@ static int mv_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
|
|||
|
||||
/* The request isn't the last request in this ep queue */
|
||||
if (req->queue.next != &ep->queue) {
|
||||
struct mv_dqh *qh;
|
||||
struct mv_req *next_req;
|
||||
|
||||
qh = ep->dqh;
|
||||
next_req = list_entry(req->queue.next, struct mv_req,
|
||||
queue);
|
||||
next_req = list_entry(req->queue.next,
|
||||
struct mv_req, queue);
|
||||
|
||||
/* Point the QH to the first TD of next request */
|
||||
writel((u32) next_req->head, &qh->curr_dtd_ptr);
|
||||
mv_prime_ep(ep, next_req);
|
||||
} else {
|
||||
struct mv_dqh *qh;
|
||||
|
||||
|
@ -1196,7 +1152,7 @@ static int mv_udc_get_frame(struct usb_gadget *gadget)
|
|||
|
||||
udc = container_of(gadget, struct mv_udc, gadget);
|
||||
|
||||
retval = readl(udc->op_regs->frindex) & USB_FRINDEX_MASKS;
|
||||
retval = readl(&udc->op_regs->frindex) & USB_FRINDEX_MASKS;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
@ -2172,11 +2128,9 @@ static int __devexit mv_udc_remove(struct platform_device *dev)
|
|||
|
||||
if (udc->cap_regs)
|
||||
iounmap(udc->cap_regs);
|
||||
udc->cap_regs = NULL;
|
||||
|
||||
if (udc->phy_regs)
|
||||
iounmap((void *)udc->phy_regs);
|
||||
udc->phy_regs = 0;
|
||||
iounmap(udc->phy_regs);
|
||||
|
||||
if (udc->status_req) {
|
||||
kfree(udc->status_req->req.buf);
|
||||
|
@ -2261,8 +2215,8 @@ static int __devinit mv_udc_probe(struct platform_device *dev)
|
|||
goto err_iounmap_capreg;
|
||||
}
|
||||
|
||||
udc->phy_regs = (unsigned int)ioremap(r->start, resource_size(r));
|
||||
if (udc->phy_regs == 0) {
|
||||
udc->phy_regs = ioremap(r->start, resource_size(r));
|
||||
if (udc->phy_regs == NULL) {
|
||||
dev_err(&dev->dev, "failed to map phy I/O memory\n");
|
||||
retval = -EBUSY;
|
||||
goto err_iounmap_capreg;
|
||||
|
@ -2273,7 +2227,8 @@ static int __devinit mv_udc_probe(struct platform_device *dev)
|
|||
if (retval)
|
||||
goto err_iounmap_phyreg;
|
||||
|
||||
udc->op_regs = (struct mv_op_regs __iomem *)((u32)udc->cap_regs
|
||||
udc->op_regs =
|
||||
(struct mv_op_regs __iomem *)((unsigned long)udc->cap_regs
|
||||
+ (readl(&udc->cap_regs->caplength_hciversion)
|
||||
& CAPLENGTH_MASK));
|
||||
udc->max_eps = readl(&udc->cap_regs->dccparams) & DCCPARAMS_DEN_MASK;
|
||||
|
@ -2433,7 +2388,7 @@ err_free_dma:
|
|||
err_disable_clock:
|
||||
mv_udc_disable_internal(udc);
|
||||
err_iounmap_phyreg:
|
||||
iounmap((void *)udc->phy_regs);
|
||||
iounmap(udc->phy_regs);
|
||||
err_iounmap_capreg:
|
||||
iounmap(udc->cap_regs);
|
||||
err_put_clk:
|
||||
|
@ -2524,7 +2479,7 @@ static struct platform_driver udc_driver = {
|
|||
.shutdown = mv_udc_shutdown,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "pxa-u2o",
|
||||
.name = "mv-udc",
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &mv_udc_pm_ops,
|
||||
#endif
|
||||
|
@ -2532,9 +2487,8 @@ static struct platform_driver udc_driver = {
|
|||
};
|
||||
|
||||
module_platform_driver(udc_driver);
|
||||
|
||||
MODULE_ALIAS("platform:mv-udc");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>");
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:pxa-u2o");
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/prefetch.h>
|
||||
#include <linux/platform_data/s3c-hsudc.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <mach/regs-s3c2443-clock.h>
|
||||
#include <plat/udc.h>
|
||||
|
||||
#define S3C_HSUDC_REG(x) (x)
|
||||
|
||||
|
@ -87,6 +88,12 @@
|
|||
#define DATA_STATE_XMIT (1)
|
||||
#define DATA_STATE_RECV (2)
|
||||
|
||||
static const char * const s3c_hsudc_supply_names[] = {
|
||||
"vdda", /* analog phy supply, 3.3V */
|
||||
"vddi", /* digital phy supply, 1.2V */
|
||||
"vddosc", /* oscillator supply, 1.8V - 3.3V */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct s3c_hsudc_ep - Endpoint representation used by driver.
|
||||
* @ep: USB gadget layer representation of device endpoint.
|
||||
|
@ -139,6 +146,7 @@ struct s3c_hsudc {
|
|||
struct device *dev;
|
||||
struct s3c24xx_hsudc_platdata *pd;
|
||||
struct otg_transceiver *transceiver;
|
||||
struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsudc_supply_names)];
|
||||
spinlock_t lock;
|
||||
void __iomem *regs;
|
||||
struct resource *mem_rsrc;
|
||||
|
@ -153,7 +161,6 @@ struct s3c_hsudc {
|
|||
#define ep_index(_ep) ((_ep)->bEndpointAddress & \
|
||||
USB_ENDPOINT_NUMBER_MASK)
|
||||
|
||||
static struct s3c_hsudc *the_controller;
|
||||
static const char driver_name[] = "s3c-udc";
|
||||
static const char ep0name[] = "ep0-control";
|
||||
|
||||
|
@ -282,8 +289,7 @@ static void s3c_hsudc_nuke_ep(struct s3c_hsudc_ep *hsep, int status)
|
|||
* All the endpoints are stopped and any pending transfer requests if any on
|
||||
* the endpoint are terminated.
|
||||
*/
|
||||
static void s3c_hsudc_stop_activity(struct s3c_hsudc *hsudc,
|
||||
struct usb_gadget_driver *driver)
|
||||
static void s3c_hsudc_stop_activity(struct s3c_hsudc *hsudc)
|
||||
{
|
||||
struct s3c_hsudc_ep *hsep;
|
||||
int epnum;
|
||||
|
@ -295,10 +301,6 @@ static void s3c_hsudc_stop_activity(struct s3c_hsudc *hsudc,
|
|||
hsep->stopped = 1;
|
||||
s3c_hsudc_nuke_ep(hsep, -ESHUTDOWN);
|
||||
}
|
||||
|
||||
spin_unlock(&hsudc->lock);
|
||||
driver->disconnect(&hsudc->gadget);
|
||||
spin_lock(&hsudc->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1135,16 +1137,15 @@ static irqreturn_t s3c_hsudc_irq(int irq, void *_dev)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int s3c_hsudc_start(struct usb_gadget_driver *driver,
|
||||
int (*bind)(struct usb_gadget *))
|
||||
static int s3c_hsudc_start(struct usb_gadget *gadget,
|
||||
struct usb_gadget_driver *driver)
|
||||
{
|
||||
struct s3c_hsudc *hsudc = the_controller;
|
||||
struct s3c_hsudc *hsudc = to_hsudc(gadget);
|
||||
int ret;
|
||||
|
||||
if (!driver
|
||||
|| driver->max_speed < USB_SPEED_FULL
|
||||
|| !bind
|
||||
|| !driver->unbind || !driver->disconnect || !driver->setup)
|
||||
|| !driver->setup)
|
||||
return -EINVAL;
|
||||
|
||||
if (!hsudc)
|
||||
|
@ -1155,21 +1156,12 @@ static int s3c_hsudc_start(struct usb_gadget_driver *driver,
|
|||
|
||||
hsudc->driver = driver;
|
||||
hsudc->gadget.dev.driver = &driver->driver;
|
||||
hsudc->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
ret = device_add(&hsudc->gadget.dev);
|
||||
if (ret) {
|
||||
dev_err(hsudc->dev, "failed to probe gadget device");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = bind(&hsudc->gadget);
|
||||
if (ret) {
|
||||
dev_err(hsudc->dev, "%s: bind failed\n", hsudc->gadget.name);
|
||||
device_del(&hsudc->gadget.dev);
|
||||
|
||||
hsudc->driver = NULL;
|
||||
hsudc->gadget.dev.driver = NULL;
|
||||
return ret;
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(hsudc->supplies),
|
||||
hsudc->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(hsudc->dev, "failed to enable supplies: %d\n", ret);
|
||||
goto err_supplies;
|
||||
}
|
||||
|
||||
/* connect to bus through transceiver */
|
||||
|
@ -1178,13 +1170,7 @@ static int s3c_hsudc_start(struct usb_gadget_driver *driver,
|
|||
if (ret) {
|
||||
dev_err(hsudc->dev, "%s: can't bind to transceiver\n",
|
||||
hsudc->gadget.name);
|
||||
driver->unbind(&hsudc->gadget);
|
||||
|
||||
device_del(&hsudc->gadget.dev);
|
||||
|
||||
hsudc->driver = NULL;
|
||||
hsudc->gadget.dev.driver = NULL;
|
||||
return ret;
|
||||
goto err_otg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1197,34 +1183,43 @@ static int s3c_hsudc_start(struct usb_gadget_driver *driver,
|
|||
hsudc->pd->gpio_init();
|
||||
|
||||
return 0;
|
||||
err_otg:
|
||||
regulator_bulk_disable(ARRAY_SIZE(hsudc->supplies), hsudc->supplies);
|
||||
err_supplies:
|
||||
hsudc->driver = NULL;
|
||||
hsudc->gadget.dev.driver = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s3c_hsudc_stop(struct usb_gadget_driver *driver)
|
||||
static int s3c_hsudc_stop(struct usb_gadget *gadget,
|
||||
struct usb_gadget_driver *driver)
|
||||
{
|
||||
struct s3c_hsudc *hsudc = the_controller;
|
||||
struct s3c_hsudc *hsudc = to_hsudc(gadget);
|
||||
unsigned long flags;
|
||||
|
||||
if (!hsudc)
|
||||
return -ENODEV;
|
||||
|
||||
if (!driver || driver != hsudc->driver || !driver->unbind)
|
||||
if (!driver || driver != hsudc->driver)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&hsudc->lock, flags);
|
||||
hsudc->driver = 0;
|
||||
hsudc->driver = NULL;
|
||||
hsudc->gadget.dev.driver = NULL;
|
||||
hsudc->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
s3c_hsudc_uninit_phy();
|
||||
if (hsudc->pd->gpio_uninit)
|
||||
hsudc->pd->gpio_uninit();
|
||||
s3c_hsudc_stop_activity(hsudc, driver);
|
||||
s3c_hsudc_stop_activity(hsudc);
|
||||
spin_unlock_irqrestore(&hsudc->lock, flags);
|
||||
|
||||
if (hsudc->transceiver)
|
||||
(void) otg_set_peripheral(hsudc->transceiver, NULL);
|
||||
|
||||
driver->unbind(&hsudc->gadget);
|
||||
device_del(&hsudc->gadget.dev);
|
||||
disable_irq(hsudc->irq);
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(hsudc->supplies), hsudc->supplies);
|
||||
|
||||
dev_info(hsudc->dev, "unregistered gadget driver '%s'\n",
|
||||
driver->driver.name);
|
||||
return 0;
|
||||
|
@ -1242,7 +1237,7 @@ static int s3c_hsudc_gadget_getframe(struct usb_gadget *gadget)
|
|||
|
||||
static int s3c_hsudc_vbus_draw(struct usb_gadget *gadget, unsigned mA)
|
||||
{
|
||||
struct s3c_hsudc *hsudc = the_controller;
|
||||
struct s3c_hsudc *hsudc = to_hsudc(gadget);
|
||||
|
||||
if (!hsudc)
|
||||
return -ENODEV;
|
||||
|
@ -1255,18 +1250,18 @@ static int s3c_hsudc_vbus_draw(struct usb_gadget *gadget, unsigned mA)
|
|||
|
||||
static struct usb_gadget_ops s3c_hsudc_gadget_ops = {
|
||||
.get_frame = s3c_hsudc_gadget_getframe,
|
||||
.start = s3c_hsudc_start,
|
||||
.stop = s3c_hsudc_stop,
|
||||
.udc_start = s3c_hsudc_start,
|
||||
.udc_stop = s3c_hsudc_stop,
|
||||
.vbus_draw = s3c_hsudc_vbus_draw,
|
||||
};
|
||||
|
||||
static int s3c_hsudc_probe(struct platform_device *pdev)
|
||||
static int __devinit s3c_hsudc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct s3c_hsudc *hsudc;
|
||||
struct s3c24xx_hsudc_platdata *pd = pdev->dev.platform_data;
|
||||
int ret;
|
||||
int ret, i;
|
||||
|
||||
hsudc = kzalloc(sizeof(struct s3c_hsudc) +
|
||||
sizeof(struct s3c_hsudc_ep) * pd->epnum,
|
||||
|
@ -1276,13 +1271,22 @@ static int s3c_hsudc_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
the_controller = hsudc;
|
||||
platform_set_drvdata(pdev, dev);
|
||||
hsudc->dev = dev;
|
||||
hsudc->pd = pdev->dev.platform_data;
|
||||
|
||||
hsudc->transceiver = otg_get_transceiver();
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hsudc->supplies); i++)
|
||||
hsudc->supplies[i].supply = s3c_hsudc_supply_names[i];
|
||||
|
||||
ret = regulator_bulk_get(dev, ARRAY_SIZE(hsudc->supplies),
|
||||
hsudc->supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "failed to request supplies: %d\n", ret);
|
||||
goto err_supplies;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(dev, "unable to obtain driver resource data\n");
|
||||
|
@ -1307,7 +1311,6 @@ static int s3c_hsudc_probe(struct platform_device *pdev)
|
|||
|
||||
spin_lock_init(&hsudc->lock);
|
||||
|
||||
device_initialize(&hsudc->gadget.dev);
|
||||
dev_set_name(&hsudc->gadget.dev, "gadget");
|
||||
|
||||
hsudc->gadget.max_speed = USB_SPEED_HIGH;
|
||||
|
@ -1319,6 +1322,7 @@ static int s3c_hsudc_probe(struct platform_device *pdev)
|
|||
|
||||
hsudc->gadget.is_otg = 0;
|
||||
hsudc->gadget.is_a_peripheral = 0;
|
||||
hsudc->gadget.speed = USB_SPEED_UNKNOWN;
|
||||
|
||||
s3c_hsudc_setup_ep(hsudc);
|
||||
|
||||
|
@ -1348,12 +1352,20 @@ static int s3c_hsudc_probe(struct platform_device *pdev)
|
|||
disable_irq(hsudc->irq);
|
||||
local_irq_enable();
|
||||
|
||||
ret = device_register(&hsudc->gadget.dev);
|
||||
if (ret) {
|
||||
put_device(&hsudc->gadget.dev);
|
||||
goto err_add_device;
|
||||
}
|
||||
|
||||
ret = usb_add_gadget_udc(&pdev->dev, &hsudc->gadget);
|
||||
if (ret)
|
||||
goto err_add_udc;
|
||||
|
||||
return 0;
|
||||
err_add_udc:
|
||||
device_unregister(&hsudc->gadget.dev);
|
||||
err_add_device:
|
||||
clk_disable(hsudc->uclk);
|
||||
clk_put(hsudc->uclk);
|
||||
err_clk:
|
||||
|
@ -1362,10 +1374,13 @@ err_irq:
|
|||
iounmap(hsudc->regs);
|
||||
|
||||
err_remap:
|
||||
release_resource(hsudc->mem_rsrc);
|
||||
kfree(hsudc->mem_rsrc);
|
||||
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
err_res:
|
||||
if (hsudc->transceiver)
|
||||
otg_put_transceiver(hsudc->transceiver);
|
||||
|
||||
regulator_bulk_free(ARRAY_SIZE(hsudc->supplies), hsudc->supplies);
|
||||
err_supplies:
|
||||
kfree(hsudc);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -194,6 +194,15 @@ config USB_EHCI_S5P
|
|||
help
|
||||
Enable support for the S5P SOC's on-chip EHCI controller.
|
||||
|
||||
config USB_EHCI_MV
|
||||
bool "EHCI support for Marvell on-chip controller"
|
||||
depends on USB_EHCI_HCD
|
||||
select USB_EHCI_ROOT_HUB_TT
|
||||
---help---
|
||||
Enables support for Marvell (including PXA and MMP series) on-chip
|
||||
USB SPH and OTG controller. SPH is a single port host, and it can
|
||||
only be EHCI host. OTG is controller that can switch to host mode.
|
||||
|
||||
config USB_W90X900_EHCI
|
||||
bool "W90X900(W90P910) EHCI support"
|
||||
depends on USB_EHCI_HCD && ARCH_W90X900
|
||||
|
|
|
@ -1371,6 +1371,11 @@ MODULE_LICENSE ("GPL");
|
|||
#define PLATFORM_DRIVER ehci_xls_driver
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_USB_EHCI_MV
|
||||
#include "ehci-mv.c"
|
||||
#define PLATFORM_DRIVER ehci_mv_driver
|
||||
#endif
|
||||
|
||||
#if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
|
||||
!defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
|
||||
!defined(XILINX_OF_PLATFORM_DRIVER)
|
||||
|
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||
* Author: Chao Xie <chao.xie@marvell.com>
|
||||
* Neil Zhang <zhangwm@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/platform_data/mv_usb.h>
|
||||
|
||||
#define CAPLENGTH_MASK (0xff)
|
||||
|
||||
struct ehci_hcd_mv {
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
/* Which mode does this ehci running OTG/Host ? */
|
||||
int mode;
|
||||
|
||||
void __iomem *phy_regs;
|
||||
void __iomem *cap_regs;
|
||||
void __iomem *op_regs;
|
||||
|
||||
struct otg_transceiver *otg;
|
||||
|
||||
struct mv_usb_platform_data *pdata;
|
||||
|
||||
/* clock source and total clock number */
|
||||
unsigned int clknum;
|
||||
struct clk *clk[0];
|
||||
};
|
||||
|
||||
static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ehci_mv->clknum; i++)
|
||||
clk_enable(ehci_mv->clk[i]);
|
||||
}
|
||||
|
||||
static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ehci_mv->clknum; i++)
|
||||
clk_disable(ehci_mv->clk[i]);
|
||||
}
|
||||
|
||||
static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv)
|
||||
{
|
||||
int retval;
|
||||
|
||||
ehci_clock_enable(ehci_mv);
|
||||
if (ehci_mv->pdata->phy_init) {
|
||||
retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs);
|
||||
if (retval)
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv)
|
||||
{
|
||||
if (ehci_mv->pdata->phy_deinit)
|
||||
ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs);
|
||||
ehci_clock_disable(ehci_mv);
|
||||
}
|
||||
|
||||
static int mv_ehci_reset(struct usb_hcd *hcd)
|
||||
{
|
||||
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
||||
struct device *dev = hcd->self.controller;
|
||||
struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev);
|
||||
int retval;
|
||||
|
||||
if (ehci_mv == NULL) {
|
||||
dev_err(dev, "Can not find private ehci data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* data structure init
|
||||
*/
|
||||
retval = ehci_init(hcd);
|
||||
if (retval) {
|
||||
dev_err(dev, "ehci_init failed %d\n", retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
hcd->has_tt = 1;
|
||||
ehci->sbrn = 0x20;
|
||||
|
||||
retval = ehci_reset(ehci);
|
||||
if (retval) {
|
||||
dev_err(dev, "ehci_reset failed %d\n", retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hc_driver mv_ehci_hc_driver = {
|
||||
.description = hcd_name,
|
||||
.product_desc = "Marvell EHCI",
|
||||
.hcd_priv_size = sizeof(struct ehci_hcd),
|
||||
|
||||
/*
|
||||
* generic hardware linkage
|
||||
*/
|
||||
.irq = ehci_irq,
|
||||
.flags = HCD_MEMORY | HCD_USB2,
|
||||
|
||||
/*
|
||||
* basic lifecycle operations
|
||||
*/
|
||||
.reset = mv_ehci_reset,
|
||||
.start = ehci_run,
|
||||
.stop = ehci_stop,
|
||||
.shutdown = ehci_shutdown,
|
||||
|
||||
/*
|
||||
* managing i/o requests and associated device resources
|
||||
*/
|
||||
.urb_enqueue = ehci_urb_enqueue,
|
||||
.urb_dequeue = ehci_urb_dequeue,
|
||||
.endpoint_disable = ehci_endpoint_disable,
|
||||
.endpoint_reset = ehci_endpoint_reset,
|
||||
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
|
||||
|
||||
/*
|
||||
* scheduling support
|
||||
*/
|
||||
.get_frame_number = ehci_get_frame,
|
||||
|
||||
/*
|
||||
* root hub support
|
||||
*/
|
||||
.hub_status_data = ehci_hub_status_data,
|
||||
.hub_control = ehci_hub_control,
|
||||
.bus_suspend = ehci_bus_suspend,
|
||||
.bus_resume = ehci_bus_resume,
|
||||
};
|
||||
|
||||
static int mv_ehci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_usb_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct usb_hcd *hcd;
|
||||
struct ehci_hcd *ehci;
|
||||
struct ehci_hcd_mv *ehci_mv;
|
||||
struct resource *r;
|
||||
int clk_i, retval = -ENODEV;
|
||||
u32 offset;
|
||||
size_t size;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "missing platform_data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (usb_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci");
|
||||
if (!hcd)
|
||||
return -ENOMEM;
|
||||
|
||||
size = sizeof(*ehci_mv) + sizeof(struct clk *) * pdata->clknum;
|
||||
ehci_mv = kzalloc(size, GFP_KERNEL);
|
||||
if (ehci_mv == NULL) {
|
||||
dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n");
|
||||
retval = -ENOMEM;
|
||||
goto err_put_hcd;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ehci_mv);
|
||||
ehci_mv->pdata = pdata;
|
||||
ehci_mv->hcd = hcd;
|
||||
|
||||
ehci_mv->clknum = pdata->clknum;
|
||||
for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) {
|
||||
ehci_mv->clk[clk_i] =
|
||||
clk_get(&pdev->dev, pdata->clkname[clk_i]);
|
||||
if (IS_ERR(ehci_mv->clk[clk_i])) {
|
||||
dev_err(&pdev->dev, "error get clck \"%s\"\n",
|
||||
pdata->clkname[clk_i]);
|
||||
retval = PTR_ERR(ehci_mv->clk[clk_i]);
|
||||
goto err_put_clk;
|
||||
}
|
||||
}
|
||||
|
||||
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs");
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_put_clk;
|
||||
}
|
||||
|
||||
ehci_mv->phy_regs = ioremap(r->start, resource_size(r));
|
||||
if (ehci_mv->phy_regs == 0) {
|
||||
dev_err(&pdev->dev, "failed to map phy I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_put_clk;
|
||||
}
|
||||
|
||||
r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs");
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "no I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_iounmap_phyreg;
|
||||
}
|
||||
|
||||
ehci_mv->cap_regs = ioremap(r->start, resource_size(r));
|
||||
if (ehci_mv->cap_regs == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_iounmap_phyreg;
|
||||
}
|
||||
|
||||
retval = mv_ehci_enable(ehci_mv);
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev, "init phy error %d\n", retval);
|
||||
goto err_iounmap_capreg;
|
||||
}
|
||||
|
||||
offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK;
|
||||
ehci_mv->op_regs =
|
||||
(void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset);
|
||||
|
||||
hcd->rsrc_start = r->start;
|
||||
hcd->rsrc_len = r->end - r->start + 1;
|
||||
hcd->regs = ehci_mv->op_regs;
|
||||
|
||||
hcd->irq = platform_get_irq(pdev, 0);
|
||||
if (!hcd->irq) {
|
||||
dev_err(&pdev->dev, "Cannot get irq.");
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
ehci = hcd_to_ehci(hcd);
|
||||
ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs;
|
||||
ehci->regs = (struct ehci_regs *) ehci_mv->op_regs;
|
||||
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
|
||||
|
||||
ehci_mv->mode = pdata->mode;
|
||||
if (ehci_mv->mode == MV_USB_MODE_OTG) {
|
||||
#ifdef CONFIG_USB_OTG_UTILS
|
||||
ehci_mv->otg = otg_get_transceiver();
|
||||
if (!ehci_mv->otg) {
|
||||
dev_err(&pdev->dev,
|
||||
"unable to find transceiver\n");
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
retval = otg_set_host(ehci_mv->otg, &hcd->self);
|
||||
if (retval < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"unable to register with transceiver\n");
|
||||
retval = -ENODEV;
|
||||
goto err_put_transceiver;
|
||||
}
|
||||
/* otg will enable clock before use as host */
|
||||
mv_ehci_disable(ehci_mv);
|
||||
#else
|
||||
dev_info(&pdev->dev, "MV_USB_MODE_OTG "
|
||||
"must have CONFIG_USB_OTG_UTILS enabled\n");
|
||||
goto err_disable_clk;
|
||||
#endif
|
||||
} else {
|
||||
if (pdata->set_vbus)
|
||||
pdata->set_vbus(1);
|
||||
|
||||
retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to add hcd with err %d\n", retval);
|
||||
goto err_set_vbus;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->private_init)
|
||||
pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs);
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"successful find EHCI device with regs 0x%p irq %d"
|
||||
" working in %s mode\n", hcd->regs, hcd->irq,
|
||||
ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host");
|
||||
|
||||
return 0;
|
||||
|
||||
err_set_vbus:
|
||||
if (pdata->set_vbus)
|
||||
pdata->set_vbus(0);
|
||||
#ifdef CONFIG_USB_OTG_UTILS
|
||||
err_put_transceiver:
|
||||
if (ehci_mv->otg)
|
||||
otg_put_transceiver(ehci_mv->otg);
|
||||
#endif
|
||||
err_disable_clk:
|
||||
mv_ehci_disable(ehci_mv);
|
||||
err_iounmap_capreg:
|
||||
iounmap(ehci_mv->cap_regs);
|
||||
err_iounmap_phyreg:
|
||||
iounmap(ehci_mv->phy_regs);
|
||||
err_put_clk:
|
||||
for (clk_i--; clk_i >= 0; clk_i--)
|
||||
clk_put(ehci_mv->clk[clk_i]);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(ehci_mv);
|
||||
err_put_hcd:
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int mv_ehci_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev);
|
||||
struct usb_hcd *hcd = ehci_mv->hcd;
|
||||
int clk_i;
|
||||
|
||||
if (hcd->rh_registered)
|
||||
usb_remove_hcd(hcd);
|
||||
|
||||
if (ehci_mv->otg) {
|
||||
otg_set_host(ehci_mv->otg, NULL);
|
||||
otg_put_transceiver(ehci_mv->otg);
|
||||
}
|
||||
|
||||
if (ehci_mv->mode == MV_USB_MODE_HOST) {
|
||||
if (ehci_mv->pdata->set_vbus)
|
||||
ehci_mv->pdata->set_vbus(0);
|
||||
|
||||
mv_ehci_disable(ehci_mv);
|
||||
}
|
||||
|
||||
iounmap(ehci_mv->cap_regs);
|
||||
iounmap(ehci_mv->phy_regs);
|
||||
|
||||
for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++)
|
||||
clk_put(ehci_mv->clk[clk_i]);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
kfree(ehci_mv);
|
||||
usb_put_hcd(hcd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULE_ALIAS("mv-ehci");
|
||||
|
||||
static const struct platform_device_id ehci_id_table[] = {
|
||||
{"pxa-u2oehci", PXA_U2OEHCI},
|
||||
{"pxa-sph", PXA_SPH},
|
||||
{"mmp3-hsic", MMP3_HSIC},
|
||||
{"mmp3-fsic", MMP3_FSIC},
|
||||
{},
|
||||
};
|
||||
|
||||
static void mv_ehci_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev);
|
||||
struct usb_hcd *hcd = ehci_mv->hcd;
|
||||
|
||||
if (!hcd->rh_registered)
|
||||
return;
|
||||
|
||||
if (hcd->driver->shutdown)
|
||||
hcd->driver->shutdown(hcd);
|
||||
}
|
||||
|
||||
static struct platform_driver ehci_mv_driver = {
|
||||
.probe = mv_ehci_probe,
|
||||
.remove = mv_ehci_remove,
|
||||
.shutdown = mv_ehci_shutdown,
|
||||
.driver = {
|
||||
.name = "mv-ehci",
|
||||
.bus = &platform_bus_type,
|
||||
},
|
||||
.id_table = ehci_id_table,
|
||||
};
|
|
@ -130,4 +130,16 @@ config FSL_USB2_OTG
|
|||
help
|
||||
Enable this to support Freescale USB OTG transceiver.
|
||||
|
||||
config USB_MV_OTG
|
||||
tristate "Marvell USB OTG support"
|
||||
depends on USB_MV_UDC
|
||||
select USB_OTG
|
||||
select USB_OTG_UTILS
|
||||
help
|
||||
Say Y here if you want to build Marvell USB OTG transciever
|
||||
driver in kernel (including PXA and MMP series). This driver
|
||||
implements role switch between EHCI host driver and gadget driver.
|
||||
|
||||
To compile this driver as a module, choose M here.
|
||||
|
||||
endif # USB || OTG
|
||||
|
|
|
@ -21,3 +21,4 @@ obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o
|
|||
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
|
||||
fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o
|
||||
obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o
|
||||
obj-$(CONFIG_USB_MV_OTG) += mv_otg.o
|
||||
|
|
|
@ -0,0 +1,957 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||
* Author: Chao Xie <chao.xie@marvell.com>
|
||||
* Neil Zhang <zhangwm@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/hcd.h>
|
||||
#include <linux/platform_data/mv_usb.h>
|
||||
|
||||
#include "mv_otg.h"
|
||||
|
||||
#define DRIVER_DESC "Marvell USB OTG transceiver driver"
|
||||
#define DRIVER_VERSION "Jan 20, 2010"
|
||||
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static const char driver_name[] = "mv-otg";
|
||||
|
||||
static char *state_string[] = {
|
||||
"undefined",
|
||||
"b_idle",
|
||||
"b_srp_init",
|
||||
"b_peripheral",
|
||||
"b_wait_acon",
|
||||
"b_host",
|
||||
"a_idle",
|
||||
"a_wait_vrise",
|
||||
"a_wait_bcon",
|
||||
"a_host",
|
||||
"a_suspend",
|
||||
"a_peripheral",
|
||||
"a_wait_vfall",
|
||||
"a_vbus_err"
|
||||
};
|
||||
|
||||
static int mv_otg_set_vbus(struct otg_transceiver *otg, bool on)
|
||||
{
|
||||
struct mv_otg *mvotg = container_of(otg, struct mv_otg, otg);
|
||||
if (mvotg->pdata->set_vbus == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
return mvotg->pdata->set_vbus(on);
|
||||
}
|
||||
|
||||
static int mv_otg_set_host(struct otg_transceiver *otg,
|
||||
struct usb_bus *host)
|
||||
{
|
||||
otg->host = host;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_set_peripheral(struct otg_transceiver *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
otg->gadget = gadget;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_run_state_machine(struct mv_otg *mvotg,
|
||||
unsigned long delay)
|
||||
{
|
||||
dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n");
|
||||
if (!mvotg->qwork)
|
||||
return;
|
||||
|
||||
queue_delayed_work(mvotg->qwork, &mvotg->work, delay);
|
||||
}
|
||||
|
||||
static void mv_otg_timer_await_bcon(unsigned long data)
|
||||
{
|
||||
struct mv_otg *mvotg = (struct mv_otg *) data;
|
||||
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 1;
|
||||
|
||||
dev_info(&mvotg->pdev->dev, "B Device No Response!\n");
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id)
|
||||
{
|
||||
struct timer_list *timer;
|
||||
|
||||
if (id >= OTG_TIMER_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
timer = &mvotg->otg_ctrl.timer[id];
|
||||
|
||||
if (timer_pending(timer))
|
||||
del_timer(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id,
|
||||
unsigned long interval,
|
||||
void (*callback) (unsigned long))
|
||||
{
|
||||
struct timer_list *timer;
|
||||
|
||||
if (id >= OTG_TIMER_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
timer = &mvotg->otg_ctrl.timer[id];
|
||||
if (timer_pending(timer)) {
|
||||
dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
init_timer(timer);
|
||||
timer->data = (unsigned long) mvotg;
|
||||
timer->function = callback;
|
||||
timer->expires = jiffies + interval;
|
||||
add_timer(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_reset(struct mv_otg *mvotg)
|
||||
{
|
||||
unsigned int loops;
|
||||
u32 tmp;
|
||||
|
||||
/* Stop the controller */
|
||||
tmp = readl(&mvotg->op_regs->usbcmd);
|
||||
tmp &= ~USBCMD_RUN_STOP;
|
||||
writel(tmp, &mvotg->op_regs->usbcmd);
|
||||
|
||||
/* Reset the controller to get default values */
|
||||
writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd);
|
||||
|
||||
loops = 500;
|
||||
while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) {
|
||||
if (loops == 0) {
|
||||
dev_err(&mvotg->pdev->dev,
|
||||
"Wait for RESET completed TIMEOUT\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
loops--;
|
||||
udelay(20);
|
||||
}
|
||||
|
||||
writel(0x0, &mvotg->op_regs->usbintr);
|
||||
tmp = readl(&mvotg->op_regs->usbsts);
|
||||
writel(tmp, &mvotg->op_regs->usbsts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_init_irq(struct mv_otg *mvotg)
|
||||
{
|
||||
u32 otgsc;
|
||||
|
||||
mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID
|
||||
| OTGSC_INTR_A_VBUS_VALID;
|
||||
mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID
|
||||
| OTGSC_INTSTS_A_VBUS_VALID;
|
||||
|
||||
if (mvotg->pdata->vbus == NULL) {
|
||||
mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID
|
||||
| OTGSC_INTR_B_SESSION_END;
|
||||
mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID
|
||||
| OTGSC_INTSTS_B_SESSION_END;
|
||||
}
|
||||
|
||||
if (mvotg->pdata->id == NULL) {
|
||||
mvotg->irq_en |= OTGSC_INTR_USB_ID;
|
||||
mvotg->irq_status |= OTGSC_INTSTS_USB_ID;
|
||||
}
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
otgsc |= mvotg->irq_en;
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
}
|
||||
|
||||
static void mv_otg_start_host(struct mv_otg *mvotg, int on)
|
||||
{
|
||||
struct otg_transceiver *otg = &mvotg->otg;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if (!otg->host)
|
||||
return;
|
||||
|
||||
dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop");
|
||||
|
||||
hcd = bus_to_hcd(otg->host);
|
||||
|
||||
if (on)
|
||||
usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
|
||||
else
|
||||
usb_remove_hcd(hcd);
|
||||
}
|
||||
|
||||
static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on)
|
||||
{
|
||||
struct otg_transceiver *otg = &mvotg->otg;
|
||||
|
||||
if (!otg->gadget)
|
||||
return;
|
||||
|
||||
dev_info(otg->dev, "gadget %s\n", on ? "on" : "off");
|
||||
|
||||
if (on)
|
||||
usb_gadget_vbus_connect(otg->gadget);
|
||||
else
|
||||
usb_gadget_vbus_disconnect(otg->gadget);
|
||||
}
|
||||
|
||||
static void otg_clock_enable(struct mv_otg *mvotg)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < mvotg->clknum; i++)
|
||||
clk_enable(mvotg->clk[i]);
|
||||
}
|
||||
|
||||
static void otg_clock_disable(struct mv_otg *mvotg)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < mvotg->clknum; i++)
|
||||
clk_disable(mvotg->clk[i]);
|
||||
}
|
||||
|
||||
static int mv_otg_enable_internal(struct mv_otg *mvotg)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
if (mvotg->active)
|
||||
return 0;
|
||||
|
||||
dev_dbg(&mvotg->pdev->dev, "otg enabled\n");
|
||||
|
||||
otg_clock_enable(mvotg);
|
||||
if (mvotg->pdata->phy_init) {
|
||||
retval = mvotg->pdata->phy_init(mvotg->phy_regs);
|
||||
if (retval) {
|
||||
dev_err(&mvotg->pdev->dev,
|
||||
"init phy error %d\n", retval);
|
||||
otg_clock_disable(mvotg);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
mvotg->active = 1;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int mv_otg_enable(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->clock_gating)
|
||||
return mv_otg_enable_internal(mvotg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_disable_internal(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->active) {
|
||||
dev_dbg(&mvotg->pdev->dev, "otg disabled\n");
|
||||
if (mvotg->pdata->phy_deinit)
|
||||
mvotg->pdata->phy_deinit(mvotg->phy_regs);
|
||||
otg_clock_disable(mvotg);
|
||||
mvotg->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mv_otg_disable(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->clock_gating)
|
||||
mv_otg_disable_internal(mvotg);
|
||||
}
|
||||
|
||||
static void mv_otg_update_inputs(struct mv_otg *mvotg)
|
||||
{
|
||||
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||
u32 otgsc;
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
|
||||
if (mvotg->pdata->vbus) {
|
||||
if (mvotg->pdata->vbus->poll() == VBUS_HIGH) {
|
||||
otg_ctrl->b_sess_vld = 1;
|
||||
otg_ctrl->b_sess_end = 0;
|
||||
} else {
|
||||
otg_ctrl->b_sess_vld = 0;
|
||||
otg_ctrl->b_sess_end = 1;
|
||||
}
|
||||
} else {
|
||||
otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID);
|
||||
otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END);
|
||||
}
|
||||
|
||||
if (mvotg->pdata->id)
|
||||
otg_ctrl->id = !!mvotg->pdata->id->poll();
|
||||
else
|
||||
otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID);
|
||||
|
||||
if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id)
|
||||
otg_ctrl->a_bus_req = 1;
|
||||
|
||||
otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID);
|
||||
otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID);
|
||||
|
||||
dev_dbg(&mvotg->pdev->dev, "%s: ", __func__);
|
||||
dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id);
|
||||
dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld);
|
||||
dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end);
|
||||
dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld);
|
||||
dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld);
|
||||
}
|
||||
|
||||
static void mv_otg_update_state(struct mv_otg *mvotg)
|
||||
{
|
||||
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||
struct otg_transceiver *otg = &mvotg->otg;
|
||||
int old_state = otg->state;
|
||||
|
||||
switch (old_state) {
|
||||
case OTG_STATE_UNDEFINED:
|
||||
otg->state = OTG_STATE_B_IDLE;
|
||||
/* FALL THROUGH */
|
||||
case OTG_STATE_B_IDLE:
|
||||
if (otg_ctrl->id == 0)
|
||||
otg->state = OTG_STATE_A_IDLE;
|
||||
else if (otg_ctrl->b_sess_vld)
|
||||
otg->state = OTG_STATE_B_PERIPHERAL;
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0)
|
||||
otg->state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
if (otg_ctrl->id)
|
||||
otg->state = OTG_STATE_B_IDLE;
|
||||
else if (!(otg_ctrl->a_bus_drop) &&
|
||||
(otg_ctrl->a_bus_req || otg_ctrl->a_srp_det))
|
||||
otg->state = OTG_STATE_A_WAIT_VRISE;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
if (otg_ctrl->a_vbus_vld)
|
||||
otg->state = OTG_STATE_A_WAIT_BCON;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
if (otg_ctrl->id || otg_ctrl->a_bus_drop
|
||||
|| otg_ctrl->a_wait_bcon_timeout) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
otg->state = OTG_STATE_A_WAIT_VFALL;
|
||||
otg_ctrl->a_bus_req = 0;
|
||||
} else if (!otg_ctrl->a_vbus_vld) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
otg->state = OTG_STATE_A_VBUS_ERR;
|
||||
} else if (otg_ctrl->b_conn) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
otg->state = OTG_STATE_A_HOST;
|
||||
}
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
if (otg_ctrl->id || !otg_ctrl->b_conn
|
||||
|| otg_ctrl->a_bus_drop)
|
||||
otg->state = OTG_STATE_A_WAIT_BCON;
|
||||
else if (!otg_ctrl->a_vbus_vld)
|
||||
otg->state = OTG_STATE_A_VBUS_ERR;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
if (otg_ctrl->id
|
||||
|| (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld)
|
||||
|| otg_ctrl->a_bus_req)
|
||||
otg->state = OTG_STATE_A_IDLE;
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
if (otg_ctrl->id || otg_ctrl->a_clr_err
|
||||
|| otg_ctrl->a_bus_drop) {
|
||||
otg_ctrl->a_clr_err = 0;
|
||||
otg->state = OTG_STATE_A_WAIT_VFALL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void mv_otg_work(struct work_struct *work)
|
||||
{
|
||||
struct mv_otg *mvotg;
|
||||
struct otg_transceiver *otg;
|
||||
int old_state;
|
||||
|
||||
mvotg = container_of((struct delayed_work *)work, struct mv_otg, work);
|
||||
|
||||
run:
|
||||
/* work queue is single thread, or we need spin_lock to protect */
|
||||
otg = &mvotg->otg;
|
||||
old_state = otg->state;
|
||||
|
||||
if (!mvotg->active)
|
||||
return;
|
||||
|
||||
mv_otg_update_inputs(mvotg);
|
||||
mv_otg_update_state(mvotg);
|
||||
|
||||
if (old_state != otg->state) {
|
||||
dev_info(&mvotg->pdev->dev, "change from state %s to %s\n",
|
||||
state_string[old_state],
|
||||
state_string[otg->state]);
|
||||
|
||||
switch (otg->state) {
|
||||
case OTG_STATE_B_IDLE:
|
||||
mvotg->otg.default_a = 0;
|
||||
if (old_state == OTG_STATE_B_PERIPHERAL)
|
||||
mv_otg_start_periphrals(mvotg, 0);
|
||||
mv_otg_reset(mvotg);
|
||||
mv_otg_disable(mvotg);
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_start_periphrals(mvotg, 1);
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
mvotg->otg.default_a = 1;
|
||||
mv_otg_enable(mvotg);
|
||||
if (old_state == OTG_STATE_A_WAIT_VFALL)
|
||||
mv_otg_start_host(mvotg, 0);
|
||||
mv_otg_reset(mvotg);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
mv_otg_set_vbus(&mvotg->otg, 1);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
if (old_state != OTG_STATE_A_HOST)
|
||||
mv_otg_start_host(mvotg, 1);
|
||||
mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER,
|
||||
T_A_WAIT_BCON,
|
||||
mv_otg_timer_await_bcon);
|
||||
/*
|
||||
* Now, we directly enter A_HOST. So set b_conn = 1
|
||||
* here. In fact, it need host driver to notify us.
|
||||
*/
|
||||
mvotg->otg_ctrl.b_conn = 1;
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
/*
|
||||
* Now, we has exited A_HOST. So set b_conn = 0
|
||||
* here. In fact, it need host driver to notify us.
|
||||
*/
|
||||
mvotg->otg_ctrl.b_conn = 0;
|
||||
mv_otg_set_vbus(&mvotg->otg, 0);
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
goto run;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t mv_otg_irq(int irq, void *dev)
|
||||
{
|
||||
struct mv_otg *mvotg = dev;
|
||||
u32 otgsc;
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
|
||||
/*
|
||||
* if we have vbus, then the vbus detection for B-device
|
||||
* will be done by mv_otg_inputs_irq().
|
||||
*/
|
||||
if (mvotg->pdata->vbus)
|
||||
if ((otgsc & OTGSC_STS_USB_ID) &&
|
||||
!(otgsc & OTGSC_INTSTS_USB_ID))
|
||||
return IRQ_NONE;
|
||||
|
||||
if ((otgsc & mvotg->irq_status) == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t mv_otg_inputs_irq(int irq, void *dev)
|
||||
{
|
||||
struct mv_otg *mvotg = dev;
|
||||
|
||||
/* The clock may disabled at this time */
|
||||
if (!mvotg->active) {
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
}
|
||||
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||
mvotg->otg_ctrl.a_bus_req);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_a_bus_req(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
/* We will use this interface to change to A device */
|
||||
if (mvotg->otg.state != OTG_STATE_B_IDLE
|
||||
&& mvotg->otg.state != OTG_STATE_A_IDLE)
|
||||
return -1;
|
||||
|
||||
/* The clock may disabled and we need to set irq for ID detected */
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
|
||||
if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_bus_req = 1;
|
||||
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_req = 1\n");
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req,
|
||||
set_a_bus_req);
|
||||
|
||||
static ssize_t
|
||||
set_a_clr_err(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
if (!mvotg->otg.default_a)
|
||||
return -1;
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_clr_err = 1;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_clr_err = 1\n");
|
||||
}
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
|
||||
|
||||
static ssize_t
|
||||
get_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||
mvotg->otg_ctrl.a_bus_drop);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
if (!mvotg->otg.default_a)
|
||||
return -1;
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
if (buf[0] == '0') {
|
||||
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_drop = 0\n");
|
||||
} else if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_bus_drop = 1;
|
||||
mvotg->otg_ctrl.a_bus_req = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_drop = 1\n");
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: and a_bus_req = 0\n");
|
||||
}
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR,
|
||||
get_a_bus_drop, set_a_bus_drop);
|
||||
|
||||
static struct attribute *inputs_attrs[] = {
|
||||
&dev_attr_a_bus_req.attr,
|
||||
&dev_attr_a_clr_err.attr,
|
||||
&dev_attr_a_bus_drop.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group inputs_attr_group = {
|
||||
.name = "inputs",
|
||||
.attrs = inputs_attrs,
|
||||
};
|
||||
|
||||
int mv_otg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
int clk_i;
|
||||
|
||||
sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group);
|
||||
|
||||
if (mvotg->irq)
|
||||
free_irq(mvotg->irq, mvotg);
|
||||
|
||||
if (mvotg->pdata->vbus)
|
||||
free_irq(mvotg->pdata->vbus->irq, mvotg);
|
||||
if (mvotg->pdata->id)
|
||||
free_irq(mvotg->pdata->id->irq, mvotg);
|
||||
|
||||
if (mvotg->qwork) {
|
||||
flush_workqueue(mvotg->qwork);
|
||||
destroy_workqueue(mvotg->qwork);
|
||||
}
|
||||
|
||||
mv_otg_disable(mvotg);
|
||||
|
||||
if (mvotg->cap_regs)
|
||||
iounmap(mvotg->cap_regs);
|
||||
|
||||
if (mvotg->phy_regs)
|
||||
iounmap(mvotg->phy_regs);
|
||||
|
||||
for (clk_i = 0; clk_i <= mvotg->clknum; clk_i++)
|
||||
clk_put(mvotg->clk[clk_i]);
|
||||
|
||||
otg_set_transceiver(NULL);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
kfree(mvotg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_usb_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct mv_otg *mvotg;
|
||||
struct resource *r;
|
||||
int retval = 0, clk_i, i;
|
||||
size_t size;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "failed to get platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
size = sizeof(*mvotg) + sizeof(struct clk *) * pdata->clknum;
|
||||
mvotg = kzalloc(size, GFP_KERNEL);
|
||||
if (!mvotg) {
|
||||
dev_err(&pdev->dev, "failed to allocate memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, mvotg);
|
||||
|
||||
mvotg->pdev = pdev;
|
||||
mvotg->pdata = pdata;
|
||||
|
||||
mvotg->clknum = pdata->clknum;
|
||||
for (clk_i = 0; clk_i < mvotg->clknum; clk_i++) {
|
||||
mvotg->clk[clk_i] = clk_get(&pdev->dev, pdata->clkname[clk_i]);
|
||||
if (IS_ERR(mvotg->clk[clk_i])) {
|
||||
retval = PTR_ERR(mvotg->clk[clk_i]);
|
||||
goto err_put_clk;
|
||||
}
|
||||
}
|
||||
|
||||
mvotg->qwork = create_singlethread_workqueue("mv_otg_queue");
|
||||
if (!mvotg->qwork) {
|
||||
dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n");
|
||||
retval = -ENOMEM;
|
||||
goto err_put_clk;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&mvotg->work, mv_otg_work);
|
||||
|
||||
/* OTG common part */
|
||||
mvotg->pdev = pdev;
|
||||
mvotg->otg.dev = &pdev->dev;
|
||||
mvotg->otg.label = driver_name;
|
||||
mvotg->otg.set_host = mv_otg_set_host;
|
||||
mvotg->otg.set_peripheral = mv_otg_set_peripheral;
|
||||
mvotg->otg.set_vbus = mv_otg_set_vbus;
|
||||
mvotg->otg.state = OTG_STATE_UNDEFINED;
|
||||
|
||||
for (i = 0; i < OTG_TIMER_NUM; i++)
|
||||
init_timer(&mvotg->otg_ctrl.timer[i]);
|
||||
|
||||
r = platform_get_resource_byname(mvotg->pdev,
|
||||
IORESOURCE_MEM, "phyregs");
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
mvotg->phy_regs = ioremap(r->start, resource_size(r));
|
||||
if (mvotg->phy_regs == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map phy I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
r = platform_get_resource_byname(mvotg->pdev,
|
||||
IORESOURCE_MEM, "capregs");
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_unmap_phyreg;
|
||||
}
|
||||
|
||||
mvotg->cap_regs = ioremap(r->start, resource_size(r));
|
||||
if (mvotg->cap_regs == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_unmap_phyreg;
|
||||
}
|
||||
|
||||
/* we will acces controller register, so enable the udc controller */
|
||||
retval = mv_otg_enable_internal(mvotg);
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev, "mv otg enable error %d\n", retval);
|
||||
goto err_unmap_capreg;
|
||||
}
|
||||
|
||||
mvotg->op_regs =
|
||||
(struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs
|
||||
+ (readl(mvotg->cap_regs) & CAPLENGTH_MASK));
|
||||
|
||||
if (pdata->id) {
|
||||
retval = request_threaded_irq(pdata->id->irq, NULL,
|
||||
mv_otg_inputs_irq,
|
||||
IRQF_ONESHOT, "id", mvotg);
|
||||
if (retval) {
|
||||
dev_info(&pdev->dev,
|
||||
"Failed to request irq for ID\n");
|
||||
pdata->id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->vbus) {
|
||||
mvotg->clock_gating = 1;
|
||||
retval = request_threaded_irq(pdata->vbus->irq, NULL,
|
||||
mv_otg_inputs_irq,
|
||||
IRQF_ONESHOT, "vbus", mvotg);
|
||||
if (retval) {
|
||||
dev_info(&pdev->dev,
|
||||
"Failed to request irq for VBUS, "
|
||||
"disable clock gating\n");
|
||||
mvotg->clock_gating = 0;
|
||||
pdata->vbus = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->disable_otg_clock_gating)
|
||||
mvotg->clock_gating = 0;
|
||||
|
||||
mv_otg_reset(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
|
||||
r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0);
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no IRQ resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
mvotg->irq = r->start;
|
||||
if (request_irq(mvotg->irq, mv_otg_irq, IRQF_SHARED,
|
||||
driver_name, mvotg)) {
|
||||
dev_err(&pdev->dev, "Request irq %d for OTG failed\n",
|
||||
mvotg->irq);
|
||||
mvotg->irq = 0;
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
retval = otg_set_transceiver(&mvotg->otg);
|
||||
if (retval < 0) {
|
||||
dev_err(&pdev->dev, "can't register transceiver, %d\n",
|
||||
retval);
|
||||
goto err_free_irq;
|
||||
}
|
||||
|
||||
retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group);
|
||||
if (retval < 0) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"Can't register sysfs attr group: %d\n", retval);
|
||||
goto err_set_transceiver;
|
||||
}
|
||||
|
||||
spin_lock_init(&mvotg->wq_lock);
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 2 * HZ);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"successful probe OTG device %s clock gating.\n",
|
||||
mvotg->clock_gating ? "with" : "without");
|
||||
|
||||
return 0;
|
||||
|
||||
err_set_transceiver:
|
||||
otg_set_transceiver(NULL);
|
||||
err_free_irq:
|
||||
free_irq(mvotg->irq, mvotg);
|
||||
err_disable_clk:
|
||||
if (pdata->vbus)
|
||||
free_irq(pdata->vbus->irq, mvotg);
|
||||
if (pdata->id)
|
||||
free_irq(pdata->id->irq, mvotg);
|
||||
mv_otg_disable_internal(mvotg);
|
||||
err_unmap_capreg:
|
||||
iounmap(mvotg->cap_regs);
|
||||
err_unmap_phyreg:
|
||||
iounmap(mvotg->phy_regs);
|
||||
err_destroy_workqueue:
|
||||
flush_workqueue(mvotg->qwork);
|
||||
destroy_workqueue(mvotg->qwork);
|
||||
err_put_clk:
|
||||
for (clk_i--; clk_i >= 0; clk_i--)
|
||||
clk_put(mvotg->clk[clk_i]);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(mvotg);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
|
||||
if (mvotg->otg.state != OTG_STATE_B_IDLE) {
|
||||
dev_info(&pdev->dev,
|
||||
"OTG state is not B_IDLE, it is %d!\n",
|
||||
mvotg->otg.state);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (!mvotg->clock_gating)
|
||||
mv_otg_disable_internal(mvotg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
u32 otgsc;
|
||||
|
||||
if (!mvotg->clock_gating) {
|
||||
mv_otg_enable_internal(mvotg);
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
otgsc |= mvotg->irq_en;
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct platform_driver mv_otg_driver = {
|
||||
.probe = mv_otg_probe,
|
||||
.remove = __exit_p(mv_otg_remove),
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = driver_name,
|
||||
},
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = mv_otg_suspend,
|
||||
.resume = mv_otg_resume,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int __init mv_otg_init(void)
|
||||
{
|
||||
return platform_driver_register(&mv_otg_driver);
|
||||
}
|
||||
|
||||
static void __exit mv_otg_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mv_otg_driver);
|
||||
}
|
||||
|
||||
module_init(mv_otg_init);
|
||||
module_exit(mv_otg_exit);
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __MV_USB_OTG_CONTROLLER__
|
||||
#define __MV_USB_OTG_CONTROLLER__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Command Register Bit Masks */
|
||||
#define USBCMD_RUN_STOP (0x00000001)
|
||||
#define USBCMD_CTRL_RESET (0x00000002)
|
||||
|
||||
/* otgsc Register Bit Masks */
|
||||
#define OTGSC_CTRL_VUSB_DISCHARGE 0x00000001
|
||||
#define OTGSC_CTRL_VUSB_CHARGE 0x00000002
|
||||
#define OTGSC_CTRL_OTG_TERM 0x00000008
|
||||
#define OTGSC_CTRL_DATA_PULSING 0x00000010
|
||||
#define OTGSC_STS_USB_ID 0x00000100
|
||||
#define OTGSC_STS_A_VBUS_VALID 0x00000200
|
||||
#define OTGSC_STS_A_SESSION_VALID 0x00000400
|
||||
#define OTGSC_STS_B_SESSION_VALID 0x00000800
|
||||
#define OTGSC_STS_B_SESSION_END 0x00001000
|
||||
#define OTGSC_STS_1MS_TOGGLE 0x00002000
|
||||
#define OTGSC_STS_DATA_PULSING 0x00004000
|
||||
#define OTGSC_INTSTS_USB_ID 0x00010000
|
||||
#define OTGSC_INTSTS_A_VBUS_VALID 0x00020000
|
||||
#define OTGSC_INTSTS_A_SESSION_VALID 0x00040000
|
||||
#define OTGSC_INTSTS_B_SESSION_VALID 0x00080000
|
||||
#define OTGSC_INTSTS_B_SESSION_END 0x00100000
|
||||
#define OTGSC_INTSTS_1MS 0x00200000
|
||||
#define OTGSC_INTSTS_DATA_PULSING 0x00400000
|
||||
#define OTGSC_INTR_USB_ID 0x01000000
|
||||
#define OTGSC_INTR_A_VBUS_VALID 0x02000000
|
||||
#define OTGSC_INTR_A_SESSION_VALID 0x04000000
|
||||
#define OTGSC_INTR_B_SESSION_VALID 0x08000000
|
||||
#define OTGSC_INTR_B_SESSION_END 0x10000000
|
||||
#define OTGSC_INTR_1MS_TIMER 0x20000000
|
||||
#define OTGSC_INTR_DATA_PULSING 0x40000000
|
||||
|
||||
#define CAPLENGTH_MASK (0xff)
|
||||
|
||||
/* Timer's interval, unit 10ms */
|
||||
#define T_A_WAIT_VRISE 100
|
||||
#define T_A_WAIT_BCON 2000
|
||||
#define T_A_AIDL_BDIS 100
|
||||
#define T_A_BIDL_ADIS 20
|
||||
#define T_B_ASE0_BRST 400
|
||||
#define T_B_SE0_SRP 300
|
||||
#define T_B_SRP_FAIL 2000
|
||||
#define T_B_DATA_PLS 10
|
||||
#define T_B_SRP_INIT 100
|
||||
#define T_A_SRP_RSPNS 10
|
||||
#define T_A_DRV_RSM 5
|
||||
|
||||
enum otg_function {
|
||||
OTG_B_DEVICE = 0,
|
||||
OTG_A_DEVICE
|
||||
};
|
||||
|
||||
enum mv_otg_timer {
|
||||
A_WAIT_BCON_TIMER = 0,
|
||||
OTG_TIMER_NUM
|
||||
};
|
||||
|
||||
/* PXA OTG state machine */
|
||||
struct mv_otg_ctrl {
|
||||
/* internal variables */
|
||||
u8 a_set_b_hnp_en; /* A-Device set b_hnp_en */
|
||||
u8 b_srp_done;
|
||||
u8 b_hnp_en;
|
||||
|
||||
/* OTG inputs */
|
||||
u8 a_bus_drop;
|
||||
u8 a_bus_req;
|
||||
u8 a_clr_err;
|
||||
u8 a_bus_resume;
|
||||
u8 a_bus_suspend;
|
||||
u8 a_conn;
|
||||
u8 a_sess_vld;
|
||||
u8 a_srp_det;
|
||||
u8 a_vbus_vld;
|
||||
u8 b_bus_req; /* B-Device Require Bus */
|
||||
u8 b_bus_resume;
|
||||
u8 b_bus_suspend;
|
||||
u8 b_conn;
|
||||
u8 b_se0_srp;
|
||||
u8 b_sess_end;
|
||||
u8 b_sess_vld;
|
||||
u8 id;
|
||||
u8 a_suspend_req;
|
||||
|
||||
/*Timer event */
|
||||
u8 a_aidl_bdis_timeout;
|
||||
u8 b_ase0_brst_timeout;
|
||||
u8 a_bidl_adis_timeout;
|
||||
u8 a_wait_bcon_timeout;
|
||||
|
||||
struct timer_list timer[OTG_TIMER_NUM];
|
||||
};
|
||||
|
||||
#define VUSBHS_MAX_PORTS 8
|
||||
|
||||
struct mv_otg_regs {
|
||||
u32 usbcmd; /* Command register */
|
||||
u32 usbsts; /* Status register */
|
||||
u32 usbintr; /* Interrupt enable */
|
||||
u32 frindex; /* Frame index */
|
||||
u32 reserved1[1];
|
||||
u32 deviceaddr; /* Device Address */
|
||||
u32 eplistaddr; /* Endpoint List Address */
|
||||
u32 ttctrl; /* HOST TT status and control */
|
||||
u32 burstsize; /* Programmable Burst Size */
|
||||
u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */
|
||||
u32 reserved[4];
|
||||
u32 epnak; /* Endpoint NAK */
|
||||
u32 epnaken; /* Endpoint NAK Enable */
|
||||
u32 configflag; /* Configured Flag register */
|
||||
u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */
|
||||
u32 otgsc;
|
||||
u32 usbmode; /* USB Host/Device mode */
|
||||
u32 epsetupstat; /* Endpoint Setup Status */
|
||||
u32 epprime; /* Endpoint Initialize */
|
||||
u32 epflush; /* Endpoint De-initialize */
|
||||
u32 epstatus; /* Endpoint Status */
|
||||
u32 epcomplete; /* Endpoint Interrupt On Complete */
|
||||
u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */
|
||||
u32 mcr; /* Mux Control */
|
||||
u32 isr; /* Interrupt Status */
|
||||
u32 ier; /* Interrupt Enable */
|
||||
};
|
||||
|
||||
struct mv_otg {
|
||||
struct otg_transceiver otg;
|
||||
struct mv_otg_ctrl otg_ctrl;
|
||||
|
||||
/* base address */
|
||||
void __iomem *phy_regs;
|
||||
void __iomem *cap_regs;
|
||||
struct mv_otg_regs __iomem *op_regs;
|
||||
|
||||
struct platform_device *pdev;
|
||||
int irq;
|
||||
u32 irq_status;
|
||||
u32 irq_en;
|
||||
|
||||
struct delayed_work work;
|
||||
struct workqueue_struct *qwork;
|
||||
|
||||
spinlock_t wq_lock;
|
||||
|
||||
struct mv_usb_platform_data *pdata;
|
||||
|
||||
unsigned int active;
|
||||
unsigned int clock_gating;
|
||||
unsigned int clknum;
|
||||
struct clk *clk[0];
|
||||
};
|
||||
|
||||
#endif
|
|
@ -185,7 +185,7 @@ static int usbhsg_dma_map(struct device *dev,
|
|||
}
|
||||
|
||||
if (dma_mapping_error(dev, pkt->dma)) {
|
||||
dev_err(dev, "dma mapping error %x\n", pkt->dma);
|
||||
dev_err(dev, "dma mapping error %llx\n", (u64)pkt->dma);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
|
|
@ -633,7 +633,6 @@ static void usbhsh_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt)
|
|||
struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv);
|
||||
struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv);
|
||||
struct urb *urb = ureq->urb;
|
||||
struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep);
|
||||
struct device *dev = usbhs_priv_to_dev(priv);
|
||||
int status = 0;
|
||||
|
||||
|
@ -651,7 +650,7 @@ static void usbhsh_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt)
|
|||
usbhsh_ureq_free(hpriv, ureq);
|
||||
|
||||
usbhsh_endpoint_sequence_save(hpriv, urb, pkt);
|
||||
usbhsh_pipe_detach(hpriv, uep);
|
||||
usbhsh_pipe_detach(hpriv, usbhsh_ep_to_uep(urb->ep));
|
||||
|
||||
usb_hcd_unlink_urb_from_ep(hcd, urb);
|
||||
usb_hcd_giveback_urb(hcd, urb, status);
|
||||
|
|
|
@ -330,8 +330,7 @@ static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe,
|
|||
if (dir_in)
|
||||
usbhsp_flags_set(pipe, IS_DIR_HOST);
|
||||
|
||||
if ((is_host && !dir_in) ||
|
||||
(!is_host && dir_in))
|
||||
if (!!is_host ^ !!dir_in)
|
||||
dir |= DIR_OUT;
|
||||
|
||||
if (!dir)
|
||||
|
|
|
@ -42,9 +42,23 @@ struct mv_usb_platform_data {
|
|||
/* only valid for HCD. OTG or Host only*/
|
||||
unsigned int mode;
|
||||
|
||||
int (*phy_init)(unsigned int regbase);
|
||||
void (*phy_deinit)(unsigned int regbase);
|
||||
/* This flag is used for that needs id pin checked by otg */
|
||||
unsigned int disable_otg_clock_gating:1;
|
||||
/* Force a_bus_req to be asserted */
|
||||
unsigned int otg_force_a_bus_req:1;
|
||||
|
||||
int (*phy_init)(void __iomem *regbase);
|
||||
void (*phy_deinit)(void __iomem *regbase);
|
||||
int (*set_vbus)(unsigned int vbus);
|
||||
int (*private_init)(void __iomem *opregs, void __iomem *phyregs);
|
||||
};
|
||||
|
||||
#ifndef CONFIG_HAVE_CLK
|
||||
/* Dummy stub for clk framework */
|
||||
#define clk_get(dev, id) NULL
|
||||
#define clk_put(clock) do {} while (0)
|
||||
#define clk_enable(clock) do {} while (0)
|
||||
#define clk_disable(clock) do {} while (0)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* S3C24XX USB 2.0 High-speed USB controller gadget driver
|
||||
*
|
||||
* Copyright (c) 2010 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com/
|
||||
*
|
||||
* The S3C24XX USB 2.0 high-speed USB controller supports upto 9 endpoints.
|
||||
* Each endpoint can be configured as either in or out endpoint. Endpoints
|
||||
* can be configured for Bulk or Interrupt transfer mode.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_USB_S3C_HSUDC_H
|
||||
#define __LINUX_USB_S3C_HSUDC_H
|
||||
|
||||
/**
|
||||
* s3c24xx_hsudc_platdata - Platform data for USB High-Speed gadget controller.
|
||||
* @epnum: Number of endpoints to be instantiated by the controller driver.
|
||||
* @gpio_init: Platform specific USB related GPIO initialization.
|
||||
* @gpio_uninit: Platform specific USB releted GPIO uninitialzation.
|
||||
*
|
||||
* Representation of platform data for the S3C24XX USB 2.0 High Speed gadget
|
||||
* controllers.
|
||||
*/
|
||||
struct s3c24xx_hsudc_platdata {
|
||||
unsigned int epnum;
|
||||
void (*gpio_init)(void);
|
||||
void (*gpio_uninit)(void);
|
||||
};
|
||||
|
||||
#endif /* __LINUX_USB_S3C_HSUDC_H */
|
|
@ -20,6 +20,7 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
|
||||
|
@ -32,6 +33,9 @@ struct usb_ep;
|
|||
* @dma: DMA address corresponding to 'buf'. If you don't set this
|
||||
* field, and the usb controller needs one, it is responsible
|
||||
* for mapping and unmapping the buffer.
|
||||
* @sg: a scatterlist for SG-capable controllers.
|
||||
* @num_sgs: number of SG entries
|
||||
* @num_mapped_sgs: number of SG entries mapped to DMA (internal)
|
||||
* @length: Length of that data
|
||||
* @stream_id: The stream id, when USB3.0 bulk streams are being used
|
||||
* @no_interrupt: If true, hints that no completion irq is needed.
|
||||
|
@ -88,6 +92,10 @@ struct usb_request {
|
|||
unsigned length;
|
||||
dma_addr_t dma;
|
||||
|
||||
struct scatterlist *sg;
|
||||
unsigned num_sgs;
|
||||
unsigned num_mapped_sgs;
|
||||
|
||||
unsigned stream_id:16;
|
||||
unsigned no_interrupt:1;
|
||||
unsigned zero:1;
|
||||
|
@ -164,7 +172,7 @@ struct usb_ep {
|
|||
unsigned maxpacket:16;
|
||||
unsigned max_streams:16;
|
||||
unsigned mult:2;
|
||||
unsigned maxburst:4;
|
||||
unsigned maxburst:5;
|
||||
u8 address;
|
||||
const struct usb_endpoint_descriptor *desc;
|
||||
const struct usb_ss_ep_comp_descriptor *comp_desc;
|
||||
|
@ -479,6 +487,7 @@ struct usb_gadget_ops {
|
|||
* @speed: Speed of current connection to USB host.
|
||||
* @max_speed: Maximal speed the UDC can handle. UDC must support this
|
||||
* and all slower speeds.
|
||||
* @sg_supported: true if we can handle scatter-gather
|
||||
* @is_otg: True if the USB device port uses a Mini-AB jack, so that the
|
||||
* gadget driver must provide a USB OTG descriptor.
|
||||
* @is_a_peripheral: False unless is_otg, the "A" end of a USB cable
|
||||
|
@ -519,6 +528,7 @@ struct usb_gadget {
|
|||
struct list_head ep_list; /* of usb_ep */
|
||||
enum usb_device_speed speed;
|
||||
enum usb_device_speed max_speed;
|
||||
unsigned sg_supported:1;
|
||||
unsigned is_otg:1;
|
||||
unsigned is_a_peripheral:1;
|
||||
unsigned b_hnp_enable:1;
|
||||
|
|
Loading…
Reference in New Issue