GCW Zero

更換螢幕(3.5吋 IPS HX8363-A 解析度640x480)


司徒改造掌機的目的就是希望這台掌機可以更趨完美,而第一個改造的東西通常是屏幕,因為屏幕算是最直覺的第一影響因素,好不好只要一開機就可以知道,因此,司徒第一任務就是更換屏幕,而原本的屏幕解析度是320x240,司徒打算更換成更高解析度的屏幕,這樣的話,至少DOSBox可以顯示更精細的遊戲內容,因此,司徒從淘寶購買一片IPS 3.5" 640x480解析度的屏幕(RGB介面),如下所示:


接著準備焊接PCB


固定屏的位置


接著開始跳線


美美的跳線


測試後發現無法正確顯示


重新焊接


更美的跳線


結果還是一樣無法正確顯示


無法正確顯示圖像


最後只好使用最安全的跳線方式


兩片PCB對接


終於可以顯示正確的圖像


正可視角


下可視角


右可視角


左可視角


上可視角

drivers/video/displays/Kconfig

config PANEL_HX8363A
  # TODO: Switch to tristate once the driver is a module.
  bool "HX8363A based panel"
  help
    Select this if you are using a HX8363A LCD driver IC.

drivers/video/displays/Makefile

obj-$(CONFIG_PANEL_HX8363A)    += panel-hx8363a.o

drivers/video/displays/panel-hx8363a.c

#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/gpio.h>

#include <video/jzpanel.h>
#include <video/panel-hx8363a.h>

struct hx8363a {
  struct hx8363a_platform_data *pdata;
};

static void hx8363a_send_cmd(struct hx8363a_platform_data *pdata, u8 data)
{
  int bit;

  gpio_direction_output(pdata->gpio_enable, 0);
  gpio_direction_output(pdata->gpio_clock, 0);
  gpio_direction_output(pdata->gpio_data, 0);
  udelay(10);
  gpio_direction_output(pdata->gpio_clock, 1);
  udelay(10);
  for(bit=7; bit>=0; bit--){
    gpio_direction_output(pdata->gpio_clock, 0);
    gpio_direction_output(pdata->gpio_data, (data >> bit) & 1);
    udelay(10);
    gpio_direction_output(pdata->gpio_clock, 1);
    udelay(10);
  }
  gpio_direction_output(pdata->gpio_enable, 1);
}

static void hx8363a_send_data(struct hx8363a_platform_data *pdata, u8 data)
{
  int bit;

  gpio_direction_output(pdata->gpio_enable, 0);
  gpio_direction_output(pdata->gpio_clock, 0);
  gpio_direction_output(pdata->gpio_data, 1);
  udelay(10);
  gpio_direction_output(pdata->gpio_clock, 1);
  udelay(10);
  for(bit=7; bit>=0; bit--){
    gpio_direction_output(pdata->gpio_clock, 0);
    gpio_direction_output(pdata->gpio_data, (data >> bit) & 1);
    udelay(10);
    gpio_direction_output(pdata->gpio_clock, 1);
    udelay(10);
  }
  gpio_direction_output(pdata->gpio_enable, 1);
}

static int hx8363a_panel_init(void **out_panel, struct device *dev, void *panel_pdata)
{
  struct hx8363a_platform_data *pdata = panel_pdata;
  struct hx8363a *panel;
  int ret;

  printk("%s++\n", __func__);
  panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
  if (!panel) {
    dev_err(dev, "Failed to alloc panel data\n");
    return -ENOMEM;
  }

  panel->pdata = pdata;

  *out_panel = panel;

  /* Reserve GPIO pins. */
  ret = devm_gpio_request(dev, pdata->gpio_reset, "LCD panel reset");
  if (ret) {
    dev_err(dev,
      "Failed to request LCD panel reset pin: %d\n", ret);
    return ret;
  }

  ret = devm_gpio_request(dev, pdata->gpio_clock, "LCD 3-wire clock");
  if (ret) {
    dev_err(dev,
      "Failed to request LCD panel 3-wire clock pin: %d\n",
      ret);
    return ret;
  }

  ret = devm_gpio_request(dev, pdata->gpio_enable, "LCD 3-wire enable");
  if (ret) {
    dev_err(dev,
      "Failed to request LCD panel 3-wire enable pin: %d\n",
      ret);
    return ret;
  }

  ret = devm_gpio_request(dev, pdata->gpio_data, "LCD 3-wire data");
  if (ret) {
    dev_err(dev,
      "Failed to request LCD panel 3-wire data pin: %d\n",
      ret);
    return ret;
  }

  /* Set initial GPIO pin directions and value. */
  gpio_direction_output(pdata->gpio_clock,  1);
  gpio_direction_output(pdata->gpio_enable, 1);
  gpio_direction_output(pdata->gpio_data,   0);

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

static void hx8363a_panel_exit(void *panel)
{
}

static void hx8363a_panel_enable(void *panel)
{
  struct hx8363a_platform_data *pdata = ((struct hx8363a *)panel)->pdata;

  printk("%s++\n", __func__);

  // Reset LCD panel
  gpio_direction_output(pdata->gpio_reset, 0);
  mdelay(50);
  gpio_direction_output(pdata->gpio_reset, 1);
  mdelay(50);

  hx8363a_send_cmd(pdata, 0xB9);
  hx8363a_send_data(pdata, 0xFF);
  hx8363a_send_data(pdata, 0x83);
  hx8363a_send_data(pdata, 0x63);

  // Set_POWER
  hx8363a_send_cmd(pdata, 0xB1);
  hx8363a_send_data(pdata, 0x81);
  hx8363a_send_data(pdata, 0x30);
  hx8363a_send_data(pdata, 0x08); // 0x08
  hx8363a_send_data(pdata, 0x36);
  hx8363a_send_data(pdata, 0x01);
  hx8363a_send_data(pdata, 0x13);
  hx8363a_send_data(pdata, 0x10);
  hx8363a_send_data(pdata, 0x10);
  hx8363a_send_data(pdata, 0x35);
  hx8363a_send_data(pdata, 0x3D);
  hx8363a_send_data(pdata, 0x1A);
  hx8363a_send_data(pdata, 0x1A);

  // Set COLMOD
  hx8363a_send_cmd(pdata, 0x3A);
  hx8363a_send_data(pdata, 0x60); // 0x55

  hx8363a_send_cmd(pdata, 0x36);
  hx8363a_send_data(pdata, 0x0b); // 0x0a

  hx8363a_send_cmd(pdata, 0xC0);
  hx8363a_send_data(pdata, 0x41);
  hx8363a_send_data(pdata, 0x19);

  hx8363a_send_cmd(pdata, 0xBF);
  hx8363a_send_data(pdata, 0x00);
  hx8363a_send_data(pdata, 0x10);

  // Set_RGBIF
  hx8363a_send_cmd(pdata, 0xB3);
  hx8363a_send_data(pdata, 0x01);

  // Set_CYC
  hx8363a_send_cmd(pdata, 0xB4);
  hx8363a_send_data(pdata, 0x01); //01
  hx8363a_send_data(pdata, 0x12);
  hx8363a_send_data(pdata, 0x72); //72
  hx8363a_send_data(pdata, 0x12); //12
  hx8363a_send_data(pdata, 0x06); //06
  hx8363a_send_data(pdata, 0x03); //03
  hx8363a_send_data(pdata, 0x54); //54
  hx8363a_send_data(pdata, 0x03); //03
  hx8363a_send_data(pdata, 0x4e); //4e
  hx8363a_send_data(pdata, 0x00);
  hx8363a_send_data(pdata, 0x00);

  // Set_VCOM
  hx8363a_send_cmd(pdata, 0xB6);
  hx8363a_send_data(pdata, 0x33); //33 //27

  // Set_PANEL
  hx8363a_send_cmd(pdata, 0xCC);
  hx8363a_send_data(pdata, 0x02); //02
  mdelay(120);

  // Set Gamma 2.2
  hx8363a_send_cmd(pdata, 0xE0);
  hx8363a_send_data(pdata, 0x01);
  hx8363a_send_data(pdata, 0x07);
  hx8363a_send_data(pdata, 0x4C);
  hx8363a_send_data(pdata, 0xB0);
  hx8363a_send_data(pdata, 0x36);
  hx8363a_send_data(pdata, 0x3F);
  hx8363a_send_data(pdata, 0x06);
  hx8363a_send_data(pdata, 0x49);
  hx8363a_send_data(pdata, 0x51);
  hx8363a_send_data(pdata, 0x96);
  hx8363a_send_data(pdata, 0x18);
  hx8363a_send_data(pdata, 0xD8);
  hx8363a_send_data(pdata, 0x18);
  hx8363a_send_data(pdata, 0x50);
  hx8363a_send_data(pdata, 0x13);

  hx8363a_send_data(pdata, 0x01);
  hx8363a_send_data(pdata, 0x07);
  hx8363a_send_data(pdata, 0x4C);
  hx8363a_send_data(pdata, 0xB0);
  hx8363a_send_data(pdata, 0x36);
  hx8363a_send_data(pdata, 0x3F);
  hx8363a_send_data(pdata, 0x06);
  hx8363a_send_data(pdata, 0x49);
  hx8363a_send_data(pdata, 0x51);
  hx8363a_send_data(pdata, 0x96);
  hx8363a_send_data(pdata, 0x18);
  hx8363a_send_data(pdata, 0xD8);
  hx8363a_send_data(pdata, 0x18);
  hx8363a_send_data(pdata, 0x50);
  hx8363a_send_data(pdata, 0x13);
  mdelay(150);

  hx8363a_send_cmd(pdata, 0x11);
  mdelay(200);

  hx8363a_send_cmd(pdata, 0x29);
  printk("%s--\n", __func__);
}

static void hx8363a_panel_disable(void *panel)
{
  struct hx8363a_platform_data *pdata = ((struct hx8363a *)panel)->pdata;

  printk("%s++\n", __func__);
  hx8363a_send_cmd(pdata, 0x10);
  printk("%s--\n", __func__);
}

struct panel_ops hx8363a_panel_ops = {
  .init    = hx8363a_panel_init,
  .exit    = hx8363a_panel_exit,
  .enable    = hx8363a_panel_enable,
  .disable  = hx8363a_panel_disable,
};

drivers/video/jz4770_fb.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gcd.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/platform_data/jz4770_fb.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/wait.h>

#include <asm/addrspace.h>
#include <asm/irq.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/processor.h>

#include <asm/mach-jz4770/jz4770cpm.h>
#include <asm/mach-jz4770/jz4770lcdc.h>
#include <asm/mach-jz4770/jz4770misc.h>
#include <asm/mach-jz4770/ipu.h>

#include <video/jzpanel.h>

#include "console/fbcon.h"

#define MAX_XRES 1024
#define MAX_YRES 1024

struct jz4760lcd_panel_t {
  unsigned int cfg;  /* panel mode and pin usage etc. */
  unsigned int w;    /* Panel Width(in pixel) */
  unsigned int h;    /* Panel Height(in line) */
  unsigned int fclk;  /* frame clk */
  unsigned int hsw;  /* hsync width, in pclk */
  unsigned int vsw;  /* vsync width, in line count */
  unsigned int elw;  /* end of line, in pclk */
  unsigned int blw;  /* begin of line, in pclk */
  unsigned int efw;  /* end of frame, in line count */
  unsigned int bfw;  /* begin of frame, in line count */
};

static const struct jz4760lcd_panel_t jz4760_lcd_panel = {
  .cfg = LCD_CFG_LCDPIN_LCD | LCD_CFG_RECOVER | /* Underrun recover */
         LCD_CFG_MODE_GENERIC_TFT | /* General TFT panel */
         LCD_CFG_MODE_TFT_18BIT |   /* output 18bpp */
         LCD_CFG_PCP |  /* Pixel clock polarity: falling edge */
         LCD_CFG_HSP |   /* Hsync polarity: active low */
         LCD_CFG_VSP,  /* Vsync polarity: leading edge is falling edge */
  /* w, h, fclk, hsw, vsw, elw, blw, efw, bfw */
  #ifdef CONFIG_PANEL_HX8347A01
    320, 320, 60, 50, 1, 10, 70, 5, 5,
  #endif

  #ifdef CONFIG_PANEL_HX8363A
    640, 640, 60, 50, 1, 10, 70, 5, 5,
  #endif
  /* Note: 432000000 / 72 = 60 * 400 * 250, so we get exactly 60 Hz. */
};

/* default output to lcd panel */
static const struct jz4760lcd_panel_t *jz_panel = &jz4760_lcd_panel;

struct jzfb {
  struct fb_info *fb;
  struct jzfb_platform_data *pdata;
  struct platform_device *pdev;
  void *panel;

  uint32_t pseudo_palette[16];
  unsigned int bpp;  /* Current 'bits per pixel' value (32 or 16) */

  uint32_t pan_offset;
  uint32_t vsync_count;
  wait_queue_head_t wait_vsync;

  struct clk *lpclk, *ipuclk;

  struct mutex lock;
  bool is_enabled;
  /*
   * Number of frames to wait until doing a forced foreground flush.
   * If it looks like we are double buffering, we can flush on vertical
   * panning instead.
   */
  unsigned int delay_flush;

  bool clear_fb;

  void __iomem *base;
  void __iomem *ipu_base;
};

static void *lcd_frame1;
static void *lcd_frame_swap;

static bool keep_aspect_ratio = true;
static bool allow_downscaling = false;

static void ctrl_enable(struct jzfb *jzfb)
{
  u32 val = readl(jzfb->base + LCD_CTRL);
  val = (val & ~LCD_CTRL_DIS) | LCD_CTRL_ENA;
  writel(val, jzfb->base + LCD_CTRL);
}

static void ctrl_disable(struct jzfb *jzfb)
{
  unsigned int cnt;
  u32 val;

  // Use regular disable: finishes current frame, then stops.
  val = readl(jzfb->base + LCD_CTRL) | LCD_CTRL_DIS;
  writel(val, jzfb->base + LCD_CTRL);

  // Wait 20 ms for frame to end (at 60 Hz, one frame is 17 ms).
  for(cnt=20; cnt; cnt-= 4){
    if(readl(jzfb->base + LCD_STATE) & LCD_STATE_LDD){
      break;
    }
    msleep(4);
  }

  if(!cnt){
    dev_err(&jzfb->pdev->dev, "LCD disable timeout!\n");
  }
  val = readl(jzfb->base + LCD_STATE);
  writel(val & ~LCD_STATE_LDD, jzfb->base + LCD_STATE);
}

static int jz4760fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, u_int transp, struct fb_info *fb)
{
  struct jzfb *jzfb = fb->par;

  if(regno >= ARRAY_SIZE(jzfb->pseudo_palette)){
    return 1;
  }

  if(fb->var.bits_per_pixel == 16){
    ((u32 *)fb->pseudo_palette)[regno] = (red & 0xf800) | ((green & 0xfc00) >> 5) | (blue >> 11);
  }
  else{
    ((u32 *)fb->pseudo_palette)[regno] = ((red & 0xff00) << 8) | (green & 0xff00) | (blue >> 8);
  }
  return 0;
}

/* Use mmap /dev/fb can only get a non-cacheable Virtual Address. */
static int jz4760fb_mmap(struct fb_info *fb, struct vm_area_struct *vma)
{
  unsigned long start;
  unsigned long off;
  u32 len;

  off = vma->vm_pgoff << PAGE_SHIFT;
  //fb->fb_get_fix(&fix, PROC_CONSOLE(info), info);

  // frame buffer memory
  start = fb->fix.smem_start;
  len = PAGE_ALIGN((start & ~PAGE_MASK) + fb->fix.smem_len);
  start&= PAGE_MASK;

  if((vma->vm_end - vma->vm_start + off) > len){
    return -EINVAL;
  }
  off+= start;

  vma->vm_pgoff = off >> PAGE_SHIFT;
  vma->vm_flags|= VM_IO;

  /* Set cacheability to cacheable, write through, no write-allocate. */
  vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
  pgprot_val(vma->vm_page_prot)&= ~_CACHE_MASK;
  pgprot_val(vma->vm_page_prot)|= _CACHE_CACHABLE_NONCOHERENT;
  if(io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)){
    return -EAGAIN;
  }
  return 0;
}

static int reduce_fraction(unsigned int *num, unsigned int *denom)
{
  unsigned long d = gcd(*num, *denom);

  if(*num > 32 * d){
    return -EINVAL;
  }

  *num/= d;
  *denom/= d;
  return 0;
}

static int jz4760fb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
{
  struct jzfb *jzfb = fb->par;
  unsigned int num, denom;

  // The minimum input size for the IPU to work is 4x4
  if(var->xres < 4){
    var->xres = 4;
  }
  if(var->yres < 4){
    var->yres = 4;
  }

  if(!allow_downscaling){
    if(var->xres > jz_panel->w){
      var->xres = jz_panel->w;
    }
    if(var->yres > jz_panel->h){
      var->yres = jz_panel->h;
    }
  }

  // Adjust the input size until we find a valid configuration
  for(num=jz_panel->w, denom=var->xres; var->xres<=MAX_XRES && reduce_fraction(&num, &denom)<0; denom++, var->xres++);
  if(var->xres > MAX_XRES){
    return -EINVAL;
  }

  for(num=jz_panel->h, denom=var->yres; var->yres<=MAX_YRES && reduce_fraction(&num, &denom)<0; denom++, var->yres++);
  if(var->yres > MAX_YRES){
    return -EINVAL;
  }

  // Reserve space for triple buffering.
  var->yres_virtual = var->yres * 3;
  var->xres_virtual = var->xres;
  var->vmode = FB_VMODE_NONINTERLACED;
  var->yoffset = 0;

  if(var->bits_per_pixel != 32 && var->bits_per_pixel != 16){
    var->bits_per_pixel = 32;
  }

  if(var->bits_per_pixel == 16){
    var->transp.length = 0;
    var->blue.length = var->red.length = 5;
    var->green.length = 6;
    var->transp.offset = 0;
    var->red.offset = 11;
    var->green.offset = 5;
    var->blue.offset = 0;
  } 
  else{
    var->transp.offset = 24;
    var->red.offset = 16;
    var->green.offset = 8;
    var->blue.offset = 0;
    var->transp.length = var->red.length = var->green.length = var->blue.length = 8;
  }

  jzfb->clear_fb = var->bits_per_pixel != fb->var.bits_per_pixel || var->xres != fb->var.xres || var->yres != fb->var.yres;
  return 0;
}

static int jzfb_wait_for_vsync(struct jzfb *jzfb)
{
  uint32_t count = jzfb->vsync_count;
  long t = wait_event_interruptible_timeout(jzfb->wait_vsync, count != jzfb->vsync_count, HZ / 10);
  return t > 0 ? 0 : (t < 0 ? (int)t : -ETIMEDOUT);
}

static void jzfb_update_frame_address(struct jzfb *jzfb)
{
  writel((u32) jzfb->fb->fix.smem_start + jzfb->pan_offset, jzfb->ipu_base + IPU_Y_ADDR);
}

static void jzfb_lcdc_enable(struct jzfb *jzfb)
{
  clk_enable(jzfb->lpclk);
  jzfb_update_frame_address(jzfb);

  jzfb->delay_flush = 0;
  writel(0, jzfb->base + LCD_STATE); // Clear LCDC status

  // Enabling the LCDC too soon after the clock will hang the system. 
  // A very short delay seems to be sufficient.
  udelay(1);

  ctrl_enable(jzfb);
}

static void jzfb_foreground_resize(struct jzfb *jzfb, unsigned int xpos, unsigned int ypos, unsigned int width, unsigned int height)
{
  /*
   * NOTE:
   * Foreground change sequence:
   *   1. Change Position Registers -> LCD_OSDCTL.Change;
   *   2. LCD_OSDCTRL.Change -> descripter->Size
   * Foreground, only one of the following can be change at one time:
   *   1. F0 size;
   *  2. F0 position
   *   3. F1 size
   *  4. F1 position
   */
  writel((ypos << 16) | xpos, jzfb->base + LCD_XYP1);
  writel((height << 16) | width, jzfb->base + LCD_SIZE1);
}

static void jzfb_ipu_enable(struct jzfb *jzfb)
{
  u32 ctrl;

  clk_enable(jzfb->ipuclk);

  // Clear the status register and enable the chip
  writel(0, jzfb->ipu_base + IPU_STATUS);
  ctrl = readl(jzfb->ipu_base + IPU_CTRL);
  writel(ctrl | IPU_CTRL_CHIP_EN | IPU_CTRL_RUN, jzfb->ipu_base + IPU_CTRL);
}

static void jzfb_ipu_disable(struct jzfb *jzfb)
{
  unsigned int timeout = 1000;
  u32 ctrl = readl(jzfb->ipu_base + IPU_CTRL);

  if(ctrl & IPU_CTRL_CHIP_EN){
    writel(ctrl | IPU_CTRL_STOP, jzfb->ipu_base + IPU_CTRL);
    do{
      u32 status = readl(jzfb->ipu_base + IPU_STATUS);
      if(status & IPU_STATUS_OUT_END){
        break;
      }
      msleep(1);
    }while(--timeout);

    if(!timeout){
      dev_err(&jzfb->pdev->dev, "Timeout while disabling IPU\n");
    }
  }
  writel(ctrl & ~IPU_CTRL_CHIP_EN, jzfb->ipu_base + IPU_CTRL);
}

static void set_downscale_bilinear_coefs(struct jzfb *jzfb, unsigned int reg, unsigned int num, unsigned int denom)
{
  unsigned int i, weight_num = denom;

  for(i=0; i<num; i++){
    u32 value;
    unsigned int weight, offset;

    weight_num = num + (weight_num - num) % (num * 2);

    /*
     * Here, "input pixel 1.0" means half of 0 and half of 1;
     * "input pixel 0.5" means all of 0; and
     * "input pixel 1.49" means almost all of 1.
     */
    weight = 512 - 512 * (weight_num - num) / (num * 2);
    weight_num += denom * 2;
    offset = (weight_num - num) / (num * 2);
    value = ((weight & 0x7FF) << 6) | (offset << 1);
    writel(value, jzfb->ipu_base + reg);
  }
}

static void set_upscale_bilinear_coefs(struct jzfb *jzfb, unsigned int reg, unsigned int num, unsigned int denom)
{
  unsigned int i, weight_num = 0;

  for(i=0; i<num; i++){
    unsigned int weight = 512 - 512 * weight_num / num;
    u32 offset = 0, value;

    weight_num+= denom;
    if(weight_num >= num){
      weight_num-= num;
      offset = 1;
    }

    value = (weight & 0x7FF) << 6 | (offset << 1);
    writel(value, jzfb->ipu_base + reg);
  }
}

static void set_upscale_nearest_neighbour_coefs(struct jzfb *jzfb, unsigned int reg, unsigned int num)
{
  unsigned int i, weight_num = 1;

  for(i=0; i<num; i++, weight_num++){
    u32 value, offset = weight_num / num;
    weight_num%= num;
    value = (512 << 6) | (offset << 1);
    writel(value, jzfb->ipu_base + reg);
  }
}

static void set_coefs(struct jzfb *jzfb, unsigned int reg, unsigned int num, unsigned int denom)
{
  // Start programmation of the LUT
  writel(1, jzfb->ipu_base + reg);

  if(denom > num){
    set_downscale_bilinear_coefs(jzfb, reg, num, denom);
  }
  else if(denom == 1){
    set_upscale_nearest_neighbour_coefs(jzfb, reg, num);
  }
  else{
    set_upscale_bilinear_coefs(jzfb, reg, num, denom);
  }
}

static inline bool scaling_required(struct jzfb *jzfb)
{
  struct fb_var_screeninfo *var = &jzfb->fb->var;
  return var->xres != jz_panel->w || var->yres != jz_panel->h;
}

static void jzfb_ipu_configure(struct jzfb *jzfb, const struct jz4760lcd_panel_t *panel)
{
  struct fb_info *fb = jzfb->fb;
  u32 ctrl, coef_index=0, size, format = 2 << IPU_D_FMT_OUT_FMT_BIT;
  unsigned int outputW=panel->w, outputH=panel->h, xpos=0, ypos=0;

  // Enable the chip, reset all the registers
  writel(IPU_CTRL_CHIP_EN | IPU_CTRL_RST, jzfb->ipu_base + IPU_CTRL);

  switch(jzfb->bpp){
  case 16:
    format|= 3 << IPU_D_FMT_IN_FMT_BIT;
    break;
  case 32:
  default:
    format|= 2 << IPU_D_FMT_IN_FMT_BIT;
    break;
  }
  writel(format, jzfb->ipu_base + IPU_D_FMT);

  // Set the input height/width/stride
  size = fb->fix.line_length << IPU_IN_GS_W_BIT | fb->var.yres << IPU_IN_GS_H_BIT;
  writel(size, jzfb->ipu_base + IPU_IN_GS);
  writel(fb->fix.line_length, jzfb->ipu_base + IPU_Y_STRIDE);

  // Set the input address
  writel((u32) fb->fix.smem_start, jzfb->ipu_base + IPU_Y_ADDR);

  ctrl = IPU_CTRL_CHIP_EN | IPU_CTRL_LCDC_SEL | IPU_CTRL_FM_IRQ_EN;
  if(fb->fix.type == FB_TYPE_PACKED_PIXELS){
    ctrl|= IPU_CTRL_SPKG_SEL;
  }

  if(scaling_required(jzfb)){
    unsigned int numW=panel->w, denomW=fb->var.xres, numH=panel->h, denomH=fb->var.yres;

    BUG_ON(reduce_fraction(&numW, &denomW) < 0);
    BUG_ON(reduce_fraction(&numH, &denomH) < 0);

    if(keep_aspect_ratio){
      unsigned int ratioW = (UINT_MAX >> 6) * numW / denomW, ratioH = (UINT_MAX >> 6) * numH / denomH;
      if(ratioW < ratioH){
        numH = numW;
        denomH = denomW;
      } 
      else{
        numW = numH;
        denomW = denomH;
      }
    }

    if(numW != 1 || denomW != 1){
      set_coefs(jzfb, IPU_HRSZ_COEF_LUT, numW, denomW);
      coef_index |= ((numW - 1) << 16);
      ctrl |= IPU_CTRL_HRSZ_EN;
    }

    if(numH != 1 || denomH != 1){
      set_coefs(jzfb, IPU_VRSZ_COEF_LUT, numH, denomH);
      coef_index |= numH - 1;
      ctrl|= IPU_CTRL_VRSZ_EN;
    }

    outputH = fb->var.yres * numH / denomH;
    outputW = fb->var.xres * numW / denomW;

    // If we are upscaling horizontally, the last columns of pixels
    // shall be hidden, as they usually contain garbage: the last
    // resizing coefficients, when applied to the last column of the
    // input frame, instruct the IPU to blend the pixels with the
    // ones that correspond to the next column, that is to say the
    // leftmost column of pixels of the input frame.
    if(numW > denomW && denomW != 1){
      outputW -= numW / denomW;
    }
  }

  writel(ctrl, jzfb->ipu_base + IPU_CTRL);

  // Set the LUT index register
  writel(coef_index, jzfb->ipu_base + IPU_RSZ_COEF_INDEX);

  // Set the output height/width/stride
  size = (outputW * 4) << IPU_OUT_GS_W_BIT | outputH << IPU_OUT_GS_H_BIT;
  writel(size, jzfb->ipu_base + IPU_OUT_GS);
  writel(outputW * 4, jzfb->ipu_base + IPU_OUT_STRIDE);

  // Resize Foreground1 to the output size of the IPU
  xpos = (panel->w - outputW) / 2;
  ypos = (panel->h - outputH) / 2;
  jzfb_foreground_resize(jzfb, xpos, ypos, outputW, outputH);

  dev_dbg(&jzfb->pdev->dev, "Scaling %ux%u to %ux%u\n", fb->var.xres, fb->var.yres, outputW, outputH);
  printk("%s, scaling %ux%u to %ux%u\n", __func__, fb->var.xres, fb->var.yres, outputW, outputH);
}

static void jzfb_power_up(struct jzfb *jzfb)
{
  pinctrl_pm_select_default_state(&jzfb->pdev->dev);

  jzfb->pdata->panel_ops->enable(jzfb->panel);

  jzfb_lcdc_enable(jzfb);
  jzfb_ipu_enable(jzfb);
}

static void jzfb_power_down(struct jzfb *jzfb)
{
  ctrl_disable(jzfb);
  clk_disable(jzfb->lpclk);

  jzfb_ipu_disable(jzfb);
  clk_disable(jzfb->ipuclk);

  jzfb->pdata->panel_ops->disable(jzfb->panel);

  pinctrl_pm_select_sleep_state(&jzfb->pdev->dev);
}

/*
 * (Un)blank the display.
 */
static int jz4760fb_blank(int blank_mode, struct fb_info *info)
{
  struct jzfb *jzfb = info->par;

  mutex_lock(&jzfb->lock);
  if(blank_mode == FB_BLANK_UNBLANK){
    if(!jzfb->is_enabled){
      jzfb_power_up(jzfb);
      jzfb->is_enabled = true;
    }
  }
  else{
    if(jzfb->is_enabled){
      jzfb_power_down(jzfb);
      jzfb->is_enabled = false;
    }
  }
  mutex_unlock(&jzfb->lock);
  return 0;
}

static int jz4760fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fb)
{
  struct jzfb *jzfb = fb->par;
  uint32_t vpan = var->yoffset;

  if(var->xoffset != fb->var.xoffset){
    return -EINVAL;
  }

  jzfb->pan_offset = fb->fix.line_length * vpan;
  jzfb->delay_flush = 8;
  
  {
    #ifdef CONFIG_PANEL_HX8347A01
      uint32_t x, y, len=fb->fix.line_length * fb->var.yres;
      uint32_t *d = lcd_frame1 + jzfb->pan_offset;
      uint32_t *s = lcd_frame_swap;
      memcpy(s, d, len);
      for(y=0; y<240; y++){
        for(x=0; x<320; x++){
          d[(x*320) + y] = s[(y*320) + x];
        }
      }
    #endif
    
    #ifdef CONFIG_PANEL_HX8363A
      uint32_t x, y, len=fb->fix.line_length * fb->var.yres;
      uint32_t *d = lcd_frame1 + jzfb->pan_offset;
      uint32_t *s = lcd_frame_swap;
      memcpy(s, d, len);
      for(y=0; y<480; y++){
        for(x=0; x<640; x++){
          d[(x*640) + y] = s[(y*320) + x];
        }
      }
    #endif
  }
  
  dma_cache_wback_inv((unsigned long)(lcd_frame1 + jzfb->pan_offset), fb->fix.line_length * var->yres);

  /*
   * The primary use of this function is to implement double buffering.
   * Explicitly waiting for vsync and then panning doesn't work in
   * practice because the woken up process doesn't always run before the
   * next frame has already started: the time between vsync and the start
   * of the next frame is typically less than one scheduler time slice.
   * Instead, we wait for vsync here in the pan function and apply the
   * new panning setting in the vsync interrupt, so we know that the new
   * panning setting has taken effect before this function returns.
   * Note that fb->var is only updated after we return, so we need our
   * own copy of the panning setting (jzfb->pan_offset).
   */
  jzfb_wait_for_vsync(jzfb);
  return 0;
}

static inline unsigned int words_per_line(unsigned int width, unsigned int bpp)
{
  return (bpp * width + 31) / 32;
}

static int jz4760fb_map_smem(struct fb_info *fb)
{
  void *page_virt;
  unsigned int size = PAGE_ALIGN(MAX_XRES * MAX_YRES * 4 * 3);

  dev_dbg(fb->device, "FG1: %u bytes\n", size);
  lcd_frame1 = alloc_pages_exact(size, GFP_KERNEL);
  if(!lcd_frame1){
    dev_err(fb->device, "Unable to map %u bytes of screen memory\n", size);
    return -ENOMEM;
  }
  
  lcd_frame_swap = alloc_pages_exact(size, GFP_KERNEL);
  if(!lcd_frame_swap){
    dev_err(fb->device, "Unable to map %u bytes of screen swap memory\n", size);
    return -ENOMEM;
  }


  /*
   * Set page reserved so that mmap will work. This is necessary
   * since we'll be remapping normal memory.
   */
  for(page_virt=lcd_frame1; page_virt<lcd_frame1+size; page_virt+=PAGE_SIZE){
    SetPageReserved(virt_to_page(page_virt));
    clear_page(page_virt);
  }

  fb->fix.smem_start = virt_to_phys(lcd_frame1);
  fb->fix.smem_len = size;
  fb->screen_base = (void *)KSEG1ADDR(lcd_frame1);
  printk("%s, smem_base: 0x%x, smem_len:%d, screen_base:0x%x\n", __func__, 
    (unsigned int)fb->fix.smem_start, (unsigned int)fb->fix.smem_len, (unsigned int)fb->screen_base);
  return 0;
}

static void jz4760fb_unmap_smem(struct fb_info *fb)
{
  if(lcd_frame1){
    void *page_virt;
    void *end = lcd_frame1 + fb->fix.smem_len;

    for(page_virt=lcd_frame1; page_virt<end; page_virt+= PAGE_SIZE){
      ClearPageReserved(virt_to_page(page_virt));
    }
    free_pages_exact(lcd_frame1, fb->fix.smem_len);
  }
  
  if(lcd_frame_swap){
    free_pages_exact(lcd_frame1, fb->fix.smem_len);
  }
}

static void jz4760fb_set_panel_mode(struct jzfb *jzfb, const struct jz4760lcd_panel_t *panel)
{
  // Configure LCDC
  writel(panel->cfg, jzfb->base + LCD_CFG);

  // Enable IPU auto-restart
  writel(LCD_IPUR_IPUREN | (panel->blw + panel->w + panel->elw) * panel->vsw / 3, jzfb->base + LCD_IPUR);

  // Set HT / VT / HDS / HDE / VDS / VDE / HPE / VPE
  writel((panel->blw + panel->w + panel->elw) << LCD_VAT_HT_BIT | (panel->bfw + panel->h + panel->efw) << LCD_VAT_VT_BIT, jzfb->base + LCD_VAT);
  writel(panel->blw << LCD_DAH_HDS_BIT | (panel->blw + panel->w) << LCD_DAH_HDE_BIT, jzfb->base + LCD_DAH);
  writel(panel->bfw << LCD_DAV_VDS_BIT | (panel->bfw + panel->h) << LCD_DAV_VDE_BIT, jzfb->base + LCD_DAV);
  writel(panel->hsw << LCD_HSYNC_HPE_BIT, jzfb->base + LCD_HSYNC);
  writel(panel->vsw << LCD_VSYNC_VPE_BIT, jzfb->base + LCD_VSYNC);

  // Enable foreground 1, OSD mode
  writew(LCD_OSDC_F1EN | LCD_OSDC_OSDEN, jzfb->base + LCD_OSDC);

  // Enable IPU, 18/24 bpp output
  writew(LCD_OSDCTRL_IPU | LCD_OSDCTRL_OSDBPP_18_24, jzfb->base + LCD_OSDCTRL);

  // Set a black background
  writel(0, jzfb->base + LCD_BGC);

  printk("%s, w:%d, h:%d\n", __func__, panel->w, panel->h);
}

static void jzfb_change_clock(struct jzfb *jzfb, const struct jz4760lcd_panel_t *panel)
{
  unsigned int rate;

  rate = panel->fclk * (panel->w + panel->elw + panel->blw) * (panel->h + panel->efw + panel->bfw);

  /* Use pixel clock for LCD panel (as opposed to TV encoder). */
  __cpm_select_pixclk_lcd();
  clk_set_rate(jzfb->lpclk, rate);
  dev_dbg(&jzfb->pdev->dev, "PixClock: req %u, got %lu\n", rate, clk_get_rate(jzfb->lpclk));
}

/* set the video mode according to info->var */
static int jz4760fb_set_par(struct fb_info *info)
{
  struct fb_var_screeninfo *var = &info->var;
  struct fb_fix_screeninfo *fix = &info->fix;
  struct jzfb *jzfb = info->par;

  if(jzfb->is_enabled){
    ctrl_disable(jzfb);
    jzfb_ipu_disable(jzfb);
  }
  else{
    clk_enable(jzfb->lpclk);
    clk_enable(jzfb->ipuclk);
  }

  jzfb->pan_offset = 0;
  jzfb->bpp = var->bits_per_pixel;
  fix->line_length = var->xres_virtual * (var->bits_per_pixel >> 3);

  jz4760fb_set_panel_mode(jzfb, jz_panel);
  //jzfb_ipu_configure(jzfb, jz_panel);

  // Clear the framebuffer to avoid artifacts
  if(jzfb->clear_fb){
    void *page_virt = lcd_frame1;
    unsigned int size = fix->line_length * var->yres * 3;

    for(; page_virt<lcd_frame1+size; page_virt+=PAGE_SIZE){
      clear_page(page_virt);
    }
    dma_cache_wback_inv((unsigned long)lcd_frame1, size);
  }

  if(jzfb->is_enabled){
    jzfb_ipu_enable(jzfb);
    jzfb_lcdc_enable(jzfb);
  } 
  else{
    clk_disable(jzfb->lpclk);
    clk_disable(jzfb->ipuclk);
  }

  fix->visual = FB_VISUAL_TRUECOLOR;
  printk("%s, xres:%d, yres:%d, bpp:%d\n", __func__, var->xres, var->yres, var->bits_per_pixel);
  return 0;
}

static void jzfb_ipu_reset(struct jzfb *jzfb)
{
  ctrl_disable(jzfb);
  clk_enable(jzfb->ipuclk);
  jzfb_ipu_disable(jzfb);
  writel(IPU_CTRL_CHIP_EN | IPU_CTRL_RST, jzfb->ipu_base + IPU_CTRL);

  jz4760fb_set_panel_mode(jzfb, jz_panel);
  jzfb_ipu_configure(jzfb, jz_panel);
  jzfb_ipu_enable(jzfb);
  ctrl_enable(jzfb);
}

static int jz4760fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
  struct jzfb *jzfb = info->par;

  switch(cmd){
  case FBIO_WAITFORVSYNC:
    return jzfb_wait_for_vsync(jzfb);
  default:
    return -ENOIOCTLCMD;
  }
}

static struct fb_ops jz4760fb_ops = {
  .owner          = THIS_MODULE,

  .fb_setcolreg    = jz4760fb_setcolreg,
  .fb_check_var   = jz4760fb_check_var,
  .fb_set_par     = jz4760fb_set_par,
  .fb_blank        = jz4760fb_blank,
  .fb_pan_display  = jz4760fb_pan_display,

  .fb_fillrect    = sys_fillrect,
  .fb_copyarea    = sys_copyarea,
  .fb_imageblit    = sys_imageblit,

  .fb_mmap        = jz4760fb_mmap,
  .fb_ioctl        = jz4760fb_ioctl,
};

static irqreturn_t jz4760fb_interrupt_handler(int irq, void *dev_id)
{
  struct jzfb *jzfb = dev_id;
  struct fb_info *fb = jzfb->fb;
  
  if(jzfb->delay_flush == 0){
    dma_cache_wback_inv((unsigned long)(lcd_frame1 + jzfb->pan_offset), fb->fix.line_length * fb->var.yres);
  }
  else{
    jzfb->delay_flush--;
  }
  
  jzfb_update_frame_address(jzfb);
  jzfb->vsync_count++;
  wake_up_interruptible_all(&jzfb->wait_vsync);

  writel(0, jzfb->ipu_base + IPU_STATUS);
  return IRQ_HANDLED;
}

static ssize_t keep_aspect_ratio_show(struct device *dev, struct device_attribute *attr, char *buf)
{
  return snprintf(buf, PAGE_SIZE, "%c\n", keep_aspect_ratio ? 'Y' : 'N');
}

static ssize_t keep_aspect_ratio_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
  struct jzfb *jzfb = dev_get_drvdata(dev);
  bool new_value = false;

  if(strtobool(buf, &new_value) < 0){
    return -EINVAL;
  }

  keep_aspect_ratio = new_value;
  if(jzfb->is_enabled && scaling_required(jzfb)){
    ctrl_disable(jzfb);
    jzfb_ipu_disable(jzfb);
    jzfb_ipu_configure(jzfb, jz_panel);
    jzfb_ipu_enable(jzfb);
    jzfb_lcdc_enable(jzfb);
  }
  return count;
}

static DEVICE_ATTR_RW(keep_aspect_ratio);
static DEVICE_BOOL_ATTR(allow_downscaling, 0644, allow_downscaling);

static int jz4760_fb_probe(struct platform_device *pdev)
{
  struct jzfb_platform_data *pdata = pdev->dev.platform_data;
  struct jzfb *jzfb;
  struct fb_info *fb;
  struct resource *res;
  int ret;

  if(!pdata){
    dev_err(&pdev->dev, "Missing platform data\n");
    return -ENXIO;
  }

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

  jzfb = fb->par;

  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  jzfb->base = devm_ioremap_resource(&pdev->dev, res);
  if(IS_ERR(jzfb->base)){
    dev_err(&pdev->dev, "Failed to request LCD registers\n");
    ret = PTR_ERR(jzfb->base);
    goto err_release_fb;
  }

  res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
  jzfb->ipu_base = devm_ioremap_resource(&pdev->dev, res);
  if(IS_ERR(jzfb->ipu_base)){
    dev_err(&pdev->dev, "Failed to request IPU registers\n");
    ret = PTR_ERR(jzfb->ipu_base);
    goto err_release_fb;
  }

  jzfb->pdev = pdev;
  jzfb->pdata = pdata;
  jzfb->bpp = 32;
  init_waitqueue_head(&jzfb->wait_vsync);

  strcpy(fb->fix.id, "jz-lcd");
  fb->fix.type = FB_TYPE_PACKED_PIXELS;
  fb->fix.type_aux  = 0;

  fb->fix.xpanstep  = 1;
  fb->fix.ypanstep  = 1;
  fb->fix.ywrapstep  = 0;
  fb->fix.accel  = FB_ACCEL_NONE;
  fb->fix.visual = FB_VISUAL_TRUECOLOR;

  fb->var.nonstd = 0;
  fb->var.activate = FB_ACTIVATE_NOW;
  fb->var.height = -1;
  fb->var.width  = -1;
  fb->var.accel_flags  = FB_ACCELF_TEXT;
  fb->var.bits_per_pixel = jzfb->bpp;

  fb->var.xres = jz_panel->w;
  fb->var.yres = jz_panel->h;
  fb->var.vmode = FB_VMODE_NONINTERLACED;
  jz4760fb_check_var(&fb->var, fb);

  fb->fbops  = &jz4760fb_ops;
  fb->flags  = FBINFO_FLAG_DEFAULT;

  fb->pseudo_palette  = jzfb->pseudo_palette;
  INIT_LIST_HEAD(&fb->modelist);

  ret = jz4760fb_map_smem(fb);
  if(ret){
    goto failed;
  }

  /* Init pixel clock. */
  jzfb->lpclk = devm_clk_get(&pdev->dev, "lpclk");
  if(IS_ERR(jzfb->lpclk)){
    ret = PTR_ERR(jzfb->lpclk);
    dev_err(&pdev->dev, "Failed to get pixel clock: %d\n", ret);
    goto failed;
  }

  jzfb->ipuclk = devm_clk_get(&pdev->dev, "ipu");
  if(IS_ERR(jzfb->ipuclk)){
    ret = PTR_ERR(jzfb->ipuclk);
    dev_err(&pdev->dev, "Failed to get ipu clock: %d\n", ret);
    goto failed;
  }

  if(request_irq(IRQ_IPU, jz4760fb_interrupt_handler, 0, "ipu", jzfb)){
    dev_err(&pdev->dev, "Failed to request IRQ.\n");
    ret = -EBUSY;
    goto failed;
  }

  mutex_init(&jzfb->lock);

  platform_set_drvdata(pdev, jzfb);
  jzfb->fb = fb;

  /*
   * We assume the LCDC is disabled initially. If you really must have
   * video in your boot loader, you'll have to update this driver.
   */
  jzfb_change_clock(jzfb, jz_panel);
  clk_enable(jzfb->lpclk);
  fb->fix.line_length = fb->var.xres_virtual * (fb->var.bits_per_pixel >> 3);
  jzfb->delay_flush = 0;

  // TODO: Panels should be proper modules that register themselves.
  //       They should be switchable via sysfs.
  //       And a module parameter should select the default panel.

  ret = pdata->panel_ops->init(&jzfb->panel, &pdev->dev, pdata->panel_pdata);
  if(ret){
    goto failed;
  }
  jzfb->pdata->panel_ops->enable(jzfb->panel);
  jzfb_ipu_reset(jzfb);
  jzfb->is_enabled = true;
  ret = device_create_file(&pdev->dev, &dev_attr_keep_aspect_ratio);
  if(ret){
    dev_err(&pdev->dev, "Unable to create sysfs node: %i\n", ret);
    goto err_exit_panel;
  }

  ret = device_create_file(&pdev->dev, &dev_attr_allow_downscaling.attr);
  if(ret){
    dev_err(&pdev->dev, "Unable to create sysfs node: %i\n", ret);
    goto err_remove_keep_aspect_ratio_file;
  }

  ret = register_framebuffer(fb);
  if(ret < 0){
    dev_err(&pdev->dev, "Failed to register framebuffer device.\n");
    goto err_remove_allow_downscaling_file;
  }
  dev_info(&pdev->dev, "fb%d: %s frame buffer device, using %dK of video memory\n", fb->node, fb->fix.id, fb->fix.smem_len>>10);
  printk("%s, x:%d, y:%d\n", __func__, fb->var.xres, fb->var.yres);
  fb_prepare_logo(jzfb->fb, 0);
  fb_show_logo(jzfb->fb, 0);
  return 0;

err_remove_allow_downscaling_file:
  device_remove_file(&pdev->dev, &dev_attr_allow_downscaling.attr);
err_remove_keep_aspect_ratio_file:
  device_remove_file(&pdev->dev, &dev_attr_keep_aspect_ratio);
err_exit_panel:
  jzfb->pdata->panel_ops->exit(jzfb->panel);
failed:
  jz4760fb_unmap_smem(fb);
err_release_fb:
  framebuffer_release(fb);
  return ret;
}

static int jz4760_fb_remove(struct platform_device *pdev)
{
  struct jzfb *jzfb = platform_get_drvdata(pdev);

  device_remove_file(&pdev->dev, &dev_attr_allow_downscaling.attr);
  device_remove_file(&pdev->dev, &dev_attr_keep_aspect_ratio);

  if(jzfb->is_enabled){
    jzfb_power_down(jzfb);
  }
  jzfb->pdata->panel_ops->exit(jzfb->panel);
  return 0;
}

#ifdef CONFIG_PM_SLEEP
static int jz4760_fb_suspend(struct device *dev)
{
  struct jzfb *jzfb = dev_get_drvdata(dev);

  dev_dbg(dev, "Suspending\n");
  if(jzfb->is_enabled){
    jzfb_power_down(jzfb);
  }
  return 0;
}

static int jz4760_fb_resume(struct device *dev)
{
  struct jzfb *jzfb = dev_get_drvdata(dev);

  dev_dbg(dev, "Resuming\n");

  if(jzfb->is_enabled){
    jzfb_power_up(jzfb);
  }
  return 0;
}
#endif /* CONFIG_PM_SLEEP */

static SIMPLE_DEV_PM_OPS(jz4760_fb_pm_ops, jz4760_fb_suspend, jz4760_fb_resume);

static struct platform_driver jz4760_fb_driver = {
  .probe  = jz4760_fb_probe,
  .remove = jz4760_fb_remove,
  .driver = {
    .name  = "jz-lcd",
    .owner = THIS_MODULE,
    .pm    = &jz4760_fb_pm_ops,
  },
};

module_platform_driver(jz4760_fb_driver);

MODULE_DESCRIPTION("Jz4770 LCD frame buffer driver");
MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>, Steward Fu <steward.fu@gmail.com>");
MODULE_LICENSE("GPL");

arch/mips/jz4770/board-gcw0.c

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/i2c-gpio.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>

#include <asm/cpu.h>
#include <asm/bootinfo.h>
#include <asm/mipsregs.h>
#include <asm/reboot.h>

#include <linux/mmc/host.h>
#include <linux/act8600_power.h>
#include <linux/platform_data/jz4770_fb.h>
#include <linux/platform_data/linkdev.h>
#include <linux/platform_data/mxc6225.h>
#include <linux/platform_data/pwm-haptic.h>
#include <linux/platform_data/usb-musb-jz4770.h>
#include <linux/pinctrl/machine.h>
#include <linux/power/gpio-charger.h>
#include <linux/power/jz4770-battery.h>
#include <linux/regulator/fixed.h>
#include <linux/regulator/machine.h>
#include <linux/rfkill-regulator.h>
#include <linux/usb/musb.h>
#include <media/radio-rda5807.h>
#include <sound/jz4770.h>
#include <video/jzpanel.h>

#ifdef CONFIG_PANEL_HX8347A01
  #include <video/panel-hx8347a01.h>
#endif

#ifdef CONFIG_PANEL_HX8363A
  #include <video/panel-hx8363a.h>
#endif

#include <asm/mach-jz4770/board-gcw0.h>
#include <asm/mach-jz4770/gpio.h>
#include <asm/mach-jz4770/jz4770i2c.h>
#include <asm/mach-jz4770/jz4770misc.h>
#include <asm/mach-jz4770/mmc.h>
#include <asm/mach-jz4770/platform.h>

#include "clock.h"


/* Video */
#define GPIO_PANEL_SOMETHING  JZ_GPIO_PORTF(0)

static int gcw0_panel_init(void **out_panel,
             struct device *dev, void *panel_pdata)
{
  int ret;

#ifdef CONFIG_PANEL_HX8347A01
  ret = hx8347a01_panel_ops.init(out_panel, dev, panel_pdata);
#endif

#ifdef CONFIG_PANEL_HX8363A
  ret = hx8363a_panel_ops.init(out_panel, dev, panel_pdata);
#endif

  if (ret)
    return ret;

  ret = devm_gpio_request(dev, GPIO_PANEL_SOMETHING, "LCD panel unknown");
  if (ret) {
    dev_err(dev,
      "Failed to request LCD panel unknown pin: %d\n", ret);
    return ret;
  }

  gpio_direction_output(GPIO_PANEL_SOMETHING, 1);

  return 0;
}

static void gcw0_panel_exit(void *panel)
{
#ifdef CONFIG_PANEL_HX8347A01
  hx8347a01_panel_ops.exit(panel);
#endif

#ifdef CONFIG_PANEL_HX8363A
  hx8363a_panel_ops.exit(panel);
#endif
}

static void gcw0_panel_enable(void *panel)
{
  act8600_output_enable(6, true);

#ifdef CONFIG_PANEL_HX8347A01
  hx8347a01_panel_ops.enable(panel);
#endif

#ifdef CONFIG_PANEL_HX8363A
  hx8363a_panel_ops.enable(panel);
#endif
}

static void gcw0_panel_disable(void *panel)
{
#ifdef CONFIG_PANEL_HX8347A01
  hx8347a01_panel_ops.disable(panel);
#endif

#ifdef CONFIG_PANEL_HX8363A
  hx8363a_panel_ops.disable(panel);
#endif

  act8600_output_enable(6, false);
}

#ifdef CONFIG_PANEL_HX8347A01
static struct hx8347a01_platform_data gcw0_panel_pdata = {
#endif

#ifdef CONFIG_PANEL_HX8363A
static struct hx8363a_platform_data gcw0_panel_pdata = {
#endif
  .gpio_reset    = JZ_GPIO_PORTE(2),
  .gpio_clock    = JZ_GPIO_PORTE(15),
  .gpio_enable    = JZ_GPIO_PORTE(16),
  .gpio_data    = JZ_GPIO_PORTE(17),
};

static struct panel_ops gcw0_panel_ops = {
  .init    = gcw0_panel_init,
  .exit    = gcw0_panel_exit,
  .enable    = gcw0_panel_enable,
  .disable  = gcw0_panel_disable,
};

static struct jzfb_platform_data gcw0_fb_pdata = {
  .panel_ops    = &gcw0_panel_ops,
  .panel_pdata    = &gcw0_panel_pdata,
};


/* Buttons */
static struct gpio_keys_button gcw0_buttons[] = {
  /* D-pad up */ {
    .gpio      = JZ_GPIO_PORTE(21),
    .active_low    = 1,
    .code      = KEY_UP,
    .debounce_interval  = 10,
  },
  /* D-pad down */ {
    .gpio      = JZ_GPIO_PORTE(25),
    .active_low    = 1,
    .code      = KEY_DOWN,
    .debounce_interval  = 10,
  },
  /* D-pad left */ {
    .gpio      = JZ_GPIO_PORTE(23),
    .active_low    = 1,
    .code      = KEY_LEFT,
    .debounce_interval  = 10,
  },
  /* D-pad right */ {
    .gpio      = JZ_GPIO_PORTE(24),
    .active_low    = 1,
    .code      = KEY_RIGHT,
    .debounce_interval  = 10,
  },
  /* A button */ {
    .gpio      = JZ_GPIO_PORTE(29),
    .active_low    = 1,
    .code      = KEY_LEFTCTRL,
    .debounce_interval  = 10,
  },
  /* B button */ {
    .gpio      = JZ_GPIO_PORTE(20),
    .active_low    = 1,
    .code      = KEY_LEFTALT,
    .debounce_interval  = 10,
  },
  /* Top button (labeled Y, should be X) */ {
    .gpio      = JZ_GPIO_PORTE(27),
    .active_low    = 1,
    .code      = KEY_SPACE,
    .debounce_interval  = 10,
  },
  /* Left button (labeled X, should be Y) */ {
    .gpio      = JZ_GPIO_PORTE(28),
    .active_low    = 1,
    .code      = KEY_LEFTSHIFT,
    .debounce_interval  = 10,
  },
  /* Left shoulder button */ {
    .gpio      = JZ_GPIO_PORTB(20),
    .active_low    = 1,
    .code      = KEY_TAB,
    .debounce_interval  = 10,
  },
  /* Right shoulder button */ {
    .gpio      = JZ_GPIO_PORTE(26),
    .active_low    = 1,
    .code      = KEY_BACKSPACE,
    .debounce_interval  = 10,
  },
  /* START button */ {
    .gpio      = JZ_GPIO_PORTB(21),
    .active_low    = 1,
    .code      = KEY_ENTER,
    .debounce_interval  = 10,
  },
  /* SELECT button */ {
    .gpio      = JZ_GPIO_PORTD(18),
    /* This is the only button that is active high,
     * since it doubles as BOOT_SEL1.
     */
    .active_low    = 0,
    .code      = KEY_ESC,
    .debounce_interval  = 10,
  },
  /* POWER slider */ {
    .gpio      = JZ_GPIO_PORTA(30),
    .active_low    = 1,
    .code      = KEY_POWER,
    .debounce_interval  = 10,
    .wakeup      = 1,
  },
  /* POWER hold */ {
    .gpio      = JZ_GPIO_PORTF(11),
    .active_low    = 1,
    .code      = KEY_PAUSE,
    .debounce_interval  = 10,
  },
};

static struct gpio_keys_platform_data gcw0_gpio_keys_pdata = {
  .buttons = gcw0_buttons,
  .nbuttons = ARRAY_SIZE(gcw0_buttons),
  .rep = 1,
};

static struct platform_device gcw0_gpio_keys_device = {
  .name = "gpio-keys",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_gpio_keys_pdata,
  },
};


/* SD cards */
static struct jz_mmc_platform_data gcw_internal_sd_data = {
  .support_sdio    = 0,
  .ocr_mask    = MMC_VDD_32_33 | MMC_VDD_33_34,
  .bus_width    = 4,
  .gpio_card_detect  = -1,
  .gpio_read_only    = -1,
  .gpio_power    = -1,
  .nonremovable    = 1,
};

static struct jz_mmc_platform_data gcw_external_sd_data = {
  .support_sdio    = 0,
  .ocr_mask    = MMC_VDD_32_33 | MMC_VDD_33_34,
  .bus_width    = 4,
  .gpio_card_detect  = JZ_GPIO_PORTB(2),
  .card_detect_active_low  = 1,
  .gpio_read_only    = -1,
  .gpio_power    = JZ_GPIO_PORTE(9),
  .power_active_low  = 1,
};


/* FM radio receiver */
static struct rda5807_platform_data gcw0_rda5807_pdata = {
  .input_flags    = RDA5807_INPUT_LNA_WC_25 | RDA5807_LNA_PORT_P,
  .output_flags    = RDA5807_OUTPUT_AUDIO_ANALOG,
};


/* Power Management Unit */
static struct act8600_outputs_t act8600_outputs[] = {
  { 4, 0x57, true  }, /* USB OTG: 5.3V */
  { 5, 0x31, true  }, /* AVD:     2.5V */
  { 6, 0x39, false }, /* LCD:     3.3V */
  { 7, 0x39, true  }, /* generic: 3.3V */
  { 8, 0x24, true  }, /* generic: 1.8V */
};

static struct act8600_platform_pdata_t act8600_platform_pdata = {
        .outputs = act8600_outputs,
        .nr_outputs = ARRAY_SIZE(act8600_outputs),
};


/* Battery */
static struct jz_battery_platform_data gcw0_battery_pdata = {
  .gpio_charge = -1,
  //.gpio_charge_active_low = 0,
  .info = {
    .name = "battery",
    .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
    .voltage_max_design = 5700000,
    .voltage_min_design = 4600000,
  },
};


/* Charger */
#define GPIO_DC_CHARGER    JZ_GPIO_PORTF(5)
#define GPIO_USB_CHARGER  JZ_GPIO_PORTB(5)

static char *gcw0_batteries[] = {
  "battery",
};

static struct gpio_charger_platform_data gcw0_dc_charger_pdata = {
  .name = "dc",
  .type = POWER_SUPPLY_TYPE_MAINS,
  .gpio = GPIO_DC_CHARGER,
  .supplied_to = gcw0_batteries,
  .num_supplicants = ARRAY_SIZE(gcw0_batteries),
};

static struct platform_device gcw0_dc_charger_device = {
  .name = "gpio-charger",
  .id = 0,
  .dev = {
    .platform_data = &gcw0_dc_charger_pdata,
  },
};

static struct gpio_charger_platform_data gcw0_usb_charger_pdata = {
  .name = "usb",
  .type = POWER_SUPPLY_TYPE_USB,
  .gpio = GPIO_USB_CHARGER,
  .supplied_to = gcw0_batteries,
  .num_supplicants = ARRAY_SIZE(gcw0_batteries),
};

static struct platform_device gcw0_usb_charger_device = {
  .name = "gpio-charger",
  .id = 1,
  .dev = {
    .platform_data = &gcw0_usb_charger_pdata,
  },
};


/* USB 1.1 Host (OHCI) */
static struct regulator_consumer_supply gcw0_internal_usb_regulator_consumer =
  REGULATOR_SUPPLY("vrfkill", "rfkill-regulator.0");

static struct regulator_init_data gcw0_internal_usb_regulator_init_data = {
  .num_consumer_supplies = 1,
  .consumer_supplies = &gcw0_internal_usb_regulator_consumer,
  .constraints = {
    .name = "USB power",
    .min_uV = 3300000,
    .max_uV = 3300000,
    .valid_modes_mask = REGULATOR_MODE_NORMAL,
    .valid_ops_mask = REGULATOR_CHANGE_STATUS,
  },
};

static struct fixed_voltage_config gcw0_internal_usb_regulator_data = {
  .supply_name = "USB power",
  .microvolts = 3300000,
  .gpio = JZ_GPIO_PORTF(10),
  .init_data = &gcw0_internal_usb_regulator_init_data,
};

static struct platform_device gcw0_internal_usb_regulator_device = {
  .name = "reg-fixed-voltage",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_internal_usb_regulator_data,
  }
};


/* USB OTG (musb) */
#define GPIO_USB_OTG_ID_PIN  JZ_GPIO_PORTF(18)

static struct jz_otg_board_data gcw0_otg_board_data = {
  .gpio_id_pin = GPIO_USB_OTG_ID_PIN,
  .gpio_id_debounce_ms = 500,
};


/* I2C devices */
/*
 * Select which I2C busses use a hardware adapter (i2c-jz4770) and which use
 * a software adapter (i2c-gpio).
 */
#if defined(CONFIG_I2C_JZ4770)
#define I2C0_USE_HW  1
#define I2C1_USE_HW  1
#else
#define I2C0_USE_HW  0
#define I2C1_USE_HW  0
#endif

static struct i2c_board_info gcw0_i2c0_devs[] __initdata = {
  {
    .type    = "radio-rda5807",
    .addr    = RDA5807_I2C_ADDR,
    .platform_data  = &gcw0_rda5807_pdata,
  },
};

/* We don't have a use for the INT pin yet. */
#define GPIO_MXC6225_INT  JZ_GPIO_PORTF(13)
static struct i2c_board_info gcw0_i2c1_devs[] __initdata = {
  {
    .type    = "mxc6225",
    .addr    = MXC6225_I2C_ADDR,
  },
};

static struct i2c_board_info gcw0_i2c3_devs[] __initdata = {
  {
    .type    = ACT8600_NAME,
    .addr    = ACT8600_I2C_ADDR,
    .platform_data  = &act8600_platform_pdata,
  },
};

static struct i2c_board_info gcw0_i2c4_devs[] __initdata = {
  /* the IT6610 is on this bus, but we don't have a driver for it */
};

/* I2C busses */
static struct i2c_jz4770_platform_data gcw0_i2c0_platform_data __initdata = {
  .use_dma    = false,
};

static struct i2c_jz4770_platform_data gcw0_i2c1_platform_data __initdata = {
  .use_dma    = false,
};

#if I2C0_USE_HW == 0

static struct i2c_gpio_platform_data gcw0_i2c0_gpio_data = {
  .sda_pin    = JZ_GPIO_PORTD(30),
  .scl_pin    = JZ_GPIO_PORTD(31),
  .udelay      = 2, /* 250 kHz */
};

static struct platform_device gcw0_i2c0_gpio_device = {
  .name      = "i2c-gpio",
  .id      = 0,
  .dev      = {
    .platform_data = &gcw0_i2c0_gpio_data,
  },
};

#endif

#if I2C1_USE_HW == 0

static struct i2c_gpio_platform_data gcw0_i2c1_gpio_data = {
  .sda_pin    = JZ_GPIO_PORTE(30),
  .scl_pin    = JZ_GPIO_PORTE(31),
  .udelay      = 2, /* 250 kHz */
};

static struct platform_device gcw0_i2c1_gpio_device = {
  .name      = "i2c-gpio",
  .id      = 1,
  .dev      = {
    .platform_data = &gcw0_i2c1_gpio_data,
  },
};

#endif

static struct i2c_gpio_platform_data gcw0_i2c3_gpio_data = {
  .sda_pin    = JZ_GPIO_PORTD(5),
  .scl_pin    = JZ_GPIO_PORTD(4),
  .udelay      = 2, /* 250 kHz */
};

static struct platform_device gcw0_i2c3_gpio_device = {
  .name      = "i2c-gpio",
  .id      = 3,
  .dev      = {
    .platform_data = &gcw0_i2c3_gpio_data,
  },
};

static struct i2c_gpio_platform_data gcw0_i2c4_gpio_data = {
  .sda_pin    = JZ_GPIO_PORTD(6),
  .scl_pin    = JZ_GPIO_PORTD(7),
  .udelay      = 5, /* 100 kHz */
};

static struct platform_device gcw0_i2c4_gpio_device = {
  .name      = "i2c-gpio",
  .id      = 4,
  .dev      = {
    .platform_data = &gcw0_i2c4_gpio_data,
  },
};


/* LCD backlight */
static struct platform_pwm_backlight_data gcw0_backlight_pdata = {
  .polarity = PWM_POLARITY_NORMAL,
  .max_brightness = 255,
  .dft_brightness = 145,
  .pwm_period_ns = 40000, /* 25 kHz: outside human hearing range */
};

static struct platform_device gcw0_backlight_device = {
  .name = "pwm-backlight",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_backlight_pdata,
  },
};


/* Audio */
static struct jz4770_icdc_platform_data gcw0_icdc_pdata = {
  .mic_mode = JZ4770_MIC_1,
};

static struct platform_device gcw0_audio_device = {
  .name = "gcw0-audio",
  .id = -1,
};


struct jz_clk_board_data jz_clk_bdata = {
  /* These two are fixed in hardware. */
  .ext_rate  =   12000000,
  .rtc_rate  =      32768,
  /*
   * Pick 432 MHz as it is the least common multiple of 27 MHz (required
   * by TV encoder) and 48 MHz (required by USB host).
   */
  .pll1_rate  =  432000000,
};

/* Power LED */
static struct gpio_led gcw0_leds[] = {
  {
    .name = "power",
    .gpio = JZ_GPIO_PORTB(30),
    .active_low = 1,
    .default_state = LEDS_GPIO_DEFSTATE_ON,
  },
};

static struct gpio_led_platform_data gcw0_led_pdata = {
  .leds = gcw0_leds,
  .num_leds = ARRAY_SIZE(gcw0_leds),
};

static struct platform_device gcw0_led_device = {
  .name = "leds-gpio",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_led_pdata,
  },
};

static struct rfkill_regulator_platform_data gcw0_rfkill_pdata = {
  .name = "gcw0-wifi",
  .type = RFKILL_TYPE_WLAN,
};

static struct platform_device gcw0_rfkill_device = {
  .name = "rfkill-regulator",
  .id = 0,
  .dev = {
    .platform_data = &gcw0_rfkill_pdata,
  },
};

static const char * gcw0_joystick_gpiokeys_whitelist[] = {
  "evdev",
};

static const struct linkdev_pdata_device_info gcw0_joystick_devices[] = {
  {
    .name = "analog joystick",
  },
  {
    .name = "gpio-keys",
    .handlers_whitelist = gcw0_joystick_gpiokeys_whitelist,
    .nb_handlers = ARRAY_SIZE(gcw0_joystick_gpiokeys_whitelist),
  },
};

static const struct linkdev_pdata_key_map gcw0_key_map[] = {
  {
    .code = KEY_UP,
    .event = {
      .type = EV_ABS,
      .code = ABS_HAT0Y,
      .value = -1,
    },
  },
  {
    .code = KEY_DOWN,
    .event = {
      .type = EV_ABS,
      .code = ABS_HAT0Y,
      .value = 1,
    }
  },
  {
    .code = KEY_LEFT,
    .event = {
      .type = EV_ABS,
      .code = ABS_HAT0X,
      .value = -1,
    },
  },
  {
    .code = KEY_RIGHT,
    .event = {
      .type = EV_ABS,
      .code = ABS_HAT0X,
      .value = 1,
    },
  },
  {
    .code = KEY_LEFTCTRL,
    .event.code = BTN_EAST,
  },
  {
    .code = KEY_LEFTALT,
    .event.code = BTN_SOUTH,
  },
  {
    .code = KEY_LEFTSHIFT,
    .event.code = BTN_WEST,
  },
  {
    .code = KEY_SPACE,
    .event.code = BTN_NORTH,
  },
  {
    .code = KEY_ENTER,
    .event.code = BTN_START,
  },
  {
    .code = KEY_ESC,
    .event.code = BTN_SELECT,
  },
  {
    .code = KEY_TAB,
    .event.code = BTN_THUMBL,
  },
  {
    .code = KEY_BACKSPACE,
    .event.code = BTN_THUMBR,
  },
};

static const struct linkdev_pdata_abs_map gcw0_abs_map[] = {
  {
    .name = "analog joystick",
    .axis = ABS_X,
    .axis_dest = ABS_X,
  },
  {
    .name = "analog joystick",
    .axis = ABS_Y,
    .axis_dest = ABS_Y,
  },
  {
    .name = "gpio-keys",
    .axis = ABS_HAT0X,
    .axis_dest = ABS_HAT0X,
  },
  {
    .name = "gpio-keys",
    .axis = ABS_HAT0Y,
    .axis_dest = ABS_HAT0Y,
  },
};

static struct linkdev_platform_data gcw0_joystick_pdata = {
  /* This specific name informs SDL about the composition of the joystick */
  .name = "linkdev device (Analog 2-axis 8-button 2-hat)",
  .devices = gcw0_joystick_devices,
  .nb_devices = ARRAY_SIZE(gcw0_joystick_devices),
  .key_map = gcw0_key_map,
  .key_map_size = ARRAY_SIZE(gcw0_key_map),
  .abs_map = gcw0_abs_map,
  .abs_map_size = ARRAY_SIZE(gcw0_abs_map),
};

/* GCW0 Input driver */
static struct platform_device gcw0_joystick_device = {
  .name = "linkdev",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_joystick_pdata,
  },
};

static struct pwm_haptic_platform_data gcw0_haptic_pdata = {
  .pwm_period_ns = 2000000,
};


/* Rumble device */
static struct platform_device gcw0_haptic_device = {
  .name = "pwm-haptic",
  .id = -1,
  .dev = {
    .platform_data = &gcw0_haptic_pdata,
  },
};


/* Device registration */
static struct platform_device *jz_platform_devices[] __initdata = {
  &gcw0_internal_usb_regulator_device,
  &jz4770_usb_ohci_device,
  &jz4770_usb_otg_xceiv_device,
  &jz4770_usb_otg_device,
  &jz4770_lcd_device,
  &jz4770_i2s_device,
  &jz4770_pcm_device,
  &jz4770_icdc_device,
#if I2C0_USE_HW == 1
  &jz4770_i2c0_device,
#endif
#if I2C1_USE_HW == 1
  &jz4770_i2c1_device,
#endif
#if I2C0_USE_HW == 0
  &gcw0_i2c0_gpio_device,
#endif
#if I2C1_USE_HW == 0
  &gcw0_i2c1_gpio_device,
#endif
  &gcw0_i2c3_gpio_device,
  &gcw0_i2c4_gpio_device,
  &jz4770_pwm_device,
  &jz4770_adc_device,
  &jz4770_rtc_device,
  &gcw0_gpio_keys_device,
  &gcw0_backlight_device,
  &gcw0_audio_device,
  &jz4770_msc0_device,
  &jz4770_msc1_device,
  &gcw0_led_device,
  &gcw0_dc_charger_device,
  &gcw0_usb_charger_device,
  &jz4770_vpu_device,
  &gcw0_rfkill_device,
  &gcw0_joystick_device,
  &jz4770_wdt_device,
  &gcw0_haptic_device,
};

static int __init gcw0_init_platform_devices(void)
{
  struct musb_hdrc_platform_data *otg_platform_data =
      jz4770_usb_otg_device.dev.platform_data;
  otg_platform_data->board_data = &gcw0_otg_board_data;

  jz4770_lcd_device.dev.platform_data = &gcw0_fb_pdata;
  jz4770_adc_device.dev.platform_data = &gcw0_battery_pdata;
  jz4770_msc0_device.dev.platform_data = &gcw_internal_sd_data;
  jz4770_msc1_device.dev.platform_data = &gcw_external_sd_data;
  jz4770_icdc_device.dev.platform_data = &gcw0_icdc_pdata;

  return platform_add_devices(jz_platform_devices,
            ARRAY_SIZE(jz_platform_devices));
}

static void __init board_i2c_init(void)
{
  jz4770_i2c0_device.dev.platform_data = &gcw0_i2c0_platform_data;
  jz4770_i2c1_device.dev.platform_data = &gcw0_i2c1_platform_data;

  i2c_register_board_info(0, gcw0_i2c0_devs, ARRAY_SIZE(gcw0_i2c0_devs));
  i2c_register_board_info(1, gcw0_i2c1_devs, ARRAY_SIZE(gcw0_i2c1_devs));
  i2c_register_board_info(3, gcw0_i2c3_devs, ARRAY_SIZE(gcw0_i2c3_devs));
  i2c_register_board_info(4, gcw0_i2c4_devs, ARRAY_SIZE(gcw0_i2c4_devs));
}

static void __init board_gpio_setup(void)
{
  /* SELECT button */
  jz_gpio_disable_pullup(JZ_GPIO_PORTD(18));

  /* DC power source present (high active) */
  jz_gpio_disable_pullup(GPIO_DC_CHARGER);

  /* USB power source present (high active) */
  jz_gpio_disable_pullup(GPIO_USB_CHARGER);

  /* MXC6225 data sheet says INT should not be pulled up or down */
  jz_gpio_disable_pullup(GPIO_MXC6225_INT);
}

static struct pinctrl_map pin_map[] __initdata = {
#if I2C0_USE_HW == 1
  PIN_MAP_MUX_GROUP("i2c-jz4770.0", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", NULL, "i2c0"),
#endif
#if I2C1_USE_HW == 1
  PIN_MAP_MUX_GROUP("i2c-jz4770.1", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", NULL, "i2c1"),
#endif
  PIN_MAP_MUX_GROUP("jz-msc.0", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", "msc0_4bit", "msc0"),
  PIN_MAP_MUX_GROUP("jz-msc.1", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", "msc1_4bit", "msc1"),
  /* pwm1: LCD backlight */
  PIN_MAP_MUX_GROUP("pwm-backlight", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", NULL, "pwm1"),
  /* pwm4: rumble motor */
  PIN_MAP_MUX_GROUP("pwm-haptic", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", NULL, "pwm4"),
  PIN_MAP_MUX_GROUP("musb-jz.0", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", NULL, "otg"),
  PIN_MAP_MUX_GROUP("jz-lcd.0", PINCTRL_STATE_DEFAULT,
        "jz4770-pinctrl", "lcd_rgb888", "lcd"),
  PIN_MAP_MUX_GROUP("jz-lcd.0", PINCTRL_STATE_SLEEP,
        "jz4770-pinctrl", "no_pins", "lcd"),
};

static struct pwm_lookup pwm_lookup[] = {
  PWM_LOOKUP("jz4770-pwm", 1, "pwm-backlight", NULL),
  PWM_LOOKUP("jz4770-pwm", 4, "pwm-haptic", NULL),
};

static void __init board_init_pins(void)
{
  pinctrl_register_mappings(pin_map, ARRAY_SIZE(pin_map));

  pwm_add_table(pwm_lookup, ARRAY_SIZE(pwm_lookup));
}

static int __init gcw0_board_setup(void)
{
  printk(KERN_INFO "GCW Zero JZ4770 setup\n");

  board_init_pins();
  board_gpio_setup();
  board_i2c_init();

  if (gcw0_init_platform_devices())
    panic("Failed to initialize platform devices");

  return 0;
}

arch_initcall(gcw0_board_setup);

menuconfig


返回上一頁