Linux i2c RTC 驅動解析

1. i2c adapter

1.1 device (dts)

首先我們需要在dts文件中增加i2c控制器(adapter)的device定義。

am57xx-evm-reva3.dtb中關於i2c adapter在\arch\arm\boot\dts\dra7.dtsi文件中定義:

/ {
	#address-cells = <2>;
	#size-cells = <2>;

	compatible = "ti,dra7xx";
	interrupt-parent = <&crossbar_mpu>;

    ...

	ocp {
		compatible = "ti,dra7-l3-noc", "simple-bus";
		#address-cells = <1>;
		#size-cells = <1>;
		ranges = <0x0 0x0 0x0 0xc0000000>;
		ti,hwmods = "l3_main_1", "l3_main_2";
		reg = <0x0 0x44000000 0x0 0x1000000>,
		      <0x0 0x45000000 0x0 0x1000>;
		interrupts-extended = <&crossbar_mpu GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
				      <&wakeupgen GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;

        ...

		i2c1: i2c@48070000 {
			compatible = "ti,omap4-i2c";
			reg = <0x48070000 0x100>;
			interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>;
			#address-cells = <1>;
			#size-cells = <0>;
			ti,hwmods = "i2c1";
			status = "disabled";
		};

		i2c2: i2c@48072000 {
			compatible = "ti,omap4-i2c";
			reg = <0x48072000 0x100>;
			interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
			#address-cells = <1>;
			#size-cells = <0>;
			ti,hwmods = "i2c2";
			status = "disabled";
		};

		i2c3: i2c@48060000 {
			compatible = "ti,omap4-i2c";
			reg = <0x48060000 0x100>;
			interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>;
			#address-cells = <1>;
			#size-cells = <0>;
			ti,hwmods = "i2c3";
			status = "disabled";
		};

		i2c4: i2c@4807a000 {
			compatible = "ti,omap4-i2c";
			reg = <0x4807a000 0x100>;
			interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
			#address-cells = <1>;
			#size-cells = <0>;
			ti,hwmods = "i2c4";
			status = "disabled";
		};

		i2c5: i2c@4807c000 {
			compatible = "ti,omap4-i2c";
			reg = <0x4807c000 0x100>;
			interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
			#address-cells = <1>;
			#size-cells = <0>;
			ti,hwmods = "i2c5";
			status = "disabled";
		};
        ...
    }
}

一共5路i2c,對應AM5728芯片手冊中的定義:

在這裏插入圖片描述

i2c adapter在dts中的定義路徑爲/ocp/i2c1,因爲/ocp定義爲"simple-bus",所以它的子節點i2c1在系統初始化時也會自動創建爲platform device。調用路徑爲:

start_kernel() -> ... ->do_initcalls() -> ... ->of_platform_populate() -> of_platform_bus_create() -> of_platform_device_create_pdata()

1.2 driver

因爲i2c adapter在dts中的定義會被自動創建爲platform device,所以i2c adapter的driver必須註冊成platform driver。

drivers\i2c\busses\i2c-omap.c:

static struct platform_driver omap_i2c_driver = {
	.probe		= omap_i2c_probe,
	.remove		= omap_i2c_remove,
	.driver		= {
		.name	= "omap_i2c",
		.pm	= OMAP_I2C_PM_OPS,
		.of_match_table = of_match_ptr(omap_i2c_of_match),
	},
};

static const struct of_device_id omap_i2c_of_match[] = {
	{
		.compatible = "ti,omap4-i2c",
		.data = &omap4_pdata,
	},
	{
		.compatible = "ti,omap3-i2c",
		.data = &omap3_pdata,
	},
	{
		.compatible = "ti,omap2430-i2c",
		.data = &omap2430_pdata,
	},
	{
		.compatible = "ti,omap2420-i2c",
		.data = &omap2420_pdata,
	},
	{ },
};

driver和device因爲.compatible = "ti,omap4-i2c"而適配上。

i2c adapter完成最重要的兩件事:

1.初始化i2c adapter,並調用i2c_register_adapter()註冊成i2c_adapter device;
2.遍歷i2c adapter(i2c 控制器)下掛的i2c設備,調用i2c_new_device()把設備註冊成i2c_client device;

static int
omap_i2c_probe(struct platform_device *pdev)
{
	struct omap_i2c_dev	*omap;
	struct i2c_adapter	*adap;
	struct resource		*mem;
	const struct omap_i2c_bus_platform_data *pdata =
		dev_get_platdata(&pdev->dev);
	struct device_node	*node = pdev->dev.of_node;
	const struct of_device_id *match;
	int irq;
	int r;
	u32 rev;
	u16 minor, major;

    /* (1) 獲取i2c adapter的中斷號,在dts中定義的"interrupts"屬性 */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "no irq resource?\n");
		return irq;
	}

	omap = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
	if (!omap)
		return -ENOMEM;

    /* (2) 獲取i2c adapter的寄存器基地址,在dts中定義的"reg"屬性 */
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	omap->base = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(omap->base))
		return PTR_ERR(omap->base);

	match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev);
	if (match) {
		u32 freq = 100000; /* default to 100000 Hz */

		pdata = match->data;
		omap->flags = pdata->flags;

		of_property_read_u32(node, "clock-frequency", &freq);
		/* convert DT freq value in Hz into kHz for speed */
		omap->speed = freq / 1000;
	} else if (pdata != NULL) {
		omap->speed = pdata->clkrate;
		omap->flags = pdata->flags;
		omap->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
	}

	omap->dev = &pdev->dev;
	omap->irq = irq;

	spin_lock_init(&omap->lock);

    /* (3) 存儲dev->driver_data數據 */
	platform_set_drvdata(pdev, omap);
	init_completion(&omap->cmd_complete);

	omap->reg_shift = (omap->flags >> OMAP_I2C_FLAG_BUS_SHIFT__SHIFT) & 3;

	pm_runtime_enable(omap->dev);
	pm_runtime_set_autosuspend_delay(omap->dev, OMAP_I2C_PM_TIMEOUT);
	pm_runtime_use_autosuspend(omap->dev);

	r = pm_runtime_get_sync(omap->dev);
	if (r < 0)
		goto err_free_mem;

	/*
	 * Read the Rev hi bit-[15:14] ie scheme this is 1 indicates ver2.
	 * On omap1/3/2 Offset 4 is IE Reg the bit [15:14] is 0 at reset.
	 * Also since the omap_i2c_read_reg uses reg_map_ip_* a
	 * readw_relaxed is done.
	 */
	rev = readw_relaxed(omap->base + 0x04);

	omap->scheme = OMAP_I2C_SCHEME(rev);
	switch (omap->scheme) {
	case OMAP_I2C_SCHEME_0:
		omap->regs = (u8 *)reg_map_ip_v1;
		omap->rev = omap_i2c_read_reg(omap, OMAP_I2C_REV_REG);
		minor = OMAP_I2C_REV_SCHEME_0_MAJOR(omap->rev);
		major = OMAP_I2C_REV_SCHEME_0_MAJOR(omap->rev);
		break;
	case OMAP_I2C_SCHEME_1:
		/* FALLTHROUGH */
	default:
		omap->regs = (u8 *)reg_map_ip_v2;
		rev = (rev << 16) |
			omap_i2c_read_reg(omap, OMAP_I2C_IP_V2_REVNB_LO);
		minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
		major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
		omap->rev = rev;
	}

	omap->errata = 0;

	if (omap->rev >= OMAP_I2C_REV_ON_2430 &&
			omap->rev < OMAP_I2C_REV_ON_4430_PLUS)
		omap->errata |= I2C_OMAP_ERRATA_I207;

	if (omap->rev <= OMAP_I2C_REV_ON_3430_3530)
		omap->errata |= I2C_OMAP_ERRATA_I462;

	if (!(omap->flags & OMAP_I2C_FLAG_NO_FIFO)) {
		u16 s;

		/* Set up the fifo size - Get total size */
		s = (omap_i2c_read_reg(omap, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
		omap->fifo_size = 0x8 << s;

		/*
		 * Set up notification threshold as half the total available
		 * size. This is to ensure that we can handle the status on int
		 * call back latencies.
		 */

		omap->fifo_size = (omap->fifo_size / 2);

		if (omap->rev < OMAP_I2C_REV_ON_3630)
			omap->b_hw = 1; /* Enable hardware fixes */

		/* calculate wakeup latency constraint for MPU */
		if (omap->set_mpu_wkup_lat != NULL)
			omap->latency = (1000000 * omap->fifo_size) /
				       (1000 * omap->speed / 8);
	}

	/* reset ASAP, clearing any IRQs */
    /* (4) i2c adapter寄存器初始化 */
	omap_i2c_init(omap);

    /* (5) 中斷註冊 */
	if (omap->rev < OMAP_I2C_OMAP1_REV_2)
		r = devm_request_irq(&pdev->dev, omap->irq, omap_i2c_omap1_isr,
				IRQF_NO_SUSPEND, pdev->name, omap);
	else
		r = devm_request_threaded_irq(&pdev->dev, omap->irq,
				omap_i2c_isr, omap_i2c_isr_thread,
				IRQF_NO_SUSPEND | IRQF_ONESHOT,
				pdev->name, omap);

	if (r) {
		dev_err(omap->dev, "failure requesting irq %i\n", omap->irq);
		goto err_unuse_clocks;
	}

    /* (6) i2c adapter 初始化 */
	adap = &omap->adapter;
	i2c_set_adapdata(adap, omap);
	adap->owner = THIS_MODULE;
	adap->class = I2C_CLASS_DEPRECATED;
	strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
	adap->algo = &omap_i2c_algo;
	adap->dev.parent = &pdev->dev;
	adap->dev.of_node = pdev->dev.of_node;
	adap->bus_recovery_info = &omap_i2c_bus_recovery_info;

	/* i2c device drivers may be active on return from add_adapter() */
	adap->nr = pdev->id;
    /* (7) i2c adapter 註冊 */
	r = i2c_add_numbered_adapter(adap);
	if (r) {
		dev_err(omap->dev, "failure adding adapter\n");
		goto err_unuse_clocks;
	}

	dev_info(omap->dev, "bus %d rev%d.%d at %d kHz\n", adap->nr,
		 major, minor, omap->speed);

	pm_runtime_mark_last_busy(omap->dev);
	pm_runtime_put_autosuspend(omap->dev);

	return 0;

err_unuse_clocks:
	omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, 0);
	pm_runtime_put(omap->dev);
	pm_runtime_disable(&pdev->dev);
err_free_mem:

	return r;
}

↓

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);
}

↓

static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	int	id;

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1,
		       GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (id < 0)
		return id == -ENOSPC ? -EBUSY : id;

	return i2c_register_adapter(adap);
}

↓

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = 0;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p))) {
		res = -EAGAIN;
		goto out_list;
	}

	/* Sanity checks */
	if (unlikely(adap->name[0] == '\0')) {
		pr_err("i2c-core: Attempt to register an adapter with "
		       "no name!\n");
		return -EINVAL;
	}
	if (unlikely(!adap->algo)) {
		pr_err("i2c-core: Attempt to register adapter '%s' with "
		       "no algo!\n", adap->name);
		return -EINVAL;
	}

	rt_mutex_init(&adap->bus_lock);
	mutex_init(&adap->userspace_clients_lock);
	INIT_LIST_HEAD(&adap->userspace_clients);

	/* Set default timeout to 1 second if not already set */
	if (adap->timeout == 0)
		adap->timeout = HZ;

    /* (7.1) 註冊i2c adapter對應的device
        可以看到, i2c adapter會註冊兩個device:
        一個是platform bus上的device,在dts中定義由系統自動創建,對應的driver爲platform driver;
        另一個是下述i2c bus上的device,在i2c adapter註冊時創建,一般沒有對應的i2c driver,如果有特殊驅動需要會在後續的__process_new_adapter()中適配;
     */
	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);
	if (res)
		goto out_list;

	dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

	pm_runtime_no_callbacks(&adap->dev);

#ifdef CONFIG_I2C_COMPAT
	res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
				       adap->dev.parent);
	if (res)
		dev_warn(&adap->dev,
			 "Failed to create compatibility class link\n");
#endif

	/* bus recovery specific initialization */
    /* (7.2) 註冊總線恢復等一系列函數 */
	if (adap->bus_recovery_info) {
		struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;

		if (!bri->recover_bus) {
			dev_err(&adap->dev, "No recover_bus() found, not using recovery\n");
			adap->bus_recovery_info = NULL;
			goto exit_recovery;
		}

		/* Generic GPIO recovery */
		if (bri->recover_bus == i2c_generic_gpio_recovery) {
			if (!gpio_is_valid(bri->scl_gpio)) {
				dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n");
				adap->bus_recovery_info = NULL;
				goto exit_recovery;
			}

			if (gpio_is_valid(bri->sda_gpio))
				bri->get_sda = get_sda_gpio_value;
			else
				bri->get_sda = NULL;

			bri->get_scl = get_scl_gpio_value;
			bri->set_scl = set_scl_gpio_value;
		} else if (!bri->set_scl || !bri->get_scl) {
			/* Generic SCL recovery */
			dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n");
			adap->bus_recovery_info = NULL;
		}
	}

exit_recovery:
	/* create pre-declared device nodes */
    /* (7.3) 遍歷i2c adapter上掛載的i2c物理設備,並創建對應的i2c_client device */
	of_i2c_register_devices(adap);
	acpi_i2c_register_devices(adap);
	acpi_i2c_install_space_handler(adap);

	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);

	/* Notify drivers */
    /* (7.4) 特殊驅動需要適配i2c adapter的i2c bus device,調用__process_new_adapter()來適配 */
	mutex_lock(&core_lock);
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

	return 0;

out_list:
	mutex_lock(&core_lock);
	idr_remove(&i2c_adapter_idr, adap->nr);
	mutex_unlock(&core_lock);
	return res;
}

1.3 i2c transfer

i2c adapter的主要目的,就是在操作i2c_client時,通過引用對應的i2c adapter,發起i2c讀寫操作。

對i2c_client的讀操作:

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;
	int ret;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.flags |= I2C_M_RD;
	msg.len = count;
	msg.buf = buf;

	ret = i2c_transfer(adap, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg received), return #bytes received,
	 * else error code.
	 */
	return (ret == 1) ? count : ret;
}

對i2c_client的寫操作:

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
	int ret;
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.len = count;
	msg.buf = (char *)buf;

	ret = i2c_transfer(adap, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg transmitted), return #bytes
	 * transmitted, else error code.
	 */
	return (ret == 1) ? count : ret;
}

讀寫都會調用到i2c_transfer()函數:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;

	if (adap->algo->master_xfer) {

		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_adapter(adap);
			if (!ret)
				/* I2C activity is ongoing. */
				return -EAGAIN;
		} else {
			i2c_lock_adapter(adap);
		}

		ret = __i2c_transfer(adap, msgs, num);
		i2c_unlock_adapter(adap);

		return ret;
	} else {
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}

↓

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	unsigned long orig_jiffies;
	int ret, try;

	if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
		return -EOPNOTSUPP;

	/* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets
	 * enabled.  This is an efficient way of keeping the for-loop from
	 * being executed when not needed.
	 */
	if (static_key_false(&i2c_trace_msg)) {
		int i;
		for (i = 0; i < num; i++)
			if (msgs[i].flags & I2C_M_RD)
				trace_i2c_read(adap, &msgs[i], i);
			else
				trace_i2c_write(adap, &msgs[i], i);
	}

	/* Retry automatically on arbitration loss */
	orig_jiffies = jiffies;
	for (ret = 0, try = 0; try <= adap->retries; try++) {
		ret = adap->algo->master_xfer(adap, msgs, num);
		if (ret != -EAGAIN)
			break;
		if (time_after(jiffies, orig_jiffies + adap->timeout))
			break;
	}

	if (static_key_false(&i2c_trace_msg)) {
		int i;
		for (i = 0; i < ret; i++)
			if (msgs[i].flags & I2C_M_RD)
				trace_i2c_reply(adap, &msgs[i], i);
		trace_i2c_result(adap, i, ret);
	}

	return ret;
}

可以看到最終調用的是adap->algo->master_xfer()函數,對應i2c-omap的實現:

static const struct i2c_algorithm omap_i2c_algo = {
	.master_xfer	= omap_i2c_xfer,
	.functionality	= omap_i2c_func,
};

↓

static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
	struct omap_i2c_dev *omap = i2c_get_adapdata(adap);
	int i;
	int r;

	r = pm_runtime_get_sync(omap->dev);
	if (r < 0)
		goto out;

	r = omap_i2c_wait_for_bb_valid(omap);
	if (r < 0)
		goto out;

	r = omap_i2c_wait_for_bb(omap);
	if (r < 0)
		goto out;

	if (omap->set_mpu_wkup_lat != NULL)
		omap->set_mpu_wkup_lat(omap->dev, omap->latency);

	for (i = 0; i < num; i++) {
		r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));
		if (r != 0)
			break;
	}

	if (r == 0)
		r = num;

	omap_i2c_wait_for_bb(omap);

	if (omap->set_mpu_wkup_lat != NULL)
		omap->set_mpu_wkup_lat(omap->dev, -1);

out:
	pm_runtime_mark_last_busy(omap->dev);
	pm_runtime_put_autosuspend(omap->dev);
	return r;
}

omap_i2c_xfer()就是最後實際實現i2c讀寫操作的地方。

1.4 i2c-dev

可以看到i2c_adapter在系統中沒有單獨使用的,必須通過操作i2c_client來操作i2c_adapter。

如果i2c_adapter控制器上沒有掛載實際的物理設備,怎麼來調試i2c_adapter呢?

系統針對每個i2c_adapter都創建了對應的字符設備/dev/i2c-%d,通過這個字符設備節點可以創建虛擬的i2c_client,進而操作i2c_adapter

drivers\i2c\i2c-dev.c:

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

    /* (1) 註冊對應的字符設備 */
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

    /* (2) 創建"/sys/class/i2c-dev" */
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
    /* (3) 針對每一個i2c_adapter,創建"/sys/class/i2c-dev/i2c-%d"
        udev會自動的創建"/dev/i2c-%d"節點 
     */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

↓

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;

    /* (3.1) 獲取到i2c_adapter的device */
	if (dev->type != &i2c_adapter_type)
		return 0;
	adap = to_i2c_adapter(dev);

	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);

	/* register this i2c device with the driver core */
    /* (3.2) 根據adap->nr,註冊對應的"/sys/class/i2c-dev/i2c-%d" */
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	if (IS_ERR(i2c_dev->dev)) {
		res = PTR_ERR(i2c_dev->dev);
		goto error;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	return_i2c_dev(i2c_dev);
	return res;
}

對應的字符設備操作函數如下:

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

在open的時候會創建虛擬的i2c_client設備:

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;

    /* (1.1) 根據從設備號找到對應的i2c_dev */
	i2c_dev = i2c_dev_get_by_minor(minor);
	if (!i2c_dev)
		return -ENODEV;

    /* (1.2) 根據i2c_dev找到對應的i2c_adapter */
	adap = i2c_get_adapter(i2c_dev->adap->nr);
	if (!adap)
		return -ENODEV;

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
    /* (1.3) 分配新的i2c_client結構 */
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

    /* (1.4) 將i2c_adapter連接到i2c_client */
	client->adapter = adap;
	file->private_data = client;

	return 0;
}

可以通過ioctl的I2C_SLAVE命令來設置i2c_client的地址:

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
        /* (1.5) 設置i2c_client的地址 */
		client->addr = arg;
		return 0;
    
    ...
}

讀寫操作都是通過i2c_client,發起對i2c_adapter的讀寫:

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	tmp = kmalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;

	pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
		iminor(file_inode(file)), count);

	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
	kfree(tmp);
	return ret;
}

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
		iminor(file_inode(file)), count);

	ret = i2c_master_send(client, tmp, count);
	kfree(tmp);
	return ret;
}

1.5 pin mux

i2c adapter也是需要把對應的芯片pin腳配置成i2c功能管腳的。

目前dts中部分配置,arch\arm\boot\dts\am57xx-beagle-x15-common.dtsi:

&dra7_pmx_core {
    ...

	i2c1_pins_default: i2c1_pins_default {
		pinctrl-single,pins = <
			DRA7XX_CORE_IOPAD(0x3800, (PIN_INPUT_PULLUP | MUX_MODE0)) /* i2c1_sda.sda */
			DRA7XX_CORE_IOPAD(0x3804, (PIN_INPUT_PULLUP | MUX_MODE0)) /* i2c1_scl.scl */
		>;
	};

	i2c4_pins_default: i2c4_pins_default {
		pinctrl-single,pins = <
			DRA7XX_CORE_IOPAD(0x3440, (PIN_INPUT_PULLUP | MUX_MODE7)) /* gpmc_a0.i2c4_scl */
			DRA7XX_CORE_IOPAD(0x3444, (PIN_INPUT_PULLUP | MUX_MODE7)) /* gpmc_a1.i2c4_sda */
		>;
	};

    ...
}

&i2c1 {
	status = "okay";
	clock-frequency = <400000>;

	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_pins_default>;

    ...
}

&i2c4 {
	status = "okay";
	clock-frequency = <400000>;

	pinctrl-names = "default";
	pinctrl-0 = <&i2c4_pins_default>;

	...
};

在驅動probe時,會自動配置pin ctrl:

driver_probe_device() -> really_probe() -> pinctrl_bind_pins():

int pinctrl_bind_pins(struct device *dev)
{
	int ret;

	dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
	if (!dev->pins)
		return -ENOMEM;

    /* (1) 獲取到device的pinctrl配置
        根據dts中的配置創建 
     */
	dev->pins->p = devm_pinctrl_get(dev);
	if (IS_ERR(dev->pins->p)) {
		dev_dbg(dev, "no pinctrl handle\n");
		ret = PTR_ERR(dev->pins->p);
		goto cleanup_alloc;
	}

    /* (2) 獲取"default"配置 */
	dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_DEFAULT);
	if (IS_ERR(dev->pins->default_state)) {
		dev_dbg(dev, "no default pinctrl state\n");
		ret = 0;
		goto cleanup_get;
	}

    /* (3) 獲取"init"配置 */
	dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_INIT);
	if (IS_ERR(dev->pins->init_state)) {
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no init pinctrl state\n");

        /* (4.1) 配置"default"配置 */
		ret = pinctrl_select_state(dev->pins->p,
					   dev->pins->default_state);
	} else {
        /* (4.2) 配置"init"配置 */
		ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
	}

	if (ret) {
		dev_dbg(dev, "failed to activate initial pinctrl state\n");
		goto cleanup_get;
	}

#ifdef CONFIG_PM
	/*
	 * If power management is enabled, we also look for the optional
	 * sleep and idle pin states, with semantics as defined in
	 * <linux/pinctrl/pinctrl-state.h>
	 */
	dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_SLEEP);
	if (IS_ERR(dev->pins->sleep_state))
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no sleep pinctrl state\n");

	dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_IDLE);
	if (IS_ERR(dev->pins->idle_state))
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no idle pinctrl state\n");
#endif

	return 0;

	/*
	 * If no pinctrl handle or default state was found for this device,
	 * let's explicitly free the pin container in the device, there is
	 * no point in keeping it around.
	 */
cleanup_get:
	devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
	devm_kfree(dev, dev->pins);
	dev->pins = NULL;

	/* Only return deferrals */
	if (ret != -EPROBE_DEFER)
		ret = 0;

	return ret;
}

2. i2c client

站在用戶的角度其實不關心i2c控制器具體是什麼實現的,我只要驅動起我的i2c設備即可,重點是在i2c設備的功能實現上。

i2c設備對應i2c_client,所以用戶的業務都在i2c_client driver中實現。

2.1 device (dts)

首先我們需要在dts中定義一個i2c_client設備。

例如我們在i2c1總線上掛載一個PCF853 RTC芯片,arch\arm\boot\dts\am57xx-beagle-x15-common.dtsi:

	i2c2_pins_default: i2c2_pins_default {
		pinctrl-single,pins = <
			DRA7XX_CORE_IOPAD(0x3808, (PIN_INPUT_PULLUP | MUX_MODE0)) /* i2c2_sda.sda */
			DRA7XX_CORE_IOPAD(0x380C, (PIN_INPUT_PULLUP | MUX_MODE0)) /* i2c2_scl.scl */
		>;
	};


&i2c2 {
	status = "okay";
	clock-frequency = <400000>;

	pinctrl-names = "default";
	pinctrl-0 = <&i2c2_pins_default>;

	pcf8563: pcf8563@51 {
		compatible = "nxp,pcf8563";
		reg = <0x51>;
		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
    };
    
    ...
}

對應的i2c_client device是在i2c adapter創建完成以後,遍歷下掛的i2c設備時創建的:

omap_i2c_probe() -> i2c_add_numbered_adapter() -> __i2c_add_numbered_adapter() -> i2c_register_adapter() -> of_i2c_register_devices():

static void of_i2c_register_devices(struct i2c_adapter *adap)
{
	struct device_node *node;

	/* Only register child devices if the adapter has a node pointer set */
	if (!adap->dev.of_node)
		return;

	dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

    /* (7.3.1) 遍歷dts中i2c adapter節點"i2cN"下掛的子節點 */
	for_each_available_child_of_node(adap->dev.of_node, node) {
		if (of_node_test_and_set_flag(node, OF_POPULATED))
			continue;
        /* (7.3.2) 把每一個子節點創建成對應的i2c_client device */
		of_i2c_register_device(adap, node);
	}
}

↓

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
						 struct device_node *node)
{
	struct i2c_client *result;
	struct i2c_board_info info = {};
	struct dev_archdata dev_ad = {};
	const __be32 *addr_be;
	u32 addr;
	int len;

	dev_dbg(&adap->dev, "of_i2c: register %s\n", node->full_name);

    /* (7.3.2.1) 獲取節點的"compatible"屬性 */
	if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
		dev_err(&adap->dev, "of_i2c: modalias failure on %s\n",
			node->full_name);
		return ERR_PTR(-EINVAL);
	}

    /* (7.3.2.2) 獲取節點的"reg"屬性,把其轉換成i2c設備地址 */
	addr_be = of_get_property(node, "reg", &len);
	if (!addr_be || (len < sizeof(*addr_be))) {
		dev_err(&adap->dev, "of_i2c: invalid reg on %s\n",
			node->full_name);
		return ERR_PTR(-EINVAL);
	}

	addr = be32_to_cpup(addr_be);
	if (addr & I2C_TEN_BIT_ADDRESS) {
		addr &= ~I2C_TEN_BIT_ADDRESS;
		info.flags |= I2C_CLIENT_TEN;
	}

	if (addr & I2C_OWN_SLAVE_ADDRESS) {
		addr &= ~I2C_OWN_SLAVE_ADDRESS;
		info.flags |= I2C_CLIENT_SLAVE;
	}

	if (i2c_check_addr_validity(addr, info.flags)) {
		dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n",
			info.addr, node->full_name);
		return ERR_PTR(-EINVAL);
	}

	info.addr = addr;
	info.of_node = of_node_get(node);
	info.archdata = &dev_ad;

	if (of_get_property(node, "wakeup-source", NULL))
		info.flags |= I2C_CLIENT_WAKE;

    /* (7.3.2.3) 創建i2c_client device */
	result = i2c_new_device(adap, &info);
	if (result == NULL) {
		dev_err(&adap->dev, "of_i2c: Failure registering %s\n",
			node->full_name);
		of_node_put(node);
		return ERR_PTR(-EINVAL);
	}
	return result;
}

↓

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

    /* (7.3.2.3.1) 分配i2c_client */
	client = kzalloc(sizeof *client, GFP_KERNEL);
	if (!client)
		return NULL;

    /* (7.3.2.3.2) 初始化i2c_client */
	client->adapter = adap;

	client->dev.platform_data = info->platform_data;

	if (info->archdata)
		client->dev.archdata = *info->archdata;

	client->flags = info->flags;
	client->addr = info->addr;
	client->irq = info->irq;

	strlcpy(client->name, info->type, sizeof(client->name));

	status = i2c_check_addr_validity(client->addr, client->flags);
	if (status) {
		dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
			client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
		goto out_err_silent;
	}

	/* Check for address business */
	status = i2c_check_addr_busy(adap, i2c_encode_flags_to_addr(client));
	if (status)
		goto out_err;

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type;
	client->dev.of_node = info->of_node;
	client->dev.fwnode = info->fwnode;

	i2c_dev_set_name(adap, client);
    /* (7.3.2.3.3) 註冊i2c_client */
	status = device_register(&client->dev);
	if (status)
		goto out_err;

	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
		client->name, dev_name(&client->dev));

	return client;

out_err:
	dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
		"(%d)\n", client->name, client->addr, status);
out_err_silent:
	kfree(client);
	return NULL;
}

2.2 driver

爲了適配PCF853 RTC芯片,必須要寫一個對應的i2c_client driver。drivers\rtc\rtc-pcf8563.c:

static struct i2c_driver pcf8563_driver = {
	.driver		= {
		.name	= "rtc-pcf8563",
		.of_match_table = of_match_ptr(pcf8563_of_match),
	},
	.probe		= pcf8563_probe,
	.id_table	= pcf8563_id,
};

static const struct of_device_id pcf8563_of_match[] = {
	{ .compatible = "nxp,pcf8563" },
	{}
};

2.3 interrupt

在of_i2c_register_device()創建的過程中,沒有處理"interrupts"屬性,但是在i2c_client driver的probe()函數中又直接使用了client->irq,這裏可能有些問題會影響alarm功能的使用:

static int pcf8563_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
    ...

	if (client->irq > 0) {
		err = devm_request_threaded_irq(&client->dev, client->irq,
				NULL, pcf8563_irq,
				IRQF_SHARED|IRQF_ONESHOT|IRQF_TRIGGER_FALLING,
				pcf8563->rtc->name, client);
		if (err) {
			dev_err(&client->dev, "unable to request IRQ %d\n",
								client->irq);
			return err;
		}

	}

    ...
}

3. RTC

3.1 rtc框架

在rtc的i2c_client driver中最重要的工作是調用devm_rtc_device_register()註冊rtc device:

static int pcf8563_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	struct pcf8563 *pcf8563;
	int err;
	unsigned char buf;
	unsigned char alm_pending;

	dev_dbg(&client->dev, "%s\n", __func__);

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
		return -ENODEV;

	pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563),
				GFP_KERNEL);
	if (!pcf8563)
		return -ENOMEM;

	dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");

	i2c_set_clientdata(client, pcf8563);
	pcf8563->client = client;
	device_set_wakeup_capable(&client->dev, 1);

	/* Set timer to lowest frequency to save power (ref Haoyu datasheet) */
	buf = PCF8563_TMRC_1_60;
	err = pcf8563_write_block_data(client, PCF8563_REG_TMRC, 1, &buf);
	if (err < 0) {
		dev_err(&client->dev, "%s: write error\n", __func__);
		return err;
	}

	err = pcf8563_get_alarm_mode(client, NULL, &alm_pending);
	if (err) {
		dev_err(&client->dev, "%s: read error\n", __func__);
		return err;
	}
	if (alm_pending)
		pcf8563_set_alarm_mode(client, 0);

	pcf8563->rtc = devm_rtc_device_register(&client->dev,
				pcf8563_driver.driver.name,
				&pcf8563_rtc_ops, THIS_MODULE);

	if (IS_ERR(pcf8563->rtc))
		return PTR_ERR(pcf8563->rtc);

	if (client->irq > 0) {
		err = devm_request_threaded_irq(&client->dev, client->irq,
				NULL, pcf8563_irq,
				IRQF_SHARED|IRQF_ONESHOT|IRQF_TRIGGER_FALLING,
				pcf8563->rtc->name, client);
		if (err) {
			dev_err(&client->dev, "unable to request IRQ %d\n",
								client->irq);
			return err;
		}

	}

#ifdef CONFIG_COMMON_CLK
	/* register clk in common clk framework */
	pcf8563_clkout_register_clk(pcf8563);
#endif

	/* the pcf8563 alarm only supports a minute accuracy */
	pcf8563->rtc->uie_unsupported = 1;

	return 0;
}

pcf8563_rtc_ops爲rtc的函數操作集,其中的功能主要分爲兩部分:timer 和 alarm。

static const struct rtc_class_ops pcf8563_rtc_ops = {
	.ioctl		= pcf8563_rtc_ioctl,
	.read_time	= pcf8563_rtc_read_time,
	.set_time	= pcf8563_rtc_set_time,
	.read_alarm	= pcf8563_rtc_read_alarm,
	.set_alarm	= pcf8563_rtc_set_alarm,
	.alarm_irq_enable = pcf8563_irq_enable,
};
  • timer的主要功能就是設置和讀取時鐘。
static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
	return pcf8563_get_datetime(to_i2c_client(dev), tm);
}

static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
	return pcf8563_set_datetime(to_i2c_client(dev), tm);
}
  • alarm的主要功能就是設置鬧鐘,到期後中斷通知喚醒。
static irqreturn_t pcf8563_irq(int irq, void *dev_id)
{
	struct pcf8563 *pcf8563 = i2c_get_clientdata(dev_id);
	int err;
	char pending;

	err = pcf8563_get_alarm_mode(pcf8563->client, NULL, &pending);
	if (err)
		return IRQ_NONE;

	if (pending) {
		rtc_update_irq(pcf8563->rtc, 1, RTC_IRQF | RTC_AF);
		pcf8563_set_alarm_mode(pcf8563->client, 1);
		return IRQ_HANDLED;
	}

	return IRQ_NONE;
}

3.2 walltime 和 rtc的同步

在系統初始化的時候linux walltime會從rtc中讀取一個基準時刻,在隨後的系統運行過程中walltime不再和rtc發生聯繫,而是使用tick來更新walltime。

static int __init rtc_hctosys(void)
{
	int err = -ENODEV;
	struct rtc_time tm;
	struct timespec64 tv64 = {
		.tv_nsec = NSEC_PER_SEC >> 1,
	};
	struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);

	if (rtc == NULL) {
		pr_info("unable to open rtc device (%s)\n",
			CONFIG_RTC_HCTOSYS_DEVICE);
		goto err_open;
	}

	err = rtc_read_time(rtc, &tm);
	if (err) {
		dev_err(rtc->dev.parent,
			"hctosys: unable to read the hardware clock\n");
		goto err_read;

	}

	tv64.tv_sec = rtc_tm_to_time64(&tm);

	err = do_settimeofday64(&tv64);

	dev_info(rtc->dev.parent,
		"setting system clock to "
		"%d-%02d-%02d %02d:%02d:%02d UTC (%lld)\n",
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
		tm.tm_hour, tm.tm_min, tm.tm_sec,
		(long long) tv64.tv_sec);

err_read:
	rtc_class_close(rtc);

err_open:
	rtc_hctosys_ret = err;

	return err;
}

walltime具體和哪個rtc同步由CONFIG_RTC_HCTOSYS_DEVICE定義決定:

CONFIG_RTC_HCTOSYS_DEVICE="rtc0"

3.3 rtc的使用

$ hwclock --help

Usage:
 hwclock [function] [option...]

Query or set the hardware clock.

Functions:
 -h, --help           show this help text and exit
 -r, --show           read hardware clock and print result
     --get            read hardware clock and print drift corrected result
     --set            set the RTC to the time given with --date
 -s, --hctosys        set the system time from the hardware clock
 -w, --systohc        set the hardware clock from the current system time
     --systz          set the system time based on the current timezone
     --adjust         adjust the RTC to account for systematic drift since
                        the clock was last set or adjusted
 -c, --compare        periodically compare the system clock with the CMOS clock
     --getepoch       print out the kernel's hardware clock epoch value
     --setepoch       set the kernel's hardware clock epoch value to the 
                        value given with --epoch
     --predict        predict RTC reading at time given with --date
 -V, --version        display version information and exit

選項:
 -u, --utc            the hardware clock is kept in UTC
     --localtime      the hardware clock is kept in local time
 -f, --rtc <file>     special /dev/... file to use instead of default
     --directisa      access the ISA bus directly instead of /dev/rtc
     --badyear        ignore RTC's year because the BIOS is broken
     --date <time>    specifies the time to which to set the hardware clock
     --epoch <year>   specifies the year which is the beginning of the
                        hardware clock's epoch value
     --update-drift   update drift factor in /etc/adjtime (requires
                        --set or --systohc)
     --noadjfile      do not access /etc/adjtime; this requires the use of
                        either --utc or --localtime
     --adjfile <file> specifies the path to the adjust file;
                        the default is /etc/adjtime
     --test           do not update anything, just show what would happen
 -D, --debug          debugging mode

$ sudo hwclock --get
2019年08月08日 星期四 02時36分56秒  .445785 second

設置硬件時鐘的操作:

# hwclock --set --date="09/17/2003 13:26:00"
或者
# clock --set --date="09/17/2003 13:26:00"

通用的設置格式:hwclock/clock --set --date=“月/日/年時:分:秒”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章