首页
搜索 搜索
聚焦

【世界播资讯】Linux SPI设备驱动:四线SPI OLED驱动实战

2023-06-16 11:44:41 justice与初中生小许

SPI从设备芯片的种类非常广泛,包括用于模拟传感器和编解码器的数字/模拟转换器、内存芯片、USB控制器或以太网适配器等外设,以及其他类型的芯片。

这样的驱动通常在linux看来是一个协议驱动,比如spi flash,负责和MTD系统打交道;比如触摸传感器,需要和input子系统打交道,再比如spi接口的OLED模块。

这样的设备使用的【接口】在驱动中使用struct spi_deivce表示


(相关资料图)

struct spi_device { struct device  dev; struct spi_controller *controller; struct spi_controller *master; /* compatibility layer */ u32   max_speed_hz; u8   chip_select; u8   bits_per_word; bool   rt;#define SPI_NO_TX BIT(31)  /* no transmit wire */#define SPI_NO_RX BIT(30)  /* no receive wire */ /*  * All bits defined above should be covered by SPI_MODE_KERNEL_MASK.  * The SPI_MODE_KERNEL_MASK has the SPI_MODE_USER_MASK counterpart,  * which is defined in "include/uapi/linux/spi/spi.h".  * The bits defined here are from bit 31 downwards, while in  * SPI_MODE_USER_MASK are from 0 upwards.  * These bits must not overlap. A static assert check should make sure of that.  * If adding extra bits, make sure to decrease the bit index below as well.  */#define SPI_MODE_KERNEL_MASK (~(BIT(30) - 1)) u32   mode; int   irq; void   *controller_state; void   *controller_data; char   modalias[SPI_NAME_SIZE]; const char  *driver_override; int   cs_gpio; /* LEGACY: chip select gpio */ struct gpio_desc *cs_gpiod; /* chip select gpio desc */ struct spi_delay word_delay; /* inter-word delay */ /* CS delays */ struct spi_delay cs_setup; struct spi_delay cs_hold; struct spi_delay cs_inactive; /* the statistics */ struct spi_statistics statistics; /*  * likely need more hooks for more protocol options affecting how  * the controller talks to each chip, like:  *  - memory packing (12 bit samples into low bits, otherszeroed)  *  - priority  *  - chipselect delays  *  - ...  */};

linux内核文档中是这样描述的

A "struct spi_device" encapsulates the controller-side interface between those two types of drivers.

因此,应该表示一个接口而不是一个驱动,当然你说这个接口连接的不就是设备吗?这么理解好像也没错。

SPI 设备驱动使用struct spi_driver表示,提供probe驱动入口,老套路了,比如

static int ssd13306_probe(struct spi_device *spi)

以上是函数原型,留下一个疑问,struct spi_device是作为一个对象传进来的,它是什么时候被构造的呢?

一、编写设备树

&ecspi2{ cs-gpios = < &gpio1 29 GPIO_ACTIVE_LOW >;//GPIO1_29 num-cs = < 1 >; pinctrl-names = "default"; pinctrl-0 = < &pinctrl_ecspi2 >; status = "okay"; oled: ssd13306@0{  compatible = "Justice,ssd13306";//自己写的oled驱动  //compatible = "spidev,spidev";//使用spidev通用驱动  //compatible = "solomon,ssd1306";//使用fbtftf的驱动,oled当成fb  spi-cpol;  spi-cpha;  spi-rx-bus-width = < 0 >;  spi-max-frequency = < 10000000 >;  reset-gpios = < &gpio1 27 GPIO_ACTIVE_LOW >;   dc-gpios = < &gpio1 31 GPIO_ACTIVE_HIGH >;  reg = < 0 >; };};

ecspi2是soc的SPI控制器,我们使用这个SPI控制器和从设备oled通信,因此要在这个设备树节点下写一个子节点表示OLED设备。下面是必填项:

cs-gpios是SPI控制器的属性,描述了从设备使用了哪些cs片选引脚,如果有3个从设备就需要写3个从cs引脚,而且顺序要和从设备设备树节点的reg属性对应。如上面gpio1 29表示片选0的从设备,reg属性表示设备片选号。reg表示此设备在这个SPI控制器中第几个片选,和cs-gpios顺序一致。compatible用于匹配驱动。pinctrl-0 = <&pinctrl_ecspi2>表示要引脚复用哪些信号
pinctrl_ecspi2:oled{//没有MISO因为ssd1306 oled不能读取   fsl,pins = <    MX6UL_PAD_UART4_RX_DATA__GPIO1_IO29 0x10b0  //cs引脚    MX6UL_PAD_UART4_TX_DATA__ECSPI2_SCLK 0x10b1    MX6UL_PAD_UART5_TX_DATA__ECSPI2_MOSI 0x10b1    MX6UL_PAD_UART5_RX_DATA__GPIO1_IO31 0x10b1 //DC引脚,分辨数据还是命令    MX6UL_PAD_UART3_RTS_B__GPIO1_IO27 0x10b1 //res复位引脚      >;  };

其余都不是必要属性,但是如果驱动异常,需要进一步调试是不是缺了某些属性。

下面列举了从设备设备树节点可选属性:

属性描述
spi-cphaspi->mode= SPI_CPHACPHA=1
spi-cpolspi->mode= SPI_CPOLCPOL=1
spi-3wirespi->mode= SPI_3WIRE使用三线SPI
spi-lsb-firstspi->mode= SPI_LSB_FIRST一般spi是MSB,指定后LSB
spi-cs-highspi->mode= SPI_CS_HIGH一般片选CS是低有效,指定后高有效
spi-tx-bus-widthspi->mode= SPI_NO_TX发送方向为0,可能只是读
= SPI_TX_DUALDUAL SPI 双线半双工
= SPI_TX_QUADQUAD SPI 四线半双工
= SPI_TX_OCTALOCTAL SPI 八线半双工
spi-rx-bus-widthspi->mode= SPI_NO_RX接受方向为0,可能只是发送
= SPI_RX_DUALDUAL SPI 双线半双工
= SPI_RX_QUADQUAD SPI 四线半双工
= SPI_RX_OCTALOCTAL SPI 八线半双工
regspi->chip_select表示spi设备在第几个片选
spi-max-frequencyspi->max_speed_hz这个spi设备使用的spi传输速率,单位Hz

说说为什么这么设置:

查看ssd13306手册,SPI接口的OLED使用的时钟周期最大是100ns,也就是频率为10M的SPI时钟,因此设置spi-max-frequency = <10000000>;

时钟极性是空闲时为高电平,因此spi-cpol = 1,填上spi-cpol;

数据在第二个边沿锁定,因此spi-cpal = 1,填上spi-cpal;

二、编写设备驱动

编写成一个字符设备驱动,提供接口供上层调用。本驱动会不断完善,加入各种新知识运用进来。说说要点:

创建设备节点三件套

需要注册字符设备,创建类,创建设备节点

oled_dev- >major = register_chrdev(0, "ssd13306", &ssd13306_fops);...oled_dev- >oled_class = class_create(THIS_MODULE, "ssd13306"); ...device_create(oled_dev- >oled_class,NULL, MKDEV(oled_dev- >major, 0), NULL, "ssd13306");

获取使用到的gpiod

gpiod是较新的gpio描述符,OLED使用到cs、dc、reset三个gpio。

其中dc引脚和SPI协议无关,只和OLED这个模块相关,用来区分发送的是命令还是显示数据。

cs引脚不需要我们自己管理,实际上架构已经为我们获取了。

oled_dev- >dc_gpio = gpiod_get(&spi- >dev, "dc", GPIOD_OUT_LOW); //初始化dc引脚dc_gpio_init(oled_dev- >dc_gpio);oled_dev- >reset_gpio = gpiod_get(&spi- >dev, "reset", GPIOD_OUT_HIGH);

SPI发送命令函数

需要发送命令初始化oled,使用spi_write这个SPI架构提供的API可以以同步的方式发送SPI数据,经过源码研究,其实无所谓同步异步了,现架构都是使用异步的,都使用工作者线程来完成spi的传输管理。

static void ssd13306_write_cmd(struct ssd13306_oled *ssd13306,unsigned char cmd){  int ret = 0; dc_gpio_set_value(g_oled- >dc_gpio,0);  ret = spi_write(ssd13306- >ssd13306, &cmd, 1); if(ret)  dev_err(&ssd13306- >ssd13306- >dev,"err spi write cmd(%d)",ret);}//spi 发送函数的原型spi_write(struct spi_device *spi, const void *buf, size_t len)

dc_gpio_set_value(g_oled->dc_gpio,0)

dc gpio拉低,表示接下来发送的都是命令。

硬件初始化,刷屏

硬件初始化只针对ssd13306,其他OLED模块另外寻找初始化序列。

static void ssd13306_hw_init(struct ssd13306_oled *ssd13306){ oled_reset(ssd13306); ssd13306_write_cmd(ssd13306,0xAE);//关闭oled显示 ssd13306_write_cmd(ssd13306,0xd5);//设置时钟分频因子 ssd13306_write_cmd(ssd13306,80);//[3:0]分频因子,[7:4]震荡频率 ssd13306_write_cmd(ssd13306,0xa8);//设置驱动路数 ssd13306_write_cmd(ssd13306,0x3f); ssd13306_write_cmd(ssd13306,0xd3);//设置显示偏移 ssd13306_write_cmd(ssd13306,0x00);//设置显示偏移 ssd13306_write_cmd(ssd13306,0x40);//设置显示开始行 ssd13306_write_cmd(ssd13306,0x8d);//设置电荷泵 ssd13306_write_cmd(ssd13306,0x14);//bit2开启或关闭 ssd13306_write_cmd(ssd13306,0x20);//设置寻址模式 ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2页地址模式 ssd13306_write_cmd(ssd13306,0xa1);//左右镜像 ssd13306_write_cmd(ssd13306,0xc8);//上下镜像 ssd13306_write_cmd(ssd13306,0xda);//设置com硬件引脚配置 ssd13306_write_cmd(ssd13306,0x12); ssd13306_write_cmd(ssd13306,0x81);//亮度设置 ssd13306_write_cmd(ssd13306,0xff); ssd13306_write_cmd(ssd13306,0xd9);//设置预充电周期 ssd13306_write_cmd(ssd13306,0xf1); ssd13306_write_cmd(ssd13306,0xdb);//设置电压倍率 ssd13306_write_cmd(ssd13306,0x30);// ssd13306_write_cmd(ssd13306,0xa4);//全局显示开启bit0 :0关闭,1开启 ssd13306_write_cmd(ssd13306,0xa6);//设置显示方式,bit0 :0正常模式,1反相模式 ssd13306_write_cmd(ssd13306,0xaf);//开启oled显示}static void oled_clear(unsigned char filldata){ int page; int col; unsigned char * data;  data = kmalloc(1, GFP_KERNEL); *data = filldata; for(page=0;page< 8;page++)    {           ssd13306_write_cmd (g_oled,0xb0+page);    //设置页地址(0~7)        ssd13306_write_cmd (g_oled,0x0);      //设置显示位置列低地址        ssd13306_write_cmd (g_oled,0x10);      //设置显示位置列高地址        for(col=0;col< 128;col++)ssd13306_write_datas(g_oled,data,1);    } kfree(data);}

其余剩下代码的完善,可以参考OLED裸机的代码添加。就仅仅学习linux SPI设备驱动来说,到这里应该就足够了。

三、完整代码

#include < linux/init.h >#include < linux/module.h >#include < linux/ioctl.h >#include < linux/fs.h >#include < linux/device.h >#include < linux/err.h >#include < linux/list.h >#include < linux/errno.h >#include < linux/mutex.h >#include < linux/slab.h >#include < linux/compat.h >#include < linux/of.h >#include < linux/of_device.h >#include < linux/spi/spi.h >#include < linux/spi/spidev.h >#include < linux/uaccess.h >#include < linux/gpio/consumer.h >#include < linux/gpio.h >#include < linux/delay.h >#define OLED_IOCTL_INIT 1#define OLED_IOCTL_S_POS 2struct ssd13306_oled { struct spi_device *ssd13306; struct class *oled_class; struct gpio_desc *dc_gpio; struct gpio_desc *reset_gpio; int major; int minor;};struct ssd13306_oled *g_oled;const unsigned char oled_asc2_8x16[95][16]={    {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};      unsigned char nao1[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x40,0x40,0x40,0xC0,0x40,0xC0,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0xE0,0x30,0x18,0x08,0x98,0xF0,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x7E,0xC1,0x86,0x86,0xCC,0x4C,0x20,0x1C,0x03,0x00,0x00,0x1C,0x2A,0x3E,0x2B,0x1D,0xE1,0x53,0xF3,0x52,0xA4,0x46,0x0B,0x18,0x1C,0x23,0xC1,0xA1,0x31,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x06,0x04,0xE8,0x38,0x18,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x00,0xC0,0x20,0x10,0x10,0x10,0x20,0xC1,0x06,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,0x3F,0x60,0xC0,0x83,0x04,0x08,0x18,0x10,0x10,0x10,0x18,0x08,0x04,0x00,0x00,0x01,0x02,0x04,0x04,0x04,0x82,0xC1,0x38,0x0F,0x00,0x00,0x00,0x00,0xC0,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x01,0x01,0x03,0x03,0x06,0x84,0xFC,0x1C,0x06,0x01,0x01,0x03,0x02,0x06,0x04,0x04,0x04,0x0C,0x0C,0x0C,0x0C,0x0C,0x04,0x04,0x06,0x02,0x03,0x03,0x0E,0x78,0xCC,0x06,0x86,0x43,0x43,0x81,0x03,0x03,0x80,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0xA0,0x20,0x20,0x20,0x20,0x40,0xC0,0x00,0x00,0xF8,0x1F,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xFE,0xE1,0x22,0x22,0x12,0x0B,0x0F,0xC0,0x60,0x10,0x10,0x10,0x90,0x60,0x00,0x00,0x00,0x80,0x40,0x41,0x42,0x42,0x42,0x42,0x43,0xC0,0x06,0x07,0x04,0x04,0x04,0xC4,0x44,0x44,0x74,0x14,0x1C,0x1C,0xF4,0x04,0x04,0x04,0x04,0x84,0x44,0x44,0x74,0x14,0x1C,0x1C,0xF4,0x04,0x04,0x04,0x04,0x07,0x00,0x02,0x60,0x60,0xA0,0x21,0x21,0x21,0x21,0x21,0x20,0xC0,0x00,0x00,0x07,0x08,0x08,0x08,0x08,0x08,0x04,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x00,0x00,0x00,0x00,0x03,0x04,0x04,0x04,0x04,0x04,0x04,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x04,0x04,0x04,0x03,0x00,};static void dc_gpio_init(struct gpio_desc *dc_gpio){ gpiod_direction_output(dc_gpio, 1);}static void oled_reset(struct ssd13306_oled *ssd13306){ gpiod_direction_output(ssd13306- >reset_gpio, 0); msleep(300); gpiod_direction_output(ssd13306- >reset_gpio, 1); msleep(300); gpiod_direction_output(ssd13306- >reset_gpio, 0);}static void dc_gpio_set_value(struct gpio_desc *dc_gpio,int value){ gpiod_set_value(dc_gpio,value);}static void ssd13306_write_cmd(struct ssd13306_oled *ssd13306,unsigned char cmd){  int ret = 0; dc_gpio_set_value(g_oled- >dc_gpio,0);  ret = spi_write(ssd13306- >ssd13306, &cmd, 1); if(ret)  dev_err(&ssd13306- >ssd13306- >dev,"err spi write cmd(%d)",ret);}static void ssd13306_write_datas(struct ssd13306_oled *ssd13306,unsigned char *data,int len){ int ret = 0;; dc_gpio_set_value(g_oled- >dc_gpio,1);  ret = spi_write(g_oled- >ssd13306, data, len); if(ret)  dev_err(&ssd13306- >ssd13306- >dev,"err spi write data(%d)",ret); }static void ssd13306_hw_init(struct ssd13306_oled *ssd13306){ oled_reset(ssd13306); ssd13306_write_cmd(ssd13306,0xAE);//关闭oled显示 ssd13306_write_cmd(ssd13306,0xd5);//设置时钟分频因子 ssd13306_write_cmd(ssd13306,80);//[3:0]分频因子,[7:4]震荡频率 ssd13306_write_cmd(ssd13306,0xa8);//设置驱动路数 ssd13306_write_cmd(ssd13306,0x3f); ssd13306_write_cmd(ssd13306,0xd3);//设置显示偏移 ssd13306_write_cmd(ssd13306,0x00);//设置显示偏移 ssd13306_write_cmd(ssd13306,0x40);//设置显示开始行 ssd13306_write_cmd(ssd13306,0x8d);//设置电荷泵 ssd13306_write_cmd(ssd13306,0x14);//bit2开启或关闭 ssd13306_write_cmd(ssd13306,0x20);//设置寻址模式 ssd13306_write_cmd(ssd13306,0x2);//0x0列地址模式;0x1行地址模式;0x2页地址模式 ssd13306_write_cmd(ssd13306,0xa1);//左右镜像 ssd13306_write_cmd(ssd13306,0xc8);//上下镜像 ssd13306_write_cmd(ssd13306,0xda);//设置com硬件引脚配置 ssd13306_write_cmd(ssd13306,0x12); ssd13306_write_cmd(ssd13306,0x81);//亮度设置 ssd13306_write_cmd(ssd13306,0xff); ssd13306_write_cmd(ssd13306,0xd9);//设置预充电周期 ssd13306_write_cmd(ssd13306,0xf1); ssd13306_write_cmd(ssd13306,0xdb);//设置电压倍率 ssd13306_write_cmd(ssd13306,0x30);// ssd13306_write_cmd(ssd13306,0xa4);//全局显示开启bit0 :0关闭,1开启 ssd13306_write_cmd(ssd13306,0xa6);//设置显示方式,bit0 :0正常模式,1反相模式 ssd13306_write_cmd(ssd13306,0xaf);//开启oled显示  }static void ssd13306_set_pos(struct ssd13306_oled *ssd13306,int x, int y){   ssd13306_write_cmd(ssd13306,0xb0+y);   ssd13306_write_cmd(ssd13306,(x&0x0f));   ssd13306_write_cmd(ssd13306,((x&0xf0) >>4)|0x10);}static long ssd13306_ioctl (struct file *filp, unsigned int cmd, unsigned long arg){ int x,y =0; switch (cmd){ case  OLED_IOCTL_INIT :{  dc_gpio_init(g_oled- >dc_gpio);  ssd13306_hw_init(g_oled);  break;   } case  OLED_IOCTL_S_POS :{  x = arg & 0xff;  y = (arg > > 8)&0xff;  ssd13306_set_pos(g_oled,x,y);  break;   } default :  break; } return 0;}static ssize_t ssd13306_write (struct file *filp, const char __user *ubuf, size_t count, loff_t * f_pos){ int done; char kbuf[count]; dc_gpio_set_value(g_oled- >dc_gpio, 1); copy_from_user(kbuf, ubuf, count); done = spi_write(g_oled- >ssd13306, kbuf, count); return done;}static void oled_clear(unsigned char filldata){ int page; int col; unsigned char * data;  data = kmalloc(1, GFP_KERNEL); *data = filldata;  for(page=0;page< 8;page++)    {          ssd13306_write_cmd (g_oled,0xb0+page);    //设置页地址(0~7)        ssd13306_write_cmd (g_oled,0x0);      //设置显示位置列低地址        ssd13306_write_cmd (g_oled,0x10);      //设置显示位置列高地址        for(col=0;col< 128;col++)ssd13306_write_datas(g_oled,data,1);    } kfree(data);}static void oled_Set_Pos(int x, int y){  ssd13306_write_cmd(g_oled,0xb0+y); ssd13306_write_cmd(g_oled,(x&0x0f));  ssd13306_write_cmd(g_oled,((x&0xf0) >>4)|0x10);}void oled_draw_char(int x, int y, unsigned char c){ int i = 0; /* 得到字模 */ const unsigned char *dots = oled_asc2_8x16[c - " "]; /* 发给OLED */ oled_Set_Pos(x, y); /* 发出8字节数据 */ //for (i = 0; i < 8; i++) // oled_write_cmd_data(dots[i], OLED_DATA); ssd13306_write_datas(g_oled,&dots[0], 8); oled_Set_Pos(x, y+1); /* 发出8字节数据 */ //for (i = 0; i < 8; i++)  //oled_write_cmd_data(dots[i+8], OLED_DATA); ssd13306_write_datas(g_oled,&dots[8], 8);}static void oled_drawstring(u8 x,u8 y,const char *p){     while((*p<="~")&&(*p >=" "))//判断是不是非法字符!    {               if(x < 0 || x > 128 || y < 0 ||y > 128)   break;        oled_draw_char(x,y,*p);          x+=16/2;        p++;    }   }void oled_drawbmp(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]){   unsigned int j=0; unsigned char x,y;    if(y1%8==0) y=y1/8;        else y=y1/8+1; for(y=y0;y< y1;y++) {  oled_Set_Pos(x0,y);    for(x=x0;x< x1;x++)     {            ssd13306_write_datas(g_oled,&BMP[j++],1);           } }} void oled_ver_scoll(int start_pg,int end_pg,int frame,int dir){ ssd13306_write_cmd(g_oled,0x2e); ssd13306_write_cmd(g_oled,dir ? 0x27:0x26); ssd13306_write_cmd(g_oled,0x0); ssd13306_write_cmd(g_oled,start_pg ); ssd13306_write_cmd(g_oled,frame ); ssd13306_write_cmd(g_oled,end_pg ); ssd13306_write_cmd(g_oled,0x0 ); ssd13306_write_cmd(g_oled,0xff );  ssd13306_write_cmd(g_oled, 0x2f);}static const struct file_operations ssd13306_fops = { .owner = THIS_MODULE, .write = ssd13306_write, //.read =  spidev_read, .unlocked_ioctl = ssd13306_ioctl, //.compat_ioctl = spidev_compat_ioctl, //.open =  spidev_open, //.release = spidev_release, //.llseek = no_llseek,};#ifdef CONFIG_OFstatic const struct of_device_id spi_oled_dt_ids[] = { { .compatible = "Justice,ssd13306" }, {},};MODULE_DEVICE_TABLE(of, spi_oled_dt_ids);#endifstatic int oledshow_thread(void *data){ static unsigned char  i = 0xff; while(1){    oled_clear(0);  msleep(100);  oled_drawbmp(0,0,54,8,nao1);  oled_drawbmp(60,0,114,8,nao1);  msleep(100); } return 0;}static int ssd13306_probe(struct spi_device *spi){ struct ssd13306_oled *oled_dev;   oled_dev = kmalloc(sizeof(*oled_dev), GFP_KERNEL); g_oled = oled_dev; oled_dev- >ssd13306 = spi; oled_dev- >major = register_chrdev(0, "ssd13306", &ssd13306_fops); if (oled_dev- >major < 0)  return -EINVAL; oled_dev- >oled_class = class_create(THIS_MODULE, "ssd13306"); if (IS_ERR(oled_dev- >oled_class)) {  unregister_chrdev(oled_dev- >major, "ssd13306");  return PTR_ERR(oled_dev- >oled_class); } device_create(oled_dev- >oled_class,    NULL, MKDEV(oled_dev- >major, 0), NULL, "ssd13306"); oled_dev- >dc_gpio = gpiod_get(&spi- >dev, "dc", GPIOD_OUT_LOW); //初始化dc引脚 dc_gpio_init(oled_dev- >dc_gpio); oled_dev- >reset_gpio = gpiod_get(&spi- >dev, "reset", GPIOD_OUT_HIGH); ssd13306_hw_init(oled_dev); oled_clear(0x0);  //oled_draw_Char(0, 30, "L"); //oled_drawbmp(0,0,54,8,nao1);//画一个小猪佩奇  oled_drawstring(0,0,"hello justice"); //oled_ver_scoll(0,7,256,0); //kthread_run(oledshow_thread,NULL,"oledthread"); printk("ssd13306 probe done!\\n"); spi_set_drvdata(spi,oled_dev); return 0;}static int ssd13306_remove(struct spi_device *spi){  struct ssd13306_oled *oled_dev = spi_get_drvdata(spi); //kthread_del(oledshow_thread,NULL,"oledthread"); gpiod_put(oled_dev- >dc_gpio); gpiod_put(oled_dev- >reset_gpio); device_destroy(oled_dev- >oled_class, MKDEV(oled_dev- >major, 0));  class_destroy(oled_dev- >oled_class); unregister_chrdev(oled_dev- >major, "ssd13306"); oled_clear(0x0); return 0;}static struct spi_driver oled_spi_driver = { .driver = {  .name =  "ssd13306_drv",  .of_match_table = of_match_ptr(spi_oled_dt_ids),   }, .probe = ssd13306_probe, .remove = ssd13306_remove,};static int __init spidev_init(void){ int status;  status = spi_register_driver(&oled_spi_driver); if (status < 0) {  pr_err("spidev_init\\n");  return status; } return status;}module_init(spidev_init);static void __exit spidev_exit(void){ spi_unregister_driver(&oled_spi_driver); }module_exit(spidev_exit);MODULE_LICENSE("GPL");