Linux驱动开发进阶(十)- I2C子系统BSP驱动

发布于:2025-04-17 ⋅ 阅读:(68) ⋅ 点赞:(0)

1、前言

  1. 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
  2. 本文属于个人学习后的总结,不太具备教学功能。

2、I2C总线注册

和其它总线驱动一样,I2C驱动的注册也依赖I2C总线的注册。下图i2c_init是i2c总线驱动的入口:

static int __init i2c_init(void)
{
	int retval;

	retval = of_alias_get_highest_id("i2c");

	down_write(&__i2c_board_lock);
	if (retval >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = retval + 1;
	up_write(&__i2c_board_lock);

	retval = bus_register(&i2c_bus_type);
	if (retval)
		return retval;

	is_registered = true;

#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) {
		retval = -ENOMEM;
		goto bus_err;
	}
#endif
	retval = i2c_add_driver(&dummy_driver);
	if (retval)
		goto class_err;

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
	if (IS_ENABLED(CONFIG_ACPI))
		WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));

	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	is_registered = false;
	bus_unregister(&i2c_bus_type);
	return retval;
}

of_alias_get_highest_id函数用于找到设备树中的aliases表,例如:

aliases {
    gpio0 = &gpio0;
    gpio1 = &gpio1;
    gpio2 = &gpio2;
    gpio3 = &gpio3;
    gpio4 = &gpio4;
    i2c0 = &i2c0;
    i2c1 = &i2c1;
    i2c2 = &i2c2;
    i2c3 = &i2c3;
    i2c4 = &i2c4;
    i2c5 = &i2c5;
};

aliases表指定了相关外设的别名,例如&i2c5的别名为i2c5,通过of_alias_get_highest_id函数,查找i2c最高的alias,即i2c5这个ID,很显然这个ID就是1。

接着就是bus_register(&i2c_bus_type),该函数完成i2c总线的注册,将总线注册到内核中,此时设备和驱动就可以引用i2c_bus_type总线了。接着class_compat_register(“i2c-adapter”)是注册设备类。

接下来再看i2c_bus_type结构体,但这里不写介绍了,主要就是match匹配函数:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

3、I2C设备注册

一样的,i2c设备分为i2c控制器设备和挂载在i2c控制器下的i2c子设备。i2c控制器设备通过i2c_add_adapter()函数来注册:

int i2c_add_adapter(struct i2c_adapter *adapter)
{
	struct device *dev = &adapter->dev;
	int id;

	if (dev->of_node) {
		id = of_alias_get_id(dev->of_node, "i2c");
		if (id >= 0) {
			adapter->nr = id;
			return __i2c_add_numbered_adapter(adapter);
		}
	}

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adapter,
		       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (WARN(id < 0, "couldn't get idr"))
		return id;

	adapter->nr = id;

	return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);

重点是__i2c_add_numbered_adapter()函数的实现,这里只截取了一部分:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	...
        
	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) {
		pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
		goto out_list;
	}

	res = of_i2c_setup_smbus_alert(adap);
	if (res)
		goto out_reg;

	pm_runtime_no_callbacks(&adap->dev);
	pm_suspend_ignore_children(&adap->dev, true);
	pm_runtime_enable(&adap->dev);

	res = i2c_init_recovery(adap);
	if (res == -EPROBE_DEFER)
		goto out_reg;

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

#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

	/* create pre-declared device nodes */
	of_i2c_register_devices(adap);
	i2c_acpi_install_space_handler(adap);
	i2c_acpi_register_devices(adap);

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

	/* Notify drivers */
	mutex_lock(&core_lock);
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

    ...
        
}

dev_set_name()用于设置i2c控制器设备的名称,然后初始化i2c控制器设备的bus和type。然后调用device_register()函数向内核注册i2c控制器设备。接着调用i2c_setup_smbus_alert()函数用于初始化SMBus总线接口。然后调用of_i2c_register_devices()函数用于将设备树中的I2C控制器设备下的所有子设备节点全部注册到内核。

i2c_add_adapter函数一般在i2c控制器驱动程序中调用,也就是说,一般在注册控制器驱动时才去注册I2C控制器设备和I2c从设备。

4、I2C驱动注册

i2c控制器抽象出了一个结构体,为:

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */
	unsigned long locked_flags;	/* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED		0
#define I2C_ALF_SUSPEND_REPORTED	1

	int nr;
	char name[48];
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;

	struct irq_domain *host_notify_domain;
};

重点关注algo函数,algo用于I2C底层的数据传输,必须要初始化。向内核注册控制器驱动很简单,就是调用i2c_add_adapter()。上面也介绍了这个函数,该函数会将I2C控制器设备和I2C从设备全部注册到内核中,并且注册一个i2c_adapter。

下面展示如何注册一个i2c控制器:

重点看一下foo_i2cadapter_algo,这是一个i2c_algorithm结构体:

struct i2c_algorithm {
	/*
	 * If an adapter algorithm can't do I2C-level access, set master_xfer
	 * to NULL. If an adapter algorithm can do SMBus access, set
	 * smbus_xfer. If set to NULL, the SMBus protocol is simulated
	 * using common I2C messages.
	 *
	 * master_xfer should return the number of messages successfully
	 * processed, or a negative value on error
	 */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*master_xfer_atomic)(struct i2c_adapter *adap,
				   struct i2c_msg *msgs, int num);
	int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
			  unsigned short flags, char read_write,
			  u8 command, int size, union i2c_smbus_data *data);
	int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
				 unsigned short flags, char read_write,
				 u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality)(struct i2c_adapter *adap);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

static int dummy_i2cadapter_xfer (struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    int i = 0, index;
    int wr_flag, stop;
    for (index = 0; index < num; index++) {
        //发生错误,无回应,I2C_M_TEN表示支持10bit地址,这里不支持10bit地址
        if( (msgs[index].flags & (I2C_M_NO_RD_ACK | I2C_M_IGNORE_NAK | I2C_M_TEN)) || msgs[i].len == 0) {
            return ENOTSUPP;
        }
        //发送开始信号,从设备地址
        if (!(msgs[index].flags & I2C_M_NOSTART)) {
            wr_flag = (msgs[index].flags & I2C_M_RD ? 0x01 : 0x00);
            printk(KERN_INFO "TX: START %x\n", (msgs[index].addr<<1) | wr_flag );
        }
        //读取数据
        if(msgs[index].flags & I2C_M_RD) {
            for(i = 0; i < msgs[index].len; i++) {
                stop = (index+1 == num && i+1 == msgs[index].len) ? 0x01 : 0x00;
                msgs[index].buf[i] = 'A';
                if(stop)
                    printk(KERN_INFO "TX: STOP\n");
            }
        }
        //发送数据
        else {
            for(i = 0; i < msgs[index].len; i++) {
                stop = (index+1 == num && i+1 == msgs[index].len) ? 0x01 : 0x00;
                printk(KERN_INFO "TX: %c \n", msgs[index].buf[i]);
                if(stop)
                    printk(KERN_INFO "TX: STOP\n");
            }
        }
    }
    return 0;
}

static u32 dummy_i2cadapter_functionality (struct i2c_adapter * adap)
{
    return I2C_FUNC_I2C;
}

static struct i2c_algorithm dummy_i2cadapter_algo = 
{
    .master_xfer = dummy_i2cadapter_xfer,
    .functionality = dummy_i2cadapter_functionality
};

在编写i2c设备驱动程序时,调用的i2c_transfer,实际调用的就是bsp驱动中的i2c_algorithm结构体中的master_xfer指向的函数,这也是Linux中使用c语言实现多态的一种表现。

完整的i2c bsp驱动程序可以参考:李山文的《Linux驱动开发进阶》i2c bsp驱动示例

总结

关于《Linux驱动开放进阶》的学习,暂时告一段落。通过该书籍的学习,加强了对Linux驱动的理解。但Linux内核驱动不仅仅只有这些,目前熟悉Linux驱动框架也是为了提高解决问题的效率。但想要继续加深理解,还是得提高驱动代码的阅读量和编写量。


网站公告

今日签到

点亮在社区的每一天
去签到