全志F1C100S usb裸机驱动移植1

一、关于全志F1C100S

全志F1C100S是珠海全志科技2017年量产的一款基于ARM9内核的SOC。芯片最高主频408MHz,内部堆叠了32MByte的DDR内存,支持UART、USB OTG、SPI、TWI、TP、SDM/MC、LCD、CVBS、DVP等众多外设接口。下图是F1C100S的内部框图:
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中分析整个过程。

致谢