i.MX 6 UL + Linux Notes

2017-09-27

/uploads/blog/2017/1551241277540-20190226232048.jpg

Notes of me bringing up an i.MX6 UL based board. Considering it's a quite commonly used chip, I might be using it in the future, I am taking some notes for future me for reference.

Camera I2C

For whatever reason, i.MX6's I2C doesn't like my camera's SCCB. I could see that camera sent the ACK, but i.MX6 didn't continue to send data. Fortunately it's quite simple to use software i2c on Linux: just enable i2c-gpio driver and modify the device tree:

i2c@0 {
    compatible = "i2c-gpio";
    gpios = <&gpio1 29 0
         &gpio1 28 0>;
    #address-cells = <1>;
    #size-cells = <0>;

    mt9m: mt9m@5d {
        compatible = "mt9m";
        reg = <0x5d>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_csi1>;
        clocks = <&clks IMX6UL_CLK_CSI>;
        clock-names = "csi_mclk";
        csi_id = <0>;
        mclk = <20000000>;
        mclk_source = <0>;
        status = "okay";
        port {
            mt9m_ep: endpoint {
                remote-endpoint = <&csi1_ep>;
            };
        };
    };
};

Don't forget to add the corresponding pin control:

pinctrl_hog_1: hoggrp-1 {
    fsl,pins = <
        MX6UL_PAD_UART4_TX_DATA__GPIO1_IO28 0x1b8b0
        MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x1b8b0
    >;
};

userspace GPIO

Simple enough, add the pins to hog but not in any specific node. Then it would be be available to use via sysfs:

/sys/class/gpio # echo 0 > export
/sys/class/gpio # echo in > gpio0/direction
/sys/class/gpio # cat gpio0/value
0
/sys/class/gpio # echo out > gpio0/direction
/sys/class/gpio # echo 1 > gpio0/value

Note the numbering is sequential. On i.MX6 each GPIO has 32pins. For example GPIO1_3 is no. 3, and GPIO4_21 is no. (4-1)*32+21=117.

Boot mode design consideration

For BOOT_MODE1 and BOOT_MODE0, pull down either via

Pull up via:

For controlling using DIPSW, no pull down resistor is needed. Simply connect SPST to VDD_SNVS_IN. Optionally via 4.7K-10K resistor.

USB design consideration

Due to the bug of i.MX6UL itself, there is VBUS leaking issue. So only one USB could work in OTG/device mode, while the other has to be host. Or 2 USBs could be both in host mode.

CSI camera

iMX6S/D/DL/Q has internal IPU (Image Processing Unit), the image captured by the CSI would go through the IPU and reach the memory. iMX6SX/iMX6SL/iMX6UL/iMX6ULL/iMX7D doesn't have IPU, images directly captured to the memory. They use different drivers, the former is located in drivers/media/platform/mxc/capture, and the latter is in drivers/media/platform/mxc/subdev.

Another thing is that the driver in linux-imx doesn't support 8-bit greyscale. But that's simple to fix, as that's basically the same as 8-bit bayerRAW.

MT9M001's clock polarity is different from the default one used by the driver, so that needs to be inverted.

@@ -273,6 +273,12 @@ static struct mx6s_fmt formats[] = {
                .pixelformat    = V4L2_PIX_FMT_SBGGR8,
                .mbus_code      = MEDIA_BUS_FMT_SBGGR8_1X8,
                .bpp            = 1,
+       }, {
+               .name           = "GREYSCALE (Y8)",
+               .fourcc         = V4L2_PIX_FMT_GREY,
+               .pixelformat    = V4L2_PIX_FMT_GREY,
+               .mbus_code      = MEDIA_BUS_FMT_Y8_1X8,
+               .bpp            = 1,
        }
 };

@@ -463,6 +469,7 @@ static void csi_init_interface(struct mx6s_csi_dev *csi_dev)
        val |= BIT_FCC;
        val |= 1 << SHIFT_MCLKDIV;
        val |= BIT_MCLKEN;
+       val |= BIT_INV_PCLK;
        __raw_writel(val, csi_dev->regbase + CSI_CSICR1);

        imag_para = (640 << 16) | 960;
@@ -804,6 +811,7 @@ static int mx6s_configure_csi(struct mx6s_csi_dev *csi_dev)
        switch (csi_dev->fmt->pixelformat) {
        case V4L2_PIX_FMT_YUV32:
        case V4L2_PIX_FMT_SBGGR8:
+       case V4L2_PIX_FMT_GREY:
                width = pix->width;
                break;
        case V4L2_PIX_FMT_UYVY:
@@ -835,6 +843,7 @@ static int mx6s_configure_csi(struct mx6s_csi_dev *csi_dev)
                        cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B;
                        break;
                case V4L2_PIX_FMT_SBGGR8:
+               case V4L2_PIX_FMT_GREY:
                        cr18 |= BIT_MIPI_DATA_FORMAT_RAW8;
                        break;
                default:

For image previewing, Unfortunately mplayer won't work as I didn't find anyway to specify the pixel format. But it's simple enough to write my own program to do that:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>

#define NR_BUFFER 4
void * vm_addr[NR_BUFFER];
void * framebuffer;

int main(int argc,char **argv)
{
    int fd, vd, fb, ret, i, type;
    char buffer[32];
    char *grey_buf = NULL;
    char capflags;
    struct fb_var_screeninfo fb_var;
    struct fb_fix_screeninfo fb_fix;
    struct v4l2_capability cap;
    struct v4l2_fmtdesc fmt;
    struct v4l2_frmsizeenum fsize;
    struct v4l2_frmivalenum fival;
    struct v4l2_streamparm sparm;

    fb = open("/dev/fb0",O_RDWR);
    if(fb < 0){
        perror("open fb");
        exit(1);
    }
    if(ioctl(fb, FBIOGET_VSCREENINFO, &fb_var) < 0){
        perror("get var");
        exit(1);
    }
    if(ioctl(fb, FBIOGET_FSCREENINFO, &fb_fix) < 0){
        perror("get fix");
        exit(1);
    }
    framebuffer = mmap(NULL,fb_var.xres*fb_var.yres*fb_var.bits_per_pixel/8 * 2,PROT_WRITE,MAP_SHARED,fb,0);
    if(framebuffer == MAP_FAILED){
        perror("fb map");
        exit(1);
    }

    //video init
    vd = open( "/dev/video0", O_RDWR);
    if(vd < 0){
        perror("open cam ");
        exit(1);
    }

    if(ioctl(vd,VIDIOC_QUERYCAP,&cap) < 0){
        perror("cam capability");
        exit(1);
    }
    printf("capability driver %s card %s businfo %s\n",cap.driver,cap.card,cap.bus_info);

    struct v4l2_format vfmt;
    memset(&vfmt,0,sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmt.fmt.pix.width = 640;
    vfmt.fmt.pix.height = 480;
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_GREY;
    vfmt.fmt.pix.field = V4L2_FIELD_NONE;
    if(ioctl(vd,VIDIOC_S_FMT,&vfmt) < 0){
        perror("cam set fmt");
        exit(1);
    }
    printf("cam set width %d height %d\n",vfmt.fmt.pix.width,vfmt.fmt.pix.height);
    memset(&sparm,0,sizeof(sparm));
    sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    sparm.parm.capture.capturemode = 0;
    sparm.parm.capture.timeperframe.numerator = 1;
    sparm.parm.capture.timeperframe.denominator = 30;
    if(ioctl(vd,VIDIOC_S_PARM,&sparm) < 0){
        perror("cam s parm");
        exit(1);
    }


    struct v4l2_requestbuffers reqb;
    memset(&reqb,0,sizeof(reqb));
    reqb.count = NR_BUFFER;
    reqb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqb.memory = V4L2_MEMORY_MMAP;
    if(ioctl(vd,VIDIOC_REQBUFS,&reqb) < 0){
        perror("cam req buf");
        exit(1);
    }
    struct v4l2_buffer vbuffer;
    for(i = 0; i < NR_BUFFER; i++){
        memset(&vbuffer,0,sizeof(vbuffer));
        vbuffer.index = i;
        vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vbuffer.memory = V4L2_MEMORY_MMAP;
        if(ioctl(vd,VIDIOC_QUERYBUF,&vbuffer) < 0){
            perror("cam querybuf");
            exit(1);
        }
        vm_addr[i] = mmap(NULL,vbuffer.length,PROT_READ,MAP_SHARED,vd,vbuffer.m.offset);
        if(vm_addr[i] == MAP_FAILED){
            perror("cam map ");
            exit(1);
        }
    }
    for(i = 0;i < NR_BUFFER; i++){
        memset(&vbuffer,0,sizeof(vbuffer));
        vbuffer.index = i;
        vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vbuffer.memory = V4L2_MEMORY_MMAP;
        if(ioctl(vd,VIDIOC_QBUF,&vbuffer) < 0){
            perror("cam qbuf");
            exit(1);
        }
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(vd,VIDIOC_STREAMON,&type) < 0){
        perror("cam streamon");
        exit(1);
    }

    memset(&vbuffer,0,sizeof(vbuffer));
    vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vbuffer.memory = V4L2_MEMORY_MMAP;
    while(1){
        if(ioctl(vd,VIDIOC_DQBUF,&vbuffer) < 0){
            perror("cam dqbuf");
            exit(1);
        }
        grey2rgb(vm_addr[vbuffer.index],framebuffer,640,480);
        if(ioctl(vd,VIDIOC_QBUF,&vbuffer) < 0){
            perror("cam qbuf");
            exit(1);
        }
    }

    if(ioctl(vd, VIDIOC_STREAMOFF, &type) < 0){
        perror("cam stream off");
        exit(1);
    }

    close(vd);
    return 0;
}

Overclocking

Proceeding at your own risk.

There are 2 speed grades of i.MX6ULL, one being 528MHz, another being 696MHz. The one I have right now is clocked at 528MHz, also viewable in the system:

root@jessie-dev:~# lscpu                                                        
Architecture:          armv7l                                                   
Byte Order:            Little Endian                                            
CPU(s):                1                                                        
On-line CPU(s) list:   0                                                        
Thread(s) per core:    1                                                        
Core(s) per socket:    1                                                        
Socket(s):             1                                                        
Model name:            ARMv7 Processor rev 5 (v7l)                              
CPU max MHz:           528.0000                                                 
CPU min MHz:           198.0000

Is there anyway to pretend it's 696MHz? Yes, the code is in arch/arm/mach-imx/mach-imx.c:

    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");
        }
    }

Comment out these code to get 696MHz.

To get even higher, the dtsi could be modified:

    operating-points = <
        /* kHz  uV */
        792000  1275000
        696000  1275000
        528000  1175000
        396000  1025000
        198000  950000
    >;
    fsl,soc-operating-points = <
        /* KHz  uV */
        792000  1275000
        696000  1275000
        528000  1175000
        396000  1175000
        198000  1175000
    >;

792MHz is what I added there.

Porting U-Boot

The U-boot I am using doesn't have SPL. I am using the uboot-imx 2015.04.

git clone -b imx_v2015.04_3.14.38_6ul_ga git://git.freescale.com/imx/uboot-imx.git

My board has 256MB DDR3 instead of 512MB, so the dcd needs some modification:

diff --git a/board/freescale/mx6ul_14x14_evk/imximage.cfg b/board/freescale/mx6ul_14x14_evk/imximage.cfg
index 1164fdd..67b8076 100644
--- a/board/freescale/mx6ul_14x14_evk/imximage.cfg
+++ b/board/freescale/mx6ul_14x14_evk/imximage.cfg
@@ -93,24 +93,25 @@ DATA 4 0x021B08C0 0x00922012
 DATA 4 0x021B0858 0x00000F00
 DATA 4 0x021B08b8 0x00000800
 DATA 4 0x021B0004 0x0002002D
-DATA 4 0x021B0008 0x1B333000
-DATA 4 0x021B000C 0x676B54F3
-DATA 4 0x021B0010 0xB68E0A83
+DATA 4 0x021B0008 0x1B333030
+DATA 4 0x021B000C 0x3F4354F3
+DATA 4 0x021B0010 0xB66D0B63
 DATA 4 0x021B0014 0x01FF00DB
 DATA 4 0x021B0018 0x00211740
 DATA 4 0x021B001C 0x00008000
 DATA 4 0x021B002C 0x000026D2
-DATA 4 0x021B0030 0x006B1023
-DATA 4 0x021B0040 0x0000004F
-DATA 4 0x021B0000 0x84180000
+DATA 4 0x021B0030 0x00431023
+DATA 4 0x021B0040 0x00000047
+DATA 4 0x021B0000 0x83180000
+DATA 4 0x021B0890 0x00400A38
 DATA 4 0x021B001C 0x02008032
 DATA 4 0x021B001C 0x00008033
 DATA 4 0x021B001C 0x00048031
 DATA 4 0x021B001C 0x15208030
 DATA 4 0x021B001C 0x04008040
-DATA 4 0x021B0020 0x00000800
+DATA 4 0x021B0020 0x00007800
 DATA 4 0x021B0818 0x00000227
-DATA 4 0x021B0004 0x0002552D
+DATA 4 0x021B0004 0x0002556D
 DATA 4 0x021B0404 0x00011006
 DATA 4 0x021B001C 0x00000000
 #endif
diff --git a/include/configs/mx6ul_14x14_evk.h b/include/configs/mx6ul_14x14_evk.h
index 833f2b0..23b087c 100644
--- a/include/configs/mx6ul_14x14_evk.h
+++ b/include/configs/mx6ul_14x14_evk.h
@@ -138,7 +138,7 @@
 #define CONFIG_POWER_PFUZE300_I2C_ADDR 0x08
 #else
 #define CONFIG_DEFAULT_FDT_FILE "imx6ul-14x14-evk.dtb"
-#define PHYS_SDRAM_SIZE                        SZ_512M
+#define PHYS_SDRAM_SIZE                        SZ_256M
 #define CONFIG_BOOTARGS_CMA_SIZE   ""
 #endif

Compiling:

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make mx6ul_14x14_evk_defconfig
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make -j4
sudo dd if=u-boot.imx of=/dev/sdb bs=1k seek=1 conv=fsync

Creating u-boot.imx manually:

./tools/mkimage -n ./board/freescale/mx6ul_14x14_evk/imximage.cfg.cfgtmp -T imximage -e 0x87800000 -d u-boot.bin u-boot.imx
sudo dd if=u-boot.imx of=/dev/sdb bs=1k seek=1 conv=fsync

Should be equivalent to just using make.