AM3358 >> C/C++

移植Framebuffer顯示驅動程式(Polling)


arch/arm/boot/dts/am335x-bonegreen-wireless.dts

  am33xx_pinmux: pinmux@44e10800 {
    pinctrl-names = "default";
    pinctrl-0 = <&gpio_keys_s0 &lcd_pins_s0>
    ...

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

arch/arm/boot/dts/am33xx.dtsi

  ocp {
    ...
    rtc@44e3e000 {
      compatible = "ti,am335x-rtc";
      reg = <0x44e3e000 0x1000>;
      interrupts = <75 76>;
      ti,hwmods = "rtc";
    };

    lcdc: lcdc@4830e000 {
      compatible = "ti,am3352-lcdc", "ti,am335x-lcdc";
      reg = <0x4830e000 0x1000>;
      interrupts = <36>;
      status = "disabled";
      ti,hwmods = "lcdc";
    };

drivers/video/Kconfig

config FB_AM335X
  tristate "AM335x 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/Makefile

obj-$(CONFIG_FB_AM335X) += am335x-fb.o

drivers/video/am335x-fb.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>
 *    AM335X 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 <linux/pm_runtime.h>

#define ILI9335_SLCD_D17     77
#define ILI9335_SLCD_D16     76
#define ILI9335_SLCD_D15     75
#define ILI9335_SLCD_D14     74
#define ILI9335_SLCD_D13     73
#define ILI9335_SLCD_D12     72
#define ILI9335_SLCD_D11     71
#define ILI9335_SLCD_D10     70
  
#define ILI9335_SLCD_RS      86
#define ILI9335_SLCD_CS      89
#define ILI9335_SLCD_RD      88
#define ILI9335_SLCD_WR      87
#define ILI9335_SLCD_RST     20

#define LCD                  0x4830E000
#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

#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); \
  }

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

  struct resource *lcdc_regs;
  void __iomem *fb_reg_base;
  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 amfb_fix = {
  .id        = "AM335X SLCD FB",
  .type      = FB_TYPE_PACKED_PIXELS,
  .visual    = FB_VISUAL_TRUECOLOR,
  .xpanstep  = 0,
  .ypanstep  = 1,
  .ywrapstep = 0,
  .accel     = FB_ACCEL_NONE,
};

static void ili9335_send_data(struct amfb *amfb, unsigned int val)
{
  iowrite32(0xff & (val >> 8), amfb->fb_reg_base + LIDD_CS0_DATA);
  iowrite32(0xff & val, amfb->fb_reg_base + LIDD_CS0_DATA);
}

static void ili9335_send_command(struct amfb *amfb, unsigned int val)
{
  iowrite32(0xff & (val >> 8), amfb->fb_reg_base + LIDD_CS0_ADDR);
  iowrite32(0xff & val, amfb->fb_reg_base + LIDD_CS0_ADDR);
}

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

static void ili9335_init(struct amfb *amfb)
{
  gpio_set_value(ILI9335_SLCD_RST, 1);
  mdelay(100);
  gpio_set_value(ILI9335_SLCD_RST, 0);
  mdelay(100);  
  gpio_set_value(ILI9335_SLCD_RST, 1);
  mdelay(100);

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

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

static int amfb_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 *amfb_get_mode(struct amfb *amfb, struct fb_var_screeninfo *var)
{
  struct fb_videomode *mode = &amfb->mode;

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

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

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

  mode = amfb_get_mode(amfb, var);
  if(mode == NULL){
    return -EINVAL;
  }

  fb_videomode_to_var(var, mode);

  /* Reserve space for double buffering. */
  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 amfb_upload_frame_cpu(struct amfb *amfb)
{
  unsigned int i;
  const int num_pixels = amfb->mode.xres * amfb->mode.yres;
  uint16_t *p = amfb->vidmem;
  
  ili9335_send_command(amfb, 0x22);
  for(i=0; i<num_pixels; i++){
    ili9335_send_data(amfb, *p++);
  }
}

static void amfb_refresh_work(struct work_struct *work)
{
  struct amfb *amfb = container_of(work, struct amfb, refresh_work.work);

  mutex_lock(&amfb->lock);
  if(amfb->is_enabled){
    amfb_upload_frame_cpu(amfb);
    schedule_delayed_work(&amfb->refresh_work, HZ / 10);
  }
  mutex_unlock(&amfb->lock);
}

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

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

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

static void amfb_disable(struct amfb *amfb)
{
  cancel_delayed_work(&amfb->refresh_work);
}

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

  mutex_lock(&amfb->lock);
  if(new_enabled){
    if(!amfb->is_enabled){
      amfb_enable(amfb);
    }
  }
  else{
    if(amfb->is_enabled){
      amfb_disable(amfb);
    }
  }

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

  return ret;
}

static int amfb_alloc_devmem(struct amfb *amfb)
{
  int max_linesize = 0, max_framesize = 0;
  int bytes_per_pixel;
  void *page;

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

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

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

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

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

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

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

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

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

static struct fb_ops amfb_ops = {
  .owner           = THIS_MODULE,
  .fb_check_var   = amfb_check_var,
  .fb_set_par     = amfb_set_par,
  .fb_setcolreg   = amfb_setcolreg,
  .fb_fillrect     = cfb_fillrect,
  .fb_copyarea     = cfb_copyarea,
  .fb_imageblit   = cfb_imageblit,
};

static int amfb_probe(struct platform_device *pdev)
{
  int ret;
  struct amfb *amfb;
  struct fb_info *fb;
  struct clk *fb_clk;
  void __iomem *fb_reg_base;
  struct resource *lcdc_regs;

  lcdc_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  fb_reg_base = devm_request_and_ioremap(&pdev->dev, lcdc_regs);
  if (!fb_reg_base) {
    printk("%s, memory resource setup failed\n", __func__);
    return -EADDRNOTAVAIL;
  }
  printk("%s, lcdc_regs: 0x%x 0x%x\n", __func__, lcdc_regs->start, lcdc_regs->end);
  printk("%s, ioremap: 0x%x\n", __func__, (unsigned int)fb_reg_base);

  fb_clk = devm_clk_get(&pdev->dev, "fck");
  if(IS_ERR(fb_clk)){
    printk("%s, can not get device clock\n", __func__);
    return -ENODEV;
  }
  ret = clk_set_rate(fb_clk, 100000000);
  if(IS_ERR(fb_clk)){
    printk("%s, can not set device clock\n", __func__);
    return -ENODEV;
  }
  pm_runtime_enable(&pdev->dev);
  pm_runtime_get_sync(&pdev->dev);

  printk("%s, lidd pid: 0x%x\n", __func__, ioread32(fb_reg_base + PID));
  iowrite32(0x00000007, fb_reg_base + CLKC_ENABLE);
  iowrite32(0x00000300, fb_reg_base + CTRL); // 100MHz / 3
  iowrite32(0x00000003, fb_reg_base + LIDD_CTRL); // LiDD
  iowrite32(0x08221044, fb_reg_base + LIDD_CS0_CONF);
  do_request(ILI9335_SLCD_RST, "slcd_reset");
  
  fb = framebuffer_alloc(sizeof(struct amfb), &pdev->dev);
  if(!fb){
    printk("%s, failed to allocate framebuffer device\n", __func__);
    return -ENOMEM;
  }

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

  amfb = fb->par;
  amfb->pdev = pdev;
  amfb->lcdc_regs = lcdc_regs;
  amfb->fb_reg_base = fb_reg_base;

  ili9335_init(amfb);
  platform_set_drvdata(pdev, amfb);

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

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

  ret = amfb_alloc_devmem(amfb);
  if(ret){
    printk("%s, failed to allocate video memory\n", __func__);
    goto err_iounmap;
  }

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

  mutex_init(&amfb->lock);
  amfb->is_enabled = 1;
  amfb_set_par(fb);
  
  ret = register_framebuffer(fb);
  if(ret){
    printk("%s, failed to register framebuffer: %d\n", __func__, ret);
    goto err_free_devmem;
  }

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

  INIT_DELAYED_WORK(&amfb->refresh_work, amfb_refresh_work);
  schedule_delayed_work(&amfb->refresh_work, 0);
  return 0;
  
  cancel_delayed_work_sync(&amfb->refresh_work);
err_free_devmem:
  fb_dealloc_cmap(&fb->cmap);
  amfb_free_devmem(amfb);
err_iounmap:
  iounmap(amfb->fb_reg_base);
  framebuffer_release(fb);
  return ret;
}

static int amfb_remove(struct platform_device *pdev)
{
  struct amfb *amfb = platform_get_drvdata(pdev);

  amfb_blank(FB_BLANK_POWERDOWN, amfb->fb);
  cancel_delayed_work_sync(&amfb->refresh_work);
  iounmap(amfb->fb_reg_base);
  fb_dealloc_cmap(&amfb->fb->cmap);
  amfb_free_devmem(amfb);

  platform_set_drvdata(pdev, NULL);

  framebuffer_release(amfb->fb);
  gpio_free(ILI9335_SLCD_RST);
  return 0;
}

#ifdef CONFIG_PM

static int amfb_suspend(struct device *dev)
{
  struct amfb *amfb = dev_get_drvdata(dev);

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

  mutex_lock(&amfb->lock);
  if(amfb->is_enabled){
    amfb_disable(amfb);
  }
  mutex_unlock(&amfb->lock);
  return 0;
}

static int amfb_resume(struct device *dev)
{
  struct amfb *amfb = dev_get_drvdata(dev);

  mutex_lock(&amfb->lock);
  if(amfb->is_enabled){
    amfb_enable(amfb);
  }
  mutex_unlock(&amfb->lock);

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

static const struct dev_pm_ops amfb_pm_ops = {
  .suspend  = amfb_suspend,
  .resume    = amfb_resume,
  .poweroff  = amfb_suspend,
  .restore  = amfb_resume,
};

#define MTFB_PM_OPS (&amfb_pm_ops)

#else
#define MTFB_PM_OPS NULL
#endif

static const struct of_device_id of_am335x_fb_match[] = {
  { .compatible = "ti,am335x-lcdc", }, {},
};

static struct platform_driver amfb_driver = {
  .probe  = amfb_probe,
  .remove  = amfb_remove,
  .driver = {
    .name  = "am335x_lcdc",
    .pm    = MTFB_PM_OPS,
    .of_match_table = of_match_ptr(of_am335x_fb_match),
  },
};

static int __init amfb_init(void)
{
  return platform_driver_register(&amfb_driver);
}
module_init(amfb_init);

static void __exit amfb_exit(void)
{
  platform_driver_unregister(&amfb_driver);
}
module_exit(amfb_exit);

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

Device Drivers > Graphics support > Support for frame buffer devices


完成


返回上一頁