diff options
Diffstat (limited to 'drivers/usb/dwc2/hcd.c')
| -rw-r--r-- | drivers/usb/dwc2/hcd.c | 147 | 
1 files changed, 118 insertions, 29 deletions
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index edaf0b6af4f0..6e2cdd7b93d4 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1567,11 +1567,20 @@ static void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg,  	}  	if (hsotg->params.host_dma) { -		dwc2_writel((u32)chan->xfer_dma, -			    hsotg->regs + HCDMA(chan->hc_num)); +		dma_addr_t dma_addr; + +		if (chan->align_buf) { +			if (dbg_hc(chan)) +				dev_vdbg(hsotg->dev, "align_buf\n"); +			dma_addr = chan->align_buf; +		} else { +			dma_addr = chan->xfer_dma; +		} +		dwc2_writel((u32)dma_addr, hsotg->regs + HCDMA(chan->hc_num)); +  		if (dbg_hc(chan))  			dev_vdbg(hsotg->dev, "Wrote %08lx to HCDMA(%d)\n", -				 (unsigned long)chan->xfer_dma, chan->hc_num); +				 (unsigned long)dma_addr, chan->hc_num);  	}  	/* Start the split */ @@ -2625,36 +2634,66 @@ static void dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg,  	}  } -#define DWC2_USB_DMA_ALIGN 4 +static int dwc2_alloc_split_dma_aligned_buf(struct dwc2_hsotg *hsotg, +					    struct dwc2_qh *qh, +					    struct dwc2_host_chan *chan) +{ +	if (!hsotg->unaligned_cache || +	    chan->max_packet > DWC2_KMEM_UNALIGNED_BUF_SIZE) +		return -ENOMEM; -struct dma_aligned_buffer { -	void *kmalloc_ptr; -	void *old_xfer_buffer; -	u8 data[0]; -}; +	if (!qh->dw_align_buf) { +		qh->dw_align_buf = kmem_cache_alloc(hsotg->unaligned_cache, +						    GFP_ATOMIC | GFP_DMA); +		if (!qh->dw_align_buf) +			return -ENOMEM; +	} + +	qh->dw_align_buf_dma = dma_map_single(hsotg->dev, qh->dw_align_buf, +					      DWC2_KMEM_UNALIGNED_BUF_SIZE, +					      DMA_FROM_DEVICE); + +	if (dma_mapping_error(hsotg->dev, qh->dw_align_buf_dma)) { +		dev_err(hsotg->dev, "can't map align_buf\n"); +		chan->align_buf = 0; +		return -EINVAL; +	} + +	chan->align_buf = qh->dw_align_buf_dma; +	return 0; +} + +#define DWC2_USB_DMA_ALIGN 4  static void dwc2_free_dma_aligned_buffer(struct urb *urb)  { -	struct dma_aligned_buffer *temp; +	void *stored_xfer_buffer; +	size_t length;  	if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))  		return; -	temp = container_of(urb->transfer_buffer, -			    struct dma_aligned_buffer, data); +	/* Restore urb->transfer_buffer from the end of the allocated area */ +	memcpy(&stored_xfer_buffer, urb->transfer_buffer + +	       urb->transfer_buffer_length, sizeof(urb->transfer_buffer)); -	if (usb_urb_dir_in(urb)) -		memcpy(temp->old_xfer_buffer, temp->data, -		       urb->transfer_buffer_length); -	urb->transfer_buffer = temp->old_xfer_buffer; -	kfree(temp->kmalloc_ptr); +	if (usb_urb_dir_in(urb)) { +		if (usb_pipeisoc(urb->pipe)) +			length = urb->transfer_buffer_length; +		else +			length = urb->actual_length; + +		memcpy(stored_xfer_buffer, urb->transfer_buffer, length); +	} +	kfree(urb->transfer_buffer); +	urb->transfer_buffer = stored_xfer_buffer;  	urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;  }  static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)  { -	struct dma_aligned_buffer *temp, *kmalloc_ptr; +	void *kmalloc_ptr;  	size_t kmalloc_size;  	if (urb->num_sgs || urb->sg || @@ -2662,22 +2701,29 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)  	    !((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1)))  		return 0; -	/* Allocate a buffer with enough padding for alignment */ +	/* +	 * Allocate a buffer with enough padding for original transfer_buffer +	 * pointer. This allocation is guaranteed to be aligned properly for +	 * DMA +	 */  	kmalloc_size = urb->transfer_buffer_length + -		sizeof(struct dma_aligned_buffer) + DWC2_USB_DMA_ALIGN - 1; +		sizeof(urb->transfer_buffer);  	kmalloc_ptr = kmalloc(kmalloc_size, mem_flags);  	if (!kmalloc_ptr)  		return -ENOMEM; -	/* Position our struct dma_aligned_buffer such that data is aligned */ -	temp = PTR_ALIGN(kmalloc_ptr + 1, DWC2_USB_DMA_ALIGN) - 1; -	temp->kmalloc_ptr = kmalloc_ptr; -	temp->old_xfer_buffer = urb->transfer_buffer; +	/* +	 * Position value of original urb->transfer_buffer pointer to the end +	 * of allocation for later referencing +	 */ +	memcpy(kmalloc_ptr + urb->transfer_buffer_length, +	       &urb->transfer_buffer, sizeof(urb->transfer_buffer)); +  	if (usb_urb_dir_out(urb)) -		memcpy(temp->data, urb->transfer_buffer, +		memcpy(kmalloc_ptr, urb->transfer_buffer,  		       urb->transfer_buffer_length); -	urb->transfer_buffer = temp->data; +	urb->transfer_buffer = kmalloc_ptr;  	urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; @@ -2802,6 +2848,32 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)  	/* Set the transfer attributes */  	dwc2_hc_init_xfer(hsotg, chan, qtd); +	/* For non-dword aligned buffers */ +	if (hsotg->params.host_dma && qh->do_split && +	    chan->ep_is_in && (chan->xfer_dma & 0x3)) { +		dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); +		if (dwc2_alloc_split_dma_aligned_buf(hsotg, qh, chan)) { +			dev_err(hsotg->dev, +				"Failed to allocate memory to handle non-aligned buffer\n"); +			/* Add channel back to free list */ +			chan->align_buf = 0; +			chan->multi_count = 0; +			list_add_tail(&chan->hc_list_entry, +				      &hsotg->free_hc_list); +			qtd->in_process = 0; +			qh->channel = NULL; +			return -ENOMEM; +		} +	} else { +		/* +		 * We assume that DMA is always aligned in non-split +		 * case or split out case. Warn if not. +		 */ +		WARN_ON_ONCE(hsotg->params.host_dma && +			     (chan->xfer_dma & 0x3)); +		chan->align_buf = 0; +	} +  	if (chan->ep_type == USB_ENDPOINT_XFER_INT ||  	    chan->ep_type == USB_ENDPOINT_XFER_ISOC)  		/* @@ -5246,6 +5318,19 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)  		}  	} +	if (hsotg->params.host_dma) { +		/* +		 * Create kmem caches to handle non-aligned buffer +		 * in Buffer DMA mode. +		 */ +		hsotg->unaligned_cache = kmem_cache_create("dwc2-unaligned-dma", +						DWC2_KMEM_UNALIGNED_BUF_SIZE, 4, +						SLAB_CACHE_DMA, NULL); +		if (!hsotg->unaligned_cache) +			dev_err(hsotg->dev, +				"unable to create dwc2 unaligned cache\n"); +	} +  	hsotg->otg_port = 1;  	hsotg->frame_list = NULL;  	hsotg->frame_list_dma = 0; @@ -5280,8 +5365,9 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)  	return 0;  error4: -	kmem_cache_destroy(hsotg->desc_gen_cache); +	kmem_cache_destroy(hsotg->unaligned_cache);  	kmem_cache_destroy(hsotg->desc_hsisoc_cache); +	kmem_cache_destroy(hsotg->desc_gen_cache);  error3:  	dwc2_hcd_release(hsotg);  error2: @@ -5322,8 +5408,9 @@ void dwc2_hcd_remove(struct dwc2_hsotg *hsotg)  	usb_remove_hcd(hcd);  	hsotg->priv = NULL; -	kmem_cache_destroy(hsotg->desc_gen_cache); +	kmem_cache_destroy(hsotg->unaligned_cache);  	kmem_cache_destroy(hsotg->desc_hsisoc_cache); +	kmem_cache_destroy(hsotg->desc_gen_cache);  	dwc2_hcd_release(hsotg);  	usb_put_hcd(hcd); @@ -5435,7 +5522,7 @@ int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg)  	dwc2_writel(hprt0, hsotg->regs + HPRT0);  	/* Wait for the HPRT0.PrtSusp register field to be set */ -	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 300)) +	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000))  		dev_warn(hsotg->dev, "Suspend wasn't generated\n");  	/* @@ -5616,6 +5703,8 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,  		return ret;  	} +	dwc2_hcd_rem_wakeup(hsotg); +  	hsotg->hibernated = 0;  	hsotg->bus_suspended = 0;  	hsotg->lx_state = DWC2_L0;  | 
