天天看點

Linux SPI裝置驅動

實作了SPI OLED外設驅動,OLED型号為SH1106。

1.主機驅動與外設驅動分離

Linux中的I2C、SPI、USB等總線驅動,都采用了主機(控制器)驅動與外設(裝置)驅動分離的思想。主機端隻負責實作總線協定相關的邏輯,總線上傳輸的資料主機并不關心,如主機的i2c控制器隻負責實作i2c總線協定相關内容,如i2c起始結束信号、i2c應答信号、i2c時鐘、發送和接收資料等,至于i2c總線上傳輸的資料,i2c控制器并不關心。外設驅動關心的是如何通路挂在總線上的裝置,即總線傳輸的資料要符合外設的規定。這樣才能被外設正确識别,如i2c從裝置ap3216c驅動,需要根據ap3216c的寄存器等資訊,設定和讀取資料。主機和外設驅動在實體是分開的,但邏輯上是密切相關的,外設驅動要借助主機驅動提供的基礎設施(API和資料結構)在總線上發起資料傳輸,以便通路自身。主機和外設驅動涉及了4個軟體子產品:

(1)主機端的驅動。根據具體的I2C、SPI、USB等控制器的硬體特性,操作具體的主機控制器,在總線上産生符合總線協定的時序。

(2)連接配接主機和外設的紐帶。外設不直接操作主機端的驅動來通路外設,而是調用一個标準的API,由這個标準的API把請求轉發給具體的主機端驅動。

(3)外設端的驅動。外設接在I2C、SPI、USB等總線上,但外設可以是觸摸屏、網卡、聲霸卡等任意類型的裝置,這就需要編寫具體的外設驅動,通過主機端的驅動來通路外設。即利用具體總線提供的

xxx_driver

結構體中

probe

函數注冊具體的外設驅動類型,通路外設通過具體總線提供的标準API。

(4)闆級邏輯。闆級邏輯描述主機和外設是如何連接配接的,通俗的說就是什麼總線上挂了什麼裝置,這部分資訊需要在裝置樹中進行描述。

2.SPI驅動架構

2.1.主機端驅動-SPI控制器驅動

在Linux中,使用

struct spi_master

結構體來描述SPI主機控制器驅動。主要成員有主機控制器的序号

bus_num

、片選數量

num_chipselect

、SPI模式

mode_bits

及資料傳輸函數

transfer

transfer_one_message

等。

transfer

transfer_one_message

用于SPI控制器和SPI外設之間傳輸資料。SPI主機端驅動已經由SOC廠家實作,imx6ull SPI驅動檔案路徑為

drivers/spi/spi-imx.c

,後續進行分析。

include <linux/spi/spi.h>
    struct spi_master {
        struct device	dev;
        struct list_head list;
        // SOC上的SPI總線數量,即SPI控制器的數量
        s16			bus_num;
        u16			num_chipselect;
        u16			mode_bits;

        /* bitmask of supported bits_per_word for transfers */
        u32			bits_per_word_mask;
        #define SPI_BPW_MASK(bits) BIT((bits) - 1)
        #define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
        #define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))

        /* limits on transfer speed */
        u32			min_speed_hz;
        u32			max_speed_hz;

        /* other constraints relevant to this driver */
        u16			flags;
        #define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
        #define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
        #define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
        #define SPI_MASTER_MUST_RX      BIT(3)		/* requires rx */
        #define SPI_MASTER_MUST_TX      BIT(4)		/* requires tx */
        spinlock_t		bus_lock_spinlock;
        struct mutex		bus_lock_mutex;
        bool			bus_lock_flag;
        int	(*setup)(struct spi_device *spi);
        // SPI控制器資料傳輸函數
        int	(*transfer)(struct spi_device *spi, struct spi_message *mesg);

        // 和spi工作線程相關的定義,imx6ul預設spi傳輸資料時使用核心線程
        bool				queued;
        struct kthread_worker		kworker;  // 管理核心線程,可以有多個線程工作在kworker上
        struct task_struct		*kworker_task;
        struct kthread_work		pump_messages;  // 具體工作,由kworker管理的線程處理
        spinlock_t			queue_lock;
        struct list_head		queue;
        struct spi_message		*cur_msg;

        int (*prepare_transfer_hardware)(struct spi_master *master);
        // SPI控制器資料傳輸函數,一次傳輸一個spi_message資訊
        int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
        int (*unprepare_transfer_hardware)(struct spi_master *master);
        int (*prepare_message)(struct spi_master *master,struct spi_message *message);
        int (*unprepare_message)(struct spi_master *master,struct spi_message *message);
        void (*set_cs)(struct spi_device *spi, bool enable);
        int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer);
        void (*handle_err)(struct spi_master *master, struct spi_message *message);
    };
           

2.2.連接配接主機驅動和外設驅動的紐帶-SPI核心層

SPI核心層提供了SPI主機控制器驅動及SPI外設驅動注冊、登出等API。

spi_alloc_master

用于配置設定一個

struct spi_master

結構體,

dev

為SPI控制器裝置結構體指針,

size

為額外配置設定記憶體空間的位元組數,

dev

結構體

driver_data

指針指向額外的空間,傳回值為

NULL

表示失敗,反之則表示成功。

devm_spi_register_master

用于注冊SPI主機控制器,

dev

為裝置結構體指針,

master

為SPI主機控制器結構體指針,傳回值為0表示注冊成功,傳回值為負值表示注冊失敗。

spi_register_master

spi_unregister_master

用于注冊和登出SPI主機控制器。

include <linux/spi/spi.h>
    struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
    int devm_spi_register_master(struct device *dev, struct spi_master *master)
    int spi_register_master(struct spi_master *master)
    void spi_unregister_master(struct spi_master *master)
           

使用

spi_register_driver

spi_unregister_driver

注冊、登出SPI外設驅動。

sdrv

為SPI外設驅動結構體指針。編寫驅動的主要工作是實作SPI外設驅動。

include <linux/spi/spi.h>
    int spi_register_driver(struct spi_driver *sdrv)
    void spi_unregister_driver(struct spi_driver *sdrv)
    // 完成module_init和module_exit的功能
    #define module_spi_driver(__spi_driver) module_driver(__spi_driver, spi_register_driver,spi_unregister_driver)
           

2.3.外設端驅動-SPI外設驅動

Linux使用

struct spi_driver

結構體描述SPI外設驅動。可以看出

spi_driver

platform_driver

結構體很相似,都有

probe()

remove()

等函數。當SPI裝置和驅動比對成功後,

probe

函數就會執行。

include <linux/spi/spi.h>
    struct spi_driver {
        const struct spi_device_id *id_table;
        int			(*probe)(struct spi_device *spi);
        int			(*remove)(struct spi_device *spi);
        void			(*shutdown)(struct spi_device *spi);
        struct device_driver	driver;
    };
           

在SPI外設驅動中,SPI總線資料傳輸時使用了一套與CPU無關的統一接口。接口的關鍵資料結構是

struct spi_transfer

,用于描述SPI總線傳輸的資料,類似于

struct i2c_msg

結構體,都是将傳輸的資料進行封裝。在一次完整的SPI資料傳輸過程中,包含一個或多個

spi_transfer

spi_message

将多個

spi_transfer

以雙向連結清單的形式組織在一起。

struct spi_transfer {
        const void	*tx_buf;
        void		*rx_buf;
        unsigned	len;
        dma_addr_t	tx_dma;
        dma_addr_t	rx_dma;
        struct sg_table tx_sg;
        struct sg_table rx_sg;
        unsigned	cs_change:1;
        unsigned	tx_nbits:3;
        unsigned	rx_nbits:3;
    #define	SPI_NBITS_SINGLE	0x01 /* 1bit transfer */
    #define	SPI_NBITS_DUAL		0x02 /* 2bits transfer */
    #define	SPI_NBITS_QUAD		0x04 /* 4bits transfer */
        u8		bits_per_word;
        u16		delay_usecs;
        u32		speed_hz;
        struct list_head transfer_list;
    };

    struct spi_message {
        struct list_head	transfers;
        struct spi_device	*spi;
        unsigned		is_dma_mapped:1;
        /* completion is reported through a callback */
        void			(*complete)(void *context);
        void			*context;
        unsigned		frame_length;
        unsigned		actual_length;
        int			status;

        /* for optional use by whatever driver currently owns the
        * spi_message ...  between calls to spi_async and then later
        * complete(), that's the spi_master controller driver.
        */
        struct list_head	queue;
        void			*state;
    };
    // 初始化spi_message結構體
    void spi_message_init(struct spi_message *m)
    // 将多個spi_transfe組織成spi_message形式,實質上是向連結清單中添加元素
    void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
    // 删除spi_transfer中的spi_message
    void spi_transfer_del(struct spi_transfer *t)
    // 初始化spi_message,并将spi_message追加到spi_transfer的雙向連結清單中
    void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers)
    // 發起一次同步SPI資料傳輸,調用會被阻塞,直到資料傳輸完成或者出錯
    int spi_sync(struct spi_device *spi, struct spi_message *message)
    // 發起一次異步SPI資料傳輸,調用不會被阻塞,總線被占用或者spi_message送出完成後傳回,可以在spi_message的complete字段挂接一個回調函數,當消息處理完後會調用此回調函數
    int spi_async(struct spi_device *spi, struct spi_message *message)
    // 通用的SPI同步寫入函數
    int spi_write(struct spi_device *spi, const void *buf, size_t len)
    // 通用的SPI同步讀取函數
    int spi_read(struct spi_device *spi, void *buf, size_t len)
    // 同步SPI傳輸函數,可包含讀寫操作
    int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers)
           

2.4.SPI裝置樹裝置節點

引腳定義:
GPIO3_IO28    LCD_DATA23    MISO
GPIO3_IO27    LCD_DATA22    MOSI    SDA
GPIO3_IO17    LCD_DATA12    RDY
GPIO3_IO25    LCD_DATA20    SCLK    SCL
GPIO3_IO26    LCD_DATA21    SS0     D/C
GPIO3_IO10    LCD_DATA05    SS1
GPIO3_IO11    LCD_DATA06    SS2
GPIO3_IO12    LCD_DATA07    SS3     RST
// SPI pinctrl
pinctrl_spi1: spi1 {
	fsl,pins = <
		MX6UL_PAD_LCD_DATA23__ECSPI1_MISO  0x10b1
		MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI  0x10b1        // SDA
		MX6UL_PAD_LCD_DATA20__ECSPI1_SCLK  0x10b1        // SCL
		MX6UL_PAD_LCD_DATA21__GPIO3_IO26   0x10b0        // D/C
		MX6UL_PAD_LCD_DATA07__GPIO3_IO12   0x10b0        // RST
	>;
};
&ecspi1 {
	// 片選數量
	fsl,spi-num-chipselects = <1>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi1>;
	status = "okay";

	// oled連接配接到spi1的第0個通道上
	oled: [email protected] {
		compatible = "oled, ssh1106";
		rst-gpio = <&gpio3 12 GPIO_ACTIVE_LOW>;  // 低電平複位
		dc-gpio = <&gpio3 26 GPIO_ACTIVE_LOW>;   // 低電平表示指令,高電平表示資料
		spi-max-frequency = <4000000>;    // 最大頻率4M
		reg = <0>;			
	};
};
           

3.imx6ull spi主機控制器驅動

// 相容屬性
    static const struct of_device_id spi_imx_dt_ids[] = {
        { .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, },
        { .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },
        { .compatible = "fsl,imx27-cspi", .data = &imx27_cspi_devtype_data, },
        { .compatible = "fsl,imx31-cspi", .data = &imx31_cspi_devtype_data, },
        { .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },
        { .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
        { .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },  // 支援imx6ull的spi控制器
        { /* sentinel */ }
    };
    static struct platform_driver spi_imx_driver = {
        .driver = {
                .name = DRIVER_NAME,
                .of_match_table = spi_imx_dt_ids,  // 裝置樹比對表
                .pm = IMX_SPI_PM,
        },
        .id_table = spi_imx_devtype,  // 傳統的比對表
        .probe = spi_imx_probe,    // probe函數
        .remove = spi_imx_remove,  // remove函數
    };
    // 注冊平台驅動
    module_platform_driver(spi_imx_driver);
    // module_platform_driver完成了平台驅動的注冊和登出
    #define module_platform_driver(__platform_driver) \
	        module_driver(__platform_driver, platform_driver_register, \
			        platform_driver_unregister)		        
           

平台驅動的probe函數是最重要的,下面分析probe函數。

spi_imx_probe
      ->of_property_read_u32      // 讀取fsl,spi-num-chipselects屬性,即片選數量
      ->spi_alloc_master          // 配置設定spi_master結構體
      ->of_get_named_gpio         // 擷取spi cs 引腳
      ->devm_gpio_request         // 請求gpio
      ->spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;  // spi配置函數,傳輸資料時會調用此函數
      ->spi_imx->bitbang.txrx_bufs = spi_imx_transfer   // 注冊spi資料傳輸函數,spi裝置傳輸資料最終調用此函數
      ->init_completion           // 初始化等待完成隊列
      ->platform_get_resource     // 擷取位址資訊
      ->devm_ioremap_resource     // 映射位址
      ->platform_get_irq          // 擷取虛拟中斷号
      ->devm_request_irq          // 注冊中斷,中斷服務函數為spi_imx_isr
      ->devm_clk_get              // 擷取ipg per時鐘
      ->clk_prepare_enable        // 使能時鐘
      ->spi_imx->devtype_data->reset(spi_imx)       // 将spi接收FIFO中的資料讀完
      ->spi_imx->devtype_data->intctrl(spi_imx, 0)  // 關閉所有中斷
      ->spi_bitbang_start
        ->spi_register_master        // 注冊spi主機驅動
          ->of_spi_register_master
            ->of_gpio_named_count    // 擷取spi片選引腳數量
            ->of_get_named_gpio      // 擷取spi片選gpio編号
          ->dev_set_name               // 設定spi主機控制器裝置名字
          ->device_add                 // 注冊裝置
          ->spi_master_initialize_queue    // 初始化spi工作隊列
            ->master->transfer = spi_queued_transfer    // 設定spi工作隊列傳輸函數
            ->master->transfer_one_message = spi_transfer_one_message    // 設定transfer_one_message函數
            ->spi_init_queue
              ->init_kthread_worker    // 初始化管理核心線程的kworker
              ->kthread_run            // 運作核心線程,執行kthread_worker_fn函數,循環處理pump_messages
               // 初始化pump_messages,設定核心線程處理spi messages的函數為spi_pump_messages,
               // 所有的spi資料傳輸都在此函數中完成
              ->init_kthread_work     
            ->spi_start_queue          
              ->master->running = true;  // 設定spi核心線程開始運作标記
              ->queue_kthread_work       // 将spi工作pump_messages添加到建立的核心線程工作隊列中
          ->of_register_spi_devices      // 注冊挂載在spi總線的裝置,周遊spi裝置樹節點下的裝置
          ->acpi_register_spi_devices    // 注冊電源相關裝置
      ->clk_disable_unprepare    // 關閉ipg時鐘
      ->clk_disable_unprepare    // 關閉per時鐘
           

分析probe函數可知,imx6ul啟用了核心線程來處理spi的工作任務,核心線程調用

spi_pump_messages

函數進行spi資料的傳輸。下面分析

spi_pump_messages

函數。

spi_pump_messages
      ->container_of    // 擷取spi_master結構體指針
      ->__spi_pump_messages(master, true)     // spi_sync也會調用此函數,但其第二個參數為false
        ->master->prepare_message()           // 使能時鐘,真正調用的是spi_imx_prepare_message
        ->master->transfer_one_message()      // 調用此函數傳輸資料,真正調用的是spi_bitbang_transfer_one
          ->list_for_each_entry               // 周遊spi_message,傳輸所有資料
          ->bitbang->setup_transfer()         // 配置spi資料傳輸環境,最終調用spi_imx_setupxfer
            ->spi_imx_setupxfer()             // 配置時鐘、極性、相位,根據位寬選擇收發函數
              ->spi_imx->rx = spi_imx_buf_rx_u8   // 設定發送函數,以8位為例
		      ->spi_imx->tx = spi_imx_buf_tx_u8;  // 設定接收函數,以8位為例
              ->spi_imx->devtype_data->config()   // 配置
                ->mx51_ecspi_config()             // 最終調用此函數進行配置
          ->bitbang->chipselect()                 // 片選,真正調用的是spi_imx_chipselect
          ->bitbang->txrx_bufs()              // 調用spi_imx_transfer
            ->spi_imx_transfer()
              ->spi_imx_pio_transfer
                ->spi_imx_push
                  ->spi_imx->tx    // 調用spi_imx_buf_tx_u8發送資料
                  ->spi_imx->devtype_data->trigger  // 發送完調用mx51_ecspi_trigger函數啟動burst傳輸
                ->spi_imx->devtype_data->intctrl
                  ->mx51_ecspi_intctrl    // 使能spi發送fifo空中斷
                ->wait_for_completion     // 等待發送完成
           

啟動傳輸後,剩下資料的收發在中斷中,中斷函數為

spi_imx_isr

,下面分析中斷函數:

spi_imx_isr
      ->spi_imx->devtype_data->rx_available()    // 是否有資料接收,真正調用的是mx51_ecspi_rx_available
      ->spi_imx->rx()    // 有資料就調用接收函數接收資料
      ->spi_imx_push()   // 如果還有資料要發送,調用發送函數發送
        ->spi_imx->tx    // 調用spi_imx_buf_tx_u8發送資料
      ->spi_imx->devtype_data->intctrl()    // 如果還有資料要接收,使能接收中斷
      ->spi_imx->devtype_data->intctrl()    // 無資料接收和發送,關閉中斷
      ->complete                            // 資料傳輸完成,發送信号,喚醒等待的線程
           

4.SPI裝置驅動源碼

/*===========================spi_sh1106.h================================*/
	#ifndef __SPI_SH1106_H__
	#define __SPI_SH1106_H__
	
	#include <linux/spi/spi.h>
	#include <linux/cdev.h>
	#include <linux/of.h> 		  // 裝置樹 
	#include <linux/mutex.h>
	#include <linux/device.h>
	#include <linux/fs.h>
	#include <uapi/linux/ioctl.h>
	
	#define NAME      "spi_sh1106"
	#define DEV_NUM   (1)
	
	#define CMD      (0x0)
	#define DATA     (0x1)
	
	struct sh1106_cdev
	{
	    struct cdev cdev;
	    dev_t devno;
	    struct class* sh1106_class;
		struct device* sh1106_device;
	    struct device_node* node;
	    int rst_pin;  // 複位引腳
	    int dc_pin;   // 資料指令控制引腳
	    struct mutex mutex;
	    struct spi_device* spi;
	};
	#endif // __SPI_SH1106_H__	
	
	/*===========================spi_sh1106.c================================*/
	#include <linux/init.h>
	#include <linux/module.h>
	#include <linux/kernel.h>
	#include <asm/uaccess.h>
	#include <linux/slab.h>
	#include <linux/stat.h>
	#include <linux/sysfs.h>
	#include <linux/string.h>
	#include <linux/delay.h>
	#include <linux/gpio.h>
	#include <linux/of_gpio.h>
	#include "spi_sh1106.h"
	
	static struct sh1106_cdev* sh1106 = NULL;
	
	static int sh1106_open(struct inode* inode, struct file* filp)
	{
	    struct sh1106_cdev* dev;
	    dev = container_of(inode->i_cdev, struct sh1106_cdev, cdev);
		// 如果是非阻塞打開,嘗試擷取互斥體,擷取失敗直接傳回,擷取成功繼續執行
		if (filp->f_flags & O_NONBLOCK) {
			// mutex_trylock和down_trylock的傳回值意義相反
			if (!mutex_trylock(&dev->mutex))
				return -EBUSY;
		}
		else mutex_lock(&dev->mutex);
	
	    // 複位oled
	    gpio_set_value(dev->rst_pin, 0);
	    // 延遲50毫秒
	    mdelay(50);
	    gpio_set_value(dev->rst_pin, 1);
	
	    filp->private_data = dev;            
	    return 0;
	}
	static ssize_t sh1106_write(struct file* filp, const char __user* buf, 
	                            size_t size, loff_t* ppos)
	{
	    int ret = 0;
	    struct spi_message m;
	    struct spi_transfer t;
	    char buffer[512] = {0};
	    struct sh1106_cdev* dev = filp->private_data;
	    if (size > sizeof(buffer)) return -EINVAL;
	
	    ret = copy_from_user(buffer, buf, size);
	    if (ret != 0) return -EFAULT;
	#if 0
	    ret = spi_write(dev->spi, buffer, size);
	    if (ret != 0) return ret;
	#else
	    memset(&m, 0, sizeof(struct spi_message));
	    memset(&t, 0, sizeof(struct spi_transfer));
	    t.tx_buf = buffer;  // 發送緩沖區位址
	    t.len = size;  // 發送資料的長度
	    spi_message_init(&m);  // 初始化spi_message
	    spi_message_add_tail(&t, &m);  // 将spi_transfer添加到spi_message
	    ret = spi_sync(dev->spi, &m);  // 同步傳輸
	    if (ret != 0) return -EFAULT;
	#endif
	    return size;
	}
	static int sh1106_release(struct inode* inode, struct file* filp)
	{
	    struct sh1106_cdev* dev;
	    dev = container_of(inode->i_cdev, struct sh1106_cdev, cdev); 
	    mutex_unlock(&dev->mutex);
	    return 0;
	}
	
	// 控制spi傳輸的資訊對oled來說是指令還是資料
	static long sh1106_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
	{
	    struct sh1106_cdev* dev = filp->private_data;
	    if (cmd == CMD)
	        gpio_set_value(dev->dc_pin, 0); // spi傳輸的資訊是指令
	    else if(cmd == DATA)
	        gpio_set_value(dev->dc_pin, 1); // spi傳輸的資訊是資料
	    else
	        return -EINVAL;
	    return 0;           
	}
	
	static const struct file_operations sh1106_ops = 
	{
	    .owner = THIS_MODULE,
	    .open = sh1106_open,
	    //.read = sh1106_read,
	    .write = sh1106_write,
	    .unlocked_ioctl = sh1106_ioctl,
	    .release = sh1106_release
	};
	// 擷取裝置樹中定義的複位、資料指令GPIO
	static int get_gpio(struct sh1106_cdev* dev)
	{
	    int ret = 0;
	    dev->rst_pin = of_get_named_gpio(dev->node, "rst-gpio", 0);
	    if (dev->rst_pin < 0) {
	        dev_err(&dev->spi->dev, "of_get_named_gpio rst-gpio failed, errno %d\n", 
	                                dev->rst_pin);
	        return dev->rst_pin;
	    }
	    ret = gpio_is_valid(dev->rst_pin);
	    if (ret == 0) {
	        dev_err(&dev->spi->dev, "gpio_is_valid rst-gpio failed, errno %d\n", ret);
	        return -ENODEV;        
	    }
	    ret = gpio_request(dev->rst_pin, "rst-gpio");
	    if (ret < 0) {
	        dev_err(&dev->spi->dev, "gpio_request rst-gpio failed, errno %d\n", ret);
	        return ret;         
	    }
	    // 設定rst引腳輸出高點平
	    gpio_direction_output(dev->rst_pin, 1);
	
	    dev->dc_pin = of_get_named_gpio(dev->node, "dc-gpio", 0);
	    if (dev->dc_pin < 0) {
	        dev_err(&dev->spi->dev, "of_get_named_gpio dc-gpio failed, errno %d\n", ret);
	        ret = dev->dc_pin;
	        goto free_rst;
	    }
	    ret = gpio_is_valid(dev->dc_pin);
	    if (ret == 0) {
	        dev_err(&dev->spi->dev, "gpio_is_valid dc-gpio failed, errno %d\n", ret);
	        ret = -ENODEV;
	        goto free_rst;        
	    }
	    ret = gpio_request(dev->dc_pin, "dc-gpio");
	    if (ret < 0) {
	        dev_err(&dev->spi->dev, "gpio_request dc-gpio failed, errno %d\n", ret);
	        goto free_rst;         
	    }
	    // 設定dc引腳輸出高點平
	    gpio_direction_output(dev->dc_pin, 1);
	    return 0;
	free_rst:
	    gpio_free(dev->rst_pin);
	    return ret;
	}
	
	static int sh1106_probe(struct spi_device* spi)
	{
	    int ret = 0;
	    sh1106 = kzalloc(sizeof(struct sh1106_cdev), GFP_KERNEL);
	    if (NULL == sh1106) {
	        dev_err(&spi->dev, "kzalloc failed\n");
	        return -ENOMEM;
	    }
	    sh1106->spi = spi;
	    sh1106->node = spi->dev.of_node;
	
	    ret = alloc_chrdev_region(&sh1106->devno, 0, DEV_NUM, NAME);
	    if (ret != 0) {
	        dev_err(&spi->dev, "alloc_chrdev_region error, errno %d\n", ret);
	        goto free_sh1106;
	    }
	    dev_info(&spi->dev, "device major numbers %d, minor numbers %d\n",
	                MAJOR(sh1106->devno), MINOR(sh1106->devno));    
	    // 初始化字元裝置和注冊操作函數結構體
	    cdev_init(&sh1106->cdev, &sh1106_ops);
	    sh1106->cdev.owner = THIS_MODULE;
	    ret = cdev_add(&sh1106->cdev, sh1106->devno, DEV_NUM);
	    if (ret < 0) {
	        dev_err(&spi->dev, "cdev_add error, errno %d\n", ret);
	        goto unregister_devno;        
	    }
	    // 建立類和裝置,便于自動生成裝置節點
		sh1106->sh1106_class = class_create(THIS_MODULE, NAME);
		if (IS_ERR(sh1106->sh1106_class)) {
			ret = PTR_ERR(sh1106->sh1106_class);
	        dev_err(&spi->dev, "class_create failed %d\n", ret);
			goto del_cdev;
		}
		sh1106->sh1106_device = device_create(sh1106->sh1106_class, 
	                        NULL, sh1106->devno, NULL, NAME);
		if (IS_ERR(sh1106->sh1106_device)) {
			ret = PTR_ERR(sh1106->sh1106_device);
	        dev_err(&spi->dev, "device_create failed %d\n", ret);
			goto clean_class;
		}
	    ret = get_gpio(sh1106);
	    if (ret != 0) goto clean_device;
	
	    mutex_init(&sh1106->mutex);
	    return 0;
	
	clean_device:
		device_destroy(sh1106->sh1106_class, sh1106->devno);
	clean_class: 
		class_destroy(sh1106->sh1106_class);    
	del_cdev:
	    cdev_del(&sh1106->cdev);
	unregister_devno:
	    unregister_chrdev_region(sh1106->devno, DEV_NUM);
	free_sh1106:
	    kfree(sh1106);
	    sh1106 = NULL;
	    return ret;
	}
	
	static int sh1106_remove(struct spi_device* spi)
	{
	    gpio_free(sh1106->dc_pin);
	    gpio_free(sh1106->rst_pin);
		device_destroy(sh1106->sh1106_class, sh1106->devno);
		class_destroy(sh1106->sh1106_class);    
	    cdev_del(&sh1106->cdev);
	    unregister_chrdev_region(sh1106->devno, DEV_NUM);
	    kfree(sh1106);
	    sh1106 = NULL;
	    return 0;
	}
	
	// 傳統的裝置比對表
	static const struct spi_device_id sh1106_id[] = {
	    {"oled, ssh1106", 0},
	    { }
	};
	// 裝置樹比對表
	static const struct of_device_id sh1106_of_match[] = {
	    {.compatible = "oled, ssh1106"},
	    { } // 最後一項必須為空
	};
	
	// 外設驅動結構體
	static struct spi_driver sh1106_driver = {
	    .id_table = sh1106_id,
	    .probe = sh1106_probe,
	    .remove = sh1106_remove,
	    .driver = {
	        .owner = THIS_MODULE,
	        .name = NAME,
	        .of_match_table = sh1106_of_match,
	    }
	};
	
	#if 1
	static __init int sh1106_init(void)
	{
	    int ret = 0;
	    ret = spi_register_driver(&sh1106_driver);
	    if (ret != 0) 
	        pr_err("spi_sh1106 register failed %d\n", ret);
	    return ret;
	}
	static __exit void sh1106_exit(void)
	{
	    spi_unregister_driver(&sh1106_driver);
	}
	
	module_init(sh1106_init);
	module_exit(sh1106_exit);
	#else
	// 同時完成sh1106_init、sh1106_exit、module_init、module_exit的功能
	module_spi_driver(sh1106_driver);
	#endif
	MODULE_LICENSE("GPL");
	MODULE_AUTHOR("[email protected]");

           

5.測試程式源碼

/*===========================test.h================================*/
	#define CMD      (0x0)
	#define DATA     (0x1)
	#define ERROR (-1)
	#define OK    (0)
	#define PATH "/dev/spi_sh1106"
	#define X_WIDTH     131
	#define Y_WIDTH     64
	typedef unsigned char byte
	const byte F6x8[][6] =
	{
	    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },   // sp
	    { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 },   // !
	    { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 },   // "
	    { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #
	    { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $
	    { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 },   // %
	    { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 },   // &
	    { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 },   // '
	    { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 },   // (
	    { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 },   // )
	    { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *
	    { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +
	    { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 },   // ,
	    { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 },   // -
	    { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },   // .
	    { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 },   // /
	    { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E },   // 0
	    { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 },   // 1
	    { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 },   // 2
	    { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 3
	    { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 },   // 4
	    { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 },   // 5
	    { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 },   // 6
	    { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 },   // 7
	    { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 },   // 8
	    { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E },   // 9
	    { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 },   // :
	    { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;
	    { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },   // <
	    { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 },   // =
	    { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 },   // >
	    { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?
	    { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E },   // @
	    { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C },   // A
	    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 },   // B
	    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 },   // C
	    { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C },   // D
	    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 },   // E
	    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 },   // F
	    { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A },   // G
	    { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F },   // H
	    { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 },   // I
	    { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 },   // J
	    { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 },   // K
	    { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 },   // L
	    { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F },   // M
	    { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F },   // N
	    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E },   // O
	    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 },   // P
	    { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E },   // Q
	    { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 },   // R
	    { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 },   // S
	    { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 },   // T
	    { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F },   // U
	    { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F },   // V
	    { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F },   // W
	    { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 },   // X
	    { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y
	    { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z
	    { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 },   // [
	    { 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 },   // 55
	    { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 },   // ]
	    { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^
	    { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 },   // _
	    { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 },   // '
	    { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 },   // a
	    { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 },   // b
	    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 },   // c
	    { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F },   // d
	    { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 },   // e
	    { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 },   // f
	    { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C },   // g
	    { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 },   // h
	    { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 },   // i
	    { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 },   // j
	    { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 },   // k
	    { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 },   // l
	    { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 },   // m
	    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 },   // n
	    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 },   // o
	    { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 },   // p
	    { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC },   // q
	    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 },   // r
	    { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 },   // s
	    { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 },   // t
	    { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C },   // u
	    { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C },   // v
	    { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C },   // w
	    { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 },   // x
	    { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C },   // y
	    { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z
	    { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }    // horiz lines
	};
	const byte F8X16[]=
	{
	  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
	  0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//!1
	  0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//"2
	  0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//#3
	  0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$4
	  0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//%5
	  0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//&6
	  0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//'7
	  0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//(8
	  0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//)9
	  0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//*10
	  0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+11
	  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//,12
	  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//-13
	  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//.14
	  0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,///15
	  0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//016
	  0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//117
	  0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//218
	  0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//319
	  0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//420
	  0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//521
	  0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//622
	  0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//723
	  0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//824
	  0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//925
	  0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//:26
	  0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//;27
	  0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//<28
	  0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//=29
	  0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//>30
	  0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//?31
	  0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@32
	  0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A33
	  0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B34
	  0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C35
	  0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D36
	  0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E37
	  0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F38
	  0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G39
	  0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H40
	  0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I41
	  0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J42
	  0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K43
	  0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L44
	  0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M45
	  0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N46
	  0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O47
	  0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P48
	  0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q49
	  0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R50
	  0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S51
	  0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T52
	  0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U53
	  0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V54
	  0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W55
	  0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X56
	  0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y57
	  0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z58
	  0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[59
	  0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\60
	  0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//]61
	  0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^62
	  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_63
	  0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//`64
	  0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a65
	  0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b66
	  0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c67
	  0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d68
	  0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e69
	  0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f70
	  0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g71
	  0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h72
	  0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i73
	  0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j74
	  0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k75
	  0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l76
	  0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m77
	  0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n78
	  0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o79
	  0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p80
	  0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q81
	  0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r82
	  0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s83
	  0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t84
	  0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u85
	  0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v86
	  0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w87
	  0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x88
	  0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y89
	  0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z90
	  0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{91
	  0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//|92
	  0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//}93
	  0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~94
	
	};
	/*===========================test.c================================*/
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <sys/ioctl.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include "test.h"
	
	static void write_data(int fd, byte val)
	{
	    int ret;
	    ioctl(fd, DATA);
	    ret = write(fd, &val, sizeof(byte));
	    if (ret != sizeof(byte)) 
	        printf("write data error\n");
	}
	
	static void write_cmd(int fd, byte val)
	{
	    int ret;
	    ioctl(fd, CMD);
	    ret = write(fd, &val, sizeof(byte));
	    if (ret != sizeof(byte)) 
	        printf("write cmd error\n");
	}
	
	static inline void Set_Display_On_Off(int fd, byte d)
	{
	    write_cmd(fd, 0xAE | d);
	}
	static inline void Set_Display_Clock(int fd, byte d)
	{
	    write_cmd(fd, 0xD5);
	    write_cmd(fd, d);
	}
	static inline void Set_Multiplex_Ratio(int fd, byte d)
	{
	    write_cmd(fd, 0xA8);
	    write_cmd(fd, d);
	}
	static inline void Set_Display_Offset(int fd, byte d)
	{
	    write_cmd(fd, 0xC0 | d);
	}
	static inline void SetStartLine(int fd, byte d)
	{
	    write_cmd(fd, 0x40 | d);
	}
	static inline void Set_Charge_Pump(int fd, byte d)
	{
	    write_cmd(fd, 0x8D);
	    write_cmd(fd, 0x10 | d);
	}
	static inline void SetAddressingMode(int fd, byte d)
	{
	    write_cmd(fd, 0x20);
	    write_cmd(fd, d);
	}
	static inline void Set_Segment_Remap(int fd, byte d)
	{
	    write_cmd(fd, 0xA0 | d);
	}
	static inline void Set_Common_Remap(int fd, byte d)
	{
	    write_cmd(fd, 0xC0 | d);
	}
	static inline void Set_Common_Config(int fd, byte d)
	{
	    write_cmd(fd, 0xDA);
	    write_cmd(fd, 0x02 | d);
	}
	static inline void SetContrastControl(int fd, byte d)
	{
	    write_cmd(fd, 0x81);
	    write_cmd(fd, d);
	}
	static inline void Set_Precharge_Period(int fd, byte d)
	{
	    write_cmd(fd, 0xD9);
	    write_cmd(fd, d);
	}
	static inline void Set_VCOMH(int fd, byte d)
	{
	    write_cmd(fd, 0xDB);
	    write_cmd(fd, d);
	}
	static inline void Set_Entire_Display(int fd, byte d)
	{
	    write_cmd(fd, 0xA4 | d);
	}
	static inline void Set_Inverse_Display(int fd, byte d)
	{
	    write_cmd(fd, 0xA6 | d);
	}
	static inline void LCD_Fill(int fd, byte d)
	{
		byte y,x;	
		for(y = 0; y < 8; y++)
		{
			write_cmd(fd, 0xB0 + y);
			write_cmd(fd, 0x01);
			write_cmd(fd, 0x10);
			for(x = 0; x < X_WIDTH; x++)
				write_data(fd, d);
		}
	}
	static inline void LCD_Set_Pos(int fd, byte x, byte y)
	{
	    write_cmd(fd, 0xB0 + y);
	    write_cmd(fd, ((x & 0xF0) >> 4) | 0x10 );
	    write_cmd(fd, (x & 0x0F) | 0x01 ); 
	}
	static void oled_init(int fd)
	{
	    Set_Display_On_Off  (fd, 0x00);		  // Display Off (0x00/0x01)
	    Set_Display_Clock   (fd, 0x80);		  // Set Clock as 100 Frames/Sec
	    Set_Multiplex_Ratio (fd, 0x3F);		  // 1/64 Duty (0x0F~0x3F)
	    Set_Display_Offset  (fd, 0x00);		  // Shift Mapping RAM Counter (0x00~0x3F)
	    SetStartLine        (fd, 0x00);	      // Set Mapping RAM Display Start Line (0x00~0x3F)
	    Set_Charge_Pump     (fd, 0x04);		  // Enable Embedded DC/DC Converter (0x00/0x04)
	    SetAddressingMode   (fd, 0x02);		  // Set Page Addressing Mode (0x00/0x01/0x02)
	    Set_Segment_Remap   (fd, 0x01);		  // Set SEG/Column Mapping     0x00左右反置 0x01正常
	    Set_Common_Remap    (fd, 0x08);		  // Set COM/Row Scan Direction 0x00上下反置 0x08正常
	    Set_Common_Config   (fd, 0x10);		  // Set Sequential Configuration (0x00/0x10)
	    SetContrastControl  (fd, 0xCF);       // Set SEG Output Current
	    Set_Precharge_Period(fd, 0xF1);	      // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	    Set_VCOMH           (fd, 0x40);		  // Set VCOM Deselect Level
	    Set_Entire_Display  (fd, 0x00);		  // Disable Entire Display On (0x00/0x01)
	    Set_Inverse_Display (fd, 0x00);		  // Disable Inverse Display On (0x00/0x01)  
	    Set_Display_On_Off  (fd, 0x01);		  // Display On (0x00/0x01)
	    LCD_Fill            (fd, 0x00);       //初始清屏
		LCD_Set_Pos         (fd, 0, 0); 
	}
	//其中x1、x2的範圍0~127,y1,y2的範圍0~63
	void LCD_Rectangle(int fd, byte x1, byte y1, byte x2, byte y2, byte gif)
	{
		byte n; 	
		LCD_Set_Pos(fd, x1, y1 >> 3);
		for(n = x1; n <= x2; n++) {
			write_data(fd, 0x01 << (y1 % 8)); 			
			if(gif == 1)
	            usleep(50 * 1000); 	
		}  
		LCD_Set_Pos(fd, x1, y2 >> 3);
	    
	    for(n = x1; n <= x2; n++) {
			write_data(fd, 0x01 << (y2 % 8)); 			
			if(gif == 1)
	            usleep(50 * 1000); 	 	
		}
	} 
	//顯示的位置(x,y),y為頁範圍0~7
	void LCD_P6x8Str(int fd, byte x, byte y, byte ch[])
	{
	    byte c = 0, i = 0, j = 0;      
	    while (ch[j] != '\0') {    
	        c = ch[j] - 32;
	        if(x > 126) {
	            x = 0;
	            y++;
	        }
	        LCD_Set_Pos(fd, x, y);    
	  	    for(i = 0; i < 6; i++)     
	  	        write_data(fd, F6x8[c][i]);  
	  	    x += 6;
	  	    j++;
	    }
	}
	// 顯示的位置(x,y),y為頁範圍0~7
	void LCD_P8x16Str(int fd, byte x, byte y, byte ch[])
	{
	    byte c = 0, i = 0, j = 0;
	    while (ch[j] != '\0') {    
	        c = ch[j] - 32;
	        if( x > 120) {
	            x = 0;
	            y++;
	        }
	        LCD_Set_Pos(fd, x, y);    
	  	    for(i = 0; i < 8; i++)     
	  	        write_data(fd, F8X16[c * 16 + i]);
	    	LCD_Set_Pos(fd, x, y + 1);    
	  	    for(i = 0; i < 8; i++)     
	  	        write_data(fd, F8X16[c * 16 + i + 8]);  
	        x += 8;
	  	    j++;
	    }
	}
	int main(int argc, char* argv[])
	{
	    int fd;
	    char str1[] = "imx6ull spi";
	    char str2[] = "oled driver";
	    fd = open(PATH, O_RDWR);
	    if (fd < 0) {
	        printf("open %s error\n", PATH);
	        return ERROR;
	    }
	    oled_init(fd);
	    //LCD_Rectangle(fd, 10, 10, 120, 60, 1);
	    //LCD_P6x8Str(fd, 20, 0, str1);
	    //LCD_P6x8Str(fd, 20, 4, str2);
	    LCD_P8x16Str(fd, 20, 1, str1);
	    LCD_P8x16Str(fd, 20, 3, str2);
	    close(fd);
	    return OK;
	}

           

6.測試結果

Linux SPI裝置驅動

繼續閱讀