#include "../ypublic.h"
#include <plat/regs-udc.h>
#include <plat/map.h>
#define GADGET_MAJOR 100
#define GADGET_DRIVER_NAME "Mry gadget"
static const char ep0name [] = "ep0";
static const char *const ep_name[] = {
ep0name, /* everyone has ep0 */
/* s3c2410 four bidirectional bulk endpoints */
"ep1-bulk", "ep2-bulk", "ep3-bulk", "ep4-bulk",
};
#define S3C2410_ENDPOINTS ARRAY_SIZE(ep_name)
struct _t_usb_gadget;
typedef enum _t_ep0_state {
EP0_IDLE,
EP0_IN_DATA_PHASE,
EP0_OUT_DATA_PHASE,
EP0_END_XFER,
EP0_STALL,
}t_ep0_state;
typedef enum _t_usb_device_speed {
USB_SPEED_UNKNOWN = 0, /* enumerating */
USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */
USB_SPEED_HIGH, /* usb 2.0 */
USB_SPEED_WIRELESS, /* wireless (usb 2.5) */
USB_SPEED_SUPER, /* usb 3.0 */
}t_usb_device_speed;
/* USB_DT_ENDPOINT: Endpoint descriptor */
typedef struct _t_usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
}t_usb_endpoint_descriptor,*t_usb_endpoint_descriptor_p;
/**
* struct usb_request - describes one i/o request
* @buf: Buffer used for data. Always provide this; some controllers
* only use PIO, or don't use DMA for some endpoints.
* @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.
* @length: Length of that data
* @no_interrupt: If true, hints that no completion irq is needed.
* Helpful sometimes with deep request queues that are handled
* directly by DMA controllers.
* @zero: If true, when writing data, makes the last packet be "short"
* by adding a zero length packet as needed;
* @short_not_ok: When reading data, makes short packets be
* treated as errors (queue stops advancing till cleanup).
* @complete: Function called when request completes, so this request and
* its buffer may be re-used. The function will always be called with
* interrupts disabled, and it must not sleep.
* Reads terminate with a short packet, or when the buffer fills,
* whichever comes first. When writes terminate, some data bytes
* will usually still be in flight (often in a hardware fifo).
* Errors (for reads or writes) stop the queue from advancing
* until the completion function returns, so that any transfers
* invalidated by the error may first be dequeued.
* @context: For use by the completion callback
* @list: For use by the gadget driver.
* @status: Reports completion code, zero or a negative errno.
* Normally, faults block the transfer queue from advancing until
* the completion callback returns.
* Code "-ESHUTDOWN" indicates completion caused by device disconnect,
* or when the driver disabled the endpoint.
* @actual: Reports bytes transferred to/from the buffer. For reads (OUT
* transfers) this may be less than the requested length. If the
* short_not_ok flag is set, short reads are treated as errors
* even when status otherwise indicates successful completion.
* Note that for writes (IN transfers) some data bytes may still
* reside in a device-side FIFO when the request is reported as
* complete.
*
* These are allocated/freed through the endpoint they're used with. The
* hardware's driver can add extra per-request data to the memory it returns,
* which often avoids separate memory allocations (potential failures),
* later when the request is queued.
*
* Request flags affect request handling, such as whether a zero length
* packet is written (the "zero" flag), whether a short read should be
* treated as an error (blocking request queue advance, the "short_not_ok"
* flag), or hinting that an interrupt is not required (the "no_interrupt"
* flag, for use with deep request queues).
*
* Bulk endpoints can use any size buffers, and can also be used for interrupt
* transfers. interrupt-only endpoints can be much less functional.
*
* NOTE: this is analagous to 'struct urb' on the host side, except that
* it's thinner and promotes more pre-allocation.
*/
typedef struct _t_usb_request {
void *buf;
unsigned length;
dma_addr_t dma;
unsigned no_interrupt:1;
unsigned zero:1;
unsigned short_not_ok:1;
void (*complete)(t_usb_ep_p ep,
struct usb_request *req);
void *context;
struct list_head list;
int status;
unsigned actual;
struct list_head queue;
}t_usb_request ,*t_usb_request_p;
typedef struct _t_usb_ep_ops {
int (*enable) (t_usb_ep ep,
const t_usb_endpoint_descriptor_p desc);
int (*disable) (t_usb_ep_p ep);
t_usb_request_p (*alloc_request) (t_usb_ep_p ep,
gfp_t gfp_flags);
void (*free_request) (t_usb_ep_p ep, t_usb_request_p req);
int (*queue) (t_usb_ep_p ep, t_usb_request_p req,
gfp_t gfp_flags);
int (*dequeue) (t_usb_ep_p ep, t_usb_request_p req);
int (*set_halt) (t_usb_ep_p ep, int value);
int (*set_wedge) (t_usb_ep_p ep);
int (*fifo_status) (t_usb_ep_p ep);
void (*fifo_flush) (t_usb_ep_p ep);
}t_usb_ep_ops,*t_usb_ep_ops_p;
typedef struct _t_usb_ep {
void *driver_data;
const char *name;
const struct usb_ep_ops *ops;
struct list_head ep_list;
unsigned maxpacket:16;
const t_usb_endpoint_descriptor_p desc;
u8 num;
unsigned short fifo_size;
u8 bEndpointAddress;
u8 bmAttributes;
struct list_head queue;
}t_usb_ep,*t_usb_ep_p;
typedef struct _t_usb_gadget {
/* readonly to gadget driver */
//const struct usb_gadget_ops *ops;
t_usb_ep_p ep0;
//struct list_head ep_list; /* of usb_ep */
t_usb_device_speed speed;
unsigned is_dualspeed:1;
unsigned is_otg:1;
unsigned is_a_peripheral:1;
unsigned b_hnp_enable:1;
unsigned a_hnp_support:1;
unsigned a_alt_hnp_support:1;
const char *name;
//struct device dev;
}t_usb_gadget,*t_usb_gadget_p;
typedef struct _t_usb_ctrlrequest {
__u8 bRequestType;
__u8 bRequest;
__le16 wValue;
__le16 wIndex;
__le16 wLength;
}t_usb_ctrlrequest,*t_usb_ctrlrequest_p;
typedef struct _t_usb_gadget_driver {
char *function;
t_usb_device_speed speed;
int (*bind)(t_usb_gadget_p);
void (*unbind)(t_usb_gadget_p);
int (*setup)(t_usb_gadget_p,
const t_usb_ctrlrequest_p);
void (*disconnect)(t_usb_gadget_p);
void (*suspend)(t_usb_gadget_p);
void (*resume)(t_usb_gadget_p);
/* FIXME support safe rmmod */
//struct device_driver driver;
}t_usb_gadget_driver,*t_usb_gadget_driver_p;
typedef struct _t_gadget_dev{
struct cdev dev;
int major;
uchar irq_state;
spinlock_t lock;
uchar connected;
t_usb_gadget gadget;
t_usb_ep ep[S3C2410_ENDPOINTS];
uchar address;
t_ep0_state ep0state;
t_usb_gadget_driver_p driver;
}t_gadget_dev,*t_gadget_dev_p;
t_gadget_dev_p _gadget_dev_p;
static const struct file_operations _fops=
{
.owner=THIS_MODULE,
/*
.read=mry_read,
.write=mry_write,
.ioctl=mry_ioctl,
.open=mry_open,
.release=mry_release,
*/
};
static void __iomem *_udc_base_addr_p;
static inline u32 y_udc_read(u32 reg)
{
return readb(_udc_base_addr_p + reg);
}
static inline void y_udc_write(u32 value, u32 reg)
{
writeb(value, _udc_base_addr_p + reg);
}
static inline void y_udc_writeb(void __iomem *base, u32 value, u32 reg)
{
writeb(value, base + reg);
}
static inline int s3c2440_udc_write_packet(int fifo,
t_usb_request_p req,
unsigned max)
{
unsigned len = min(req->length - req->actual, max);
u8 *buf = req->buf + req->actual;
prefetch(buf);
printk(KERN_INFO "%s %d %d %d %d\n", __func__,
req->actual, req->length, len, req->actual + len);
req->actual += len;
udelay(5);
writesb(_udc_base_addr_p + fifo, buf, len);
return len;
}
static inline void s3c2440_udc_set_ep0_de_in(void __iomem *base)
{
y_udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG);
y_udc_writeb(base, (S3C2410_UDC_EP0_CSR_IPKRDY
| S3C2410_UDC_EP0_CSR_DE),
S3C2410_UDC_EP0_CSR_REG);
}
static void s3c2440_udc_done(t_usb_ep_p ep,
t_usb_request_p req, int status)
{
unsigned halted = ep->halted;
list_del_init(&req->queue);
if (likely (req->status == -EINPROGRESS))
req->status = status;
else
status = req->status;
ep->halted = 1;
req->complete(ep, req);
ep->halted = halted;
}
static inline void s3c2440_udc_set_ep0_ipr(void __iomem *base)
{
udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG);
udc_writeb(base, S3C2410_UDC_EP0_CSR_IPKRDY, S3C2410_UDC_EP0_CSR_REG);
}
/*
* s3c2410_udc_write_fifo
*
* return: 0 = still running, 1 = completed, negative = errno
*/
static int s3c2440_udc_write_fifo(t_usb_ep_p ep,
t_usb_request_p req)
{
unsigned count;
int is_last;
u32 idx;
int fifo_reg;
u32 ep_csr;
idx = ep->bEndpointAddress & 0x7F;
switch (idx) {
default:
idx = 0;
case 0:
fifo_reg = S3C2410_UDC_EP0_FIFO_REG;
break;
case 1:
fifo_reg = S3C2410_UDC_EP1_FIFO_REG;
break;
case 2:
fifo_reg = S3C2410_UDC_EP2_FIFO_REG;
break;
case 3:
fifo_reg = S3C2410_UDC_EP3_FIFO_REG;
break;
case 4:
fifo_reg = S3C2410_UDC_EP4_FIFO_REG;
break;
}
count = s3c2440_udc_write_packet(fifo_reg, req, ep->maxpacket);
/* last packet is often short (sometimes a zlp) */
if (count != ep->maxpacket)
is_last = 1;
else if (req->length != req->actual || req->zero)
is_last = 0;
else
is_last = 2;
/* Only ep0 debug messages are interesting */
if (idx == 0)
dprintk(DEBUG_NORMAL,
"Written ep%d %d.%d of %d b [last %d,z %d]\n",
idx, count, req->actual, req->length,
is_last, req->zero);
if (is_last) {
/* The order is important. It prevents sending 2 packets
* at the same time */
if (idx == 0) {
/* Reset signal => no need to say 'data sent' */
if (! (y_udc_read(S3C2410_UDC_USB_INT_REG)
& S3C2410_UDC_USBINT_RESET))
s3c2440_udc_set_ep0_de_in(base_addr);
ep->dev->ep0state=EP0_IDLE;
} else {
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
ep_csr = y_udc_read(S3C2410_UDC_IN_CSR1_REG);
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
y_udc_write(ep_csr | S3C2410_UDC_ICSR1_PKTRDY,
S3C2410_UDC_IN_CSR1_REG);
}
s3c2440_udc_done(ep, req, 0);
is_last = 1;
} else {
if (idx == 0) {
/* Reset signal => no need to say 'data sent' */
if (! (y_udc_read(S3C2410_UDC_USB_INT_REG)
& S3C2410_UDC_USBINT_RESET))
s3c2440_udc_set_ep0_ipr(base_addr);
} else {
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
ep_csr = y_udc_read(S3C2410_UDC_IN_CSR1_REG);
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
y_udc_write(ep_csr | S3C2410_UDC_ICSR1_PKTRDY,
S3C2410_UDC_IN_CSR1_REG);
}
}
return is_last;
}
/*
* handle_ep - Manage I/O endpoints
*/
static void s3c2440_udc_handle_ep(t_usb_ep_p ep)
{
struct s3c2410_request *req;
int is_in = ep->bEndpointAddress & USB_DIR_IN;
u32 ep_csr1;
u32 idx;
if (likely (!list_empty(&ep->queue)))
req = list_entry(ep->queue.next,
t_usb_request, queue);
else
req = NULL;
idx = ep->bEndpointAddress & 0x7F;
if (is_in) {
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
ep_csr1 = y_udc_read(S3C2410_UDC_IN_CSR1_REG);
printk(KERN_INFO "ep%01d write csr:%02x %d\n",
idx, ep_csr1, req ? 1 : 0);
if (ep_csr1 & S3C2410_UDC_ICSR1_SENTSTL) {
printk(KERN_INFO "st\n");
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
y_udc_write(ep_csr1 & ~S3C2410_UDC_ICSR1_SENTSTL,
S3C2410_UDC_IN_CSR1_REG);
return;
}
if (!(ep_csr1 & S3C2410_UDC_ICSR1_PKTRDY) && req) {
s3c2440_udc_write_fifo(ep,req);
}
} else {
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
ep_csr1 = y_udc_read(S3C2410_UDC_OUT_CSR1_REG);
printk(KERN_INFO "ep%01d rd csr:%02x\n", idx, ep_csr1);
if (ep_csr1 & S3C2410_UDC_OCSR1_SENTSTL) {
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
y_udc_write(ep_csr1 & ~S3C2410_UDC_OCSR1_SENTSTL,
S3C2410_UDC_OUT_CSR1_REG);
return;
}
if ((ep_csr1 & S3C2410_UDC_OCSR1_PKTRDY) && req) {
s3c2410_udc_read_fifo(ep,req);
}
}
}
static irqreturn_t gadget_on_irq(int irq, void *p)
{
t_gadget_dev_p dev = p;
int usb_status;
int usbd_status;
int pwr_reg;
int ep0csr;
int i;
u32 idx;
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
/* Driver connected ? */
if (!dev->driver) {
/* Clear interrupts */
y_udc_write(y_udc_read(S3C2410_UDC_USB_INT_REG),
S3C2410_UDC_USB_INT_REG);
y_udc_write(y_udc_read(S3C2410_UDC_EP_INT_REG),
S3C2410_UDC_EP_INT_REG);
}
/* Save index */
idx = y_udc_read(S3C2410_UDC_INDEX_REG);
/* Read status registers */
usb_status = y_udc_read(S3C2410_UDC_USB_INT_REG);
usbd_status = y_udc_read(S3C2410_UDC_EP_INT_REG);
pwr_reg = y_udc_read(S3C2410_UDC_PWR_REG);
y_udc_writeb(_udc_base_addr_p, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG);
ep0csr = y_udc_read(S3C2410_UDC_IN_CSR1_REG);
printk(KERN_INFO"usbs=%02x, usbds=%02x, pwr=%02x ep0csr=%02x\n",
usb_status, usbd_status, pwr_reg, ep0csr);
/*
* Now, handle interrupts. There's two types :
* - Reset, Resume, Suspend coming -> usb_int_reg
* - EP -> ep_int_reg
*/
/* RESET */
if (usb_status & S3C2410_UDC_USBINT_RESET) {
/* two kind of reset :
* - reset start -> pwr reg = 8
* - reset end -> pwr reg = 0
**/
printk(KERN_INFO "USB reset csr %x pwr %x\n",
ep0csr, pwr_reg);
dev->gadget.speed = USB_SPEED_UNKNOWN;
y_udc_write(0x00, S3C2410_UDC_INDEX_REG);
y_udc_write((dev->ep[0].maxpacket & 0x7ff) >> 3,
S3C2410_UDC_MAXP_REG);
dev->address = 0;
dev->ep0state = EP0_IDLE;
dev->gadget.speed = USB_SPEED_FULL;
/* clear interrupt */
y_udc_write(S3C2410_UDC_USBINT_RESET,
S3C2410_UDC_USB_INT_REG);
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
/* RESUME */
if (usb_status & S3C2410_UDC_USBINT_RESUME) {
printk(KERN_INFO"USB resume\n");
/* clear interrupt */
y_udc_write(S3C2410_UDC_USBINT_RESUME,
S3C2410_UDC_USB_INT_REG);
if (dev->gadget.speed != USB_SPEED_UNKNOWN
&& dev->driver
&& dev->driver->resume)
dev->driver->resume(&dev->gadget);
}
/* SUSPEND */
if (usb_status & S3C2410_UDC_USBINT_SUSPEND) {
printk(KERN_INFO"USB suspend\n");
/* clear interrupt */
y_udc_write(S3C2410_UDC_USBINT_SUSPEND,
S3C2410_UDC_USB_INT_REG);
if (dev->gadget.speed != USB_SPEED_UNKNOWN
&& dev->driver
&& dev->driver->suspend)
dev->driver->suspend(&dev->gadget);
dev->ep0state = EP0_IDLE;
}
/* EP */
/* control traffic */
/* check on ep0csr != 0 is not a good idea as clearing in_pkt_ready
* generate an interrupt
*/
if (usbd_status & S3C2410_UDC_INT_EP0) {
printk(KERN_INFO"USB ep0 irq\n");
/* Clear the interrupt bit by setting it to 1 */
y_udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_REG);
//s3c2410_udc_handle_ep0(dev);
}
/* endpoint data transfers */
for (i = 1; i < S3C2410_ENDPOINTS; i++) {
u32 tmp = 1 << i;
if (usbd_status & tmp) {
printk(KERN_INFO "USB ep%d irq\n", i);
/* Clear the interrupt bit by setting it to 1 */
y_udc_write(tmp, S3C2410_UDC_EP_INT_REG);
s3c2440_udc_handle_ep(&dev->ep[i]);
}
}
printk(KERN_INFO"irq: %d s3c2440_udc_done.\n", IRQ_USBD);
/* Restore old index */
y_udc_write(idx, S3C2410_UDC_INDEX_REG);
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
static int gadget_init_do(t_gadget_dev_p pdev)
{
int err;
int rsrc_start = S3C2410_PA_USBDEV;
int rsrc_len = S3C24XX_SZ_USBDEV;
if (!request_mem_region(rsrc_start, rsrc_len, GADGET_DRIVER_NAME))
{
err=-EBUSY;
goto err1;
}
_udc_base_addr_p = ioremap(rsrc_start, rsrc_len);
if (!_udc_base_addr_p) {
err = -ENOMEM;
goto err2;
}
err=request_irq(IRQ_USBD,gadget_on_irq,IRQF_DISABLED,"gadget_irq",pdev);
pdev->irq_state=err;
if(err)
{
printk(KERN_ERR"usb irq busy");
err=-EBUSY;
}
else printk(KERN_ERR"usb irq ok");
spin_lock_init (&pdev->lock);
return err;
err2:
release_mem_region(rsrc_start,rsrc_len);
err1:
printk(KERN_ERR"error: fuction:gadget_init_do id:%d",err);
return err;
}
static int gadget_setup_cdev(t_gadget_dev_p pdev,int index)
{
int err,devno=MKDEV(pdev->major,index);
cdev_init(&pdev->dev,&_fops);
pdev->dev.owner=THIS_MODULE;
pdev->dev.ops=&_fops;
err=cdev_add(&pdev->dev,devno,1);
return err;
}
static int gadget_init(void)
{
int rs;
int mry_major=GADGET_MAJOR;
dev_t devno=MKDEV(mry_major,0);
if(mry_major)
rs=register_chrdev_region(devno,1,GADGET_DRIVER_NAME);
else
{
rs=alloc_chrdev_region(&devno,0,1,GADGET_DRIVER_NAME);
mry_major=MAJOR(devno);
}
if(rs<0){
goto err1;
}
_gadget_dev_p=kzalloc(sizeof(t_gadget_dev),GFP_KERNEL);
if(!_gadget_dev_p)
{
rs=-ENOMEM;
goto err2;
}
//memset(_gadget_dev_p,0,sizeof(t_gadget_dev));
_gadget_dev_p->major=mry_major;
rs=gadget_setup_cdev(_gadget_dev_p,0);
if(rs)
{
goto err3;
}
printk(KERN_INFO "gadget driver ok");
gadget_init_do(_gadget_dev_p);
return 0;
err3:
err2:
unregister_chrdev_region(devno,1);
err1:
printk(KERN_ERR "gadget driver failed,ERROR:%d",rs);
return rs;
}
static void gadget_exit(void)
{
int mry_major=_gadget_dev_p->major;
if(_udc_base_addr_p)
{
yiounmap(S3C2410_PA_USBDEV,_udc_base_addr_p,S3C24XX_SZ_USBDEV);
}
if(!_gadget_dev_p->irq_state)
free_irq(IRQ_USBD,_gadget_dev_p);
cdev_del(&_gadget_dev_p->dev);
kfree(_gadget_dev_p);
unregister_chrdev_region(MKDEV(mry_major,0),1);
printk(KERN_INFO "exit gadget");
}
module_init(gadget_init);
module_exit(gadget_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrY");
|