blob: 454f5c1199b42b202ad576cba24f1ce0c7ff7ed0 [file] [log] [blame]
/*
* 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);