/*
 * saa7127 - Philips SAA7127 video encoder driver version 0.2
 *
 * Copyright (C) 2003 Roy Bulter <rbulter@hetnet.nl>
 *
 * Based on SAA7126 video encoder driver by Gillem & Andreas Oberritter
 *
 * Copyright (C) 2000-2001 Gillem <htoa@gmx.net>
 * Copyright (C) 2002 Andreas Oberritter <obi@saftware.de>
 *
 * Based on Stadis 4:2:2 MPEG-2 Decoder Driver by Nathan Laredo
 *
 * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org>
 *
 * This driver is designed for the Hauppauge 250/350 Linux driver
 * designed by the Ivytv Project (ivtv.sf.net) 
 *  
 * Copyright (C) 2003 Kevin Thayer <nufan_wfk@yahoo.com>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Revision History
 *
 *
 * Revision : 0.1 (09-05-2003)
 * Change   : First Version
 *
 * Revision : 0.2 (21-05-2003)
 * Change   : solved compiler error on line 785(800) 
 *            reg61h variable was not set in saa7127_set_norm function
 *
 * Revision : 0.3 (21-07-2003) Matt T. Yourst <yourst@yourst.com>
 * Change   : Update configuration tables to make NTSC appear correctly;
 *            Enable alternative outputs (s-video, composite, RGB, etc.)
 */

#include <linux/version.h>

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/init.h>

#include <linux/slab.h>

#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/signal.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <linux/types.h>
#include <linux/wrapper.h>

#include <linux/version.h>
#include <asm/uaccess.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/videodev.h>
#include <linux/video_encoder.h>

#include "saa7127.h"

/*
 **********************************************************************
 *  Debug Macro's
 *
 *
 **********************************************************************
 */

#define CURRENT_MASK   3
#define NO_MASK        0
#define INFO_MASK      1      /*  0b0000000000001 */
#define ERROR_MASK     2      /*  0b0000000000010 */
#define ENTER_MASK     4      /*  0b0000000000100 */
#define RETURN_MASK    8      /*  0b0000000001000 */
#define TRACE1_MASK    16     /*  0b0000000010000 */
#define TRACE2_MASK    32     /*  0b0000000100000 */
#define TRACE3_MASK    64     /*  0b0000001000000 */
#define TRACE4_MASK    128    /*  0b0000010000000 */
#define TRACE5_MASK    256    /*  0b0000100000000 */
#define TRACE6_MASK    512    /*  0b0001000000000 */
#define TRACE7_MASK    1024   /*  0b0010000000000 */
#define TRACE8_MASK    2048   /*  0b0100000000000 */
#define TRACE9_MASK    4096   /*  0b1000000000000 */


static int debug_mask = CURRENT_MASK;
static int test_image = 0;
static int pal = 0;
static int enable_output = 0;
static int output_select = SAA7127_OUTPUT_TYPE_SVIDEO;

#define INFO(format, args...)\
      if ((debug_mask&INFO_MASK) == INFO_MASK)\
      {\
        printk("[%s: INFO]: ", __FILE__);\
        printk(format, ##args);\
        printk("\n");\
      }\

#define ERROR(format, args...)\
      if ((debug_mask&ERROR_MASK) == ERROR_MASK)\
      {\
        printk("[%s: %d: ERROR]: ", __FILE__,__LINE__);\
        printk(format, ##args);\
        printk("\n");\
      }\


#ifdef DEBUG

#define ENTER\
  if ((debug_mask&ENTER_MASK) == ENTER_MASK)\
  {\
    printk("[%s : %s: %d: ENTER]: ",__FILE__,__FUNCTION__,__LINE__);\
    printk("\n");\
  }\

#define RETURN(value)\
      if ((debug_mask&RETURN_MASK) == RETURN_MASK)\
      {\
        printk("[%s : %s : %d: RETURN]: ", __FILE__,__FUNCTION__,__LINE__);\
        printk("value: %x",value);\
        printk("\n");\
      }\
      return(value);\

static int get_trace_mask(int num)
{
  switch (num)
  {
    case 1:
      return TRACE1_MASK;
      break;
    case 2:
      return TRACE2_MASK;
      break;
    case 3:
      return TRACE3_MASK;
      break;
    case 4:
      return TRACE4_MASK;
      break;
    case 5:
      return TRACE5_MASK;
      break;
    case 6:
      return TRACE6_MASK;
      break;
    case 7:
      return TRACE7_MASK;
      break;
    case 8:
      return TRACE8_MASK;
      break;
    case 9:
      return TRACE9_MASK;
      break;
    default:
      return NO_MASK;
  }
}

#define TRACE(num, format, args...) \
      if ((debug_mask&get_trace_mask(num)) == get_trace_mask(num)) \
      {\
        printk("[%s: %d: TRACE%d] ", __FILE__, __LINE__,num);\
        printk(format, ##args);\
        printk("\n");\
      }\
      
#else

#define ENTER 
#define RETURN(value) return(value);
#define TRACE(num, format, args...) 

#endif /* DEBUG */

/*
 **********************************************************************
 *  
 *  Array's with configuration parameters for the SAA7127
 *
 **********************************************************************
 */

struct i2c_reg_value 
{
  unsigned char reg;
  unsigned char value;
};

struct i2c_reg_value saa7127_init_config_common[] = 
{
  {SAA7127_REG_WIDESCREEN_CONFIG,               0x0d},
  {SAA7127_REG_WIDESCREEN_ENABLE,               0x00},
  {SAA7127_REG_COPYGEN_0,                       0x77},
  {SAA7127_REG_COPYGEN_1,                       0x41},
  {SAA7127_REG_COPYGEN_2,                       0x00}, // (Macrovision enable/disable)
  {SAA7127_REG_OUTPUT_PORT_CONTROL,             0x9e},
  {SAA7127_REG_GAIN_LUMINANCE_RGB,              0x00},
  {SAA7127_REG_GAIN_COLORDIFF_RGB,              0x00},
  {SAA7127_REG_INPUT_PORT_CONTROL_1,            0x80}, // (for color bars)
  {SAA7127_REG_LINE_21_ODD_0,                   0x77},
  {SAA7127_REG_LINE_21_ODD_1,                   0x41},
  {SAA7127_REG_LINE_21_EVEN_0,                  0x88},
  {SAA7127_REG_LINE_21_EVEN_1,                  0x41},
  {SAA7127_REG_RCV_PORT_CONTROL,                0x12},
  {SAA7127_REG_VTRIG,                           0xf9},
  {SAA7127_REG_HTRIG_HI,                        0x00},
  {SAA7127_REG_RCV2_OUTPUT_START,               0x41},
  {SAA7127_REG_RCV2_OUTPUT_END,                 0xc3},
  {SAA7127_REG_RCV2_OUTPUT_MSBS,                0x00},
  {SAA7127_REG_TTX_REQUEST_H_START,             0x3e},
  {SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH,      0xb8},
  {SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT,       0x03},
  {SAA7127_REG_TTX_ODD_REQ_VERT_START,          0x15},
  {SAA7127_REG_TTX_ODD_REQ_VERT_END,            0x16},
  {SAA7127_REG_TTX_EVEN_REQ_VERT_START,         0x15},
  {SAA7127_REG_TTX_EVEN_REQ_VERT_END,           0x16},
  {SAA7127_REG_FIRST_ACTIVE,                    0x1a},
  {SAA7127_REG_LAST_ACTIVE,                     0x01},
  {SAA7127_REG_MSB_VERTICAL,                    0xc0},
  {SAA7127_REG_DISABLE_TTX_LINE_LO_0,           0x00},
  {SAA7127_REG_DISABLE_TTX_LINE_LO_1,           0x00},
  {0, 0}
};

#define SAA7127_NTSC_DAC_CONTROL 0x05
struct i2c_reg_value saa7127_init_config_ntsc[] = 
{
  {SAA7127_REG_BURST_START,                     0x19},
  {SAA7127_REG_BURST_END,                       0x1d},
  {SAA7127_REG_CHROMA_PHASE,                    0x27},
  {SAA7127_REG_GAINU,                           0x88},
  {SAA7127_REG_GAINV,                           0xc0},
  {SAA7127_REG_BLACK_LEVEL,                     0x3f},
  {SAA7127_REG_BLANKING_LEVEL,                  0x36},
  {SAA7127_REG_VBI_BLANKING,                    0x36},
  {SAA7127_REG_DAC_CONTROL,                     0x05},
  {SAA7127_REG_BURST_AMP,                       0x4a},
  {SAA7127_REG_SUBC3,                           0x1f},
  {SAA7127_REG_SUBC2,                           0x7c},
  {SAA7127_REG_SUBC1,                           0xf0},
  {SAA7127_REG_SUBC0,                           0x21},
  {SAA7127_REG_MULTI,                           0x90},
  {SAA7127_REG_CLOSED_CAPTION,                  0x14},
  {0, 0}
};

#define SAA7127_PAL_DAC_CONTROL 0x02
struct i2c_reg_value saa7127_init_config_pal[] = 
{
  {SAA7127_REG_BURST_START,                     0x21},
  {SAA7127_REG_BURST_END,                       0x1d},
  {SAA7127_REG_CHROMA_PHASE,                    0x3f},
  {SAA7127_REG_GAINU,                           0x7d},
  {SAA7127_REG_GAINV,                           0xaf},
  {SAA7127_REG_BLACK_LEVEL,                     0x23},
  {SAA7127_REG_BLANKING_LEVEL,                  0x35},
  {SAA7127_REG_VBI_BLANKING,                    0x35},
  {SAA7127_REG_DAC_CONTROL,                     0x02},
  {SAA7127_REG_BURST_AMP,                       0x2f},
  {SAA7127_REG_SUBC3,                           0xcb}, 
  {SAA7127_REG_SUBC2,                           0x8a},
  {SAA7127_REG_SUBC1,                           0x09},
  {SAA7127_REG_SUBC0,                           0x2a},
  {SAA7127_REG_MULTI,                           0xa0},
  {SAA7127_REG_CLOSED_CAPTION,                  0x00},
  {0, 0}
};

/*
 **********************************************************************
 *  
 *  Encoder Struct, holds the configuration state of the encoder
 *
 **********************************************************************
 */

struct saa7127
{
  enum SAA7127_video_norm       norm;
  enum SAA7127_input_type       input_type;
  enum SAA7127_output_type      output_type;
  enum SAA7127_enable_type      enable;
  enum SAA7127_wss_enable_type  wss_enable;
  enum SAA7127_wss_mode_type    wss_mode;
  u8 reg_2d;
  u8 reg_3a;
  u8 reg_61;
};

/* ----------------------------------------------------------------------- */

static int saa7127_read (struct i2c_client *client,
                         u8                 reg)
{
  ENTER;
  RETURN (i2c_smbus_read_byte_data(client, reg));
}


/* ----------------------------------------------------------------------- */


static int saa7127_writereg(struct i2c_client *client, u8 reg, u8 val)
{
  ENTER;
  if (i2c_smbus_write_byte_data(client, reg, val)  < 0)
  {
      ERROR ("I2C Write Problem");
      return (-1);
  }
  TRACE(4,"I2C Write to reg: %x, data: %x",reg,val); 
  RETURN (0);
}


/* ----------------------------------------------------------------------- */

static int saa7127_write_inittab(struct i2c_client *client, const struct i2c_reg_value *regs)
{
  ENTER;

  while (regs->reg != 0) 
  {
    if (i2c_smbus_write_byte_data(client, regs->reg, regs->value) < 0)
    {
      ERROR ("I2C Write Problem");
      RETURN (-1);
    }
    TRACE(4,"I2C Write to reg: %x, data: %x",regs->reg,regs->value);
    regs++;
  }
  RETURN (0);
}


/* ----------------------------------------------------------------------- */


static int saa7127_set_wss(struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;

  ENTER;
  switch (encoder->wss_enable)
  {
    case SAA7127_WSS_DISABLE:
      TRACE(3,"Disable Wide Screen Signal");
      saa7127_writereg(client, 0x27, 0x00);
      break;
    case SAA7127_WSS_ENABLE:
      TRACE(3,"Enable Wide Screen Signal");    
      saa7127_writereg(client, 0x27, 0x80);
      break;
    default:
      return (-EINVAL);
  }
  return (0);
}



/* ----------------------------------------------------------------------- */


static int saa7127_set_wss_mode(struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;

  ENTER;

  switch (encoder->wss_mode)
  {
    case SAA7127_WSS_MODE_4_3_FULL_FORMAT:
      TRACE(3,"Widescreen Mode 4:3 Full Format");    
      saa7127_writereg(client, 0x26, 0x08);
      break;
    case SAA7127_WSS_MODE_BOX_14_9_C:
      TRACE(3,"Widescreen Mode Box 14:9 Center");        
      saa7127_writereg(client, 0x26, 0x01);
      break;
    case SAA7127_WSS_MODE_BOX_14_9_TOP:
      TRACE(3,"Widescreen Mode Box 14:9 Top");     
      saa7127_writereg(client, 0x26, 0x02);
      break;
    case SAA7127_WSS_MODE_BOX_16_9_C:
      TRACE(3,"Widescreen Mode Box 16:9 Center");         
      saa7127_writereg(client, 0x26, 0x0b);
      break;
    case SAA7127_WSS_MODE_BOX_16_9_TOP:
      TRACE(3,"Widescreen Mode Box 16:9 Top");        
      saa7127_writereg(client, 0x26, 0x04);
      break;
    case SAA7127_WSS_MODE_SMALL_BOX_16_9_C:
      TRACE(3,"Widescreen Mode Small Box 16:9 Center");            
      saa7127_writereg(client, 0x26, 0x0d);
      break;
    case SAA7127_WSS_MODE_4_3_14_9_FULL_FORMAT:
      TRACE(3,"Widescreen Mode 14:9 Full Format");                
      saa7127_writereg(client, 0x26, 0x0e);
      break;
    case SAA7127_WSS_MODE_16_9_ANAMORPHIC:
      TRACE(3,"Widescreen Mode 16:9 Full Format");                    
      saa7127_writereg(client, 0x26, 0x07);
      break;
    default:
      RETURN (-EINVAL);
  }
  RETURN (0);
}



/* ----------------------------------------------------------------------- */


static int saa7127_set_enable (struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;

  ENTER;

  switch (encoder->enable)
  {
    case SAA7127_DISABLE:
      TRACE(3,"Disable Video Output");       
      saa7127_writereg(client, 0x2d, (encoder->reg_2d & 0xf0));
      saa7127_writereg(client, 0x61, (encoder->reg_61 | 0xc0));
      break;
    case SAA7127_ENABLE:
      TRACE(3,"Enable Video Output");          
      saa7127_writereg(client, 0x2d, encoder->reg_2d);
      saa7127_writereg(client, 0x61, encoder->reg_61);
      break;
    default:
      RETURN (-EINVAL);
  }

#if 0
  int j;
  for (j = 0; j < 128/16; j++) 
  {
    TRACE(3, "saa7127 registers 0x%02x-0x%02x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
           j*16, j*16 + 15,
           saa7127_read(client, j*16 + 0),
           saa7127_read(client, j*16 + 1),
           saa7127_read(client, j*16 + 2),
           saa7127_read(client, j*16 + 3),
           saa7127_read(client, j*16 + 4),
           saa7127_read(client, j*16 + 5),
           saa7127_read(client, j*16 + 6),
           saa7127_read(client, j*16 + 7),
           saa7127_read(client, j*16 + 8),
           saa7127_read(client, j*16 + 9),
           saa7127_read(client, j*16 + 10),
           saa7127_read(client, j*16 + 11),
           saa7127_read(client, j*16 + 12),
           saa7127_read(client, j*16 + 13),
           saa7127_read(client, j*16 + 14),
           saa7127_read(client, j*16 + 15));
  }
#endif

  RETURN (0);
}


/* ----------------------------------------------------------------------- */

static int saa7127_set_norm(struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;
  const struct i2c_reg_value *inittab;

  ENTER;
  
  switch (encoder->norm)
  {
    case SAA7127_VIDEO_NORM_NTSC:
      TRACE(3,"Selecting NTSC video Standard");       
      inittab = saa7127_init_config_ntsc;
      encoder->reg_61 = SAA7127_NTSC_DAC_CONTROL;
      break;
    case SAA7127_VIDEO_NORM_PAL:
      TRACE(3,"Selecting PAL video Standard");                
      inittab = saa7127_init_config_pal;
      encoder->reg_61 = SAA7127_PAL_DAC_CONTROL;
      break;
    default:
      RETURN (-EINVAL);
  }

  /* Write Table */
  saa7127_write_inittab(client, inittab);
  RETURN (0);
}

/* ----------------------------------------------------------------------- */

static int saa7127_set_output_type(struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;

  ENTER;

  encoder->reg_3a = 0x13;   // by default swithch YUV to RGB-matrix on

  switch (encoder->output_type)
  {
  case SAA7127_OUTPUT_TYPE_RGB:
    TRACE(3,"Selecting RGB Output Type");
    encoder->reg_2d = 0x0f; // RGB + CVBS (for sync)
    break;
  case SAA7127_OUTPUT_TYPE_COMPOSITE:
    TRACE(3,"Selecting Composite Output Type");
    encoder->reg_2d = 0x08; // 00001000 CVBS only, RGB DAC's off (high impedance mode) !!!
    break;
  case SAA7127_OUTPUT_TYPE_SVIDEO:
    TRACE(3,"Selecting S-Video Output Type");      
    encoder->reg_2d = 0xff; // 11111111  croma -> R, luma -> CVBS + G + B
    break;
  case SAA7127_OUTPUT_TYPE_YUV_V:
    TRACE(3,"Selecting YUV V Output Type");        
    encoder->reg_2d = 0x4f; // reg 2D = 01001111, all DAC's on, RGB + VBS
    encoder->reg_3a = 0x0b; // reg 3A = 00001011, bypass RGB-matrix
    break;
  case SAA7127_OUTPUT_TYPE_YUV_C:
    TRACE(3,"Selecting YUV C Output Type");
    encoder->reg_2d = 0x0f; // reg 2D = 00001111, all DAC's on, RGB + CVBS
    encoder->reg_3a = 0x0b; // reg 3A = 00001011, bypass RGB-matrix
    break;
  default:
    RETURN (-EINVAL);
  }

  /* Configure Encoder */

  saa7127_writereg(client, 0x2d, encoder->reg_2d);
  saa7127_writereg(client, 0x3a, (encoder->input_type == SAA7127_INPUT_TYPE_TEST_IMAGE) ? 0x80 : encoder->reg_3a);

  RETURN (0);
}


static int saa7127_set_input_type(struct i2c_client *client)
{
  struct saa7127 *encoder = (struct saa7127 *) client->data;

  ENTER;
  
  switch (encoder->input_type)
  {
  case SAA7127_INPUT_TYPE_NORMAL:         /* avia */
    TRACE(3,"Selecting Normal Encoder Input");          
    saa7127_writereg(client, 0x3a, encoder->reg_3a);
    break;
  case SAA7127_INPUT_TYPE_TEST_IMAGE:     /* color bar */
    TRACE(3,"Selecting Colour Bar generator");
    saa7127_writereg(client, 0x3a, 0x80);
    break;
  default:
    RETURN (-EINVAL);
  }

  RETURN (0);
}


/* ----------------------------------------------------------------------- */


static int saa7127_command (struct i2c_client *client,
                            unsigned int       cmd,
                            void              *parg)
{
  struct saa7127 *encoder = client->data;
  unsigned long arg = (unsigned long) parg;
  struct video_encoder_capability *cap = parg;
  ENTER;
  printk("saa7127_command: entered with cmd = %d\n", cmd);

  switch (cmd) 
  {
    case ENCODER_GET_CAPABILITIES:
      TRACE(3,"Asking Encoder Capabilities");
      cap->flags = VIDEO_ENCODER_PAL | VIDEO_ENCODER_NTSC;
      cap->inputs  = 1;
      cap->outputs = 1;
      break;

    case ENCODER_SET_NORM:
      TRACE(3,"Setting Encoder Video Standard");        
      switch (arg)
      {
        case VIDEO_MODE_NTSC:
          TRACE(3,"Set NTSC Video Mode");    
          encoder->norm  = SAA7127_VIDEO_NORM_NTSC;
          break;
        case VIDEO_MODE_PAL:
          TRACE(3,"Set PAL Video Mode");        
          encoder->norm  = SAA7127_VIDEO_NORM_PAL;
          break;
        default:
          return (-EINVAL);
      }
        saa7127_set_norm(client);
      break;

    case ENCODER_SET_INPUT:
      TRACE(3,"Setting Encoder Input");
      switch (arg)
      {
        case SAA7127_INPUT_NORMAL:    /* encoder input selected */
          TRACE(3,"Select Normal input");        
          encoder->input_type = SAA7127_INPUT_TYPE_NORMAL;
          break;
        case SAA7127_INPUT_TESTIMAGE: /* Internal colourbars selected */
          TRACE(3,"Select ColourBar Generator");              
          encoder->input_type = SAA7127_INPUT_TYPE_TEST_IMAGE;
          break;
        default:
          RETURN (-EINVAL);
      }
      saa7127_set_input_type (client);
      break;

    case ENCODER_SET_OUTPUT:
      TRACE(3,"Setting Encoder Output");
      encoder->output_type = arg;
      saa7127_set_output_type(client);
      break;

    case ENCODER_ENABLE_OUTPUT:
      TRACE(3,"Turn on/off Output");    
      switch (arg)
      {     
        case SAA7127_VIDEO_ENABLE:
          TRACE (3,"Turn on Video Output");
          encoder->enable=SAA7127_ENABLE;
          break;
        case SAA7127_VIDEO_DISABLE:
          TRACE (3,"Turn off Video Output");      
          encoder->enable=SAA7127_DISABLE;
          break;
        default:
          RETURN (-EINVAL);
      }
      saa7127_set_enable(client);
      break;

    default:
      RETURN (-EINVAL);
  }
  RETURN (0);
}



/* ----------------------------------------------------------------------- */

/* i2c implementation */
#ifndef NEW_I2C

static void saa7127_inc_use (struct i2c_client *client)
{
  #ifdef MODULE
    MOD_INC_USE_COUNT;
  #endif
}


/* ----------------------------------------------------------------------- */

static void saa7127_dec_use (struct i2c_client *client)
{
  #ifdef MODULE
    MOD_DEC_USE_COUNT;
  #endif
}

#else
    /* ver >= i2c 2.8.0 */
#endif


/*
 * Generic i2c probe
 * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
 */

static unsigned short normal_i2c[]        = { I2C_SAA7127_ADRESS >> 1,I2C_CLIENT_END };
static unsigned short normal_i2c_range[]  = { I2C_CLIENT_END };

I2C_CLIENT_INSMOD;

static int saa7127_i2c_id = 0;
struct i2c_driver i2c_driver_saa7127;


/* ----------------------------------------------------------------------- */



static int saa7127_detect_client (struct i2c_adapter *adapter,
                                  int                 address,
                                  unsigned short      flags,
                                  int                 kind)
{
  struct i2c_client *client;
  struct saa7127 *encoder;
  int read_result = 0;
  
  ENTER;
  
  TRACE(1,"detecting saa7127 client on address 0x%x", address << 1);

  /* Check if the adapter supports the needed features */
  if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
    return (0);

  client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
  if (client == 0)
    return (-ENOMEM);

  memset(client, 0, sizeof(struct i2c_client));
  client->addr = address;
  client->adapter = adapter;
  client->driver = &i2c_driver_saa7127;
  client->flags = I2C_CLIENT_ALLOW_USE;
  client->id = saa7127_i2c_id++;
  snprintf(client->name, sizeof(client->name) - 1, "saa7127[%d]", client->id);

  client->data = encoder = kmalloc(sizeof(struct saa7127), GFP_KERNEL);

  if (encoder == NULL)
  {
    kfree(client);
    return (-ENOMEM);
  }

  memset(encoder, 0, sizeof(struct saa7127));


  /* Initialize default values */
  encoder->output_type  = output_select;
  encoder->wss_enable   = SAA7127_WSS_DISABLE;
  encoder->wss_mode     = SAA7127_WSS_MODE_4_3_FULL_FORMAT;


  /* Look if the pal module parameter is set */

  if (pal == 1)
  {
    /* Select PAL Video Standard */
    encoder->norm         = SAA7127_VIDEO_NORM_PAL;
  }
  else
  {
    /* Select NTSC Video Standard, default */  
    encoder->norm         = SAA7127_VIDEO_NORM_NTSC;
  }

  /* Look if the Encoder needs to be enabled */
  
  if (enable_output == 1)
  {
    encoder->enable       = SAA7127_ENABLE;
  }
  else
  {
    /* for default disable output */
    /* Because the MPEG DECODER is not initialised */
    encoder->enable       = SAA7127_DISABLE;
  }

  /* The Encoder is does have internal Colourbar generator */
  /* This can be used for debugging, configuration values for the encoder */
  
  if (test_image == 1)
  {
     /* Select ColourBar Generator */
     encoder->input_type = SAA7127_INPUT_TYPE_TEST_IMAGE;
  }
  else
  {
     /* Select normal input */
     encoder->input_type = SAA7127_INPUT_TYPE_NORMAL;
  }


  TRACE(2,"writing init values");

  /* Configure Encoder */

  printk("saa7127: Configuring encoder...");
  saa7127_write_inittab(client, saa7127_init_config_common);
  saa7127_set_norm(client);
  saa7127_set_output_type (client);
  saa7127_set_wss (client);
  saa7127_set_wss_mode (client);
  saa7127_set_input_type (client);
  saa7127_set_enable (client);


  set_current_state(TASK_INTERRUPTIBLE);
  schedule_timeout(2*HZ);

  read_result = saa7127_read(client, 0x00);

  TRACE(4,"Read status register (00h) : 0x%02x ", read_result);

  i2c_attach_client(client);

  RETURN (0);
}


/* ----------------------------------------------------------------------- */


static int saa7127_attach_adapter (struct i2c_adapter *adapter)
{

  TRACE(2,"starting probe for adapter %s (0x%x)", adapter->name, adapter->id);
  return (i2c_probe(adapter, &addr_data, &saa7127_detect_client));
}


/* ----------------------------------------------------------------------- */


static int saa7127_detach_client (struct i2c_client *client)
{
  struct saa7127 *encoder = client->data;
  int err;
  
  /* Turn off TV output */

  encoder->enable       = SAA7127_DISABLE;
  saa7127_set_enable (client);

  err = i2c_detach_client(client);

  if (err)
  {
    return (err);
  }

  kfree(encoder);
  kfree(client);
  return (0);
}

/* ----------------------------------------------------------------------- */


struct i2c_driver i2c_driver_saa7127 =
{
  .name           = "saa7127",
  .id             = I2C_DRIVERID_SAA7127,
  .flags          = I2C_DF_NOTIFY,
  .attach_adapter = saa7127_attach_adapter,
  .detach_client  = saa7127_detach_client,
  .command        = saa7127_command,
#ifndef NEW_I2C
  .inc_use        = saa7127_inc_use,
  .dec_use        = saa7127_dec_use,
#else
    /* ver >= i2c 2.8.0 */
   .owner          = THIS_MODULE,
#endif
};

EXPORT_NO_SYMBOLS;


/* ----------------------------------------------------------------------- */


static int __init saa7127_init (void)
{
  INFO ("SAA7127 video encoder driver loaded");
  TRACE (1,"Driver version: V %s",SAA7127_DRIVER_VERSION);
  return (i2c_add_driver(&i2c_driver_saa7127));
}



/* ----------------------------------------------------------------------- */


static void __exit saa7127_exit (void)
{
 
  INFO ("SAA7127 video encoder driver unloaded");
  i2c_del_driver(&i2c_driver_saa7127);
}



/* ----------------------------------------------------------------------- */


module_init(saa7127_init);
module_exit(saa7127_exit);


MODULE_DESCRIPTION("Philips SAA7127 video encoder driver");
MODULE_AUTHOR("Roy Bulter");
MODULE_LICENSE("GPL");
MODULE_PARM(debug_mask, "i");
MODULE_PARM(test_image, "i");
MODULE_PARM(pal, "i");
MODULE_PARM(enable_output, "i");
MODULE_PARM(output_select, "i");
MODULE_PARM_DESC(debug_mask, "debug_mask (0-8192) ");
MODULE_PARM_DESC(test_image, "test_image (0-1) ");
MODULE_PARM_DESC(pal, "pal (0-1) ");
MODULE_PARM_DESC(enable_output, "enable_output (0-1) ");
MODULE_PARM_DESC(output_select, "output_select (0 = composite, 1 = s-video, 2 = rgb, 3 = YUVc, 4 = YUVv)");
