]> bbs.cooldavid.org Git - net-next-2.6.git/commitdiff
Merge master.kernel.org:/pub/scm/linux/kernel/git/lethal/genesis-2.6 into devel-stable
authorRussell King <rmk+kernel@arm.linux.org.uk>
Thu, 28 Oct 2010 19:14:38 +0000 (20:14 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Thu, 28 Oct 2010 19:14:38 +0000 (20:14 +0100)
Conflicts:
drivers/video/sh_mobile_hdmi.c

1  2 
arch/arm/mach-shmobile/board-ap4evb.c
arch/sh/boards/mach-ecovec24/setup.c
drivers/video/sh_mobile_hdmi.c
drivers/video/sh_mobile_lcdcfb.c

index 14923989ea0563831f0c176377c0400f603a6416,bb1790784cf768cf585f97d9fa1ba7b9b65cca89..f5d55efda386cd19ca3c4c315ae1725f1dc2a6d6
@@@ -30,7 -30,6 +30,6 @@@
  #include <linux/mtd/mtd.h>
  #include <linux/mtd/partitions.h>
  #include <linux/mtd/physmap.h>
- #include <linux/mmc/host.h>
  #include <linux/mmc/sh_mmcif.h>
  #include <linux/i2c.h>
  #include <linux/i2c/tsc2007.h>
  #include <linux/input/sh_keysc.h>
  #include <linux/usb/r8a66597.h>
  
+ #include <media/sh_mobile_ceu.h>
+ #include <media/sh_mobile_csi2.h>
+ #include <media/soc_camera.h>
  #include <sound/sh_fsi.h>
  
  #include <video/sh_mobile_hdmi.h>
@@@ -238,7 -241,7 +241,7 @@@ static struct platform_device smc911x_d
  /* SH_MMCIF */
  static struct resource sh_mmcif_resources[] = {
        [0] = {
-               .name   = "SH_MMCIF",
+               .name   = "MMCIF",
                .start  = 0xE6BD0000,
                .end    = 0xE6BD00FF,
                .flags  = IORESOURCE_MEM,
@@@ -375,10 -378,40 +378,40 @@@ static struct platform_device usb1_host
        .resource       = usb1_host_resources,
  };
  
+ const static struct fb_videomode ap4evb_lcdc_modes[] = {
+       {
+ #ifdef CONFIG_AP4EVB_QHD
+               .name           = "R63302(QHD)",
+               .xres           = 544,
+               .yres           = 961,
+               .left_margin    = 72,
+               .right_margin   = 600,
+               .hsync_len      = 16,
+               .upper_margin   = 8,
+               .lower_margin   = 8,
+               .vsync_len      = 2,
+               .sync           = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
+ #else
+               .name           = "WVGA Panel",
+               .xres           = 800,
+               .yres           = 480,
+               .left_margin    = 220,
+               .right_margin   = 110,
+               .hsync_len      = 70,
+               .upper_margin   = 20,
+               .lower_margin   = 5,
+               .vsync_len      = 5,
+               .sync           = 0,
+ #endif
+       },
+ };
  static struct sh_mobile_lcdc_info lcdc_info = {
        .ch[0] = {
                .chan = LCDC_CHAN_MAINLCD,
                .bpp = 16,
+               .lcd_cfg = ap4evb_lcdc_modes,
+               .num_cfg = ARRAY_SIZE(ap4evb_lcdc_modes),
        }
  };
  
@@@ -517,27 -550,6 +550,6 @@@ static struct platform_device *qhd_devi
  
  /* FSI */
  #define IRQ_FSI               evt2irq(0x1840)
- #define FSIACKCR      0xE6150018
- static void fsiackcr_init(struct clk *clk)
- {
-       u32 status = __raw_readl(clk->enable_reg);
-       /* use external clock */
-       status &= ~0x000000ff;
-       status |= 0x00000080;
-       __raw_writel(status, clk->enable_reg);
- }
- static struct clk_ops fsiackcr_clk_ops = {
-       .init = fsiackcr_init,
- };
- static struct clk fsiackcr_clk = {
-       .ops            = &fsiackcr_clk_ops,
-       .enable_reg     = (void __iomem *)FSIACKCR,
-       .rate           = 0, /* unknown */
- };
  static struct sh_fsi_platform_info fsi_info = {
        .porta_flags = SH_FSI_BRS_INV |
                       SH_FSI_OUT_SLAVE_MODE |
@@@ -577,26 -589,6 +589,6 @@@ static struct sh_mobile_lcdc_info sh_mo
                .interface_type = RGB24,
                .clock_divider = 1,
                .flags = LCDC_FLAGS_DWPOL,
-               .lcd_cfg = {
-                       .name = "HDMI",
-                       /* So far only 720p is supported */
-                       .xres = 1280,
-                       .yres = 720,
-                       /*
-                        * If left and right margins are not multiples of 8,
-                        * LDHAJR will be adjusted accordingly by the LCDC
-                        * driver. Until we start using EDID, these values
-                        * might have to be adjusted for different monitors.
-                        */
-                       .left_margin = 200,
-                       .right_margin = 88,
-                       .hsync_len = 48,
-                       .upper_margin = 20,
-                       .lower_margin = 5,
-                       .vsync_len = 5,
-                       .pixclock = 13468,
-                       .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
-               },
        }
  };
  
@@@ -608,7 -600,7 +600,7 @@@ static struct resource lcdc1_resources[
                .flags  = IORESOURCE_MEM,
        },
        [1] = {
-               .start  = intcs_evt2irq(0x17a0),
+               .start  = intcs_evt2irq(0x1780),
                .flags  = IORESOURCE_IRQ,
        },
  };
@@@ -689,6 -681,95 +681,95 @@@ static struct platform_device leds_devi
        },
  };
  
+ static struct i2c_board_info imx074_info = {
+       I2C_BOARD_INFO("imx074", 0x1a),
+ };
+ struct soc_camera_link imx074_link = {
+       .bus_id         = 0,
+       .board_info     = &imx074_info,
+       .i2c_adapter_id = 0,
+       .module_name    = "imx074",
+ };
+ static struct platform_device ap4evb_camera = {
+       .name   = "soc-camera-pdrv",
+       .id     = 0,
+       .dev    = {
+               .platform_data = &imx074_link,
+       },
+ };
+ static struct sh_csi2_client_config csi2_clients[] = {
+       {
+               .phy            = SH_CSI2_PHY_MAIN,
+               .lanes          = 3,
+               .channel        = 0,
+               .pdev           = &ap4evb_camera,
+       },
+ };
+ static struct sh_csi2_pdata csi2_info = {
+       .type           = SH_CSI2C,
+       .clients        = csi2_clients,
+       .num_clients    = ARRAY_SIZE(csi2_clients),
+       .flags          = SH_CSI2_ECC | SH_CSI2_CRC,
+ };
+ static struct resource csi2_resources[] = {
+       [0] = {
+               .name   = "CSI2",
+               .start  = 0xffc90000,
+               .end    = 0xffc90fff,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = intcs_evt2irq(0x17a0),
+               .flags  = IORESOURCE_IRQ,
+       },
+ };
+ static struct platform_device csi2_device = {
+       .name   = "sh-mobile-csi2",
+       .id     = 0,
+       .num_resources  = ARRAY_SIZE(csi2_resources),
+       .resource       = csi2_resources,
+       .dev    = {
+               .platform_data = &csi2_info,
+       },
+ };
+ static struct sh_mobile_ceu_info sh_mobile_ceu_info = {
+       .flags = SH_CEU_FLAG_USE_8BIT_BUS,
+       .csi2_dev = &csi2_device.dev,
+ };
+ static struct resource ceu_resources[] = {
+       [0] = {
+               .name   = "CEU",
+               .start  = 0xfe910000,
+               .end    = 0xfe91009f,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = intcs_evt2irq(0x880),
+               .flags  = IORESOURCE_IRQ,
+       },
+       [2] = {
+               /* place holder for contiguous memory */
+       },
+ };
+ static struct platform_device ceu_device = {
+       .name           = "sh_mobile_ceu",
+       .id             = 0, /* "ceu0" clock */
+       .num_resources  = ARRAY_SIZE(ceu_resources),
+       .resource       = ceu_resources,
+       .dev    = {
+               .platform_data  = &sh_mobile_ceu_info,
+       },
+ };
  static struct platform_device *ap4evb_devices[] __initdata = {
        &leds_device,
        &nor_flash_device,
        &lcdc1_device,
        &lcdc_device,
        &hdmi_device,
+       &csi2_device,
+       &ceu_device,
+       &ap4evb_camera,
  };
  
  static int __init hdmi_init_pm_clock(void)
                goto out;
        }
  
-       ret = clk_set_parent(&pllc2_clk, &dv_clki_div2_clk);
+       ret = clk_set_parent(&sh7372_pllc2_clk, &sh7372_dv_clki_div2_clk);
        if (ret < 0) {
-               pr_err("Cannot set PLLC2 parent: %d, %d users\n", ret, pllc2_clk.usecount);
+               pr_err("Cannot set PLLC2 parent: %d, %d users\n", ret, sh7372_pllc2_clk.usecount);
                goto out;
        }
  
-       pr_debug("PLLC2 initial frequency %lu\n", clk_get_rate(&pllc2_clk));
+       pr_debug("PLLC2 initial frequency %lu\n", clk_get_rate(&sh7372_pllc2_clk));
  
-       rate = clk_round_rate(&pllc2_clk, 594000000);
+       rate = clk_round_rate(&sh7372_pllc2_clk, 594000000);
        if (rate < 0) {
                pr_err("Cannot get suitable rate: %ld\n", rate);
                ret = rate;
                goto out;
        }
  
-       ret = clk_set_rate(&pllc2_clk, rate);
+       ret = clk_set_rate(&sh7372_pllc2_clk, rate);
        if (ret < 0) {
                pr_err("Cannot set rate %ld: %d\n", rate, ret);
                goto out;
  
        pr_debug("PLLC2 set frequency %lu\n", rate);
  
-       ret = clk_set_parent(hdmi_ick, &pllc2_clk);
+       ret = clk_set_parent(hdmi_ick, &sh7372_pllc2_clk);
        if (ret < 0) {
                pr_err("Cannot set HDMI parent: %d\n", ret);
                goto out;
@@@ -752,11 -836,51 +836,51 @@@ out
  
  device_initcall(hdmi_init_pm_clock);
  
+ #define FSIACK_DUMMY_RATE 48000
+ static int __init fsi_init_pm_clock(void)
+ {
+       struct clk *fsia_ick;
+       int ret;
+       /*
+        * FSIACK is connected to AK4642,
+        * and the rate is depend on playing sound rate.
+        * So, set dummy rate (= 48k) here
+        */
+       ret = clk_set_rate(&sh7372_fsiack_clk, FSIACK_DUMMY_RATE);
+       if (ret < 0) {
+               pr_err("Cannot set FSIACK dummy rate: %d\n", ret);
+               return ret;
+       }
+       fsia_ick = clk_get(&fsi_device.dev, "icka");
+       if (IS_ERR(fsia_ick)) {
+               ret = PTR_ERR(fsia_ick);
+               pr_err("Cannot get FSI ICK: %d\n", ret);
+               return ret;
+       }
+       ret = clk_set_parent(fsia_ick, &sh7372_fsiack_clk);
+       if (ret < 0) {
+               pr_err("Cannot set FSI-A parent: %d\n", ret);
+               goto out;
+       }
+       ret = clk_set_rate(fsia_ick, FSIACK_DUMMY_RATE);
+       if (ret < 0)
+               pr_err("Cannot set FSI-A rate: %d\n", ret);
+ out:
+       clk_put(fsia_ick);
+       return ret;
+ }
+ device_initcall(fsi_init_pm_clock);
  /*
   * FIXME !!
   *
   * gpio_no_direction
-  * gpio_pull_up
   * are quick_hack.
   *
   * current gpio frame work doesn't have
@@@ -768,49 -892,37 +892,37 @@@ static void __init gpio_no_direction(u3
        __raw_writeb(0x00, addr);
  }
  
- static void __init gpio_pull_up(u32 addr)
- {
-       u8 data = __raw_readb(addr);
-       data &= 0x0F;
-       data |= 0xC0;
-       __raw_writeb(data, addr);
- }
  /* TouchScreen */
+ #ifdef CONFIG_AP4EVB_QHD
+ # define GPIO_TSC_IRQ GPIO_FN_IRQ28_123
+ # define GPIO_TSC_PORT        GPIO_PORT123
+ #else /* WVGA */
+ # define GPIO_TSC_IRQ GPIO_FN_IRQ7_40
+ # define GPIO_TSC_PORT        GPIO_PORT40
+ #endif
  #define IRQ28 evt2irq(0x3380) /* IRQ28A */
  #define IRQ7  evt2irq(0x02e0) /* IRQ7A */
  static int ts_get_pendown_state(void)
  {
-       int val1, val2;
+       int val;
  
-       gpio_free(GPIO_FN_IRQ28_123);
-       gpio_free(GPIO_FN_IRQ7_40);
+       gpio_free(GPIO_TSC_IRQ);
  
-       gpio_request(GPIO_PORT123, NULL);
-       gpio_request(GPIO_PORT40, NULL);
+       gpio_request(GPIO_TSC_PORT, NULL);
  
-       gpio_direction_input(GPIO_PORT123);
-       gpio_direction_input(GPIO_PORT40);
+       gpio_direction_input(GPIO_TSC_PORT);
  
-       val1 = gpio_get_value(GPIO_PORT123);
-       val2 = gpio_get_value(GPIO_PORT40);
+       val = gpio_get_value(GPIO_TSC_PORT);
  
-       gpio_request(GPIO_FN_IRQ28_123, NULL);  /* for QHD */
-       gpio_request(GPIO_FN_IRQ7_40, NULL);    /* for WVGA */
+       gpio_request(GPIO_TSC_IRQ, NULL);
  
-       return val1 ^ val2;
+       return !val;
  }
  
- #define PORT40CR      0xE6051028
- #define PORT123CR     0xE605007B
  static int ts_init(void)
  {
-       gpio_request(GPIO_FN_IRQ28_123, NULL);  /* for QHD */
-       gpio_request(GPIO_FN_IRQ7_40, NULL);    /* for WVGA */
-       gpio_pull_up(PORT40CR);
-       gpio_pull_up(PORT123CR);
+       gpio_request(GPIO_TSC_IRQ, NULL);
  
        return 0;
  }
@@@ -955,14 -1067,6 +1067,6 @@@ static void __init ap4evb_init(void
                clk_put(clk);
        }
  
-       /* change parent of FSI A */
-       clk = clk_get(NULL, "fsia_clk");
-       if (!IS_ERR(clk)) {
-               clk_register(&fsiackcr_clk);
-               clk_set_parent(clk, &fsiackcr_clk);
-               clk_put(clk);
-       }
        /*
         * set irq priority, to avoid sound chopping
         * when NFS rootfs is used
                                ARRAY_SIZE(i2c1_devices));
  
  #ifdef CONFIG_AP4EVB_QHD
        /*
-        * QHD
+        * For QHD Panel (MIPI-DSI, CONFIG_AP4EVB_QHD=y) and
+        * IRQ28 for Touch Panel, set dip switches S3, S43 as OFF, ON.
         */
  
        /* enable KEYSC */
        lcdc_info.ch[0].interface_type          = RGB24;
        lcdc_info.ch[0].clock_divider           = 1;
        lcdc_info.ch[0].flags                   = LCDC_FLAGS_DWPOL;
-       lcdc_info.ch[0].lcd_cfg.name            = "R63302(QHD)";
-       lcdc_info.ch[0].lcd_cfg.xres            = 544;
-       lcdc_info.ch[0].lcd_cfg.yres            = 961;
-       lcdc_info.ch[0].lcd_cfg.left_margin     = 72;
-       lcdc_info.ch[0].lcd_cfg.right_margin    = 600;
-       lcdc_info.ch[0].lcd_cfg.hsync_len       = 16;
-       lcdc_info.ch[0].lcd_cfg.upper_margin    = 8;
-       lcdc_info.ch[0].lcd_cfg.lower_margin    = 8;
-       lcdc_info.ch[0].lcd_cfg.vsync_len       = 2;
-       lcdc_info.ch[0].lcd_cfg.sync            = FB_SYNC_VERT_HIGH_ACT |
-                                                 FB_SYNC_HOR_HIGH_ACT;
        lcdc_info.ch[0].lcd_size_cfg.width      = 44;
        lcdc_info.ch[0].lcd_size_cfg.height     = 79;
  
  
  #else
        /*
-        * WVGA
+        * For WVGA Panel (18-bit RGB, CONFIG_AP4EVB_WVGA=y) and
+        * IRQ7 for Touch Panel, set dip switches S3, S43 to ON, OFF.
         */
        gpio_request(GPIO_FN_LCDD17,   NULL);
        gpio_request(GPIO_FN_LCDD16,   NULL);
        gpio_request(GPIO_FN_LCDD15,   NULL);
        lcdc_info.ch[0].interface_type          = RGB18;
        lcdc_info.ch[0].clock_divider           = 2;
        lcdc_info.ch[0].flags                   = 0;
-       lcdc_info.ch[0].lcd_cfg.name            = "WVGA Panel";
-       lcdc_info.ch[0].lcd_cfg.xres            = 800;
-       lcdc_info.ch[0].lcd_cfg.yres            = 480;
-       lcdc_info.ch[0].lcd_cfg.left_margin     = 220;
-       lcdc_info.ch[0].lcd_cfg.right_margin    = 110;
-       lcdc_info.ch[0].lcd_cfg.hsync_len       = 70;
-       lcdc_info.ch[0].lcd_cfg.upper_margin    = 20;
-       lcdc_info.ch[0].lcd_cfg.lower_margin    = 5;
-       lcdc_info.ch[0].lcd_cfg.vsync_len       = 5;
-       lcdc_info.ch[0].lcd_cfg.sync            = 0;
        lcdc_info.ch[0].lcd_size_cfg.width      = 152;
        lcdc_info.ch[0].lcd_size_cfg.height     = 91;
  
        i2c_register_board_info(0, &tsc_device, 1);
  #endif /* CONFIG_AP4EVB_QHD */
  
+       /* CEU */
+       /*
+        * TODO: reserve memory for V4L2 DMA buffers, when a suitable API
+        * becomes available
+        */
+       /* MIPI-CSI stuff */
+       gpio_request(GPIO_FN_VIO_CKO, NULL);
+       clk = clk_get(NULL, "vck1_clk");
+       if (!IS_ERR(clk)) {
+               clk_set_rate(clk, clk_round_rate(clk, 13000000));
+               clk_enable(clk);
+               clk_put(clk);
+       }
        sh7372_add_standard_devices();
  
        /* HDMI */
@@@ -1097,7 -1201,7 +1201,7 @@@ static void __init ap4evb_timer_init(vo
        shmobile_timer.init();
  
        /* External clock source */
-       clk_set_rate(&dv_clki_clk, 27000000);
+       clk_set_rate(&sh7372_dv_clki_clk, 27000000);
  }
  
  static struct sys_timer ap4evb_timer = {
  };
  
  MACHINE_START(AP4EVB, "ap4evb")
 -      .phys_io        = 0xe6000000,
 -      .io_pg_offst    = ((0xe6000000) >> 18) & 0xfffc,
        .map_io         = ap4evb_map_io,
        .init_irq       = sh7372_init_irq,
        .init_machine   = ap4evb_init,
index 71a3368ab1fc00837ab9161298637f999fb8bed5,feadf5dada773b12c34ba988c562c55a7fbd6cbe..0161deb770ccbe332b2117d35af5176975f83d3d
@@@ -231,14 -231,41 +231,41 @@@ static struct platform_device usb1_comm
  };
  
  /* LCDC */
+ const static struct fb_videomode ecovec_lcd_modes[] = {
+       {
+               .name           = "Panel",
+               .xres           = 800,
+               .yres           = 480,
+               .left_margin    = 220,
+               .right_margin   = 110,
+               .hsync_len      = 70,
+               .upper_margin   = 20,
+               .lower_margin   = 5,
+               .vsync_len      = 5,
+               .sync           = 0, /* hsync and vsync are active low */
+       },
+ };
+ const static struct fb_videomode ecovec_dvi_modes[] = {
+       {
+               .name           = "DVI",
+               .xres           = 1280,
+               .yres           = 720,
+               .left_margin    = 220,
+               .right_margin   = 110,
+               .hsync_len      = 40,
+               .upper_margin   = 20,
+               .lower_margin   = 5,
+               .vsync_len      = 5,
+               .sync = 0, /* hsync and vsync are active low */
+       },
+ };
  static struct sh_mobile_lcdc_info lcdc_info = {
        .ch[0] = {
                .interface_type = RGB18,
                .chan = LCDC_CHAN_MAINLCD,
                .bpp = 16,
-               .lcd_cfg = {
-                       .sync = 0, /* hsync and vsync are active low */
-               },
                .lcd_size_cfg = { /* 7.0 inch */
                        .width = 152,
                        .height = 91,
@@@ -1079,33 -1106,18 +1106,18 @@@ static int __init arch_setup(void
        if (gpio_get_value(GPIO_PTE6)) {
                /* DVI */
                lcdc_info.clock_source                  = LCDC_CLK_EXTERNAL;
-               lcdc_info.ch[0].clock_divider           = 1,
-               lcdc_info.ch[0].lcd_cfg.name            = "DVI";
-               lcdc_info.ch[0].lcd_cfg.xres            = 1280;
-               lcdc_info.ch[0].lcd_cfg.yres            = 720;
-               lcdc_info.ch[0].lcd_cfg.left_margin     = 220;
-               lcdc_info.ch[0].lcd_cfg.right_margin    = 110;
-               lcdc_info.ch[0].lcd_cfg.hsync_len       = 40;
-               lcdc_info.ch[0].lcd_cfg.upper_margin    = 20;
-               lcdc_info.ch[0].lcd_cfg.lower_margin    = 5;
-               lcdc_info.ch[0].lcd_cfg.vsync_len       = 5;
+               lcdc_info.ch[0].clock_divider           = 1;
+               lcdc_info.ch[0].lcd_cfg                 = ecovec_dvi_modes;
+               lcdc_info.ch[0].num_cfg                 = ARRAY_SIZE(ecovec_dvi_modes);
  
                gpio_set_value(GPIO_PTA2, 1);
                gpio_set_value(GPIO_PTU1, 1);
        } else {
                /* Panel */
                lcdc_info.clock_source                  = LCDC_CLK_PERIPHERAL;
-               lcdc_info.ch[0].clock_divider           = 2,
-               lcdc_info.ch[0].lcd_cfg.name            = "Panel";
-               lcdc_info.ch[0].lcd_cfg.xres            = 800;
-               lcdc_info.ch[0].lcd_cfg.yres            = 480;
-               lcdc_info.ch[0].lcd_cfg.left_margin     = 220;
-               lcdc_info.ch[0].lcd_cfg.right_margin    = 110;
-               lcdc_info.ch[0].lcd_cfg.hsync_len       = 70;
-               lcdc_info.ch[0].lcd_cfg.upper_margin    = 20;
-               lcdc_info.ch[0].lcd_cfg.lower_margin    = 5;
-               lcdc_info.ch[0].lcd_cfg.vsync_len       = 5;
+               lcdc_info.ch[0].clock_divider           = 2;
+               lcdc_info.ch[0].lcd_cfg                 = ecovec_lcd_modes;
+               lcdc_info.ch[0].num_cfg                 = ARRAY_SIZE(ecovec_lcd_modes);
  
                gpio_set_value(GPIO_PTR1, 1);
  
  
        /* set SPU2 clock to 83.4 MHz */
        clk = clk_get(NULL, "spu_clk");
 -      if (clk) {
 +      if (!IS_ERR(clk)) {
                clk_set_rate(clk, clk_round_rate(clk, 83333333));
                clk_put(clk);
        }
  
        /* change parent of FSI B */
        clk = clk_get(NULL, "fsib_clk");
 -      if (clk) {
 +      if (!IS_ERR(clk)) {
                clk_register(&fsimckb_clk);
                clk_set_parent(clk, &fsimckb_clk);
                clk_set_rate(clk, 11000);
  
        /* set VPU clock to 166 MHz */
        clk = clk_get(NULL, "vpu_clk");
 -      if (clk) {
 +      if (!IS_ERR(clk)) {
                clk_set_rate(clk, clk_round_rate(clk, 166000000));
                clk_put(clk);
        }
index ef989d94511c1fccdae24fbb1482ab986340f7f1,f0ff2848700c7a96b942dc115101423d3d03a9ab..55b3077ff6fff567e2fc3c1831b1255498cb1c07
  #include <linux/slab.h>
  #include <linux/types.h>
  #include <linux/workqueue.h>
 +#include <sound/soc-dapm.h>
 +#include <sound/initval.h>
  
  #include <video/sh_mobile_hdmi.h>
  #include <video/sh_mobile_lcdc.h>
  
+ #include "sh_mobile_lcdcfb.h"
  #define HDMI_SYSTEM_CTRL                      0x00 /* System control */
  #define HDMI_L_R_DATA_SWAP_CTRL_RPKT          0x01 /* L/R data swap control,
                                                        bits 19..16 of 20-bit N for Audio Clock Regeneration packet */
@@@ -206,12 -206,15 +208,15 @@@ enum hotplug_state 
  
  struct sh_hdmi {
        void __iomem *base;
-       enum hotplug_state hp_state;
+       enum hotplug_state hp_state;    /* hot-plug status */
+       bool preprogrammed_mode;        /* use a pre-programmed VIC or the external mode */
        struct clk *hdmi_clk;
        struct device *dev;
        struct fb_info *info;
+       struct mutex mutex;             /* Protect the info pointer */
        struct delayed_work edid_work;
        struct fb_var_screeninfo var;
+       struct fb_monspecs monspec;
  };
  
  static void hdmi_write(struct sh_hdmi *hdmi, u8 data, u8 reg)
@@@ -224,60 -227,8 +229,60 @@@ static u8 hdmi_read(struct sh_hdmi *hdm
        return ioread8(hdmi->base + reg);
  }
  
 +/*
 + *    HDMI sound
 + */
 +static unsigned int sh_hdmi_snd_read(struct snd_soc_codec *codec,
 +                                   unsigned int reg)
 +{
 +      struct sh_hdmi *hdmi = snd_soc_codec_get_drvdata(codec);
 +
 +      return hdmi_read(hdmi, reg);
 +}
 +
 +static int sh_hdmi_snd_write(struct snd_soc_codec *codec,
 +                           unsigned int reg,
 +                           unsigned int value)
 +{
 +      struct sh_hdmi *hdmi = snd_soc_codec_get_drvdata(codec);
 +
 +      hdmi_write(hdmi, value, reg);
 +      return 0;
 +}
 +
 +static struct snd_soc_dai_driver sh_hdmi_dai = {
 +      .name = "sh_mobile_hdmi-hifi",
 +      .playback = {
 +              .stream_name = "Playback",
 +              .channels_min = 2,
 +              .channels_max = 8,
 +              .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100  |
 +                       SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200  |
 +                       SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
 +                       SNDRV_PCM_RATE_192000,
 +              .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
 +      },
 +};
 +
 +static int sh_hdmi_snd_probe(struct snd_soc_codec *codec)
 +{
 +      dev_info(codec->dev, "SH Mobile HDMI Audio Codec");
 +
 +      return 0;
 +}
 +
 +static struct snd_soc_codec_driver soc_codec_dev_sh_hdmi = {
 +      .probe          = sh_hdmi_snd_probe,
 +      .read           = sh_hdmi_snd_read,
 +      .write          = sh_hdmi_snd_write,
 +};
 +
 +/*
 + *    HDMI video
 + */
 +
  /* External video parameter settings */
- static void hdmi_external_video_param(struct sh_hdmi *hdmi)
+ static void sh_hdmi_external_video_param(struct sh_hdmi *hdmi)
  {
        struct fb_var_screeninfo *var = &hdmi->var;
        u16 htotal, hblank, hdelay, vtotal, vblank, vdelay, voffset;
        if (var->sync & FB_SYNC_VERT_HIGH_ACT)
                sync |= 8;
  
-       pr_debug("H: %u, %u, %u, %u; V: %u, %u, %u, %u; sync 0x%x\n",
-                htotal, hblank, hdelay, var->hsync_len,
-                vtotal, vblank, vdelay, var->vsync_len, sync);
+       dev_dbg(hdmi->dev, "H: %u, %u, %u, %u; V: %u, %u, %u, %u; sync 0x%x\n",
+               htotal, hblank, hdelay, var->hsync_len,
+               vtotal, vblank, vdelay, var->vsync_len, sync);
  
        hdmi_write(hdmi, sync | (voffset << 4), HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS);
  
  
        hdmi_write(hdmi, var->vsync_len, HDMI_EXTERNAL_V_DURATION);
  
-       /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for manual mode */
+       /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for external mode */
+       if (!hdmi->preprogrammed_mode)
+               hdmi_write(hdmi, sync | 1 | (voffset << 4),
+                          HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS);
  }
  
  /**
@@@ -372,9 -326,6 +380,9 @@@ static void sh_hdmi_video_config(struc
   */
  static void sh_hdmi_audio_config(struct sh_hdmi *hdmi)
  {
 +      u8 data;
 +      struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
 +
        /*
         * [7:4] L/R data swap control
         * [3:0] appropriate N[19:16]
         * [6:5] set required down sampling rate if required
         * [4:3] set required audio source
         */
 -      hdmi_write(hdmi, 0x00, HDMI_AUDIO_SETTING_1);
 +      switch (pdata->flags & HDMI_SND_SRC_MASK) {
 +      default:
 +              /* fall through */
 +      case HDMI_SND_SRC_I2S:
 +              data = 0x0 << 3;
 +              break;
 +      case HDMI_SND_SRC_SPDIF:
 +              data = 0x1 << 3;
 +              break;
 +      case HDMI_SND_SRC_DSD:
 +              data = 0x2 << 3;
 +              break;
 +      case HDMI_SND_SRC_HBR:
 +              data = 0x3 << 3;
 +              break;
 +      }
 +      hdmi_write(hdmi, data, HDMI_AUDIO_SETTING_1);
  
        /* [3:0] set sending channel number for channel status */
        hdmi_write(hdmi, 0x40, HDMI_AUDIO_SETTING_2);
  }
  
  /**
-  * sh_hdmi_phy_config()
+  * sh_hdmi_phy_config() - configure the HDMI PHY for the used video mode
   */
  static void sh_hdmi_phy_config(struct sh_hdmi *hdmi)
  {
-       /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */
-       hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
-       hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
-       hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
-       /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */
-       hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
-       hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
-       hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
-       hdmi_write(hdmi, 0x0E, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
-       hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
-       hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       if (hdmi->var.yres > 480) {
+               /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */
+               /*
+                * [1:0]        Speed_A
+                * [3:2]        Speed_B
+                * [4]          PLLA_Bypass
+                * [6]          DRV_TEST_EN
+                * [7]          DRV_TEST_IN
+                */
+               hdmi_write(hdmi, 0x0f, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
+               /* PLLB_CONFIG[17], PLLA_CONFIG[17] - not in PHY datasheet */
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
+               /*
+                * [2:0]        BGR_I_OFFSET
+                * [6:4]        BGR_V_OFFSET
+                */
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
+               /* PLLA_CONFIG[7:0]: VCO gain, VCO offset, LPF resistance[0] */
+               hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
+               /*
+                * PLLA_CONFIG[15:8]: regulator voltage[0], CP current,
+                * LPF capacitance, LPF resistance[1]
+                */
+               hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
+               /* PLLB_CONFIG[7:0]: LPF resistance[0], VCO offset, VCO gain */
+               hdmi_write(hdmi, 0x4A, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
+               /*
+                * PLLB_CONFIG[15:8]: regulator voltage[0], CP current,
+                * LPF capacitance, LPF resistance[1]
+                */
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
+               /* DRV_CONFIG, PE_CONFIG */
+               hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
+               /*
+                * [2:0]        AMON_SEL (4 == LPF voltage)
+                * [4]          PLLA_CONFIG[16]
+                * [5]          PLLB_CONFIG[16]
+                */
+               hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       } else {
+               /* for 480p8bit 27MHz */
+               hdmi_write(hdmi, 0x19, HDMI_SLIPHDMIT_PARAM_SETTINGS_1);
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2);
+               hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3);
+               hdmi_write(hdmi, 0x44, HDMI_SLIPHDMIT_PARAM_SETTINGS_5);
+               hdmi_write(hdmi, 0x32, HDMI_SLIPHDMIT_PARAM_SETTINGS_6);
+               hdmi_write(hdmi, 0x48, HDMI_SLIPHDMIT_PARAM_SETTINGS_7);
+               hdmi_write(hdmi, 0x0F, HDMI_SLIPHDMIT_PARAM_SETTINGS_8);
+               hdmi_write(hdmi, 0x20, HDMI_SLIPHDMIT_PARAM_SETTINGS_9);
+               hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10);
+       }
  }
  
  /**
   */
  static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi)
  {
+       u8 vic;
        /* AVI InfoFrame */
        hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_INDEX);
  
        hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB1);
  
        /*
-        * C = No Data
-        * M = 16:9 Picture Aspect Ratio
-        * R = Same as picture aspect ratio
+        * [7:6] C = Colorimetry: no data
+        * [5:4] M = 2: 16:9, 1: 4:3 Picture Aspect Ratio
+        * [3:0] R = 8: Active Frame Aspect Ratio: same as picture aspect ratio
         */
        hdmi_write(hdmi, 0x28, HDMI_CTRL_PKT_BUF_ACCESS_PB2);
  
  
        /*
         * VIC = 1280 x 720p: ignored if external config is used
-        * Send 2 for 720 x 480p, 16 for 1080p
+        * Send 2 for 720 x 480p, 16 for 1080p, ignored in external mode
         */
-       hdmi_write(hdmi, 4, HDMI_CTRL_PKT_BUF_ACCESS_PB4);
+       if (hdmi->var.yres == 1080 && hdmi->var.xres == 1920)
+               vic = 16;
+       else if (hdmi->var.yres == 480 && hdmi->var.xres == 720)
+               vic = 2;
+       else
+               vic = 4;
+       hdmi_write(hdmi, vic, HDMI_CTRL_PKT_BUF_ACCESS_PB4);
  
        /* PR = No Repetition */
        hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB5);
@@@ -591,100 -574,6 +647,6 @@@ static void sh_hdmi_audio_infoframe_set
        hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB10);
  }
  
- /**
-  * sh_hdmi_gamut_metadata_setup() - Gamut Metadata Packet of CONTROL PACKET
-  */
- static void sh_hdmi_gamut_metadata_setup(struct sh_hdmi *hdmi)
- {
-       int i;
-       /* Gamut Metadata Packet */
-       hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_INDEX);
-       /* Packet Type = 0x0A */
-       hdmi_write(hdmi, 0x0A, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* Gamut Packet is not used, so default value */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Gamut Packet is not used, so default value */
-       hdmi_write(hdmi, 0x10, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-       /* GBD bytes 0 through 27 */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0_63H - PB27_7EH */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
- }
- /**
-  * sh_hdmi_acp_setup() - Audio Content Protection Packet (ACP)
-  */
- static void sh_hdmi_acp_setup(struct sh_hdmi *hdmi)
- {
-       int i;
-       /* Audio Content Protection Packet (ACP) */
-       hdmi_write(hdmi, 0x01, HDMI_CTRL_PKT_BUF_INDEX);
-       /* Packet Type = 0x04 */
-       hdmi_write(hdmi, 0x04, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* ACP_Type */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-       /* GBD bytes 0 through 27 */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
- }
- /**
-  * sh_hdmi_isrc1_setup() - ISRC1 Packet
-  */
- static void sh_hdmi_isrc1_setup(struct sh_hdmi *hdmi)
- {
-       int i;
-       /* ISRC1 Packet */
-       hdmi_write(hdmi, 0x02, HDMI_CTRL_PKT_BUF_INDEX);
-       /* Packet Type = 0x05 */
-       hdmi_write(hdmi, 0x05, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* ISRC_Cont, ISRC_Valid, Reserved (0), ISRC_Status */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-       /* PB0 UPC_EAN_ISRC_0-15 */
-       /* Bytes PB16-PB27 shall be set to a value of 0. */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
- }
- /**
-  * sh_hdmi_isrc2_setup() - ISRC2 Packet
-  */
- static void sh_hdmi_isrc2_setup(struct sh_hdmi *hdmi)
- {
-       int i;
-       /* ISRC2 Packet */
-       hdmi_write(hdmi, 0x03, HDMI_CTRL_PKT_BUF_INDEX);
-       /* HB0 Packet Type = 0x06 */
-       hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_ACCESS_HB0);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB1);
-       /* Reserved (0) */
-       hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_HB2);
-       /* PB0 UPC_EAN_ISRC_16-31 */
-       /* Bytes PB16-PB27 shall be set to a value of 0. */
-       for (i = 0; i <= 27; i++)
-               /* HDMI_CTRL_PKT_BUF_ACCESS_PB0 - PB27 */
-               hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB0 + i);
- }
  /**
   * sh_hdmi_configure() - Initialise HDMI for output
   */
@@@ -705,18 -594,6 +667,6 @@@ static void sh_hdmi_configure(struct sh
        /* Audio InfoFrame */
        sh_hdmi_audio_infoframe_setup(hdmi);
  
-       /* Gamut Metadata packet */
-       sh_hdmi_gamut_metadata_setup(hdmi);
-       /* Audio Content Protection (ACP) Packet */
-       sh_hdmi_acp_setup(hdmi);
-       /* ISRC1 Packet */
-       sh_hdmi_isrc1_setup(hdmi);
-       /* ISRC2 Packet */
-       sh_hdmi_isrc2_setup(hdmi);
        /*
         * Control packet auto send with VSYNC control: auto send
         * General control, Gamut metadata, ISRC, and ACP packets
        hdmi_write(hdmi, 0x40, HDMI_SYSTEM_CTRL);
  }
  
- static void sh_hdmi_read_edid(struct sh_hdmi *hdmi)
+ static unsigned long sh_hdmi_rate_error(struct sh_hdmi *hdmi,
+                                       const struct fb_videomode *mode)
  {
-       struct fb_var_screeninfo *var = &hdmi->var;
-       struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
-       struct fb_videomode *lcd_cfg = &pdata->lcd_chan->lcd_cfg;
-       unsigned long height = var->height, width = var->width;
-       int i;
+       long target = PICOS2KHZ(mode->pixclock) * 1000,
+               rate = clk_round_rate(hdmi->hdmi_clk, target);
+       unsigned long rate_error = rate > 0 ? abs(rate - target) : ULONG_MAX;
+       dev_dbg(hdmi->dev, "%u-%u-%u-%u x %u-%u-%u-%u\n",
+               mode->left_margin, mode->xres,
+               mode->right_margin, mode->hsync_len,
+               mode->upper_margin, mode->yres,
+               mode->lower_margin, mode->vsync_len);
+       dev_dbg(hdmi->dev, "\t@%lu(+/-%lu)Hz, e=%lu / 1000, r=%uHz\n", target,
+                rate_error, rate_error ? 10000 / (10 * target / rate_error) : 0,
+                mode->refresh);
+       return rate_error;
+ }
+ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi)
+ {
+       struct fb_var_screeninfo tmpvar;
+       struct fb_var_screeninfo *var = &tmpvar;
+       const struct fb_videomode *mode, *found = NULL;
+       struct fb_info *info = hdmi->info;
+       struct fb_modelist *modelist = NULL;
+       unsigned int f_width = 0, f_height = 0, f_refresh = 0;
+       unsigned long found_rate_error = ULONG_MAX; /* silly compiler... */
+       bool exact_match = false;
        u8 edid[128];
+       char *forced;
+       int i;
  
        /* Read EDID */
-       pr_debug("Read back EDID code:");
+       dev_dbg(hdmi->dev, "Read back EDID code:");
        for (i = 0; i < 128; i++) {
                edid[i] = hdmi_read(hdmi, HDMI_EDID_KSV_FIFO_ACCESS_WINDOW);
  #ifdef DEBUG
  #ifdef DEBUG
        printk(KERN_CONT "\n");
  #endif
-       fb_parse_edid(edid, var);
-       pr_debug("%u-%u-%u-%u x %u-%u-%u-%u @ %lu kHz monitor detected\n",
-                var->left_margin, var->xres, var->right_margin, var->hsync_len,
-                var->upper_margin, var->yres, var->lower_margin, var->vsync_len,
-                PICOS2KHZ(var->pixclock));
-       /* FIXME: Use user-provided configuration instead of EDID */
-       var->width              = width;
-       var->xres               = lcd_cfg->xres;
-       var->xres_virtual       = lcd_cfg->xres;
-       var->left_margin        = lcd_cfg->left_margin;
-       var->right_margin       = lcd_cfg->right_margin;
-       var->hsync_len          = lcd_cfg->hsync_len;
-       var->height             = height;
-       var->yres               = lcd_cfg->yres;
-       var->yres_virtual       = lcd_cfg->yres * 2;
-       var->upper_margin       = lcd_cfg->upper_margin;
-       var->lower_margin       = lcd_cfg->lower_margin;
-       var->vsync_len          = lcd_cfg->vsync_len;
-       var->sync               = lcd_cfg->sync;
-       var->pixclock           = lcd_cfg->pixclock;
-       hdmi_external_video_param(hdmi);
+       fb_edid_to_monspecs(edid, &hdmi->monspec);
+       fb_get_options("sh_mobile_lcdc", &forced);
+       if (forced && *forced) {
+               /* Only primitive parsing so far */
+               i = sscanf(forced, "%ux%u@%u",
+                          &f_width, &f_height, &f_refresh);
+               if (i < 2) {
+                       f_width = 0;
+                       f_height = 0;
+               }
+               dev_dbg(hdmi->dev, "Forced mode %ux%u@%uHz\n",
+                       f_width, f_height, f_refresh);
+       }
+       /* Walk monitor modes to find the best or the exact match */
+       for (i = 0, mode = hdmi->monspec.modedb;
+            f_width && f_height && i < hdmi->monspec.modedb_len && !exact_match;
+            i++, mode++) {
+               unsigned long rate_error = sh_hdmi_rate_error(hdmi, mode);
+               /* No interest in unmatching modes */
+               if (f_width != mode->xres || f_height != mode->yres)
+                       continue;
+               if (f_refresh == mode->refresh || (!f_refresh && !rate_error))
+                       /*
+                        * Exact match if either the refresh rate matches or it
+                        * hasn't been specified and we've found a mode, for
+                        * which we can configure the clock precisely
+                        */
+                       exact_match = true;
+               else if (found && found_rate_error <= rate_error)
+                       /*
+                        * We otherwise search for the closest matching clock
+                        * rate - either if no refresh rate has been specified
+                        * or we cannot find an exactly matching one
+                        */
+                       continue;
+               /* Check if supported: sufficient fb memory, supported clock-rate */
+               fb_videomode_to_var(var, mode);
+               if (info && info->fbops->fb_check_var &&
+                   info->fbops->fb_check_var(var, info)) {
+                       exact_match = false;
+                       continue;
+               }
+               found = mode;
+               found_rate_error = rate_error;
+       }
+       /*
+        * TODO 1: if no ->info is present, postpone running the config until
+        * after ->info first gets registered.
+        * TODO 2: consider registering the HDMI platform device from the LCDC
+        * driver, and passing ->info with HDMI platform data.
+        */
+       if (info && !found) {
+               modelist = hdmi->info->modelist.next &&
+                       !list_empty(&hdmi->info->modelist) ?
+                       list_entry(hdmi->info->modelist.next,
+                                  struct fb_modelist, list) :
+                       NULL;
+               if (modelist) {
+                       found = &modelist->mode;
+                       found_rate_error = sh_hdmi_rate_error(hdmi, found);
+               }
+       }
+       /* No cookie today */
+       if (!found)
+               return -ENXIO;
+       dev_info(hdmi->dev, "Using %s mode %ux%u@%uHz (%luHz), clock error %luHz\n",
+                modelist ? "default" : "EDID", found->xres, found->yres,
+                found->refresh, PICOS2KHZ(found->pixclock) * 1000, found_rate_error);
+       if ((found->xres == 720 && found->yres == 480) ||
+           (found->xres == 1280 && found->yres == 720) ||
+           (found->xres == 1920 && found->yres == 1080))
+               hdmi->preprogrammed_mode = true;
+       else
+               hdmi->preprogrammed_mode = false;
+       fb_videomode_to_var(&hdmi->var, found);
+       sh_hdmi_external_video_param(hdmi);
+       return 0;
  }
  
  static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id)
        hdmi_write(hdmi, 0xFF, HDMI_INTERRUPT_STATUS_2);
  
        if (printk_ratelimit())
-               pr_debug("IRQ #%d: Status #1: 0x%x & 0x%x, #2: 0x%x & 0x%x\n",
-                        irq, status1, mask1, status2, mask2);
+               dev_dbg(hdmi->dev, "IRQ #%d: Status #1: 0x%x & 0x%x, #2: 0x%x & 0x%x\n",
+                       irq, status1, mask1, status2, mask2);
  
        if (!((status1 & mask1) | (status2 & mask2))) {
                return IRQ_NONE;
                udelay(500);
  
                msens = hdmi_read(hdmi, HDMI_HOT_PLUG_MSENS_STATUS);
-               pr_debug("MSENS 0x%x\n", msens);
+               dev_dbg(hdmi->dev, "MSENS 0x%x\n", msens);
                /* Check, if hot plug & MSENS pin status are both high */
                if ((msens & 0xC0) == 0xC0) {
                        /* Display plug in */
        return IRQ_HANDLED;
  }
  
- static void hdmi_display_on(void *arg, struct fb_info *info)
+ /* locking:   called with info->lock held, or before register_framebuffer() */
+ static void sh_hdmi_display_on(void *arg, struct fb_info *info)
  {
+       /*
+        * info is guaranteed to be valid, when we are called, because our
+        * FB_EVENT_FB_UNBIND notify is also called with info->lock held
+        */
        struct sh_hdmi *hdmi = arg;
        struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
+       struct sh_mobile_lcdc_chan *ch = info->par;
  
-       if (info->var.xres != 1280 || info->var.yres != 720) {
-               dev_warn(info->device, "Unsupported framebuffer geometry %ux%u\n",
-                        info->var.xres, info->var.yres);
-               return;
-       }
+       dev_dbg(hdmi->dev, "%s(%p): state %x\n", __func__,
+               pdata->lcd_dev, info->state);
+       /* No need to lock */
+       hdmi->info = info;
  
-       pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state);
        /*
-        * FIXME: not a good place to store fb_info. And we cannot nullify it
-        * even on monitor disconnect. What should the lifecycle be?
+        * hp_state can be set to
+        * HDMI_HOTPLUG_DISCONNECTED:   on monitor unplug
+        * HDMI_HOTPLUG_CONNECTED:      on monitor plug-in
+        * HDMI_HOTPLUG_EDID_DONE:      on EDID read completion
         */
-       hdmi->info = info;
        switch (hdmi->hp_state) {
        case HDMI_HOTPLUG_EDID_DONE:
                /* PS mode d->e. All functions are active */
                hdmi_write(hdmi, 0x80, HDMI_SYSTEM_CTRL);
-               pr_debug("HDMI running\n");
+               dev_dbg(hdmi->dev, "HDMI running\n");
                break;
        case HDMI_HOTPLUG_DISCONNECTED:
                info->state = FBINFO_STATE_SUSPENDED;
        default:
-               hdmi->var = info->var;
+               hdmi->var = ch->display_var;
        }
  }
  
- static void hdmi_display_off(void *arg)
+ /* locking: called with info->lock held */
+ static void sh_hdmi_display_off(void *arg)
  {
        struct sh_hdmi *hdmi = arg;
        struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
  
-       pr_debug("%s(%p)\n", __func__, pdata->lcd_dev);
+       dev_dbg(hdmi->dev, "%s(%p)\n", __func__, pdata->lcd_dev);
        /* PS mode e->a */
        hdmi_write(hdmi, 0x10, HDMI_SYSTEM_CTRL);
  }
  
+ static bool sh_hdmi_must_reconfigure(struct sh_hdmi *hdmi)
+ {
+       struct fb_info *info = hdmi->info;
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct fb_var_screeninfo *new_var = &hdmi->var, *old_var = &ch->display_var;
+       struct fb_videomode mode1, mode2;
+       fb_var_to_videomode(&mode1, old_var);
+       fb_var_to_videomode(&mode2, new_var);
+       dev_dbg(info->dev, "Old %ux%u, new %ux%u\n",
+               mode1.xres, mode1.yres, mode2.xres, mode2.yres);
+       if (fb_mode_is_equal(&mode1, &mode2))
+               return false;
+       dev_dbg(info->dev, "Switching %u -> %u lines\n",
+               mode1.yres, mode2.yres);
+       *old_var = *new_var;
+       return true;
+ }
+ /**
+  * sh_hdmi_clk_configure() - set HDMI clock frequency and enable the clock
+  * @hdmi:     driver context
+  * @pixclock: pixel clock period in picoseconds
+  * return:    configured positive rate if successful
+  *            0 if couldn't set the rate, but managed to enable the clock
+  *            negative error, if couldn't enable the clock
+  */
+ static long sh_hdmi_clk_configure(struct sh_hdmi *hdmi, unsigned long pixclock)
+ {
+       long rate;
+       int ret;
+       rate = PICOS2KHZ(pixclock) * 1000;
+       rate = clk_round_rate(hdmi->hdmi_clk, rate);
+       if (rate > 0) {
+               ret = clk_set_rate(hdmi->hdmi_clk, rate);
+               if (ret < 0) {
+                       dev_warn(hdmi->dev, "Cannot set rate %ld: %d\n", rate, ret);
+                       rate = 0;
+               } else {
+                       dev_dbg(hdmi->dev, "HDMI set frequency %lu\n", rate);
+               }
+       } else {
+               rate = 0;
+               dev_warn(hdmi->dev, "Cannot get suitable rate: %ld\n", rate);
+       }
+       ret = clk_enable(hdmi->hdmi_clk);
+       if (ret < 0) {
+               dev_err(hdmi->dev, "Cannot enable clock: %d\n", ret);
+               return ret;
+       }
+       return rate;
+ }
  /* Hotplug interrupt occurred, read EDID */
- static void edid_work_fn(struct work_struct *work)
+ static void sh_hdmi_edid_work_fn(struct work_struct *work)
  {
        struct sh_hdmi *hdmi = container_of(work, struct sh_hdmi, edid_work.work);
        struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
+       struct sh_mobile_lcdc_chan *ch;
+       int ret;
  
-       pr_debug("%s(%p): begin, hotplug status %d\n", __func__,
-                pdata->lcd_dev, hdmi->hp_state);
+       dev_dbg(hdmi->dev, "%s(%p): begin, hotplug status %d\n", __func__,
+               pdata->lcd_dev, hdmi->hp_state);
  
        if (!pdata->lcd_dev)
                return;
  
+       mutex_lock(&hdmi->mutex);
        if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) {
-               pm_runtime_get_sync(hdmi->dev);
                /* A device has been plugged in */
-               sh_hdmi_read_edid(hdmi);
+               pm_runtime_get_sync(hdmi->dev);
+               ret = sh_hdmi_read_edid(hdmi);
+               if (ret < 0)
+                       goto out;
+               /* Reconfigure the clock */
+               clk_disable(hdmi->hdmi_clk);
+               ret = sh_hdmi_clk_configure(hdmi, hdmi->var.pixclock);
+               if (ret < 0)
+                       goto out;
                msleep(10);
                sh_hdmi_configure(hdmi);
                /* Switched to another (d) power-save mode */
                msleep(10);
  
                if (!hdmi->info)
-                       return;
+                       goto out;
+               ch = hdmi->info->par;
  
                acquire_console_sem();
  
                /* HDMI plug in */
-               hdmi->info->var = hdmi->var;
-               if (hdmi->info->state != FBINFO_STATE_RUNNING)
+               if (!sh_hdmi_must_reconfigure(hdmi) &&
+                   hdmi->info->state == FBINFO_STATE_RUNNING) {
+                       /*
+                        * First activation with the default monitor - just turn
+                        * on, if we run a resume here, the logo disappears
+                        */
+                       if (lock_fb_info(hdmi->info)) {
+                               sh_hdmi_display_on(hdmi, hdmi->info);
+                               unlock_fb_info(hdmi->info);
+                       }
+               } else {
+                       /* New monitor or have to wake up */
                        fb_set_suspend(hdmi->info, 0);
-               else
-                       hdmi_display_on(hdmi, hdmi->info);
+               }
  
                release_console_sem();
        } else {
+               ret = 0;
                if (!hdmi->info)
-                       return;
+                       goto out;
  
                acquire_console_sem();
  
  
                release_console_sem();
                pm_runtime_put(hdmi->dev);
+               fb_destroy_modedb(hdmi->monspec.modedb);
        }
  
-       pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev);
+ out:
+       if (ret < 0)
+               hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED;
+       mutex_unlock(&hdmi->mutex);
+       dev_dbg(hdmi->dev, "%s(%p): end\n", __func__, pdata->lcd_dev);
+ }
+ static int sh_hdmi_notify(struct notifier_block *nb,
+                         unsigned long action, void *data);
+ static struct notifier_block sh_hdmi_notifier = {
+       .notifier_call = sh_hdmi_notify,
+ };
+ static int sh_hdmi_notify(struct notifier_block *nb,
+                         unsigned long action, void *data)
+ {
+       struct fb_event *event = data;
+       struct fb_info *info = event->info;
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
+       struct sh_hdmi *hdmi = board_cfg->board_data;
+       if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info)
+               return NOTIFY_DONE;
+       switch(action) {
+       case FB_EVENT_FB_REGISTERED:
+               /* Unneeded, activation taken care by sh_hdmi_display_on() */
+               break;
+       case FB_EVENT_FB_UNREGISTERED:
+               /*
+                * We are called from unregister_framebuffer() with the
+                * info->lock held. This is bad for us, because we can race with
+                * the scheduled work, which has to call fb_set_suspend(), which
+                * takes info->lock internally, so, sh_hdmi_edid_work_fn()
+                * cannot take and hold info->lock for the whole function
+                * duration. Using an additional lock creates a classical AB-BA
+                * lock up. Therefore, we have to release the info->lock
+                * temporarily, synchronise with the work queue and re-acquire
+                * the info->lock.
+                */
+               unlock_fb_info(hdmi->info);
+               mutex_lock(&hdmi->mutex);
+               hdmi->info = NULL;
+               mutex_unlock(&hdmi->mutex);
+               lock_fb_info(hdmi->info);
+               return NOTIFY_OK;
+       }
+       return NOTIFY_DONE;
  }
  
  static int __init sh_hdmi_probe(struct platform_device *pdev)
  {
        struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct sh_mobile_lcdc_board_cfg *board_cfg;
        int irq = platform_get_irq(pdev, 0), ret;
        struct sh_hdmi *hdmi;
        long rate;
                return -ENOMEM;
        }
  
-       ret =  snd_soc_register_codec(&pdev->dev,
-                       &soc_codec_dev_sh_hdmi, &sh_hdmi_dai, 1);
-       if (ret < 0)
-               goto esndreg;
+       mutex_init(&hdmi->mutex);
 +
        hdmi->dev = &pdev->dev;
  
        hdmi->hdmi_clk = clk_get(&pdev->dev, "ick");
                goto egetclk;
        }
  
-       rate = PICOS2KHZ(pdata->lcd_chan->lcd_cfg.pixclock) * 1000;
-       rate = clk_round_rate(hdmi->hdmi_clk, rate);
+       /* Some arbitrary relaxed pixclock just to get things started */
+       rate = sh_hdmi_clk_configure(hdmi, 37037);
        if (rate < 0) {
                ret = rate;
-               dev_err(&pdev->dev, "Cannot get suitable rate: %ld\n", rate);
                goto erate;
        }
  
-       ret = clk_set_rate(hdmi->hdmi_clk, rate);
-       if (ret < 0) {
-               dev_err(&pdev->dev, "Cannot set rate %ld: %d\n", rate, ret);
-               goto erate;
-       }
-       pr_debug("HDMI set frequency %lu\n", rate);
-       ret = clk_enable(hdmi->hdmi_clk);
-       if (ret < 0) {
-               dev_err(&pdev->dev, "Cannot enable clock: %d\n", ret);
-               goto eclkenable;
-       }
-       dev_info(&pdev->dev, "Enabled HDMI clock at %luHz\n", rate);
+       dev_dbg(&pdev->dev, "Enabled HDMI clock at %luHz\n", rate);
  
        if (!request_mem_region(res->start, resource_size(res), dev_name(&pdev->dev))) {
                dev_err(&pdev->dev, "HDMI register region already claimed\n");
  
        platform_set_drvdata(pdev, hdmi);
  
- #if 1
        /* Product and revision IDs are 0 in sh-mobile version */
        dev_info(&pdev->dev, "Detected HDMI controller 0x%x:0x%x\n",
                 hdmi_read(hdmi, HDMI_PRODUCT_ID), hdmi_read(hdmi, HDMI_REVISION_ID));
- #endif
  
        /* Set up LCDC callbacks */
-       pdata->lcd_chan->board_cfg.board_data = hdmi;
-       pdata->lcd_chan->board_cfg.display_on = hdmi_display_on;
-       pdata->lcd_chan->board_cfg.display_off = hdmi_display_off;
+       board_cfg = &pdata->lcd_chan->board_cfg;
+       board_cfg->owner = THIS_MODULE;
+       board_cfg->board_data = hdmi;
+       board_cfg->display_on = sh_hdmi_display_on;
+       board_cfg->display_off = sh_hdmi_display_off;
  
-       INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn);
+       INIT_DELAYED_WORK(&hdmi->edid_work, sh_hdmi_edid_work_fn);
  
        pm_runtime_enable(&pdev->dev);
        pm_runtime_resume(&pdev->dev);
                goto ereqirq;
        }
  
++      ret = snd_soc_register_codec(&pdev->dev,
++                      &soc_codec_dev_sh_hdmi, &sh_hdmi_dai, 1);
++      if (ret < 0) {
++              dev_err(&pdev->dev, "codec registration failed\n");
++              goto ecodec;
++      }
++
        return 0;
  
++ecodec:
++      free_irq(irq, hdmi);
  ereqirq:
        pm_runtime_disable(&pdev->dev);
        iounmap(hdmi->base);
@@@ -1050,12 -1145,10 +1228,10 @@@ emap
        release_mem_region(res->start, resource_size(res));
  ereqreg:
        clk_disable(hdmi->hdmi_clk);
- eclkenable:
  erate:
        clk_put(hdmi->hdmi_clk);
  egetclk:
-       snd_soc_unregister_codec(&pdev->dev);
- esndreg:
+       mutex_destroy(&hdmi->mutex);
        kfree(hdmi);
  
        return ret;
@@@ -1066,21 -1159,24 +1242,26 @@@ static int __exit sh_hdmi_remove(struc
        struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
        struct sh_hdmi *hdmi = platform_get_drvdata(pdev);
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct sh_mobile_lcdc_board_cfg *board_cfg = &pdata->lcd_chan->board_cfg;
        int irq = platform_get_irq(pdev, 0);
  
-       pdata->lcd_chan->board_cfg.display_on = NULL;
-       pdata->lcd_chan->board_cfg.display_off = NULL;
-       pdata->lcd_chan->board_cfg.board_data = NULL;
 +      snd_soc_unregister_codec(&pdev->dev);
 +
+       board_cfg->display_on = NULL;
+       board_cfg->display_off = NULL;
+       board_cfg->board_data = NULL;
+       board_cfg->owner = NULL;
  
+       /* No new work will be scheduled, wait for running ISR */
        free_irq(irq, hdmi);
-       pm_runtime_disable(&pdev->dev);
+       /* Wait for already scheduled work */
        cancel_delayed_work_sync(&hdmi->edid_work);
+       pm_runtime_disable(&pdev->dev);
        clk_disable(hdmi->hdmi_clk);
        clk_put(hdmi->hdmi_clk);
        iounmap(hdmi->base);
        release_mem_region(res->start, resource_size(res));
+       mutex_destroy(&hdmi->mutex);
        kfree(hdmi);
  
        return 0;
index 7a1419279c8f501bf4d94a4101259cf6aaa0860e,f8282c3e9f8f7704b3439865312e56989c50fbc5..50963739a40977832e03285b1049be65f4cde1dd
@@@ -12,7 -12,6 +12,6 @@@
  #include <linux/init.h>
  #include <linux/delay.h>
  #include <linux/mm.h>
- #include <linux/fb.h>
  #include <linux/clk.h>
  #include <linux/pm_runtime.h>
  #include <linux/platform_device.h>
  #include <linux/vmalloc.h>
  #include <linux/ioctl.h>
  #include <linux/slab.h>
+ #include <linux/console.h>
  #include <video/sh_mobile_lcdc.h>
  #include <asm/atomic.h>
  
- #define PALETTE_NR 16
+ #include "sh_mobile_lcdcfb.h"
  #define SIDE_B_OFFSET 0x1000
  #define MIRROR_OFFSET 0x2000
  
@@@ -53,11 -54,8 +54,8 @@@ static int lcdc_shared_regs[] = 
  };
  #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
  
- /* per-channel registers */
- enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
-        LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
-        LDHAJR,
-        NR_CH_REGS };
+ #define DEFAULT_XRES 1280
+ #define DEFAULT_YRES 1024
  
  static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
        [LDDCKPAT1R] = 0x400,
@@@ -112,23 -110,21 +110,21 @@@ static unsigned long lcdc_offs_sublcd[N
  #define LDRCNTR_MRC   0x00000001
  #define LDSR_MRS      0x00000100
  
- struct sh_mobile_lcdc_priv;
- struct sh_mobile_lcdc_chan {
-       struct sh_mobile_lcdc_priv *lcdc;
-       unsigned long *reg_offs;
-       unsigned long ldmt1r_value;
-       unsigned long enabled; /* ME and SE in LDCNT2R */
-       struct sh_mobile_lcdc_chan_cfg cfg;
-       u32 pseudo_palette[PALETTE_NR];
-       unsigned long saved_ch_regs[NR_CH_REGS];
-       struct fb_info *info;
-       dma_addr_t dma_handle;
-       struct fb_deferred_io defio;
-       struct scatterlist *sglist;
-       unsigned long frame_end;
-       unsigned long pan_offset;
-       wait_queue_head_t frame_end_wait;
-       struct completion vsync_completion;
+ static const struct fb_videomode default_720p = {
+       .name = "HDMI 720p",
+       .xres = 1280,
+       .yres = 720,
+       .left_margin = 200,
+       .right_margin = 88,
+       .hsync_len = 48,
+       .upper_margin = 20,
+       .lower_margin = 5,
+       .vsync_len = 5,
+       .pixclock = 13468,
+       .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT,
  };
  
  struct sh_mobile_lcdc_priv {
@@@ -409,8 -405,8 +405,8 @@@ static void sh_mobile_lcdc_start_stop(s
  
  static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch)
  {
-       struct fb_var_screeninfo *var = &ch->info->var;
-       unsigned long h_total, hsync_pos;
+       struct fb_var_screeninfo *var = &ch->info->var, *display_var = &ch->display_var;
+       unsigned long h_total, hsync_pos, display_h_total;
        u32 tmp;
  
        tmp = ch->ldmt1r_value;
        lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r);
  
        /* horizontal configuration */
-       h_total = var->xres + var->hsync_len +
-               var->left_margin + var->right_margin;
+       h_total = display_var->xres + display_var->hsync_len +
+               display_var->left_margin + display_var->right_margin;
        tmp = h_total / 8; /* HTCN */
-       tmp |= (var->xres / 8) << 16; /* HDCN */
+       tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */
        lcdc_write_chan(ch, LDHCNR, tmp);
  
-       hsync_pos = var->xres + var->right_margin;
+       hsync_pos = display_var->xres + display_var->right_margin;
        tmp = hsync_pos / 8; /* HSYNP */
-       tmp |= (var->hsync_len / 8) << 16; /* HSYNW */
+       tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */
        lcdc_write_chan(ch, LDHSYNR, tmp);
  
        /* vertical configuration */
-       tmp = var->yres + var->vsync_len +
-               var->upper_margin + var->lower_margin; /* VTLN */
-       tmp |= var->yres << 16; /* VDLN */
+       tmp = display_var->yres + display_var->vsync_len +
+               display_var->upper_margin + display_var->lower_margin; /* VTLN */
+       tmp |= min(display_var->yres, var->yres) << 16; /* VDLN */
        lcdc_write_chan(ch, LDVLNR, tmp);
  
-       tmp = var->yres + var->lower_margin; /* VSYNP */
-       tmp |= var->vsync_len << 16; /* VSYNW */
+       tmp = display_var->yres + display_var->lower_margin; /* VSYNP */
+       tmp |= display_var->vsync_len << 16; /* VSYNW */
        lcdc_write_chan(ch, LDVSYNR, tmp);
  
        /* Adjust horizontal synchronisation for HDMI */
-       tmp = ((var->xres & 7) << 24) |
-               ((h_total & 7) << 16) |
-               ((var->hsync_len & 7) << 8) |
+       display_h_total = display_var->xres + display_var->hsync_len +
+               display_var->left_margin + display_var->right_margin;
+       tmp = ((display_var->xres & 7) << 24) |
+               ((display_h_total & 7) << 16) |
+               ((display_var->hsync_len & 7) << 8) |
                hsync_pos;
        lcdc_write_chan(ch, LDHAJR, tmp);
  }
  static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
  {
        struct sh_mobile_lcdc_chan *ch;
-       struct fb_videomode *lcd_cfg;
        struct sh_mobile_lcdc_board_cfg *board_cfg;
        unsigned long tmp;
        int k, m;
                        m = 1 << 6;
                tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
  
-               lcdc_write_chan(ch, LDDCKPAT1R, 0x00000000);
+               /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */
+               lcdc_write_chan(ch, LDDCKPAT1R, 0);
                lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
        }
  
  
        for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
                ch = &priv->ch[k];
-               lcd_cfg = &ch->cfg.lcd_cfg;
  
                if (!ch->enabled)
                        continue;
  
                /* set bpp format in PKF[4:0] */
                tmp = lcdc_read_chan(ch, LDDFR);
-               tmp &= ~(0x0001001f);
+               tmp &= ~0x0001001f;
                tmp |= (ch->info->var.bits_per_pixel == 16) ? 3 : 0;
                lcdc_write_chan(ch, LDDFR, tmp);
  
                        continue;
  
                board_cfg = &ch->cfg.board_cfg;
-               if (board_cfg->display_on)
+               if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
                        board_cfg->display_on(board_cfg->board_data, ch->info);
+                       module_put(board_cfg->owner);
+               }
        }
  
        return 0;
@@@ -614,7 -613,7 +613,7 @@@ static void sh_mobile_lcdc_stop(struct 
                 * flush frame, and wait for frame end interrupt
                 * clean up deferred io and enable clock
                 */
-               if (ch->info->fbdefio) {
+               if (ch->info && ch->info->fbdefio) {
                        ch->frame_end = 0;
                        schedule_delayed_work(&ch->info->deferred_work, 0);
                        wait_event(ch->frame_end_wait, ch->frame_end);
                }
  
                board_cfg = &ch->cfg.board_cfg;
-               if (board_cfg->display_off)
+               if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
                        board_cfg->display_off(board_cfg->board_data);
+                       module_put(board_cfg->owner);
+               }
        }
  
        /* stop the lcdc */
@@@ -704,7 -705,6 +705,6 @@@ static int sh_mobile_lcdc_setup_clocks(
                        return PTR_ERR(priv->dot_clk);
                }
        }
-       atomic_set(&priv->hw_usecnt, -1);
  
        /* Runtime PM support involves two step for this driver:
         * 1) Enable Runtime PM
@@@ -837,6 -837,102 +837,102 @@@ static int sh_mobile_ioctl(struct fb_in
        return retval;
  }
  
+ static void sh_mobile_fb_reconfig(struct fb_info *info)
+ {
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       struct fb_videomode mode1, mode2;
+       struct fb_event event;
+       int evnt = FB_EVENT_MODE_CHANGE_ALL;
+       if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
+               /* More framebuffer users are active */
+               return;
+       fb_var_to_videomode(&mode1, &ch->display_var);
+       fb_var_to_videomode(&mode2, &info->var);
+       if (fb_mode_is_equal(&mode1, &mode2))
+               return;
+       /* Display has been re-plugged, framebuffer is free now, reconfigure */
+       if (fb_set_var(info, &ch->display_var) < 0)
+               /* Couldn't reconfigure, hopefully, can continue as before */
+               return;
+       info->fix.line_length = mode2.xres * (ch->cfg.bpp / 8);
+       /*
+        * fb_set_var() calls the notifier change internally, only if
+        * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
+        * user event, we have to call the chain ourselves.
+        */
+       event.info = info;
+       event.data = &mode2;
+       fb_notifier_call_chain(evnt, &event);
+ }
+ /*
+  * Locking: both .fb_release() and .fb_open() are called with info->lock held if
+  * user == 1, or with console sem held, if user == 0.
+  */
+ static int sh_mobile_release(struct fb_info *info, int user)
+ {
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       mutex_lock(&ch->open_lock);
+       dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
+       ch->use_count--;
+       /* Nothing to reconfigure, when called from fbcon */
+       if (user) {
+               acquire_console_sem();
+               sh_mobile_fb_reconfig(info);
+               release_console_sem();
+       }
+       mutex_unlock(&ch->open_lock);
+       return 0;
+ }
+ static int sh_mobile_open(struct fb_info *info, int user)
+ {
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       mutex_lock(&ch->open_lock);
+       ch->use_count++;
+       dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
+       mutex_unlock(&ch->open_lock);
+       return 0;
+ }
+ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+ {
+       struct sh_mobile_lcdc_chan *ch = info->par;
+       if (var->xres < 160 || var->xres > 1920 ||
+           var->yres < 120 || var->yres > 1080 ||
+           var->left_margin < 32 || var->left_margin > 320 ||
+           var->right_margin < 12 || var->right_margin > 240 ||
+           var->upper_margin < 12 || var->upper_margin > 120 ||
+           var->lower_margin < 1 || var->lower_margin > 64 ||
+           var->hsync_len < 32 || var->hsync_len > 240 ||
+           var->vsync_len < 2 || var->vsync_len > 64 ||
+           var->pixclock < 6000 || var->pixclock > 40000 ||
+           var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) {
+               dev_warn(info->dev, "Invalid info: %u %u %u %u %u %u %u %u %u!\n",
+                        var->xres, var->yres,
+                        var->left_margin, var->right_margin,
+                        var->upper_margin, var->lower_margin,
+                        var->hsync_len, var->vsync_len,
+                        var->pixclock);
+               return -EINVAL;
+       }
+       return 0;
+ }
  
  static struct fb_ops sh_mobile_lcdc_ops = {
        .owner          = THIS_MODULE,
        .fb_imageblit   = sh_mobile_lcdc_imageblit,
        .fb_pan_display = sh_mobile_fb_pan_display,
        .fb_ioctl       = sh_mobile_ioctl,
+       .fb_open        = sh_mobile_open,
+       .fb_release     = sh_mobile_release,
+       .fb_check_var   = sh_mobile_check_var,
  };
  
  static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
@@@ -958,6 -1057,7 +1057,7 @@@ static const struct dev_pm_ops sh_mobil
        .runtime_resume = sh_mobile_lcdc_runtime_resume,
  };
  
+ /* locking: called with info->lock held */
  static int sh_mobile_lcdc_notify(struct notifier_block *nb,
                                 unsigned long action, void *data)
  {
        struct fb_info *info = event->info;
        struct sh_mobile_lcdc_chan *ch = info->par;
        struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
-       struct fb_var_screeninfo *var;
+       int ret;
  
        if (&ch->lcdc->notifier != nb)
-               return 0;
+               return NOTIFY_DONE;
  
        dev_dbg(info->dev, "%s(): action = %lu, data = %p\n",
                __func__, action, event->data);
  
        switch(action) {
        case FB_EVENT_SUSPEND:
-               if (board_cfg->display_off)
+               if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
                        board_cfg->display_off(board_cfg->board_data);
+                       module_put(board_cfg->owner);
+               }
                pm_runtime_put(info->device);
+               sh_mobile_lcdc_stop(ch->lcdc);
                break;
        case FB_EVENT_RESUME:
-               var = &info->var;
+               mutex_lock(&ch->open_lock);
+               sh_mobile_fb_reconfig(info);
+               mutex_unlock(&ch->open_lock);
  
                /* HDMI must be enabled before LCDC configuration */
-               if (board_cfg->display_on)
-                       board_cfg->display_on(board_cfg->board_data, ch->info);
-               /* Check if the new display is not in our modelist */
-               if (ch->info->modelist.next &&
-                   !fb_match_mode(var, &ch->info->modelist)) {
-                       struct fb_videomode mode;
-                       int ret;
-                       /* Can we handle this display? */
-                       if (var->xres > ch->cfg.lcd_cfg.xres ||
-                           var->yres > ch->cfg.lcd_cfg.yres)
-                               return -ENOMEM;
-                       /* Add to the modelist */
-                       fb_var_to_videomode(&mode, var);
-                       ret = fb_add_videomode(&mode, &ch->info->modelist);
-                       if (ret < 0)
-                               return ret;
+               if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
+                       board_cfg->display_on(board_cfg->board_data, info);
+                       module_put(board_cfg->owner);
                }
  
-               pm_runtime_get_sync(info->device);
-               sh_mobile_lcdc_geometry(ch);
-               break;
+               ret = sh_mobile_lcdc_start(ch->lcdc);
+               if (!ret)
+                       pm_runtime_get_sync(info->device);
        }
  
-       return 0;
+       return NOTIFY_OK;
  }
  
  static int sh_mobile_lcdc_remove(struct platform_device *pdev);
@@@ -1020,14 -1107,13 +1107,13 @@@ static int __devinit sh_mobile_lcdc_pro
  {
        struct fb_info *info;
        struct sh_mobile_lcdc_priv *priv;
-       struct sh_mobile_lcdc_info *pdata;
-       struct sh_mobile_lcdc_chan_cfg *cfg;
+       struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data;
        struct resource *res;
        int error;
        void *buf;
        int i, j;
  
-       if (!pdev->dev.platform_data) {
+       if (!pdata) {
                dev_err(&pdev->dev, "no platform data defined\n");
                return -EINVAL;
        }
        }
  
        priv->irq = i;
-       pdata = pdev->dev.platform_data;
+       atomic_set(&priv->hw_usecnt, -1);
  
        j = 0;
        for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) {
-               priv->ch[j].lcdc = priv;
-               memcpy(&priv->ch[j].cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
+               struct sh_mobile_lcdc_chan *ch = priv->ch + j;
+               ch->lcdc = priv;
+               memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i]));
  
-               error = sh_mobile_lcdc_check_interface(&priv->ch[j]);
+               error = sh_mobile_lcdc_check_interface(ch);
                if (error) {
                        dev_err(&pdev->dev, "unsupported interface type\n");
                        goto err1;
                }
-               init_waitqueue_head(&priv->ch[j].frame_end_wait);
-               init_completion(&priv->ch[j].vsync_completion);
-               priv->ch[j].pan_offset = 0;
+               init_waitqueue_head(&ch->frame_end_wait);
+               init_completion(&ch->vsync_completion);
+               ch->pan_offset = 0;
  
                switch (pdata->ch[i].chan) {
                case LCDC_CHAN_MAINLCD:
-                       priv->ch[j].enabled = 1 << 1;
-                       priv->ch[j].reg_offs = lcdc_offs_mainlcd;
+                       ch->enabled = 1 << 1;
+                       ch->reg_offs = lcdc_offs_mainlcd;
                        j++;
                        break;
                case LCDC_CHAN_SUBLCD:
-                       priv->ch[j].enabled = 1 << 2;
-                       priv->ch[j].reg_offs = lcdc_offs_sublcd;
+                       ch->enabled = 1 << 2;
+                       ch->reg_offs = lcdc_offs_sublcd;
                        j++;
                        break;
                }
  
        for (i = 0; i < j; i++) {
                struct fb_var_screeninfo *var;
-               struct fb_videomode *lcd_cfg;
-               cfg = &priv->ch[i].cfg;
+               const struct fb_videomode *lcd_cfg, *max_cfg = NULL;
+               struct sh_mobile_lcdc_chan *ch = priv->ch + i;
+               struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg;
+               const struct fb_videomode *mode = cfg->lcd_cfg;
+               unsigned long max_size = 0;
+               int k;
  
-               priv->ch[i].info = framebuffer_alloc(0, &pdev->dev);
-               if (!priv->ch[i].info) {
+               ch->info = framebuffer_alloc(0, &pdev->dev);
+               if (!ch->info) {
                        dev_err(&pdev->dev, "unable to allocate fb_info\n");
                        error = -ENOMEM;
                        break;
                }
  
-               info = priv->ch[i].info;
+               info = ch->info;
                var = &info->var;
-               lcd_cfg = &cfg->lcd_cfg;
                info->fbops = &sh_mobile_lcdc_ops;
-               var->xres = var->xres_virtual = lcd_cfg->xres;
-               var->yres = lcd_cfg->yres;
+               info->par = ch;
+               mutex_init(&ch->open_lock);
+               for (k = 0, lcd_cfg = mode;
+                    k < cfg->num_cfg && lcd_cfg;
+                    k++, lcd_cfg++) {
+                       unsigned long size = lcd_cfg->yres * lcd_cfg->xres;
+                       if (size > max_size) {
+                               max_cfg = lcd_cfg;
+                               max_size = size;
+                       }
+               }
+               if (!mode)
+                       max_size = DEFAULT_XRES * DEFAULT_YRES;
+               else if (max_cfg)
+                       dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n",
+                               max_cfg->xres, max_cfg->yres);
+               info->fix = sh_mobile_lcdc_fix;
+               info->fix.smem_len = max_size * (cfg->bpp / 8) * 2;
+               if (!mode)
+                       mode = &default_720p;
+               fb_videomode_to_var(var, mode);
                /* Default Y virtual resolution is 2x panel size */
                var->yres_virtual = var->yres * 2;
-               var->width = cfg->lcd_size_cfg.width;
-               var->height = cfg->lcd_size_cfg.height;
                var->activate = FB_ACTIVATE_NOW;
-               var->left_margin = lcd_cfg->left_margin;
-               var->right_margin = lcd_cfg->right_margin;
-               var->upper_margin = lcd_cfg->upper_margin;
-               var->lower_margin = lcd_cfg->lower_margin;
-               var->hsync_len = lcd_cfg->hsync_len;
-               var->vsync_len = lcd_cfg->vsync_len;
-               var->sync = lcd_cfg->sync;
-               var->pixclock = lcd_cfg->pixclock;
  
                error = sh_mobile_lcdc_set_bpp(var, cfg->bpp);
                if (error)
                        break;
  
-               info->fix = sh_mobile_lcdc_fix;
-               info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8);
-               info->fix.smem_len = info->fix.line_length *
-                       var->yres_virtual;
                buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len,
-                                        &priv->ch[i].dma_handle, GFP_KERNEL);
+                                        &ch->dma_handle, GFP_KERNEL);
                if (!buf) {
                        dev_err(&pdev->dev, "unable to allocate buffer\n");
                        error = -ENOMEM;
                        break;
                }
  
-               info->pseudo_palette = &priv->ch[i].pseudo_palette;
+               info->pseudo_palette = &ch->pseudo_palette;
                info->flags = FBINFO_FLAG_DEFAULT;
  
                error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0);
                if (error < 0) {
                        dev_err(&pdev->dev, "unable to allocate cmap\n");
                        dma_free_coherent(&pdev->dev, info->fix.smem_len,
-                                         buf, priv->ch[i].dma_handle);
+                                         buf, ch->dma_handle);
                        break;
                }
  
-               memset(buf, 0, info->fix.smem_len);
-               info->fix.smem_start = priv->ch[i].dma_handle;
+               info->fix.smem_start = ch->dma_handle;
+               info->fix.line_length = var->xres * (cfg->bpp / 8);
                info->screen_base = buf;
                info->device = &pdev->dev;
-               info->par = &priv->ch[i];
+               ch->display_var = *var;
        }
  
        if (error)
  
        for (i = 0; i < j; i++) {
                struct sh_mobile_lcdc_chan *ch = priv->ch + i;
+               const struct fb_videomode *mode = ch->cfg.lcd_cfg;
+               if (!mode)
+                       mode = &default_720p;
  
                info = ch->info;
  
                        }
                }
  
+               fb_videomode_to_modelist(mode, ch->cfg.num_cfg, &info->modelist);
                error = register_framebuffer(info);
                if (error < 0)
                        goto err1;
                         pdev->name,
                         (ch->cfg.chan == LCDC_CHAN_MAINLCD) ?
                         "mainlcd" : "sublcd",
-                        (int) ch->cfg.lcd_cfg.xres,
-                        (int) ch->cfg.lcd_cfg.yres,
+                        info->var.xres, info->var.yres,
                         ch->cfg.bpp);
  
                /* deferred io mode: disable clock to save power */
@@@ -1243,10 -1349,8 +1349,10 @@@ static int sh_mobile_lcdc_remove(struc
                if (priv->ch[i].sglist)
                        vfree(priv->ch[i].sglist);
  
 -              dma_free_coherent(&pdev->dev, info->fix.smem_len,
 -                                info->screen_base, priv->ch[i].dma_handle);
 +              if (info->screen_base)
 +                      dma_free_coherent(&pdev->dev, info->fix.smem_len,
 +                                        info->screen_base,
 +                                        priv->ch[i].dma_handle);
                fb_dealloc_cmap(&info->cmap);
                framebuffer_release(info);
        }