diff options
author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2010-07-21 06:13:21 -0400 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2010-08-04 03:12:15 -0400 |
commit | 6011bdeaa6089d49c02de69f05980da7bad314ab (patch) | |
tree | 8b0109726f0397e3ef7dbc4ffecb1fd4fdacdb00 /drivers/video/sh_mobile_lcdcfb.c | |
parent | c2439398170be9d7af28eb3ab59593369cb303f3 (diff) |
fbdev: sh-mobile: HDMI support for SH-Mobile SoCs
Some SH-Mobile SoCs have an HDMI controller and a PHY, attached to one of their
LCDC interfaces. This patch adds a preliminary static support for such
controllers, this means, that only the 720p mode is handled ATM. Support for
more modes and a dynamic switching between them will be added by a follow up
patch.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Acked-by: Magnus Damm <damm@opensource.se>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 188 |
1 files changed, 139 insertions, 49 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index b9e6f93642d2..d72075a9f01c 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -56,6 +56,7 @@ static int lcdc_shared_regs[] = { | |||
56 | /* per-channel registers */ | 56 | /* per-channel registers */ |
57 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 57 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, |
58 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | 58 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, |
59 | LDHAJR, | ||
59 | NR_CH_REGS }; | 60 | NR_CH_REGS }; |
60 | 61 | ||
61 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | 62 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
@@ -74,6 +75,7 @@ static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | |||
74 | [LDVLNR] = 0x450, | 75 | [LDVLNR] = 0x450, |
75 | [LDVSYNR] = 0x454, | 76 | [LDVSYNR] = 0x454, |
76 | [LDPMR] = 0x460, | 77 | [LDPMR] = 0x460, |
78 | [LDHAJR] = 0x4a0, | ||
77 | }; | 79 | }; |
78 | 80 | ||
79 | static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { | 81 | static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { |
@@ -137,6 +139,7 @@ struct sh_mobile_lcdc_priv { | |||
137 | struct clk *dot_clk; | 139 | struct clk *dot_clk; |
138 | unsigned long lddckr; | 140 | unsigned long lddckr; |
139 | struct sh_mobile_lcdc_chan ch[2]; | 141 | struct sh_mobile_lcdc_chan ch[2]; |
142 | struct notifier_block notifier; | ||
140 | unsigned long saved_shared_regs[NR_SHARED_REGS]; | 143 | unsigned long saved_shared_regs[NR_SHARED_REGS]; |
141 | int started; | 144 | int started; |
142 | }; | 145 | }; |
@@ -404,6 +407,56 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, | |||
404 | lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */ | 407 | lcdc_write(priv, _LDDCKSTPR, 1); /* stop dotclock */ |
405 | } | 408 | } |
406 | 409 | ||
410 | static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | ||
411 | { | ||
412 | struct fb_var_screeninfo *var = &ch->info->var; | ||
413 | unsigned long h_total, hsync_pos; | ||
414 | u32 tmp; | ||
415 | |||
416 | tmp = ch->ldmt1r_value; | ||
417 | tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1 << 28; | ||
418 | tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1 << 27; | ||
419 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? 1 << 26 : 0; | ||
420 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? 1 << 25 : 0; | ||
421 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? 1 << 24 : 0; | ||
422 | tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? 1 << 17 : 0; | ||
423 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? 1 << 16 : 0; | ||
424 | lcdc_write_chan(ch, LDMT1R, tmp); | ||
425 | |||
426 | /* setup SYS bus */ | ||
427 | lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); | ||
428 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); | ||
429 | |||
430 | /* horizontal configuration */ | ||
431 | h_total = var->xres + var->hsync_len + | ||
432 | var->left_margin + var->right_margin; | ||
433 | tmp = h_total / 8; /* HTCN */ | ||
434 | tmp |= (var->xres / 8) << 16; /* HDCN */ | ||
435 | lcdc_write_chan(ch, LDHCNR, tmp); | ||
436 | |||
437 | hsync_pos = var->xres + var->right_margin; | ||
438 | tmp = hsync_pos / 8; /* HSYNP */ | ||
439 | tmp |= (var->hsync_len / 8) << 16; /* HSYNW */ | ||
440 | lcdc_write_chan(ch, LDHSYNR, tmp); | ||
441 | |||
442 | /* vertical configuration */ | ||
443 | tmp = var->yres + var->vsync_len + | ||
444 | var->upper_margin + var->lower_margin; /* VTLN */ | ||
445 | tmp |= var->yres << 16; /* VDLN */ | ||
446 | lcdc_write_chan(ch, LDVLNR, tmp); | ||
447 | |||
448 | tmp = var->yres + var->lower_margin; /* VSYNP */ | ||
449 | tmp |= var->vsync_len << 16; /* VSYNW */ | ||
450 | lcdc_write_chan(ch, LDVSYNR, tmp); | ||
451 | |||
452 | /* Adjust horizontal synchronisation for HDMI */ | ||
453 | tmp = ((var->xres & 7) << 24) | | ||
454 | ((h_total & 7) << 16) | | ||
455 | ((var->hsync_len & 7) << 8) | | ||
456 | hsync_pos; | ||
457 | lcdc_write_chan(ch, LDHAJR, tmp); | ||
458 | } | ||
459 | |||
407 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | 460 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) |
408 | { | 461 | { |
409 | struct sh_mobile_lcdc_chan *ch; | 462 | struct sh_mobile_lcdc_chan *ch; |
@@ -470,49 +523,11 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
470 | if (!ch->enabled) | 523 | if (!ch->enabled) |
471 | continue; | 524 | continue; |
472 | 525 | ||
473 | tmp = ch->ldmt1r_value; | 526 | sh_mobile_lcdc_geometry(ch); |
474 | tmp |= (lcd_cfg->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : 1 << 28; | ||
475 | tmp |= (lcd_cfg->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : 1 << 27; | ||
476 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? 1 << 26 : 0; | ||
477 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? 1 << 25 : 0; | ||
478 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? 1 << 24 : 0; | ||
479 | tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? 1 << 17 : 0; | ||
480 | tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? 1 << 16 : 0; | ||
481 | lcdc_write_chan(ch, LDMT1R, tmp); | ||
482 | |||
483 | /* setup SYS bus */ | ||
484 | lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); | ||
485 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); | ||
486 | |||
487 | /* horizontal configuration */ | ||
488 | tmp = lcd_cfg->xres + lcd_cfg->hsync_len; | ||
489 | tmp += lcd_cfg->left_margin; | ||
490 | tmp += lcd_cfg->right_margin; | ||
491 | tmp /= 8; /* HTCN */ | ||
492 | tmp |= (lcd_cfg->xres / 8) << 16; /* HDCN */ | ||
493 | lcdc_write_chan(ch, LDHCNR, tmp); | ||
494 | |||
495 | tmp = lcd_cfg->xres; | ||
496 | tmp += lcd_cfg->right_margin; | ||
497 | tmp /= 8; /* HSYNP */ | ||
498 | tmp |= (lcd_cfg->hsync_len / 8) << 16; /* HSYNW */ | ||
499 | lcdc_write_chan(ch, LDHSYNR, tmp); | ||
500 | 527 | ||
501 | /* power supply */ | 528 | /* power supply */ |
502 | lcdc_write_chan(ch, LDPMR, 0); | 529 | lcdc_write_chan(ch, LDPMR, 0); |
503 | 530 | ||
504 | /* vertical configuration */ | ||
505 | tmp = lcd_cfg->yres + lcd_cfg->vsync_len; | ||
506 | tmp += lcd_cfg->upper_margin; | ||
507 | tmp += lcd_cfg->lower_margin; /* VTLN */ | ||
508 | tmp |= lcd_cfg->yres << 16; /* VDLN */ | ||
509 | lcdc_write_chan(ch, LDVLNR, tmp); | ||
510 | |||
511 | tmp = lcd_cfg->yres; | ||
512 | tmp += lcd_cfg->lower_margin; /* VSYNP */ | ||
513 | tmp |= lcd_cfg->vsync_len << 16; /* VSYNW */ | ||
514 | lcdc_write_chan(ch, LDVSYNR, tmp); | ||
515 | |||
516 | board_cfg = &ch->cfg.board_cfg; | 531 | board_cfg = &ch->cfg.board_cfg; |
517 | if (board_cfg->setup_sys) | 532 | if (board_cfg->setup_sys) |
518 | ret = board_cfg->setup_sys(board_cfg->board_data, ch, | 533 | ret = board_cfg->setup_sys(board_cfg->board_data, ch, |
@@ -943,6 +958,62 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | |||
943 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | 958 | .runtime_resume = sh_mobile_lcdc_runtime_resume, |
944 | }; | 959 | }; |
945 | 960 | ||
961 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, | ||
962 | unsigned long action, void *data) | ||
963 | { | ||
964 | struct fb_event *event = data; | ||
965 | struct fb_info *info = event->info; | ||
966 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
967 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; | ||
968 | struct fb_var_screeninfo *var; | ||
969 | |||
970 | if (&ch->lcdc->notifier != nb) | ||
971 | return 0; | ||
972 | |||
973 | dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", | ||
974 | __func__, action, event->data); | ||
975 | |||
976 | switch(action) { | ||
977 | case FB_EVENT_SUSPEND: | ||
978 | if (board_cfg->display_off) | ||
979 | board_cfg->display_off(board_cfg->board_data); | ||
980 | pm_runtime_put(info->device); | ||
981 | break; | ||
982 | case FB_EVENT_RESUME: | ||
983 | var = &info->var; | ||
984 | |||
985 | /* HDMI must be enabled before LCDC configuration */ | ||
986 | if (board_cfg->display_on) | ||
987 | board_cfg->display_on(board_cfg->board_data, ch->info); | ||
988 | |||
989 | /* Check if the new display is not in our modelist */ | ||
990 | if (ch->info->modelist.next && | ||
991 | !fb_match_mode(var, &ch->info->modelist)) { | ||
992 | struct fb_videomode mode; | ||
993 | int ret; | ||
994 | |||
995 | /* Can we handle this display? */ | ||
996 | if (var->xres > ch->cfg.lcd_cfg.xres || | ||
997 | var->yres > ch->cfg.lcd_cfg.yres) | ||
998 | return -ENOMEM; | ||
999 | |||
1000 | /* Add to the modelist */ | ||
1001 | fb_var_to_videomode(&mode, var); | ||
1002 | ret = fb_add_videomode(&mode, &ch->info->modelist); | ||
1003 | if (ret < 0) | ||
1004 | return ret; | ||
1005 | } | ||
1006 | |||
1007 | pm_runtime_get_sync(info->device); | ||
1008 | |||
1009 | sh_mobile_lcdc_geometry(ch); | ||
1010 | |||
1011 | break; | ||
1012 | } | ||
1013 | |||
1014 | return 0; | ||
1015 | } | ||
1016 | |||
946 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 1017 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
947 | 1018 | ||
948 | static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | 1019 | static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) |
@@ -1031,6 +1102,8 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1031 | } | 1102 | } |
1032 | 1103 | ||
1033 | for (i = 0; i < j; i++) { | 1104 | for (i = 0; i < j; i++) { |
1105 | struct fb_var_screeninfo *var; | ||
1106 | struct fb_videomode *lcd_cfg; | ||
1034 | cfg = &priv->ch[i].cfg; | 1107 | cfg = &priv->ch[i].cfg; |
1035 | 1108 | ||
1036 | priv->ch[i].info = framebuffer_alloc(0, &pdev->dev); | 1109 | priv->ch[i].info = framebuffer_alloc(0, &pdev->dev); |
@@ -1041,22 +1114,33 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1041 | } | 1114 | } |
1042 | 1115 | ||
1043 | info = priv->ch[i].info; | 1116 | info = priv->ch[i].info; |
1117 | var = &info->var; | ||
1118 | lcd_cfg = &cfg->lcd_cfg; | ||
1044 | info->fbops = &sh_mobile_lcdc_ops; | 1119 | info->fbops = &sh_mobile_lcdc_ops; |
1045 | info->var.xres = info->var.xres_virtual = cfg->lcd_cfg.xres; | 1120 | var->xres = var->xres_virtual = lcd_cfg->xres; |
1046 | info->var.yres = cfg->lcd_cfg.yres; | 1121 | var->yres = lcd_cfg->yres; |
1047 | /* Default Y virtual resolution is 2x panel size */ | 1122 | /* Default Y virtual resolution is 2x panel size */ |
1048 | info->var.yres_virtual = info->var.yres * 2; | 1123 | var->yres_virtual = var->yres * 2; |
1049 | info->var.width = cfg->lcd_size_cfg.width; | 1124 | var->width = cfg->lcd_size_cfg.width; |
1050 | info->var.height = cfg->lcd_size_cfg.height; | 1125 | var->height = cfg->lcd_size_cfg.height; |
1051 | info->var.activate = FB_ACTIVATE_NOW; | 1126 | var->activate = FB_ACTIVATE_NOW; |
1052 | error = sh_mobile_lcdc_set_bpp(&info->var, cfg->bpp); | 1127 | var->left_margin = lcd_cfg->left_margin; |
1128 | var->right_margin = lcd_cfg->right_margin; | ||
1129 | var->upper_margin = lcd_cfg->upper_margin; | ||
1130 | var->lower_margin = lcd_cfg->lower_margin; | ||
1131 | var->hsync_len = lcd_cfg->hsync_len; | ||
1132 | var->vsync_len = lcd_cfg->vsync_len; | ||
1133 | var->sync = lcd_cfg->sync; | ||
1134 | var->pixclock = lcd_cfg->pixclock; | ||
1135 | |||
1136 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); | ||
1053 | if (error) | 1137 | if (error) |
1054 | break; | 1138 | break; |
1055 | 1139 | ||
1056 | info->fix = sh_mobile_lcdc_fix; | 1140 | info->fix = sh_mobile_lcdc_fix; |
1057 | info->fix.line_length = cfg->lcd_cfg.xres * (cfg->bpp / 8); | 1141 | info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8); |
1058 | info->fix.smem_len = info->fix.line_length * | 1142 | info->fix.smem_len = info->fix.line_length * |
1059 | info->var.yres_virtual; | 1143 | var->yres_virtual; |
1060 | 1144 | ||
1061 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, | 1145 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, |
1062 | &priv->ch[i].dma_handle, GFP_KERNEL); | 1146 | &priv->ch[i].dma_handle, GFP_KERNEL); |
@@ -1121,10 +1205,14 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1121 | ch->cfg.bpp); | 1205 | ch->cfg.bpp); |
1122 | 1206 | ||
1123 | /* deferred io mode: disable clock to save power */ | 1207 | /* deferred io mode: disable clock to save power */ |
1124 | if (info->fbdefio) | 1208 | if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) |
1125 | sh_mobile_lcdc_clk_off(priv); | 1209 | sh_mobile_lcdc_clk_off(priv); |
1126 | } | 1210 | } |
1127 | 1211 | ||
1212 | /* Failure ignored */ | ||
1213 | priv->notifier.notifier_call = sh_mobile_lcdc_notify; | ||
1214 | fb_register_client(&priv->notifier); | ||
1215 | |||
1128 | return 0; | 1216 | return 0; |
1129 | err1: | 1217 | err1: |
1130 | sh_mobile_lcdc_remove(pdev); | 1218 | sh_mobile_lcdc_remove(pdev); |
@@ -1138,6 +1226,8 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
1138 | struct fb_info *info; | 1226 | struct fb_info *info; |
1139 | int i; | 1227 | int i; |
1140 | 1228 | ||
1229 | fb_unregister_client(&priv->notifier); | ||
1230 | |||
1141 | for (i = 0; i < ARRAY_SIZE(priv->ch); i++) | 1231 | for (i = 0; i < ARRAY_SIZE(priv->ch); i++) |
1142 | if (priv->ch[i].info && priv->ch[i].info->dev) | 1232 | if (priv->ch[i].info && priv->ch[i].info->dev) |
1143 | unregister_framebuffer(priv->ch[i].info); | 1233 | unregister_framebuffer(priv->ch[i].info); |