使用 QEMU模拟的硬件,它的硬件资源可以随意扩展。 在 IMX6ULL QEMU 虚拟开发板上,我们为它设计了 4个 LED。
从上图可知,这 4个 LED 用到了 GPIO5_3、GPIO1_3、GPIO1_5、GPIO1_6 共 4个引脚。 在芯片手册里,这些引脚的名字是:GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06。可以根据名字搜到对应的寄存器。 当这些引脚输出低电平时,对应的 LED被点亮;输出高电平时,LED熄灭。
步骤 1:
使能 GPIO1、GPIO5
设置 b[31:30]、b[27:26]就可以使能 GPIO5、GPIO1,设置为什么值呢?
注意:在 imx6ullrm.pdf中,CCM_CCGR1的 b[31:30]是保留位;我以前写程序时错用了 imx6ul(不是imx6ull)的手册,导致程序中额外操作了这些保留位。不去设置 b[31:30],GPIO5也是默认使能的。 看下图,设置为 0b11:
① 00:该 GPIO模块全程被关闭
② 01:该 GPIO模块在 CPU run mode情况下是使能的;在 WAIT或 STOP模式下,关闭
③ 10:保留
④ 11:该 GPIO模块全程使能
步骤 2:
设置 GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06为 GPIO模式
① 对于 GPIO5_IO03,设置如下寄存器:
② 对于 GPIO1_IO03,设置如下寄存器:
③ 对于 GPIO1_IO05,设置如下寄存器:
④ 对于 GPIO1_IO06,设置如下寄存器:
步骤 3:
设置 GPIO5_IO03、GPIO1_IO03、GPIO1_IO05、GPIO1_IO06为输出引脚,设置其输出电平 寄存器地址为:
设置方向寄存器,把引脚设置为输出引脚:
设置数据寄存器,设置引脚的输出电平:
使用 GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
05_嵌入式 Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\xxxxxx_imx6ull-qemu_src_bin
硬件相关的文件是 board_xxxxxx_imx6ull-qemu.c,其他文件跟 LED框架驱动程序完全一样。
涉及的寄存器挺多,一个一个去执行 ioremap效率太低。 先定义结构体,然后对结构体指针进行 ioremap,这些结构体在。 对于 IOMUXC,可以如下定义:
struct iomux {
volatile unsigned int unnames[23];
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO07;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08;
volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO09;
};
struct iomux *iomux = ioremap(0x20e0000, sizeof(struct iomux));
对于 GPIO,可以如下定义:
struct imx6ull_gpio {
volatile unsigned int dr;
volatile unsigned int gdir;
volatile unsigned int psr;
volatile unsigned int icr1;
volatile unsigned int icr2;
volatile unsigned int imr;
volatile unsigned int isr;
volatile unsigned int edge_sel;
};
struct imx6ull_gpio *gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
struct imx6ull_gpio *gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
开始详细分析 board_xxxxxx_imx6ull-qemu.c。 它首先构造了一个 led_operations结构体,用来表示 LED的硬件操作:
176 static struct led_operations board_demo_led_opr = {
177 .num = 4,
178 .init = board_demo_led_init,
179 .ctl = board_demo_led_ctl,
180 };
181
led_operations结构体中有 init函数指针,它指向 board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为 GPIO模式、设置为输出引脚。
值得关注的是第 61~66行,对于寄存器要先使用 ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
57 static int board_demo_led_init (int which) /* 初始化 LED, which-哪个 LED */
58 {
59 if (!CCM_CCGR1)
60 {
61 CCM_CCGR1 = ioremap(0x20C406C, 4);
62 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
63
64 iomux = ioremap(0x20e0000, sizeof(struct iomux));
65 gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
66 gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
67 }
68
69 if (which == 0)
70 {
71 /* 1. enable GPIO5
72 * CG15, b[31:30] = 0b11
73 */
74 *CCM_CCGR1 |= (3<<30);
75
76 /* 2. set GPIO5_IO03 as GPIO
77 * MUX_MODE, b[3:0] = 0b101
78 */
79 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = 5;
80
81 /* 3. set GPIO5_IO03 as output
82 * GPIO5 GDIR, b[3] = 0b1
83 */
84 gpio5->gdir |= (1<<3);
85 }
86 else if(which == 1)
87 {
88 /* 1. enable GPIO1
89 * CG13, b[27:26] = 0b11
90 */
91 *CCM_CCGR1 |= (3<<26);
92
93 /* 2. set GPIO1_IO03 as GPIO
94 * MUX_MODE, b[3:0] = 0b101
95 */
96 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 5;
97
98 /* 3. set GPIO1_IO03 as output
99 * GPIO1 GDIR, b[3] = 0b1
100 */
101 gpio1->gdir |= (1<<3);
102 }
103 else if(which == 2)
104 {
105 /* 1. enable GPIO1
106 * CG13, b[27:26] = 0b11
107 */
108 *CCM_CCGR1 |= (3<<26);
109
110 /* 2. set GPIO1_IO05 as GPIO
111 * MUX_MODE, b[3:0] = 0b101
112 */
113 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05 = 5;
114
115 /* 3. set GPIO1_IO05 as output
116 * GPIO1 GDIR, b[5] = 0b1
117 */
118 gpio1->gdir |= (1<<5);
119 }
120 else if(which == 3)
121 {
122 /* 1. enable GPIO1
123 * CG13, b[27:26] = 0b11
124 */
125 *CCM_CCGR1 |= (3<<26);
126
127 /* 2. set GPIO1_IO06 as GPIO
128 * MUX_MODE, b[3:0] = 0b101
129 */
130 iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06 = 5;
131
132 /* 3. set GPIO1_IO06 as output
133 * GPIO1 GDIR, b[6] = 0b1
134 */
135 gpio1->gdir |= (1<<6);
136 }
137
138 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
139 return 0;
140 }
141
led_operations结构体中有 ctl函数指针,它指向 board_demo_led_ctl函数,在里面将会根据参数设置 LED引脚的输出电平:
142 static int board_demo_led_ctl (int which, char status) /* 控制 LED, which-哪个 LED, status:1-亮,0-灭 */
143 {
144 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
145 if (which == 0)
146 {
147 if (status) /* on : output 0 */
148 gpio5->dr &= ~(1<<3);
149 else /* on : output 1 */
150 gpio5->dr |= (1<<3);
151 }
152 else if (which == 1)
153 {
154 if (status) /* on : output 0 */
155 gpio1->dr &= ~(1<<3);
156 else /* on : output 1 */
157 gpio1->dr |= (1<<3);
158 }
159 else if (which == 2)
160 {
161 if (status) /* on : output 0 */
162 gpio1->dr &= ~(1<<5);
163 else /* on : output 1 */
164 gpio1->dr |= (1<<5);
165 }
166 else if (which == 3)
167 {
168 if (status) /* on : output 0 */
169 gpio1->dr &= ~(1<<6);
170 else /* on : output 1 */
171 gpio1->dr |= (1<<6);
172 }
173 return 0;
174 }
175
下面的 get_board_led_opr函数供上层调用,给上层提供 led_operations结构体:
182 struct led_operations *get_board_led_opr(void)
183 {
184 return &board_demo_led_opr;
185 }
186
先启动 IMX6ULL QEMU模拟器,挂载 NFS文件系统。
运行 QEMU时, QEMU内部为主机虚拟出一个网卡, IP为 10.0.2.2, IMX6ULL有一个网卡, IP为 10.0.2.15, 它连接到主机的虚拟网卡。 这样 IMX6ULL就可以通过 10.0.2.2去访问 Ubuntu了。
然后执行以下命令安装驱动、执行测试程序:
# insmod xxxxxx_led.ko
# ./ledtest /dev/xxxxxx_led0 on
# ./ledtest /dev/xxxxxx_led0 off
a. 在驱动里有 ioremap,什么时候执行 iounmap?请完善程序
b. 驱动程序中有太多的 if判断,请优化程序减少 if的使用