Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505333
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类: 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_writesaa711x_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的说明文档。

阅读(3639) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~