实现了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及数据传输函数transfertransfer_one_message等。transfertransfer_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_masterspi_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_driverspi_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_driverplatform_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_transferspi_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_messagevoid 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  0x10b1MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI  0x10b1        // SDAMX6UL_PAD_LCD_DATA20__ECSPI1_SCLK  0x10b1        // SCLMX6UL_PAD_LCD_DATA21__GPIO3_IO26   0x10b0        // D/CMX6UL_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: ssh1106@0 {compatible = "oled, ssh1106";rst-gpio = <&gpio3 12 GPIO_ACTIVE_LOW>;  // 低电平复位dc-gpio = <&gpio3 26 GPIO_ACTIVE_LOW>;   // 低电平表示命令,高电平表示数据spi-max-frequency = <4000000>;    // 最大频率4Mreg = <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);// 复位oledgpio_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 0ret = spi_write(dev->spi, buffer, size);if (ret != 0) return ret;#elsememset(&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_messagespi_message_add_tail(&t, &m);  // 将spi_transfer添加到spi_messageret = spi_sync(dev->spi, &m);  // 同步传输if (ret != 0) return -EFAULT;#endifreturn 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传输的信息是数据elsereturn -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};// 获取设备树中定义的复位、数据命令GPIOstatic 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 1static __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);#endifMODULE_LICENSE("GPL");MODULE_AUTHOR("liyang.plus@foxmail.com");

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     64typedef unsigned char byteconst 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,// 00x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//!10x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//"20x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//#30x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$40xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//%50x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//&60x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//'70x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//(80x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//)90x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//*100x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+110x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//,120x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//-130x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//.140x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,///150x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0160x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1170x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2180x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3190x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4200x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5210x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6220x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7230x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8240x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9250x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//:260x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//;270x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//<280x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//=290x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//>300x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//?310xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@320x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A330x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B340xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C350x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D360x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E370x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F380xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G390x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H400x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I410x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J420x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K430x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L440x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M450x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N460xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O470x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P480xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q490x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R500x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S510x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T520x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U530x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V540xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W550x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X560x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y570x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z580x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[590x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\600x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//]610x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^620x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_630x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//`640x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a650x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b660x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c670x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d680x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e690x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f700x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g710x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h720x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i730x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j740x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k750x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l760x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m770x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n780x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o790x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p800x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q810x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r820x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s830x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t840x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u850x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v860x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w870x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x880x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y890x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z900x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{910x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//|920x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//}930x00,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/SecSet_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 CurrentSet_Precharge_Period(fd, 0xF1);         // Set Pre-Charge as 15 Clocks & Discharge as 1 ClockSet_VCOMH           (fd, 0x40);       // Set VCOM Deselect LevelSet_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~63void 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~7void 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~7void 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设备驱动相关推荐

  1. linux spi屏驱动程序,65 linux spi设备驱动之spi LCD屏驱动

    SPI的控制器驱动由平台设备与平台驱动来实现. 驱动后用spi_master对象来描述.在设备驱动中就可以通过函数spi_write, spi_read, spi_w8r16, spi_w8r8等函数 ...

  2. LINUX SPI设备驱动模型分析之二 SPI总线模块分析

    上一篇文章我们简要介绍了SPI驱动模块,本章我们详细说明一下spi总线.设备.驱动模块的注册.注销以及这几个模块之间的关联. SPI总线的注册 spi模块也是基于LINUX设备-总线-驱动模型进行开发 ...

  3. linux probe函数调用,linux spi设备驱动中probe函数何时被调用

    这两天被设备文件快搞疯了,也怪自己学东西一知半解吧,弄了几天总算能把设备注册理清楚一点点了.就以spi子设备的注册为例总结一下,免得自己忘记. 首先以注册一个spidev的设备为例: static s ...

  4. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  5. Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5.18)

    SPI基础支持此处不再赘述,直接分析linux中的SPI驱动源码. 1.SPI设备驱动架构图 2.源码分析 本次分析基于kernel5.18,linux/drivers/spi/spidev.c 设备 ...

  6. 二十、SPI设备驱动及应用(一)

    先给出Linux SPI子系统的体系结构图: SPI子系统体系结构 下面开始分析SPI子系统. Linux中SPI子系统的初始化是从drivers/spi/spi.c文件中的spi_init函数开始的 ...

  7. linux字符设备驱动的 ioctl 幻数

    在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...

  8. imx6ul spi 设备驱动开发

    imx6ul spi 设备驱动开发 spi设备树格式 spi设备树配置 spi 驱动 设备树解析 spi设备驱动使用 spi通用设备驱动 spi测试工具 spi时序对比 spi api 接口 spi设 ...

  9. linux3.0字符设备驱动,linux字符设备驱动的 ioctl 幻数

    在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...

最新文章

  1. 软件测试:黑盒白盒与动态静态之间有必然联系吗
  2. linux /home recovering journal,linux报错:/dev/sdb2:recovering journal
  3. 向上取整的方法_瓷砖测量的方法有哪些?瓷砖尺寸一般是多少?
  4. 手把手教你部署一个最小化的 Kubernetes 集群
  5. 德国巴伐利亚山谷积雪遍地 汽车被大雪掩埋
  6. Virut样本取证特征
  7. android app应用签名生成工具,android应用签名详细步骤
  8. 为什么redhat6/centos6里看到的网卡是em*?
  9. JProfiler分析内存泄漏
  10. 2010年软考 考试日期安排
  11. jhipster使用简明教程
  12. 转载:肖知兴:管理到底是个什么鬼,以及怎么破
  13. 抖音网上如何赚钱变现,有哪些具体的方法
  14. java释放string_java – 释放stringbuilder内存的最快方法
  15. 屏蔽html查看源代码,禁止查看网页源代码方法
  16. 爬去东方财富网龙虎榜(wechat:15353378609)
  17. 今日芯声 | 微软 Xbox 老大:关闭游戏直播平台 Mixer,我没有遗憾
  18. 【python】基础七:编码问题
  19. 从零开始速通百度云网盘
  20. Java开发——JDK环境配置

热门文章

  1. 服务器kvm切换器维修,服务器数字KVM切换器
  2. 启动jupyter notebook 报错:ImportError:DLL load failed,找不到指定模块的解决办法
  3. 空气质量天气质量数据来源整理
  4. 机器学习导论——机器学习三要素
  5. 干货分享丨HDR 技术产品实践与探索
  6. Try tracing
  7. win10 windows许可证即将过期的解决办法
  8. oracle创建用户,授权connect,resource后无法建表
  9. 成都拓嘉启远:拼多多评论置顶该怎样去弄
  10. 2020Java后端开发面试题总结(春招+秋招+社招)