dw-spi-mmio控制器驱动

需要知道:SPI大致分为了以下三个层次,分别是SPI核心,SPI控制器驱动,SPI设备驱动。

SPI核心层代码位于kernel/include/linux/spi/spi.hkernel/drivers/spi/spi.c,核心层提供了SPI总线驱动和设备驱动的注册,注册方法,并提供一些控制器驱动实现的回调函数。核心层包含一些重要的结构体,struct spi_controller,struct spi_transfer,struct spi_message等。

控制器驱动就是本文核心。主要实现SPI硬件数据收发功能,只有这样,挂接在SPI总线上的SPI设备才能通过SPI控制器读写数据并与CPU进行数据交互。

SPI设备驱动一般挂接在受CPU控制的SPI控制器上,通过SPI控制器与CPU进行数据交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
graph TD 
subgraph 用户空间层
U3[应用程序]
end
subgraph 内核空间层
K4[SPI设备驱动]
K3[SPI设备驱动]
K2[SPI核心]
K1[SPI控制器驱动]
end
subgraph 硬件层
H1[SPI控制器]
H2[SPI设备1]
H3[SPI设备2]
end
U3 --> K4
U3 --> K3
K4 --> K2
K3 --> K2
K2 --> K1
K1 --> H1
H1 --> H2
H1 --> H3

设备树配置

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
spi0: spi@f0383000 {
compatible = "snps,dw-apb-ssi";
status = "disabled";
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
reg = <0x0 0xf0383000 0x0 0x1000>;
reg-io-width = <4>;
num-cs = <1>;
dmas = <&dw_axi_dmac 0x1 0 0 &dw_axi_dmac 0x0 0 0>;
dma-names = "tx", "rx";
};
&spi0 {
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
// pinctrl-names = "default";
// pinctrl-0 = <&spi0_pinctrl &gpiob1_pinctrl>;
cs-gpios = <&portb 1 GPIO_ACTIVE_LOW>;
spi0_flash0: wnbnd0@0 {
compatible = "jedec,spi-nor";
spi-max-frequency = <400000>;
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
partition@spi-test0{
label = "test0";
reg = <0x0 0x800000>; /* 8MB */
};
};
};

spi0控制器下挂载了spi0_flash的flash。

spi0控制器对应的文件为:kernel/drivers/spi/spi-dw-mmio.c

与dw相关的驱动文件还有kernel/drivers/spi/spi-dw-core.c kernel/drivers/spi/spi-dw-dma.c

spi0_flash0对应的驱动文件为:kernel/drivers/mtd/devices/m25p80.c

与flash相关的重要驱动文件有kernel/drivers/mtd/spi-nor/spi-nor.c,实现了spi_nor_scan,spi_nor_setup。在spi-nor.h中只有spi_nor_scan开放出来了,因此这个函数需要重点分析

关于片选引脚:

  • 在spi_device中有成员:chip_select,表示spi_master下的第几个设备

  • 在spi_controller中有一个cs_gpios数组,里面存放有下面各个spi设备的片选引脚

  • spi_device的片选引脚就是:cs_gpios[spi_device.chip_select]

  • 在spi_device中cs_gpio是可选项,也可以把spi_device的片选引脚记录在这里

  • 在spi_controller中的是cs_gpios数组

SPI控制器驱动

spi-dw-mimo.c注册的是平台驱动

SPI 控制器驱动(platform 驱动类型),负责匹配设备树中的 SPI 控制器节点(spi@f0383000)

1
2
3
4
static const struct of_device_id dw_spi_mmio_of_match[] = {
{ .compatible = "snps,dw-apb-ssi", .data = dw_spi_dw_apb_init},
{ /* end of table */}
};
  • compatible字段:这是匹配的 “关键字”,与设备树中控制器节点的compatible属性完全对应。设备树中spi@f0383000compatible = "snps,dw-apb-ssi",与第一个条目匹配。
  • .data字段:可选的附加数据,这里指向dw_spi_dw_apb_init函数,用于初始化该类型控制器的特定参数(这里实现的是DMA配置实现)
1
2
3
4
5
6
7
8
9
10
static struct platform_driver dw_spi_mmio_driver = {
.probe = dw_spi_mmio_probe, // 设备匹配成功后执行的初始化函数
.remove = dw_spi_mmio_remove, // 设备移除时执行的清理函数
.driver = {
.name = DRIVER_NAME, // 驱动名称(辅助匹配,优先级低于compatible)
.of_match_table = dw_spi_mmio_of_match, // 关联设备树匹配表
.acpi_match_table = ACPI_PTR(dw_spi_mmio_acpi_match), // 关联ACPI匹配表(用于非设备树系统)
.pm = SPI_DEV_PM_OPS, // 电源管理操作集
},
};

SPI 控制器是直接挂在 CPU 总线上的设备(如 APB 总线),属于platform设备类型(内核对 “挂在总线上的设备” 的抽象),因此其驱动需用platform_driver结构体进行注册

SPI从设备驱动

SPI 从设备驱动(spi_driver 类型),负责匹配 SPI 总线上的从设备(即wnbnd0@0 flash)

这里只列出通过spi总线访问nor flash的情况,对于通过qspi控制器访问nor flash暂时还没细看

1
2
3
4
5
6
7
8
9
static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80", // 驱动名称
.of_match_table = m25p_of_table, // 关联设备树匹配表
},
.id_table = m25p_ids, // 基于SPI设备名称的匹配表(旧方式,优先级低于of_match_table)
.probe = m25p_probe, // 设备匹配成功后执行的初始化函数
.remove = m25p_remove, // 设备移除时的清理函数
};

SPI 闪存是挂在 SPI 总线上的从设备,属于spi设备类型(内核对 “SPI 总线上的设备” 的抽象),因此其驱动需用spi_driver结构体

id_table的作用:早期没有设备树时,通过 SPI 设备的name字段匹配(如name = "m25p80"),现在主要用于兼容旧设备,优先级低于of_match_table

设备与驱动的匹配流程(结合设备树)

  1. 内核启动时扫描设备树
    解析spi@f0383000(控制器)和wnbnd0@0(闪存)节点,分别创建platform_device(控制器设备)和spi_device(闪存设备)结构体,其中包含节点的compatible属性。
  2. 驱动加载时注册匹配信息
    • spi-dw-mmio.ko加载后,通过platform_driver_register()注册dw_spi_mmio_driver,内核将其of_match_table加入全局设备树匹配表。
    • m25p80.ko加载后,通过spi_register_driver()注册m25p80_driver,内核将其of_match_table加入全局设备树匹配表。
  3. 匹配过程
    • 对于spi@f0383000(platform 设备):内核遍历所有platform_driver,发现dw_spi_mmio_driverof_match_tablecompatible = "snps,dw-apb-ssi"与设备一致,触发dw_spi_mmio_probe()初始化控制器。
    • 对于wnbnd0@0(spi 设备):内核遍历所有spi_driver,发现m25p80_driverof_match_tablecompatible = "jedec,spi-nor"与设备一致,触发m25p_probe()初始化闪存。
  4. 匹配成功后
    控制器驱动初始化 SPI 硬件,设备驱动通过 SPI 核心层接口(如spi_sync())与控制器交互,完成数据传输。

SPI DW控制器驱动probe解析

当设备树中的spi节点被解析为paltform_device之后,会与platform_driver.driver->of_device_id中的compatible属性进行match,匹配成功后则会调用probe函数

1
2
3
4
5
static const struct of_device_id dw_spi_mmio_of_match[] = {
{ .compatible = "snps,dw-apb-ssi", .data = dw_spi_dw_apb_init},
{ /* end of table */}
};
MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match);

probe函数调用关系

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
dw_spi_mmio_probe
//分配dw_spi_mmio结构体
struct dw_spi_mmio *dwsmmio = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_mmio),GFP_KERNEL);
//获取寄存器资源 并赋值给paddr
struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dwsmmio->dws.paddr = mem->start;
//进行地址ioremap,并与spi device进行绑定
dwsmmio->dws.regs = devm_ioremap_resource(&pdev->dev, mem);
//设置中断
dwsmmio->dws.irq = platform_get_irq(pdev, 0);
//获取时钟
dwsmmio->clk = devm_clk_get(&pdev->dev, NULL);
dwsmmio->pclk = devm_clk_get(&pdev->dev, "pclk");//optional
dwsmmio->dws.max_freq = clk_get_rate(dwsmmio->clk);
//获取复位控制器
dwsmmio->rstc = devm_reset_control_get_optional_exclusive(&pdev->dev,"spi");
reset_control_deassert(dwsmmio->rstc);

//获取设备树属性
device_property_read_u32(&pdev->dev, "reg-io-width", &dwsmmio->dws.reg_io_width);
device_property_read_u32(&pdev->dev, "num-cs", &dwsmmio->dws.num-cs);

//获取总线num,id代表不同spi实例
dwsmmio->dws.bus_num = pdev->id;

//私有数据 获取struct of_device_id dw_spi_mmio_of_match[0] = {
//{ .compatible = "snps,dw-apb-ssi", .data = dw_spi_dw_apb_init},
//};中的data指针数据,这里是函数指针,指向dw_spi_dw_apb_init。该函数完成DMA的dma_ops操作集的实现配置
init_func = of_device_get_match_data(&pdev->dev);
ret = init_func(pdev, dwsmmio);

/*将dw_spi添加到
* 后续重点分析dw_spi_add_host*/
ret = dw_spi_add_host(&pdev->dev, &dwsmmio->dws);

//将dwsmmio添加到device设备的driver_data中,以共供后续使用
platform_set_drvdata(pdev, dwsmmio);

dma_ops操作集初始化

完成DMA的dma_ops操作集的实现配置,文件位置在kernel/drivers/spi/spi-dw-dma.c可以通过标志位配置是否使用dma传输。

1
2
3
init_func = of_device_get_match_data(&pdev->dev);
ret = init_func(pdev, dwsmmio);
==>static int dw_spi_dw_apb_init(pdev,dwsmmio)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int dw_spi_dw_apb_init(struct platform_device *pdev,
struct dw_spi_mmio *dwsmmio)
{
dw_spi_dma_setup_generic(&dwsmmio->dws);
return 0;
}
static const struct dw_spi_dma_ops dw_spi_dma_generic_ops = {
.dma_init = dw_spi_dma_init_generic,
.dma_exit = dw_spi_dma_exit,
.dma_setup = dw_spi_dma_setup,
.can_dma = dw_spi_can_dma,
.dma_transfer = dw_spi_dma_transfer,
.dma_stop = dw_spi_dma_stop,
};

void dw_spi_dma_setup_generic(struct dw_spi *dws)
{
dws->dma_ops = &dw_spi_dma_generic_ops;
}
EXPORT_SYMBOL_GPL(dw_spi_dma_setup_generic);

SPI DW控制器驱动add_host解析

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
ret = dw_spi_add_host(&pdev->dev, &dwsmmio->dws);
dw_spi_add_host
//分配spi_master结构体
dws->master = spi_alloc_master(dev, 0);

//设置DMA寄存器偏移地址
dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR);
/* Restart the controller, disable all interrupts, clean rx fifo */
spi_hw_init(dev, dws);
//请求中断
ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev),master);

//初始化memory operations成员
dw_spi_init_mem_ops(dws);

//初始化其他成员
dws->master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;
dws->master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16);
dws->master->bus_num = dws->bus_num;
dws->master->num_chipselect = dws->num_cs;
dws->master->setup = dw_spi_setup;
dws->master->cleanup = dw_spi_cleanup;
//控制片选信号(硬件片选或 GPIO 模拟)
if (dws->set_cs)
dws->master->set_cs = dws->set_cs;//GPIO模拟 走这个分支?!
else
dws->master->set_cs = dw_spi_set_cs;//硬件片选
dws->master->transfer_one = dw_spi_transfer_one;
dws->master->handle_err = dw_spi_handle_err;
if (dws->mem_ops.exec_op)//1
dws->master->mem_ops = &dws->mem_ops;
dws->master->max_speed_hz = dws->max_freq;
dws->master->dev.of_node = dev->of_node;
dws->master->dev.fwnode = dev->fwnode;
dws->master->flags = SPI_MASTER_GPIO_SS;//使用GPIO片选标志?
dws->master->auto_runtime_pm = true;

device_property_read_u32(dev, "rx-sample-delay-ns",&dws->def_rx_sample_dly_ns);
// DMA支持:若驱动实现了DMA操作函数(dma_ops),则调用dma_init初始化DMA通道
// dma_ops是在probe的init_func进行实现的
if (dws->dma_ops && dws->dma_ops->dma_init) {
ret = dws->dma_ops->dma_init(dev, dws);
if (ret) {
dev_warn(dev, "DMA init failed\n");
} else {
master->can_dma = dws->dma_ops->can_dma;
master->flags |= SPI_CONTROLLER_MUST_TX;
}

//注册将dws->master注册进spi controller中
ret = spi_register_controller(dws->master);

//文件系统调试
dw_spi_debugfs_init(dws);
//将dws与master绑定,后续驱动逻辑可通过dev_set/get_drvdata(master->dev) 和 spi_controller_get_devdata(master)访问硬件相关的私有配置(如寄存器基地址、中断号等等)
spi_controller_set_devdata(master, dws);

SPI DW控制器驱动dw_spi_transfer_one解析

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
static int dw_spi_transfer_one(struct spi_controller *master,
struct spi_device *spi, struct spi_transfer *transfer)
{
struct dw_spi *dws = spi_controller_get_devdata(master);
struct dw_spi_cfg cfg = {
.tmode = SPI_TMOD_TR,
.dfs = transfer->bits_per_word,
.freq = transfer->speed_hz,
};
int ret;

dws->dma_mapped = 0;
dws->n_bytes = DIV_ROUND_UP(transfer->bits_per_word, BITS_PER_BYTE);
dws->tx = (void *)transfer->tx_buf;
dws->tx_len = transfer->len / dws->n_bytes;
dws->rx = transfer->rx_buf;
dws->rx_len = dws->tx_len;
/* Ensure the data above is visible for all CPUs */
smp_mb();

spi_enable_chip(dws, 0);

dw_spi_update_config(dws, spi, &cfg);

/* Check if current transfer is a DMA transaction */
if (master->can_dma && master->can_dma(master, spi, transfer))
dws->dma_mapped = master->cur_msg_mapped;

/* For poll mode just disable all interrupts */
spi_mask_intr(dws, 0xff);
// DMA模式 对应设备树中 dmas 属性
if (dws->dma_mapped) {
// DMA 传输初始化
ret = dws->dma_ops->dma_setup(dws, transfer);
if (ret)
return ret;
}

spi_enable_chip(dws, 1);

if (dws->dma_mapped)
// 优先 DMA 传输模式
return dws->dma_ops->dma_transfer(dws, transfer);
// 轮询模式
else if (dws->irq == IRQ_NOTCONNECTED)
return dw_spi_poll_transfer(dws, transfer);
// 默认中断传输模式
dw_spi_irq_setup(dws);
return 1;
}

dw_spi_transfer_one实现了SPI传输,有3种模式,DMA模式 poll轮询模式 中断传输模式。在ts芯片中优先使用DMA模式进行传输

SPI DW控制器驱动spi_register_controller解析

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
int spi_register_controller(struct spi_controller *ctlr)
//完成解析cs-gpios属性
status = of_spi_register_master(ctlr);
for (i = 0; i < nb; i++) cs[i] = of_get_named_gpio(ctlr->dev.of_node, "cs-gpios", i);
//
if (ctlr->bus_num >= 0) {
/* 用指定的总线号分配 id */
mutex_lock(&board_lock);
id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,
ctlr->bus_num + 1, GFP_KERNEL);
mutex_unlock(&board_lock);
if (WARN(id < 0, "couldn't get idr"))
return id == -ENOSPC ? -EBUSY : id;
ctlr->bus_num = id;
}
//核心数据初始化
INIT_LIST_HEAD(&ctlr->queue); // 初始化传输请求队列
spin_lock_init(&ctlr->queue_lock); // 保护传输队列的自旋锁
spin_lock_init(&ctlr->bus_lock_spinlock); // 总线锁定的自旋锁
mutex_init(&ctlr->bus_lock_mutex); // 总线锁定的互斥锁
mutex_init(&ctlr->io_mutex); // I/O 操作互斥锁
ctlr->bus_lock_flag = 0; // 总线锁定状态标记
init_completion(&ctlr->xfer_completion); // 传输完成量
if (!ctlr->max_dma_len)
ctlr->max_dma_len = INT_MAX; // DMA 最大传输长度默认值

//注册控制器为内核设备
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num); // 设置设备名(如 "spi0")
status = device_add(&ctlr->dev); // 注册到内核设备模型
if (status < 0) {
/* 注册失败,释放总线号 */
mutex_lock(&board_lock);
idr_remove(&spi_master_idr, ctlr->bus_num);
mutex_unlock(&board_lock);
goto done;
}
//传输队列初始化(驱动模式适配)
if (ctlr->transfer)
dev_info(dev, "controller is unqueued, this is deprecated\n");
else {
status = spi_controller_initialize_queue(ctlr);
if (status) {
device_del(&ctlr->dev);
/* 释放总线号 */
mutex_lock(&board_lock);
idr_remove(&spi_master_idr, ctlr->bus_num);
mutex_unlock(&board_lock);
goto done;
}
}
/* 初始化统计信息锁 */
spin_lock_init(&ctlr->statistics.lock);

/* 将控制器添加到全局链表,并匹配板级设备 */
mutex_lock(&board_lock);
list_add_tail(&ctlr->list, &spi_controller_list); // 加入全局控制器链表
list_for_each_entry(bi, &board_list, list) // 遍历板级设备信息
spi_match_controller_to_boardinfo(ctlr, &bi->board_info); // 匹配设备
mutex_unlock(&board_lock);

of_register_spi_devices(ctlr); // 从设备树注册 SPI 从设备 即M25P80驱动
acpi_register_spi_devices(ctlr); // 从 ACPI 注册 SPI 从设备

SPI DW控制器注册从设备

of_register_spi_devices 遍历控制器设备树节点的子节点(每个子节点代表一个 SPI 从设备),解析 compatiblereg(reg表示片选号,代表第几个spi设备,不是寄存器)、spi-max-frequency 等属性,自动创建 struct spi_device 并注册。

1
2
3
4
5
6
7
8
9
10
spi_register_controller
of_register_spi_devices
of_register_spi_device
/* Alloc an spi_device */
spi = spi_alloc_device(ctlr);
/* Select device driver */
rc = of_modalias_node(nc, spi->modalias,sizeof(spi->modalias));
rc = of_spi_parse_dt(ctlr, spi, nc);
/* Register the new device */
rc = spi_add_device(spi);
1
2
3
4
5
6
7
8
9
10
11
12
13
spi0 {
spi0_flash0: wnbnd0@0 {
compatible = "jedec,spi-nor";
spi-max-frequency = <1000000>;
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
partition@spi-test0{
label = "test0";
reg = <0x0 0x800000>; /* 8MB */
};
};
};

SPI从设备驱动(补充)

针对上一节中SPI从设备驱动的具体补充。由于这个从设备既和spi总线有关,又和mtd子系统有关,同时又从属于spi-nor系统框架之下,因此要想搞明白需要花很大的时间。这里只是简单介绍一些查到的介绍。

前置知识

mtd子系统

MTD设备通常可分为四层 ,设备节点、MTD设备层、MTD原始设备层和硬件驱动层。

硬件驱动层主要文件位于

1
2
3
4
drivers/mtd/chips :   CFI/jedec接口通用驱动 
drivers/mtd/nand : nand通用驱动和部分底层驱动程序
drivers/mtd/maps : nor flash映射关系相关函数
drivers/mtd/spi-nor: nor flash底层驱动

MTD设备层主要文件位于

1
2
mtdchar.c :  MTD字符设备接口相关实现 
mtdblock.c : MTD块设备接口相关实现

设备节点位于/dev/mtdx/dev/mtdblockX,分别是字符设备节点和块设备节点。两者一般是一一对应的,在物理上两者其实是同一个东西,但是在软件上是进行区分的,可以理解为他们是对同一分区的不同描述方法。

  • dev/mtdx:是mtdchar.c注册生成的,支持open,read,write,ioctl等。flash_erase, nanddump等都是对字符节点,通过read,write,ioctl等执行
  • 对应mtdblock.c和mtd_blkdevs.c,是Flash驱动中用add_mtd_partitions()添加MTD设备分区,而生成的对应的块设备
  • MTD块设备驱动程序可以让flash器件伪装成块设备,实际上它通过把整块的erase block放到ram里面进行访问,然后再更新到flash,用户可以在这个块设备上创建通常的文件系统
  • 对于MTD块设备,MTD设备层是不提供ioctl的实现方法的,不能使用nandwrite,flash_eraseall,flash_erase等工具去对/dev/mtdblockN去进行操作
  • mtd-utils工具只能用与/dev/mtdN的MTD字符设备
  • mount、umount命令只对/dev/mtdblockN的MTD块设备有效

需要注意的是:MTD设备既非块设备也不是字符设备,但可以同时提供字符设备和块设备接口来操作它

spi-nor 系统框架

SPI总线只能处理字节流,总线控制器的操作与所连接的特定设备无关。SPI控制器只知道发送或接收字节(Tx和Rx)。因此,我们定义一个新的分层方案,在该方案下,控制器驱动程序知道SPI NOR协议的操作码、寻址和其他细节。

内容越看越多,暂时就整理这么多吧。先把从设备驱动分析一下,mtd子系统和spi nor系统框架单独整理到一个新文件中。

重要结构体

struct spi_nor

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
struct spi_nor {
struct mtd_info mtd;//指向mtd_info结构体,所有的存储设备,都可以挂载到mtd子系统中
struct mutex lock;
struct device *dev;
u32 page_size;//SPI NOR的页面大小
u8 addr_width;// 地址字节数
u8 erase_opcode;//用于擦除扇区的操作码
u8 read_opcode;
u8 read_dummy;
u8 program_opcode;//program操作码
//spi_nor_protocol 1线 4线 8线
enum spi_nor_protocol read_proto;//用于读取操作的SPI协议
enum spi_nor_protocol write_proto;//用于写操作的SPI协议
enum spi_nor_protocol reg_proto;//用于读\写\擦除操作的SPI协议
bool sst_write_second;
u32 flags;
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];

int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);

ssize_t (*read)(struct spi_nor *nor, loff_t from,
size_t len, u_char *read_buf);
ssize_t (*write)(struct spi_nor *nor, loff_t to,
size_t len, const u_char *write_buf);
int (*erase)(struct spi_nor *nor, loff_t offs);

int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);

void *priv;
};

设备树匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
spi0 {
spi0_flash0: wnbnd0@0 {
compatible = "jedec,spi-nor";
spi-max-frequency = <1000000>;
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
partition@spi-test0{
label = "test0";
reg = <0x0 0x800000>; /* 8MB */
};
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static const struct of_device_id m25p_of_table[] = {
/*
* Generic compatibility for SPI NOR that can be identified by the
* JEDEC READ ID opcode (0x9F). Use this, if possible.
*/
{ .compatible = "jedec,spi-nor" },
{}
};

static struct spi_driver m25p80_driver = {
.driver = {
.name = "m25p80", // 驱动名称
.of_match_table = m25p_of_table, // 关联设备树匹配表
},
.id_table = m25p_ids, // 基于SPI设备名称的匹配表(旧方式,优先级低于of_match_table)
.probe = m25p_probe, // 设备匹配成功后执行的初始化函数
.remove = m25p_remove, // 设备移除时的清理函数
};

probe函数

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
static int m25p_probe(struct spi_device *spi)
{
struct flash_platform_data *data;
struct m25p *flash;
struct spi_nor *nor;
struct spi_nor_hwcaps hwcaps = {
.mask = SNOR_HWCAPS_READ |
SNOR_HWCAPS_READ_FAST |
SNOR_HWCAPS_PP,
};
char *flash_name;
int ret;
printk("m25p80: %s\n", __func__);
data = dev_get_platdata(&spi->dev);

flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;

nor = &flash->spi_nor;

/* install the hooks */
nor->read = m25p80_read;
nor->write = m25p80_write;
nor->write_reg = m25p80_write_reg;
nor->read_reg = m25p80_read_reg;

nor->dev = &spi->dev;
spi_nor_set_flash_node(nor, spi->dev.of_node);
nor->priv = flash;

spi_set_drvdata(spi, flash);
flash->spi = spi;

if (spi->mode & SPI_RX_QUAD) {
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;

if (spi->mode & SPI_TX_QUAD)
hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
SNOR_HWCAPS_PP_1_1_4 |
SNOR_HWCAPS_PP_1_4_4);
} else if (spi->mode & SPI_RX_DUAL) {
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;

if (spi->mode & SPI_TX_DUAL)
hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
}

if (data && data->name)
nor->mtd.name = data->name;

/* For some (historical?) reason many platforms provide two different
* names in flash_platform_data: "name" and "type". Quite often name is
* set to "m25p80" and then "type" provides a real chip name.
* If that's the case, respect "type" and ignore a "name".
*/
if (data && data->type)
flash_name = data->type;
else if (!strcmp(spi->modalias, "spi-nor"))
flash_name = NULL; /* auto-detect */
else
flash_name = spi->modalias;

ret = spi_nor_scan(nor, flash_name, &hwcaps);
if (ret)
{
printk("%s %d ret:%d",__func__,__LINE__,ret);
return ret;
}


return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
data ? data->nr_parts : 0);
}

可以按照顺序划分为以下几个部分

  1. 设置struct spi_nor结构体
  2. 设置struct spi_nor_hwcaps结构体
  3. spi_nor_scan注册struct spi_nor结构体

后面需要重点分析spi_nor_scan结构体

  1. mtd device注册

spi_nor_scan**(重点分析)**

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
int spi_nor_scan(struct spi_nor *nor, const char *name,
const struct spi_nor_hwcaps *hwcaps)
{
struct spi_nor_flash_parameter params;
const struct flash_info *info = NULL;
struct device *dev = nor->dev;
struct mtd_info *mtd = &nor->mtd;
struct device_node *np = spi_nor_get_flash_node(nor);
int ret;
int i;
printk("spi_nor_scan start\n");
ret = spi_nor_check(nor);
if (ret)
return ret;

/* Reset SPI protocol for all commands. */
nor->reg_proto = SNOR_PROTO_1_1_1;
nor->read_proto = SNOR_PROTO_1_1_1;
nor->write_proto = SNOR_PROTO_1_1_1;

if (name)
info = spi_nor_match_id(name);
/* Try to auto-detect if chip name wasn't specified or not found */
if (!info)
info = spi_nor_read_id(nor);
if (IS_ERR_OR_NULL(info))
return -ENOENT;

/*
* If caller has specified name of flash model that can normally be
* detected using JEDEC, let's verify it.
*/
if (name && info->id_len) {
const struct flash_info *jinfo;

jinfo = spi_nor_read_id(nor);
if (IS_ERR(jinfo)) {
return PTR_ERR(jinfo);
} else if (jinfo != info) {
/*
* JEDEC knows better, so overwrite platform ID. We
* can't trust partitions any longer, but we'll let
* mtd apply them anyway, since some partitions may be
* marked read-only, and we don't want to lose that
* information, even if it's not 100% accurate.
*/
dev_warn(dev, "found %s, expected %s\n",
jinfo->name, info->name);
info = jinfo;
}
}

mutex_init(&nor->lock);

/*
* Make sure the XSR_RDY flag is set before calling
* spi_nor_wait_till_ready(). Xilinx S3AN share MFR
* with Atmel spi-nor
*/
if (info->flags & SPI_S3AN)
nor->flags |= SNOR_F_READY_XSR_RDY;

/* Parse the Serial Flash Discoverable Parameters table. */
ret = spi_nor_init_params(nor, info, &params);
if (ret)
return ret;

/*
* Atmel, SST, Intel/Numonyx, and others serial NOR tend to power up
* with the software protection bits set
*/

if (JEDEC_MFR(info) == SNOR_MFR_ATMEL ||
JEDEC_MFR(info) == SNOR_MFR_INTEL ||
JEDEC_MFR(info) == SNOR_MFR_SST ||
info->flags & SPI_NOR_HAS_LOCK) {
write_enable(nor);
write_sr(nor, 0);
spi_nor_wait_till_ready(nor);
}

if (!mtd->name)
mtd->name = dev_name(dev);
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;

/* NOR protection support for STmicro/Micron chips and similar */
if (JEDEC_MFR(info) == SNOR_MFR_MICRON ||
info->flags & SPI_NOR_HAS_LOCK) {
nor->flash_lock = stm_lock;
nor->flash_unlock = stm_unlock;
nor->flash_is_locked = stm_is_locked;
}

if (nor->flash_lock && nor->flash_unlock && nor->flash_is_locked) {
mtd->_lock = spi_nor_lock;
mtd->_unlock = spi_nor_unlock;
mtd->_is_locked = spi_nor_is_locked;
}

/* sst nor chips use AAI word program */
if (info->flags & SST_WRITE)
mtd->_write = sst_write;
else
mtd->_write = spi_nor_write;

if (info->flags & USE_FSR)
nor->flags |= SNOR_F_USE_FSR;
if (info->flags & SPI_NOR_HAS_TB)
nor->flags |= SNOR_F_HAS_SR_TB;
if (info->flags & NO_CHIP_ERASE)
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
if (info->flags & USE_CLSR)
nor->flags |= SNOR_F_USE_CLSR;

if (info->flags & SPI_NOR_NO_ERASE)
mtd->flags |= MTD_NO_ERASE;

mtd->dev.parent = dev;
nor->page_size = params.page_size;
mtd->writebufsize = nor->page_size;

if (np) {
/* If we were instantiated by DT, use it */
if (of_property_read_bool(np, "m25p,fast-read"))
params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
else
params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
} else {
/* If we weren't instantiated by DT, default to fast-read */
params.hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
}

/* Some devices cannot do fast-read, no matter what DT tells us */
if (info->flags & SPI_NOR_NO_FR)
params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;

/*
* Configure the SPI memory:
* - select op codes for (Fast) Read, Page Program and Sector Erase.
* - set the number of dummy cycles (mode cycles + wait states).
* - set the SPI protocols for register and memory accesses.
* - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
*/
ret = spi_nor_setup(nor, info, &params, hwcaps);
if (ret)
return ret;

if (nor->addr_width) {
/* already configured from SFDP */
} else if (info->addr_width) {
nor->addr_width = info->addr_width;
} else if (mtd->size > 0x1000000) {
/* enable 4-byte addressing if the device exceeds 16MiB */
nor->addr_width = 4;
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
info->flags & SPI_NOR_4B_OPCODES)
spi_nor_set_4byte_opcodes(nor, info);
else
set_4byte(nor, info, 1);
} else {
nor->addr_width = 3;
}

if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) {
dev_err(dev, "address width is too large: %u\n",
nor->addr_width);
return -EINVAL;
}

if (info->flags & SPI_S3AN) {
ret = s3an_nor_scan(info, nor);
if (ret)
return ret;
}

dev_info(dev, "%s (%lld Kbytes)\n", info->name,
(long long)mtd->size >> 10);

dev_dbg(dev,
"mtd .name = %s, .size = 0x%llx (%lldMiB), "
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20),
mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions);
printk("spi_nor_scan end\n");
if (mtd->numeraseregions)
for (i = 0; i < mtd->numeraseregions; i++)
dev_dbg(dev,
"mtd.eraseregions[%d] = { .offset = 0x%llx, "
".erasesize = 0x%.8x (%uKiB), "
".numblocks = %d }\n",
i, (long long)mtd->eraseregions[i].offset,
mtd->eraseregions[i].erasesize,
mtd->eraseregions[i].erasesize / 1024,
mtd->eraseregions[i].numblocks);
return 0;
}
EXPORT_SYMBOL_GPL(spi_nor_scan);

需要重点分析

几个重要的函数

m25p80_read_reg

1
2
3
4
5
6
7
8
9
10
11
12
static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
int ret;

ret = spi_write_then_read(spi, &code, 1, val, len);
if (ret < 0)
dev_err(&spi->dev, "error %d reading %x\n", ret, code);

return ret;
}

m25p80_write_reg

1
2
3
4
5
6
7
8
9
10
11
static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;

flash->command[0] = opcode;
if (buf)
memcpy(&flash->command[1], buf, len);

return spi_write(spi, flash->command, len + 1);
}

m25p80_write

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
static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
const u_char *buf)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
struct spi_transfer t[3] = {};
struct spi_message m;
int cmd_sz = m25p_cmdsz(nor);
ssize_t ret;

/* get transfer protocols. */
inst_nbits = spi_nor_get_protocol_inst_nbits(nor->write_proto);
addr_nbits = spi_nor_get_protocol_addr_nbits(nor->write_proto);
data_nbits = spi_nor_get_protocol_data_nbits(nor->write_proto);

spi_message_init(&m);

if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
cmd_sz = 1;

flash->command[0] = nor->program_opcode;
m25p_addr2cmd(nor, to, flash->command);

t[0].tx_buf = flash->command;
t[0].tx_nbits = inst_nbits;
t[0].len = cmd_sz;
spi_message_add_tail(&t[0], &m);

/* split the op code and address bytes into two transfers if needed. */
data_idx = 1;
if (addr_nbits != inst_nbits) {
t[0].len = 1;

t[1].tx_buf = &flash->command[1];
t[1].tx_nbits = addr_nbits;
t[1].len = cmd_sz - 1;
spi_message_add_tail(&t[1], &m);

data_idx = 2;
}

t[data_idx].tx_buf = buf;
t[data_idx].tx_nbits = data_nbits;
t[data_idx].len = len;
spi_message_add_tail(&t[data_idx], &m);

ret = spi_sync(spi, &m);
if (ret)
return ret;

ret = m.actual_length - cmd_sz;
if (ret < 0)
return -EIO;
return ret;
}

m25p80_read

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
static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
u_char *buf)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
unsigned int inst_nbits, addr_nbits, data_nbits, data_idx;
struct spi_transfer t[3];
struct spi_message m;
unsigned int dummy = nor->read_dummy;
ssize_t ret;
int cmd_sz;

/* get transfer protocols. */
inst_nbits = spi_nor_get_protocol_inst_nbits(nor->read_proto);
addr_nbits = spi_nor_get_protocol_addr_nbits(nor->read_proto);
data_nbits = spi_nor_get_protocol_data_nbits(nor->read_proto);

/* convert the dummy cycles to the number of bytes */
dummy = (dummy * addr_nbits) / 8;

if (spi_flash_read_supported(spi)) {
struct spi_flash_read_message msg;

memset(&msg, 0, sizeof(msg));

msg.buf = buf;
msg.from = from;
msg.len = len;
msg.read_opcode = nor->read_opcode;
msg.addr_width = nor->addr_width;
msg.dummy_bytes = dummy;
msg.opcode_nbits = inst_nbits;
msg.addr_nbits = addr_nbits;
msg.data_nbits = data_nbits;

ret = spi_flash_read(spi, &msg);
if (ret < 0)
return ret;
return msg.retlen;
}

spi_message_init(&m);
memset(t, 0, sizeof(t));

flash->command[0] = nor->read_opcode;
m25p_addr2cmd(nor, from, flash->command);

t[0].tx_buf = flash->command;
t[0].tx_nbits = inst_nbits;
t[0].len = m25p_cmdsz(nor) + dummy;
spi_message_add_tail(&t[0], &m);

/*
* Set all dummy/mode cycle bits to avoid sending some manufacturer
* specific pattern, which might make the memory enter its Continuous
* Read mode by mistake.
* Based on the different mode cycle bit patterns listed and described
* in the JESD216B specification, the 0xff value works for all memories
* and all manufacturers.
*/
cmd_sz = t[0].len;
memset(flash->command + cmd_sz - dummy, 0xff, dummy);

/* split the op code and address bytes into two transfers if needed. */
data_idx = 1;
if (addr_nbits != inst_nbits) {
t[0].len = 1;

t[1].tx_buf = &flash->command[1];
t[1].tx_nbits = addr_nbits;
t[1].len = cmd_sz - 1;
spi_message_add_tail(&t[1], &m);

data_idx = 2;
}

t[data_idx].rx_buf = buf;
t[data_idx].rx_nbits = data_nbits;
t[data_idx].len = min3(len, spi_max_transfer_size(spi),
spi_max_message_size(spi) - cmd_sz);
spi_message_add_tail(&t[data_idx], &m);

ret = spi_sync(spi, &m);
if (ret)
return ret;

ret = m.actual_length - cmd_sz;
if (ret < 0)
return -EIO;
return ret;
}