linux i2c架构

i2c架构图

i2c控制器注册

i2c控制器通过适配器来描述,即一个i2c控制器对应一个i2c适配器,一个i2c控制器也对应着一条i2c总线。

1.分配私有结构体struct rk3x_i2c

2.填充struct i2c_adapter,调用i2c_add_adapter注册adpater设备。对于非smbus功能控制器,控制器收发功能通过master_xfer控制。

static const struct i2c_algorithm rk3x_i2c_algorithm = {

    .master_xfer        = rk3x_i2c_xfer,

    .functionality      = rk3x_i2c_func,

};

......

    strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));

    i2c->adap.owner = THIS_MODULE;

    i2c->adap.algo = &rk3x_i2c_algorithm;

    i2c->adap.retries = 3;

    i2c->adap.dev.of_node = np;

    i2c->adap.algo_data = i2c;

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

......

i2c_add_adapter:
1.adapter->nr未赋值则分配后注册,若已赋值则按已赋值id进行注册。
2.of_i2c_register_devices注册挂在i2c总线下的i2c设备(设备树实例化i2c_client)。3.i2c_scan_static_board_info扫描__i2c_board_list注册属于该总线下的i2c设备(i2c_register_board_info方式实例化i2c_client)。
4.bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)扫描i2c总线上的驱动,通过驱动提供的探测函数探测总线下是否存在设备,若存在注册i2c设备(驱动探测i2c设备实例化i2c_client)

/*

 * i2c_adapter is the structure used to identify a physical i2c bus along

 * with the access algorithms necessary to access it.

 */

struct i2c_adapter {

    struct module *owner;

    unsigned int class;       /* classes to allow probing for */

    const struct i2c_algorithm *algo; /* the algorithm to access the bus */

    void *algo_data;

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

    const struct i2c_lock_operations *lock_ops;

    struct rt_mutex bus_lock;

    struct rt_mutex mux_lock;

    int timeout;    //默认HZ        /* in jiffies */

    int retries;        //重试次数

    struct device dev;      /* the adapter device */

    int nr;

    char name[48];

    struct completion dev_released;

    struct mutex userspace_clients_lock;

    struct list_head userspace_clients;

    struct i2c_bus_recovery_info *bus_recovery_info;

    const struct i2c_adapter_quirks *quirks;

    struct irq_domain *host_notify_domain;

};

i2c设备注册

instantiate an i2c device,i2c设备的注册过程是通过实例化一个i2c_client进行的。

/**

 * struct i2c_client - represent an I2C slave device

 * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;

 *  I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking

 * @addr: Address used on the I2C bus connected to the parent adapter.

 * @name: Indicates the type of the device, usually a chip name that's

 *  generic enough to hide second-sourcing and compatible revisions.

 * @adapter: manages the bus segment hosting this I2C device

 * @dev: Driver model device node for the slave.

 * @irq: indicates the IRQ generated by this device (if any)

 * @detected: member of an i2c_driver.clients list or i2c-core's

 *  userspace_devices list

 * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter

 *  calls it to pass on slave events to the slave driver.

 *

 * An i2c_client identifies a single device (i.e. chip) connected to an

 * i2c bus. The behaviour exposed to Linux is defined by the driver

 * managing the device.

 */

struct i2c_client {

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

    unsigned short addr;        /* chip address - NOTE: 7bit    */

                    /* addresses are stored in the  */

                    /* _LOWER_ 7 bits       */

    char name[I2C_NAME_SIZE];

    struct i2c_adapter *adapter;    /* the adapter we sit on    */

    struct device dev;      /* the device structure     */

    int init_irq;           /* irq set at initialization    */

    int irq;            /* irq issued by device     */

    struct list_head detected;

#if IS_ENABLED(CONFIG_I2C_SLAVE)

    i2c_slave_cb_t slave_cb;    /* callback for slave mode  */

#endif

};

一、实例化i2c_client的几种方法(最终调用的都是i2c_new_device(4.19版本)/i2c_new_client_device(5.10版本))

1.设备树,通过在对应的i2c总线下创建i2c设备节点。

2.调用i2c_register_board_info将i2c设备信息挂载到__i2c_board_list链表上,后续adapter注册会扫描__i2c_board_list上的i2c设备信息进行实例化i2c设备

1) i2c_register_board_info定义I2C器件信息(Name,Address,etc.

static struct i2c_board_info __initdata pi2c_board_info[] = {

       {

              I2C_BOARD_INFO("max1586", 0x14),

              .platform_data = &max1587a_info,

       },

};

i2c_register_board_info(1, ARRAY_AND_SIZE(pi2c_board_info));

/* 1表示该I2C设备挂在I2C-1上 ,注册I2C adapt时相应的id = 1 */

3.显示调用i2c_new_device(4.19版本)/i2c_new_client_device(5.10版本)实例化i2c_client

struct i2c_board_info ser_info = {

        .of_node = rxport->remote_of_node,

    };

......

    ser_info.addr = rxport->ser_alias;

    i2c_new_client_device(priv->client->adapter, &ser_info);

......

4.驱动提供探测函数主动探测adapter上的i2c设备,探测到相应设备则进行实例化(smbus类i2c设备才有此功能,i2c驱动注册或i2c适配器注册时会调用bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter)去处理)

static struct i2c_driver lm90_driver = {

    .class      = I2C_CLASS_HWMON,

    .driver = {

        .name   = "lm90",

        .of_match_table = of_match_ptr(lm90_of_match),

    },

    .probe      = lm90_probe,

    .alert      = lm90_alert,

    .id_table   = lm90_id,

    .detect     = lm90_detect,

    .address_list   = normal_i2c,

};

5.用户空间实例化

1)创建 i2c 设备

echo i2c_test 0x48 > /sys/bus/i2c/devices/i2c-6/new_device

2)删除设备

echo 0x48 > /sys/bus/i2c/devices/i2c-6/delete_device

二、相关i2c_client实例化接口(i2c设备注册)

i2c_new_device(4.19版本)/i2c_new_client_device(5.10)

struct i2c_client *i2c_new_dummy(struct i2c_adapter *adapter, u16 address)(4.9)//info.type="dummy"

struct i2c_client *i2c_new_dummy_device(struct i2c_adapter *adapter, u16 address)(5.10)

struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,

              struct i2c_board_info *info,

              unsigned short const *addr_list,

              int (*probe)(struct i2c_adapter *, unsigned short addr))(内核4.9版本)

struct i2c_client *i2c_new_scanned_device(struct i2c_adapter *adap,

               struct i2c_board_info *info,

               unsigned short const *addr_list,

               int (*probe)(struct i2c_adapter *adap, unsigned short addr))(内核5.10)

i2c驱动注册

一、驱动注册方法

1.module_i2c_driver注册

static struct i2c_driver ds90ub953_driver = {

    .probe_new  = ub953_probe,

    .remove     = ub953_remove,

    .id_table   = ub953_id,

    .driver = {

        .name   = "ds90ub953",

        .owner = THIS_MODULE,

        .of_match_table = of_match_ptr(ub953_dt_ids),

    },

};

module_i2c_driver(ds90ub953_driver);

2.编写module_init函数,手动调用i2c_add_driver

static struct i2c_driver techpoint_i2c_driver = {

    .driver = {

           .name = TECHPOINT_NAME,

           .pm = &techpoint_pm_ops,

           .of_match_table = of_match_ptr(techpoint_of_match),

            },

    .probe = &techpoint_probe,

    .remove = &techpoint_remove,

    .id_table = techpoint_match_id,

};

static int __init sensor_mod_init(void)

{

    return i2c_add_driver(&techpoint_i2c_driver);

}

static void __exit sensor_mod_exit(void)

{

    i2c_del_driver(&techpoint_i2c_driver);

}

device_initcall_sync(sensor_mod_init);

module_exit(sensor_mod_exit);

二、实例化i2c_client与driver匹配过程

1.driver_register和device_register最终都会调用driver_match_device,

static inline int driver_match_device(struct device_driver *drv,

                      struct device *dev)

{

    return drv->bus->match ? drv->bus->match(dev, drv) : 1;

}

2.i2c_bus_type的match函数为i2c_device_match,有三个匹配过程,任何一个成功即认为匹配成功
a.i2c_of_match_device//设备树匹配
b.acpi_driver_match_device //ACPI style match 
c.i2c_match_id//driver.id_table.name跟client.name比较

i2c内核层i2c通信方法

1.regmap方式访问

struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,

                      const struct regmap_config *config,

                      struct lock_class_key *lock_key,

                      const char *lock_name);

#define devm_regmap_init_i2c(i2c, config)               \

    __regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config,   \

                i2c, config)

2.调用封装之后的接口

//发起单个struct i2c_msg传输,读数据,rk356x adapter驱动单个读会发i2c启动写,把reg地址写成0x00,然后再i2c启动读,tda4直接启动读。

static inline int i2c_master_recv(const struct i2c_client *client,

                  char *buf, int count)//从client中取地址,取adapter

static inline int i2c_master_send(const struct i2c_client *client,

                  const char *buf, int count)//发起单个struct i2c_msg传输,发送数据

3.直接调用底层收发接口

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,

            int num)//发起多个struct i2c_msg传输,读数据或者发数据

i2c控制器做slave 

在i2c控制器支持slave模式得情况下,系统可以做为slave被外部master访问

一、设备树节点修改方法

i2c slave设备实例化需要info->flags |= I2C_CLIENT_SLAVE

struct i2c_board_info info;

......

info->flags |= I2C_CLIENT_SLAVE;

......

 client = i2c_new_device(adap, &info);

所以以设备树方式实例化i2c client,reg i2C地址bit30置1,of_i2c_get_board_info时会判断reg bit 30是否置1,置1才会info->flags |= I2C_CLIENT_SLAVE

&i2c1 {

status = "okay";

pinctrl-0 = <&i2c1_xfer>;

slave-testunit: i2cslave@40000044 {

compatible = "slave-testunit";

reg = <0x40000044>;

二、adapter注册方式

i2c控制器如果支持作为从设备,则需要adap.algo支持reg_slave,在i2c slave设备驱动程序中会调用i2c_slave_register,该函数会调用algo->reg_slave

static const struct i2c_algorithm cdns_i2c_algo = {

    .master_xfer    = cdns_i2c_master_xfer,

    .functionality  = cdns_i2c_func,

#if IS_ENABLED(CONFIG_I2C_SLAVE)

    .reg_slave  = cdns_reg_slave,

    .unreg_slave    = cdns_unreg_slave,

#endif

};

三、soc作为i2c从设备驱动

1、CONFIG_I2C_SLAVE打开,内核框架涉及文件drivers/i2c//i2c-core-slave.c

2、测试驱动见drivers/i2c//i2c-slave-testunit.c

3、详细过程

驱动注册函数

int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb)

i2c_slave_register解析:

1、检查i2c client是否是slave client,地址是否合法,algo->reg_slave函数是否存在

2、client->slave_cb = slave_cb;

3、client->adapter->algo->reg_slave(client);

reg_slave函数:

1、判断是否已经控制器绑定了slave client,不做重复绑定

    struct cdns_i2c *id = container_of(slave->adapter, struct cdns_i2c,

                                    adap);

    if (id->slave)

        return -EBUSY;

2、slave client存入i2c 控制器私有结构体

/* Store slave information */

    id->slave = slave;

3、i2c控制器切换成i2c slave模式 ,slave client的addr写入控制器地址寄存器,使能收发中断

    case CDNS_I2C_MODE_SLAVE:

        /* Enable i2c slave */

        cdns_i2c_writereg(id->ctrl_reg_diva_divb &

                  CDNS_I2C_CR_SLAVE_EN_MASK,

                  CDNS_I2C_CR_OFFSET);

        /* Setting slave address */

        cdns_i2c_writereg(id->slave->addr & CDNS_I2C_ADDR_MASK,

                  CDNS_I2C_ADDR_OFFSET);

        /* Enable slave send/receive interrupts */

        cdns_i2c_writereg(CDNS_I2C_IXR_SLAVE_INTR_MASK,

                  CDNS_I2C_IER_OFFSET);

        break;

slave_cb函数:

1、i2c控制器驱动注册的中断函数会调用i2c_slave_event

static void cdns_i2c_slave_rcv_data(struct cdns_i2c *id)

{

    u8 bytes;

    unsigned char data;

    /* Prepare backend for data reception */

    if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {

        id->slave_state = CDNS_I2C_SLAVE_STATE_RECV;

        i2c_slave_event(id->slave, I2C_SLAVE_WRITE_REQUESTED, NULL);

    }

    /* Fetch number of bytes to receive */

    bytes = cdns_i2c_readreg(CDNS_I2C_XFER_SIZE_OFFSET);

    /* Read data and send to backend */

    while (bytes--) {

        data = cdns_i2c_readreg(CDNS_I2C_DATA_OFFSET);

        i2c_slave_event(id->slave, I2C_SLAVE_WRITE_RECEIVED, &data);

    }

}

static void cdns_i2c_slave_send_data(struct cdns_i2c *id)

{

    u8 data;

    /* Prepare backend for data transmission */

    if (id->slave_state == CDNS_I2C_SLAVE_STATE_IDLE) {

        id->slave_state = CDNS_I2C_SLAVE_STATE_SEND;

        i2c_slave_event(id->slave, I2C_SLAVE_READ_REQUESTED, &data);

    } else {

        i2c_slave_event(id->slave, I2C_SLAVE_READ_PROCESSED, &data);

    }

    /* Send data over bus */

    cdns_i2c_writereg(data, CDNS_I2C_DATA_OFFSET);

}

/**

 * cdns_i2c_slave_isr - Interrupt handler for the I2C device in slave role

 * @ptr:       Pointer to I2C device private data

 *

 * This function handles the data interrupt and transfer complete interrupt of

 * the I2C device in slave role.

 *

 * Return: IRQ_HANDLED always

 */

static irqreturn_t cdns_i2c_slave_isr(void *ptr)

{

    struct cdns_i2c *id = ptr;

    unsigned int isr_status, i2c_status;

    /* Fetch the interrupt status */

    isr_status = cdns_i2c_readreg(CDNS_I2C_ISR_OFFSET);

    cdns_i2c_writereg(isr_status, CDNS_I2C_ISR_OFFSET);

    /* Ignore masked interrupts */

    isr_status &= ~cdns_i2c_readreg(CDNS_I2C_IMR_OFFSET);

    /* Fetch transfer mode (send/receive) */

    i2c_status = cdns_i2c_readreg(CDNS_I2C_SR_OFFSET);

    /* Handle data send/receive */

    if (i2c_status & CDNS_I2C_SR_RXRW) {

        /* Send data to master */

        if (isr_status & CDNS_I2C_IXR_DATA)

            cdns_i2c_slave_send_data(id);

        if (isr_status & CDNS_I2C_IXR_COMP) {

            id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;

            i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);

        }

    } else {

        /* Receive data from master */

        if (isr_status & CDNS_I2C_IXR_DATA)

            cdns_i2c_slave_rcv_data(id);

        if (isr_status & CDNS_I2C_IXR_COMP) {

            cdns_i2c_slave_rcv_data(id);

            id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;

            i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);

        }

    }

    /* Master indicated xfer stop or fifo underflow/overflow */

    if (isr_status & (CDNS_I2C_IXR_NACK | CDNS_I2C_IXR_RX_OVF |

              CDNS_I2C_IXR_RX_UNF | CDNS_I2C_IXR_TX_OVF)) {

        id->slave_state = CDNS_I2C_SLAVE_STATE_IDLE;

        i2c_slave_event(id->slave, I2C_SLAVE_STOP, NULL);

        cdns_i2c_writereg(CDNS_I2C_CR_CLR_FIFO, CDNS_I2C_CR_OFFSET);

    }

    return IRQ_HANDLED;

}

enum i2c_slave_event {

    I2C_SLAVE_READ_REQUESTED,

    I2C_SLAVE_WRITE_REQUESTED,

    I2C_SLAVE_READ_PROCESSED,

    I2C_SLAVE_WRITE_RECEIVED,

    I2C_SLAVE_STOP,

};

int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb);

int i2c_slave_unregister(struct i2c_client *client);

bool i2c_detect_slave_mode(struct device *dev);

static inline int i2c_slave_event(struct i2c_client *client,

                  enum i2c_slave_event event, u8 *val)

{

    return client->slave_cb(client, event, val);

}

2、slave i2c设备驱动根据收到的event做相应的处理

static int i2c_slave_testunit_slave_cb(struct i2c_client *client,

                     enum i2c_slave_event event, u8 *val)

{

    struct testunit_data *tu = i2c_get_clientdata(client);

    int ret = 0;

    switch (event) {

    case I2C_SLAVE_WRITE_RECEIVED:

        if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags))

            return -EBUSY;

        if (tu->reg_idx < TU_NUM_REGS)

            tu->regs[tu->reg_idx] = *val;

        else

            ret = -EMSGSIZE;

        if (tu->reg_idx <= TU_NUM_REGS)

            tu->reg_idx++;

        /* TU_REG_CMD always written at this point */

        if (tu->regs[TU_REG_CMD] >= TU_NUM_CMDS)

            ret = -EINVAL;

        break;

    case I2C_SLAVE_STOP:

        if (tu->reg_idx == TU_NUM_REGS) {

            set_bit(TU_FLAG_IN_PROCESS, &tu->flags);

            queue_delayed_work(system_long_wq, &tu->worker,

                       msecs_to_jiffies(10 * tu->regs[TU_REG_DELAY]));

        }

        fallthrough;

    case I2C_SLAVE_WRITE_REQUESTED:

        tu->reg_idx = 0;

        break;

    case I2C_SLAVE_READ_REQUESTED:

    case I2C_SLAVE_READ_PROCESSED:

        *val = TU_CUR_VERSION;

        break;

    }

    return ret;

}

i2c master应用层操作方法

上层应用通过调用i2c_dev.c的驱动操作i2c

两种方法

1.open之后调用ioctl(fd, I2C_RDWR, &i2c_msg)进行读写

2.open之后调用ioctl(fd, I2C_SLAVE_FORCE, address),然后调用read/write进行读写

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>

#define I2C_DEVICE "/dev/i2c-1" // I2C设备文件路径
#define I2C_ADDRESS 0x50        // I2C设备地址

// 使用ioctl函数i2c读数据
int i2c_read(int fd, unsigned char slave_addr, unsigned char reg_addr)
{
    unsigned char data[1];
    struct i2c_rdwr_ioctl_data i2c_msg;
    struct i2c_msg messages[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        [1] = {
            .addr = slave_addr,
            .flags = I2C_M_RD,
            .len = sizeof(data),
            .buf = data,
        },
    };
    i2c_msg.msgs = messages;
    i2c_msg.nmsgs = 2;

    int ret = ioctl(fd, I2C_RDWR, &i2c_msg);
    if (ret < 0)
    {
        printf("I2C read error\n");
        return ret;
    }
    return data[0];
}

// 使用ioctl函数i2c写数据
void i2c_write(int fd, unsigned char slave_addr, int reg_addr, unsigned char *data, int len)
{
    unsigned char buf[256];
    struct i2c_rdwr_ioctl_data i2c_msg;
    struct i2c_msg messages[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = len + 1,
            .buf = buf,
        },
    };
    buf[0] = reg_addr;
    memcpy(&buf[1], data, len);
    i2c_msg.msgs = messages;
    i2c_msg.nmsgs = 1;

    int ret = ioctl(fd, I2C_RDWR, &i2c_msg);
    if (ret < 0)
    {
        printf("I2C write error\n");
    }
}

// 使用read和write驱动i2c通信
void i2c_kernel_write(int fd, int reg_addr, unsigned char data)
{
    unsigned char wr_data[2];
    wr_data[0] = reg_addr;
    wr_data[1] = data;

    int ret = write(fd, wr_data, sizeof(wr_data));
    if (ret < 0)
    {
        printf("I2C write error\n");
    }
}
int i2c_kernel_read(int fd, unsigned char reg_addr)
{
    int ret;
    unsigned char rd_data[1];
    rd_data[0] = reg_addr;

    write(fd, rd_data, sizeof(rd_data));
    read(fd, rd_data, sizeof(rd_data));

    printf("I2C read data: %x\n", rd_data[0]);

    ret = rd_data[0];

    return ret;
}

int main(int argc, char *argv[])
{
    int fd;
    int REG_VALUE;
    fd = open("I2C_DEVICE", O_RDWR);
    if (fd < 0)
    {
        printf("Failed to open I2C device\n");
        return -1;
    }
    i2c_write(fd, I2C_ADDRESS, 0x00, 0x55, 1);

    REG_VALUE = i2c_read(fd, I2C_ADDRESS, 0x00);

    printf("REG_VALUE: %d\n", REG_VALUE);

    // 使用read和write驱动i2c通信
    // ioctl(fd,I2C_SLAVE_FORCE,I2C_ADDRESS);
    //  i2c_kernel_write(fd, 0x00, 0x55);
    //  REG_VALUE = i2c_kernel_read(fd, 0x00);
    //  printf("REG_VALUE: %d\n", REG_VALUE);

    close(fd);

    return 0;
}
 

参考文章

Linux下的I2C通信_linux iic通信-CSDN博客

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值