spi接口的oled显示驱动

硬件连接图

sh1106原理图

GPIO配置表

GPIO FUNCTION
GPIO_0 SPI_MOSI
GPIO_1 SPI_MISO
GPIO_2 SPI_CS_N
GPIO_3 SPI_CLK
GPIO_71 OLED_A0
GPIO_93 OLED_PWR_CTL

设备树配置

代码位置: kernel/arch/arm/boot/dts/qcom/mdm9640.dtsi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 spi_0: spi@78b5000 { /* BLSP1 QUP1 */
compatible = "qcom,spi-qup-v2";
#address-cells = <1>;
#size-cells = <0>;
reg-names = "spi_physical", "spi_bam_physical";
reg = <0x78b5000 0x600>,
<0x7884000 0x23000>;
interrupt-names = "spi_irq", "spi_bam_irq";
interrupts = <0 95 0>, <0 238 0>;
spi-max-frequency = <19200000>;
pinctrl-names = "spi_default", "spi_sleep";
pinctrl-0 = <&spi0_default &spi0_cs0_active>;
pinctrl-1 = <&spi0_sleep &spi0_cs0_sleep>;
clocks = <&clock_gcc clk_gcc_blsp1_ahb_clk>,
<&clock_gcc clk_gcc_blsp1_qup1_spi_apps_clk>;
clock-names = "iface_clk", "core_clk";
qcom,infinite-mode = <0>;
qcom,use-pinctrl;
qcom,ver-reg-exists;
qcom,master-id = <86>;
status = "ok";
qcom-spi-lcd@0 {
compatible = "qcom,spi-oled";
reg = <0>;
spi-max-frequency = <10000000>;
pinctrl-names = "active", "sleep";
pinctrl-0 = <&oled_rst_active>;
pinctrl-1 = <&oled_rst_sleep>;
interrupt-parent = <&tlmm_pinmux>;
interrupts = <0 0>;
qcom_spi_oled,irq-gpio = <&tlmm_pinmux 94 0x00>;
};
};

驱动流程

驱动与设备匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//SPI Driver Info                                                                                   
static struct spi_driver spi_oled_driver = {
.driver = {
.name = "qcom,spi-oled",
.owner = THIS_MODULE,
.of_match_table = qcom_spi_oled_table,
},
.probe = spi_oled_probe,
};
static int __init spi_oled_init(void)
{
return spi_register_driver(&spi_oled_driver);
}
static void __exit spi_oled_exit(void)
{
spi_unregister_driver(&spi_oled_driver);
}

probe函数

probe函数中主要功能就是解析设备树,cs、irq、cpha、cpol等状态,以及对oled的硬件初始化、

注册framebuffer结构体,设置fb_info的var和fix参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
static int spi_oled_probe(struct spi_device *spi)
{
int irq_gpio = -1;
int irq;
int cs;
int cpha,cpol,cs_high;
unsigned int max_speed;
int i,ret;
struct page *buffer_page;
int page_order;
struct fb_info * fbinfo;

/*----------------- resource init ---------------------------*/
printk("start spi_oled_probe!\r\n");

oled.dev = &spi->dev;
page_order = ffz(~(OLED_BUFFER_SIZE/PAGE_SIZE)) + 1;
fb_buf = (uint8_t *)__get_free_pages(GFP_KERNEL, page_order);
for( i = 0;i < (OLED_BUFFER_SIZE/PAGE_SIZE) ; i += PAGE_SIZE)
{
buffer_page = virt_to_page(fb_buf+i);
SetPageReserved(buffer_page);
}
/** spi work mode **/
if(spi->dev.of_node){
irq_gpio = of_get_named_gpio_flags(spi->dev.of_node,
"qcom_spi_oled,irq-gpio", 0, NULL);
}
irq = spi->irq;
cs = spi->chip_select;
cpha = ( spi->mode & SPI_CPHA ) ? 1:0;
cpol = ( spi->mode & SPI_CPOL ) ? 1:0;
cs_high = ( spi->mode & SPI_CS_HIGH ) ? 1:0;
max_speed = spi->max_speed_hz;
printk("gpio [%d] irq [%d] gpio_irq [%d] cs [%x] CPHA[%x] CPOL [%x] CS_HIGH [%x]\n",
irq_gpio, irq, gpio_to_irq(irq_gpio), cs, cpha, cpol,cs_high);
printk("Max_speed [%d]\n", max_speed );
//Transfer can be done after spi_device structure is created
spi->bits_per_word = 8;

/** pin control register remap **/
oled.virt_base = devm_ioremap_nocache(&spi->dev,TLMM_BASE_ADDR,100 * 0x1000);
if( oled.virt_base == NULL)
{
printk("%s ioremap error !\n",__func__);
return -ENOMEM;
}
init_rwsem(&oled.rg.lock);
/*----------------------- pin config -----------------------------*/
oled.gpio_reset = GPIO_OLED_RESET;
oled.gpio_switch = GPIO_OLED_CMD_DATA;
oled_reset_pin_config();
oled_switch_pin_config();
/*---------------------- lcd chip init ---------------------------*/
sh1106g_hw_reset();
sh1106g_disp_init();
/*--------------------- data transfer test -----------------------*/
sh1106g_disp_pic(PIC01);
/*--------------------- framebuffer register ----------------------*/

fbinfo = kzalloc(sizeof(struct fb_info),GFP_KERNEL);
if( fbinfo == NULL)
{
printk("%s fbinfo alloc error !\n",__func__);
return -ENOMEM;
}
fbinfo->fbops = &oled_fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->fix.visual = FB_VISUAL_MONO10;
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.capabilities = 0;
fbinfo->fix.smem_start = virt_to_phys(fb_buf);
fbinfo->fix.smem_len = sizeof(fb_buf);
fbinfo->fix.accel = FB_ACCEL_NONE;

fbinfo->var.width = 128;
fbinfo->var.height = 64;
fbinfo->var.xres = 128;
fbinfo->var.yres = 64;
fbinfo->var.xres_virtual= 128;
fbinfo->var.yres_virtual= 64;
//fbinfo->var.left_margin = 20;
fbinfo->var.hsync_len = 128;
fbinfo->var.vsync_len = 64;
fbinfo->var.grayscale = 1;
fbinfo->var.bits_per_pixel = 8;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.pixclock = 500000;
//fbinfo->var.rotate = FB_ROTATE_CW;
fbinfo->fix.mmio_start = virt_to_phys(fb_buf);
fbinfo->fix.mmio_len = sizeof(fb_buf);
fbinfo->var.red.offset = 0;
fbinfo->var.red.length = fbinfo->var.bits_per_pixel;
fbinfo->var.green = fbinfo->var.red;
fbinfo->var.blue = fbinfo->var.red;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
fbinfo->screen_base = fb_buf;
fbinfo->screen_size = sizeof(fb_buf);

ret = register_framebuffer(fbinfo);
if( ret )
{
printk("oled register framebuffer !\r\n");
}

//dev_err(&spi->dev, "SPI sync returned [%d]\n",spi_oled_transfer(spi));
/*--------------------- lcd refresh thread ---------------------*/
for( i = 0; i < 8192 ;i ++ )
{
fb_buf[i] = PIC01[i%128+(i/1024*128)]&(1<<(7-((i/128)&7)));
}
sh1106g_disp_refresh();
oled.disp_status = OLED_DISP_ON;;
kthread_run(oled_refresh,NULL,"oled");
printk("oled write ok!\r\n");
return 0;
}

oled_reset_pin_config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int oled_reset_pin_config(void)
{
int result;
/** active state **/
oled.pinctrl = devm_pinctrl_get(oled.dev);
if (IS_ERR_OR_NULL(oled.pinctrl)) {
dev_err(oled.dev, "Failed to get pin ctrl\n");
return PTR_ERR(oled.pinctrl);
}
oled.pins_active = pinctrl_lookup_state(oled.pinctrl,"active");
if (IS_ERR_OR_NULL(oled.pins_active)) {
dev_err(oled.dev, "Failed to lookup pinctrl active state\n");
return PTR_ERR(oled.pins_active);
}
oled.pins_sleep = pinctrl_lookup_state(oled.pinctrl,"sleep");
if (IS_ERR_OR_NULL(oled.pins_sleep)) {
dev_err(oled.dev, "Failed to lookup pinctrl sleep state\n");
return PTR_ERR(oled.pins_sleep);
}
result = pinctrl_select_state(oled.pinctrl, oled.pins_active);
if (result) {
dev_err(oled.dev, "%s: Can not set %s pins\n",
__func__, "active");
return -1;
}
/** output high,no pull,12mA **/
gpio_tlmm_config(oled.gpio_reset,0,GPIO_OUTPUT,GPIO_NO_PULL,GPIO_8MA);
return 0;
}

这是利用pinctrl子系统来进行状态选择的,

sh1106g_hw_reset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*--------------------------------   lcd operation -------------------------------*/
static void sh1106g_hw_reset(void)
{
/** drive high **/
oled_reset_pin_high();
/** delay **/
udelay(100);
/** drive low **/
oled_reset_pin_low();
/** delay **/
udelay(100);
/** drive high **/
oled_reset_pin_high();
}

这是一个硬件复位,先把reset脚拉低再拉高

sh1106g_disp_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** init oled output mode **/  
static void sh1106g_disp_init(void)
{
sh1106g_write_cmd(0xAE); //Set Display Off
sh1106g_write_cmd(0xD5); //display divide ratio/osc. freq. mode
sh1106g_write_cmd(0x80);
sh1106g_write_cmd(0xA8); //multiplex ration mode:63
sh1106g_write_cmd(0x3F);
sh1106g_write_cmd(0xD3); //Set Display Offset
sh1106g_write_cmd(0x00);
sh1106g_write_cmd(0x40); //Set Display Start Line
sh1106g_write_cmd(0xAD); //DC-DC Control Mode Set
sh1106g_write_cmd(0x8B); //DC-DC ON/OFF Mode Set 0x8A: external dc-dc 0x8b: internal dc-dc
sh1106g_write_cmd(0x32);//Set Pump voltage value
sh1106g_write_cmd(0xA1);//Segment Remap
sh1106g_write_cmd(0xC8); //Sst COM Output Scan Direction
sh1106g_write_cmd(0xDA); //common pads hardware: alternative
sh1106g_write_cmd(0x12);
sh1106g_write_cmd(0x81);//contrast control
sh1106g_write_cmd(CONTRAST);
sh1106g_write_cmd(0xD9);//set pre-charge period
sh1106g_write_cmd(0x1F);
sh1106g_write_cmd(0xDB);//VCOM deselect level mode
sh1106g_write_cmd(0x40);
sh1106g_write_cmd(0xA4);//Set Entire Display On/Off
sh1106g_write_cmd(0xA6);//Set Normal Display
sh1106g_write_cmd(0x8D);//Set Charge Pump 0x8D, 0x14
sh1106g_write_cmd(0x14);//
sh1106g_write_cmd(0xAF);//Set Display On
}

fb_ops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static struct fb_ops oled_fb_ops =
{
.owner = THIS_MODULE,
.fb_open = oled_fb_open,
.fb_release = oled_fb_release,
.fb_read = oled_fb_read,
.fb_write = oled_fb_write,
.fb_mmap = oled_fb_mmap,
.fb_set_par = oled_set_par,
.fb_blank = oled_blank,
.fb_setcolreg = oled_setcolreg,
.fb_fillrect = oled_fillrect,
.fb_copyarea = oled_copyarea,
.fb_check_var = oled_check_var,
.fb_imageblit = oled_imageblit,
.fb_ioctl = oled_fb_ioctl,
};

fb_ops 是对framebuffer的操作接口函数

oled_fb_mmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* perform fb specific mmap */
static int oled_fb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
{
struct fb_fix_screeninfo *fix = &fbi->fix;
struct oled_mem_region *rg = &oled.rg;
unsigned long start;
u32 len;
int r;

oled_get_mem_region(rg);
start = fix->smem_start;
len = fix->smem_len;
if( vma->vm_end - vma->vm_start > OLED_BUFFER_SIZE)
{
r = -EINVAL;
goto error;
}
r = remap_pfn_range(vma,vma->vm_start,
virt_to_phys((void*)((unsigned long)fb_buf)) >> PAGE_SHIFT,
vma->vm_end-vma->vm_start,vma->vm_flags | PAGE_SHARED);
if(r != 0)
goto error;
/* vm_ops.open won't be called for mmap itself. */
atomic_inc(&rg->map_count);
oled_put_mem_region(rg);
return 0;
error:
oled_put_mem_region(rg);
return r;
}

oled_fb_ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int oled_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case FBIO_IOCTL_ON:
oled.disp_status = OLED_DISP_ON;
break;
case FBIO_IOCTL_OFF:
oled.disp_status = 0;
break;
default:
ret = -ENOTTY;
}
return ret;
}

总结

提供了ioctl接口来点亮以及关闭oled,上层可以调用响应的ioctl函数。图片的显示利用framebuffer的mmap接口直接映射。避免的数据从用户空间到内核的拷贝。