文章目录
- 1.协议:芯片和芯片间通讯,4个pin,M(master)I(input)
- 2.spi总线的验证方法:如下测得返回也是1
- 3.spi控制器驱动:波形是由spi控制器拉出,dev节点进行数据发送和接收
-
- [3.1 platform_get_resource:获取pdev resource(probe函数传入pdev,设备树中所有节点被转换为platform_device,设备树中reg和interrupts属性转为resource,reg是索引0,interrupts是1)](#3.1 platform_get_resource:获取pdev resource(probe函数传入pdev,设备树中所有节点被转换为platform_device,设备树中reg和interrupts属性转为resource,reg是索引0,interrupts是1))
- 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)](#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))
- 6.spidev的使用(spi用户态api):spidev.c访问spi_device进而访问spi_master里传输函数访问硬件
- 7.使用spidev操作SPI_DAC模块:转为模拟信号
- [8.衍生协议:Dual/Quad SPI(只针对SPI Flash而言,不是针对所有SPI外设)](#8.衍生协议:Dual/Quad SPI(只针对SPI Flash而言,不是针对所有SPI外设))
- 9.spi设备驱动:spi_write传入的buf和len构造一个spi_transfer
- 10.spi_master驱动:对每个设备都可以构造一个spi_message
- [1.BMC升级:BMC用的flash是nor flash直连BMC的FMC](#1.BMC升级:BMC用的flash是nor flash直连BMC的FMC)
-
- [1.1 controller:fmc指定为spi](#1.1 controller:fmc指定为spi)
- [1.2 device:主flash是CS0,备是CS1](#1.2 device:主flash是CS0,备是CS1)
- [1.3 添加controller&device:spi0(bus).0(cs)](#1.3 添加controller&device:spi0(bus).0(cs))
- [2.BIOS升级:OCM卡上有两块BIOS Flash,断电重启后默认从Master Flash启动,升级BIOS后需要refresh生效](#2.BIOS升级:OCM卡上有两块BIOS Flash,断电重启后默认从Master Flash启动,升级BIOS后需要refresh生效)
- [3.MAC/FPGA升级:flash挂在BMC GPIO下,需实现一个SPI-GPIO controller](#3.MAC/FPGA升级:flash挂在BMC GPIO下,需实现一个SPI-GPIO controller)
-
- [3.1 添加controller与device:MAC flash和FPGA flash的SPI线相同,由CPLD mux选通,所以只建一个flash通过mux切换后进行refresh_fpga_mac_mtd_dev()的unbind和bind操作动态识别flash再进行升级操作](#3.1 添加controller与device:MAC flash和FPGA flash的SPI线相同,由CPLD mux选通,所以只建一个flash通过mux切换后进行refresh_fpga_mac_mtd_dev()的unbind和bind操作动态识别flash再进行升级操作)
- 4.BCM53134升级:eeprom字节序在controller配置,默认大端序并不能正常升级,但因为controller代码spi-gpio.c移植难度大,考虑手翻转image字节后再烧进eeprom
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实现同时读写,读和写的长度是一样的,注释了xfer[1]。

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

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

同时写和读,只能用ioctl,先传输tx_buf[0](存入高字节数据),后传输tx_buf[1](存入低字节数据)。如下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"
}