linux I2C驱动

I2C驱动分为I2C适配器驱动和I2C设备驱动,I2C适配器驱动芯片厂商已经帮我们实现好了,I2C设备驱动需要用户自己编写。下面我们主要就这两个方面进行分析。另外这里举例是用NXP的imx6ull芯片。

I2C适配器驱动

linux启动最先运行的I2C驱动相关函数是i2c_init(),这个函数是linux内核实现的,而不是厂商实现的,位于drivers\i2c\i2c-core.c文件第1879行:

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

	return 0;

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


postcore_initcall(i2c_init);

 主要的作用是注册I2C总线,注册总线之后其他驱动程序才能向I2C总线注册驱动或者设备:

bus_register(&i2c_bus_type);

所以需要最早执行。

最后的postcore_initcall(i2c_init);和module_init宏定义作用类似,只不过放在上电执行优先级更高的内存区域。

接下来执行的是i2c_adap_imx_init(void),位于drivers/i2c/busses/i2c-imx.c文件中,第1131行,这个函数是芯片厂商实现的,如下:

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

可以看到这是一个platform设备驱动,作用主要是添加I2C适配器,所以,I2C的总线初始化也是要借助platform。最后的subsys_initcall(i2c_adap_imx_init);也类似与module_init宏,执行优先级晚于postcore_initcall,早于module_init。

下面看一下这个platform设备的probe函数:

static int i2c_imx_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
							   &pdev->dev);
	struct imx_i2c_struct *i2c_imx;
	struct resource *res;
	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
	void __iomem *base;
	int irq, ret;
	dma_addr_t phy_addr;

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

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "can't get irq number\n");
		return irq;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);

	phy_addr = (dma_addr_t)res->start;
	i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
	if (!i2c_imx)
		return -ENOMEM;

	if (of_id)
		i2c_imx->hwdata = of_id->data;
	else
		i2c_imx->hwdata = (struct imx_i2c_hwdata *)
				platform_get_device_id(pdev)->driver_data;

	/* Setup i2c_imx driver structure */
	strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
	i2c_imx->adapter.owner		= THIS_MODULE;
	i2c_imx->adapter.algo		= &i2c_imx_algo;
	i2c_imx->adapter.dev.parent	= &pdev->dev;
	i2c_imx->adapter.nr		= pdev->id;
	i2c_imx->adapter.dev.of_node	= pdev->dev.of_node;
	i2c_imx->base			= base;

	/* Get I2C clock */
	i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(i2c_imx->clk)) {
		dev_err(&pdev->dev, "can't get I2C clock\n");
		return PTR_ERR(i2c_imx->clk);
	}

	ret = clk_prepare_enable(i2c_imx->clk);
	if (ret) {
		dev_err(&pdev->dev, "can't enable I2C clock\n");
		return ret;
	}
	/* Request IRQ */
	ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
			       IRQF_NO_SUSPEND, pdev->name, i2c_imx);
	if (ret) {
		dev_err(&pdev->dev, "can't claim irq %d\n", irq);
		goto clk_disable;
	}

	/* Init queue */
	init_waitqueue_head(&i2c_imx->queue);

	/* Set up adapter data */
	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

	/* Set up clock divider */
	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "clock-frequency", &i2c_imx->bitrate);
	if (ret < 0 && pdata && pdata->bitrate)
		i2c_imx->bitrate = pdata->bitrate;

	/* Set up chip registers to defaults */
	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
			i2c_imx, IMX_I2C_I2CR);
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);

	/* Add I2C adapter */
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
	if (ret < 0) {
		dev_err(&pdev->dev, "registration failed\n");
		goto clk_disable;
	}

	/* Set up platform driver data */
	platform_set_drvdata(pdev, i2c_imx);
	clk_disable_unprepare(i2c_imx->clk);

	dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
	dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
	dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
		i2c_imx->adapter.name);
	dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

	/* Init DMA config if supported */
	i2c_imx_dma_request(i2c_imx, phy_addr);

	return 0;   /* Return OK */

clk_disable:
	clk_disable_unprepare(i2c_imx->clk);
	return ret;
}

可以看到NXP定义了一个struct imx_i2c_struct 结构体指针,并为它分配空间,struct imx_i2c_struct结构体中有一个非常重要的成员变量,也是一个结构体

struct i2c_adapter    adapter;

在probe函数的第37行对其进行了简单的初始化赋值,37行有一个重要的函数i2c_imx->adapter.algo        = &i2c_imx_algo; 函数i2c_imx_algo就是实际直接操作寄存器完成I2C通信的函数,感兴趣可以自行阅读源码,这个函数肯定要厂商自行实现,因为不同的芯片I2C寄存器肯定是不一样的,操作规则肯定也不一样。

probe函数我们不逐行分析了,主要是根据设备树中的信息进行一些设置,我们着重看84行的一个函数i2c_add_numbered_adapter(&i2c_imx->adapter);作用是添加I2C适配器,其调用关系如下:

i2c_add_numbered_adapter->

    __i2c_add_numbered_adapter->

          i2c_register_adapter

i2c_register_adapter定义中我们关注一个函数device_register:

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

	...

	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;


    ...

exit_recovery:
	/* create pre-declared device nodes */
	of_i2c_register_devices(adap);
    ...
}

可以看到struct i2c_adapter结构体中有个struct device的结构体,我们将adap->dev这个设备添加到了I2C总线上,之后我们在驱动的其他部分就可以通过类似container_of的操作来获取完整的struct i2c_adapter结构体了。

其实I2C适配器struct i2c_adapter和用户在设备树中自定义的I2C设备都会被添加到I2C总线上,但是他们的dev.type不同,所以可以作为区分。

至此I2C适配器已经添加到总线上了,其实用户可以通过设备文件直接操作I2C适配器,但大多数情况下,用户是通过在自定义的I2C设备驱动中,操作I2C适配器,完成通信。

到这里我想插一些东西,通过上述i2c_register_adapter,我们成功将I2C适配器设备添加到了I2C总线上,但是同时还有其他的一些工作,I2C适配器节点的子节点,也就是用户自定义的I2C设备,也会在此函数中添加到I2C总线上:

上述代码中会调用一个函数of_i2c_register_devices(adap), 此函数下面的调用关系如下:

of_i2c_register_devices->

    of_i2c_register_device->

        i2c_new_device->

            device_register

在i2c_new_device函数中,还会创建struct i2c_client 结构体(此结构体用户为自定义设备编写驱动时会用),最终调用device_register函数将I2C适配器节点下的子设备注册到I2C总线下面去。

下面我们接着I2C适配器设备来说,I2C适配器的驱动部分:

首先我们先来看用户直接通过设备文件操作I2C适配器部分的驱动是如何实现的:

文件drivers/i2c/i2c-dev.c,620行函数__init i2c_dev_init(void):

static int __init i2c_dev_init(void)
{
	int res;

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

	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

	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 */
	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;
}

module_init(i2c_dev_init);

此函数为I2C适配器创建设备文件,i2c_for_each_dev(NULL, i2cdev_attach_adapter);遍历I2C总线上的所有设备,每个设备都调用i2cdev_attach_adapter()函数,定义如下:

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

	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 */
	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;
}

可以看到第7行,只对I2C适配器对应的struct device做处理,如果是用户自定义的设备树驱动,直接返回了。16行就时创建设备文件。至此所有的I2C适配器在/dev目录下都有了自己的设备文件,用户就可以通过直接操作设备文件来操作I2C适配器了,struct file_operations i2cdev_fops,的open、read、write、ioctl函数就省略了,这些都是linux内核实现的,I2C驱动中用到的另一个重要的结构体struct i2c_client  client是在open函数中创建的,感兴趣可以自行查看。这个结构体无论是I2C适配器还是自定义I2C设备都要使用,对于自定义的I2C设备下面还会详细讲述。

 自定义I2C设备驱动

这里以I2C传感器ap3216c为例:

static int __init ap3216c_init(void){
	return i2c_add_driver(&ap3216c);
}

static void __exit ap3216c_exit(void){
	i2c_del_driver(&ap3216c);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);

其中i2c_add_driver完成整个I2C驱动和设备的匹配,以及调用i2c_driver->probe函数:

#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

i2c_register_driver()函数定义如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p)))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

	INIT_LIST_HEAD(&driver->clients);
	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

其中res = driver_register(&driver->driver);是通用函数,所有向所有类型的总线(不止I2C总线)添加driver的操作都会调用这个函数,所以不详细介绍,只简单给出调用路径:

driver_register-->

    bus_add_driver-->

      driver_attach(drv)-->

        bus_for_each_dev-->  (这里是遍历总线所有设备,并对每个设备调用__driver_attach函数)

          -->__driver_attach

              -->driver_match_device

                     -->drv->bus->match(此函数指针实际调用的是i2c_device_match)

              -->driver_probe_device

                      -->really_probe

                              -->dev->bus->probe(此函数指针实际调用的是i2c_device_probe)

下面我们详细说明一下i2c_device_probe函数:

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	if (!client->irq && dev->of_node) {
		int irq = of_irq_get(dev->of_node, 0);

		if (irq == -EPROBE_DEFER)
			return irq;
		if (irq < 0)
			irq = 0;

		client->irq = irq;
	}

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;

	if (!device_can_wakeup(&client->dev))
		device_init_wakeup(&client->dev,
					client->flags & I2C_CLIENT_WAKE);
	dev_dbg(dev, "probe\n");

	status = of_clk_set_defaults(dev->of_node, false);
	if (status < 0)
		return status;

	status = dev_pm_domain_attach(&client->dev, true);
	if (status != -EPROBE_DEFER) {
		status = driver->probe(client, i2c_match_id(driver->id_table,
					client));
		if (status)
			dev_pm_domain_detach(&client->dev, true);
	}

	return status;
}

第3行struct i2c_client    *client = i2c_verify_client(dev);具体定义如下:

struct i2c_client *i2c_verify_client(struct device *dev)
{
	return (dev->type == &i2c_client_type)
			? to_i2c_client(dev)
			: NULL;
}

可以看到,此probe函数只针对设备树中用户自定义的struct device有效,对I2C适配器不起作用。

另外I2C驱动中,另外一个重要的结构体struct i2c_client    *client,就是在这里获取的。和I2C适配器不同,这里的struct i2c_client 不用像I2C适配器一样需要struct file_operation 中的open函数创建,而是内核从设备树提取I2C自定义设备信息的时候创建的,这里只是通过to_i2c_client(实质就是container_of)来获取。

获取到struct i2c_client结构体之后,会在36行 status = driver->probe(client, i2c_match_id(driver->id_table, client)); 调用用户自定义的probe函数,至此,就去执行用户添加的代码了。

这里给出一个自定义I2C设备驱动的例子(ap3216c芯片):

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "ap3216c.h"

#define AP3216C_CNT		1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev{
	struct cdev cdev;
	dev_t dev_id;
	int major;
	int minor;
	struct class * cls;
	struct device * device;
	struct i2c_client * client;
};

static int ap3216c_read_regs(struct ap3216c_dev * dev, u8 reg, u8 * buf, int len){
	struct i2c_client * client = dev->client;
	struct i2c_msg msgs[] = {
		{
			.addr = client->addr,
			.flags = 0,
			.len = 1,
			.buf = &reg
		},
		{
			.addr = client->addr,
			.flags = I2C_M_RD,
			.len = len,
			.buf = buf
		}
	};
	int ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
	if(ret == ARRAY_SIZE(msgs)){
		return 0;
	}
	else{
		printk("i2c rd failed=%d reg=%06x len=%d\n", ret, reg, len);
		return -EREMOTEIO;
	}
}

static int ap3216c_write_regs(struct ap3216c_dev * dev, u8 reg, u8 * buf, int len){
	struct i2c_client * client = dev->client;
	u8 b[256];
	int ret;	
	struct i2c_msg msgs[] = {
		{
			.addr = client->addr,
			.flags = 0,
			.len = len + 1,
			.buf = b
		},

	};
	b[0] = reg;
	memcpy(&b[1], buf, len);
	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
	if(ret == ARRAY_SIZE(msgs)){
		return 0;
	}
	else{
		printk("i2c rd failed=%d reg=%06x len=%d\n", ret, reg, len);
		return -EREMOTEIO;
	}
}

static int ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg){
	struct i2c_client * client = dev->client;
	u8 data;
	ap3216c_read_regs(dev, reg, &data, 1);
	return data;
	return i2c_smbus_read_byte_data(client, reg);
}

static int ap3216c_write_reg(struct ap3216c_dev * dev, u8 reg, u8 data){
	struct i2c_client * client = dev->client;
	return ap3216c_write_regs(dev, reg, &data, 1);
	return i2c_smbus_write_byte_data(client, reg, data);
}

static int ap3216c_open(struct inode *inode, struct file *filp){
	int ret = 0;
	struct ap3216c_dev * dev = (struct ap3216c_dev *)container_of(inode->i_cdev, struct ap3216c_dev, cdev);
	filp->private_data = dev;	

    ret = ap3216c_write_reg(dev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216c */
	if(ret < 0){
		printk("ap3216c_write_reg failed ret = %d.\n", ret);
		return ret;
	}
    mdelay(10);
    ap3216c_write_reg(dev, AP3216C_SYSTEMCONG, 0x03); /* 开启ALS, PS+IR */
	if(ret < 0)
		return ret;
    ret = ap3216c_read_reg(dev, AP3216C_SYSTEMCONG);
    if(ret == 0x03)
        return 0; /* 成功 */
    else
        return -EREMOTEIO; /* 失败 */
}

static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

ssize_t ap3216c_read(struct file *file, char __user *to, size_t size, loff_t *ppos){
	
	unsigned char buf[6];
	unsigned char i;
	int ret = 0;
	struct ap3216c_dev * dev = file->private_data;
	unsigned short ir = 0, ps = 0, als = 0, data[3];

	/* 循环读取所有传感器数据 */	
	for(i = 0; i < 6; i++)
	{
		ret = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
		if(ret < 0)
			return -EREMOTEIO;
		else
			buf[i] = (unsigned char)ret;
	}

	if(buf[0] & 0X80)	/* IR_OF位为1,则数据无效 */
		ir = 0;
	else				/* 读取IR传感器的数据			*/
		ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);

	als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据			 */

	if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 		   */
		ps = 0;
	else				/* 读取PS传感器的数据	 */
		ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
	
	data[0] = ir;
	data[1] = als;
	data[2] = ps;
	ret = copy_to_user(to, data, sizeof(data));
	if(ret)
		return -EINVAL;
	else
		return sizeof(data);
}



static const struct file_operations  ops = {
	.owner = THIS_MODULE,
	.read = ap3216c_read,
	.open = ap3216c_open,
	.release = ap3216c_release
};

static int ap3216c_probe(struct i2c_client * client, const struct i2c_device_id * id){
	int ret = 0;
	struct ap3216c_dev * dev = devm_kzalloc(&client->dev, sizeof(struct ap3216c_dev), GFP_KERNEL);
	if(!dev)
		return -ENOMEM;

	dev->client = client;
	
	if(dev->major){
		dev->dev_id = MKDEV(dev->major, 0);
		ret = register_chrdev_region(dev->dev_id, AP3216C_CNT, AP3216C_NAME);
		dev->minor = MINOR(dev->dev_id);
	}
	else{
		ret = alloc_chrdev_region(&dev->dev_id, 0, AP3216C_CNT, AP3216C_NAME);
		dev->major = MAJOR(dev->dev_id);
		dev->minor = MINOR(dev->dev_id);
	}
	if(ret){
		printk("register_chrdev_region failed.\n");
		return ret;
	}

	dev->cdev.owner = THIS_MODULE;
	cdev_init(&dev->cdev, &ops);
	ret = cdev_add(&dev->cdev, dev->dev_id, AP3216C_CNT);
	if(ret){
		printk("cdev_add failed.\n");
		goto unreg_chrdev;
	}

	dev->cls = class_create(THIS_MODULE, AP3216C_NAME);
	if(IS_ERR(dev->cls)){
		printk("kernel class_create failed.\r\n");
		ret = PTR_ERR(dev->cls);
		goto failed_cdev_del;
	}
	
	dev->device = device_create(dev->cls, NULL, dev->dev_id, NULL, AP3216C_NAME);
	if(IS_ERR(dev->cls)){
		printk("kernel device_create failed.\r\n");
		ret = PTR_ERR(dev->device);
		goto failed_dest_class;
	}

	dev_set_drvdata(&client->dev, dev);
	
	return 0;
failed_dest_class:
	class_destroy(dev->cls);
failed_cdev_del:
	cdev_del(&dev->cdev);
unreg_chrdev:
	unregister_chrdev_region(dev->dev_id, AP3216C_CNT);
	return ret;

}
static int ap3216c_remove(struct i2c_client * client){
	struct ap3216c_dev * dev = dev_get_drvdata(&client->dev);
	device_destroy(dev->cls, dev->dev_id);
	class_destroy(dev->cls);
	cdev_del(&dev->cdev);
	unregister_chrdev_region(dev->dev_id, AP3216C_CNT);
	return 0;
}

static const struct of_device_id ap3216c_of_match[] = {
	{
		.compatible = "sallenkey,ap3216c",
	},
	{ /* Sentinel */ }
};

static const struct i2c_device_id ap3216c_id[] = {
	{.name = "sallenkey,ap3216c", 0},
	{ /* Sentinel */ }
};


static struct i2c_driver ap3216c = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = ap3216c_of_match
	},
	.id_table = ap3216c_id,
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
};


static int __init ap3216c_init(void){
	return i2c_add_driver(&ap3216c);
}

static void __exit ap3216c_exit(void){
	i2c_del_driver(&ap3216c);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_AUTHOR("sallenkey");
MODULE_LICENSE("GPL");

源码在:源码

设备树可以参考:设备树文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值