U-Boot和linux内核移植

使用厂商提供的uboot 但是有些外设要自己移植。

uboot启动顺序

首先第一步执行入口函数 入口函数跳转到汇编函数start.s 这个函数会跳转到start_code 处 它的主要作用有几个

  1. 创建c语言的运行环境
  2. 关闭中断和mmc
  3. 蒋处理模式升级成特权模式
    然后运行第一个c语言 它的作用就是 进行重定向 让uboot放在内存中运行 并且启动网口 内存 gpio uart等外设 然后进行循环的命令处理
    最重要的 是把内和信息加载到对应的位置 然后等待内核启动
    主要是加上两个方面的驱动 uboot就相当于是一个裸机驱动

LCD 驱动修改

在uboot里找到mx6ull_alientek_emmc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 struct display_info_t const displays[] = {{
.bus = MX6UL_LCDIF1_BASE_ADDR,
.addr = 0,
.pixfmt = 24,
.detect = NULL,
.enable = do_enable_parallel_lcd,
.mode = {
.name = "TFT7016",
.xres = 1024,
.yres = 600,
.pixclock = 19531,
.left_margin = 140, //HBPD
.right_margin = 160, //HFPD
.lower_margin = 12, //VFBD
.hsync_len = 20, //HSPW
.vsync_len = 3, //VSPW
.sync = 0,
.vmode = FB_VMODE_NONINTERLACED
} } };

就是lcd驱动的一些匹配的格式 这些鞋就做好了lcd的驱动 具体lcd是什么在后面讲

网络驱动修改

首先结构图
pCQrzng.png
先把开发商的板子网络驱动卸掉 网络驱动主要在

  1. mx6ull_alientek_emmc.h 这里形成的 CONFIG_FEC_ENET_DEV 处 把这里的地址改成我们上面的phy地址0x0
  2. 还有mx6ull_alientek_emmc.c 会有一个 班上驱动的init 这个删除 还有iox——pad 这个也要清除
  3. board_init 函数 调用2中的两个函数 现在可以填写我们的驱动函数了
    首先修改2 因为在mx6ull_alientek_emmc.c函数里本来就有fec1_pads[] 我们自己的结构体 只是我们要对其进行修改就是在没一行最后添加上复位的接口和盘配置 MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL), 然后再初始化这个结构体2 setup_iomux_fec函数中进行这个结构体的初始化 函数如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void setup_iomux_fec(int fec_id)
{
if (fec_id == 0)
{

imx_iomux_v3_setup_multiple_pads(fec1_pads,
ARRAY_SIZE(fec1_pads));

gpio_direction_output(ENET1_RESET, 1);
gpio_set_value(ENET1_RESET, 0);
mdelay(20);
gpio_set_value(ENET1_RESET, 1);
}
else
{
imx_iomux_v3_setup_multiple_pads(fec2_pads,
ARRAY_SIZE(fec2_pads));
gpio_direction_output(ENET2_RESET, 1);
gpio_set_value(ENET2_RESET, 0);
mdelay(20);
gpio_set_value(ENET2_RESET, 1);
}
}

linux内核启动

启动流程

  1. 初始化:一旦开始加载内核之后 系统就会读取设备树文件 开始建立内存映射表 初始化进程 文件系统 等等
  2. 核初始化完毕后,会启动第一个用户空间进程 init(或 systemd),init 是 Linux 系统中的第一个进程,它负责初始化系统环境并启动其他进程
  3. init 进程启动,它会初始化用户空间,包括启动各种系统服务、运行 shell 程序等等。

linux内核就涉及到了设备树了 所以不能和刚才主机

修改驱动

EMMC 驱动

Linux 内核驱动里面 EMMC 默认是 4 线模式的,4 线模式肯定没有 8 线模式的速度快,所以我们将 EMMC 的驱动修改为 8 线模式
直接修改设备树

1
2
3
4
5
6
7
8
9
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};

网络驱动

首先修改设备树
首先添加网络引脚抚慰信息 iomuxc_snvs 节点 添加

1
2
3
4
5
6
7
8
9
10
11
12
13
pinctrl_enet1_reset: enet1resetgrp {
fsl,pins = <
/* used for enet1 reset */
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
>;
};

/*enet2 reset zuozhongkai*/
pinctrl_enet2_reset: enet2resetgrp {
fsl,pins = <
/* used for enet2 reset */
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0 7 >;
};

修改时钟配置:

1
2
3
4
5
6
7
8
9
10
11
12
pinctrl_enet1: enet1grp {
fsl,pins = <
MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
>;
}

phy设备地址在fec1 后面

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

reg = <2> 就是地址我们修改成phy的地址 然后就可以使用了
然后把fec1 的内容修改为

1
2
3
4
5
6
7
8
 pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1
&pinctrl_enet1_reset>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;

phy-mode = “rmii”;:该行代码指定以太网PHY的模式。RMII(Reduced Media Independent Interface)是一种简化版的MII(Media Independent Interface),通常用于以太网系统中。

phy-handle = <&ethphy0>;:该行代码指定以太网PHY的设备节点。&ethphy0引用指向另一个描述PHY的设备树节点。

phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;:该行代码指定用于重置以太网PHY的GPIO引脚和极性。在这种情况下,使用GPIO 5,引脚 7,并且极性是低电平触发,这意味着当引脚为低电平时,重置信号处于活动状态。

phy-reset-duration = <200>;:该行代码指定PHY复位持续时间(以毫秒为单位)。在这种情况下,它设置为200毫秒。
inctrl_enet1
&pinctrl_enet1_reset我们修改为

1
2
3
4
5
6
7
8
9
10
11
12
  pinctrl_enet1: enet1grp {
fsl,pins = <
MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
>;
};

根文件构建

这个步骤很简单 网上查查都有资料 就不罗嗦了

I2C &开发AP3216C

原理:

是很常见的一种总线协议,有两个线控制足迹和从机进行数据通信,一条是一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),

  1. 空闲时都处于高电平的状态开始时候
  2. 开始位:scl高电平sda下降
  3. 停止位SCL 位高电平的时候,SDA出现上升沿就表示为停止位
  4. 数据传输 都平滑
  5. sDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机告诉主机它接收到数据了

写时序:

  1. 发送开始信号。
  2. 发送从机地址 ,七位是设备地址,剩一位就是读写位 0是读,一是写
  3. 发送确认信号
  4. 发送重新开始信号
  5. 发送寄存器地址
  6. 发送应答信号
  7. 发送要写入的寄存器地址
  8. 停止信息

读时序:读时序比较难的是首先要写入寄存器就是说

  1. 前面七步都一样
  2. 再继续发送从地址 不过读写位改成写

常见的寄存器

  1. I2Cx_IADR 从设备寄存器
  2. I2Cx_IFDR 频率的寄存器
  3. I2Cx_I2CR 控制寄存器
  4. I2Cx_I2DR 数据寄存器 还可以存寄存器

裸机代码

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

#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "stdio.h"

/*
* @description : 初始化I2C,波特率100KHZ
* @param - base : 要初始化的IIC设置
* @return : 无
*/
void i2c_init(I2C_Type *base)
{
/* 1、配置I2C */
base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C */

/* 设置波特率为100K
* I2C的时钟源来源于IPG_CLK_ROOT=66Mhz
* IC2 时钟 = PERCLK_ROOT/dividison(IFDR寄存器)
* 设置寄存器IFDR,IFDR寄存器参考IMX6UL参考手册P1260页,表29-3,
* 根据表29-3里面的值,挑选出一个还是的分频数,比如本例程我们
* 设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.
* 在表29-3里面查找,没有660这个值,但是有640,因此就用640,
* 即寄存器IFDR的IC位设置为0X15
*/
base->IFDR = 0X15 << 0;

/*
* 设置寄存器I2CR,开启I2C
* bit[7] : 1 使能I2C,I2CR寄存器其他位其作用之前,此位必须最先置1
*/
base->I2CR |= (1<<7);
}

/*
* @description : 发送重新开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction : 方向
* @return : 0 正常 其他值 出错
*/
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
/* I2C忙并且工作在从模式,跳出 */
if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))
return 1;

/*
* 设置寄存器I2CR
* bit[4]: 1 发送
* bit[2]: 1 产生重新开始信号
*/
base->I2CR |= (1 << 4) | (1 << 2);

/*
* 设置寄存器I2DR
* bit[7:0] : 要发送的数据,这里写入从设备地址
* 参考资料:IMX6UL参考手册P1249
*/
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);

return 0;
}

/*
* @description : 发送开始信号
* @param - base : 要使用的IIC
* @param - addrss : 设备地址
* @param - direction : 方向
* @return : 0 正常 其他值 出错
*/
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction)
{
if(base->I2SR & (1 << 5)) /* I2C忙 */
return 1;

/*
* 设置寄存器I2CR
* bit[5]: 1 主模式
* bit[4]: 1 发送
*/
base->I2CR |= (1 << 5) | (1 << 4);

/*
* 设置寄存器I2DR
* bit[7:0] : 要发送的数据,这里写入从设备地址
* 参考资料:IMX6UL参考手册P1249
*/
base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
return 0;
}

/*
* @description : 检查并清除错误
* @param - base : 要使用的IIC
* @param - status : 状态
* @return : 状态结果
*/
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{
/* 检查是否发生仲裁丢失错误 */
if(status & (1<<4))
{
base->I2SR &= ~(1<<4); /* 清除仲裁丢失错误位 */

base->I2CR &= ~(1 << 7); /* 先关闭I2C */
base->I2CR |= (1 << 7); /* 重新打开I2C */
return I2C_STATUS_ARBITRATIONLOST;
}
else if(status & (1 << 0)) /* 没有接收到从机的应答信号 */
{
return I2C_STATUS_NAK; /* 返回NAK(No acknowledge) */
}
return I2C_STATUS_OK;
}

/*
* @description : 停止信号
* @param - base : 要使用的IIC
* @param : 无
* @return : 状态结果
*/
unsigned char i2c_master_stop(I2C_Type *base)
{
unsigned short timeout = 0xffff;

/*
* 清除I2CR的bit[5:3]这三位
*/
base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));

/* 等待忙结束 */
while((base->I2SR & (1 << 5)))
{
timeout--;
if(timeout == 0) /* 超时跳出 */
return I2C_STATUS_TIMEOUT;
}
return I2C_STATUS_OK;
}

/*
* @description : 发送数据
* @param - base : 要使用的IIC
* @param - buf : 要发送的数据
* @param - size : 要发送的数据大小
* @param - flags : 标志
* @return : 无
*/
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
{
/* 等待传输完成 */
while(!(base->I2SR & (1 << 7)));

base->I2SR &= ~(1 << 1); /* 清除标志位 */
base->I2CR |= 1 << 4; /* 发送数据 */

while(size--)
{
base->I2DR = *buf++; /* 将buf中的数据写入到I2DR寄存器 */

while(!(base->I2SR & (1 << 1))); /* 等待传输完成 */
base->I2SR &= ~(1 << 1); /* 清除标志位 */

/* 检查ACK */
if(i2c_check_and_clear_error(base, base->I2SR))
break;
}

base->I2SR &= ~(1 << 1);
i2c_master_stop(base); /* 发送停止信号 */
}

/*
* @description : 读取数据
* @param - base : 要使用的IIC
* @param - buf : 读取到数据
* @param - size : 要读取的数据大小
* @return : 无
*/
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size)
{
volatile uint8_t dummy = 0;

dummy++; /* 防止编译报错 */

/* 等待传输完成 */
while(!(base->I2SR & (1 << 7)));

base->I2SR &= ~(1 << 1); /* 清除中断挂起位 */
base->I2CR &= ~((1 << 4) | (1 << 3)); /* 接收数据 */

/* 如果只接收一个字节数据的话发送NACK信号 */
if(size == 1)
base->I2CR |= (1 << 3);

dummy = base->I2DR; /* 假读 */

while(size--)
{
while(!(base->I2SR & (1 << 1))); /* 等待传输完成 */
base->I2SR &= ~(1 << 1); /* 清除标志位 */

if(size == 0)
{
i2c_master_stop(base); /* 发送停止信号 */
}

if(size == 1)
{
base->I2CR |= (1 << 3);
}
*buf++ = base->I2DR;
}
}

/*
* @description : I2C数据传输,包括读和写
* @param - base: 要使用的IIC
* @param - xfer: 传输结构体
* @return : 传输结果,0 成功,其他值 失败;
*/
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
unsigned char ret = 0;
enum i2c_direction direction = xfer->direction;

base->I2SR &= ~((1 << 1) | (1 << 4)); /* 清除标志位 */

/* 等待传输完成 */
while(!((base->I2SR >> 7) & 0X1)){};

/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
{
direction = kI2C_Write;
}

ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */
if(ret)
{
return ret;
}

while(!(base->I2SR & (1 << 1))){}; /* 等待传输完成 */

ret = i2c_check_and_clear_error(base, base->I2SR); /* 检查是否出现传输错误 */
if(ret)
{
i2c_master_stop(base); /* 发送出错,发送停止信号 */
return ret;
}

/* 发送寄存器地址 */
if(xfer->subaddressSize)
{
do
{
base->I2SR &= ~(1 << 1); /* 清除标志位 */
xfer->subaddressSize--; /* 地址长度减一 */

base->I2DR = ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器写入子地址

while(!(base->I2SR & (1 << 1))); /* 等待传输完成 */

/* 检查是否有错误发生 */
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
i2c_master_stop(base); /* 发送停止信号 */
return ret;
}
} while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

if(xfer->direction == kI2C_Read) /* 读取数据 */
{

base->I2SR &= ~(1 << 1); /* 清除中断挂起位 */
i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 发送重复开始信号和从机地址 */
while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 */

/* 检查是否有错误发生 */
ret = i2c_check_and_clear_error(base, base->I2SR);
if(ret)
{
ret = I2C_STATUS_ADDRNAK;
i2c_master_stop(base); /* 发送停止信号 */
return ret;
}

}
}


/* 发送数据 */
if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
{
i2c_master_write(base, xfer->data, xfer->dataSize);
}

/* 读取数据 */
if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
{
i2c_master_read(base, xfer->data, xfer->dataSize);
}
return 0;
}

在写代码时侯吃的亏有两个 1.写数据时侯不要ack 收数据的时候才要 2. 手册上说的那个中断挂起位 其实是一个数据传输或者接收后变化 要手动修改

驱动代码

驱动代码是利用在i2c总线来匹配啊设备 然后i2c驱动来传输信息 可以比之前方便很多
首先修改设备树
把节点改成i2c

1
2
3
4
5
6
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};

追加节点:

1
2
3
4
ap3216c@1e {
compatible = "alientek,ap3216c"; reg = <0x1e>;
};
};

然后写程序 这个程序太啰嗦了 不贴了 不过有两点 匹配结束之后 把 i2c_client 放在 dev的private_data 里·二 这个时候 i2c_client->addr 就是你的设备地址 写数据是发两条数据 第一条是写寄存器 然后是读然后再写数据的时候 data[0] 放的是你的寄存器 后面的才是数据

spi ICM-20608

其实我觉得弄好i2c之后再写spi的驱动舒服得多
先说一下差别 就是spi用的是片选线 没有地址 而且传递消息SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式 而且会有两根线 一个用来主设备给从设备传信息 一个是从设备给主设备传信息
看硬件原理图如下:
要注意点 1:片选线是0 2: 用了spi3

裸机

要注意的是裸机驱动非常简单我们因为是双向的 所以我们不用费力 就可以在驱动写下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned char spich0_readwrite_byte(ECSPI_Type *base, unsigned char txdata)
{
uint32_t spirxdata = 0;
uint32_t spitxdata = txdata;

/* 选择通道0 */
base->CONREG &= ~(3 << 18);
base->CONREG |= (0 << 18);

while((base->STATREG & (1 << 0)) == 0){} /* 等待发送FIFO为空 */
base->TXDATA = spitxdata;

while((base->STATREG & (1 << 3)) == 0){} /* 等待接收FIFO有数据 */
spirxdata = base->RXDATA;
return spirxdata;
}

然后直接读写 需要注意的是:和i2c一样 读寄存器写地址只有低7位有效,寄存器地址最高位是读/写标志位读的时候要为1,写的时候要为0。然后也注意 读还是多一个步骤 先写寄存器的值

驱动

驱动就比较简单粗暴了
首先还是修改设备树:
首先写pinctrl 就是把spi复位称自己想要的状态

1
2
3
4
5
6
7
8
9
pinctrl_ecspi3: icm20608 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
>;
};

然后追加节点;

1
2
3
4
5
6
7
8
9
10
11
12
13
 &ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";

spidev: icm20608@0 {
compatible = "alientek,icm20608";
spi-max-frequency = <8000000>;
reg = <0>;
};
};

然后直接spi总线匹配
值得注意的是 读取:

1
2
3
4
5
6
7
txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址 bit8 要置 1 */ 
t->tx_buf = txdata; /* 要发送的数据 */
t->rx_buf = rxdata; /* 要读取的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m)

要把发送的地址放在写寄存器里
写:

1
2
3
4
5
6
7
*txdata = reg & ~0x80; /* 写数据的时候首寄存器地址 bit8 要清零 */
memcpy(txdata+1, buf, len); /* 把 len 个寄存器拷贝到 txdata 里 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = len+1; /* t->len=发送的长度+读取的长度 */
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);

直接写就可以而来

中断

这个就不说裸机了 太臭太长了
我们直接从驱动开始说我们直接先来解析key的设备树 进而解析设备树的中断处理过程

1
2
3
4
5
6
7
8
9
10
11
key{
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};

这段关于案件的设备树我们在引用的时候 可以直接调用irq_of_parse_and_map 来在intereupts中直接找到那个节点的中断 然后去:对应的节点设备树下 找到对应的中断号 然后往中断号里注册中断

1
2
3
4
5
6
7
8
 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;

在这里是设备树的知识点 这个中断代码是这样的流程首先
注册驱动 然后申请input函数 记录按键过程 最后利用定时器来消抖

input总线

就是首先定义keyinputdev.inputdev = input_allocate_device();keyinputdev.inputdev->name = KEYINPUT_NAME; 然后填充结构体 比如结构体的什么时间 案件时间 具体哪一个按键 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0); 然后把设备和总线挂钩ret = input_register_device(keyinputdev.inputdev); 然后有事件上报input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */input_sync(dev->inputdev);

lcd

原理

所谓lcd的原理其实就是填充他的结构体的一些东西:
VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的
LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。
VSPW:有些地方也叫做 tvp,是 VSYNC 信号宽度,也就是 VSYNC 信号持续时间,单位
为 1 行的时间。
VBP:有些地方叫做 tvb,前面已经讲过了,术语叫做帧同步信号后肩,单位为 1 行的时
间。
LINE:有些地方叫做 tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为 1024600,
那么 LINE 就是 600 行的时间。
VFP:有些地方叫做 tvf,前面已经讲过了,术语叫做帧同步信号前肩,单位为 1 行的时间
还有就是lcd的引脚 他的原理图是这样的 大部分引脚都是 rgb888 的像素一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就
可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、
G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit
3=24bit,也就是说一个像素点
3 个字节,这种像素格式称为 RGB888
pCGszn0.png
还有一个定义叫显存内存来存放像素数据,那么 1024600 分辨率就需要 1024600*4=2457600B≈2.4MB 内存。但是 RGB LCD 内部是没有内存的,所以就需要在开发板上的 DDR3 中分出一段内存作为 RGB
LCD 屏幕的显存

驱动

修改设备树:

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
pinctrl_lcdif_dat: lcdifdatgrp {
fsl,pins = <
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79
MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79
MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79
MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79
MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79
MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79
MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79
MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79
MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79
MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79
MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79
MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79
MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79
MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79
MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79
MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79
MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79
MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79
MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
>;
};
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
>;
pinctrl_pwm1: pwm1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
>;

就是使能几个lcd的引脚
现在需要修改时钟参数 以及上面提到的那几个要填充的结构体的值:

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
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
&pinctrl_lcdif_ctrl
&pinctrl_lcdif_reset>;
display = <&display0>;
status = "okay";

display0: display { /* LCD 属性信息 */
bits-per-pixel = <16>; /* 一个像素占用几个 bit */
bus-width = <24>; /* 总线宽度 */

display-timings {
native-mode = <&timing0>; /* 时序信息 */
timing0: timing0 {
clock-frequency = <9200000>; /* LCD 像素时钟,单位 Hz */
hactive = <480>; /* LCD X 轴像素个数 */
vactive = <272>; /* LCD Y 轴像素个数 */
hfront-porch = <8>; /* LCD hfp 参数 */
hback-porch = <4>; /* LCD hbp 参数 */
hsync-len = <41>; /* LCD hspw 参数 */
vback-porch = <2>; /* LCD vbp 参数 */
vfront-porch = <4>; /* LCD vfp 参数 */
vsync-len = <10>; /* LCD vspw 参数 */

hsync-active = <0>; /* hsync 数据线极性 */
vsync-active = <0>; /* vsync 数据线极性 */
de-active = <1>; /* de 数据线极性 */
pixelclk-active = <0>; /* clk 数据线先极性 */
};
};
};
};

代码就不用写了 因为lcd的代码内核里早就写了
直接用就行了 但是这个注册之后会生成一个/dev/fb0设备文件 这个一会会用到

lcd触摸屏

原理 就是内部有个的触摸芯片 然后通过i2c传递数据 还有他的每次触屏都涉及到了input
首先修改设备树:
首先复位io和中断io都是gpio普通引脚

1
2
3
4
5
6
7
8
9
10
11
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
>;
};
//复位
pinctrl_tsc_reset: tsc_reset {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};

现在修改i2c信息:

1
2
3
4
5
6
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};

添加触摸屏节点:

1
2
3
4
5
6
7
8
9
10
11
12
ft5426: ft5426@38 {
compatible = "edt,edt-ft5426";
reg = <0x38>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
};

我们来说一下代码

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
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ft5x06.c
作者 : 左忠凯
版本 : V1.0
描述 : FT5X06,包括FT5206、FT5426等触摸屏驱动程序
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/12/23 左忠凯创建个
***************************************************************/

#define MAX_SUPPORT_POINTS 5 /* 5点触摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接触 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */

/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG 0X02 /* 状态寄存器地址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中断模式 */
#define FT5X06_READLEN 29 /* 要读取的寄存器个数 */

struct ft5x06_dev {
struct device_node *nd; /* 设备节点 */
int irq_pin,reset_pin; /* 中断和复位IO */
int irqnum; /* 中断号 */
void *private_data; /* 私有数据 */
struct input_dev *input; /* input结构体 */
struct i2c_client *client; /* I2C客户端 */
};

static struct ft5x06_dev ft5x06;

/*
* @description : 复位FT5X06
* @param - client : 要操作的i2c
* @param - multidev: 自定义的multitouch设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;

if (gpio_is_valid(dev->reset_pin)) { /* 检查IO是否有效 */
/* 申请复位IO,并且默认输出低电平 */
ret = devm_gpio_request_one(&client->dev,
dev->reset_pin, GPIOF_OUT_INIT_LOW,
"edt-ft5x06 reset");
if (ret) {
return ret;
}

msleep(5);
gpio_set_value(dev->reset_pin, 1); /* 输出高电平,停止复位 */
msleep(300);
}

return 0;
}

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

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

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

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

/*
* @description : 向ft5x06多个寄存器写入数据
* @param - dev: ft5x06设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;

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

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

msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */

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

/*
* @description : 向ft5x06指定寄存器写入指定的值,写一个寄存器
* @param - dev: ft5x06设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ft5x06_write_regs(dev, reg, &buf, 1);
}

/*
* @description : FT5X06中断服务函数
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
struct ft5x06_dev *multidata = dev_id;

u8 rdbuf[29];
int i, type, x, y, id;
int offset, tplen;
int ret;
bool down;

offset = 1; /* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
tplen = 6; /* 一个触摸点有6个寄存器来保存触摸值 */

memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */

/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
if (ret) {
goto fail;
}

/* 上报每一个触摸点坐标 */
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];

/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
* bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件
* bit5:4 保留
* bit3:0 X轴触摸点的11~8位。
*/
type = buf[0] >> 6; /* 获取触摸类型 */
if (type == TOUCH_EVENT_RESERVED)
continue;

/* 我们所使用的触摸屏和FT5X06是反过来的 */
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;

/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
* bit7:4 Touch ID 触摸ID,表示是哪个触摸点
* bit3:0 Y轴触摸点的11~8位。
*/
id = (buf[2] >> 4) & 0x0f;
down = type != TOUCH_EVENT_UP;

input_mt_slot(multidata->input, id);
input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);

if (!down)
continue;

input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
}

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

fail:
return IRQ_HANDLED;

}

/*
* @description : FT5x06中断初始化
* @param - client : 要操作的i2c
* @param - multidev: 自定义的multitouch设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;

/* 1,申请中断GPIO */
if (gpio_is_valid(dev->irq_pin)) {
ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
GPIOF_IN, "edt-ft5x06 irq");
if (ret) {
dev_err(&client->dev,
"Failed to request GPIO %d, error %d\n",
dev->irq_pin, ret);
return ret;
}
}

/* 2,申请中断,client->irq就是IO中断, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &ft5x06);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}

return 0;
}

/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;

ft5x06.client = client;

/* 1,获取设备树中的中断和复位引脚 */
ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

/* 2,复位FT5x06 */
ret = ft5x06_ts_reset(client, &ft5x06);
if(ret < 0) {
goto fail;
}

/* 3,初始化中断 */
ret = ft5x06_ts_irq(client, &ft5x06);
if(ret < 0) {
goto fail;
}

/* 4,初始化FT5X06 */
ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); /* 进入正常模式 */
ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); /* FT5426中断模式 */

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

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

input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);
ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}

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

return 0;

fail:
return ret;
}

/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_remove(struct i2c_client *client)
{
/* 释放input_dev */
input_unregister_device(ft5x06.input);
return 0;
}


/*
* 传统驱动匹配表
*/
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "edt-ft5206", 0, },
{ "edt-ft5426", 0, },
{ /* sentinel */ }
};

/*
* 设备树匹配表
*/
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", },
{ .compatible = "edt,edt-ft5426", },
{ /* sentinel */ }
};

/* i2c驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(ft5x06_of_match),
},
.id_table = ft5x06_ts_id,
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ft5x06_init(void)
{
int ret = 0;

ret = i2c_add_driver(&ft5x06_ts_driver);

return ret;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}

module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

代码逻辑是这样的:首先引导i2c驱动 然后在prope函数里注册中断 中断就是有input读取信息

tslib 移植

tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行校准
这个很简单网上会有教程:就是在写配置的时候

1
2
3
4
5
6
export TSLIB_TSDEVICE=/dev/input/event1  //你刚才写的触摸屏
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0 //上面写的屏幕

然后移植qt

音频驱动

i2o

阻塞

并发

比如说打印机这个设备,我们在同一时刻必须一个人使用,其实越往后学习 越感觉的到所写的驱动程序 其实就是申请了一个dev我们对它进行操作被应用层调用而已,我们在打开的时候上我们的锁 或者是原子操作再关闭上收回锁 就是有几种方式我们要一个一个来

原子操作

原子操作,原子操作就是指不能再进一步分割的操作,一般原子操作用于变量
或者位操作。假如现在要对无符号整形变量 a 赋值,值为 3,对于 C 语言来讲很简单,直接就
是:
a = 3
但是 C 语言要先编译为成汇编指令,ARM 架构不支持直接对寄存器进行读写操作,比如
要借助寄存器 R0、R1 等来完成赋值操作。假设变量 a 的地址为 0X3000000,“a=3”这一行 C
语言可能会被编译为如下所示的汇编代码:

1
2
3
ldr r0, =0X30000000 /* 变量 a 地址 */
ldr r1, = 3 /* 要写入的值 */
str r1, [r0] /* 将 3 写入到 a 变量中 */

所以我们需要原子操作 这个很简单 就是在设备结构体里加上atomic_t lock; ,/然后再

1
2
3
4
5
6
7
8
9
//open函数里判断原子操作
if (!atomic_dec_and_test(&gpioled.lock)) {
atomic_inc(&gpioled.lock);/* 小于 0 的话就加 1,使其原子变量等于 0 */
return -EBUSY; /* LED 被使用,返回忙 */
}
//relase函数里关闭
atomic_inc(&dev->lock);
//init函数里初始化
atomic_set(&gpioled.lock, 1);

自旋锁

原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,在 Linux内核中就是自旋锁。
代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
开自旋锁:
spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */
if (gpioled.dev_stats) { /* 如果设备被使用了 */
spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */
return -EBUSY;
}
gpioled.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */

return 0;
}
//关设备锁
spin_lock_irqsave(&dev->lock, flags); /* 上锁 */
if (dev->dev_stats) {
dev->dev_stats--;
}
spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */

信号量

初始化sema_init(&gpioled.sem, 1); open函数:

1
2
3
4
5
 /* 获取信号量,进入休眠状态的进程可以被信号打断 */
if (down_interruptible(&gpioled.sem)) {
return -ERESTARTSYS;
}
up(&dev->sem);

互斥体

大差不差

区别

信号量/互斥体允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待,所以有以下区别应用
1)、信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合于保持时间非常短的情况
2)、自旋锁可以用于中断,不能用于进程上下文(会引起死锁)。而信号量不允许使用在中断中,而可以用于进程上下文
3)、自旋锁保持期间是抢占失效的,自旋锁被持有时,内核不能被抢占,而信号量和读写信号量保持期间是可以被抢占的