I2C 学习笔记
一、从地址映射
例如:实际地址0x08(0001000) 打印出来的从地址写:0x11(00010001),读0x10(00010000)
原因:映射时,从地址向左移一位,写则在最低位写1,读则写0.因此,对于i2c有两个地址
参考资料:
https://www.cnblogs.com/aspirs/p/12371237.html
二、I2C 驱动注册过程
2.1 module_init(mt_i2c_init); 模块注册流程:
module_init(drv)
->__initcall(x)
-> device_initcall(fn)
-> __define_initcall(fn, 6)//这个6代表启动时顺序等级,还有如下等级,如果想要自己所在模块提前启动,可以使用其他的宏
include/linux/init.h
#define pure_initcall(fn) __define_initcall(fn, 0)
#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
初始化在SMP 模块之前,只适合非嵌入模块
#define early_initcall(fn) __define_initcall(fn, early)
init_module中调用load_module然后将用户传入的内核文件创建为一整个内核模块,返回一个module结构体,系统即以此表示嵌入的模块。
include/linux/module.h
struct module
{
enum module_state state;
/* Member of list of modules */
struct list_head list;
/* Unique handle for this module */
char name[MODULE_NAME_LEN];
/*省略下面的内容*/
} state是一个枚举型变量,代表模块的状态
enum module_state
{
MODULE_STATE_LIVE, //模块存活,也就是已经加载
MODULE_STATE_COMING, //模块已经加载,正在初始化
MODULE_STATE_GOING, //模块正在被卸载
MODULE_STATE_UNFORMED,//模块正在加载
};
2.2 i2c init 函数操作
drivers/i2c/busses/i2c-mtk.c
1、 enable_arbitration();//配置pericfg寄存器,使能I2C
-> pericfg_node = of_find_compatible_node(NULL, NULL, “mediatek,pericfg”);
2、 if (!map_dma_regs()) 配置寄存器
3、 if (!mt_i2c_parse_comp_data()) 获取设备共用参数(设备树)
4、 register_syscore_ops(&mtk_i2c_syscore_ops); 向系统注册i2c操作函数(挂起和释放)
5、 platform_driver_register(&mt_i2c_driver);注册I2C平台驱动
2.3 平台驱动注册
platform_driver_register
-> __platform_driver_register(drv, THIS_MODULE)
-> driver_register(&drv->driver);
在驱动注册的时候,无论是平台总线,I2C、 SPI 总线,最终调用到的函数都是driver_register
/**
* driver_register - register driver with bus
* @drv: driver to register
*
* We pass off most of the work to the bus_add_driver() call,
* since most of the things we have to do deal with the bus
* structures.
*/
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus);//判断是否已经注册了
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);//将驱动加入到注册表
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
2.4 I2C 驱动Probe 流程
前面提及了,所有模块驱动init后都会调用到driver_register。之后便是驱动设备匹配,跑probe函数了。大致流程如下:
drivers/base/dd.c drivers/base/bus.c drivers/base/*
driver_register –》bus_add_driver –》driver_attach –》bus_for_each_dev ->__driver_attach(无论是)
1、 ID 匹配
–》driver_match_device
–》(drv->bus->match ? drv->bus->match(dev, drv) : 1;)
-》platform_match
2、匹配完成后probe函数调用
-》driver_probe_device(drv, dev);
-》really_probe(dev, drv);
2.1这里会有两种probe方式
if (dev->bus->probe) {//注册设备时,发起的匹配
-》dev->bus->probe(dev);
else if (drv->probe) {
-》drv->probe(dev);
drivers/base/bus.c
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus || !bus->p)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);//此处调用匹配函数__driver_attach,进行匹配,如果匹配到多个节点,则probe也会多次运行
klist_iter_exit(&i);
return error;
}
存在5种匹配方式,暂时未深入研究
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table) //(常用)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
前面已经提及了,在匹配过后,代码会根据匹配的类型调用probe函数,获取到dev设备,进行I2C的配置了
static int mt_i2c_probe(struct platform_device *pdev)
{
//1、解析dts树,获取到设置资源
i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);
ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//后两个变量定位资源
i2c->base = devm_ioremap_resource(&pdev->dev, res);//IO映射得到内存中的地址
if (i2c->id < I2C_MAX_CHANNEL)//最大10路
g_mt_i2c[i2c->id] = i2c;//保存I2C设备,是属于哪路I2C的从设备
if (!i2c->fifo_only) {//FIFO 类型
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
i2c->pdmabase = devm_ioremap_resource(&pdev->dev, res);
}
//GPIO 地址
i2c->gpiobase = devm_ioremap(&pdev->dev, i2c->gpio_start, i2c->mem_len);
//中断号
i2c->irqnr = platform_get_irq(pdev, 0);
init_waitqueue_head(&i2c->wait);
//中断设置
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);
//I2C适配器设置,属于平台层面的,一个适配器可能管理多路I2C设备
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);
//MDA 大小匹配,在dts中配置,I2C Init时会解析到Match ID的data结构体中
if (i2c->dev_comp->dma_support == MDA_SUPPORT_8G) {
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(33))) {
dev_info(&pdev->dev, “dma_set_mask return error.\n”);
return -EINVAL;
}
} else if (i2c->dev_comp->dma_support == DMA_SUPPORT_64G) {
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(36))) {
dev_info(&pdev->dev, “dma_set_mask return error.\n”);
return -EINVAL;
}
}
//以下是时钟配置,开始
#if !defined(CONFIG_MT_I2C_FPGA_ENABLE)
i2c->clk_main = devm_clk_get(&pdev->dev, "main");
if (IS_ERR(i2c->clk_main)) {
dev_info(&pdev->dev, "cannot get main clock\n");
return PTR_ERR(i2c->clk_main);
}
i2c->clk_dma = devm_clk_get(&pdev->dev, "dma");
if (IS_ERR(i2c->clk_dma)) {
if (!i2c->fifo_only) {
dev_info(&pdev->dev, "cannot get dma clock\n");
return PTR_ERR(i2c->clk_dma);
}
i2c->clk_dma = NULL;
}
i2c->clk_arb = devm_clk_get(&pdev->dev, "arb");
if (IS_ERR(i2c->clk_arb))
i2c->clk_arb = NULL;
else
dev_dbg(&pdev->dev, "i2c%d has the relevant arbitrator clk.\n",
i2c->id);
i2c->clk_pal = devm_clk_get(&pdev->dev, "pal");
if (IS_ERR(i2c->clk_pal))
i2c->clk_pal = NULL;
else
dev_dbg(&pdev->dev, "i2c%d has the relevant pal clk.\n",
i2c->id);
//锁相环设定(PLL)
if (i2c->i2c_pll_info->clk_mux == NULL) {
i2c->i2c_pll_info->clk_mux = devm_clk_get(&pdev->dev, "mux");
if (IS_ERR(i2c->i2c_pll_info->clk_mux)) {
i2c->i2c_pll_info->clk_mux = NULL;
dev_info(&pdev->dev, "cannot get mux clock\n");
} else
dev_info(&pdev->dev,
"i2c%d has the relevant clk_mux clk.\n",
i2c->id);
}
if (i2c->i2c_pll_info->clk_p_main == NULL) {
i2c->i2c_pll_info->clk_p_main =
devm_clk_get(&pdev->dev, "p_main");
if (IS_ERR(i2c->i2c_pll_info->clk_p_main)) {
i2c->i2c_pll_info->clk_p_main = NULL;
dev_info(&pdev->dev, "cannot get p_main clock\n");
} else
dev_info(&pdev->dev,
"i2c%d has the relevant clk_p_main clk.\n",
i2c->id);
}
if (i2c->i2c_pll_info->clk_p_univ == NULL) {
i2c->i2c_pll_info->clk_p_univ =
devm_clk_get(&pdev->dev, "p_univ");
if (IS_ERR(i2c->i2c_pll_info->clk_p_univ)) {
i2c->i2c_pll_info->clk_p_univ = NULL;
dev_info(&pdev->dev, "cannot get p_univ clock\n");
} else
dev_info(&pdev->dev,
"i2c%d has the relevant clk_p_univ clk.\n",
i2c->id);
}
#endif
if (i2c->have_pmic) {
i2c->clk_pmic = devm_clk_get(&pdev->dev, "pmic");
if (IS_ERR(i2c->clk_pmic)) {
dev_info(&pdev->dev, "cannot get pmic clock\n");
return PTR_ERR(i2c->clk_pmic);
}
//换算时钟,pmic出来的时钟除以分频倍数
clk_src_in_hz = clk_get_rate(i2c->clk_pmic) / i2c->clk_src_div;
} else {
clk_src_in_hz = clk_get_rate(i2c->clk_main) / i2c->clk_src_div;
}
i2c->main_clk = clk_src_in_hz;
dev_info(&pdev->dev, "i2c%d clock source %p,clock src frequency %d\n",
i2c->id, i2c->clk_main, clk_src_in_hz);
//时钟选择 结束
//设置I2C 速度
strlcpy(i2c->adap.name, I2C_DRV_NAME, sizeof(i2c->adap.name));
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);
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);//设置I2C适配器。I2C数据
/* ret = i2c_add_adapter(&i2c->adap); */
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret) {
dev_info(&pdev->dev, "Failed to add i2c bus to i2c core\n");
free_i2c_dma_bufs(i2c);
return ret;
}
platform_set_drvdata(pdev, i2c);//保存I2C数据到Pdev结构体中
if (!map_cg_regs(i2c))
pr_info("Map cg regs successfully.\n");
return 0;
}
device 与drv 注册流程图
参考博客资料:
https://blog.csdn.net/happy987818/article/details/74741466
https://blog.csdn.net/Richard_LiuJH/article/details/48245715?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=1328666.20549.16160529210091425&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control