驱动第九天

驱动第九天

1. 基本上,linux驱动中的所有设备都是挂载在总线上的。
2. 字符设备的作用是将文件当作字符设备处理。

【ADC指导手册】

一、  原理&作用

1.     作用

模拟电压信号转换为数字信号

(1)       原理

(2)    积分型

(3)     逐次比较型

(4)     并行比较型



二、 数据手册阅读

阅读《S5PC100_UM_REV104.pdf》如下内容:

章节

目的

10.07 ADC

了解S5PC100芯片的ADC的功能性能及使用方法

三、名词解释

(1)       Resolution

分辨率,每个电压值转换之后的二进制位数,位数越高,精度越高

(2)       Differential Linearity Error

微分线性误差,转化出来的每个值,相对于正确值的误差

(3)       Integral Linearity Error

积分线性误差,转化出来的曲线,相对于正确曲线的误差

四、实验

(1)       目的

(2)      查看原理图

(3)       设置

1.       多功能管脚设置

ADC使用的是专用模拟信号管脚,不需要设置

2.       时钟设置

(1)       开启时钟

CLK_GATE_D1_5[7]1

(2)     时钟分频

ADCCON[6-14]0x1ff

3.       ADC参数设置

(1)       分辨率

ADCCON[16]1(12bit)

4.       ADC通道选择

ADCMUX [0:3]0b0000

5.       使能

ADCCON[1:2]0b10

6.       读取

ADCCON[15]1时,读取ADCDAT0[0:11]

注意:设置完使能ADC后,需要直接读取一次ADCDAT0[0:11]


【PWM指导手册】

一、作用&原理

1.  作用


(1)      波形调制(Pulse Width Modulation)

(2)       定时器

2.   原理

利用时钟信号实现定时功能,然后利用定时功能控制输出高电平或低电平的时间。具体实现如下:

1.       分频

利用PrescalerDivider对输入时钟进行分频

2.       定时

利用Down Counter和分频产生的时钟信号实现定时,时钟信号每过一个周期,Down Counter的值减1Down Counter值到0时,定时结束, Down Counter值存储在TCNT寄存器

3.       波形调制

TCNT寄存器的值递减到等于TCMP寄存器的值时,输出管脚电平反转。TCNT的值到达0时,PWM会自动加载TCNTB寄存器的值到TCNTTCMPB寄存器的值到TCMP,开始一个新的定时周期,从而实现周期性方波

二、        数据手册阅读

阅读《S5PC100_UM_REV104.pdf》如下内容:

章节

目的

7.01 PWM

了解S5PC100芯片的PWM功能性能及使用方法

三、名词解释

(1)       占空比

占空比 = 高电平时间/(高电平时间+低电平时间)

(2)       Prescaler

输出时钟频率 = 输入时钟频率 / 分频因子(factor)

(3)       Divider

输出时钟频率 = 输入时钟频率 / 除法因子(factor )

(4)       Mux

多路选择,Divider一定要和Mux联合使用

四、        实验

1.        目的

(1)       理解PWM基本原理

(2)       会使用S5PC100中的PWM

2.       查看原理图

查看《FS_S5PC100_DEV.pdf》文档的beep电路图

3.       设置

1.       多功能管脚设置

GPDCON[ 4:7]0b0010(PWM管脚控制)

2.      时钟设置

(1)      时钟开关

CLK_GATE_D1_3[6]1(打开PCLKPWM开关)

(2)       配置分频器

TCFG0[0:7]0xff(Prescaler的分频因子为256)

(3)       配置除法器

TCFG1[ 4:7] 0b0100(除法因子1/16

(4)       配置定时器

TCNTB1寄存器的值决定定时时间

TCMPB1寄存器的值,决定电平反转时间

(5)       手动加载

TCON[9]1,然后置TCON[9]0

(加载TCNTB0寄存器到TCNT0寄存器,加载TCMPB0寄存器到TCMP0寄存器)

(6)       开启PWM

TCON[8:11]0b1001


【关于IIC驱动】
1. 原理分布图

2. 层次图


3. I2C驱动框架


4. 总线内部结构


5. 总线结构图


6. 驱动层次图



7. 驱动结构图


8. iic软件图解

【内核修改i2c驱动支持】
(1)修改drivers/i2c/busses/Kconfig 文件中的526行:
 depends on ARCH_S3C2410 || ARCH_S3C64XX
改为:
  depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100

(2)配置并重新编译内核
make menuconfig
 Device Drivers --->
  <*> I2C support --->
   <*> I2C device interface
   I2C Hardware Bus support --->
    <*> S3C2410 I2C Driver
 
【添加i2c支持】
修改arch/arm/mach-s5pc100/mach-smdkc100.c
修改:
static struct i2c_board_info i2c_devs0[] __initdata = {
};
为:
static struct i2c_board_info i2c_devs0[] __initdata = {
 {
  I2C_BOARD_INFO("lm75", 0x48),
 },
};

【IIC标准手册】

1. 原理&作用

(1)      作用

用于主从设备数据传输的总线,它支持挂接多个主设备和多个从设备,但是一次通讯只能有一个主设备;

通讯速度为标准速度100kbps,快速400kbps,高速3.4Mbps

(2)       原理


SCL:时钟管脚,时钟由主设备产生

SDA:数据管脚,数据可以双向传输

Rp:上拉电阻,空闲时,将SCLSDA上拉成高电平

2.查看数据手册

阅读S5PC100_UM_REV104.pdf》如下内容:

章节

目的

8.02

理解I2C总线原理及s5pc100I2C总线使用

3.名词解释

(1)       Transfer


(2)      StartStop

1.       等待数据(wait)

2.       从端请求停止(Abort)


3.      ACK


四、        驱动原理


五、        接口驱动

1.       初始化

1.       多功能管脚设置

GPDCON [12:15][16:19]0b0010

2.       时钟(速度)设置(100k)


(1)     时钟分频

I2CCON0 [0:3]0

(2)       时钟除法

I2CCON0 [6]1

3.       ACK设置

I2CCON0[7]1

4.       使能发送接收中断

I2CCON0[5]1

 

2.       数据传送

(1)      发送地址

I2CDS0[1:7]为地址

I2CDS0[0]0

(2)       设置主发送模式

I2CSTAT0[6:7]3

(3)       使能发送接收

I2CSTAT0 [4]1

(4)       启动发送

I2CSTAT0 [5]1

(5)       等待发送完成

等待I2CCON0[4]1

(6)       发送数据

I2CDS0为数据

(7)       清除中断标志

I2CCON0[4]0

(8)       等待发送完成

等待I2CCON0[4]1

 

3       接收数据

(1)       发送地址

I2CDS0[1:7]为地址

I2CDS0[0]1

(2)       设置主接收模式

I2CSTAT0[6:7]2

(3)       使能发送接收

I2CSTAT0 [4]1

(4)       启动接收

I2CSTAT0 [5]1

(5)       清除中断标志

I2CCON0[4]0

(6)       等待地址发送完成

等待I2CCON0[4]1

 (7)    清除中断标志

I2CCON0[4]0

(8)       等待接收完成

等待I2CCON0[4]1

(9)       读取接收数据

读取I2CDS0中的数据

 

4       停止发送接收

I2CSTAT0 [5]0

I2CCON0 [4]0


【学习总结】

/***************************************************I2C设备驱动**********************************************************************/

/*linuxI2C驱动分为3部分:

 *1I2C核心:

 *I2C核心核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm(算法))上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

 *

 *2I2C总线驱动:

 *      I2C总线驱动是对I2C硬件体系结构中适配器(就是i2c控制器)端的实现,适配器可由CPU控制。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapterI2C适配器的algorithm数据结构

 *i2c_algorithm和控制I2C适配器产生通信信号的函数经由I2C总线驱动的代码,我们可控制I2C适配器以产生主控方式的开始位、停止位、读写周期以及以从设备方式被读写、产生ACK等。

 *

 *3I2C设备驱动:

 *I2C设备驱动(也称客户程序)是对I2C硬件体系结构中设备端的实现,设备一般挂接在I2C适配器上,I2C设备驱动主要包括数据结构i2c_driveri2c_client,我们需要根据具体设备实现其中的成员函数。

 *

 *linux2.6内核中,所有的I2C设备都在sysfs文件系统中显示,存于/sys/bus/i2c/目录下。

 *

 *linux内核源代码中的drivers下的i2c目录包含如下文件和目录:

 *1i2c-core.c    这个文件实现了I2C核心的功能以及/proc/bus/i2c接口

 *

 *2i2c-dev.c    实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,此设备号为0-255,应用程序通过“i2c-%d”(i2c-0,i2c-1,......)文件名

 *                并使用文件操作接口open()write()read()ioctl()close()等来访问设备,从而访问挂接在I2C适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

 *               

 *3chip目录      这个目录包含了一些特定的I2C设备驱动,如 DS1337实时时钟芯片、I2C接口的EEPROM驱动等,在具体的I2C设备驱动中,调用的都是I2C核心提供的接口,因     此,这使得具体的I2C设备驱动不依赖于

 *                 CPU的类型和I2C控制器的硬件特性。

 *

 *4busses目录    这个文件中包含一些I2C总线的驱动,如针对S3C2410S3C2440S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c

 *

 *5algos目录     实现了一些I2C总线适配器的algorithm

 *

 *此外在i2c.h中对i2c_driver i2c_client i2c_adapter i2c_algorithm4个数据结构进行了定义,理解这4个结构体非常重要 

 */

 

--------------------------------------------struct i2c_adapter----------------------------------------------------

struct i2c_adapter {      //该结构对应物理上的一个适配器(I2C控制器),一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期,缺少i2c_algorithmi2c_adapter什么都做不了,

                          //因此该结构中包含了i2c_algorithm指针

                         

  struct module *owner;   //THIS_MODULE

  unsigned int id;        //algorithms的类型,定义于i2c-id.h,以I2C_ALGO_开始

  unsigned int class;     /* classes to allow probing for */   //允许探测的类

  const struct i2c_algorithm *algo; /* the algorithm to access the bus */   //总线通信方法结构体指针

  void *algo_data;        //algorithm数据,多数I2C总线驱动会定义设备私有结构体,通常用此指针指向它

 

  /* --- administration stuff. */

  int (*client_register)(struct i2c_client *) __deprecated;   //client注册时调用

  int (*client_unregister)(struct i2c_client *) __deprecated; //client注销时调用

 

  /* data fields that are valid for all devices */

  u8 level;       /* nesting level for lockdep */

  struct mutex bus_lock;     //控制并发的信号量

  struct mutex clist_lock;

 

  int timeout;      /* in jiffies */

  int retries;                                     //重试次数

  struct device dev;    /* the adapter device */   //适配器设备

 

  int nr;                                          //适配器的个数,若超过一个则应该调用int i2c_add_numbered_adapter(struct i2c_adapter *adap)函数来添加适配器

  struct list_head clients; /* DEPRECATED */       //client链表头(由于一个适配器上可以连接多个I2C设备,所以一个i2c_adapter可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client的链表)

  char name[48];                                   //适配器名称

  struct completion dev_released;                  //用于同步

}; 

-----------------------------------------------------------------------------------------------------------------

 

--------------------------------------------struct i2c_algorithm-------------------------------------------------

struct i2c_algorithm {   //该结构对应一套通信方法

  /* If an adapter algorithm can't do I2C-level access, set master_xfer

     to NULL. If an adapter algorithm can do SMBus access, set

     smbus_xfer. If set to NULL, the SMBus protocol is simulated

     using common I2C messages */

  /* master_xfer should return the number of messages successfully

     processed, or a negative value on error */

//I2C传输函数指针,用于产生I2C访问周期需要的信号,以i2c_msg(I2c消息)为单位,该结构也非常重要,其定义如下:

//struct i2c_msg {   //早期I2C设备上读写数据的时序和数据通常通过该结构体数组来组织,最后通过i2c_transfer发送(实际还是通过master_xfer发送)

//          __u16 addr;      //7位是设备地址,

//          __u16 flags;     //标志,标志读(flags=1)还是写(flags=0)

//          #define I2C_M_TEN   0x0010  /* this is a ten bit chip address */  //芯片的地址是10

//          #define I2C_M_RD    0x0001  /* read data, from slave to master */ //从设备读取数据

//          #define I2C_M_NOSTART   0x4000  /* if I2C_FUNC_PROTOCOL_MANGLING */

//          #define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */

//          #define I2C_M_IGNORE_NAK  0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */

//          #define I2C_M_NO_RD_ACK   0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */

//          #define I2C_M_RECV_LEN    0x0400  /* length will be first received byte */

//          __u16 len;    //消息长度

//          __u8 *buf;    // 指向消息数据的指针

//               };

         int num);

  int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,                //smbus传输函数指针,SMbus大部分基于I2C总线规范,SMbus不需要增加额外的引脚,与I2C总线相比,

                                                                        //SMbus增加了一些新的功能特性,在访问时序也有一定的差异

         unsigned short flags, char read_write,

         u8 command, int size, union i2c_smbus_data *data);

 

  /* To determine what the adapter supports */

  u32 (*functionality) (struct i2c_adapter *);                         //返回适配器支持的功能

}; 

----------------------------------------------------------------------------------------------------------------

 

--------------------------------------------struct i2c_driver---------------------------------------------------           

struct i2c_driver {    //该结构提供了一套驱动的方法,其主要成员函数probe()remove()suspend()resume()

  int id;              

  unsigned int class;

 

  /* Notifies the driver that a new bus has appeared. This routine

   * can be used by the driver to test if the bus meets its conditions

   * & seek for the presence of the chip(s) it supports. If found, it

   * registers the client(s) that are on the bus to the i2c admin. via

   * i2c_attach_client.  (LEGACY I2C DRIVERS ONLY)

   */

  int (*attach_adapter)(struct i2c_adapter *);  

  //依附i2c_adapter函数指针,I2C driver在调用I2C_add_driver() 注册时,

  //对发现的每一个I2C adapter都要调用该函数,检查该I2C adapter是否符合I2C driver的特定条件,

  //如果符合条件则连接此I2C adapter,并通过I2C adapter来实现对I2C总线

  //I2C设备的访问直接调用I2C核心的i2c_probe函数)

                                                

  int (*detach_adapter)(struct i2c_adapter *);  

  //脱离i2c_adapter函数指针,I2C driver在删除一个I2C device时调用该函数,

  //清除描述这个I2C device的数据结构,这样以后就不能访问该设备了

 

  /* tells the driver that a client is about to be deleted & gives it

   * the chance to remove its private data. Also, if the client struct

   * has been dynamically allocated by the driver in the function above,

   * it must be freed here.  (LEGACY I2C DRIVERS ONLY)

   */

  int (*detach_client)(struct i2c_client *) __deprecated;  //i2c_client脱离函数指针

 

  /* Standard driver model interfaces, for "new style" i2c drivers.

   * With the driver model, device enumeration is NEVER done by drivers;

   * it's done by infrastructure.  (NEW STYLE DRIVERS ONLY)

   */

  int (*probe)(struct i2c_client *, const struct i2c_device_id *); //探测函数

  int (*remove)(struct i2c_client *);   //移除函数

 

  /* driver model interfaces that don't relate to enumeration  */

  void (*shutdown)(struct i2c_client *);  //关闭设备

  int (*suspend)(struct i2c_client *, pm_message_t mesg);//挂起设备

  int (*resume)(struct i2c_client *);  //恢复设备

 

  /* a ioctl like command that can be used to perform specific functions

   * with the device.

   */

  int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

 

  struct device_driver driver;

  const struct i2c_device_id *id_table;          

  //该驱动所支持的设备类型,是该驱动所支持的I2C设备的ID表,在i2c总线驱动i2c_bus_typemach()函数

  //i2c_device_match()中会调用i2c_match_id()

  //函数比较i2c_client中的namei2c_driverid_table->name是否相同,若相同则支持。

  //

  //struct i2c_device_id {

  //                      char name[I2C_NAME_SIZE];

  //                      kernel_ulong_t driver_data  /* Data private to the driver */

  //                      __attribute__((aligned(sizeof(kernel_ulong_t))));

  //                      };

 

  /* Device detection callback for automatic device creation */

  int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);

  const struct i2c_client_address_data *address_data;

  struct list_head clients;

};

----------------------------------------------------------------------------------------------------------------

 

--------------------------------------------struct i2c_client---------------------------------------------------

struct i2c_client { 

//该结构对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述,i2c_driveri2c_client的关系是一对多,

//一个i2c_driver上可以支持多个同等类型的i2c_client。由于一个适配器上可以连接多个

//I2C设备,所以一个i2c_adapter可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client的链表。

                    

  unsigned short flags;   /* div., see below    */            //标志

  unsigned short addr;    /* chip address - NOTE: 7bit  */    //7位为芯片地址

          /* addresses are stored in the  */

          /* _LOWER_ 7 bits   */

  char name[I2C_NAME_SIZE];   //设备名称,在i2c总线驱动i2c_bus_typemach()函数

  //i2c_device_match()中会调用i2c_match_id()函数比较namei2c_driver

                              //id_table->name是否相同,若相同则支持

                                                             

  struct i2c_adapter *adapter;  /* the adapter we sit on  */  //依附的i2c_adapter

  struct i2c_driver *driver;  /* and our access routines  */  //依附的i2c_driver

  struct device dev;    /* the device structure   */          //设备结构体

  int irq;      /* irq issued by device   */                  //使用的中断号

  struct list_head list;    /* DEPRECATED */                  //链表头

  struct list_head detected;

  struct completion released;                                 //用于同步

};

----------------------------------------------------------------------------------------------------------------

/*

 *如何写linuxI2C设备驱动程序:

 *

 *首先适配器可能是linux内核本身还不包含的,其次,挂接在适配器上的具体设备驱动可能也是linux内核还不包含的,

 *因此需要实现的主要工作如下:

 *

 *1:(总线驱动)提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2CI/O地址和中断号)、驱动CPU控制的

 *    I2C适配器从硬件上产生各种信号以及处理I2C中断等

 *

 *2:(总线驱动)提供I2C适配器的algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithmmaster_xfer指针,

 *    并把i2c_algorithm指针赋值给i2c_adapteralgo指针

 *

 *3:(设备驱动)实现I2C设备驱动中的i2c_driver接口,用具体设备xxxxxx_probe() xxx_remove() xxx_suspend() 

 *    xxx_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driverproberemovesuspendresumeid_table指针

 *             

 *4:(设备驱动)实现I2C设备所对应类型的具体驱动,i2c_driver只是实现了设备与总线的挂接,而挂接在总线上的设备

 *    则是千差万别。例如,如果是字符设备,就实现文件系统接口(read,write等系统调用的操作函数)如果是声卡,就实现ALSA驱动

 *             

 *I2C核心中提供了一组不依赖于硬件平台的接口函数:

 */

int i2c_add_adapter(struct i2c_adapter *adapter);   //增加i2c_adapter(该函数中注册了i2c_adapter 

int i2c_del_adapter(struct i2c_adapter *adap);      //删除i2c_adapter(该函数中注销了i2c_adapter      

 

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);  //注册              

void i2c_del_driver(struct i2c_driver *driver);  //删除i2c_driver(该函数中注销了i2c_driver

int i2c_add_driver(struct i2c_driver *driver);   //增加i2c_driver(该函数中注册了i2c_driver

              

int i2c_attach_client(struct i2c_client *client);

//依附i2c_client,当一个具体的client被侦测到被关联的时候,设备和sysfs文件将被注册(device_register(&client->dev))

int i2c_detach_client(struct i2c_client *client);

//脱离i2c_client,当一个具体的client被脱离的时候,sysfs文件和设备被注销(device_unregister(&client->dev))           

              

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

//i2c的传输(该函数用于进行I2C适配器和I2C设备之间的一组消息互传,该函数实际使用了i2c_algorithmmaster_xfer函数真正驱动硬件),numi2c_msg的个数

int i2c_master_send(struct i2c_client *client,const char *buf ,int count);

//i2c的发送(该函数会调用i2c_transfer来完成一条消息的写),buf为指向要发送数据的指针,count为要发送的数据的大小(单位为字节)

int i2c_master_recv(struct i2c_client *client, char *buf ,int count);     

//i2c的接收(该函数会调用i2c_transfer来完成一条消息的读)          

              

---------------------------------------------------------------------------------------------------------------

/*              

 *I2C总线驱动:

 *

 *加载模块的任务:

 *1:初始化I2C适配器所使用的硬件资源,如申请I/O地址,中断号等

 *2:初始化i2c_adapter数据结构成员

 *3:通过i2c_add_adapter添加i2c_adapter的数据结构

 *

 *卸载模块:

 *1:释放I2C适配器所使用的硬件资源,如释放I/O地址、中断号等

 *2:通过i2c_del_adapter删除i2c_adapter数据结构

 *

 *另外我们还需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithmmaster_xfer函数和functionality函数:

 *

 *1functionality函数非常坚持,用于返回algorithm所支持的协议,如I2C_FUNC_I2CI2C_FUNC_10BIT_ADDR等在i2c.h中定义

 *2master_xfer函数在i2c适配器上完成传输给它的i2c_msg数组中的每个I2C信息,其模板如下(当然也可以中断方式实现):

 */

static int i2c_adapter_xxx_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,  int num)

{

   .......

   for(i=0;i<num;i++)

      {

        i2c_adapter_xxx_start();     //产生开始位,需要根据具体硬件实现

        if(msgs[i]->flags&I2C_M_RD)  //判断是如果是读

           {

            i2c_adapter_xxx_setaddr((msgs->addr<<1)|1);   //发送从设备地址,需要根据具体硬件实现

            i2c_adapter_xxx_wait_ack();                   //等待应答信号,需要根据具体硬件实现

            i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);

            //读取长度为msgs[i]->len的数据到msgs[i]->buf,用于从设备接收一串数据,该函数内部也会涉及应答,需要根据具体硬件实现

           }

        else  //否则是写数据

          {

           i2c_adapter_xxx_setaddr(msgs->addr<<1);//发送从设备地址,需要根据具体硬件实现

           i2c_adapter_xxx_wait_ack();            //等待应答信号,需要根据具体硬件实现

           i2c_adapter_xxx_writebytes(msgs[i]->buf,msgs[i]->len);

           //写长度为msgs[i]->len的数据到msgs[i]->buf,用于向设备写入一串数据,该函数内部也会涉及应答,需要根据具体硬件实现

          }

      }

   i2c_adapter_xxx_stop();  //产生停止位,需要根据具体硬件实现

  }

 

//参考总线驱动:i2c-s3c2410.c

 

-------------------------------------------------------------------------------------------------------------

//I2C设备驱动:              

 

//1:I2C设备驱动要使用i2c_driveri2c_client数据结构并填充i2c_driver中的成员函数,i2c_client一般被包含在设备的私有信息结构体中,

//i2c_driver则合适被定义为全局变量并初始化,如下:

static struct i2c_driver xxx_driver = {

  .driver = {

    .name = "xxx",

    .owner = THIS_MODULE,

  },

  .probe = xxx_probe,

  .remove = __devexit_p(xxx_remove),

  .id_table = xxx_ids,

  .attach_adapter = xxx_attach_adapter,

  .detach_adapter = xxx_detach_adapter,

};              

//2:定义分配一个i2c_client数据结构并初始化它 ,例如:

  xxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);

  xxx_client->addr    = address;

  xxx_client->adapter = adapter;

  xxx_client->driver  = &xxx_driver;

  strcpy(xxx_client->name, "xxx");

  i2c_attach_client(xxx_client);

              

//2:通过i2c_add_driver函数添加i2c_driver(也即注册)

 

//3:通过i2c_del_driver函数删除i2c_driver(也即注销)

              

//4:定义和设置i2c_msg数组,最后调用i2c_transfer函数来传输数据,例如:

  msg[0].addr  = xxx_client->addr;  /* 目的 */

  msg[0].buf   = val;                   /*  */

  msg[0].len   = 2;                     /* 地址+数据=2 byte */

  msg[0].flags = 0;                     /* 表示写 */

 

  ret = i2c_transfer(xxx_client->adapter, msg, 1);

/*              

 *参考设备驱动:i2c-dev.c

 *关于i2c-dev.c 的说明:该文件可以被看作一个I2C设备驱动,不过,它实现了一个虚拟i2c_client是虚拟、临时的,

 *该文件实现了i2c_driver的成员函数已经文件操作接口,所以该文件的主体是“i2c_driver成员函数+字符设备驱动

 *但是很遗憾,大多数稍微复杂一点I2C设备的读写并不对应于一条消息,往往需要两条以上的消息来进行一次读写周期,

 *因此该文件下 i2cdev_readi2cdev_write函数不具备太强的通用性,对于两条以上消息组成的读写,在用户空间

 *需要组织i2c_msg消息数组并调用I2C_RDWRIOCTL命令(ioctl系统调用)            

 *

 *常用的IOCTL包括I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备ACK情况下重试次数,默认为1)、

 *I2C_TIMEOU(超时)、I2C_RDWR

 */                                                                                                                                                                                             

/*********************************************************end*****************************************************************/


【学习代码】
【模数ADC的Platform实现】

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <asm/uaccess.h>

#include <linux/platform_device.h>

#include <plat/adc.h>

#include <plat/regs-adc.h>

 

MODULE_LICENSE ("GPL");

 

int adc_major = 250;

int adc_minor = 0;

int number_of_devices = 1;

struct s3c_adc_client *client;

 

struct cdev cdev;

dev_t devno = 0;

 

static ssize_t adc_convert_read(struct file *file, char __user *buff, size_t count, loff_t *offset)

{

  unsigned data;

  unsigned ch;

  data = 10;

  ch = 0;

 

  /*

   * @brief         读取adc的采样值

   * @param[in]     client                      adc客户端(从系统注册过来)

   * @param[in]     ch                          读取的通道

   * @return        读取的采样值(电压值 = 采样值 / (2 ^ 量化位数) * 电压范围)

   */

  // int s3c_adc_read(struct s3c_adc_client *client, unsigned int ch)

 

  data = s3c_adc_read(client, ch);

  printk("data0 = %d\n", data);

 

  // 拷贝数据到用户空间

  if(copy_to_user(buff, (char *)&data, sizeof(data)))

    return -EFAULT;

 

  return 0;

}

 

static int adc_convert_open(struct inode *inode, struct file *file)

{

  return 0;

}

 

static int adc_convert_release(struct inode *inode, struct file *file)

{

  return 0;

}


static struct file_operations adc_convert_fops = {

  .owner  = THIS_MODULE,

  .read = adc_convert_read,

  .open   = adc_convert_open,

  .release  = adc_convert_release,

};


/*

 * @brief           探测函数,当设备和驱动匹配,由总线的probe函数调用

 * @param[in]       pdev                        平台设备(BSP)

 * @return          @li 0                       加载成功

 *                  @li < 0                     错误码

 * @notes           __devinit                   修饰probe为设备初始化函数

 */

int __devinit adc_convert_probe(struct platform_device *pdev)

{

  struct device *dev = &pdev->dev;

  int ret = -EINVAL;

 

  printk("function = %s\n", __func__);

 

  // 求设备号

  devno = MKDEV(adc_major, adc_minor);

 

  // 申请设备号

  ret = register_chrdev_region(devno, number_of_devices, "adc_convert");

  if( ret )

  {

    dev_err(dev, "failed to register device number\n");

    goto err_register_chrdev_region;

  }

 

  // 初始化字符设备

  cdev_init(&cdev, &adc_convert_fops);

  cdev.owner = THIS_MODULE;

 

  // 添加字符设备到内核

  ret = cdev_add(&cdev, devno, number_of_devices);

  if( ret )

  {

    dev_err(dev, "failed to add device\n");

    goto err_cdev_add;

  }

 

  // 1. 注册adc客户端

  /*

   * @brief       注册ADC设备(client)

   * @param[in]   pdev                          平台设备(BSP中添加)

   * @param[in]   select                        选择函数(一般使用默认)

   * @param[in]   conv                          转换函数(一般不使用,在外面转)

   * @param[in]   is_ts                         是否是触摸屏

   * @return      返回adc客户端                  三星的adc使用架构是client-->server

   *

  struct s3c_adc_client *s3c_adc_register(

       struct platform_device *pdev,

void (*select)(struct s3c_adc_client *client,    unsigned int selected),

void (*conv)(struct s3c_adc_client *client, unsigned d0,

    unsigned d1unsigned*samples_left),

unsigned int is_ts);

  */

  client = s3c_adc_register (pdev, NULL, NULL, 0);

  if(IS_ERR( client ))

  {

    dev_err(dev, "failed to register adc client\n");

    goto err_s3c_adc_register;

  }

 

  return 0;

 

err_s3c_adc_register:

  cdev_del( &cdev );

err_cdev_add:

  unregister_chrdev_region(devno, number_of_devices);

err_register_chrdev_region:

  return ret;

}

 

 

static int __devexit adc_convert_remove(struct platform_device *pdev)

{

  // 2. 注销

  /*

   * @brief       注销adc客户端

   * @param[in]   client                        注销的客户端

   */

  s3c_adc_release(client);

  cdev_del( &cdev );

  unregister_chrdev_region(devno, number_of_devices);

  return 0;

}

 

static struct platform_driver adc_convert_driver = {

  .driver = {

    .name   = "adc_convert",

    .owner  = THIS_MODULE,

  },

  .probe  = adc_convert_probe,

  .remove = __devexit_p(adc_convert_remove)

};

 

static int __init adc_convert_init (void)

{

  return platform_driver_register( &adc_convert_driver );

}

 

static void __exit adc_convert_exit (void)

{

  platform_driver_unregister( &adc_convert_driver );

}

 

module_init (adc_convert_init);

module_exit (adc_convert_exit);


【PWM的Platform实现】 

【驱动头文件pwm.h】

#ifndef __PWM_H__

#define __PWM_H__

 

#define BEEP_ON  _IO('k', 0)

#define BEEP_OFF _IO('k', 1)

#define SET_CNT  _IO('k', 2)

#define SET_PRE  _IO('k', 3)

 

#endif // __PWM_H__

【应用程序头文件pwm_music.h】

#ifndef __PWM_MUSIC_H__

#define __PWM_MUSIC_H__

 

#define BIG_D

 

#define PCLK (66750000)

 

typedef struct

{

  int pitch;

  int dimation;

}Note;

// 1      2   3    4      5       6       7

// C      D   E    F      G     A   B

//261.6256  293.6648   329.6276 349.2282   391.9954   440   493.8833

 

//C大调

#ifdef BIG_C

#define DO 262

#define RE 294

#define MI 330

#define FA 349

#define SOL 392

#define LA  440

#define SI  494

#define TIME 6000

#endif

 

 //D大调

#ifdef BIG_D

#define DO 293

#define RE 330

#define MI 370

#define FA 349

#define SOL 440

#define LA  494

#define SI  554

#define TIME 6000

#endif


Note MumIsTheBestInTheWorld[] =

{

  //6.          //_5     //3     //5 

  {LA, TIME + TIME / 2}, {SOL, TIME / 2}, {MI, TIME}, {SOL, TIME}, 

  //1^       //6_     //_5    //6-

  {DO * 2, TIME}, {LA, TIME / 2}, {SOL, TIME / 2} ,{LA, 2 * TIME},

  // 3      //5_    //_6       //5

  {MI, TIME}, {SOL, TIME / 2}, {LA, TIME / 2}, {SOL, TIME},

  // 3    //1_    //_6,

  {MI, TIME}, {DO, TIME / 2}, {LA / 2, TIME / 2},

  //5_    //_3    //2-       //2.

  {SOL, TIME / 2}, {MI, TIME / 2}, {RE, TIME * 2}, {RE, TIME + TIME / 2},

  //_3  //5     //5_      //_6

  {MI, TIME / 2}, {SOL, TIME}, {SOL, TIME / 2}, {LA, TIME / 2},

  // 3    //2     //1-      //5.

  {MI, TIME}, {RE, TIME}, {DO, TIME * 2}, {SOL, TIME + TIME / 2},

  //_3    //2_    //_1    //6,_

  {MI, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME / 2},

  //_1    //5,--

  {DO, TIME / 2}, {SOL / 2, TIME * 3}

 

};

 

Note GreatlyLongNow[] = {  

  // 2    3     3   3.        _2        1

  {RE, TIME}, {MI, TIME}, {MI, TIME}, {MI, TIME + TIME / 2}, {RE, TIME / 2}, {DO, TIME},

  //6,    1     2   1--       2     3     3

  {LA / 2, TIME}, {DO, TIME}, {RE, TIME}, {DO, TIME * 3}, {RE, TIME}, {MI, TIME}, {MI, TIME},

  //3.        _5      3     3     2     3

  {MI, TIME + TIME / 2}, {SOL, TIME / 2}, {MI, TIME}, {MI, TIME}, {RE, TIME}, {MI, TIME},

  //3--   5     6     6     6.        _5

  {MI, TIME * 3}, {SOL, TIME}, {LA, TIME}, {LA, TIME}, {LA, TIME + TIME / 2}, {SOL, TIME / 2},

  // 3    3   5       6   5---      2     3

  {MI, TIME}, {MI, TIME}, {SOL, TIME}, {LA, TIME}, {SOL, TIME * 3}, {RE, TIME}, {MI, TIME},

  // 3    2.        _3        3     2     3

  {MI, TIME}, {RE, TIME + TIME / 2}, {MI, TIME / 2}, {MI, TIME}, {RE, TIME}, {MI, TIME},

  //6,    1_        _6,       6,-

  {LA / 2, TIME}, {DO, TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 2},

  //2_    _2      2_        _1      6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  //2_    _2      2_        _1        6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  // 2    3   1     2.          _3      5

  {RE, TIME}, {MI, TIME}, {DO, TIME}, {RE,TIME + TIME / 2}, {MI, TIME / 2}, {SOL, TIME},

  //6_    _6        6_      _5      3

  {LA, TIME / 2}, {LA, TIME / 2}, {LA, TIME / 2}, {SOL, TIME / 2}, {MI, TIME},

  //2_    _2      2_        _1      6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  //6,    5,.           _6,      6,--

  {LA / 2, TIME}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 3},

  //2_    _2      2_        _1      6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  //2_    _2      2_        _1        6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  // 2    3   1     2.          _3      5

  {RE, TIME}, {MI, TIME}, {DO, TIME}, {RE, TIME + TIME / 2}, {MI, TIME / 2}, {SOL, TIME},

  //6_    _6        6_      _5      3

  {LA, TIME / 2}, {LA, TIME / 2}, {LA, TIME / 2}, {SOL, TIME / 2}, {MI, TIME},

  //2_    _2      2_        _1      6,

  {RE, TIME / 2}, {RE, TIME / 2}, {RE, TIME / 2}, {DO, TIME / 2}, {LA / 2, TIME},

  //6,    5,.           _6,      6,--

  {LA / 2, TIME}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {LA / 2, TIME * 3}

 

};

Note FishBoat[]={ //3.        _5      6._         =1^      6_

  {MI, TIME + TIME / 2}, {SOL, TIME / 2}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2},

  //_5      3 -.    2     1.       _3      2._

  {SOL, TIME / 2}, {MI, TIME * 3}, {RE, TIME}, {DO, TIME + TIME / 2}, {MI, TIME / 2},{RE, TIME / 2 + TIME / 4},

  //=3      2_      _1     2--      3.        _5

  {MI, TIME / 4}, {RE, TIME / 2}, {DO, TIME / 2}, {RE, TIME * 4}, {MI, TIME + TIME / 2}, {SOL, TIME / 2},

  // 2    1   6._         =1^       6_      _5

  {RE, TIME}, {DO, TIME}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2}, {SOL, TIME / 2},

  //6-     5,.          _6,     1._         =3

  {LA, TIME * 2}, {SOL / 2, TIME + TIME / 2}, {LA / 2, TIME / 2}, {DO, TIME / 2 + TIME / 4}, {MI, TIME / 4},

  //2_      _1     5,--

  {RE, TIME / 2}, {DO, TIME / 2}, {SOL / 2, TIME * 4},

  //3.        _5      6._         =1^     6_

  {MI, TIME + TIME / 2}, {SOL, TIME / 2}, {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2},

  //_5      3-.     5_      _6      1^_          _6

  {SOL, TIME / 2}, {MI, TIME * 3}, {SOL, TIME / 2}, {LA, TIME / 2}, {DO * 2, TIME + TIME / 2}, {LA, TIME / 2},

  //5._         =6      5_      _3      2--

  {SOL, TIME / 2 + TIME / 4}, {LA, TIME / 4}, {SOL, TIME / 2}, {MI, TIME / 2}, {RE, TIME * 4},

  //3.        _5      2._         =3      2_      _1

  {MI, TIME + TIME / 2}, {SOL, TIME / 2}, {RE, TIME / 2 + TIME / 4}, {MI, TIME / 4}, {RE, TIME / 2}, {DO, TIME / 2},

  //6._       =1^       6_      _5      6-      1.

  {LA, TIME / 2 + TIME / 4}, {DO * 2, TIME / 4}, {LA, TIME / 2}, {SOL, TIME / 2},{LA, TIME * 2}, {DO, TIME + TIME / 2},

  //_2       3_     _5          2_      _3      1--

  {RE, TIME / 2}, {MI, TIME / 2}, {SOL, TIME / 2}, {RE, TIME / 2}, {MI, TIME / 2}, {DO, TIME * 4}

};

#endif

【驱动主程序pwm_driver.c】

/*

 * 1. 使用platform总线上的字符设备框架

 * 2. 添加pwm操作(本身按照面向对象的来写)

 * 3. 添加并发处理

 */

#include <linux/init.h>

#include <linux/module.h>

#include <linux/platform_device.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/slab.h>

#include <asm/io.h>

#include <plat/regs-timer.h>

#include <mach/regs-gpio.h>

 

// 1. 信号量

#include <linux/semaphore.h>

 

#include "pwm.h"

 

struct pwm_t {

  // 设备类型(字符设备、块设备、网络设备)

  struct cdev cdev;

 

  // 设备号

  dev_t devno;

 

  // 设备类

  struct class *class;

 

  // 设备

  struct device *dev;

 

  // 设备本身特征

 

  // 信号量

   struct semaphore sem;

 

};

 

void pwm_init(struct pwm_t *pwm)

{

  unsigned long reg = 0;

 

  // 多功能管脚

  reg = readl(S5PC100_GPD_BASE);

  reg &= ~(0xF << 4);

  reg |= 0x2 << 4;

  writel(reg, S5PC100_GPD_BASE);

 

  // 清空分频器

  reg = readl(S3C2410_TCFG0);

  reg &= ~0xff;

  writel(reg, S3C2410_TCFG0);

 

  // 配置除法器为1

  reg = readl(S3C2410_TCFG1);

  reg &= ~(0xf << 4);

  reg |= (0x2 << 4);

  writel(reg, S3C2410_TCFG1);

 

  // 配置定时器

  writel(65, S3C2410_TCNTB(1));

  writel(65 / 2, S3C2410_TCMPB(1));

 

  // 手动更新

  reg = readl(S3C2410_TCON);

  reg |= (0x1 << 9);

  writel(reg, S3C2410_TCON);

  reg &= ~(0x1 << 9);

  writel(reg, S3C2410_TCON);

 

  // 开启

  reg = readl(S3C2410_TCON);

  reg &= ~(0xf << 8);

  reg |= (0x9 << 8);

  writel(reg, S3C2410_TCON);

}

 

void pwm_on(struct pwm_t *pwm)

{

  unsigned long reg = 0;

 

  reg = readl(S3C2410_TCON);

  reg &= ~(0xf << 8);

  reg |= (0x9 << 8);

  writel(reg, S3C2410_TCON);

}

 

void pwm_off(struct pwm_t *pwm)

{

  unsigned long reg = 0;

 

  reg = readl(S3C2410_TCON);

  reg &= ~(0xf << 8);

  writel(reg, S3C2410_TCON);

}

 

void set_cnt(struct pwm_t *pwm, unsigned long arg)

{

  printk("arg: %ld\n", arg);

  writel(arg, S3C2410_TCNTB(1));

  writel(arg / 2, S3C2410_TCMPB(1));

}

 

void set_pre(struct pwm_t *pwm, unsigned long arg)

{

  unsigned long reg = 0;

 

  reg = readl(S3C2410_TCFG0);

  reg &= ~0xff;

  reg |= (arg & 0xff);

  writel(reg, S3C2410_TCFG0);

}

 

int pwm_open(struct inode *inode, struct file *filp)

{

  struct pwm_t *pwm = container_of(inode->i_cdev, struct pwm_t, cdev);

  filp->private_data = pwm;

 

  printk("pwm: device open\n");

 

  // 3. 添加互斥访问

  if (down_interruptible(&pwm->sem)) {

    return -ERESTARTSYS;

  }

 

  pwm_init(pwm);

  set_pre(pwm, 255);

 

  up(&pwm->sem);

 

  return 0;

}

 

int pwm_release(struct inode *inode, struct file *filp)

{

  struct pwm_t *pwm = (struct pwm_t *)filp->private_data;

  printk("pwm: device close\n");

 

  // 3. 添加互斥访问

  if (down_interruptible(&pwm->sem)) {

    return -ERESTARTSYS;

  }

  pwm_off(pwm);

  up(&pwm->sem);

 

  return 0;

}

 

long pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

  int ret = 0;

  struct pwm_t *pwm = (struct pwm_t *)filp->private_data;

 

  // 3. 添加互斥访问

  if (down_interruptible(&pwm->sem)) {

    ret = -ERESTARTSYS;

    goto exit;

  }

 

  printk("pwm: device ioctl\n");

  switch(cmd)

  {

  case BEEP_ON:

    printk("pwm: BEEP ON\n");

    pwm_on(pwm);

    break;

   

  case BEEP_OFF:

    printk("pwm: BEEP OFF\n");

    pwm_off(pwm);

    break;

 

  case SET_CNT:

    printk("pwm: SET CNT\n");

    set_cnt(pwm, arg);

    break;

 

  case SET_PRE:

    printk("pwm: SET PRE\n");

    set_pre(pwm, arg);

    break;

   

  default:

    printk("pwm: available command\n");

    ret = -ENOTTY;

    break;

  }

  up(&pwm->sem);

 

exit:

  return ret;

}

 

struct file_operations fops =

{

  .owner = THIS_MODULE,

  .open  = pwm_open,

  .release = pwm_release,

  .unlocked_ioctl = pwm_ioctl,

};

 

int pwm_probe(struct platform_device *dev)

{

  int ret = 0;

  struct pwm_t *pwm;

  

  printk("platform: match ok!\n");

 

  pwm = (struct pwm_t*)kmalloc(sizeof(struct pwm_t), GFP_KERNEL);

  if (NULL == pwm) {

    ret = -ENOMEM;                         

    goto exit;

  }

  memset(pwm, 0, sizeof(struct pwm_t));

  platform_set_drvdata(dev, pwm);

 

  cdev_init(&pwm->cdev, &fops);

  pwm->cdev.owner = THIS_MODULE;

 

  // 只需要初始化一次的资源

  // 初始化硬件 

 

  // 初始化信号量

  sema_init(&pwm->sem, 1);

 

  ret = alloc_chrdev_region(&pwm->devno, 0, 1, "pwm device");

  if (ret) {

    goto alloc_chrdev_region_err;

  }

 

  ret = cdev_add(&pwm->cdev, pwm->devno, 1);

  if (ret) {

    goto cdev_add_err;

  }

 

  pwm->class = class_create(THIS_MODULE, "pwm");

  if (IS_ERR(pwm->class)) {

    ret = PTR_ERR(pwm->class);

    goto class_create_err;

  }

  

  pwm->dev = device_create(pwm->class, NULL, pwm->devno, NULL, "pwm");

  if (IS_ERR(pwm->dev)) {

    ret = PTR_ERR(pwm->dev);

    goto device_create_err;

  }

 

  goto exit;

 

device_create_err:

  class_destroy(pwm->class);

class_create_err:

  cdev_del(&pwm->cdev);

cdev_add_err:

  unregister_chrdev_region(pwm->devno, 1); 

alloc_chrdev_region_err:

  kfree(pwm);

exit:

  return ret;

}

 

int pwm_remove(struct platform_device *dev)

{

  struct pwm_t *pwm = (struct pwm_t *)platform_get_drvdata(dev);

   

  printk("platform: driver remove\n");

 

  device_destroy(pwm->class, pwm->devno);

  class_destroy(pwm->class);

  cdev_del(&pwm->cdev);

  unregister_chrdev_region(pwm->devno, 1);

  kfree(pwm);

  return 0;

}

 

struct platform_driver pwm_driver = {

  .probe = pwm_probe,

  .remove = __devexit_p(pwm_remove),

  .driver = {

  .name = "pwm_device",

  },

};

 

int __init pwm_module_init(void)

{

  return platform_driver_register(&pwm_driver);

}

 

void __exit pwm_module_exit(void)

{

  platform_driver_unregister(&pwm_driver);

}

 

module_init(pwm_module_init);

module_exit(pwm_module_exit);

 

MODULE_LICENSE("GPL"); 

【应用主程序pwm_music.c】

/*

 * main.c : test demo driver

 */

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/ioctl.h>

#include "pwm_music.h"

#include "pwm.h"

 

int main()

{

  int i = 0;

  int n = 2;

  int fd;

  int div;

 

  fd = open("/dev/pwm", O_RDWR | O_NONBLOCK);

  if (fd == -1) {

    perror("open");

    exit(1);

  }

  ioctl(fd, SET_PRE, 255);

 

  for (= 0; i < sizeof(MumIsTheBestInTheWorld) / sizeof(Note); i++)

  {

    div = (PCLK / 256 / 16) / (MumIsTheBestInTheWorld[i].pitch);

    ioctl(fd, SET_CNT, div);

    usleep(MumIsTheBestInTheWorld[i].dimation * 100);

  }

 

  for (= 0; i < sizeof(GreatlyLongNow) / sizeof(Note); i++)

  {

    div = (PCLK / 256 / 16) / (GreatlyLongNow[i].pitch);

    ioctl(fd, SET_CNT, div);

    usleep(GreatlyLongNow[i].dimation * 100);

  }

 

  for (= 0; i < sizeof(FishBoat) / sizeof(Note); i++)

  {

    div = (PCLK / 256 / 16) / (FishBoat[i].pitch);

    ioctl(fd, SET_CNT, div);

    usleep(FishBoat[i].dimation * 100);

  }

  return 0;

}


注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值