zync spi flash 频率配置

spi  flash的频率配置 代码流程及最终的频率值。

驱动目录

  基于4.14.55 内核,

 \drivers\spi\spi-dw-fmsh.c (控制器)
\drivers\spi\spi-dw.c
\drivers\mtd\devices\m25p80.c  (设备)

\drivers\spi\spi.c 

spi dts配置说明

	spi0: spi@e0001000 {compatible = "fmsh,dw-apb-ssi","snps,dw-apb-ssi";#address-cells = <1>;#size-cells = <0>;reg = <0xe0001000 0x1000>;interrupts = <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;num-cs = <3>;clocks = <&clkc NCLK_SPI0>, <&clkc NCLK_APB_SPI0>;clock-names = "clk_ref", "pclk";reg-io-width = <4>;spi-max-frequency = <1000000>;cs-gpios = <&portb 10 0>;status = "disabled";flash1@0 {compatible = "spi-flash","spansion,s25fl256s1", "jedec,spi-nor";reg = <0>;spi-max-frequency = <500000>;};spidev@1 {				compatible = "spidev";spi-max-frequency = <20000000>;reg = <1>;};};    
属性说明
cs-gpios 片选的配置。对于zync,其可能采用MIO或者EMIO,在设计时,vivado里面就配置好管脚

初始化流程

很多设备的套路都是先初始化控制器,然后再扫描控制器下的设备,对设备进行初始化。

初始化包括对硬件的初始化,以及根据硬件及DTS等配置初始化相关结构体,最终构成软件操作依赖。

控制器的初始化比较简单,只要明了驱动,进入probe就可以。

控制器

  probe探测

   


static int dw_spi_fmsh_probe(struct platform_device *pdev)
{struct dw_spi_mmio *dwsmmio;struct dw_spi *dws;struct resource *mem;int ret;dws->bus_num = pdev->id;//读取控制的输入频率,例如166M HZdws->max_freq = clk_get_rate(dwsmmio->clk);ret = dw_spi_add_host(&pdev->dev, dws);if (ret)goto fail;printk("xiehj end: dw_spi_fmsh_probe\n");platform_set_drvdata(pdev, dwsmmio);return 0;fail:clk_disable_unprepare(dwsmmio->pclk);
fail_pclk:clk_disable_unprepare(dwsmmio->clk);return ret;
}

初始化

   如下将其配置为master,SPI 通信分为master  、slave。


int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
{struct spi_master *master;int ret;ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev),master);if (ret < 0) {dev_err(dev, "can not get IRQ\n");goto err_free_master;}// 注册操作接口,这些操作接口在设备初始化时可能会回调,master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP;master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);master->bus_num = dws->bus_num;master->num_chipselect = dws->num_cs;master->setup = dw_spi_setup;master->cleanup = dw_spi_cleanup;master->set_cs = dw_spi_set_cs;master->transfer_one = dw_spi_transfer_one;master->handle_err = dw_spi_handle_err;master->max_speed_hz = dws->max_freq; //master是控制器设备,设置为166Mmaster->dev.of_node = dev->of_node;master->flags = SPI_MASTER_GPIO_SS;}

将控制器添加到设备后,后续芯片初始化时,命令的发送如何知道走整个控制器的相关接口的呢?此为通用的注册流程,即在下面的接口后,会进一步扫描DTS中的设备子节点,进而建立关联,此处非本文重点,感兴趣的自行阅读代码。套路都一样的。

ret = devm_spi_register_master(dev, master);

控制器的注册

  


int spi_register_controller(struct spi_controller *ctlr)
{struct device		*dev = ctlr->dev.parent;struct boardinfo	*bi;int			status = -ENODEV;int			id, first_dynamic;.......................省去一堆/* add statistics */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);/* Register devices from the device tree and ACPI */这里注册SPI下挂的设备of_register_spi_devices(ctlr);acpi_register_spi_devices(ctlr);
done:return status;
}

解析设备树

获取设备树里面配置

static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,struct device_node *nc)
{u32 value;int rc;/* Mode (clock phase/polarity/etc.) *//* Device speed */rc = of_property_read_u32(nc, "spi-max-frequency", &value);if (rc) {dev_err(&ctlr->dev,"%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc);return rc;}//注意这里从DTS读出的值,翻到了spi_device中,也就是FLASH等端点设备中,也就是端点设备需要的速率。spi->max_speed_hz = value;return 0;
}

 设备添加

  所谓的设备添加,即将控制器下面的设备添加到系统中,以便匹配后续的驱动。

  在此流程中,如果设备最大频率没有配置,则采用控制器的最大频率


int spi_setup(struct spi_device *spi)
{//如下判断是否需要采用控制器的最大频率if (!spi->max_speed_hz)spi->max_speed_hz = spi->controller->max_speed_hz;if (spi->controller->setup)status = spi->controller->setup(spi);spi_set_cs(spi, false);dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s%u bits/w, %u Hz max --> %d\n",(int) (spi->mode & (SPI_CPOL | SPI_CPHA)),(spi->mode & SPI_CS_HIGH) ? "cs_high, " : "",(spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",(spi->mode & SPI_3WIRE) ? "3wire, " : "",(spi->mode & SPI_LOOP) ? "loopback, " : "",spi->bits_per_word, spi->max_speed_hz,status);return status;
}

设备初始化

   设备添加到系统后,就调用驱动


/** board specific setup should have ensured the SPI clock used here* matches what the READ command supports, at least until this driver* understands FAST_READ (for clocks over 25 MHz).*/
static int m25p_probe(struct spi_device *spi)
{struct flash_platform_data	*data;struct m25p *flash;struct spi_nor *nor;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 */elseflash_name = spi->modalias;ret = spi_nor_scan(nor, flash_name, &hwcaps);if (ret)return ret;return mtd_device_register(&nor->mtd, data ? data->parts : NULL,data ? data->nr_parts : 0);
}

  提供: 

   1) flash 的读写接口,及寄存器读写接口。在通用的nor驱动里面回调这些接口。通用nor驱动主要对nor 命令进行封装。

   2) 扫描设备。在设备驱动中扫描设备逻辑存在问题。在控制器中扫描更合适。

   3) 将设备注册为MTD设备。

  这里只要兼容,jedec,spi-nor 都会进到这个驱动中。  

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" },{}
};

读写flash的流程 

具体控制器的transfer接口

 SPI的频率设置在每次transfer时都会进行,因而需要关注此流程。从下面代码我们了解到

进行分频系数的设置,但是涉及到transfer->speed-hz

static inline void spi_set_clk(struct dw_spi *dws, u16 div)
{dw_writel(dws, DW_SPI_BAUDR, div);
}static int dw_spi_transfer_one(struct spi_master *master,struct spi_device *spi, struct spi_transfer *transfer)
{struct dw_spi *dws = spi_master_get_devdata(master);struct chip_data *chip = spi_get_ctldata(spi);u8 imask = 0;u16 txlevel = 0;u32 cr0;int ret;//在发送的时候,设置波特率的分频,每次都单打设置。/* Handle per transfer options for bpw and speed */if (transfer->speed_hz != dws->current_freq) {if (transfer->speed_hz != chip->speed_hz) {/* clk_div doesn't support odd number */chip->clk_div = (DIV_ROUND_UP(dws->max_freq, transfer->speed_hz) + 1) & 0xfffe;chip->speed_hz = transfer->speed_hz;}dws->current_freq = transfer->speed_hz;spi_set_clk(dws, chip->clk_div);}if (chip->poll_mode)return poll_transfer(dws);return 1;
}

transfer speed hz

\drivers\spi\spi.c

__spi_sync ---》 status = __spi_validate(spi, message);


static int __spi_validate(struct spi_device *spi, struct spi_message *message)
{struct spi_controller *ctlr = spi->controller;struct spi_transfer *xfer;int w_size;if (!xfer->speed_hz)xfer->speed_hz = spi->max_speed_hz; //首先将xfer的频率设置为设备请求的最大频率if (!xfer->speed_hz)xfer->speed_hz = ctlr->max_speed_hz; //如果没有则设置控制器的最大频率if (ctlr->max_speed_hz && xfer->speed_hz > ctlr->max_speed_hz)xfer->speed_hz = ctlr->max_speed_hz;if (__spi_validate_bits_per_word(ctlr, xfer->bits_per_word))return -EINVAL;/** SPI transfer length should be multiple of SPI word size* where SPI word size should be power-of-two multiple*/if (xfer->bits_per_word <= 8)w_size = 1;else if (xfer->bits_per_word <= 16)w_size = 2;elsew_size = 4;/* No partial transfers accepted */if (xfer->len % w_size)return -EINVAL;if (xfer->speed_hz && ctlr->min_speed_hz &&xfer->speed_hz < ctlr->min_speed_hz)return -EINVAL;if (xfer->tx_buf && !xfer->tx_nbits)xfer->tx_nbits = SPI_NBITS_SINGLE;if (xfer->rx_buf && !xfer->rx_nbits)xfer->rx_nbits = SPI_NBITS_SINGLE;/* check transfer tx/rx_nbits:* 1. check the value matches one of single, dual and quad* 2. check tx/rx_nbits match the mode in spi_device*/if (xfer->tx_buf) {if (xfer->tx_nbits != SPI_NBITS_SINGLE &&xfer->tx_nbits != SPI_NBITS_DUAL &&xfer->tx_nbits != SPI_NBITS_QUAD)return -EINVAL;if ((xfer->tx_nbits == SPI_NBITS_DUAL) &&!(spi->mode & (SPI_TX_DUAL | SPI_TX_QUAD)))return -EINVAL;if ((xfer->tx_nbits == SPI_NBITS_QUAD) &&!(spi->mode & SPI_TX_QUAD))return -EINVAL;}/* check transfer rx_nbits */if (xfer->rx_buf) {if (xfer->rx_nbits != SPI_NBITS_SINGLE &&xfer->rx_nbits != SPI_NBITS_DUAL &&xfer->rx_nbits != SPI_NBITS_QUAD)return -EINVAL;if ((xfer->rx_nbits == SPI_NBITS_DUAL) &&!(spi->mode & (SPI_RX_DUAL | SPI_RX_QUAD)))return -EINVAL;if ((xfer->rx_nbits == SPI_NBITS_QUAD) &&!(spi->mode & SPI_RX_QUAD))return -EINVAL;}}message->status = -EINPROGRESS;return 0;
}

频率配置总结

频率的来源

1) 控制器的时钟配置

2) DTS 中设备 频率字段的配置

真正工作频率

  在spi dw中,实际工作的频率计算公式为:

  chip->clk_div = (DIV_ROUND_UP(dws->max_freq, transfer->speed_hz) + 1) & 0xfffe;
            chip->speed_hz = transfer->speed_hz;

此处计算分频值,计算

DIV_ROUND_UP(A,B) = int( (A+B-1)/B ),

例如,max_freq =166M,  speed_hz配置为20M

则clk_div =(166.7+20-1)/20 +1= 9.285+1= 10也就是分频系数是10,此时设备期望的最大工作频率是20Mhz,实际工作为16.66MHZ=166.6/10

​​​​​​​

比如25M (166.7+24)/25=8.6 =8;  实际工作频率 166.7/8= 20.83Mhz

spi 信号在没有操作时,连时钟都没有输出,或者是由于这个流程。

问题分析

读取ID 失败

m25p80 spi2.0: unrecognized JEDEC id bytes: 00, 00, 00

偶尔能读出一次。

经分析,由于 spi控制器与设备间经过逻辑转换,导致CS信号没有到设备侧导致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/225832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2. 学成在线 - 项目搭建

文章目录 一、基础工程搭建1.1 构建父工程1.2 构建基础工程 二、数据库环境2.1 内容管理content数据库 三、模块需求分析介绍四、部署前端和系统管理服务 一、基础工程搭建 整个项目分为三大类工程&#xff1a;父工程、基础工程 和微服务工程。 父工程&#xff1a;xuecheng-pl…

Android Studio 实现音乐播放器

目录 一、引言 视频效果展示&#xff1a; 1.启动页效果 2.登录页效果 3.注册页效果 4.歌曲列表页效果 5.播放页效果 二、详细设计 1.登陆注册功能 2.音乐列表页面 2.音乐播放功能 三、源码获取 一、引言 Android初学者开发第一个完整的实例项目应该就属《音乐播放器…

Docker中的常见命令

Docker开机自启 systemctl enable dockerDocker容器开机自启 docker update --restartalways [容器名/容器id]案例&#xff1a;docker操作nginx 拉取Nginx镜像 docker pull nginx查看镜像 docker images创建并运行Nginx容器 docker run -d --name nginx -p 80:80 nginx查…

LT8711HE方案《任天堂Switch底座方案》

LT8711HE Type-c转HDMI方案 LT8711HE是高性能的Type-C/DP1.2转HDMI2.0转换器&#xff0c;设计用于连接 USB Type-C 源或 DP1.2 源到 HDMI2.0 接收器。该LT8711HE集成了符合 DP1.2 标准的接收器和符合 HDMI2.0 标准的发射器。此外&#xff0c;两个 CC 控制器是包括用于 CC 通信以…

1.6 实战:Postman请求Get接口-获取用于登录的图形验证码

上一小节我们学习了Postman的布局,对Postman有了一个整体的认知,本小节我们就来实操一下Get接口。 我们打开Postman,点击我们之前创建的请求”获取登录页验证码“。我们在地址栏里填入获取登录页验证码的接口地址。怎么查看这个接口地址呢?我们打开校园二手交易系统,打开…

Leetcode—2414.最长的字母序连续子字符串的长度【中等】

2023每日刷题&#xff08;六十&#xff09; Leetcode—2414.最长的字母序连续子字符串的长度 实现代码 class Solution { public:int longestContinuousSubstring(string s) {int ans 1;int t 1;for(int i 1; i < s.size(); i) {if(s[i] - s[i - 1] 1) {t;ans max(an…

师兄啊师兄第二季开播:男主成海神?玄机是懂联动的!

《师兄啊师兄》动画第二季在12月14日终于正式开播&#xff0c;首播两集&#xff0c;还是很有诚意的。 这部动画改编自言归正传的小说《我师兄实在太稳健了》&#xff0c;原著的知名度不算很高&#xff0c;但玄机制作的动画让这个IP火出了圈。 动画第一季就凭借高颜值的人物建模…

spring boot 实现直播聊天室

spring boot 实现直播聊天室 技术方案: spring bootwebsocketrabbitmq 使用 rabbitmq 提高系统吞吐量 引入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.42&…

LeetCode 300最长递增子序列 674最长连续递增序列 718最长重复子数组 | 代码随想录25期训练营day52

动态规划算法10 LeetCode 300 最长递增子序列 2023.12.15 题目链接代码随想录讲解[链接] int lengthOfLIS(vector<int>& nums) {//创建变量result存储最终答案,设默认值为1int result 1;//1确定dp数组&#xff0c;dp[i]表示以nums[i]为结尾的子数组的最长长度ve…

VS Code连接远程Linux服务器调试C程序

1.在 VS Code 上安装扩展 C/C 2.通过 VS Code 连接远程 Linux 服务器 3.通过 VS Code 在远程 Linux 服务器上安装扩展 C/C 4.打开远程 Linux 服务器上的文件夹 【注】本文以 /root/ 为例。 5.创建项目文件夹&#xff0c;并在项目文件夹下创建C程序 6.按 F5&#xff0c;选…

mysql中的int(1)和int(10)的区别

今天偶然发现同事在写sql建表的时候把int类型括号后面的数字写成了1&#xff0c;但是我发现数据库里面的值已经远远超过了1位所能表示的范围&#xff0c;所以括号里面的数字肯定不是表示长度了&#xff08;印象中早期的navivat建表的时候&#xff0c;int类型如果默认不指定长度…

devc++如何建立一个c++项目?devc++提示源文件未编译?

打开devc APP后是这样的界面&#xff1b; 点击文件-> 新建->项目&#xff0c;这一点应该不难&#xff0c;主要是最后这个选择什么&#xff1f; 这样即可。 devc提示源文件未编译&#xff1f; 点击工具->编译选项&#xff1b; 如果不能解决&#xff0c;那就是可能路径…

文物数字化建模纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 1、文物3D数字化建模的特点 文物埋在地下历经千年&#xff0c;由于时…

转动惯量与惯性张量 的推导

从牛顿第二定律推出绕固定轴旋转的转动惯量&#xff0c;再用类似方法从牛顿第二定律推出绕固定点转动的惯性张量 基础定义 角速度 ω \omega ω是一个三维向量&#xff0c;方向表示旋转轴&#xff0c;用右手定则代表旋转方向&#xff0c;长度代表旋转弧度的速度 线速度&#…

WebGL+Three.js入门与实战——给画布换颜色、绘制一个点、三维坐标系

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

为了吃鸡苦练狙击,避免坑队友自己造一个狙击游戏!

引言 一文教会你造一个简易的狙击游戏。 说到狙击&#xff0c;相信大家都不陌生&#xff0c;无论是影视作品还是网络游戏&#xff0c;都经常能看到狙击枪的身影&#xff0c;最深刻的是它能够从百里之外&#xff0c;一枪爆头。 本文将介绍如何在Cocos Creator中造一个简易的狙…

UE5 动画 Sequencer-学习笔记

P2. 课程介绍 资料&#xff1a;https://www.bilibili.com/video/BV1Ag411873f?p2&vd_source707ec8983cc32e6e065d5496a7f79ee6 Sequencer不仅可以做互动动画&#xff0c;还可以导出视频与序列帧 P3-4. 界面介绍 https://www.bilibili.com/video/BV1Ag411873f?p3&spm_…

数据挖掘任务一般流程

数据挖掘是从大量数据中提取有价值信息的过程。它涉及多个步骤&#xff0c;每一步都对整个数据挖掘过程至关重要。以下是数据挖掘任务的一般流程&#xff1a; 业务理解&#xff1a; 确定业务目标。评估当前情况。定义数据挖掘问题。制定一个初步计划来达到这些目标。 数据理…

WPF-附加属性《十二》

非常重要 依赖属性和附加属性&#xff0c;两者是有关系的&#xff0c;也是有些区别的&#xff0c;很多时候&#xff0c;可能会把两者混淆了。 附加属性&#xff08;Attach Property&#xff09; 顾名思义&#xff0c;就是附加上面的属性&#xff0c;自身是没有的&#xff0c;…

人工智能在红斑狼疮应用主要以下4个方面

人工智能&#xff08;Artificial Intelligence, AI&#xff09;在医学领域的应用已取得了一定的进展。红斑狼疮&#xff08;Systemic Lupus Erythematosus, SLE&#xff09;是一种免疫系统性疾病&#xff0c;对该疾病进行诊断和治疗是一个复杂的过程。人工智能可以发挥作用&…