当前位置:网站首页>I2C 子系统(三):I2C Driver
I2C 子系统(三):I2C Driver
2022-07-03 02:46:00 【嵌入式Linux系统开发】
I2C driver 分四个部分来写
I2C SW Architecture
I2C Data Structure
I2C Register Flow
I2C Data Transfer
文章以 MTK 平台为例,code 来源于小米开源项目,小米每做一个手机项目,都会将 kernel 部分开源,因为必须遵循 GPL 协议
https://github.com/MiCode/Xiaomi_Kernel_OpenSource
I2C driver 源码目录
/kernel-4.14/drivers/i2c/i2c-core-base.c //Linux common 驱动
/kernel-4.14/drivers/i2c/i2c-core.h
/kernel-4.14/include/linux/i2c.h
/kernel-4.14/drivers/i2c/busses/i2c-mt65xx.c //i2c 控制器驱动
/kernel-4.14/arch/arm64/boot/dts/
demo
/kernel-4.14/drivers/input/touchscreen/
1、I2C SW Architecture
【driver 驱动层】由普通驱动工程师负责,【i2c 核心层】由 Linux 提供,【i2c 核心层】以下由芯片原厂负责。
I2C 子系统通过 i2c-core 将 i2c 设备驱动和 i2c 总线驱动进行了分离,从而使得 i2c 设备驱动中不用关心 i2c 总线传输细节,专注于 i2c 设备逻辑的实现。
抽象如下:
I2C 总线驱动重点是 I2C 适配器(控制器)驱动,这里用到两个重要的数据结构:i2c_adapter和i2c_algorithm。其中,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,i2c_algorithm 只是一些传输的实现函数合集。
在 Linux 系统中会出现如下节点:
2、I2C Data Structure
我们要搞懂一个 Linux 子系统,必须研究它的数据结构,搞懂每个结构体存储了什么东西,才能梳理清该子系统的架构。
I2C 子系统有几个主要的结构体:
I2C 控制器端:i2c_adapter、i2c_algorithm、mtk_i2c
I2C 设备驱动端:i2c_client、i2c_driver
传输端:i2c_msg
i2c_adapter:i2c-core 层描述一个 I2C 控制器,假如一个芯片有 8 路 I2C bus,则有 8 个 i2c_adapter
struct i2c_adapter {
struct module *owner;
unsigned int class; /* 该 I2C bus 支持哪些类型的从设备 */
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;/* 超过该时间无法重发 */
int retries;/* I2C发送失败重试次数 */
struct device dev; /* the adapter device */
unsigned long locked_flags; /* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED 0
#define I2C_ALF_SUSPEND_REPORTED 1
int nr;/*I2C bus id*/
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;
struct regulator *bus_regulator;
};
i2c_algorithm:I2C 传输函数合集,其中 master_xfer 是真正的传输函数,芯片原厂写 I2C 控制器驱动时必须实现。functionality 函数会返回该 I2C 控制器支持什么通信协议,也需要实现,其他的函数即便 Linux 规定了,芯片原厂也可以不实现。
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*master_xfer_atomic)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
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 *adap);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
MTK 只实现了其中两个
i2c_client:描述设备信息的
struct i2c_client {
unsigned short flags;/* I2C 传输标志位如下*/
#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */
#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address */
/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */
#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol */
/* Must match I2C_M_STOP|IGNORE_NAK */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;/* 所处的那一路 I2C bus */
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
void *devres_group_id; /* ID of probe devres group */
};
i2c_driver:普通驱动工程师写驱动时,必须实现其中的 probe 函数和 remove 函数。
struct i2c_driver {
unsigned int class;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
int (*probe_new)(struct i2c_client *client);
void (*shutdown)(struct i2c_client *client);
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
const unsigned short *address_list;
struct list_head clients;
};
mtk_i2c:MTK 平台用该结构体表示 I2C 控制器,定义在/kernel-4.14/drivers/i2c/busses/i2c-mt65xx.c
struct mtk_i2c {
struct i2c_adapter adap; /* i2c host adapter */
struct device *dev;
struct completion msg_complete;
/* set in i2c probe */
void __iomem *base; /* i2c base addr */
void __iomem *pdmabase; /* dma base address*/
struct clk *clk_main; /* main clock for i2c bus */
struct clk *clk_dma; /* DMA clock for i2c via DMA */
struct clk *clk_pmic; /* PMIC clock for i2c from PMIC */
bool have_pmic; /* can use i2c pins from PMIC */
bool use_push_pull; /* IO config push-pull mode */
u16 irq_stat; /* interrupt status */
unsigned int clk_src_div;
unsigned int speed_hz; /* The speed in transfer */
enum mtk_trans_op op;
u16 timing_reg;
u16 high_speed_reg;
unsigned char auto_restart;
bool ignore_restart_irq;
const struct mtk_i2c_compatible *dev_comp;
};
i2c_msg:I2C 读写必须填充 i2c_msg,写为 0 ,读为 I2C_M_RD,其他的 flag 大家可以参考
struct i2c_msg {
__u16 addr;
__u16 flags;
#define I2C_M_RD 0x0001 /* guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* use only if I2C_FUNC_10BIT_ADDR */
#define I2C_M_DMA_SAFE 0x0200 /* use only in kernel space */
#define I2C_M_RECV_LEN 0x0400 /* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */
#define I2C_M_NO_RD_ACK 0x0800 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* use only if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len;//无符号16位,65536个byte,一次 I2C 传输最大 64KB
__u8 *buf;
};
I2C_M_NO_RD_ACK:忽略所有 ACK/NACK 一直读
I2C_M_IGNORE_NAK:忽略所有的 NACK 继续读
I2C_M_NOSTART:没有 START 信号
3、I2C Register Flow
/kernel-5.10/drivers/i2c/i2c-core-base.c 是 I2C 的核心部分,I2C 核心提供了一些与具体硬件无关的 API 函数
1、i2c_adapter 注册/注销函数
int i2c_add_adapter(struct i2c_adapter *adapter)//自动分配 adapter ID
int i2c_add_numbered_adapter(struct i2c_adapter *adap)//指定 ID
void i2c_del_adapter(struct i2c_adapter * adap)
2、i2c_driver 注册/注销函数
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver(struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
上述 API 一般需要在 init/exit 或者 probe/remove 函数中成对使用。
设备和驱动的匹配过程也是由 I2C 总线完成的,I2C 总线的数据结构为 i2c_bus_type,定义在 /kernel-5.10/drivers/i2c/i2c-core-base.c 文件,i2c_bus_type 内容如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C 设备和驱动匹配。
acpi_driver_match_device 函数用于 ACPI 形式的匹配。
i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。
控制器驱动
I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter 和 i2c_algorithm。其中,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter。
对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 I2C 设备进行通信的方法。
I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter 或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter。
I2C 控制器驱动加载
设备树 mt6885.dts
驱动
驱动和设备树匹配以后,probe 函数开始执行,重要的地方博主都进行了注释,不重要的部分进行了删除。
static int mt_i2c_probe(struct platform_device *pdev)
{
int ret = 0;
struct mt_i2c *i2c; //控制器结构体
unsigned int clk_src_in_hz;
struct resource *res;
const struct of_device_id *of_id;
//申请内存
i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);
//获取设备树节点
ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c);
//从设备树获取 I2C 控制器寄存器物理基地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//进行内存映射,得到 Linux 内核使用的虚拟地址
i2c->base = devm_ioremap_resource(&pdev->dev, res);
.....
//获取中断号
i2c->irqnr = platform_get_irq(pdev, 0);
init_waitqueue_head(&i2c->wait);
//请求中断,中断服务函数为 mt_i2c_irq
ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
IRQF_NO_SUSPEND | IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);
of_id = of_match_node(mtk_i2c_of_match, pdev->dev.of_node);
//填充 adapter 结构体各个参数
i2c->dev_comp = of_id->data;
i2c->i2c_pll_info = &i2c_pll_info;
i2c->adap.dev.of_node = pdev->dev.of_node;
i2c->dev = &i2c->adap.dev;
i2c->adap.dev.parent = &pdev->dev;
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &mt_i2c_algorithm;
i2c->adap.algo_data = NULL;
i2c->adap.timeout = 2 * HZ;
i2c->adap.retries = 1;
i2c->adap.nr = i2c->id;
spin_lock_init(&i2c->cg_lock);
......
mutex_init(&i2c->i2c_mutex);
ret = i2c_set_speed(i2c, clk_src_in_hz);
ret = mt_i2c_clock_enable(i2c);
mt_i2c_init_hw(i2c);
mt_i2c_clock_disable(i2c);
// DMA 相关
if (i2c->ch_offset_default)
i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
(PAGE_SIZE * 2), &i2c->dma_buf.paddr, GFP_KERNEL);
else
i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);
if (i2c->dma_buf.vaddr == NULL) {
dev_info(&pdev->dev, "dma_alloc_coherent fail\n");
return -ENOMEM;
}
i2c_set_adapdata(&i2c->adap, i2c);
//向 Linux 内核注册 i2c_adapter
ret = i2c_add_numbered_adapter(&i2c->adap);
platform_set_drvdata(pdev, i2c);
return 0;
}
节点创建
i2c_add_adapter、i2c_add_numbered_adapter 是注册 i2c_adapter,这两个 API 最终是调用 i2c_register_adapter。
static int i2c_register_adapter(struct i2c_adapter *adap)
{
......
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
/* create pre-declared device nodes */
of_i2c_register_devices(adap);
i2c_acpi_install_space_handler(adap);
i2c_acpi_register_devices(adap);
.....
}
dev_set_name(&adap->dev, “i2c-%d”, adap->nr);会在Linux 中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6 节点。
of_i2c_register_devices 会调用 i2c_new_client_device
struct i2c_client *i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
......
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = of_node_get(info->of_node);
client->dev.fwnode = info->fwnode;
device_enable_async_suspend(&client->dev);
i2c_dev_set_name(adap, client, info);
......
status = device_register(&client->dev);
......
}
i2c_dev_set_name 会在 Linux 系统中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6/6-0058 ,这里是挂在某个 i2c bus 上的,从机地址为 0x58 的设备节点。
到这里,大家应该能看懂下图,这些节点在 I2C 控制器驱动加载时,自动创建的。大家在 I2C 控制器的子节点中声明的 I2C 从机设备,也会在这一步创建节点。
设备驱动
I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver。i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容。
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。
i2c_driver 中包含 probe 函数和 device_driver 结构体。如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
当 I2C 设备和驱动匹配以后,probe 函数就会执行。
因此,对于 Linux 来讲,不区分 I2C 控制器和 I2C 从机设备的不同,用的都是同一套东西,即【总线、设备、驱动】框架,都有 probe 函数。
设备树
注意,I2C 设备在设备树中必须挂到对应的总线下,如图是在 &i2c0 下。
驱动
当驱动和设备匹配时,probe 函数开始执行,重要的地方博主都写了注释
static int goodix_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
struct goodix_ts_device *ts_device = NULL;
struct goodix_ts_board_data *ts_bdata = NULL;
int r = 0;
//检查 I2C 控制器支持是否支持标准 I2C 协议
r = i2c_check_functionality(client->adapter,I2C_FUNC_I2C);
/* 板级信息分配内存 */
ts_bdata = devm_kzalloc(&client->dev,sizeof(struct goodix_ts_board_data), GFP_KERNEL);
ts_device = devm_kzalloc(&client->dev,sizeof(struct goodix_ts_device), GFP_KERNEL);
//填充结构体元素
/* use pinctrl in core.c */
ts_bdata->pinctrl_dev = client->adapter->dev.parent;
ts_device->name = "GT9886 TouchDevcie";
ts_device->dev = &client->dev;
ts_device->board_data = ts_bdata;
ts_device->hw_ops = &hw_i2c_ops;
touch_filter_bdata = ts_bdata;
/* ts core device */
goodix_pdev = kzalloc(sizeof(struct platform_device), GFP_KERNEL);
//填充结构体元素
goodix_pdev->name = GOODIX_CORE_DRIVER_NAME;
goodix_pdev->id = 0;
goodix_pdev->num_resources = 0;
goodix_pdev->dev.platform_data = ts_device;
goodix_pdev->dev.release = goodix_pdev_release;
r = platform_device_register(goodix_pdev);
/* register platform driver*/
r = goodix_ts_core_init();
......
}
I2C 从机设备驱动中的 probe 函数和器件强相关,各家撰写方式不一样。
在 I2C 设备驱动中,除了 init、exit、probe、remove 函数外,还要实现 read、write 函数才行。
I2C 写
I2C spec 规定如下
详细解析如下
对从机进行写操作时,主设备发出开始标志 (S) 和写地址 (从机地址加一个 R/W 位,1 为读,0 为写)。从机产生应答信号。然后主设备开始传送寄存器地址 (RA),接到应答后,开始传送寄存器数据,然后仍然要有应答信号,连续写入多字节时依次推。
举例(例子进行了两次封装,便于调用)
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
/* 初始化AP3216C */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
return 0;
}
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* ap3216c地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
I2C 读
I2C 先写后读
其实所谓的 I2C 读,都是先写后读。
I2C spec 规定如下
详细解析如下(详细看图)
对从机进行读操作时,主设备发出开始标志 (S) 和读地址 (从机地址加一个 R/W 位,1 为读,0 为写)。等待从机产生应答信号。然后发送寄存器地址,告诉从机读哪一个寄存器。紧接着,收到应答信号后,主设备再发一个开始信号,然后发送从设备读地址。从机产生应答信号并开始发送寄存器数据。通信以主设备产生的拒绝应答信号 (NACK) 和结束标志 § 结束。
举例(例子进行了两次封装)
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i =0;
unsigned char buf[6];
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
}
}
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
}
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ap3216c地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
4、I2C Data Transfer
I2C 数据传输主要有三个 API
int i2c_master_send(const struct i2c_client *client,const char *buf,int count)
client:I2C 设备对应的 i2c_client。
buf:要发送的数据。
count:要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
int i2c_master_recv(const struct i2c_client *client,char *buf,int count)
client:I2C 设备对应的 i2c_client。
buf:要接收的数据。
count:要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。
i2c_master_send 和 i2c_master_recv 都是对 i2c_transfer 的封装。因此我们重点研究 i2c_transfer。
其中,adap->algo->master_xfer 由芯片原厂提供。在 MTK 平台,是 mtk_i2c_transfer 函数,不同平台命名不同。
static int mtk_i2c_transfer(struct i2c_adapter *adap,struct i2c_msg msgs[], int num)
{
int ret;
int left_num = num;
struct mtk_i2c *i2c = i2c_get_adapdata(adap);
//打开时钟
ret = mtk_i2c_clock_enable(i2c);
if (ret)
return ret;
//初始化硬件
mtk_i2c_init_hw(i2c);
i2c->auto_restart = i2c->dev_comp->auto_restart;
if (i2c->auto_restart && num == 2) {
if (!(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD) &&
msgs[0].addr == msgs[1].addr) {
i2c->auto_restart = 0;
}
}
if (i2c->auto_restart && num >= 2 && i2c->speed_hz > MAX_FS_MODE_SPEED)
i2c->ignore_restart_irq = true;
else
i2c->ignore_restart_irq = false;
while (left_num--) {
if (!msgs->buf) {
dev_dbg(i2c->dev, "data buffer is NULL.\n");
ret = -EINVAL;
goto err_exit;
}
if (msgs->flags & I2C_M_RD)
i2c->op = I2C_MASTER_RD;
else
i2c->op = I2C_MASTER_WR;
if (!i2c->auto_restart) {
if (num > 1) {
/* combined two messages into one transaction */
i2c->op = I2C_MASTER_WRRD;
left_num--;
}
}
/* always use DMA mode. */
ret = mtk_i2c_do_transfer(i2c, msgs, num, left_num);
if (ret < 0)
goto err_exit;
msgs++;
}
/* the return value is number of executed messages */
ret = num;
err_exit:
mtk_i2c_clock_disable(i2c);
return ret;
}
mtk_i2c_transfer 再调用 mtk_i2c_do_transfer,这一步就是最底层的 I2C 传输了,大量操作寄存器。
该函数和芯片底层强相关,是最底层的实现,一般情况下不会修改。重要的地方博主放了注释。
static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs,int num, int left_num)
{
u16 addr_reg;
u16 start_reg;
u16 control_reg;
u16 restart_flag = 0;
u32 reg_4g_mode;
u8 *w_buf = NULL;
u8 *r_buf = NULL;
dma_addr_t rpaddr = 0;
dma_addr_t wpaddr = 0;
int ret;
i2c->irq_stat = 0;
if (i2c->auto_restart)
restart_flag = I2C_RS_TRANSFER;
reinit_completion(&i2c->msg_complete);
control_reg = readw(i2c->base + OFFSET_CONTROL) &
~(I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS);
if ((i2c->speed_hz > 400000) || (left_num >= 1))
control_reg |= I2C_CONTROL_RS;
if (i2c->op == I2C_MASTER_WRRD)
control_reg |= I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS;
writew(control_reg, i2c->base + OFFSET_CONTROL);
/* 设置开始条件 */
if (i2c->speed_hz <= 100000)
writew(I2C_ST_START_CON, i2c->base + OFFSET_EXT_CONF);
else
writew(I2C_FS_START_CON, i2c->base + OFFSET_EXT_CONF);
addr_reg = i2c_8bit_addr_from_msg(msgs);
writew(addr_reg, i2c->base + OFFSET_SLAVE_ADDR);
/* 清中断 */
writew(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_STAT);
writew(I2C_FIFO_ADDR_CLR, i2c->base + OFFSET_FIFO_ADDR_CLR);
/* 使能中断 */
writew(restart_flag | I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_MASK);
/* 设置 transfer and transaction len */
if (i2c->op == I2C_MASTER_WRRD) {
if (i2c->dev_comp->aux_len_reg) {
writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN);
writew((msgs + 1)->len, i2c->base +
OFFSET_TRANSFER_LEN_AUX);
} else {
writew(msgs->len | ((msgs + 1)->len) << 8,
i2c->base + OFFSET_TRANSFER_LEN);
}
writew(I2C_WRRD_TRANAC_VALUE, i2c->base + OFFSET_TRANSAC_LEN);
} else {
writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN);
writew(num, i2c->base + OFFSET_TRANSAC_LEN);
}
/* 准备 buffer data to start transfer */
if (i2c->op == I2C_MASTER_RD) {
writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG);
writel(I2C_DMA_CON_RX, i2c->pdmabase + OFFSET_CON);
r_buf = kzalloc(msgs->len, GFP_KERNEL);
if (r_buf == NULL)
return -ENOMEM;
rpaddr = dma_map_single(i2c->dev, r_buf,
msgs->len, DMA_FROM_DEVICE);
if (dma_mapping_error(i2c->dev, rpaddr)) {
kfree(r_buf);
return -ENOMEM;
}
if (i2c->dev_comp->support_33bits) {
reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr);
writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE);
}
writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR);
writel(msgs->len, i2c->pdmabase + OFFSET_RX_LEN);
} else if (i2c->op == I2C_MASTER_WR) {
writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG);
writel(I2C_DMA_CON_TX, i2c->pdmabase + OFFSET_CON);
w_buf = kzalloc(msgs->len, GFP_KERNEL);
if (w_buf == NULL)
return -ENOMEM;
memcpy(w_buf, msgs->buf, msgs->len);
wpaddr = dma_map_single(i2c->dev, w_buf,
msgs->len, DMA_TO_DEVICE);
if (dma_mapping_error(i2c->dev, wpaddr)) {
kfree(w_buf);
return -ENOMEM;
}
if (i2c->dev_comp->support_33bits) {
reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr);
writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE);
}
writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR);
writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN);
} else {
writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_INT_FLAG);
writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_CON);
w_buf = kzalloc(msgs->len, GFP_KERNEL);
if (w_buf == NULL)
return -ENOMEM;
r_buf = kzalloc((msgs + 1)->len, GFP_KERNEL);
if (r_buf == NULL) {
kfree(w_buf);
return -ENOMEM;
}
memcpy(w_buf, msgs->buf, msgs->len);
wpaddr = dma_map_single(i2c->dev, w_buf,
msgs->len, DMA_TO_DEVICE);
if (dma_mapping_error(i2c->dev, wpaddr)) {
kfree(w_buf);
kfree(r_buf);
return -ENOMEM;
}
rpaddr = dma_map_single(i2c->dev, r_buf,
(msgs + 1)->len,
DMA_FROM_DEVICE);
if (dma_mapping_error(i2c->dev, rpaddr)) {
dma_unmap_single(i2c->dev, wpaddr,
msgs->len, DMA_TO_DEVICE);
kfree(w_buf);
kfree(r_buf);
return -ENOMEM;
}
if (i2c->dev_comp->support_33bits) {
reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr);
writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE);
reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr);
writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE);
}
writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR);
writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR);
writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN);
writel((msgs + 1)->len, i2c->pdmabase + OFFSET_RX_LEN);
}
writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN);
if (!i2c->auto_restart) {
start_reg = I2C_TRANSAC_START;
} else {
start_reg = I2C_TRANSAC_START | I2C_RS_MUL_TRIG;
if (left_num >= 1)
start_reg |= I2C_RS_MUL_CNFG;
}
writew(start_reg, i2c->base + OFFSET_START);
ret = wait_for_completion_timeout(&i2c->msg_complete,
i2c->adap.timeout);
/* Clear interrupt mask */
writew(~(restart_flag | I2C_HS_NACKERR | I2C_ACKERR |
I2C_TRANSAC_COMP), i2c->base + OFFSET_INTR_MASK);
if (i2c->op == I2C_MASTER_WR) {
dma_unmap_single(i2c->dev, wpaddr,
msgs->len, DMA_TO_DEVICE);
kfree(w_buf);
} else if (i2c->op == I2C_MASTER_RD) {
dma_unmap_single(i2c->dev, rpaddr,
msgs->len, DMA_FROM_DEVICE);
memcpy(msgs->buf, r_buf, msgs->len);
kfree(r_buf);
} else {
dma_unmap_single(i2c->dev, wpaddr, msgs->len,
DMA_TO_DEVICE);
dma_unmap_single(i2c->dev, rpaddr, (msgs + 1)->len,
DMA_FROM_DEVICE);
memcpy((msgs + 1)->buf, r_buf, (msgs + 1)->len);
kfree(w_buf);
kfree(r_buf);
}
if (ret == 0) {
dev_dbg(i2c->dev, "addr: %x, transfer timeout\n", msgs->addr);
mtk_i2c_init_hw(i2c);
return -ETIMEDOUT;
}
completion_done(&i2c->msg_complete);
if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) {
dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", msgs->addr);
mtk_i2c_init_hw(i2c);
return -ENXIO;
}
return 0;
}
mtk_i2c_irq,该中断处理函数在 I2C 传输 ACKERR 和传输 STOP 时触发
static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id)
{
struct mtk_i2c *i2c = dev_id;
u16 restart_flag = 0;
u16 intr_stat;
if (i2c->auto_restart)
restart_flag = I2C_RS_TRANSFER;
intr_stat = readw(i2c->base + OFFSET_INTR_STAT);
writew(intr_stat, i2c->base + OFFSET_INTR_STAT);
i2c->irq_stat |= intr_stat;
if (i2c->ignore_restart_irq && (i2c->irq_stat & restart_flag)) {
i2c->ignore_restart_irq = false;
i2c->irq_stat = 0;
writew(I2C_RS_MUL_CNFG | I2C_RS_MUL_TRIG | I2C_TRANSAC_START,
i2c->base + OFFSET_START);
} else {
if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag))
complete(&i2c->msg_complete);
}
return IRQ_HANDLED;
}
优先级翻转与优先级继承
优先级翻转在可剥夺内核中是非常常见的,例子如下(H:High、M:Middle、L:Low)
- 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。
- 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
- 任务 L 获得信号量并开始使用该共享资源。
- 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
- 任务 H 开始运行。
- 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
- 任务 L 继续运行。
- 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的 CPU 使用权。
- 任务 M 处理该处理的事。
- 任务 M 执行完毕后,将 CPU 使用权归还给任务 L。
- 任务 L 继续运行。
- 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换。
- 任务 H 得到该信号量并接着运行。
在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。
Linux 内核底层用 rt_mutex 来解决该问题,rt_mutex 是带优先级继承的互斥锁。
当一个 rt_mutex 正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个 rt_mutex 的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。
优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。
rt_mutex 不能用于中断服务函数中,原因如下:
- rt_mutex 有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待 rt_mutex 而设置阻塞时间进入阻塞态。
在 i2c_transfer 调用 __i2c_transfer 之前,就加了 rt_mutex.
边栏推荐
- Interview stereotyped version
- The solution of "the required function is not supported" in win10 remote desktop connection is to modify the Registry [easy to understand]
- Packing and unpacking of JS
- Practice of traffic recording and playback in vivo
- [hcia]no.15 communication between VLANs
- Counter统计数量后,如何返回有序的key
- GBase 8c触发器(二)
- random shuffle注意
- cvpr2022去雨去雾
- Can netstat still play like this?
猜你喜欢
Add automatic model generation function to hade
Joking about Domain Driven Design (III) -- Dilemma
Didi programmers are despised by relatives: an annual salary of 800000 is not as good as two teachers
内存泄漏工具VLD安装及使用
What does "where 1=1" mean
[flutter] example of asynchronous programming code between future and futurebuilder (futurebuilder constructor setting | handling flutter Chinese garbled | complete code example)
Check log4j problems using stain analysis
Privatization lightweight continuous integration deployment scheme -- 01 environment configuration (Part 2)
Pytest (6) -fixture (Firmware)
Add some hard dishes to the interview: how to improve throughput and timeliness in delayed task scenarios!
随机推荐
Your family must be very poor if you fight like this!
Why choose a frame? What frame to choose
Matlab tips (24) RBF, GRNN, PNN neural network
Gbase 8C system table PG_ database
HTB-Devel
错误Invalid bound statement (not found): com.ruoyi.stock.mapper.StockDetailMapper.xxxx解决
Build a private cloud disk cloudrev
Error invalid bound statement (not found): com ruoyi. stock. mapper. StockDetailMapper. XXXX solution
JMeter performance test JDBC request (query database to obtain database data) use "suggestions collection"
Gbase 8C system table PG_ constraint
Simple understanding of SVG
[shutter] banner carousel component (shutter_wiper plug-in | swiper component)
Deep Reinforcement Learning for Intelligent Transportation Systems: A Survey 论文阅读笔记
Strategy application of Dameng database
[principles of multithreading and high concurrency: 1_cpu multi-level cache model]
Basic operation of binary tree (C language version)
xiaodi-笔记
Gbase 8C system table PG_ auth_ members
MUX VLAN Foundation
Classes and objects - initialization and cleanup of objects - constructor call rules