LS1043A 自定义板测试详细记录
前言
本次目的是使用自定义的硬件,CPU为LS1043A,系统跑起来,通过配置或者编写驱动使各个外设能够正常使用,硬件部分主要是参考,主要记录软件开发过程中遇到的困难以及解决方法。主要包括的外设有IIC温度传感器 TMP75、IIC RTC时钟 DS1339、看门狗、GPIO、PWM以及四线风扇、网络、普通串口以及485串口等。 本文重点不是系统启动等调试,而是在UBOOT和内核文件系统能正常启动的情况下,依据硬件外设,使外设能够正常符合标准使用,驱动分析涉及一部分,由于内容太多不会太详细。开发环境
- 开发系统:ubuntu20.04
- NXP SDK : LSDK21.08
- 内核版本 : linux 5.10
- 内核默认配置: ls1043ardb_defconfig
一些基础知识
1. 寄存器读写
LS1043的寄存器有的是大端有的是小端,如果要对寄存器进行配置,需要根据手册查找,然后进行配置;并且,官方内核不能使用 /dev/mem 访问物理地址,具体原因未去探究,可使用mtools。
2. 设备树
对于设备树,中断号配置需要说明一下,以 LPUART1 为例, 手册中中断向量号为80,设备树中中断号为 则为 80 - 32 = 48 ,其他相同;
lpuart0: serial@2950000 {
compatible = "fsl,ls1021a-lpuart";
reg = <0x0 0x2950000 0x0 0x1000>;
interrupts = <0 48 0x4>;
clocks = <&clockgen 0 0>;
clock-names = "ipg";
status = "disabled";
};
3.RCW
对于RCW,IO的多路复用,根据手册配置即可,如果使用的是LSDK的工具;
各模块测试
1. RTC时钟 (DS1339)
(1)硬件部分
使用I2C1,IIC地址为0x68
(2)软件部分
主要是配置设备树以及内核配置,设备树以fsl-ls1043a-rdb.dts 为主,涉及fsl-ls1043.dtsi 作为基础,RTC芯片使用的DS1339。
设备树
I2C控制器硬件信息已经在设备树fsl-ls1043.dtsi中由官方给出, 其中scl-gpios是用来处理I2C死锁问题的,配置I2C总线的SCL总线即可,在发生死锁的时候驱动会将SCL配置成GPIO模型,然后控制总线,解除死锁,具体请看驱动源码:
i2c0: i2c@2180000 {
compatible = "fsl,vf610-i2c", "fsl,ls1043a-vf610-i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x2180000 0x0 0x10000>;
interrupts = <0 56 0x4>;
clock-names = "i2c";
clocks = <&clockgen 4 0>;
dmas = <&edma0 1 39>,
<&edma0 1 38>;
dma-names = "tx", "rx";
scl-gpios = <&gpio4 12 0>
status = "disabled";
};
注意: 此处的I2C0即硬件我们使用的I2C1, 只是标号不一样,寄存器的值0x2180000是重点,请根据硬件和参考手册去具体选择相应的I2C控制器。
我们只需要修改fsl-ls1043a-rdb.dts 中i2c0节点的部分即可,ds1339 如下,地址0x68
&i2c0 {
status = "okay";
rtc@68 {
compatible = "dallas,ds1339";
reg = <0x68>;
};
}
设备驱动使用的是 driver/rtc/rtc-ds1307.c ,其支持的部分设备包含DS1339,如下:
static const struct of_device_id ds1307_of_match[] = {
{
.compatible = "dallas,ds1307",
.data = (void *)ds_1307
},
{
.compatible = "dallas,ds1308",
.data = (void *)ds_1308
},
{
.compatible = "dallas,ds1337",
.data = (void *)ds_1337
},
{
.compatible = "dallas,ds1338",
.data = (void *)ds_1338
},
{
.compatible = "dallas,ds1339",
.data = (void *)ds_1339
},
{
.compatible = "dallas,ds1388",
.data = (void *)ds_1388
},
{
.compatible = "dallas,ds1340",
.data = (void *)ds_1340
},
{
.compatible = "dallas,ds1341",
.data = (void *)ds_1341
},
{
.compatible = "maxim,ds3231",
.data = (void *)ds_3231
},
{
.compatible = "st,m41t0",
.data = (void *)m41t0
},
{
.compatible = "st,m41t00",
.data = (void *)m41t00
},
{
.compatible = "st,m41t11",
.data = (void *)m41t11
},
{
.compatible = "microchip,mcp7940x",
.data = (void *)mcp794xx
},
{
.compatible = "microchip,mcp7941x",
.data = (void *)mcp794xx
},
{
.compatible = "pericom,pt7c4338",
.data = (void *)ds_1307
},
{
.compatible = "epson,rx8025",
.data = (void *)rx_8025
},
{
.compatible = "isil,isl12057",
.data = (void *)ds_1337
},
{
.compatible = "epson,rx8130",
.data = (void *)rx_8130
},
{ }
};
内核配置
根据LSDK手册配置RTC驱动:
以及DS1339的设备驱动:
软件测试
在以上配置成功之后,进入文件系统,我们会在设备中看到RTC,并且使用工具hwclock能读取到硬件时间,以及写入时间,重启设备时间不变等。
注意
当使用hwclock的时候,可能i2c_dectect 工具并不能很好的探测到RTC时钟。
2. 温度传感器(tmp75)
(1)硬件部分
使用I2C1,IIC地址为0x4a
(2)软件部分
设备树
我们只需要修改fsl-ls1043a-rdb.dts 中i2c0节点的部分即可,tmp75 如下,地址0x4a
&i2c0 {
status = "okay";
tmp: tmp@4a {
compatible = "ti,tmp75";
reg = <0x4a>;
// 与linux thermal架构相关
#thermal-sensor-cells = <1>;
};
rtc@68 {
compatible = "dallas,ds1339";
reg = <0x68>;
};
};
设备驱动使用的是 driver/hwmon/lm75.c,其支持的设备包含tmp75, 如下:
static const struct of_device_id __maybe_unused lm75_of_match[] = {
{
.compatible = "adi,adt75",
.data = (void *)adt75
},
{
.compatible = "dallas,ds1775",
.data = (void *)ds1775
},
{
.compatible = "dallas,ds75",
.data = (void *)ds75
},
{
.compatible = "dallas,ds7505",
.data = (void *)ds7505
},
{
.compatible = "gmt,g751",
.data = (void *)g751
},
{
.compatible = "national,lm75",
.data = (void *)lm75
},
{
.compatible = "national,lm75a",
.data = (void *)lm75a
},
{
.compatible = "national,lm75b",
.data = (void *)lm75b
},
{
.compatible = "maxim,max6625",
.data = (void *)max6625
},
{
.compatible = "maxim,max6626",
.data = (void *)max6626
},
{
.compatible = "maxim,max31725",
.data = (void *)max31725
},
{
.compatible = "maxim,max31726",
.data = (void *)max31725
},
{
.compatible = "maxim,mcp980x",
.data = (void *)mcp980x
},
{
.compatible = "nxp,pct2075",
.data = (void *)pct2075
},
{
.compatible = "st,stds75",
.data = (void *)stds75
},
{
.compatible = "st,stlm75",
.data = (void *)stlm75
},
{
.compatible = "microchip,tcn75",
.data = (void *)tcn75
},
{
.compatible = "ti,tmp100",
.data = (void *)tmp100
},
{
.compatible = "ti,tmp101",
.data = (void *)tmp101
},
{
.compatible = "ti,tmp105",
.data = (void *)tmp105
},
{
.compatible = "ti,tmp112",
.data = (void *)tmp112
},
{
.compatible = "ti,tmp175",
.data = (void *)tmp175
},
{
.compatible = "ti,tmp275",
.data = (void *)tmp275
},
{
.compatible = "ti,tmp75",
.data = (void *)tmp75
},
{
.compatible = "ti,tmp75b",
.data = (void *)tmp75b
},
{
.compatible = "ti,tmp75c",
.data = (void *)tmp75c
},
{ },
};
内核配置
选择lm75相应的驱动即可:
要选中配置 HWMON 以及 THERMAL,为后面应用层使用提供支持;
软件测试
软件测试可以有三种方式:
- 使用i2ctools 中的i2cget直接操作总线去读相应地址寄存器获取温度,这个需要参考手册去获取;
- 使用文件系统中的接口获取
- 使用应用程序sensors 获取温度:
注意
使用 sensors 工具获取温度可能更新频率比较慢,温度很长时间没有变化,没有直接使用i2ctools 读取的快;
3. PWM
pwm部分主要是为风扇提供可控转速的,可以使用系统PWM或者GPIO模拟;
(1)硬件部分
我们使用的是PWM8,0通道
(2)软件部分
设备树
官方自带的PWM设备树在 fsl-ls1043a.dtsi 中,包括PWM和GPIO模拟部分:
pwm0: pwm@29f0000 {
compatible = "fsl,vf610-ftm-pwm";
#pwm-cells = <3>;
reg = <0x0 0x29f0000 0x0 0x10000>;
clock-names = "ftm_sys", "ftm_ext",
"ftm_fix", "ftm_cnt_clk_en";
clocks = <&clockgen 4 1>, <&clockgen 4 1>,
<&clockgen 4 1>, <&clockgen 4 1>;
status = "okay";
};
参考官方设备树,设置添加自己使用PWM8,基地址为 0x2a4_0000:
pwm8: pwm@2a40000 {
compatible = "fsl,vf610-ftm-pwm";
#pwm-cells = <3>;
reg = <0 0x2a40000 0 0x10000>;
clock-names = "ftm_sys", "ftm_fix",
"ftm_ext", "ftm_cnt_clk_en";
clocks = <&clockgen 4 1>, <&clockgen 4 1>, <&clockgen 4 1>, <&clockgen 4 1>;
big-endian;
status = "okay";
};
// GPIO模拟PWM, 设备树文件,使用GPIO4_2模拟
pwm1:gpio-pwms {
compatible = "gpio-pwms";
pinctrl-names = "default";
#pwm-cells = <3>;
status = "okay";
pwm1 {
label = "pwm1";
gpios = <&gpio4 2 0>;
};
};
注意此处的 big-endian; ,官方的PWM0没有这一字段,但是这一字段是必须的,因为这一区域1043是大端的,不设置会有问题,这是一大坑。同时,对于PWM输出,只用CH脚是输出PWM的,比如我用的是FTM8_CH0, 代表FTM8的通道0输出,这个硬件设计应注意。
GPIO模拟部分PWM部分,参考GPIO模拟PWM驱动
内核配置
- 首先是引脚的多路复用配置,我使用的是FTM8_CH0管脚,这一个脚配置是在SCFG_RCWPMUXCR0 寄存器中,具体参考:
LS1043A IIC3 IIC4 复用功能的配置
UBOOT中使用命令 mm 往 0x157040c 写入 0x11510000 即配置成功;因为是大端,所以 51 为 16 - 23的数据;
内核配置为:
GPIO模拟驱动改天另发;
软件测试
该教程使用的 FTM1 的通道1,所以export传入的是1,根据自己的情况导出不同参数;
因为我的是FTM8 0 通道,所以命令如下,dmesg是我自己添加的内核驱动打印,根据周期占比,风扇会有不同程度的转速改变,或者使用示波器抓取波形即可;
注意
PWM整体比较简单,官方源码就能用,主要注意的点就是设备树的大端设置问题,官方指导手册里也没有着重描述这边;
4. 四线风扇
四线风扇的使用,需要用到PWM控制风扇转速,然后需要FG脚获取风扇转速,获取风扇转速是通过获取FG脚上的脉冲频率计算得到的,只需要设置该脚的IO中断即可,具体如下。
通过FG脚检测风扇转速
根据风扇规格书可知风扇风速=60/(2*脉冲周期),周期T=1/频率。那么我们需要获取FG脚上的脉冲频率,即可获取风扇风速。
(1)硬件部分
(2)软件部分
设备树展示:
fan1: pwm_fan1 {
compatible = "pwm-fan";
cooling-min-state = <0>;
cooling-max-state = <3>;
#cooling-cells = <2>;
fg_gpio = <&gpio4 3 IRQ_TYPE_EDGE_RISING>;
pwms = <&pwm1 0 100000000 0>;
cooling-levels = <102 153 204 250>;
};
fan2: pwm_fan2 {
compatible = "pwm-fan";
cooling-min-state = <0>;
cooling-max-state = <3>;
#cooling-cells = <2>;
fg_gpio = <&gpio4 11 IRQ_TYPE_EDGE_RISING>;
pwms = <&pwm8 0 10000000 0>;
cooling-levels = <102 153 204 250>;
};
&thermal_zones {
core-cluster {
cooling-maps {
map1 {
trip = <&core_cluster_alert>;
cooling-device =
<&fan1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&fan2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
};
};
// 板级,名字自定义
board {
// 温控发生时的轮询周期(ms)
polling-delay-passive = <1000>;
// 温控未发生时轮询周期(ms)
polling-delay = <5000>;
// 对应的传感器, 使用前面的 tmp75 节点
thermal-sensors = <&tmp 0>;
// 温控出发点
trips {
// 第一部分, 0 - 35° 的时候
balert0: board-alert0 {
temperature = <35000>;
// 滞后温度,表示下降到 35 - 5 = 30° 时解除温控
hysteresis = <5000>;
// 四种类型,passive、active、hot、critical
// 非 critical 类型由 governor 的温控策略来降温
type = "active";
};
// 温度高于 55°的时候
balert1: board-alert1 {
temperature = <55000>;
hysteresis = <5000>;
type = "passive";
};
// 温度高于 65° 的时候
bcrit: board-crit {
temperature = <65000>;
hysteresis = <5000>;
type = "passive";
};
};
cooling-maps {
// 温度高于35°, 风扇1和风扇2 状态 0 - 1
map0 {
trip = <&balert0>;
cooling-device =
<&fan1 THERMAL_NO_LIMIT 1>,
<&fan2 THERMAL_NO_LIMIT 1>;
};
// 温度高于55°, 风扇1和风扇2 状态 2 - 3
map1 {
trip = <&balert1>;
cooling-device =
<&fan1 2 THERMAL_NO_LIMIT>,
<&fan2 2 THERMAL_NO_LIMIT>;
};
// 温度高于65°, 风扇1和风扇2 状态无限制,全速转
map2 {
trip = <&bcrit>;
cooling-device =
<&fan1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
<&fan2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
};
};
};
};
内核配置
驱动源码在 driver/hwmon/pwm-fan.c, 详细介绍参考pwm_fan风扇驱动;
static int pwm_fan_probe(struct platform_device *pdev)
{
// 获取平台信息,中断号
ctx->irq = platform_get_irq_optional(pdev, 0);
if (ctx->irq == -EPROBE_DEFER)
return ctx->irq;
...
if (ctx->irq > 0) {
// 获取中断号,申请中断处理,计算风扇转速
ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
pdev->name, ctx);
if (ret) {
dev_err(dev, "Failed to request interrupt: %d\n", ret);
return ret;
}
ctx->sample_start = ktime_get();
mod_timer(&ctx->rpm_timer, jiffies + HZ);
}
...
}
static irqreturn_t pulse_handler(int irq, void *dev_id)
{
struct pwm_fan_ctx *ctx = dev_id;
// 只是简单的加1计数
atomic_inc(&ctx->pulses);
return IRQ_HANDLED;
}
// 注册一个定时器,每一秒中断处理一次
static void sample_timer(struct timer_list *t)
{
struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer);
unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start);
int pulses;
if (delta) {
pulses = atomic_read(&ctx->pulses);
atomic_sub(pulses, &ctx->pulses);
// 计算风扇转速
ctx->rpm = (unsigned int)(pulses * 1000 * 60) /
(ctx->pulses_per_revolution * delta);
ctx->sample_start = ktime_get();
}
mod_timer(&ctx->rpm_timer, jiffies + HZ);
}
软件测试
-
风扇转速可以使用 sensors 应用获取到;
-
可以使用 fancontrol 应用进行控制风扇转速;
-
因为设备树中已经讲两个风扇添加进 thermal 架构中,所以,当温度传感器 tmp75温度到达指定的阈值时,风扇会自动采用不同的策略进行转速控制,可手动模拟温度,修改文件 /sys/class/thermal/thermal_zone0/emul_temp 模拟设置温度即可,路径根据自己实际情况修改;
注意
此部分注意的点不多,软件部分只有一个PWM和IO中断去处理,注意RCW配置多路复用;如果有问题,可以从硬件着手,先查看PWM是否正常,然后查看IO中断是否能够触发,其他的就没什么了。
5. WATCHDOG(看门狗)
(1)硬件部分
无
(2)软件部分
设备树
设备树不用改,官方自带的 fsl-ls1043a.dtsi 中有一个,也可根据自身情况进行添加,修改的地方只有寄存器和中断号
wdog0: wdog@2ad0000 {
compatible = "fsl,ls1043a-wdt", "fsl,imx21-wdt";
reg = <0x0 0x2ad0000 0x0 0x10000>;
interrupts = <0 83 0x4>;
clocks = <&clockgen 4 0>;
clock-names = "wdog";
big-endian;
};
如果需要产生外部重启信号,需要设备树配置 fsl,ext-reset-output:
wdev->ext_reset = of_property_read_bool(dev->of_node,
"fsl,ext-reset-output");
内核配置
软件测试
打开/dev/watchdog,写任何数据或者使用 ioctl 即可喂狗,如果超时,会提前一秒产生 PRETIMEOU 中断,且 REST_REQ_B 会有脉冲;
static inline void keep_alive(void)
{
int dummy;
int ret;
ret = ioctl(fd, WDIOC_KEEPALIVE, &dummy);
if (!ret)
printf(".");
}
int main(int argc, char *argv[])
{
int flags;
int ret;
char *file = "/dev/watchdog";
fd = open(file, O_WRONLY);
if (fd == -1) {
if (errno == ENOENT)
printf("Watchdog device (%s) not found.\n", file);
else if (errno == EACCES)
printf("Run watchdog as root.\n");
else
printf("Watchdog device open failed %s\n",
strerror(errno));
exit(-1);
}
flags = WATCHOUT_TIMEOUT + 1;
ret = ioctl(fd, WDIOC_SETTIMEOUT, &flags);
if (!ret)
printf("Watchdog timeout set to %u seconds.\n", flags);
else {
printf("WDIOC_SETTIMEOUT error '%s'\n", strerror(errno));
close(fd);
exit(ret);
}
flags = 1;
ret = ioctl(fd, WDIOC_SETPRETIMEOUT, &flags);
if (!ret)
printf("Watchdog pretimeout set to %u seconds.\n", flags);
else {
printf("WDIOC_SETPRETIMEOUT error '%s'\n", strerror(errno));
close(fd);
exit(ret);
}
while (1) {
keep_alive();
sleep(3);
}
close(fd);
return 0;
}
注意
- 看门狗常用的是用来复位,但是官方说,1043的看门狗没有内部复位电路,所以需要外部电路辅助才能复位,当看门狗超时时,会在 REST_REQ_B 脚有个脉冲,外部去处理复位逻辑。
- 1043的看门狗还有一个功能可能会用到,PRETIMEOUT这个功能,中文翻译不清楚,意思是在看门狗超时前多少秒会产生一个中断,进行一些处理,比如 imx2_wdt_set_pretimeout 设置了1秒,看门狗超时时间是10秒,那么在第9秒就会产生一个中断,中断处理中,会调用通知链中的处理函数,这方面改天细说吧。
static irqreturn_t imx2_wdt_isr(int irq, void *wdog_arg)
{
struct watchdog_device *wdog = wdog_arg;
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
regmap_write_bits(wdev->regmap, IMX2_WDT_WICR,
IMX2_WDT_WICR_WTIS, IMX2_WDT_WICR_WTIS);
watchdog_notify_pretimeout(wdog);
return IRQ_HANDLED;
}
6. GPIO输入输出
(1)硬件部分
无,就是IO直连
(2)软件部分
默认内核配置已经包含GPIO模块驱动,
设备树
fsl-ls1043a.dtsi系统自带,无需修改:
gpio1: gpio@2300000 {
compatible = "fsl,ls1043a-gpio", "fsl,qoriq-gpio";
reg = <0x0 0x2300000 0x0 0x10000>;
interrupts = <0 66 0x4>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio2:。。。
gpio3:。。。
gpio4:。。。
内核配置
软件测试
GPIO寄存器比较少,映射地址如下:
应用层控制GPIO口,以GPIO1_31为例,首先查看GPIO1的目录:
我们得到,GPIO1 是 gpiochip480目录下的文件, 480 + 31 = 511, 那么我们就导出 511:
cd /sys/class/gpio
echo 511 > export
cd gpio511
echo out > direction # out 设置属性为输出 in 为输入
echo 0 > value # 输出低电平 car value 读取值
echo 1 > value # 输出高电平
注意
所使用的GPIO,要通过RCW配置成GPIO功能。
7. 485串口
(1)硬件部分
硬件部分,主要注意的是使用了RTS,串口使用LPUART1_RTS
(2)软件部分
设备树
内核配置
软件测试
应用程序初始化部分最为重要,此处仅展示串口初始化函数,其中主要注意的是使能485模式,SER_RS485_ENABLED,这样才能正常使用RTS信号;
int UART0_Set(int fd,int speed,int flow_ctrl,int databits,int stopbits,int parity)
{
int i;
int status;
int speed_arr[] = {B2000000,B1500000,B1152000,B1000000,B921600,B576000,B500000,
B460800,B230400, B115200, B19200, B9600, B4800, B2400, B1200, B300};
int name_arr[] = {2000000,1500000,1152000,1000000,921600,576000,500000,460800,230400,
115200,19200,9600,4800,2400,1200,300};
struct termios options;
struct serial_rs485 rs485conf;
if (ioctl (fd, TIOCGRS485, &rs485conf) < 0) {
printf("Error: TIOCGRS485 ioctl not supported.\n");
}
/* Enable RS-485 mode: */
rs485conf.flags |= SER_RS485_ENABLED;
/* Set rts/txen delay before send, if needed: (in microseconds) */
rs485conf.delay_rts_before_send = 100;
/* Set rts/txen delay after send, if needed: (in microseconds) */
rs485conf.delay_rts_after_send = 100;
if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
printf("Error: TIOCSRS485 ioctl not supported.\n");
}
/*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.
*/
if( tcgetattr( fd,&options) != 0)
{
perror("SetupSerial 1");
return(FALSE);
}
int seted_flag = 0;
//设置串口输入波特率和输出波特率
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++)
{
printf("------ baudrate Speed = %d\n",name_arr[i]);
if (speed == name_arr[i])
{
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
seted_flag = 1;
printf("Setting baudrate!!\n");
break;
}
}
if(seted_flag == 0)
{
printf("Unknow baudrate!!\n");
return FALSE;
}
//修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;
//修改控制模式,使得能够从串口中读取输入数据
options.c_cflag |= CREAD;
//设置数据流控制
switch(flow_ctrl)
{
case 0 ://不使用流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1 ://使用硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2 ://使用软件流控制
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
//设置数据位
//屏蔽其他标志位
options.c_cflag &= ~CSIZE;
switch (databits)
{
case 5 :
options.c_cflag |= CS5;
break;
case 6 :
options.c_cflag |= CS6;
break;
case 7 :
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default: fprintf(stderr,"Unsupported data size\n");
return (FALSE);
}
//设置校验位
switch (parity)
{
case 'n':
case 'N': //无奇偶校验位。
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O'://设置为奇校验
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E'://设置为偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S': //设置为空格
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
// 设置停止位
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB; break;
case 2:
options.c_cflag |= CSTOPB; break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
//修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//options.c_lflag &= ~(ISIG | ICANON);
//设置等待时间和最小接收字符
options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush(fd,TCIFLUSH);
//激活配置 (将修改后的termios数据设置到串口中)
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("com set error!\n");
return (FALSE);
}
return (TRUE);
}
注意
如果使用了RTS信号线,就要在软件中使能485模式,才能使用;
fsl_lpuart.c drivers\tty\serial 485串口配置部分代码
if (rs485->flags & SER_RS485_ENABLED) {
/* Enable auto RS-485 RTS mode */
modem |= UARTMODEM_TXRTSE;
/*
* RTS needs to be logic HIGH either during transfer _or_ after
* transfer, other variants are not supported by the hardware.
*/
if (!(rs485->flags & (SER_RS485_RTS_ON_SEND |
SER_RS485_RTS_AFTER_SEND)))
rs485->flags |= SER_RS485_RTS_ON_SEND;
if (rs485->flags & SER_RS485_RTS_ON_SEND &&
rs485->flags & SER_RS485_RTS_AFTER_SEND)
rs485->flags &= ~SER_RS485_RTS_AFTER_SEND;
/*
* The hardware defaults to RTS logic HIGH while transfer.
* Switch polarity in case RTS shall be logic HIGH
* after transfer.
* Note: UART is assumed to be active high.
*/
if (rs485->flags & SER_RS485_RTS_ON_SEND)
modem &= ~UARTMODEM_TXRTSPOL;
else if (rs485->flags & SER_RS485_RTS_AFTER_SEND)
modem |= UARTMODEM_TXRTSPOL;
}
8. 网络PHY
(1)硬件部分
使用的是裕泰8531和8614;硬件原理图,请参考裕泰硬件进行设计,此处就别贴了。请注意,对于PHY来说,第一次使用不正常,那么就要进行调试。
首先第一步,要确定是否识别到phy。读取PHY ID 使用MDIO,MDIO与MDC两根总线要上拉。然后在内核中添加打印,PHY ID能够正确读取。
整体架构是这样的,8531使用 EC1和EC2, 8614使用QSGMII,一共六个网口,qsgmii 地址从3开始,下图地址有点出入;
(2)软件部分
RCW 配置
由芯片手册查看RCW配置:
SerDes 我们需要配成 0x4558, 可以参考 https://icode.best/i/45534640628716
0x4558 : qsgmii 1,2,5,6 两个PCIE以及一个SATA
对于RCW的配置,还要注意 SerDes 时钟的配置,否则还是不能使用,我们是100M,00即可:
设备树
文件名: fsl-ls1043a-rdb.dts, 仅供参考
&fman0 {
// 1 2 5 6
// MAC1
ethernet@e0000 {
phy-handle = <&qsgmii_phy1>;
phy-connection-type = "qsgmii";
local-mac-address = [a4 ae 12 78 fb 01];
};
// MAC2
ethernet@e2000 {
phy-handle = <&qsgmii_phy2>;
phy-connection-type = "qsgmii";
local-mac-address = [a4 ae 12 78 fb 02];
};
// MAC5
ethernet@e8000 {
phy-handle = <&qsgmii_phy3>;
phy-connection-type = "qsgmii";
local-mac-address = [a4 ae 12 78 fb 05];
};
// MAC6
ethernet@ea000 {
phy-handle = <&qsgmii_phy4>;
phy-connection-type = "qsgmii";
local-mac-address = [a4 ae 12 78 fb 06];
};
// MAC3
ethernet@e4000 {
phy-handle = <&rgmii_phy1>;
phy-connection-type = "rgmii-id";
local-mac-address = [a4 ae 12 78 fb 03];
};
// MAC4
ethernet@e6000 {
phy-handle = <&rgmii_phy2>;
phy-connection-type = "rgmii-id";
local-mac-address = [a4 ae 12 78 fb 04];
};
// MAC9
ethernet@f0000 { /* 10GEC1 */
status = "okay";
local-mac-address = [a4 ae 12 78 fb 09];
};
mdio@fc000 {
rgmii_phy1: ethernet-phy@1 {
reg = <0x1>;
};
qsgmii_phy1: ethernet-phy@3 {
reg = <0x3>;
};
qsgmii_phy2: ethernet-phy@4 {
reg = <0x4>;
};
qsgmii_phy3: ethernet-phy@5 {
reg = <0x5>;
};
qsgmii_phy4: ethernet-phy@6 {
reg = <0x6>;
};
};
mdio@fd000 {
rgmii_phy2: ethernet-phy@2 {
reg = <0x2>;
};
};
};
内核配置
内核配置方面,没找到具体资料,只能贴部分配置了,在默认配置的基础上,进行添加:
CONFIG_ETHERNET=y
CONFIG_NET_VENDOR_FREESCALE=y
CONFIG_FEC=y
CONFIG_FSL_FMAN=y
CONFIG_FSL_XGMAC_MDIO=y
CONFIG_FSL_DPAA_ETH=y
CONFIG_FSL_DPAA2_ETH=y
CONFIG_FSL_DPAA2_MAC=y
CONFIG_FSL_ENETC_MDIO=y
PHY驱动
PHY驱动可以向裕泰官方去要,不过支持不太积极.8531的驱动就说几点注意的地方:
// 自己添加的初始化配置,需要根据手册配置
static int ytphy_config_init(struct phy_device *phydev)
{
int val = 0;
val = ytphy_read_ext(phydev, 0xa012);
printk("phy ext_0a12 val : 0x%x\n", val);
val = 0xd0;
ytphy_write_ext(phydev, 0xa012, val);
ytphy_write_ext(phydev, 0xa00c, 0x2600);
ytphy_write_ext(phydev, 0xa00d, 0x1870);
ytphy_write_ext(phydev, 0xa00e, 0x1870);
return 0;
}
// 8531 部分
{
/* same as 8511 */
.phy_id = PHY_ID_YT8531,
.name = "YT8531 Gigabit Ethernet",
.phy_id_mask = MOTORCOMM_PHY_ID_MASK,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) )
.features = PHY_BASIC_FEATURES | PHY_GBIT_FEATURES,
.flags = PHY_HAS_INTERRUPT,
#endif
.config_aneg = genphy_config_aneg,
#if ( LINUX_VERSION_CODE < KERNEL_VERSION(4,0,0) ) || ( LINUX_VERSION_CODE > KERNEL_VERSION(5,3,0) )
.config_init = ytphy_config_init,
#else
.config_init = genphy_config_init,
#endif
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
#if (YTPHY_ENABLE_WOL)
.get_wol = &ytphy_get_wol,
.set_wol = &ytphy_set_wol,
#endif
}
软件测试
网络测试能PING通就好了,一般内网没什么问题的,PING外网要注意DNS设置,否则PING不通的,DPAA没有测试。
注意
这部分时间有点久了,细节没有很清楚,如果有问题与疑问,请留言或者邮箱 1004824817@qq.com.