spi接口的oled显示驱动

原理图

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

    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>; 
        };   
    };

驱动流程

驱动与设备匹配

//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参数

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

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

/*--------------------------------   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

/** 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

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

/* 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

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接口直接映射。避免的数据从用户空间到内核的拷贝。

剑气纵横三万里

“为什么要努力?” “想去的地方很远,想要的东西很贵,喜欢的人很优秀,父母的白发,朋友的约定,周围人的嘲笑,以及,天生傲骨。”

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐

暂无内容!