spi子系統之驅動SSD1306 OLED 接觸Linux之前,曾以為讀源碼可以更快的學習軟體,於是前幾個博客都是一邊讀源碼一邊添加註釋,甚至精讀到每一行代碼,實際上效果並不理想,看過之後就忘記了。主要原因是沒理解透程式架構,各個模塊之間的關係,如何聯繫在一起,再加上沒有實例驗證。後來逐漸發現,理解 ...
spi子系統之驅動SSD1306 OLED
接觸Linux之前,曾以為讀源碼可以更快的學習軟體,於是前幾個博客都是一邊讀源碼一邊添加註釋,甚至精讀到每一行代碼,實際上效果並不理想,看過之後就忘記了。主要原因是沒理解透程式架構,各個模塊之間的關係,如何聯繫在一起,再加上沒有實例驗證。後來逐漸發現,理解框架能達到事半功倍的效果,理解框架之後,反而代碼也更容易看懂,甚至可以猜部分代碼的作用,印象更加深刻。
理解SPI的驅動框架,還是從最基本的三個入口點觸發,platform_device,platform_bus,platform_driver。
其中內核一提供給platform_bus,platform_driver在spi_s3c24xx_gpio.c和spi_s3c24xxc.c中,其中spi_s3c24xx_gpio.c用於IO模擬SPI (本例討論的是IO模擬SPI),spi_s3c24xxc.c用於s3c24xx的硬體SPI。因此,我們需要動手寫一個platform_device。
看看spi_s3c24xx_gpio.c做了些什麼。
1 static int s3c2410_spigpio_probe(struct platform_device *dev) 2 { 3 ... ... 4 /* [cgw]: 分配一個SPI主機 */ 5 master = spi_alloc_master(&dev->dev, sizeof(struct s3c2410_spigpio)); 6 ... ... 7 8 sp = spi_master_get_devdata(master); 9 10 platform_set_drvdata(dev, sp); 11 12 /* [cgw]: 分配與spi硬體相關的配置,如指定哪些IO為MISO,MOSI,SCLK,CS,SPI工作模式,最大時鐘等等 */ 13 /* copy in the plkatform data */ 14 sp->info = dev->dev.platform_data; 15 16 /* [cgw]: 提供實現SPI各種模式的時序的基本方法,和CS的激活方法 */ 17 /* setup spi bitbang adaptor */ 18 sp->bitbang.master = spi_master_get(master); 19 sp->bitbang.chipselect = s3c2410_spigpio_chipselect; 20 21 sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0; 22 sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1; 23 sp->bitbang.txrx_word[SPI_MODE_2] = s3c2410_spigpio_txrx_mode2; 24 sp->bitbang.txrx_word[SPI_MODE_3] = s3c2410_spigpio_txrx_mode3; 25 26 /* [cgw]: 配置相關io為輸入輸出 */ 27 /* set state of spi pins */ 28 s3c2410_gpio_setpin(sp->info->pin_clk, 0); 29 s3c2410_gpio_setpin(sp->info->pin_mosi, 0); 30 31 s3c2410_gpio_cfgpin(sp->info->pin_clk, S3C2410_GPIO_OUTPUT); 32 s3c2410_gpio_cfgpin(sp->info->pin_mosi, S3C2410_GPIO_OUTPUT); 33 s3c2410_gpio_cfgpin(sp->info->pin_miso, S3C2410_GPIO_INPUT); 34 35 /* [cgw]: 設置spi的收發,如註冊一個工作隊列,收發時序的方法,8/16/32的spi數據等等 */ 36 ret = spi_bitbang_start(&sp->bitbang); 37 ... ... 38 39 /* [cgw]: 註冊sp->info->board_size個spi設備,這幾個spi設備都是掛接在統一spi匯流排上的 */ 40 /* register the chips to go with the board */ 41 for (i = 0; i < sp->info->board_size; i++) { 42 dev_info(&dev->dev, "registering %p: %s\n", 43 &sp->info->board_info[i], 44 sp->info->board_info[i].modalias); 45 46 sp->info->board_info[i].controller_data = sp; 47 spi_new_device(master, sp->info->board_info + i); 48 } 49 ... ... 50 }
- 註冊了一個platform_driver
- 在s3c2410_spigpio_probe中,分配並註冊了一個spi主機,並註冊了掛接在這個SPI主機上的所有spi設備
要想s3c2410_spigpio_probe得到調用,即探測到有效的platform_device,我們需要一個與platform同名("s3c24xx-spi-gpio")的platform_device。
1 static struct spi_board_info board_info[1] = { 2 { 3 .modalias = "spi_ssd1306", /* [cgw]: spi設備名,和設備驅動名對應 */ 4 .bus_num = 0, /* [cgw]: spi匯流排號,即spi0 */ 5 .chip_select = 2, /* [cgw]: spi匯流排上的設備號,即spi0.2 */ 6 .max_speed_hz = 50000, /* [cgw]: spi時鐘 */ 7 .mode = SPI_MODE_3, /* [cgw]: spi數據模式 */ 8 }, 9 }; 10 11 static struct s3c2410_spigpio_info spi_dev = { 12 .pin_clk = S3C2410_GPG7, 13 .pin_mosi = S3C2410_GPG5, 14 .pin_miso = S3C2410_GPG6, 15 .board_size = 1, /* [cgw]: 設置板上spi介面數量為1 */ 16 .board_info = &board_info[0], 17 .chip_select = ssd1306_chip_select 18 }; 19 20 static struct platform_device spi_platform_dev = { 21 .name = "s3c24xx-spi-gpio", /* [cgw]: 設置平臺設備名,和平臺驅動名對應 */ 22 .id = -1, 23 .dev = { 24 .release = spi_dev_release, 25 .platform_data = (void *)&spi_dev, /* [cgw]: 通過platform_data傳遞spi_dev給平臺驅動 26 * 平臺驅動可以訪問spi_dev 27 */ 28 }, 29 }; 30 31 static int spi_dev_init(void) 32 { 33 /* [cgw]: 註冊spi_platform_dev平臺設備 */ 34 platform_device_register(&spi_platform_dev); 35 return 0; 36 }
spi_bitbang.c提供了spi底層一些實現細節,註冊工作隊列(SPI數據的傳送最終是通過調用工作隊列實現的),spi工作模式,工作頻率等。
1 int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t) 2 { 3 struct spi_bitbang_cs *cs = spi->controller_state; 4 u8 bits_per_word; 5 u32 hz; 6 7 if (t) { 8 /* [cgw]: spi驅動指定幾位數據模式,和傳送速度 */ 9 bits_per_word = t->bits_per_word; 10 hz = t->speed_hz; 11 } else { 12 bits_per_word = 0; 13 hz = 0; 14 } 15 16 /* [cgw]: 根據spi位數,選擇合適的時序 */ 17 /* spi_transfer level calls that work per-word */ 18 if (!bits_per_word) 19 bits_per_word = spi->bits_per_word; 20 if (bits_per_word <= 8) 21 cs->txrx_bufs = bitbang_txrx_8; 22 else if (bits_per_word <= 16) 23 cs->txrx_bufs = bitbang_txrx_16; 24 else if (bits_per_word <= 32) 25 cs->txrx_bufs = bitbang_txrx_32; 26 else 27 return -EINVAL; 28 29 /* [cgw]: 設置SCLK的時鐘頻率 */ 30 /* nsecs = (clock period)/2 */ 31 if (!hz) 32 hz = spi->max_speed_hz; 33 if (hz) { 34 cs->nsecs = (1000000000/2) / hz; 35 if (cs->nsecs > (MAX_UDELAY_MS * 1000 * 1000)) 36 return -EINVAL; 37 } 38 39 return 0; 40 } 41 42 int spi_bitbang_setup(struct spi_device *spi) 43 { 44 struct spi_bitbang_cs *cs = spi->controller_state; 45 struct spi_bitbang *bitbang; 46 int retval; 47 48 bitbang = spi_master_get_devdata(spi->master); 49 50 /* REVISIT: some systems will want to support devices using lsb-first 51 * bit encodings on the wire. In pure software that would be trivial, 52 * just bitbang_txrx_le_cphaX() routines shifting the other way, and 53 * some hardware controllers also have this support. 54 */ 55 /* [cgw]: 預設不支持LSB模式,要想使用LSB模式,只要bitbang_txrx_le_cphaX()改變移位的方向即可 */ 56 if ((spi->mode & SPI_LSB_FIRST) != 0) 57 return -EINVAL; 58 59 if (!cs) { 60 cs = kzalloc(sizeof *cs, GFP_KERNEL); 61 if (!cs) 62 return -ENOMEM; 63 spi->controller_state = cs; 64 } 65 66 /* [cgw]: 設置spi的預設位數 */ 67 if (!spi->bits_per_word) 68 spi->bits_per_word = 8; 69 70 /* per-word shift register access, in hardware or bitbanging */ 71 /* [cgw]: 設置spi的工作模式,四種 */ 72 cs->txrx_word = bitbang->txrx_word[spi->mode & (SPI_CPOL|SPI_CPHA)]; 73 if (!cs->txrx_word) 74 return -EINVAL; 75 76 /* [cgw]: 調用spi_bitbang_setup_transfer */ 77 retval = bitbang->setup_transfer(spi, NULL); 78 if (retval < 0) 79 return retval; 80 81 dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec/bit\n", 82 __FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA), 83 spi->bits_per_word, 2 * cs->nsecs); 84 85 /* NOTE we _need_ to call chipselect() early, ideally with adapter 86 * setup, unless the hardware defaults cooperate to avoid confusion 87 * between normal (active low) and inverted chipselects. 88 */ 89 90 /* [cgw]: spi忙的話,通過改變CS的狀態釋放SPI */ 91 /* deselect chip (low or high) */ 92 spin_lock(&bitbang->lock); 93 if (!bitbang->busy) { 94 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 95 ndelay(cs->nsecs); 96 } 97 spin_unlock(&bitbang->lock); 98 99 return 0; 100 } 101 102 103 static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t) 104 { 105 struct spi_bitbang_cs *cs = spi->controller_state; 106 unsigned nsecs = cs->nsecs; 107 108 /* [cgw]: 具體數據收發就是這裡實現的 */ 109 return cs->txrx_bufs(spi, cs->txrx_word, nsecs, t); 110 } 111 112 int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m) 113 { 114 struct spi_bitbang *bitbang; 115 unsigned long flags; 116 int status = 0; 117 118 m->actual_length = 0; 119 m->status = -EINPROGRESS; 120 121 bitbang = spi_master_get_devdata(spi->master); 122 123 spin_lock_irqsave(&bitbang->lock, flags); 124 if (!spi->max_speed_hz) 125 status = -ENETDOWN; 126 else { 127 /* [cgw]: 入隊一個工作到工作隊列 */ 128 list_add_tail(&m->queue, &bitbang->queue); 129 queue_work(bitbang->workqueue, &bitbang->work); 130 } 131 spin_unlock_irqrestore(&bitbang->lock, flags); 132 133 return status; 134 } 135 136 int spi_bitbang_start(struct spi_bitbang *bitbang) 137 { 138 int status; 139 140 if (!bitbang->master || !bitbang->chipselect) 141 return -EINVAL; 142 143 /* [cgw]: 註冊一個工作隊列 */ 144 INIT_WORK(&bitbang->work, bitbang_work); 145 spin_lock_init(&bitbang->lock); 146 INIT_LIST_HEAD(&bitbang->queue); 147 148 /* [cgw]: 配置相關方法 */ 149 if (!bitbang->master->transfer) 150 bitbang->master->transfer = spi_bitbang_transfer; 151 if (!bitbang->txrx_bufs) { 152 bitbang->use_dma = 0; 153 bitbang->txrx_bufs = spi_bitbang_bufs; 154 if (!bitbang->master->setup) { 155 if (!bitbang->setup_transfer) 156 bitbang->setup_transfer = 157 spi_bitbang_setup_transfer; 158 bitbang->master->setup = spi_bitbang_setup; 159 bitbang->master->cleanup = spi_bitbang_cleanup; 160 } 161 } else if (!bitbang->master->setup) 162 return -EINVAL; 163 164 /* [cgw]: 創建一個單線程,用於調度工作隊列 */ 165 /* this task is the only thing to touch the SPI bits */ 166 bitbang->busy = 0; 167 bitbang->workqueue = create_singlethread_workqueue( 168 bitbang->master->cdev.dev->bus_id); 169 if (bitbang->workqueue == NULL) { 170 status = -EBUSY; 171 goto err1; 172 } 173 174 /* [cgw]: 註冊一個spi主機 */ 175 /* driver may get busy before register() returns, especially 176 * if someone registered boardinfo for devices 177 */ 178 status = spi_register_master(bitbang->master); 179 if (status < 0) 180 goto err2; 181 182 return status; 183 184 err2: 185 destroy_workqueue(bitbang->workqueue); 186 err1: 187 return status; 188 }
因為在s3c2410_spigpio_probe中註冊了spi的設備,因此我們還需為這些設備提供驅動,以被這些設備探測到,探測這些驅動的條件也是設備和驅動的名字同名,即spi_ssd1306。我們這裡提供了一個ssd1306 OLED的驅動
1 static struct spi_driver spi_ssd1306_driver = { 2 .driver = { 3 .name = "spi_ssd1306", 4 .bus = &spi_bus_type, 5 .owner = THIS_MODULE, 6 }, 7 .probe = spi_ssd1306_probe, 8 .remove = __devexit_p(spi_ssd1306_remove), 9 }; 10 11 static int spi_ssd1306_init(void) 12 { 13 return spi_register_driver(&spi_ssd1306_driver); 14 }
到這裡,基本工作已經完成。怎樣驅動ssd1306 OLED呢?
ssd1306 OLED的使用方法,請參考相關的手冊。
本例提供的ssd1306 OLED驅動,只需要我們提供一個基本9位spi數據收發的介面即可。
static void ssd1306_write_byte(uint8_t chData, uint8_t chCmd) { struct spi_transfer t; struct spi_message m; uint16_t data = chData; /* [cgw]: 情況spi_transfer */ memset(&t,0,sizeof(struct spi_transfer)); /* [cgw]: 第9位表示前8位是命令還是數據,1:數據,0:命令 */ if (chCmd) { data |= (1 << 8); } else { data &= ~(1 << 8); } /* [cgw]: 要發送的數據 */ t.tx_buf = &data; /* [cgw]: 長度,2位元組 */ t.len = 2; /* [cgw]: 9位spi */ t.bits_per_word = 9; //t.cs_change = 1; /* [cgw]: 把數據添加到收發列表,工作隊列調度時會從收發隊列中取出,併進行收發 * 註意這裡並沒有直接收發 */ spi_message_init(&m); spi_message_add_tail(&t, &m); spi_sync(spi_ssd1306_dev, &m); }
註意,在網上看到一些例子,用8位模式驅動ssd1306 OLED的,需要用DC的狀態來表示數據或命令的,他們的做法如下:
1 void ssd1306_write_cmd(uint8_t cmd) 2 { 3 ssd1306_dc_clr(); 4 spi_write(cmd); 5 ssd1306_dc_set(); 6 } 7 8 void ssd1306_write_data(uint8_t data) 9 { 10 ssd1306_dc_set(); 11 spi_write(data); 12 ssd1306_dc_clr(); 13 }
我本人認為是不正確的,至少不符合這個spi框架的邏輯,因為spi數據的收發並不是直接在spi_write()實現,而是在工作隊列bitbang_work()中實現。儘管這樣仍然能驅動ssd1306 OLED,但理論上不應該這麼做。要改的話應該改bitbang_work()中改,添加DC狀態的控制。
1 static void bitbang_work(struct work_struct *work) 2 { 3 struct spi_bitbang *bitbang = 4 container_of(work, struct spi_bitbang, work); 5 unsigned long flags; 6 7 spin_lock_irqsave(&bitbang->lock, flags); 8 bitbang->busy = 1; 9 /* [cgw]: 隊列不為空 */ 10 while (!list_empty(&bitbang->queue)) { 11 struct spi_message *m; 12 struct spi_device *spi; 13 unsigned nsecs; 14 struct spi_transfer *t = NULL; 15 unsigned tmp; 16 unsigned cs_change; 17 int status; 18 int (*setup_transfer)(struct spi_device *, 19 struct spi_transfer *); 20 21 /* [cgw]: 取出spi_message */ 22 m = container_of(bitbang->queue.next, struct spi_message, 23 queue); 24 /* [cgw]: 刪除這個節點 */ 25 list_del_init(&m->queue); 26 /* [cgw]: 進入臨界區 */ 27 spin_unlock_irqrestore(&bitbang->lock, flags); 28 29 /* FIXME this is made-up ... the correct value is known to 30 * word-at-a-time bitbang code, and presumably chipselect() 31 * should enforce these requirements too? 32 */ 33 nsecs = 100; 34 35 spi = m->spi; 36 tmp = 0; 37 cs_change = 1; 38 status = 0; 39 setup_transfer = NULL; 40 41 /* [cgw]: 歷遍spi_message中的收發列表 */ 42 list_for_each_entry (t, &m->transfers, transfer_list) { 43 44 /* override or restore speed and wordsize */ 45 /* [cgw]: 如果驅動指定了spi速度,和位數,重新調用spi_bitbang_setup_transfer 46 * 更改預設設置 47 */ 48 if (t->speed_hz || t->bits_per_word) { 49 setup_transfer = bitbang->setup_transfer; 50 if (!setup_transfer) { 51 status = -ENOPROTOOPT; 52 break; 53 } 54 } 55 if (setup_transfer) { 56 status = setup_transfer(spi, t); 57 if (status < 0) 58 break; 59 } 60 61 /* set up default clock polarity, and activate chip; 62 * this implicitly updates clock and spi modes as 63 * previously recorded for this device via setup(). 64 * (and also deselects any other chip that might be 65 * selected ...) 66 */ 67 /* [cgw]: 激活spi */ 68 if (cs_change) { 69 bitbang->chipselect(spi, BITBANG_CS_ACTIVE); 70 ndelay(nsecs); 71 } 72 /* [cgw]: 驅動指定收發完一幀數據要不要改變恢復CS為空閑 */ 73 cs_change = t->cs_change; 74 /* [cgw]: 收發包為空,則無效 */ 75 if (!t->tx_buf && !t->rx_buf && t->len) { 76 status = -EINVAL; 77 break; 78 } 79 80 /* transfer data. the lower level code handles any 81 * new dma mappings it needs. our caller always gave 82 * us dma-safe buffers. 83 */ 84 if (t->len) { 85 /* REVISIT dma API still needs a designated 86 * DMA_ADDR_INVALID; ~0 might be better. 87 */ 88 if (!m->is_dma_mapped) 89 t->rx_dma = t->tx_dma = 0; 90 /* [cgw]: 這裡才是真正的實現spi收發時序 */ 91 status = bitbang->txrx_bufs(spi, t); 92 } 93 if (status != t->len) { 94 if (status > 0) 95 status = -EMSGSIZE; 96 break; 97 } 98 m->actual_length += status; 99 status = 0; 100 101 /* protocol tweaks before next transfer */ 102 if (t->delay_usecs) 103 udelay(t->delay_usecs); 104 105 /* [cgw]: 收發完一幀,不改變CS狀態 */ 106 if (!cs_change) 107 continue; 108 109 /* [cgw]: 收發列表已經沒有數據,結束 */ 110 if (t->transfer_list.next == &m->transfers) 111 break; 112 113 /* sometimes a short mid-message deselect of the chip 114 * may be needed to terminate a mode or command 115 */ 116 /* [cgw]: 釋放spi */ 117 ndelay(nsecs); 118 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 119 ndelay(nsecs); 120 } 121 122 m->status = status; 123 m->complete(m->context); 124 125 /* restore speed and wordsize */ 126 /* [cgw]: 速度和位數恢復預設 */ 127 if (setup_transfer) 128 setup_transfer(spi, NULL); 129 130 /* normally deactivate chipselect ... unless no error and 131 * cs_change has hinted that the next message will probably 132 * be for this chip too. 133 */ 134 if (!(status == 0 && cs_change)) { 135 ndelay(nsecs); 136 bitbang->chipselect(spi, BITBANG_CS_INACTIVE); 137 ndelay(nsecs); 138 } 139 140 spin_lock_irqsave(&bitbang->lock, flags); 141 } 142 bitbang->busy = 0; 143 /* [cgw]: 退出臨界區 */ 144 spin_unlock_irqrestore(&bitbang->lock, flags); 145 }
代碼:
spi_platform_dev.c
1 #include <asm/arch/spi-gpio.h> 2 3 4 static struct spi_board_info board_info[1] = { 5 { 6 .modalias = "spi_ssd1306", /* [cgw]: spi設備名,和設備驅動名對應 */ 7 .bus_num = 0, /* [cgw]: spi匯流排號,即spi0 */ 8 .chip_select = 2, /* [cgw]: spi匯流排上的設備號,即spi0.2 */ 9 .max_speed_hz = 50000, /* [cgw]: spi時鐘