GCW Zero
更換螢幕(3.5吋 IPS HX8363-A 解析度640x480)
司徒改造掌機的目的就是希望這台掌機可以更趨完美,而第一個改造的東西通常是屏幕,因為屏幕算是最直覺的第一影響因素,好不好只要一開機就可以知道,因此,司徒第一任務就是更換屏幕,而原本的屏幕解析度是320x240,司徒打算更換成更高解析度的屏幕,這樣的話,至少DOSBox可以顯示更精細的遊戲內容,因此,司徒從淘寶購買一片IPS 3.5" 640x480解析度的屏幕(RGB介面),如下所示:
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
接著準備焊接PCB
固定屏的位置
接著開始跳線
美美的跳線
測試後發現無法正確顯示
重新焊接
更美的跳線
結果還是一樣無法正確顯示
無法正確顯示圖像
最後只好使用最安全的跳線方式
兩片PCB對接
終於可以顯示正確的圖像
正可視角
下可視角
右可視角
左可視角
上可視角