从零开始之驱动发开、linux驱动(五十五、linux4.19的IIC驱动adaptor)
前面用了8节内容已经分析了2.6.35.7内核的iic驱动,可以发现,在linux中iic驱动还是稍微有些复杂的。
在linux4.19内核中,其实百分之85~90还是和2.6内核的一样的。
关于4.19的i2c,我大概会用两三节来进行分析,主要还是对硬件这个设备树引入后的变化分析。
唯一不同的在于,linux内核引入设备树后,驱动层和硬件相关的内容都是通过设备树文件来指定。
整个框架是没有任何改变的。
唯一变的的,就是硬件相关的内容。也就是适配器的内容。
这里我以三星的s5pv210的来进行比较分析。
这款Soc上有三个iic控制器。
iic0和iic2是普通的iic,iic1适用于HDMI接口。
这里我们就看一下i2c0
先看一下设备树文件。
i2c0: [email protected] {
compatible = "samsung,s3c2440-i2c"; //和平台驱动比较匹配
reg = <0xe1800000 0x1000>;
interrupt-parent = <&vic1>;
interrupts = <14>;
clocks = <&clocks CLK_I2C0>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_bus>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled"; //默认关闭,使用时需要okay
};
主要包括四部分内容。
地址信息,中断信息,时钟信息,引脚信息。
地址信息在上面的硬件手册已经给出了。唯一要说的是这里设备树给的范围是0x1000,这个主要是因为ioremap时,是以页为最小单位映射的,所以即使这里只有20个字节,也是要映射一页的。
中断信息,比较明显,父节点是vic1,使用的是vic1上的14号中断。
时钟信息,也很明显,是iic0的,使用的时候, clk_prepare_enable(clk)就可以了。
引脚信息就比较明显来,查看原理图
所以设备树文件引脚就是gpd1-0和gpd1-1
i2c0_bus: i2c0-bus {
samsung,pins = "gpd1-0", "gpd1-1";
samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-pud = <S3C64XX_PIN_PULL_UP>;
samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
};
可以看到控制寄存器,的值为0x02,所以功能也是2。iic必须接上拉这个没得说,驱动能力没要求,随便设一个就可以。
设备树文件中一个iic控制器(adaptor)基本就搞定了。
接下来我们看一下总线驱动程序
#ifdef CONFIG_OF
static const struct of_device_id s3c24xx_i2c_match[] = {
{ .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },
{ .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
{ .compatible = "samsung,s3c2440-hdmiphy-i2c",
.data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) },
{ .compatible = "samsung,exynos5-sata-phy-i2c",
.data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },
{},
};
MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);
#endif
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);
基本的平台设备驱动模型,这里以为使用的设备树方式,所以of_match_table匹配的是2440-i2c的那个。
s3c24xx_i2c_match后面的两个我们看到有QUIRK_NO_GPIO标志,猜想应该是没有gpio接口的。i2c没gpio接口怎么用?很明显,这个是soc内部使用,用于各种phy的。
接下来继续看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); //如果没设备树文件,那么必须有平台设备的私有数据
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
/* 1.1 申请一个s3c24xx_i2c */
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c)
return -ENOMEM;
/* 1.2 申请一个s3c2410_platform_i2c */
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata)
return -ENOMEM;
/* 1.3 获得控制器类型*/
i2c->quirks = s3c24xx_get_device_quirks(pdev);
i2c->sysreg = ERR_PTR(-ENOENT);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); /* 1.4 解析设备树 */
/* 设置i2c的adaptor的各种参数 */
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //这里算法很重要,和之前的2.6一样,这里就不拿出来了
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_DEPRECATED;
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");
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,获取内存(arm,寄存器和内存一样)资源,映射这段寄存器 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
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->adap.dev.parent = &pdev->dev;
/* 1.5 设置控制引脚 */
i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);
/* inititalise the i2c gpio lines */
/* 如果没使用设备树,需要在mach-xxx.c中,在平台设备的私有数据中,初始化gpio */
if (i2c->pdata->cfg_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);
if (ret) {
dev_err(&pdev->dev, "I2C clock enable failed\n");
return ret;
}
/* 1.6 初始化iic控制器,关闭时钟 */
ret = s3c24xx_i2c_init(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
*/
/* 对于向phy那种soc内部的,是不用注册中断函数的 */
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;
}
/* 注册中断函数,这个s3c24xx_i2c_irq是核心,但前面已经分析过,这里就不再分析了 */
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;
}
}
/* 设置已经使用i2c的时钟 */
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.
*/
/* 设置adaptor编号,设置设备树节点,使用设备树的情况下,都是-1 */
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
platform_set_drvdata(pdev, i2c); /* 把i2c绑定到平台驱动的私有数据上 */
pm_runtime_enable(&pdev->dev);
/* 1.7 注册一个adaptor */
ret = i2c_add_numbered_adapter(&i2c->adap);
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;
}
1.1 申请一个s3c24xx_i2c
struct s3c24xx_i2c {
wait_queue_head_t wait; //等待队列
kernel_ulong_t quirks;
unsigned int suspended:1; //标识是否挂起这个iic控制器
struct i2c_msg *msg; //指向当前发生的msg
unsigned int msg_num; //执行当前有几个msg在发送
unsigned int msg_idx; //标记当前已经发送的msg数量
unsigned int msg_ptr; //标识当前msg中数据已经发送那个位置了
unsigned int tx_setup; //数据发送要建立的时间
unsigned int irq; //这个iic控制器的中断号
enum s3c24xx_i2c_state state; //当前处于的状态(空闲,启动,发送,接收)
unsigned long clkrate; //iic的clk线速率
void __iomem *regs; //映射后的虚拟地址的起始位置
struct clk *clk;
struct device *dev; //平台设备中的那个dev
struct i2c_adapter adap; //最重要的就是这个适配器
struct s3c2410_platform_i2c *pdata; //私有数据
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;
};
1.2 平台总选的信息s3c2410_platform_i2c
/**
* 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; //那个iic
unsigned int flags;
unsigned int slave_addr; //从机地址
unsigned long frequency; //
unsigned int sda_delay;
void (*cfg_gpio)(struct platform_device *dev);
};
1.3 从设备树或平台设备获得,iic控制器类型(也就是前面的2410-i2c,2440-i2c,2442-phy-i2c之类)
static inline kernel_ulong_t s3c24xx_get_device_quirks(struct platform_device *pdev)
{
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(s3c24xx_i2c_match, pdev->dev.of_node);
return (kernel_ulong_t)match->data;
}
return platform_get_device_id(pdev)->driver_data;
}
1.4 解析设备树内容
/*
* Parse the device tree node and retreive the platform data.
*/
static void
s3c24xx_i2c_parse_dt(struct device_node *np, struct s3c24xx_i2c *i2c)
{
struct s3c2410_platform_i2c *pdata = i2c->pdata;
int id;
if (!np)
return;
/* 从设备树文件获得这几个值 */
pdata->bus_num = -1; /* i2c bus number is dynamically assigned */
of_property_read_u32(np, "samsung,i2c-sda-delay", &pdata->sda_delay);
of_property_read_u32(np, "samsung,i2c-slave-addr", &pdata->slave_addr);
of_property_read_u32(np, "samsung,i2c-max-bus-freq",
(u32 *)&pdata->frequency);
/*
* Exynos5's legacy i2c controller and new high speed i2c
* controller have muxed interrupt sources. By default the
* interrupts for 4-channel HS-I2C controller are enabled.
* If nodes for first four channels of legacy i2c controller
* are available then re-configure the interrupts via the
* system register.
*/
/* exyson-5以上cpu会有高速iic,传统和高速的复用了中断,如果是高速的话,这里要重新做一下映射,见下面regmap_update_bits */
id = of_alias_get_id(np, "i2c");
i2c->sysreg = syscon_regmap_lookup_by_phandle(np,
"samsung,sysreg-phandle");
if (IS_ERR(i2c->sysreg))
return;
regmap_update_bits(i2c->sysreg, EXYNOS5_SYS_I2C_CFG, BIT(id), 0);
}
这里拿去一个exysons-5.dtsi中的i2c_0,可以看到有samsung,sysreg-phandle属性。
sysreg_system_controller: [email protected] {
compatible = "samsung,exynos5-sysreg", "syscon";
reg = <0x10050000 0x5000>;
};
i2c_0: [email protected] {
compatible = "samsung,s3c2440-i2c";
reg = <0x12C60000 0x100>;
interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
samsung,sysreg-phandle = <&sysreg_system_controller>;
status = "disabled";
};
1.5 设置控制引脚 ,这里是使用根据设备树选择的引脚和功能进行设置
static inline struct pinctrl * __must_check devm_pinctrl_get_select_default(
struct device *dev)
{
return devm_pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT);
}
static inline struct pinctrl * __must_check devm_pinctrl_get_select(
struct device *dev, const char *name)
{
struct pinctrl *p;
struct pinctrl_state *s;
int ret;
p = devm_pinctrl_get(dev);
if (IS_ERR(p))
return p;
s = pinctrl_lookup_state(p, name);
if (IS_ERR(s)) {
devm_pinctrl_put(p);
return ERR_CAST(s);
}
ret = pinctrl_select_state(p, s);
if (ret < 0) {
devm_pinctrl_put(p);
return ERR_PTR(ret);
}
return p;
}
1.6 初始化iic控制器,关闭时钟
开始之前,说一下,在这个函数开始之前打开时钟,接收后关闭时钟,是因为这个函数里面要计算时钟频率。
/*
* 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);
writel(0, i2c->regs + S3C2410_IICSTAT);
/* we need to work out the divisors for the clock... */
/* 设置时钟 */
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
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.7 注册一个adaptor
/**
* i2c_add_numbered_adapter - declare i2c adapter, use static bus number
* @adap: the adapter to register (with adap->nr initialized)
* Context: can sleep
*
* This routine is used to declare an I2C adapter when its bus number
* matters. For example, use it for I2C adapters from system-on-chip CPUs,
* or otherwise built in to the system's mainboard, and where i2c_board_info
* is used to properly configure I2C devices.
*
* If the requested bus number is set to -1, then this function will behave
* identically to i2c_add_adapter, and will dynamically assign a bus number.
*
* If no devices have pre-been declared for this bus, then be sure to
* register the adapter before any dynamically allocated ones. Otherwise
* the required bus ID may not be available.
*
* When this returns zero, the specified adapter became available for
* clients using the bus number provided in adap->nr. Also, the table
* of I2C devices pre-declared using i2c_register_board_info() is scanned,
* and the appropriate driver model device nodes are created. Otherwise, a
* negative errno value is returned.
*/
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
return __i2c_add_numbered_adapter(adap);
}
上面注释很多,已经说得很清晰了。
编号为-1时,动态分配编号后注册。
不为-1时,直接用这个编号注册。