目录
1. sf probe命令的执行流程
uboot下sf命令:sf probe 1000000 0x2103
do_spi_flash() [cmd/sf.c]
|->do_spi_flash_probe() [cmd/sf.c]
|->spi_flash_probe_bus_cs() [driver/mtd/spi/sf-uclass.c]
|->spi_get_bus_and_cs()[drivers/spi/spi-uclass.c]
重点看下这个函数:spi_get_bus_and_cs(busnum, cs, max_hz, spi_mode,"jedec_spi_nor", str, &bus, &slave);
1.1 查找dev
uclass_get_device_by_seq(UCLASS_SPI, busnum, &bus)[driver/core/uclass.c]
|->uclass_find_device_by_seq(id, seq, false, &dev)[driver/core/uclass.c]
|->uclass_foreach_dev(dev, uc)[include/dm/uclass.h]
if ((find_req_seq ? dev->req_seq : dev->seq) ==seq_or_req_seq) {
*devp = dev;
log_debug(" - found\n");
return 0;
}
[drivers/spi/spi-uclass.c]
UCLASS_DRIVER(spi) = {
.id = UCLASS_SPI,
.name = "spi",
.flags = DM_UC_FLAG_SEQ_ALIAS,
...
};
|->spi_find_chip_select(bus, cs, &dev)[drivers/spi/spi-uclass.c]
在UCLASS_SPI对应的udevice列表中查找符合seq的udevice,其中,uclass_foreach_dev(dev, uc)是一个宏,会创建一个 for() 循环,该循环按从头到尾的顺序遍历uclass中的可用设备。
1.2 绑定dev
device_bind_driver(bus, drv_name, dev_name, &dev)[drivers/core/lists.c]
|->device_bind_driver_to_node()
|->device_bind_with_driver_data()[drivers/core/device.c]
|->device_bind_common()
|->uclass_get(drv->id, &uc)
|->calloc(1, sizeof(struct udevice))
|->dev->driver = drv
|->dev->req_seq = -1
|->if (CONFIG_IS_ENABLED(DM_SEQ_ALIAS) && (uc->uc_drv->flags &
DM_UC_FLAG_SEQ_ALIAS))
|->if(CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA))
|->if (uc->uc_drv->name && ofnode_valid(node))
dev_read_alias_seq(dev, &dev->req_seq);
一些设备,例如 SPI 总线、I2C 总线和串行端口使用别名编号,"DM_UC_FLAG_SEQ_ALIAS"就是配置的别名,有别名的设备,会分配一个reg_seq。
1.3 probe dev
device_probe(dev)[drivers/core/device.c]
|->drv = dev->driver;
|->drv->probe(dev);
即spi_flash_std_probe()[drivers/mtd/spi/sf_probe.c]
|->spi_flash_probe_slave(flash)
[drivers/mtd/spi/sf_probe.c]
static const struct udevice_id spi_flash_std_ids[] = {
{ .compatible = "jedec,spi-nor" },
{ }
};
U_BOOT_DRIVER(jedec_spi_nor) = {
.name = "jedec_spi_nor",
.id = UCLASS_SPI_FLASH,
.of_match = spi_flash_std_ids,
.probe = spi_flash_std_probe,
.remove = spi_flash_std_remove,
.priv_auto_alloc_size = sizeof(struct spi_flash),
.ops = &spi_flash_std_ops,
};
[arch/arm/dts/*.dts]
&qspi {
status = "okay";
page-size = <256>;
block-size = <16>;
#address-cells = <1>;
#size-cells = <0>;
spi-flash@0 {
compatible = "jedec,spi-nor";
spi-max-frequency = <1000000>;
reg = <1>; /* CS1 */
};
由compatible中的字符串"jedec_spi_nor"匹配到dts文件中spi-flash子节点,从而调用到spi_flash_std_probe()函数,进而调用spi_flash_probe_slave(flash),可以probe总线上的一个SPI flash设备。
接下来看下这个spi_flash_probe_slave(flash)函数;
spi_flash_probe_slave()
|->spi_claim_bus(spi)[drivers/spi/spi-uclass.c]
|->dm_spi_claim_bus(slave->dev)
spi_set_speed_mode(bus, speed, slave->mode)
|->spi_get_ops(bus)
|->ops->set_speed(bus, speed)
即cadence_spi_set_speed()[drivers/spi/cadence_qspi.c]
|->ops->set_mode(bus, mode)
即cadence_spi_set_mode()[drivers/spi/cadence_qspi.c]
|->spi_nor_scan()[drivers/mtd/spi/spi-nor-core.c]
spi_claim_bus()函数主要就是设置spi flash设备的速率和模式;
主要看下spi_nor_scan()函数;
|->nor->reg_proto = SNOR_PROTO_1_1_1;
|->nor->read_proto = SNOR_PROTO_1_1_1;
|->nor->write_proto = SNOR_PROTO_1_1_1;
|->nor->read = spi_nor_read_data;
|->nor->write = spi_nor_write_data;
|->nor->read_reg = spi_nor_read_reg;
|->nor->write_reg = spi_nor_write_reg;
...
1.3.1 为所有命令重置 SPI 协议,并且连接SPI层的read、write、read_reg、write_reg的操作接口。
|->spi_nor_read_id(nor)
|->spi_nor_init_params(nor, info, ¶ms)
|->spi_nor_set_read_settings()
|->spi_nor_set_pp_settings()
|->spi_nor_parse_sfdp()
[drivers/mtd/spi/spi-nor-core.c]
info = spi_nor_ids;//白名单数组
for (; info->name; info++) {
if (info->id_len) {
if (!memcmp(info->id, id, info->id_len))
return info;
}
}
1.3.2 读出flash设备的jedec id,读出后会与白名单数组进行比较,并找到与之匹配的一项,之后初始化flash设备的参数,包括快速读取设置,页面程序设置以及解析串行flash可发现参数(sfdp),用来获取erase、read及write的操作码(opcode)和协议(protocol)。
对于串行flash可发现参数(sfdp),JEDEC JESD216 规范描述了串行闪存可发现参数。 这是几乎所有(Q)SPI存储器制造商都支持的标准。 这些硬编码表允许我们在运行时了解执行基本SPI闪存操作(例如快速读取、页面编程或扇区擦除命令)所需的主要参数。
|->if (!mtd->name)
mtd->name = info->name;
|->mtd->priv = nor;
|->mtd->type = MTD_NORFLASH;
|->mtd->writesize = 1;
|->mtd->flags = MTD_CAP_NORFLASH;
|->mtd->size = params.size;
|->mtd->_erase = spi_nor_erase;
|->mtd->_read = spi_nor_read;
|->mtd->_write = spi_nor_write;
1.3.3 连接MTD层的erase、read、write接口。
|->spi_nor_setup(nor, info, ¶ms, &hwcaps)
|->spi_nor_select_read(nor, params, shared_mask)
|->spi_nor_select_pp(nor, params, shared_mask)
|->spi_nor_select_erase(nor, info)
1.3.4 配置SPI内存,包括为快速读取、页面编程和扇区擦除选择操作码,设置虚拟周期数(模式周期 + 等待状态),设置寄存器和内存访问的 SPI 协议,如果需要支持4线模式,还需设置 Quad Enable 位。
if (nor->addr_width) {
} else if (info->addr_width) {
nor->addr_width = info->addr_width;
} else if (mtd->size > SZ_16M) {
nor->addr_width = 4;
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
info->flags & SPI_NOR_4B_OPCODES)
|->spi_nor_set_4byte_opcodes(nor, info);
[drivers/mtd/spi/spi-nor-ids.c]
const struct flash_info spi_nor_ids[] = {
{ INFO("mt35xu512aba", 0x2c5b1a, 0, 128 * 1024, 512, USE_FSR | SPI_NOR_OCTAL_READ |
SPI_NOR_4B_OPCODES) },
...
}
1.3.5 如果flash设备大于16MB,就需要启用4字节寻址,在函数spi_nor_set_4byte_opcodes(nor, info)中执行指令切换到4字节指令,需要注意的是,在白名单数组中,对于大于16MB的flash设备的配置中,需要在flags项中配置上宏定义SPI_NOR_4B_OPCODES,只有配置上该宏定义才会切换到4字节寻址。
|->spi_nor_init(nor)
|->printf("SF: Detected %s with page size ", nor->name);
print_size(nor->page_size, ", erase size ");
print_size(nor->erase_size, ", total ");
print_size(nor->size, "");
puts("\n");
//例如:SF: Detected gd25lq128d with page size 256 Bytes, erase size 4 KiB, total 16 MiB
1.3.6 发送所有必须的SPI flash命令以初始化flash设备。
2. read id和read data流程
首先看下qspi控制器相关的数据结构的初始化和与之匹配的dts节点,这样可以方便追踪read id和read data流程。
[drivers/spi/cadence.qspi.c]
static const struct spi_controller_mem_ops cadence_spi_mem_ops = {
.exec_op = cadence_spi_mem_exec_op,
};
static const struct dm_spi_ops cadence_spi_ops = {
.set_speed = cadence_spi_set_speed,
.set_mode = cadence_spi_set_mode,
.mem_ops = &cadence_spi_mem_ops,
};
static const struct udevice_id cadence_spi_ids[] = {
{ .compatible = "cdns,qspi-nor" },
{ .compatible = "ti,am654-ospi" },
{ }
};
U_BOOT_DRIVER(cadence_spi) = {
.name = "cadence_spi",
.id = UCLASS_SPI,
.of_match = cadence_spi_ids,
.ops = &cadence_spi_ops,
.ofdata_to_platdata = cadence_spi_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct cadence_spi_platdata),
.priv_auto_alloc_size = sizeof(struct cadence_spi_priv),
.probe = cadence_spi_probe,
.remove = cadence_spi_remove,
.flags = DM_FLAG_OS_PREPARE,
};
qspi: spi@f1000000 {
compatible = "cdns,qspi-nor";
status = "disabled";
reg = <0xf1000000 0x4000>,
<0xe0000000 0x10000000>;
num-cs = <4>;
cdns,fifo-depth = <256>;
cdns,fifo-width = <4>;
cdns,trigger-address = <0xe0000000>;
/*cdns,is-decoded-cs = <0>;*/
};
2.1 read id流程
spi_nor_read_id(nor)[drivers/mtd/spi/spi-nor-core.c]
|->nor->read_reg()
即spi_nor_read_reg()
|->struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(len, NULL, 1));
|->spi_nor_read_write_reg(nor, &op, val)
|->spi_mem_exec_op(nor->spi, op)[drivers/spi/spi-mem.c]
|->ops->mem_ops->exec_op(slave, op)
即cadence_spi_mem_exec_op()[drivers/spi/cadence.qspi.c]
|->cadence_qspi_apb_command_read(base, op)[drivers/spi/cadence_qspi_apb.c]
|->cadence_qspi_apb_exec_flash_cmd(reg_base, reg)
2.2 read data流程
nor->read()
即spi_nor_read_data()[drivers/mtd/spi/spi-nor-core.c]
|->struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
SPI_MEM_OP_DATA_IN(len, buf, 1));
|->spi_mem_exec_op(nor->spi, &op)[drivers/spi/spi-mem.c]
|->ops->mem_ops->exec_op(slave, op)
即cadence_spi_mem_exec_op()[drivers/spi/cadence.qspi.c]
|->cadence_qspi_apb_read_setup(plat, op)[drivers/spi/cadence_qspi_apb.c]
|->cadence_qspi_apb_read_execute(plat, op)
|->memcpy_fromio(buf, plat->ahbbase + from, len)