/* * Freescale MXS PxP driver * * Author: Matt Porter * * Copyright 2008-2010 Freescale Semiconductor, Inc. * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mxs_pxp.h" #define PXP_BASE_ADDR IO_ADDRESS(PXP_PHYS_ADDR) #define PXP_DRIVER_NAME "mxs-pxp" #define PXP_DRIVER_MAJOR 1 #define PXP_DRIVER_MINOR 0 #define PXP_DEF_BUFS 2 #define PXP_MIN_PIX 8 #define V4L2_OUTPUT_TYPE_INTERNAL 4 #define PXP_WAITCON ((__raw_readl(PXP_BASE_ADDR + HW_PXP_NEXT) & \ BM_PXP_NEXT_ENABLED) != BM_PXP_NEXT_ENABLED) #define REG_OFFSET 0x10 #define REGS1_NUMS 16 #define REGS2_NUMS 5 #define REGS3_NUMS 32 static u32 regs1[REGS1_NUMS]; static u32 regs2[REGS2_NUMS]; static u32 regs3[REGS3_NUMS]; static struct pxp_data_format pxp_s0_formats[] = { { .name = "24-bit RGB", .bpp = 4, .fourcc = V4L2_PIX_FMT_RGB24, .colorspace = V4L2_COLORSPACE_SRGB, .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB888, }, { .name = "16-bit RGB 5:6:5", .bpp = 2, .fourcc = V4L2_PIX_FMT_RGB565, .colorspace = V4L2_COLORSPACE_SRGB, .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB565, }, { .name = "16-bit RGB 5:5:5", .bpp = 2, .fourcc = V4L2_PIX_FMT_RGB555, .colorspace = V4L2_COLORSPACE_SRGB, .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__RGB555, }, { .name = "YUV 4:2:0 Planar", .bpp = 2, .fourcc = V4L2_PIX_FMT_YUV420, .colorspace = V4L2_COLORSPACE_JPEG, .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV420, }, { .name = "YUV 4:2:2 Planar", .bpp = 2, .fourcc = V4L2_PIX_FMT_YUV422P, .colorspace = V4L2_COLORSPACE_JPEG, .ctrl_s0_fmt = BV_PXP_CTRL_S0_FORMAT__YUV422, }, }; struct v4l2_queryctrl pxp_controls[] = { { .id = V4L2_CID_HFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Horizontal Flip", .minimum = 0, .maximum = 1, .step = 1, .default_value = 0, .flags = 0, }, { .id = V4L2_CID_VFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Vertical Flip", .minimum = 0, .maximum = 1, .step = 1, .default_value = 0, .flags = 0, }, { .id = V4L2_CID_PRIVATE_BASE, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Rotation", .minimum = 0, .maximum = 270, .step = 90, .default_value = 0, .flags = 0, }, { .id = V4L2_CID_PRIVATE_BASE + 1, .name = "Background Color", .minimum = 0, .maximum = 0xFFFFFF, .step = 1, .default_value = 0, .flags = 0, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_PRIVATE_BASE + 2, .name = "Set S0 Chromakey", .minimum = -1, .maximum = 0xFFFFFF, .step = 1, .default_value = -1, .flags = 0, .type = V4L2_CTRL_TYPE_INTEGER, }, { .id = V4L2_CID_PRIVATE_BASE + 3, .name = "YUV Colorspace", .minimum = 0, .maximum = 1, .step = 1, .default_value = 0, .flags = 0, .type = V4L2_CTRL_TYPE_BOOLEAN, }, }; static void pxp_set_ctrl(struct pxps *pxp) { u32 ctrl; ctrl = BF_PXP_CTRL_S0_FORMAT(pxp->s0_fmt->ctrl_s0_fmt); ctrl |= BF_PXP_CTRL_OUTBUF_FORMAT(BV_PXP_CTRL_OUTBUF_FORMAT__RGB888); ctrl |= BM_PXP_CTRL_CROP; if (pxp->scaling) ctrl |= BM_PXP_CTRL_SCALE; if (pxp->vflip) ctrl |= BM_PXP_CTRL_VFLIP; if (pxp->hflip) ctrl |= BM_PXP_CTRL_HFLIP; if (pxp->rotate) ctrl |= BF_PXP_CTRL_ROTATE(pxp->rotate / 90); ctrl |= BM_PXP_CTRL_IRQ_ENABLE; if (pxp->active) ctrl |= BM_PXP_CTRL_ENABLE; __raw_writel(ctrl, PXP_BASE_ADDR + HW_PXP_CTRL); pxp->regs_virt->ctrl = ctrl; } static void pxp_set_outbuf(struct pxps *pxp) { pxp->regs_virt->outbuf = pxp->outb_phys; /* Always equal to the FB size */ if (pxp->rotate % 180) pxp->regs_virt->outsize = BF_PXP_OUTSIZE_WIDTH(pxp->fb.fmt.height) | BF_PXP_OUTSIZE_HEIGHT(pxp->fb.fmt.width); else pxp->regs_virt->outsize = BF_PXP_OUTSIZE_WIDTH(pxp->fb.fmt.width) | BF_PXP_OUTSIZE_HEIGHT(pxp->fb.fmt.height); } static void pxp_set_s0colorkey(struct pxps *pxp) { /* Low and high are set equal. V4L does not allow a chromakey range */ if (pxp->s0_chromakey == -1) { /* disable color key */ pxp->regs_virt->s0colorkeylow = 0xFFFFFF; pxp->regs_virt->s0colorkeyhigh = 0; } else { pxp->regs_virt->s0colorkeylow = pxp->s0_chromakey; pxp->regs_virt->s0colorkeyhigh = pxp->s0_chromakey; } } static void pxp_set_s1colorkey(struct pxps *pxp) { /* Low and high are set equal. V4L does not allow a chromakey range */ if (pxp->s1_chromakey_state != 0 && pxp->s1_chromakey != -1) { pxp->regs_virt->olcolorkeylow = pxp->s1_chromakey; pxp->regs_virt->olcolorkeyhigh = pxp->s1_chromakey; } else { /* disable color key */ pxp->regs_virt->olcolorkeylow = 0xFFFFFF; pxp->regs_virt->olcolorkeyhigh = 0; } } static void pxp_set_oln(struct pxps *pxp) { pxp->regs_virt->ol0.ol = (u32) pxp->fb.base; if (pxp->rotate % 180) pxp->regs_virt->ol0.olsize = BF_PXP_OLnSIZE_WIDTH(pxp->fb.fmt.height >> 3) | BF_PXP_OLnSIZE_HEIGHT(pxp->fb.fmt.width >> 3); else pxp->regs_virt->ol0.olsize = BF_PXP_OLnSIZE_WIDTH(pxp->fb.fmt.width >> 3) | BF_PXP_OLnSIZE_HEIGHT(pxp->fb.fmt.height >> 3); } static void pxp_set_olparam(struct pxps *pxp) { u32 olparam; struct v4l2_pix_format *fmt = &pxp->fb.fmt; olparam = BF_PXP_OLnPARAM_ALPHA(pxp->global_alpha); if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) olparam |= BF_PXP_OLnPARAM_FORMAT(BV_PXP_OLnPARAM_FORMAT__RGB888); else olparam |= BF_PXP_OLnPARAM_FORMAT(BV_PXP_OLnPARAM_FORMAT__RGB565); if (pxp->global_alpha_state) olparam |= BF_PXP_OLnPARAM_ALPHA_CNTL (BV_PXP_OLnPARAM_ALPHA_CNTL__Override); if (pxp->s1_chromakey_state) olparam |= BM_PXP_OLnPARAM_ENABLE_COLORKEY; if (pxp->overlay_state) olparam |= BM_PXP_OLnPARAM_ENABLE; pxp->regs_virt->ol0.olparam = olparam; } static void pxp_set_s0param(struct pxps *pxp) { u32 s0param; s0param = BF_PXP_S0PARAM_XBASE(pxp->drect.left >> 3); s0param |= BF_PXP_S0PARAM_YBASE(pxp->drect.top >> 3); s0param |= BF_PXP_S0PARAM_WIDTH(pxp->s0_width >> 3); s0param |= BF_PXP_S0PARAM_HEIGHT(pxp->s0_height >> 3); pxp->regs_virt->s0param = s0param; } static void pxp_set_s0crop(struct pxps *pxp) { u32 s0crop; s0crop = BF_PXP_S0CROP_XBASE(pxp->srect.left >> 3); s0crop |= BF_PXP_S0CROP_YBASE(pxp->srect.top >> 3); s0crop |= BF_PXP_S0CROP_WIDTH(pxp->drect.width >> 3); s0crop |= BF_PXP_S0CROP_HEIGHT(pxp->drect.height >> 3); pxp->regs_virt->s0crop = s0crop; } static int pxp_set_scaling(struct pxps *pxp) { int ret = 0; u32 xscale, yscale, s0scale; if ((pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV420) && (pxp->s0_fmt->fourcc != V4L2_PIX_FMT_YUV422P)) { pxp->scaling = 0; ret = -EINVAL; goto out; } if ((pxp->srect.width == pxp->drect.width) && (pxp->srect.height == pxp->drect.height)) { pxp->regs_virt->s0scale = 0x10001000; pxp->scaling = 0; goto out; } pxp->scaling = 1; xscale = pxp->srect.width * 0x1000 / pxp->drect.width; yscale = pxp->srect.height * 0x1000 / pxp->drect.height; if (xscale > PXP_DOWNSCALE_THRESHOLD) xscale = PXP_DOWNSCALE_THRESHOLD; if (yscale > PXP_DOWNSCALE_THRESHOLD) yscale = PXP_DOWNSCALE_THRESHOLD; s0scale = BF_PXP_S0SCALE_YSCALE(yscale) | BF_PXP_S0SCALE_XSCALE(xscale); pxp->regs_virt->s0scale = s0scale; out: pxp_set_ctrl(pxp); return ret; } static int pxp_set_fbinfo(struct pxps *pxp) { struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; struct v4l2_framebuffer *fb = &pxp->fb; int err; err = mxsfb_get_info(&var, &fix); fb->fmt.width = var.xres; fb->fmt.height = var.yres; if (var.bits_per_pixel == 16) fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565; else fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24; fb->base = (void *)fix.smem_start; return err; } static void pxp_set_s0bg(struct pxps *pxp) { pxp->regs_virt->s0background = pxp->s0_bgcolor; } static void pxp_set_csc(struct pxps *pxp) { if (pxp->yuv) { /* YUV colorspace */ __raw_writel(0x04030000, PXP_BASE_ADDR + HW_PXP_CSCCOEFF0); __raw_writel(0x01230208, PXP_BASE_ADDR + HW_PXP_CSCCOEFF1); __raw_writel(0x076b079b, PXP_BASE_ADDR + HW_PXP_CSCCOEFF2); } else { /* YCrCb colorspace */ __raw_writel(0x84ab01f0, PXP_BASE_ADDR + HW_PXP_CSCCOEFF0); __raw_writel(0x01230204, PXP_BASE_ADDR + HW_PXP_CSCCOEFF1); __raw_writel(0x0730079c, PXP_BASE_ADDR + HW_PXP_CSCCOEFF2); } } static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc) { if (vc->id == V4L2_CID_HFLIP) pxp->hflip = vc->value; else if (vc->id == V4L2_CID_VFLIP) pxp->vflip = vc->value; else if (vc->id == V4L2_CID_PRIVATE_BASE) { if (vc->value % 90) return -ERANGE; pxp->rotate = vc->value; } else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) { pxp->s0_bgcolor = vc->value; pxp_set_s0bg(pxp); } else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) { pxp->s0_chromakey = vc->value; pxp_set_s0colorkey(pxp); } else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) { pxp->yuv = vc->value; pxp_set_csc(pxp); } pxp_set_ctrl(pxp); return 0; } static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc) { if (vc->id == V4L2_CID_HFLIP) vc->value = pxp->hflip; else if (vc->id == V4L2_CID_VFLIP) vc->value = pxp->vflip; else if (vc->id == V4L2_CID_PRIVATE_BASE) vc->value = pxp->rotate; else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) vc->value = pxp->s0_bgcolor; else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) vc->value = pxp->s0_chromakey; else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) vc->value = pxp->yuv; return 0; } static int pxp_enumoutput(struct file *file, void *fh, struct v4l2_output *o) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); if ((o->index < 0) || (o->index > 1)) return -EINVAL; memset(o, 0, sizeof(struct v4l2_output)); if (o->index == 0) { strcpy(o->name, "PxP Display Output"); pxp->output = 0; } else { strcpy(o->name, "PxP Virtual Output"); pxp->output = 1; } o->type = V4L2_OUTPUT_TYPE_INTERNAL; o->std = 0; o->reserved[0] = pxp->outb_phys; return 0; } static int pxp_g_output(struct file *file, void *fh, unsigned int *i) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); *i = pxp->output; return 0; } static int pxp_s_output(struct file *file, void *fh, unsigned int i) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct v4l2_pix_format *fmt = &pxp->fb.fmt; int bpp; if ((i < 0) || (i > 1)) return -EINVAL; if (pxp->outb) goto out; /* Output buffer is same format as fbdev */ if (fmt->pixelformat == V4L2_PIX_FMT_RGB24) bpp = 4; else bpp = 2; pxp->outb = kmalloc(fmt->width * fmt->height * bpp, GFP_KERNEL); pxp->outb_phys = virt_to_phys(pxp->outb); dma_map_single(NULL, pxp->outb, fmt->width * fmt->height * bpp, DMA_TO_DEVICE); out: pxp_set_outbuf(pxp); return 0; } static int pxp_enum_fmt_video_output(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) { enum v4l2_buf_type type = fmt->type; int index = fmt->index; if ((fmt->index < 0) || (fmt->index >= ARRAY_SIZE(pxp_s0_formats))) return -EINVAL; memset(fmt, 0, sizeof(struct v4l2_fmtdesc)); fmt->index = index; fmt->type = type; fmt->pixelformat = pxp_s0_formats[index].fourcc; strcpy(fmt->description, pxp_s0_formats[index].name); return 0; } static int pxp_g_fmt_video_output(struct file *file, void *fh, struct v4l2_format *f) { struct v4l2_pix_format *pf = &f->fmt.pix; struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct pxp_data_format *fmt = pxp->s0_fmt; pf->width = pxp->s0_width; pf->height = pxp->s0_height; pf->pixelformat = fmt->fourcc; pf->field = V4L2_FIELD_NONE; pf->bytesperline = fmt->bpp * pf->width; pf->sizeimage = pf->bytesperline * pf->height; pf->colorspace = fmt->colorspace; pf->priv = 0; return 0; } static struct pxp_data_format *pxp_get_format(struct v4l2_format *f) { struct pxp_data_format *fmt; int i; for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) { fmt = &pxp_s0_formats[i]; if (fmt->fourcc == f->fmt.pix.pixelformat) break; } if (i == ARRAY_SIZE(pxp_s0_formats)) return NULL; return &pxp_s0_formats[i]; } static int pxp_try_fmt_video_output(struct file *file, void *fh, struct v4l2_format *f) { int w = f->fmt.pix.width; int h = f->fmt.pix.height; struct pxp_data_format *fmt = pxp_get_format(f); if (!fmt) return -EINVAL; w = min(w, 2040); w = max(w, 8); h = min(h, 2040); h = max(h, 8); f->fmt.pix.field = V4L2_FIELD_NONE; f->fmt.pix.width = w; f->fmt.pix.height = h; f->fmt.pix.pixelformat = fmt->fourcc; return 0; } static int pxp_s_fmt_video_output(struct file *file, void *fh, struct v4l2_format *f) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct v4l2_pix_format *pf = &f->fmt.pix; int ret = pxp_try_fmt_video_output(file, fh, f); if (ret == 0) { pxp->s0_fmt = pxp_get_format(f); pxp->s0_width = pf->width; pxp->s0_height = pf->height; pxp_set_ctrl(pxp); pxp_set_s0param(pxp); } return ret; } static int pxp_g_fmt_output_overlay(struct file *file, void *fh, struct v4l2_format *f) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct v4l2_window *wf = &f->fmt.win; memset(wf, 0, sizeof(struct v4l2_window)); wf->chromakey = pxp->s1_chromakey; wf->global_alpha = pxp->global_alpha; wf->field = V4L2_FIELD_NONE; wf->clips = NULL; wf->clipcount = 0; wf->bitmap = NULL; wf->w.left = pxp->srect.left; wf->w.top = pxp->srect.top; wf->w.width = pxp->srect.width; wf->w.height = pxp->srect.height; return 0; } static int pxp_try_fmt_output_overlay(struct file *file, void *fh, struct v4l2_format *f) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct v4l2_window *wf = &f->fmt.win; struct v4l2_rect srect; u32 s1_chromakey = wf->chromakey; u8 global_alpha = wf->global_alpha; memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect)); pxp_g_fmt_output_overlay(file, fh, f); wf->chromakey = s1_chromakey; wf->global_alpha = global_alpha; /* Constrain parameters to the input buffer */ wf->w.left = srect.left; wf->w.top = srect.top; wf->w.width = min(srect.width, ((__s32) pxp->s0_width - wf->w.left)); wf->w.height = min(srect.height, ((__s32) pxp->s0_height - wf->w.top)); return 0; } static int pxp_s_fmt_output_overlay(struct file *file, void *fh, struct v4l2_format *f) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); struct v4l2_window *wf = &f->fmt.win; int ret = pxp_try_fmt_output_overlay(file, fh, f); if (ret == 0) { pxp->srect.left = wf->w.left; pxp->srect.top = wf->w.top; pxp->srect.width = wf->w.width; pxp->srect.height = wf->w.height; pxp->global_alpha = wf->global_alpha; pxp->s1_chromakey = wf->chromakey; pxp_set_s0param(pxp); pxp_set_s0crop(pxp); pxp_set_scaling(pxp); pxp_set_olparam(pxp); pxp_set_s1colorkey(pxp); } return ret; } static int pxp_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *r) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); return videobuf_reqbufs(&pxp->s0_vbq, r); } static int pxp_querybuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); return videobuf_querybuf(&pxp->s0_vbq, b); } static int pxp_qbuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); return videobuf_qbuf(&pxp->s0_vbq, b); } static int pxp_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK); } static int pxp_streamon(struct file *file, void *priv, enum v4l2_buf_type t) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); int ret = 0; if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -EINVAL; pxp_set_outbuf(pxp); ret = videobuf_streamon(&pxp->s0_vbq); if (!ret && (pxp->output == 0)) mxsfb_cfg_pxp(1, pxp->outb_phys); return ret; } static int pxp_streamoff(struct file *file, void *priv, enum v4l2_buf_type t) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); int ret = 0; if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -EINVAL; ret = videobuf_streamoff(&pxp->s0_vbq); if (!ret) mxsfb_cfg_pxp(0, 0); return ret; } static int pxp_buf_setup(struct videobuf_queue *q, unsigned int *count, unsigned *size) { struct pxps *pxp = q->priv_data; *size = pxp->s0_width * pxp->s0_height * pxp->s0_fmt->bpp; if (0 == *count) *count = PXP_DEF_BUFS; return 0; } static void pxp_buf_free(struct videobuf_queue *q, struct videobuf_buffer *vb) { if (in_interrupt()) BUG(); videobuf_dma_contig_free(q, vb); vb->state = VIDEOBUF_NEEDS_INIT; } static int pxp_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { struct pxps *pxp = q->priv_data; int ret = 0; vb->width = pxp->s0_width; vb->height = pxp->s0_height; vb->size = vb->width * vb->height * pxp->s0_fmt->bpp; vb->field = V4L2_FIELD_NONE; vb->state = VIDEOBUF_NEEDS_INIT; ret = videobuf_iolock(q, vb, NULL); if (ret) goto fail; vb->state = VIDEOBUF_PREPARED; return 0; fail: pxp_buf_free(q, vb); return ret; } static void pxp_buf_next(struct pxps *pxp) { dma_addr_t Y, U, V; if (pxp->active) { pxp->active->state = VIDEOBUF_ACTIVE; Y = videobuf_to_dma_contig(pxp->active); pxp->regs_virt->s0buf = Y; if ((pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) || (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV422P)) { int s = 1; /* default to YUV 4:2:2 */ if (pxp->s0_fmt->fourcc == V4L2_PIX_FMT_YUV420) s = 2; U = Y + (pxp->s0_width * pxp->s0_height); V = U + ((pxp->s0_width * pxp->s0_height) >> s); pxp->regs_virt->s0ubuf = U; pxp->regs_virt->s0vbuf = V; } pxp->regs_virt->ctrl = __raw_readl(PXP_BASE_ADDR + HW_PXP_CTRL) | BM_PXP_CTRL_ENABLE; } __raw_writel(pxp->regs_phys, PXP_BASE_ADDR + HW_PXP_NEXT); } static void pxp_next_handle(struct work_struct *w) { struct pxps *pxp = container_of(w, struct pxps, work); struct pxp_buffer *buf, *next; unsigned long flags; if (pxp->next_queue_ended == 1) return; spin_lock_irqsave(&pxp->lock, flags); while (!list_empty(&pxp->nextq)) { spin_unlock_irqrestore(&pxp->lock, flags); if (!wait_event_interruptible_timeout(pxp->done, PXP_WAITCON, 5 * HZ) || signal_pending(current)) { spin_lock_irqsave(&pxp->lock, flags); list_for_each_entry_safe(buf, next, &pxp->nextq, queue) list_del(&buf->queue); spin_unlock_irqrestore(&pxp->lock, flags); pxp->next_queue_ended = 1; return; } spin_lock_irqsave(&pxp->lock, flags); buf = list_entry(pxp->nextq.next, struct pxp_buffer, queue); list_del_init(&buf->queue); pxp->active = &buf->vb; pxp->active->state = VIDEOBUF_QUEUED; pxp_buf_next(pxp); } spin_unlock_irqrestore(&pxp->lock, flags); } static void pxp_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct pxps *pxp = q->priv_data; struct pxp_buffer *buf; unsigned long flags; spin_lock_irqsave(&pxp->lock, flags); if (list_empty(&pxp->outq)) { list_add_tail(&vb->queue, &pxp->outq); vb->state = VIDEOBUF_QUEUED; pxp->active = vb; pxp_buf_next(pxp); } else { list_add_tail(&vb->queue, &pxp->outq); buf = container_of(vb, struct pxp_buffer, vb); list_add_tail(&buf->queue, &pxp->nextq); queue_work(pxp->workqueue, &pxp->work); } spin_unlock_irqrestore(&pxp->lock, flags); } static void pxp_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) { pxp_buf_free(q, vb); } static struct videobuf_queue_ops pxp_vbq_ops = { .buf_setup = pxp_buf_setup, .buf_prepare = pxp_buf_prepare, .buf_queue = pxp_buf_queue, .buf_release = pxp_buf_release, }; static int pxp_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); memset(cap, 0, sizeof(*cap)); strcpy(cap->driver, "pxp"); strcpy(cap->card, "pxp"); strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev), sizeof(cap->bus_info)); cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR; cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_OVERLAY | V4L2_CAP_STREAMING; return 0; } static int pxp_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *fb) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); memset(fb, 0, sizeof(*fb)); fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY | V4L2_FBUF_CAP_CHROMAKEY | V4L2_FBUF_CAP_LOCAL_ALPHA | V4L2_FBUF_CAP_GLOBAL_ALPHA; if (pxp->global_alpha_state) fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA; if (pxp->local_alpha_state) fb->flags |= V4L2_FBUF_FLAG_LOCAL_ALPHA; if (pxp->s1_chromakey_state) fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY; return 0; } static int pxp_s_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *fb) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); pxp->overlay_state = (fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0; pxp->global_alpha_state = (fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0; pxp->local_alpha_state = (fb->flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) != 0; /* Global alpha overrides local alpha if both are requested */ if (pxp->global_alpha_state && pxp->local_alpha_state) pxp->local_alpha_state = 0; pxp->s1_chromakey_state = (fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0; pxp_set_olparam(pxp); pxp_set_s0crop(pxp); pxp_set_scaling(pxp); return 0; } static int pxp_g_crop(struct file *file, void *fh, struct v4l2_crop *c) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) return -EINVAL; c->c.left = pxp->drect.left; c->c.top = pxp->drect.top; c->c.width = pxp->drect.width; c->c.height = pxp->drect.height; return 0; } static int pxp_s_crop(struct file *file, void *fh, struct v4l2_crop *c) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); int l = c->c.left; int t = c->c.top; int w = c->c.width; int h = c->c.height; int fbw = pxp->fb.fmt.width; int fbh = pxp->fb.fmt.height; if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY) return -EINVAL; /* Constrain parameters to FB limits */ w = min(w, fbw); w = max(w, PXP_MIN_PIX); h = min(h, fbh); h = max(h, PXP_MIN_PIX); if ((l + w) > fbw) l = 0; if ((t + h) > fbh) t = 0; /* Round up values to PxP pixel block */ l = roundup(l, PXP_MIN_PIX); t = roundup(t, PXP_MIN_PIX); w = roundup(w, PXP_MIN_PIX); h = roundup(h, PXP_MIN_PIX); pxp->drect.left = l; pxp->drect.top = t; pxp->drect.width = w; pxp->drect.height = h; pxp_set_s0param(pxp); pxp_set_s0crop(pxp); pxp_set_scaling(pxp); return 0; } static int pxp_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *qc) { int i; for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) if (qc->id && qc->id == pxp_controls[i].id) { memcpy(qc, &(pxp_controls[i]), sizeof(*qc)); return 0; } return -EINVAL; } static int pxp_g_ctrl(struct file *file, void *priv, struct v4l2_control *vc) { int i; struct pxps *pxp = video_get_drvdata(video_devdata(file)); for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) if (vc->id == pxp_controls[i].id) return pxp_get_cstate(pxp, vc); return -EINVAL; } static int pxp_s_ctrl(struct file *file, void *priv, struct v4l2_control *vc) { int i; struct pxps *pxp = video_get_drvdata(video_devdata(file)); for (i = 0; i < ARRAY_SIZE(pxp_controls); i++) if (vc->id == pxp_controls[i].id) { if (vc->value < pxp_controls[i].minimum || vc->value > pxp_controls[i].maximum) return -ERANGE; return pxp_set_cstate(pxp, vc); } return -EINVAL; } void pxp_release(struct video_device *vfd) { struct pxps *pxp = video_get_drvdata(vfd); spin_lock(&pxp->lock); video_device_release(vfd); spin_unlock(&pxp->lock); } static int pxp_hw_init(struct pxps *pxp) { struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; int err; err = mxsfb_get_info(&var, &fix); if (err) return err; /* Pull PxP out of reset */ __raw_writel(0, PXP_BASE_ADDR + HW_PXP_CTRL); /* Config defaults */ pxp->active = NULL; pxp->s0_fmt = &pxp_s0_formats[0]; pxp->drect.left = pxp->srect.left = 0; pxp->drect.top = pxp->srect.top = 0; pxp->drect.width = pxp->srect.width = pxp->s0_width = var.xres; pxp->drect.height = pxp->srect.height = pxp->s0_height = var.yres; pxp->s0_bgcolor = 0; pxp->output = 0; err = pxp_set_fbinfo(pxp); if (err) return err; pxp->scaling = 0; pxp->hflip = 0; pxp->vflip = 0; pxp->rotate = 0; pxp->yuv = 0; pxp->overlay_state = 0; pxp->global_alpha_state = 0; pxp->global_alpha = 0; pxp->local_alpha_state = 0; pxp->s1_chromakey_state = 0; pxp->s1_chromakey = -1; pxp->s0_chromakey = -1; /* Write default h/w config */ pxp_set_ctrl(pxp); pxp_set_s0param(pxp); pxp_set_s0crop(pxp); pxp_set_oln(pxp); pxp_set_olparam(pxp); pxp_set_s0colorkey(pxp); pxp_set_s1colorkey(pxp); pxp_set_csc(pxp); return 0; } static int pxp_open(struct file *file) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); int ret = 0; mutex_lock(&pxp->mutex); pxp->users++; if (pxp->users > 1) { pxp->users--; ret = -EBUSY; goto out; } out: mutex_unlock(&pxp->mutex); if (ret) return ret; pxp->next_queue_ended = 0; pxp->workqueue = create_singlethread_workqueue("pxp"); videobuf_queue_dma_contig_init(&pxp->s0_vbq, &pxp_vbq_ops, &pxp->pdev->dev, &pxp->lock, V4L2_BUF_TYPE_VIDEO_OUTPUT, V4L2_FIELD_NONE, sizeof(struct pxp_buffer), pxp); return 0; } static int pxp_close(struct file *file) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); if (pxp->workqueue) destroy_workqueue(pxp->workqueue); videobuf_stop(&pxp->s0_vbq); videobuf_mmap_free(&pxp->s0_vbq); pxp->active = NULL; mutex_lock(&pxp->mutex); pxp->users--; mutex_unlock(&pxp->mutex); return 0; } static int pxp_mmap(struct file *file, struct vm_area_struct *vma) { struct pxps *pxp = video_get_drvdata(video_devdata(file)); int ret; ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma); return ret; } static const struct v4l2_file_operations pxp_fops = { .owner = THIS_MODULE, .open = pxp_open, .release = pxp_close, .ioctl = video_ioctl2, .mmap = pxp_mmap, }; static const struct v4l2_ioctl_ops pxp_ioctl_ops = { .vidioc_querycap = pxp_querycap, .vidioc_reqbufs = pxp_reqbufs, .vidioc_querybuf = pxp_querybuf, .vidioc_qbuf = pxp_qbuf, .vidioc_dqbuf = pxp_dqbuf, .vidioc_streamon = pxp_streamon, .vidioc_streamoff = pxp_streamoff, .vidioc_enum_output = pxp_enumoutput, .vidioc_g_output = pxp_g_output, .vidioc_s_output = pxp_s_output, .vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output, .vidioc_try_fmt_vid_out = pxp_try_fmt_video_output, .vidioc_g_fmt_vid_out = pxp_g_fmt_video_output, .vidioc_s_fmt_vid_out = pxp_s_fmt_video_output, .vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay, .vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay, .vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay, .vidioc_g_fbuf = pxp_g_fbuf, .vidioc_s_fbuf = pxp_s_fbuf, .vidioc_g_crop = pxp_g_crop, .vidioc_s_crop = pxp_s_crop, .vidioc_queryctrl = pxp_queryctrl, .vidioc_g_ctrl = pxp_g_ctrl, .vidioc_s_ctrl = pxp_s_ctrl, }; static const struct video_device pxp_template = { .name = "PxP", .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING, .fops = &pxp_fops, .release = pxp_release, .minor = -1, .ioctl_ops = &pxp_ioctl_ops, }; static irqreturn_t pxp_irq(int irq, void *dev_id) { struct pxps *pxp = (struct pxps *)dev_id; struct videobuf_buffer *vb; unsigned long flags; spin_lock_irqsave(&pxp->lock, flags); __raw_writel(BM_PXP_STAT_IRQ, PXP_BASE_ADDR + HW_PXP_STAT_CLR); if (list_empty(&pxp->outq)) { pr_warning("irq: outq empty!!!\n"); goto out; } vb = list_entry(pxp->outq.next, struct videobuf_buffer, queue); list_del_init(&vb->queue); vb->state = VIDEOBUF_DONE; do_gettimeofday(&vb->ts); vb->field_count++; wake_up(&vb->done); wake_up(&pxp->done); out: spin_unlock_irqrestore(&pxp->lock, flags); return IRQ_HANDLED; } static int pxp_probe(struct platform_device *pdev) { struct pxps *pxp; struct resource *res; int irq; int err = 0; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || irq < 0) { err = -ENODEV; goto exit; } pxp = kzalloc(sizeof(*pxp), GFP_KERNEL); if (!pxp) { dev_err(&pdev->dev, "failed to allocate control object\n"); err = -ENOMEM; goto exit; } dev_set_drvdata(&pdev->dev, pxp); pxp->res = res; pxp->irq = irq; pxp->regs_virt = dma_alloc_coherent(NULL, PAGE_ALIGN(sizeof (struct pxp_registers)), &pxp->regs_phys, GFP_KERNEL); if (pxp->regs_virt == NULL) { dev_err(&pdev->dev, "failed to allocate pxp_register object\n"); err = -ENOMEM; goto exit; } init_waitqueue_head(&pxp->done); INIT_WORK(&pxp->work, pxp_next_handle); INIT_LIST_HEAD(&pxp->outq); INIT_LIST_HEAD(&pxp->nextq); spin_lock_init(&pxp->lock); mutex_init(&pxp->mutex); if (!request_mem_region(res->start, res->end - res->start + 1, PXP_DRIVER_NAME)) { err = -EBUSY; goto freepxp; } pxp->regs = (void __iomem *)res->start; /* it is already ioremapped */ pxp->pdev = pdev; err = request_irq(pxp->irq, pxp_irq, 0, PXP_DRIVER_NAME, pxp); if (err) { dev_err(&pdev->dev, "interrupt register failed\n"); goto release; } pxp->vdev = video_device_alloc(); if (!pxp->vdev) { dev_err(&pdev->dev, "video_device_alloc() failed\n"); err = -ENOMEM; goto freeirq; } memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template)); video_set_drvdata(pxp->vdev, pxp); err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, 0); if (err) { dev_err(&pdev->dev, "failed to register video device\n"); goto freevdev; } err = pxp_hw_init(pxp); if (err) { dev_err(&pdev->dev, "failed to initialize hardware\n"); goto freevdev; } dev_info(&pdev->dev, "initialized\n"); exit: return err; freevdev: video_device_release(pxp->vdev); freeirq: free_irq(pxp->irq, pxp); release: release_mem_region(res->start, res->end - res->start + 1); freepxp: kfree(pxp); return err; } static int __devexit pxp_remove(struct platform_device *pdev) { struct pxps *pxp = platform_get_drvdata(pdev); video_unregister_device(pxp->vdev); video_device_release(pxp->vdev); if (pxp->regs_virt) dma_free_coherent(0, PAGE_ALIGN(sizeof(struct pxp_registers)), pxp->regs_virt, pxp->regs_phys); kfree(pxp->outb); kfree(pxp); return 0; } #ifdef CONFIG_PM static int pxp_suspend(struct platform_device *pdev, pm_message_t state) { int i; while (__raw_readl(PXP_BASE_ADDR + HW_PXP_CTRL) & BM_PXP_CTRL_ENABLE) ; for (i = 0; i < REGS1_NUMS; i++) regs1[i] = __raw_readl(PXP_BASE_ADDR + HW_PXP_CTRL + REG_OFFSET * i); for (i = 0; i < REGS2_NUMS; i++) regs2[i] = __raw_readl(PXP_BASE_ADDR + HW_PXP_S0COLORKEYLOW + REG_OFFSET * i); for (i = 0; i < REGS3_NUMS; i++) regs3[i] = __raw_readl(PXP_BASE_ADDR + HW_PXP_OLn(0) + REG_OFFSET * i); __raw_writel(BM_PXP_CTRL_SFTRST, PXP_BASE_ADDR + HW_PXP_CTRL); return 0; } static int pxp_resume(struct platform_device *pdev) { int i; /* Pull PxP out of reset */ __raw_writel(0, PXP_BASE_ADDR + HW_PXP_CTRL); for (i = 0; i < REGS1_NUMS; i++) __raw_writel(regs1[i], PXP_BASE_ADDR + HW_PXP_CTRL + REG_OFFSET * i); for (i = 0; i < REGS2_NUMS; i++) __raw_writel(regs2[i], PXP_BASE_ADDR + HW_PXP_S0COLORKEYLOW + REG_OFFSET * i); for (i = 0; i < REGS3_NUMS; i++) __raw_writel(regs3[i], PXP_BASE_ADDR + HW_PXP_OLn(0) + REG_OFFSET * i); return 0; } #else #define pxp_suspend NULL #define pxp_resume NULL #endif static struct platform_driver pxp_driver = { .driver = { .name = PXP_DRIVER_NAME, }, .probe = pxp_probe, .remove = __exit_p(pxp_remove), .suspend = pxp_suspend, .resume = pxp_resume, }; static int __devinit pxp_init(void) { return platform_driver_register(&pxp_driver); } static void __exit pxp_exit(void) { platform_driver_unregister(&pxp_driver); } module_init(pxp_init); module_exit(pxp_exit); MODULE_DESCRIPTION("STMP37xx PxP driver"); MODULE_AUTHOR("Matt Porter "); MODULE_LICENSE("GPL");