分类: LINUX
2010-04-07 10:28:01
Saa7115的驱动支持saa7111, saa7111a, saa7113, saa7114,saa7115 and saa7118.这里分析的代码是来自linux-2.6.19内核。具体路径为/driver/media/video/saa7115.c。
1. 模块的加载与卸载
module_init(saa711x_init_module);
module_exit(saa711x_cleanup_module);
static int __init saa711x_init_module(void)
{
return i2c_add_driver(&i2c_driver_saa711x);
}
static void __exit saa711x_cleanup_module(void)
{
i2c_del_driver(&i2c_driver_saa711x);
}
i2c_add_driver()用于注册设备驱动程序的i2c_driver数据结构。
i2c_del_driver()则相反,用于注销设备驱动程序的i2c_driver树结构。
由此加载i2c_driver的结构体i2c_driver_saa711x
static struct i2c_driver i2c_driver_saa711x = {
.driver = {
.name = "saa7115",
},
.id = I2C_DRIVERID_SAA711X,
.attach_adapter = saa711x_probe,
.detach_client = saa711x_detach,
.command = saa711x_command,
};
其中driver为驱动名。id为驱动号。attach_adapter依附i2c_adapter函数指针。detach_client为脱离i2c_client函数指针。command为实现针对设备的控制命令指针,类似ioctl。
下面分别对saa711x_probe(),saa711x_detach(),saa711x_command()作分析。
2. saa711x_probe()函数
saa711x_probe()是实现attach_adapter的函数。可以简单的通过调用i2c_probe()函数来实现。
static int saa711x_probe(struct i2c_adapter *adapter)
{
if (adapter->class & I2C_CLASS_TV_ANALOG || adapter->class & I2C_CLASS_TV_DIGITAL)
return i2c_probe(adapter, &addr_data, &saa711x_attach);
return 0;
}
adapter:内核指针数组adapters[]的一个元素,代表当前被扫描的i2c 总线。addr_data:在ltc3445.h中由I2C_CLIENT_INSMOD宏创建的静态二维数组,表示使用该驱动程序的所有设备的所有可能地址的集合。
saa711x_attach:为一个在地址成功检测时被调用的回调函数,用于创建描述该设备的i2c_client数据结构并初始化。
i2c_probe 函数用于认领adapter所指适配器上的所有合适的设备。设备可能使用的地址的“线索”由addr_data二元数组指出,如果检测到存在实际设备,则调用saa711x_attach回调函数分配、初始化设备的i2c_client等数据结构。
由此引出了函数saa7111x_attach():
static int saa711x_attach(struct i2c_adapter *adapter, int address, int kind);
adapter:i2c_adapter类型的适配器 address:chip address。
下面分析该函数的代码:
struct i2c_client *client; //具体设备名
struct saa711x_state *state; //saa711x_state结构体
/*
saa711x_state的结构体如下:
struct saa711x_state {
v4l2_std_id std;
int input;
int output;
int enable;
int radio;
int bright;
int contrast;
int hue;
int sat;
int width;
int height;
u32 ident;
u32 audclk_freq;
u32 crystal_freq;
u8 ucgc;
u8 cgcdiv;
u8 apll;
};
*/
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return 0;
这里调用了函数i2c_check_functionality()判断指定适配器是否支持相应的方法。
I2C_FUNC_SMBUS_BYTE_DATA: Handles the SMBUS read_byte_data and write_byte_data commands.
接着是对i2c_client初始化,初始化的过程中用到了saa711x_write和saa711x_read两个函数。
l saa711x_write():
static inline int saa711x_write(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
这个函数的功能是把value的值写到client设备的reg寄存器中。它通过调用i2c_smbus_write_byte_data来实现。
s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_smbus_xfer(client->adapter,client->addr,client->flags,
I2C_SMBUS_WRITE,command,
I2C_SMBUS_BYTE_DATA,&data);
}
EXPORT_SYMBOL(i2c_smbus_write_byte_data);
可以看出实际上是调用i2c_smbus_xfer()来实现。这个函数通过适配器驱动提供的总线访问方法(i2c_algorithm的smbus_xfer方法)尝试访问处于addr地址上的设备。
s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,
char read_write, u8 command, int size,
union i2c_smbus_data * data)
{
s32 res;
flags &= I2C_M_TEN | I2C_CLIENT_PEC;
if (adapter->algo->smbus_xfer) {
mutex_lock(&adapter->bus_lock);
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
command,size,data);
mutex_unlock(&adapter->bus_lock);
} else
res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
command,size,data);
return res;
}
而目前i2c中没有实现smbus_xfer方法而只实现了master_xfer方法,所以相关的操作就由i2c_smbus_xfer_emulated函数来模拟。这个函数比较长,所以只分析它的参数。其中size为I2C_SMBUS_QUICK的一段。第二个参数为需要检测的设备地址,第四个参数read_write为0,表示进行写入操作。如果使用这个总线地址和设备通信成功,则说明设备使用的正是这个地址。
l saa711x_read():
static inline int saa711x_read(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
这个函数的功能是把client设备的reg寄存器中的值读出并返回。它通过调用i2c_smbus_read_byte_data来实现。
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
if (i2c_smbus_xfer(client->adapter,client->addr,client->flags,
I2C_SMBUS_READ,command, I2C_SMBUS_BYTE_DATA,&data))
return -1;
else
return data.byte;
}
EXPORT_SYMBOL(i2c_smbus_read_byte_data);
可以看出,它也是调用i2c_smbus_xfer函数来实现的,因此和saa711x_write()的过程基本一样。
再回头看i2c_client的初始化:
分配i2c_client的内存空间
client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (client == 0)
return -ENOMEM;
初始化client的addr,adapter和driver。没什么可说的。
client->addr = address;
client->adapter = adapter;
client->driver = &i2c_driver_saa711x;
初始化client的name。
snprintf(client->name, sizeof(client->name) - 1, "saa7115");
for (i = 0; i < 0x0f; i++) {
saa711x_write(client, 0, i);
name[i] = (saa711x_read(client, 0) & 0x0f) + '0';
if (name[i] > '9')
name[i] += 'a' - '9' - 1;
}
name[i] = '\0';
查看芯片号是否为saa711x。
saa711x_write(client, 0, 5);
chip_id = saa711x_read(client, 0) & 0x0f;
/* Check whether this chip is part of the saa711x series */
if (memcmp(name, "1f711", 5)) {
v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
address << 1, name);
kfree(client);
return 0;
}
snprintf(client->name, sizeof(client->name) - 1, "saa711%d",chip_id);
v4l_info(client, "saa711%d found (%s) @ 0x%x (%s)\n", chip_id, name, address << 1, adapter->name);
到此,i2c_client结构体client初始化完毕。下面开始对saa711x_state结构体state进行初始化。
state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
i2c_set_clientdata(client, state);//将state存到client->dev中
if (state == NULL) {
kfree(client);
return -ENOMEM;
}
state->input = -1;
state->output = SAA7115_IPORT_ON;
state->enable = 1;
state->radio = 0;
state->bright = 128;
state->contrast = 64;
state->hue = 0;
state->sat = 64;
然后根据芯片号确认是哪款芯片。并选择不同的初始化函数。
switch (chip_id) {
case 1:
state->ident = V4L2_IDENT_SAA7111;
break;
case 3:
state->ident = V4L2_IDENT_SAA7113;
break;
case 4:
state->ident = V4L2_IDENT_SAA7114;
break;
case 5:
state->ident = V4L2_IDENT_SAA7115;
break;
case 8:
state->ident = V4L2_IDENT_SAA7118;
break;
default:
state->ident = V4L2_IDENT_SAA7111;
v4l_info(client, "WARNING: Chip is not known - Falling back to saa7111\n");
}
state->audclk_freq = 48000;
v4l_dbg(1, debug, client, "writing init values\n");
/* init to 60hz/48khz */
state->crystal_freq = SAA7115_FREQ_24_576_MHZ;
switch (state->ident) {
case V4L2_IDENT_SAA7111:
saa711x_writeregs(client, saa7111_init);
break;
case V4L2_IDENT_SAA7113:
saa711x_writeregs(client, saa7113_init);
break;
default:
state->crystal_freq = SAA7115_FREQ_32_11_MHZ;
saa711x_writeregs(client, saa7115_init_auto_input);
}
以7111的初始化函数saa7111_init()为例分析芯片的初始化。这里用到了saa711x_writeregs()函数。在saa711x_writeregs()中又调用了i2c_get_clientdata(),saa711x_has_reg()以及saa711x_write()。saa711x_write()在前面已经分析过了,这里重点分析i2c_getclientdata()和saa711x_has_reg()。
l i2c_get_clientdata()是i2c的一个API。
static inline void *i2c_get_clientdata (struct i2c_client *dev)
{
return dev_get_drvdata (&dev->dev);
}
static inline void *
dev_get_drvdata (struct device *dev)
{
return dev->driver_data;
}
很明显,是将一个i2c_client结构体中的dev->driver_data返回。
l saa711x_has_reg()函数
/* Sanity routine to check if a register is present */
static int saa711x_has_reg(const int id, const u8 reg)
{
if (id == V4L2_IDENT_SAA7111)
return reg < 0x20 && reg != 0x01 && reg != 0x0f &&
(reg < 0x13 || reg > 0x19) && reg != 0x1d && reg != 0x1e;
/* common for saa7113/4/5/8 */
if (unlikely((reg >= 0x3b && reg <= 0x3f) || reg == 0x5c || reg == 0x5f ||
reg == 0xa3 || reg == 0xa7 || reg == 0xab || reg == 0xaf || (reg >= 0xb5 && reg <= 0xb7) ||
reg == 0xd3 || reg == 0xd7 || reg == 0xdb || reg == 0xdf || (reg >= 0xe5 && reg <= 0xe7) ||
reg == 0x82 || (reg >= 0x89 && reg <= 0x8e)))
return 0;
switch (id) {
case V4L2_IDENT_SAA7113:
return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
reg != 0x5d && reg < 0x63;
case V4L2_IDENT_SAA7114:
return (reg < 0x1a || reg > 0x1e) && (reg < 0x20 || reg > 0x2f) &&
(reg < 0x63 || reg > 0x7f) && reg != 0x33 && reg != 0x37 &&
reg != 0x81 && reg < 0xf0;
case V4L2_IDENT_SAA7115:
return (reg < 0x20 || reg > 0x2f) && reg != 0x65 && (reg < 0xfc || reg > 0xfe);
case V4L2_IDENT_SAA7118:
return (reg < 0x1a || reg > 0x1d) && (reg < 0x20 || reg > 0x22) &&
(reg < 0x26 || reg > 0x28) && reg != 0x33 && reg != 0x37 &&
(reg < 0x63 || reg > 0x7f) && reg != 0x81 && reg < 0xf0;
}
return 1;
}
检查寄存器是否存在。
static int saa711x_writeregs(struct i2c_client *client, const unsigned char *regs)
{
struct saa711x_state *state = i2c_get_clientdata(client);
unsigned char reg, data;
while (*regs != 0x00) {
reg = *(regs++);
data = *(regs++);
/* According with datasheets, reserved regs should be
filled with 0 - seems better not to touch on they */
if (saa711x_has_reg(state->ident,reg)) {
if (saa711x_write(client, reg, data) < 0)
return -1;
} else {
v4l_dbg(1, debug, client, "tried to access reserved reg 0x%02x\n", reg);
}
}
return 0;
}
实际上就是将regs写入到client中去。regs是一个由寄存器地址和寄存器赋值组成的char类型表。
接着看saa711x_attach()
saa711x_writeregs(client, saa7115_init_misc);
saa711x_set_v4lstd(client, V4L2_STD_NTSC);
这里调用了saa711x_set_v4lstd(),看字面意思就知道是设置v4l标准的。看下这个函数的具体代码:
static void saa711x_set_v4lstd(struct i2c_client *client, v4l2_std_id std)
{
struct saa711x_state *state = i2c_get_clientdata(client);
/* Prevent unnecessary standard changes. During a standard
change the I-Port is temporarily disabled. Any devices
reading from that port can get confused.
Note that VIDIOC_S_STD is also used to switch from
radio to TV mode, so if a VIDIOC_S_STD is broadcast to
all I2C devices then you do not want to have an unwanted
side-effect here. */
if (std == state->std)
return;
state->std = std;
// This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
if (std & V4L2_STD_525_60) {
v4l_dbg(1, debug, client, "decoder set standard 60 Hz\n");
saa711x_writeregs(client, saa7115_cfg_60hz_video);
saa711x_set_size(client, 720, 480);
} else {
v4l_dbg(1, debug, client, "decoder set standard 50 Hz\n");
saa711x_writeregs(client, saa7115_cfg_50hz_video);
saa711x_set_size(client, 720, 576);
}
/* Register 0E - Bits D6-D4 on NO-AUTO mode
(SAA7111 and SAA7113 doesn't have auto mode)
50 Hz / 625 lines 60 Hz / 525 lines
000 PAL BGDHI (4.43Mhz) NTSC M (3.58MHz)
001 NTSC 4.43 (50 Hz) PAL 4.43 (60 Hz)
010 Combination-PAL N (3.58MHz) NTSC 4.43 (60 Hz)
011 NTSC N (3.58MHz) PAL M (3.58MHz)
100 reserved NTSC-Japan (3.58MHz)
*/
if (state->ident == V4L2_IDENT_SAA7111 ||
state->ident == V4L2_IDENT_SAA7113) {
u8 reg = saa711x_read(client, R_0E_CHROMA_CNTL_1) & 0x8f;
if (std == V4L2_STD_PAL_M) {
reg |= 0x30;
} else if (std == V4L2_STD_PAL_N) {
reg |= 0x20;
} else if (std == V4L2_STD_PAL_60) {
reg |= 0x10;
} else if (std == V4L2_STD_NTSC_M_JP) {
reg |= 0x40;
} else if (std & V4L2_STD_SECAM) {
reg |= 0x50;
}
saa711x_write(client, R_0E_CHROMA_CNTL_1, reg);
} else {
/* restart task B if needed */
int taskb = saa711x_read(client, R_80_GLOBAL_CNTL_1) & 0x10;
if (taskb && state->ident == V4L2_IDENT_SAA7114) {
saa711x_writeregs(client, saa7115_cfg_vbi_on);
}
/* switch audio mode too! */
saa711x_set_audio_clock_freq(client, state->audclk_freq);
}
}
具体的设置就不仔细分析了。最后是
i2c_attach_client(client);
该函数向设备所在的总线注册设备数据结构。
首先检查这个新设备所使用的地址不与总线上已有设备的地址冲突。否则返回-EBUSY。
一条I2C总线上所有已注册设备的i2c_client数据结构被组织在i2c_adapter中的clients指针数组中。所谓注册,其实也就是在clients数据中找到第一个未使用的元素,并将其指向新的i2c_client数据结构。
最后,如果适配器驱动程序模块中提供了client_register方法则调用之,这个方法用于完成适配器端的额外的注册工作。
到此saa711x_attach()函数就结束了。
3. saa711x_detech()函数:
static int saa711x_detach(struct i2c_client *client)
{
struct saa711x_state *state = i2c_get_clientdata(client);
int err;
err = i2c_detach_client(client);
if (err) {
return err;
}
kfree(state);
kfree(client);
return 0;
}
这个函数相对比较简单。就是调用i2c_detach_client()。
4. saa711x_command()函数
这个函数实现了针对设备的控制命令。具体的控制命令是设备相关的。这里主要是留出了V4L2的API。具体含义请参照V4L2的说明文档。