【notes10】spi

文章目录


1.协议:芯片和芯片间通讯,4个pin,M(master)I(input)

主设备总是生成时钟,其速度可达80Mhz,实际没有速度限制(也远比I2C快),ss线(片选同一时刻只有一个有效,保证数据不干扰)也由主设备管理。

eeprom有1024位存储空间即128(如下8x8x2)个字节(不是FF十进制255个字节),每个字节都有属于自己的地址,那怎么读取这1024位数据呢?最直接的办法就是给每一位都外接一根线,显然不现实,spi只需4根线就可读写1024bit数据了,一般ss为低电平有效,但是看93c46手册高电平有效。

如下7位地址码不同的排列组合就有2的7次方=128种,正好访问存储器的128个字节。把高低电平通过mosi引脚发出去,数据就能成功写入?不是,因为spi是串行同步通信数据线要和时钟线配合。

SS片选信号被拉高后,从设备开始生效。芯片资料上有2个特殊寄存器配置位:CPOL(极性:sck空闲时时钟信号高还是低电平,1高,数据传输从跳变沿开始)。

CPHA(相位:sck的1个周期2个跳变沿,0从第1个跳变沿采样,1从第2...)。

如下就是spi的4中模式:

如下控制寄存器设置极性和相位,状态寄存器表示数据已经发送完成或使能中断,波特率寄存器是设置时钟频率,数据寄存器是写入数据并一位一位发送出去,同时从DI引脚上采样数据。

2.spi总线的验证方法:如下测得返回也是1

如下clk空闲时为高且在第二个跳变沿采样即上升沿采样。第三条线上数据如下,10进制为49即数字1。

如下三个管脚用的是spi3,所以配置spi3。

下面dtsi描述的就是spi控制器,控制器也是驱动,pinctrl描述gpio配置。

如下看手册里Address Mapping:所有的寄存器的基地址如fe640000。

如下添加设备。

3.spi控制器驱动:波形是由spi控制器拉出,dev节点进行数据发送和接收

如下获取设备树的reg,再做ioremap(因为寄存器地址kernel没法直接访问)。如下rs就是自己定义的结构体,最后会赋值给ctrl。

如下还是在probe函数中,如下第一行的函数调用platform_get_resource(dev,IORESOURCE_IRQ,num)。

如下第一行是不是休眠唤醒,pdev->id是3,mode_bit默认0。

3.1 platform_get_resource:获取pdev resource(probe函数传入pdev,设备树中所有节点被转换为platform_device,设备树中reg和interrupts属性转为resource,reg是索引0,interrupts是1)

如下源码num_resource就是上面的reg和interrupt的2个资源。

如下因为从pdev拿出resource(内存区域,中断等)。

4.设备驱动模型:platform_driver和platform_device都需要注册,注册时都会和对方比较(通过platform_bus_type的match函数比较),如果match函数返回成功,则匹配上调用probe


5.spi设备树处理过程:compatible属性找到spi_master驱动(如spi-gpio.c),spi_master驱动probe里会of_register_spi_device(struct spi_device , of_find_property读设备树属性填充spi_device,最后spi_add_device注册spi_device)

如下是spi_master(不是spi_slave)必须有的属性。

如下bits_per_word不是来自设备树,应用程序发起传输可设置这个值,表示每次传输的bit数。spi_device结构体里有master父节点,所以可用spi_master的传输函数。

6.spidev的使用(spi用户态api):spidev.c访问spi_device进而访问spi_master里传输函数访问硬件

minor次设备号来自位图中第一个未被占用的号码,片选chip_select来自设备树,表示是这个spi_master下的第几个设备。device_create去创建/dev/spi...节点。

open时候如何根据minor...实现在spidev_open函数如下,devt含有主次设备号,要和打开的设备节点的主次设备号(inode->i_rdev)比较,相等的话就是找到了这个设备,找到后分配发送和接收缓冲区,flip->private_data=spidev将spidev即spidev_data存入打开文件的私有数据里,read时候直接取出,就不需要重新找到这spi设备。

内核提供测试程序:tools/spi/spidev_fdx.c:spidev_fdx -r 100 /dev/spidevB.D。main函数中调用do_read -> read,驱动中.read=spidev_read,spidev_read调用如下spidev_sync_read(同步,等待结果),读完后再copy_to_user拷贝回用户空间,write同理。

不同上面do_read方式,如下用ioctl实现同时读写,读和写的长度是一样的,注释了xfer1

7.使用spidev操作SPI_DAC模块:转为模拟信号

数据可以通过DOUT传给下一个芯片,也可以传给master如下图。

同时写和读,只能用ioctl,先传输tx_buf0(存入高字节数据),后传输tx_buf1(存入低字节数据)。如下dac_test.c。

如下底板原理图,第四组gpio的第26个引脚。


pulse duration脉冲持续时间。一个周期包含高低电平。




insmod spidev.ko,出现/dev/spidev0.0节点。

将dac_test.c第64行右移2位,如下就是600。

如下spi->modalias就是设备树中compatible。

8.衍生协议:Dual/Quad SPI(只针对SPI Flash而言,不是针对所有SPI外设)

Dual SPI:上面讲的是standard spi全双工(同时发送和接收),对于SPI Flash,全双工并不常用(bmc主设备MOSI发送数据,flash从设备不需要MISO返回数据),因此扩展了mosi和miso的用法,让它们工作在半双工(2根线同时发或收),用以加倍数据传输。发送一个命令字节进入dual mode,这样MOSI复用成SIO0(serial io0),MISO复用成SIO1(serial io1),这样一个时钟周期内就能传输2个bit数据。

Quad SPI:在Dual SPI的基础上增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit。

9.spi设备驱动:spi_write传入的buf和len构造一个spi_transfer

如下spidev.c,用spi_transfer构造一个spi_message,最后调用spi_sync。

如下dac_test.c,修改为printf("SPI_IOC_MESSAGE %d\n",errno);,打印出115(定义在errno.h中)。

因为传输完成后,没有设置mesg->status=0,上面ioctl - spidev_ioctl(spidev.c)- spidev_message - spidev_sync - spi_sync -__spi_sync(message->status = -EINPROGRESS)- __spi_async - master->transfer。如果传输完成不设置message->status=0,则会打印出115。

如下全是dac_drv.c。

将上一章节设备树中改成compatible="100ask,dac",probe函数中最重要的是spidev_fops。spi_unregister_driver会调remove。

10.spi_master驱动:对每个设备都可以构造一个spi_message


如下c中传输不是立马完成,需要等待,传输完成会产生中断。

c 复制代码
// virtual_spi_master.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>

static struct spi_master *g_virtual_master;
static struct work_struct g_virtual_ws;

static void spi_virtual_work(struct work_struct *work)
{
	struct spi_message *mesg;
	while(!list_empty(&g_virtual_master->queue)) {
		mesg = list_entry(g_virtual_master->queue.next, struct spi_message, queue);  // a , b, c
		list_del_init(&mesg->queue); // 上行取出后就删除该消息
		/* 这里触发硬件传输 */
		mesg->status = 0; // 与下同理
		if(mesg->complete) 
			mesg->complete(mesg->context);  // d/4
	}
}

static int spi_virtual_transfer(struct spi_device *spi, struct spi_message *mesg)
{
#if 1
	/* 方法1: 直接完成spi传输 */
	/* 假装传输完成, 直接唤醒,没有发送数据给硬件 */
	mesg->status=0; // 完成,不是115
	mesg->complete(mesg->context); // d/4
	return 0;
#else
	/* 方法2: 使用工作队列启动SPI传输、等待完成 */
	mesg->actual_length = 0; // 还没传输任何一个字节
	mesg->status = -EINPROGRESS;  // 正在处理
	list_add_tail(&mesg->queue, &spi->master->queue); // 把消息放入master队列 ,2
	schedule_work(&g_virtual_ws);  // 启动工作队列 , 3
	return 0; // 直接返回
#endif
}

/////////////////////////////////////////////////////////////////////////////
static int spi_virtual_probe(struct platform_device *pdev)
{
	struct spi_master *master;
	int ret;
	/* 分配/设置/注册spi_master */
	g_virtual_master = master = spi_alloc_master(&pdev->dev, 0);
	if (master == NULL) {
		dev_err(&pdev->dev, "spi_alloc_master error.\n");
		return -ENOMEM;
	}

	master->transfer = spi_virtual_transfer;  // 设置即填充,spi_dev.c调用, 1
	INIT_WORK(&g_virtual_ws, spi_virtual_work); // 初始化工作队列,启动在上行spi_virtual_transfer中
	master->dev.of_node = pdev->dev.of_node; // 设置master的设备树信息,下面注册master时才会去设备树得到子节点

	ret = spi_register_master(master);
	if(ret < 0) {
		printk(KERN_ERR "spi_register_master error.\n");
		spi_master_put(master); // 相当于free
		return ret;
	}
	return 0;
}
static int spi_virtual_remove(struct platform_device *pdev)
{
	spi_unregister_master(g_virtual_master);
	spi_master_put(g_virtual_master);
	return 0;
}
static const struct of_device_id spi_virtual_dt_ids[] = {
	{ .compatible = "virtual_spi_master" },
	{ /* sentinel */ }
};
static struct platform_driver spidev_spi_driver = {
	.driver = {
		.name = "virtual_spi_master",
		.of_match_table = of_match_ptr(spi_virtual_dt_ids),
	},
	.probe = spi_virtual_probe,
	.remove = spi_virtual_remove,
};
static int __init virtual_master_init(void)
{
	return platform_register(spidev_spi_driver);
}
module_init(virtual_master_init);
static void __exit virtual_master_exit(void)
{
	platform_driver_unregister(&spidev_spi_driver);
}
module_exit(virtual_master_exit);
c 复制代码
// 。。。.dts
vitural_spi_master { // 拼写错
	compatible = "virtual_spi_master";  // 匹配
	status = "okay";
	cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
	num-chipselects = <1>;
	#address-cells = <1>;
	#size-cells = <0>;
	virtual_spi_dev:virual_spi_dev@0 {
		compatible = "spidev";
		reg = <0>;
		spi-max-frequency: <100000>;
	};
};

如下会在/sys/bus/platform/devices/下生成virtural_spi_master文件夹。

如下设备树中spi_master解析到了,但是没有识别到设备树的子节点,即slave device。

如下在spi.c,32766来源于下面函数第一行dyn_bus_id,新增一个master就会减1,在如下函数里of_...函数解析设备树。没有提供of_node的话直接返回。

如下probe函数中添加master->dev.of_node =,同时上面spi_master文件夹变成spi32764总线,下面是第0个设备。同时insmod spidev.ko。

probe函数中还能添加如下,再分配spi_master。

1.BMC升级:BMC用的flash是nor flash直连BMC的FMC

shell 复制代码
FMC: Flash/FRAM Memory Controller
SPI0, SPI1: SPI Flash Controller

flashcp比flashrom快。

spi flash:bios用的spi1,bmc用的fwspi,其他都是bmc4个gpio模拟spi信号。

spi eeprom:BCM53134芯片下挂了1kB的spi eeprom 93c46d,SPI挂在BMC的GPIO上。
spi-gpio controller:MAC(PCIe) flash、FPGA flash、BCM53134 eeprom使用同一路gpio模拟spi controller,用GPIO和CPLD做mux。

c 复制代码
// flash分区可以写在如下kernel dts中,也可以写在dev-spi-fb.c中
&fmc {
	status = "okay";
	flash@0 {
		status = "okay";
		label = "bmc";
		m25p,fast-read;  // mtd设备
		spi-max-frequency = <50000000>;
#include "openbmc-flash-layout-64.dtsi"
	};
	flash@1 {
		status = "okay";
		label = "alt-bmc";
		m25p,fast-read;
		spi-max-frequency = <50000000>;
#include "...alt.dtsi"
	};
};

// 增加spi eeprom驱动: CONFIG_EEPROM_AT25=y   // 内核已编译进AT25 EEPROM驱动程序
// 如下在dts中增加spi eeprom device 在spi1.0上:
&spi1 {
	status = "okay";
	compatible = "aspeed,ast2500-spi-master";  // 匹配驱动中设备
	eeprom@0 {
		compatible = "atmel,at25"; // 匹配驱动中设备
		spi-max-frequency = <5000000>;
		reg = <0>;  // 表示 EEPROM 设备在 SPI 总线上的地址
		// 以下是 EEPROM 设备的自定义属性
		at25,addr-mode = <2>;
		at25,page-size = <64>;
		at25,byte-len = <0x8000>;
	};
};

bmc的flash 64M:BOM表(物料表)显示MX25L51245GMI-08G,spi-nor.c没有,查找DS知RDID或JEDEC ID是0xC2201A。ID一致则支持,名字不重要。如果不支持可以用RDID去最新kernel找。

c 复制代码
// linux-aspeed/drivers/mtd/spi-nor/spi-nor.c的spi_nor_ids
{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_QUAD_READ) },

1.1 controller:fmc指定为spi

c 复制代码
// 如下都在linux-aspeed/arch/arm/plat-aspeed/dev-spi-fb.c
static struct platform_device ast_fmc_device = { // 表示一个平台设备,用于注册并使用FMC设备
    .name           = "fmc-spi",
    .id             = 0,  // 添加fmc controller即spi0 controller,id=0
    .dev = {
        .platform_data = &ast_fmc_driver_data,
    },
    .num_resources  = ARRAY_SIZE(ast_fmc_resource),
    .resource       = ast_fmc_resource,
};

1.2 device:主flash是CS0,备是CS1

c 复制代码
// dev-spi-fb.c
// 最高支持100MHz,此处用经验值50MHz,配成SPI_MODE_0
static struct spi_board_info ast_single_flash_fmc_devices[] = {
    {
        .modalias           = "m25p80",
        .platform_data      = &ast_legacy_platform_data,
        .chip_select        = 0,      ///
        .max_speed_hz       = 50 * 1000 * 1000,
        .bus_num            = 0,     // 因为ast_fmc_device指定id=0
        .mode               = SPI_MODE_0,
    },
#if defined(CONFIG_A)
    {
        .modalias           = "m25p80",
        .platform_data      = &ast_legacy_rom_platform_data,
        .chip_select        = 1,    ///
        .max_speed_hz       = 50 * 1000 * 1000,
        .bus_num            = 0,    ///
        .mode               = SPI_MODE_0,
    },
#endif
};

static struct flash_platform_data ast_legacy_platform_data = { // 初始化设备data填充
    .type       = "mx25l25635e",
    .nr_parts   = ARRAY_SIZE(ast_legacy_partitions),
    .parts      = ast_legacy_partitions,
};
static struct flash_platform_data ast_legacy_rom_platform_data = {
    .type       = "mx25l25635e",
    .nr_parts   = ARRAY_SIZE(ast_legacy_rom_partitions),
    .parts      = ast_legacy_rom_partitions,
};

static struct mtd_partition ast_legacy_partitions[] = {  // 主分区
    ...
    {
        .name       = "flash0",  /* 用来flashcp升级 */
        .offset     = 0,        /* From 0 */
        .size       = MTDPART_SIZ_FULL,  /* full size */
    },
};
static struct mtd_partition ast_legacy_rom_partitions[] = {  // 备分区省略同上
        .name       = "u-bootro",
        //.mask_flags = MTD_WRITEABLE,
        .name       = "envro",
        .name       = "kernelro",
        .name       = "rootfsro",
        .name       = "dataro",
        .name       = "flashro", }

1.3 添加controller&device:spi0(bus).0(cs)

c 复制代码
// dev-spi-fb.c
void __init ast_add_device_spi(void)
{
    platform_device_register(&ast_fmc_device);  // 添加controller设备
    spi_register_board_info(ast_single_flash_fmc_devices, ARRAY_SIZE(ast_single_flash_fmc_devices)); // 添加bmc flash设备,modalias与m25p80.c(spi和flash的结合,如果spi挂的eeprom就不能用这驱动)驱动中id_table设备match后走probe。probe中调用spi_nor_scan(spi-nor.c提供此函数去读flash的id,不是名字),如果match到spi-nor.c中spi_nor_ids则进行最后分区(都在m25p80.c中probe函数里)。

// dmesg打印找到主/备两块flash:m25p80驱动(用以创建flash的MTD分区),mx66l51235l设备
[   27.495927] m25p80 spi0.0: found mx66l51235l 
[   27.500242] m25p80 spi0.0: mx66l51235l (65536 Kbytes)
[   27.514884] Creating 6 MTD partitions on "spi0.0":
[   27.519707] 0x000000000000-0x000000060000 : "u-boot"
[   27.526484] 0x000000060000-0x000000080000 : "env"
[   27.532818] 0x000000080000-0x000000480000 : "kernel"
[   27.539590] 0x000000480000-0x000001f80000 : "rootfs"
[   27.546407] 0x000001f80000-0x000004000000 : "data0"
[   27.552978] 0x000000000000-0x000004000000 : "flash0"
[   27.559866] m25p80 spi0.1: found mx66l51235l
[   27.564275] m25p80 spi0.1: mx66l51235l (65536 Kbytes)
[   27.578758] Creating 6 MTD partitions on "spi0.1":
...
[   27.617713] 0x000000000000-0x000004000000 : "flashro" // pingpong flash的全片的64MB区域

2.BIOS升级:OCM卡上有两块BIOS Flash,断电重启后默认从Master Flash启动,升级BIOS后需要refresh生效

c 复制代码
// dev-spi-fb.c
static struct spi_board_info ast_spi0_devices[] = { // 从0开始
    {   .modalias           = "spidev",  // 设备直接去匹配spi/spidev.c驱动(不是mtd/spi-nor.c,专用Flash驱动/dev/mtd0)用以创建flash的/dev/spidev1.0(支持flashrom【慢】用的普通SPI controller)
        .chip_select        = 0,
        .max_speed_hz       = 33 * 1000 * 1000,
        .bus_num            = 1,}

spi_register_board_info(ast_spi0_devices, ARRAY_SIZE(ast_spi0_devices)); // 这行是设备,驱动是CONFIG_SPI_SPIDEV=y
shell 复制代码
&spi1 {
	status = "okay";
	m25p,fast-read;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi1_default>;  # gpio引脚复用,不用指定哪个gpio, bmc芯片固定AD13
	flash@0 {
		label = "BIOS_flash";
		status = "okay";
	};
	flash@1 {
		label = "spi1:1";
		status = "okay";
	};
};

# gpioset gpiochip0 147=1
# echo spi1.0 > /sys/bus/spi/drivers/spi-nor/bind
[  162.807166] spi-nor spi1.0: w25q512jv (65536 Kbytes)
[  162.813579] ASPEED_FMC_SPI 1e630000.spi: freq: 50MHz
[  162.819234] ASPEED_FMC_SPI 1e630000.spi: read bus width: 2 [0x203c0641]
[  162.826697] ASPEED_FMC_SPI 1e630000.spi: write bus width: 1 [0x00120602]

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 04000000 00010000 "bmc"
..
mtd6: 08000000 00010000 "alt-bmc"  # 必须有放flash
..
mtd12: 04000000 00010000 "BIOS_flash"

烧录器read 全是 0xff(dd出来或直接hexdump读取)。

如下改版本直接改bb名字,sha256sum编译时会报错进行复制。

shell 复制代码
flash_erase /dev/mtd11 0 0
# 切spi,切cs
 &spi1 {
 	status = "okay";
+	m25p,fast-read;
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_spi1_default>;
 	flash@0 {
 		status = "okay";
-		spi-max-frequency = <25000000>; /* 25MHz */
 		spi-rx-bus-width = <1>; # 不写这2行,flashcp -v 会报Verifying data: 10k/65536k (0%)File does not seem to match flash data. First mismatch at 0x00000000-0x00002800 ,硬件上用的标准单线模式,不写默认其他模式导致数据传输不完整或错位
 		spi-tx-bus-width = <1>;
-		compatible = "spidev";   # CONFIG_SPI_SPIDEV=y
+		label = "BIOS_flash";
 	};
 };

如下框图红框mux默认切到红色CPU0上SPI_Flash通到加载bios spi flash,拿掉绿色port module卡后蓝色mux直通bios spi flash。

如下接着如上左边。

bmc的sgpio通到如下cpld控制输出为下面红框,控制MUX器件(就是框图中红框MUX)切换,cs默认拉低选中。

shell 复制代码
root@bmc-mac564f0f5e9b5a:~# gpioset 2 255=1  # bmc接通bios spi flash
root@bmc-mac564f0f5e9b5a:~# echo spi1.0 > /sys/bus/spi/drivers/spi-nor/bind  
[  198.042020] spi-nor spi1.0: mx25u51245g (65536 Kbytes)
[  198.059377] spi-aspeed-smc 1e630000.spi: No good frequency, using dumb slow  # 删除spi-max-frequency = <25000000>后不会出现这行
[  198.067244] spi-aspeed-smc 1e630000.spi: CE0 tx width:0 [0x00130001]

如下在spi-aspeed-smc.c控制器驱动中。


当前机器200/50=4 , 200M 4分频 就是50M。root@yt:~# dmidecode -t 0

2.1 spi.sh:cpld将spi切到fpga/bios,不能同时升级

shell 复制代码
# root@bmc-oob:/etc/platform/xs9880-8c# cat fpgarom.layout
# 00000000:003FEFFF golden
# 003FF000:003FFFFF timer1
# 00400000:006FFFFF primary
# 00700000:0070003B timer2

#!/bin/bash
layout_file="/etc/platform/__MACHINE__/fpgarom.layout"

get_layout_image_startaddr()
{
    layout_file=$1
    image=$2  # primary/golden
    while read -r line
    do
        cut_ret=$(echo "$line" | cut -d ' ' -f 2)
        if [ "$cut_ret" = "$image" ];then
            startaddr=$(echo "$line" | cut -d ':' -f 1)  # 逐行遍历layout_file
            startaddr=$((16#$startaddr))  # 转16进制
            echo "$startaddr"
            return 0
        fi
    done < "$layout_file"
    return 1
}

# $1: input file size      $2: flash size (目标文件大小(单位为字节))  
# $3: output file path (重命名)  
# 将$1填充为$3  
pad_ff()  
{
    out_file=$3
    pad_size=$(($2 - $1))
    dd if=/dev/zero bs=$pad_size count=1 | tr "\000" "\377" >> "$out_file"
    # 从/dev/zero读一次pad_size大小,并将00转为ff
}

# $1: image_size    $2: storage_sz     $3: image_startaddr    $4: out_file
pad_touchfile()
{
    image_size=$1  # 镜像bin文件大小
    storage_sz=$2   # bin文件烧进去的flash大小
    image_startaddr=$3  # 真实起始地址
    out_file=$4  #备份的.bin(最后要烧录进flash的),下面操作都是对out_file操作
    dd if=/dev/zero bs="$image_startaddr" count=1 | tr "\000" "\377" >> "$out_file"  #头
    dd if="$in_file" bs="$image_size" count=1 >> "$out_file" # 中间
    declare -i left_size="$storage_sz"-"$image_startaddr"-"$image_size"
    dd if=/dev/zero bs="$left_size" count=1 | tr "\000" "\377" >> "$out_file"  #尾
}

resize_file()
{
    in_file=$1     # .bin
    out_file=$2    # /tmp/.bin 做成符合flash大小的bin文件
    spi_no=$3
    image_size=$4
    image_startaddr=$5
    in_file_sz=$(stat -c%s "$in_file")
    storage_sz=0

    if flash_sz=$(get_flash_size "$spi_no");then
        storage_sz=$((flash_sz * 1024))
    else
        echo "debug message: $flash_sz"
        exit 1
    fi
    if [ "$image_size" -eq 0 ] && [ "$image_startaddr" -eq 0 ];then  # 不用layout文件
        if [ "$in_file_sz" -ne $storage_sz ];then   #.bin < flash
            cp "$in_file" "$out_file"   # 拷贝.bin (不动bin原文件)
            pad_ff "$in_file_sz" $storage_sz "$out_file"   #尾追加,只能是bios,或mulit混合
        else
            ln -s "$(realpath "${in_file}")" "${out_file}"  
        fi
    else
        touch "$out_file"
        pad_touchfile "$image_size" $storage_sz "$image_startaddr" "$out_file"
    fi
}

check_fpga_imagesize(){
    if flash_sz=$(get_flash_size "$spi_no");then  # flash_sz   k
        storage_sz=$((flash_sz * 1024))  # B 字节
    else
        echo "debug message: $flash_sz"
        exit 1
    fi
    image_startaddr=$(get_layout_image_startaddr "$layout_file" "$image")  # image_startaddr 字节
    declare -i max_image_size="$storage_sz"-"$image_startaddr"
    file_sz=$(stat -c%s "$file")
    if [ "$file_sz" -gt "$max_image_size" ];then
        echo "The maximum image file size is $max_image_size B."
        exit 1
    fi
}

write_flash_to_file() 
{
    spi_no=$1
    in_file=$2   # .bin 
    image=$3   # primary/golden
    if [ $# -eq 3 ];then
        image_size=$(stat -c%s "$in_file")
        image_startaddr=$(get_layout_image_startaddr "$layout_file" "$image")
        mode="layout"
    else #不用layout文件
        image_size=0
        image_startaddr=0
        mode="normal"
    fi
    tmp_file=$(basename "${in_file}")
    out_file="/tmp/spi${spi_no}_${tmp_file}"
    resize_file "$in_file" "$out_file" "$spi_no" $image_size $image_startaddr
    type=$(get_flash_first_type "$spi_no")
    if [ $mode = "normal" ];then
        if ! flashrom -p linux_spi:dev=/dev/spidev"${spi_no}".0 -w "$out_file" -c "$type";then
            echo "debug cmd: [flashrom -p linux_spi:dev=/dev/spidev${spi_no}.0 -w $out_file -c $type]"
            rm -f "$out_file"
            exit 1
        fi
    elif [ $mode = "layout" ];then
        if ! flashrom -p linux_spi:dev=/dev/spidev"${spi_no}".0 --layout "$layout_file" --image "$image" -w "$out_file" -c "$type";then
            echo "debug cmd: [flashrom -p linux_spi:dev=/dev/spidev${spi_no}.0 --layout $layout_file --image $image -w $out_file -c $type]"
            rm -f "$out_file"
            exit 1
        fi
    fi
}

3.MAC/FPGA升级:flash挂在BMC GPIO下,需实现一个SPI-GPIO controller

这路模拟SPI下挂了三个device通过cs选择,分别是MAC flash、FPGA flash、BCM53134 eeprom。

c 复制代码
 // linux-aspeed/drivers/spi/spi-gpio.c描述了如何快速新建一个SPI-GPIO controller driver
 *  Kconfig support; the C code can be a total of six lines: // 只需要输出下面6行代码
 *		#define DRIVER_NAME	"myboard_spi2"
 *		#define	SPI_MISO_GPIO	119
 *		#define	SPI_MOSI_GPIO	120
 *		#define	SPI_SCK_GPIO	121
 *		#define	SPI_N_CHIPSEL	4
 *		#include "spi-gpio.c"
c 复制代码
// linux-aspeed/drivers/spi/spi-gpio-fb.c
#include <mach/gpio.h>      // arch/arm/mach-aspeed/include/mach/gpio.h包含所需的GPIO宏
#define  SPI_SCK_GPIO   PIN_GPION1   // 根据上图gpio对应
#define  SPI_MISO_GPIO  PIN_GPION2
#define  SPI_MOSI_GPIO  PIN_GPION3
#define  SPI_N_CHIPSEL  2    // 这个SPI下挂了几个flash设备
#define  DRIVER_NAME    "spi-gpio-fb"   // 使用文件名
#include "spi-gpio.c"

3.1 添加controller与device:MAC flash和FPGA flash的SPI线相同,由CPLD mux选通,所以只建一个flash通过mux切换后进行refresh_fpga_mac_mtd_dev()的unbind和bind操作动态识别flash再进行升级操作

shell 复制代码
# gpio或cpld切完cs后,进行如下刷新
refresh_fpga_mac_mtd_dev()
{
    if [ -d /sys/bus/spi/drivers/m25p80/spi2.0 ];then
        echo spi2.0 > /sys/bus/spi/drivers/m25p80/unbind
    fi
    echo spi2.0 > /sys/bus/spi/drivers/m25p80/bind
}

93c46d的地址模式由一根ORG pin决定,EE_ADDR16是EE连线决定,参考SCH(电路图)与DS。 EE_SIZE1K是芯片型号决定,参考DS。

shell 复制代码
# linux-aspeed/drivers/misc/eeprom/eeprom_93xx46.c
static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = {
    .flags = EE_SIZE1K,
    .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ |
          EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH,
# quirks配置芯片的特殊属性,这个属性与型号相关,具体要看linux驱动源码。..SINGLE_WORD_READ指每次SPI读时序只能读单个word,如果读多个word,则只有第一个word是有效的
};
shell 复制代码
# linux-aspeed/arch/arm/plat-aspeed/dev-spi-fb.c , 这路SPI-GPIO还挂载了一块eeprom。 
#if defined(CONFIG_TAMA)
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#include <linux/spi/spi_gpio.h>
#include <linux/eeprom_93xx46.h>
#define SPI_SCK_GPIO             PIN_GPION1
#define SPI_MISO_GPIO            PIN_GPION2
#define SPI_MOSI_GPIO            PIN_GPION3
#define SPI_NUM_CS               2
#define SPI_CS_FLASH             PIN_GPIOP5  ############
#define SPI_CS_EEPROM            PIN_GPION4  ############
#endif

struct eeprom_93xx46_platform_data spi_eeprom_data = {
    .flags   =  EE_ADDR16 | EE_SIZE1K,
    .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH,
    .prepare = NULL,
    .finish  = NULL,
};

################################################################
struct spi_gpio_platform_data spi_gpio_info = {   # 将gpio与spi总线进行绑定以实现软件模拟SPI接口
    .sck = SPI_SCK_GPIO,  # 定义spi时钟引脚所对应gpio引脚
    .mosi = SPI_MOSI_GPIO,
    .miso = SPI_MISO_GPIO,
    .num_chipselect = SPI_NUM_CS,
};
struct platform_device ast_spi_gpio_device_plat = {
        .name          = "spi-gpio-fb",
        .id            = 2,    # 指定controller为2总线,即虚拟了一个SPI2,当然也可以写成别的数字
        .dev    = {
            .platform_data  = &spi_gpio_info,
    },
};

#################################################################
static struct mtd_partition spi_flash_partitions[] = {  # flash的分区表,由FPGA提供,size有字节对齐规则,配不对则无法烧录,从fpga_primary烧录FPGA primary FW,从extflash烧录MAC FW(即PCIe FW)或FPGA multi boot FW
    {
        .name       = "fpga_gloden",
        .offset     = 0,                   /* From 0 */
        .size       = 0x127000,             /* Size 4092K */
        .mask_flags = MTD_WRITEABLE,
    }, {
        .name       = "fpga_timer1",
        .offset     = 0x37F000,
        .size       = 0x1000,
    }, {
        .name       = "fpga_primary",
        .offset     = 0x380000,
        .size       = 0x130000,
    }, {
        .name       = "fpga_timer2",
        .offset     = 0x700000,
        .size       = 0x1000,
    }, {
        .name       = "extflash",
        .offset     = 0,                   /* From 0 */
        .size       = MTDPART_SIZ_FULL,    /* full size */
    },
};
static struct flash_platform_data spi_flash_data = {
    .type       = "mx25l25635e",
    .nr_parts   = ARRAY_SIZE(spi_flash_partitions),
    .parts      = spi_flash_partitions,
};
static struct spi_board_info ast_spi_gpio_device_info[] = { # 这里最主要,需要填充的,在上面定义
    {
        .modalias               = "m25p80",
        .platform_data          = &spi_flash_data,
        .mode                   = SPI_MODE_0,  # 一般配成这样,EE若有要求可以修改
        .bus_num                = 2, # controller的id(ast_spi_gpio_device_plat 的id = 2)
        .chip_select            = 0,
        .controller_data        = SPI_CS_FLASH,  # 上面定义了,实际做片选的GPIO
        .max_speed_hz           = 16.5 * 1000 * 1000, # 参考DS,用GPIO做的效率低,SIV实际测量的频率是百KHz级别
    },
    {
        .modalias               = "93xx46",
        .mode                   = SPI_MODE_0 | SPI_CS_HIGH,   # CS高有效
        .bus_num                = 2,
        .chip_select            = 1,
        .controller_data        = SPI_CS_EEPROM,
        .max_speed_hz           = 200 * 1000,
        .platform_data          = &spi_eeprom_data,  # 从93xx46的驱动看,初始化需要一个platform_data
    },
};

####################################################################
    platform_device_register(&ast_spi_gpio_device_plat);  # 注册controller
    spi_register_board_info(ast_spi_gpio_device_info, ARRAY_SIZE(ast_spi_gpio_device_info));  # 注册device

4.BCM53134升级:eeprom字节序在controller配置,默认大端序并不能正常升级,但因为controller代码spi-gpio.c移植难度大,考虑手翻转image字节后再烧进eeprom

当前kernel 4.1.51版本有96xx46.c驱动,但是不支持本产品用的93c46d型号,具体的不支持点是read函数部分采取了多word读取,hexdump eeprom时的表现,而93c46d只支持单word读取,参考最新linux源码进行打patch:https://github.com/torvalds/linux/blob/master/drivers/misc/eeprom/eeprom_93xx46.c。

shell 复制代码
byte_revert()
{
    file=$1
    file_size=$(ls -l $file|awk '{print $5}')
    numodd="0"
    numeven="0"
    odd=()
    even=()
    for((i=0;i<"$file_size";i++))
    {
        bin=$(hexdump $file -n 1 -s "$i"|head -n 1|awk '{print $2}')
        if [ -z "$bin" ];then
            bin="0000"
        fi

        if [ $((i%2)) = "0" ]; then
            even[$numeven]="\x${bin:2}"
            ((numeven++))
        else
            odd[$numodd]="\x${bin:2}"
            ((numodd++))
        fi
    }

    out=""
    for((i=0;i<"$numodd";i++))
    {
        out=${out}"${odd[$i]}${even[$i]}"
    }
    filename=$(basename "$file")
    out_file="/tmp/bmc_53134_${filename}"
    echo -e -n $out > "$out_file"
}
相关推荐
HLC++13 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室14 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy16 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕17 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫18 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生19 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1120 小时前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器