uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发

2023-11-20

一、综述

本文通过如何通过编写特定板子的spi master驱动从而识别到spi norflash设备,完成norflash设备的读写。

二、UCLASS架构解析

2.1 uclass

uclass可以理解为一些具有相同属性的udevice对外操作的接口,uclass的驱动是uclass_driver,主要为上层提供接口。
udevice的是指具体设备的抽象,对应驱动是driver,driver主要负责和硬件通信,为uclass提供实际的操作集。
udevice找到对应的uclass的方式主要是通过:udevice对应的driver的id和uclass对应的uclass_driver的id是否匹配。
udevice会和uclass绑定。driver会和udevice绑定。uclass_driver会和uclass绑定。
在这里插入图片描述
uclass和udevice都是动态生成的。在解析fdt中的设备的时候,会动态生成udevice。
然后找到udevice对应的driver,通过driver中的uclass id得到uclass_driver id。从uclass链表中查找对应的uclass是否已经生成,没有生成的话则动态生成uclass。

2.2 udevice

连接到对应uclass中
也就是会连接到uclass->dev_head中
连接到父设备的子设备链表中
也就是会连接到udevice->child_head中,并且最终的根设备是gd->dm_root这个根设备。

struct uclass {
    void *priv;                             //uclass的私有数据
    struct uclass_driver *uc_drv;           //uclass类的操作函数集合
    struct list_head dev_head;              //该uclass的所有设备
    struct list_head sibling_node;          //下一个uclass的节点
};

2.3 uclass driver

主要函数:

struct uclass_driver {
    const char *name; // 该uclass_driver的命令
    enum uclass_id id; // 对应的uclass id
/* 以下函数指针主要是调用时机的区别 */
    int (*post_bind)(struct udevice *dev); // 在udevice被绑定到该uclass之后调用
    int (*pre_unbind)(struct udevice *dev); // 在udevice被解绑出该uclass之前调用
    int (*pre_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之前调用
    int (*post_probe)(struct udevice *dev); // 在该uclass的一个udevice进行probe之后调用
    int (*pre_remove)(struct udevice *dev);// 在该uclass的一个udevice进行remove之前调用
    int (*child_post_bind)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备被绑定到该udevice之后调用
    int (*child_pre_probe)(struct udevice *dev); // 在该uclass的一个udevice的一个子设备进行probe之前调用
    int (*init)(struct uclass *class); // 安装该uclass的时候调用
    int (*destroy)(struct uclass *class); // 销毁该uclass的时候调用
    int priv_auto_alloc_size; // 需要为对应的uclass分配多少私有数据
    int per_device_auto_alloc_size; //
    int per_device_platdata_auto_alloc_size; //
    int per_child_auto_alloc_size; //
    int per_child_platdata_auto_alloc_size;  //
    const void *ops; //操作集合
    uint32_t flags;   // 标识为
};

spi-uclass驱动:

UCLASS_DRIVER(spi) = {
	.id		= UCLASS_SPI,
	.name		= "spi",
	.flags		= DM_UC_FLAG_SEQ_ALIAS,
#if CONFIG_IS_ENABLED(OF_REAL)
	.post_bind	= dm_scan_fdt_dev,
#endif
	.post_probe	= spi_post_probe,
	.child_pre_probe = spi_child_pre_probe,
	.per_device_auto	= sizeof(struct dm_spi_bus),
	.per_child_auto	= sizeof(struct spi_slave),
	.per_child_plat_auto	= sizeof(struct dm_spi_slave_plat),
#if CONFIG_IS_ENABLED(OF_REAL)
	.child_post_bind = spi_child_post_bind,
#endif
};

//存放在段._u_boot_list_2_uclass_2_spi中,也就是section段的内容可以在uboot.map可以查看
在这里插入图片描述

想要获取uclass_driver需要先获取uclass_driver table。

struct uclass_driver *uclass =
        ll_entry_start(struct uclass_driver, uclass); 
// 会根据.u_boot_list_2_uclass_1的段地址来得到uclass_driver table的地址

    const int n_ents = ll_entry_count(struct uclass_driver, uclass);
// 获得uclass_driver table的长度

struct uclass_driver *lists_uclass_lookup(enum uclass_id id)
// 从uclass_driver table中获取uclass id为id的uclass_driver。

2.4 driver

主要函数:

struct driver {
    char *name;    // 驱动名
    enum uclass_id id;  // 对应的uclass id
    const struct udevice_id *of_match;    // compatible字符串的匹配表,用于和device tree里面的设备节点匹配
    int (*bind)(struct udevice *dev);   // 用于绑定目标设备到该driver中
    int (*probe)(struct udevice *dev);   // 用于probe目标设备,激活
    int (*remove)(struct udevice *dev); // 用于remove目标设备。禁用
    int (*unbind)(struct udevice *dev); // 用于解绑目标设备到该driver中
    int (*ofdata_to_platdata)(struct udevice *dev); // 在probe之前,解析对应udevice的dts节点,转化成udevice的平台数据
    int (*child_post_bind)(struct udevice *dev); // 如果目标设备的一个子设备被绑定之后,调用
    int (*child_pre_probe)(struct udevice *dev); // 在目标设备的一个子设备被probe之前,调用
    int (*child_post_remove)(struct udevice *dev); // 在目标设备的一个子设备被remove之后,调用
    int priv_auto_alloc_size; //需要分配多少空间作为其udevice的私有数据
    int platdata_auto_alloc_size; //需要分配多少空间作为其udevice的平台数据
    int per_child_auto_alloc_size;  // 对于目标设备的每个子设备需要分配多少空间作为父设备的私有数据
    int per_child_platdata_auto_alloc_size; // 对于目标设备的每个子设备需要分配多少空间作为父设备的平台数据
    const void *ops;    /* driver-specific operations */ // 操作集合的指针,提供给uclass使用,没有规定操作集的格式,由具体uclass决定
    uint32_t flags; // 一些标志位
};
2.4.1 spi master driver
static const struct dm_spi_ops noru_spi_ops = {
    .claim_bus	= winbond_spi_claim_bus,
	.release_bus	= winbond_spi_release_bus,
	.xfer		= noru_spi_master_xfer,
	.set_speed	= noru_spi_set_speed,
	.set_mode	= noru_spi_set_mode,
};

static const struct udevice_id noru_spi_ids[] = {
	{ .compatible = "noru,spi_master" },
	{ }
};

U_BOOT_DRIVER(noru_spi_master) = {
	.name = "noru_spi_master",
	.id = UCLASS_SPI,
	.of_match = noru_spi_ids,
	.ops = &noru_spi_ops,
    .bind = winbond_sf_bind_emul,
	.ofdata_to_platdata = noru_spi_ofdata_to_platdata,
	.platdata_auto_alloc_size = sizeof(struct noru_spi_platdata),
	.priv_auto_alloc_size = sizeof(struct noru_spi_priv),
	.probe = noru_spi_probe,
};

由上面结构体可得,其定义之后都被存放在了段._u_boot_list_2_driver_2_noru_spi_master中,那么去哪里可以看到呢?

在u-boot.map文件中搜索,._u_boot_list_2_driver,就可以查到程序中定义的所有驱动程序。
在这里插入图片描述**注意:**spi master驱动中读写函数noru_spi_master_xfer需要根据设备具体spi协议进行编写代码

三、uboot代码解析

3.1 DM的初始化

DM的初始化分为两个部分,一个是在relocate重定向之前的初始化:initf_dm,一个是在relocate重定向之后的初始化:initr_dm
创建根设备root的udevice,存放在gd->dm_root中。
根设备其实是一个虚拟设备,主要是为uboot的其他设备提供一个挂载点。
初始化uclass链表gd->uclass_root

DM中udevice和uclass的解析

1、udevice的创建和uclass的创建
2、udevice和uclass的绑定
3、uclass_driver和uclass的绑定
4、driver和udevice的绑定
5、部分driver函数的调用

在这里插入图片描述

static int initf_dm(void)
{
#if defined(CONFIG_DM) && CONFIG_VAL(SYS_MALLOC_F_LEN)
    int ret;
    bootstage_start(BOOTSTAGE_ID_ACCUM_DM_F, "dm_f");
    ret = dm_init_and_scan(true);                   //这里为true
    bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_F);
    if (ret)
        return ret;
#endif
#ifdef CONFIG_TIMER_EARLY
    ret = dm_timer_init();
    if (ret)
        return ret;
#endif

    return 0;
}

static int initr_dm(void)
{
    int ret;

    /* Save the pre-reloc driver model and start a new one */
    gd->dm_root_f = gd->dm_root;
    gd->dm_root = NULL;
#ifdef CONFIG_TIMER
    gd->timer = NULL;
#endif
    bootstage_start(BOOTSTAGE_ID_ACCUM_DM_R, "dm_r");
    ret = dm_init_and_scan(false);                      //这里为false
    bootstage_accum(BOOTSTAGE_ID_ACCUM_DM_R);
    if (ret)
        return ret;

    return 0;
}

首先说明一下dts节点中的“u-boot,dm-pre-reloc”属性,当设置了这个属性时,则表示这个设备在relocate之前就需要使用。当dm_init_and_scan的参数为true时,只会对带有“u-boot,dm-pre-reloc”属性的节点进行解析,如在board_f.c文件中调用。而当参数为false的时候,则会对所有节点都进行解析,如在board_r.c文件中调用,重定位的意义,uboot重定位之前运行在SDRAM中,重定位运行在ddr中,运行空间更大。

代码解析:

int dm_init_and_scan(bool pre_reloc_only)
{
    int ret;

    ret = dm_init();    // DM的初始化
    ret = dm_scan_platdata(pre_reloc_only); //从平台设备中解析udevice和uclass,uboot一般不用该方式,采用dts方式


    if (CONFIG_IS_ENABLED(OF_CONTROL)) {//CONFIG_OF_CONTROL注意打开
        ret = dm_scan_fdt(gd->fdt_blob, pre_reloc_only); // 从dtb中解析udevice和uclass
    }
    ret = dm_scan_other(pre_reloc_only);
    return 0;
}
#define DM_ROOT_NON_CONST       (((gd_t *)gd)->dm_root) // 宏定义根设备指针gd->dm_root
#define DM_UCLASS_ROOT_NON_CONST    (((gd_t *)gd)->uclass_root) // 宏定义gd->uclass_root,uclass的链表

int dm_init(void)
{
    int ret;
    if (gd->dm_root) {// 根设备已经存在,说明DM已经初始化过了
        dm_warn("Virtual root driver already exists!\n");
        return -EINVAL;
    }

    INIT_LIST_HEAD(&DM_UCLASS_ROOT_NON_CONST);// 初始化uclass链表

    ret = device_bind_by_name(NULL, false, &root_info, &DM_ROOT_NON_CONST);
        // DM_ROOT_NON_CONST是指根设备udevice,root_info是表示根设备的设备信息
        // device_bind_by_name会查找和设备信息匹配的driver,然后创建对应的udevice和uclass并进行绑定,最后放在DM_ROOT_NON_CONST中。
        // device_bind_by_name后续我们会进行说明,这里我们暂时只需要了解root根设备的udevice以及对应的uclass都已经创建完成。

#if CONFIG_IS_ENABLED(OF_CONTROL)
    DM_ROOT_NON_CONST->of_offset = 0;
#endif
    ret = device_probe(DM_ROOT_NON_CONST);// 对根设备执行probe操作,在initf_dm执行(未重定位之前)只是创建root节点和uclass。

    return 0;
}

dm_init创建root和uclass设备后开始解析设备树内容,从而创建相应的udevice

int dm_scan_fdt(const void *blob, bool pre_reloc_only)
// 此时传进来的参数blob=gd->fdt_blob, pre_reloc_only=0,即initr_dm才会扫dts所有内容
{
    return dm_scan_fdt_node(gd->dm_root, blob, 0, pre_reloc_only);
// 直接调用dm_scan_fdt_node
}

int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset,
             bool pre_reloc_only)
// 此时传进来的参数
// parent=gd->dm_root,表示以root设备作为父设备开始解析
// blob=gd->fdt_blob,指定了对应的dtb
// offset=0,从偏移0的节点开始扫描
// pre_reloc_only=0,不只是解析relotion之前的设备
{
    int ret = 0, err;

        /*  以下步骤相当于是遍历每一个dts节点并且调用lists_bind_fdt对其进行解析 */

    for (offset = fdt_first_subnode(blob, offset);
        // 获得blob设备树的offset偏移下的节点的第一个子节点
         offset > 0;
         offset = fdt_next_subnode(blob, offset)) {
               // 循环查找下一个子节点
        if (!fdtdec_get_is_enabled(blob, offset)) {
                        // 判断节点状态是否是disable,如果是的话直接忽略,注意dts中节点status = “ok”
            dm_dbg("   - ignoring disabled device\n");
            continue;
        }
        err = lists_bind_fdt(parent, blob, offset, NULL);
                // 解析绑定这个节点,dm_scan_fdt的核心,下面具体分析
        if (err && !ret) {
            ret = err;
            debug("%s: ret=%d\n", fdt_get_name(blob, offset, NULL),
                  ret);
        }
    }
    return ret;
}

lists_bind_fdt是从dtb中解析udevice和uclass的核心,通过读取dts内容读取相应的driver生成相应的 udevice,再根据driver中对应的UCLASS_ID绑定对应的uclass,如dts中定义一个spi master驱动,对应的驱动名字为mxc_spi,id为UCLASS_SPI,则该udevice会追加到spi uclass节点下,同时也会加到gd->root下。

int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
           struct udevice **devp)
// parent指定了父设备,通过blob和offset可以获得对应的设备的dts节点,对应udevice结构通过devp返回
{
    struct driver *driver = ll_entry_start(struct driver, driver);
// 获取driver table地址
    const int n_ents = ll_entry_count(struct driver, driver);
// 获取driver table长度
    const struct udevice_id *id;
    struct driver *entry;
    struct udevice *dev;
    bool found = false;
    const char *name;
    int result = 0;
    int ret = 0;

    dm_dbg("bind node %s\n", fdt_get_name(blob, offset, NULL));
// 打印当前解析的节点的名称
    if (devp)
        *devp = NULL;
    for (entry = driver; entry != driver + n_ents; entry++) {
// 遍历driver table中的所有driver,即通过U_BOOT_DRIVER(mxc_spi)声明的驱动
        ret = driver_check_compatible(blob, offset, entry->of_match,
                          &id);
// 判断driver中的compatibile字段和dts节点是否匹配
        name = fdt_get_name(blob, offset, NULL);
// 获取节点名称
        if (ret == -ENOENT) {
            continue;
        } else if (ret == -ENODEV) {
            dm_dbg("Device '%s' has no compatible string\n", name);
            break;
        } else if (ret) {
            dm_warn("Device tree error at offset %d\n", offset);
            result = ret;
            break;
        }

        dm_dbg("   - found match at '%s'\n", entry->name);
        ret = device_bind(parent, entry, name, NULL, offset, &dev);
// 找到对应的driver,调用device_bind进行绑定,会在这个函数中创建对应udevice,然后根据id和uclass并且进行绑定
       if (ret) {
            dm_warn("Error binding driver '%s': %d\n", entry->name,
                ret);
            return ret;
        } else {
            dev->driver_data = id->data;
            found = true;
            if (devp)
                *devp = dev;
// 将udevice设置到devp指向的地方中,进行返回
        }
        break;
    }

    if (!found && !result && ret != -ENODEV) {
        dm_dbg("No match for node '%s'\n",
               fdt_get_name(blob, offset, NULL));
    }

    return result;
}

这里会将udevice相关的信息进行填充,获取对应的parent、uclass以及它们对应的平台属性,然后将该udevice绑定到parent和uclass链表中。

int device_bind(struct udevice *parent, const struct driver *drv,
        const char *name, void *platdata, int of_offset,
        struct udevice **devp)
// parent:父设备
// drv:设备对应的driver
// name:设备名称
// platdata:设备的平台数据指针
// of_offset:在dtb中的偏移,即代表了其dts节点
// devp:所创建的udevice的指针,用于返回
{
    struct udevice *dev;
    struct uclass *uc;
    int size, ret = 0;

    ret = uclass_get(drv->id, &uc);
        // 获取driver id对应的uclass,如果uclass原先并不存在,那么会在这里创建uclass并其uclass_driver进行绑定

    dev = calloc(1, sizeof(struct udevice));
        // 分配一个udevice

    dev->platdata = platdata; // 设置udevice的平台数据指针
    dev->name = name; // 设置udevice的name
    dev->of_offset = of_offset; // 设置udevice的dts节点偏移
    dev->parent = parent; // 设置udevice的父设备
    dev->driver = drv;    // 设置udevice的对应的driver,相当于driver和udevice的绑定
    dev->uclass = uc;    // 设置udevice的所属uclass

    dev->seq = -1;
    dev->req_seq = -1;
    if (CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_SEQ_ALIAS)) {
        /*
         * Some devices, such as a SPI bus, I2C bus and serial ports
         * are numbered using aliases.
         *
         * This is just a 'requested' sequence, and will be
         * resolved (and ->seq updated) when the device is probed.
         */
        if (uc->uc_drv->flags & DM_UC_FLAG_SEQ_ALIAS) {
            if (uc->uc_drv->name && of_offset != -1) {
                fdtdec_get_alias_seq(gd->fdt_blob,
                        uc->uc_drv->name, of_offset,
                        &dev->req_seq);
            }
                    // 设置udevice的alias请求序号
        }
    }

    if (!dev->platdata && drv->platdata_auto_alloc_size) {
        dev->flags |= DM_FLAG_ALLOC_PDATA;
        dev->platdata = calloc(1, drv->platdata_auto_alloc_size);
                // 为udevice分配平台数据的空间,由driver中的platdata_auto_alloc_size决定
    }

    size = uc->uc_drv->per_device_platdata_auto_alloc_size;
    if (size) {
        dev->flags |= DM_FLAG_ALLOC_UCLASS_PDATA;
        dev->uclass_platdata = calloc(1, size);
                // 为udevice分配给其所属uclass使用的平台数据的空间,由所属uclass的driver中的per_device_platdata_auto_alloc_size决定
    }

    /* put dev into parent's successor list */
    if (parent)
        list_add_tail(&dev->sibling_node, &parent->child_head);
        // 添加到父设备的子设备链表中

    ret = uclass_bind_device(dev);
        // uclass和udevice进行绑定,主要是实现了将udevice链接到uclass的设备链表中

    /* if we fail to bind we remove device from successors and free it */
    if (drv->bind) {
        ret = drv->bind(dev);
        // 执行udevice对应driver的bind函数
    }

    if (parent && parent->driver->child_post_bind) {
        ret = parent->driver->child_post_bind(dev);
        // 执行父设备的driver的child_post_bind函数
    }
    if (uc->uc_drv->post_bind) {
        ret = uc->uc_drv->post_bind(dev);
        if (ret)
            goto fail_uclass_post_bind;
        // 执行所属uclass的post_bind函数
    }

    if (devp)
        *devp = dev;
        // 将udevice进行返回

    dev->flags |= DM_FLAG_BOUND;
        // 设置已经绑定的标志
        // 后续可以通过dev->flags & DM_FLAG_ACTIVATED或者device_active宏来判断设备是否已经被激活

    return 0;

当udevie和driver、uclass绑定之后开始最后的激活阶段
查找是否有对应的设备分配设备的私有数据
对父设备进行probe
执行probe device之前uclass需要调用的一些函数
调用driver的ofdata_to_platdata,将dts信息转化为设备的平台数据
调用driver的probe函数
执行probe device之后uclass需要调用的一些函数

int device_probe(struct udevice *dev)
{
    const struct driver *drv;
    int size = 0;
    int ret;
    int seq;

    if (dev->flags & DM_FLAG_ACTIVATED)
        return 0;
// 表示这个设备已经被激活了

    drv = dev->driver;
    assert(drv);
// 获取这个设备对应的driver

    /* Allocate private data if requested and not reentered */
    if (drv->priv_auto_alloc_size && !dev->priv) {
        dev->priv = alloc_priv(drv->priv_auto_alloc_size, drv->flags);
// 为设备分配私有数据
    }

    /* Allocate private data if requested and not reentered */
    size = dev->uclass->uc_drv->per_device_auto_alloc_size;
    if (size && !dev->uclass_priv) {
        dev->uclass_priv = calloc(1, size);
// 为设备所属uclass分配私有数据
    }

// 这里过滤父设备的probe

    seq = uclass_resolve_seq(dev);
    if (seq < 0) {
        ret = seq;
        goto fail;
    }
    dev->seq = seq;

    dev->flags |= DM_FLAG_ACTIVATED;
// 设置udevice的激活标志

    ret = uclass_pre_probe_device(dev);
// uclass在probe device之前的一些函数的调用

    if (drv->ofdata_to_platdata && dev->of_offset >= 0) {
        ret = drv->ofdata_to_platdata(dev);
// 调用driver中的ofdata_to_platdata将dts信息转化为设备的平台数据
    }

    if (drv->probe) {
        ret = drv->probe(dev);
// 调用driver的probe函数,到这里设备才真正激活了
    }

    ret = uclass_post_probe_device(dev);

    return ret;
}

3.2 spi norflash设备识别

通过dts中子节点匹配的驱动进行创建:

static const struct dm_spi_flash_ops spi_flash_std_ops = {
	.read = spi_flash_std_read,
	.write = spi_flash_std_write,
	.erase = spi_flash_std_erase,
};

static const struct udevice_id spi_flash_std_ids[] = {
	{ .compatible = "noru,spi-flash" },
	{	}
};


U_BOOT_DRIVER(spi_flash_std) = {
	.name		= "spi_flash_std",
	.id		= UCLASS_SPI_FLASH,
	.of_match	= spi_flash_std_ids,
	.probe		= spi_flash_std_probe,
	.priv_auto_alloc_size = sizeof(struct spi_flash),
	.ops		= &spi_flash_std_ops,
};

在这里插入图片描述

3.3 设备树内容

在设备树添加设备信息

在对应的dts文件中添加相应的设备节点和信息,注意simple-bus创建,代码解析:
在这里插入图片描述
如果dts中没有设置clocks节点直接回退出,原因是spi通信是基于clock进行数据传输的

在这里插入图片描述

chosen节点在scan dts内容是需要的,可以加入

/dts-v1/;

/ {
  #address-cells = <2>;
  #size-cells = <2>;
  compatible = "evb,noru";
  model = "NORU vu440";

	aliases {
		spi0 = &spi;
	} ;

	chosen {
		bootargs = "console=ttyS0,115200n8 loglevel=7";
		stdout-path = "uart0:115200n8";
  	};

	clocks {
			compatible = "simple-bus";  //bus总线驱动和clock
			u-boot,dm-pre-reloc;

    		spiclk: virt_100mhz {
   				#clock-cells = <0>;
    			compatible = "fixed-clock";
    			clock-frequency = <100000000>;
  			};
	};

	spi: spi@c100000 {
		compatible = "noru,spi_master"; //spi master 驱动
		status = "okay";
    	reg = <0x0 0xc100000 0x0 0x10000>;
		#address-cells = <1>;
		#size-cells = <0>;
		num-cs = <1>;
		clocks = <&spiclk>;
			flash@0 {
			compatible = "noru,spi-flash";//flash 驱动
			spi-max-frequency = <50000000>;
			reg = <0>;
			spi-cpol;
			spi-cpha;
		};
	};
};

3.4 .config配置

CONFIG_DEFAULT_DEVICE_TREE="xxxxx" //指定设备树名
CONFIG_OF_EMBED=y //那么uboot会把设备树编译进镜像内
CONFIG_OF_CONTROL=y //使能设备树的支持
CONFIG_SPI_FLASH_BAR=y //支持4B模式,大于16MB的spi打开


```c
//执行命令
sf probe
'spi@c100000'
   - found
spi_find_chip_select: plat=00000089ff6bd4c0, cs=0
spi_flash_probe_bus_cs==========bus:0,===cs:0,===speed:1000000,====mode:3
spi_flash@0:0,the str is spi_flash@0:0
uclass_get_device_by_seq===
 'spi@c100000'
   - found
spi_find_chip_select: plat=00000089ff6bd4c0, cs=0
==============spi_flash_std_probe
winbond_spi_set_speed: ==========eric=======winbond_spi_claim_bus

SF: Detected w25q256fw with page size 256 Bytes, erase size 4 KiB, total 32 MiB
SF: Warning - Only lower 16MiB accessible, Full access #define CONFIG_SPI_FLASH_BAR

上面Warning因为现在用的winbod spi支持32MB,设置的3B模式只支持16MB,需要在config中打开CONFIG_SPI_FLASH_BAR=y

注意加入该norflash相关id
drivers/mtd/spi/spi_flash_ids.c
在这里插入图片描述

3.5 spi读写测试

//初始化spi
sf probe
//tftp传入需要烧录的文件
tftp 0x8801000000  pkg_bin/noru_preloader_pkt.bin
tftp 0x8802000000  pkg_bin/noru_loader_pkt.bin
tftp 0x8803000000  pkg_bin/fw_pkt_compress.image 

//擦除原有数据
sf erase 0 0x200000

//从ddr写入数据
sf write 0x8801000000 0 0x38000
sf write 0x8802000000 0x70000 0x80000
sf write 0x8803000000 0xF1000 0x80000

//读数据到ddr
sf read 0x8801000000 0 0x38000
sf read 0x8802000000 0x70000 0x80000
sf read 0x8803000000 0xF1000 0x80000

四、其他相关链接

1、SPI协议详细总结附实例图文讲解通信过程

2、Linux下spi网卡dm9051驱动移植及驱动调试分析总结

3、Linux下设备树dts内容总结及示例解析

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发 的相关文章

  • DRM几个重要的结构体及panel开发

    一 DRM Linux下的DRM框架内容众多 结构复杂 本文将简单介绍下开发过程中用到的几个结构体 这几个结构体都在之前文章里面开发DRM驱动时用到的 未用到的暂不介绍 DRM中的KMS包含Framebuffer CRTC ENCODER
  • 驱动 - platform总线驱动

    include
  • 嵌入式linux驱动之路19:U-Boot 移植(2)

    uboot 的最终目的就是启动 Linux 内核 所以需要通过启动 Linux 内核来判断 uboot 移植是否成功 在启动 Linux 内核之前我们先来学习两个重要的环境变量 bootcmd 和 bootargs bootcmd 环境变量
  • 驱动开发 作业 day7 9/18

    基于GPIO子系统实现led灯点亮 head h ifndef HEAD H define HEAD H 构建LED开关的功能码 不添加ioctl第三个参数 define LED ON IO l 1 define LED OFF IO l
  • Linux驱动_多点电容触摸

    一丶Linux下多点电容触摸驱动框架 电容触摸屏IC是FT5426 为IIC协议芯片 因此需要编写IIC驱动 触摸IC会发出中断信号 并在中断服务函数中上报信息 因此需要编写中断框架 触摸屏向Linux内核上报的信息都属于Input子系统
  • Linux驱动开发--平台总线id和设备树匹配

    目录 一 ID匹配之框架代码 二 ID匹配之led驱动 三 设备树匹配 四 设备树匹配之led驱动 五 一个编写驱动用的宏 一 ID匹配之框架代码 id匹配 可想象成八字匹配 一个驱动可以对应多个设备 优先级次低 注意事项 device模块
  • Linux设备驱动-procfs

    在Linux中 procfs是进程文件系统 file system 的缩写 包含一个伪文件系统 启动时动态生成的文件系统 可用于内核层和用户层交互信息 这个文件系统通常被挂载到 proc 目录 由于 proc 不是一个真正的文件系统 它也就
  • 38.驱动调试——printk

    printk的用法 mz linux ChinaUnix博客 printk函数的用法 wwwlyj123321的博客 CSDN博客 printk 34 Linux printk分析 使用 FILE FUNCTION LINE 调试 诺谦 博
  • STM32F407基于RT-Thread连接ESP8266WiFi模块

    1 连接规则 STM32F4连接ESP8266无线通信 串口通信 首先 本次用到两个串口 我使用的是普中STM32F407 第一个串口为USART1 PA2 PA3 串口一 就是数据线连接单片机和电脑时用的口 串口三USART3 PB10T
  • 异步通知实验(信号)

    目录 异步通知 异步通知简介 驱动中的信号处理 应用程序对异步通知的处理 硬件原理图分析 实验程序编写 修改设备树文件 程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测试 在前面使用阻塞或者非阻塞的方式来读取驱动中按键值
  • 自举电路原理

    文章目录 一 自举电路核心原理 二 为什么要自举升压 三 简单的自举电路模型 四 自举电路在高电压栅极驱动电路中的应用 1 MOS管Q开通时 2 MOS管Q关断时 一 自举电路核心原理 电容两端电压不能突变 根据电容公式 i t C du
  • Android HAL 层框架分析(一)

    作为一个搞android驱动或者说搞底层的人 我觉得对于hal那是必须要掌握的 而且必须达到一定深度 于是我总结了一下 将整个自己的分析思路写下来 主要是看android源代码 根据源代码得到的思路 看源代码比看什么著作书籍都管用 andr
  • 使用 Microchip SAM9X60 OTP 存储板卡的MAC地址和序列号

    1 介绍 SAM9X60 处理器有部分OTP One Time Programming Aera 可用于存储user data 这样的话我们就可以将板卡 MAC Address和 SN 序列号写到固定的OTP User Area中 为什么要
  • 5V转±12V无变压器双boost电路

    最近有个新项目 需要 10V范围的模拟量输出 非隔离 对于5V以下供电的控制板而言单端输出绝对没问题 可现在需要有正负输出 是少不了正负电源的 因此准备设计一个5V转 12V的电源 然后选择一个双向供电的运放 来实现单端模拟量信号向双向模拟
  • LCD背光调节实验

    目录 LCD 背光调节简介 硬件原理分析 实验程序编写 编译下载验证 编写Makefile 和链接脚本 编译下载 不管是使用显示器还是手机 其屏幕背光都是可以调节的 通过调节背光就可以控制屏幕的亮度 在户外阳光强烈的时候可以通过调高背光来看
  • <Linux开发>驱动开发 -之- Linux LCD 驱动

    Linux开发 驱动开发 之 Linux LCD 驱动 交叉编译环境搭建 Linux开发 linux开发工具 之 交叉编译环境搭建 uboot移植可参考以下 Linux开发 之 系统移植 uboot移植过程详细记录 第一部分 Linux开发
  • uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发

    文章目录 一 综述 二 UCLASS架构解析 2 1 uclass 2 2 udevice 2 3 uclass driver 2 4 driver 2 4 1 spi master driver 三 uboot代码解析 3 1 DM的初始
  • linux ARM64 处理器内存屏障

    一 内存类型 ARMv8架构将系统中所有的内存 按照它们的特性 划分成两种 即普通内存和设备内存 并且它们是互斥的 也就是说系统中的某段内存要么是普通内存 要么是设备内存 不能都是 1 普通内存 Normal Memory 普通内存的特性是
  • linux ARM64 处理器内存屏障

    一 内存类型 ARMv8架构将系统中所有的内存 按照它们的特性 划分成两种 即普通内存和设备内存 并且它们是互斥的 也就是说系统中的某段内存要么是普通内存 要么是设备内存 不能都是 1 普通内存 Normal Memory 普通内存的特性是
  • OpenHarmony基于HDF简单驱动开发实例

    背景 OpenHarmony 3 0 LTS qemu small system demo liteos a qemu 添加配置 device qemu arm virt liteos a hdf config device info de

随机推荐

  • 多对一查询

    是这样的 首先学生表中有三个属性 前两个属性要出现在结果集中 第三个属性哦用于查找教师的名字 然后呢 用resultMap重新处理 将老师的名字重新对应为学生表的第三个属性 其实这样做有局限 多对一查询 SQL语句 SELECT s id
  • python中文件的操作

    gt gt gt f open home ceshi mmm txt w w 为读写 gt gt gt f write zuoshouzhiyue n 写入数据 记得加 n gt gt gt f tell 当前在文件中的位置 14 gt g
  • QToolButton和QPushButton的区别

    用代码实践了一下QPushButton和QToolButton的差异 include widget h include
  • Mathematica函数大全

    一 运算符及特殊符号 Line1 执行Line 不显示结果 Line1 line2 顺次执行Line1 2 并显示结果 name 关于系统变量name 的信息 name 关于系统变量name 的全部信息 command 执行Dos 命令 n
  • 《权力的游戏》最终季上线!谁是你最喜爱的演员?这里有一份Python教程

    译者 刘畅 编辑 琥珀 出品 AI科技大本营 id rgznai100 权力的游戏 最终季已于近日开播 对于全世界翘首以待的粉丝们来说 其最大的魅力就在于 无法预知的人物命运 那些在魔幻时代的洪流中不断沉浮的人们 将会迎来怎样的结局 近日
  • DS18B20的原理及实例代码(51单片机、STM32单片机)

    一 DS18B20介绍 DS18B20数字温度传感器是DALLAS公司生产的单总线器件 用它来组成一个测温系统具有线路简单 体积小 在一根通信线上可以挂很多这样的数字温度传感器 十分方便 温度传感器种类众多 应用在高精度 高可靠性的场合时D
  • c语言程序for循环处设断点,C语言程序设计-循环.ppt

    C语言程序设计 循环 C语言程序设计 循环结构 while循环结构 例 求1 100之和 即 注意事项 如果循环体内只有一条语句 则可以不用大括号 语法上并没有要求一定要存在循环初始条件的设定 也没有要求循环条件中的变量必须在循环体内改变
  • 使用JTDS连接sqlserver数据库

    Java连接SQL Server 2000数据库时 有两种方法 1 通过Microsoft的JDBC驱动连接 此JDBC驱动共有三个文件 分别是mssqlserver jar msutil jar和msbase jar 但是Microsof
  • IDEA 查看Springboot单元测试代码覆盖率报错

    报错信息 D resource java jdk 1 8 261 bin java exe ea javaagent C Users 镜 AppData Local JetBrains IntelliJIdea2020 2 testAgen
  • 软路由自建iptv服务器,LEDE软路由 iPTV 实现任意端口看电视的方法

    摘 要 1 光猫直接连接电脑 超级管理员进入光猫后台修改光猫为桥接模式 把IPTV 的VLAN ID绑定到跟宽带一样的数据端口 如下图 2 软路由 我们以6网口的来演示 进入Lede后台 系统 进阶设置 配置dnsmasq添加代码 dhcp
  • 互联网寒冬?应届生还应该加入么?

    大家都在各种唱衰互联网行业 下面从真实的示例来分析一下 还能否加入这个行业 互联网企业现状 互联网企业地域分布 从业者从一线城市逐渐向低线扩展 对新一线及低线城市的青睐度上升 腾讯地域扩张 腾讯业务版图 阿里地域扩张 阿里业务版图 字节地域
  • PhpStorm为什么值得推荐?

    智能编码辅助 PhpStorm 是一个 PHP IDE 它实际上 获取 您的代码 它支持 PHP 5 3 5 4 5 5 5 6 7 0 7 1 7 2 提供动态错误预防 最佳自动完成和代码重构 零配置调试以及扩展的 HTML CSS 和J
  • sql ntext數據類型字符替換

    ntext數據類型字符替換 2011 08 21 塗聚文 create table tt sid INT IDENTITY 1 1 cont ntext go insert into tt cont values N fd sad fdsa
  • Qt 信号与槽

    Qt 信号与槽 在这章节里 我们学习 Qt 的信号与槽 这里分一个章节来学习这个 Qt 的信号与槽 可见 这个信号与槽有多么重要 在学习 Qt 的过程中 信号与槽是必不可少的部分 也是 Qt 编程的 基础 是 Qt 编程的一大创新 其实与
  • 文件通讯录

    copyright C 2014 2015 Lighting Studio Co Ltd File name Author Jerey Jobs Version 0 1 Date Description Funcion List inclu
  • 世界最强的黑客为何都在俄罗斯?他们到底有多逆天?

    世界上只有两种黑客 一种 是俄罗斯黑客 另一种 是 其他黑客 江湖传闻 俄罗斯黑客曾攻击美国政府网站 操纵美国大选 就连FBI都怕他们三分 今天带大家认见识见识战斗民族的另一面 黑客帝国 俄罗斯的黑客有多逆天 1994年成名战 美国最大的银
  • 用where in遇到null时的解决方法1

    参考 https www 2cto com database 201109 104960 html http ask csdn net questions 680006 1 SELECT FROM 华东 WHERE 公司代码 IN SELE
  • Matlab将mat格式文件多层数据逐级导出为excel

    我在处理牛津电池数据集时 因为我更喜欢用python来进行深度学习方面的操作 所以我需要将mat格式数据导出为excel表格 由于该数据分为多层 所以导出操作较为复杂 在网上查询许久后发现并没有相关的文章 后来便自己倒腾出来了 供需要的小伙
  • GPU 渲染管线与着色器 大白话总结 ---- 一篇就够

    转载自 https blog csdn net newchenxf article details 119803489 真的写的非常不错 大力推荐 GPU 渲染管线与着色器 大白话总结 一篇就够 文章目录 GPU 渲染管线与着色器 大白话总
  • uboot下UCLASS框架详解---结合项目工作中spi master和flash驱动开发

    文章目录 一 综述 二 UCLASS架构解析 2 1 uclass 2 2 udevice 2 3 uclass driver 2 4 driver 2 4 1 spi master driver 三 uboot代码解析 3 1 DM的初始