📄 sl811.c
字号:
/* * SL811 Host Controller Interface driver for USB. * * Copyright (c) 2003/06, Courage Co., Ltd. * * Based on: * 1.uhci.c by Linus Torvalds, Johannes Erdfelt, Randy Dunlap, * Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, * Adam Richter, Gregory P. Smith; * 2.Original SL811 driver (hc_sl811.o) by Pei Liu <pbl@cypress.com> * 3.Rewrited as sl811.o by Yin Aihua <yinah:couragetech.com.cn> * * It's now support isochornous mode and more effective than hc_sl811.o * Support x86 architecture now. * * 19.09.2003 (05.06.2003) HNE * sl811_alloc_hc: Set "bus->bus_name" at init. * sl811_reg_test (hc_reset,regTest): * Stop output at first failed pattern. * Down-Grade for Kernel 2.4.20 and from 2.4.22 * Splitt hardware depens into file sl811-x86.h and sl811-arm.h. * * 22.09.2003 HNE * sl811_found_hc: First patterntest, than interrupt enable. * Do nothing, if patterntest failed. Release io, if failed. * Stop Interrupts first, than remove handle. (Old blocked Shred IRQ) * Alternate IO-Base for second Controller (CF/USB1). * * 24.09.2003 HNE * Remove all arm specific source (moved into include/asm/sl811-hw.h). * * 03.10.2003 HNE * Low level only for port io into hardware-include. * * To do: * 1.Modify the timeout part, it's some messy * 2.Use usb-a and usb-b set in Ping-Pong mode * o Floppy do not work. * o driver crash, if io region can't register * o Only as module tested! Compiled in Version not tested! * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * */#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/smp_lock.h>#include <linux/list.h>#include <linux/ioport.h>#include <asm/io.h>#include <linux/irq.h>#include <linux/usb.h>#include "../hcd.h"#include "../hub.h"#include "sl811.h"//#include "hc_sl811.h"//#include "hc_simple.h"#define DRIVER_VERSION "v0.30"#define MODNAME "SL811"#define DRIVER_AUTHOR "Yin Aihua <yinah@couragetech.com.cn>, Henry Nestler <hne@ist1.de>"#define DRIVER_DESC "Sl811 USB Host Controller Alternate Driver"#define SL11H_HWREVREG 0x0E /* read */static LIST_HEAD(sl811_hcd_list);/* * 0, normal prompt and information * 1, error should not occur in normal * 2, error maybe occur in normal * 3, useful and detail debug information * 4, function level enter and level inforamtion * 5, endless information will output because of timer function or interrupt */static int debug = 1;MODULE_PARM(debug,"i");MODULE_PARM_DESC(debug,"debug level");#include <asm/sl811-hw.h> /* Include hardware and board depens */static void sl811_rh_int_timer_do(unsigned long ptr);static void sl811_transfer_done(struct sl811_hc *hc, int sof);/* * Read a byte of data from the SL811H/SL11H */static __u8 inline sl811_read(struct sl811_hc *hc, __u8 offset){ sl811_write_index (hc, offset); return (sl811_read_data (hc));}/* * Write a byte of data to the SL811H/SL11H */static void inline sl811_write(struct sl811_hc *hc, __u8 offset, __u8 data){ sl811_write_index_data (hc, offset, data);}/* * Read consecutive bytes of data from the SL811H/SL11H buffer */static void inline sl811_read_buf(struct sl811_hc *hc, __u8 offset, __u8 *buf, __u8 size){// sl811_write_index (hc, offset);//printk("\n sl811 rdbuf:"); sl811_write_index (hc, offset); while (size--) {// *buf++ = sl811_read_data(hc); *buf = sl811_read_data(hc);//printk("%2x ",*buf); buf++; }}/* * Write consecutive bytes of data to the SL811H/SL11H buffer */static void inline sl811_write_buf(struct sl811_hc *hc, __u8 offset, __u8 *buf, __u8 size){//printk("\n sl811 wrbuf:"); sl811_write_index (hc, offset); while (size--) {// sl811_write_data (hc, *buf);//printk("%2x ",*buf); sl811_write_data (hc, *buf); buf++; }}/* * This routine test the Read/Write functionality of SL811HS registers */static int sl811_reg_test(struct sl811_hc *hc){ int i, data, result = 0; __u8 buf[256]; for (i = 0x10; i < 256; i++) { /* save the original buffer */ buf[i] = sl811_read(hc, i); /* Write the new data to the buffer */ sl811_write(hc, i, i); } /* compare the written data */ for (i = 0x10; i < 256; i++) { data = sl811_read(hc, i); if (data != i) { PDEBUG(1, "Pattern test failed!! value = 0x%x, s/b 0x%x", data, i); result = -1; /* If no Debug, show only first failed Address */ if (!debug) break; } } /* restore the data */ for (i = 0x10; i < 256; i++) sl811_write(hc, i, buf[i]); return result;}/* * Display all SL811HS register values */#if 0 /* unused (hne) */static void sl811_reg_show(struct sl811_hc *hc){ int i; for (i = 0; i < 256; i++) PDEBUG(4, "offset %d: 0x%x", i, sl811_read(hc, i));}#endif/* * This function enables SL811HS interrupts */static void sl811_enable_interrupt(struct sl811_hc *hc){ PDEBUG(4, "enter"); sl811_write(hc, SL811_INTR, SL811_INTR_DONE_A | SL811_INTR_SOF | SL811_INTR_INSRMV);}/* * This function disables SL811HS interrupts */static void sl811_disable_interrupt(struct sl811_hc *hc){ PDEBUG(4, "enter"); // Disable all other interrupt except for insert/remove. sl811_write(hc, SL811_INTR, SL811_INTR_INSRMV);}/* * SL811 Virtual Root Hub *//* Device descriptor */static __u8 sl811_rh_dev_des[] ={ 0x12, /* __u8 bLength; */ 0x01, /* __u8 bDescriptorType; Device */ 0x10, /* __u16 bcdUSB; v1.1 */ 0x01, 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ 0x00, /* __u8 bDeviceSubClass; */ 0x00, /* __u8 bDeviceProtocol; */ 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ 0x00, /* __u16 idVendor; */ 0x00, 0x00, /* __u16 idProduct; */ 0x00, 0x00, /* __u16 bcdDevice; */ 0x00, 0x00, /* __u8 iManufacturer; */ 0x02, /* __u8 iProduct; */ 0x01, /* __u8 iSerialNumber; */ 0x01 /* __u8 bNumConfigurations; */};/* Configuration descriptor */static __u8 sl811_rh_config_des[] ={ 0x09, /* __u8 bLength; */ 0x02, /* __u8 bDescriptorType; Configuration */ 0x19, /* __u16 wTotalLength; */ 0x00, 0x01, /* __u8 bNumInterfaces; */ 0x01, /* __u8 bConfigurationValue; */ 0x00, /* __u8 iConfiguration; */ 0x40, /* __u8 bmAttributes; Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */ 0x00, /* __u8 MaxPower; */ /* interface */ 0x09, /* __u8 if_bLength; */ 0x04, /* __u8 if_bDescriptorType; Interface */ 0x00, /* __u8 if_bInterfaceNumber; */ 0x00, /* __u8 if_bAlternateSetting; */ 0x01, /* __u8 if_bNumEndpoints; */ 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ 0x00, /* __u8 if_bInterfaceSubClass; */ 0x00, /* __u8 if_bInterfaceProtocol; */ 0x00, /* __u8 if_iInterface; */ /* endpoint */ 0x07, /* __u8 ep_bLength; */ 0x05, /* __u8 ep_bDescriptorType; Endpoint */ 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ 0x03, /* __u8 ep_bmAttributes; Interrupt */ 0x08, /* __u16 ep_wMaxPacketSize; */ 0x00, 0xff /* __u8 ep_bInterval; 255 ms */};/* root hub class descriptor*/static __u8 sl811_rh_hub_des[] ={ 0x09, /* __u8 bLength; */ 0x29, /* __u8 bDescriptorType; Hub-descriptor */ 0x01, /* __u8 bNbrPorts; */ 0x00, /* __u16 wHubCharacteristics; */ 0x00, 0x50, /* __u8 bPwrOn2pwrGood; 2ms */ 0x00, /* __u8 bHubContrCurrent; 0 mA */ 0xfc, /* __u8 DeviceRemovable; *** 7 Ports max *** */ 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */};/* * This function examine the port change in the virtual root hub. HUB INTERRUPT ENDPOINT. */static int sl811_rh_send_irq(struct sl811_hc *hc, __u8 *rh_change, int rh_len){ __u8 data = 0; PDEBUG(5, "enter"); /* * Right now, It is assume the power is good and no changes and only one port. */ if (hc->rh_status.wPortChange & (USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE)) { data = 1<<1; *(__u8 *)rh_change = data; return 1; } else return 0;}/* * This function creates a timer that act as interrupt pipe in the virtual hub. * * Note: The virtual root hub's interrupt pipe are polled by the timer * every "interval" ms */static void sl811_rh_init_int_timer(struct urb * urb){ struct sl811_hc *hc = urb->dev->bus->hcpriv; hc->rh.interval = urb->interval; init_timer(&hc->rh.rh_int_timer); hc->rh.rh_int_timer.function = sl811_rh_int_timer_do; hc->rh.rh_int_timer.data = (unsigned long)urb; hc->rh.rh_int_timer.expires = jiffies + (HZ * (urb->interval < 30? 30: urb->interval)) / 1000; add_timer (&hc->rh.rh_int_timer);}/* * This function is called when the timer expires. It gets the the port * change data and pass along to the upper protocol. */static void sl811_rh_int_timer_do(unsigned long ptr){ int len; struct urb *urb = (struct urb *)ptr; struct sl811_hc *hc = urb->dev->bus->hcpriv; PDEBUG (5, "enter"); if(hc->rh.send) { len = sl811_rh_send_irq(hc, urb->transfer_buffer, urb->transfer_buffer_length); if (len > 0) { urb->actual_length = len; if (urb->complete) urb->complete(urb); } }#ifdef SL811_TIMEOUT { struct list_head *head, *tmp; struct sl811_urb_priv *urbp; struct urb *u; int i; static int timeout_count = 0;// check time out every second if (++timeout_count > 4) { int max_scan = hc->active_urbs; timeout_count = 0; for (i = 0; i < 6; ++i) { head = &hc->urb_list[i]; tmp = head->next; while (tmp != head && max_scan--) { u = list_entry(tmp, struct urb, urb_list); urbp = (struct sl811_urb_priv *)u->hcpriv; tmp = tmp->next; // Check if the URB timed out if (u->timeout && time_after_eq(jiffies, urbp->inserttime + u->timeout)) { PDEBUG(3, "urb = %p time out, we kill it", urb); u->transfer_flags |= USB_TIMEOUT_KILLED; } } } }}#endif // re-activate the timer sl811_rh_init_int_timer(urb);}/* helper macro */#define OK(x) len = (x); break/* * This function handles all USB request to the the virtual root hub */static int sl811_rh_submit_urb(struct urb *urb){ struct usb_device *usb_dev = urb->dev; struct sl811_hc *hc = usb_dev->bus->hcpriv; struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *)urb->setup_packet; void *data = urb->transfer_buffer; int buf_len = urb->transfer_buffer_length; unsigned int pipe = urb->pipe; __u8 data_buf[16]; __u8 *bufp = data_buf; int len = 0; int status = 0; __u16 bmRType_bReq; __u16 wValue; __u16 wIndex; __u16 wLength; if (usb_pipeint(pipe)) { hc->rh.urb = urb; hc->rh.send = 1; hc->rh.interval = urb->interval; sl811_rh_init_int_timer(urb); urb->status = 0; return 0; } bmRType_bReq = cmd->bRequestType | (cmd->bRequest << 8); wValue = le16_to_cpu (cmd->wValue); wIndex = le16_to_cpu (cmd->wIndex); wLength = le16_to_cpu (cmd->wLength); PDEBUG(5, "submit rh urb, req = %d(%x) len=%d", bmRType_bReq, bmRType_bReq, wLength); /* Request Destination: without flags: Device, USB_RECIP_INTERFACE: interface, USB_RECIP_ENDPOINT: endpoint, USB_TYPE_CLASS means HUB here, USB_RECIP_OTHER | USB_TYPE_CLASS almost ever means HUB_PORT here */ switch (bmRType_bReq) { case RH_GET_STATUS: *(__u16 *)bufp = cpu_to_le16(1); OK(2); case RH_GET_STATUS | USB_RECIP_INTERFACE: *(__u16 *)bufp = cpu_to_le16(0); OK(2); case RH_GET_STATUS | USB_RECIP_ENDPOINT: *(__u16 *)bufp = cpu_to_le16(0); OK(2); case RH_GET_STATUS | USB_TYPE_CLASS: *(__u32 *)bufp = cpu_to_le32(0); OK(4); case RH_GET_STATUS | USB_RECIP_OTHER | USB_TYPE_CLASS: *(__u32 *)bufp = cpu_to_le32(hc->rh_status.wPortChange<<16 | hc->rh_status.wPortStatus); OK(4); case RH_CLEAR_FEATURE | USB_RECIP_ENDPOINT: switch (wValue) { case 1: OK(0); } break; case RH_CLEAR_FEATURE | USB_TYPE_CLASS: switch (wValue) { case C_HUB_LOCAL_POWER: OK(0); case C_HUB_OVER_CURRENT: OK(0); } break; case RH_CLEAR_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: switch (wValue) { case USB_PORT_FEAT_ENABLE: hc->rh_status.wPortStatus &= ~USB_PORT_STAT_ENABLE; OK(0); case USB_PORT_FEAT_SUSPEND: hc->rh_status.wPortStatus &= ~USB_PORT_STAT_SUSPEND; OK(0); case USB_PORT_FEAT_POWER: hc->rh_status.wPortStatus &= ~USB_PORT_STAT_POWER; OK(0); case USB_PORT_FEAT_C_CONNECTION: hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_CONNECTION; OK(0); case USB_PORT_FEAT_C_ENABLE: hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_ENABLE; OK(0); case USB_PORT_FEAT_C_SUSPEND: hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_SUSPEND; OK(0); case USB_PORT_FEAT_C_OVER_CURRENT: hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_OVERCURRENT; OK(0); case USB_PORT_FEAT_C_RESET: hc->rh_status.wPortChange &= ~USB_PORT_STAT_C_RESET; OK(0); } break; case RH_SET_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: switch (wValue) { case USB_PORT_FEAT_SUSPEND: hc->rh_status.wPortStatus |= USB_PORT_STAT_SUSPEND; OK(0); case USB_PORT_FEAT_RESET: hc->rh_status.wPortStatus |= USB_PORT_STAT_RESET; hc->rh_status.wPortChange = 0; hc->rh_status.wPortChange |= USB_PORT_STAT_C_RESET; hc->rh_status.wPortStatus &= ~USB_PORT_STAT_RESET; hc->rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; OK(0); case USB_PORT_FEAT_POWER: hc->rh_status.wPortStatus |= USB_PORT_STAT_POWER; OK(0); case USB_PORT_FEAT_ENABLE: hc->rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; OK(0); } break; case RH_SET_ADDRESS: hc->rh.devnum = wValue; OK(0); case RH_GET_DESCRIPTOR: switch ((wValue & 0xff00) >> 8) { case USB_DT_DEVICE: len = sizeof(sl811_rh_dev_des); bufp = sl811_rh_dev_des; OK(len); case USB_DT_CONFIG: len = sizeof(sl811_rh_config_des); bufp = sl811_rh_config_des; OK(len); case USB_DT_STRING: len = usb_root_hub_string(wValue & 0xff, (int)(long)0, "SL811HS", data, wLength); if (len > 0) { bufp = data; OK(len); } default: status = -EPIPE; } break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -