AM3354 >> C/C++

2.4" IPS 320x240(ST7789V)


arch/arm/boot/dts/am335x-evm/dts

&am33xx_pinmux {
  pinctrl-names = "default";
  pinctrl-0 = <&gpio_keys_s0>;

  ...
  lcd_pins_default: lcd_pins_default {
    pinctrl-single,pins = >
      0xa0 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data0.lcd_data0 */
      0xa4 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data1.lcd_data1 */
      0xa8 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data2.lcd_data2 */
      0xac (PIN_OUTPUT | MUX_MODE0)    /* lcd_data3.lcd_data3 */
      0xb0 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data4.lcd_data4 */
      0xb4 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data5.lcd_data5 */
      0xb8 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data6.lcd_data6 */
      0xbc (PIN_OUTPUT | MUX_MODE0)    /* lcd_data7.lcd_data7 */
      0xc0 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data8.lcd_data8 */
      0xc4 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data9.lcd_data9 */
      0xc8 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data10.lcd_data10 */
      0xcc (PIN_OUTPUT | MUX_MODE0)    /* lcd_data11.lcd_data11 */
      0xd0 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data12.lcd_data12 */
      0xd4 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data13.lcd_data13 */
      0xd8 (PIN_OUTPUT | MUX_MODE0)    /* lcd_data14.lcd_data14 */
      0xdc (PIN_OUTPUT | MUX_MODE0)    /* lcd_data15.lcd_data15 */
      0xe0 (PIN_OUTPUT | MUX_MODE0)    /* lcd_vsync.lcd_vsync */
      0xe4 (PIN_OUTPUT | MUX_MODE0)    /* lcd_hsync.lcd_hsync */
      0xe8 (PIN_OUTPUT | MUX_MODE0)    /* lcd_pclk.lcd_pclk */
      0xec (PIN_OUTPUT | MUX_MODE0)    /* lcd_ac_bias_en.lcd_ac_bias_en */
    >;
  };
  ...

&lcdc {
  status = "okay";
  compatible = "ti,am335x-lcdc";
  pinctrl-names = "default", "sleep";
  pinctrl-0 = <&lcd_pins_default>;
  pinctrl-1 = <&lcd_pins_sleep>;
};

drivers/video/fbdev/Kconfig

config FB_ST7789V
  tristate "ST7789V Framebuffer support"
  depends on FB && SOC_AM33XX
  select FB_CFB_FILLRECT
  select FB_CFB_COPYAREA
  select FB_CFB_IMAGEBLIT
  select FB_CFB_REV_PIXELS_IN_BYTE
  ---help---
    This is the frame buffer device driver for the TI LCD controller
    found on AM335x SoCs.
    If unsure, say N.

drivers/video/fbdev/Makefile

obj-$(CONFIG_FB_ST7789V) += st7789v-fb.o

drivers/video/fbdev/st7789v-fb.c

/*
 * Copyright (C) 2008-2009 MontaVista Software Inc.
 * Copyright (C) 2008-2009 Texas Instruments Inc
 * Copyright (C) 2017 Steward Fu <steward.fu@gmail.com>
 *
 * AM335x framebuffer driver for ST7789V SLCD panel
 *
 * Based on the LCD driver for TI Avalanche processors written by
 * Ajay Singh and Shalom Hai.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/console.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/lcm.h>
#include <linux/clk-provider.h>
#include <video/of_display_timing.h>
#include <linux/gpio.h>
#include <linux/omapfb.h>
#include <asm/div64.h>

#define DRIVER_NAME "st7789v_lcdc"

#define do_request(pin, name) \
  if(gpio_request(pin, name) < 0){ \
    printk("failed to request gpio: %s\n", name); \
  } \
  else{ \
    printk("request successfully for gpio: %s\n", name); \
    gpio_direction_output(pin, 1); \
  }

#define PALETTE_SIZE          256

#define ST7789V_SLCD_RST    ((32 * 0) + 19)
#define ST7789V_SLCD_SCS    ((32 * 0) +  0)
#define ST7789V_SLCD_SCK    ((32 * 0) +  1)
#define ST7789V_SLCD_SDI    ((32 * 2) + 18)
#define ST7789V_SLCD_SDO    ((32 * 3) + 10)

#define ST7789V_SLCD_D0     ((32 * 2) +  6)
#define ST7789V_SLCD_D1     ((32 * 2) +  7)
#define ST7789V_SLCD_D2     ((32 * 2) +  8)
#define ST7789V_SLCD_D3     ((32 * 2) +  9)
#define ST7789V_SLCD_D4     ((32 * 2) + 10)
#define ST7789V_SLCD_D5     ((32 * 2) + 11)
#define ST7789V_SLCD_D6     ((32 * 2) + 12)
#define ST7789V_SLCD_D7     ((32 * 2) + 13)
#define ST7789V_SLCD_D8     ((32 * 2) + 14)
#define ST7789V_SLCD_D9     ((32 * 2) + 15)
#define ST7789V_SLCD_D10    ((32 * 2) + 16)
#define ST7789V_SLCD_D11    ((32 * 2) + 17)
#define ST7789V_SLCD_D12    ((32 * 0) +  8)
#define ST7789V_SLCD_D13    ((32 * 0) +  9)
#define ST7789V_SLCD_D14    ((32 * 0) + 10)
#define ST7789V_SLCD_D15    ((32 * 0) + 11)
#define ST7789V_SLCD_HSYNC  ((32 * 2) + 23)
#define ST7789V_SLCD_VSYNC  ((32 * 2) + 22)
#define ST7789V_SLCD_PCLK   ((32 * 2) + 24)
#define ST7789V_SLCD_EN     ((32 * 2) + 25)

#define PID                   0x0
#define CTRL                  0x4
#define LIDD_CTRL             0xC
#define LIDD_CS0_CONF         0x10
#define LIDD_CS0_ADDR         0x14
#define LIDD_CS0_DATA         0x18
#define LIDD_CS1_CONF         0x1C
#define LIDD_CS1_ADDR         0x20
#define LIDD_CS1_DATA         0x24
#define RASTER_CTRL           0x28
#define RASTER_TIMING_0       0x2C
#define RASTER_TIMING_1       0x30
#define RASTER_TIMING_2       0x34
#define RASTER_SUBPANEL       0x38
#define RASTER_SUBPANEL2      0x3C
#define LCDDMA_CTRL           0x40
#define LCDDMA_FB0_BASE       0x44
#define LCDDMA_FB0_CEILING    0x48
#define LCDDMA_FB1_BASE       0x4C
#define LCDDMA_FB1_CEILING    0x50
#define SYSCONFIG             0x54
#define IRQSTATUS_RAW         0x58
#define IRQSTATUS             0x5C
#define IRQENABLE_SET         0x60
#define IRQENABLE_CLEAR       0x64
#define CLKC_ENABLE           0x6C
#define CLKC_RESET            0x70

/* LCD Timing_0 Register */
#define LCD_HBPLSB(x)            ((((x)-1) & 0xFF) << 24)
#define LCD_HFPLSB(x)            ((((x)-1) & 0xFF) << 16)
#define LCD_HSWLSB(x)            ((((x)-1) & 0x3F) << 10)
#define LCD_HORLSB(x)            (((((x) >> 4)-1) & 0x3F) << 4)
#define LCD_HORMSB(x)            (((((x) >> 4)-1) & 0x40) >> 4)
/* LCD Timing_1 Register */
#define LCD_VBP(x)              ((x) << 24)
#define LCD_VFP(x)              ((x) << 16)
#define LCD_VSW(x)              (((x)-1) << 10)
#define LCD_VERLSB(x)            (((x)-1) & 0x3FF)
/* LCD Timing_2 Register */
#define LCD_HSWMSB(x)            ((((x)-1) & 0x3C0) << 21)
#define LCD_VERMSB(x)            ((((x)-1) & 0x400) << 16)
#define LCD_HBPMSB(x)            ((((x)-1) & 0x300) >> 4)
#define LCD_HFPMSB(x)            ((((x)-1) & 0x300) >> 8)
#define LCD_INVMASK(x)          ((x) & 0x3F00000)
/* LCD Raster Ctrl Register */
#define LCD_PALMODE_RAWDATA      0x200000
#define LCD_TFT_MODE            0x000080
#define LCD_RASTER_ENABLE        0x000001

struct st7789v_fb_par {
  struct device *dev;

  resource_size_t p_palette_base;
  unsigned short *v_palette_base;

  dma_addr_t vram_phys;
  unsigned long vram_size;
  void *vram_virt;

  dma_addr_t tram_phys;
  void *tram_virt;

  dma_addr_t lram_phys[2];
  unsigned long lram_size;
  void *lram_virt[2];

  int irq;
  u32 pseudo_palette[16];
  struct fb_videomode mode;
  unsigned int bpp;
};

static struct resource *lcdc_regs;
static void __iomem *st7789v_fb_reg_base;
static struct fb_var_screeninfo st7789v_fb_var;
static struct fb_fix_screeninfo st7789v_fb_fix = {
  .id = "AM335x FB",
  .type = FB_TYPE_PACKED_PIXELS,
  .type_aux = 0,
  .visual = FB_VISUAL_TRUECOLOR,
  .xpanstep = 0,
  .ypanstep = 1,
  .ywrapstep = 0,
  .accel = FB_ACCEL_NONE
};

static void st7789v_send_spi(uint8_t type, uint8_t data)
{
  int bit;

  gpio_set_value(ST7789V_SLCD_SCS, 0);
  gpio_set_value(ST7789V_SLCD_SCK, 0); 
  gpio_set_value(ST7789V_SLCD_SDI, type); 
  udelay(10);
  gpio_set_value(ST7789V_SLCD_SCK, 1); 
  udelay(10);
  for(bit=7; bit>=0; bit--){
    gpio_set_value(ST7789V_SLCD_SCK, 0); 
    gpio_set_value(ST7789V_SLCD_SDI, (data >> bit) & 1); 
    udelay(10);
    gpio_set_value(ST7789V_SLCD_SCK, 1); 
    udelay(10);
  }
  gpio_set_value(ST7789V_SLCD_SCS, 1);
}

static void st7789v_send_cmd(uint8_t data)
{
  st7789v_send_spi(0, data);
}

static void st7789v_send_data(uint8_t data)
{
  st7789v_send_spi(1, data);
}

static void st7789v_init(void)
{
  printk("%s, ++\n", __func__);
  gpio_set_value(ST7789V_SLCD_RST, 0);
  mdelay(100);  
  gpio_set_value(ST7789V_SLCD_RST, 1);
  mdelay(150);

  st7789v_send_cmd(0x36);
  st7789v_send_data(0x80);

  st7789v_send_cmd(0xb2); // porch
  st7789v_send_data(0x03); // bpa
  st7789v_send_data(0x03); // fpa
  st7789v_send_data(0x00); // psen
  st7789v_send_data(0x33); // bpb
  st7789v_send_data(0x33); // bpc

  st7789v_send_cmd(0xb7);
  st7789v_send_data(0x35);

  st7789v_send_cmd(0xb8);
  st7789v_send_data(0x2f);
  st7789v_send_data(0x2b);
  st7789v_send_data(0x2f);

  st7789v_send_cmd(0xbb);
  st7789v_send_data(0x24);

  st7789v_send_cmd(0xc0);
  st7789v_send_data(0x2C);

  st7789v_send_cmd(0xc3);
  st7789v_send_data(0x10);

  st7789v_send_cmd(0xc4);
  st7789v_send_data(0x20);

  st7789v_send_cmd(0xc6);
  st7789v_send_data(0x11);

  st7789v_send_cmd(0xd0);
  st7789v_send_data(0xa4);
  st7789v_send_data(0xa1);

  st7789v_send_cmd(0xe8);
  st7789v_send_data(0x03);

  st7789v_send_cmd(0xe9);
  st7789v_send_data(0x0d);
  st7789v_send_data(0x12);
  st7789v_send_data(0x00);

  st7789v_send_cmd(0xe0);
  st7789v_send_data(0xd0);
  st7789v_send_data(0x00);
  st7789v_send_data(0x00);
  st7789v_send_data(0x08);
  st7789v_send_data(0x11);
  st7789v_send_data(0x1a);
  st7789v_send_data(0x2b);
  st7789v_send_data(0x33);
  st7789v_send_data(0x42);
  st7789v_send_data(0x26);
  st7789v_send_data(0x12);
  st7789v_send_data(0x21);
  st7789v_send_data(0x2f);
  st7789v_send_data(0x11);

  st7789v_send_cmd(0xe1);
  st7789v_send_data(0xd0);
  st7789v_send_data(0x02);
  st7789v_send_data(0x09);
  st7789v_send_data(0x0d);
  st7789v_send_data(0x0d);
  st7789v_send_data(0x27);
  st7789v_send_data(0x2b);
  st7789v_send_data(0x33);
  st7789v_send_data(0x42);
  st7789v_send_data(0x17);
  st7789v_send_data(0x12);
  st7789v_send_data(0x11);
  st7789v_send_data(0x2f);
  st7789v_send_data(0x31);

  st7789v_send_cmd(0x21);

  st7789v_send_cmd(0xb0);
  st7789v_send_data(0x11); // rgb interface
  st7789v_send_data(0x00); 
  st7789v_send_data(0x00); 

  st7789v_send_cmd(0xb1);
  st7789v_send_data(0x40); // de mode
  st7789v_send_data(0x00); 
  st7789v_send_data(0x00); // bpb, hpb 

  st7789v_send_cmd(0x3a); 
  st7789v_send_data(0x55);

  st7789v_send_cmd(0x2a);
  st7789v_send_data(0x00);
  st7789v_send_data(0x00);
  st7789v_send_data(0x00);
  st7789v_send_data(0xef);

  st7789v_send_cmd(0x2b);
  st7789v_send_data(0x00);
  st7789v_send_data(0x00);
  st7789v_send_data(0x01);
  st7789v_send_data(0x3f);

  st7789v_send_cmd(0x11); 
  mdelay(120);

  st7789v_send_cmd(0x29);
  st7789v_send_cmd(0x2c); 
  printk("%s, --\n", __func__);
}

#define CNVT_TOHW(val, width) ((((val) << (width)) + 0x7FFF - (val)) >> 16)
static int fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info)
{
  //printk("%s, visual:%d, bits_per_pixel:%d, regno:%d, r:0x%x, g:0x%x, b:0x%x\n", __func__, info->fix.visual, info->var.bits_per_pixel, regno, red, green, blue);
  red = CNVT_TOHW(red, info->var.red.length);
  blue = CNVT_TOHW(blue, info->var.blue.length);
  green = CNVT_TOHW(green, info->var.green.length);
  ((u32*)(info->pseudo_palette))[regno] = (red << info->var.red.offset) | (green << info->var.green.offset) | (blue << info->var.blue.offset);
  return 0;
}
#undef CNVT_TOHW

static int fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
  int err = 0;
  int bpp = var->bits_per_pixel >> 3;
  struct st7789v_fb_par *par = info->par;
  unsigned long line_size = var->xres_virtual * bpp;

  //if((var->xres != 320) || (var->yres != 240) || (var->bits_per_pixel != 16)){
    //return -EINVAL;
  //}

  printk("%s, xres:%d, yres:%d, bpp:%d\n", __func__, var->xres, var->yres, var->bits_per_pixel);
  var->transp.offset = 0;
  var->transp.length = 0;
  var->red.offset = 11;
  var->red.length = 5;
  var->green.offset = 5;
  var->green.length = 6;
  var->blue.offset = 0;
  var->blue.length = 5;
  var->red.msb_right = 0;
  var->green.msb_right = 0;
  var->blue.msb_right = 0;
  var->transp.msb_right = 0;
  if(line_size * var->yres_virtual > par->vram_size){
    var->yres_virtual = par->vram_size / line_size;
  }
  if(var->yres > var->yres_virtual){
    var->yres = var->yres_virtual;
  }
  if(var->xres > var->xres_virtual){
    var->xres = var->xres_virtual;
  }
  if(var->xres + var->xoffset > var->xres_virtual){
    var->xoffset = var->xres_virtual - var->xres;
  }
  if(var->yres + var->yoffset > var->yres_virtual){
    var->yoffset = var->yres_virtual - var->yres;
  }
  return err;
}

static int fb_remove(struct platform_device *dev)
{
  struct fb_info *info = dev_get_drvdata(&dev->dev);

  if(info){
    struct st7789v_fb_par *par = info->par;
    unregister_framebuffer(info);
    fb_dealloc_cmap(&info->cmap);
    dma_free_coherent(NULL, PALETTE_SIZE, par->v_palette_base, par->p_palette_base);
    dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys);
    dma_free_coherent(NULL, par->lram_size, par->lram_virt[0], par->lram_phys[0]);
    dma_free_coherent(NULL, par->lram_size, par->lram_virt[1], par->lram_phys[1]);
    pm_runtime_put_sync(&dev->dev);
    pm_runtime_disable(&dev->dev);
    framebuffer_release(info);
  }
  return 0;
}

static int fb_set_par(struct fb_info *info)
{
  struct st7789v_fb_par *par = info->par;
  
  fb_var_to_videomode(&par->mode, &info->var);
  printk("%s, xres:%d, yres:%d, bpp:%d, xoffset:%d, yoffset:%d\n", __func__, 
    info->var.xres, info->var.yres, info->var.bits_per_pixel, info->var.xoffset, info->var.yoffset);
  par->bpp = info->var.bits_per_pixel;
  info->fix.visual = FB_VISUAL_TRUECOLOR;
  info->fix.line_length = (par->mode.xres * par->bpp) / 8; 
  return 0;
}

static int fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
  printk("%s(cmd: 0x%x)++\n", __func__, cmd);
  switch(cmd){
  case OMAPFB_QUERY_PLANE:
    printk("OMAPFB_QUERY_PLANE\n");
    break;
  case OMAPFB_QUERY_MEM:
    printk("OMAPFB_QUERY_MEM\n");
    break;
  case OMAPFB_SETUP_PLANE:
    printk("OMAPFB_SETUP_PLANE\n");
    break;
  case OMAPFB_SETUP_MEM:
    printk("OMAPFB_SETUP_MEM\n");
    break;
  }
  printk("%s\n", __func__);
  return 0;
}

static int fb_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 struct fb_ops st7789v_fb_ops = {
  .owner = THIS_MODULE,
  .fb_check_var = fb_check_var,
  .fb_set_par   = fb_set_par,
  .fb_setcolreg = fb_setcolreg,
  .fb_ioctl     = fb_ioctl,
  .fb_mmap      = fb_mmap,

  .fb_fillrect  = cfb_fillrect,
  .fb_copyarea  = cfb_copyarea,
  .fb_imageblit = cfb_imageblit,
};

static irqreturn_t lcdc_irq_handler(int irq, void *arg)
{
  unsigned int stat=0;
  struct st7789v_fb_par *par=(struct st7789v_fb_par*)arg;
  
  stat = ioread32(st7789v_fb_reg_base + IRQSTATUS);
  if((stat & 0x04) || (stat & 0x20)){ // LCD_SYNC_LOST, LCD_FIFO_UNDERFLOW
    iowrite32(LCD_PALMODE_RAWDATA | LCD_TFT_MODE, st7789v_fb_reg_base + RASTER_CTRL);
    printk("%s, stat: 0x%x, LCDC sync lost or underflow error occured\n", __func__, stat);
    while(!(ioread32(st7789v_fb_reg_base + IRQSTATUS) & 0x01)){
      mdelay(1);
    }
    printk("wait complete\n");
    iowrite32(stat, st7789v_fb_reg_base + IRQSTATUS);
    iowrite32(LCD_PALMODE_RAWDATA | LCD_TFT_MODE | LCD_RASTER_ENABLE, st7789v_fb_reg_base + RASTER_CTRL);
  }
  else{
    uint32_t x, y;
    uint16_t *s = par->vram_virt;
    uint16_t *d = par->lram_virt[0];
    for(y=0; y<240; y++){
      for(x=0; x<320; x++){
        d[(x * 240) + y] = s[(y * 320) + x];
      }
    }
    iowrite32(stat, st7789v_fb_reg_base + IRQSTATUS);
  }
  return IRQ_HANDLED;
}

static int fb_probe(struct platform_device *device)
{
  int i, ret;
  uint32_t ulcm, raster_timing0, raster_timing1, raster_timing2, raster_ctrl;
  struct st7789v_lcdc_platform_data *fb_pdata = device->dev.platform_data;
  struct fb_videomode *lcdc_info;
  struct fb_info *st7789v_fb_info;
  struct clk *fb_clk = NULL;
  struct st7789v_fb_par *par;

  uint32_t hactive = 320;
  uint32_t vactive = 240;
  uint32_t hfp = 3;
  uint32_t hbp = 3;
  uint32_t hsw = 10;
  uint32_t vbp = 3;
  uint32_t vfp = 3;
  uint32_t vsw = 10;

  printk("%s, ++\n", __func__);
  if((fb_pdata == NULL) && (!device->dev.of_node)){
    dev_err(&device->dev, "can not get platform data\n");
    return -ENOENT;
  }

  lcdc_info = devm_kzalloc(&device->dev, sizeof(struct fb_videomode), GFP_KERNEL);
  if(lcdc_info == NULL){
    return -ENODEV;
  }
  lcdc_info->name = "320x240";
  lcdc_info->xres = 320;
  lcdc_info->yres = 240;
  lcdc_info->vmode = FB_VMODE_NONINTERLACED;
  lcdc_regs = platform_get_resource(device, IORESOURCE_MEM, 0);
  st7789v_fb_reg_base = devm_ioremap_resource(&device->dev, lcdc_regs);
  if(!st7789v_fb_reg_base){
    dev_err(&device->dev, "memory resource setup failed\n");
    return -EADDRNOTAVAIL;
  }
  printk("%s, lcdc_reg: 0x%x\n", __func__, lcdc_regs);
  printk("%s, fb_reg_base: 0x%x\n", __func__, st7789v_fb_reg_base);

  fb_clk = devm_clk_get(&device->dev, "fck");
  if(IS_ERR(fb_clk)){
    dev_err(&device->dev, "can not get device clock\n");
    return -ENODEV;
  }

  ret = clk_get_rate(fb_clk);
  printk("%s, current clk rate before setting: %d\n", __func__, ret);

  // st7789v clock 
  // de clock: 60ns
  // pclk clock: 120ns
  // hsync/vsync setup time: 30ns
  ret = clk_set_rate(fb_clk, 83000000);
  if(IS_ERR(fb_clk)){
    dev_err(&device->dev, "can not set device clock\n");
    return -ENODEV;
  }
  pm_runtime_enable(&device->dev);
  pm_runtime_get_sync(&device->dev);
  
  ret = clk_get_rate(fb_clk);
  printk("%s, current clk rate after setting: %d\n", __func__, ret);

  printk("%s, lidd pid: 0x%x\n", __func__, ioread32(st7789v_fb_reg_base + PID));

  raster_timing0 = LCD_HORLSB(hactive) | LCD_HORMSB(hactive) | LCD_HFPLSB(hfp) | LCD_HBPLSB(hbp) | LCD_HSWLSB(hsw);
  raster_timing1 = LCD_VBP(vbp) | LCD_VFP(vfp) | LCD_VSW(vsw) | LCD_VERLSB(vactive);
  raster_timing2 = LCD_HSWMSB(hsw) | LCD_VERMSB(vactive) | LCD_HBPMSB(hbp) | LCD_HFPMSB(hfp) | 0x00000a00 | 0x400000; // invert pixel clock
  printk("%s, timing2: 0x%x\n", __func__, raster_timing2);

  iowrite32(0x00000007, st7789v_fb_reg_base + CLKC_ENABLE);
  iowrite32(0x00000a01, st7789v_fb_reg_base + CTRL); // clock/10, raster mode
  iowrite32(raster_timing0, st7789v_fb_reg_base + RASTER_TIMING_0);
  iowrite32(raster_timing1, st7789v_fb_reg_base + RASTER_TIMING_1);
  iowrite32(raster_timing2, st7789v_fb_reg_base + RASTER_TIMING_2);
 
  st7789v_fb_info = framebuffer_alloc(sizeof(struct st7789v_fb_par), &device->dev);
  if(!st7789v_fb_info){
    dev_dbg(&device->dev, "memory allocation failed for fb_info\n");
    ret = -ENOMEM;
    goto err_pm_runtime_disable;
  }

  par = st7789v_fb_info->par;
  par->dev = &device->dev;
  par->bpp = 16;
  
  fb_videomode_to_var(&st7789v_fb_var, lcdc_info);
  printk("%s, xres: %d, yres:%d, bpp:%d\n", __func__, lcdc_info->xres, lcdc_info->yres, par->bpp);

  // allocate frame buffer
  par->vram_size = lcdc_info->xres * lcdc_info->yres * par->bpp;
  ulcm = lcm((lcdc_info->xres * par->bpp * 2) / 8, PAGE_SIZE); // double buffer
  par->vram_size = roundup(par->vram_size/8, ulcm);
  par->vram_size = par->vram_size;
  par->vram_virt = dma_alloc_coherent(NULL, par->vram_size, (resource_size_t*) &par->vram_phys, GFP_KERNEL | GFP_DMA);
  if(!par->vram_virt){
    dev_err(&device->dev, "GLCD: kmalloc for frame buffer(vram) failed\n");
    ret = -EINVAL;
    goto err_release_fb;
  }

  // swap video ram
  par->tram_virt = dma_alloc_coherent(NULL, par->vram_size, (resource_size_t*)&par->tram_phys, GFP_KERNEL | GFP_DMA);
  if(!par->tram_virt){
    dev_err(&device->dev, "GLCD: kmalloc for frame buffer(tram) failed\n");
    ret = -EINVAL;
    goto err_release_fb;
  }

  par->lram_size = 320 * 240 * 2; // fixed size for st7789v panel
  for(i=0; i<2; i++){
    par->lram_virt[i] = dma_alloc_coherent(NULL, par->lram_size, (resource_size_t*) &par->lram_phys[i], GFP_KERNEL | GFP_DMA);
    if(!par->lram_virt[i]){
      dev_err(&device->dev, "GLCD: kmalloc for frame buffer[%d](lram) failed\n", i);
      ret = -EINVAL;
      goto err_release_fb;
    }
    memset(par->lram_virt[i], 0, par->lram_size);
  }
  st7789v_fb_info->screen_base = (char __iomem*) par->vram_virt;
  st7789v_fb_fix.smem_start    = par->vram_phys;
  st7789v_fb_fix.smem_len      = par->vram_size;
  st7789v_fb_fix.line_length   = (lcdc_info->xres * par->bpp) / 8;

  // allocate palette buffer
  par->v_palette_base = dma_alloc_coherent(NULL, PALETTE_SIZE, (resource_size_t*)&par->p_palette_base, GFP_KERNEL | GFP_DMA);
  if(!par->v_palette_base){
    dev_err(&device->dev, "GLCD: kmalloc for palette buffer failed\n");
    ret = -EINVAL;
    goto err_release_fb_mem;
  }
  memset(par->v_palette_base, 0, PALETTE_SIZE);

  par->irq = platform_get_irq(device, 0);
  if(par->irq < 0){
    ret = -ENOENT;
    goto err_release_pl_mem;
  }

  st7789v_fb_var.grayscale = 0;
  st7789v_fb_var.bits_per_pixel = par->bpp;

  // Initialize fbinfo
  st7789v_fb_info->flags = FBINFO_FLAG_DEFAULT;
  st7789v_fb_info->fix = st7789v_fb_fix;
  st7789v_fb_info->var = st7789v_fb_var;
  st7789v_fb_info->fbops = &st7789v_fb_ops;
  st7789v_fb_info->pseudo_palette = par->pseudo_palette;
  st7789v_fb_info->fix.visual = (st7789v_fb_info->var.bits_per_pixel <= 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;

  ret = fb_alloc_cmap(&st7789v_fb_info->cmap, PALETTE_SIZE, 0);
  if(ret){
    goto err_release_pl_mem;
  }
  st7789v_fb_info->cmap.len = 32;

  // initialize var_screeninfo
  st7789v_fb_var.activate = FB_ACTIVATE_FORCE;
  fb_set_var(st7789v_fb_info, &st7789v_fb_var);
  dev_set_drvdata(&device->dev, st7789v_fb_info);

  // Register the Frame Buffer
  if(register_framebuffer(st7789v_fb_info) < 0){
    dev_err(&device->dev, "GLCD: Frame Buffer Registration Failed(/dev/fb0) !\n");
    ret = -EINVAL;
    goto err_dealloc_cmap;
  }

  do_request(ST7789V_SLCD_RST, "slcd_rst");
  do_request(ST7789V_SLCD_SDO, "slcd_sdo");
  do_request(ST7789V_SLCD_SDI, "slcd_sdi");
  do_request(ST7789V_SLCD_SCK, "slcd_sck");
  do_request(ST7789V_SLCD_SCS, "slcd_scs");
  gpio_direction_input(ST7789V_SLCD_SDO);

  st7789v_init();
  iowrite32(0x00000040, st7789v_fb_reg_base + LCDDMA_CTRL); // burst=16, 1 frame
  iowrite32(0x00000325, st7789v_fb_reg_base + IRQENABLE_SET);

  iowrite32(par->lram_phys[0], st7789v_fb_reg_base + LCDDMA_FB0_BASE);
  iowrite32(par->lram_phys[0] + (320 * 240 * 2) - 1, st7789v_fb_reg_base + LCDDMA_FB0_CEILING);

  iowrite32(par->lram_phys[1], st7789v_fb_reg_base + LCDDMA_FB1_BASE);
  iowrite32(par->lram_phys[1] + (320 * 240 * 2) - 1, st7789v_fb_reg_base + LCDDMA_FB1_CEILING); 

  ret = devm_request_irq(&device->dev, par->irq, lcdc_irq_handler, 0, DRIVER_NAME, par);
  if(ret){
    goto irq_freq;
  } 
  raster_ctrl = LCD_PALMODE_RAWDATA | LCD_TFT_MODE | LCD_RASTER_ENABLE;
  iowrite32(raster_ctrl, st7789v_fb_reg_base + RASTER_CTRL);
  printk("%s, raster_ctrl: 0x%x\n", __func__, raster_ctrl);

  fb_prepare_logo(st7789v_fb_info, 0);
  fb_show_logo(st7789v_fb_info, 0);

  printk("%s, --\n", __func__);
  return 0;

irq_freq:
  unregister_framebuffer(st7789v_fb_info);

err_dealloc_cmap:
  fb_dealloc_cmap(&st7789v_fb_info->cmap);

err_release_pl_mem:
  dma_free_coherent(NULL, PALETTE_SIZE, par->v_palette_base, par->p_palette_base);

err_release_fb_mem:
  dma_free_coherent(NULL, par->vram_size, par->tram_virt, par->tram_phys);
  dma_free_coherent(NULL, par->vram_size, par->vram_virt, par->vram_phys);

err_release_fb:
  framebuffer_release(st7789v_fb_info);

err_pm_runtime_disable:
  pm_runtime_put_sync(&device->dev);
  pm_runtime_disable(&device->dev);

  return ret;
}

#ifdef CONFIG_PM
static int fb_suspend(struct platform_device *dev, pm_message_t state)
{
  struct fb_info *info = platform_get_drvdata(dev);

  console_lock();
  fb_set_suspend(info, 1);
  pm_runtime_put_sync(&dev->dev);
  console_unlock();
  return 0;
}

static int fb_resume(struct platform_device *dev)
{
  struct fb_info *info = platform_get_drvdata(dev);

  console_lock();
  pm_runtime_get_sync(&dev->dev);
  fb_set_suspend(info, 0);
  console_unlock();
  return 0;
}

#else

#define fb_suspend NULL
#define fb_resume NULL

#endif

static const struct of_device_id st7789v_fb_of_match[] = {
  {.compatible = "ti,am335x-lcdc", },
  {},
};
MODULE_DEVICE_TABLE(of, st7789v_fb_of_match);

static struct platform_driver st7789v_fb_driver = {
  .probe = fb_probe,
  .remove = fb_remove,
  .suspend = fb_suspend,
  .resume = fb_resume,
  .driver = {
    .name = DRIVER_NAME,
    .owner = THIS_MODULE,
    .of_match_table = of_match_ptr(st7789v_fb_of_match),
  },
};

static int __init st7789v_fb_init(void)
{
  printk("%s\n", __func__);
  return platform_driver_register(&st7789v_fb_driver);
}

static void __exit st7789v_fb_cleanup(void)
{
  printk("%s\n", __func__);
  platform_driver_unregister(&st7789v_fb_driver);
}

module_init(st7789v_fb_init);
module_exit(st7789v_fb_cleanup);

MODULE_DESCRIPTION("AM335x framebuffer driver for ST7789V SLCD panel");
MODULE_AUTHOR("Steward Fu <steward.fu@gmail.com>");
MODULE_LICENSE("GPL");

menuconfig


完成


返回上一頁