Linux下SPI驱动框架分析

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_devices3c_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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值