[PATCH v2 3/3] usb: dwc2: Add High Bandwidth ISOC OUT support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Updated checking of chain full condition based on  mult count.
For each packet in uframe (dpi) created new desc by
setting size of transfer to mps. Buffer addresses in descs
differ by mps in function dwc2_gadget_fill_isoc_desc().

In function dwc2_gadget_start_isoc_ddma() upadted loop
boundaries according to desc chain length.

In function dwc2_hsotg_ep_queue() added request length
checking for HB ISOC OUT transfers. Added dword aligned
limitation on maxpacket size for HB ISOC OUT's.

In function dwc2_gadget_complete_isoc_request_ddma()
separated processing of descs for HB ISOC OUT.
If completed descs PID equal MDATA do nothing, else
get remaining for frame descs and process them.
Actual length accumulated based on descs. Break
execution of the loop if data packet PID not MDATA.
If host sends less than mult data packets then skipping
unused desc for current uframe by restarting ISOC transfers.

In function dwc2_hsotg_ep_enable() desc chain allocation/
deallocation increased by mult times. Added bInterval
limit checking for HB ISOC OUT because on completion frame
number from desc used to check frame elapsed or no.

Signed-off-by: Minas Harutyunyan <hminas@xxxxxxxxxxxx>
---
 drivers/usb/dwc2/gadget.c | 344 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 278 insertions(+), 66 deletions(-)

diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index d6a57606e49f..c0db308e3665 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -806,55 +806,82 @@ static int dwc2_gadget_fill_isoc_desc(struct dwc2_hsotg_ep *hs_ep,
 	u32 index;
 	u32 maxsize = 0;
 	u32 mask = 0;
+	u8 dpi, i;
 
+	/* Get descritor length limits */
 	maxsize = dwc2_gadget_get_desc_params(hs_ep, &mask);
 
 	index = hs_ep->next_desc;
-	desc = &hs_ep->desc_list[index];
+
+	dpi = 1;
+	if (!hs_ep->dir_in)
+		dpi = hs_ep->mc;
 
 	/* Check if descriptor chain full */
-	if ((desc->status >> DEV_DMA_BUFF_STS_SHIFT) ==
-	    DEV_DMA_BUFF_STS_HREADY) {
-		dev_dbg(hsotg->dev, "%s: desc chain full\n", __func__);
-		return 1;
+	for (i = 0; i < dpi; i++) {
+		/* Check descriptor chain rollover */
+		if ((index + i) >= dpi * MAX_DMA_DESC_NUM_GENERIC)
+			index = -1;
+
+		desc = &hs_ep->desc_list[index + i];
+		if ((desc->status >> DEV_DMA_BUFF_STS_SHIFT) ==
+		    DEV_DMA_BUFF_STS_HREADY) {
+			dev_dbg(hsotg->dev, "%s: desc chain full\n", __func__);
+			return 1;
+		}
 	}
 
-	/* Clear L bit of previous desc if more than one entries in the chain */
-	if (hs_ep->next_desc)
-		hs_ep->desc_list[index - 1].status &= ~DEV_DMA_L;
+	for (i = 0; i < dpi; i++) {
+		index = hs_ep->next_desc;
 
-	dev_dbg(hsotg->dev, "%s: Filling ep %d, dir %s isoc desc # %d\n",
-		__func__, hs_ep->index, hs_ep->dir_in ? "in" : "out", index);
+		/* Clear L bit of previous desc if more
+		 * than one entries in the chain
+		 */
+		if (hs_ep->next_desc)
+			hs_ep->desc_list[index - 1].status &= ~DEV_DMA_L;
 
-	desc->status = 0;
-	desc->status |= (DEV_DMA_BUFF_STS_HBUSY	<< DEV_DMA_BUFF_STS_SHIFT);
+		desc = &hs_ep->desc_list[index];
 
-	desc->buf = dma_buff;
-	desc->status |= (DEV_DMA_L | DEV_DMA_IOC |
-			 ((len << DEV_DMA_NBYTES_SHIFT) & mask));
+		dev_dbg(hsotg->dev, "%s: Filling ep %d, dir %s isoc desc # %d\n",
+			__func__, hs_ep->index, hs_ep->dir_in ? "in" : "out",
+			index);
 
-	if (hs_ep->dir_in) {
-		desc->status |= ((hs_ep->mc << DEV_DMA_ISOC_PID_SHIFT) &
-				 DEV_DMA_ISOC_PID_MASK) |
-				((len % hs_ep->ep.maxpacket) ?
-				 DEV_DMA_SHORT : 0) |
-				((hs_ep->target_frame <<
-				  DEV_DMA_ISOC_FRNUM_SHIFT) &
-				 DEV_DMA_ISOC_FRNUM_MASK);
-	}
+		desc->status = 0;
+		desc->status |= (DEV_DMA_BUFF_STS_HBUSY <<
+				DEV_DMA_BUFF_STS_SHIFT);
 
-	desc->status &= ~DEV_DMA_BUFF_STS_MASK;
-	desc->status |= (DEV_DMA_BUFF_STS_HREADY << DEV_DMA_BUFF_STS_SHIFT);
+		desc->buf = dma_buff + i * hs_ep->ep.maxpacket;
+		desc->status |= (DEV_DMA_L | DEV_DMA_IOC);
+		if (hs_ep->dir_in)
+			desc->status |= ((len << DEV_DMA_NBYTES_SHIFT)
+					& mask);
+		else
+			desc->status |= ((hs_ep->ep.maxpacket <<
+					DEV_DMA_NBYTES_SHIFT) & mask);
+
+		if (hs_ep->dir_in) {
+			desc->status |= ((hs_ep->mc << DEV_DMA_ISOC_PID_SHIFT) &
+					 DEV_DMA_ISOC_PID_MASK) |
+					((len % hs_ep->ep.maxpacket) ?
+					 DEV_DMA_SHORT : 0) |
+					((hs_ep->target_frame <<
+					  DEV_DMA_ISOC_FRNUM_SHIFT) &
+					 DEV_DMA_ISOC_FRNUM_MASK);
+		}
 
-	/* Increment frame number by interval for IN */
-	if (hs_ep->dir_in)
-		dwc2_gadget_incr_frame_num(hs_ep);
+		desc->status &= ~DEV_DMA_BUFF_STS_MASK;
+		desc->status |= (DEV_DMA_BUFF_STS_HREADY
+				 << DEV_DMA_BUFF_STS_SHIFT);
 
-	/* Update index of last configured entry in the chain */
-	hs_ep->next_desc++;
-	if (hs_ep->next_desc >= MAX_DMA_DESC_NUM_GENERIC)
-		hs_ep->next_desc = 0;
+		/* Increment frame number by interval for IN */
+		if (hs_ep->dir_in)
+			dwc2_gadget_incr_frame_num(hs_ep);
 
+		/* Update index of last configured entry in the chain */
+		hs_ep->next_desc++;
+		if (hs_ep->next_desc >= dpi * MAX_DMA_DESC_NUM_GENERIC)
+			hs_ep->next_desc = 0;
+	}
 	return 0;
 }
 
@@ -876,14 +903,23 @@ static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep)
 	u32 depctl;
 	u32 ctrl;
 	struct dwc2_dma_desc *desc;
+	u8 dpi;
 
 	if (list_empty(&hs_ep->queue)) {
+		hs_ep->target_frame = TARGET_FRAME_INITIAL;
 		dev_dbg(hsotg->dev, "%s: No requests in queue\n", __func__);
 		return;
 	}
 
+	/* Descriptor count per interval (dpi): for IN set to 1, for OUT
+	 * set to mult, i.e. 1 descritor per each packet in frame
+	 */
+	dpi = 1;
+	if (!hs_ep->dir_in)
+		dpi = hs_ep->mc;
+
 	/* Initialize descriptor chain by Host Busy status */
-	for (i = 0; i < MAX_DMA_DESC_NUM_GENERIC; i++) {
+	for (i = 0; i < dpi * MAX_DMA_DESC_NUM_GENERIC; i++) {
 		desc = &hs_ep->desc_list[i];
 		desc->status = 0;
 		desc->status |= (DEV_DMA_BUFF_STS_HBUSY
@@ -904,6 +940,16 @@ static void dwc2_gadget_start_isoc_ddma(struct dwc2_hsotg_ep *hs_ep)
 	/* write descriptor chain address to control register */
 	dwc2_writel(hs_ep->desc_list_dma, hsotg->regs + dma_reg);
 
+	/* Wait for EOPF interrupt for current (u)frame */
+	if (dpi > 1) {
+		if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS,
+					    GINTSTS_EOPF, 100))
+			dev_warn(hsotg->dev,
+				 "%s: timeout GINTSTS.EOPF\n", __func__);
+		dwc2_writel(DXEPINT_OUTTKNEPDIS,
+			    hsotg->regs + DOEPINT(hs_ep->index));
+	}
+
 	ctrl = dwc2_readl(hsotg->regs + depctl);
 	ctrl |= DXEPCTL_EPENA | DXEPCTL_CNAK;
 	dwc2_writel(ctrl, hsotg->regs + depctl);
@@ -1310,10 +1356,17 @@ static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req,
 				req->length, maxsize);
 			return -EINVAL;
 		}
-		/* ISOC OUT high bandwidth not supported */
-		if (!hs_ep->dir_in && req->length > hs_ep->ep.maxpacket) {
-			dev_err(hs->dev, "ISOC OUT: wrong length %d (mps=%d)\n",
-				req->length, hs_ep->ep.maxpacket);
+		/* Checkings for ISOC OUT including high bandwidth */
+		if (!hs_ep->dir_in &&
+		    hs_ep->mc > 1 && (hs_ep->ep.maxpacket % 4)) {
+			dev_err(hs->dev, "mps not a dword aligned:%d\n",
+				hs_ep->ep.maxpacket);
+			return -EINVAL;
+		}
+		if (!hs_ep->dir_in &&
+		    (req->length > (hs_ep->mc * hs_ep->ep.maxpacket))) {
+			dev_err(hs->dev, "wrong len %d > mult*mps=%d\n",
+				req->length, hs_ep->mc * hs_ep->ep.maxpacket);
 			return -EINVAL;
 		}
 	}
@@ -2013,6 +2066,9 @@ static void dwc2_hsotg_complete_request(struct dwc2_hsotg *hsotg,
 		dwc2_gadget_start_next_request(hs_ep);
 }
 
+static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg,
+				   struct dwc2_hsotg_ep *hs_ep);
+
 /*
  * dwc2_gadget_complete_isoc_request_ddma - complete an isoc request in DDMA
  * @hs_ep: The endpoint the request was on.
@@ -2027,18 +2083,29 @@ static void dwc2_gadget_complete_isoc_request_ddma(struct dwc2_hsotg_ep *hs_ep)
 	struct dwc2_hsotg *hsotg = hs_ep->parent;
 	struct dwc2_hsotg_req *hs_req;
 	struct usb_request *ureq;
-	int index;
 	dma_addr_t dma_addr;
 	u32 dma_reg;
 	u32 depdma;
 	u32 desc_sts;
 	u32 mask;
+	int idx;
+	u32 index;
+	u32 pcount;
+	u16 sumofpid;
+	u8 dpi;
+
+	dpi = 1;
+	if (!hs_ep->dir_in)
+		dpi = hs_ep->mc;
 
 	hs_req = get_ep_head(hs_ep);
 	if (!hs_req) {
+		hs_ep->target_frame = TARGET_FRAME_INITIAL;
+		dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep);
 		dev_warn(hsotg->dev, "%s: ISOC EP queue empty\n", __func__);
 		return;
 	}
+
 	ureq = &hs_req->req;
 
 	dma_addr = hs_ep->desc_list_dma;
@@ -2046,26 +2113,157 @@ static void dwc2_gadget_complete_isoc_request_ddma(struct dwc2_hsotg_ep *hs_ep)
 	dma_reg = hs_ep->dir_in ? DIEPDMA(hs_ep->index) : DOEPDMA(hs_ep->index);
 	depdma = dwc2_readl(hsotg->regs + dma_reg);
 
-	index = (depdma - dma_addr) / sizeof(struct dwc2_dma_desc) - 1;
+	idx = (depdma - dma_addr) / sizeof(struct dwc2_dma_desc) - 1;
 	/* Check descriptor chain rollover */
-	if (index < 0)
-		index = MAX_DMA_DESC_NUM_GENERIC - 1;
+	if (idx < 0)
+		index = dpi * MAX_DMA_DESC_NUM_GENERIC - 1;
+	else
+		index = idx;
 
 	desc_sts = hs_ep->desc_list[index].status;
-	/* Check completion status */
-	if ((desc_sts & DEV_DMA_STS_MASK) >> DEV_DMA_STS_SHIFT ==
-	    DEV_DMA_STS_SUCC) {
-		mask = hs_ep->dir_in ? DEV_DMA_ISOC_TX_NBYTES_MASK :
-		       DEV_DMA_ISOC_RX_NBYTES_MASK;
-		ureq->actual = ureq->length -
-			       ((desc_sts & mask) >>
-				DEV_DMA_ISOC_NBYTES_SHIFT);
-
-		/* Adjust actual len for ISOC Out if len is not align of 4 */
-		if (!hs_ep->dir_in && ureq->length & 0x3)
-			ureq->actual += 4 - (ureq->length & 0x3);
+
+	if (dpi == 1) {
+		/* Check completion status for not High Bandwidth ISOC Out */
+		if ((desc_sts & DEV_DMA_STS_MASK) >> DEV_DMA_STS_SHIFT ==
+		    DEV_DMA_STS_SUCC) {
+			mask = hs_ep->dir_in ? DEV_DMA_ISOC_TX_NBYTES_MASK :
+			       DEV_DMA_ISOC_RX_NBYTES_MASK;
+			ureq->actual = ureq->length -
+				       ((desc_sts & mask) >>
+					DEV_DMA_ISOC_NBYTES_SHIFT);
+
+			/* Adjust actual len for ISOC Out if len is
+			 * not align of 4.
+			 */
+			if (!hs_ep->dir_in && dpi == 1 && ureq->length & 0x3)
+				ureq->actual += 4 - (ureq->length & 0x3);
+		}
+		dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
+	} else {
+		/* Check completion status for High Bandwidth ISOC Out. */
+		/* Ignore interrupt if it not last in uframe. */
+		if (((desc_sts & DEV_DMA_ISOC_PID_MASK) >>
+		      DEV_DMA_ISOC_PID_SHIFT) == DEV_DMA_ISOC_PID_MDATA)
+			return;
+
+		idx = 0;
+		/* Complete requests by actual = 0 if some uframes are skiped */
+		while ((hs_ep->target_frame & (DEV_DMA_ISOC_FRNUM_MASK >>
+			DEV_DMA_ISOC_FRNUM_SHIFT)) !=
+		       (desc_sts & DEV_DMA_ISOC_FRNUM_MASK)
+			>> DEV_DMA_ISOC_FRNUM_SHIFT) {
+			idx++;
+			dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
+			hs_req = get_ep_head(hs_ep);
+			if (!hs_req) {
+				hs_ep->target_frame = TARGET_FRAME_INITIAL;
+				dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep);
+				dev_warn(hsotg->dev, "%s: ISOC EP queue empty\n",
+					 __func__);
+				/* Clear late XFERCOMPL interrupts if any */
+				dwc2_writel(DXEPINT_XFERCOMPL, hsotg->regs +
+					DOEPINT(hs_ep->index));
+				return;
+			}
+			ureq = &hs_req->req;
+			dwc2_gadget_incr_frame_num(hs_ep);
+		}
+
+		/* Increment 1 more to set target to next uframe */
+		dwc2_gadget_incr_frame_num(hs_ep);
+
+		/* Need to restart if any request completed in above loop */
+		if (idx) {
+			dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
+			hs_req = get_ep_head(hs_ep);
+			if (!hs_req) {
+				hs_ep->target_frame = TARGET_FRAME_INITIAL;
+				dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep);
+				dev_warn(hsotg->dev, "%s: ISOC EP queue empty\n",
+					 __func__);
+				/* Clear late XFERCOMPL interrupts, if any */
+				dwc2_writel(DXEPINT_XFERCOMPL, hsotg->regs +
+					DOEPINT(hs_ep->index));
+				return;
+			}
+			goto out;
+		}
+		ureq = &hs_req->req;
+
+		/* Because of interrupt latency after receiving interrupt
+		 * for 1st descriptor and when start handling the descriptor
+		 * index can be shifted to second or third descriptor.
+		 * On last descriptor (for uframe) completion process all
+		 * descritors for current uframe.
+		 */
+		sumofpid = 0;
+
+		for (idx = (index / dpi) * dpi; idx <= index; idx++) {
+			desc_sts = hs_ep->desc_list[idx].status;
+
+			if ((desc_sts & DEV_DMA_STS_MASK) >> DEV_DMA_STS_SHIFT
+			     == DEV_DMA_STS_SUCC) {
+				ureq->actual += hs_ep->ep.maxpacket -
+						((desc_sts &
+						  DEV_DMA_ISOC_RX_NBYTES_MASK) >>
+						  DEV_DMA_ISOC_NBYTES_SHIFT);
+				sumofpid += (desc_sts & DEV_DMA_ISOC_PID_MASK)
+					     >> DEV_DMA_ISOC_PID_SHIFT;
+				} else {
+					ureq->actual = 0;
+					break;
+				}
+		}
+
+		/* Safety check packets PID sequence based on sumofpids:
+		 * if packet count = 1 then sumofpid should be 0
+		 * if packet count = 2 then sumofpid should be 3+2=5
+		 * if packet count = 3 then sumofpid should be 3+3+1=7
+		 */
+		pcount = index - (index / dpi) * dpi + 1;
+
+		switch (pcount) {
+		case 1:
+			if (sumofpid != 0)
+				ureq->actual = 0;
+			break;
+		case 2:
+			if (sumofpid != 5)
+				ureq->actual = 0;
+			break;
+		case 3:
+			if (sumofpid != 7)
+				ureq->actual = 0;
+			break;
+		default:
+			dev_dbg(hsotg->dev, "%s: wrong sum of PID's %d\n",
+				__func__, sumofpid);
+		}
+
+		/* Clear EOPF intr for frame if some descs should be skiped */
+		if (pcount < dpi)
+			dwc2_writel(GINTSTS_EOPF, hsotg->regs + GINTSTS);
+
+		/* Safety check */
+		if (ureq->actual > ureq->length)
+			ureq->actual = ureq->length;
+
+		dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
+
+		/* If packet count in current uframe less than mult then need
+		 * to skip unused for uframe descrioptors by restarting xfers
+		 */
+		idx = dpi - (index % dpi) - 1;
+out:
+		if (idx > 0) {
+			dwc2_hsotg_ep_stop_xfr(hsotg, hs_ep);
+			dwc2_gadget_start_isoc_ddma(hs_ep);
+		}
+
+		/* Clear late XFERCOMPL and OUTTKNEPDIS interrupts if any */
+		dwc2_writel(DXEPINT_XFERCOMPL | DXEPINT_OUTTKNEPDIS,
+			    hsotg->regs + DOEPINT(hs_ep->index));
 	}
-	dwc2_hsotg_complete_request(hsotg, hs_ep, hs_req, 0);
 }
 
 /*
@@ -2737,12 +2935,14 @@ static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
 	 */
 	tmp = dwc2_hsotg_read_frameno(hsotg);
 
-	dwc2_hsotg_complete_request(hsotg, ep, get_ep_head(ep), 0);
-
 	if (using_desc_dma(hsotg)) {
 		if (ep->target_frame == TARGET_FRAME_INITIAL) {
-			/* Start first ISO Out */
+			dwc2_hsotg_complete_request(hsotg, ep,
+						    get_ep_head(ep), 0);
 			ep->target_frame = tmp;
+			dwc2_gadget_incr_frame_num(ep);
+			/* Clear EOPF interrupt and start ISOC OUT*/
+			dwc2_writel(GINTSTS_EOPF, hsotg->regs + GINTSTS);
 			dwc2_gadget_start_isoc_ddma(ep);
 		}
 		return;
@@ -3763,6 +3963,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
 	u32 mps;
 	u32 mc;
 	u32 mask;
+	u8 dpi;
 	unsigned int dir_in;
 	unsigned int i, val, size;
 	int ret = 0;
@@ -3785,17 +3986,20 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
 		return -EINVAL;
 	}
 
+	mc = usb_endpoint_maxp_mult(desc);
+
 	ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
 	/* ISOC DDMA supported bInterval up to 12 */
-	if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC &&
-	    dir_in && desc->bInterval > 12) {
-		dev_err(hsotg->dev,
-			"%s: ISOC IN: bInterval>12 not supported!\n", __func__);
-		return -EINVAL;
+	if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC) {
+		if ((dir_in || (!dir_in && mc > 1)) && desc->bInterval > 12) {
+			dev_err(hsotg->dev,
+				"%s: ISOC IN/OUT(HB): bInterval>12 not supported!\n",
+				__func__);
+			return -EINVAL;
+		}
 	}
 
 	mps = usb_endpoint_maxp(desc);
-	mc = usb_endpoint_maxp_mult(desc);
 
 	/* note, we handle this here instead of dwc2_hsotg_set_ep_maxpacket */
 
@@ -3805,10 +4009,18 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
 	dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n",
 		__func__, epctrl, epctrl_reg);
 
+	/* ISOC Descriptor count per interval (dpi): for IN set to 1,
+	 * for OUT set to mult, i.e. 1 descritor per each packet in frame
+	 */
+	dpi = 1;
+	if (using_desc_dma(hsotg) && ep_type == USB_ENDPOINT_XFER_ISOC &&
+	    !dir_in)
+		dpi = mc;
+
 	/* Allocate DMA descriptor chain for non-ctrl endpoints */
 	if (using_desc_dma(hsotg) && !hs_ep->desc_list) {
 		hs_ep->desc_list = dmam_alloc_coherent(hsotg->dev,
-			MAX_DMA_DESC_NUM_GENERIC *
+			dpi * MAX_DMA_DESC_NUM_GENERIC *
 			sizeof(struct dwc2_dma_desc),
 			&hs_ep->desc_list_dma, GFP_ATOMIC);
 		if (!hs_ep->desc_list) {
@@ -3929,7 +4141,7 @@ static int dwc2_hsotg_ep_enable(struct usb_ep *ep,
 
 error2:
 	if (ret && using_desc_dma(hsotg) && hs_ep->desc_list) {
-		dmam_free_coherent(hsotg->dev, MAX_DMA_DESC_NUM_GENERIC *
+		dmam_free_coherent(hsotg->dev, dpi * MAX_DMA_DESC_NUM_GENERIC *
 			sizeof(struct dwc2_dma_desc),
 			hs_ep->desc_list, hs_ep->desc_list_dma);
 		hs_ep->desc_list = NULL;
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux