usb: gadget: r8a66597-udc: add support for SUDMAC

SH7757 has a USB function with internal DMA controller (SUDMAC).
This patch supports the SUDMAC. The SUDMAC is incompatible with
general-purpose DMAC. So, it doesn't use dmaengine.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
Yoshihiro Shimoda 2011-09-30 20:07:38 +09:00 committed by Felipe Balbi
parent 12158f4280
commit b8a56e17e1
3 changed files with 430 additions and 20 deletions

View File

@ -18,13 +18,14 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include "r8a66597-udc.h"
#define DRIVER_VERSION "2009-08-18"
#define DRIVER_VERSION "2011-09-26"
static const char udc_name[] = "r8a66597_udc";
static const char *r8a66597_ep_name[] = {
@ -184,6 +185,54 @@ static inline void control_reg_sqclr(struct r8a66597 *r8a66597, u16 pipenum)
}
}
static void control_reg_sqset(struct r8a66597 *r8a66597, u16 pipenum)
{
unsigned long offset;
pipe_stop(r8a66597, pipenum);
if (pipenum == 0) {
r8a66597_bset(r8a66597, SQSET, DCPCTR);
} else if (pipenum < R8A66597_MAX_NUM_PIPE) {
offset = get_pipectr_addr(pipenum);
r8a66597_bset(r8a66597, SQSET, offset);
} else {
dev_err(r8a66597_to_dev(r8a66597),
"unexpect pipe num(%d)\n", pipenum);
}
}
static u16 control_reg_sqmon(struct r8a66597 *r8a66597, u16 pipenum)
{
unsigned long offset;
if (pipenum == 0) {
return r8a66597_read(r8a66597, DCPCTR) & SQMON;
} else if (pipenum < R8A66597_MAX_NUM_PIPE) {
offset = get_pipectr_addr(pipenum);
return r8a66597_read(r8a66597, offset) & SQMON;
} else {
dev_err(r8a66597_to_dev(r8a66597),
"unexpect pipe num(%d)\n", pipenum);
}
return 0;
}
static u16 save_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum)
{
return control_reg_sqmon(r8a66597, pipenum);
}
static void restore_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum,
u16 toggle)
{
if (toggle)
control_reg_sqset(r8a66597, pipenum);
else
control_reg_sqclr(r8a66597, pipenum);
}
static inline int get_buffer_size(struct r8a66597 *r8a66597, u16 pipenum)
{
u16 tmp;
@ -220,18 +269,51 @@ static inline unsigned short mbw_value(struct r8a66597 *r8a66597)
return MBW_16;
}
static void r8a66597_change_curpipe(struct r8a66597 *r8a66597, u16 pipenum,
u16 isel, u16 fifosel)
{
u16 tmp, mask, loop;
int i = 0;
if (!pipenum) {
mask = ISEL | CURPIPE;
loop = isel;
} else {
mask = CURPIPE;
loop = pipenum;
}
r8a66597_mdfy(r8a66597, loop, mask, fifosel);
do {
tmp = r8a66597_read(r8a66597, fifosel);
if (i++ > 1000000) {
dev_err(r8a66597_to_dev(r8a66597),
"r8a66597: register%x, loop %x "
"is timeout\n", fifosel, loop);
break;
}
ndelay(1);
} while ((tmp & mask) != loop);
}
static inline void pipe_change(struct r8a66597 *r8a66597, u16 pipenum)
{
struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum];
if (ep->use_dma)
return;
r8a66597_bclr(r8a66597, DREQE, ep->fifosel);
r8a66597_mdfy(r8a66597, pipenum, CURPIPE, ep->fifosel);
ndelay(450);
r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
if (r8a66597_is_sudmac(r8a66597) && ep->use_dma)
r8a66597_bclr(r8a66597, mbw_value(r8a66597), ep->fifosel);
else
r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
if (ep->use_dma)
r8a66597_bset(r8a66597, DREQE, ep->fifosel);
}
static int pipe_buffer_setting(struct r8a66597 *r8a66597,
@ -336,9 +418,15 @@ static void r8a66597_ep_setting(struct r8a66597 *r8a66597,
ep->fifoaddr = CFIFO;
ep->fifosel = CFIFOSEL;
ep->fifoctr = CFIFOCTR;
ep->fifotrn = 0;
ep->pipectr = get_pipectr_addr(pipenum);
if (is_bulk_pipe(pipenum) || is_isoc_pipe(pipenum)) {
ep->pipetre = get_pipetre_addr(pipenum);
ep->pipetrn = get_pipetrn_addr(pipenum);
} else {
ep->pipetre = 0;
ep->pipetrn = 0;
}
ep->pipenum = pipenum;
ep->ep.maxpacket = usb_endpoint_maxp(desc);
r8a66597->pipenum2ep[pipenum] = ep;
@ -498,6 +586,124 @@ static void start_ep0_write(struct r8a66597_ep *ep,
}
}
static void disable_fifosel(struct r8a66597 *r8a66597, u16 pipenum,
u16 fifosel)
{
u16 tmp;
tmp = r8a66597_read(r8a66597, fifosel) & CURPIPE;
if (tmp == pipenum)
r8a66597_change_curpipe(r8a66597, 0, 0, fifosel);
}
static void change_bfre_mode(struct r8a66597 *r8a66597, u16 pipenum,
int enable)
{
struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum];
u16 tmp, toggle;
/* check current BFRE bit */
r8a66597_write(r8a66597, pipenum, PIPESEL);
tmp = r8a66597_read(r8a66597, PIPECFG) & R8A66597_BFRE;
if ((enable && tmp) || (!enable && !tmp))
return;
/* change BFRE bit */
pipe_stop(r8a66597, pipenum);
disable_fifosel(r8a66597, pipenum, CFIFOSEL);
disable_fifosel(r8a66597, pipenum, D0FIFOSEL);
disable_fifosel(r8a66597, pipenum, D1FIFOSEL);
toggle = save_usb_toggle(r8a66597, pipenum);
r8a66597_write(r8a66597, pipenum, PIPESEL);
if (enable)
r8a66597_bset(r8a66597, R8A66597_BFRE, PIPECFG);
else
r8a66597_bclr(r8a66597, R8A66597_BFRE, PIPECFG);
/* initialize for internal BFRE flag */
r8a66597_bset(r8a66597, ACLRM, ep->pipectr);
r8a66597_bclr(r8a66597, ACLRM, ep->pipectr);
restore_usb_toggle(r8a66597, pipenum, toggle);
}
static int sudmac_alloc_channel(struct r8a66597 *r8a66597,
struct r8a66597_ep *ep,
struct r8a66597_request *req)
{
struct r8a66597_dma *dma;
if (!r8a66597_is_sudmac(r8a66597))
return -ENODEV;
/* Check transfer type */
if (!is_bulk_pipe(ep->pipenum))
return -EIO;
if (r8a66597->dma.used)
return -EBUSY;
/* set SUDMAC parameters */
dma = &r8a66597->dma;
dma->used = 1;
if (ep->desc->bEndpointAddress & USB_DIR_IN) {
dma->dir = 1;
} else {
dma->dir = 0;
change_bfre_mode(r8a66597, ep->pipenum, 1);
}
/* set r8a66597_ep paramters */
ep->use_dma = 1;
ep->dma = dma;
ep->fifoaddr = D0FIFO;
ep->fifosel = D0FIFOSEL;
ep->fifoctr = D0FIFOCTR;
/* dma mapping */
req->req.dma = dma_map_single(r8a66597_to_dev(ep->r8a66597),
req->req.buf, req->req.length,
dma->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
return 0;
}
static void sudmac_free_channel(struct r8a66597 *r8a66597,
struct r8a66597_ep *ep,
struct r8a66597_request *req)
{
if (!r8a66597_is_sudmac(r8a66597))
return;
dma_unmap_single(r8a66597_to_dev(ep->r8a66597),
req->req.dma, req->req.length,
ep->dma->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
r8a66597_bclr(r8a66597, DREQE, ep->fifosel);
r8a66597_change_curpipe(r8a66597, 0, 0, ep->fifosel);
ep->dma->used = 0;
ep->use_dma = 0;
ep->fifoaddr = CFIFO;
ep->fifosel = CFIFOSEL;
ep->fifoctr = CFIFOCTR;
}
static void sudmac_start(struct r8a66597 *r8a66597, struct r8a66597_ep *ep,
struct r8a66597_request *req)
{
BUG_ON(req->req.length == 0);
r8a66597_sudmac_write(r8a66597, LBA_WAIT, CH0CFG);
r8a66597_sudmac_write(r8a66597, req->req.dma, CH0BA);
r8a66597_sudmac_write(r8a66597, req->req.length, CH0BBC);
r8a66597_sudmac_write(r8a66597, CH0ENDE, DINTCTRL);
r8a66597_sudmac_write(r8a66597, DEN, CH0DEN);
}
static void start_packet_write(struct r8a66597_ep *ep,
struct r8a66597_request *req)
{
@ -508,11 +714,29 @@ static void start_packet_write(struct r8a66597_ep *ep,
disable_irq_empty(r8a66597, ep->pipenum);
pipe_start(r8a66597, ep->pipenum);
tmp = r8a66597_read(r8a66597, ep->fifoctr);
if (unlikely((tmp & FRDY) == 0))
pipe_irq_enable(r8a66597, ep->pipenum);
else
irq_packet_write(ep, req);
if (req->req.length == 0) {
transfer_complete(ep, req, 0);
} else {
r8a66597_write(r8a66597, ~(1 << ep->pipenum), BRDYSTS);
if (sudmac_alloc_channel(r8a66597, ep, req) < 0) {
/* PIO mode */
pipe_change(r8a66597, ep->pipenum);
disable_irq_empty(r8a66597, ep->pipenum);
pipe_start(r8a66597, ep->pipenum);
tmp = r8a66597_read(r8a66597, ep->fifoctr);
if (unlikely((tmp & FRDY) == 0))
pipe_irq_enable(r8a66597, ep->pipenum);
else
irq_packet_write(ep, req);
} else {
/* DMA mode */
pipe_change(r8a66597, ep->pipenum);
disable_irq_nrdy(r8a66597, ep->pipenum);
pipe_start(r8a66597, ep->pipenum);
enable_irq_nrdy(r8a66597, ep->pipenum);
sudmac_start(r8a66597, ep, req);
}
}
}
static void start_packet_read(struct r8a66597_ep *ep,
@ -527,17 +751,26 @@ static void start_packet_read(struct r8a66597_ep *ep,
pipe_start(r8a66597, pipenum);
pipe_irq_enable(r8a66597, pipenum);
} else {
if (ep->use_dma) {
r8a66597_bset(r8a66597, TRCLR, ep->fifosel);
pipe_change(r8a66597, pipenum);
r8a66597_bset(r8a66597, TRENB, ep->fifosel);
pipe_stop(r8a66597, pipenum);
if (ep->pipetre) {
enable_irq_nrdy(r8a66597, pipenum);
r8a66597_write(r8a66597, TRCLR, ep->pipetre);
r8a66597_write(r8a66597,
(req->req.length + ep->ep.maxpacket - 1)
/ ep->ep.maxpacket,
ep->fifotrn);
DIV_ROUND_UP(req->req.length, ep->ep.maxpacket),
ep->pipetrn);
r8a66597_bset(r8a66597, TRENB, ep->pipetre);
}
if (sudmac_alloc_channel(r8a66597, ep, req) < 0) {
/* PIO mode */
change_bfre_mode(r8a66597, ep->pipenum, 0);
pipe_start(r8a66597, pipenum); /* trigger once */
pipe_irq_enable(r8a66597, pipenum);
} else {
pipe_change(r8a66597, pipenum);
sudmac_start(r8a66597, ep, req);
pipe_start(r8a66597, pipenum); /* trigger once */
}
pipe_start(r8a66597, pipenum); /* trigger once */
pipe_irq_enable(r8a66597, pipenum);
}
}
@ -694,6 +927,9 @@ __acquires(r8a66597->lock)
if (!list_empty(&ep->queue))
restart = 1;
if (ep->use_dma)
sudmac_free_channel(ep->r8a66597, ep, req);
spin_unlock(&ep->r8a66597->lock);
req->req.complete(&ep->ep, &req->req);
spin_lock(&ep->r8a66597->lock);
@ -1170,6 +1406,65 @@ __acquires(r8a66597->lock)
}
}
static void sudmac_finish(struct r8a66597 *r8a66597, struct r8a66597_ep *ep)
{
u16 pipenum;
struct r8a66597_request *req;
u32 len;
int i = 0;
pipenum = ep->pipenum;
pipe_change(r8a66597, pipenum);
while (!(r8a66597_read(r8a66597, ep->fifoctr) & FRDY)) {
udelay(1);
if (unlikely(i++ >= 10000)) { /* timeout = 10 msec */
dev_err(r8a66597_to_dev(r8a66597),
"%s: FRDY was not set (%d)\n",
__func__, pipenum);
return;
}
}
r8a66597_bset(r8a66597, BCLR, ep->fifoctr);
req = get_request_from_ep(ep);
/* prepare parameters */
len = r8a66597_sudmac_read(r8a66597, CH0CBC);
req->req.actual += len;
/* clear */
r8a66597_sudmac_write(r8a66597, CH0STCLR, DSTSCLR);
/* check transfer finish */
if ((!req->req.zero && (req->req.actual == req->req.length))
|| (len % ep->ep.maxpacket)) {
if (ep->dma->dir) {
disable_irq_ready(r8a66597, pipenum);
enable_irq_empty(r8a66597, pipenum);
} else {
/* Clear the interrupt flag for next transfer */
r8a66597_write(r8a66597, ~(1 << pipenum), BRDYSTS);
transfer_complete(ep, req, 0);
}
}
}
static void r8a66597_sudmac_irq(struct r8a66597 *r8a66597)
{
u32 irqsts;
struct r8a66597_ep *ep;
u16 pipenum;
irqsts = r8a66597_sudmac_read(r8a66597, DINTSTS);
if (irqsts & CH0ENDS) {
r8a66597_sudmac_write(r8a66597, CH0ENDC, DINTSTSCLR);
pipenum = (r8a66597_read(r8a66597, D0FIFOSEL) & CURPIPE);
ep = r8a66597->pipenum2ep[pipenum];
sudmac_finish(r8a66597, ep);
}
}
static irqreturn_t r8a66597_irq(int irq, void *_r8a66597)
{
struct r8a66597 *r8a66597 = _r8a66597;
@ -1180,6 +1475,9 @@ static irqreturn_t r8a66597_irq(int irq, void *_r8a66597)
u16 savepipe;
u16 mask0;
if (r8a66597_is_sudmac(r8a66597))
r8a66597_sudmac_irq(r8a66597);
spin_lock(&r8a66597->lock);
intsts0 = r8a66597_read(r8a66597, INTSTS0);
@ -1556,6 +1854,8 @@ static int __exit r8a66597_remove(struct platform_device *pdev)
usb_del_gadget_udc(&r8a66597->gadget);
del_timer_sync(&r8a66597->timer);
iounmap(r8a66597->reg);
if (r8a66597->pdata->sudmac)
iounmap(r8a66597->sudmac_reg);
free_irq(platform_get_irq(pdev, 0), r8a66597);
r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req);
#ifdef CONFIG_HAVE_CLK
@ -1572,6 +1872,26 @@ static void nop_completion(struct usb_ep *ep, struct usb_request *r)
{
}
static int __init r8a66597_sudmac_ioremap(struct r8a66597 *r8a66597,
struct platform_device *pdev)
{
struct resource *res;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sudmac");
if (!res) {
dev_err(&pdev->dev, "platform_get_resource error(sudmac).\n");
return -ENODEV;
}
r8a66597->sudmac_reg = ioremap(res->start, resource_size(res));
if (r8a66597->sudmac_reg == NULL) {
dev_err(&pdev->dev, "ioremap error(sudmac).\n");
return -ENOMEM;
}
return 0;
}
static int __init r8a66597_probe(struct platform_device *pdev)
{
#ifdef CONFIG_HAVE_CLK
@ -1649,6 +1969,11 @@ static int __init r8a66597_probe(struct platform_device *pdev)
clk_enable(r8a66597->clk);
}
#endif
if (r8a66597->pdata->sudmac) {
ret = r8a66597_sudmac_ioremap(r8a66597, pdev);
if (ret < 0)
goto clean_up2;
}
disable_controller(r8a66597); /* make sure controller is disabled */
@ -1681,7 +2006,6 @@ static int __init r8a66597_probe(struct platform_device *pdev)
r8a66597->ep[0].fifoaddr = CFIFO;
r8a66597->ep[0].fifosel = CFIFOSEL;
r8a66597->ep[0].fifoctr = CFIFOCTR;
r8a66597->ep[0].fifotrn = 0;
r8a66597->ep[0].pipectr = get_pipectr_addr(0);
r8a66597->pipenum2ep[0] = &r8a66597->ep[0];
r8a66597->epaddr2ep[0] = &r8a66597->ep[0];
@ -1714,6 +2038,8 @@ clean_up2:
#endif
clean_up:
if (r8a66597) {
if (r8a66597->sudmac_reg)
iounmap(r8a66597->sudmac_reg);
if (r8a66597->ep0_req)
r8a66597_free_request(&r8a66597->ep[0].ep,
r8a66597->ep0_req);

View File

@ -43,6 +43,7 @@
((pipenum >= R8A66597_BASE_PIPENUM_ISOC) && \
(pipenum < (R8A66597_BASE_PIPENUM_ISOC + R8A66597_MAX_NUM_ISOC)))
#define r8a66597_is_sudmac(r8a66597) (r8a66597->pdata->sudmac)
struct r8a66597_pipe_info {
u16 pipe;
u16 epnum;
@ -60,6 +61,7 @@ struct r8a66597_request {
struct r8a66597_ep {
struct usb_ep ep;
struct r8a66597 *r8a66597;
struct r8a66597_dma *dma;
struct list_head queue;
unsigned busy:1;
@ -75,13 +77,20 @@ struct r8a66597_ep {
unsigned char fifoaddr;
unsigned char fifosel;
unsigned char fifoctr;
unsigned char fifotrn;
unsigned char pipectr;
unsigned char pipetre;
unsigned char pipetrn;
};
struct r8a66597_dma {
unsigned used:1;
unsigned dir:1; /* 1 = IN(write), 0 = OUT(read) */
};
struct r8a66597 {
spinlock_t lock;
void __iomem *reg;
void __iomem *sudmac_reg;
#ifdef CONFIG_HAVE_CLK
struct clk *clk;
@ -94,6 +103,7 @@ struct r8a66597 {
struct r8a66597_ep ep[R8A66597_MAX_NUM_PIPE];
struct r8a66597_ep *pipenum2ep[R8A66597_MAX_NUM_PIPE];
struct r8a66597_ep *epaddr2ep[16];
struct r8a66597_dma dma;
struct timer_list timer;
struct usb_request *ep0_req; /* for internal request */
@ -251,7 +261,21 @@ static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata)
return clock;
}
static inline u32 r8a66597_sudmac_read(struct r8a66597 *r8a66597,
unsigned long offset)
{
return ioread32(r8a66597->sudmac_reg + offset);
}
static inline void r8a66597_sudmac_write(struct r8a66597 *r8a66597, u32 val,
unsigned long offset)
{
iowrite32(val, r8a66597->sudmac_reg + offset);
}
#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2)
#define get_pipetre_addr(pipenum) (PIPE1TRE + (pipenum - 1) * 4)
#define get_pipetrn_addr(pipenum) (PIPE1TRN + (pipenum - 1) * 4)
#define enable_irq_ready(r8a66597, pipenum) \
enable_pipe_irq(r8a66597, pipenum, BRDYENB)

View File

@ -48,6 +48,9 @@ struct r8a66597_platdata {
/* (external controller only) set one = WR0_N shorted to WR1_N */
unsigned wr0_shorted_to_wr1:1;
/* set one = using SUDMAC */
unsigned sudmac:1;
};
/* Register definitions */
@ -417,5 +420,62 @@ struct r8a66597_platdata {
#define USBSPD 0x00C0
#define RTPORT 0x0001
/* SUDMAC registers */
#define CH0CFG 0x00
#define CH1CFG 0x04
#define CH0BA 0x10
#define CH1BA 0x14
#define CH0BBC 0x18
#define CH1BBC 0x1C
#define CH0CA 0x20
#define CH1CA 0x24
#define CH0CBC 0x28
#define CH1CBC 0x2C
#define CH0DEN 0x30
#define CH1DEN 0x34
#define DSTSCLR 0x38
#define DBUFCTRL 0x3C
#define DINTCTRL 0x40
#define DINTSTS 0x44
#define DINTSTSCLR 0x48
#define CH0SHCTRL 0x50
#define CH1SHCTRL 0x54
/* SUDMAC Configuration Registers */
#define SENDBUFM 0x1000 /* b12: Transmit Buffer Mode */
#define RCVENDM 0x0100 /* b8: Receive Data Transfer End Mode */
#define LBA_WAIT 0x0030 /* b5-4: Local Bus Access Wait */
/* DMA Enable Registers */
#define DEN 0x0001 /* b1: DMA Transfer Enable */
/* DMA Status Clear Register */
#define CH1STCLR 0x0002 /* b2: Ch1 DMA Status Clear */
#define CH0STCLR 0x0001 /* b1: Ch0 DMA Status Clear */
/* DMA Buffer Control Register */
#define CH1BUFW 0x0200 /* b9: Ch1 DMA Buffer Data Transfer Enable */
#define CH0BUFW 0x0100 /* b8: Ch0 DMA Buffer Data Transfer Enable */
#define CH1BUFS 0x0002 /* b2: Ch1 DMA Buffer Data Status */
#define CH0BUFS 0x0001 /* b1: Ch0 DMA Buffer Data Status */
/* DMA Interrupt Control Register */
#define CH1ERRE 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Enable */
#define CH0ERRE 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Enable */
#define CH1ENDE 0x0002 /* b2: Ch1 DMA Transfer End Int Enable */
#define CH0ENDE 0x0001 /* b1: Ch0 DMA Transfer End Int Enable */
/* DMA Interrupt Status Register */
#define CH1ERRS 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Status */
#define CH0ERRS 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Status */
#define CH1ENDS 0x0002 /* b2: Ch1 DMA Transfer End Int Status */
#define CH0ENDS 0x0001 /* b1: Ch0 DMA Transfer End Int Status */
/* DMA Interrupt Status Clear Register */
#define CH1ERRC 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Stat Clear */
#define CH0ERRC 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Stat Clear */
#define CH1ENDC 0x0002 /* b2: Ch1 DMA Transfer End Int Stat Clear */
#define CH0ENDC 0x0001 /* b1: Ch0 DMA Transfer End Int Stat Clear */
#endif /* __LINUX_USB_R8A66597_H */