CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问: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目前是