diff options
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 1280 |
1 files changed, 744 insertions, 536 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index aac5b369d73..7a0b301587f 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -8,26 +8,27 @@ * for more details. */ -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/delay.h> -#include <linux/mm.h> +#include <linux/atomic.h> +#include <linux/backlight.h> #include <linux/clk.h> -#include <linux/pm_runtime.h> -#include <linux/platform_device.h> +#include <linux/console.h> #include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/init.h> #include <linux/interrupt.h> -#include <linux/videodev2.h> -#include <linux/vmalloc.h> #include <linux/ioctl.h> -#include <linux/slab.h> -#include <linux/console.h> -#include <linux/backlight.h> -#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/mm.h> #include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/videodev2.h> +#include <linux/vmalloc.h> + #include <video/sh_mobile_lcdc.h> #include <video/sh_mobile_meram.h> -#include <linux/atomic.h> #include "sh_mobile_lcdcfb.h" @@ -37,6 +38,24 @@ #define MAX_XRES 1920 #define MAX_YRES 1080 +struct sh_mobile_lcdc_priv { + void __iomem *base; + int irq; + atomic_t hw_usecnt; + struct device *dev; + struct clk *dot_clk; + unsigned long lddckr; + struct sh_mobile_lcdc_chan ch[2]; + struct notifier_block notifier; + int started; + int forced_fourcc; /* 2 channel LCDC must share fourcc setting */ + struct sh_mobile_meram_info *meram_dev; +}; + +/* ----------------------------------------------------------------------------- + * Registers access + */ + static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { [LDDCKPAT1R] = 0x400, [LDDCKPAT2R] = 0x404, @@ -75,38 +94,6 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { [LDPMR] = 0x63c, }; -static const struct fb_videomode default_720p = { - .name = "HDMI 720p", - .xres = 1280, - .yres = 720, - - .left_margin = 220, - .right_margin = 110, - .hsync_len = 40, - - .upper_margin = 20, - .lower_margin = 5, - .vsync_len = 5, - - .pixclock = 13468, - .refresh = 60, - .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, -}; - -struct sh_mobile_lcdc_priv { - void __iomem *base; - int irq; - atomic_t hw_usecnt; - struct device *dev; - struct clk *dot_clk; - unsigned long lddckr; - struct sh_mobile_lcdc_chan ch[2]; - struct notifier_block notifier; - int started; - int forced_fourcc; /* 2 channel LCDC must share fourcc setting */ - struct sh_mobile_meram_info *meram_dev; -}; - static bool banked(int reg_nr) { switch (reg_nr) { @@ -127,6 +114,11 @@ static bool banked(int reg_nr) return false; } +static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan) +{ + return chan->cfg->chan == LCDC_CHAN_SUBLCD; +} + static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, int reg_nr, unsigned long data) { @@ -169,11 +161,72 @@ static void lcdc_wait_bit(struct sh_mobile_lcdc_priv *priv, cpu_relax(); } -static int lcdc_chan_is_sublcd(struct sh_mobile_lcdc_chan *chan) +/* ----------------------------------------------------------------------------- + * Clock management + */ + +static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) +{ + if (atomic_inc_and_test(&priv->hw_usecnt)) { + if (priv->dot_clk) + clk_enable(priv->dot_clk); + pm_runtime_get_sync(priv->dev); + if (priv->meram_dev && priv->meram_dev->pdev) + pm_runtime_get_sync(&priv->meram_dev->pdev->dev); + } +} + +static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) { - return chan->cfg.chan == LCDC_CHAN_SUBLCD; + if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { + if (priv->meram_dev && priv->meram_dev->pdev) + pm_runtime_put_sync(&priv->meram_dev->pdev->dev); + pm_runtime_put(priv->dev); + if (priv->dot_clk) + clk_disable(priv->dot_clk); + } } +static int sh_mobile_lcdc_setup_clocks(struct sh_mobile_lcdc_priv *priv, + int clock_source) +{ + struct clk *clk; + char *str; + + switch (clock_source) { + case LCDC_CLK_BUS: + str = "bus_clk"; + priv->lddckr = LDDCKR_ICKSEL_BUS; + break; + case LCDC_CLK_PERIPHERAL: + str = "peripheral_clk"; + priv->lddckr = LDDCKR_ICKSEL_MIPI; + break; + case LCDC_CLK_EXTERNAL: + str = NULL; + priv->lddckr = LDDCKR_ICKSEL_HDMI; + break; + default: + return -EINVAL; + } + + if (str == NULL) + return 0; + + clk = clk_get(priv->dev, str); + if (IS_ERR(clk)) { + dev_err(priv->dev, "cannot get dot clock %s\n", str); + return PTR_ERR(clk); + } + + priv->dot_clk = clk; + return 0; +} + +/* ----------------------------------------------------------------------------- + * Display, panel and deferred I/O + */ + static void lcdc_sys_write_index(void *handle, unsigned long data) { struct sh_mobile_lcdc_chan *ch = handle; @@ -216,74 +269,11 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { lcdc_sys_read_data, }; -static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var) -{ - if (var->grayscale > 1) - return var->grayscale; - - switch (var->bits_per_pixel) { - case 16: - return V4L2_PIX_FMT_RGB565; - case 24: - return V4L2_PIX_FMT_BGR24; - case 32: - return V4L2_PIX_FMT_BGR32; - default: - return 0; - } -} - -static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var) -{ - return var->grayscale > 1; -} - -static bool sh_mobile_format_is_yuv(const struct fb_var_screeninfo *var) -{ - if (var->grayscale <= 1) - return false; - - switch (var->grayscale) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - case V4L2_PIX_FMT_NV24: - case V4L2_PIX_FMT_NV42: - return true; - - default: - return false; - } -} - -static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) -{ - if (atomic_inc_and_test(&priv->hw_usecnt)) { - if (priv->dot_clk) - clk_enable(priv->dot_clk); - pm_runtime_get_sync(priv->dev); - if (priv->meram_dev && priv->meram_dev->pdev) - pm_runtime_get_sync(&priv->meram_dev->pdev->dev); - } -} - -static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) -{ - if (atomic_sub_return(1, &priv->hw_usecnt) == -1) { - if (priv->meram_dev && priv->meram_dev->pdev) - pm_runtime_put_sync(&priv->meram_dev->pdev->dev); - pm_runtime_put(priv->dev); - if (priv->dot_clk) - clk_disable(priv->dot_clk); - } -} - static int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pagelist) { struct sh_mobile_lcdc_chan *ch = info->par; - unsigned int nr_pages_max = info->fix.smem_len >> PAGE_SHIFT; + unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT; struct page *page; int nr_pages = 0; @@ -299,7 +289,7 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info, struct list_head *pagelist) { struct sh_mobile_lcdc_chan *ch = info->par; - struct sh_mobile_lcdc_board_cfg *bcfg = &ch->cfg.board_cfg; + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; /* enable clocks before accessing hardware */ sh_mobile_lcdc_clk_on(ch->lcdc); @@ -323,16 +313,15 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info, unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist); /* trigger panel update */ - dma_map_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); - if (bcfg->start_transfer) - bcfg->start_transfer(bcfg->board_data, ch, - &sh_mobile_lcdc_sys_bus_ops); + dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); + if (panel->start_transfer) + panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); - dma_unmap_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); + dma_unmap_sg(ch->lcdc->dev, ch->sglist, nr_pages, + DMA_TO_DEVICE); } else { - if (bcfg->start_transfer) - bcfg->start_transfer(bcfg->board_data, ch, - &sh_mobile_lcdc_sys_bus_ops); + if (panel->start_transfer) + panel->start_transfer(ch, &sh_mobile_lcdc_sys_bus_ops); lcdc_write_chan(ch, LDSM2R, LDSM2R_OSTRG); } } @@ -345,6 +334,217 @@ static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) schedule_delayed_work(&info->deferred_work, fbdefio->delay); } +static void sh_mobile_lcdc_display_on(struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; + + if (ch->tx_dev) { + int ret; + + ret = ch->tx_dev->ops->display_on(ch->tx_dev); + if (ret < 0) + return; + + if (ret == SH_MOBILE_LCDC_DISPLAY_DISCONNECTED) + ch->info->state = FBINFO_STATE_SUSPENDED; + } + + /* HDMI must be enabled before LCDC configuration */ + if (panel->display_on) + panel->display_on(); +} + +static void sh_mobile_lcdc_display_off(struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; + + if (panel->display_off) + panel->display_off(); + + if (ch->tx_dev) + ch->tx_dev->ops->display_off(ch->tx_dev); +} + +static bool +sh_mobile_lcdc_must_reconfigure(struct sh_mobile_lcdc_chan *ch, + const struct fb_videomode *new_mode) +{ + dev_dbg(ch->info->dev, "Old %ux%u, new %ux%u\n", + ch->display.mode.xres, ch->display.mode.yres, + new_mode->xres, new_mode->yres); + + /* It can be a different monitor with an equal video-mode */ + if (fb_mode_is_equal(&ch->display.mode, new_mode)) + return false; + + dev_dbg(ch->info->dev, "Switching %u -> %u lines\n", + ch->display.mode.yres, new_mode->yres); + ch->display.mode = *new_mode; + + return true; +} + +static int sh_mobile_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); + +static int sh_mobile_lcdc_display_notify(struct sh_mobile_lcdc_chan *ch, + enum sh_mobile_lcdc_entity_event event, + const struct fb_videomode *mode, + const struct fb_monspecs *monspec) +{ + struct fb_info *info = ch->info; + struct fb_var_screeninfo var; + int ret = 0; + + switch (event) { + case SH_MOBILE_LCDC_EVENT_DISPLAY_CONNECT: + /* HDMI plug in */ + if (lock_fb_info(info)) { + console_lock(); + + ch->display.width = monspec->max_x * 10; + ch->display.height = monspec->max_y * 10; + + if (!sh_mobile_lcdc_must_reconfigure(ch, mode) && + info->state == FBINFO_STATE_RUNNING) { + /* First activation with the default monitor. + * Just turn on, if we run a resume here, the + * logo disappears. + */ + info->var.width = monspec->max_x * 10; + info->var.height = monspec->max_y * 10; + sh_mobile_lcdc_display_on(ch); + } else { + /* New monitor or have to wake up */ + fb_set_suspend(info, 0); + } + + console_unlock(); + unlock_fb_info(info); + } + break; + + case SH_MOBILE_LCDC_EVENT_DISPLAY_DISCONNECT: + /* HDMI disconnect */ + if (lock_fb_info(info)) { + console_lock(); + fb_set_suspend(info, 1); + console_unlock(); + unlock_fb_info(info); + } + break; + + case SH_MOBILE_LCDC_EVENT_DISPLAY_MODE: + /* Validate a proposed new mode */ + fb_videomode_to_var(&var, mode); + var.bits_per_pixel = info->var.bits_per_pixel; + var.grayscale = info->var.grayscale; + ret = sh_mobile_check_var(&var, info); + break; + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct sh_mobile_lcdc_format_info { + u32 fourcc; + unsigned int bpp; + bool yuv; + u32 lddfr; +}; + +static const struct sh_mobile_lcdc_format_info sh_mobile_format_infos[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .bpp = 16, + .yuv = false, + .lddfr = LDDFR_PKF_RGB16, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .bpp = 24, + .yuv = false, + .lddfr = LDDFR_PKF_RGB24, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .bpp = 32, + .yuv = false, + .lddfr = LDDFR_PKF_ARGB32, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .bpp = 12, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_420, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .bpp = 16, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_422, + }, { + .fourcc = V4L2_PIX_FMT_NV24, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + }, { + .fourcc = V4L2_PIX_FMT_NV42, + .bpp = 24, + .yuv = true, + .lddfr = LDDFR_CC | LDDFR_YF_444, + }, +}; + +static const struct sh_mobile_lcdc_format_info * +sh_mobile_format_info(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sh_mobile_format_infos); ++i) { + if (sh_mobile_format_infos[i].fourcc == fourcc) + return &sh_mobile_format_infos[i]; + } + + return NULL; +} + +static int sh_mobile_format_fourcc(const struct fb_var_screeninfo *var) +{ + if (var->grayscale > 1) + return var->grayscale; + + switch (var->bits_per_pixel) { + case 16: + return V4L2_PIX_FMT_RGB565; + case 24: + return V4L2_PIX_FMT_BGR24; + case 32: + return V4L2_PIX_FMT_BGR32; + default: + return 0; + } +} + +static int sh_mobile_format_is_fourcc(const struct fb_var_screeninfo *var) +{ + return var->grayscale > 1; +} + +/* ----------------------------------------------------------------------------- + * Start, stop and IRQ + */ + static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) { struct sh_mobile_lcdc_priv *priv = data; @@ -385,6 +585,26 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) return IRQ_HANDLED; } +static int sh_mobile_wait_for_vsync(struct sh_mobile_lcdc_chan *ch) +{ + unsigned long ldintr; + int ret; + + /* Enable VSync End interrupt and be careful not to acknowledge any + * pending interrupt. + */ + ldintr = lcdc_read(ch->lcdc, _LDINTR); + ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK; + lcdc_write(ch->lcdc, _LDINTR, ldintr); + + ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion, + msecs_to_jiffies(100)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, int start) { @@ -416,53 +636,52 @@ 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, *display_var = &ch->display_var; + const struct fb_var_screeninfo *var = &ch->info->var; + const struct fb_videomode *mode = &ch->display.mode; unsigned long h_total, hsync_pos, display_h_total; u32 tmp; tmp = ch->ldmt1r_value; tmp |= (var->sync & FB_SYNC_VERT_HIGH_ACT) ? 0 : LDMT1R_VPOL; tmp |= (var->sync & FB_SYNC_HOR_HIGH_ACT) ? 0 : LDMT1R_HPOL; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0; - tmp |= (ch->cfg.flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DWPOL) ? LDMT1R_DWPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DIPOL) ? LDMT1R_DIPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DAPOL) ? LDMT1R_DAPOL : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_HSCNT) ? LDMT1R_HSCNT : 0; + tmp |= (ch->cfg->flags & LCDC_FLAGS_DWCNT) ? LDMT1R_DWCNT : 0; lcdc_write_chan(ch, LDMT1R, tmp); /* setup SYS bus */ - lcdc_write_chan(ch, LDMT2R, ch->cfg.sys_bus_cfg.ldmt2r); - lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); + lcdc_write_chan(ch, LDMT2R, ch->cfg->sys_bus_cfg.ldmt2r); + lcdc_write_chan(ch, LDMT3R, ch->cfg->sys_bus_cfg.ldmt3r); /* horizontal configuration */ - h_total = display_var->xres + display_var->hsync_len + - display_var->left_margin + display_var->right_margin; + h_total = mode->xres + mode->hsync_len + mode->left_margin + + mode->right_margin; tmp = h_total / 8; /* HTCN */ - tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */ + tmp |= (min(mode->xres, ch->xres) / 8) << 16; /* HDCN */ lcdc_write_chan(ch, LDHCNR, tmp); - hsync_pos = display_var->xres + display_var->right_margin; + hsync_pos = mode->xres + mode->right_margin; tmp = hsync_pos / 8; /* HSYNP */ - tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */ + tmp |= (mode->hsync_len / 8) << 16; /* HSYNW */ lcdc_write_chan(ch, LDHSYNR, tmp); /* vertical configuration */ - 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 */ + tmp = mode->yres + mode->vsync_len + mode->upper_margin + + mode->lower_margin; /* VTLN */ + tmp |= min(mode->yres, ch->yres) << 16; /* VDLN */ lcdc_write_chan(ch, LDVLNR, tmp); - tmp = display_var->yres + display_var->lower_margin; /* VSYNP */ - tmp |= display_var->vsync_len << 16; /* VSYNW */ + tmp = mode->yres + mode->lower_margin; /* VSYNP */ + tmp |= mode->vsync_len << 16; /* VSYNW */ lcdc_write_chan(ch, LDVSYNR, tmp); /* Adjust horizontal synchronisation for HDMI */ - 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 & 7); + display_h_total = mode->xres + mode->hsync_len + mode->left_margin + + mode->right_margin; + tmp = ((mode->xres & 7) << 24) | ((display_h_total & 7) << 16) + | ((mode->hsync_len & 7) << 8) | (hsync_pos & 7); lcdc_write_chan(ch, LDHAJR, tmp); } @@ -498,7 +717,7 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) /* Power supply */ lcdc_write_chan(ch, LDPMR, 0); - m = ch->cfg.clock_divider; + m = ch->cfg->clock_divider; if (!m) continue; @@ -525,32 +744,10 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) sh_mobile_lcdc_geometry(ch); - switch (sh_mobile_format_fourcc(&ch->info->var)) { - case V4L2_PIX_FMT_RGB565: - tmp = LDDFR_PKF_RGB16; - break; - case V4L2_PIX_FMT_BGR24: - tmp = LDDFR_PKF_RGB24; - break; - case V4L2_PIX_FMT_BGR32: - tmp = LDDFR_PKF_ARGB32; - break; - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - tmp = LDDFR_CC | LDDFR_YF_420; - break; - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - tmp = LDDFR_CC | LDDFR_YF_422; - break; - case V4L2_PIX_FMT_NV24: - case V4L2_PIX_FMT_NV42: - tmp = LDDFR_CC | LDDFR_YF_444; - break; - } + tmp = ch->format->lddfr; - if (sh_mobile_format_is_yuv(&ch->info->var)) { - switch (ch->info->var.colorspace) { + if (ch->format->yuv) { + switch (ch->colorspace) { case V4L2_COLORSPACE_REC709: tmp |= LDDFR_CF1; break; @@ -563,7 +760,7 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) lcdc_write_chan(ch, LDDFR, tmp); lcdc_write_chan(ch, LDMLSR, ch->pitch); lcdc_write_chan(ch, LDSA1R, ch->base_addr_y); - if (sh_mobile_format_is_yuv(&ch->info->var)) + if (ch->format->yuv) lcdc_write_chan(ch, LDSA2R, ch->base_addr_c); /* When using deferred I/O mode, configure the LCDC for one-shot @@ -571,7 +768,7 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) * continuous read mode. */ if (ch->ldmt1r_value & LDMT1R_IFM && - ch->cfg.sys_bus_cfg.deferred_io_msec) { + ch->cfg->sys_bus_cfg.deferred_io_msec) { lcdc_write_chan(ch, LDSM1R, LDSM1R_OS); lcdc_write(priv, _LDINTR, LDINTR_FE); } else { @@ -580,7 +777,7 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) } /* Word and long word swap. */ - switch (sh_mobile_format_fourcc(&priv->ch[0].info->var)) { + switch (priv->ch[0].format->fourcc) { case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV61: @@ -609,7 +806,6 @@ static void __sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) { struct sh_mobile_meram_info *mdev = priv->meram_dev; - struct sh_mobile_lcdc_board_cfg *board_cfg; struct sh_mobile_lcdc_chan *ch; unsigned long tmp; int ret; @@ -626,15 +822,15 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) lcdc_wait_bit(priv, _LDCNT2R, LDCNT2R_BR, 0); for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { - ch = &priv->ch[k]; + const struct sh_mobile_lcdc_panel_cfg *panel; + ch = &priv->ch[k]; if (!ch->enabled) continue; - board_cfg = &ch->cfg.board_cfg; - if (board_cfg->setup_sys) { - ret = board_cfg->setup_sys(board_cfg->board_data, ch, - &sh_mobile_lcdc_sys_bus_ops); + panel = &ch->cfg->panel_cfg; + if (panel->setup_sys) { + ret = panel->setup_sys(ch, &sh_mobile_lcdc_sys_bus_ops); if (ret) return ret; } @@ -642,33 +838,30 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) /* Compute frame buffer base address and pitch for each channel. */ for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { - struct sh_mobile_meram_cfg *cfg; int pixelformat; + void *meram; ch = &priv->ch[k]; if (!ch->enabled) continue; - ch->base_addr_y = ch->info->fix.smem_start; - ch->base_addr_c = ch->base_addr_y - + ch->info->var.xres - * ch->info->var.yres_virtual; - ch->pitch = ch->info->fix.line_length; + ch->base_addr_y = ch->dma_handle; + ch->base_addr_c = ch->base_addr_y + ch->xres * ch->yres_virtual; /* Enable MERAM if possible. */ - cfg = ch->cfg.meram_cfg; - if (mdev == NULL || mdev->ops == NULL || cfg == NULL) + if (mdev == NULL || mdev->ops == NULL || + ch->cfg->meram_cfg == NULL) continue; /* we need to de-init configured ICBs before we can * re-initialize them. */ - if (ch->meram_enabled) { - mdev->ops->meram_unregister(mdev, cfg); - ch->meram_enabled = 0; + if (ch->meram) { + mdev->ops->meram_unregister(mdev, ch->meram); + ch->meram = NULL; } - switch (sh_mobile_format_fourcc(&ch->info->var)) { + switch (ch->format->fourcc) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_NV16: @@ -687,13 +880,15 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) break; } - ret = mdev->ops->meram_register(mdev, cfg, ch->pitch, - ch->info->var.yres, pixelformat, - ch->base_addr_y, ch->base_addr_c, - &ch->base_addr_y, &ch->base_addr_c, + meram = mdev->ops->meram_register(mdev, ch->cfg->meram_cfg, + ch->pitch, ch->yres, pixelformat, &ch->pitch); - if (!ret) - ch->meram_enabled = 1; + if (!IS_ERR(meram)) { + mdev->ops->meram_update(mdev, meram, + ch->base_addr_y, ch->base_addr_c, + &ch->base_addr_y, &ch->base_addr_c); + ch->meram = meram; + } } /* Start the LCDC. */ @@ -707,7 +902,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) if (!ch->enabled) continue; - tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; + tmp = ch->cfg->sys_bus_cfg.deferred_io_msec; if (ch->ldmt1r_value & LDMT1R_IFM && tmp) { ch->defio.deferred_io = sh_mobile_lcdc_deferred_io; ch->defio.delay = msecs_to_jiffies(tmp); @@ -715,11 +910,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) fb_deferred_io_init(ch->info); } - board_cfg = &ch->cfg.board_cfg; - if (board_cfg->display_on && try_module_get(board_cfg->owner)) { - board_cfg->display_on(board_cfg->board_data, ch->info); - module_put(board_cfg->owner); - } + sh_mobile_lcdc_display_on(ch); if (ch->bl) { ch->bl->props.power = FB_BLANK_UNBLANK; @@ -733,7 +924,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) { struct sh_mobile_lcdc_chan *ch; - struct sh_mobile_lcdc_board_cfg *board_cfg; int k; /* clean up deferred io and ask board code to disable panel */ @@ -760,20 +950,14 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) backlight_update_status(ch->bl); } - board_cfg = &ch->cfg.board_cfg; - if (board_cfg->display_off && try_module_get(board_cfg->owner)) { - board_cfg->display_off(board_cfg->board_data); - module_put(board_cfg->owner); - } + sh_mobile_lcdc_display_off(ch); /* disable the meram */ - if (ch->meram_enabled) { - struct sh_mobile_meram_cfg *cfg; + if (ch->meram) { struct sh_mobile_meram_info *mdev; - cfg = ch->cfg.meram_cfg; mdev = priv->meram_dev; - mdev->ops->meram_unregister(mdev, cfg); - ch->meram_enabled = 0; + mdev->ops->meram_unregister(mdev, ch->meram); + ch->meram = 0; } } @@ -790,86 +974,9 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) sh_mobile_lcdc_clk_off(priv); } -static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) -{ - int interface_type = ch->cfg.interface_type; - - switch (interface_type) { - case RGB8: - case RGB9: - case RGB12A: - case RGB12B: - case RGB16: - case RGB18: - case RGB24: - case SYS8A: - case SYS8B: - case SYS8C: - case SYS8D: - case SYS9: - case SYS12: - case SYS16A: - case SYS16B: - case SYS16C: - case SYS18: - case SYS24: - break; - default: - return -EINVAL; - } - - /* SUBLCD only supports SYS interface */ - if (lcdc_chan_is_sublcd(ch)) { - if (!(interface_type & LDMT1R_IFM)) - return -EINVAL; - - interface_type &= ~LDMT1R_IFM; - } - - ch->ldmt1r_value = interface_type; - return 0; -} - -static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, - int clock_source, - struct sh_mobile_lcdc_priv *priv) -{ - char *str; - - switch (clock_source) { - case LCDC_CLK_BUS: - str = "bus_clk"; - priv->lddckr = LDDCKR_ICKSEL_BUS; - break; - case LCDC_CLK_PERIPHERAL: - str = "peripheral_clk"; - priv->lddckr = LDDCKR_ICKSEL_MIPI; - break; - case LCDC_CLK_EXTERNAL: - str = NULL; - priv->lddckr = LDDCKR_ICKSEL_HDMI; - break; - default: - return -EINVAL; - } - - if (str) { - priv->dot_clk = clk_get(&pdev->dev, str); - if (IS_ERR(priv->dot_clk)) { - dev_err(&pdev->dev, "cannot get dot clock %s\n", str); - return PTR_ERR(priv->dot_clk); - } - } - - /* Runtime PM support involves two step for this driver: - * 1) Enable Runtime PM - * 2) Force Runtime PM Resume since hardware is accessed from probe() - */ - priv->dev = &pdev->dev; - pm_runtime_enable(priv->dev); - pm_runtime_resume(priv->dev); - return 0; -} +/* ----------------------------------------------------------------------------- + * Frame buffer operations + */ static int sh_mobile_lcdc_setcolreg(u_int regno, u_int red, u_int green, u_int blue, @@ -936,14 +1043,12 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, unsigned long new_pan_offset; unsigned long base_addr_y, base_addr_c; unsigned long c_offset; - bool yuv = sh_mobile_format_is_yuv(&info->var); - if (!yuv) - new_pan_offset = var->yoffset * info->fix.line_length - + var->xoffset * (info->var.bits_per_pixel / 8); + if (!ch->format->yuv) + new_pan_offset = var->yoffset * ch->pitch + + var->xoffset * (ch->format->bpp / 8); else - new_pan_offset = var->yoffset * info->fix.line_length - + var->xoffset; + new_pan_offset = var->yoffset * ch->pitch + var->xoffset; if (new_pan_offset == ch->pan_offset) return 0; /* No change, do nothing */ @@ -952,39 +1057,33 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, /* Set the source address for the next refresh */ base_addr_y = ch->dma_handle + new_pan_offset; - if (yuv) { + if (ch->format->yuv) { /* Set y offset */ - c_offset = var->yoffset * info->fix.line_length - * (info->var.bits_per_pixel - 8) / 8; - base_addr_c = ch->dma_handle - + info->var.xres * info->var.yres_virtual + c_offset = var->yoffset * ch->pitch + * (ch->format->bpp - 8) / 8; + base_addr_c = ch->dma_handle + ch->xres * ch->yres_virtual + c_offset; /* Set x offset */ - if (sh_mobile_format_fourcc(&info->var) == V4L2_PIX_FMT_NV24) + if (ch->format->fourcc == V4L2_PIX_FMT_NV24) base_addr_c += 2 * var->xoffset; else base_addr_c += var->xoffset; } - if (ch->meram_enabled) { - struct sh_mobile_meram_cfg *cfg; + if (ch->meram) { struct sh_mobile_meram_info *mdev; - int ret; - cfg = ch->cfg.meram_cfg; mdev = priv->meram_dev; - ret = mdev->ops->meram_update(mdev, cfg, + mdev->ops->meram_update(mdev, ch->meram, base_addr_y, base_addr_c, &base_addr_y, &base_addr_c); - if (ret) - return ret; } ch->base_addr_y = base_addr_y; ch->base_addr_c = base_addr_c; lcdc_write_chan_mirror(ch, LDSA1R, base_addr_y); - if (yuv) + if (ch->format->yuv) lcdc_write_chan_mirror(ch, LDSA2R, base_addr_c); if (lcdc_chan_is_sublcd(ch)) @@ -999,27 +1098,6 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, return 0; } -static int sh_mobile_wait_for_vsync(struct fb_info *info) -{ - struct sh_mobile_lcdc_chan *ch = info->par; - unsigned long ldintr; - int ret; - - /* Enable VSync End interrupt and be careful not to acknowledge any - * pending interrupt. - */ - ldintr = lcdc_read(ch->lcdc, _LDINTR); - ldintr |= LDINTR_VEE | LDINTR_STATUS_MASK; - lcdc_write(ch->lcdc, _LDINTR, ldintr); - - ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion, - msecs_to_jiffies(100)); - if (!ret) - return -ETIMEDOUT; - - return 0; -} - static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { @@ -1027,7 +1105,7 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, switch (cmd) { case FBIO_WAITFORVSYNC: - retval = sh_mobile_wait_for_vsync(info); + retval = sh_mobile_wait_for_vsync(info->par); break; default: @@ -1040,7 +1118,8 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, 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_var_screeninfo var; + struct fb_videomode mode; struct fb_event event; int evnt = FB_EVENT_MODE_CHANGE_ALL; @@ -1048,14 +1127,19 @@ static void sh_mobile_fb_reconfig(struct fb_info *info) /* More framebuffer users are active */ return; - fb_var_to_videomode(&mode1, &ch->display_var); - fb_var_to_videomode(&mode2, &info->var); + fb_var_to_videomode(&mode, &info->var); - if (fb_mode_is_equal(&mode1, &mode2)) + if (fb_mode_is_equal(&ch->display.mode, &mode)) return; /* Display has been re-plugged, framebuffer is free now, reconfigure */ - if (fb_set_var(info, &ch->display_var) < 0) + var = info->var; + fb_videomode_to_var(&var, &ch->display.mode); + var.width = ch->display.width; + var.height = ch->display.height; + var.activate = FB_ACTIVATE_NOW; + + if (fb_set_var(info, &var) < 0) /* Couldn't reconfigure, hopefully, can continue as before */ return; @@ -1065,7 +1149,7 @@ static void sh_mobile_fb_reconfig(struct fb_info *info) * user event, we have to call the chain ourselves. */ event.info = info; - event.data = &mode1; + event.data = &ch->display.mode; fb_notifier_call_chain(evnt, &event); } @@ -1124,8 +1208,8 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in * distance between two modes is defined as the size of the * non-overlapping parts of the two rectangles. */ - for (i = 0; i < ch->cfg.num_cfg; ++i) { - const struct fb_videomode *mode = &ch->cfg.lcd_cfg[i]; + for (i = 0; i < ch->cfg->num_modes; ++i) { + const struct fb_videomode *mode = &ch->cfg->lcd_modes[i]; unsigned int dist; /* We can only round up. */ @@ -1144,7 +1228,7 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in } /* If no available mode can be used, return an error. */ - if (ch->cfg.num_cfg != 0) { + if (ch->cfg->num_modes != 0) { if (best_dist == (unsigned int)-1) return -EINVAL; @@ -1161,32 +1245,17 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in var->yres_virtual = var->yres; if (sh_mobile_format_is_fourcc(var)) { - switch (var->grayscale) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - var->bits_per_pixel = 12; - break; - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_NV61: - var->bits_per_pixel = 16; - break; - case V4L2_PIX_FMT_BGR24: - case V4L2_PIX_FMT_NV24: - case V4L2_PIX_FMT_NV42: - var->bits_per_pixel = 24; - break; - case V4L2_PIX_FMT_BGR32: - var->bits_per_pixel = 32; - break; - default: + const struct sh_mobile_lcdc_format_info *format; + + format = sh_mobile_format_info(var->grayscale); + if (format == NULL) return -EINVAL; - } + var->bits_per_pixel = format->bpp; /* Default to RGB and JPEG color-spaces for RGB and YUV formats * respectively. */ - if (!sh_mobile_format_is_yuv(var)) + if (!format->yuv) var->colorspace = V4L2_COLORSPACE_SRGB; else if (var->colorspace != V4L2_COLORSPACE_REC709) var->colorspace = V4L2_COLORSPACE_JPEG; @@ -1246,22 +1315,28 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in static int sh_mobile_set_par(struct fb_info *info) { struct sh_mobile_lcdc_chan *ch = info->par; - u32 line_length = info->fix.line_length; int ret; sh_mobile_lcdc_stop(ch->lcdc); - if (sh_mobile_format_is_yuv(&info->var)) - info->fix.line_length = info->var.xres; + ch->format = sh_mobile_format_info(sh_mobile_format_fourcc(&info->var)); + ch->colorspace = info->var.colorspace; + + ch->xres = info->var.xres; + ch->xres_virtual = info->var.xres_virtual; + ch->yres = info->var.yres; + ch->yres_virtual = info->var.yres_virtual; + + if (ch->format->yuv) + ch->pitch = info->var.xres; else - info->fix.line_length = info->var.xres - * info->var.bits_per_pixel / 8; + ch->pitch = info->var.xres * ch->format->bpp / 8; ret = sh_mobile_lcdc_start(ch->lcdc); - if (ret < 0) { + if (ret < 0) dev_err(info->dev, "%s: unable to restart LCDC\n", __func__); - info->fix.line_length = line_length; - } + + info->fix.line_length = ch->pitch; if (sh_mobile_format_is_fourcc(&info->var)) { info->fix.type = FB_TYPE_FOURCC; @@ -1290,8 +1365,8 @@ static int sh_mobile_lcdc_blank(int blank, struct fb_info *info) /* blank the screen? */ if (blank > FB_BLANK_UNBLANK && ch->blank_status == FB_BLANK_UNBLANK) { struct fb_fillrect rect = { - .width = info->var.xres, - .height = info->var.yres, + .width = ch->xres, + .height = ch->yres, }; sh_mobile_lcdc_fillrect(info, &rect); } @@ -1307,8 +1382,8 @@ static int sh_mobile_lcdc_blank(int blank, struct fb_info *info) * mode will reenable the clocks and update the screen in time, * so it does not need this. */ if (!info->fbdefio) { - sh_mobile_wait_for_vsync(info); - sh_mobile_wait_for_vsync(info); + sh_mobile_wait_for_vsync(ch); + sh_mobile_wait_for_vsync(ch); } sh_mobile_lcdc_clk_off(p); } @@ -1334,25 +1409,161 @@ static struct fb_ops sh_mobile_lcdc_ops = { .fb_set_par = sh_mobile_set_par, }; +static void +sh_mobile_lcdc_channel_fb_unregister(struct sh_mobile_lcdc_chan *ch) +{ + if (ch->info && ch->info->dev) + unregister_framebuffer(ch->info); +} + +static int __devinit +sh_mobile_lcdc_channel_fb_register(struct sh_mobile_lcdc_chan *ch) +{ + struct fb_info *info = ch->info; + int ret; + + if (info->fbdefio) { + ch->sglist = vmalloc(sizeof(struct scatterlist) * + ch->fb_size >> PAGE_SHIFT); + if (!ch->sglist) { + dev_err(ch->lcdc->dev, "cannot allocate sglist\n"); + return -ENOMEM; + } + } + + info->bl_dev = ch->bl; + + ret = register_framebuffer(info); + if (ret < 0) + return ret; + + dev_info(ch->lcdc->dev, "registered %s/%s as %dx%d %dbpp.\n", + dev_name(ch->lcdc->dev), (ch->cfg->chan == LCDC_CHAN_MAINLCD) ? + "mainlcd" : "sublcd", info->var.xres, info->var.yres, + info->var.bits_per_pixel); + + /* deferred io mode: disable clock to save power */ + if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) + sh_mobile_lcdc_clk_off(ch->lcdc); + + return ret; +} + +static void +sh_mobile_lcdc_channel_fb_cleanup(struct sh_mobile_lcdc_chan *ch) +{ + struct fb_info *info = ch->info; + + if (!info || !info->device) + return; + + if (ch->sglist) + vfree(ch->sglist); + + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); +} + +static int __devinit +sh_mobile_lcdc_channel_fb_init(struct sh_mobile_lcdc_chan *ch, + const struct fb_videomode *mode, + unsigned int num_modes) +{ + struct sh_mobile_lcdc_priv *priv = ch->lcdc; + struct fb_var_screeninfo *var; + struct fb_info *info; + int ret; + + /* Allocate and initialize the frame buffer device. Create the modes + * list and allocate the color map. + */ + info = framebuffer_alloc(0, priv->dev); + if (info == NULL) { + dev_err(priv->dev, "unable to allocate fb_info\n"); + return -ENOMEM; + } + + ch->info = info; + + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &sh_mobile_lcdc_ops; + info->device = priv->dev; + info->screen_base = ch->fb_mem; + info->pseudo_palette = &ch->pseudo_palette; + info->par = ch; + + fb_videomode_to_modelist(mode, num_modes, &info->modelist); + + ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); + if (ret < 0) { + dev_err(priv->dev, "unable to allocate cmap\n"); + return ret; + } + + /* Initialize fixed screen information. Restrict pan to 2 lines steps + * for NV12 and NV21. + */ + info->fix = sh_mobile_lcdc_fix; + info->fix.smem_start = ch->dma_handle; + info->fix.smem_len = ch->fb_size; + info->fix.line_length = ch->pitch; + + if (ch->format->yuv) + info->fix.visual = FB_VISUAL_FOURCC; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + if (ch->format->fourcc == V4L2_PIX_FMT_NV12 || + ch->format->fourcc == V4L2_PIX_FMT_NV21) + info->fix.ypanstep = 2; + + /* Initialize variable screen information using the first mode as + * default. The default Y virtual resolution is twice the panel size to + * allow for double-buffering. + */ + var = &info->var; + fb_videomode_to_var(var, mode); + var->width = ch->cfg->panel_cfg.width; + var->height = ch->cfg->panel_cfg.height; + var->yres_virtual = var->yres * 2; + var->activate = FB_ACTIVATE_NOW; + + /* Use the legacy API by default for RGB formats, and the FOURCC API + * for YUV formats. + */ + if (!ch->format->yuv) + var->bits_per_pixel = ch->format->bpp; + else + var->grayscale = ch->format->fourcc; + + ret = sh_mobile_check_var(var, info); + if (ret) + return ret; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Backlight + */ + static int sh_mobile_lcdc_update_bl(struct backlight_device *bdev) { struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); - struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg; int brightness = bdev->props.brightness; if (bdev->props.power != FB_BLANK_UNBLANK || bdev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) brightness = 0; - return cfg->set_brightness(cfg->board_data, brightness); + return ch->cfg->bl_info.set_brightness(brightness); } static int sh_mobile_lcdc_get_brightness(struct backlight_device *bdev) { struct sh_mobile_lcdc_chan *ch = bl_get_data(bdev); - struct sh_mobile_lcdc_board_cfg *cfg = &ch->cfg.board_cfg; - return cfg->get_brightness(cfg->board_data); + return ch->cfg->bl_info.get_brightness(); } static int sh_mobile_lcdc_check_fb(struct backlight_device *bdev, @@ -1373,7 +1584,7 @@ static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent, { struct backlight_device *bl; - bl = backlight_device_register(ch->cfg.bl_info.name, parent, ch, + bl = backlight_device_register(ch->cfg->bl_info.name, parent, ch, &sh_mobile_lcdc_bl_ops, NULL); if (IS_ERR(bl)) { dev_err(parent, "unable to register backlight device: %ld\n", @@ -1381,7 +1592,7 @@ static struct backlight_device *sh_mobile_lcdc_bl_probe(struct device *parent, return NULL; } - bl->props.max_brightness = ch->cfg.bl_info.max_brightness; + bl->props.max_brightness = ch->cfg->bl_info.max_brightness; bl->props.brightness = bl->props.max_brightness; backlight_update_status(bl); @@ -1393,6 +1604,10 @@ static void sh_mobile_lcdc_bl_remove(struct backlight_device *bdev) backlight_device_unregister(bdev); } +/* ----------------------------------------------------------------------------- + * Power management + */ + static int sh_mobile_lcdc_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -1436,6 +1651,10 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { .runtime_resume = sh_mobile_lcdc_runtime_resume, }; +/* ----------------------------------------------------------------------------- + * Framebuffer notifier + */ + /* locking: called with info->lock held */ static int sh_mobile_lcdc_notify(struct notifier_block *nb, unsigned long action, void *data) @@ -1443,7 +1662,6 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, 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; if (&ch->lcdc->notifier != nb) return NOTIFY_DONE; @@ -1453,10 +1671,7 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, switch(action) { case FB_EVENT_SUSPEND: - if (board_cfg->display_off && try_module_get(board_cfg->owner)) { - board_cfg->display_off(board_cfg->board_data); - module_put(board_cfg->owner); - } + sh_mobile_lcdc_display_off(ch); sh_mobile_lcdc_stop(ch->lcdc); break; case FB_EVENT_RESUME: @@ -1464,47 +1679,60 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, sh_mobile_fb_reconfig(info); mutex_unlock(&ch->open_lock); - /* HDMI must be enabled before LCDC configuration */ - if (board_cfg->display_on && try_module_get(board_cfg->owner)) { - board_cfg->display_on(board_cfg->board_data, info); - module_put(board_cfg->owner); - } - + sh_mobile_lcdc_display_on(ch); sh_mobile_lcdc_start(ch->lcdc); } return NOTIFY_OK; } +/* ----------------------------------------------------------------------------- + * Probe/remove and driver init/exit + */ + +static const struct fb_videomode default_720p __devinitconst = { + .name = "HDMI 720p", + .xres = 1280, + .yres = 720, + + .left_margin = 220, + .right_margin = 110, + .hsync_len = 40, + + .upper_margin = 20, + .lower_margin = 5, + .vsync_len = 5, + + .pixclock = 13468, + .refresh = 60, + .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, +}; + static int sh_mobile_lcdc_remove(struct platform_device *pdev) { struct sh_mobile_lcdc_priv *priv = platform_get_drvdata(pdev); - struct fb_info *info; int i; fb_unregister_client(&priv->notifier); for (i = 0; i < ARRAY_SIZE(priv->ch); i++) - if (priv->ch[i].info && priv->ch[i].info->dev) - unregister_framebuffer(priv->ch[i].info); + sh_mobile_lcdc_channel_fb_unregister(&priv->ch[i]); sh_mobile_lcdc_stop(priv); for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { - info = priv->ch[i].info; + struct sh_mobile_lcdc_chan *ch = &priv->ch[i]; - if (!info || !info->device) - continue; + if (ch->tx_dev) { + ch->tx_dev->lcdc = NULL; + module_put(ch->cfg->tx_dev->dev.driver->owner); + } - if (priv->ch[i].sglist) - vfree(priv->ch[i].sglist); + sh_mobile_lcdc_channel_fb_cleanup(ch); - 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); + if (ch->fb_mem) + dma_free_coherent(&pdev->dev, ch->fb_size, + ch->fb_mem, ch->dma_handle); } for (i = 0; i < ARRAY_SIZE(priv->ch); i++) { @@ -1512,11 +1740,10 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) sh_mobile_lcdc_bl_remove(priv->ch[i].bl); } - if (priv->dot_clk) + if (priv->dot_clk) { + pm_runtime_disable(&pdev->dev); clk_put(priv->dot_clk); - - if (priv->dev) - pm_runtime_disable(priv->dev); + } if (priv->base) iounmap(priv->base); @@ -1527,49 +1754,82 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) return 0; } -static int __devinit sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch, - struct device *dev) +static int __devinit sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) { - struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg; + int interface_type = ch->cfg->interface_type; + + switch (interface_type) { + case RGB8: + case RGB9: + case RGB12A: + case RGB12B: + case RGB16: + case RGB18: + case RGB24: + case SYS8A: + case SYS8B: + case SYS8C: + case SYS8D: + case SYS9: + case SYS12: + case SYS16A: + case SYS16B: + case SYS16C: + case SYS18: + case SYS24: + break; + default: + return -EINVAL; + } + + /* SUBLCD only supports SYS interface */ + if (lcdc_chan_is_sublcd(ch)) { + if (!(interface_type & LDMT1R_IFM)) + return -EINVAL; + + interface_type &= ~LDMT1R_IFM; + } + + ch->ldmt1r_value = interface_type; + return 0; +} + +static int __devinit +sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_priv *priv, + struct sh_mobile_lcdc_chan *ch) +{ + const struct sh_mobile_lcdc_format_info *format; + const struct sh_mobile_lcdc_chan_cfg *cfg = ch->cfg; const struct fb_videomode *max_mode; const struct fb_videomode *mode; - struct fb_var_screeninfo *var; - struct fb_info *info; + unsigned int num_modes; unsigned int max_size; - int num_cfg; - void *buf; - int ret; - int i; + unsigned int i; mutex_init(&ch->open_lock); + ch->notify = sh_mobile_lcdc_display_notify; - /* Allocate the frame buffer device. */ - ch->info = framebuffer_alloc(0, dev); - if (!ch->info) { - dev_err(dev, "unable to allocate fb_info\n"); - return -ENOMEM; + /* Validate the format. */ + format = sh_mobile_format_info(cfg->fourcc); + if (format == NULL) { + dev_err(priv->dev, "Invalid FOURCC %08x.\n", cfg->fourcc); + return -EINVAL; } - info = ch->info; - info->fbops = &sh_mobile_lcdc_ops; - info->par = ch; - info->pseudo_palette = &ch->pseudo_palette; - info->flags = FBINFO_FLAG_DEFAULT; - /* Iterate through the modes to validate them and find the highest * resolution. */ max_mode = NULL; max_size = 0; - for (i = 0, mode = cfg->lcd_cfg; i < cfg->num_cfg; i++, mode++) { + for (i = 0, mode = cfg->lcd_modes; i < cfg->num_modes; i++, mode++) { unsigned int size = mode->yres * mode->xres; /* NV12/NV21 buffers must have even number of lines */ if ((cfg->fourcc == V4L2_PIX_FMT_NV12 || cfg->fourcc == V4L2_PIX_FMT_NV21) && (mode->yres & 0x1)) { - dev_err(dev, "yres must be multiple of 2 for YCbCr420 " - "mode.\n"); + dev_err(priv->dev, "yres must be multiple of 2 for " + "YCbCr420 mode.\n"); return -EINVAL; } @@ -1582,93 +1842,59 @@ static int __devinit sh_mobile_lcdc_channel_init(struct sh_mobile_lcdc_chan *ch, if (!max_size) max_size = MAX_XRES * MAX_YRES; else - dev_dbg(dev, "Found largest videomode %ux%u\n", + dev_dbg(priv->dev, "Found largest videomode %ux%u\n", max_mode->xres, max_mode->yres); - /* Create the mode list. */ - if (cfg->lcd_cfg == NULL) { + if (cfg->lcd_modes == NULL) { mode = &default_720p; - num_cfg = 1; + num_modes = 1; } else { - mode = cfg->lcd_cfg; - num_cfg = cfg->num_cfg; + mode = cfg->lcd_modes; + num_modes = cfg->num_modes; } - fb_videomode_to_modelist(mode, num_cfg, &info->modelist); + /* Use the first mode as default. */ + ch->format = format; + ch->xres = mode->xres; + ch->xres_virtual = mode->xres; + ch->yres = mode->yres; + ch->yres_virtual = mode->yres * 2; - /* Initialize variable screen information using the first mode as - * default. The default Y virtual resolution is twice the panel size to - * allow for double-buffering. - */ - var = &info->var; - fb_videomode_to_var(var, mode); - var->width = cfg->lcd_size_cfg.width; - var->height = cfg->lcd_size_cfg.height; - var->yres_virtual = var->yres * 2; - var->activate = FB_ACTIVATE_NOW; - - switch (cfg->fourcc) { - case V4L2_PIX_FMT_RGB565: - var->bits_per_pixel = 16; - break; - case V4L2_PIX_FMT_BGR24: - var->bits_per_pixel = 24; - break; - case V4L2_PIX_FMT_BGR32: - var->bits_per_pixel = 32; - break; - default: - var->grayscale = cfg->fourcc; - break; + if (!format->yuv) { + ch->colorspace = V4L2_COLORSPACE_SRGB; + ch->pitch = ch->xres * format->bpp / 8; + } else { + ch->colorspace = V4L2_COLORSPACE_REC709; + ch->pitch = ch->xres; } - /* Make sure the memory size check won't fail. smem_len is initialized - * later based on var. - */ - info->fix.smem_len = UINT_MAX; - ret = sh_mobile_check_var(var, info); - if (ret) - return ret; - - max_size = max_size * var->bits_per_pixel / 8 * 2; + ch->display.width = cfg->panel_cfg.width; + ch->display.height = cfg->panel_cfg.height; + ch->display.mode = *mode; - /* Allocate frame buffer memory and color map. */ - buf = dma_alloc_coherent(dev, max_size, &ch->dma_handle, GFP_KERNEL); - if (!buf) { - dev_err(dev, "unable to allocate buffer\n"); + /* Allocate frame buffer memory. */ + ch->fb_size = max_size * format->bpp / 8 * 2; + ch->fb_mem = dma_alloc_coherent(priv->dev, ch->fb_size, &ch->dma_handle, + GFP_KERNEL); + if (ch->fb_mem == NULL) { + dev_err(priv->dev, "unable to allocate buffer\n"); return -ENOMEM; } - ret = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); - if (ret < 0) { - dev_err(dev, "unable to allocate cmap\n"); - dma_free_coherent(dev, max_size, buf, ch->dma_handle); - return ret; - } - - /* Initialize fixed screen information. Restrict pan to 2 lines steps - * for NV12 and NV21. - */ - info->fix = sh_mobile_lcdc_fix; - info->fix.smem_start = ch->dma_handle; - info->fix.smem_len = max_size; - if (cfg->fourcc == V4L2_PIX_FMT_NV12 || - cfg->fourcc == V4L2_PIX_FMT_NV21) - info->fix.ypanstep = 2; - - if (sh_mobile_format_is_yuv(var)) { - info->fix.line_length = var->xres; - info->fix.visual = FB_VISUAL_FOURCC; - } else { - info->fix.line_length = var->xres * var->bits_per_pixel / 8; - info->fix.visual = FB_VISUAL_TRUECOLOR; + /* Initialize the transmitter device if present. */ + if (cfg->tx_dev) { + if (!cfg->tx_dev->dev.driver || + !try_module_get(cfg->tx_dev->dev.driver->owner)) { + dev_warn(priv->dev, + "unable to get transmitter device\n"); + return -EINVAL; + } + ch->tx_dev = platform_get_drvdata(cfg->tx_dev); + ch->tx_dev->lcdc = ch; + ch->tx_dev->def_mode = *mode; } - info->screen_base = buf; - info->device = dev; - ch->display_var = *var; - - return 0; + return sh_mobile_lcdc_channel_fb_init(ch, mode, num_modes); } static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) @@ -1698,6 +1924,8 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) return -ENOMEM; } + priv->dev = &pdev->dev; + priv->meram_dev = pdata->meram_dev; platform_set_drvdata(pdev, priv); error = request_irq(i, sh_mobile_lcdc_irq, 0, @@ -1714,7 +1942,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) struct sh_mobile_lcdc_chan *ch = priv->ch + num_channels; ch->lcdc = priv; - memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i])); + ch->cfg = &pdata->ch[i]; error = sh_mobile_lcdc_check_interface(ch); if (error) { @@ -1726,7 +1954,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) ch->pan_offset = 0; /* probe the backlight is there is one defined */ - if (ch->cfg.bl_info.max_brightness) + if (ch->cfg->bl_info.max_brightness) ch->bl = sh_mobile_lcdc_bl_probe(&pdev->dev, ch); switch (pdata->ch[i].chan) { @@ -1757,18 +1985,19 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) if (!priv->base) goto err1; - error = sh_mobile_lcdc_setup_clocks(pdev, pdata->clock_source, priv); + error = sh_mobile_lcdc_setup_clocks(priv, pdata->clock_source); if (error) { dev_err(&pdev->dev, "unable to setup clocks\n"); goto err1; } - priv->meram_dev = pdata->meram_dev; + /* Enable runtime PM. */ + pm_runtime_enable(&pdev->dev); for (i = 0; i < num_channels; i++) { struct sh_mobile_lcdc_chan *ch = priv->ch + i; - error = sh_mobile_lcdc_channel_init(ch, &pdev->dev); + error = sh_mobile_lcdc_channel_init(priv, ch); if (error) goto err1; } @@ -1781,31 +2010,10 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) for (i = 0; i < num_channels; i++) { struct sh_mobile_lcdc_chan *ch = priv->ch + i; - struct fb_info *info = ch->info; - - if (info->fbdefio) { - ch->sglist = vmalloc(sizeof(struct scatterlist) * - info->fix.smem_len >> PAGE_SHIFT); - if (!ch->sglist) { - dev_err(&pdev->dev, "cannot allocate sglist\n"); - goto err1; - } - } - - info->bl_dev = ch->bl; - error = register_framebuffer(info); - if (error < 0) + error = sh_mobile_lcdc_channel_fb_register(ch); + if (error) goto err1; - - dev_info(info->dev, "registered %s/%s as %dx%d %dbpp.\n", - pdev->name, (ch->cfg.chan == LCDC_CHAN_MAINLCD) ? - "mainlcd" : "sublcd", info->var.xres, info->var.yres, - info->var.bits_per_pixel); - - /* deferred io mode: disable clock to save power */ - if (info->fbdefio || info->state == FBINFO_STATE_SUSPENDED) - sh_mobile_lcdc_clk_off(priv); } /* Failure ignored */ |