从零开始之驱动发开、linux驱动(五十五、linux4.19的IIC驱动adaptor)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/88936464

前面用了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: i2c@e1800000 {
			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: syscon@10050000 {
			compatible = "samsung,exynos5-sysreg", "syscon";
			reg = <0x10050000 0x5000>;
		};



		i2c_0: i2c@12c60000 {
			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时,直接用这个编号注册。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/88936464