USB Gadget驅動又稱USB器件驅動。主要用於運行linux的嵌入式系統中,使得系統擁有普通USB設備的功能。mini2440具有USB1.1設備控制器,所以可以使用USB Gadget功能。但是linux2.6.32.2內核對於mini2440的支持不是很完全。開啟USB Gadget功能之後,不能使得主機發現USB硬件。這個問題主要是USB接口的上拉電阻的問題,mini2440使用GPC5來上拉USB,使得主機集線器發現有USB設備鏈接從而枚舉設備。但是在linux2.6.32.2內核中,沒有設置GPC5的代碼。所以導致不能使用Gadget功能。解決辦法網上也有一些,就是增加額外的模塊置位GPC5,但是我認為這樣不是最好的辦法。認真分析s3c2410_udc.c以及g_zero.c的代碼後,發現在注冊Gadget功能驅動的時候會調用s3c2410_udc.c提供的usb_gadget_register_driver函數,而這個函數最後會調用s3c2410_udc_enable。這個函數就是使能UDC的。代碼如下:
- static void s3c2410_udc_enable(struct s3c2410_udc *dev)
- {
- int i;
-
- dprintk(DEBUG_NORMAL, "s3c2410_udc_enable called\n");
-
- /* dev->gadget.speed = USB_SPEED_UNKNOWN; */
- dev->gadget.speed = USB_SPEED_FULL;
-
- /* Set MAXP for all endpoints */
- for (i = 0; i < S3C2410_ENDPOINTS; i++) {
- udc_write(i, S3C2410_UDC_INDEX_REG);
- udc_write((dev->ep[i].ep.maxpacket & 0x7ff) >> 3,
- S3C2410_UDC_MAXP_REG);
- }
-
- /* Set default power state */
- udc_write(DEFAULT_POWER_STATE, S3C2410_UDC_PWR_REG);
-
- /* Enable reset and suspend interrupt interrupts */
- udc_write(S3C2410_UDC_USBINT_RESET | S3C2410_UDC_USBINT_SUSPEND,
- S3C2410_UDC_USB_INT_EN_REG);
-
- /* Enable ep0 interrupt */
- udc_write(S3C2410_UDC_INT_EP0, S3C2410_UDC_EP_INT_EN_REG);
-
- /* time to say "hello, world" */
-
- if (udc_info && udc_info->udc_command) {
- udc_info->udc_command(S3C2410_UDC_P_ENABLE);
- }
- }
我們發現這個函數除了前面使能中斷的操作後,最後有個判斷語句,判斷udc_info以及udc_info->command是否有值。然後調用udc_command,這個函數的調用參數為S3C2410_UDC_P_ENABLE。很顯然這個就是使能UDC的關鍵操作。我們看一下udc_info又是什麼,在s3c2410_udc.c的最開始有這樣的定義:
static struct s3c2410_udc_mach_info *udc_info;
說明這是一個指向s3c2410_udc_mach_info結構的指針。s3c2410_udc_mach_info結構在udc.h中定義:
- struct s3c2410_udc_mach_info {
- void (*udc_command)(enum s3c2410_udc_cmd_e);
- void (*vbus_draw)(unsigned int ma);
- unsigned int vbus_pin;
- unsigned char vbus_pin_inverted;
- };
那麼這個指針又是什麼時候賦值的,是在s3c2410_udc_probe函數中。這就說明在注冊s3c2410_udc驅動的時候,由platform總線找到相應的設備匹配後,調用的。如下:
udc_info = pdev->dev.platform_data;
那麼什麼又是platfom_data呢,這個又是在什麼時候賦值的呢。要理解這個還得需要平台驅動的只是,也就是platform driver的知識。s3c2410的udc驅動是一個platform驅動,所以USB設備控制器是platform device。那麼這個platform_data又是在哪賦的值。一般而言platform device在系統板級初始化的時候初始化的。也就是板級初始化的時候賦值。但是用Kscope怎麼也找不到給他賦值的語句。說明根本就沒人給他賦值。所以在注冊g_zero功能驅動的時候udc_info是空的,沒有執行udc_info->udc_command()。我們要做的就是給usb gadget platform device的platform_data初始化。在mach-mini2440.c中增加如下代碼:
- static void s3c2410_udc_pullup(enum s3c2410_udc_cmd_e cmd)
- {
- switch (cmd) {
- case S3C2410_UDC_P_ENABLE :
- s3c2410_gpio_setpin(S3C2410_GPC(5), 1);
- break;
- case S3C2410_UDC_P_DISABLE :
- s3c2410_gpio_setpin(S3C2410_GPC(5), 0);
- break;
- case S3C2410_UDC_P_RESET :
- break;
- default:
- break;
- }
- }
這個函數就是udc_info->udc_command()執行的函數,在這裡使得GPC5為高電平,使能USB設備。
- static struct s3c2410_udc_mach_info s3c2410_udc_cfg __initdata = {
- .udc_command = s3c2410_udc_pullup,
- };
這個結構體定義了platform_data的初始值。
修改mini2440_machine_init函數,增加s3c24xx_udc_set_platdata(&s3c2410_udc_cfg);如下
- static void __init mini2440_machine_init(void)
- {
- #if defined (LCD_WIDTH)
- s3c24xx_fb_set_platdata(&mini2440_fb_info);
- #endif
- s3c_i2c0_set_platdata(NULL);
-
- s3c2410_gpio_cfgpin(S3C2410_GPC(0), S3C2410_GPC0_LEND);
-
- s3c_device_nand.dev.platform_data = &friendly_arm_nand_info;
- s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg;
- s3c24xx_udc_set_platdata(&s3c2410_udc_cfg); //增加的代碼
- platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
- s3c_pm_init();
- }
s3c24xx_udc_set_platdata()這個函數定義與devs.c,如下:
- void __init s3c24xx_udc_set_platdata(struct s3c2410_udc_mach_info *pd)
- {
- struct s3c2410_udc_mach_info *npd;
-
- npd = kmalloc(sizeof(*npd), GFP_KERNEL);
- if (npd) {
- memcpy(npd, pd, sizeof(*npd));
- s3c_device_usbgadget.dev.platform_data = npd;
- } else {
- printk(KERN_ERR "no memory for udc platform data\n");
- }
- }
最後還要在頭文件中包含plat/udc.h。這樣我們注冊Gadget功能驅動的時候自動使能了USB設備功能。但是在卸載驅動的時候發生了問題,內核打印出一大堆調試信息。問題出在composite.c中的composite_unbind函數中。這個函數開頭有一行代碼WARN_ON(cdev->config);就是如果cdev-config不為0,那麼內核就會打印出調試信息。上面還給了注釋
/* composite_disconnect() must already have been called
* by the underlying peripheral controller driver!
* so there's no i/o concurrency that could affect the
* state protected by cdev->lock.
*/
這個composite_unbind是在卸載udc功能驅動的時候調用的,調用關系如下:usb_composite_unregister 調用 usb_gadget_unregister_driver而usb_gadget_unregister_driver如下定義:
- int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
- {
- struct s3c2410_udc *udc = the_controller;
-
- if (!udc)
- return -ENODEV;
-
- if (!driver || driver != udc->driver || !driver->unbind)
- return -EINVAL;
-
- dprintk(DEBUG_NORMAL,"usb_gadget_register_driver() '%s'\n",
- driver->driver.name);
-
- driver->disconnect(&udc->gadget);
- //此處為新加語句,這條語句調用 composite_disconnect,然後使得cdev->config為NULL
- driver->unbind(&udc->gadget);
- //這裡就是composite_unbind
-
-
- device_del(&udc->gadget.dev);
- udc->driver = NULL;
-
- /* Disable udc */
- s3c2410_udc_disable(udc);
-
- return 0;
- }
從注釋上可以看出調用composite_unbind的前提是要首先保證composite_disconnect被調用。這樣才不會出現警告的內核信息。在增加上面的代碼後重新編譯內核,將USB Gadget設置稱為模塊。然後make modules 在通過ftp將s3c2410_udc.ko與g_zero.ko傳入開發板,先後加載這兩個模塊。在主機上lsusb會發現出現新設備
Bus 005 Device 023: ID 0525:a4a0 Netchip Technology, Inc. Linux-USB "Gadget Zero"
卸載g_zero.ko後,新設備就會消失。這樣基本的USB Gadget驅動功能就開啟了。類似的還可以測試其他的USB Gagget。