SPI 從設(shè)備芯片的種類非常廣泛,包括用于模擬傳感器和編解碼器的數(shù)字/模擬轉(zhuǎn)換器、內(nèi)存芯片、USB 控制器或以太網(wǎng)適配器等外設(shè),以及其他類型的芯片。
這樣的驅(qū)動(dòng)通常在linux看來(lái)是一個(gè)協(xié)議驅(qū)動(dòng),比如spi flash,負(fù)責(zé)和MTD系統(tǒng)打交道;比如觸摸傳感器,需要和input子系統(tǒng)打交道,再比如spi接口的OLED模塊。
這樣的設(shè)備使用的【接口】在驅(qū)動(dòng)中使用struct spi_deivce表示
struct spi_device {
struct device dev;
struct spi_controller *controller;
struct spi_controller *master; /* compatibility layer */
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
bool rt;
#define SPI_NO_TX BIT(31) /* no transmit wire */
#define SPI_NO_RX BIT(30) /* no receive wire */
/*
* All bits defined above should be covered by SPI_MODE_KERNEL_MASK.
* The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart,
* which is defined in 'include/uapi/linux/spi/spi.h'.
* The bits defined here are from bit 31 downwards, while in
* SPI_MODE_USER_MASK are from 0 upwards.
* These bits must not overlap. A static assert check should make sure of that.
* If adding extra bits, make sure to decrease the bit index below as well.
*/
#define SPI_MODE_KERNEL_MASK (~(BIT(30) - 1))
u32 mode;
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
const char *driver_override;
int cs_gpio; /* LEGACY: chip select gpio */
struct gpio_desc *cs_gpiod; /* chip select gpio desc */
struct spi_delay word_delay; /* inter-word delay */
/* CS delays */
struct spi_delay cs_setup;
struct spi_delay cs_hold;
struct spi_delay cs_inactive;
/* the statistics */
struct spi_statistics statistics;
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - chipselect delays
* - ...
*/
};
linux內(nèi)核文檔中是這樣描述的
A "struct spi_device" encapsulates the controller-side interface between those two types of drivers.
因此,應(yīng)該表示一個(gè)接口而不是一個(gè)驅(qū)動(dòng),當(dāng)然你說(shuō)這個(gè)接口連接的不就是設(shè)備嗎?這么理解好像也沒(méi)錯(cuò)。
SPI 設(shè)備驅(qū)動(dòng)使用struct spi_driver表示,提供probe驅(qū)動(dòng)入口,老套路了,比如
static int ssd13306_probe(struct spi_device *spi)
以上是函數(shù)原型,留下一個(gè)疑問(wèn),struct spi_device是作為一個(gè)對(duì)象傳進(jìn)來(lái)的,它是什么時(shí)候被構(gòu)造的呢?
一、編寫設(shè)備樹(shù)
&ecspi2{
cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >;//GPIO1_29
num-cs = < 1 >;
pinctrl-names = "default";
pinctrl-0 = < &pinctrl_ecspi2 >;
status = "okay";
oled: ssd13306@0{
compatible = "Justice,ssd13306";//自己寫的oled驅(qū)動(dòng)
//compatible = "spidev,spidev";//使用spidev通用驅(qū)動(dòng)
//compatible = "solomon,ssd1306";//使用fbtftf的驅(qū)動(dòng),oled當(dāng)成fb
spi-cpol;
spi-cpha;
spi-rx-bus-width = < 0 >;
spi-max-frequency = < 10000000 >;
reset-gpios = < &gpio1 27 GPIO_ACTIVE_LOW >;
dc-gpios = < &gpio1 31 GPIO_ACTIVE_HIGH >;
reg = < 0 >;
};
};
ecspi2是soc的SPI控制器,我們使用這個(gè)SPI控制器和從設(shè)備oled通信,因此要在這個(gè)設(shè)備樹(shù)節(jié)點(diǎn)下寫一個(gè)子節(jié)點(diǎn)表示OLED設(shè)備。下面是必填項(xiàng):
- cs-gpios 是SPI控制器的屬性,描述了從設(shè)備使用了哪些cs片選引腳,如果有3個(gè)從設(shè)備就需要寫3個(gè)從cs引腳,而且順序要和從設(shè)備設(shè)備樹(shù)節(jié)點(diǎn)的reg屬性對(duì)應(yīng)。如上面gpio1 29表示片選0的從設(shè)備,reg屬性表示設(shè)備片選號(hào)。
- reg 表示此設(shè)備在這個(gè)SPI控制器中第幾個(gè)片選,和cs-gpios順序一致。
- compatible 用于匹配驅(qū)動(dòng)。
- pinctrl-0 = <&pinctrl_ecspi2> 表示要引腳復(fù)用哪些信號(hào)
pinctrl_ecspi2:oled{//沒(méi)有MISO因?yàn)閟sd1306 oled不能讀取
fsl,pins = <
MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x10b0 //cs引腳
MX6UL_PAD_UART4_TX_DATA__ECSPI2_SCLK 0x10b1
MX6UL_PAD_UART5_TX_DATA__ECSPI2_MOSI 0x10b1
MX6UL_PAD_UART5_RX_DATA__GPIO1_IO31 0x10b1 //DC引腳,分辨數(shù)據(jù)還是命令
MX6UL_PAD_UART3_RTS_B__GPIO1_IO27 0x10b1 //res復(fù)位引腳
>;
};
其余都不是必要屬性,但是如果驅(qū)動(dòng)異常,需要進(jìn)一步調(diào)試是不是缺了某些屬性。
下面列舉了從設(shè)備設(shè)備樹(shù)節(jié)點(diǎn)可選屬性:
屬性 | 描述 | |||
---|---|---|---|---|
spi-cpha | spi->mode | = SPI_CPHA | CPHA=1 | |
spi-cpol | spi->mode | = SPI_CPOL | CPOL=1 | |
spi-3wire | spi->mode | = SPI_3WIRE | 使用三線SPI | |
spi-lsb-first | spi->mode | = SPI_LSB_FIRST | 一般spi是MSB,指定后LSB | |
spi-cs-high | spi->mode | = SPI_CS_HIGH | 一般片選CS是低有效,指定后高有效 | |
spi-tx-bus-width | spi->mode | = SPI_NO_TX | 發(fā)送方向?yàn)?,可能只是讀 | |
= SPI_TX_DUAL | DUAL SPI 雙線半雙工 | |||
= SPI_TX_QUAD | QUAD SPI 四線半雙工 | |||
= SPI_TX_OCTAL | OCTAL SPI 八線半雙工 | |||
spi-rx-bus-width | spi->mode | = SPI_NO_RX | 接受方向?yàn)?,可能只是發(fā)送 | |
= SPI_RX_DUAL | DUAL SPI 雙線半雙工 | |||
= SPI_RX_QUAD | QUAD SPI 四線半雙工 | |||
= SPI_RX_OCTAL | OCTAL SPI 八線半雙工 | |||
reg | spi->chip_select | 表示spi設(shè)備在第幾個(gè)片選 | ||
spi-max-frequency | spi->max_speed_hz | 這個(gè)spi設(shè)備使用的spi傳輸速率,單位Hz |
說(shuō)說(shuō)為什么這么設(shè)置:
查看ssd13306手冊(cè),SPI接口的OLED使用的時(shí)鐘周期最大是100ns,也就是頻率為10M的SPI時(shí)鐘,因此設(shè)置spi-max-frequency = <10000000>;
時(shí)鐘極性是空閑時(shí)為高電平,因此spi-cpol = 1,填上spi-cpol;
數(shù)據(jù)在第二個(gè)邊沿鎖定,因此spi-cpal = 1,填上spi-cpal;
二、編寫設(shè)備驅(qū)動(dòng)
編寫成一個(gè)字符設(shè)備驅(qū)動(dòng),提供接口供上層調(diào)用。本驅(qū)動(dòng)會(huì)不斷完善,加入各種新知識(shí)運(yùn)用進(jìn)來(lái)。說(shuō)說(shuō)要點(diǎn):
創(chuàng)建設(shè)備節(jié)點(diǎn)三件套
需要注冊(cè)字符設(shè)備,創(chuàng)建類,創(chuàng)建設(shè)備節(jié)點(diǎn)
oled_dev- >major = register_chrdev(0, "ssd13306", &ssd13306_fops);
...
oled_dev- >oled_class = class_create(THIS_MODULE, "ssd13306");
...
device_create(oled_dev- >oled_class,NULL, MKDEV(oled_dev- >major, 0), NULL, "ssd13306");
獲取使用到的gpiod
gpiod是較新的gpio描述符,OLED使用到cs、dc、reset三個(gè)gpio。
其中dc引腳和SPI協(xié)議無(wú)關(guān),只和OLED這個(gè)模塊相關(guān),用來(lái)區(qū)分發(fā)送的是命令還是顯示數(shù)據(jù)。
cs引腳不需要我們自己管理,實(shí)際上架構(gòu)已經(jīng)為我們獲取了。
oled_dev- >dc_gpio = gpiod_get(&spi- >dev, "dc", GPIOD_OUT_LOW);
//初始化dc引腳
dc_gpio_init(oled_dev- >dc_gpio);
oled_dev- >reset_gpio = gpiod_get(&spi- >dev, "reset", GPIOD_OUT_HIGH);
SPI發(fā)送命令函數(shù)
需要發(fā)送命令初始化oled,使用spi_write這個(gè)SPI架構(gòu)提供的API可以以同步的方式發(fā)送SPI數(shù)據(jù),經(jīng)過(guò)源碼研究,其實(shí)無(wú)所謂同步異步了,現(xiàn)架構(gòu)都是使用異步的,都使用工作者線程來(lái)完成spi的傳輸管理。
static void ssd13306_write_cmd(struct ssd13306_oled *ssd13306,unsigned char cmd)
{
int ret = 0;
dc_gpio_set_value(g_oled- >dc_gpio,0);
ret = spi_write(ssd13306- >ssd13306, &cmd, 1);
if(ret)
dev_err(&ssd13306- >ssd13306- >dev,"err spi write cmd(%d)",ret);
}
//spi 發(fā)送函數(shù)的原型
spi_write(struct spi_device *spi, const void *buf, size_t len)
dc_gpio_set_value(g_oled->dc_gpio,0)
dc gpio拉低,表示接下來(lái)發(fā)送的都是命令。
硬件初始化,刷屏
硬件初始化只針對(duì)ssd13306,其他OLED模塊另外尋找初始化序列。
static void ssd13306_hw_init(struct ssd13306_oled *ssd13306)
{
oled_reset(ssd13306);
ssd13306_write_cmd(ssd13306,0xAE);//關(guān)閉oled顯示
ssd13306_write_cmd(ssd13306,0xd5);//設(shè)置時(shí)鐘分頻因子
ssd13306_write_cmd(ssd13306,80);//[3:0]分頻因子,[7:4]震蕩頻率
ssd13306_write_cmd(ssd13306,0xa8);//設(shè)置驅(qū)動(dòng)路數(shù)
ssd13306_write_cmd(ssd13306,0x3f);
ssd13306_write_cmd(ssd13306,0xd3);//設(shè)置顯示偏移
ssd13306_write_cmd(ssd13306,0x00);//設(shè)置顯示偏移
ssd13306_write_cmd(ssd13306,0x40);//設(shè)置顯示開(kāi)始行
ssd13306_write_cmd(ssd13306,0x8d);//設(shè)置電荷泵
ssd13306_write_cmd(ssd13306,0x14);//bit2開(kāi)啟或關(guān)閉
ssd13306_write_cmd(ssd13306,0x20);//設(shè)置尋址模式
ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2頁(yè)地址模式
ssd13306_write_cmd(ssd13306,0xa1);//左右鏡像
ssd13306_write_cmd(ssd13306,0xc8);//上下鏡像
ssd13306_write_cmd(ssd13306,0xda);//設(shè)置com硬件引腳配置
ssd13306_write_cmd(ssd13306,0x12);
ssd13306_write_cmd(ssd13306,0x81);//亮度設(shè)置
ssd13306_write_cmd(ssd13306,0xff);
ssd13306_write_cmd(ssd13306,0xd9);//設(shè)置預(yù)充電周期
ssd13306_write_cmd(ssd13306,0xf1);
ssd13306_write_cmd(ssd13306,0xdb);//設(shè)置電壓倍率
ssd13306_write_cmd(ssd13306,0x30);//
ssd13306_write_cmd(ssd13306,0xa4);//全局顯示開(kāi)啟bit0 :0關(guān)閉,1開(kāi)啟
ssd13306_write_cmd(ssd13306,0xa6);//設(shè)置顯示方式,bit0 :0正常模式,1反相模式
ssd13306_write_cmd(ssd13306,0xaf);//開(kāi)啟oled顯示
}
static void oled_clear(unsigned char filldata)
{
int page;
int col;
unsigned char * data;
data = kmalloc(1, GFP_KERNEL);
*data = filldata;
for(page=0;page< 8;page++)
{
ssd13306_write_cmd (g_oled,0xb0+page); //設(shè)置頁(yè)地址(0~7)
ssd13306_write_cmd (g_oled,0x0); //設(shè)置顯示位置列低地址
ssd13306_write_cmd (g_oled,0x10); //設(shè)置顯示位置列高地址
for(col=0;col< 128;col++)ssd13306_write_datas(g_oled,data,1);
}
kfree(data);
}