File:  [DVB] / kfir / driver / saa7113.c
Revision 1.4: download - view: text, annotated - select for diffs
Fri Feb 18 14:40:27 2005 UTC (19 years, 3 months ago) by kls
Branches: MAIN
CVS tags: HEAD
ported to kernel 2.6 (by Anssi Hannula)

/* 
    SAA7113 - Philips video decoder driver
    
    
    Copyright (C) 2000 Ralph Metzler <ralph@convergence.de>
                  for Convergence Integrated Media GmbH
    Copyright (C) 2004 Anssi Hannula <anssi.hannula@gmail.com>
                  ported for kernel 2.6

    based on the SAAA7110 and SAA7111 drivers by:

    Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl>

    Copyright (C) 1998 Dave Perks <dperks@ibm.net>

    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 <linux/pci.h>
#include <linux/sched.h>
#include <linux/video_decoder.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/videodev.h>
#include <linux/i2c.h>
#include "saa7113.h"

#define I2C_SAA7113        0x4a	/* or 0x48 */

#define	I2C_DELAY		10	/* 10 us or 100khz */

MODULE_DESCRIPTION("SAA7113 - Philips video decoder driver");
MODULE_AUTHOR("Ralph Metzler, Anssi Hannula");
MODULE_LICENSE("GPL");

static struct i2c_client client_template;

struct saa7113 {
	struct i2c_client *client;
	int addr;
	unsigned char reg[0x62];

	int norm;
	int input;
	int enable;
	int bright;
	int contrast;
	int hue;
	int sat;
};

static int writereg(struct i2c_client *client,
		    unsigned char reg, unsigned char data)
{
	int ret;
	unsigned char msg[] = { 0x1f, 0x00 };

	msg[0] = reg;
	msg[1] = data;
	ret = i2c_master_send(client, msg, 2);
	if (ret != 2)
		printk(KERN_INFO "saa7113: writereg error\n");

	((struct saa7113 *) i2c_get_clientdata(client))->reg[reg] = data;
	return ret;
}

static int writeregs(struct i2c_client *client, const unsigned char *regs)
{
	unsigned char reg, data;

	while (*regs != 0xff) {
		reg = *(regs++);
		data = *(regs++);
		if (writereg(client, reg, data) < 0)
			return -1;
	}
	return 0;
}

static u8 readreg(struct i2c_client *client, unsigned char reg)
{
	struct i2c_adapter *adap = client->adapter;
	unsigned char mm1[] = { 0x1e };
	unsigned char mm2[] = { 0x00 };
	struct i2c_msg msgs[2];

	msgs[0].flags = 0;
	msgs[1].flags = I2C_M_RD;
	msgs[0].addr = msgs[1].addr = client->addr;
	mm1[0] = reg;
	msgs[0].len = 1;
	msgs[1].len = 1;
	msgs[0].buf = mm1;
	msgs[1].buf = mm2;
	i2c_transfer(adap, msgs, 2);

	return mm2[0];
}

static const unsigned char init_saa7113[] = {
	0x01, 0x08,
	0x02, 0xc0,		/* c7 s-video */
	0x03, 0x23,
	0x04, 0x00,
	0x05, 0x00,
	0x06, 0xeb,
	0x07, 0xe0,

	0x08, 0x88,
	0x09, 0x00,
	0x0a, 0x80,
	0x0b, 0x47,
	0x0c, 0x40,
	0x0d, 0x00,
	0x0e, 0x01,
	0x0f, 0xaa,

	0x10, 0x00,
	0x11, 0x1C,
	0x12, 0x01,
	0x13, 0x00,
	0x15, 0x00,
	0x16, 0x00,
	0x17, 0x00,

	0x40, 0x82,
	0x58, 0x00,
	0x59, 0x54,
	0x5a, 0x0a,
	0x5b, 0x83,
	0x5e, 0x00,

	0xff
};


void init(struct i2c_client *client)
{
	struct saa7113 *decoder =
	    (struct saa7113 *) i2c_get_clientdata(client);

	decoder->addr = client->addr;
	decoder->norm = VIDEO_MODE_AUTO;
	decoder->input = 0;
	decoder->enable = 1;
	decoder->bright = 32768;
	decoder->contrast = 32768;
	decoder->hue = 32768;
	decoder->sat = 32768;

	decoder->client = client;

	writeregs(client, init_saa7113);
	printk(KERN_INFO "saa7113: status=%02x\n", readreg(client, 0x1f));
}


int attach_adapter(struct i2c_adapter *adap)
{
	struct saa7113 *decoder;
	struct i2c_client *client;
	u8 version;

	client_template.adapter = adap;

	if (i2c_master_send(&client_template, NULL, 0))
		return -1;

	client_template.adapter = adap;

	version = readreg(&client_template, 0x00);
	printk(KERN_INFO "saa7113: version=%02x\n", version);

	if (NULL ==
	    (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
		return -ENOMEM;
	memcpy(client, &client_template, sizeof(struct i2c_client));

	decoder = kmalloc(sizeof(struct saa7113), GFP_KERNEL);
	i2c_set_clientdata(client, decoder);

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

	memset(decoder, 0, sizeof(struct saa7113));

	printk(KERN_INFO "saa7113: attaching SAA7113 at 0x%02x\n",
	       (client->addr) << 1);
	i2c_attach_client(client);

	init(client);

	printk(KERN_INFO "saa7113: attached to adapter %s\n", adap->name);
	return 0;
}

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


int detach_client(struct i2c_client *client)
{
	struct saa7113 *decoder =
	    (struct saa7113 *) i2c_get_clientdata(client);
	i2c_detach_client(client);
	kfree(client);
	kfree(decoder);
	return 0;
}

static int saa7113_command(struct i2c_client *client, unsigned int cmd,
			   void *arg)
{
	struct saa7113 *decoder =
	    (struct saa7113 *) i2c_get_clientdata(client);
	int v;

	switch (cmd) {

	case DECODER_GET_CAPABILITIES:
		{
			struct video_decoder_capability *dc = arg;
			dc->flags = VIDEO_DECODER_PAL
			    | VIDEO_DECODER_NTSC
			    | VIDEO_DECODER_SECAM
			    | VIDEO_DECODER_AUTO | VIDEO_DECODER_CCIR;
			dc->inputs = 2;
			dc->outputs = 1;
		}
		break;

	case DECODER_GET_STATUS:
		{
			int *iarg = arg;
			int status;
			int res;

			status = readreg(client, 0x1f);
			res = 0;
			if ((status & (1 << 6)) == 0) {
				res |= DECODER_STATUS_GOOD;
			}
			switch (decoder->norm) {
			case VIDEO_MODE_NTSC:
				res |= DECODER_STATUS_NTSC;
				break;
			case VIDEO_MODE_PAL:
				res |= DECODER_STATUS_PAL;
				break;
			default:
			case VIDEO_MODE_AUTO:
				if ((status & (1 << 5)) != 0) {
					res |= DECODER_STATUS_NTSC;
				} else {
					res |= DECODER_STATUS_PAL;
				}
				break;
			}
			if ((status & (1 << 0)) != 0) {
				res |= DECODER_STATUS_COLOR;
			}
			*iarg = res;
		}
		break;


	case DECODER_SET_NORM:
		v = *(int *) arg;

		switch (v) {
		case VIDEO_MODE_NTSC:
			writereg(client, 0x08,
				 (decoder->reg[0x08] & 0x3f) | 0x40);
			break;

		case VIDEO_MODE_PAL:
			writereg(client, 0x08,
				 (decoder->reg[0x08] & 0x3f) | 0x00);
			break;

		case VIDEO_MODE_AUTO:
			writereg(client, 0x08,
				 (decoder->reg[0x08] & 0x3f) | 0x80);
			break;

		default:
			return -EINVAL;

		}
		decoder->norm = v;

		break;

	case DECODER_SET_INPUT:
		{
			int *iarg = arg;

			if (*iarg < 0 || *iarg > 7) {
				return -EINVAL;
			}
			if (decoder->input != *iarg) {
				decoder->input = *iarg;
				/* select mode */
				writereg(client, 0x02,
					 (decoder->
					  reg[0x02] & 0xf8) | decoder->
					 input);
				/* bypass chrominance trap for modes 4..7 */
				writereg(client, 0x09,
					 (decoder->reg[0x09] & 0x7f) |
					 ((decoder->input >
					   3) ? 0x80 : 0));
			}
		}
		break;


	case DECODER_SET_OUTPUT:
		v = *(int *) arg;
		/* not much choice of outputs */
		if (v != 0)
			return -EINVAL;
		break;

	case DECODER_ENABLE_OUTPUT:
		{
			int *iarg = arg;
			int enable = (*iarg != 0);

			if (decoder->enable != enable) {
				decoder->enable = enable;

// RJ: If output should be disabled (for playing videos), we also need a open PLL.
				//     The input is set to 0 (where no input source is connected), although this
				//     is not necessary.
				//
				//     If output should be enabled, we have to reverse the above.

				if (decoder->enable) {
					writereg(client, 0x02,
						 (decoder->
						  reg[0x02] & 0xf8) |
						 decoder->input);
					writereg(client, 0x08,
						 (decoder->
						  reg[0x08] & 0xfb));
					writereg(client, 0x11,
						 (decoder->
						  reg[0x11] & 0xf3) |
						 0x0c);
				} else {
					writereg(client, 0x02,
						 (decoder->
						  reg[0x02] & 0xf8));
					writereg(client, 0x08,
						 (decoder->
						  reg[0x08] & 0xfb) |
						 0x04);
					writereg(client, 0x11,
						 (decoder->
						  reg[0x11] & 0xf3));
				}
			}
		}
		break;

	case DECODER_SET_PICTURE:
		{
			struct video_picture *pic = arg;

			if (decoder->bright != pic->brightness) {
				/* We want 0 to 255 we get 0-65535 */
				decoder->bright = pic->brightness;
				writereg(client, 0x0a,
					 decoder->bright >> 8);
			}
			if (decoder->contrast != pic->contrast) {
				/* We want 0 to 127 we get 0-65535 */
				decoder->contrast = pic->contrast;
				writereg(client, 0x0b,
					 decoder->contrast >> 9);
			}
			if (decoder->sat != pic->colour) {
				/* We want 0 to 127 we get 0-65535 */
				decoder->sat = pic->colour;
				writereg(client, 0x0c, decoder->sat >> 9);
			}
			if (decoder->hue != pic->hue) {
				/* We want -128 to 127 we get 0-65535 */
				decoder->hue = pic->hue;
				writereg(client, 0x0d,
					 (decoder->hue - 32768) >> 8);
			}
		}
		break;

	default:
		printk(KERN_WARNING
		       "saa7113: unknown saa7113_command??(%d)\n", cmd);
		return -EINVAL;
	}

	return 0;
}

static struct i2c_driver saa7113_driver = {
	.owner = THIS_MODULE,
	.name = "SAA7113",
	.id = I2C_DRIVERID_SAA7113,
	.flags = I2C_DF_NOTIFY,
	.attach_adapter = attach_adapter,
	.detach_client = detach_client,
	.command = saa7113_command,
};

static struct i2c_client client_template = {
	.name = "SAA7113",
	.id = I2C_DRIVERID_SAA7113,
	.flags = 0,
	.addr = (0x4a >> 1),
	.adapter = NULL,
	.driver = &saa7113_driver,
};


#ifdef MODULE
int init_module(void)
#else
int saa7113_init(void)
#endif
{
	int res;

	if ((res = i2c_add_driver(&saa7113_driver))) {
		printk(KERN_ERR
		       "saa7113: Driver registration failed, module not inserted.\n");
		return res;
	}

	printk(KERN_INFO "saa7113: init_module\n");
	return 0;
}

#ifdef MODULE
void cleanup_module(void)
{
	int res;

	if ((res = i2c_del_driver(&saa7113_driver))) {
		printk(KERN_ERR
		       "saa7113: Driver deregistration failed, module not removed.\n");
	}
}
#endif

LinuxTV legacy CVS <linuxtv.org/cvs>