LinkIt Smart MT7688 >> C/C++

移植Framebuffer顯示驅動程式


dts/LINKIT7688.dts

gpio-slcd {
  compatible = "mt7688-fb";
};

P.S. compatible = "mt7688-fb"將會對應到驅動程式的MODULE_ALIAS("platform:mt7688-fb")

drivers/video/fbdev/Kconfig

config FB_MT7688
  tristate "MT7688 Smart LCD framebuffer support"
  depends on FB
  select FB_SYS_FILLRECT
  select FB_SYS_COPYAREA
  select FB_SYS_IMAGEBLIT
  help
    This is the frame buffer device driver for MT7688 chip.
    If you say Y here, please say N to 'MT7688 framebuffer support'.

drivers/video/fbdev/Makefile

obj-$(CONFIG_FB_MT7688) += mt7688fb.o

drivers/video/fbdev/mt7688fb.c

/*
 *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
 *  Copyright (C) 2010, Maarten ter Huurne <maarten@treewalker.org>
 *  Copyright (C) 2017, Steward Fu <steward.fu@gmail.com>
 *
 *    MT7688 Smart LCD framebuffer driver
 *
 *  This program is free software; you can redistribute   it and/or modify it
 *  under  the terms of   the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 *  You should have received a copy of the  GNU General Public License along
 *  with this program; if not, write  to the Free Software Foundation, Inc.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/console.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/kdev_t.h>
#include <asm/page.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/gpio.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>

#include <asm/mach-ralink/mt7688/gpio.h>
#include <asm/mach-ralink/mt7688/base.h>

#define gpio_reg_set(buf, pin) buf[pin >> 5]|= (1u << (pin % 32))
#define gpio_reg_clr(buf, pin) buf[(pin >> 5) + 3]|= (1u << (pin % 32))
#define reg_assign(val, buf, pin) \
  if(val){ \
    gpio_reg_set(buf, pin); \
  } \
  else{ \
    gpio_reg_clr(buf, pin); \
  }

struct mtfb {
  struct fb_info *fb;
  struct platform_device *pdev;

  volatile unsigned long *gpio_reg[14];

  struct fb_videomode mode;

  size_t vidmem_size;
  void *vidmem;
  dma_addr_t vidmem_phys;

  size_t blackline_size;
  void *blackline;
  dma_addr_t blackline_phys;

  unsigned is_enabled:1;
  struct mutex lock;

  struct delayed_work refresh_work;

  uint32_t pseudo_palette[16];
};

static struct fb_fix_screeninfo mtfb_fix = {
  .id        = "MT7688 SLCD FB",
  .type      = FB_TYPE_PACKED_PIXELS,
  .visual    = FB_VISUAL_TRUECOLOR,
  .xpanstep  = 0,
  .ypanstep  = 1,
  .ywrapstep = 0,
  .accel     = FB_ACCEL_NONE,
};

static void mtfb_gpio_dir(struct mtfb *mtfb, int pin, int is_output)
{
  if(is_output){
    *mtfb->gpio_reg[GPIO_CTRL_0_INDEX + (pin >> 5)]|= (1u << (pin % 32));
  }
  else{
    *mtfb->gpio_reg[GPIO_CTRL_0_INDEX + (pin >> 5)]&= ~(1u << (pin % 32));
  }
}

static void mtfb_gpio_set(struct mtfb *mtfb, int pin, int value)
{
  if(value){
    *mtfb->gpio_reg[GPIO_DSET_0_INDEX + (pin >> 5)] = (1u << (pin % 32));
  }
  else{
    *mtfb->gpio_reg[GPIO_DCLR_0_INDEX + (pin >> 5)] = (1u << (pin % 32));
  }
}

static unsigned int mtfb_gpio_get(struct mtfb *mtfb, int pin)
{
  return (*mtfb->gpio_reg[GPIO_DATA_0_INDEX + (pin >> 5)] >> (pin % 32)) & 1u;
}

static void mtfb_alloc_gpio(struct mtfb *mtfb)
{
  unsigned int i;
  const unsigned long reg_base[]={
    GPIO1_MODE_ADDR,
    GPIO2_MODE_ADDR,
    GPIO_CTRL_0_ADDR,
    GPIO_CTRL_1_ADDR,
    GPIO_CTRL_2_ADDR,
    GPIO_DATA_0_ADDR,
    GPIO_DATA_1_ADDR,
    GPIO_DATA_2_ADDR,
    GPIO_DSET_0_ADDR,
    GPIO_DSET_1_ADDR,
    GPIO_DSET_2_ADDR,
    GPIO_DCLR_0_ADDR,
    GPIO_DCLR_1_ADDR,
    GPIO_DCLR_2_ADDR,
  };

  for(i=0; i<14; i++){
    mtfb->gpio_reg[i] = (volatile unsigned long*)ioremap(reg_base[i], 4);
  }
  
  *mtfb->gpio_reg[GPIO1_MODE_INDEX]&= 0xffff0fff;
  *mtfb->gpio_reg[GPIO1_MODE_INDEX]|= 0x51550555;
  *mtfb->gpio_reg[GPIO2_MODE_INDEX] = 0x55555555;
  
  mtfb_gpio_dir(mtfb, 37, 1);
  mtfb_gpio_dir(mtfb, 44, 1);
  mtfb_gpio_dir(mtfb, 46, 1);
  mtfb_gpio_dir(mtfb, 45, 1);
  mtfb_gpio_dir(mtfb, 13, 1);
  mtfb_gpio_dir(mtfb, 12, 1);
  mtfb_gpio_dir(mtfb, 5, 1);
  mtfb_gpio_dir(mtfb, 4, 1);
  mtfb_gpio_dir(mtfb, 6, 1);
  mtfb_gpio_dir(mtfb, 18, 1);
  mtfb_gpio_dir(mtfb, 19, 1);
  mtfb_gpio_dir(mtfb, 17, 1);
  mtfb_gpio_dir(mtfb, 16, 1);
}

static void mtfb_free_gpio(struct mtfb *mtfb)
{
  unsigned int i;

  for(i=0; i<14; i++){
    iounmap(mtfb->gpio_reg[i]);
  }
}

static void ili9335_send_command(struct mtfb *mtfb, unsigned int val)
{
  unsigned char i, cmd=0;
  unsigned int reg[6]={0};

  mtfb_gpio_set(mtfb, ILI9335_SLCD_RS, 0);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_RD, 1);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_CS, 0);
  for(i=0; i<2; i++){
    reg[0] = 0;
    reg[3] = 0;
    cmd = (i == 0) ? (val >> 8) : val;
    reg_assign(cmd & 0x01, reg, ILI9335_SLCD_D10);
    reg_assign(cmd & 0x02, reg, ILI9335_SLCD_D11);
    reg_assign(cmd & 0x04, reg, ILI9335_SLCD_D12);
    reg_assign(cmd & 0x08, reg, ILI9335_SLCD_D13);
    reg_assign(cmd & 0x10, reg, ILI9335_SLCD_D14);
    reg_assign(cmd & 0x20, reg, ILI9335_SLCD_D15);
    reg_assign(cmd & 0x40, reg, ILI9335_SLCD_D16);
    reg_assign(cmd & 0x80, reg, ILI9335_SLCD_D17);
    *mtfb->gpio_reg[GPIO_DSET_0_INDEX] = reg[0];
    *mtfb->gpio_reg[GPIO_DCLR_0_INDEX] = reg[3];

    mtfb_gpio_set(mtfb, ILI9335_SLCD_WR, 0);
    mtfb_gpio_set(mtfb, ILI9335_SLCD_WR, 1);
  }
  mtfb_gpio_set(mtfb, ILI9335_SLCD_CS, 1);
}

static void ili9335_send_data(struct mtfb *mtfb, unsigned int val)
{
  unsigned char i, dat=0;
  unsigned long reg[6]={0};

  mtfb_gpio_set(mtfb, ILI9335_SLCD_RS, 1);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_RD, 1);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_CS, 0);
  for(i=0; i<2; i++){
    reg[0] = 0;
    reg[3] = 0;
    dat = (i == 0) ? (val >> 8) : val;
    reg_assign(dat & 0x01, reg, ILI9335_SLCD_D10);
    reg_assign(dat & 0x02, reg, ILI9335_SLCD_D11);
    reg_assign(dat & 0x04, reg, ILI9335_SLCD_D12);
    reg_assign(dat & 0x08, reg, ILI9335_SLCD_D13);
    reg_assign(dat & 0x10, reg, ILI9335_SLCD_D14);
    reg_assign(dat & 0x20, reg, ILI9335_SLCD_D15);
    reg_assign(dat & 0x40, reg, ILI9335_SLCD_D16);
    reg_assign(dat & 0x80, reg, ILI9335_SLCD_D17);
    *mtfb->gpio_reg[GPIO_DSET_0_INDEX] = reg[0];
    *mtfb->gpio_reg[GPIO_DCLR_0_INDEX] = reg[3];
    mtfb_gpio_set(mtfb, ILI9335_SLCD_WR, 0);
    mtfb_gpio_set(mtfb, ILI9335_SLCD_WR, 1);
  }
  mtfb_gpio_set(mtfb, ILI9335_SLCD_CS, 1);
}

static void ili9335_send_register(struct mtfb *mtfb, unsigned int cmd, unsigned int data)
{
  ili9335_send_command(mtfb, cmd);
  ili9335_send_data(mtfb, data);
}

static void ili9335_init(struct mtfb *mtfb)
{
  mtfb_gpio_set(mtfb, ILI9335_SLCD_CS, 1);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_RESET, 1);
  mdelay(100);
  mtfb_gpio_set(mtfb, ILI9335_SLCD_RESET, 0);
  mdelay(100);  
  mtfb_gpio_set(mtfb, ILI9335_SLCD_RESET, 1);
  mdelay(100);

  ili9335_send_register(mtfb, 0x0001, 0x0100);
  ili9335_send_register(mtfb, 0x0002, 0x0200);
  ili9335_send_register(mtfb, 0x0003, 0x1018);
  ili9335_send_register(mtfb, 0x0008, 0x0202);
  ili9335_send_register(mtfb, 0x0009, 0x0000);
  ili9335_send_register(mtfb, 0x000A, 0x0000);
  ili9335_send_register(mtfb, 0x000C, 0x0000);
  ili9335_send_register(mtfb, 0x000D, 0x0000);
  ili9335_send_register(mtfb, 0x0060, 0x2700);  
  ili9335_send_register(mtfb, 0x0061, 0x0000);
  ili9335_send_register(mtfb, 0x006A, 0x0000);
  mdelay(10);
  ili9335_send_register(mtfb, 0x0010, 0x1490);
  ili9335_send_register(mtfb, 0x0011, 0x0227);
  mdelay(80);
  ili9335_send_register(mtfb, 0x0012, 0x000c);
  mdelay(10);
  ili9335_send_register(mtfb, 0x0013, 0x1000);
  ili9335_send_register(mtfb, 0x0029, 0x000b);
  ili9335_send_register(mtfb, 0x002b, 0x000b);
  mdelay(10);
  ili9335_send_register(mtfb, 0x0020, 0x0000);
  ili9335_send_register(mtfb, 0x0021, 0x0000);
  
  ili9335_send_register(mtfb, 0x0030, 0x0000);
  ili9335_send_register(mtfb, 0x0031, 0x0406);
  ili9335_send_register(mtfb, 0x0032, 0x0002);
  ili9335_send_register(mtfb, 0x0035, 0x0402);
  ili9335_send_register(mtfb, 0x0036, 0x0004);
  ili9335_send_register(mtfb, 0x0037, 0x0507);
  ili9335_send_register(mtfb, 0x0038, 0x0103);
  ili9335_send_register(mtfb, 0x0039, 0x0707);
  ili9335_send_register(mtfb, 0x003c, 0x0204);
  ili9335_send_register(mtfb, 0x003d, 0x0004);
  
  ili9335_send_register(mtfb, 0x0050, 0x0000);
  ili9335_send_register(mtfb, 0x0051, 0x00ef);
  ili9335_send_register(mtfb, 0x0052, 0x0000);
  ili9335_send_register(mtfb, 0x0053, 0x013f);

  mdelay(10);
  ili9335_send_register(mtfb, 0x0007, 0x0133);  
  ili9335_send_command(mtfb, 0x22);
}

static int mtfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fb)
{
  if(regno >= 16){
    return -EINVAL;
  }

  red = (red * ((1 << fb->var.red.length) - 1)) / ((1 << 16) - 1);
  green = (green * ((1 << fb->var.green.length) - 1)) / ((1 << 16) - 1);
  blue = (blue * ((1 << fb->var.blue.length) - 1)) / ((1 << 16) - 1);
  ((uint32_t*)fb->pseudo_palette)[regno] = (red << fb->var.red.offset) | (green << fb->var.green.offset) | (blue  << fb->var.blue.offset);
  return 0;
}

static struct fb_videomode *mtfb_get_mode(struct mtfb *mtfb, struct fb_var_screeninfo *var)
{
  struct fb_videomode *mode = &mtfb->mode;

  if((mode->xres == var->xres) && (mode->yres == var->yres)){
    return mode;
  }
  return NULL;
}

static int mtfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
{
  struct mtfb *mtfb = fb->par;
  struct fb_videomode *mode;

  if(var->bits_per_pixel != 16){
    return -EINVAL;
  }

  mode = mtfb_get_mode(mtfb, var);
  if(mode == NULL){
    return -EINVAL;
  }

  fb_videomode_to_var(var, mode);

  var->yres_virtual = var->yres * 2;
  var->red.offset = 11;
  var->red.length = 5;
  var->green.offset = 5;
  var->green.length = 6;
  var->blue.offset = 0;
  var->blue.length = 5;
  return 0;
}

static void print_time(void)
{
  struct timeval t;
  struct tm broken;
  do_gettimeofday(&t);
  time_to_tm(t.tv_sec, 0, &broken);
  printk("%d:%d:%d:%ld\n", broken.tm_hour, broken.tm_min, broken.tm_sec, t.tv_usec);
}

static void mtfb_upload_frame_cpu(struct mtfb *mtfb)
{
  unsigned int i;
  const int num_pixels = mtfb->mode.xres * mtfb->mode.yres;
  uint16_t *p = mtfb->vidmem;
  
  *mtfb->gpio_reg[GPIO1_MODE_INDEX]&= 0xffff0fff;
  *mtfb->gpio_reg[GPIO1_MODE_INDEX]|= 0x51550555;
  *mtfb->gpio_reg[GPIO2_MODE_INDEX] = 0x55555555;
  
  mtfb_gpio_dir(mtfb, 37, 1);
  mtfb_gpio_dir(mtfb, 44, 1);
  mtfb_gpio_dir(mtfb, 46, 1);
  mtfb_gpio_dir(mtfb, 45, 1);
  mtfb_gpio_dir(mtfb, 13, 1);
  mtfb_gpio_dir(mtfb, 12, 1);
  mtfb_gpio_dir(mtfb, 5, 1);
  mtfb_gpio_dir(mtfb, 4, 1);
  mtfb_gpio_dir(mtfb, 6, 1);
  mtfb_gpio_dir(mtfb, 18, 1);
  mtfb_gpio_dir(mtfb, 19, 1);
  mtfb_gpio_dir(mtfb, 17, 1);
  mtfb_gpio_dir(mtfb, 16, 1);

  for(i=0; i<num_pixels; i++){
    ili9335_send_data(mtfb, *p++);
  }
}

static void mtfb_refresh_work(struct work_struct *work)
{
  struct mtfb *mtfb = container_of(work, struct mtfb, refresh_work.work);

  mutex_lock(&mtfb->lock);
  if(mtfb->is_enabled){
    mtfb_upload_frame_cpu(mtfb);
    schedule_delayed_work(&mtfb->refresh_work, HZ / 60);
  }
  mutex_unlock(&mtfb->lock);
}

static int mtfb_set_par(struct fb_info *info)
{
  struct mtfb *mtfb = info->par;
  struct fb_var_screeninfo *var = &info->var;
  struct fb_videomode *mode;

  mode = mtfb_get_mode(mtfb, var);
  if(mode == NULL){
    return -EINVAL;
  }
  info->mode = mode;
  return 0;
}

static void mtfb_enable(struct mtfb *mtfb)
{
  schedule_delayed_work(&mtfb->refresh_work, 0);
}

static void mtfb_disable(struct mtfb *mtfb)
{
  cancel_delayed_work(&mtfb->refresh_work);
}

static int mtfb_blank(int blank_mode, struct fb_info *info)
{
  struct mtfb *mtfb = info->par;
  int ret = 0;
  int new_enabled = (blank_mode == FB_BLANK_UNBLANK);

  mutex_lock(&mtfb->lock);
  if(new_enabled){
    if(!mtfb->is_enabled){
      mtfb_enable(mtfb);
    }
  }
  else{
    if(mtfb->is_enabled){
      mtfb_disable(mtfb);
    }
  }

  if(!ret){
    mtfb->is_enabled = new_enabled;
  }
  mutex_unlock(&mtfb->lock);

  return ret;
}

static int mtfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
  const unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
  const unsigned long size = vma->vm_end - vma->vm_start;

  if(offset + size > info->fix.smem_len){
    return -EINVAL;
  }

  if(remap_pfn_range(vma, vma->vm_start, (info->fix.smem_start + offset) >> PAGE_SHIFT, size, vma->vm_page_prot)){
    return -EAGAIN;
  }
  return 0;
}

static int mtfb_alloc_devmem(struct mtfb *mtfb)
{
  int max_linesize = 0, max_framesize = 0;
  int bytes_per_pixel;
  void *page;
  int i;

  if(max_linesize < mtfb->mode.xres){
    max_linesize = mtfb->mode.xres;
  }

  if(max_framesize < mtfb->mode.xres * mtfb->mode.yres){
    max_framesize = mtfb->mode.xres * mtfb->mode.yres;
  }
  printk("%s, xres: %d, yres: %d\n", __func__, mtfb->mode.xres, mtfb->mode.yres);

  bytes_per_pixel = 16 >> 3;
  max_linesize *= bytes_per_pixel;
  max_framesize *= bytes_per_pixel;

  mtfb->blackline_size = max_linesize;
  mtfb->blackline = dma_alloc_coherent(&mtfb->pdev->dev, mtfb->blackline_size, &mtfb->blackline_phys, GFP_KERNEL);
  if(!mtfb->blackline){
    printk("%s, failed  to allocate memory for blackline\n", __func__);
    return -ENOMEM;
  }

  memset(mtfb->blackline, 0, mtfb->blackline_size);

  mtfb->vidmem_size = PAGE_ALIGN(max_framesize * 2);
  mtfb->vidmem = dma_alloc_coherent(&mtfb->pdev->dev, mtfb->vidmem_size, &mtfb->vidmem_phys, GFP_KERNEL);
  if(!mtfb->vidmem){
    printk("%s, failed  to allocate memory for vidmem\n", __func__);
    goto err_free_blackline;
  }

  for(page = mtfb->vidmem; page < mtfb->vidmem + PAGE_ALIGN(mtfb->vidmem_size); page+= PAGE_SIZE){
    SetPageReserved(virt_to_page(page));
  }
  return 0;

err_free_blackline:
  dma_free_coherent(&mtfb->pdev->dev, mtfb->blackline_size, mtfb->blackline, mtfb->blackline_phys);
  return -ENOMEM;
}

static void mtfb_free_devmem(struct mtfb *mtfb)
{
  dma_free_coherent(&mtfb->pdev->dev, mtfb->vidmem_size, mtfb->vidmem, mtfb->vidmem_phys);
  dma_free_coherent(&mtfb->pdev->dev, mtfb->blackline_size, mtfb->blackline, mtfb->blackline_phys);
}

static struct fb_ops mtfb_ops = {
  .owner        = THIS_MODULE,
  .fb_check_var = mtfb_check_var,
  .fb_set_par   = mtfb_set_par,
  .fb_setcolreg = mtfb_setcolreg,
  .fb_fillrect  = sys_fillrect,
  .fb_copyarea  = sys_copyarea,
  .fb_imageblit = sys_imageblit,
};

static int mtfb_probe(struct platform_device *pdev)
{
  int ret;
  struct mtfb *mtfb;
  struct fb_info *fb;

  fb = framebuffer_alloc(sizeof(struct mtfb), &pdev->dev);
  if(!fb){
    dev_err(&pdev->dev, "Failed to allocate framebuffer device\n");
    return -ENOMEM;
  }

  fb->fbops = &mtfb_ops;
  fb->flags = FBINFO_DEFAULT;

  mtfb = fb->par;
  mtfb->pdev = pdev;

  mtfb_alloc_gpio(mtfb);
  ili9335_init(mtfb);
  platform_set_drvdata(pdev, mtfb);

  mtfb->mode.name = "320x240";
  mtfb->mode.xres = 320;
  mtfb->mode.yres = 240;
  mtfb->mode.vmode = FB_VMODE_NONINTERLACED;
  fb_videomode_to_modelist(&mtfb->mode, 1, &fb->modelist);
  fb->mode = &mtfb->mode;

  fb_videomode_to_var(&fb->var, fb->mode);
  fb->var.bits_per_pixel = 16;
  mtfb_check_var(&fb->var, fb);

  ret = mtfb_alloc_devmem(mtfb);
  if(ret){
    dev_err(&pdev->dev, "Failed to allocate video memory\n");
    goto err_iounmap;
  }

  fb->fix = mtfb_fix;
  fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8;
  fb->fix.smem_start = mtfb->vidmem_phys;
  fb->fix.smem_len =  fb->fix.line_length * fb->var.yres_virtual;
  fb->screen_base = mtfb->vidmem;
  fb->pseudo_palette = mtfb->pseudo_palette;
  fb_alloc_cmap(&fb->cmap, 256, 0);

  mutex_init(&mtfb->lock);
  mtfb->is_enabled = 1;
  mtfb_set_par(fb);
  
  ret = register_framebuffer(fb);
  if(ret){
    dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret);
    goto err_free_devmem;
  }

  mtfb->fb = fb;
  fb_prepare_logo(mtfb->fb, 0);
  fb_show_logo(mtfb->fb, 0);

  INIT_DELAYED_WORK(&mtfb->refresh_work, mtfb_refresh_work);
  schedule_delayed_work(&mtfb->refresh_work, 0);
  return 0;
  
  cancel_delayed_work_sync(&mtfb->refresh_work);
err_free_devmem:
  fb_dealloc_cmap(&fb->cmap);
  mtfb_free_devmem(mtfb);
err_iounmap:
  mtfb_free_gpio(mtfb);
  framebuffer_release(fb);
  return ret;
}

static int mtfb_remove(struct platform_device *pdev)
{
  struct mtfb *mtfb = platform_get_drvdata(pdev);

  mtfb_blank(FB_BLANK_POWERDOWN, mtfb->fb);
  cancel_delayed_work_sync(&mtfb->refresh_work);
  mtfb_free_gpio(mtfb);

  fb_dealloc_cmap(&mtfb->fb->cmap);
  mtfb_free_devmem(mtfb);

  platform_set_drvdata(pdev, NULL);

  framebuffer_release(mtfb->fb);
  return 0;
}

#ifdef CONFIG_PM

static int mtfb_suspend(struct device *dev)
{
  struct mtfb *mtfb = dev_get_drvdata(dev);

  console_lock();
  fb_set_suspend(mtfb->fb, 1);
  console_unlock();

  mutex_lock(&mtfb->lock);
  if(mtfb->is_enabled){
    mtfb_disable(mtfb);
  }
  mutex_unlock(&mtfb->lock);
  return 0;
}

static int mtfb_resume(struct device *dev)
{
  struct mtfb *mtfb = dev_get_drvdata(dev);
  clk_enable(mtfb->ldclk);

  mutex_lock(&mtfb->lock);
  if(mtfb->is_enabled){
    mtfb_enable(mtfb);
  }
  mutex_unlock(&mtfb->lock);

  console_lock();
  fb_set_suspend(mtfb->fb, 0);
  console_unlock();
  return 0;
}

static const struct dev_pm_ops mtfb_pm_ops = {
  .suspend  = mtfb_suspend,
  .resume   = mtfb_resume,
  .poweroff = mtfb_suspend,
  .restore  = mtfb_resume,
};

#define MTFB_PM_OPS (&mtfb_pm_ops)

#else
#define MTFB_PM_OPS NULL
#endif

static const struct of_device_id of_mt7688_fb_match[] = {
  { .compatible = "mt7688-fb", }, {},
};

static struct platform_driver mtfb_driver = {
  .probe  = mtfb_probe,
  .remove = mtfb_remove,
  .driver = {
    .name = "mt7688-fb",
    .pm   = MTFB_PM_OPS,
    .of_match_table = of_match_ptr(of_mt7688_fb_match),
  },
};

static int __init mtfb_init(void)
{
  return platform_driver_register(&mtfb_driver);
}
module_init(mtfb_init);

static void __exit mtfb_exit(void)
{
  platform_driver_unregister(&mtfb_driver);
}
module_exit(mtfb_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>, Maarten ter Huurne <maarten@treewalker.org>, Steward Fu <steward.fu@gmail.com>");
MODULE_DESCRIPTION("MT7688 SLCD framebuffer driver");
MODULE_ALIAS("platform:mt7688-fb");

$ make ARCH=mips menuconfig




完成


$ ls -al /dev/f*


P.S. 刷一個320x240的畫面,大約需要0.06秒

使用山寨邏輯分析儀測試LCD_WR腳位後,發現其實畫面更新應該是有超過60fps才對,LCD_WR腳位Low的時間大約是80ns(Channel-0是AM335x LCD Clock 33MHz,而Channl-1則是MT7688 580MHz)


計算一下:
320(寬) x 240(長) x 2(WR Clock) x 2(2 Bytes) = 307200
1 / 3027200 = 3.25us
因此只要WR Clock小於6.5us (High + Low), 基本上就可以達到60fps


返回上一頁