一、Hardware

1.1 整体框图

  1. Top1层

Top1

  1. Gnd2层

GND2

  1. Sig3层

SIG3

  1. Sig4层

SIG4

  1. Power5层

POWER5

  1. Bottom6层

Bottom6

  1. 3D视图

Linux卡片电脑1

Linux卡片电脑2

1.2 阻抗匹配

阻抗匹配1

阻抗匹配2

阻抗匹配3

1.3 手工焊接

手工焊接1

手工焊接2

手工焊接3

1.4 DDR3位宽说明

K4B4G1646B 4Gbit 256M x 16bit内存芯片,A0~A14用做行地址,A0~A9用做列地址,这款芯片同时含有B0~B2用来选择bank。bank address有三个bit,所以单个16bit DDR3内部有8个bank。

  1. 表示行的有A0~A14,共15个bit,说明一个bank中有2^15个行。
  2. 表示列的有A0~A9, 共10个bit,说明一个bank中有2^10个列。
  3. 所以单块16bit DDR3可以寻址2^32^152^10=2^28=256M个地址。
  4. 我们的位宽是16bit,也即访问一个地址,内存认为是访问16bit的数据,所以单个内存颗粒的容量为512M Bytes

我们的H3用两片16bit的K4B4G1646B拼成了一个32bit位宽、总容量为1G Bytes的内存:

  1. 第一片16bit DDR3的BA0, BA1, BA2连接到了CPU的BA0, BA1, BA2。
  2. 第二片16bit DDR3的BA0, BA1, BA2也连接到了CPU的BA0, BA1, BA2。
  3. 第一片16bit DDR3的A0~A14连接到了CPU的A0~A14。
  4. 第二片16bit DDR3的A0~A14连接到了CPU的A0~A14。
  5. 第一片16bit DDR3的D0~D15连接到了CPU的D0~D15。
  6. 第二片16bit DDR3的D0~D15连接到了CPU的D16~D31。

再来看看两个16bit是如何组成一个32bit的。这儿所说的两个16bit组成一个32bit,指的是数据位宽,与地址没有关系。

每一块16bit DDR3中有8个bank,2^15个row,2^10个column。也就是有256M个地址。看前面的连线可知,两块16bit DDR3的BA0BA2和A0\A14其实是并联连接到CPU,也就是说,CPU其实认为只有一块内存,访问的时候按照BA0~BA2和A0~A14给出地址。

CPU发出地址,两块16bit DDR3都收到了该地址。要么将给定地址上2个字节送到数据线上,要么是将数据线上的两个字节写入到指定的地址。

再看数据线的连接,第一片的D0~D15连接到了CPU的D0~D15,第二片的D0~D15连接到了CPU的D16~D31。CPU认为自己访问的是一块32bit的内存,所以CPU每给出一个地址,将访问4个字节的数据,读取/写入。这4字节数据对应到CPU的D0~D31,又分别被连接到两片内存的D0~D15,这样一个32bit就被拆成了两个16bit,也就是两个16bit组成了一个32bit。

CPU可访问的内存地址有256M个,每访问一个地址,将访问4个字节,这样CPU能访问的内存即为256M x 32bit = 1G Bytes

原理图可以看到我们的CPU的A15接到了K4B4G1646B的M7,但实际上M7是NC,我们A15接上去是为了兼容大容量的有A15地址线的ddr3,同时ddr3的ODT1,CS1#,CKE1,ZQ1都不接,我们的K4B4G1646B对应的引脚J1 L1 J9 L9都是NC,方便布线就不接了,其他的ddr3有些用到了这几个引脚,也即他们不是NC,所以省略不接的缺陷就是不能兼容其他的ddr3,我们这里就用K4B4G1646B颗粒,不接没事。

二、系统移植

2.1 安装交叉编译器

①下载交叉编译器arm-cortexa9-linux-gnueabihf-4.9.3.tar.xz,然后解压编译器:

1
2
mkdir -p /opt/YuanPi-Plus/toolchain
tar xf arm-cortexa9-linux-gnueabihf-4.9.3.tar.xz -C /opt/FriendlyARM/toolchain/

②将编译器的路径加入到PATH中,vi ~/.bashrc,在末尾加入以下内容:

1
2
3
export PATH=/opt/FriendlyARM/toolchain/4.9.3/bin:$PATH
export GCC_COLORS=auto
# 执行一下~/.bashrc脚本,让设置立即在当前shell窗口中生效,注意"."后面有个空格:. ~/.bashrc

③这个编译器是64位的,不能在32位的Linux系统上运行,安装完成后,验证是否安装成功:

1
2
arm-linux-gcc -v
# 显示出gcc version 4.9.3 (ctng-1.21.0-229g-FA)即成功了

2.2 编译适配U-boot

①下载U-boot源码,并切换分支:

1
git clone https://github.com/friendlyarm/u-boot.git -b sunxi-v2017.x --depth 1

②安装Python库:

1
apt-get install swig python-dev python3-dev

③使用nanopi_h3_defconfig配置:

1
2
3
4
# 修改DDR3频率为768M,经过测试我的硬件最大支持这个频率
vim configs/nanopi_h3_defconfig
# 将 CONFIG_DRAM_CLK=408 修改为 CONFIG_DRAM_CLK=768
make nanopi_h3_defconfig ARCH=arm CROSS_COMPILE=arm-linux-

④编译U-boot:

1
2
make ARCH=arm CROSS_COMPILE=arm-linux- -j12
# 编译成功后会生成文件u-boot-sunxi-with-spl.bin

⑤更新SD上的U-boot:

1
2
3
4
dd if=u-boot-sunxi-with-spl.bin of=/dev/sdX bs=1024 seek=8
sync && eject /dev/sdX
# /dev/sdX替换为实际的TF卡设备文件名。
# sync命令可以确保数据成功写到TF卡中,eject命令用于弹出TF卡。

⑥SD卡运行系统时,可以先用scp命令拷贝u-boot-sunxi-with-spl.bin到开发板上,然后用dd命令更新SD卡上的U-boot:

1
2
3
4
scp u-boot-sunxi-with-spl.bin root@192.168.31.134:/root/
dd if=/root/u-boot-sunxi-with-spl.bin of=/dev/mmcblk0 bs=1024 seek=8
# root@后面替换成板子上的IP地址,EMMC也可以用此方法
# H3的启动设备的设备节点总是/dev/mmcblk0

⑦成功效果如下:

2.软件适配1

2.3 编译适配Linux内核

①下载Linux内核源码,并切换分支:

1
git clone https://github.com/friendlyarm/linux.git -b sunxi-4.14.y --depth 1

②默认配置先跑起来:

1
2
3
4
5
6
7
8
9
10
11
apt-get install u-boot-tools
touch .scmversion
make sunxi_defconfig ARCH=arm CROSS_COMPILE=arm-linux-
make zImage dtbs ARCH=arm CROSS_COMPILE=arm-linux-
# 编译完成后会在arch/arm/boot/目录下生成zImage,在arch/arm/boot/dts/目录下生成dtb文件

#当完成某任务后,可以保存当前的配置:
make savedefconfig ARCH=arm CROSS_COMPILE=arm-linux-

#目前我已将最新的配置文件保存在:
arch/arm/configs/YuanPi-plus-defconfig

③更新SD上的zImage和dtb文件:假设SD卡的boot分区挂载在/media/SD/boot/

1
2
3
4
5
cp arch/arm/boot/zImage /media/SD/boot/
cp arch/arm/boot/dts/sun8i-*-nanopi-*.dtb /media/SD/boot/
# 也可以用scp命令通过网络更新:
scp arch/arm/boot/zImage root@192.168.31.134:/boot
scp arch/arm/boot/dts/sun8i-*-nanopi-*.dtb root@192.168.31.134:/boot

④bootargs与bootcmd:全志H3使用boot.cmd生成boot.scr来描述配置,boot.cmd:

2.软件适配2

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
# Recompile with: mkimage -C none -A arm -T script -d boot.cmd boot.scr CPU=H3
# OS=friendlycore/ubuntu-oled/ubuntu-wifiap/openwrt/debian/debian-nas...

echo "running boot.scr"
setenv fsck.repair yes
setenv ramdisk rootfs.cpio.gz
setenv kernel zImage

setenv env_addr 0x43000000
setenv kernel_addr 0x46000000
setenv ramdisk_addr 0x47000000
setenv dtb_addr 0x48000000

fatload mmc 0 ${kernel_addr} ${kernel}
fatload mmc 0 ${ramdisk_addr} ${ramdisk}
setenv ramdisk_size ${filesize}

fatload mmc 0 ${dtb_addr} sun8i-h3-YuanPi-plus.dtb
fdt addr ${dtb_addr}

# setup MAC address
fdt set ethernet0 local-mac-address ${mac_node}

# setup boot_device
fdt set mmc0 boot_device <1>

setenv fbcon map:1

setenv overlayfs data=/dev/mmcblk0p3
#setenv hdmi_res drm_kms_helper.edid_firmware=HDMI-A-1:edid/1280x720.bin video=HDMI-A-1:1280x720@60

setenv bootargs console=tty1 console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootfstype=ext4 rw rootwait fsck.repair=${fsck.repair} panic=10 ${extra} fbcon=${fbcon} ${hdmi_res} ${overlayfs}
bootz ${kernel_addr} ${ramdisk_addr}:${ramdisk_size} ${dtb_addr}

boot.cmd –> boot.scr:将boot.scr也放入SD卡的/boot中即可

1
mkimage -C none -A arm -T script -d boot.cmd boot.scr CPU=H3

2.4 文件系统

①文件系统解压到SD卡:

1
sudo tar -xvf rootfs.tar -C /media/qing/rootfs/  

②编译和更新驱动模块:更新SD卡上rootfs的驱动模块:

1
2
make modules ARCH=arm CROSS_COMPILE=arm-linux-
make modules_install INSTALL_MOD_PATH=/media/SD/rootfs/ ARCH=arm CROSS_COMPILE=arm-linux-

make modules_install命令的作用是:

  1. 将编译好的内核模块从内核源代码目录copy到/lib/modules下。也可自己指定ko安装路径,在交叉编译的情况下,需要将ko模块安装到rootfs。也即:INSTALL_MOD_PATH=/media/SD/rootfs/。
  2. 运行modules_install的另一个作用是会运行depmod去生成modules.dep文件,该文件记录了模块之间的依赖关系。这样当modprobe XXX的时候就能够把XXX所依赖的模块一并加载了。

2.5 Wifi模块驱动

  1. 如果是江师兄和稚晖君的版本,他们使用的是RTL8723bu,友善之臂的linux工程没有RTL8723bu驱动,我目前使用的是稚晖君基于友善之臂添加后的linux工程,RTL8723bu驱动代码放在了:drivers/net/wireless/realtek/rtl8723bu,并且稚晖君的配置文件:linux_card_defconfig已经使能了该驱动,编译linux工程会将RTL8723bu编译成模块。这份驱动代码会依赖其他的一些驱动,所以按照上一节操作,将驱动导出到rootfs,然后:

    1
    2
    wifi驱动的目录:/lib/modules/4.14.111/kernel/drivers/net/wireless/realtek/rtl8723bu
    装rtl8723bu驱动的时候先depmod一下,再modprobe,下次开机会自动装载。
  2. 如果是友善之臂的h3 M1 Plus,或者是我做的版本,使用的是AP6212,友善之臂已经适配了ap6212;YuanPi-plus-defconfig配置是我基于友善之臂的sunxi_defconfig修改的,直接使用我修改后的配置编译出的zImage启动即可。当然,也有可能ap6212驱动代码不在linux工程中,是友善之臂的文件系统里面有适配好的ko文件,然后启动时脚本自动装载了,具体代码或者ko文件是啥我没去找过,能用就行。

  3. 不管是稚晖君还是我的设备树,都是友善之臂的复制过来修改的,所以设备树没有区别,区别只在配置文件和有无RTL8723bu驱动代码。RTL8723bu是usb wifi,ap6212是sdio接口,他们可以共存。

  4. 连接wifi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    开启Wi-Fi:
    nmcli r wifi on

    扫描附近的Wi-Fi:
    nmcli dev wifi

    连接到特定的Wi-Fi:
    nmcli dev wifi connect "SSID" password "PASSWORD" ifname wlan0

    重启网卡设备:
    sudo ifconfig wlan0 down
    sudo ifconfig wlan0 up

    连接成功后,下次开机,WiFi 也会自动连接。

    如果你的USB WiFi无法正常工作, 大概率是因为文件系统里缺少了对应的USB WiFi固件。
    可以通过下列命令安装所有的USB WiFi固件:
    apt-get install linux-firmware

2.6 nfs网络文件系统

  1. ubuntu先安装nfs服务:apt-get install nfs-kernel-server rpcbind
  2. 配置相关文件夹为nfs文件夹:vi /etc/exports
  3. 在最后一行加上文件夹路径:/home/qing/work/nfs/rootfs_friendlycore-focal_4.14 *(rw,sync,no_root_squash)
  4. 然后重启ubuntu服务:/etc/init.d/nfs-kernel-server restart
  5. 被挂载的文件夹最好 :chmod 777 xxx/
  6. 在开发板里面也要安装nfs服务:apt-get install nfs-kernel-server rpcbind
  7. 重启开发板的nfs服务:/etc/init.d/nfs-kernel-server restart
  8. 挂载:mount -t nfs ubuntu-IP:/home/qing/work/nfs/rootfs_friendlycore-focal_4.14/ /mnt/ -o nolock

参考OrangePi,以太网要修改sun8i-h3-YuanPi-plus.dts:因为友善M1-Plus用了RTL8211E,我们是直连的。

2.5以太网

三、软件适配

3.1 st7789v彩屏(SPI)

Frame Buffer设备驱动在另一篇文章已经讲解,这里直接在内核自带的fbtft框架上修改。

3.1.1 修改设备树

①首先复制一份sun8i-h3-nanopi-m1-plus.dts重命名为sun8i-h3-YuanPi-plus.dts,并修改设备树目录下的Makefile加上我们的设备树。最后在sun8i-h3-YuanPi-plus.dts中添加:

3.1.1修改设备树1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&spi0 {
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
cs-gpios = <&pio 2 3 GPIO_ACTIVE_HIGH>;

st7789vw: st7789vw@0{
compatible ="sitronix,st7789vw";
reg = <0>;
status = "okay";
spi-max-frequency = <50000000>;
buswidth = <8>;
rotate = <0>;
fps = <60>;
rgb;
spi-cpol;
spi-cpha;
dc-gpios = <&pio 0 0 GPIO_ACTIVE_HIGH>; /* PA0 */
reset-gpios = <&pio 0 1 GPIO_ACTIVE_HIGH>; /* PA1 */
debug = <0x00>;
};
};

②修改sun8i-h3-nanopi.dtsi,将spi0结点下的设备都disabled:

3.1.1修改设备树2

3.1.2 修改kernel

①make menuconfig使能fbtft框架,并勾选fb_st7789vw.c:

3.1.2修改kernel1

1
2
3
4
5
6
7
Device Drivers  --->
[*] Staging drivers --->
<*> Support for small TFT LCD display modules --->
<*> Support sysfs for small TFT LCD display modules
<*> FB driver for the ST7789VW LCD Controller
<*> Generic FB driver for TFT LCD displays
<*> Module to for adding FBTFT devices

②修改drivers/staging/fbtft/fb_st7789vw.c:

我们的屏幕不需要偏移,将偏移去除:

3.1.2修改kernel2

修改分辨率:

3.1.2修改kernel3

最后修改初始化代码:将init_display(struct fbtft_par *par)中初始化代码换成当下屏幕的即可。

3.1.3修改bootargs

修改boot.cmd –> boot.scr,让屏幕作为console:

3.1.3修改bootargs1

1
2
fatload mmc 0 ${dtb_addr} sun8i-h3-YuanPi-plus.dtb
setenv bootargs console=tty1 console=ttyS0,115200 earlyprintk...

3.1.4 编译烧录找bug

编译出zImage、sun8i-h3-YuanPi-plus.dtb、boot.scr,重新烧录后发现屏幕不起作用,但是在启动阶段发现有微弱的闪动,开启找bug之旅。。。

首先怀疑屏幕坏了,毕竟买了挺久了,用之前Frame Buffer设备驱动的程序在RK3399上测试,屏幕正常。

去掉fbtft框架,用以前自己写的Frame Buffer设备驱动的程序测试,还是只有微弱的闪光,那么问题应该是出在spi驱动能力上,给SCK时钟线接一个3.3v,2.2k上拉,再次实验,成功了。重新加上fbtft框架编译烧录即可。注:这也不是板子硬件bug,将来屏幕转接板上拉即可,同时要注意上拉的电流不能太大,不然时间久了会烧屏。

经过多次测试,spi频率最大能到72M;此时SCK接的是3.3v,10k上拉,频率为50M,最终效果如下:

3.1.4编译烧录找bug1

最终发现不用外接上拉电阻,直接将spi0配置成上拉模式即可,并且spi频率也可以设置为72M,96M不行,屏幕大。

3.1.4编译烧录找bug2

3.1.5 音视频播放

①安装mplayer:

1
apt-get install mplayer

②安装alsa包:

1
2
3
4
apt-get update
apt-get install libasound2
apt-get install alsa-base
apt-get install alsa-utils

③播放视频:

1
mplayer -ao alsa -vo fbdev:/dev/fb_st7789vw -x 240 -y 240 -zoom 被动-伍佰.mp4

④alsamixer可以调节音量,声音太大到了高潮部分功率上不去会卡死。

⑤爱伍佰,做他一半就好……

3.1.5音视频播放1

3.2 GT911电容触摸(IIC)

3.2.1 修改设备树

GT911使用的是IIC接口,可以设置中断模式,当有触摸动作则会触发中断,在中断中读取寄存器信息,通过input上报坐标,因为板子上的i2c0在硬件上已经上拉,所以设备树不用上拉,修改设备树如下:sun8i-h3-YuanPi-plus.dts

3.2.1修改设备树1

3.2.2 编写驱动

电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:

  1. IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
  2. 通过中断引脚(INT)向 内核上报触摸信息,因此需要用到中断驱动框架。坐标的上报在中断服务函数中完成。
  3. 触摸屏的坐标信息、屏幕按下和抬起信息都属于input 子系统,因此向内核上报触摸屏坐标信息就得使用 input 子系统。只是我们得按照 linux 内核规定的规则来上报坐标信息。

以上内容在前面的博文已有介绍,不再赘述,在实际驱动GT911需要注意如下几点:

  1. 与其他的IIC设备不同,GT911的器件地址需要根据初始化时序来确定,地址确定后才能用i2cdetect检测出地址。
  2. 当输出中断后请在一个中断周期内读走坐标并将buffer status(0x814E)写为0,若未在中断内读走坐标,下次IC即使检测到坐标更新会再输出一个中断脉冲,但不更新坐标,如果一直没有读取坐标则会一直打脉冲,导致中断一直被触发。
  3. 我们的屏幕进行了旋转,所以读取出来的坐标也需要在软件上进行调整,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    我们的屏幕旋转了90度,对应如下:
    原先 --> 旋转后
    (0,0) --> (0,240)
    (240,0) --> (0,0)
    (0,320) --> (320,240)
    (240,320) --> (320,0)
    所以对应的调整关系是:
    (X,Y) --> (Y,|X-240|)

完整代码如下所示,相应注释已给出:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <asm/unaligned.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

#define GT_CTRL_REG 0X8040 /* GT911控制寄存器 */
#define GT_9xx_CFGS_REG 0X8047 /* GT9147配置起始地址寄存器 */
#define GT_1xx_CFGS_REG 0X8050 /* GT1151配置起始地址寄存器 */
#define GT_PID_REG 0X8140 /* GT911产品ID寄存器 */
#define GT_GSTID_REG 0X814E /* GT911当前检测到的触摸情况 */
#define GT_TP1_REG 0X814F /* 第一个触摸点数据地址 */
#define MAX_SUPPORT_POINTS 5 /* 最多5点电容触摸 */

const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */

struct gt911_dev {
struct gpio_desc *reset_pin;
struct gpio_desc *irq_pin;
int irqnum; /* 中断号 */
int irqtype; /* 中断类型 */
int max_x; /* 最大横坐标 */
int max_y; /* 最大纵坐标 */
void *private_data; /* 私有数据 */
struct input_dev *input; /* input结构体 */
struct i2c_client *client; /* I2C客户端 */

};
struct gt911_dev gt911;

/*
* @description : 复位GT911
* @param - client : 要操作的i2c
* @param - multidev: 自定义的multitouch设备
* @return : 0,成功;其他负值,失败
*/
static int gt911_reset(struct i2c_client *client, struct gt911_dev *dev)
{
printk("reset_pin and int_pin = 0 hold 10ms!\n");
gpiod_set_value(dev->reset_pin, 0); //设低电平
gpiod_set_value(dev->irq_pin, 0); //设低电平
mdelay(10);

printk("reset_pin = 1 hold 10ms!\n");
gpiod_set_value(dev->reset_pin, 1); //设高电平
mdelay(10);

printk("set int_pin as input!\n");
gpiod_direction_input(dev->irq_pin);

printk("now gt911's addr is 0xBA/0xBB, and i2c addr is 0x5d\n");

return 0;
}

/*
* @description : 向GT911多个寄存器写入数据,寄存器的地址指针会在写操作后自动加 1,所以当需要对连续地址的寄存器进行写操作时,可以在一次写操作中连续写入。
* @param - dev: GT911设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 gt911_write_regs(struct gt911_dev *dev, u16 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;

b[0] = reg >> 8; /* 寄存器首地址低8位 */
b[1] = reg & 0XFF; /* 寄存器首地址高8位 */
memcpy(&b[2],buf,len); /* 将要写入的数据拷贝到数组b里面 */

msg.addr = client->addr; /* gt911地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 2; /* 要写入的数据长度,总长度要加上两个寄存器 */

return i2c_transfer(client->adapter, &msg, 1);
}

/*
* @description : 从GT911读取多个寄存器数据
* @param - dev: GT911设备
* @param - reg: 要读取的寄存器首地址
* @param - buf: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int gt911_read_regs(struct gt911_dev *dev, u16 reg, u8 *buf, int len)
{
int ret;
u8 regdata[2];
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;

/* GT911寄存器长度为2个字节 */
regdata[0] = reg >> 8;
regdata[1] = reg & 0xFF;

/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* GT911的i2c地址 */
msg[0].flags = !I2C_M_RD; /* 标记为发送数据 */
msg[0].buf = &regdata[0]; /* 读取的首地址 */
msg[0].len = 2; /* reg长度*/

/* msg[1]读取数据 */
msg[1].addr = client->addr; /* GT911的i2c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = buf; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/

ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}

/*
* @description : GT911读取固件
* @param - client : 要操作的i2c
* @param - multidev: 自定义的multitouch设备
* @return : 0,成功;其他负值,失败
*/
static int gt911_read_firmware(struct i2c_client *client, struct gt911_dev *dev)
{
int ret = 0, version = 0;
u16 id = 0;
u8 data[7]={0};
char id_str[5];
ret = gt911_read_regs(dev, GT_PID_REG, data, 6);
if (ret) {
dev_err(&client->dev, "Unable to read PID.\n");
return ret;
}
memcpy(id_str, data, 4);
id_str[4] = 0;
if (kstrtou16(id_str, 10, &id))
{
id = 0x1001;
}
version = get_unaligned_le16(&data[4]);
dev_info(&client->dev, "ID %d, version: %04x\n", id, version);
switch (id) { /* 由于不同的芯片配置寄存器地址不一样需要判断一下 */
case 911:
ret = gt911_read_regs(dev, GT_9xx_CFGS_REG, data, 7);
break;
default:
return -1;
break;
}
if (ret) {
dev_err(&client->dev, "Unable to read Firmware.\n");
return ret;
}
dev->max_x = (data[2] << 8) + data[1];
dev->max_y = (data[4] << 8) + data[3];
dev->irqtype = data[6] & 0x3;
printk("X_MAX: %d, Y_MAX: %d, Touch Number: %d, TRIGGER: 0x%02x\n", dev->max_x, dev->max_y, data[5], dev->irqtype);

return 0;
}

/*
我们的屏幕旋转了90度,对应如下:
原先 --> 旋转后
(0,0) --> (0,240)
(240,0) --> (0,0)
(0,320) --> (320,240)
(240,320) --> (320,0)
所以对应的调整关系是:
(X,Y) --> (Y,|X-240|)
*/
static void adjust_x_y(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
if(tmp >= gt911.max_x)
*y = tmp - gt911.max_x;
else
*y = gt911.max_x - tmp;

}

static irqreturn_t gt911_irq_handler(int irq, void *dev_id)
{
int touch_num = 0;
int input_x, input_y;
int id = 0;
int ret = 0;
u8 data;
u8 touch_data[5];
struct gt911_dev *dev = dev_id;

ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1);
if (data == 0x00) { /* 没有触摸数据,直接返回 */
goto fail;
} else { /* 统计触摸点数据 */
touch_num = data & 0x0f;
}

//这里暂时使用单点触摸
if(touch_num) { /* 单点触摸按下 */
gt911_read_regs(dev, GT_TP1_REG, touch_data, 5);
id = touch_data[0] & 0x0F;
if(id == 0) {
input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);

//旋转了屏幕,从软件上调整坐标
adjust_x_y(&input_x, &input_y);

printk("X: %d, Y: %d\n", input_x, input_y);

//上报坐标
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);
}
} else if(touch_num == 0){ /* 单点触摸释放 */
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);
}

input_mt_report_pointer_emulation(dev->input, true);
input_sync(dev->input);

data = 0x00; /* 向0X814E寄存器写0 */
gt911_write_regs(dev, GT_GSTID_REG, &data, 1);

fail:
return IRQ_HANDLED;
}

/*
* @description : GT911中断初始化
* @param - client : 要操作的i2c
* @param - multidev: 自定义的multitouch设备
* @return : 0,成功;其他负值,失败
*/
static int gt911_ts_irq(struct i2c_client *client, struct gt911_dev *dev)
{
int ret = 0;
/* 申请中断,client->irq就是IO中断, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
gt911_irq_handler, irq_table[dev->irqtype] | IRQF_ONESHOT,
client->name, &gt911);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}

return 0;
}

static int GT911_probe(struct i2c_client *client, const struct i2c_device_id *id)
{

u8 data, ret;
gt911.client = client;

printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

/* 1,获取设备树中的中断和复位引脚 */
gt911.irq_pin = devm_gpiod_get(&client->dev,"interrupt",GPIOD_OUT_LOW);
gt911.reset_pin = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_LOW);

/* 2,复位GT911 */
gt911_reset(client, &gt911);

/* 3,初始化GT911 */
data = 0x02;
gt911_write_regs(&gt911, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x0;
gt911_write_regs(&gt911, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);

/* 4,初始化GT911,读取固件 */
ret = gt911_read_firmware(client, &gt911);
if(ret != 0) {
printk("Fail !!! check !!\r\n");
goto fail;
}

/* 5,input设备注册 */
gt911.input = devm_input_allocate_device(&client->dev);
if (!gt911.input) {
ret = -ENOMEM;
goto fail;
}
gt911.input->name = client->name;
gt911.input->id.bustype = BUS_I2C;
gt911.input->dev.parent = &client->dev;

__set_bit(EV_KEY, gt911.input->evbit);
__set_bit(EV_ABS, gt911.input->evbit);
__set_bit(BTN_TOUCH, gt911.input->keybit);

input_set_abs_params(gt911.input, ABS_X, 0, gt911.max_x, 0, 0);
input_set_abs_params(gt911.input, ABS_Y, 0, gt911.max_y, 0, 0);
input_set_abs_params(gt911.input, ABS_MT_POSITION_X,0, gt911.max_x, 0, 0);
input_set_abs_params(gt911.input, ABS_MT_POSITION_Y,0, gt911.max_y, 0, 0);
ret = input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}

ret = input_register_device(gt911.input);
if (ret)
goto fail;

/* 6,最后初始化中断 */
ret = gt911_ts_irq(client, &gt911);
if(ret < 0) {
goto fail;
}

return 0;

fail:
return ret;
}

static const struct of_device_id of_match_ids_GT911[] = {
{ .compatible = "my_gt911", .data = NULL },
{ /* END OF LIST */ },
};

static const struct i2c_device_id GT911_ids[] = {
{ "gt911", (kernel_ulong_t)NULL },
{ /* END OF LIST */ }
};

static int GT911_remove(struct i2c_client *client)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

return 0;
}

static struct i2c_driver i2c_GT911_driver = {
.driver = {
.name = "gt911",
.of_match_table = of_match_ids_GT911,
},
.probe = GT911_probe,
.remove = GT911_remove,
.id_table = GT911_ids,
};

static int __init i2c_driver_GT911_init(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
return i2c_add_driver(&i2c_GT911_driver);
}
module_init(i2c_driver_GT911_init);

static void __exit i2c_driver_GT911_exit(void)
{
i2c_del_driver(&i2c_GT911_driver);
}
module_exit(i2c_driver_GT911_exit);

MODULE_AUTHOR("qingqing");
MODULE_LICENSE("GPL");

3.2.3 移植tslib

首先装载gt911驱动,自己的驱动成功效果如下:

3.3.1移植tslib1

tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行校准。虽然电容屏不需要校准,但主要的是 tslib 提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。最新版本的 tslib 已经支持了多点电容触摸屏,因此可以通过 tslib 来直观的测试多点电容触摸屏驱动,这个要比观看 eventX 原始数据方便的多。

①获取tslib源码。git 地址为:https://github.com/kergoth/tslib ,目前最新的版本是1.21。

②修改 tslib 源码所属用户,修改解压得到的 tslib-1.21 目录所属用户为当前用户,这一步一定要做!否则在稍后的编译中会遇到各种问题。我当前 ubuntu 的登录用户名为“qing”,修改命令如下:

1
sudo chown qing:qing tslib-1.21 -R

③ubuntu工具安装:

1
2
3
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool

④编译tslib:编译完成后在安装目录中,bin 目录下是可执行文件,包括 tslib 的测试工具。etc 目录下是 tslib 的配置文件,lib目录下是相关的库文件,将所有文件拷贝到开发板中。

1
2
3
./autogen.sh
./configure --host=arm-linux --prefix="安装目录"
make && make install

⑤配置tslib

  1. 打开etc/ts.conf 文件,找到module_raw input,如果这句前面有“#”的话就删除掉“#”。
  2. 在开发板终端输入:
    1
    2
    3
    4
    5
    6
    7
    # 注意,下面路径是根目录,如果没有这些文件,运行时会生成
    export TSLIB_TSDEVICE=/dev/input/event2
    export TSLIB_CALIBFILE=/etc/pointercal
    export TSLIB_CONFFILE=/etc/ts.conf
    export TSLIB_PLUGINDIR=/lib/ts
    export TSLIB_CONSOLEDEVICE=none
    export TSLIB_FBDEVICE=/dev/fb1

⑥ tslib 测试:使用ts_test_mt来测试触摸屏工作是否正常,测试结果如下,一切正常:

3.3.1移植tslib2

至此,从头写GT911触摸驱动就圆满完成了!