// SPDX-License-Identifier: GPL-2.0-or-later
/*
* saa717x - Philips SAA717xHL video decoder driver
*
* Based on the saa7115 driver
*
* Changes by Ohta Kyuma <alpha292@bremen.or.jp>
* - Apply to SAA717x,NEC uPD64031,uPD64083. (1/31/2004)
*
* Changes by T.Adachi (tadachi@tadachi-net.com)
* - support audio, video scaler etc, and checked the initialize sequence.
*
* Cleaned up by Hans Verkuil <hverkuil@xs4all.nl>
*
* Note: this is a reversed engineered driver based on captures from
* the I2C bus under Windows. This chip is very similar to the saa7134,
* though. Unfortunately, this driver is currently only working for NTSC.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/videodev2.h>
#include <linux/i2c.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
MODULE_DESCRIPTION("Philips SAA717x audio/video decoder driver" );
MODULE_AUTHOR("K. Ohta, T. Adachi, Hans Verkuil" );
MODULE_LICENSE("GPL" );
static int debug;
module_param(debug, int , 0644 );
MODULE_PARM_DESC(debug, "Debug level (0-1)" );
/*
* Generic i2c probe
* concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
*/
struct saa717x_state {
struct v4l2_subdev sd;
struct v4l2_ctrl_handler hdl;
v4l2_std_id std;
int input;
int enable;
int radio;
int playback;
int audio;
int tuner_audio_mode;
int audio_main_mute;
int audio_main_vol_r;
int audio_main_vol_l;
u16 audio_main_bass;
u16 audio_main_treble;
u16 audio_main_volume;
u16 audio_main_balance;
int audio_input;
};
static inline struct saa717x_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct saa717x_state, sd);
}
static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
{
return &container_of(ctrl->handler, struct saa717x_state, hdl)->sd;
}
/* ----------------------------------------------------------------------- */
/* for audio mode */
#define TUNER_AUDIO_MONO 0 /* LL */
#define TUNER_AUDIO_STEREO 1 /* LR */
#define TUNER_AUDIO_LANG1 2 /* LL */
#define TUNER_AUDIO_LANG2 3 /* RR */
#define SAA717X_NTSC_WIDTH (704 )
#define SAA717X_NTSC_HEIGHT (480 )
/* ----------------------------------------------------------------------- */
static int saa717x_write(struct v4l2_subdev *sd, u32 reg, u32 value)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct i2c_adapter *adap = client->adapter;
int fw_addr = reg == 0 x454 || (reg >= 0 x464 && reg <= 0 x478) || reg == 0 x480 || reg == 0 x488;
unsigned char mm1[6 ];
struct i2c_msg msg;
msg.flags = 0 ;
msg.addr = client->addr;
mm1[0 ] = (reg >> 8 ) & 0 xff;
mm1[1 ] = reg & 0 xff;
if (fw_addr) {
mm1[4 ] = (value >> 16 ) & 0 xff;
mm1[3 ] = (value >> 8 ) & 0 xff;
mm1[2 ] = value & 0 xff;
} else {
mm1[2 ] = value & 0 xff;
}
msg.len = fw_addr ? 5 : 3 ; /* Long Registers have *only* three bytes! */
msg.buf = mm1;
v4l2_dbg(2 , debug, sd, "wrote: reg 0x%03x=%08x\n" , reg, value);
return i2c_transfer(adap, &msg, 1 ) == 1 ;
}
static void saa717x_write_regs(struct v4l2_subdev *sd, u32 *data)
{
while (data[0 ] || data[1 ]) {
saa717x_write(sd, data[0 ], data[1 ]);
data += 2 ;
}
}
static u32 saa717x_read(struct v4l2_subdev *sd, u32 reg)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct i2c_adapter *adap = client->adapter;
int fw_addr = (reg >= 0 x404 && reg <= 0 x4b8) || reg == 0 x528;
unsigned char mm1[2 ];
unsigned char mm2[4 ] = { 0 , 0 , 0 , 0 };
struct i2c_msg msgs[2 ];
u32 value;
msgs[0 ].flags = 0 ;
msgs[1 ].flags = I2C_M_RD;
msgs[0 ].addr = msgs[1 ].addr = client->addr;
mm1[0 ] = (reg >> 8 ) & 0 xff;
mm1[1 ] = reg & 0 xff;
msgs[0 ].len = 2 ;
msgs[0 ].buf = mm1;
msgs[1 ].len = fw_addr ? 3 : 1 ; /* Multibyte Registers contains *only* 3 bytes */
msgs[1 ].buf = mm2;
i2c_transfer(adap, msgs, 2 );
if (fw_addr)
value = (mm2[2 ] << 16 ) | (mm2[1 ] << 8 ) | mm2[0 ];
else
value = mm2[0 ];
v4l2_dbg(2 , debug, sd, "read: reg 0x%03x=0x%08x\n" , reg, value);
return value;
}
/* ----------------------------------------------------------------------- */
static u32 reg_init_initialize[] =
{
/* from linux driver */
0 x101, 0 x008, /* Increment delay */
0 x103, 0 x000, /* Analog input control 2 */
0 x104, 0 x090, /* Analog input control 3 */
0 x105, 0 x090, /* Analog input control 4 */
0 x106, 0 x0eb, /* Horizontal sync start */
0 x107, 0 x0e0, /* Horizontal sync stop */
0 x109, 0 x055, /* Luminance control */
0 x10f, 0 x02a, /* Chroma gain control */
0 x110, 0 x000, /* Chroma control 2 */
0 x114, 0 x045, /* analog/ADC */
0 x118, 0 x040, /* RAW data gain */
0 x119, 0 x080, /* RAW data offset */
0 x044, 0 x000, /* VBI horizontal input window start (L) TASK A */
0 x045, 0 x000, /* VBI horizontal input window start (H) TASK A */
0 x046, 0 x0cf, /* VBI horizontal input window stop (L) TASK A */
0 x047, 0 x002, /* VBI horizontal input window stop (H) TASK A */
0 x049, 0 x000, /* VBI vertical input window start (H) TASK A */
0 x04c, 0 x0d0, /* VBI horizontal output length (L) TASK A */
0 x04d, 0 x002, /* VBI horizontal output length (H) TASK A */
0 x064, 0 x080, /* Lumina brightness TASK A */
0 x065, 0 x040, /* Luminance contrast TASK A */
0 x066, 0 x040, /* Chroma saturation TASK A */
/* 067H: Reserved */
0 x068, 0 x000, /* VBI horizontal scaling increment (L) TASK A */
0 x069, 0 x004, /* VBI horizontal scaling increment (H) TASK A */
0 x06a, 0 x000, /* VBI phase offset TASK A */
0 x06e, 0 x000, /* Horizontal phase offset Luma TASK A */
0 x06f, 0 x000, /* Horizontal phase offset Chroma TASK A */
0 x072, 0 x000, /* Vertical filter mode TASK A */
0 x084, 0 x000, /* VBI horizontal input window start (L) TAKS B */
0 x085, 0 x000, /* VBI horizontal input window start (H) TAKS B */
0 x086, 0 x0cf, /* VBI horizontal input window stop (L) TAKS B */
0 x087, 0 x002, /* VBI horizontal input window stop (H) TAKS B */
0 x089, 0 x000, /* VBI vertical input window start (H) TAKS B */
0 x08c, 0 x0d0, /* VBI horizontal output length (L) TASK B */
0 x08d, 0 x002, /* VBI horizontal output length (H) TASK B */
0 x0a4, 0 x080, /* Lumina brightness TASK B */
0 x0a5, 0 x040, /* Luminance contrast TASK B */
0 x0a6, 0 x040, /* Chroma saturation TASK B */
/* 0A7H reserved */
0 x0a8, 0 x000, /* VBI horizontal scaling increment (L) TASK B */
0 x0a9, 0 x004, /* VBI horizontal scaling increment (H) TASK B */
0 x0aa, 0 x000, /* VBI phase offset TASK B */
0 x0ae, 0 x000, /* Horizontal phase offset Luma TASK B */
0 x0af, 0 x000, /*Horizontal phase offset Chroma TASK B */
0 x0b2, 0 x000, /* Vertical filter mode TASK B */
0 x00c, 0 x000, /* Start point GREEN path */
0 x00d, 0 x000, /* Start point BLUE path */
0 x00e, 0 x000, /* Start point RED path */
0 x010, 0 x010, /* GREEN path gamma curve --- */
0 x011, 0 x020,
0 x012, 0 x030,
0 x013, 0 x040,
0 x014, 0 x050,
0 x015, 0 x060,
0 x016, 0 x070,
0 x017, 0 x080,
0 x018, 0 x090,
0 x019, 0 x0a0,
0 x01a, 0 x0b0,
0 x01b, 0 x0c0,
0 x01c, 0 x0d0,
0 x01d, 0 x0e0,
0 x01e, 0 x0f0,
0 x01f, 0 x0ff, /* --- GREEN path gamma curve */
0 x020, 0 x010, /* BLUE path gamma curve --- */
0 x021, 0 x020,
0 x022, 0 x030,
0 x023, 0 x040,
0 x024, 0 x050,
0 x025, 0 x060,
0 x026, 0 x070,
0 x027, 0 x080,
0 x028, 0 x090,
0 x029, 0 x0a0,
0 x02a, 0 x0b0,
0 x02b, 0 x0c0,
0 x02c, 0 x0d0,
0 x02d, 0 x0e0,
0 x02e, 0 x0f0,
0 x02f, 0 x0ff, /* --- BLUE path gamma curve */
0 x030, 0 x010, /* RED path gamma curve --- */
0 x031, 0 x020,
0 x032, 0 x030,
0 x033, 0 x040,
0 x034, 0 x050,
0 x035, 0 x060,
0 x036, 0 x070,
0 x037, 0 x080,
0 x038, 0 x090,
0 x039, 0 x0a0,
0 x03a, 0 x0b0,
0 x03b, 0 x0c0,
0 x03c, 0 x0d0,
0 x03d, 0 x0e0,
0 x03e, 0 x0f0,
0 x03f, 0 x0ff, /* --- RED path gamma curve */
0 x109, 0 x085, /* Luminance control */
/**** from app start ****/
0 x584, 0 x000, /* AGC gain control */
0 x585, 0 x000, /* Program count */
0 x586, 0 x003, /* Status reset */
0 x588, 0 x0ff, /* Number of audio samples (L) */
0 x589, 0 x00f, /* Number of audio samples (M) */
0 x58a, 0 x000, /* Number of audio samples (H) */
0 x58b, 0 x000, /* Audio select */
0 x58c, 0 x010, /* Audio channel assign1 */
0 x58d, 0 x032, /* Audio channel assign2 */
0 x58e, 0 x054, /* Audio channel assign3 */
0 x58f, 0 x023, /* Audio format */
0 x590, 0 x000, /* SIF control */
0 x595, 0 x000, /* ?? */
0 x596, 0 x000, /* ?? */
0 x597, 0 x000, /* ?? */
0 x464, 0 x00, /* Digital input crossbar1 */
0 x46c, 0 xbbbb10, /* Digital output selection1-3 */
0 x470, 0 x101010, /* Digital output selection4-6 */
0 x478, 0 x00, /* Sound feature control */
0 x474, 0 x18, /* Softmute control */
0 x454, 0 x0425b9, /* Sound Easy programming(reset) */
0 x454, 0 x042539, /* Sound Easy programming(reset) */
/**** common setting( of DVD play, including scaler commands) ****/
0 x042, 0 x003, /* Data path configuration for VBI (TASK A) */
0 x082, 0 x003, /* Data path configuration for VBI (TASK B) */
0 x108, 0 x0f8, /* Sync control */
0 x2a9, 0 x0fd, /* ??? */
0 x102, 0 x089, /* select video input "mode 9" */
0 x111, 0 x000, /* Mode/delay control */
0 x10e, 0 x00a, /* Chroma control 1 */
0 x594, 0 x002, /* SIF, analog I/O select */
0 x454, 0 x0425b9, /* Sound */
0 x454, 0 x042539,
0 x111, 0 x000,
0 x10e, 0 x00a,
0 x464, 0 x000,
0 x300, 0 x000,
0 x301, 0 x006,
0 x302, 0 x000,
0 x303, 0 x006,
0 x308, 0 x040,
0 x309, 0 x000,
0 x30a, 0 x000,
0 x30b, 0 x000,
0 x000, 0 x002,
0 x001, 0 x000,
0 x002, 0 x000,
0 x003, 0 x000,
0 x004, 0 x033,
0 x040, 0 x01d,
0 x041, 0 x001,
0 x042, 0 x004,
0 x043, 0 x000,
0 x080, 0 x01e,
0 x081, 0 x001,
0 x082, 0 x004,
0 x083, 0 x000,
0 x190, 0 x018,
0 x115, 0 x000,
0 x116, 0 x012,
0 x117, 0 x018,
0 x04a, 0 x011,
0 x08a, 0 x011,
0 x04b, 0 x000,
0 x08b, 0 x000,
0 x048, 0 x000,
0 x088, 0 x000,
0 x04e, 0 x012,
0 x08e, 0 x012,
0 x058, 0 x012,
0 x098, 0 x012,
0 x059, 0 x000,
0 x099, 0 x000,
0 x05a, 0 x003,
0 x09a, 0 x003,
0 x05b, 0 x001,
0 x09b, 0 x001,
0 x054, 0 x008,
0 x094, 0 x008,
0 x055, 0 x000,
0 x095, 0 x000,
0 x056, 0 x0c7,
0 x096, 0 x0c7,
0 x057, 0 x002,
0 x097, 0 x002,
0 x0ff, 0 x0ff,
0 x060, 0 x001,
0 x0a0, 0 x001,
0 x061, 0 x000,
0 x0a1, 0 x000,
0 x062, 0 x000,
0 x0a2, 0 x000,
0 x063, 0 x000,
0 x0a3, 0 x000,
0 x070, 0 x000,
0 x0b0, 0 x000,
0 x071, 0 x004,
0 x0b1, 0 x004,
0 x06c, 0 x0e9,
0 x0ac, 0 x0e9,
0 x06d, 0 x003,
0 x0ad, 0 x003,
0 x05c, 0 x0d0,
0 x09c, 0 x0d0,
0 x05d, 0 x002,
0 x09d, 0 x002,
0 x05e, 0 x0f2,
0 x09e, 0 x0f2,
0 x05f, 0 x000,
0 x09f, 0 x000,
0 x074, 0 x000,
0 x0b4, 0 x000,
0 x075, 0 x000,
0 x0b5, 0 x000,
0 x076, 0 x000,
0 x0b6, 0 x000,
0 x077, 0 x000,
0 x0b7, 0 x000,
0 x195, 0 x008,
0 x0ff, 0 x0ff,
0 x108, 0 x0f8,
0 x111, 0 x000,
0 x10e, 0 x00a,
0 x2a9, 0 x0fd,
0 x464, 0 x001,
0 x454, 0 x042135,
0 x598, 0 x0e7,
0 x599, 0 x07d,
0 x59a, 0 x018,
0 x59c, 0 x066,
0 x59d, 0 x090,
0 x59e, 0 x001,
0 x584, 0 x000,
0 x585, 0 x000,
0 x586, 0 x003,
0 x588, 0 x0ff,
0 x589, 0 x00f,
0 x58a, 0 x000,
0 x58b, 0 x000,
0 x58c, 0 x010,
0 x58d, 0 x032,
0 x58e, 0 x054,
0 x58f, 0 x023,
0 x590, 0 x000,
0 x595, 0 x000,
0 x596, 0 x000,
0 x597, 0 x000,
0 x464, 0 x000,
0 x46c, 0 xbbbb10,
0 x470, 0 x101010,
0 x478, 0 x000,
0 x474, 0 x018,
0 x454, 0 x042135,
0 x598, 0 x0e7,
0 x599, 0 x07d,
0 x59a, 0 x018,
0 x59c, 0 x066,
0 x59d, 0 x090,
0 x59e, 0 x001,
0 x584, 0 x000,
0 x585, 0 x000,
0 x586, 0 x003,
0 x588, 0 x0ff,
0 x589, 0 x00f,
0 x58a, 0 x000,
0 x58b, 0 x000,
0 x58c, 0 x010,
0 x58d, 0 x032,
0 x58e, 0 x054,
0 x58f, 0 x023,
0 x590, 0 x000,
0 x595, 0 x000,
0 x596, 0 x000,
0 x597, 0 x000,
0 x464, 0 x000,
0 x46c, 0 xbbbb10,
0 x470, 0 x101010,
0 x478, 0 x000,
0 x474, 0 x018,
0 x454, 0 x042135,
0 x598, 0 x0e7,
0 x599, 0 x07d,
0 x59a, 0 x018,
0 x59c, 0 x066,
0 x59d, 0 x090,
0 x59e, 0 x001,
0 x584, 0 x000,
0 x585, 0 x000,
0 x586, 0 x003,
0 x588, 0 x0ff,
0 x589, 0 x00f,
0 x58a, 0 x000,
0 x58b, 0 x000,
0 x58c, 0 x010,
0 x58d, 0 x032,
0 x58e, 0 x054,
0 x58f, 0 x023,
0 x590, 0 x000,
0 x595, 0 x000,
0 x596, 0 x000,
0 x597, 0 x000,
0 x464, 0 x000,
0 x46c, 0 xbbbb10,
0 x470, 0 x101010,
0 x478, 0 x000,
0 x474, 0 x018,
0 x454, 0 x042135,
0 x193, 0 x000,
0 x300, 0 x000,
0 x301, 0 x006,
0 x302, 0 x000,
0 x303, 0 x006,
0 x308, 0 x040,
0 x309, 0 x000,
0 x30a, 0 x000,
0 x30b, 0 x000,
0 x000, 0 x002,
0 x001, 0 x000,
0 x002, 0 x000,
0 x003, 0 x000,
0 x004, 0 x033,
0 x040, 0 x01d,
0 x041, 0 x001,
0 x042, 0 x004,
0 x043, 0 x000,
0 x080, 0 x01e,
0 x081, 0 x001,
0 x082, 0 x004,
0 x083, 0 x000,
0 x190, 0 x018,
0 x115, 0 x000,
0 x116, 0 x012,
0 x117, 0 x018,
0 x04a, 0 x011,
0 x08a, 0 x011,
0 x04b, 0 x000,
0 x08b, 0 x000,
0 x048, 0 x000,
0 x088, 0 x000,
0 x04e, 0 x012,
0 x08e, 0 x012,
0 x058, 0 x012,
0 x098, 0 x012,
0 x059, 0 x000,
0 x099, 0 x000,
0 x05a, 0 x003,
0 x09a, 0 x003,
0 x05b, 0 x001,
0 x09b, 0 x001,
0 x054, 0 x008,
0 x094, 0 x008,
0 x055, 0 x000,
0 x095, 0 x000,
0 x056, 0 x0c7,
0 x096, 0 x0c7,
0 x057, 0 x002,
0 x097, 0 x002,
0 x060, 0 x001,
0 x0a0, 0 x001,
0 x061, 0 x000,
0 x0a1, 0 x000,
0 x062, 0 x000,
0 x0a2, 0 x000,
0 x063, 0 x000,
0 x0a3, 0 x000,
0 x070, 0 x000,
0 x0b0, 0 x000,
0 x071, 0 x004,
0 x0b1, 0 x004,
0 x06c, 0 x0e9,
0 x0ac, 0 x0e9,
0 x06d, 0 x003,
0 x0ad, 0 x003,
0 x05c, 0 x0d0,
0 x09c, 0 x0d0,
0 x05d, 0 x002,
0 x09d, 0 x002,
0 x05e, 0 x0f2,
0 x09e, 0 x0f2,
0 x05f, 0 x000,
0 x09f, 0 x000,
0 x074, 0 x000,
0 x0b4, 0 x000,
0 x075, 0 x000,
0 x0b5, 0 x000,
0 x076, 0 x000,
0 x0b6, 0 x000,
0 x077, 0 x000,
0 x0b7, 0 x000,
0 x195, 0 x008,
0 x598, 0 x0e7,
0 x599, 0 x07d,
0 x59a, 0 x018,
0 x59c, 0 x066,
0 x59d, 0 x090,
0 x59e, 0 x001,
0 x584, 0 x000,
0 x585, 0 x000,
0 x586, 0 x003,
0 x588, 0 x0ff,
0 x589, 0 x00f,
0 x58a, 0 x000,
0 x58b, 0 x000,
0 x58c, 0 x010,
0 x58d, 0 x032,
0 x58e, 0 x054,
0 x58f, 0 x023,
0 x590, 0 x000,
0 x595, 0 x000,
0 x596, 0 x000,
0 x597, 0 x000,
0 x464, 0 x000,
0 x46c, 0 xbbbb10,
0 x470, 0 x101010,
0 x478, 0 x000,
0 x474, 0 x018,
0 x454, 0 x042135,
0 x193, 0 x0a6,
0 x108, 0 x0f8,
0 x042, 0 x003,
0 x082, 0 x003,
0 x454, 0 x0425b9,
0 x454, 0 x042539,
0 x193, 0 x000,
0 x193, 0 x0a6,
0 x464, 0 x000,
0 , 0
};
/* Tuner */
static u32 reg_init_tuner_input[] = {
0 x108, 0 x0f8, /* Sync control */
0 x111, 0 x000, /* Mode/delay control */
0 x10e, 0 x00a, /* Chroma control 1 */
0 , 0
};
/* Composite */
static u32 reg_init_composite_input[] = {
0 x108, 0 x0e8, /* Sync control */
0 x111, 0 x000, /* Mode/delay control */
0 x10e, 0 x04a, /* Chroma control 1 */
0 , 0
};
/* S-Video */
static u32 reg_init_svideo_input[] = {
0 x108, 0 x0e8, /* Sync control */
0 x111, 0 x000, /* Mode/delay control */
0 x10e, 0 x04a, /* Chroma control 1 */
0 , 0
};
static u32 reg_set_audio_template[4 ][2 ] =
{
{ /* for MONO
tadachi 6/29 DMA audio output select?
Register 0x46c
7-4: DMA2, 3-0: DMA1 ch. DMA4, DMA3 DMA2, DMA1
0: MAIN left, 1: MAIN right
2: AUX1 left, 3: AUX1 right
4: AUX2 left, 5: AUX2 right
6: DPL left, 7: DPL right
8: DPL center, 9: DPL surround
A: monitor output, B: digital sense */
0 xbbbb00,
/* tadachi 6/29 DAC and I2S output select?
Register 0x470
7-4:DAC right ch. 3-0:DAC left ch.
I2S1 right,left I2S2 right,left */
0 x00,
},
{ /* for STEREO */
0 xbbbb10, 0 x101010,
},
{ /* for LANG1 */
0 xbbbb00, 0 x00,
},
{ /* for LANG2/SAP */
0 xbbbb11, 0 x111111,
}
};
/* Get detected audio flags (from saa7134 driver) */
static void get_inf_dev_status(struct v4l2_subdev *sd,
int *dual_flag, int *stereo_flag)
{
u32 reg_data3;
static char *stdres[0 x20] = {
[0 x00] = "no standard detected" ,
[0 x01] = "B/G (in progress)" ,
[0 x02] = "D/K (in progress)" ,
[0 x03] = "M (in progress)" ,
[0 x04] = "B/G A2" ,
[0 x05] = "B/G NICAM" ,
[0 x06] = "D/K A2 (1)" ,
[0 x07] = "D/K A2 (2)" ,
[0 x08] = "D/K A2 (3)" ,
[0 x09] = "D/K NICAM" ,
[0 x0a] = "L NICAM" ,
[0 x0b] = "I NICAM" ,
[0 x0c] = "M Korea" ,
[0 x0d] = "M BTSC " ,
[0 x0e] = "M EIAJ" ,
[0 x0f] = "FM radio / IF 10.7 / 50 deemp" ,
[0 x10] = "FM radio / IF 10.7 / 75 deemp" ,
[0 x11] = "FM radio / IF sel / 50 deemp" ,
[0 x12] = "FM radio / IF sel / 75 deemp" ,
[0 x13 ... 0 x1e] = "unknown" ,
[0 x1f] = "??? [in progress]" ,
};
*dual_flag = *stereo_flag = 0 ;
/* (demdec status: 0x528) */
/* read current status */
reg_data3 = saa717x_read(sd, 0 x0528);
v4l2_dbg(1 , debug, sd, "tvaudio thread status: 0x%x [%s%s%s]\n" ,
reg_data3, stdres[reg_data3 & 0 x1f],
(reg_data3 & 0 x000020) ? ",stereo" : "" ,
(reg_data3 & 0 x000040) ? ",dual" : "" );
v4l2_dbg(1 , debug, sd, "detailed status: "
"%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s#%s\n" ,
(reg_data3 & 0 x000080) ? " A2/EIAJ pilot tone " : "" ,
(reg_data3 & 0 x000100) ? " A2/EIAJ dual " : "" ,
(reg_data3 & 0 x000200) ? " A2/EIAJ stereo " : "" ,
(reg_data3 & 0 x000400) ? " A2/EIAJ noise mute " : "" ,
(reg_data3 & 0 x000800) ? " BTSC/FM radio pilot " : "" ,
(reg_data3 & 0 x001000) ? " SAP carrier " : "" ,
(reg_data3 & 0 x002000) ? " BTSC stereo noise mute " : "" ,
(reg_data3 & 0 x004000) ? " SAP noise mute " : "" ,
(reg_data3 & 0 x008000) ? " VDSP " : "" ,
(reg_data3 & 0 x010000) ? " NICST " : "" ,
(reg_data3 & 0 x020000) ? " NICDU " : "" ,
(reg_data3 & 0 x040000) ? " NICAM muted " : "" ,
(reg_data3 & 0 x080000) ? " NICAM reserve sound " : "" ,
(reg_data3 & 0 x100000) ? " init done " : "" );
if (reg_data3 & 0 x000220) {
v4l2_dbg(1 , debug, sd, "ST!!!\n" );
*stereo_flag = 1 ;
}
if (reg_data3 & 0 x000140) {
v4l2_dbg(1 , debug, sd, "DUAL!!!\n" );
*dual_flag = 1 ;
}
}
/* regs write to set audio mode */
static void set_audio_mode(struct v4l2_subdev *sd, int audio_mode)
{
v4l2_dbg(1 , debug, sd, "writing registers to set audio mode by set %d\n" ,
audio_mode);
saa717x_write(sd, 0 x46c, reg_set_audio_template[audio_mode][0 ]);
saa717x_write(sd, 0 x470, reg_set_audio_template[audio_mode][1 ]);
}
/* write regs to set audio volume, bass and treble */
static int set_audio_regs(struct v4l2_subdev *sd,
struct saa717x_state *decoder)
{
u8 mute = 0 xac; /* -84 dB */
u32 val;
unsigned int work_l, work_r;
/* set SIF analog I/O select */
saa717x_write(sd, 0 x0594, decoder->audio_input);
v4l2_dbg(1 , debug, sd, "set audio input %d\n" ,
decoder->audio_input);
/* normalize ( 65535 to 0 -> 24 to -40 (not -84)) */
work_l = (min(65536 - decoder->audio_main_balance, 32768 ) * decoder->audio_main_volume) / 32768 ;
work_r = (min(decoder->audio_main_balance, (u16)32768 ) * decoder->audio_main_volume) / 32768 ;
decoder->audio_main_vol_l = (long )work_l * (24 - (-40 )) / 65535 - 40 ;
decoder->audio_main_vol_r = (long )work_r * (24 - (-40 )) / 65535 - 40 ;
/* set main volume */
/* main volume L[7-0],R[7-0],0x00 24=24dB,-83dB, -84(mute) */
/* def:0dB->6dB(MPG600GR) */
/* if mute is on, set mute */
if (decoder->audio_main_mute) {
val = mute | (mute << 8 );
} else {
val = (u8)decoder->audio_main_vol_l |
((u8)decoder->audio_main_vol_r << 8 );
}
saa717x_write(sd, 0 x480, val);
/* set bass and treble */
val = decoder->audio_main_bass & 0 x1f;
val |= (decoder->audio_main_treble & 0 x1f) << 5 ;
saa717x_write(sd, 0 x488, val);
return 0 ;
}
/********** scaling staff ***********/
static void set_h_prescale(struct v4l2_subdev *sd,
int task, int prescale)
{
static const struct {
int xpsc;
int xacl;
int xc2_1;
int xdcg;
int vpfy;
} vals[] = {
/* XPSC XACL XC2_1 XDCG VPFY */
{ 1 , 0 , 0 , 0 , 0 },
{ 2 , 2 , 1 , 2 , 2 },
{ 3 , 4 , 1 , 3 , 2 },
{ 4 , 8 , 1 , 4 , 2 },
{ 5 , 8 , 1 , 4 , 2 },
{ 6 , 8 , 1 , 4 , 3 },
{ 7 , 8 , 1 , 4 , 3 },
{ 8 , 15 , 0 , 4 , 3 },
{ 9 , 15 , 0 , 4 , 3 },
{ 10 , 16 , 1 , 5 , 3 },
};
static const int count = ARRAY_SIZE(vals);
int i, task_shift;
task_shift = task * 0 x40;
for (i = 0 ; i < count; i++)
if (vals[i].xpsc == prescale)
break ;
if (i == count)
return ;
/* horizontal prescaling */
saa717x_write(sd, 0 x60 + task_shift, vals[i].xpsc);
/* accumulation length */
saa717x_write(sd, 0 x61 + task_shift, vals[i].xacl);
/* level control */
saa717x_write(sd, 0 x62 + task_shift,
(vals[i].xc2_1 << 3 ) | vals[i].xdcg);
/*FIR prefilter control */
saa717x_write(sd, 0 x63 + task_shift,
(vals[i].vpfy << 2 ) | vals[i].vpfy);
}
/********** scaling staff ***********/
static void set_v_scale(struct v4l2_subdev *sd, int task, int yscale)
{
int task_shift;
task_shift = task * 0 x40;
/* Vertical scaling ratio (LOW) */
saa717x_write(sd, 0 x70 + task_shift, yscale & 0 xff);
/* Vertical scaling ratio (HI) */
saa717x_write(sd, 0 x71 + task_shift, yscale >> 8 );
}
static int saa717x_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct v4l2_subdev *sd = to_sd(ctrl);
struct saa717x_state *state = to_state(sd);
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
saa717x_write(sd, 0 x10a, ctrl->val);
return 0 ;
case V4L2_CID_CONTRAST:
saa717x_write(sd, 0 x10b, ctrl->val);
return 0 ;
case V4L2_CID_SATURATION:
saa717x_write(sd, 0 x10c, ctrl->val);
return 0 ;
case V4L2_CID_HUE:
saa717x_write(sd, 0 x10d, ctrl->val);
return 0 ;
case V4L2_CID_AUDIO_MUTE:
state->audio_main_mute = ctrl->val;
break ;
case V4L2_CID_AUDIO_VOLUME:
state->audio_main_volume = ctrl->val;
break ;
case V4L2_CID_AUDIO_BALANCE:
state->audio_main_balance = ctrl->val;
break ;
case V4L2_CID_AUDIO_TREBLE:
state->audio_main_treble = ctrl->val;
break ;
case V4L2_CID_AUDIO_BASS:
state->audio_main_bass = ctrl->val;
break ;
default :
return 0 ;
}
set_audio_regs(sd, state);
return 0 ;
}
static int saa717x_s_video_routing(struct v4l2_subdev *sd,
u32 input, u32 output, u32 config)
{
struct saa717x_state *decoder = to_state(sd);
int is_tuner = input & 0 x80; /* tuner input flag */
input &= 0 x7f;
v4l2_dbg(1 , debug, sd, "decoder set input (%d)\n" , input);
/* inputs from 0-9 are available*/
/* saa717x have mode0-mode9 but mode5 is reserved. */
if (input > 9 || input == 5 )
return -EINVAL;
if (decoder->input != input) {
int input_line = input;
decoder->input = input_line;
v4l2_dbg(1 , debug, sd, "now setting %s input %d\n" ,
input_line >= 6 ? "S-Video" : "Composite" ,
input_line);
/* select mode */
saa717x_write(sd, 0 x102,
(saa717x_read(sd, 0 x102) & 0 xf0) |
input_line);
/* bypass chrominance trap for modes 6..9 */
saa717x_write(sd, 0 x109,
(saa717x_read(sd, 0 x109) & 0 x7f) |
(input_line < 6 ? 0 x0 : 0 x80));
/* change audio_mode */
if (is_tuner) {
/* tuner */
set_audio_mode(sd, decoder->tuner_audio_mode);
} else {
/* Force to STEREO mode if Composite or
* S-Video were chosen */
set_audio_mode(sd, TUNER_AUDIO_STEREO);
}
/* change initialize procedure (Composite/S-Video) */
if (is_tuner)
saa717x_write_regs(sd, reg_init_tuner_input);
else if (input_line >= 6 )
saa717x_write_regs(sd, reg_init_svideo_input);
else
saa717x_write_regs(sd, reg_init_composite_input);
}
return 0 ;
}
#ifdef CONFIG_VIDEO_ADV_DEBUG
static int saa717x_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
{
reg->val = saa717x_read(sd, reg->reg);
reg->size = 1 ;
return 0 ;
}
static int saa717x_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg)
{
u16 addr = reg->reg & 0 xffff;
u8 val = reg->val & 0 xff;
saa717x_write(sd, addr, val);
return 0 ;
}
#endif
static int saa717x_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *format)
{
struct v4l2_mbus_framefmt *fmt = &format->format;
int prescale, h_scale, v_scale;
v4l2_dbg(1 , debug, sd, "decoder set size\n" );
if (format->pad || fmt->code != MEDIA_BUS_FMT_FIXED)
return -EINVAL;
/* FIXME need better bounds checking here */
if (fmt->width < 1 || fmt->width > 1440 )
return -EINVAL;
if (fmt->height < 1 || fmt->height > 960 )
return -EINVAL;
fmt->field = V4L2_FIELD_INTERLACED;
fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
if (format->which == V4L2_SUBDEV_FORMAT_TRY)
return 0 ;
/* scaling setting */
/* NTSC and interlace only */
prescale = SAA717X_NTSC_WIDTH / fmt->width;
if (prescale == 0 )
prescale = 1 ;
h_scale = 1024 * SAA717X_NTSC_WIDTH / prescale / fmt->width;
/* interlace */
v_scale = 512 * 2 * SAA717X_NTSC_HEIGHT / fmt->height;
/* Horizontal prescaling etc */
set_h_prescale(sd, 0 , prescale);
set_h_prescale(sd, 1 , prescale);
/* Horizontal scaling increment */
/* TASK A */
saa717x_write(sd, 0 x6C, (u8)(h_scale & 0 xFF));
saa717x_write(sd, 0 x6D, (u8)((h_scale >> 8 ) & 0 xFF));
/* TASK B */
saa717x_write(sd, 0 xAC, (u8)(h_scale & 0 xFF));
saa717x_write(sd, 0 xAD, (u8)((h_scale >> 8 ) & 0 xFF));
/* Vertical prescaling etc */
set_v_scale(sd, 0 , v_scale);
set_v_scale(sd, 1 , v_scale);
/* set video output size */
/* video number of pixels at output */
/* TASK A */
saa717x_write(sd, 0 x5C, (u8)(fmt->width & 0 xFF));
saa717x_write(sd, 0 x5D, (u8)((fmt->width >> 8 ) & 0 xFF));
/* TASK B */
saa717x_write(sd, 0 x9C, (u8)(fmt->width & 0 xFF));
saa717x_write(sd, 0 x9D, (u8)((fmt->width >> 8 ) & 0 xFF));
/* video number of lines at output */
/* TASK A */
saa717x_write(sd, 0 x5E, (u8)(fmt->height & 0 xFF));
saa717x_write(sd, 0 x5F, (u8)((fmt->height >> 8 ) & 0 xFF));
/* TASK B */
saa717x_write(sd, 0 x9E, (u8)(fmt->height & 0 xFF));
saa717x_write(sd, 0 x9F, (u8)((fmt->height >> 8 ) & 0 xFF));
return 0 ;
}
static int saa717x_s_radio(struct v4l2_subdev *sd)
{
struct saa717x_state *decoder = to_state(sd);
decoder->radio = 1 ;
return 0 ;
}
static int saa717x_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
{
struct saa717x_state *decoder = to_state(sd);
v4l2_dbg(1 , debug, sd, "decoder set norm " );
v4l2_dbg(1 , debug, sd, "(not yet implemented)\n" );
decoder->radio = 0 ;
decoder->std = std;
return 0 ;
}
static int saa717x_s_audio_routing(struct v4l2_subdev *sd,
u32 input, u32 output, u32 config)
{
struct saa717x_state *decoder = to_state(sd);
if (input < 3 ) { /* FIXME! --tadachi */
decoder->audio_input = input;
v4l2_dbg(1 , debug, sd,
"set decoder audio input to %d\n" ,
decoder->audio_input);
set_audio_regs(sd, decoder);
return 0 ;
}
return -ERANGE;
}
static int saa717x_s_stream(struct v4l2_subdev *sd, int enable)
{
struct saa717x_state *decoder = to_state(sd);
v4l2_dbg(1 , debug, sd, "decoder %s output\n" ,
enable ? "enable" : "disable" );
decoder->enable = enable;
saa717x_write(sd, 0 x193, enable ? 0 xa6 : 0 x26);
return 0 ;
}
/* change audio mode */
static int saa717x_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt)
{
struct saa717x_state *decoder = to_state(sd);
int audio_mode;
char *mes[4 ] = {
"MONO" , "STEREO" , "LANG1" , "LANG2/SAP"
};
audio_mode = TUNER_AUDIO_STEREO;
switch (vt->audmode) {
case V4L2_TUNER_MODE_MONO:
audio_mode = TUNER_AUDIO_MONO;
break ;
case V4L2_TUNER_MODE_STEREO:
audio_mode = TUNER_AUDIO_STEREO;
break ;
case V4L2_TUNER_MODE_LANG2:
audio_mode = TUNER_AUDIO_LANG2;
break ;
case V4L2_TUNER_MODE_LANG1:
audio_mode = TUNER_AUDIO_LANG1;
break ;
}
v4l2_dbg(1 , debug, sd, "change audio mode to %s\n" ,
mes[audio_mode]);
decoder->tuner_audio_mode = audio_mode;
/* The registers are not changed here. */
/* See DECODER_ENABLE_OUTPUT section. */
set_audio_mode(sd, decoder->tuner_audio_mode);
return 0 ;
}
static int saa717x_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
{
struct saa717x_state *decoder = to_state(sd);
int dual_f, stereo_f;
if (decoder->radio)
return 0 ;
get_inf_dev_status(sd, &dual_f, &stereo_f);
v4l2_dbg(1 , debug, sd, "DETECT==st:%d dual:%d\n" ,
stereo_f, dual_f);
/* mono */
if ((dual_f == 0 ) && (stereo_f == 0 )) {
vt->rxsubchans = V4L2_TUNER_SUB_MONO;
v4l2_dbg(1 , debug, sd, "DETECT==MONO\n" );
}
/* stereo */
if (stereo_f == 1 ) {
if (vt->audmode == V4L2_TUNER_MODE_STEREO ||
vt->audmode == V4L2_TUNER_MODE_LANG1) {
vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
v4l2_dbg(1 , debug, sd, "DETECT==ST(ST)\n" );
} else {
vt->rxsubchans = V4L2_TUNER_SUB_MONO;
v4l2_dbg(1 , debug, sd, "DETECT==ST(MONO)\n" );
}
}
/* dual */
if (dual_f == 1 ) {
if (vt->audmode == V4L2_TUNER_MODE_LANG2) {
vt->rxsubchans = V4L2_TUNER_SUB_LANG2 | V4L2_TUNER_SUB_MONO;
v4l2_dbg(1 , debug, sd, "DETECT==DUAL1\n" );
} else {
vt->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_MONO;
v4l2_dbg(1 , debug, sd, "DETECT==DUAL2\n" );
}
}
return 0 ;
}
static int saa717x_log_status(struct v4l2_subdev *sd)
{
struct saa717x_state *state = to_state(sd);
v4l2_ctrl_handler_log_status(&state->hdl, sd->name);
return 0 ;
}
/* ----------------------------------------------------------------------- */
static const struct v4l2_ctrl_ops saa717x_ctrl_ops = {
.s_ctrl = saa717x_s_ctrl,
};
static const struct v4l2_subdev_core_ops saa717x_core_ops = {
#ifdef CONFIG_VIDEO_ADV_DEBUG
.g_register = saa717x_g_register,
.s_register = saa717x_s_register,
#endif
.log_status = saa717x_log_status,
};
static const struct v4l2_subdev_tuner_ops saa717x_tuner_ops = {
.g_tuner = saa717x_g_tuner,
.s_tuner = saa717x_s_tuner,
.s_radio = saa717x_s_radio,
};
static const struct v4l2_subdev_video_ops saa717x_video_ops = {
.s_std = saa717x_s_std,
.s_routing = saa717x_s_video_routing,
.s_stream = saa717x_s_stream,
};
static const struct v4l2_subdev_audio_ops saa717x_audio_ops = {
.s_routing = saa717x_s_audio_routing,
};
static const struct v4l2_subdev_pad_ops saa717x_pad_ops = {
.set_fmt = saa717x_set_fmt,
};
static const struct v4l2_subdev_ops saa717x_ops = {
.core = &saa717x_core_ops,
.tuner = &saa717x_tuner_ops,
.audio = &saa717x_audio_ops,
.video = &saa717x_video_ops,
.pad = &saa717x_pad_ops,
};
/* ----------------------------------------------------------------------- */
/* i2c implementation */
/* ----------------------------------------------------------------------- */
static int saa717x_probe(struct i2c_client *client)
{
struct saa717x_state *decoder;
struct v4l2_ctrl_handler *hdl;
struct v4l2_subdev *sd;
u8 id = 0 ;
char *p = "" ;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
decoder = devm_kzalloc(&client->dev, sizeof (*decoder), GFP_KERNEL);
if (decoder == NULL)
return -ENOMEM;
sd = &decoder->sd;
v4l2_i2c_subdev_init(sd, client, &saa717x_ops);
if (saa717x_write(sd, 0 x5a4, 0 xfe) &&
saa717x_write(sd, 0 x5a5, 0 x0f) &&
saa717x_write(sd, 0 x5a6, 0 x00) &&
saa717x_write(sd, 0 x5a7, 0 x01))
id = saa717x_read(sd, 0 x5a0);
if (id != 0 xc2 && id != 0 x32 && id != 0 xf2 && id != 0 x6c) {
v4l2_dbg(1 , debug, sd, "saa717x not found (id=%02x)\n" , id);
return -ENODEV;
}
if (id == 0 xc2)
p = "saa7173" ;
else if (id == 0 x32)
p = "saa7174A" ;
else if (id == 0 x6c)
p = "saa7174HL" ;
else
p = "saa7171" ;
v4l2_info(sd, "%s found @ 0x%x (%s)\n" , p,
client->addr << 1 , client->adapter->name);
hdl = &decoder->hdl;
v4l2_ctrl_handler_init(hdl, 9 );
/* add in ascending ID order */
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0 , 255 , 1 , 128 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_CONTRAST, 0 , 255 , 1 , 68 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_SATURATION, 0 , 255 , 1 , 64 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_HUE, -128 , 127 , 1 , 0 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0 , 65535 , 65535 / 100 , 42000 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_AUDIO_BALANCE, 0 , 65535 , 65535 / 100 , 32768 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_AUDIO_BASS, -16 , 15 , 1 , 0 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_AUDIO_TREBLE, -16 , 15 , 1 , 0 );
v4l2_ctrl_new_std(hdl, &saa717x_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0 , 1 , 1 , 0 );
sd->ctrl_handler = hdl;
if (hdl->error) {
int err = hdl->error;
v4l2_ctrl_handler_free(hdl);
return err;
}
decoder->std = V4L2_STD_NTSC;
decoder->input = -1 ;
decoder->enable = 1 ;
/* FIXME!! */
decoder->playback = 0 ; /* initially capture mode used */
decoder->audio = 1 ; /* DECODER_AUDIO_48_KHZ */
decoder->audio_input = 2 ; /* FIXME!! */
decoder->tuner_audio_mode = TUNER_AUDIO_STEREO;
/* set volume, bass and treble */
decoder->audio_main_vol_l = 6 ;
decoder->audio_main_vol_r = 6 ;
v4l2_dbg(1 , debug, sd, "writing init values\n" );
/* FIXME!! */
saa717x_write_regs(sd, reg_init_initialize);
v4l2_ctrl_handler_setup(hdl);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(2 *HZ);
return 0 ;
}
static void saa717x_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
v4l2_device_unregister_subdev(sd);
v4l2_ctrl_handler_free(sd->ctrl_handler);
}
/* ----------------------------------------------------------------------- */
static const struct i2c_device_id saa717x_id[] = {
{ "saa717x" },
{ }
};
MODULE_DEVICE_TABLE(i2c, saa717x_id);
static struct i2c_driver saa717x_driver = {
.driver = {
.name = "saa717x" ,
},
.probe = saa717x_probe,
.remove = saa717x_remove,
.id_table = saa717x_id,
};
module_i2c_driver(saa717x_driver);
Messung V0.5 in Prozent C=89 H=91 G=89
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland