I2C适配器驱动

i2c_adapter的定义

  • i2c适配器用结构体struct i2c_adapter来表示;
  • 适配器对应一个I2C总线(soc上的一个i2c控制器),如果soc上有多个I2C总线,那么内核就必须用多个struct i2c_adapter来表示。
  • 内核总有多个适配器,就需要用一定的数据结构来组织和管理。
  • struct i2c_adapter的定义如下:



    class -- 表示适配器的类型有:

  2C_CLASS_HWMON -- 硬件
  I2C_CLASS_DDC --  DDC bus
  I2C_CLASS_SPD   -- 内存模块

algo -- 表示访问总线的方法
nr -- 一般用来表示总线的编号

i2c_algorithm的定义

  • struct i2c_adapter结构中的成员algo是真正实现数据传输的方法集合,其定义如下:


master_xfer //指向传输数据的函数
smbus_xfer //smbus协议的函数
functionality //用于返回该适配器的功能

传输信息的定义

  • i2c_algorithm结构的成员master_xfer()是实现的数据传输的函数,这里数据使用struct i2c_msg 来表示的:


i2c适配器的组织管理

  • 内核中的i2c_adapter是通过一种叫做idr的数据结构来组织管理的。
    idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制
    例如,内核中的i2c_adapter->nr成员表示适配的ID,这个ID与对应的i2c_adapter结构体指针管理,
    通过这个ID可以获取对用的i2c_adapter结构体。
    在文件i2c-core.c中有idr的定义如下:



    定义了一个名为i2c_adapter_idr的数据结构
    往i2c_adapter_idr插入一个关联数据的函数是:
    i2c_add_adapter() --由内核觉得ID
    i2c_add_numbered_adapter() --用户自定ID

idr数据结构的操作方法如下

定义一个idr数据结构
#define DEFINE_IDR(name)
为idr分配内存
int idr_pre_get(struct idr *idp, unsigned int gfp_mask);
分配ID号并将ID号和指针关联
int idr_get_new(struct idr *idp, void *ptr, int *id);
int idr_get_new_above(struct idr *idp, void *ptr, int start_id, int *id);
通过ID号搜索对应的指针
void *idr_find(struct idr *idp, int id);
删除ID
void idr_remove(struct idr *idp, int id);

编写i2c适配器驱动

  • 分配并初始化一个i2c_adapter结构体变量,主要初始化成员:name,class,algo,nr等成员
  • 然后使用下面的函数注册:
    如果没有指定nr(nr == -1),用i2c_add_adapter()
    如果指定nr,用i2c_add_numbered_adapter()
  • 而i2c_adapter驱动成主要要实现的是algo成员的
    master_xfer()
    functionality()

4412 i2c收发流程


代码实战

#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of_i2c.h>
#include <linux/of_gpio.h>

#include <asm/irq.h>

#include <plat/iic.h>
#include <plat/gpio-cfg.h>
#include <mach/map.h>

#define TINY4412_IICCON         0x00
#define TINY4412_IICSTAT            0x04
#define TINY4412_IICADD         0x08
#define TINY4412_IICDS          0x0C

#define TINY4412_IICCON_ACKEN       (1 << 7)
#define TINY4412_IICCON_TXDIV_16        (0 << 6)
#define TINY4412_IICCON_TXDIV_512   (1 << 6)
#define TINY4412_IICCON_IRQEN       (1 << 5)
#define TINY4412_IICCON_IRQPEND     (1 << 4)
#define TINY4412_IICCON_SCALE(x)        ((x) & 0xf)
#define TINY4412_IICCON_SCALEMASK   (0xf)

#define TINY4412_IICSTAT_MASTER_RX  (2 << 6)
#define TINY4412_IICSTAT_MASTER_TX  (3 << 6)
#define TINY4412_IICSTAT_SLAVE_RX   (0 << 6)
#define TINY4412_IICSTAT_SLAVE_TX   (1 << 6)
#define TINY4412_IICSTAT_MODEMASK   (3 << 6)

#define TINY4412_IICSTAT_START      (1 << 5)
#define TINY4412_IICSTAT_BUSBUSY        (1 << 5)
#define TINY4412_IICSTAT_TXRXEN     (1 << 4)
#define TINY4412_IICSTAT_ARBITR     (1 << 3)
#define TINY4412_IICSTAT_ASSLAVE        (1 << 2)
#define TINY4412_IICSTAT_ADDR0      (1 << 1)
#define TINY4412_IICSTAT_LASTBIT        (1 << 0)

enum tiny4412_i2c_state {
    STATE_IDLE,
    STATE_START,
    STATE_READ,
    STATE_WRITE,
    STATE_STOP
};

struct tiny4412_i2c {
    wait_queue_head_t       wait;
    struct i2c_msg          *msg;
    unsigned int                msg_num;
    unsigned int                msg_idx;
    unsigned int                msg_ptr;
    enum tiny4412_i2c_state state;
    void __iomem                *regs;
    struct clk                  *clk;
    struct device               *dev;
    struct i2c_adapter          adap;

}*i2c;

static inline void tiny4412_i2c_disable_ack(struct tiny4412_i2c *i2c)
{
    unsigned long tmp;
    tmp = readl(i2c->regs + TINY4412_IICCON);
    writel(tmp & ~TINY4412_IICCON_ACKEN, i2c->regs + TINY4412_IICCON);
}

static inline void tiny4412_i2c_enable_ack(struct tiny4412_i2c *i2c)
{
    unsigned long tmp;
    tmp = readl(i2c->regs + TINY4412_IICCON);
    writel(tmp | TINY4412_IICCON_ACKEN, i2c->regs + TINY4412_IICCON);
}

static inline void tiny4412_i2c_disable_irq(struct tiny4412_i2c *i2c)
{
    unsigned long tmp;
    tmp = readl(i2c->regs + TINY4412_IICCON);
    writel(tmp & ~TINY4412_IICCON_IRQEN, i2c->regs + TINY4412_IICCON);
}

static inline void tiny4412_i2c_enable_irq(struct tiny4412_i2c *i2c)
{
    unsigned long tmp;
    tmp = readl(i2c->regs + TINY4412_IICCON);
    writel(tmp | TINY4412_IICCON_IRQEN, i2c->regs + TINY4412_IICCON);
}

static inline void tiny4412_i2c_master_complete(struct tiny4412_i2c *i2c, int ret)
{
    i2c->msg_ptr = 0;
    i2c->msg = NULL;
    i2c->msg_idx++;
    i2c->msg_num = 0;
    if (ret)
        i2c->msg_idx = ret;
    wake_up(&i2c->wait);
}

static void tiny4412_i2c_message_start(struct tiny4412_i2c *i2c,struct i2c_msg *msg)
{
    unsigned int addr = (msg->addr & 0x7f) << 1;
    unsigned long stat;
    unsigned long iiccon;

    stat = 0;
    stat |=  TINY4412_IICSTAT_TXRXEN;

    if (msg->flags & I2C_M_RD) {
        stat |= TINY4412_IICSTAT_MASTER_RX;
        addr |= 1;
    } else
        stat |= TINY4412_IICSTAT_MASTER_TX;

    if (msg->flags & I2C_M_REV_DIR_ADDR)
        addr ^= 1;

    tiny4412_i2c_enable_ack(i2c);

    iiccon = readl(i2c->regs + TINY4412_IICCON);
    writel(stat, i2c->regs + TINY4412_IICSTAT);
    writeb(addr, i2c->regs + TINY4412_IICDS);
    ndelay(50);
    
    writel(iiccon, i2c->regs + TINY4412_IICCON);
    
    stat |= TINY4412_IICSTAT_START;
    writel(stat, i2c->regs + TINY4412_IICSTAT);
}

static inline void tiny4412_i2c_stop(struct tiny4412_i2c *i2c, int ret)
{
    unsigned long iicstat = readl(i2c->regs + TINY4412_IICSTAT);

    iicstat &= ~TINY4412_IICSTAT_START;

    writel(iicstat, i2c->regs + TINY4412_IICSTAT);

    i2c->state = STATE_STOP;

    tiny4412_i2c_master_complete(i2c, ret);
    tiny4412_i2c_disable_irq(i2c);
}

static inline int is_lastmsg(struct tiny4412_i2c *i2c)
{
    return i2c->msg_idx >= (i2c->msg_num - 1);
}

static inline int is_msglast(struct tiny4412_i2c *i2c)
{
    if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1)
        return 0;

    return i2c->msg_ptr == i2c->msg->len-1;
}

static inline int is_msgend(struct tiny4412_i2c *i2c)
{
    return i2c->msg_ptr >= i2c->msg->len;
}

static int i2c_irq_nextbyte(struct tiny4412_i2c *i2c, unsigned long iicstat)
{
    unsigned long tmp;
    unsigned char byte;
    int ret = 0;

    switch (i2c->state) {

    case STATE_IDLE:
        goto out;

    case STATE_STOP:
        tiny4412_i2c_disable_irq(i2c);
        goto out_ack;

    case STATE_START:
        if (iicstat & TINY4412_IICSTAT_LASTBIT &&
            !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
            tiny4412_i2c_stop(i2c, -ENXIO);
            goto out_ack;
        }

        if (i2c->msg->flags & I2C_M_RD)
            i2c->state = STATE_READ;
        else
            i2c->state = STATE_WRITE;
        if (is_lastmsg(i2c) && i2c->msg->len == 0) {
            tiny4412_i2c_stop(i2c, 0);
            goto out_ack;
        }

        if (i2c->state == STATE_READ)
            goto prepare_read;

    case STATE_WRITE:

        if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
            if (iicstat & TINY4412_IICSTAT_LASTBIT) {
                tiny4412_i2c_stop(i2c, -ECONNREFUSED);
                goto out_ack;
            }
        }

 retry_write:

        if (!is_msgend(i2c)) {
            byte = i2c->msg->buf[i2c->msg_ptr++];
            writeb(byte, i2c->regs + TINY4412_IICDS);
            ndelay(50);

        } else if (!is_lastmsg(i2c)) {
            i2c->msg_ptr = 0;
            i2c->msg_idx++;
            i2c->msg++;
            if (i2c->msg->flags & I2C_M_NOSTART) {

                if (i2c->msg->flags & I2C_M_RD) {
                    tiny4412_i2c_stop(i2c, -EINVAL);
                }

                goto retry_write;
            } else {
                tiny4412_i2c_message_start(i2c, i2c->msg);
                i2c->state = STATE_START;
            }

        } else {
            tiny4412_i2c_stop(i2c, 0);
        }
        break;

    case STATE_READ:
        byte = readb(i2c->regs + TINY4412_IICDS);
        i2c->msg->buf[i2c->msg_ptr++] = byte;
        if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1)
            i2c->msg->len += byte;
 prepare_read:
        if (is_msglast(i2c)) {
            if (is_lastmsg(i2c))
                tiny4412_i2c_disable_ack(i2c);

        } else if (is_msgend(i2c)) {
            if (is_lastmsg(i2c)) {
                tiny4412_i2c_stop(i2c, 0);
            } else {
                i2c->msg_ptr = 0;
                i2c->msg_idx++;
                i2c->msg++;
            }
        }
        break;
    }
 out_ack:
    tmp = readl(i2c->regs + TINY4412_IICCON);
    tmp &= ~TINY4412_IICCON_IRQPEND;
    writel(tmp, i2c->regs + TINY4412_IICCON);
 out:
    return ret;
}

static irqreturn_t tiny4412_i2c_irq(int irqno, void *dev_id)
{
    struct tiny4412_i2c *i2c = dev_id;
    unsigned long status;
    unsigned long tmp;

    status = readl(i2c->regs + TINY4412_IICSTAT);


    if (i2c->state == STATE_IDLE) {
        tmp = readl(i2c->regs + TINY4412_IICCON);
        tmp &= ~TINY4412_IICCON_IRQPEND;
        writel(tmp, i2c->regs +  TINY4412_IICCON);
        goto out;
    }

    i2c_irq_nextbyte(i2c, status);

 out:
    return IRQ_HANDLED;
}

static int tiny4412_i2c_wait_busy(struct tiny4412_i2c *i2c)
{
    unsigned long iicstat;
    int timeout = 400;

    while (timeout-- > 0) {
        iicstat = readl(i2c->regs + TINY4412_IICSTAT);

        if (!(iicstat & TINY4412_IICSTAT_BUSBUSY))
            return 0;

        msleep(1);
    }

    return -ETIMEDOUT;
}

static int tiny4412_i2c_doxfer(struct tiny4412_i2c *i2c, struct i2c_msg *msgs, int num)
{
    int ret;

    tiny4412_i2c_wait_busy(i2c);

    i2c->msg     = msgs;
    i2c->msg_num = num;
    i2c->msg_ptr = 0;
    i2c->msg_idx = 0;
    i2c->state   = STATE_START;

    tiny4412_i2c_enable_irq(i2c);
    tiny4412_i2c_message_start(i2c, msgs);

    wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

    ret = i2c->msg_idx;
    
    return ret;
}


static int tiny4412_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
{
    struct tiny4412_i2c *i2c = (struct tiny4412_i2c *)adap->algo_data;
    int ret;

    clk_enable(i2c->clk);
    ret = tiny4412_i2c_doxfer(i2c, msgs, num);
    clk_disable(i2c->clk);
    
    return ret;
}

static u32 tiny4412_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |
        I2C_FUNC_PROTOCOL_MANGLING;
}

static const struct i2c_algorithm tiny4412_i2c_algorithm = {
    .master_xfer        = tiny4412_i2c_xfer,
    .functionality      = tiny4412_i2c_func,
};


static int tiny4412_i2c_init(struct tiny4412_i2c *i2c)
{
    unsigned long iicon = TINY4412_IICCON_IRQEN |TINY4412_IICCON_TXDIV_512| TINY4412_IICCON_ACKEN;
    s3c_gpio_cfgall_range(EXYNOS4_GPD1(0), 2, S3C_GPIO_SFN(2), S3C_GPIO_PULL_UP);
    writeb(0x10, i2c->regs + TINY4412_IICADD);
    writel(iicon, i2c->regs + TINY4412_IICCON);
    return 0;
}

static int __init i2c_adap_init(void)
{
    i2c = kzalloc(sizeof(struct tiny4412_i2c), GFP_KERNEL);

    /*初始化adapter*/
    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    i2c->adap.owner   = THIS_MODULE;
    i2c->adap.algo    =&tiny4412_i2c_algorithm;     
    i2c->adap.class   = I2C_CLASS_HWMON|I2C_CLASS_SPD;
    i2c->adap.nr = 0;
    
    init_waitqueue_head(&i2c->wait);

    i2c->clk = clk_get_sys("s3c2440-i2c.0", "i2c"); 
    clk_enable(i2c->clk);

    i2c->regs = ioremap(S3C_PA_IIC, SZ_4K);

    i2c->adap.algo_data = i2c;

    tiny4412_i2c_init(i2c);

    request_irq(IRQ_IIC, tiny4412_i2c_irq, 0, "tiny4412", i2c);

    i2c_add_numbered_adapter(&i2c->adap);//注册函数
    
    return 0;

}

static void __exit i2c_adap_exit(void)
{
    i2c_del_adapter(&i2c->adap);
    free_irq(IRQ_IIC, i2c);
    clk_disable(i2c->clk);
    clk_put(i2c->clk);
    iounmap(i2c->regs);
}

module_init(i2c_adap_init);
module_exit(i2c_adap_exit);
MODULE_LICENSE("GPL");
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容

  • 简介 I2C驱动由I2C核心,I2C总线驱动和I2C设备驱动组成.I2C核心是I2C总线驱动和I2C设备驱动的中间...
    傀儡世界阅读 1,113评论 0 1
  • 一、前言 I2C总线 是一种常用的总线协议,在设备中经常看到,比如 sensor、陀螺仪等都是使用 I2C总线。而...
    wipping的技术小栈阅读 3,017评论 0 3
  • I2C总线 I2C(又称IIC)总线是由PHILIPS公司开发的串行总线,用于连接微控制器与外围设备,特点如下总线...
    CodeDog阅读 1,958评论 0 5
  • 一,前言 MPU6500六轴陀螺仪linux驱动(spi&i2c合并)--Apple的学习笔记[https://w...
    applecai阅读 1,082评论 0 0
  • Linux i2c system I2C总线是由PHILIPS公司开发的两线式串行总线,每个连接到总线的器件都可以...
    Creator_Ly阅读 1,823评论 0 8