[PATCH] Indycam / VINO drivers

Rewrite of the Indycam / VINO video v4l2 drivers for the SGI Indy.

Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Signed-off-by: Mikael Nousiainen <tmnousia@cc.hut.fi>
Cc: <video4linux-list@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/drivers/media/video/saa7191.c b/drivers/media/video/saa7191.c
new file mode 100644
index 0000000..454f5c1
--- /dev/null
+++ b/drivers/media/video/saa7191.c
@@ -0,0 +1,512 @@
+/*
+ *  saa7191.c - Philips SAA7191 video decoder driver
+ *
+ *  Copyright (C) 2003 Ladislav Michl <ladis@linux-mips.org>
+ *  Copyright (C) 2004,2005 Mikael Nousiainen <tmnousia@cc.hut.fi>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#include <linux/videodev.h>
+#include <linux/video_decoder.h>
+#include <linux/i2c.h>
+
+#include "saa7191.h"
+
+#define SAA7191_MODULE_VERSION "0.0.3"
+
+MODULE_DESCRIPTION("Philips SAA7191 video decoder driver");
+MODULE_VERSION(SAA7191_MODULE_VERSION);
+MODULE_AUTHOR("Mikael Nousiainen <tmnousia@cc.hut.fi>");
+MODULE_LICENSE("GPL");
+
+#define VINO_ADAPTER	(I2C_ALGO_SGI | I2C_HW_SGI_VINO)
+
+struct saa7191 {
+	struct i2c_client *client;
+
+	/* the register values are stored here as the actual
+	 * I2C-registers are write-only */
+	unsigned char reg[25];
+
+	unsigned char norm;
+	unsigned char input;
+};
+
+static struct i2c_driver i2c_driver_saa7191;
+
+static const unsigned char initseq[] = {
+	0,	/* Subaddress */
+	0x50,	/* SAA7191_REG_IDEL */
+	0x30,	/* SAA7191_REG_HSYB */
+	0x00,	/* SAA7191_REG_HSYS */
+	0xe8,	/* SAA7191_REG_HCLB */
+	0xb6,	/* SAA7191_REG_HCLS */
+	0xf4,	/* SAA7191_REG_HPHI */
+	0x01,	/* SAA7191_REG_LUMA - chrominance trap active (CVBS) */
+	0x00,	/* SAA7191_REG_HUEC */
+	0xf8,	/* SAA7191_REG_CKTQ */
+	0xf8,	/* SAA7191_REG_CKTS */
+	0x90,	/* SAA7191_REG_PLSE */
+	0x90,	/* SAA7191_REG_SESE */
+	0x00,	/* SAA7191_REG_GAIN */
+	0x0c,	/* SAA7191_REG_STDC - not SECAM, slow time constant */
+	0x78,	/* SAA7191_REG_IOCK - chrominance from CVBS, GPSW1 & 2 off */
+	0x99,	/* SAA7191_REG_CTL3 - automatic field detection */
+	0x00,	/* SAA7191_REG_CTL4 */
+	0x2c,	/* SAA7191_REG_CHCV */
+	0x00,	/* unused */
+	0x00,	/* unused */
+	0x34,	/* SAA7191_REG_HS6B */
+	0x0a,	/* SAA7191_REG_HS6S */
+	0xf4,	/* SAA7191_REG_HC6B */
+	0xce,	/* SAA7191_REG_HC6S */
+	0xf4,	/* SAA7191_REG_HP6I */
+};
+
+/* SAA7191 register handling */
+
+static unsigned char saa7191_read_reg(struct i2c_client *client,
+				      unsigned char reg)
+{
+	return ((struct saa7191 *)i2c_get_clientdata(client))->reg[reg];
+}
+
+static int saa7191_read_status(struct i2c_client *client,
+			       unsigned char *value)
+{
+	int ret;
+
+	ret = i2c_master_recv(client, value, 1);
+	if (ret < 0) {
+		printk(KERN_ERR "SAA7191: saa7191_read_status(): read failed");
+		return ret;
+	}
+
+	return 0;
+}
+
+
+static int saa7191_write_reg(struct i2c_client *client, unsigned char reg,
+			     unsigned char value)
+{
+
+	((struct saa7191 *)i2c_get_clientdata(client))->reg[reg] = value;
+	return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+/* the first byte of data must be the first subaddress number (register) */
+static int saa7191_write_block(struct i2c_client *client,
+			       unsigned char length, unsigned char *data)
+{
+	int i;
+	int ret;
+
+	struct saa7191 *decoder = (struct saa7191 *)i2c_get_clientdata(client);
+	for (i = 0; i < (length - 1); i++) {
+		decoder->reg[data[0] + i] = data[i + 1];
+	}
+
+	ret = i2c_master_send(client, data, length);
+	if (ret < 0) {
+		printk(KERN_ERR "SAA7191: saa7191_write_block(): "
+		       "write failed");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Helper functions */
+
+static int saa7191_set_input(struct i2c_client *client, int input)
+{
+	unsigned char luma = saa7191_read_reg(client, SAA7191_REG_LUMA);
+	unsigned char iock = saa7191_read_reg(client, SAA7191_REG_IOCK);
+	int err;
+
+	switch (input) {
+	case SAA7191_INPUT_COMPOSITE: /* Set Composite input */
+		iock &= ~(SAA7191_IOCK_CHRS | SAA7191_IOCK_GPSW1
+			  | SAA7191_IOCK_GPSW2);
+		/* Chrominance trap active */
+		luma &= ~SAA7191_LUMA_BYPS;
+		break;
+	case SAA7191_INPUT_SVIDEO: /* Set S-Video input */
+		iock |= SAA7191_IOCK_CHRS | SAA7191_IOCK_GPSW2;
+		/* Chrominance trap bypassed */
+		luma |= SAA7191_LUMA_BYPS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = saa7191_write_reg(client, SAA7191_REG_LUMA, luma);
+	if (err)
+		return -EIO;
+	err = saa7191_write_reg(client, SAA7191_REG_IOCK, iock);
+	if (err)
+		return -EIO;
+
+	return 0;
+}
+
+static int saa7191_set_norm(struct i2c_client *client, int norm)
+{
+	struct saa7191 *decoder = i2c_get_clientdata(client);
+	unsigned char stdc = saa7191_read_reg(client, SAA7191_REG_STDC);
+	unsigned char ctl3 = saa7191_read_reg(client, SAA7191_REG_CTL3);
+	unsigned char chcv = saa7191_read_reg(client, SAA7191_REG_CHCV);
+	int err;
+
+	switch(norm) {
+	case SAA7191_NORM_AUTO: {
+		unsigned char status;
+
+		// does status depend on current norm ?
+		if (saa7191_read_status(client, &status))
+			return -EIO;
+
+		stdc &= ~SAA7191_STDC_SECS;
+		ctl3 &= ~SAA7191_CTL3_FSEL;
+		ctl3 |= SAA7191_CTL3_AUFD;
+		chcv = (status & SAA7191_STATUS_FIDT)
+			       ? SAA7191_CHCV_NTSC : SAA7191_CHCV_PAL;
+		break;
+	}
+	case SAA7191_NORM_PAL:
+		stdc &= ~SAA7191_STDC_SECS;
+		ctl3 &= ~(SAA7191_CTL3_AUFD | SAA7191_CTL3_FSEL);
+		chcv = SAA7191_CHCV_PAL;
+		break;
+	case SAA7191_NORM_NTSC:
+		stdc &= ~SAA7191_STDC_SECS;
+		ctl3 &= ~SAA7191_CTL3_AUFD;
+		ctl3 |= SAA7191_CTL3_FSEL;
+		chcv = SAA7191_CHCV_NTSC;
+		break;
+	case SAA7191_NORM_SECAM:
+		stdc |= SAA7191_STDC_SECS;
+		ctl3 &= ~(SAA7191_CTL3_AUFD | SAA7191_CTL3_FSEL);
+		chcv = SAA7191_CHCV_PAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = saa7191_write_reg(client, SAA7191_REG_CTL3, ctl3);
+	if (err)
+		return -EIO;
+	err = saa7191_write_reg(client, SAA7191_REG_STDC, stdc);
+	if (err)
+		return -EIO;
+	err = saa7191_write_reg(client, SAA7191_REG_CHCV, chcv);
+	if (err)
+		return -EIO;
+
+	decoder->norm = norm;
+
+	return 0;
+}
+
+static int saa7191_get_controls(struct i2c_client *client,
+				struct saa7191_control *ctrl)
+{
+	unsigned char hue = saa7191_read_reg(client, SAA7191_REG_HUEC);
+	unsigned char stdc = saa7191_read_reg(client, SAA7191_REG_STDC);
+
+	if (hue < 0x80) {
+		hue += 0x80;
+	} else {
+		hue -= 0x80;
+	}
+	ctrl->hue = hue;
+
+	ctrl->vtrc = (stdc & SAA7191_STDC_VTRC)
+		? SAA7191_VALUE_ENABLED : SAA7191_VALUE_DISABLED;
+
+	return 0;
+}
+
+static int saa7191_set_controls(struct i2c_client *client,
+				struct saa7191_control *ctrl)
+{
+	int err;
+
+	if (ctrl->hue >= 0) {
+		unsigned char hue = ctrl->hue & 0xff;
+		if (hue < 0x80) {
+			hue += 0x80;
+		} else {
+			hue -= 0x80;
+		}
+		err = saa7191_write_reg(client, SAA7191_REG_HUEC, hue);
+		if (err)
+			return -EIO;
+	}
+	if (ctrl->vtrc >= 0) {
+		unsigned char stdc =
+			saa7191_read_reg(client, SAA7191_REG_STDC);
+
+		if (ctrl->vtrc) {
+			stdc |= SAA7191_STDC_VTRC;
+		} else {
+			stdc &= ~SAA7191_STDC_VTRC;
+		}
+
+		err = saa7191_write_reg(client, SAA7191_REG_STDC, stdc);
+		if (err)
+			return -EIO;
+	}
+
+	return 0;
+}
+
+/* I2C-interface */
+
+static int saa7191_attach(struct i2c_adapter *adap, int addr, int kind)
+{
+	int err = 0;
+	struct saa7191 *decoder;
+	struct i2c_client *client;
+
+	printk(KERN_INFO "Philips SAA7191 driver version %s\n",
+	       SAA7191_MODULE_VERSION);
+
+	client = kmalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return -ENOMEM;
+	decoder = kmalloc(sizeof(*decoder), GFP_KERNEL);
+	if (!decoder) {
+		err = -ENOMEM;
+		goto out_free_client;
+	}
+
+	memset(client, 0, sizeof(struct i2c_client));
+	memset(decoder, 0, sizeof(struct saa7191));
+
+	client->addr = addr;
+	client->adapter = adap;
+	client->driver = &i2c_driver_saa7191;
+	client->flags = 0;
+	strcpy(client->name, "saa7191 client");
+	i2c_set_clientdata(client, decoder);
+
+	decoder->client = client;
+
+	err = i2c_attach_client(client);
+	if (err)
+		goto out_free_decoder;
+
+	decoder->input = SAA7191_INPUT_COMPOSITE;
+	decoder->norm = SAA7191_NORM_AUTO;
+
+	err = saa7191_write_block(client, sizeof(initseq),
+				  (unsigned char *)initseq);
+	if (err) {
+		printk(KERN_ERR "SAA7191 initialization failed\n");
+		goto out_detach_client;
+	}
+
+	printk(KERN_INFO "SAA7191 initialized\n");
+
+	return 0;
+
+out_detach_client:
+	i2c_detach_client(client);
+out_free_decoder:
+	kfree(decoder);
+out_free_client:
+	kfree(client);
+	return err;
+}
+
+static int saa7191_probe(struct i2c_adapter *adap)
+{
+	/* Always connected to VINO */
+	if (adap->id == VINO_ADAPTER)
+		return saa7191_attach(adap, SAA7191_ADDR, 0);
+	/* Feel free to add probe here :-) */
+	return -ENODEV;
+}
+
+static int saa7191_detach(struct i2c_client *client)
+{
+	struct saa7191 *decoder = i2c_get_clientdata(client);
+
+	i2c_detach_client(client);
+	kfree(decoder);
+	kfree(client);
+	return 0;
+}
+
+static int saa7191_command(struct i2c_client *client, unsigned int cmd,
+			   void *arg)
+{
+	struct saa7191 *decoder = i2c_get_clientdata(client);
+
+	switch (cmd) {
+	case DECODER_GET_CAPABILITIES: {
+		struct video_decoder_capability *cap = arg;
+
+		cap->flags  = VIDEO_DECODER_PAL | VIDEO_DECODER_NTSC |
+			      VIDEO_DECODER_SECAM | VIDEO_DECODER_AUTO;
+		cap->inputs = (client->adapter->id == VINO_ADAPTER) ? 2 : 1;
+		cap->outputs = 1;
+		break;
+	}
+	case DECODER_GET_STATUS: {
+		int *iarg = arg;
+		unsigned char status;
+		int res = 0;
+
+		if (saa7191_read_status(client, &status)) {
+			return -EIO;
+		}
+		if ((status & SAA7191_STATUS_HLCK) == 0)
+			res |= DECODER_STATUS_GOOD;
+		if (status & SAA7191_STATUS_CODE)
+			res |= DECODER_STATUS_COLOR;
+		switch (decoder->norm) {
+		case SAA7191_NORM_NTSC:
+			res |= DECODER_STATUS_NTSC;
+			break;
+		case SAA7191_NORM_PAL:
+			res |= DECODER_STATUS_PAL;
+			break;
+		case SAA7191_NORM_SECAM:
+			res |= DECODER_STATUS_SECAM;
+			break;
+		case SAA7191_NORM_AUTO:
+		default:
+			if (status & SAA7191_STATUS_FIDT)
+				res |= DECODER_STATUS_NTSC;
+			else
+				res |= DECODER_STATUS_PAL;
+			break;
+		}
+		*iarg = res;
+		break;
+	}
+	case DECODER_SET_NORM: {
+		int *iarg = arg;
+
+		switch (*iarg) {
+		case VIDEO_MODE_AUTO:
+			return saa7191_set_norm(client, SAA7191_NORM_AUTO);
+		case VIDEO_MODE_PAL:
+			return saa7191_set_norm(client, SAA7191_NORM_PAL);
+		case VIDEO_MODE_NTSC:
+			return saa7191_set_norm(client, SAA7191_NORM_NTSC);
+		case VIDEO_MODE_SECAM:
+			return saa7191_set_norm(client, SAA7191_NORM_SECAM);
+		default:
+			return -EINVAL;
+		}
+		break;
+	}
+	case DECODER_SET_INPUT:	{
+		int *iarg = arg;
+
+		switch (client->adapter->id) {
+		case VINO_ADAPTER:
+			return saa7191_set_input(client, *iarg);
+		default:
+			if (*iarg != 0)
+				return -EINVAL;
+		}
+		break;
+	}
+	case DECODER_SET_OUTPUT: {
+		int *iarg = arg;
+
+		/* not much choice of outputs */
+		if (*iarg != 0)
+			return -EINVAL;
+		break;
+	}
+	case DECODER_ENABLE_OUTPUT: {
+		/* Always enabled */
+		break;
+	}
+	case DECODER_SET_PICTURE: {
+		struct video_picture *pic = arg;
+		unsigned val;
+		int err;
+
+		val = (pic->hue >> 8) - 0x80;
+		err = saa7191_write_reg(client, SAA7191_REG_HUEC, val);
+		if (err)
+			return -EIO;
+		break;
+	}
+	case DECODER_SAA7191_GET_STATUS: {
+		struct saa7191_status *status = arg;
+		unsigned char status_reg;
+
+		if (saa7191_read_status(client, &status_reg))
+			return -EIO;
+		status->signal = ((status_reg & SAA7191_STATUS_HLCK) == 0)
+			? SAA7191_VALUE_ENABLED : SAA7191_VALUE_DISABLED;
+		status->ntsc = (status_reg & SAA7191_STATUS_FIDT)
+			? SAA7191_VALUE_ENABLED : SAA7191_VALUE_DISABLED;
+		status->color = (status_reg & SAA7191_STATUS_CODE)
+			? SAA7191_VALUE_ENABLED : SAA7191_VALUE_DISABLED;
+
+		status->input = decoder->input;
+		status->norm = decoder->norm;
+	}
+	case DECODER_SAA7191_SET_NORM: {
+		int *norm = arg;
+		return saa7191_set_norm(client, *norm);
+	}
+	case DECODER_SAA7191_GET_CONTROLS: {
+		struct saa7191_control *ctrl = arg;
+		return saa7191_get_controls(client, ctrl);
+	}
+	case DECODER_SAA7191_SET_CONTROLS: {
+		struct saa7191_control *ctrl = arg;
+		return saa7191_set_controls(client, ctrl);
+	}
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver i2c_driver_saa7191 = {
+	.owner		= THIS_MODULE,
+	.name 		= "saa7191",
+	.id 		= I2C_DRIVERID_SAA7191,
+	.flags 		= I2C_DF_NOTIFY,
+	.attach_adapter = saa7191_probe,
+	.detach_client 	= saa7191_detach,
+	.command 	= saa7191_command
+};
+
+static int saa7191_init(void)
+{
+	return i2c_add_driver(&i2c_driver_saa7191);
+}
+
+static void saa7191_exit(void)
+{
+	i2c_del_driver(&i2c_driver_saa7191);
+}
+
+module_init(saa7191_init);
+module_exit(saa7191_exit);