@@ -22,6 +22,10 @@
*/
#define S3C_FB_MAX_WIN (5)
+#define S3C_FB_MAX_WIN_SOURCES (2)
+
+struct s3c_fifo_link;
+
/**
* struct s3c_fb_pd_win - per window setup data
* @win_mode: The display parameters to initialise (not for window 0)
@@ -35,6 +39,8 @@ struct s3c_fb_pd_win {
unsigned short max_bpp;
unsigned short virtual_x;
unsigned short virtual_y;
+
+ struct s3c_fifo_link *fifo_sources[S3C_FB_MAX_WIN_SOURCES];
};
/**
@@ -24,9 +24,14 @@
#include <linux/uaccess.h>
#include <linux/interrupt.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
#include <mach/map.h>
#include <plat/regs-fb-v4.h>
#include <plat/fb.h>
+#include <plat/fifo.h>
/* This driver will export a number of framebuffer interfaces depending
* on the configuration passed in via the platform data. Each fb instance
@@ -56,6 +61,18 @@
#define VSYNC_TIMEOUT_MSEC 50
struct s3c_fb;
+struct s3c_fb_win;
+
+struct s3c_fb_win_sd {
+ struct v4l2_subdev sd;
+ unsigned int index;
+ struct s3c_fb_win *win;
+ struct s3c_fifo_link *link;
+ struct v4l2_format fmt;
+ int streaming;
+ struct v4l2_crop default_rect;
+ struct v4l2_crop osd_rect;
+};
#define VALID_BPP(x) (1 << ((x) - 1))
@@ -157,6 +174,9 @@ struct s3c_fb_palette {
* @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
* @index: The window number of this window.
* @palette: The bitfields for changing r/g/b into a hardware palette entry.
+ * @sources:
+ * @local_path: flag indicating the lcd controller input mode:
+ * 0 - local path from other SoC subsystem, 1 - DMA
*/
struct s3c_fb_win {
struct s3c_fb_pd_win *windata;
@@ -168,6 +188,8 @@ struct s3c_fb_win {
u32 *palette_buffer;
u32 pseudo_palette[16];
unsigned int index;
+ struct s3c_fb_win_sd *sources[S3C_FB_MAX_WIN_SOURCES];
+ unsigned int local_path;
};
/**
@@ -361,13 +383,10 @@ static int s3c_fb_calc_pixclk(struct s3c_fb *sfb, unsigned int pixclk)
*/
static int s3c_fb_align_word(unsigned int bpp, unsigned int pix)
{
- int pix_per_word;
-
if (bpp > 16)
return pix;
- pix_per_word = (8 * 32) / bpp;
- return ALIGN(pix, pix_per_word);
+ return round_down(pix, (bpp > 8) ? 2 : 4);
}
/**
@@ -419,6 +438,9 @@ static int s3c_fb_set_par(struct fb_info *info)
u32 pagewidth;
int clkdiv;
+ if (win->local_path)
+ return -EBUSY;
+
dev_dbg(sfb->dev, "setting framebuffer parameters\n");
switch (var->bits_per_pixel) {
@@ -486,7 +508,7 @@ static int s3c_fb_set_par(struct fb_info *info)
data = VIDTCON2_LINEVAL(var->yres - 1) |
VIDTCON2_HOZVAL(var->xres - 1);
- writel(data, regs +sfb->variant.vidtcon + 8 );
+ writel(data, regs + sfb->variant.vidtcon + 8);
}
/* write the buffer address */
@@ -711,6 +733,9 @@ static int s3c_fb_setcolreg(unsigned regno,
dev_dbg(sfb->dev, "%s: win %d: %d => rgb=%d/%d/%d\n",
__func__, win->index, regno, red, green, blue);
+ if (win->local_path)
+ return -EBUSY;
+
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
/* true-colour, use pseudo-palette */
@@ -786,6 +811,9 @@ static int s3c_fb_blank(int blank_mode, struct fb_info *info)
dev_dbg(sfb->dev, "blank mode %d\n", blank_mode);
+ if (win->local_path)
+ return -EBUSY;
+
wincon = readl(sfb->regs + sfb->variant.wincon + (index * 4));
switch (blank_mode) {
@@ -896,6 +924,168 @@ static int s3c_fb_pan_display(struct fb_var_screeninfo *var,
}
/**
+ * s3c_fb_set_osd() - set position and size of the framebuffer window
+ *
+ * @win: framebuffer window to get data for
+ * @cr: pixel cropping reactangle
+ *
+ * Set framebuffer window position and size. cr rectangle will be modified
+ * if it does not meet the hardware alignment requirements.
+ */
+int s3c_fb_set_osd(struct s3c_fb_win *win, struct v4l2_rect *cr, int bpp)
+{
+ u32 data, width;
+ struct s3c_fb *sfb = win->parent;
+ void __iomem *regs = sfb->regs;
+
+ if (win->index >= S3C_FB_MAX_WIN)
+ return -EINVAL;
+
+ shadow_protect(win);
+
+ cr->left = s3c_fb_align_word(bpp, cr->left);
+ data = VIDOSDxA_TOPLEFT_X(cr->left) | VIDOSDxA_TOPLEFT_Y(cr->top);
+ writel(data, regs + VIDOSD_A(win->index, sfb->variant));
+
+ width = s3c_fb_align_word(bpp, cr->width - 1);
+
+ data = VIDOSDxB_BOTRIGHT_X(cr->left + width)
+ | VIDOSDxB_BOTRIGHT_Y(cr->top + cr->height - 1);
+
+ writel(data, regs + VIDOSD_B(win->index, sfb->variant));
+
+ data = (width + 1) * cr->height;
+ vidosd_set_size(win, data);
+
+ shadow_noprotect(win);
+
+ dev_dbg(sfb->dev, "%s(): l:%d t:%d w:%d h:%d", __func__,
+ cr->left, cr->top, cr->width, cr->height);
+
+ return 0;
+}
+
+/**
+ * s3c_fb_get_osd() - get position and size of the frame buffer window
+ *
+ * @win: framebuffer window to get data for
+ * @cr: current cropping rectangle
+ */
+int s3c_fb_get_osd(struct s3c_fb_win *win, struct v4l2_rect *cr)
+{
+ u32 reg, ltx, lty;
+ struct s3c_fb *sfb = win->parent;
+ void __iomem *regs = sfb->regs;
+
+ if (!cr || win->index >= S3C_FB_MAX_WIN)
+ return -EINVAL;
+
+ reg = readl(regs + VIDOSD_A(win->index, sfb->variant));
+
+ ltx = (reg >> VIDOSDxA_TOPLEFT_X_SHIFT) & VIDOSDxA_TOPLEFT_X_LIMIT;
+ lty = (reg >> VIDOSDxA_TOPLEFT_Y_SHIFT) & VIDOSDxA_TOPLEFT_Y_LIMIT;
+
+ reg = readl(regs + VIDOSD_B(win->index, sfb->variant));
+
+ cr->width = ((reg >> VIDOSDxB_BOTRIGHT_X_SHIFT)
+ & VIDOSDxB_BOTRIGHT_X_LIMIT) - ltx + 1;
+
+ cr->height = ((reg >> VIDOSDxB_BOTRIGHT_Y_SHIFT)
+ & VIDOSDxB_BOTRIGHT_Y_LIMIT) - lty + 1;
+ cr->left = ltx;
+ cr->top = lty;
+
+ dev_dbg(sfb->dev, "%s(): l:%d t:%d w:%d h:%d", __func__,
+ cr->left, cr->top, cr->width, cr->height);
+
+ return 0;
+}
+
+/**
+ * s3c_fb_enable_local() - switch window between input DMA and fifo modes
+ *
+ * @en: nonzero - switch from input DMA to fifo mode and apply
+ * window size and position set by the window's subdevice
+ * 0 - restore from fifo to DMA mode
+ * @fb_sd: window subdevice for fifo input
+ */
+static int s3c_fb_enable_local_in(struct s3c_fb_win_sd *fb_sd, int en)
+{
+ struct s3c_fb_win *win = fb_sd->win;
+ struct s3c_fb *sfb = win->parent;
+ static u32 wincon;
+ u32 reg;
+ int ret = 0, bpp = 32;
+
+ /* disable video output and the window logic before altering
+ window setup */
+ reg = readl(sfb->regs + WINCON(win->index));
+ reg &= ~WINCONx_ENWIN;
+ writel(reg, sfb->regs + WINCON(win->index));
+
+ shadow_protect(win);
+
+ reg = readl(sfb->regs + WINCON(win->index));
+
+ if (en) {
+ if (fb_sd->streaming)
+ return 0;
+
+ wincon = reg;
+
+ switch (fb_sd->fmt.fmt.pix.pixelformat) {
+ case V4L2_PIX_FMT_YUYV: /* YCbCr 4:4:4 */
+ reg |= WINCONx_YCbCr | WINCONx_ENLOCAL;
+ bpp = 16;
+ break;
+
+ case V4L2_PIX_FMT_RGB24:
+ default:
+ reg &= ~(WINCONx_YCbCr | WINCONx_WSWP | WINCONx_HAWSWP |
+ WINCONx_BYTSWP | WINCONx_BITSWP |
+ WINCON0_BPPMODE_MASK | WINCONx_BURSTLEN_MASK);
+
+ reg |= WINCON0_BPPMODE_24BPP_888 |
+ WINCONx_BURSTLEN_4WORD |
+ WINCONx_ENLOCAL;
+ bpp = 24;
+ break;
+ }
+ ret = s3c_fb_set_osd(fb_sd->win, &fb_sd->osd_rect.c, bpp);
+ if (!ret) {
+ fb_sd->streaming = 1;
+ writel(reg, sfb->regs + WINCON(win->index));
+ }
+ } else {
+ /* here we need to be aligned with VSYNC interrupt */
+ if (!fb_sd->streaming)
+ return 0;
+
+ reg = wincon & ~WINCONx_ENLOCAL;
+ fb_sd->streaming = 0;
+ /* restore OSD values from before we enabled local mode */
+ bpp = win->fbinfo->var.bits_per_pixel;
+ s3c_fb_set_osd(fb_sd->win, &fb_sd->default_rect.c, bpp);
+ writel(reg, sfb->regs + WINCON(win->index));
+ }
+
+ shadow_noprotect(win);
+
+ reg = readl(sfb->regs + WINCON(win->index));
+ reg |= WINCONx_ENWIN;
+ writel(reg, sfb->regs + WINCON(win->index));
+
+ return ret;
+}
+
+/* Returns current horizontal line number. It will be 0 during VSYNC. */
+inline u32 s3c_fb_get_line_count(struct s3c_fb *sfb)
+{
+ u32 reg = readl(sfb->regs + VIDCON1);
+ return VIDCON1_LINECNT_GET(reg);
+}
+
+/**
* s3c_fb_enable_irq() - enable framebuffer interrupts
* @sfb: main hardware state
*/
@@ -912,7 +1102,7 @@ static void s3c_fb_enable_irq(struct s3c_fb *sfb)
irq_ctrl_reg |= VIDINTCON0_INT_FRAME;
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK;
- irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC;
+ irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_FRONTPORCH;
irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK;
irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE;
@@ -1089,6 +1279,247 @@ static void s3c_fb_free_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
fbi->screen_base, fbi->fix.smem_start);
}
+/* V4L2 frambuffer subdevice */
+
+static struct s3c_fb_win_sd *to_fb_win_sd(struct v4l2_subdev *s)
+{
+ return container_of(s, struct s3c_fb_win_sd, sd);
+}
+
+/**
+ * v4l2_sd_fb_s_stream() - switch between DMA on local path mode
+ *
+ * @win: window to change operation mode for.
+ * @sd:
+ * @en: if nonzero - apply cropping rectangle and switch to local path,
+ * otherwise - restore cropping rectangle and switch to input DMA mode.
+ */
+static int v4l2_sd_fb_s_stream(struct v4l2_subdev *sd, int en)
+{
+ unsigned long flags;
+ struct s3c_fb_win_sd *w_sd = to_fb_win_sd(sd);
+ struct s3c_fb_win *win = w_sd->win;
+ struct s3c_fb *sfb = win->parent;
+ int retr = 100;
+ int ret = 0;
+
+ if (win->index > 2)
+ return -EINVAL;
+
+ mutex_lock(&win->fbinfo->lock);
+
+ if (en) {
+ ret = s3c_fb_enable_local_in(w_sd, en);
+ win->local_path = 1;
+ } else {
+ /* fimc-frambuffer fifo need to be stopped shortly after VSYNC,
+ for this reason horizontal line count is additionally
+ examined after waking up by an interrupt. If it is 0 we are
+ still at VSYNC and therefore are save to disable fifo.
+ Waiting for VSYNC is repeated unless it is the right time
+ to proceed. */
+ while (retr--) {
+ ret = s3c_fb_wait_for_vsync(sfb, 0);
+ if (ret)
+ continue;
+ local_irq_save(flags);
+ if (s3c_fb_get_line_count(sfb) == 0) {
+
+ s3c_fb_enable_local_in(w_sd, 0);
+ /* Notify FIMC driver */
+ v4l2_subdev_notify(&w_sd->sd, 0, NULL);
+ local_irq_restore(flags);
+ break;
+ }
+ local_irq_restore(flags);
+ }
+ if (!retr)
+ ret = -ETIMEDOUT;
+ win->local_path = 0;
+ }
+ mutex_unlock(&win->fbinfo->lock);
+
+ return ret;
+}
+
+/**
+ * v4l2_sd_fb_s_fmt() - set format for local input path mode
+ *
+ * @sd: pointer to v4l2 subdevice
+ * @fmt: pixel format to set
+ */
+static int v4l2_sd_fb_s_fmt(struct v4l2_subdev *sd, struct v4l2_format *fmt)
+{
+ struct s3c_fb_win_sd *fb_sd = to_fb_win_sd(sd);
+ int fourcc = fmt->fmt.pix.pixelformat;
+
+ if (!fmt || (fourcc != V4L2_PIX_FMT_YUYV
+ && fourcc != V4L2_PIX_FMT_RGB24))
+ return -EINVAL;
+ fb_sd->fmt.fmt.pix.pixelformat = fmt->fmt.pix.pixelformat;
+ return 0;
+}
+
+/**
+ * v4l2_sd_fb_cropcap() - get cropping capabilities for local fifo input mode
+ *
+ * @sd: pointer to v4l2 subdevice
+ * @cc:
+ */
+static int v4l2_sd_fb_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *cc)
+{
+ struct s3c_fb_win *win = to_fb_win_sd(sd)->win;
+ struct s3c_fb_pd_win *windata = win->windata;
+
+ if (!windata)
+ return -ENODEV;
+
+ mutex_lock(&win->fbinfo->lock);
+
+ cc->defrect.width = windata->win_mode.xres;
+ cc->defrect.height = windata->win_mode.yres;
+ cc->defrect.left = 0;
+ cc->defrect.top = 0;
+ cc->bounds = cc->defrect;
+
+ mutex_unlock(&win->fbinfo->lock);
+
+ return 0;
+}
+
+/**
+ * v4l2_sd_fb_s_crop() - set window position and size
+ *
+ * @sd: pointer to v4l2 subdevice
+ * @cr: cropping rectangle to set for local path mode
+ */
+static int v4l2_sd_fb_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *cr)
+{
+ struct s3c_fb_win_sd *fb_sd = to_fb_win_sd(sd);
+ u32 fourcc = fb_sd->fmt.fmt.pix.pixelformat;
+ struct v4l2_rect *r;
+
+ fb_sd->osd_rect = *cr;
+
+ if (fourcc == V4L2_PIX_FMT_YUYV) {
+ r = &cr->c;
+ r->left = round_down(r->left, 8);
+ r->top = round_down(r->top, 8);
+ r->width = round_down(r->width, 8);
+ r->height = round_down(r->height, 8);
+ }
+
+ return 0;
+}
+
+/**
+ * v4l2_sd_fb_g_crop() - set window position and size
+ *
+ * @sd: pointer to v4l2 subdevice
+ * @cr: rectangle to return current cropping parameters to
+ *
+ * Implements g_crop operation for camera interface driver.
+ */
+static int v4l2_sd_fb_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *cr)
+{
+ struct s3c_fb_win_sd *fb_sd = to_fb_win_sd(sd);
+
+ *cr = fb_sd->osd_rect;
+
+ return 0;
+}
+
+
+static struct v4l2_subdev_core_ops v4l2_sd_core_fb_ops = { NULL };
+
+static struct v4l2_subdev_video_ops v4l2_sd_video_fb_ops = {
+ .s_stream = v4l2_sd_fb_s_stream,
+ .s_fmt = v4l2_sd_fb_s_fmt,
+ .cropcap = v4l2_sd_fb_cropcap,
+ .s_crop = v4l2_sd_fb_s_crop,
+ .g_crop = v4l2_sd_fb_g_crop,
+};
+
+static struct v4l2_subdev_ops v4l2_sd_fb_ops = {
+ .core = &v4l2_sd_core_fb_ops,
+ .video = &v4l2_sd_video_fb_ops,
+};
+
+static int s3c_fb_unregister_subdevices(struct s3c_fb_win *win)
+{
+ int i;
+ struct s3c_fb *sfb = win->parent;
+
+ if (win->index >= S3C_FB_MAX_WIN)
+ return -ENODEV;
+
+ for (i = 0; i < S3C_FB_MAX_WIN_SOURCES; i++) {
+ if (win->sources[i]) {
+ /* remove sub_dev pointer from link */
+ win->sources[i]->link->sub_dev = NULL;
+ kfree(win->sources[i]);
+ dev_dbg(sfb->dev,
+ "s3c-fb subdevice %d removed from window %d\n",
+ i, win->index);
+ }
+ }
+
+ return 0;
+}
+
+/* Create the subdevice per each data source of the framebuffer window.
+ Locking: The caller holds win->parent->dev->mutex. */
+static int s3c_fb_register_subdevices(struct s3c_fb_win *win)
+{
+ int i;
+ struct s3c_fb *sfb = win->parent;
+ struct s3c_fb_pd_win *windata = win->windata;
+ struct s3c_fb_win_sd *win_sd;
+ struct v4l2_rect *r;
+
+
+ if (win->index >= S3C_FB_MAX_WIN)
+ return -ENODEV;
+
+ for (i = 0; i < S3C_FB_MAX_WIN_SOURCES; i++) {
+ if (!windata->fifo_sources[i])
+ continue;
+ win_sd = kzalloc(sizeof(struct s3c_fb_win_sd), GFP_KERNEL);
+ if (win_sd == NULL)
+ return -ENOMEM;
+ win_sd->index = i;
+ win_sd->win = win;
+ win_sd->link = windata->fifo_sources[i];
+ win_sd->streaming = 0;
+ v4l2_subdev_init(&win_sd->sd, &v4l2_sd_fb_ops);
+ snprintf(win_sd->sd.name, sizeof(win_sd->sd.name),
+ "s3cfb-local");
+
+ /* hook up pointer to slave device */
+ win_sd->link->sub_dev = &win_sd->sd;
+ win->sources[i] = win_sd;
+
+ /* set default rectangle to current window */
+ s3c_fb_get_osd(win, &win_sd->default_rect.c);
+
+ /* set fimc fifo output rectangle to current window */
+ r = &win_sd->osd_rect.c;
+ r->width = windata->win_mode.xres;
+ r->height = windata->win_mode.yres;
+ r->left = 0;
+ r->top = 0;
+
+ win_sd->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
+
+ dev_dbg(sfb->dev, "%s(): l:%d t:%d w:%d h:%d",
+ __func__, r->left, r->top, r->width, r->height);
+
+ dev_dbg(sfb->dev, "subdevice %d registered at window %d\n",
+ i, win->index);
+ }
+ return 0;
+}
+
/**
* s3c_fb_release_win() - release resources for a framebuffer window.
* @win: The window to cleanup the resources for.
@@ -1099,6 +1530,7 @@ static void s3c_fb_free_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
static void s3c_fb_release_win(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
if (win->fbinfo) {
+ s3c_fb_unregister_subdevices(win);
unregister_framebuffer(win->fbinfo);
if (&win->fbinfo->cmap)
fb_dealloc_cmap(&win->fbinfo->cmap);
@@ -1157,6 +1589,7 @@ static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
win->windata = windata;
win->index = win_no;
win->palette_buffer = (u32 *)(win + 1);
+ win->local_path = 0;
ret = s3c_fb_alloc_memory(sfb, win);
if (ret) {
@@ -1212,11 +1645,16 @@ static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
else
dev_err(sfb->dev, "failed to allocate fb cmap\n");
+ /* run the check_var and set_par on our configuration. */
s3c_fb_set_par(fbinfo);
- dev_dbg(sfb->dev, "about to register framebuffer\n");
+ ret = s3c_fb_register_subdevices(win);
+ if (ret < 0) {
+ dev_err(sfb->dev, "failed to register s3c-fb subdevices\n");
+ return ret;
+ }
- /* run the check_var and set_par on our configuration. */
+ dev_dbg(sfb->dev, "about to register framebuffer\n");
ret = register_framebuffer(fbinfo);
if (ret < 0) {