天天看点

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设备驱动

继续阅读