diff options
Diffstat (limited to 'drivers/usb/musb/musb_host.c')
| -rw-r--r-- | drivers/usb/musb/musb_host.c | 119 | 
1 files changed, 118 insertions, 1 deletions
diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 1ce1fcf3f3e..8914dec49f0 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -2453,7 +2453,7 @@ static int musb_bus_suspend(struct usb_hcd *hcd)  	if (musb->is_active) {  		WARNING("trying to suspend as %s while active\n", -				otg_state_string(musb->xceiv->state)); +				usb_otg_state_string(musb->xceiv->state));  		return -EBUSY;  	} else  		return 0; @@ -2465,6 +2465,118 @@ static int musb_bus_resume(struct usb_hcd *hcd)  	return 0;  } + +#ifndef CONFIG_MUSB_PIO_ONLY + +#define MUSB_USB_DMA_ALIGN 4 + +struct musb_temp_buffer { +	void *kmalloc_ptr; +	void *old_xfer_buffer; +	u8 data[0]; +}; + +static void musb_free_temp_buffer(struct urb *urb) +{ +	enum dma_data_direction dir; +	struct musb_temp_buffer *temp; + +	if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) +		return; + +	dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + +	temp = container_of(urb->transfer_buffer, struct musb_temp_buffer, +			    data); + +	if (dir == DMA_FROM_DEVICE) { +		memcpy(temp->old_xfer_buffer, temp->data, +		       urb->transfer_buffer_length); +	} +	urb->transfer_buffer = temp->old_xfer_buffer; +	kfree(temp->kmalloc_ptr); + +	urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; +} + +static int musb_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) +{ +	enum dma_data_direction dir; +	struct musb_temp_buffer *temp; +	void *kmalloc_ptr; +	size_t kmalloc_size; + +	if (urb->num_sgs || urb->sg || +	    urb->transfer_buffer_length == 0 || +	    !((uintptr_t)urb->transfer_buffer & (MUSB_USB_DMA_ALIGN - 1))) +		return 0; + +	dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + +	/* Allocate a buffer with enough padding for alignment */ +	kmalloc_size = urb->transfer_buffer_length + +		sizeof(struct musb_temp_buffer) + MUSB_USB_DMA_ALIGN - 1; + +	kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); +	if (!kmalloc_ptr) +		return -ENOMEM; + +	/* Position our struct temp_buffer such that data is aligned */ +	temp = PTR_ALIGN(kmalloc_ptr, MUSB_USB_DMA_ALIGN); + + +	temp->kmalloc_ptr = kmalloc_ptr; +	temp->old_xfer_buffer = urb->transfer_buffer; +	if (dir == DMA_TO_DEVICE) +		memcpy(temp->data, urb->transfer_buffer, +		       urb->transfer_buffer_length); +	urb->transfer_buffer = temp->data; + +	urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; + +	return 0; +} + +static int musb_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, +				      gfp_t mem_flags) +{ +	struct musb	*musb = hcd_to_musb(hcd); +	int ret; + +	/* +	 * The DMA engine in RTL1.8 and above cannot handle +	 * DMA addresses that are not aligned to a 4 byte boundary. +	 * For such engine implemented (un)map_urb_for_dma hooks. +	 * Do not use these hooks for RTL<1.8 +	 */ +	if (musb->hwvers < MUSB_HWVERS_1800) +		return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); + +	ret = musb_alloc_temp_buffer(urb, mem_flags); +	if (ret) +		return ret; + +	ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); +	if (ret) +		musb_free_temp_buffer(urb); + +	return ret; +} + +static void musb_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) +{ +	struct musb	*musb = hcd_to_musb(hcd); + +	usb_hcd_unmap_urb_for_dma(hcd, urb); + +	/* Do not use this hook for RTL<1.8 (see description above) */ +	if (musb->hwvers < MUSB_HWVERS_1800) +		return; + +	musb_free_temp_buffer(urb); +} +#endif /* !CONFIG_MUSB_PIO_ONLY */ +  const struct hc_driver musb_hc_driver = {  	.description		= "musb-hcd",  	.product_desc		= "MUSB HDRC host driver", @@ -2484,6 +2596,11 @@ const struct hc_driver musb_hc_driver = {  	.urb_dequeue		= musb_urb_dequeue,  	.endpoint_disable	= musb_h_disable, +#ifndef CONFIG_MUSB_PIO_ONLY +	.map_urb_for_dma	= musb_map_urb_for_dma, +	.unmap_urb_for_dma	= musb_unmap_urb_for_dma, +#endif +  	.hub_status_data	= musb_hub_status_data,  	.hub_control		= musb_hub_control,  	.bus_suspend		= musb_bus_suspend,  |