Linux下SPI驱动框架分析
Linux的spi接口驱动实现目录在linux-2.6.22.6\drivers\spi
下。首先通过看Kconfig 和 Makefile来找找思路
先看Makefile,
#
# Makefile for kernel SPI drivers.
#
ifeq ($(CONFIG_SPI_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif
# small core, mostly translating board-specific
# config declarations into driver model code
obj-$(CONFIG_SPI_MASTER) += spi.o
#//spi 作为主机操作,我们可以通过make menuconfig的时候选上自己对应平台的
# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o
obj-$(CONFIG_SPI_AU1550) += au1550_spi.o
obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o
obj-$(CONFIG_SPI_IMX) += spi_imx.o
obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o
obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o
obj-$(CONFIG_SPI_MPC52xx_PSC) += mpc52xx_psc_spi.o
obj-$(CONFIG_SPI_MPC83xx) += spi_mpc83xx.o
obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o
obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o
# ... add above this line ...
# SPI protocol drivers (device/link on bus) //spi 驱动相关
obj-$(CONFIG_SPI_AT25) += at25.o
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
# ... add above this line ...
#//spi 作为从机操作,目前还没有支持
# SPI slave controller drivers (upstream link)
# ... add above this line ...
# SPI slave drivers (protocol for that link)
# ... add above this line ...
于是从Makefile里可以得出我们需要进行分析文件代码是:spi.c ,spi_s3c24xx.c。
spi_s3c24xx.c:
static struct platform_driver s3c24xx_spidrv = {
.probe = s3c24xx_spi_probe,
.remove = s3c24xx_spi_remove,
.suspend = s3c24xx_spi_suspend,
.resume = s3c24xx_spi_resume,
.driver = {
.name = "s3c2410-spi",
.owner = THIS_MODULE,
},
};
static int __init s3c24xx_spi_init(void)
{
return platform_driver_register(&s3c24xx_spidrv);
}
此处定义一个platform_driver ,当定义并添加一个platform_device时(platform子系统),系统调用s3c24xx_spi_probe。
platform_device相关代码在arch/arm/plat-s3c24xx/devs.c
中:
static struct resource s3c_spi0_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI,
.end = S3C24XX_PA_SPI + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI0,
.end = IRQ_SPI0,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &s3c_device_spi0_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
static struct resource s3c_spi1_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI + S3C2410_SPI1,
.end = S3C24XX_PA_SPI + S3C2410_SPI1 + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI1,
.end = IRQ_SPI1,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi1_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &s3c_device_spi1_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
系统通过platform_add_device
将s3c_spi0_resource
加入到平台总线中(目前系统尚未添加,需要自己添加)。
spi注册到平台总线会直接调用s3c24xx_spi_probe。
s3c24xx_spi_probe:
1、调用spi核心层的接口分配一个spi核心层能识别的spi控制器对象struct spi_master
并额外分配出我们驱动需要的上下文数据空间sizeof(struct s3c24xx_spi)
,这个空间的地址可以通过核心层提供的接口提取出来:
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi));
hw = spi_master_get_devdata(master);
提取平台设备指定的平台数据:
hw->pdata = pdev->dev.platform_data;
在上面贴出的平台设备端代码里面没有设置平台数据(在进行spi移植时需要自己填充数据内容),它的类型为:
struct s3c2410_spi_info {
unsigned long pin_cs; /* simple gpio cs */
unsigned long board_size;
struct spi_board_info *board_info;
void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);
};
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[KOBJ_NAME_LEN];
const void *platform_data;
void *controller_data;
int irq;
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz;
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num;
u16 chip_select;
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode;
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
然后根据平台设备指定的资源(寄存器地址空间、中断号)向操作系统请求资源空间并建立起映射为以后所用。最终将平台设备指定的数据注射到spi:
/* register all the devices associated */
bi = &hw->pdata->board_info[0];
for (i = 0; i < hw->pdata->board_size; i++, bi++) {
dev_info(hw->dev, "registering %s\n", bi->modalias);
bi->controller_data = hw;
spi_new_device(master, bi);
}
spi移植准备条件如上,下面可是进行spi驱动的移植。
spi驱动移植
1、 make menuconfig添加spi配置
2、spi采取主控制器与设备分离的思想,需要platform_driver及platform_device数据结构,系统默认已存在platform_driver(spi_s3c24xx.c),硬件平台platform_device定义在arch\arm\plat-s3c24xx\devs中,相关定义如下:
/* SPI (0) */
static struct resource s3c_spi0_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI,
.end = S3C24XX_PA_SPI + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI0,
.end = IRQ_SPI0,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &s3c_device_spi0_dmamask,
.coherent_dma_mask = 0xffffffffUL,
}
};
EXPORT_SYMBOL(s3c_device_spi0);
/* SPI (1) */
static struct resource s3c_spi1_resource[] = {
[0] = {
.start = S3C24XX_PA_SPI + S3C2410_SPI1,
.end = S3C24XX_PA_SPI + S3C2410_SPI1 + 0x1f,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SPI1,
.end = IRQ_SPI1,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_spi1_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &s3c_device_spi1_dmamask,
.coherent_dma_mask = 0xffffffffUL,
}
};
以移植spi0为例,在arch\arm\plat-s3c24xx\common-smdk.c的platform_device总集中添加,
static struct platform_device __initdata *smdk_devs[] = {
&s3c_device_nand,
#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE)
&s3c_device_dm9k,
#endif
#ifdef CONFIG_SERIAL_EXTEND_S3C24xx
&s3c_device_8250,
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C2410
&s3c_device_ts,
#endif
&smdk_keyboard,
&s3c_device_spi0,
};
然后进行make uImage进行编译并烧录启动。
启动后发生错误打印信息:
s3c2410-spi s3c2410-spi.1: No platform data supplied
s3c2410-spi: probe of s3c2410-spi.1 failed with error -2
根据打印信息查看源码(s3c24xx_spi_probe函数):
说明s3c24xx_spi_probe函数中的pdev->dev.platform_data没有赋值。
由上述移植前准备分析,需要加s3c2410_spi_info结构的数据。于是在devs.c中添加如下代码(红色部分是添加代码):
static struct spi_board_info s3c2410_spi0_board[] = {
[0] = {
.modalias = "spidev", //设备号名称
.platform_data = NULL, // 设备的私有数据
//.irq = IRQ_EINT1,
.chip_select = 0, // 用于多个SPI选择,
.bus_num = 1,
.max_speed_hz = 500*1000,
},
};
static struct s3c2410_spi_info s3c2410_spi_platdata = {
.pin_cs = S3C2410_GPG2, // CS 片选
.board_info = s3c2410_spi0_board,
.board_size = ARRAY_SIZE(s3c2410_spi0_board),
};
static u64 s3c_device_spi0_dmamask = 0xffffffffUL;
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource),
.resource = s3c_spi0_resource,
.dev = {
.dma_mask = &s3c_device_spi0_dmamask,
.coherent_dma_mask = 0xffffffffUL,
.platform_data = &s3c2410_spi_platdata,
}
};
编译下载后,串口没有提示任何spi的信息,在sys/bus/platform/device看到了spi的设备文件信息。但是/dev下确实没有spi设备信息。说明没有设备文件。
通过网络搜索,做一下修改(为啥这样修改还不清楚):
在arch\arm\plat-s3c24xx\common-smdk.c文件中添加:
static struct spi_board_info s3c2410_spi0_board[] = {
[0] = {
.modalias = "spidev",
.platform_data = NULL, // 设备的私有数据
//.irq = IRQ_EINT1,
.chip_select = 0, // 用于多个SPI选择,
.bus_num = 1,
.max_speed_hz = 500*1000,
},
};
static struct s3c2410_spi_info s3c2410_spi_platdata = {
.pin_cs = S3C2410_GPG2, // CS 片选
.board_info = s3c2410_spi0_board,
.board_size = ARRAY_SIZE(s3c2410_spi0_board),
};
初始化函数smdk_machine_init添加:
s3c_device_spi0.dev.platform_data=&s3c2410_spi_platdata;
spi_register_board_info(s3c2410_spi0_board,ARRAY_SIZE(s3c2410_spi0_board));
编译下载后,/dev/目录下出现设备文件
测试:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
static int verbose;
static void do_read(int fd, int len)
{
unsigned char buf[32], *bp;
int status;
/* read at least 2 bytes, no more than 32 */
if (len < 2)
len = 2;
else if (len > sizeof(buf))
len = sizeof(buf);
memset(buf, 0, sizeof buf);
status = read(fd, buf, len);
if (status < 0) {
perror("read");
return;
}
if (status != len) {
fprintf(stderr, "short read\n");
return;
}
printf("read(%2d, %2d): %02x %02x,", len, status,
buf[0], buf[1]);
status -= 2;
bp = buf + 2;
while (status-- > 0)
printf(" %02x", *bp++);
printf("\n");
}
static void do_msg(int fd, int len)
{
struct spi_ioc_transfer xfer[2];
unsigned char buf[32], *bp;
int status;
memset(xfer, 0, sizeof xfer);
memset(buf, 0, sizeof buf);
if (len > sizeof buf)
len = sizeof buf;
buf[0] = 0xaa;
xfer[0].tx_buf = *(__u64*) buf;
xfer[0].len = 1;
xfer[1].rx_buf = *(__u64*) buf;
xfer[1].len = len;
status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
if (status < 0) {
perror("SPI_IOC_MESSAGE");
return;
}
printf("response(%2d, %2d): ", len, status);
for (bp = buf; len; len--)
printf(" %02x", *bp++);
printf("\n");
}
static void dumpstat(const char *name, int fd)
{
__u8 mode, lsb, bits;
__u32 speed;
if (ioctl(fd, SPI_IOC_RD_MODE, &mode) < 0) {
perror("SPI rd_mode");
return;
}
if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {
perror("SPI rd_lsb_fist");
return;
}
if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {
perror("SPI bits_per_word");
return;
}
if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {
perror("SPI max_speed_hz");
return;
}
printf("%s: spi mode %d, %d bits %sper word, %d Hz max\n",
name, mode, bits, lsb ? "(lsb first) " : "", speed);
}
int main(int argc, char **argv)
{
int c;
int readcount = 0;
int msglen = 0;
int fd;
const char name[] = "/dev/spidev0.0";
msglen =1;
readcount = 1;
fd = open(name, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
dumpstat(name, fd);
if (msglen)
do_msg(fd, msglen);
//if (readcount)
// do_read(fd, readcount);
close(fd);
return 0;
}