/*
    cm7327.c

    video4linux-2 driver for RTD Embedded Technologies PC/104-Plus CM7327
    frame grabber utilityModule(TM).

    Please visit http://www.rtd.com/PC104/UM/video/cm7327.htm for more
    information about this card.
    
    Copyright (C) 2002 Miguel Freitas
    Copyright (C) 2003 Michael Hunold <michael@mihu.de>
    Copyright (C) 2004 Robert W. Boone & RTD Embedded Technologies, Inc.

    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.
*/

#include <media/saa7146_vv.h>

/*=============================================================================
Encapsulation of 2.4 and 2.6 kernel differences.  The following issues need to
be addressed:
    - Addition of extra saa7146_i2c_adapter_prepare() parameter in 2.6 kernel.
    - Change in type of first parameters to saa7146_register_device() and
      saa7146_unregister_device() in 2.6 kernel.
 =============================================================================*/

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 0))

#define SAA7146_I2C_ADAPTER_PREPARE(device, adapter, bit_rate) \
    saa7146_i2c_adapter_prepare( \
	(device), \
	(adapter), \
	(bit_rate) \
    )

#define VIDEO_DEVICE_VARIABLE video_device_p

#define VIDEO_DEVICE_DECLARATION *VIDEO_DEVICE_VARIABLE

#else

#define SAA7146_I2C_ADAPTER_PREPARE(device, adapter, bit_rate) \
    saa7146_i2c_adapter_prepare( \
	(device), \
	(adapter), \
	I2C_CLASS_TV_ANALOG, \
	(bit_rate) \
    )

#define VIDEO_DEVICE_VARIABLE video_device

#define VIDEO_DEVICE_DECLARATION VIDEO_DEVICE_VARIABLE

#endif

/*=============================================================================
#define's
 =============================================================================*/

#define RTD_SUBSYSTEM_VENDOR_ID		0x1435

#define CM7327_SUBSYSTEM_DEVICE_ID	0xA104

/*
 * Address of Samsung S5D0127X01 video decoder chip I2C interface
 */

#define S5D0127X01_I2C_ADDRESS		0x6C

/*=============================================================================
Global variables
 =============================================================================*/

static int cm7327_num = 0;

/*=============================================================================
Forward declarations
 =============================================================================*/

static struct saa7146_extension cm7327_extension;
static struct saa7146_ext_vv cm7327_ext_vv;

/*=============================================================================
Structure defining data to be sent to the S5D0127X01 video decoder chip via the
I2C interface
 =============================================================================*/

struct cm7327_i2c_data {
	u16 address;
	u8 value;
};

/*=============================================================================
SAA7146 device extension video standards initialization table
 =============================================================================*/

static struct saa7146_standard cm7327_standards[] = {
	{
	 .name = "PAL",
	 .id = V4L2_STD_PAL,
	 .v_offset = 28,
	 .v_field = 288,
	 .h_offset = 1,
	 .h_pixels = 680,
	 .v_max_out = 576,
	 .h_max_out = 768},
	{
	 .name = "NTSC",
	 .id = V4L2_STD_NTSC,
	 .v_offset = 24,
	 .v_field = 240,
	 .h_offset = 5,
	 .h_pixels = 640,
	 .v_max_out = 480,
	 .h_max_out = 640},
	{
	 .name = "SECAM",
	 .id = V4L2_STD_SECAM,
	 .v_offset = 28,
	 .v_field = 288,
	 .h_offset = 1,
	 .h_pixels = 720,
	 .v_max_out = 576,
	 .h_max_out = 768}
};

#define CM7327_STANDARDS \
    (sizeof(cm7327_standards) / sizeof(struct saa7146_standard))

/*=============================================================================
Samsung S5D0127X01 video decoder chip I2C register initialization table
 =============================================================================*/

#define S5D0127X01_I2C_REGISTERS	0x100

static const u8 s5d0127x01_init_table[S5D0127X01_I2C_REGISTERS] = {
	0x00, 0x52, 0x30, 0x40, 0x01, 0x0C, 0x2A, 0x10,	/* 0x00 to 0x07 */
	0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x0F, 0x06,	/* 0x08 to 0x0F */
	0x00, 0x00, 0xE4, 0xC0, 0x00, 0x00, 0x00, 0x00,	/* 0x10 to 0x17 */
	0x14, 0x9B, 0xFE, 0xFF, 0xFC, 0xFF, 0x03, 0x22,	/* 0x18 to 0x1F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x20 to 0x27 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x9B, 0x00,	/* 0x28 to 0x2F */
	0x00, 0x00, 0x10, 0x80, 0x80, 0x10, 0x80, 0x80,	/* 0x30 to 0x37 */
	0x01, 0x04, 0x00, 0x00, 0x00, 0x29, 0xC0, 0x00,	/* 0x38 to 0x3F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x40 to 0x47 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x48 to 0x4F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x50 to 0x57 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x58 to 0x5F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x60 to 0x67 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x68 to 0x6F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x70 to 0x77 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x78 to 0x7F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x80 to 0x87 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x88 to 0x8F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x90 to 0x97 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0x98 to 0x9F */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xA0 to 0xA7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xA8 to 0xAF */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xB0 to 0xB7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xB8 to 0xBF */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xC0 to 0xC7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xC8 to 0xCF */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xD0 to 0xD7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xD8 to 0xDF */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xE0 to 0xE7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xE8 to 0xEF */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* 0xF0 to 0xF7 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00	/* 0xF8 to 0xFF */
};

/*=============================================================================
Samsung S5D0127X01 video decoder chip analog input channel select table.  Each
table entry is a I2C address/value pair.

For the value, the bits are interpreted as follows:
    - bits 3 through 0 select the analog input channel.
    - bits 7 through 4 control the analog AGC freeze, AGC gain control mode,
      VS edge alignment, and AGC gain calculation.
Bits 7 through 4 always have the value 0x6 which reduces AGC gain on ADC
overflow and aligns VS edges thusly: 1) leading edge aligned to half line or
beginning of line, and 2) trailing edge aligned to beginning of line.
 =============================================================================*/

static const struct cm7327_i2c_data s5d0127x01_input_channels[] = {
	{0x02, 0x60},
	{0x02, 0x64},
	{0x02, 0x61},
	{0x02, 0x65},
	{0x02, 0x62},
	{0x02, 0x66},
	{0x02, 0x68},
	{0x02, 0x69},
	{0x02, 0x6A}
};

#define S5D0127X01_INPUT_CHANNELS \
    (sizeof(s5d0127x01_input_channels) / sizeof(struct cm7327_i2c_data))

/*=============================================================================
video4linux-2 video inputs table
 =============================================================================*/

static struct v4l2_input cm7327_inputs[] = {
	{
	 0,
	 "CM7327 video 1",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0},
	{
	 1,
	 "CM7327 video 2",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0},
	{
	 2,
	 "CM7327 video 3",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0},
	{
	 3,
	 "CM7327 video 4",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0},
	{
	 4,
	 "CM7327 video 5",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0},
	{
	 5,
	 "CM7327 video 6",
	 V4L2_INPUT_TYPE_CAMERA,
	 2,
	 0,
	 (V4L2_STD_PAL_BG | V4L2_STD_NTSC_M),
	 0}
};

#define CM7327_INPUTS \
    (sizeof(cm7327_inputs) / sizeof(struct v4l2_input))

/*=============================================================================
Supported ioctl() request codes table
 =============================================================================*/

static struct saa7146_extension_ioctls cm7327_ioctls[] = {
	{VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE},
	{VIDIOC_G_INPUT, SAA7146_EXCLUSIVE},
	{VIDIOC_S_INPUT, SAA7146_EXCLUSIVE},
	{VIDIOC_S_STD, SAA7146_AFTER},
	{0, 0}
};

/*=============================================================================
CM7327 device descriptor structure
 =============================================================================*/

struct cm7327 {
	struct video_device VIDEO_DEVICE_DECLARATION;
	struct i2c_adapter i2c_adapter;
	u8 current_channel;
};

/*=============================================================================
Samsung S5D0127X01 video decoder chip signal standards initialization tables.
These tables contains pairs of I2C addresses and data.  Each table is terminated
by an entry having an address of 0xFFFF and a data value of 0xFF.
 =============================================================================*/

/*
 * PAL
 */

static const struct cm7327_i2c_data s5d0127x01_pal[] = {
	{0x01, 0x52},		/* Control Register A */
	{0x12, 0x64},		/* Chroma Control Register A */
	{0x2D, 0x2C},		/* VAV Begin Register */
	{0x2E, 0x9B},		/* VAV End Register */
	{0xFFFF, 0xFF}
};

/*
 * NTSC
 */

static const struct cm7327_i2c_data s5d0127x01_ntsc[] = {
	{0x01, 0x53},		/* Control Register A */
	{0x12, 0x04},		/* Chroma Control Register A */
	{0x2D, 0x23},		/* VAV Begin Register */
	{0x2E, 0x81},		/* VAV End Register */
	{0xFFFF, 0xFF}
};

/*
 * SECAM
 */

static const struct cm7327_i2c_data s5d0127x01_secam[] = {
	{0x01, 0x52},		/* Control Register A */
	{0x12, 0x64},		/* Chroma Control Register A */
	{0x2D, 0x2C},		/* VAV Begin Register */
	{0x2E, 0x9B},		/* VAV End Register */
	{0xFFFF, 0xFF}
};

/*
 * Table of all supported standards
 */

static const struct cm7327_i2c_data *s5d0127x01_standards[] = {
	&(s5d0127x01_pal[0]),
	&(s5d0127x01_ntsc[0]),
	&(s5d0127x01_secam[0])
};

/*
 * Indices in above table for supported standards
 */

#define S5D0127X01_STD_PAL	0
#define S5D0127X01_STD_NTSC	1
#define S5D0127X01_STD_SECAM	2

/*=============================================================================
Functions which encapsulate access to the Samsung S5D0127X01 video decoder chip
 =============================================================================*/

/******************************************************************************
s5d0127x01_read_from_i2c()

    Purpose:
        Read from a control register via the I2C interface.

    Parameters:
        cm7327_p ====> Address of CM7327 device descriptor.
        i2c_address => Address of control register.
        i2c_value_p => Address where value read will be stored.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -EIO
                    An error occurred reading from the I2C interface.
 ******************************************************************************/

static int
s5d0127x01_read_from_i2c(struct cm7327 *cm7327_p,
			 u16 i2c_address, u8 * i2c_value_p)
{
	union i2c_smbus_data i2c_data;

	if (i2c_smbus_xfer(&(cm7327_p->i2c_adapter),
			   S5D0127X01_I2C_ADDRESS,
			   0,
			   I2C_SMBUS_READ,
			   i2c_address, I2C_SMBUS_BYTE_DATA, &i2c_data)
	    != 0) {
		printk(KERN_ERR
		       "s5d0127x01_read_from_i2c(): FAILED I2C read at address %x!\n",
		       i2c_address);
		return -EIO;
	}

	*i2c_value_p = i2c_data.byte;

	return 0;
}

/******************************************************************************
s5d0127x01_write_to_i2c()

    Purpose:
        Write to a control register via the I2C interface.

    Parameters:
        cm7327_p ====> Address of CM7327 device descriptor.
        i2c_address => Control register address.
        i2c_value ===> Value to write into control register.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -EIO
                    An error occurred writing to the I2C interface.
 ******************************************************************************/

static int
s5d0127x01_write_to_i2c(struct cm7327 *cm7327_p, u16 i2c_address, u8 i2c_value)
{
	union i2c_smbus_data i2c_data;

	i2c_data.byte = i2c_value;

	if (i2c_smbus_xfer(&(cm7327_p->i2c_adapter),
			   S5D0127X01_I2C_ADDRESS,
			   0,
			   I2C_SMBUS_WRITE,
			   i2c_address, I2C_SMBUS_BYTE_DATA, &i2c_data)
	    != 0) {
		printk(KERN_ERR
		       "s5d0127x01_write_to_i2c(): FAILED I2C write at address %x!\n",
		       i2c_address);
		return -EIO;
	}

	return 0;
}

/******************************************************************************
s5d0127x01_hardware_init()

    Purpose:
        Initialize the video decoder chip's control registers.

    Parameters:
        cm7327_p => Address of CM7327 device descriptor.

    Return Value:
        0
            Success.

        < 0
            Failure.  Please see the description of s5d0127x01_write_to_i2c()
            for information on possible values returned in this case.
 ******************************************************************************/

static int s5d0127x01_hardware_init(struct cm7327 *cm7327_p)
{
	u16 i2c_address;

	for (i2c_address = 0;
	     i2c_address < S5D0127X01_I2C_REGISTERS; i2c_address++) {
		int status;

		status =
		    s5d0127x01_write_to_i2c(cm7327_p, i2c_address,
					    s5d0127x01_init_table[i2c_address]
		    );
		if (status != 0) {
			printk(KERN_ERR
			       "s5d0127x01_hardware_init(): FAILED to initialize hardware!\n");
			return status;
		}
	}

	return 0;
}

/******************************************************************************
s5d0127x01_probe()

    Purpose:
        Probe for a Samsung S5D0127X01 video decoder chip during CM7327 device
        initialization.  Basically the probe consists of determining exactly
        what type of chip is present.

    Parameters:
        cm7327_p => Address of CM7327 device descriptor.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -ENODEV
                    Command Register E contains an invalid bit pattern in bits
                    3 through 0 (Chip revision ID).

            Please see the description of s5d0127x01_read_from_i2c() for
            information on other possible values returned in this case.
 ******************************************************************************/

static int s5d0127x01_probe(struct cm7327 *cm7327_p)
{
	int status;
	u8 command_register_e;
	u8 status_register;

	/*
	 * Read the Status Register located at offset 0x00
	 */

	status = s5d0127x01_read_from_i2c(cm7327_p, 0x00, &status_register);
	if (status != 0) {
		printk(KERN_ERR
		       "s5d0127x01_probe(): Status Register read FAILED!\n");
		return status;
	}

	/*
	 * Mask off everything except bit 7 (Chip version ID)
	 */

	status_register &= 0x80;

	/*
	 * If bit 7 is 0, then the chip is a KS0122S
	 */

	if (status_register == 0) {
		printk(KERN_INFO "CM7327: KS0122S video decoder chip.\n");
		return 0;
	}

	/*
	 * Status Register bit 7 is set, so more decoding is needed
	 */

	/*
	 * Read the Command Register E located at offset 0x3D
	 */

	status = s5d0127x01_read_from_i2c(cm7327_p, 0x3D, &command_register_e);
	if (status != 0) {
		printk(KERN_ERR
		       "s5d0127x01_probe(): Command Register E read FAILED!\n");
		return status;
	}

	/*
	 * Mask off everything except bits 3 through 0 (Chip revision ID)
	 */

	command_register_e &= 0x0F;

	/*
	 * Decode the Chip Revision ID
	 */

	switch (command_register_e) {
	case 0x0:
		printk(KERN_INFO "CM7327: S5D0124D01 video decoder chip.\n");
		break;

	case 0x9:
		printk(KERN_INFO "CM7327: S5D0127X01 video decoder chip.\n");
		break;

	default:
		printk(KERN_ERR
		       "s5d0127x01_probe(): Invalid chip revision ID %x found in "
		       "Command Register E!\n", command_register_e);
		return -ENODEV;
		break;
	}

	return 0;
}

/******************************************************************************
s5d0127x01_select_input_channel()

    Purpose:
        Set the analog video input channel for the video decoder chip.

    Parameters:
        cm7327_p => Address of CM7327 device descriptor.
        channel ==> Analog video input channel to select.

    Return Value:
        0
            Success.

        < 0
            Failure.  Please see the description of s5d0127x01_write_to_i2c()
            for information on possible values returned in this case.
 ******************************************************************************/

static int s5d0127x01_select_input_channel(struct cm7327 *cm7327_p, u8 channel)
{
	int status;

	status = s5d0127x01_write_to_i2c(cm7327_p,
					 s5d0127x01_input_channels[channel].
					 address,
					 s5d0127x01_input_channels[channel].
					 value);
	if (status != 0) {
		printk(KERN_ERR
		       "s5d0127x01_select_input_channel(): FAILED to set input channel!\n");
		return status;
	}

	cm7327_p->current_channel = channel;

	return 0;
}

/******************************************************************************
s5d0127x01_set_standard()

    Purpose:
        Set the video standard for the video decoder chip.

    Parameters:
        cm7327_p => Address of CM7327 device descriptor.
        standard => Video standard to be set.  No error checking is performed
                    on this parameter since it assumed that either the caller
                    passed in a correct value or the caller already validated
                    it.

    Return Value:
        0
            Success.

        < 0
            Failure.  Please see the description of s5d0127x01_write_to_i2c()
            for information on other possible values returned in this case.
 ******************************************************************************/

static int
s5d0127x01_set_standard(struct cm7327 *cm7327_p, v4l2_std_id standard)
{
	const struct cm7327_i2c_data *standard_p;
	u8 i2c_index;

	switch (standard) {
	case V4L2_STD_PAL:
		standard_p = s5d0127x01_standards[S5D0127X01_STD_PAL];
		break;

	case V4L2_STD_NTSC:
		standard_p = s5d0127x01_standards[S5D0127X01_STD_NTSC];
		break;

	case V4L2_STD_SECAM:
		standard_p = s5d0127x01_standards[S5D0127X01_STD_SECAM];
		break;

	default:
		standard_p = s5d0127x01_standards[S5D0127X01_STD_PAL];
		break;
	}

	for (i2c_index = 0; ((standard_p[i2c_index].address != 0xFFFF)
			     && (standard_p[i2c_index].value != 0xFF)
	     ); i2c_index++) {
		int status;

		status = s5d0127x01_write_to_i2c(cm7327_p,
						 standard_p[i2c_index].address,
						 standard_p[i2c_index].value);
		if (status != 0) {
			printk(KERN_ERR
			       "s5d0127_set_standard(): FAILED to set video standard!\n");
			return status;
		}
	}

	return 0;
}

/*=============================================================================
Functions which either do not directly access the Samsung S5D0127X01 video
decoder chip or which use other functions to do so
 =============================================================================*/

/******************************************************************************
cm7327_hardware_init()

    Purpose:
        Initialize a CM7327 device during device probe and set up.

    Parameters:
        device_p ===> Address of SAA7146 device descriptor.

    Return Value:
        0
            Success.

        < 0
            Failure.  Please see the descriptions of s5d0127x01_write_to_i2c(),
            s5d0127x01_set_standard(), and s5d0127x01_select_input_channel()
            for information on possible values returned in this case.
 ******************************************************************************/

static int cm7327_hardware_init(struct saa7146_dev *device_p)
{
	int status;
	struct cm7327 *cm7327_p = (struct cm7327 *)device_p->ext_priv;

    /*-------------------------------------------------------------------------
    Philips SAA7146 multimedia bridge chip initialization
     -------------------------------------------------------------------------*/

	/*
	 * Set bits 8 and 10 (while not disturbing other bits) in Main Control
	 * Register 1.  Setting bit 8 enables the I2C port pins.  Setting bit 10
	 * enables the real time video port pins.
	 *
	 * Note that Main Control Register 1 and 2 are broken into 2 16-bit pieces.
	 * Bits 15 through 0 in each register control how the chip operates.  Bits
	 * 31 through 16 function as a mask to control which bits from 15 through 0
	 * can be modified.
	 */

	saa7146_write(device_p, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26));

	/*
	 * Set bits 6 and 4 in General Purpose I/O 2 Control Register; this sets
	 * general purpose I/O pin 2 to be a fixed high output
	 */

	saa7146_setgpio(device_p, 2, SAA7146_GPIO_OUTHI);

	/*
	 * Set bits 8, 9, 10, 24, 25, and 26 in Dual D1 Interface Init Register.
	 * Setting bits 24, 25, and 26 sets sync edge selection and field detection
	 * mode internal SyncA signals to be derived from SAV and EAV decoded from
	 * data stream at D1_A port.  Setting bits 8, 9, and 10 sets sync edge
	 * selection and field detection mode internal SyncB signals to be derived
	 * from SAV and EAV decoded from data stream at D1_B port.
	 */

	saa7146_write(device_p, DD1_INIT, 0x07000700);

	/*
	 * Clear all bits in Video Data Stream Port D1_A/D1_B Handling Register.
	 * Bits 31 through 16 control port D1_A.  Bits 15 through 0 control port
	 * D1_B.  Clearing bits 31 and 30 selects video data Port_A amd PXQ_A to be
	 * 3-state inputs.  Clearing bit 29 indicates no SAV and EAV data will be
	 * contained in port D1_A video data output stream.  Clearing bit 26 sets
	 * polarity change of Port_A field identification signal to that detected
	 * in field detection.  Clearing bits 15 and 14 selects video data Port_A
	 * amd PXQ_A to be 3-state inputs.  Clearing bit 13 indicates no SAV and
	 * EAV data will be contained in port D1_B video data output stream.
	 * Clearing bit 10 sets polarity change of Port_B field identification
	 * signal to that detected in field detection.
	 */

	saa7146_write(device_p, DD1_STREAM_B, 0x00000000);

	/*
	 * Set bits 9 and 10 (while not disturbing other bits) in Main Control
	 * Register 2.  Setting bit 9 uploads video data stream handling at port
	 * D1_A.  Setting bit 10 uploads video data stream handling at port D1_B.
	 */

	saa7146_write(device_p, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26));

    /*-------------------------------------------------------------------------
    End of Philips SAA7146 multimedia bridge chip initialization
     -------------------------------------------------------------------------*/

	/*
	 * Now initialize the Samsung S5D0127X01 video decoder chip
	 */

	status = s5d0127x01_hardware_init(cm7327_p);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_hardware_init(): S5D0127X01 initialization FAILED!\n");
		return status;
	}

	status = s5d0127x01_set_standard(cm7327_p, V4L2_STD_PAL);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_hardware_init(): S5D0127X01 standard set FAILED!\n");
		return status;
	}

	status = s5d0127x01_select_input_channel(cm7327_p, 0);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_hardware_init(): S5D0127X01 input channel select FAILED!\n");
		return status;
	}

	return 0;
}

/******************************************************************************
cm7327_attach()

    Purpose:
        Prepare a CM7327 board to be used as a video device.

    Parameters:
        device_p =======> Address of SAA7146 device descriptor.
        pci_ext_data_p => Address of SAA7146 PCI extension data descriptor.
                          This is unused.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -ENODEV
                    The PCI subsystem vendor ID contained in the SAA7146 device
                    descriptor is not valid.

                -ENODEV
                    The PCI subsystem device ID contained in the SAA7146 device
                    descriptor is not valid.

                -ENODEV
                    CM7327 device's I2C adapter could not be prepared.

                -ENODEV
                    CM7327 device's I2C adapter could not be added to system.

                -ENODEV
                    Video interface initialization failed.

                -ENODEV
                    Registering CM7327 device as an SAA7146 extension failed.

                -ENOMEM
                    Kernel memory allocation for CM7327 device descriptor
                    failed.

            Please see the descriptions of s5d0127x01_probe() and
            cm7327_hardware_init() for information on other possible values
            returned in this case.
 ******************************************************************************/

static int
cm7327_attach(struct saa7146_dev *device_p,
	      struct saa7146_pci_extension_data *pci_ext_data_p)
{
	int status;
	struct cm7327 *cm7327_p;

	cm7327_p = (struct cm7327 *)kmalloc(sizeof(struct cm7327), GFP_KERNEL);
	if (cm7327_p == NULL) {
		printk(KERN_ERR
		       "cm7327_attach(): Kernel memory allocation FAILED!\n");
		return -ENOMEM;
	}

	memset(cm7327_p, 0x0, sizeof(struct cm7327));

	if (device_p->pci->subsystem_vendor != RTD_SUBSYSTEM_VENDOR_ID) {
		printk(KERN_ERR
		       "cm7327_attach(): Invalid subsystem vendor ID 0x%x!\n",
		       device_p->pci->subsystem_device);
		kfree(cm7327_p);
		return -ENODEV;
	}

	if (device_p->pci->subsystem_device != CM7327_SUBSYSTEM_DEVICE_ID) {
		printk(KERN_ERR
		       "cm7327_attach(): Invalid subsystem device ID 0x%x!\n",
		       device_p->pci->subsystem_device);
		kfree(cm7327_p);
		return -ENODEV;
	}

	device_p->ext_priv = cm7327_p;

	if (SAA7146_I2C_ADAPTER_PREPARE(device_p,
					&(cm7327_p->i2c_adapter),
					SAA7146_I2C_BUS_BIT_RATE_480)
	    != 0) {
		printk(KERN_ERR
		       "cm7327_attach(): I2C adapter prepare FAILED!\n");
		kfree(cm7327_p);
		return -ENODEV;
	}

	strlcpy(&(((cm7327_p->i2c_adapter).name)[0]),
		"CM7327", sizeof((cm7327_p->i2c_adapter).name)
	    );

	if (i2c_add_adapter(&(cm7327_p->i2c_adapter)) < 0) {
		printk(KERN_ERR "cm7327_attach(): I2C adapter add FAILED!\n");
		kfree(cm7327_p);
		return -ENODEV;
	}

	status = s5d0127x01_probe(cm7327_p);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_attach(): Video decoder probe FAILED!\n");
		i2c_del_adapter(&(cm7327_p->i2c_adapter));
		kfree(cm7327_p);
		return status;
	}

	status = cm7327_hardware_init(device_p);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_attach(): Hardware initialization FAILED!\n");
		i2c_del_adapter(&(cm7327_p->i2c_adapter));
		kfree(cm7327_p);
		return status;
	}

	if (saa7146_vv_init(device_p, &cm7327_ext_vv) != 0) {
		printk(KERN_ERR
		       "cm7327_attach(): Video interface initialization FAILED!\n");
		i2c_del_adapter(&(cm7327_p->i2c_adapter));
		kfree(cm7327_p);
		return -ENODEV;
	}

	status = saa7146_register_device(&(cm7327_p->VIDEO_DEVICE_VARIABLE),
					 device_p, "CM7327", VFL_TYPE_GRABBER);
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_attach(): SAA7146 device registration FAILED!\n");
		i2c_del_adapter(&(cm7327_p->i2c_adapter));
		kfree(cm7327_p);
		return -ENODEV;
	}

	printk(KERN_INFO
	       "cm7327_attach(): Attached CM7327 device %d.\n", cm7327_num);

	cm7327_num++;

	return 0;
}

/******************************************************************************
cm7327_detach()

    Purpose:
        Prepare a CM7327 board so that it can no longer be used as a video
        device.

    Parameters:
        device_p => Address of SAA7146 device descriptor.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -ENODEV
                    Unregistering CM7327 device as an SAA7146 extension failed.

                -ENODEV
                    Video interface release failed.

            Please see the kernel function i2c_del_adapter() for information on
            other possible values returned in this case.
 ******************************************************************************/

static int cm7327_detach(struct saa7146_dev *device_p)
{
	int status;
	struct cm7327 *cm7327_p = (struct cm7327 *)device_p->ext_priv;

	if (saa7146_unregister_device(&(cm7327_p->VIDEO_DEVICE_VARIABLE),
				      device_p)
	    != 0) {
		printk(KERN_ERR
		       "cm7327_detach(): SAA7146 device unregistration FAILED!\n");
		kfree(cm7327_p);
		return -ENODEV;
	}

	if (saa7146_vv_release(device_p) != 0) {
		printk(KERN_ERR
		       "cm7327_detach(): Video interface release FAILED!\n");
		kfree(cm7327_p);
		return -ENODEV;
	}

	status = i2c_del_adapter(&(cm7327_p->i2c_adapter));
	if (status != 0) {
		printk(KERN_ERR
		       "cm7327_detach(): I2C adapter delete FAILED!\n");
		kfree(cm7327_p);
		return status;
	}

	kfree(cm7327_p);

	cm7327_num--;

	printk(KERN_INFO
	       "cm7327_detach(): Detached CM7327 device %d.\n", cm7327_num);

	return 0;
}

/******************************************************************************
cm7327_ioctl()

    Purpose:
        Handle video ioctl() requests on a CM7327 device.

    Parameters:
        fh_p =======> Address of SAA7146 device file handle.
        request ====> Request code of video ioctl().
        argument_p => Address of ioctl() argument.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -EINVAL
                    request is VIDIOC_ENUMINPUT and the v4l2_input structure
                    referred to by argument_p contains input index which is not
                    valid.

                -EINVAL
                    request is VIDIOC_S_INPUT and the analog input channel
                    referred to by argument_p is not valid.

                -ENOIOCTLCMD
                    request is not a valid ioctl() request code.

            Please see the description of s5d0127x01_select_input_channel() for
            information on other possible values returned in this case.
 ******************************************************************************/

static int
cm7327_ioctl(struct saa7146_fh *fh_p, unsigned int request, void *argument_p)
{
	int status;
	struct saa7146_dev *device_p = fh_p->dev;
	struct cm7327 *cm7327_p = (struct cm7327 *)device_p->ext_priv;

	switch (request) {
	case VIDIOC_ENUMINPUT:{
			struct v4l2_input *input_p = argument_p;

			if ((input_p->index < 0)
			    || (input_p->index >= CM7327_INPUTS)) {
				printk(KERN_ERR
				       "cm7327_ioctl(VIDIOC_ENUMINPUT): Invalid index %d!\n",
				       input_p->index);
				return -EINVAL;
			}

			memcpy(input_p,
			       &cm7327_inputs[input_p->index],
			       sizeof(struct v4l2_input)
			    );

			status = 0;

			break;
		}

	case VIDIOC_G_INPUT:{
			int *input_p = (int *)argument_p;

			*input_p = cm7327_p->current_channel;
			status = 0;
			break;
		}

	case VIDIOC_S_INPUT:{
			int input = *((int *)argument_p);

			if ((input < 0) || (input >= CM7327_INPUTS)) {
				printk(KERN_ERR
				       "cm7327_ioctl(VIDIOC_S_INPUT): Invalid channel %d!\n",
				       input);
				return -EINVAL;
			}

			status =
			    s5d0127x01_select_input_channel(cm7327_p, input);
			break;
		}

	default:
		printk(KERN_ERR "cm7327_ioctl(): Invalid request code!\n");
		status = -ENOIOCTLCMD;
		break;
	}

	return status;
}

/******************************************************************************
cm7327_std_callback()

    Purpose:
        Process video standard change requests from application level.

    Parameters:
        device_p ===> Address of SAA7146 device descriptor.
        standard_p => Address of standard requested.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -EINVAL
                    The video standard requested is not valid.

            Please see the description of s5d0127x01_set_standard() for
            information on other possible values returned in this case.
 ******************************************************************************/

static int
cm7327_std_callback(struct saa7146_dev *device_p,
		    struct saa7146_standard *standard_p)
{
	int status;
	struct cm7327 *cm7327_p = (struct cm7327 *)device_p->ext_priv;

	switch (standard_p->id) {
	case V4L2_STD_PAL:
	case V4L2_STD_NTSC:
	case V4L2_STD_SECAM:
		status = s5d0127x01_set_standard(cm7327_p, standard_p->id);
		break;

	default:
		printk(KERN_ERR
		       "cm7327_std_callback(): Invalid video standard!\n");
		status = -EINVAL;
		break;
	}

	return status;
}

/*=============================================================================
SAA7146 device extension PCI data descriptor
 =============================================================================*/

static const struct saa7146_pci_extension_data cm7327_pci_ext_data = {
	.ext = &cm7327_extension,
	.ext_priv = "RTD Embedded Technologies CM7327",
};

/*=============================================================================
Table of PCI devices supported by the driver
 =============================================================================*/

static struct pci_device_id cm7327_pci_table[] = {
	MAKE_EXTENSION_PCI(cm7327_pci_ext_data,
			   RTD_SUBSYSTEM_VENDOR_ID,
			   CM7327_SUBSYSTEM_DEVICE_ID),
	{
	 .vendor = 0,
	 }
};

/*=============================================================================
SAA7146 device extension descriptor for CM7327 device
 =============================================================================*/

static struct saa7146_extension cm7327_extension = {
	.name = "RTD CM7327",
	.flags = SAA7146_USE_I2C_IRQ,
	.pci_tbl = &(cm7327_pci_table[0]),
	.module = THIS_MODULE,
	.probe = NULL,
	.attach = cm7327_attach,
	.detach = cm7327_detach,
	.irq_mask = 0,
	.irq_func = NULL,
};

/*=============================================================================
SAA7146 device video interface descriptor
 =============================================================================*/

static struct saa7146_ext_vv cm7327_ext_vv = {
	.inputs = CM7327_INPUTS,
	.audios = 0,
	.capabilities = 0,
	.flags = 0,
	.stds = &(cm7327_standards[0]),
	.num_stds = CM7327_STANDARDS,
	.std_callback = cm7327_std_callback,
	.ioctls = &(cm7327_ioctls[0]),
	.ioctl = cm7327_ioctl,
};

/*=============================================================================
Functions related to kernel module handling
 =============================================================================*/

/******************************************************************************
cm7327_init_module()

    Purpose:
        Perform all actions necessary to initialize the CM7327 driver at module
        load time.

    Parameters:
        None.

    Return Value:
        0
            Success.

        < 0
            Failure with value returned as follows:
                -ENODEV
                    PCI driver setup failed. 
 ******************************************************************************/

int __init cm7327_init_module(void)
{
	return saa7146_register_extension(&cm7327_extension);
}

/******************************************************************************
cm7327_cleanup_module()

    Purpose:
        Perform all actions necessary to deinitialize the CM7327 driver at
        module unload time.

    Parameters:
        None.

    Return Value:
        None.
 ******************************************************************************/

void __exit cm7327_cleanup_module(void)
{
	saa7146_unregister_extension(&cm7327_extension);
}

module_init(cm7327_init_module);
module_exit(cm7327_cleanup_module);

MODULE_AUTHOR("Miguel Freitas, Michael Hunold, Robert W. Boone &"
	      " RTD Embedded Technologies, Inc.");
MODULE_DESCRIPTION("RTD CM7327 frame grabber video4linux-2 driver");
MODULE_LICENSE("GPL");
