zynq pl i2c控制器驱动程序

明确一下关系:

adapter--对应的是soc上实际的iic控制器;

struct i2c_adapter {

struct module *owner;              //所属模块

unsigned int id;                //algorithm的类型,定义于i2c-id.h,

unsigned int class;

const struct i2c_algorithm *algo;     //总线通信方法结构体指针

void *algo_data;             //algorithm数据

struct rt_mutex bus_lock;        //控制并发访问的自旋锁

int timeout;

int retries;                //重试次数

struct device dev;             //适配器设备

int nr;         //存放在i2c_adapter_idr里的位置号

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

struct completion dev_released;    //用于同步

struct list_head userspace_clients; //client链表头

};

驱动工程师通过使用i2c_register_adapter函数,向内核注册一个iic控制器。被注册到内核的iic控制器下面又可以挂接iic设备(例如eeprom,电源管理芯片等)。

 

xiic_i2c 包含了xilinx iic驱动程序操作的

struct xiic_i2c {

    struct device       *dev;

    void __iomem        *base;

    wait_queue_head_t   wait;

    struct i2c_adapter  adap;

    struct i2c_msg      *tx_msg;

    struct mutex        lock;

    unsigned int        tx_pos;

    unsigned int        nmsgs;

    enum xilinx_i2c_state   state;

    struct i2c_msg      *rx_msg;

    int         rx_pos;

    enum xiic_endian    endianness;

    struct clk *clk;

};

1、设备指针;

2、内存基地址;

3、等待队列头,实现阻塞操作使用,后面介绍;

4、iic适配器;

5、msg内容指针;

6、互斥锁;

7、当前发送的位置;

8、消息数量;

9、状态;

10、大小端

11、时钟

 

在probe函数中:

1、

i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);

是具有资源管理的 kzalloc() ,使用资源管理(resource-managed)函数分配的内存,是会与所属设备相关联。当设备从系统中分离或者设备驱动被卸载,该内存会被自动释放,也可以通过 devm_kfree() 来释放内存。同理 socket_kzalloc() 等等。

2、

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//得到物理地址(from devicetree)

i2c->base = devm_ioremap_resource(&pdev->dev, res);//转换成虚拟地址

3、

irq = platform_get_irq(pdev, 0);//获取中断号 (from device tree)

4、pdata = dev_get_platdata(&pdev->dev);//获取私有数据

5、添加iic控制器到iic总线?

/* hook up driver to tree */

    platform_set_drvdata(pdev, i2c);

    i2c->adap = xiic_adapter;

    i2c_set_adapdata(&i2c->adap, i2c);

    i2c->adap.dev.parent = &pdev->dev;

    i2c->adap.dev.of_node = pdev->dev.of_node;

6、初始化互斥锁和等待队列;

互斥锁在一个进程拿到该锁时,其他线程如果试图获取该锁就会进入休眠状态。

等待队列时进入等待队列的进程会进入休眠状态,直到其他进程或者中断服务函数调用了 weak_up_waitqueue();

7、i2c->clk = devm_clk_get(&pdev->dev, NULL);//向系统申请IIC时钟资源

8、ret = clk_prepare_enable(i2c->clk);//使能时钟

9、i2c->dev = &pdev->dev;

10、电源管理相关:

pm_runtime_enable(i2c->dev);

    pm_runtime_set_autosuspend_delay(i2c->dev, XIIC_PM_TIMEOUT);

    pm_runtime_use_autosuspend(i2c->dev);

    pm_runtime_set_active(i2c->dev);

11、 ret = devm_request_threaded_irq(&pdev>dev, irq, xiic_isr,xiic_process, IRQF_ONESHOT,pdev->name, i2c);//注册中断服务函数。

linux中,中断处理机制分为上半部和下半部。上半部是直接在中断服务函数中执行的代码段,下半部主要有以下几种机制1、tasklet 2、workqueue 3、中断线程化 4、软irq

怎么区分它们呢?tasklet和软irq是一个级别的,在执行代码中不能有sleep。一般用于执行完top handler就立即执行的情况。workqueue和线程化中断相当于开启一个线程,在线程中完成中断处理。

12、xiic_reinit(i2c);

13、ret = i2c_add_adapter(&i2c->adap);/* add i2c adapter to i2c tree */

14、  for (i = 0; i < pdata->num_devices; i++)i2c_new_device(&i2c- >adap, pdata->devices + i);/* add in known devices to the bus */

添加已经连接在总线上的设备到IIC BUS;

虽然整个probe主要有14个函数,但是基本内容是:

1、分配设备结构体

2、获取硬件内存地址资源;

3、获取CPU中断资源,注册中断服务函数;

4、初始化锁,等待队列;

5、把IIC控制器加入到内核的IIC BUS中;

 

上面说过i2c_adapter,对应的一个具体的iic控制器,这里需要实例化一个i2c_adapter。然后注册到内核中。

static struct i2c_adapter xiic_adapter = {

    .owner = THIS_MODULE,

    .name = DRIVER_NAME,

    .class = I2C_CLASS_DEPRECATED,

    .algo = &xiic_algorithm,

};

在i2c_adapter 中,主要是.algo成员,该成员实现了iic控制器数据传输的方法;

static const struct i2c_algorithm xiic_algorithm = {

    .master_xfer = xiic_xfer,

    .functionality = xiic_func,

};

主要来看:

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

1、struct xiic_i2c *i2c = i2c_get_adapdata(adap); //获取到iic资源

2、发送数据:i2c->tx_msg = msgs; i2c->nmsgs = num; xiic_start_xfer(i2c);

接收数据呢? 看下之前注册的中断handler

static irqreturn_t xiic_isr(int irq, void *dev_id)

{

    struct xiic_i2c *i2c = dev_id;

    u32 pend, isr, ier;

    irqreturn_t ret = IRQ_NONE;

    /* Do not processes a devices interrupts if the device has no

     * interrupts pending

     */

    dev_dbg(i2c->adap.dev.parent, "%s entry\n", __func__);

    isr = xiic_getreg32(i2c, XIIC_IISR_OFFSET);

    ier = xiic_getreg32(i2c, XIIC_IIER_OFFSET);

    pend = isr & ier;

    if (pend)

        ret = IRQ_WAKE_THREAD;

    return ret;

}

注意,在注册中断服务函数的时候,我们注册的线程化中断。因此,当中断发生时,会先执行中断服务函数,执行完中断服务函数以后,就会在线程中断中,处理线程化中断。

1、mutex_lock(&i2c->lock);首先,先把临界资源锁住;

2、isr = xiic_getreg32(i2c, XIIC_IISR_OFFSET);

   ier = xiic_getreg32(i2c, XIIC_IIER_OFFSET);

   pend = isr & ier;

读取中断状态寄存器,下面将判断是接收中断?错误中断?发送中断?

3、if ((pend & XIIC_INTR_ARB_LOST_MASK) ||((pend & XIIC_INTR_TX_ERROR_MASK) &&

  !(pend & XIIC_INTR_RX_FULL_MASK)));

错误中断,重启控制器; 

4、if (pend & XIIC_INTR_RX_FULL_MASK) //接收中断

在接收中断中,先判断i2c->rx_msg是否已经被分配过,接下来就是调用接收函数对iic控制器fifo中的数据进行读取。

其余的就是一些fifo状态的判断等。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux内核与驱动,底层驱动,字符设备)