前一段时间弄了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);。这样主设备的优先级就低于设备驱动的优先级就在驱动载入完载入了。 可是总感觉这样不是非常合理的方式,假设有朋友有更好的办法请不吝赐教。