主篇文章主要记录imx6的研究过程,将工作中有的到的重要部分提炼出来,主要描述与软件相关的内容。

相关文章:i.MX 6ULL研究笔记_硬件篇

搭建编译工程

i.mx6官方提供的软件开发指导是基于yocto工程,文档中包含了许多内容,比如:

  • 指导如何配置主机(需要安装哪些包);
  • 指导如何下载yocto工程(不包含实际代码);
  • 如果配置编译,执行编译(编译过程中下载代码);
  • ……

yocto开发模式的优点:

  • 编译工程也集成非常多的软、硬件支持;
  • 可编译出各种类型的系统(虚拟系统、带图形界面系统、PC系统等待);

yocto开发模式的缺点:

  • 对开发者要求比较高;
  • 编译执行速度非常慢;
  • 编译出错不容易定位;

结合实际情况,我们不选yocto开发模式,自己搭建编译工程

源码获取

自己搭建编译工程需要单独下载源码
fresscale的代码存放在http://git.freescale.com/git

根据yocto指导手册可以找到i.MX6ULL需要代码
kernel:git://git.freescale.com/imx/uboot-imx.git
​ branch:imx_4.1.15_2.0.0_ga(最新的是imx_4.9.11_1.0.0_ga)
uboot:git://git.freescale.com/imx/uboot-imx.git
​ branch:imx_v2016.03_4.1.15_2.0.0_ga(最新的是imx_v2017.03_4.9.11_1.0.0_ga)

工具链获取

自己搭建编译工程需要单独下载工具链,可以从linaro官网下载,根据需求选择正确的工具链

编译&移植

uboot编译&移植

  1. 官方代码提供了几多板型支持,根据实际情况选择最接近的一个,拷贝一份(有多个文件),命名为hd_mx6ull_ddr3
  2. 在uboot根目录下执行make hd_mx6ull_ddr3_defconfig生成原始配置;
  3. 根据实际硬件及需求进行调试代码;
  4. 将编译生成-boot.imx文件拷贝到目标位置;

    注:编译前需要配置好工具链

kernel编译&移植

  1. 官方代码提供了几多板型支持,根据实际情况选择最接近的一个拷贝命令名为imx6ull-14x14-hd.dts,同时添加对imx6ull-14x14-hd.dts的编译控制;
  2. 在kernel根目录下执行make ARCH=arm imx_v7_defconfig生成原始配置;
  3. 执行make menuconfig,根据实现硬件及需求进行配置;
  4. 根据实际硬件及需求进行调试代码;
  5. 将编译生成的zImage和imx6ull-14x14-hd.dtb 文件拷贝到目标位置;

    注:编译前需要配置好工具链和ARCH

rootfs编译

可以使用ubuntu core作为文件系统,直接从ubuntu 官网下载,然后再根据具体需求进行增减功能

ububtu core可以从官网下载:http://old-releases.ubuntu.com/releases/ubuntu-core/releases

CPU超频方法

  1. 设备树operating-points

    CPU可工作的频率由设备树的operating-points决定,目前的设备树

    1
    2
    3
    4
    5
    6
    7
    8
    operating-points = <
    /* kHz uV */
    996000 1275000
    792000 1225000
    528000 1175000
    396000 1025000
    198000 950000
    >;

    从设备树来看,没有做限制(当然也可以自己修改来限制最高运行频率,不过应该没人这么干
    既然设备树里没有限制,那就应该是代码中识别了CPU的型号,取消了其中某些选项

  2. 查找代码

    最终定位到mach-imx6ull.c文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    static void __init imx6ul_opp_check_speed_grading(struct device *cpu_dev)
    {
    ……
    /*
    * Speed GRADING[1:0] defines the max speed of ARM:
    * 2b'00: Reserved;
    * 2b'01: 528000000Hz;
    * 2b'10: 700000000Hz(i.MX6UL), 800000000Hz(i.MX6ULL);
    * 2b'11: Reserved(i.MX6UL), 1GHz(i.MX6ULL);
    * We need to set the max speed of ARM according to fuse map.
    */
    val = readl_relaxed(base + OCOTP_CFG3);
    val >>= OCOTP_CFG3_SPEED_SHIFT;
    val &= 0x3;
    if (cpu_is_imx6ul()) {
    if (val < OCOTP_CFG3_SPEED_696MHZ) {
    if (dev_pm_opp_disable(cpu_dev, 696000000))
    pr_warn("Failed to disable 696MHz OPP\n");
    }
    }

    if (cpu_is_imx6ull()) {
    if (val != OCOTP_CFG3_SPEED_1_GHZ) { //将这几行屏蔽可超频到996Mhz
    if (dev_pm_opp_disable(cpu_dev, 996000000))
    pr_warn("Failed to disable 996MHz OPP\n");
    }
    if (val != OCOTP_CFG3_SPEED_696MHZ) { //将这几行屏蔽可超频到792Mhz
    if (dev_pm_opp_disable(cpu_dev, 792000000))
    pr_warn("Failed to disable 792MHz OPP\n");
    }
    }
    iounmap(base);

    put_node:
    of_node_put(np);
    }

    通过分析代码可知,读取OCOTP_CFG3寄存器来判断是否要屏蔽掉某些工作频率,datasheet中关于OCOTP_CFG3的描述

    注:提升主频相应的VDD_SOC_IN也要提升,否则可能不稳定

实测

目前开发板使用的CPU标称频率是528Mhz,通过修改代码超频到792Mhz,进行对比测试(使用浮点运行、双精度浮点运算、整形运行3种模式运行对比测试):

从5128Hhz超频到792Mhz,CPU计算性能提升50%,主频提升成正比关系

几点说明:

1
2
3
4
1. 超频后CPU 100%运行,CPU温度没有明显变化(手摸没有明显发热);
2. 次测试在开发板上执行,超频后没有做稳定性挂机测试;
3. 发板上VDD_SOC_IN电压只有1.2V,理论上是不够的,但测试时没发现问题;
4. 代码中792Mhz对应的ARM电压是1.225V,因为VDD_SOC_IN不够,实测只有1.19V;

内存配置

内存配置涉及到以下3部分:

  • 内存配置参数
  • 内存参数校准
  • 内存大小自适应

内存配置参数

内存参数的配置在uboot/board/freescale/xxx/imximage.cfg (xxx是板子名,目前是hd_mx6ull_ddr3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
DATA 4 0x020c4068 0xffffffff
DATA 4 0x020c406c 0xffffffff
DATA 4 0x020c4070 0xffffffff
DATA 4 0x020c4074 0xffffffff
DATA 4 0x020c4078 0xffffffff
DATA 4 0x020c407c 0xffffffff
DATA 4 0x020c4080 0xffffffff

DATA 4 0x020E04B4 0x000C0000
DATA 4 0x020E04AC 0x00000000
DATA 4 0x020E027C 0x00000030
DATA 4 0x020E0250 0x00000030
...

这些东西,是什么意思呢?不在这里讲,这里只讲这些数据怎么得到

  1. 下载DDR配置脚本文件(1个excel文件),点击这里下载,也可以进入NXP的官方社区搜索下载

    根据实际使用的内存参数(需要查看内存芯片手册)进行配置,详细的说明文档点击这里可以下载

  2. 将excel文件中的RealView.inc表格里的文件复位出来,创建一个文本文件(文件名随便取,后缀为inc,比如取名:imx6ull_hd_ddr3.inc),到此,已经得到一份基础的配置文件,不过如下图所示,基础配置文件中有几项红色的需要校准

内存参数校准

内存参数校准目的:摘自官网论坛

It performs write leveling, DQS gating, read/write delay calibration on the target board to match the layout of the board and archive the best DDR performance.

说明校准与PCB设计相关

  1. 修改基础的配置文件,将Disable WDOG屏蔽,具体修改如下

    1
    2
    3
    4
    5
    wait = on			
    //=============================================================================
    // Disable WDOG
    //=============================================================================
    //setmem /16 0x020bc000 = 0x30 //将一行屏蔽

    修改原因(官网论坛有描述)

    Q. I see an error message that states “ERROR: DCD addr is out of valid range.”, why is this and how do I resolve?

    A. Sometimes, when using the register programming aid, there are registers writes that are not supported in the DCD range. Try looking for the following items and comment them out from the DDR initialization script:
    wait = on
    setmem /16 0x020bc000 = 0x30 // disable watchdog (note the address for this may be different between i.MX6x devices)

  2. 根据实际的硬件校准参数,需要下载校准工具,目前最新版本2.7.0,点击这里下载
    注意:使用该工具时,需要将设备切换到Serial Download模式

  3. 经过一段时间的校准后会生成校准数据,将校准参数写入到基础配置文件
  4. 重新加载配置文件,进行压力测试,如果测试成功则可以将配置文件导入到uboot里
    导入方法:手工对照一个一个复制,也可以用编辑工具编辑好再复制,总之只能手工

内存大小自适应

前面讲的内存参数配置里包含了大小,但这个大小只涉及内存控制器,相应的参数会填充到内存控制器里。
imx6的默认uboot代码,识别内存大小的方法是直接从控制器里读取配置,如果设备的内存大小是固定的,不同大小的内存对应不同的软件版本,这种方法是可行啊,但是…

我们希望一个软件版本能适应不同内存大小,这要怎么做呢?

  1. 首先要确认设备需要使用的最大内存是多少,内存大小参数按照最大的来填写
  2. uboot代码里使用uboot官方的做法,使用get_ram_size去获取内存大小
  3. kernel设备树里的内存配置,使用最大值

原理说明(感兴趣的可以阅读):

  • 写入到内存控制器的参数(片选、bank数、位宽、行列地址等)会控制内存访问时发出正确的读写时序,细节可查看CPU datasheet;
  • 不同容量同类型的DDR,一般只有行地址(Row Address)不同(有兴趣的可以看DDR手册),其他参数是一致的;
  • CPU访问内存时,imx6有2种地址映射方法(从高地址到低地址):
    • BANK-ROW-COL(bank interleaving off)
    • ROW-BANK-COL(bank interleaving on)
      详情可查看CPU datasheet
  • 当bank interleaving on里,行地址(ROW)被映射到最高位,访问高地址和低地址对就的时序是一样的

从原理可以看出,内存大小自适应是有前提的:

  • 打开bank interleaving,在MMDC_MDMISC寄存器中(官方默认配置是打开的)
  • 需要使用相同类型的内存:位宽bank数、列地址大小、页大小相同,关键的时序参数也要相同

uboot下硬件接口适配

官方默认uboot代码可以驱动串口、网口、USB、TF卡、I2C等,在新的硬件下,这些接口需要重新进行适配

串口适配

每1步:需要正确配置串口引脚,由setup_iomux_uart函数完成

1
2
3
4
static void setup_iomux_uart(void)
{
imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads));
}
1
2
3
4
static iomux_v3_cfg_t const uart1_pads[] = {
MX6_PAD_UART1_TX_DATA__UART1_DCE_TX | MUX_PAD_CTRL(UART_PAD_CTRL),
MX6_PAD_UART1_RX_DATA__UART1_DCE_RX | MUX_PAD_CTRL(UART_PAD_CTRL),
};

注:请确保使用的引脚配置没有被其他代码重新配置

第2步:配置软件需要使用的串口,在全局配置文件中

1
2
#define CONFIG_MXC_UART
#define CONFIG_MXC_UART_BASE UART1_BASE

网口适配

这里说的网口是指CPU自带的网口
第1步:根据硬件设计,确认选择哪个网口(imx6ull最多有2个网口,一般会选择0),在全局配置文件中

第2步:根据硬件设计,确认选择的PHY接口类型(通常会有RMII)以及PHY的设备地址(需要查看PHY芯片手册)

第3步:选择PHY芯片驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifdef CONFIG_CMD_NET
#define CONFIG_CMD_PING
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MII
#define CONFIG_FEC_MXC
#define CONFIG_MII
#define CONFIG_FEC_ENET_DEV 0 //选择网口0

#if (CONFIG_FEC_ENET_DEV == 0)
#define IMX_FEC_BASE ENET_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x0 //PHY的设备地址
#define CONFIG_FEC_XCV_TYPE RMII
#elif (CONFIG_FEC_ENET_DEV == 1)
#define IMX_FEC_BASE ENET2_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR 0x1
#define CONFIG_FEC_XCV_TYPE RMII
#endif
#define CONFIG_ETHPRIME "FEC" //网口名称,无关紧要

#define CONFIG_PHYLIB //表示需要使用外部PHY,相应的驱动代码会编译
#define CONFIG_PHY_SMSC //硬件使用的PHY厂家,表示使用SMSC的PHY,相应的SMSC的PHY驱动会被编译
#endif

注:如果硬件使用的PHY,uboot代码中没对应的驱动,可以尝试使用通用驱动,即:只定义CONFIG_PHYLIB,一般通用驱动可以用于决大部分硬件的驱动

第4步:硬件引脚复用配置,由setup_iomux_fec函数完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void setup_iomux_fec(int fec_id)
{
if (fec_id == 0){
imx_iomux_v3_setup_multiple_pads(fec1_pads,
ARRAY_SIZE(fec1_pads));

/* Reset the PHY */
gpio_direction_output(IMX_GPIO_NR(5, 9) , 0);
mdelay(100);
gpio_direction_output(IMX_GPIO_NR(5, 9) , 1);
}else {
imx_iomux_v3_setup_multiple_pads(fec2_pads,
ARRAY_SIZE(fec2_pads));
/* Reset the PHY */
gpio_direction_output(IMX_GPIO_NR(5, 6) , 0);
mdelay(100);
gpio_direction_output(IMX_GPIO_NR(5, 6) , 1);
}
}

正确配置fec1_pads(或fec2_pads)即可

注意:setup_iomux_fec包含了PHY复位的控制,相应的GPIO也要配置正确

USB适配

第1步:配置实际使用的USB接口数量
第2步:硬件是否使用OTG功能,使用OTG功能时,需要正确配置ID引脚

1
#define CONFIG_USB_MAX_CONTROLLER_COUNT 2 //硬件使用几个USB接口
1
2
3
static iomux_v3_cfg_t const usb_otg_pads[] = {
MX6_PAD_GPIO1_IO00__ANATOP_OTG1_ID | MUX_PAD_CTRL(OTG_ID_PAD_CTRL),
};

TF卡适配

按照正常的设计i.MX6 ULL的TF卡都会接在USDHC1上,一般不会有什么问题

需要注意一点:官方默认代码有USDHC的复位控制,实际硬件如果不使用复位功能,需要去掉相应的代码

I2C适配

uboot下一般不需要使用I2C功能,如果确实需要使用,要进行以下几点适配

  • 确认使用哪路I2C(i.MX6 ULL可提供多路I2C),在主配置文件中
  • 正确配置引脚复用
1
2
#define CONFIG_SYS_I2C_MXC_I2C1		/* enable I2C bus 1 */
#define CONFIG_SYS_I2C_MXC_I2C2 /* enable I2C bus 2 */
1
2
3
4
5
6
7
8
9
10
11
12
static struct i2c_pads_info i2c_pad_info1 = {
.scl = {
.i2c_mode = MX6_PAD_UART4_TX_DATA__I2C1_SCL | PC,
.gpio_mode = MX6_PAD_UART4_TX_DATA__GPIO1_IO28 | PC,
.gp = IMX_GPIO_NR(1, 28),
},
.sda = {
.i2c_mode = MX6_PAD_UART4_RX_DATA__I2C1_SDA | PC,
.gpio_mode = MX6_PAD_UART4_RX_DATA__GPIO1_IO29 | PC,
.gp = IMX_GPIO_NR(1, 29),
},
};

I2C引脚配置中有GPIO的配置,作用:I2C初化时会尝试检测总线是否可以正常工作,需要用到GPIO模式

linux系统下接口调试

linux系统下的接口调试主要通过配置设备树来完成,设备树需要与代码匹配(设备树中的东西在kernel代码中是有对应的处理流程的)

涉及的内容比较多,这里只做简单描述。

设备树裁剪

默认设备树代码中添加了许多设备,需要根据实际的硬件进行裁剪,比如:

1
2
3
4
5
6
7
8
9
10
11
12
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "disabled"; //设置成disabled,则不会注册到系统中,但为了代码的简洁性,可以将整个backlight删除
};

pxp_v4l2 {
compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
status = "disabled";
};

串口

包含2部分

  1. 注册串口设计(当然也是修改设备树),比如下面的配置是添加注册2个串口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    &uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1>;//UART1使用的引脚,需要正确设置,从设备树已有代码很容易看出怎么使用
    status = "okay"; //表示将uart1注册到系统中
    };
    &uart2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart2>;//UART2使用的引脚
    fsl,uart-has-rtscts; //打开流控功能
    /* for DTE mode, add below change */
    /* fsl,dte-mode; */
    /* pinctrl-0 = <&pinctrl_uart2dte>; */
    status = "okay";//表示将uart2注册到系统中
    };
  2. 指定系统使用哪个串口作为标准输出口(输出kernel打印)

    1
    2
    3
    chosen {
    stdout-path = &uart1;
    };

网口驱动

网口驱动主要是在PHY的适配上,细节如下:

  • 看设备树中,默认代码将mdio总线注册在fec2(总共有fec1和fec2),不合常理,一般应该注册在第一个(fec1);
  • 阅读代码,代码设计将mdio注册在eth0上,代码中按照probe顺序注册网口,因为在设备树中fec2在fec1前面,因此fec2注册成eth0,fec1注册成eth1
  • 为方便常理认知,以及后续单网口与双网口兼容,将fec1放在fec2前面

针对Y2系统,支持2个网口,但只有一个MDIO总线引脚,如果需要使用Y2的双网口功能,需要软件特别处理,官方代码设计已经有处理,但需要做简单修改,原因及修改如下:

  • 硬件设计中phy的时钟由CPU提供;
  • MDIO总线只能注册在第1个网口上(比如注册在fec1上)
  • 初始化fec1时,fec2没有打开,对应的phy没有时钟,因此不能自动探测到phy型号;
  • 设备树中需要指定fec2的phy ID

修改示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mdio {
#address-cells = <1>;
#size-cells = <0>;

ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <0>;
};

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-id0007.c0f1";
reg = <1>;
};
};

SD/EMMC驱动

默认代码运行时,sd插入会有错误打印

1
2
mmc0: tuning execution failed
mmc0: error -5 whilst initialising SD card

需要修改设备树,添加“no-1-8-v”属性

USB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
&usbotg1 {
dr_mode = "otg"; //注意模式选择
srp-disable; //OTG模式里需要这些选项
hnp-disable;
adp-disable;
status = "okay";
};

&usbotg2 {
dr_mode = "host";
disable-over-current; //关闭过流检测,硬件一般不设计这个功能,如果硬件设计了,则需要正配置
status = "okay";
};

&usbphy1 {
tx-d-cal = <0x5>;//D_CAL,用于补偿电路USB电流的损耗,值越大补偿越多
};

&usbphy2 {
tx-d-cal = <0x5>;
};