平台简介
开发板:TQ2440 (NandFlash:256M 内存:64M)
u-boot版本:u-boot-2015.04
内核版本:Linux-3.14
作者:彭东林
邮箱:pengdonglin137@163.com
摘要
这篇博客的目的是简要分析两种spi驱动的实现,一种是利用Samsung的S3C2440自带的硬件SPI控制器,另一种是利用Linux内核已经写好的用GPIO模拟SPI时序,实现一个软件SPI控制器。操作的外设是韦东山的SPI视频教程中提供的OLED模块,同时分享一下在使用逻辑分析仪Saleae16调试SPI时遇到的问题。
相关的内核代码已经上传:git@code.csdn.net:pengdonglin137/linux-3-14-y.git
可以看看代码提交记录。
正文
SPI驱动实现之硬件控制器
一、驱动框架
二、代码
SPI硬件控制器
这里采用的是platform架构,分为device和driver两个部分。
1、platform_device
文件:arch/arm/plat-samsung/devs.c
1: static struct resource s3c_spi0_resource[] = {
2: [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32),
3: [1] = DEFINE_RES_IRQ(IRQ_SPI0),
4: };
5:
6: static void s3c24xx_spi_set_cs(struct s3c2410_spi_info *spi, int cs, int pol)
7: {
8: gpio_set_value(cs, pol);
9: }
10:
11: static struct s3c2410_spi_info s3c_spi_info[] = {
12: {
13: .num_cs = S3C_GPIO_END,
14: .bus_num = 0,
15: .set_cs = s3c24xx_spi_set_cs,
16: }
17: };
18:
19: struct platform_device s3c_device_spi0 = {
20: .name = "s3c2410-spi",
21: .id = 0,
22: .num_resources = ARRAY_SIZE(s3c_spi0_resource),
23: .resource = s3c_spi0_resource,
24: .dev = {
25: .dma_mask = &samsung_device_dma_mask,
26: .coherent_dma_mask = DMA_BIT_MASK(32),
27: .platform_data = (void *)s3c_spi_info,
28: }
29: };
第15行是片选函数,它的第二个参数cs来自spi从设备的板级信息,表示这个从设备的片选引脚;
第14行表示spi控制器的编号是0,将来在spi从设备的板级信息中有体现,意思是将来这个spi从设备挂载在编号为0的spi总线下面;
第27行,在linux原生的代码中没有实现platform_data,在调用probe函数的时候会报错;
2、platform_driver
文件:drivers/spi/spi-s3c24xx.c
1: MODULE_ALIAS("platform:s3c2410-spi");
2: static struct platform_driver s3c24xx_spi_driver = {
3: .probe = s3c24xx_spi_probe,
4: .remove = s3c24xx_spi_remove,
5: .driver = {
6: .name = "s3c2410-spi",
7: .owner = THIS_MODULE,
8: .pm = S3C24XX_SPI_PMOPS,
9: },
10: };
11: module_platform_driver(s3c24xx_spi_driver);
12:
OLED 板级信息
这里调用了spi子系统提供的函数接口。
1、板级信息
文件:arch/arm/mach-s3c24xx/mach-tq2440.c
1: /* SPI OLED */
2: static struct spi_board_info tq2440_spi_board_info[] __initdata = {
3: {
4: .modalias = "oled",
5: .max_speed_hz = 10000000,
6: .bus_num = 0,
7: .mode = SPI_MODE_0,
8: .chip_select = S3C2410_GPG(1),
9: .platform_data = (const void *)S3C2410_GPF(3),
10: },
11: };
12:
13: static struct platform_device *tq2440_devices[] __initdata = {
14: ......
15: &s3c_device_spi0,
16: };
17:
18: static void __init tq2440_machine_init(void)
19: {
20: ......
21: spi_register_board_info(tq2440_spi_board_info, ARRAY_SIZE(tq2440_spi_board_info));
22: ......
23: }
24:
25: MACHINE_START(TQ2440, "TQ2440")
26: ......
27: .init_machine = tq2440_machine_init,
28: ......
29: MACHINE_END
第4行,将来会跟驱动中的name进行匹配;
第5行,表示通信速率,这里设置的是10MHz;
第6行,表示使用的spi总线的编号是0;
第7行,表示使用的spi模式是0,这里要根据oled的芯片手册(SSD1306-Revision 1.1 (Charge Pump).pdf)
第8行,oled使用的片选引脚;
第9行,用于区分命令和数据模式的GPIO资源,这个会在驱动中解析;
第21行,注册spi从设备板级信息;
2、oled驱动
文件:drivers/spi/oled/spi_oled_drv.c
1: #include 2: #include 3: #include 4: #include 5: #include 6: #include 7: #include 8: #include 9: #include 10: 11: #include 12: #include 13: 14: #include 15: #include 16: 17: /* 构造注册 spi_driver */ 18: 19: static int major; 20: static struct class *class; 21: 22: static int spi_oled_dc_pin; 23: static struct spi_device *spi_oled_dev; 24: static unsigned char *ker_buf; 25: 26: static void OLED_Set_DC(char val) 27: { 28: gpio_set_value(spi_oled_dc_pin, val); 29: } 30: 31: static void OLEDWriteCmd(unsigned char cmd) 32: { 33: OLED_Set_DC(0); /* command */ 34: spi_write(spi_oled_dev, &cmd, 1); 35: OLED_Set_DC(1); /* */ 36: } 37: 38: static void OLEDWriteDat(unsigned char dat) 39: { 40: OLED_Set_DC(1); /* data */ 41: spi_write(spi_oled_dev, &dat, 1); 42: OLED_Set_DC(1); /* */ 43: } 44: 45: static void OLEDSetPageAddrMode(void) 46: { 47: OLEDWriteCmd(0x20); 48: OLEDWriteCmd(0x02); 49: } 50: 51: static void OLEDSetPos(int page, int col) 52: { 53: OLEDWriteCmd(0xB0 + page); /* page address */ 54: 55: OLEDWriteCmd(col & 0xf); /* Lower Column Start Address */ 56: OLEDWriteCmd(0x10 + (col >> 4)); /* Lower Higher Start Address */ 57: } 58: 59: 60: static void OLEDClear(void) 61: { 62: int page, i; 63: for (page = 0; page < 8; page ++) 64: { 65: OLEDSetPos(page, 0); 66: for (i = 0; i < 128; i++) 67: OLEDWriteDat(0); 68: } 69: } 70: 71: void OLEDClearPage(int page) 72: { 73: int i; 74: OLEDSetPos(page, 0); 75: for (i = 0; i < 128; i++) 76: OLEDWriteDat(0); 77: } 78: 79: void OLEDInit(void) 80: { 81: /* 向OLED发命令以初始化 */ 82: OLEDWriteCmd(0xAE); /*display off*/ 83: OLEDWriteCmd(0x00); /*set lower column address*/ 84: OLEDWriteCmd(0x10); /*set higher column address*/ 85: OLEDWriteCmd(0x40); /*set display start line*/ 86: OLEDWriteCmd(0xB0); /*set page address*/ 87: OLEDWriteCmd(0x81); /*contract control*/ 88: OLEDWriteCmd(0x66); /*128*/ 89: OLEDWriteCmd(0xA1); /*set segment remap*/ 90: OLEDWriteCmd(0xA6); /*normal / reverse*/ 91: OLEDWriteCmd(0xA8); /*multiplex ratio*/ 92: OLEDWriteCmd(0x3F); /*duty = 1/64*/ 93: OLEDWriteCmd(0xC8); /*Com scan direction*/ 94: OLEDWriteCmd(0xD3); /*set display offset*/ 95: OLEDWriteCmd(0x00); 96: OLEDWriteCmd(0xD5); /*set osc division*/ 97: OLEDWriteCmd(0x80); 98: OLEDWriteCmd(0xD9); /*set pre-charge period*/ 99: OLEDWriteCmd(0x1f); 100: OLEDWriteCmd(0xDA); /*set COM pins*/ 101: OLEDWriteCmd(0x12); 102: OLEDWriteCmd(0xdb); /*set vcomh*/ 103: OLEDWriteCmd(0x30); 104: OLEDWriteCmd(0x8d); /*set charge pump enable*/ 105: OLEDWriteCmd(0x14); 106: 107: OLEDSetPageAddrMode(); 108: 109: OLEDClear(); 110: 111: OLEDWriteCmd(0xAF); /*display ON*/ 112: } 113: 114: 115: #define OLED_CMD_INIT 0x100001