63 linux内核的SPI设备驱动模型及应用程序调用SPI控制器的方法

时间:2021-01-27 17:56:41

SPI设备驱动模型与I2C设备驱动模型基本一样.

SPI的控制器驱动由平台设备与平台驱动来实现. 驱动后用spi_master对象来描述.在设备驱动中就可以通过函数spi_write, spi_read, spi_w8r16, spi_w8r8来调用控制器.

spi的设备也是先用spi_board_info来描述,在spi_master对象注册时再生成相应的spi_device对象. spi设备驱动由spi_driver对象描述. spi_device对象与spi_driver都是挂载到spi总线上的.

描述spi设备的结构体:

struct spi_device {
struct device dev; //基于device成员扩展, 在/sys/bus/spi/devie目录有相应的子目录(名为spi%d.%d)
struct spi_master *master; //spi控制器对象的地址
u32 max_speed_hz; //设备工作时钟最大多少HZ
u8 chip_select;
u8 mode;
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
u8 bits_per_word; //传输的数据以多少位为单位
int irq; //中断号
void *controller_state;
void *controller_data; //给控制器的驱动使用
char modalias[SPI_NAME_SIZE]; //spi设备的名字
};
// spi设备驱动类型
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver; //基于device_driver扩展, 驱动模型
};


extern int spi_register_driver(struct spi_driver *sdrv);
static inline void spi_unregister_driver(struct spi_driver *sdrv);

/////////////////////////

spi总线:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device,
.uevent = spi_uevent,
.pm = &spi_pm,
};
EXPORT_SYMBOL_GPL(spi_bus_type);

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
...
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi); //如设备驱动有id_table则按id_table里指定的设备名来匹配 

return strcmp(spi->modalias, drv->name) == 0; //如设备驱动没有id_table则按名字来匹配 
}

////////////////////////////////////////////////////////////////////////
声明spi设备有两种方法:

1) 在script.bin里声明spi设备. 这种方法只适用于SOC里spi控制器, 不适用基于GPIO口的控制器.

[spi_devices]
spi_dev_num = 1 //表示spi设备的个数

[spi_board0] //第0个spi设备
modalias = "spidev" //设备名
max_speed_hz = 33000000 //设备最高的工作时钟
bus_num = 0 //此设备是连接在编号为0的控制器上的
chip_select = 0 //使用控制器的第0个片选 
mode = 0 //工作时序的方式
full_duplex = 1 //全双工, 表示MISO,MOSI都用上
manual_cs = 0 //表示片选线是由控制器自动控制的

2) 在内核源码声明spi_board_info对象, 再内核初始化函数里spi_register_board_info注册设备信息.

struct spi_board_info myspi_info = {
.modalias = "spidev",
.controller_data = GPIOA(20), //注意这里是指定设备的片选脚, 需查看控制器驱动代码里对controller_data成员的使用。
.max_speed_hz = 100000, //100Khz
.bus_num = 0, //控制器的编号
.chip_select = 0, //第0个片选
.mode = SPI_MODE_2,
};

/////////////////////////////////////////////////////////////////
应用程序调用spi控制器的方法.

在内核里有实现好一个spi的设备驱动,用于供应用程序调用.

make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
Device Drivers --->
[*] SPI support --->
<*> User mode SPI device driver support //spi设备驱动,供应用程序调用控制器用

源码在”drivers/spi/spidev.c”

static int __init spidev_init(void)
{
int status;

status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); // cdev字符设备初始化
...

spidev_class = class_create(THIS_MODULE, "spidev");
...

status = spi_register_driver(&spidev_spi_driver); //注册spi设备驱动
...
}
module_init(spidev_init);

//////////////////////////

static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev", //只可与名为"spidev"的spi设备匹配
.owner = THIS_MODULE,
},
.probe = spidev_probe,
...
};

static int __devinit spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;


spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
...

spidev->spi = spi;
...

minor = find_first_zero_bit(minors, N_SPI_MINORS);
if (minor < N_SPI_MINORS) {
struct device *dev;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor); //准备设备号
dev = device_create(spidev_class, &spi->dev, spidev->devt,
spidev, "spidev%d.%d", //创建出设备文件, 第1个%d指定控制器的编号,第2指定第几个片选 
spi->master->bus_num, spi->chip_select);
status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
...
}
...
}

///////////////////////////////////////////////////////////////////////////////////
全志h3的script.bin里默认增加了一个名为”spidev”的spi设备, 只要内核编译项选上”spidev”设备驱动, 在系统的/dev目录下应出现设备文件”spidev0.0”.

可使用内核源码目录下的”Documentation/spi/spidev_test.c”测试spi控制器程序.
只要把spi控制器的MISO与MOSI短接起来,执行spidev_test.c应会接到收到程序发出的数据.