您的位置:首页 > 运维架构 > Linux

beaglebone black上使用TI sdk中的linux系统来编写spi驱动

2015-07-13 14:28 871 查看
首先看下spi驱动的大概构成:



下边看下编写spi驱动的大致流程(3.2及之前版本内核):



而在TI最新的sdk中内核版本为3.14, 使用了dts. 编写方法稍有不同.

我们先来了解一下3.2及之前版本内核的spi驱动编写方法:

spi上层驱动:

先编写spi_board_info结构体, 然后调用spi_register_board_info方法, 它会自动根据spi_board_info的bus_num成员匹配spi_master, 如果匹配成功则创建spi_device, 另外spi_device的名称就是根据spi_board_info结构体的名称来命名的, 同时如果查到系统中有同名的spi_driver则调用spi_driver的probe函数, 然后我们就可以在probe函数中做自己需要的工作, 由此可见spi_board_info的中的名称(.modalias)必须和spi_driver中的名称一样.
我们需要做的是编写两个文件, 一个是spi_board_info和spi_register_board_info, 这个文件要被编译到内核里, 因为spi_register_board_info并未被导出必须编译到系统中, 如果编译为模块会发现该函数未定义. 另外一个就是我们需要实现的具体驱动, 一般都在probe里创建chrdev然后用chrdev的read/write/ioctl里调用spi_read/spi_write.

spi核心层:

spi驱动上层中调用spi_write和spi_read, 然后spi核心层的spi.c中将它们处理之后转交为spi_master来处理, spi_master在io上发出时序信号. 我们不需要关心这部分的内容

spi驱动底层(控制器):

spi_master 负责跟芯片的spi控制器打交道, mster中的transfer就是产生时序信号的函数.spi_master使用了platform总线. 那么我们首先查看spi_master驱动中platform_driver的名称, 根据名称到板级初始化文件中创建platform_device结构体, 并把设备添加到板级初始化中, 到内核里使能spi主控驱动, 这样系统启动时候就能发现同名的platform driver和device, spi master随之初始化. 剩下的就是编写spi驱动来调用spi_writer和spi_read等工作.
spi_master驱动一般由厂家提供, 我们只需要完善platform_device部分即可.

注意: spi上层是 spi_driver和spi_device spi底层是 platform_device 和platform_driver

在TI提供的系统中, spi_master驱动位于 sdk/board-support/linux-3.14.26-g2489c02/drivers/spi/spi-omap2-mcspi.c中, 而且在系统中已经创建了同名的设备, 所以只要一开机就会初始化spi_master驱动, 我们需要关心的只是去dts里配置spi的引脚即可.

根据之前的博客, 我们已经可以从tftp下载内核 并且挂载nfs为根文件系统了, 我们可以到 /tftp 目录下查看到启动系统使用的内核和设备数文件:

zImage-am335x-evm.bin 和 am335x-boneblack.dtb

然后再查看一下 /dev 下有没有spi设备( ls /dev ), sdk中的系统是没有开启spi设备的.

首先我们来编写注册spi_board_info的代码 (新建 sdk//board-support/linux-3.14.26-g2489c02/drivers/spi/bbb_spi_info.c):

#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/io.h>

#include <linux/gpio.h>

static struct spi_board_info spi_info_jz2440[] = {
{
.modalias = "spi_tst",  /* 对应的spi_driver名字也是"oled" */
.max_speed_hz = 10000000,      /* max spi clock (SCK) speed in HZ */
.bus_num = 1,     /* jz2440里OLED接在SPI CONTROLLER 1 */
.mode    = SPI_MODE_0,
//     .chip_select   = S3C2410_GPF(1), /* oled_cs, 它的含义由spi_master确定 */
//      .platform_data = (const void *)S3C2410_GPG(4) , /* oled_dc, 它在spi_driver里使用 */
},
{
.modalias = "100ask_spi_flash",  /* 对应的spi_driver名字也是"oled" */
.max_speed_hz = 80000000,      /* max spi clock (SCK) speed in HZ */
.bus_num = 1,     /* jz2440里OLED接在SPI CONTROLLER 1 */
.mode    = SPI_MODE_0,
//   .chip_select   = S3C2410_GPG(2), /* flash_cs, 它的含义由spi_master确定 */
}
};

static int spi_info_jz2440_init(void)
{
printk ("spi_info_jz2440_init......................................................\n");
return spi_register_board_info(spi_info_jz2440, ARRAY_SIZE(spi_info_jz2440));
}

module_init(spi_info_jz2440_init);
MODULE_DESCRIPTION("OLED SPI Driver");
MODULE_AUTHOR("weidongshan@qq.com,www.100ask.net");
MODULE_LICENSE("GPL");
这里我们借用韦东山老师的代码, 但是我们只更改 .modalias 成员为 "spi_tst" , 别的不做更改. 我们这样就能测试是否是该成员起作用啦~

为了将这个bbb_spi_info.c编译进内核我们打开 sdk//board-support/linux-3.14.26-g2489c02/drivers/spi/下的Makefile文件并做如下更改:



这样重新编译内核之后bbb_spi_info.c就被编译进内核啦.也就是说系统里已经有了名称为 spi_tst 的spi_device.那么很明显我们接下来要做的就是编写一个名为spi_tst的spi_driver . 这样spi_driver的probe函数就可以被顺利调用啦.

编译内核的方法:

回到sdk路径下

sudo make linux


新的内核在sdk/ board-support/linux-3.14.26-g2489c02/arch/arm/boot下, 将生成的 zImage拷贝到 /tftpboot 目录下

然后更改uboot的启动参数, 将内核名称由之前的 zImage-am335x-evm.bin更改为zImage, 这样就能通过tftp将新内核下载运行了.

setenv bootfile zImage
saveenv
run netboot
这样就使用新的内核启动了.

接下来我们编写spi_driver驱动:

思路: 我们在spi_driver的probe函数里创建字符设备并且新建设备节点, 如果与spi_device匹配成功的话就可以在/dev下看到我们新建的设备.

/*
* tst-spi.c
* 测试spi_driver能否与spi_device匹配并调用probe创建/dev下的节点(仿照spidec.c来编写)
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>

#define SPIDEV_MAJOR 163
#define N_SPI_MINORS 32

static DECLARE_BITMAP(minors, N_SPI_MINORS);
static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);

static struct class *tst_spi_class;

static int tst_spi_probe(struct spi_device *spi)
{
device_create(tst_spi_class, NULL, MKDEV(SPIDEV_MAJOR, 0), NULL, "abc");

return status;
}

static ssize_t tst_spi_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
printk ("tst_spi_write\n");
}
static ssize_t tst_spi_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
printk ("tst_spi_read\n");
}
static long tst_spi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk ("tst_spi_ioctl\n");
}
static int tst_spi_open(struct inode *inode, struct file *filp)
{
printk ("tst_spi_open\n");
}

static int tst_spi_release(struct inode *inode, struct file *filp)
{
printk ("tst_spi_release\n");
}

static const struct file_operations tst_spi_fops = {
.owner = THIS_MODULE,
.write = tst_spi_write,
.read = tst_spi_read,
.open = tst_spi_open,
.unlocked_ioctl = tst_spi_ioctl,
.release = tst_spi_release,
};

static const struct of_device_id tst_dt_ids[] = {
{.compatible = "tst_spi"},
};

static int tst_spi_remove(struct spi_device *spi)
{
printk ("tst_spi_remove\n");
}

static struct spi_driver tst_spi_driver = {
.driver = {
.name = "tst_spi",<span style="white-space:pre">		</span>//注意这个要与 spi_board_info里的 .modalias成员一样, 因为spi_device就是使用这个成员的名字命名的!!!!
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tst_dt_ids),
},
.probe = tst_spi_probe,
.remove = tst_spi_remove,
};

static int tst_spi_init(void)
{
int status;

status = register_chrdev(SPIDEV_MAJOR, "tst_spi", &tst_spi_fops);
if (status < 0)
{
printk ("tst_spi_init failed/////////////////////////////////////////////////////\n");

}

tst_spi_class = class_create(THIS_MODULE, "tst_spi");
if (IS_ERR(tst_spi_class))
{
unregister_chrdev(SPIDEV_MAJOR, "tst_spi");
return PTR_ERR(tst_spi_class);
}

status = spi_register_driver(&tst_spi_driver);
if (status < 0)
{
class_destroy(tst_spi_class);
unregister_chrdev(SPIDEV_MAJOR, "tst_spi");
}

return status;
}

void tst_spi_exit(void)
{
spi_unregister_driver(&tst_spi_driver);
class_destroy(tst_spi_class);
unregister_chrdev(SPIDEV_MAJOR, "tst_spi");
}

module_init(tst_spi_init);
module_exit(tst_spi_exit);
MODULE_LICENSE("GPL");


将该驱动编译为ko, 拿到开发板上insmod就可以发现 /dev/abc 节点已经被成功创建了.

这时候我们已经可以在xxx_probe中调用spi_write/spi_read来与底层通信了.

小结: spi上层使用spi核心层提供的spi_register_board_info函数通过将spi_board_info信息赋值给spi_device. 我们只要spi_driver中名字与其一样就能创建设备节点. 应用层打开该节点并调用read/write/ioctl就能操作spi底层.

接下来分析一下底层的spi_master驱动:

spi_master驱动使用了platform技术, 驱动的platform_driver中如果发现同名的platform就能初始化spi_master, TI提供的系统在初始化时已经提供了omap2_mcspi的platform设备.

,

spi_master驱动文件: spi-omap2-mcspi.c

static const struct of_device_id omap_mcspi_of_match[] = {
{
.compatible = "ti,omap2-mcspi",
.data = &omap2_pdata,
},
{
.compatible = "ti,omap4-mcspi",
.data = &omap4_pdata,
},
{ },
};


static struct platform_driver omap2_mcspi_driver = {
.driver = {
.name =         "omap2_mcspi",
.owner =        THIS_MODULE,
.pm =           &omap2_mcspi_pm_ops,
.of_match_table = omap_mcspi_of_match, //匹配dts中的项
},
.probe =        omap2_mcspi_probe,
.remove =       omap2_mcspi_remove,
};
所以系统初始化时候就已经初始化了spi_master, 因为系统是3.14, 使用了dts来初始化设备. 我们来看下如何初始化spi设备.

首先我们看下设备书文件 am335x-boneblack.dtb, dtb是将dts文件编译之后的生成的文件,它无法直接更改,因此我们需要将它反编译回dts进行更改:

dtc -I dtb -O dts am335x-boneblack.dtb am335x-boneblack.dts
在dts中查找关于spi0的内容:

spi@48030000 {
compatible = "ti,omap4-mcspi";
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x48030000 0x400>;
interrupts = <0x41>;
ti,spi-num-cs = <0x2>;
ti,hwmods = "spi0";
dmas = <0x25 0x10 0x25 0x11 0x25 0x12 0x25 0x13>;
dma-names = "tx0", "rx0", "tx1", "rx1";
status = "disable";
};

.接下来还要配置dts文件.首先我们看下这个

spi@48030000 {
中的 48030000是什么, 翻到am335x芯片手册的 180页



这个地址就是spi0的寄存器起始地址.

下边我们打开TI的pin mux工具来配置dts:

root@beaglebone:/lib/firmware# cat ADAFRUIT-SPI0-00A0.dts
/dts-v1/;

/ {
compatible = "ti,beaglebone", "ti,beaglebone-black";
part-number = "ADAFRUIT-SPI0";
version = "00A0";

fragment@0 {
target = <0xdeadbeef>;

__overlay__ {

spi0_pins_s0 {
pinctrl-single,pins = <0x150 0x30 0x154 0x30 0x158 0x10 0x15c 0x10>;
linux,phandle = <0x1>;
phandle = <0x1>;
};
};
};

fragment@1 {
target = <0xdeadbeef>;

__overlay__ {
#address-cells = <0x1>;
#size-cells = <0x0>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <0x1>;

spidev@0 {
spi-max-frequency = <0x16e3600>;
reg = <0x0>;
compatible = "spidev";
};

spidev@1 {
spi-max-frequency = <0x16e3600>;
reg = <0x1>;
compatible = "spidev";
};
};
};

__symbols__ {
spi0_pins_s0 = "/fragment@0/__overlay__/spi0_pins_s0";
};

__fixups__ {
am33xx_pinmux = "/fragment@0:target:0";
spi0 = "/fragment@1:target:0";
};

__local_fixups__ {
fixup = "/fragment@1/__overlay__:pinctrl-0:0";
};
};


这个是工具生成的dts文件, 参考上边的文件我们来修改一下我们的dts中spi部分:



注意compatiable 不能更改, 因为这个是为了匹配 sdk/board-support/linux-3.14.26-g2489c02/drivers/spispi-omap2-mcspi.c中的 omap2_mcspi_driver, 一定要将这里的 status 更改为 "okay", 然后编译为dtb文件放到 /tftpboot 下

dtc -I dts -O dtb am335x-boneblack.dts > am335x-boneblack.dtb
这时spi控制器的引脚已经配置好了.

理论上应用层打开 /dev/abc之后就可以操作芯片的spi控制器了(spi_driver的代码后续完善.....)

应用层 : read

spi上层: tst_spi_read

spi底层:master->transfer
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: