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對接

終於可以顯示正確的圖像

正可視角

下可視角

右可視角

左可視角

上可視角