diff options
author | Hans Verkuil <hverkuil@xs4all.nl> | 2007-07-20 09:29:43 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2007-10-09 22:03:02 -0300 |
commit | 32db775452818656d5fd8fd8b0f54425f5cfc177 (patch) | |
tree | 36289a03f0cea5cb247181700e62fe38a094b2ff /drivers/media/video/ivtv/ivtv-fb.c | |
parent | d3c35acf2455fe354bdd00085511bef6bd6d52f1 (diff) | |
download | lwn-32db775452818656d5fd8fd8b0f54425f5cfc177.tar.gz lwn-32db775452818656d5fd8fd8b0f54425f5cfc177.zip |
V4L/DVB (5902): Add ivtv-fb framebuffer driver.
Add the ivtv-fb framebuffer driver for cx23415 devices (currently
only the Hauppauge PVR-350 cards). This makes it possible to use
the On-Screen Display functionality of these cards, either for menus
during MPEG playback, or as a console or X display.
Signed-off-by: Kevin Thayer <nufan_wfk@yahoo.com>
Signed-off-by: Chris Kennedy <c@groovy.org>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: John P Harvey <john.p.harvey@btinternet.com>
Signed-off-by: Ian Armstrong <ian@iarmst.demon.co.uk>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/video/ivtv/ivtv-fb.c')
-rw-r--r-- | drivers/media/video/ivtv/ivtv-fb.c | 1215 |
1 files changed, 1215 insertions, 0 deletions
diff --git a/drivers/media/video/ivtv/ivtv-fb.c b/drivers/media/video/ivtv/ivtv-fb.c new file mode 100644 index 000000000000..8e9bd75fc2bf --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-fb.c @@ -0,0 +1,1215 @@ +/* + On Screen Display cx23415 Framebuffer driver + + This module presents the cx23415 OSD (onscreen display) framebuffer memory + as a standard Linux /dev/fb style framebuffer device. The framebuffer has + support for 8,16 & 32 bpp packed pixel formats with alpha channel. In 16bpp + mode, there is a choice of a three color depths (12, 15 or 16 bits), but no + local alpha. The colorspace is selectable between rgb & yuv. + Depending on the TV standard configured in the ivtv module at load time, + the initial resolution is either 640x400 (NTSC) or 640x480 (PAL) at 8bpp. + Video timings are locked to ensure a vertical refresh rate of 50Hz (PAL) + or 59.94 (NTSC) + + Copyright (c) 2003 Matt T. Yourst <yourst@yourst.com> + + Derived from drivers/video/vesafb.c + Portions (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + + 2.6 kernel port: + Copyright (C) 2004 Matthias Badaire + + Copyright (C) 2004 Chris Kennedy <c@groovy.org> + + Copyright (C) 2006 Ian Armstrong <ian@iarmst.demon.co.uk> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/bitops.h> +#include <linux/pagemap.h> +#include <linux/matroxfb.h> + +#include <asm/io.h> +#include <asm/ioctl.h> + +#ifdef CONFIG_MTRR +#include <asm/mtrr.h> +#endif + +#include "ivtv-driver.h" +#include "ivtv-queue.h" +#include "ivtv-udma.h" +#include "ivtv-irq.h" +#include "ivtv-fileops.h" +#include "ivtv-mailbox.h" +#include "ivtv-cards.h" +#include <media/ivtv-fb.h> + +/* card parameters */ +static int ivtv_fb_card_id = -1; +static int ivtv_fb_debug = 0; +static int osd_laced; +static int osd_compat; +static int osd_depth; +static int osd_upper; +static int osd_left; +static int osd_yres; +static int osd_xres; + +module_param(ivtv_fb_card_id, int, 0444); +module_param_named(debug,ivtv_fb_debug, int, 0644); +module_param(osd_laced, bool, 0444); +module_param(osd_compat, bool, 0444); +module_param(osd_depth, int, 0444); +module_param(osd_upper, int, 0444); +module_param(osd_left, int, 0444); +module_param(osd_yres, int, 0444); +module_param(osd_xres, int, 0444); + +MODULE_PARM_DESC(ivtv_fb_card_id, + "Only use framebuffer of the specified ivtv card (0-31)\n" + "\t\t\tdefault -1: initialize all available framebuffers"); + +MODULE_PARM_DESC(debug, + "Debug level (bitmask). Default: errors only\n" + "\t\t\t(debug = 3 gives full debugging)"); + +MODULE_PARM_DESC(osd_compat, + "Compatibility mode - Display size is locked (use for old X drivers)\n" + "\t\t\t0=off\n" + "\t\t\t1=on\n" + "\t\t\tdefault off"); + +/* Why upper, left, xres, yres, depth, laced ? To match terminology used + by fbset. + Why start at 1 for left & upper coordinate ? Because X doesn't allow 0 */ + +MODULE_PARM_DESC(osd_laced, + "Interlaced mode\n" + "\t\t\t0=off\n" + "\t\t\t1=on\n" + "\t\t\tdefault off"); + +MODULE_PARM_DESC(osd_depth, + "Bits per pixel - 8,16,32\n" + "\t\t\tdefault 8"); + +MODULE_PARM_DESC(osd_upper, + "Vertical start position\n" + "\t\t\tdefault 0 (Centered)"); + +MODULE_PARM_DESC(osd_left, + "Horizontal start position\n" + "\t\t\tdefault 0 (Centered)"); + +MODULE_PARM_DESC(osd_yres, + "Display height\n" + "\t\t\tdefault 480 (PAL)\n" + "\t\t\t 400 (NTSC)"); + +MODULE_PARM_DESC(osd_xres, + "Display width\n" + "\t\t\tdefault 640"); + +MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil, John Harvey, Ian Armstrong"); +MODULE_LICENSE("GPL"); + +/* --------------------------------------------------------------------- */ + +#define IVTV_FB_DBGFLG_WARN (1 << 0) +#define IVTV_FB_DBGFLG_INFO (1 << 1) + +#define IVTV_FB_DEBUG(x, type, fmt, args...) \ + do { \ + if ((x) & ivtv_fb_debug) \ + printk(KERN_INFO "ivtv-fb%d " type ": " fmt, itv->num , ## args); \ + } while (0) +#define IVTV_FB_DEBUG_WARN(fmt, args...) IVTV_FB_DEBUG(IVTV_FB_DBGFLG_WARN, "warning", fmt , ## args) +#define IVTV_FB_DEBUG_INFO(fmt, args...) IVTV_FB_DEBUG(IVTV_FB_DBGFLG_INFO, "info", fmt , ## args) + +/* Standard kernel messages */ +#define IVTV_FB_ERR(fmt, args...) printk(KERN_ERR "ivtv-fb%d: " fmt, itv->num , ## args) +#define IVTV_FB_WARN(fmt, args...) printk(KERN_WARNING "ivtv-fb%d: " fmt, itv->num , ## args) +#define IVTV_FB_INFO(fmt, args...) printk(KERN_INFO "ivtv-fb%d: " fmt, itv->num , ## args) + +/* --------------------------------------------------------------------- */ + +#define IVTV_OSD_MAX_WIDTH 720 +#define IVTV_OSD_MAX_HEIGHT 576 + +#define IVTV_OSD_BPP_8 0x00 +#define IVTV_OSD_BPP_16_444 0x03 +#define IVTV_OSD_BPP_16_555 0x02 +#define IVTV_OSD_BPP_16_565 0x01 +#define IVTV_OSD_BPP_32 0x04 + +struct osd_info { + /* Timing info for modes */ + u32 pixclock; + u32 hlimit; + u32 vlimit; + + /* Physical base address */ + unsigned long video_pbase; + /* Relative base address (relative to start of decoder memory) */ + u32 video_rbase; + /* Mapped base address */ + volatile char __iomem *video_vbase; + /* Buffer size */ + u32 video_buffer_size; + +#ifdef CONFIG_MTRR + /* video_base rounded down as required by hardware MTRRs */ + unsigned long fb_start_aligned_physaddr; + /* video_base rounded up as required by hardware MTRRs */ + unsigned long fb_end_aligned_physaddr; +#endif + + /* Store the buffer offset */ + int set_osd_coords_x; + int set_osd_coords_y; + + /* Current dimensions (NOT VISIBLE SIZE!) */ + int display_width; + int display_height; + int display_byte_stride; + + /* Current bits per pixel */ + int bits_per_pixel; + int bytes_per_pixel; + + /* Frame buffer stuff */ + struct fb_info ivtvfb_info; + struct fb_var_screeninfo ivtvfb_defined; + struct fb_fix_screeninfo ivtvfb_fix; +}; + +struct ivtv_osd_coords { + unsigned long offset; + unsigned long max_offset; + int pixel_stride; + int lines; + int x; + int y; +}; + +/* --------------------------------------------------------------------- */ + +/* ivtv API calls for framebuffer related support */ + +static int ivtv_fb_get_framebuffer(struct ivtv *itv, u32 *fbbase, + u32 *fblength) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + int rc; + + rc = ivtv_vapi_result(itv, data, CX2341X_OSD_GET_FRAMEBUFFER, 0); + *fbbase = data[0]; + *fblength = data[1]; + return rc; +} + +static int ivtv_fb_get_osd_coords(struct ivtv *itv, + struct ivtv_osd_coords *osd) +{ + u32 data[CX2341X_MBOX_MAX_DATA]; + + ivtv_vapi_result(itv, data, CX2341X_OSD_GET_OSD_COORDS, 0); + + osd->offset = data[0] - itv->osd_info->video_rbase; + osd->max_offset = itv->osd_info->display_width * itv->osd_info->display_height * 4; + osd->pixel_stride = data[1]; + osd->lines = data[2]; + osd->x = data[3]; + osd->y = data[4]; + return 0; +} + +static int ivtv_fb_set_osd_coords(struct ivtv *itv, const struct ivtv_osd_coords *osd) +{ + itv->osd_info->display_width = osd->pixel_stride; + itv->osd_info->display_byte_stride = osd->pixel_stride * itv->osd_info->bytes_per_pixel; + itv->osd_info->set_osd_coords_x += osd->x; + itv->osd_info->set_osd_coords_y = osd->y; + + return ivtv_vapi(itv, CX2341X_OSD_SET_OSD_COORDS, 5, + osd->offset + itv->osd_info->video_rbase, + osd->pixel_stride, + osd->lines, osd->x, osd->y); +} + +static int ivtv_fb_set_display_window(struct ivtv *itv, struct v4l2_rect *ivtv_window) +{ + + int osd_height_limit = itv->is_50hz ? 576 : 480; + + /* Only fail if resolution too high, otherwise fudge the start coords. */ + if ((ivtv_window->height > osd_height_limit) || (ivtv_window->width > IVTV_OSD_MAX_WIDTH)) + return -EINVAL; + + /* Ensure we don't exceed display limits */ + if (ivtv_window->top + ivtv_window->height > osd_height_limit) { + IVTV_FB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid height setting (%d,%d)\n", + ivtv_window->top, ivtv_window->height); + ivtv_window->top = osd_height_limit - ivtv_window->height; + } + + if (ivtv_window->left + ivtv_window->width > IVTV_OSD_MAX_WIDTH) { + IVTV_FB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid width setting (%d,%d)\n", + ivtv_window->left, ivtv_window->width); + ivtv_window->left = IVTV_OSD_MAX_WIDTH - ivtv_window->width; + } + + /* Set the OSD origin */ + write_reg((ivtv_window->top << 16) | ivtv_window->left, 0x02a04); + + /* How much to display */ + write_reg(((ivtv_window->top+ivtv_window->height) << 16) | (ivtv_window->left+ivtv_window->width), 0x02a08); + + /* Pass this info back the yuv handler */ + itv->yuv_info.osd_vis_w = ivtv_window->width; + itv->yuv_info.osd_vis_h = ivtv_window->height; + itv->yuv_info.osd_x_offset = ivtv_window->left; + itv->yuv_info.osd_y_offset = ivtv_window->top; + + return 0; +} + +static int ivtv_fb_prep_dec_dma_to_device(struct ivtv *itv, + unsigned long ivtv_dest_addr, void __user *userbuf, + int size_in_bytes) +{ + DEFINE_WAIT(wait); + int ret = 0; + int got_sig = 0; + + mutex_lock(&itv->udma.lock); + /* Map User DMA */ + if (ivtv_udma_setup(itv, ivtv_dest_addr, userbuf, size_in_bytes) <= 0) { + mutex_unlock(&itv->udma.lock); + IVTV_FB_WARN("ivtvfb_prep_dec_dma_to_device, " + "Error with get_user_pages: %d bytes, %d pages returned\n", + size_in_bytes, itv->udma.page_count); + + /* get_user_pages must have failed completely */ + return -EIO; + } + + IVTV_FB_DEBUG_INFO("ivtvfb_prep_dec_dma_to_device, %d bytes, %d pages\n", + size_in_bytes, itv->udma.page_count); + + ivtv_udma_prepare(itv); + prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); + /* if no UDMA is pending and no UDMA is in progress, then the DMA + is finished */ + while (itv->i_flags & (IVTV_F_I_UDMA_PENDING | IVTV_F_I_UDMA)) { + /* don't interrupt if the DMA is in progress but break off + a still pending DMA. */ + got_sig = signal_pending(current); + if (got_sig && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) + break; + got_sig = 0; + schedule(); + } + finish_wait(&itv->dma_waitq, &wait); + + /* Unmap Last DMA Xfer */ + ivtv_udma_unmap(itv); + mutex_unlock(&itv->udma.lock); + if (got_sig) { + IVTV_DEBUG_INFO("User stopped OSD\n"); + return -EINTR; + } + + return ret; +} + +static int ivtv_fb_prep_frame(struct ivtv *itv, int cmd, void __user *source, unsigned long dest_offset, int count) +{ + DEFINE_WAIT(wait); + + /* Nothing to do */ + if (count == 0) { + IVTV_FB_DEBUG_WARN("ivtv_fb_prep_frame: Nothing to do. count = 0\n"); + return -EINVAL; + } + + /* Check Total FB Size */ + if ((dest_offset + count) > itv->osd_info->video_buffer_size) { + IVTV_FB_WARN( + "ivtv_fb_prep_frame: Overflowing the framebuffer %ld, " + "only %d available\n", + (dest_offset + count), itv->osd_info->video_buffer_size); + return -E2BIG; + } + + /* Not fatal, but will have undesirable results */ + if ((unsigned long)source & 3) + IVTV_FB_WARN ("ivtv_fb_prep_frame: Source address not 32 bit aligned (0x%08lx)\n",(unsigned long)source); + + if (dest_offset & 3) + IVTV_FB_WARN ("ivtv_fb_prep_frame: Dest offset not 32 bit aligned (%ld)\n",dest_offset); + + if (count & 3) + IVTV_FB_WARN ("ivtv_fb_prep_frame: Count not a multiple of 4 (%d)\n",count); + + /* Check Source */ + if (!access_ok(VERIFY_READ, source + dest_offset, count)) { + IVTV_FB_WARN( + "Invalid userspace pointer!!! 0x%08lx\n", + (unsigned long)source); + + IVTV_FB_DEBUG_WARN( + "access_ok() failed for offset 0x%08lx source 0x%08lx count %d\n", + dest_offset, (unsigned long)source, + count); + return -EINVAL; + } + + /* OSD Address to send DMA to */ + dest_offset += IVTV_DEC_MEM_START + itv->osd_info->video_rbase; + + /* Fill Buffers */ + return ivtv_fb_prep_dec_dma_to_device(itv, dest_offset, source, count); +} + +static int ivtvfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + DEFINE_WAIT(wait); + struct ivtv *itv = (struct ivtv *)info->par; + int rc=0; + + switch (cmd) { + + case FBIOGET_VBLANK: { + struct fb_vblank vblank; + u32 trace; + + vblank.flags = FB_VBLANK_HAVE_COUNT |FB_VBLANK_HAVE_VCOUNT | + FB_VBLANK_HAVE_VSYNC; + trace = read_reg (0x028c0) >> 16; + if (itv->is_50hz && trace > 312) trace -= 312; + else if (itv->is_60hz && trace > 262) trace -= 262; + if (trace == 1) vblank.flags |= FB_VBLANK_VSYNCING; + vblank.count = itv->lastVsyncFrame; + vblank.vcount = trace; + vblank.hcount = 0; + if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) + return -EFAULT; + return 0; + } + + case FBIO_WAITFORVSYNC: { + prepare_to_wait(&itv->vsync_waitq, &wait, TASK_INTERRUPTIBLE); + if (!schedule_timeout(HZ/20)) rc = -ETIMEDOUT; + finish_wait (&itv->vsync_waitq, &wait); + return rc; + } + + case IVTVFB_IOCTL_PREP_FRAME: { + struct ivtvfb_ioctl_dma_host_to_ivtv_args args; + + IVTV_FB_DEBUG_INFO("IVTVFB_IOCTL_PREP_FRAME\n"); + if (copy_from_user(&args, (void __user *)arg, sizeof(args))) + return -EFAULT; + + return ivtv_fb_prep_frame(itv, cmd, args.source, args.dest_offset, args.count); + } + + default: + IVTV_FB_ERR("Unknown IOCTL %d\n",cmd); + return -EINVAL; + } + return 0; +} + +/* Framebuffer device handling */ + +static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var) +{ + + struct ivtv_osd_coords ivtv_osd; + struct v4l2_rect ivtv_window; + + IVTV_FB_DEBUG_INFO("ivtvfb_set_var\n"); + + /* Select color space */ + if (var->nonstd) /* YUV */ + write_reg (read_reg(0x02a00) | 0x0002000,0x02a00); + else /* RGB */ + write_reg (read_reg(0x02a00) & ~0x0002000,0x02a00); + + /* Set the color mode + Although rare, occasionally things go wrong. The extra mode + change seems to help... */ + + switch (var->bits_per_pixel) { + case 8: + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, IVTV_OSD_BPP_8); + break; + case 32: + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, IVTV_OSD_BPP_32); + break; + case 16: + switch (var->green.length) { + case 4: + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, IVTV_OSD_BPP_16_444); + break; + case 5: + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, IVTV_OSD_BPP_16_555); + break; + case 6: + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); + ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, IVTV_OSD_BPP_16_565); + break; + default: + IVTV_FB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); + } + break; + default: + IVTV_FB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); + } + + itv->osd_info->bits_per_pixel = var->bits_per_pixel; + itv->osd_info->bytes_per_pixel = var->bits_per_pixel / 8; + + /* Set the flicker filter */ + switch (var->vmode & FB_VMODE_MASK) { + case FB_VMODE_NONINTERLACED: /* Filter on */ + ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 1); + break; + case FB_VMODE_INTERLACED: /* Filter off */ + ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 0); + break; + default: + IVTV_FB_DEBUG_WARN("ivtvfb_set_var - Invalid video mode\n"); + } + + /* Read the current osd info */ + ivtv_fb_get_osd_coords(itv, &ivtv_osd); + + /* Now set the OSD to the size we want */ + ivtv_osd.pixel_stride = var->xres_virtual; + ivtv_osd.lines = var->yres_virtual; + ivtv_osd.x = 0; + ivtv_osd.y = 0; + ivtv_fb_set_osd_coords(itv, &ivtv_osd); + + /* Can't seem to find the right API combo for this. + Use another function which does what we need through direct register access. */ + ivtv_window.width = var->xres; + ivtv_window.height = var->yres; + + /* Minimum margin cannot be 0, as X won't allow such a mode */ + if (!var->upper_margin) var->upper_margin ++; + if (!var->left_margin) var->left_margin ++; + ivtv_window.top = var->upper_margin - 1; + ivtv_window.left = var->left_margin - 1; + + ivtv_fb_set_display_window(itv, &ivtv_window); + + /* Force update of yuv registers */ + itv->yuv_info.yuv_forced_update = 1; + + IVTV_FB_INFO("=== Display mode change ===\n"); + IVTV_FB_INFO("Display size %dx%d (%dx%d Virtual) @ %dbpp\n", + var->xres, + var->yres, + var->xres_virtual, + var->yres_virtual, + var->bits_per_pixel); + + IVTV_FB_INFO("Display position %d,%d\n", + var->left_margin, + var->upper_margin); + + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) { + IVTV_FB_INFO("Display filter : on\n"); + } + else { + IVTV_FB_INFO("Display filter : off\n"); + } + + if (var->nonstd) { + IVTV_FB_INFO("Color space : YUV\n"); + } + else { + IVTV_FB_INFO("Color space : RGB\n"); + } + + return 0; +} + +static int ivtvfb_get_fix(struct ivtv *itv, struct fb_fix_screeninfo *fix) +{ + IVTV_FB_DEBUG_INFO ("ivtvfb_get_fix\n"); + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id, "cx23415 TV out"); + fix->smem_start = itv->osd_info->video_pbase; + fix->smem_len = itv->osd_info->video_buffer_size; + fix->type = FB_TYPE_PACKED_PIXELS; + fix->visual = (itv->osd_info->bits_per_pixel == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; + fix->xpanstep = 1; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->line_length = itv->osd_info->display_byte_stride; + fix->accel = FB_ACCEL_NONE; + return 0; +} + +/* Check the requested display mode, returning -EINVAL if we can't + handle it. */ + +static int _ivtvfb_check_var(struct fb_var_screeninfo *var, struct ivtv *itv) +{ + int osd_height_limit = itv->is_50hz ? 576 : 480; + + IVTV_FB_DEBUG_INFO ("ivtvfb_check_var\n"); + + /* Check the bits per pixel */ + if (osd_compat) { + if (var->bits_per_pixel != 32) { + IVTV_FB_DEBUG_WARN ("Invalid colour mode: %d\n",var->bits_per_pixel); + return -EINVAL; + } + } + + if (var->bits_per_pixel == 8 || var->bits_per_pixel == 32) { + var->transp.offset = 24; + var->transp.length = 8; + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + } + else if (var->bits_per_pixel == 16) { + /* To find out the true mode, check green length */ + switch (var->green.length) { + case 4: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 8; + var->red.length = 4; + var->green.offset = 4; + var->green.length = 4; + var->blue.offset = 0; + var->blue.length = 4; + break; + case 5: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + break; + default: + var->transp.offset = 0; + var->transp.length = 0; + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + break; + } + } + else { + IVTV_FB_DEBUG_WARN ("Invalid colour mode: %d\n",var->bits_per_pixel); + return -EINVAL; + } + + /* Check the resolution */ + if (osd_compat) { + if (var->xres != itv->osd_info->ivtvfb_defined.xres || var->yres != itv->osd_info->ivtvfb_defined.yres || + var->xres_virtual != itv->osd_info->ivtvfb_defined.xres_virtual || var->yres_virtual != + itv->osd_info->ivtvfb_defined.yres_virtual) { + IVTV_FB_DEBUG_WARN ("Invalid resolution: %d x %d (%d x %d Virtual)\n", + var->xres,var->yres, var->xres_virtual,var->yres_virtual); + return -EINVAL; + } + } + else { + if (var->xres > IVTV_OSD_MAX_WIDTH || var->yres > osd_height_limit ) { + IVTV_FB_DEBUG_WARN ("Invalid resolution: %d x %d\n", + var->xres,var->yres); + return -EINVAL; + } + + /* Max horizontal size is 1023 @ 32bpp, 2046 & 16bpp, 4092 @ 8bpp */ + if (var->xres_virtual > 4095 / (var->bits_per_pixel / 8) || + var->xres_virtual * var->yres_virtual * (var->bits_per_pixel/8) > itv->osd_info->video_buffer_size || + var->xres_virtual < var->xres || + var->yres_virtual < var->yres) { + IVTV_FB_DEBUG_WARN ("Invalid virtual resolution: %d x %d\n", + var->xres_virtual, var->yres_virtual); + return -EINVAL; + } + } + + /* Some extra checks if in 8 bit mode */ + if (var->bits_per_pixel == 8) { + /* Width must be a multiple of 4 */ + if (var->xres & 3) { + IVTV_FB_DEBUG_WARN ("Invalid resolution for 8bpp: %d\n", var->xres); + return -EINVAL; + } + if (var->xres_virtual & 3) { + IVTV_FB_DEBUG_WARN ("Invalid virtual resolution for 8bpp: %d)\n", var->xres_virtual); + return -EINVAL; + } + } + else if (var->bits_per_pixel == 16) { + /* Width must be a multiple of 2 */ + if (var->xres & 1) { + IVTV_FB_DEBUG_WARN ("Invalid resolution for 16bpp: %d\n", var->xres); + return -EINVAL; + } + if (var->xres_virtual & 1) { + IVTV_FB_DEBUG_WARN ("Invalid virtual resolution for 16bpp: %d)\n", var->xres_virtual); + return -EINVAL; + } + } + + /* Now check the offsets */ + if (var->xoffset >= var->xres_virtual || var->yoffset >= var->yres_virtual) { + IVTV_FB_DEBUG_WARN ("Invalid offset: %d (%d) %d (%d)\n",var->xoffset,var->xres_virtual, + var->yoffset,var->yres_virtual); + return -EINVAL; + } + + /* Check pixel format */ + if (var->nonstd > 1) { + IVTV_FB_DEBUG_WARN ("Invalid nonstd % d\n",var->nonstd); + return -EINVAL; + } + + /* Check video mode */ + if (((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) && + ((var->vmode & FB_VMODE_MASK) != FB_VMODE_INTERLACED)) { + IVTV_FB_DEBUG_WARN ("Invalid video mode: %d\n",var->vmode & FB_VMODE_MASK); + return -EINVAL; + } + + /* Check the left & upper margins + If the margins are too large, just center the screen + (enforcing margins causes too many problems) */ + + if (var->left_margin + var->xres > IVTV_OSD_MAX_WIDTH + 1) { + var->left_margin = 1 + ((IVTV_OSD_MAX_WIDTH - var->xres) / 2); + } + if (var->upper_margin + var->yres > (itv->is_50hz ? 577 : 481)) { + var->upper_margin = 1 + (((itv->is_50hz ? 576 : 480) - var->yres) / 2); + } + + /* Maintain overall 'size' for a constant refresh rate */ + var->right_margin = itv->osd_info->hlimit - var->left_margin - var->xres; + var->lower_margin = itv->osd_info->vlimit - var->upper_margin - var->yres; + + /* Fixed sync times */ + var->hsync_len = 24; + var->vsync_len = 2; + + /* Non-interlaced / interlaced mode is used to switch the OSD filter + on or off. Adjust the clock timings to maintain a constant + vertical refresh rate. */ + var->pixclock = itv->osd_info->pixclock; + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) + var->pixclock /= 2; + + IVTV_FB_DEBUG_INFO ("ivtvfb_check_var - Parameters validated\n"); + + IVTV_FB_INFO("=== Validated display mode ===\n"); + IVTV_FB_INFO("Display size %dx%d (%dx%d Virtual) @ %dbpp\n", + var->xres, + var->yres, + var->xres_virtual, + var->yres_virtual, + var->bits_per_pixel); + + IVTV_FB_INFO("Display position %d,%d\n", + var->left_margin, + var->upper_margin); + + if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) { + IVTV_FB_INFO("Display filter : on\n"); + } + else { + IVTV_FB_INFO("Display filter : off\n"); + } + + if (var->nonstd) { + IVTV_FB_INFO("Color space : YUV\n"); + } + else { + IVTV_FB_INFO("Color space : RGB\n"); + } + return 0; +} + +static int ivtvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct ivtv *itv = (struct ivtv *) info->par; + IVTV_FB_DEBUG_INFO ("ivtvfb_check_var\n"); + return _ivtvfb_check_var (var,itv); +} + +static int ivtvfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + u32 osd_pan_index; + struct ivtv *itv = (struct ivtv *) info->par; + + osd_pan_index = (var->xoffset + (var->yoffset * var->xres_virtual))*var->bits_per_pixel/8; + write_reg (osd_pan_index,0x02A0C); + + /* Pass this info back the yuv handler */ + itv->yuv_info.osd_x_pan = var->xoffset; + itv->yuv_info.osd_y_pan = var->yoffset; + /* Force update of yuv registers */ + itv->yuv_info.yuv_forced_update = 1; + return 0; +} + +static int ivtvfb_set_par(struct fb_info *info) +{ + int rc = 0; + struct ivtv *itv = (struct ivtv *) info->par; + + IVTV_FB_DEBUG_INFO ("ivtvfb_set_par\n"); + + rc = ivtvfb_set_var(itv, &info->var); + ivtvfb_pan_display(&info->var, info); + ivtvfb_get_fix (itv, &info->fix); + return rc; +} + +static int ivtvfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) +{ + u32 color, *palette; + struct ivtv *itv = (struct ivtv *) info->par; + + if (regno >= info->cmap.len) + return -EINVAL; + + color = ((transp & 0xFF00) << 16) |((red & 0xFF00) << 8) | (green & 0xFF00) | ((blue & 0xFF00) >> 8); + if (info->var.bits_per_pixel <= 8) { + write_reg(regno, 0x02a30); + write_reg(color, 0x02a34); + } + else { + if (regno >= 16) + return -EINVAL; + + palette = info->pseudo_palette; + if (info->var.bits_per_pixel == 16) { + switch (info->var.green.length) { + case 4: + color = ((red & 0xf000) >> 4) | + ((green & 0xf000) >> 8) | + ((blue & 0xf000) >> 12); + break; + case 5: + color = ((red & 0xf800) >> 1) | + ((green & 0xf800) >> 6) | + ((blue & 0xf800) >> 11); + break; + case 6: + color = (red & 0xf800 ) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + } + } + palette[regno] = color; + } + + return 0; +} + +/* We don't really support blanking. All this does is enable or + disable the OSD. */ +static int ivtvfb_blank(int blank_mode, struct fb_info *info) +{ + struct ivtv *itv = (struct ivtv *)info->par; + + IVTV_FB_DEBUG_INFO ("Set blanking mode : %d\n",blank_mode); + switch (blank_mode) { + case FB_BLANK_UNBLANK: + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 1); + break; + case FB_BLANK_NORMAL: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); + break; + } + return 0; +} + +static struct fb_ops ivtvfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = ivtvfb_check_var, + .fb_set_par = ivtvfb_set_par, + .fb_setcolreg = ivtvfb_setcolreg, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = NULL, + .fb_ioctl = ivtvfb_ioctl, + .fb_pan_display = ivtvfb_pan_display, + .fb_blank = ivtvfb_blank, +}; + +/* Initialization */ + + +/* Setup our initial video mode */ +static int ivtvfb_init_vidmode(struct ivtv *itv) +{ + int max_height; + struct v4l2_rect start_window; + + /* Set base references for mode calcs. */ + if (itv->is_50hz) { + itv->osd_info->pixclock = 84316; + itv->osd_info->hlimit = 776; + itv->osd_info->vlimit = 591; + } + else { + itv->osd_info->pixclock = 83926; + itv->osd_info->hlimit = 776; + itv->osd_info->vlimit = 495; + } + + /* Color mode */ + + if (osd_compat) osd_depth = 32; + if (osd_depth != 8 && osd_depth != 16 && osd_depth != 32) osd_depth = 8; + itv->osd_info->bits_per_pixel = osd_depth; + itv->osd_info->bytes_per_pixel = itv->osd_info->bits_per_pixel / 8; + + /* Horizontal size & position */ + + if (osd_xres > 720) osd_xres = 720; + + /* Must be a multiple of 4 for 8bpp & 2 for 16bpp */ + if (osd_depth == 8) + osd_xres &= ~3; + else if (osd_depth == 16) + osd_xres &= ~1; + + if (osd_xres) + start_window.width = osd_xres; + else + start_window.width = osd_compat ? 720: 640; + + /* Check horizontal start (osd_left). */ + if (osd_left && osd_left + start_window.width > 721) { + IVTV_FB_ERR ("Invalid osd_left - assuming default\n"); + osd_left = 0; + } + + /* Hardware coords start at 0, user coords start at 1. */ + osd_left --; + + start_window.left = + osd_left >= 0 ? osd_left : ((IVTV_OSD_MAX_WIDTH - start_window.width) / 2); + + itv->osd_info->display_byte_stride = + start_window.width * itv->osd_info->bytes_per_pixel; + + /* Vertical size & position */ + + max_height = itv->is_50hz ? 576 : 480; + + if ( osd_yres > max_height) osd_yres = max_height; + + if (osd_yres) + start_window.height = osd_yres; + else { + if (itv->is_50hz) + start_window.height = osd_compat ? max_height : 480; + else + start_window.height = osd_compat ? max_height : 400; + } + + /* Check vertical start (osd_upper). */ + if (osd_upper + start_window.height > max_height + 1) { + IVTV_FB_ERR ("Invalid osd_upper - assuming default\n"); + osd_upper = 0; + } + + /* Hardware coords start at 0, user coords start at 1. */ + osd_upper --; + + start_window.top = osd_upper >= 0 ? osd_upper : ((max_height - start_window.height) / 2); + + itv->osd_info->display_width = start_window.width; + itv->osd_info->display_height = start_window.height; + + /* Generate a valid fb_var_screeninfo */ + + itv->osd_info->ivtvfb_defined.xres = itv->osd_info->display_width; + itv->osd_info->ivtvfb_defined.yres = itv->osd_info->display_height; + itv->osd_info->ivtvfb_defined.xres_virtual = itv->osd_info->display_width; + itv->osd_info->ivtvfb_defined.yres_virtual = itv->osd_info->display_height; + itv->osd_info->ivtvfb_defined.bits_per_pixel = itv->osd_info->bits_per_pixel; + itv->osd_info->ivtvfb_defined.vmode = (osd_laced ? FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED); + itv->osd_info->ivtvfb_defined.left_margin = start_window.left + 1; + itv->osd_info->ivtvfb_defined.upper_margin = start_window.top + 1; + itv->osd_info->ivtvfb_defined.accel_flags = FB_ACCEL_NONE; + itv->osd_info->ivtvfb_defined.nonstd = 0; + + /* We've filled in the most data, let the usual mode check + routine fill in the rest. */ + _ivtvfb_check_var (&itv->osd_info->ivtvfb_defined,itv); + + /* Generate valid fb_fix_screeninfo */ + + ivtvfb_get_fix(itv,&itv->osd_info->ivtvfb_fix); + + /* Generate valid fb_info */ + + itv->osd_info->ivtvfb_info.node = -1; + itv->osd_info->ivtvfb_info.flags = FBINFO_FLAG_DEFAULT; + itv->osd_info->ivtvfb_info.fbops = &ivtvfb_ops; + itv->osd_info->ivtvfb_info.par = itv; + itv->osd_info->ivtvfb_info.var = itv->osd_info->ivtvfb_defined; + itv->osd_info->ivtvfb_info.fix = itv->osd_info->ivtvfb_fix; + itv->osd_info->ivtvfb_info.screen_base = (u8 __iomem *)itv->osd_info->video_vbase; + itv->osd_info->ivtvfb_info.fbops = &ivtvfb_ops; + + /* Supply some monitor specs. Bogus values will do for now */ + itv->osd_info->ivtvfb_info.monspecs.hfmin = 8000; + itv->osd_info->ivtvfb_info.monspecs.hfmax = 70000; + itv->osd_info->ivtvfb_info.monspecs.vfmin = 10; + itv->osd_info->ivtvfb_info.monspecs.vfmax = 100; + + /* Allocate color map */ + if (fb_alloc_cmap(&itv->osd_info->ivtvfb_info.cmap, 256, 1)) { + IVTV_FB_ERR ("abort, unable to alloc cmap\n"); + return -ENOMEM; + } + + /* Allocate the pseudo palette */ + itv->osd_info->ivtvfb_info.pseudo_palette = kmalloc(sizeof (u32) * 16, GFP_KERNEL); + + if (!itv->osd_info->ivtvfb_info.pseudo_palette) { + IVTV_FB_ERR ("abort, unable to alloc pseudo pallete\n"); + return -ENOMEM; + } + + return 0; +} + +/* Find OSD buffer base & size. Add to mtrr. Zero osd buffer. */ + +static int ivtvfb_init_io(struct ivtv *itv) +{ + ivtv_fb_get_framebuffer(itv, &itv->osd_info->video_rbase, &itv->osd_info->video_buffer_size); + + /* The osd buffer size depends on the number of video buffers allocated + on the PVR350 itself. For now we'll hardcode the smallest osd buffer + size to prevent any overlap. */ + itv->osd_info->video_buffer_size = 1704960; + + itv->osd_info->video_pbase = itv->base_addr + IVTV_DECODER_OFFSET + itv->osd_info->video_rbase; + itv->osd_info->video_vbase = itv->dec_mem + itv->osd_info->video_rbase; + + if (!itv->osd_info->video_vbase) { + IVTV_FB_ERR("abort, video memory 0x%x @ 0x%lx isn't mapped!\n", + itv->osd_info->video_buffer_size, itv->osd_info->video_pbase); + return -EIO; + } + + IVTV_FB_INFO("Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n", + itv->osd_info->video_pbase, itv->osd_info->video_vbase, + itv->osd_info->video_buffer_size / 1024); + +#ifdef CONFIG_MTRR + { + /* Find the largest power of two that maps the whole buffer */ + int size_shift = 31; + + while (!(itv->osd_info->video_buffer_size & (1 << size_shift))) { + size_shift--; + } + size_shift++; + itv->osd_info->fb_start_aligned_physaddr = itv->osd_info->video_pbase & ~((1 << size_shift) - 1); + itv->osd_info->fb_end_aligned_physaddr = itv->osd_info->video_pbase + itv->osd_info->video_buffer_size; + itv->osd_info->fb_end_aligned_physaddr += (1 << size_shift) - 1; + itv->osd_info->fb_end_aligned_physaddr &= ~((1 << size_shift) - 1); + if (mtrr_add(itv->osd_info->fb_start_aligned_physaddr, + itv->osd_info->fb_end_aligned_physaddr - itv->osd_info->fb_start_aligned_physaddr, + MTRR_TYPE_WRCOMB, 1) < 0) { + IVTV_FB_ERR("warning: mtrr_add() failed to add write combining region 0x%08x-0x%08x\n", + (unsigned int)itv->osd_info->fb_start_aligned_physaddr, + (unsigned int)itv->osd_info->fb_end_aligned_physaddr); + } + } +#endif /* CONFIG_MTRR */ + + /* Blank the entire osd. */ + memset_io(itv->osd_info->video_vbase, 0, itv->osd_info->video_buffer_size); + + return 0; +} + +/* Release any memory we've grabbed & remove mtrr entry */ +static void ivtvfb_release_buffers (struct ivtv *itv) +{ + /* Release cmap */ + if (itv->osd_info->ivtvfb_info.cmap.len); + fb_dealloc_cmap(&itv->osd_info->ivtvfb_info.cmap); + + /* Release pseudo palette */ + if (itv->osd_info->ivtvfb_info.pseudo_palette) + kfree(itv->osd_info->ivtvfb_info.pseudo_palette); + +#ifdef CONFIG_MTRR + mtrr_del(-1, itv->osd_info->fb_start_aligned_physaddr, + (itv->osd_info->fb_end_aligned_physaddr - itv->osd_info->fb_start_aligned_physaddr)); +#endif /* CONFIG_MTRR */ + + kfree(itv->osd_info); + itv->osd_info = NULL; +} + +/* Initialize the specified card */ + +static int ivtvfb_init_card (struct ivtv *itv) +{ + int rc; + + if (itv->osd_info) { + IVTV_FB_ERR("Card %d already initialised\n", ivtv_fb_card_id); + return -EBUSY; + } + + itv->osd_info = kzalloc(sizeof(struct osd_info), GFP_ATOMIC); + if (itv->osd_info == 0) { + IVTV_FB_ERR("Failed to allocate memory for osd_info\n"); + return -ENOMEM; + } + + /* Find & setup the OSD buffer */ + if ((rc = ivtvfb_init_io (itv))) + return rc; + + /* Set the startup video mode information */ + if ((rc = ivtvfb_init_vidmode (itv))) { + ivtvfb_release_buffers(itv); + return rc; + } + + /* Register the framebuffer */ + if (register_framebuffer(&itv->osd_info->ivtvfb_info) < 0) { + ivtvfb_release_buffers(itv); + return -EINVAL; + } + + itv->osd_video_pbase = itv->osd_info->video_pbase; + + /* Set the card to the requested mode */ + ivtvfb_set_par(&itv->osd_info->ivtvfb_info); + + /* Set color 0 to black */ + write_reg(0, 0x02a30); + write_reg(0, 0x02a34); + + /* Enable the osd */ + ivtvfb_blank(FB_BLANK_UNBLANK, &itv->osd_info->ivtvfb_info); + + /* Note if we're running in compatibility mode */ + if (osd_compat) + IVTV_FB_INFO("Running in compatibility mode. Display resize & mode change disabled\n"); + + /* Allocate DMA */ + ivtv_udma_alloc(itv); + return 0; + +} + +static int __init ivtvfb_init(void) +{ + struct ivtv *itv; + int i, registered = 0; + + if (ivtv_fb_card_id < -1 || ivtv_fb_card_id >= IVTV_MAX_CARDS) { + printk(KERN_ERR "ivtv-fb: ivtv_fb_card_id parameter is out of range (valid range: -1 - %d)\n", + IVTV_MAX_CARDS - 1); + return -EINVAL; + } + + /* Locate & initialise all cards supporting an OSD. */ + for (i = 0; i < ivtv_cards_active; i++) { + if (ivtv_fb_card_id != -1 && i != ivtv_fb_card_id) + continue; + itv = ivtv_cards[i]; + if (itv && (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT)) { + if (ivtvfb_init_card(itv) == 0) { + IVTV_FB_INFO("Framebuffer registered on ivtv card id %d\n", i); + registered++; + } + } + } + if (!registered) { + printk(KERN_ERR "ivtv-fb: no cards found"); + return -ENODEV; + } + return 0; +} + +static void ivtvfb_cleanup(void) +{ + struct ivtv *itv; + int i; + + printk(KERN_INFO "ivtv-fb: Unloading framebuffer module\n"); + + for (i = 0; i < ivtv_cards_active; i++) { + itv = ivtv_cards[i]; + if (itv && (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) && itv->osd_info) { + IVTV_FB_DEBUG_INFO("Unregister framebuffer %d\n", i); + ivtvfb_blank(FB_BLANK_POWERDOWN, &itv->osd_info->ivtvfb_info); + unregister_framebuffer(&itv->osd_info->ivtvfb_info); + ivtvfb_release_buffers(itv); + itv->osd_video_pbase = 0; + } + } +} + +module_init(ivtvfb_init); +module_exit(ivtvfb_cleanup); |