// SPDX-License-Identifier: GPL-2.0-or-later 
 | 
/* 
 | 
    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> 
 | 
  
 | 
 */ 
 | 
  
 | 
#include "ivtv-driver.h" 
 | 
#include "ivtv-cards.h" 
 | 
#include "ivtv-i2c.h" 
 | 
#include "ivtv-udma.h" 
 | 
#include "ivtv-mailbox.h" 
 | 
#include "ivtv-firmware.h" 
 | 
  
 | 
#include <linux/fb.h> 
 | 
#include <linux/ivtvfb.h> 
 | 
  
 | 
#ifdef CONFIG_X86_64 
 | 
#include <asm/memtype.h> 
 | 
#endif 
 | 
  
 | 
/* card parameters */ 
 | 
static int ivtvfb_card_id = -1; 
 | 
static int ivtvfb_debug = 0; 
 | 
static bool ivtvfb_force_pat = IS_ENABLED(CONFIG_VIDEO_FB_IVTV_FORCE_PAT); 
 | 
static bool osd_laced; 
 | 
static int osd_depth; 
 | 
static int osd_upper; 
 | 
static int osd_left; 
 | 
static int osd_yres; 
 | 
static int osd_xres; 
 | 
  
 | 
module_param(ivtvfb_card_id, int, 0444); 
 | 
module_param_named(debug,ivtvfb_debug, int, 0644); 
 | 
module_param_named(force_pat, ivtvfb_force_pat, bool, 0644); 
 | 
module_param(osd_laced, 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(ivtvfb_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(force_pat, 
 | 
         "Force initialization on x86 PAT-enabled systems (bool).\n"); 
 | 
  
 | 
/* 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 IVTVFB_DBGFLG_WARN  (1 << 0) 
 | 
#define IVTVFB_DBGFLG_INFO  (1 << 1) 
 | 
  
 | 
#define IVTVFB_DEBUG(x, type, fmt, args...) \ 
 | 
    do { \ 
 | 
        if ((x) & ivtvfb_debug) \ 
 | 
            printk(KERN_INFO "ivtvfb%d " type ": " fmt, itv->instance , ## args); \ 
 | 
    } while (0) 
 | 
#define IVTVFB_DEBUG_WARN(fmt, args...)  IVTVFB_DEBUG(IVTVFB_DBGFLG_WARN, "warning", fmt , ## args) 
 | 
#define IVTVFB_DEBUG_INFO(fmt, args...)  IVTVFB_DEBUG(IVTVFB_DBGFLG_INFO, "info", fmt , ## args) 
 | 
  
 | 
/* Standard kernel messages */ 
 | 
#define IVTVFB_ERR(fmt, args...)   printk(KERN_ERR  "ivtvfb%d: " fmt, itv->instance , ## args) 
 | 
#define IVTVFB_WARN(fmt, args...)  printk(KERN_WARNING  "ivtvfb%d: " fmt, itv->instance , ## args) 
 | 
#define IVTVFB_INFO(fmt, args...)  printk(KERN_INFO "ivtvfb%d: " fmt, itv->instance , ## 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 { 
 | 
    /* 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; 
 | 
  
 | 
    /* 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; 
 | 
    int wc_cookie; 
 | 
  
 | 
    /* 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; 
 | 
  
 | 
    /* Used for a warm start */ 
 | 
    struct fb_var_screeninfo fbvar_cur; 
 | 
    int blank_cur; 
 | 
    u32 palette_cur[256]; 
 | 
    u32 pan_cur; 
 | 
}; 
 | 
  
 | 
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 ivtvfb_get_framebuffer(struct ivtv *itv, u32 *fbbase, 
 | 
                       u32 *fblength) 
 | 
{ 
 | 
    u32 data[CX2341X_MBOX_MAX_DATA]; 
 | 
    int rc; 
 | 
  
 | 
    ivtv_firmware_check(itv, "ivtvfb_get_framebuffer"); 
 | 
    rc = ivtv_vapi_result(itv, data, CX2341X_OSD_GET_FRAMEBUFFER, 0); 
 | 
    *fbbase = data[0]; 
 | 
    *fblength = data[1]; 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
static int ivtvfb_get_osd_coords(struct ivtv *itv, 
 | 
                      struct ivtv_osd_coords *osd) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    u32 data[CX2341X_MBOX_MAX_DATA]; 
 | 
  
 | 
    ivtv_vapi_result(itv, data, CX2341X_OSD_GET_OSD_COORDS, 0); 
 | 
  
 | 
    osd->offset = data[0] - oi->video_rbase; 
 | 
    osd->max_offset = oi->display_width * oi->display_height * 4; 
 | 
    osd->pixel_stride = data[1]; 
 | 
    osd->lines = data[2]; 
 | 
    osd->x = data[3]; 
 | 
    osd->y = data[4]; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_set_osd_coords(struct ivtv *itv, const struct ivtv_osd_coords *osd) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
  
 | 
    oi->display_width = osd->pixel_stride; 
 | 
    oi->display_byte_stride = osd->pixel_stride * oi->bytes_per_pixel; 
 | 
    oi->set_osd_coords_x += osd->x; 
 | 
    oi->set_osd_coords_y = osd->y; 
 | 
  
 | 
    return ivtv_vapi(itv, CX2341X_OSD_SET_OSD_COORDS, 5, 
 | 
            osd->offset + oi->video_rbase, 
 | 
            osd->pixel_stride, 
 | 
            osd->lines, osd->x, osd->y); 
 | 
} 
 | 
  
 | 
static int ivtvfb_set_display_window(struct ivtv *itv, struct v4l2_rect *ivtv_window) 
 | 
{ 
 | 
    int osd_height_limit = itv->is_out_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) { 
 | 
        IVTVFB_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) { 
 | 
        IVTVFB_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 ivtvfb_prep_dec_dma_to_device(struct ivtv *itv, 
 | 
                  unsigned long ivtv_dest_addr, void __user *userbuf, 
 | 
                  int size_in_bytes) 
 | 
{ 
 | 
    DEFINE_WAIT(wait); 
 | 
    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); 
 | 
        IVTVFB_WARN("ivtvfb_prep_dec_dma_to_device, Error with pin_user_pages: %d bytes, %d pages returned\n", 
 | 
                   size_in_bytes, itv->udma.page_count); 
 | 
  
 | 
        /* pin_user_pages must have failed completely */ 
 | 
        return -EIO; 
 | 
    } 
 | 
  
 | 
    IVTVFB_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 (test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags) || 
 | 
           test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { 
 | 
        /* 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 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_prep_frame(struct ivtv *itv, int cmd, void __user *source, 
 | 
                  unsigned long dest_offset, int count) 
 | 
{ 
 | 
    DEFINE_WAIT(wait); 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
  
 | 
    /* Nothing to do */ 
 | 
    if (count == 0) { 
 | 
        IVTVFB_DEBUG_WARN("ivtvfb_prep_frame: Nothing to do. count = 0\n"); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    /* Check Total FB Size */ 
 | 
    if ((dest_offset + count) > oi->video_buffer_size) { 
 | 
        IVTVFB_WARN("ivtvfb_prep_frame: Overflowing the framebuffer %ld, only %d available\n", 
 | 
            dest_offset + count, oi->video_buffer_size); 
 | 
        return -E2BIG; 
 | 
    } 
 | 
  
 | 
    /* Not fatal, but will have undesirable results */ 
 | 
    if ((unsigned long)source & 3) 
 | 
        IVTVFB_WARN("ivtvfb_prep_frame: Source address not 32 bit aligned (%p)\n", 
 | 
                source); 
 | 
  
 | 
    if (dest_offset & 3) 
 | 
        IVTVFB_WARN("ivtvfb_prep_frame: Dest offset not 32 bit aligned (%ld)\n", dest_offset); 
 | 
  
 | 
    if (count & 3) 
 | 
        IVTVFB_WARN("ivtvfb_prep_frame: Count not a multiple of 4 (%d)\n", count); 
 | 
  
 | 
    /* Check Source */ 
 | 
    if (!access_ok(source + dest_offset, count)) { 
 | 
        IVTVFB_WARN("Invalid userspace pointer %p\n", source); 
 | 
  
 | 
        IVTVFB_DEBUG_WARN("access_ok() failed for offset 0x%08lx source %p count %d\n", 
 | 
                  dest_offset, source, count); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    /* OSD Address to send DMA to */ 
 | 
    dest_offset += IVTV_DECODER_OFFSET + oi->video_rbase; 
 | 
  
 | 
    /* Fill Buffers */ 
 | 
    return ivtvfb_prep_dec_dma_to_device(itv, dest_offset, source, count); 
 | 
} 
 | 
  
 | 
static ssize_t ivtvfb_write(struct fb_info *info, const char __user *buf, 
 | 
                        size_t count, loff_t *ppos) 
 | 
{ 
 | 
    unsigned long p = *ppos; 
 | 
    void *dst; 
 | 
    int err = 0; 
 | 
    int dma_err; 
 | 
    unsigned long total_size; 
 | 
    struct ivtv *itv = (struct ivtv *) info->par; 
 | 
    unsigned long dma_offset = 
 | 
            IVTV_DECODER_OFFSET + itv->osd_info->video_rbase; 
 | 
    unsigned long dma_size; 
 | 
    u16 lead = 0, tail = 0; 
 | 
  
 | 
    if (info->state != FBINFO_STATE_RUNNING) 
 | 
        return -EPERM; 
 | 
  
 | 
    total_size = info->screen_size; 
 | 
  
 | 
    if (total_size == 0) 
 | 
        total_size = info->fix.smem_len; 
 | 
  
 | 
    if (p > total_size) 
 | 
        return -EFBIG; 
 | 
  
 | 
    if (count > total_size) { 
 | 
        err = -EFBIG; 
 | 
        count = total_size; 
 | 
    } 
 | 
  
 | 
    if (count + p > total_size) { 
 | 
        if (!err) 
 | 
            err = -ENOSPC; 
 | 
        count = total_size - p; 
 | 
    } 
 | 
  
 | 
    dst = (void __force *) (info->screen_base + p); 
 | 
  
 | 
    if (info->fbops->fb_sync) 
 | 
        info->fbops->fb_sync(info); 
 | 
  
 | 
    /* If transfer size > threshold and both src/dst 
 | 
    addresses are aligned, use DMA */ 
 | 
    if (count >= 4096 && 
 | 
        ((unsigned long)buf & 3) == ((unsigned long)dst & 3)) { 
 | 
        /* Odd address = can't DMA. Align */ 
 | 
        if ((unsigned long)dst & 3) { 
 | 
            lead = 4 - ((unsigned long)dst & 3); 
 | 
            if (copy_from_user(dst, buf, lead)) 
 | 
                return -EFAULT; 
 | 
            buf += lead; 
 | 
            dst += lead; 
 | 
        } 
 | 
        /* DMA resolution is 32 bits */ 
 | 
        if ((count - lead) & 3) 
 | 
            tail = (count - lead) & 3; 
 | 
        /* DMA the data */ 
 | 
        dma_size = count - lead - tail; 
 | 
        dma_err = ivtvfb_prep_dec_dma_to_device(itv, 
 | 
               p + lead + dma_offset, (void __user *)buf, dma_size); 
 | 
        if (dma_err) 
 | 
            return dma_err; 
 | 
        dst += dma_size; 
 | 
        buf += dma_size; 
 | 
        /* Copy any leftover data */ 
 | 
        if (tail && copy_from_user(dst, buf, tail)) 
 | 
            return -EFAULT; 
 | 
    } else if (copy_from_user(dst, buf, count)) { 
 | 
        return -EFAULT; 
 | 
    } 
 | 
  
 | 
    if  (!err) 
 | 
        *ppos += count; 
 | 
  
 | 
    return (err) ? err : 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; 
 | 
  
 | 
            memset(&vblank, 0, sizeof(struct fb_vblank)); 
 | 
  
 | 
            vblank.flags = FB_VBLANK_HAVE_COUNT |FB_VBLANK_HAVE_VCOUNT | 
 | 
                    FB_VBLANK_HAVE_VSYNC; 
 | 
            trace = read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16; 
 | 
            if (itv->is_out_50hz && trace > 312) 
 | 
                trace -= 312; 
 | 
            else if (itv->is_out_60hz && trace > 262) 
 | 
                trace -= 262; 
 | 
            if (trace == 1) 
 | 
                vblank.flags |= FB_VBLANK_VSYNCING; 
 | 
            vblank.count = itv->last_vsync_field; 
 | 
            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(msecs_to_jiffies(50))) 
 | 
                rc = -ETIMEDOUT; 
 | 
            finish_wait(&itv->vsync_waitq, &wait); 
 | 
            return rc; 
 | 
  
 | 
        case IVTVFB_IOC_DMA_FRAME: { 
 | 
            struct ivtvfb_dma_frame args; 
 | 
  
 | 
            IVTVFB_DEBUG_INFO("IVTVFB_IOC_DMA_FRAME\n"); 
 | 
            if (copy_from_user(&args, (void __user *)arg, sizeof(args))) 
 | 
                return -EFAULT; 
 | 
  
 | 
            return ivtvfb_prep_frame(itv, cmd, args.source, args.dest_offset, args.count); 
 | 
        } 
 | 
  
 | 
        default: 
 | 
            IVTVFB_DEBUG_INFO("Unknown ioctl %08x\n", cmd); 
 | 
            return -EINVAL; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* Framebuffer device handling */ 
 | 
  
 | 
static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    struct ivtv_osd_coords ivtv_osd; 
 | 
    struct v4l2_rect ivtv_window; 
 | 
    int osd_mode = -1; 
 | 
  
 | 
    IVTVFB_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 */ 
 | 
    switch (var->bits_per_pixel) { 
 | 
        case 8: 
 | 
            osd_mode = IVTV_OSD_BPP_8; 
 | 
            break; 
 | 
        case 32: 
 | 
            osd_mode = IVTV_OSD_BPP_32; 
 | 
            break; 
 | 
        case 16: 
 | 
            switch (var->green.length) { 
 | 
            case 4: 
 | 
                osd_mode = IVTV_OSD_BPP_16_444; 
 | 
                break; 
 | 
            case 5: 
 | 
                osd_mode = IVTV_OSD_BPP_16_555; 
 | 
                break; 
 | 
            case 6: 
 | 
                osd_mode = IVTV_OSD_BPP_16_565; 
 | 
                break; 
 | 
            default: 
 | 
                IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); 
 | 
            } 
 | 
            break; 
 | 
        default: 
 | 
            IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); 
 | 
    } 
 | 
  
 | 
    /* Set video mode. Although rare, the display can become scrambled even 
 | 
       if we don't change mode. Always 'bounce' to osd_mode via mode 0 */ 
 | 
    if (osd_mode != -1) { 
 | 
        ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); 
 | 
        ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, osd_mode); 
 | 
    } 
 | 
  
 | 
    oi->bits_per_pixel = var->bits_per_pixel; 
 | 
    oi->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: 
 | 
            IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid video mode\n"); 
 | 
    } 
 | 
  
 | 
    /* Read the current osd info */ 
 | 
    ivtvfb_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; 
 | 
    ivtvfb_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; 
 | 
  
 | 
    ivtvfb_set_display_window(itv, &ivtv_window); 
 | 
  
 | 
    /* Pass screen size back to yuv handler */ 
 | 
    itv->yuv_info.osd_full_w = ivtv_osd.pixel_stride; 
 | 
    itv->yuv_info.osd_full_h = ivtv_osd.lines; 
 | 
  
 | 
    /* Force update of yuv registers */ 
 | 
    itv->yuv_info.yuv_forced_update = 1; 
 | 
  
 | 
    /* Keep a copy of these settings */ 
 | 
    memcpy(&oi->fbvar_cur, var, sizeof(oi->fbvar_cur)); 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", 
 | 
              var->xres, var->yres, 
 | 
              var->xres_virtual, var->yres_virtual, 
 | 
              var->bits_per_pixel); 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display position: %d, %d\n", 
 | 
              var->left_margin, var->upper_margin); 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display filter: %s\n", 
 | 
            (var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); 
 | 
    IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_get_fix(struct ivtv *itv, struct fb_fix_screeninfo *fix) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("ivtvfb_get_fix\n"); 
 | 
    memset(fix, 0, sizeof(struct fb_fix_screeninfo)); 
 | 
    strscpy(fix->id, "cx23415 TV out", sizeof(fix->id)); 
 | 
    fix->smem_start = oi->video_pbase; 
 | 
    fix->smem_len = oi->video_buffer_size; 
 | 
    fix->type = FB_TYPE_PACKED_PIXELS; 
 | 
    fix->visual = (oi->bits_per_pixel == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; 
 | 
    fix->xpanstep = 1; 
 | 
    fix->ypanstep = 1; 
 | 
    fix->ywrapstep = 0; 
 | 
    fix->line_length = oi->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) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    int osd_height_limit; 
 | 
    u32 pixclock, hlimit, vlimit; 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("ivtvfb_check_var\n"); 
 | 
  
 | 
    /* Set base references for mode calcs. */ 
 | 
    if (itv->is_out_50hz) { 
 | 
        pixclock = 84316; 
 | 
        hlimit = 776; 
 | 
        vlimit = 591; 
 | 
        osd_height_limit = 576; 
 | 
    } 
 | 
    else { 
 | 
        pixclock = 83926; 
 | 
        hlimit = 776; 
 | 
        vlimit = 495; 
 | 
        osd_height_limit = 480; 
 | 
    } 
 | 
  
 | 
    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->red.offset = 8; 
 | 
                var->red.length = 4; 
 | 
                var->green.offset = 4; 
 | 
                var->green.length = 4; 
 | 
                var->blue.offset = 0; 
 | 
                var->blue.length = 4; 
 | 
                var->transp.offset = 12; 
 | 
                var->transp.length = 1; 
 | 
                break; 
 | 
            case 5: 
 | 
                var->red.offset = 10; 
 | 
                var->red.length = 5; 
 | 
                var->green.offset = 5; 
 | 
                var->green.length = 5; 
 | 
                var->blue.offset = 0; 
 | 
                var->blue.length = 5; 
 | 
                var->transp.offset = 15; 
 | 
                var->transp.length = 1; 
 | 
                break; 
 | 
            default: 
 | 
                var->red.offset = 11; 
 | 
                var->red.length = 5; 
 | 
                var->green.offset = 5; 
 | 
                var->green.length = 6; 
 | 
                var->blue.offset = 0; 
 | 
                var->blue.length = 5; 
 | 
                var->transp.offset = 0; 
 | 
                var->transp.length = 0; 
 | 
                break; 
 | 
        } 
 | 
    } 
 | 
    else { 
 | 
        IVTVFB_DEBUG_WARN("Invalid colour mode: %d\n", var->bits_per_pixel); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    /* Check the resolution */ 
 | 
    if (var->xres > IVTV_OSD_MAX_WIDTH || var->yres > osd_height_limit) { 
 | 
        IVTVFB_DEBUG_WARN("Invalid resolution: %dx%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) > oi->video_buffer_size || 
 | 
        var->xres_virtual < var->xres || 
 | 
        var->yres_virtual < var->yres) { 
 | 
        IVTVFB_DEBUG_WARN("Invalid virtual resolution: %dx%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) { 
 | 
            IVTVFB_DEBUG_WARN("Invalid resolution for 8bpp: %d\n", var->xres); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
        if (var->xres_virtual & 3) { 
 | 
            IVTVFB_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) { 
 | 
            IVTVFB_DEBUG_WARN("Invalid resolution for 16bpp: %d\n", var->xres); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
        if (var->xres_virtual & 1) { 
 | 
            IVTVFB_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) { 
 | 
        IVTVFB_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) { 
 | 
        IVTVFB_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)) { 
 | 
        IVTVFB_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_out_50hz ? 577 : 481)) 
 | 
        var->upper_margin = 1 + (((itv->is_out_50hz ? 576 : 480) - 
 | 
            var->yres) / 2); 
 | 
  
 | 
    /* Maintain overall 'size' for a constant refresh rate */ 
 | 
    var->right_margin = hlimit - var->left_margin - var->xres; 
 | 
    var->lower_margin = 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. */ 
 | 
    if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) 
 | 
        var->pixclock = pixclock / 2; 
 | 
    else 
 | 
        var->pixclock = pixclock; 
 | 
  
 | 
    itv->osd_rect.width = var->xres; 
 | 
    itv->osd_rect.height = var->yres; 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", 
 | 
              var->xres, var->yres, 
 | 
              var->xres_virtual, var->yres_virtual, 
 | 
              var->bits_per_pixel); 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display position: %d, %d\n", 
 | 
              var->left_margin, var->upper_margin); 
 | 
  
 | 
    IVTVFB_DEBUG_INFO("Display filter: %s\n", 
 | 
            (var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); 
 | 
    IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) 
 | 
{ 
 | 
    struct ivtv *itv = (struct ivtv *) info->par; 
 | 
    IVTVFB_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; 
 | 
  
 | 
    if (var->yoffset + info->var.yres > info->var.yres_virtual || 
 | 
        var->xoffset + info->var.xres > info->var.xres_virtual) 
 | 
        return -EINVAL; 
 | 
  
 | 
    osd_pan_index = var->yoffset * info->fix.line_length 
 | 
              + var->xoffset * info->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; 
 | 
    /* Remember this value */ 
 | 
    itv->osd_info->pan_cur = osd_pan_index; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_set_par(struct fb_info *info) 
 | 
{ 
 | 
    int rc = 0; 
 | 
    struct ivtv *itv = (struct ivtv *) info->par; 
 | 
  
 | 
    IVTVFB_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); 
 | 
    ivtv_firmware_check(itv, "ivtvfb_set_par"); 
 | 
    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); 
 | 
        itv->osd_info->palette_cur[regno] = color; 
 | 
        return 0; 
 | 
    } 
 | 
    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; 
 | 
  
 | 
    IVTVFB_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); 
 | 
        ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); 
 | 
        break; 
 | 
    case FB_BLANK_NORMAL: 
 | 
    case FB_BLANK_HSYNC_SUSPEND: 
 | 
    case FB_BLANK_VSYNC_SUSPEND: 
 | 
        ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); 
 | 
        ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); 
 | 
        break; 
 | 
    case FB_BLANK_POWERDOWN: 
 | 
        ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); 
 | 
        ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); 
 | 
        break; 
 | 
    } 
 | 
    itv->osd_info->blank_cur = blank_mode; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct fb_ops ivtvfb_ops = { 
 | 
    .owner = THIS_MODULE, 
 | 
    .fb_write       = ivtvfb_write, 
 | 
    .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, 
 | 
}; 
 | 
  
 | 
/* Restore hardware after firmware restart */ 
 | 
static void ivtvfb_restore(struct ivtv *itv) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    int i; 
 | 
  
 | 
    ivtvfb_set_var(itv, &oi->fbvar_cur); 
 | 
    ivtvfb_blank(oi->blank_cur, &oi->ivtvfb_info); 
 | 
    for (i = 0; i < 256; i++) { 
 | 
        write_reg(i, 0x02a30); 
 | 
        write_reg(oi->palette_cur[i], 0x02a34); 
 | 
    } 
 | 
    write_reg(oi->pan_cur, 0x02a0c); 
 | 
} 
 | 
  
 | 
/* Initialization */ 
 | 
  
 | 
  
 | 
/* Setup our initial video mode */ 
 | 
static int ivtvfb_init_vidmode(struct ivtv *itv) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    struct v4l2_rect start_window; 
 | 
    int max_height; 
 | 
  
 | 
    /* Color mode */ 
 | 
  
 | 
    if (osd_depth != 8 && osd_depth != 16 && osd_depth != 32) 
 | 
        osd_depth = 8; 
 | 
    oi->bits_per_pixel = osd_depth; 
 | 
    oi->bytes_per_pixel = oi->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; 
 | 
  
 | 
    start_window.width = osd_xres ? osd_xres : 640; 
 | 
  
 | 
    /* Check horizontal start (osd_left). */ 
 | 
    if (osd_left && osd_left + start_window.width > 721) { 
 | 
        IVTVFB_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); 
 | 
  
 | 
    oi->display_byte_stride = 
 | 
            start_window.width * oi->bytes_per_pixel; 
 | 
  
 | 
    /* Vertical size & position */ 
 | 
  
 | 
    max_height = itv->is_out_50hz ? 576 : 480; 
 | 
  
 | 
    if (osd_yres > max_height) 
 | 
        osd_yres = max_height; 
 | 
  
 | 
    start_window.height = osd_yres ? 
 | 
        osd_yres : itv->is_out_50hz ? 480 : 400; 
 | 
  
 | 
    /* Check vertical start (osd_upper). */ 
 | 
    if (osd_upper + start_window.height > max_height + 1) { 
 | 
        IVTVFB_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); 
 | 
  
 | 
    oi->display_width = start_window.width; 
 | 
    oi->display_height = start_window.height; 
 | 
  
 | 
    /* Generate a valid fb_var_screeninfo */ 
 | 
  
 | 
    oi->ivtvfb_defined.xres = oi->display_width; 
 | 
    oi->ivtvfb_defined.yres = oi->display_height; 
 | 
    oi->ivtvfb_defined.xres_virtual = oi->display_width; 
 | 
    oi->ivtvfb_defined.yres_virtual = oi->display_height; 
 | 
    oi->ivtvfb_defined.bits_per_pixel = oi->bits_per_pixel; 
 | 
    oi->ivtvfb_defined.vmode = (osd_laced ? FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED); 
 | 
    oi->ivtvfb_defined.left_margin = start_window.left + 1; 
 | 
    oi->ivtvfb_defined.upper_margin = start_window.top + 1; 
 | 
    oi->ivtvfb_defined.accel_flags = FB_ACCEL_NONE; 
 | 
    oi->ivtvfb_defined.nonstd = 0; 
 | 
  
 | 
    /* We've filled in the most data, let the usual mode check 
 | 
       routine fill in the rest. */ 
 | 
    _ivtvfb_check_var(&oi->ivtvfb_defined, itv); 
 | 
  
 | 
    /* Generate valid fb_fix_screeninfo */ 
 | 
  
 | 
    ivtvfb_get_fix(itv, &oi->ivtvfb_fix); 
 | 
  
 | 
    /* Generate valid fb_info */ 
 | 
  
 | 
    oi->ivtvfb_info.node = -1; 
 | 
    oi->ivtvfb_info.flags = FBINFO_FLAG_DEFAULT; 
 | 
    oi->ivtvfb_info.par = itv; 
 | 
    oi->ivtvfb_info.var = oi->ivtvfb_defined; 
 | 
    oi->ivtvfb_info.fix = oi->ivtvfb_fix; 
 | 
    oi->ivtvfb_info.screen_base = (u8 __iomem *)oi->video_vbase; 
 | 
    oi->ivtvfb_info.fbops = &ivtvfb_ops; 
 | 
  
 | 
    /* Supply some monitor specs. Bogus values will do for now */ 
 | 
    oi->ivtvfb_info.monspecs.hfmin = 8000; 
 | 
    oi->ivtvfb_info.monspecs.hfmax = 70000; 
 | 
    oi->ivtvfb_info.monspecs.vfmin = 10; 
 | 
    oi->ivtvfb_info.monspecs.vfmax = 100; 
 | 
  
 | 
    /* Allocate color map */ 
 | 
    if (fb_alloc_cmap(&oi->ivtvfb_info.cmap, 256, 1)) { 
 | 
        IVTVFB_ERR("abort, unable to alloc cmap\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
  
 | 
    /* Allocate the pseudo palette */ 
 | 
    oi->ivtvfb_info.pseudo_palette = 
 | 
        kmalloc_array(16, sizeof(u32), GFP_KERNEL|__GFP_NOWARN); 
 | 
  
 | 
    if (!oi->ivtvfb_info.pseudo_palette) { 
 | 
        IVTVFB_ERR("abort, unable to alloc pseudo palette\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* Find OSD buffer base & size. Add to mtrr. Zero osd buffer. */ 
 | 
  
 | 
static int ivtvfb_init_io(struct ivtv *itv) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
    /* Find the largest power of two that maps the whole buffer */ 
 | 
    int size_shift = 31; 
 | 
  
 | 
    mutex_lock(&itv->serialize_lock); 
 | 
    if (ivtv_init_on_first_open(itv)) { 
 | 
        mutex_unlock(&itv->serialize_lock); 
 | 
        IVTVFB_ERR("Failed to initialize ivtv\n"); 
 | 
        return -ENXIO; 
 | 
    } 
 | 
    mutex_unlock(&itv->serialize_lock); 
 | 
  
 | 
    if (ivtvfb_get_framebuffer(itv, &oi->video_rbase, 
 | 
                    &oi->video_buffer_size) < 0) { 
 | 
        IVTVFB_ERR("Firmware failed to respond\n"); 
 | 
        return -EIO; 
 | 
    } 
 | 
  
 | 
    /* 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. */ 
 | 
    oi->video_buffer_size = 1704960; 
 | 
  
 | 
    oi->video_pbase = itv->base_addr + IVTV_DECODER_OFFSET + oi->video_rbase; 
 | 
    oi->video_vbase = itv->dec_mem + oi->video_rbase; 
 | 
  
 | 
    if (!oi->video_vbase) { 
 | 
        IVTVFB_ERR("abort, video memory 0x%x @ 0x%lx isn't mapped!\n", 
 | 
             oi->video_buffer_size, oi->video_pbase); 
 | 
        return -EIO; 
 | 
    } 
 | 
  
 | 
    IVTVFB_INFO("Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n", 
 | 
            oi->video_pbase, oi->video_vbase, 
 | 
            oi->video_buffer_size / 1024); 
 | 
  
 | 
    while (!(oi->video_buffer_size & (1 << size_shift))) 
 | 
        size_shift--; 
 | 
    size_shift++; 
 | 
    oi->fb_start_aligned_physaddr = oi->video_pbase & ~((1 << size_shift) - 1); 
 | 
    oi->fb_end_aligned_physaddr = oi->video_pbase + oi->video_buffer_size; 
 | 
    oi->fb_end_aligned_physaddr += (1 << size_shift) - 1; 
 | 
    oi->fb_end_aligned_physaddr &= ~((1 << size_shift) - 1); 
 | 
    oi->wc_cookie = arch_phys_wc_add(oi->fb_start_aligned_physaddr, 
 | 
                     oi->fb_end_aligned_physaddr - 
 | 
                     oi->fb_start_aligned_physaddr); 
 | 
    /* Blank the entire osd. */ 
 | 
    memset_io(oi->video_vbase, 0, oi->video_buffer_size); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
/* Release any memory we've grabbed & remove mtrr entry */ 
 | 
static void ivtvfb_release_buffers (struct ivtv *itv) 
 | 
{ 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
  
 | 
    /* Release cmap */ 
 | 
    if (oi->ivtvfb_info.cmap.len) 
 | 
        fb_dealloc_cmap(&oi->ivtvfb_info.cmap); 
 | 
  
 | 
    /* Release pseudo palette */ 
 | 
    kfree(oi->ivtvfb_info.pseudo_palette); 
 | 
    arch_phys_wc_del(oi->wc_cookie); 
 | 
    kfree(oi); 
 | 
    itv->osd_info = NULL; 
 | 
} 
 | 
  
 | 
/* Initialize the specified card */ 
 | 
  
 | 
static int ivtvfb_init_card(struct ivtv *itv) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
#ifdef CONFIG_X86_64 
 | 
    if (pat_enabled()) { 
 | 
        if (ivtvfb_force_pat) { 
 | 
            pr_info("PAT is enabled. Write-combined framebuffer caching will be disabled.\n"); 
 | 
            pr_info("To enable caching, boot with nopat kernel parameter\n"); 
 | 
        } else { 
 | 
            pr_warn("ivtvfb needs PAT disabled for write-combined framebuffer caching.\n"); 
 | 
            pr_warn("Boot with nopat kernel parameter to use caching, or use the\n"); 
 | 
            pr_warn("force_pat module parameter to run with caching disabled\n"); 
 | 
            return -ENODEV; 
 | 
        } 
 | 
    } 
 | 
#endif 
 | 
  
 | 
    if (itv->osd_info) { 
 | 
        IVTVFB_ERR("Card %d already initialised\n", ivtvfb_card_id); 
 | 
        return -EBUSY; 
 | 
    } 
 | 
  
 | 
    itv->osd_info = kzalloc(sizeof(struct osd_info), 
 | 
                    GFP_KERNEL|__GFP_NOWARN); 
 | 
    if (itv->osd_info == NULL) { 
 | 
        IVTVFB_ERR("Failed to allocate memory for osd_info\n"); 
 | 
        return -ENOMEM; 
 | 
    } 
 | 
  
 | 
    /* Find & setup the OSD buffer */ 
 | 
    rc = ivtvfb_init_io(itv); 
 | 
    if (rc) { 
 | 
        ivtvfb_release_buffers(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); 
 | 
  
 | 
    /* Enable restart */ 
 | 
    itv->ivtvfb_restore = ivtvfb_restore; 
 | 
  
 | 
    /* Allocate DMA */ 
 | 
    ivtv_udma_alloc(itv); 
 | 
    itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps |= 
 | 
        V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
    itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps |= 
 | 
        V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
    itv->v4l2_cap |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
    return 0; 
 | 
  
 | 
} 
 | 
  
 | 
static int __init ivtvfb_callback_init(struct device *dev, void *p) 
 | 
{ 
 | 
    struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); 
 | 
    struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); 
 | 
  
 | 
    if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { 
 | 
        if (ivtvfb_init_card(itv) == 0) { 
 | 
            IVTVFB_INFO("Framebuffer registered on %s\n", 
 | 
                    itv->v4l2_dev.name); 
 | 
            (*(int *)p)++; 
 | 
        } 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int ivtvfb_callback_cleanup(struct device *dev, void *p) 
 | 
{ 
 | 
    struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); 
 | 
    struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); 
 | 
    struct osd_info *oi = itv->osd_info; 
 | 
  
 | 
    if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { 
 | 
        itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps &= 
 | 
            ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
        itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps &= 
 | 
            ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
        itv->v4l2_cap &= ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; 
 | 
        unregister_framebuffer(&itv->osd_info->ivtvfb_info); 
 | 
        IVTVFB_INFO("Unregister framebuffer %d\n", itv->instance); 
 | 
        itv->ivtvfb_restore = NULL; 
 | 
        ivtvfb_blank(FB_BLANK_VSYNC_SUSPEND, &oi->ivtvfb_info); 
 | 
        ivtvfb_release_buffers(itv); 
 | 
        itv->osd_video_pbase = 0; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int __init ivtvfb_init(void) 
 | 
{ 
 | 
    struct device_driver *drv; 
 | 
    int registered = 0; 
 | 
    int err; 
 | 
  
 | 
  
 | 
    if (ivtvfb_card_id < -1 || ivtvfb_card_id >= IVTV_MAX_CARDS) { 
 | 
        pr_err("ivtvfb_card_id parameter is out of range (valid range: -1 - %d)\n", 
 | 
             IVTV_MAX_CARDS - 1); 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    drv = driver_find("ivtv", &pci_bus_type); 
 | 
    err = driver_for_each_device(drv, NULL, ®istered, ivtvfb_callback_init); 
 | 
    (void)err;    /* suppress compiler warning */ 
 | 
    if (!registered) { 
 | 
        pr_err("no cards found\n"); 
 | 
        return -ENODEV; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void ivtvfb_cleanup(void) 
 | 
{ 
 | 
    struct device_driver *drv; 
 | 
    int err; 
 | 
  
 | 
    pr_info("Unloading framebuffer module\n"); 
 | 
  
 | 
    drv = driver_find("ivtv", &pci_bus_type); 
 | 
    err = driver_for_each_device(drv, NULL, NULL, ivtvfb_callback_cleanup); 
 | 
    (void)err;    /* suppress compiler warning */ 
 | 
} 
 | 
  
 | 
module_init(ivtvfb_init); 
 | 
module_exit(ivtvfb_cleanup); 
 |