在这一节将会介绍S3C2440 I2C适配器的注册,以及AT24C08、SSD1606 OLED I2C设备驱动的编写。
一、I2C适配器注册
在Mini2440裸机开发之I2C(AT24C08)我们介绍了S3C2440这款SOC的I2C结构,其内部只有一个I2C控制器,其中SCL连接GPE14引脚,SDA连接GPE15引脚。
1.1 platform设备注册(s3c2410-i2c)
1.1.1 s3c_device_i2c0
向往常一样,我们依然是定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件,在该文我们可以找到platform设备s3c_device_i2c0变量,其定义在arch/arm/plat-samsung/devs.c文件:
/* I2C */ static struct resource s3c_i2c0_resource[] = { [0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K), [1] = DEFINE_RES_IRQ(IRQ_IIC), }; struct platform_device s3c_device_i2c0 = { // platform设备信息 .name = "s3c2410-i2c", .id = 0, .num_resources = ARRAY_SIZE(s3c_i2c0_resource), .resource = s3c_i2c0_resource, }; struct s3c2410_platform_i2c default_i2c_data __initdata = { // 用于初始化&s3c_device_i2c0->dev.platform_data .flags = 0, .slave_addr = 0x10, // 从设备地址 .frequency = 100*1000, // 频率 100kb .sda_delay = 100, }; void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd) // 用于绑定s3c_device_i2c0->dev.platform_data和default_i2c_data的关系 { struct s3c2410_platform_i2c *npd; if (!pd) { pd = &default_i2c_data; pd->bus_num = 0; } npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_i2c0); // &s3c_device_i2c0->dev.platform_data = default_i2c_data if (!npd->cfg_gpio): npd->cfg_gpio = s3c_i2c0_cfg_gpio; }
我们重点关注一下platform设备资源定义,这里定义了2个资源,第1个是内存资源、第2个是中断资源:
- 第一个资源是IO内存资源,起始地址是I2C寄存器寄基地址,即0x54000000,大小位4Kb;
- 第二个资源必须是中断资源,中断号位IRQ_IIC,即27;
我们已经定义了I2C控制器相关的platform_device设备s3c_device_i2c0,并进行了初始化,那platform设备啥时候注册的呢?
linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <[email protected]> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。
1.1.2 smdk2440_machine_init
static void __init smdk2440_machine_init(void) { s3c24xx_fb_set_platdata(&smdk2440_fb_info); s3c_i2c0_set_platdata(NULL); // 初始化s3c_device_i2c0->dev.platform_data platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等 smdk_machine_init(); // s3c24x0系列若干个platform设备注册(通用) }
这里利用platform_add_devices进行若干个platform设备的注册,该函数通过调用platform_device_register实现platform设备注册。
static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_ohci, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &smdk2440_device_eth, };
1.2 platform驱动注册(s3c2410-i2c)
I2C控制器platform驱动定义位于drivers/i2c/busses/i2c-s3c2410.c文件中。
1.2.1 入口和出口函数
我们在i2c-s3c2410.c文件定位到驱动模块的入口和出口:
static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, .id_table = s3c24xx_driver_ids, .driver = { .name = "s3c-i2c", .pm = S3C24XX_DEV_PM_OPS, .of_match_table = of_match_ptr(s3c24xx_i2c_match), }, }; static int __init i2c_adap_s3c_init(void) { return platform_driver_register(&s3c24xx_i2c_driver); } subsys_initcall(i2c_adap_s3c_init); static void __exit i2c_adap_s3c_exit(void) { platform_driver_unregister(&s3c24xx_i2c_driver); } module_exit(i2c_adap_s3c_exit);
在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c24xx_i2c_probe函数。
1.2.2 I2C相关数据结构
在介绍s3c24xx_i2c_probe之前,先来看一下s3c24xx_i2c结构体定义:
struct s3c24xx_i2c { // 用来表示SOC的一个I2C控制器 wait_queue_head_t wait; // 等待队列 kernel_ulong_t quirks; struct i2c_msg *msg; // 存放要发送的I2C数据包 unsigned int msg_num; // 数据包个数 实际只有1个 unsigned int msg_idx; // 当前发送的数据包索引 unsigned int msg_ptr; // 当前传输的字节在数据包的索引 unsigned int tx_setup; // 用来延时,等待SCL被释放 unsigned int irq; // 中断号 enum s3c24xx_i2c_state state; // I2C传输状态 STATE_START、STATE_IDEL、STATE_STOP、STATE_WRITE、STATE_READ unsigned long clkrate; void __iomem *regs; struct clk *clk; // 时钟 struct device *dev; // 存储platform设备的dev成员 struct i2c_adapter adap; // 描述该I2C控制器的适配器 struct s3c2410_platform_i2c *pdata; // 私有数据 默认初始化为&default_i2c_data int gpios[2]; struct pinctrl *pctrl; #if defined(CONFIG_ARM_S3C24XX_CPUFREQ) struct notifier_block freq_transition; #endif struct regmap *sysreg; unsigned int sys_i2c_cfg; };
s3c2410_platform_i2c定义在include/linux/platform_data/i2c-s3c2410.h文件:
/** * struct s3c2410_platform_i2c - Platform data for s3c I2C. * @bus_num: The bus number to use (if possible). * @flags: Any flags for the I2C bus (E.g. S3C_IICFLK_FILTER). * @slave_addr: The I2C address for the slave device (if enabled). * @frequency: The desired frequency in Hz of the bus. This is * guaranteed to not be exceeded. If the caller does * not care, use zero and the driver will select a * useful default. * @sda_delay: The delay (in ns) applied to SDA edges. * @cfg_gpio: A callback to configure the pins for I2C operation. */ struct s3c2410_platform_i2c { int bus_num; unsigned int flags; unsigned int slave_addr; unsigned long frequency; unsigned int sda_delay; void (*cfg_gpio)(struct platform_device *dev); // 配置gpio口 };
1.2.3 s3c24xx_i2c_probe
static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c; struct s3c2410_platform_i2c *pdata = NULL; struct resource *res; int ret; if (!pdev->dev.of_node) { pdata = dev_get_platdata(&pdev->dev); // &pdev->dev.platform_data即default_i2c_data if (!pdata) { dev_err(&pdev->dev, "no platform data\n"); return -EINVAL; } } i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); // 为i2c动态申请内存,&pdev->dev卸载时释放 if (!i2c) return -ENOMEM; i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); // 为i2c->pdata动态申请内存,&pdev->dev卸载时释放 if (!i2c->pdata) return -ENOMEM; i2c->quirks = s3c24xx_get_device_quirks(pdev); i2c->sysreg = ERR_PTR(-ENOENT); if (pdata) memcpy(i2c->pdata, pdata, sizeof(*pdata)); // 走这里,初始化i2c->pdata else s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); // 解析设备树 strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); // 设置I2C适配器名称 i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm; // 设置I2C适配器通信算法 i2c->adap.retries = 2; // 重试次数 i2c->adap.class = I2C_CLASS_DEPRECATED; // 设置成这个??是是有问题 I2C_CLASS_HWMON i2c->tx_setup = 50; init_waitqueue_head(&i2c->wait); // 初始化等待队列 /* find the clock and enable it */ i2c->dev = &pdev->dev; i2c->clk = devm_clk_get(&pdev->dev, "i2c"); // 获取i2c时钟 if (IS_ERR(i2c->clk)) { dev_err(&pdev->dev, "cannot get clock\n"); return -ENOENT; } dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); /* map the registers */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取第一个IO内存资源 i2c->regs = devm_ioremap_resource(&pdev->dev, res); // 物理地址->虚拟地址映射 映射是I2C控制器寄存器地址 if (IS_ERR(i2c->regs)) return PTR_ERR(i2c->regs); dev_dbg(&pdev->dev, "registers %p (%p)\n", i2c->regs, res); /* setup info block for the i2c core */ i2c->adap.algo_data = i2c; // 私有数据指向i2c i2c->adap.dev.parent = &pdev->dev; // 设置I2C适配器dev.parent为&pdev->dev i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /* inititalise the i2c gpio lines */ if (i2c->pdata->cfg_gpio) // 配置gpio i2c->pdata->cfg_gpio(to_platform_device(i2c->dev)); else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) return -EINVAL; /* initialise the i2c controller */ ret = clk_prepare_enable(i2c->clk); // 使能时钟 CLKCON寄存器的bit[66]对应着I2C模块 if (ret) { dev_err(&pdev->dev, "I2C clock enable failed\n"); return ret; } ret = s3c24xx_i2c_init(i2c); // 初始化I2C相关的寄存器 clk_disable(i2c->clk); // 禁止时钟 if (ret != 0) { dev_err(&pdev->dev, "I2C controller init failed\n"); clk_unprepare(i2c->clk); return ret; } /* * find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ if (!(i2c->quirks & QUIRK_POLL)) { i2c->irq = ret = platform_get_irq(pdev, 0); // 获取中断资源 if (ret <= 0) { dev_err(&pdev->dev, "cannot find IRQ\n"); clk_unprepare(i2c->clk); return ret; } ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, // 注册中断服务 0, dev_name(&pdev->dev), i2c); if (ret != 0) { dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); clk_unprepare(i2c->clk); return ret; } } ret = s3c24xx_i2c_register_cpufreq(i2c); if (ret < 0) { dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); clk_unprepare(i2c->clk); return ret; } /* * Note, previous versions of the driver used i2c_add_adapter() * to add the bus at any number. We now pass the bus number via * the platform data, so if unset it will now default to always * being bus 0. */ i2c->adap.nr = i2c->pdata->bus_num; // 设置I2C适配器编号 i2c->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, i2c); // 设置pdev.dev.plat_data = i2c pm_runtime_enable(&pdev->dev); ret = i2c_add_numbered_adapter(&i2c->adap); // 注册I2C适配器 if (ret < 0) { pm_runtime_disable(&pdev->dev); s3c24xx_i2c_deregister_cpufreq(i2c); clk_unprepare(i2c->clk); return ret; } dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); return 0; }
这段代码属实有点长了,让人一眼看过去,就有点想放弃去读的想法,既然都学习到了这一步,我们还是耐着性去分析吧。这里代码理解起来还是比较简单的,主要流程如下:
- 设置i2c_adapter适配器结构体;
- 设置name为s3c2410-i2c;
- 设置algo为s3c24xx_i2c_algorithm;
- 设置retries为2;
- 设置algo_data 为i2c;
- 设置dev.parent为&pdev->dev;
- 设置nr为i2c->pdata->bus_num;
- ...
- 调用s3c24xx_i2c_init初始化S3C2440 I2C相关的寄存器;
- 调用devm_request_irq注册I2C中断,中断处理服务函数为s3c24xx_i2c_irq;
- 调用i2c_add_numbered_adapter注册i2c_adapter适配器结构体;
1.2.4 s3c24xx_i2c_algorithm
/* i2c bus registration info */ static const struct i2c_algorithm s3c24xx_i2c_algorithm = { .master_xfer = s3c24xx_i2c_xfer, /* 数据传输,这个上一节已经介绍了 */ .functionality = s3c24xx_i2c_func, };
s3c24xx_i2c_xfer函数在u上一节已经介绍了,与SOC的I2C控制器寄存器设置相关了,基本是裸机相关的内容,无非就是产生 I2C 通信时序,如开始信号,停止信号、应答信号等。
1.2.5 s3c24xx_i2c_init
s3c24xx_i2c_init函数主要就是进行I2C控制器的初始化工作:
/* * initialise the controller, set the IO lines and frequency */ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c) { struct s3c2410_platform_i2c *pdata; unsigned int freq; /* get the plafrom data */ pdata = i2c->pdata; /* write slave address */ writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD); // 写入从设备地址 dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr); writel(0, i2c->regs + S3C2410_IICCON); // 初始化IICCON=0 writel(0, i2c->regs + S3C2410_IICSTAT); // 初始化IICSTAT=0 /* we need to work out the divisors for the clock... */ if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) { // 这个函数主要就是根据当前i2c时钟频率以及预设的I2C控制器目标频率,计算IIC总线发送时钟预分频系数,freq为设置的实际频率 dev_err(i2c->dev, "cannot meet bus frequency required\n"); return -EINVAL; } /* todo - check that the i2c lines aren't being dragged anywhere */ dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq); dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02x\n", readl(i2c->regs + S3C2410_IICCON)); return 0; }
1.2.6 s3c24xx_i2c_irq
s3c24xx_i2c_irq调用下一个字节传输函数i2s_s3c_irq_nextbyte()来传输数据。
/* * top level IRQ servicing routine */ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) { struct s3c24xx_i2c *i2c = dev_id; unsigned long status; unsigned long tmp; status = readl(i2c->regs + S3C2410_IICSTAT); if (status & S3C2410_IICSTAT_ARBITR) { // 仲裁失败 /* deal with arbitration loss */ dev_err(i2c->dev, "deal with arbitration loss\n"); } if (i2c->state == STATE_IDLE) { // 空闲状态 dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); // 清I2C中断挂起 IICCON位[4] goto out; } /* * pretty much this leaves us with the fact that we've * transmitted or received whatever byte we last sent */ i2c_s3c_irq_nextbyte(i2c, status); // 继续数据传输 out: return IRQ_HANDLED; }
二、AT24C08设备驱动注册
标签:I2C,s3c24xx,dev,platform,OLED,pdev,驱动,i2c,移植 From: https://www.cnblogs.com/zyly/p/17133091.html