OTG驅動分析(一) 分類: arm-linux-Ubuntu USB OTG驅動 2015-06-02 17:32 315人閱讀 評論(0) 收藏


前一段時間弄了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;
        *= 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);。這樣主設備的優先級就低於設備驅動的優先級就在驅動加載完加載了。 但是總感覺這樣不是很合理的方式,如果有朋友有更好的辦法請指教。

版權聲明:本文為博主原創文章,未經博主允許不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM