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
- Any value resistor
- Tie to ground
Pull up via:
- Tie to VDD_SNVS_IN
- 10K or 4.7K ohm resistor to VDD_SNVS_IN
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.