一、关于全志F1C100S
全志F1C100S是珠海全志科技2017年量产的一款基于ARM9内核的SOC。芯片最高主频408MHz,内部堆叠了32MByte的DDR内存,支持UART、USB OTG、SPI、TWI、TP、SDM/MC、LCD、CVBS、DVP等众多外设接口。下图是F1C100S的内部框图:
二、F1C100S USB接口介绍
目前常见的soc芯片用的usb接口的IP常见的就那么几种。其中用的最多的要数Synopsys(新思科技)公司下的DesignWare系的DWC和MentorGraphics公司下的Musb。采用dwc的常见芯片有STM32,RK3288等,而采用musb的有am335x,pic32等。而全志的F1C100S则是采用musb的usb phy ip,其中寄存器的地址偏移有修改,不是标准的musb寄存器排布顺序。这里给出musb官方的编程指导文档musb_programming_guide.pdf。
三、F1C100S官方linux bsp项目里的USB驱动
全志F1C100S主要面向市场是唱戏机,其中官方有提供一个sdk是Linux bsp【c600】。这个SDK里面有包含usb的驱动源码(在/c600/linux-3.10/drivers/usb/sunxi_usb目录下),关于sdk的编译过程可以参考网友们给出的资料和教程>>【2】step by step 编译全志 f1c100s 官方linux bsp;而usb device模式遇到的问题和解决方法参考>>f1c100s USB otg device 模式可以用吗?。主要遇到的问题是没有识别到usb插入,可能是其他地方没有调用到相应的函数把is_udc_enable变量置一的原因。
四、移植编写F1C100S的裸机usb驱动
有了上面Linux下的驱动后,可以把整个sunxi_usb拷贝出来。然后通过eclipse导入查看具体的函数调用。经过分析可以得出主要的usb驱动函数是下面这个:
int sunxi_usb_device_enable(void)
{
struct platform_device *pdev = g_udc_pdev;
struct sunxi_udc *udc = &sunxi_udc;
int retval = 0;
DMSG_INFO_UDC("sunxi_usb_device_enable start\n");
if (pdev == NULL) {
DMSG_PANIC("pdev is null\n");
return -1;
}
usb_connect = 0;
crq_bRequest = 0;
is_controller_alive = 1;
retval = sunxi_udc_bsp_init(&g_sunxi_udc_io);
if (retval != 0) {
DMSG_PANIC("ERR: sunxi_udc_bsp_init failed\n");
return -1;
}
sunxi_udc_disable(udc);
udc->irq_no = platform_get_irq(pdev, 0);
if (udc->irq_no < 0) {
DMSG_PANIC("%s,%d: error to get irq\n", __func__, __LINE__);
return -EINVAL;
}
udc->sunxi_udc_io = &g_sunxi_udc_io;
udc->usbc_no = usbd_port_no;
strcpy((char *)udc->driver_name, gadget_name);
udc->pdev = pdev;
udc->controller = &(pdev->dev);
#ifdef CONFIG_OF
udc->controller->dma_mask = &sunxi_udc_mask;
udc->controller->coherent_dma_mask = DMA_BIT_MASK(32);
#endif
if (is_udc_support_dma()) {
retval = sunxi_udc_dma_probe(udc);
if (retval != 0) {
DMSG_PANIC("ERR: sunxi_udc_dma_probe failef\n");
retval = -EBUSY;
goto err;
}
}
retval = request_irq(udc->irq_no, sunxi_udc_irq,
IRQF_DISABLED, gadget_name, udc);
if (retval != 0) {
DMSG_PANIC("ERR: cannot get irq %i, err %d\n", udc->irq_no, retval);
retval = -EBUSY;
goto err;
}
if (udc->driver && is_udc_enable) {
sunxi_udc_enable(udc);
cfg_udc_command(SW_UDC_P_ENABLE);
}
DMSG_INFO_UDC("sunxi_usb_device_enable end\n");
return 0;
err:
if (is_udc_support_dma()) {
sunxi_udc_dma_remove(udc);
}
sunxi_udc_bsp_exit(&g_sunxi_udc_io);
return retval;
}
经过处理后到裸机里面的话可以整理成以下代码:
int usb_device_init(unsigned char type)
{
int retval = 0;
if(current_usb_type != type)
{
usbprint("sunxi_usb_device_enable start\n");
current_usb_type = type;
retval = usb_dev_bsp_init();
if (retval != 0)
{
usbprint("ERR: sunxi_udc_bsp_init failed\n");
return -1;
}
sunxi_udc_disable();
retval = request_irq(IRQ_USBOTG, usb_irq_handle,0);
if (retval != 0)
{
usbprint("ERR: cannot get irq %i, err %d\n", IRQ_USBOTG, retval);
retval = -1;
}
sunxi_udc_enable();
usbprint("sunxi_usb_device_enable end\n");
return retval;
}
return retval;
}
代码包括以下几个步骤:
1. 初始化usb_phy
通过调用sunxi_udc_bsp_init函数进行usb接口物理层的初始化。其中的动作包括了开外设时钟,配置引脚,上下拉id和数据脚让主机端可以识别设备插入等。
2. 复位usb和申请usb中断
接着就是对usb进行复位,清除中断。调用的是sunxi_udc_disable函数和request_irq函数,其中request_irq函数在Linux BSP 里面属于专门配置中断的一个驱动函数,牵涉到的内容比较多,具体移植可以参考另外一篇博客全志F1C100S的中断解析和驱动编写。后续usb枚举,收发数据都是通过usb_irq_handle中断函数实现的。
3. 使能usb端点并配置中断
最后调用sunxi_udc_enable完成usb模式设置,配置中断,使能usb。做完这些动作后,如果设备和主机之间硬件连接没有问题的话,此时主机则会检测到usb并向设备端发出suspend中断、reset中断 。这个时候,usb_irq_handle中断会被中断管理函数调用。到此,整个usb的接口初始化已经完成,剩下操作的则是全部通过usb_irq_handle来进行。
四、总结
通过上面的移植,我们可以让主机识别到设备的usb,并能够顺利的进入usb中断函数。但是过了一会后,主机端会提示未知usb设备(设备描述符请求失败),这是因为我们在usb中断函数里什么都没有做,没有去响应主机端发出的指令和枚举请求,所以主机端重复请求超过四次后没有得到响应的话则会报未知设备。虽然说还没有完成整个usb驱动的代码移植,但是我们到这里已经完成大部分驱动方面的工作了,能够让主机识别到usb并且设备能够正确进入usb中断函数是驱动正确工作的 关键中的关键 。接下来,就是对主机的命令和枚举请求在usb_irq_handle函数里面一一响应,这个会在全志F1C100S usb裸机驱动移植2中分析整个过程。
致谢
- 感谢Xboot提供的裸机代码参考Xboot项目和交流平台XBOOT裸机开发官方群
658250248
- 感谢泽畔无材提供的基于F1C100S的开源硬件
lichee pi nano
- 感谢挖坑侠晕哥提供的嵌入式交流平台挖坑网/填坑网 DebugDump Forum
- 感谢达克罗德提供的F1C100S裸机编译工程F1C100S裸奔framebuffer+PWM+GPIO驱动
- 感谢szyusong、ippen 在f1c100s USB otg device 模式可以用吗?提供的信息
- 感谢mirkerson提供的Linux Bsp项目
资料下载:
F1C100S裸机usb工程(支持hid和cdc类):F1C100S\_USB\_Driver\_V1.0.zip