AM335X——SPI设备驱动

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 OMAPUser 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.内核自带的测试驱动程序的compatiblerohm,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 CPUSmakezImagej{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目前是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值