USB OTG驅動分析(一)

前一段時間弄了 2 個禮拜的 OTG 驅動調試,感覺精神疲憊啊。主要原因還是自己對 OTG 功能不瞭解造成的。現在終於完成但是對實質原理還有些模糊。所以 自己重新總結一下。因爲自己是菜鳥,所以用菜鳥的白話方式分析。高手濾過吧。 所謂 OTG 功能 就是具備該功能的設備即可當主設備(host) 去輪詢別人,也可以當從設備 (device) 去被別人輪~~(雙性人?)。正所謂所有的產品和功能都是因爲需求 存在的,舉個最簡單的需求,原來 MP3 想傳送一個歌曲都得通過電腦。現在只要兩個 MP3 鏈接,其中一個 MP3 有 OTG 功能作爲主設備(相當於電腦主機),然後另外一個是從設備就可以 實現數據的傳送了。 那麼話說回來,具有 OTG 功能的設備如何確定自己是主還是從設備那。原來原來 USB 接口上有 4 個管腳, OTG 功能有 5 個。原來 4 個分別是電 D+ D- 地。現在增加了一個 ID 。這個 ID 線就決定了自己做主設備還是從設備。如果 ID 線是高則自己是從設備,反之是主設備。

 

 

下面開始分析代碼。
 向平時一樣定義platform_device資源等信息。 
定義platform_device結構 
 static struct platform_device __maybe_unused dr_otg_device = 
{ . name = "fsl-usb2-otg" , //設備的名稱 日後匹配用 

. id = - 1, //只有一個這樣的設備 

. dev = { . release = dr_otg_release, 
. dma_mask = & dr_otg_dmamask, 
. coherent_dma_mask = 0xffffffff, 
 } , 
 . resource = otg_resources, //設備的資源 看下面

 . num_resources = ARRAY_SIZE( otg_resources) , 
} ; 

定義platform_device下的struct resource設備資源結構
static struct resource otg_resources[ ] = { 
[ 0] = { 
. start = ( u32) ( USB_OTGREGS_BASE) , //描述設備實體在cpu總線上的線 性起始物理地址

. end = ( u32) ( USB_OTGREGS_BASE + 0x1ff) , //描述設備實體在cpu總線上的線性結尾物理地址 

. flags = IORESOURCE_MEM, } , 
[ 1] = { 
. start = MXC_INT_USB_OTG, //中斷號 

. flags = IORESOURCE_IRQ, } , 
} ;

 

定義平臺設備私 有數據,以後驅動要使用 
static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { 
. name = "DR" , 
. platform_init = usbotg_init, 
. platform_uninit = usbotg_uninit, 
. phy_mode = FSL_USB2_PHY_UTMI_WIDE, 
. power_budget = 500, /* via RT9706 */ 
. gpio_usb_active = gpio_usbotg_utmi_active, 
. gpio_usb_inactive = gpio_usbotg_utmi_inactive, 
. transceiver = "utmi" , 
. wake_up_enable = _wake_up_enable, 
} ; 
# define PDATA ( & dr_utmi_config) 定義platform_device下的DEV設備下的平臺私有數據(就是該設備私有的數據)


static inline void dr_register_otg( void ) { 
 PDATA- > operating_mode = FSL_USB2_DR_OTG; //將模式更改(上面定義的時候定義的是FSL_USB2_PHY_UTMI_WIDE,不知道爲什麼開始不定義這個,可能是爲了兼 容) 

dr_otg_device. dev. platform_data = PDATA; //該設備的私有數據賦值,就 是上面定義的dr_utmi_config 

if ( platform_device_register( & dr_otg_device) ) 
 printk( KERN_ERR "usb: can't register otg device/n" ) ; 
else 
printk( KERN_INFO "usb: DR OTG registered/n" ) ; 
}

 

上面幾個過程主要是完成了設備的註冊。這個過程是: 
1. 定 義platform_device結構。 
2. 定義 platform_device下的struct resource設備資源結構 
3. 定義platform_device下的DEV設備下的平臺私有數據(就是該設備私有的數據) 
4. 調用platform_device_register將 platform_device結構
註冊上面4個過程調用結束後,設備的信息就被註冊到系統中,等待驅動的使用

 

下 面分析驅動和設備的鏈接過程

定義platform_driver結構 
struct platform_driver fsl_otg_driver = { 
. probe = fsl_otg_probe, //定義處理函數,該函數在設備名字匹配到後調用,也就是發現該驅動 對應的設備在系統中註冊過。 

. remove = fsl_otg_remove, 
. driver = { 
. name = "fsl-usb2-otg" , //通過該名字匹配開始註冊進系統的設備 

. owner = THIS_MODULE, 
} , 
} ; 
將platform_driver結構註冊進系統,系統通 過註冊名字匹配該設備是否已經在系統中,如果在調用註冊的probe = fsl_otg_probe函數 
static int __init fsl_usb_otg_init( void ) 
{ 
printk( KERN_INFO DRIVER_DESC " loaded, %s/n" , DRIVER_VERSION) ; 
return platform_driver_register( & fsl_otg_driver) ; 
}


 


調用 fsl_otg_probe 函數,函數參數 platform_device *pdev ,就是我們上面註冊進系統 的platform_device 結構,現在由系統賦值調用 fsl_otg_probe

 

static int __init fsl_otg_probe( struct platform_device * pdev) 
{ 
    int status; 
    struct fsl_usb2_platform_data * pdata; 

    DBG( "pdev=0x%p/n" , pdev) ; 

    if ( ! pdev) 
        return - ENODEV; 
/*
 判斷是否有設備自己的數據,就是檢查我們上面定義的 3 的過程*/ 
    if ( ! pdev- > dev. platform_data) 
        return - ENOMEM; 

    pdata = pdev- > dev. platform_data; 

    /* configure the OTG */ 
    status = fsl_otg_conf( pdev) ; 
    if ( status) { 
        printk( KERN_INFO "Couldn't init OTG module/n" ) ; 
        return - status; 
    } 

    /* start OTG */ 
    status = usb_otg_start( pdev) ; 

    if ( register_chrdev( FSL_OTG_MAJOR, FSL_OTG_NAME, & otg_fops) ) { 
        printk( KERN_WARNING FSL_OTG_NAME
         ": unable to register FSL OTG device/n" ) ; 
        return - EIO; 
    } 

    create_proc_file( ) ; 
    return status; 
}


上面函數中調用了 fsl_otg_conf ,我們來看看他幹了什麼。

 

static int fsl_otg_conf( struct platform_device * pdev) 
{ 
    int status; 
    struct fsl_otg * fsl_otg_tc; 
    struct fsl_usb2_platform_data * pdata; 

    pdata = pdev- > dev. platform_data; 

    DBG( ) ; 
/**************************************************************/

struct fsl_otg {
 struct otg_transceiver otg;
 struct otg_fsm fsm;
 struct usb_dr_mmap *dr_mem_map;
 struct delayed_work otg_event;

 /*used for usb host */
 struct work_struct work_wq;
 u8 host_working;

 int irq;
};

/**************************************************************/ 
    if ( fsl_otg_dev) 
        return 0; 

    /* allocate space to fsl otg device */ 
    fsl_otg_tc = kzalloc( sizeof ( struct fsl_otg) , GFP_KERNEL) ; 
    if ( ! fsl_otg_tc) 
        return - ENODEV; 

    INIT_DELAYED_WORK( & fsl_otg_tc- > otg_event, fsl_otg_event) ; 

    INIT_LIST_HEAD( & active_timers) ; 
    status = fsl_otg_init_timers( & fsl_otg_tc- > fsm) ; 
    if ( status) { 
        printk( KERN_INFO "Couldn't init OTG timers/n" ) ; 
        fsl_otg_uninit_timers( ) ; 
        kfree( fsl_otg_tc) ; 
        return status; 
    } 
    spin_lock_init( & fsl_otg_tc- > fsm. lock) ; 

    /* Set OTG state machine operations */

/**************************************************************/

static struct otg_fsm_ops fsl_otg_ops = {
 .chrg_vbus = fsl_otg_chrg_vbus,
 .drv_vbus = fsl_otg_drv_vbus,
 .loc_conn = fsl_otg_loc_conn,
 .loc_sof = fsl_otg_loc_sof,
 .start_pulse = fsl_otg_start_pulse,

 .add_timer = fsl_otg_add_timer,
 .del_timer = fsl_otg_del_timer,

 .start_host = fsl_otg_start_host,
 .start_gadget = fsl_otg_start_gadget,
};

/**************************************************************/ 
    fsl_otg_tc- > fsm. ops = & fsl_otg_ops; 

    /* initialize the otg structure */ 
    fsl_otg_tc- > otg. label = DRIVER_DESC; 
    fsl_otg_tc- > otg. set_host = fsl_otg_set_host; 
    fsl_otg_tc- > otg. set_peripheral = fsl_otg_set_peripheral; 
    fsl_otg_tc- > otg. set_power = fsl_otg_set_power; 
    fsl_otg_tc- > otg. start_hnp = fsl_otg_start_hnp; 
    fsl_otg_tc- > otg. start_srp = fsl_otg_start_srp; 

    fsl_otg_dev = fsl_otg_tc; 

    /* Store the otg transceiver */

/***************************************************************/

int otg_set_transceiver(struct otg_transceiver *x)
{
 if (xceiv && x)
  return -EBUSY;
 xceiv = x;
 return 0;
}

該函數就是將struct otg_transceiver結構副給一個全局變量保存,供以後使用,以後會通過調用下面函數得到該結構

struct otg_transceiver *otg_get_transceiver(void)
{
 if (xceiv)
  get_device(xceiv->dev);
 return xceiv;
}

/***************************************************************/ 
    status = otg_set_transceiver( & fsl_otg_tc- > otg) ; 
    if ( status) { 
        printk( KERN_WARNING ": unable to register OTG transceiver./n" ) ; 
        return status; 
    } 

    return 0; 
}

 

 

 

int usb_otg_start( struct platform_device * pdev) 
{ 
    struct fsl_otg * p_otg;

/*獲得otg_transceiver結構 */ 
    struct otg_transceiver * otg_trans = otg_get_transceiver( ) ; 
    struct otg_fsm * fsm; 
    volatile unsigned long * p; 
    int status; 
    struct resource * res; 
    u32 temp;

/*獲得設備的私有數據*/ 
    struct fsl_usb2_platform_data * pdata = pdev- > dev. platform_data; 
/* 使用container_of宏定義可以通過結構中一個變量的指針獲得該結構首地址
 */
    p_otg = container_of( otg_trans, struct fsl_otg, otg) ; 
    fsm = & p_otg- > fsm; 

    /* Initialize the state machine structure with default values */ 
    SET_OTG_STATE( otg_trans, OTG_STATE_UNDEFINED) ; 
    fsm- > transceiver = & p_otg- > otg; 

    /* We don't require predefined MEM/IRQ resource index */

/*獲得設備的資源,是在設備註冊時結構體裏面的內容*/ 
    res = platform_get_resource( pdev, IORESOURCE_MEM, 0) ; 
    if ( ! res) 
        return - ENXIO; 

    /* We don't request_mem_region here to enable resource sharing
     * with host/device */
 
/*通過資源中 獲得的物理地址映射一個可以被驅動訪問的虛擬地址指針*/
    usb_dr_regs = ioremap( res- > start, sizeof ( struct usb_dr_mmap) ) ;

/*將該指針保存到p_otg - > dr_mem_map中 */ 
    p_otg- > dr_mem_map = ( struct usb_dr_mmap * ) usb_dr_regs; 
    pdata- > regs = ( void * ) usb_dr_regs; 

    /* request irq */

/*獲得設備註冊時候的中斷並註冊,在 OTG ID發生變化時觸發中斷,然後調用註冊的中斷例程函數,函數後面分析*/ 
    p_otg- > irq = platform_get_irq( pdev, 0) ; 
    status = request_irq( p_otg- > irq, fsl_otg_isr, 
                IRQF_SHARED, driver_name, p_otg) ; 
    if ( status) { 
        dev_dbg( p_otg- > otg. dev, "can't get IRQ %d, error %d/n" , 
            p_otg- > irq, status) ; 
        iounmap( p_otg- > dr_mem_map) ; 
        kfree( p_otg) ; 
        return status; 
    } 

    if ( pdata- > platform_init & & pdata- > platform_init( pdev) ! = 0) 
        return - EINVAL; 


    /* Export DR controller resources */

/**************************************************/

int otg_set_resources(struct resource *resources)
{
 otg_resources = resources;
 return 0;
}

和otg_set_transceiver功能類似將設備資源保存到一個全局變量中

/**************************************************/ 
    otg_set_resources( pdev- > resource) ; 
/*開始配置USB寄存器*/
    /* stop the controller */ 
    temp = readl( & p_otg- > dr_mem_map- > usbcmd) ; 
    temp & = ~ USB_CMD_RUN_STOP; 
    writel( temp, & p_otg- > dr_mem_map- > usbcmd) ; 

    /* reset the controller */ 
    temp = readl( & p_otg- > dr_mem_map- > usbcmd) ; 
    temp | = USB_CMD_CTRL_RESET; 
    writel( temp, & p_otg- > dr_mem_map- > usbcmd) ; 

    /* wait reset completed */ 
    while ( readl( & p_otg- > dr_mem_map- > usbcmd) & USB_CMD_CTRL_RESET) ; 

    /* configure the VBUSHS as IDLE(both host and device) */ 
    temp = USB_MODE_STREAM_DISABLE | ( pdata- > es ? USB_MODE_ES : 0) ; 
    writel( temp, & p_otg- > dr_mem_map- > usbmode) ; 

    /* configure PHY interface */ 
    temp = readl( & p_otg- > dr_mem_map- > portsc) ; 
    temp & = ~ ( PORTSC_PHY_TYPE_SEL | PORTSC_PTW) ; 
    switch ( pdata- > phy_mode) { 
    case FSL_USB2_PHY_ULPI: 
        temp | = PORTSC_PTS_ULPI; 
        break ; 
    case FSL_USB2_PHY_UTMI_WIDE: 
        temp | = PORTSC_PTW_16BIT; 
        /* fall through */ 
    case FSL_USB2_PHY_UTMI: 
        temp | = PORTSC_PTS_UTMI; 
        /* fall through */ 
    default : 
        break ; 
    } 
    writel( temp, & p_otg- > dr_mem_map- > portsc) ; 

    if ( pdata- > have_sysif_regs) { 
        /* configure control enable IO output, big endian register */ 
        p = ( volatile unsigned long * ) ( & p_otg- > dr_mem_map- > control) ; 
        temp = * p; 
        temp | = USB_CTRL_IOENB; 
        * p = temp; 
    } 

    /* disable all interrupt and clear all OTGSC status */ 
    temp = readl( & p_otg- > dr_mem_map- > otgsc) ; 
    temp & = ~ OTGSC_INTERRUPT_ENABLE_BITS_MASK; 
    temp | = OTGSC_INTERRUPT_STATUS_BITS_MASK | OTGSC_CTRL_VBUS_DISCHARGE; 
    writel( temp, & p_otg- > dr_mem_map- > otgsc) ; 


    /*
     * The identification (id) input is FALSE when a Mini-A plug is inserted
     * in the devices Mini-AB receptacle. Otherwise, this input is TRUE.
     * Also: record initial state of ID pin
     */
 
    if ( le32_to_cpu( p_otg- > dr_mem_map- > otgsc) & OTGSC_STS_USB_ID) { 
        p_otg- > otg. state = OTG_STATE_UNDEFINED; 
        p_otg- > fsm. id = 1; 
    } else { 
        p_otg- > otg. state = OTG_STATE_A_IDLE; 
        p_otg- > fsm. id = 0; 
    } 

    DBG( "initial ID pin=%d/n" , p_otg- > fsm. id) ; 

    /* enable OTG ID pin interrupt */ 
    temp = readl( & p_otg- > dr_mem_map- > otgsc) ; 
    temp | = OTGSC_INTR_USB_ID_EN; 
    temp & = ~ ( OTGSC_CTRL_VBUS_DISCHARGE | OTGSC_INTR_1MS_TIMER_EN) ; 
    writel( temp, & p_otg- > dr_mem_map- > otgsc) ; 

    return 0; 
}

 

下面分析下 中斷例程函數

該函數就是判斷ID的高低,也就是自己做主設備還是從設備

irqreturn_t fsl_otg_isr( int irq, void * dev_id) 
{ 
    struct otg_fsm * fsm = & ( ( struct fsl_otg * ) dev_id) - > fsm; 
    struct otg_transceiver * otg = & ( ( struct fsl_otg * ) dev_id) - > otg; 
    u32 otg_int_src, otg_sc; 
/* 獲得ID的變化信息*/
    otg_sc = le32_to_cpu( usb_dr_regs- > otgsc) ; 
    otg_int_src = otg_sc & OTGSC_INTSTS_MASK & ( otg_sc > > 8) ; 

    /* Only clear otg interrupts */ 
    usb_dr_regs- > otgsc | = cpu_to_le32( otg_sc & OTGSC_INTSTS_MASK) ; 

    /*FIXME: ID change not generate when init to 0 */ 
    fsm- > id = ( otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; 
    otg- > default_a = ( fsm- > id = = 0) ; 

    /* process OTG interrupts */ 
    if ( otg_int_src) { 
        if ( otg_int_src & OTGSC_INTSTS_USB_ID) { 
            fsm- > id = ( otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; 
            otg- > default_a = ( fsm- > id = = 0) ; 
            /* clear conn information */ 
            if ( fsm- > id) 
                fsm- > b_conn = 0; 
            else 
                fsm- > a_conn = 0; 

            if ( otg- > host) 
                otg- > host- > is_b_host = fsm- > id; 
            if ( otg- > gadget) 
                otg- > gadget- > is_a_peripheral = ! fsm- > id; 
            VDBG( "ID int (ID is %d)/n" , fsm- > id) ; 

            if ( fsm- > id) {     /* switch to gadget *///從設備

/*schedule_delayed_work函數先停止主設備後打開從設備 */

/***************************************************/

schedule_delayed_work( & ( ( struct fsl_otg * ) 
                            dev_id) - > otg_event, 
                            100) ;

函數就 是延遲100秒調用otg_event,就是下面函數。

static void fsl_otg_event(struct work_struct *work)
{
 struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work);
 struct otg_fsm *fsm = &og->fsm;

 if (fsm->id) {  /* switch to gadget */
  fsl_otg_start_host(fsm, 0);
  otg_drv_vbus(fsm, 0);
  fsl_otg_start_gadget(fsm, 1);
 }
}

/***************************************************/
                schedule_delayed_work( & ( ( struct fsl_otg * ) 
                            dev_id) - > otg_event, 
                            100) ; 
            } else {     /* switch to host *///主設備 
                cancel_delayed_work( & 
                         ( ( struct fsl_otg * ) dev_id) - > 
                         otg_event) ; 
                fsl_otg_start_gadget( fsm, 0) ;//停止從設備 
                otg_drv_vbus( fsm, 1) ; 
                fsl_otg_start_host( fsm, 1) ;//打開主 
            } 

            return IRQ_HANDLED; 
        } 
    } 

    return IRQ_NONE; 
}

 

int fsl_otg_start_host( struct otg_fsm * fsm, int on) 
{ 
    struct otg_transceiver * xceiv = fsm- > transceiver; 
    struct device * dev; 
    struct fsl_otg * otg_dev = container_of( xceiv, struct fsl_otg, otg) ; 
    struct platform_driver * host_pdrv; 
    struct platform_device * host_pdev; 
    u32 retval = 0; 
/*判 斷是否有主設備的驅動註冊進系統*/
    if ( ! xceiv- > host) 
        return - ENODEV; 
    dev = xceiv- > host- > controller;

/*找到主設備驅動的platform_driver結構,爲下面的停止和恢複函數調用做準備 */ 
    host_pdrv = container_of( ( dev- > driver) , struct platform_driver, driver); 
    host_pdev = to_platform_device( dev) ; 

    /* Update a_vbus_vld state as a_vbus_vld int is disabled
     * in device mode
     */
 
    fsm- > a_vbus_vld = 
     ( le32_to_cpu( usb_dr_regs- > otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0; 
    if ( on) { 
        /* start fsl usb host controller */ 
        if ( otg_dev- > host_working) 
            goto end; 
        else { 
            otg_reset_controller( ) ; 
            VDBG( "host on....../n" ) ; 
            if ( host_pdrv- > resume) { 
                retval = host_pdrv- > resume( host_pdev) ; 
                if ( fsm- > id) { 
                    /* default-b */ 
                    fsl_otg_drv_vbus( 1) ; 
                    /* Workaround: b_host can't driver
                     * vbus, but PP in PORTSC needs to
                     * be 1 for host to work.
                     * So we set drv_vbus bit in
                     * transceiver to 0 thru ULPI. */
 
# if defined( CONFIG_ISP1504_MXC) 
                    write_ulpi( 0x0c, 0x20) ; 
# endif 
                } 
            } 

            otg_dev- > host_working = 1; 
        } 
    } else { 
        /* stop fsl usb host controller */ 
        if ( ! otg_dev- > host_working) 
            goto end; 
        else { 
            VDBG( "host off....../n" ) ; 
            if ( host_pdrv- > suspend) { 
                retval = host_pdrv- > suspend( host_pdev, 
                            otg_suspend_state) ; 
                if ( fsm- > id) 
                    /* default-b */ 
                    fsl_otg_drv_vbus( 0) ; 
            } 
            otg_dev- > host_working = 0; 
        } 
    } 
end: 
    return retval; 
}

可以看到最後設備是使用還是停止調用的函數 分別是

host_pdrv - > suspend

host_pdrv - > resume

而上面兩個指針的函數賦值是在主設備驅動中完成的。

 

int fsl_otg_start_gadget( struct otg_fsm * fsm, int on) 
{ 
    struct otg_transceiver * xceiv = fsm- > transceiver; 
    struct device * dev; 
    struct platform_driver * gadget_pdrv; 
    struct platform_device * gadget_pdev; 
/*判斷是否有從設備驅動註冊*/
    if ( ! xceiv- > gadget | | ! xceiv- > gadget- > dev. parent) 
        return - ENODEV; 

    VDBG( "gadget %s /n" , on ? "on" : "off" ) ; 
    dev = xceiv- > gadget- > dev. parent; 
/*找到從設備驅動的platform_driver結構首地址,爲 下面調用其提供的功能函數做準備
 */
    gadget_pdrv = container_of( ( dev- > driver) , 
            struct platform_driver, driver) ; 
    gadget_pdev = to_platform_device( dev) ; 

    if ( on) 
        gadget_pdrv- > resume( gadget_pdev) ; 
    else 
        gadget_pdrv- > suspend( gadget_pdev, otg_suspend_state) ; 

    return 0; 
}

和上面主設備一樣

到底是從設備停止還是恢復是調用 
        gadget_pdrv - > resume ( gadget_pdev ) ; 
        gadget_pdrv - > suspend ( gadget_pdev , otg_suspend_state ) ;

上面兩個函數的指針就是在從設備驅動註冊時鏈接的。

上面部分就是 OTG功能的 OTG驅動部分。 OTG功能還要有做主設備使用的主設備驅動和做從設備的從設備驅動。

從上面代碼分析我們歸納出流程:

分兩個大部分:

一 設備的註冊  其中包括

1. 定義platform_device結構。 
2. 定義platform_device下的struct resource設備資源結構 
3. 定義 platform_device下的DEV設備下的平臺私有數據(就是該設備私有的數據) 
4. 調用platform_device_register將platform_device結構

二 OTG驅動的註冊 其中包括

1.struct platform_driver fsl_otg_driver 結構的註冊

2.匹配到有設備存在時調用的PORE函數,對設備進行初始化設置和功能函數的綁定

3.完成中斷函數的綁定和中斷例程的註冊。

 

經過上面的處理後,只要OTG ID的變化就會觸發中斷,調用中斷例程函數,決定是調用主設備還是從設備驅動。 而主設備和從設備驅動和OTG調用的鏈接是分別在主從設備驅動中完成的。後面我們介紹主從設備驅動中會介紹到。

 在文章的最後想起來這次調OTG遇見的問題,分享給大家希望大家有幫助。我調試OTG時,開始將OTG編譯到內核中。(Y)。結果插入U盤沒有反 應。後來發現原來我加入內核後,主設備驅動的先OTG設備驅動被執行,造成主設備函數和OTG功能的鏈接出現問題。(應該是OTG先初始化 然後從和主設備驅動鏈接。)後來我使用模塊方式編譯OTG功能。按照先載入OTG後載入從和主設備。(insmod方式),結果OTG就可以使用了。 後來通過降低主設備的優先級方式,把OTG編譯進內核,然後因爲主設備優先級低所以最後被調用。 也就是在主設備註冊那使用
late_initcall(ehci_hcd_init); 代替//module_init(ehci_hcd_init);。這樣主設備的優先級就低於設備驅動的優先級就在驅動加載完加載了。 但是總感覺這樣不是很合理的方式,如果有朋友有更好的辦法請指教。

發佈了51 篇原創文章 · 獲贊 18 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章