SPI 從設(shè)備芯片的種類非常廣泛,包括用于模擬傳感器和編解碼器的數(shù)字/模擬轉(zhuǎn)換器、內(nèi)存芯片、USB 控制器或以太網(wǎng)適配器等外設(shè),以及其他類型的芯片。
這樣的驅(qū)動通常在linux看來是一個協(xié)議驅(qū)動,比如spi flash,負(fù)責(zé)和MTD系統(tǒng)打交道;比如觸摸傳感器,需要和input子系統(tǒng)打交道,再比如spi接口的OLED模塊。
這樣的設(shè)備使用的【接口】在驅(qū)動中使用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)該表示一個接口而不是一個驅(qū)動,當(dāng)然你說這個接口連接的不就是設(shè)備嗎?這么理解好像也沒錯。
SPI 設(shè)備驅(qū)動使用struct spi_driver表示,提供probe驅(qū)動入口,老套路了,比如
static int ssd13306_probe(struct spi_device *spi)
以上是函數(shù)原型,留下一個疑問,struct spi_device是作為一個對象傳進(jìn)來的,它是什么時候被構(gòu)造的呢?
一、編寫設(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ū)動
//compatible = "spidev,spidev";//使用spidev通用驅(qū)動
//compatible = "solomon,ssd1306";//使用fbtftf的驅(qū)動,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控制器,我們使用這個SPI控制器和從設(shè)備oled通信,因此要在這個設(shè)備樹節(jié)點下寫一個子節(jié)點表示OLED設(shè)備。下面是必填項:
- cs-gpios 是SPI控制器的屬性,描述了從設(shè)備使用了哪些cs片選引腳,如果有3個從設(shè)備就需要寫3個從cs引腳,而且順序要和從設(shè)備設(shè)備樹節(jié)點的reg屬性對應(yīng)。如上面gpio1 29表示片選0的從設(shè)備,reg屬性表示設(shè)備片選號。
- reg 表示此設(shè)備在這個SPI控制器中第幾個片選,和cs-gpios順序一致。
- compatible 用于匹配驅(qū)動。
- pinctrl-0 = <&pinctrl_ecspi2> 表示要引腳復(fù)用哪些信號
pinctrl_ecspi2:oled{//沒有MISO因為ssd1306 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ū)動異常,需要進(jìn)一步調(diào)試是不是缺了某些屬性。
下面列舉了從設(shè)備設(shè)備樹節(jié)點可選屬性:
屬性 | 描述 | |||
---|---|---|---|---|
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ā)送方向為0,可能只是讀 | |
= SPI_TX_DUAL | DUAL SPI 雙線半雙工 | |||
= SPI_TX_QUAD | QUAD SPI 四線半雙工 | |||
= SPI_TX_OCTAL | OCTAL SPI 八線半雙工 | |||
spi-rx-bus-width | spi->mode | = SPI_NO_RX | 接受方向為0,可能只是發(fā)送 | |
= SPI_RX_DUAL | DUAL SPI 雙線半雙工 | |||
= SPI_RX_QUAD | QUAD SPI 四線半雙工 | |||
= SPI_RX_OCTAL | OCTAL SPI 八線半雙工 | |||
reg | spi->chip_select | 表示spi設(shè)備在第幾個片選 | ||
spi-max-frequency | spi->max_speed_hz | 這個spi設(shè)備使用的spi傳輸速率,單位Hz |
說說為什么這么設(shè)置:
查看ssd13306手冊,SPI接口的OLED使用的時鐘周期最大是100ns,也就是頻率為10M的SPI時鐘,因此設(shè)置spi-max-frequency = <10000000>;
時鐘極性是空閑時為高電平,因此spi-cpol = 1,填上spi-cpol;
數(shù)據(jù)在第二個邊沿鎖定,因此spi-cpal = 1,填上spi-cpal;
二、編寫設(shè)備驅(qū)動
編寫成一個字符設(shè)備驅(qū)動,提供接口供上層調(diào)用。本驅(qū)動會不斷完善,加入各種新知識運用進(jìn)來。說說要點:
創(chuàng)建設(shè)備節(jié)點三件套
需要注冊字符設(shè)備,創(chuàng)建類,創(chuàng)建設(shè)備節(jié)點
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三個gpio。
其中dc引腳和SPI協(xié)議無關(guān),只和OLED這個模塊相關(guān),用來區(qū)分發(fā)送的是命令還是顯示數(shù)據(jù)。
cs引腳不需要我們自己管理,實際上架構(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這個SPI架構(gòu)提供的API可以以同步的方式發(fā)送SPI數(shù)據(jù),經(jīng)過源碼研究,其實無所謂同步異步了,現(xiàn)架構(gòu)都是使用異步的,都使用工作者線程來完成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拉低,表示接下來發(fā)送的都是命令。
硬件初始化,刷屏
硬件初始化只針對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è)置時鐘分頻因子
ssd13306_write_cmd(ssd13306,80);//[3:0]分頻因子,[7:4]震蕩頻率
ssd13306_write_cmd(ssd13306,0xa8);//設(shè)置驅(qū)動路數(shù)
ssd13306_write_cmd(ssd13306,0x3f);
ssd13306_write_cmd(ssd13306,0xd3);//設(shè)置顯示偏移
ssd13306_write_cmd(ssd13306,0x00);//設(shè)置顯示偏移
ssd13306_write_cmd(ssd13306,0x40);//設(shè)置顯示開始行
ssd13306_write_cmd(ssd13306,0x8d);//設(shè)置電荷泵
ssd13306_write_cmd(ssd13306,0x14);//bit2開啟或關(guān)閉
ssd13306_write_cmd(ssd13306,0x20);//設(shè)置尋址模式
ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2頁地址模式
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);//全局顯示開啟bit0 :0關(guān)閉,1開啟
ssd13306_write_cmd(ssd13306,0xa6);//設(shè)置顯示方式,bit0 :0正常模式,1反相模式
ssd13306_write_cmd(ssd13306,0xaf);//開啟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è)置頁地址(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);
}