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