平台简介
开发板: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