diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/video/sh_mipi_dsi.c | 32 | ||||
-rw-r--r-- | drivers/video/sh_mobile_hdmi.c | 629 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 348 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.h | 41 |
4 files changed, 691 insertions, 359 deletions
diff --git a/drivers/video/sh_mipi_dsi.c b/drivers/video/sh_mipi_dsi.c index 5699ce0c1780..3f3d431033ca 100644 --- a/drivers/video/sh_mipi_dsi.c +++ b/drivers/video/sh_mipi_dsi.c @@ -123,83 +123,87 @@ static int __init sh_mipi_setup(struct sh_mipi *mipi, u32 linelength; bool yuv; - /* Select data format */ + /* + * Select data format. MIPI DSI is not hot-pluggable, so, we just use + * the default videomode. If this ever becomes a problem, We'll have to + * move this to mipi_display_on() above and use info->var.xres + */ switch (pdata->data_format) { case MIPI_RGB888: pctype = 0; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; - linelength = ch->lcd_cfg.xres * 3; + linelength = ch->lcd_cfg[0].xres * 3; yuv = false; break; case MIPI_RGB565: pctype = 1; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; - linelength = ch->lcd_cfg.xres * 2; + linelength = ch->lcd_cfg[0].xres * 2; yuv = false; break; case MIPI_RGB666_LP: pctype = 2; datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; - linelength = ch->lcd_cfg.xres * 3; + linelength = ch->lcd_cfg[0].xres * 3; yuv = false; break; case MIPI_RGB666: pctype = 3; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; - linelength = (ch->lcd_cfg.xres * 18 + 7) / 8; + linelength = (ch->lcd_cfg[0].xres * 18 + 7) / 8; yuv = false; break; case MIPI_BGR888: pctype = 8; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_24; pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; - linelength = ch->lcd_cfg.xres * 3; + linelength = ch->lcd_cfg[0].xres * 3; yuv = false; break; case MIPI_BGR565: pctype = 9; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_16; pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; - linelength = ch->lcd_cfg.xres * 2; + linelength = ch->lcd_cfg[0].xres * 2; yuv = false; break; case MIPI_BGR666_LP: pctype = 0xa; datatype = MIPI_DSI_PIXEL_STREAM_3BYTE_18; pixfmt = MIPI_DCS_PIXEL_FMT_24BIT; - linelength = ch->lcd_cfg.xres * 3; + linelength = ch->lcd_cfg[0].xres * 3; yuv = false; break; case MIPI_BGR666: pctype = 0xb; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_18; pixfmt = MIPI_DCS_PIXEL_FMT_18BIT; - linelength = (ch->lcd_cfg.xres * 18 + 7) / 8; + linelength = (ch->lcd_cfg[0].xres * 18 + 7) / 8; yuv = false; break; case MIPI_YUYV: pctype = 4; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; - linelength = ch->lcd_cfg.xres * 2; + linelength = ch->lcd_cfg[0].xres * 2; yuv = true; break; case MIPI_UYVY: pctype = 5; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16; pixfmt = MIPI_DCS_PIXEL_FMT_16BIT; - linelength = ch->lcd_cfg.xres * 2; + linelength = ch->lcd_cfg[0].xres * 2; yuv = true; break; case MIPI_YUV420_L: pctype = 6; datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; - linelength = (ch->lcd_cfg.xres * 12 + 7) / 8; + linelength = (ch->lcd_cfg[0].xres * 12 + 7) / 8; yuv = true; break; case MIPI_YUV420: @@ -207,7 +211,7 @@ static int __init sh_mipi_setup(struct sh_mipi *mipi, datatype = MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12; pixfmt = MIPI_DCS_PIXEL_FMT_12BIT; /* Length of U/V line */ - linelength = (ch->lcd_cfg.xres + 1) / 2; + linelength = (ch->lcd_cfg[0].xres + 1) / 2; yuv = true; break; default: @@ -281,7 +285,7 @@ static int __init sh_mipi_setup(struct sh_mipi *mipi, iowrite32(0x00e00000, base + 0x8024); /* VMCTR2 */ /* * 0x660 = 1632 bytes per line (RGB24, 544 pixels: see - * sh_mobile_lcdc_info.ch[0].lcd_cfg.xres), HSALEN = 1 - default + * sh_mobile_lcdc_info.ch[0].lcd_cfg[0].xres), HSALEN = 1 - default * (unused, since VMCTR2[HSABM] = 0) */ iowrite32(1 | (linelength << 16), base + 0x8028); /* VMLEN1 */ diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index ef989d94511c..55b3077ff6ff 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -28,6 +28,8 @@ #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 +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) @@ -277,7 +282,7 @@ static struct snd_soc_codec_driver soc_codec_dev_sh_hdmi = { */ /* 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; @@ -309,9 +314,9 @@ static void hdmi_external_video_param(struct sh_hdmi *hdmi) 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); @@ -336,7 +341,10 @@ static void hdmi_external_video_param(struct sh_hdmi *hdmi) 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); } /** @@ -454,21 +462,61 @@ static void sh_hdmi_audio_config(struct sh_hdmi *hdmi) } /** - * 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); + } } /** @@ -476,6 +524,8 @@ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) */ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) { + u8 vic; + /* AVI InfoFrame */ hdmi_write(hdmi, 0x06, HDMI_CTRL_PKT_BUF_INDEX); @@ -500,9 +550,9 @@ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) 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); @@ -516,9 +566,15 @@ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) /* * 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); @@ -592,100 +648,6 @@ static void sh_hdmi_audio_infoframe_setup(struct sh_hdmi *hdmi) } /** - * 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 */ static void sh_hdmi_configure(struct sh_hdmi *hdmi) @@ -705,18 +667,6 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi) /* 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 @@ -734,17 +684,42 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi) 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 @@ -759,29 +734,97 @@ static void sh_hdmi_read_edid(struct sh_hdmi *hdmi) #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) @@ -809,8 +852,8 @@ 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; @@ -821,7 +864,7 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) 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 */ @@ -857,83 +900,176 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) 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(); @@ -942,15 +1078,67 @@ static void edid_work_fn(struct work_struct *work) 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; @@ -964,10 +1152,7 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) 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; @@ -978,30 +1163,14 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) 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"); @@ -1018,18 +1187,18 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) 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); @@ -1041,8 +1210,17 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) 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 +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 +1242,26 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev) 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); snd_soc_unregister_codec(&pdev->dev); - pdata->lcd_chan->board_cfg.display_on = NULL; - pdata->lcd_chan->board_cfg.display_off = NULL; - pdata->lcd_chan->board_cfg.board_data = NULL; + 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; diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 7a1419279c8f..50963739a409 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -12,7 +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> @@ -21,10 +20,12 @@ #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 @@ 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 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { #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 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, 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; @@ -428,31 +424,33 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) 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); } @@ -460,7 +458,6 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) 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; @@ -503,7 +500,8 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) 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); } @@ -518,7 +516,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { ch = &priv->ch[k]; - lcd_cfg = &ch->cfg.lcd_cfg; if (!ch->enabled) continue; @@ -547,7 +544,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) /* 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); @@ -591,8 +588,10 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) 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 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) * 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); @@ -624,8 +623,10 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) } 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 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, 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 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, 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, @@ -848,6 +944,9 @@ static struct fb_ops sh_mobile_lcdc_ops = { .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 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { .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) { @@ -965,53 +1065,40 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, 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 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) { 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; } @@ -1055,31 +1141,33 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) } 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; } @@ -1103,69 +1191,83 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) 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) @@ -1179,6 +1281,10 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) 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; @@ -1191,6 +1297,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) } } + fb_videomode_to_modelist(mode, ch->cfg.num_cfg, &info->modelist); error = register_framebuffer(info); if (error < 0) goto err1; @@ -1200,8 +1307,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) 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 */ diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h new file mode 100644 index 000000000000..9ecee2fba1d7 --- /dev/null +++ b/drivers/video/sh_mobile_lcdcfb.h @@ -0,0 +1,41 @@ +#ifndef SH_MOBILE_LCDCFB_H +#define SH_MOBILE_LCDCFB_H + +#include <linux/completion.h> +#include <linux/fb.h> +#include <linux/mutex.h> +#include <linux/wait.h> + +/* per-channel registers */ +enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, + LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, + LDHAJR, + NR_CH_REGS }; + +#define PALETTE_NR 16 + +struct sh_mobile_lcdc_priv; +struct fb_info; + +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; + struct fb_var_screeninfo display_var; + int use_count; + struct mutex open_lock; /* protects the use counter */ +}; + +#endif |