AM335X——SPI设备驱动

sdk6下启用spidev Com3354开发板 , 资料基于sdk6.0: 一   首先去除与spi0冲突的i2c1: 其他的板子 如果没有冲突的引脚可以略过这一步. 二    内核配置: Device Drivers  --->  [*] SPI support  --->    McSPI driver for OMAP  如果是要模拟spi设备接口的话,还需要在配置的时候加上 阅读详情

优快云仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/01/AM335X——SPI设备驱动/#more

最近在AM335X上写了几个SPI设备驱动,记录一下心得。

1. 准备工作

与前面写过的I2C驱动一样,SPI驱动也涉及SPI控制器(适配器)驱动和SPI设备驱动。
SPI控制器驱动这里不就写了,直接使用SDK自带的,只写SPI设备的驱动。
在写SPI设备驱动之前,需要先验证下SDK提供的SPI驱动是否能用,相关的设置是否正确,因此使用内核自带的一个SPI设备驱动进行测试。

1.1 驱动

首先配置内核,执行make menuconfig,勾选上McSPI driver for OMAP和User mode SPI device driver support。

1.2 设备树

然后还要向设备树添加如下内容:
{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;
ti,pindir-d0-out-d1-in;

spidev@0 {
    spi-max-frequency = <25000000>;
    reg = <0>;
    compatible = "rohm,dh2228fv";

    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low */
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};
};
{% endcodeblock %}
这里有几个细节,简单说一下:
1.pinctrl-0属性引用的spi1_pins,指定了SPI的几个复用引脚,里面的0x190是真实地址0x990-0x800,也就是说设备树中的地址是相对0x800的偏移;
2.注意SPI复用引脚不要在设备树其它节点中使用,不然SPI驱动可能用不了;
3.AM335x的MOSI和MISO可以互换,需要加上ti,pindir-d0-out-d1-in;来指定D0是MOSI,D1是MISO;
4.reg = <0>;表示硬件片选,这里为硬件片选0;
5.内核自带的测试驱动程序的compatible为rohm,dh2228fv;

1.3 测试程序

使用内核提供的测试程序,编译,测试。
测试文件路径:Documentation/spi/spidev_test.c;
交叉编译后执行./spidev_test -D /dev/spidev1.0 -v
结果:

spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 KHz)
TX | FF FF FF FF FF FF 40 00 00 00 00 95 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F0 0D  | ......@....▒..................▒.
RX | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF  | ................................

此时可以通过逻辑分析仪或者短接MOSI和MISO,判断发出和接收的数据是否正常,从而验证SPI控制器驱动是否正常。

1.4 编译脚本

为了方便后续的编译,写了一个脚本进行操作,这个脚本也相当于操作流程,以供参考:
{% codeblock lang:sh [compiler_kernel.sh] %}
#!/bin/bash

#step 0:set env
export CPUS=grep -c processor /proc/cpuinfo
export ARCH=arm
export CROSS_COMPILE=/home/hceng/gcc-linaro-5.3-2016.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
export PATH=/home/hceng/gcc-linaro-5.3-2016.02-x86_64_arm-linux-gnueabihf/bin:$PATH

#step 1:clean kernel
#make distclean

#step 2:copy kernel config file
#make tisdk_am335x-evm_defconfig

#step 3:compiler kernel
#make uImage LOADADDR=0x10008000 -j C P U S m a k e z I m a g e − j {CPUS} make zImage -j CPUSmakezImage−j{CPUS}

#step 4:compiler device tree
#make dtbs
make am335x-evm.dtb

#step 5:compiler driver module file(dynamic loading)
#make modules
#make modules_install INSTALL_MOD_PATH=~/rootfs/lib/modules/4.1.18-gbbe8cfc

#step 6:copy zImage and dtb to tftp download
rm /home/hceng/tftp/zImage
rm /home/hceng/tftp/am335x-evm.dtb

cp ./arch/arm/boot/zImage /home/hceng/tftp/
cp ./arch/arm/boot/dts/am335x-evm.dtb /home/hceng/tftp/
{% endcodeblock %}

其中第六步,拷贝倒tftp目录下,是为了方便板子启动的时候,通过U-Boot直接tftp下载编译过的内核和设备树,相关命令如下:

setenv ipaddr 192.168.1.14; setenv serverip 192.168.1.11; setenv gatewayip 192.168.1.1; setenv netmask 255.255.255.0; setenv fdtfile 'am335x-evm.dtb'; setenv rootpath '/home/hceng/rootfs';

setenv netargs "setenv bootargs console=${console} ${optargs}  root=/dev/nfs  rootfstype=nfsroot  nfsroot=${serverip}:${rootpath}  ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth0:off"

setenv netboot "echo Booting from network ...; setenv autoload no; tftp ${fdtaddr} ${fdtfile}; tftp ${loadaddr} ${bootfile}; run netargs; bootz ${loadaddr} - ${fdtaddr}"

saveenv

run netboot

2. SPI设备——tlc5615

TLC5615是一个10位的DAC,最大输出电压位基准电压的两倍。
这个驱动比较简单,没什么特别的难点,
唯一特殊的是TLC5615每次传输是12位数据(10位data+2位extra),因此在probe()函数里,需要spi->bits_per_word = 12;;
另外,因为每次传输的数据位12位,spi_write()的第三个参数不再是1,而是spi_write(spi_tlc5615_dev, &ker_buf, 2);;

2.1 设备树

{% codeblock lang:dts %}
/* SPI Busses */
&spi1 {
status = “okay”;
pinctrl-names = “default”;
pinctrl-0 = <&spi1_pins>;

spidev@0 {
    spi-max-frequency = <25000000>;
    reg = <0>;
    compatible = "rohm,dh2228fv";

    /* spi-cpha; sets CPHA=1, default is CPHA=0 */
    /* spi-cpol; sets CPOL=1, default is CPOL=0 */
    /* spi-cs-high; default is spi cs low       */
};

spidev@1 {
    spi-max-frequency = <25000000>;
    reg = <1>;
    compatible = "ti,tlc5615";
};

};

&am33xx_pinmux {
spi1_pins: pinmux_spi1 {
pinctrl-single,pins = <
0x190 (PIN_INPUT_PULLUP | MUX_MODE3) /* spi1_sclk /
0x194 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d0 /
0x198 (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_d1 /
0x19c (PIN_INPUT_PULLUP | MUX_MODE3) /
spi1_cs0 */
>;
};
};
{% endcodeblock %}

2.2 驱动程序

{% codeblock lang:c [tl5615.c] %}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>

static int major;
static dev_t devid;
static struct class *tlc5615_class;
static struct cdev tlc5615_cdev;
static struct spi_device *spi_tlc5615_dev;

static int tlc5615_open (struct inode *node, struct file *filp)
{
return 0;
}

static int tlc5615_release (struct inode *node, struct file *filp)
{
return 0;
}

static ssize_t tlc5615_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{

int ret;
unsigned int ker_buf;

if(copy_from_user(&ker_buf, buf, 4))
   return 0;

if (ker_buf > 1023)
    ker_buf = 1023;

ker_buf = (ker_buf << 2) & (0xFFC);

//printk("ker_buf=%d\n", ker_buf);
ret = spi_write(spi_tlc5615_dev, &ker_buf, 2);
if(ret != 0)
{
    printk("spi write error\n");
    return -EINVAL;
}

return 4; 

}

static struct file_operations tlc5615_ops = {
.owner = THIS_MODULE,
.open = tlc5615_open,
.write = tlc5615_write,
.release = tlc5615_release,
};

static int tlc5615_probe(struct spi_device *spi)
{
int ret;

spi->bits_per_word = 12; //tl5615 transmits 12bits(10bits data + 2bit extra)each time.
if (spi_setup(spi) < 0)
{
    printk("spi master doesn't support 12 bits/word \n");
    return -EINVAL;
}   
    
spi_tlc5615_dev = spi;

if(alloc_chrdev_region(&devid, 0, 1, "tlc5615") < 0)
{
    printk(KERN_INFO"Unable to alloc_chrdev_region.\n");
    return -EINVAL;
}   
    
major = MAJOR(devid);
cdev_init(&tlc5615_cdev, &tlc5615_ops);        
ret = cdev_add(&tlc5615_cdev, devid, 1);
if (ret < 0)
{
    printk(KERN_ERR "Unable to cdev_add.\n");
    goto error;
}
    
tlc5615_class = class_create(THIS_MODULE, "tlc5615"); 

device_create(tlc5615_class, NULL, MKDEV(major, 0), NULL, "tlc5615"); // /dev/tlc5615

return 0;

error:
unregister_chrdev_region(devid, 1);

return -EINVAL;

}

static int tlc5615_remove(struct spi_device *spi)
{
device_destroy(tlc5615_class, MKDEV(major, 0));

class_destroy(tlc5615_class);

unregister_chrdev_region(devid, 1);
cdev_del(&tlc5615_cdev);

return 0;

}

static const struct of_device_id of_match_spi[] = {
{ .compatible = “ti,tlc5615”, .data = NULL },
{ /* sentinel */ }
};

static struct spi_driver tlc5615_driver = {
.probe = tlc5615_probe,
.remove = tlc5615_remove,
.driver = {
.name = “tlc5615”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
};

module_spi_driver(tlc5615_driver);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am335x board spi device: tl5615 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}

2.3 测试程序

{% codeblock lang:c [tl5615_app.c] %}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// ./tl5615_app val
// val = 0~4.096v (0~1023)

int main(int argc, char **argv)
{
int fd;
float f_val = atof(argv[1]);
unsigned int i_val = f_val * 1000 / 4;
if(i_val > 1023)
i_val = 1023;

fd = open("/dev/tlc5615", O_RDWR);   
if (fd < 0)
    printf("Can't open!\n");

write(fd, &i_val, 4);

close(fd);

return 0;

}
{% endcodeblock %}
输出电压计算公式:

output=2*(Vref)*(val/1024)  //其中val为SPI传输的前10位数据。

3. SPI设备——ssd1306

SSD1306是一个分辨率为128*64的OLED显示屏。
OLED的驱动稍微麻烦一点,除了对SSD1306的基本操作,还要解决以下两个问题。

  • 1.需要设备树提供DC引脚
    采用SPI接口的OLED,除了时钟引脚(CLK)、使能引脚(EN)、数据发送引脚(MOSI)外,还需要数据/命令切换引脚(DC)。
    因为OLED只接收数据的缘故,AM335X的数据接收引脚(MISO)就不需要了。
    因此需要在设备树中加入DC引脚信息,并在驱动中解析使用。

  • 2.需要支持软件片选
    AM335X的SPI只有两个硬件片选CS0和CS1,现在有了三个设备,两个片选自然是不够的,因此需要添加软件片选,即使用GPIO作为片选引脚。
    而AM335X的SDK目前是不支持软件片选的,因此需要修改SPI控制器驱动(spi-omap2-mcspi.c)来实现。

修改的思路也比较简单,关注两个变量**spi->chip_select和spi->cs_gpio**。
spi->chip_select是设备树中reg的值,为0、1分别表示CS0和CS1,从2开始,就意味着该设备使用GPIO作为片选了;
spi->cs_gpio是设备树中cs-gpios的值,如果在设备树中值为<0>,这里将为-2

Linux spi设备驱动 Linux SPI驱动分主机驱动和设备驱动,主机驱动往往由厂商实现好,第三方开发员直接开发设备驱动即可。下面不阐述SPI子系统及驱动相关的架构,直接上一个SPI接口的温度传感器(TMP125)驱动来理解SPI驱动如何运作。这里也涉及Linux 驱动模型的知识。    在板级文件增加SPI设备信息: static struct omap2_mcspi_device_config ads784 阅读详情

相关推荐

Linux驱动中设备树的pinctrl和gpio子系统实践

使用#gpio-cells属性指定gpio引脚节点中gpio编号和GPIO属性值的个数,这里是2。pinctrl子系统用于描述引脚的功能和属性,而gpio子系统用于描述通用输入输出引脚的功能和属性。在pinctrl-names属性中指定了使用的pinctrl配置名称(这里是"default”),并通过pinctrl-0属性将之前定义的pinctrl节点引入到设备节点中。以上是对Linux驱动中设备树中pinctrl和gpio子系统应用实践的介绍,通过阅读本文,相信读者对这方面的知识有了更深入的理解。

CodeSageX的博客 484

打造德州仪器AM335X的QNX操作系统 BSP

AM335X系列处理器由德州仪器(Texas Instruments,简称TI)开发,是一种广泛应用于嵌入式系统的高性能处理器。与这些处理器相配套的QNX BSP(Board Support Package)是德州仪器为基于QNX神经元实时操作系统(RTOS)开发板所提供的支持软件包。QNX BSP为开发者提供了一系列预配置和优化的工具、驱动程序和应用程序,使得开发者可以更快地启动开发进程并简化应用的开发和部署。

weixin_35755434的博客 837

CentOS 7 常用命令大全

CentOS中Linux命令

m0_63075763的博客 2017

Direct-LiNGAM算法理解

现在是大数据的时代,数据即是财富。那么如何从数据中发现因果关系呢?这是我们促进我们更加高效利用数据的一个问题?本文介绍了一种方法,可以在符合条件的观察数据中恢复出数据背后的因果结构,帮助我们更好理解数据的产生机制。

小小程序猿的博客 4589

编译原理(东南大学)一:【编译原理期末冲刺救命笔记】速存!看完再上考场稳了!

本文详细介绍了编译原理的核心概念和流程,包括程序设计语言的分类、编译程序的各个阶段(如词法分析、语法分析、语义分析、中间代码生成、优化和目标代码生成)以及编译程序生成和构造的基础知识。文章还探讨了编译中的表格管理、出错处理和遍的概念,以及编译基础知识如高级语言、语法规则、字母表和符号串的运算。此外,文中还涉及文法与语言的关系,包括文法的概念、非终结符、终结符、产生式、推导和规约等。整体内容旨在为学习编译原理的学生提供全面的复习资料和考试准备指南。

一位搞嵌入式和前端的genius的博客 1026

用LangFuse+LangChain打造可观测的AI应用:从埋点到成本监控实战

本文详细介绍了如何将开源可观测性平台LangFuse深度集成到基于LangChain构建的AI应用中,特别是电商客服机器人场景。通过从基础SDK集成、提示词管理到成本监控的完整实战,帮助开发者实现对LLM应用内部工作流的清晰追踪、问题排查与费用分析,从而打造稳定、可控的生产级AI应用。

dapp9builder的博客 709

Balser相机的硬件触发实战:从连线到信号处理

本文详细介绍了Balser相机硬件触发的实战应用,从硬件接线、信号定义到软件配置(Pylon Viewer及API)提供了完整指南。重点阐述了如何利用外部物理电信号实现微秒级精准触发,解决工业场景下的同步与延迟问题,并分享了信号调理、抗干扰等高级实战经验。

y9z0a1b的博客 336

科研级YOLOv8 Neck革新:BiFPN双向特征金字塔赋能,让多尺度融合更高效的实战指南

在文件夹下新建BiFPN.py,将上述BiFPNLayer和BiFPN类的代码写入其中。这一步是为了让YOLOv8的代码架构能识别我们的自定义Neck模块。

m0_64661770的博客 152

【WorkBuddy专栏27】WorkBuddy 和 CodeBuddy 到底什么关系——一篇文章终结所有混淆

WorkBuddy 和 CodeBuddy 到底什么关系——一篇文章终结所有混淆

AI人人懂 2565

华为od 面试八股文_前端_02_含答案

华为od 面试八股文_前端_02_含答案

MISAYAONE的博客 1996

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

上一篇: AM335X——1-Wire和IrDA驱动
下一篇: AM335X——hwmon和input子系统
hceng_blog
博客等级 码龄10年 263粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值