Linux spi总线驱动


spi 硬件


spi_flash的gpio引脚安排:
Vcc : 3.3v j1-pin17
Vdd : 0 j1-pin20
spimiso :GPG5 j3-pin33
spimosi :GPG6 j3-pin35
spiclk :GPG7 j3-pin34
flash_cs:GPG2 j3-pin36


标准的spi工作模式do在时钟的上升沿将数据写到flash,
在时钟的下降沿将数据从flash读回来。


spi控制器往spi设备发送数据是通过Do引脚,总是一位一位地发,先发高位,再发低位.第一个写出去的数据是bit7,第二个写出去的数据是bit6,依次写到bit0
spi控制器通过Di引脚将spi设备的数据读回来时,也是先读高位再读低位.第一个读回来的数据是bit7,第二个都回来的是bit6,依次读到bit0
读的时候是连续地读的,两个字节的数据接着回来。


flash的操作:
program:在烧写之前要先erase对应地址,erase之后要去除flash芯片的写保护(设置stat reg就可以去除芯片的写保护),
读写stat reg之前也要去除stat reg的保护(主机向spi flash发出指定command)

read:在读flash的特定地址之前要先按照芯片手册的特定指令发出去就行了。


status regs:
WEL:0 ->write disable :在完成一个擦除,烧写或者写操作之后WEL会自动恢复到0状态
必须先状态写使能才可以:页烧写,扇区擦除,块擦除,片擦除或者写状态寄存器

BUSY: 1:正忙,0:有空(read only)
WEL(read only): 1:when write enable 0:when write disable
BP2,BP1,BP0: 决定每一块内存是否有写保护,通过设置这几位可以进行块保护,默认为取消所有块保护.------>重要

在WP引脚没有控制的条件下,将SRP1,SRP0都设置为0,在write_enble并且WEL=1的前提下,statu regs可写。
SRP0,SRP1:是可读可写位,作用是确定状态寄存器是否可以读写(软件保护,硬件保护,电源支持保护,单次烧写保护);
没有默认状态(要对状态寄存器进行操作,必须先设置这两位)------>重要

TB:与SRP0,SRP1,WEL配合使用,用于设置块保护,default 0;
SEC: 与BP2,BP1,BP0配合使用,用于设置块保护,default 0;
CMP: 与SEC,TB,BP2,BP1,BP0配合使用为点阵保护提供灵活的设置,default 0;
SUS(read only): 1:command 75, 0:command 7a;
LB3,LB2,LB0: 安全寄存器保护位,默认为0,不保护。

setting the Status Register Protect (SRP0, SRP1) and Block Protect (SEC,TB, BP2, BP1 and BP0) bits.
These settings allow a portion or all of the memory to be configured as read only


spi linux驱动程序*


内核里有关spi的使用情况介绍:linux-5.8.5\Documentation\spi\spi-summary.rst

spi_device相关:
static struct ads7846_platform_data ads_info = {
.vref_delay_usecs = 100,
.x_plate_ohms = 580,
.y_plate_ohms = 410,
};

static struct spi_board_info spi_board_info[] __initdata = {
	{
		.modalias	= "ads7846",
		.platform_data	= &ads_info,//遥相呼应1
		.mode		= SPI_MODE_0,
		.irq		= GPIO_IRQ(31),
		.max_speed_hz	= 120000 /* max sample rate at 3V */ * 16,
		.bus_num	= 1,
		.chip_select	= 0,
	},
};

下面这个函数的作用register SPI devices for a given board ->:linux-5.8.5\drivers\spi\spi.c
spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info));//将一款单板上的所有spi设备一起注册进去。

spi_driver相关:
static struct spi_driver CHIP_driver = {
.driver = {
.name = “CHIP”,
.owner = THIS_MODULE,
.pm = &CHIP_pm_ops,
},

	.probe		= CHIP_probe,
	.remove		= CHIP_remove,
};

static int CHIP_probe(struct spi_device *spi)
{
	struct CHIP			*chip;
	struct CHIP_platform_data	*pdata;

	/* assuming the driver requires board-specific data: */
	pdata = &spi->dev.platform_data;   //遥相呼应2  
	if (!pdata)
		return -ENODEV;

	/* get memory for driver's per-chip state */
	chip = kzalloc(sizeof *chip, GFP_KERNEL);
	if (!chip)
		return -ENOMEM;
	spi_set_drvdata(spi, chip);

	... etc
	return 0;
}

spi_async(),
spi_read(), 
spi_write(), 
spi_write_then_read(),
spi_w8r16(): writing an eight bit command and reading a sixteen bit response
spi_transfer,spi_transfer,
	If you like, spi_message_alloc() and spi_message_free() convenience
routines are available to allocate and zero-initialize an spi_message
with several transfers.

spi_device的设备驱动的编写可以参考linux-5.8.5\arch\sh\boards\board-sh7757lcr.c



字符设备驱动的copy_from_user最多只可以传输4k的数据。


重点:向内核注册的spi_device的片选引脚(cs-pin)按正常来说是由所注册的spi_device来指定,要修改spi_control的驱动代码,提供set_cs函数,也就是修改spi_master的驱动程序
提供可以从spi_device获取片选引脚的set_cs函数,详细参考:
linux-5.8.5\drivers\spi\spi-s3c24xx.c line:566~582
linux-5.8.5\arch\arm\plat-samsung\devs.c line:887~905

  但是spi_control的驱动程序一般也有一个默认的片选引脚(假如spi_device注册的时候没有提供片选引脚,即使用默认的片选引脚),此时master的驱动程序不需要提供set_cs函数,
  只需要提供默认的片选引脚pin_cs,详细参考:	 
 linux-5.8.5\arch\arm\plat-samsung\devs.c line:890
  zhangjiaqi\linux-5.8.5\drivers\spi\spi-s3c24xx.c    line:566~580

spi_master驱动:两个核心参数:设备驱动层创建的spi_device* spi,设备驱动层创建的message(message里面包含有设备驱动层(spi_driver)设置的要收发的数据spi_transfer),
spi_master的功能就是将设备驱动层spi_driver包装好的多个数据message收发出去,这多个message是放在一个message链表里面。

			spi_message:一个spi_message表示一个不可以打断的发送过程,里面包含的transfer可以有一个或者多个,这些transfer是连续的,即同一个片选信号的情况下
			             完成传输。
			
			一次spi_write -> 一个message -> n个transfer -> 每个transfer有len字节数据 -> 依次将每个transfer的每一字节数据发送出去 -> 每次中断发8bit

设备驱动层调用底层驱动去收发数据的过程:
spi_write_then_read
spi_sync(spi, &message)
__spi_sync(spi, message)
status = spi_async_locked(spi, message); //核心
__spi_async(spi, message);
ctlr->transfer(spi, message);//调用底层spi_control的transfer函数,传递进去的两个参数分别是设备驱动创建的spi_device,spi_write或者spi_read
为每次数据传输而建立的message,里面包含此次传输的spi_transfer(一个或者多个);

分配spi_master的函数:spi_alloc_master(struct device *host,unsigned int size),这个函数会返回一个spi_control指针,这个指针所指向的内存大小为(sizeof(spi_control)+size(第二个参数))
第一个参数为设备数生成的platform_device->dev的指针,第二个参数为任意指定的priv_data函数的大小可以为null

1.带操作系统的需要打开时钟
2.读激励也是要查询是否正忙
3.w25q16的烧写每次最多只能256字节的烧写,256字节256字节地烧写,注意不可跨页操作。
4.每调用一次spi_write,spi_master总线驱动会片选一次
5.在board-nokia770.c中注册spi device时(单板相关的文件):
static struct spi_board_info nokia770_spi_board_info[] __initdata = {
[0] = {
.modalias = “lcd_mipid”,
.bus_num = 2,
.chip_select = 3, //这里的片选引脚是标号3,他们的片选管脚是集成在spi_controller的某个寄存器的,这个3表示是使用这个寄存器的第3个io口作为片选引脚
//而不是某个简单的io口(gpg2,gpg3这些);
.max_speed_hz = 12000000,
.platform_data = &nokia770_mipid_platform_data,
},
[1] = {
.modalias = “ads7846”,
.bus_num = 2,
.chip_select = 0,
.max_speed_hz = 2500000,
.platform_data = &nokia770_ads7846_platform_data,
},
};
6.调试总结:
1.总线驱动卡住probe函数不能往下走,没反应,解决办法:读写函数__raw_write,__raw_read没做好,目标寄存器偏移值没做好。
2.不能正确读取出id值:原因片选函数没有实现正真的片选,新的内核不能直接用gpio_set_pin(S3C2410_GPG(2),0)这个函数来实现片选了,
在新的内核里spi_device传过来的只是一个gpio标号比如2或者3(不再是S3C2410_GPG(2)),新的内核spi_controller的寄存器是跟spi的所有可用
cs引脚放在一起。cs引脚不再是开发者随便找一个gpio口充当,而是spi_controller register里有一排cs口可供选择。
3.使用spi_transfer和spi_message来传输数据时,忘记spi_message_init(&message);导致不能正确写数据。
4.测试spi_flash的设备驱动:可以读写出正确的flash id号,并且可以正确mkfs.vfat 这个spi_flash
mkfs.vfat /dev/mtdblock4
mount -t vfat /dev/mtdblock4 /mnt 会提示:
mount: mounting /dev/mtdblock4 on myshare failed: Invalid argument

Guess you like

Origin blog.csdn.net/qq_43418840/article/details/119248633