设备驱动中的i2c(kernel-4.7)
来源:互联网 发布:linux自动登录ssh脚本 编辑:程序博客网 时间:2024/05/13 07:22
必要的概念和已有的总结还是要拷贝来的:
I2C架构概述
Linux的I2C体系结构分为3个组成部分:
I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。
I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据
linux驱动中i2c驱动架构
上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂
架构层次分类
第一层:提供i2c adapter
的硬件驱动,探测、初始化i2c adapter
(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter
在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层
第二层:提供i2c adapter
的algorithm,用具体适配器的xxx_xferf()
数来填充i2c_algorithm
的master_xfer
函数指针,并把赋值后的i2c_algorithm
再赋值给i2c_adapte
的algo指针。覆盖图中的访问抽象层、i2c核心层
第三层:实现i2c设备驱动中的i2c_driver
接口,用具体的i2c device
设备的attach_adapter()
、detach_adapter()
方法赋值给i2c_driver
的成员函数指针。实现设备device与总线(或者叫adapter)的挂接。覆盖图中的driver驱动层
第四层:实现i2c设备所对应的具体device的驱动,i2c_driver
只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()
、read()
、ioctl()
等方法,赋值给file_operations
,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层
I2C调用图示:
Linux下I2C体系文件构架
在Linux内核源代码中的driver目录下包含一个i2c目录 i2c-core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。 i2c-dev.c
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。i2c-dev.c
并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
algos文件夹实现了一些I2C总线适配器的algorithm.
以上总结可知:
在Linux驱动中I2C系统中主要包含以下几个成员: i2c_adapter
即I2C适配器 i2c_driver
某个I2C设备的设备驱动,可以以driver理解。 i2c_client
某个I2C设备的设备声明,可以以device理解。 i2c_algorithm
某个I2C设备数据的传输算法,对应一套通信方法。
I2C adapter
i2c_adapter
对应与物理上的一个适配器,而i2c_algorithm
对应一套通信方法,一个i2c适配器需要i2c_algorithm
中提供的(i2c_algorithm
中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm
的i2c_adapter
什么也做不了,因此i2c_adapter
中包含其使用i2c_algorithm
的指针。 i2c_algorithm
中的关键函数master_xfer()
用于产生i2c访问周期需要的start stop ack信号,以i2c_msg
(即i2c消息)为单位发送和接收通信数据。 i2c_adapter
是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,但最主要的工作是需要完成i2c_algorithm
结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型。i2c_algorithm结构体如下:
/** * struct i2c_algorithm - represent I2C transfer method * @master_xfer: Issue a set of i2c transactions to the given I2C adapter * defined by the msgs array, with num messages available to transfer via * the adapter specified by adap. * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this * is not present, then the bus layer will try and convert the SMBus calls * into I2C transfers instead. * @functionality: Return the flags that this algorithm/adapter pair supports * from the I2C_FUNC_* flags. * @reg_slave: Register given client to I2C slave mode of this adapter * @unreg_slave: Unregister given client from I2C slave mode of this adapter * * The following structs are for those who like to implement new bus drivers: * i2c_algorithm is the interface to a class of hardware solutions which can * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584 * to name two of the most common. * * The return codes from the @master_xfer field should indicate the type of * error code that occurred during the transfer, as documented in the kernel * Documentation file Documentation/i2c/fault-codes. */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); //I2C传输函数指针 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); //smbus传输函数指针 /* To determine what the adapter supports */ u32 (*functionality) (struct i2c_adapter *); //返回适配器支持的功能#if IS_ENABLED(CONFIG_I2C_SLAVE) int (*reg_slave)(struct i2c_client *client); int (*unreg_slave)(struct i2c_client *client);#endif};
如果一个I2C适配器不支持I2C通道,那么就将master_xfer
成员设为NULL。如果适配器支持SMBUS协议,那么需要去实现smbus_xfer
,如果smbus_xfer
指针被设为NULL,那么当使用SMBUS协议的时候将会通过I2C通道进行仿真。master_xfer
指向的函数的返回值应该是已经成功处理的消息数,或者返回负数表示出错了。functionality指针很简单,告诉询问着这个I2C主控器都支持什么功能。
在内核的drivers/i2c/i2c-stub.c中实现了一个i2c adapter的例子,其中实现的是更为复杂的SMBUS。
SMBus 与 I2C的区别
通常情况下,I2C和SMBus是兼容的,但是还是有些微妙的区别的。
时钟速度对比:
在电气特性上他们也有所不同,SMBus要求的电压范围更低。
以下为i2c_adapter
的结构体定义:
/* * i2c_adapter is the structure used to identify a physical i2c bus along * with the access algorithms necessary to access it. */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 */ struct rt_mutex bus_lock; //控制并发访问的自旋锁 struct rt_mutex mux_lock; int timeout; /* in jiffies */ int retries;//重试次数 struct device dev;//适配器设备/* the adapter device */ int nr; char name[48];//适配器名称 struct completion dev_released;//用于同步 struct mutex userspace_clients_lock; struct list_head userspace_clients; //client链表头 struct i2c_bus_recovery_info *bus_recovery_info; const struct i2c_adapter_quirks *quirks; void (*lock_bus)(struct i2c_adapter *, unsigned int flags); int (*trylock_bus)(struct i2c_adapter *, unsigned int flags); void (*unlock_bus)(struct i2c_adapter *, unsigned int flags);};#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)
I2C driver
具体的I2C设备驱动,如相机、传感器、触摸屏、背光控制器常见硬件设备大多都有或都是通过I2C协议与主机进行数据传输、控制。结构体如下:
/** * struct i2c_driver - represent an I2C device driver * @class: What kind of i2c device we instantiate (for detect) * @attach_adapter: Callback for bus addition (deprecated) * @probe: Callback for device binding * @remove: Callback for device unbinding * @shutdown: Callback for device shutdown * @alert: Alert callback, for example for the SMBus alert protocol * @command: Callback for bus-wide signaling (optional) * @driver: Device driver model driver * @id_table: List of I2C devices supported by this driver * @detect: Callback for device detection * @address_list: The I2C addresses to probe (for detect) * @clients: List of detected clients we created (for i2c-core use only) * * The driver.owner field should be set to the module owner of this driver. * The driver.name field should be set to the name of this driver. * * For automatic device detection, both @detect and @address_list must * be defined. @class should also be set, otherwise only devices forced * with module parameters will be created. The detect function must * fill at least the name field of the i2c_board_info structure it is * handed upon successful detection, and possibly also the flags field. * * If @detect is missing, the driver will still work fine for enumerated * devices. Detected devices simply won't be supported. This is expected * for the many I2C/SMBus devices which can't be detected reliably, and * the ones which can always be enumerated in practice. * * The i2c_client structure which is handed to the @detect callback is * not a real i2c_client. It is initialized just enough so that you can * call i2c_smbus_read_byte_data and friends on it. Don't do anything * else with it. In particular, calling dev_dbg and friends on it is * not allowed. */struct i2c_driver { unsigned int class; /* Notifies the driver that a new bus has appeared. You should avoid * using this, it will be removed in a near future. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; //旧的与设备进行绑定的接口函数 /* Standard driver model interfaces */ int (*probe)(struct i2c_client *, const struct i2c_device_id *);//现行通用的与对应设备进行绑定的接口函数 int (*remove)(struct i2c_client *);//现行通用与对应设备进行解绑的接口函数 /* driver model interfaces that don't relate to enumeration */ void (*shutdown)(struct i2c_client *);//关闭设备 /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert response's low bit ("event flag"). */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions * with the device. */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver;//I2C设备的驱动模型 const struct i2c_device_id *id_table;//匹配设备列表 /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients;};#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)
I2C client
即I2C设备。I2C设备的注册一般在板级代码中,在解析实例前还是先熟悉几个定义:
/** * struct i2c_client - represent an I2C slave device * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address; * I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking * @addr: Address used on the I2C bus connected to the parent adapter. * @name: Indicates the type of the device, usually a chip name that's * generic enough to hide second-sourcing and compatible revisions. * @adapter: manages the bus segment hosting this I2C device * @dev: Driver model device node for the slave. * @irq: indicates the IRQ generated by this device (if any) * @detected: member of an i2c_driver.clients list or i2c-core's * userspace_devices list * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter * calls it to pass on slave events to the slave driver. * * An i2c_client identifies a single device (i.e. chip) connected to an * i2c bus. The behaviour exposed to Linux is defined by the driver * managing the device. */struct i2c_client { unsigned short flags; /* div., see below */ unsigned short addr; /* chip address - NOTE: 7bit */ /* addresses are stored in the */ /* _LOWER_ 7 bits */ char name[I2C_NAME_SIZE]; struct i2c_adapter *adapter; /* the adapter we sit on */ struct device dev; /* the device structure */ int irq; /* irq issued by device */ struct list_head detected;#if IS_ENABLED(CONFIG_I2C_SLAVE) i2c_slave_cb_t slave_cb; /* callback for slave mode */#endif};#define to_i2c_client(d) container_of(d, struct i2c_client, dev)/** * struct i2c_board_info - template for device creation * @type: chip type, to initialize i2c_client.name * @flags: to initialize i2c_client.flags * @addr: stored in i2c_client.addr * @platform_data: stored in i2c_client.dev.platform_data * @archdata: copied into i2c_client.dev.archdata * @of_node: pointer to OpenFirmware device node * @fwnode: device node supplied by the platform firmware * @irq: stored in i2c_client.irq * * I2C doesn't actually support hardware probing, although controllers and * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's * a device at a given address. Drivers commonly need more information than * that, such as chip type, configuration, associated IRQ, and so on. * * i2c_board_info is used to build tables of information listing I2C devices * that are present. This information is used to grow the driver model tree. * For mainboards this is done statically using i2c_register_board_info(); * bus numbers identify adapters that aren't yet available. For add-on boards, * i2c_new_device() does this dynamically with the adapter already known. */struct i2c_board_info { char type[I2C_NAME_SIZE]; unsigned short flags; unsigned short addr; void *platform_data; struct dev_archdata *archdata; struct device_node *of_node; struct fwnode_handle *fwnode; int irq;};/** * I2C_BOARD_INFO - macro used to list an i2c device and its address * @dev_type: identifies the device type * @dev_addr: the device's address on the bus. * * This macro initializes essential fields of a struct i2c_board_info, * declaring what has been provided on a particular board. Optional * fields (such as associated irq, or device-specific platform_data) * are provided using conventional syntax. */#define I2C_BOARD_INFO(dev_type, dev_addr) \ .type = dev_type, .addr = (dev_addr)
S3C2410 I2C 总线驱动实例
1.S3C2410I2C 控制器硬件描述
S3C2410处理器内部集成了一个I2C控制器,通过4个寄存器就可方便地对其进行控制,这4个寄存器如下。
1 IICCON:I2C 控制寄存器。2 IICSTAT:I2C 状态寄存器。3 IICDS:I2C 收发数据移位寄存器。4 IICADD:I2C 地址寄存器。
S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器获取。
2.S3C2410I2C 总线驱动总体分析
S3C2410的I2C总线驱动设计主要要完成以下工作。
1, 设计对应于i2c_adapter_xxx_init()
模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()
函数模板的模块卸载函数。
2, 设计对应于i2c_adapter_xxx_xfer()
模板的S3C2410适配器的通信方法函数。针对 S3C2410, functionality()
函数 只需 简单 地返 回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING
表明其支持的功能。
内核源代码中的/drivers/i2c/busses/i2c-s3c2410.c
图示给出了S3C2410驱动中的主要函数与模板函数的对应关系,由于实现通信方法的方式不一样,模板的一个函数可能对应与S3C2410I2C 总线驱动的多个函数。
3.S3C2410I2C 适配器驱动的模块加载与卸载
I2C适配器驱动被作为一个单独的模块加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体, 结构体包含了具体适配器的probe()
函数、remove()
函数、resume()
函数指针等信息,它需要被定义和赋值:
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);
s3c24xx_i2c_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; } } i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); if (!i2c) return -ENOMEM; i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!i2c->pdata) return -ENOMEM; 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); strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm; 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的时钟*/ 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 */ /*映射寄存器*/ 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的信息块*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /* inititalise the i2c gpio lines */ 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; } /*初始化I2C控制器*/ 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 */ /*申请中断*/ 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; } 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; } } 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. */ /*添加i2c_adapter*/ i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, i2c); pm_runtime_enable(&pdev->dev); ret = i2c_add_numbered_adapter(&i2c->adap); if (ret < 0) { dev_err(&pdev->dev, "failed to add bus to i2c core\n"); 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;}
上述代码中的主体工作是使能硬件并申请I2C适配器使用的I/O地址,在这些工作都完成无误后添加i2c adaper适配器。 因为 S3C2410内部 集成 I2C控制 器,可以确定 I2C适配 器一 定存 在, s3c24xx_i2c_probe()
函数虽然命名“探测”,但实际没有也不必进行任何探测工作,之所以这样命名完全是一种设计习惯。
与s3c24xx_i2c_probe()
函数完成相反功能的函数是s3c24xx_i2c_remove()
函数,它在适配器模块卸载函数调用platform_driver_unregister()
函数时所示通过platform_driver
的remove指针方式被调用。 s3cxxx_i2c_remove()
卸载函数代码:
static int s3c24xx_i2c_remove(struct platform_device *pdev){ struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev); clk_unprepare(i2c->clk); pm_runtime_disable(&pdev->dev); s3c24xx_i2c_deregister_cpufreq(i2c); i2c_del_adapter(&i2c->adap); if (pdev->dev.of_node && IS_ERR(i2c->pctrl)) s3c24xx_i2c_dt_gpio_free(i2c); return 0;}
以上函数用到的s3c24xx_i2c
结构体进行适配器所有信息的封装,类似于私有信息结构体,它与xxx_i2c
结构体模板对应。 s3c24xx_i2c
结构体如下:
struct s3c24xx_i2c { wait_queue_head_t wait; kernel_ulong_t quirks; unsigned int suspended:1; struct i2c_msg *msg; unsigned int msg_num; unsigned int msg_idx; unsigned int msg_ptr; unsigned int tx_setup; unsigned int irq; enum s3c24xx_i2c_state state; unsigned long clkrate; void __iomem *regs; struct clk *clk; struct device *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;};
4.S3C2410I2C 总线通信方法
由以上probe()函数中i2c->adap.algo = &s3c24xx_i2c_algorithm;
行可得I2C适配器对应的i2c_algorithm
结构体 s3c24xx_i2c_algorithm
的定义如下:
/* i2c bus registration info */static const struct i2c_algorithm s3c24xx_i2c_algorithm = { .master_xfer = s3c24xx_i2c_xfer, .functionality = s3c24xx_i2c_func,};
上述代码指定了S3C2410 I2C 总线通信传输函数s3c24xx_i2c_xfer()
,这个
函数非常关键,所有I2C总线上对设备的访问最终应该由它来完成。
S3C2410 I2C 总线驱动的master_xfer
函数:
/* * first port of call from the i2c bus code when an message needs * transferring across the i2c bus. */static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){ struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data; int retry; int ret; ret = clk_enable(i2c->clk); if (ret) return ret; for (retry = 0; retry < adap->retries; retry++) { ret = s3c24xx_i2c_doxfer(i2c, msgs, num); if (ret != -EAGAIN) { clk_disable(i2c->clk); return ret; } dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry); udelay(100); } clk_disable(i2c->clk); return -EREMOTEIO;}
调用了s3c24xx_i2c_doxfer
函数 :
/* * this starts an i2c transfer */static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num){ unsigned long timeout; int ret; if (i2c->suspended) return -EIO; ret = s3c24xx_i2c_set_master(i2c); if (ret != 0) { dev_err(i2c->dev, "cannot get bus (error %d)\n", ret); ret = -EAGAIN; goto out; } i2c->msg = msgs; i2c->msg_num = num; i2c->msg_ptr = 0; i2c->msg_idx = 0; i2c->state = STATE_START; s3c24xx_i2c_enable_irq(i2c); s3c24xx_i2c_message_start(i2c, msgs); if (i2c->quirks & QUIRK_POLL) { ret = i2c->msg_idx; if (ret != num) dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret); goto out; } timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); ret = i2c->msg_idx; /* * Having these next two as dev_err() makes life very * noisy when doing an i2cdetect */ if (timeout == 0) dev_dbg(i2c->dev, "timeout\n"); else if (ret != num) dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret); /* For QUIRK_HDMIPHY, bus is already disabled */ if (i2c->quirks & QUIRK_HDMIPHY) goto out; s3c24xx_i2c_wait_idle(i2c); s3c24xx_i2c_disable_bus(i2c); out: i2c->state = STATE_IDLE; return ret;}
调用了s3c24xx_i2c_message_start
函数:
/* * put the start of a message onto the bus */static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg){ unsigned int addr = (msg->addr & 0x7f) << 1; unsigned long stat; unsigned long iiccon; stat = 0; stat |= S3C2410_IICSTAT_TXRXEN; if (msg->flags & I2C_M_RD) { stat |= S3C2410_IICSTAT_MASTER_RX; addr |= 1; } else stat |= S3C2410_IICSTAT_MASTER_TX; if (msg->flags & I2C_M_REV_DIR_ADDR) addr ^= 1; /* todo - check for whether ack wanted or not */ s3c24xx_i2c_enable_ack(i2c); iiccon = readl(i2c->regs + S3C2410_IICCON); writel(stat, i2c->regs + S3C2410_IICSTAT); dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr); writeb(addr, i2c->regs + S3C2410_IICDS); /* * delay here to ensure the data byte has gotten onto the bus * before the transaction is started */ ndelay(i2c->tx_setup); dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); writel(iiccon, i2c->regs + S3C2410_IICCON); stat |= S3C2410_IICSTAT_START; writel(stat, i2c->regs + S3C2410_IICSTAT); if (i2c->quirks & QUIRK_POLL) { while ((i2c->msg_num != 0) && is_ack(i2c)) { i2c_s3c_irq_nextbyte(i2c, stat); stat = readl(i2c->regs + S3C2410_IICSTAT); if (stat & S3C2410_IICSTAT_ARBITR) dev_err(i2c->dev, "deal with arbitration loss\n"); } }}
s3c24xx_i2c_xfer()
函数调用s3c24xx_i2c_doxfer()
函数传输I2C消息,s3c24xx_i2c_doxfer()
首先将S3C2410的I2C适配器设置为I2C主设备,其后使能I2C中断,并用s3c24xx_i2c_message_start()
函数启动I2C消息的传输。 s3c24xx_i2c_message_start()
函数写S3C2410适配器对应的控制寄存器,向I2C从设备传递开始位和从设备地址。
上述代码只是启动了I2C消息数组的传输周期,并没有完整实现下图中给出的algorithm master_xfer
流程。这个流程的完整实现需要借助I2C适配器上的中断来步步推进。
S3C2410 I2C 适配器中断处理函数以及其依赖的i2s_s3c_irq_nextbyte()
函数的源代码:
/* * process an interrupt and work out what to do */static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat){ unsigned long tmp; unsigned char byte; int ret = 0; switch (i2c->state) { case STATE_IDLE: dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__); goto out; case STATE_STOP: dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__); s3c24xx_i2c_disable_irq(i2c); goto out_ack; case STATE_START: /* * last thing we did was send a start condition on the * bus, or started a new i2c message */ if (iicstat & S3C2410_IICSTAT_LASTBIT && !(i2c->msg->flags & I2C_M_IGNORE_NAK)) { /* ack was not received... */ dev_dbg(i2c->dev, "ack was not received\n"); s3c24xx_i2c_stop(i2c, -ENXIO); goto out_ack; } if (i2c->msg->flags & I2C_M_RD) i2c->state = STATE_READ; else i2c->state = STATE_WRITE; /* * Terminate the transfer if there is nothing to do * as this is used by the i2c probe to find devices. */ if (is_lastmsg(i2c) && i2c->msg->len == 0) { s3c24xx_i2c_stop(i2c, 0); goto out_ack; } if (i2c->state == STATE_READ) goto prepare_read; /* * fall through to the write state, as we will need to * send a byte as well */ case STATE_WRITE: /* * we are writing data to the device... check for the * end of the message, and if so, work out what to do */ if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) { if (iicstat & S3C2410_IICSTAT_LASTBIT) { dev_dbg(i2c->dev, "WRITE: No Ack\n"); s3c24xx_i2c_stop(i2c, -ECONNREFUSED); goto out_ack; } } retry_write: if (!is_msgend(i2c)) { byte = i2c->msg->buf[i2c->msg_ptr++]; writeb(byte, i2c->regs + S3C2410_IICDS); /* * delay after writing the byte to allow the * data setup time on the bus, as writing the * data to the register causes the first bit * to appear on SDA, and SCL will change as * soon as the interrupt is acknowledged */ ndelay(i2c->tx_setup); } else if (!is_lastmsg(i2c)) { /* we need to go to the next i2c message */ dev_dbg(i2c->dev, "WRITE: Next Message\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; /* check to see if we need to do another message */ if (i2c->msg->flags & I2C_M_NOSTART) { if (i2c->msg->flags & I2C_M_RD) { /* * cannot do this, the controller * forces us to send a new START * when we change direction */ s3c24xx_i2c_stop(i2c, -EINVAL); } goto retry_write; } else { /* send the new start */ s3c24xx_i2c_message_start(i2c, i2c->msg); i2c->state = STATE_START; } } else { /* send stop */ s3c24xx_i2c_stop(i2c, 0); } break; case STATE_READ: /* * we have a byte of data in the data register, do * something with it, and then work out whether we are * going to do any more read/write */ byte = readb(i2c->regs + S3C2410_IICDS); i2c->msg->buf[i2c->msg_ptr++] = byte; /* Add actual length to read for smbus block read */ if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1) i2c->msg->len += byte; prepare_read: if (is_msglast(i2c)) { /* last byte of buffer */ if (is_lastmsg(i2c)) s3c24xx_i2c_disable_ack(i2c); } else if (is_msgend(i2c)) { /* * ok, we've read the entire buffer, see if there * is anything else we need to do */ if (is_lastmsg(i2c)) { /* last message, send stop and complete */ dev_dbg(i2c->dev, "READ: Send Stop\n"); s3c24xx_i2c_stop(i2c, 0); } else { /* go to the next transfer */ dev_dbg(i2c->dev, "READ: Next Transfer\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; } } break; } /* acknowlegde the IRQ and get back on with the work */ out_ack: tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); out: return ret;}
中断处理函数s3c24xx_i2c_irq()
主要通过调用i2s_s3c_irq_nextbyte()
函数进行传输工作的进一步 推进 。i2s_s3c_irq_nextbyte()
函数 通过switch(i2c->state)
语句分成i2c->state
的不同状态进行处理,在每种状态下, 先检查i2c->state
的状态与硬件寄存器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状态STATE_READ
或写状态STATE_WRITE
时,通过is_lastmsg()
函数判断是否传输的是最后一条I2C消息 ,如果是,则产生停止位,否则通过i2c->msg_idx++
、i2c->msg++
推进到下一条消息。
i2c设备驱动调用图示:
未完待续。。。。
- 设备驱动中的i2c(kernel-4.7)
- Linux Kernel 设备驱动之I2C之i2c设备文件
- i2c设备驱动分析-At24(kernel-3.17)
- 设备驱动中的device(kernel-4.7)
- 设备驱动中的device_driver(kernel-4.7)
- 设备驱动中的bus(kernel-4.7)
- 设备驱动中的kobject(kernel-4.7)
- 设备驱动中的kset(kernel-4.7)
- 设备驱动中的pinctrl(kernel-4.7)
- 设备驱动中的platform(kernel-4.7)
- 设备驱动中的class(kernel-4.7)
- 设备驱动中的tty(kernel-4.7)
- 设备驱动中的gadget(kernel-4.7)
- 设备驱动中的mutex(kernel-4.7)
- 设备驱动中的inode(kernel-4.7)
- 设备驱动中的spin_lock(kernel-4.7)
- 设备驱动中的misc(kernel-4.7)
- 设备驱动中的iomem(kernel-4.7)
- Python金融大数据分析-PCA分析
- 一步一步学习数据结构(4)-树和二叉树基础知识
- POJ 1942Paths on a Grid(组合数学)
- window下eclipse安装python插件
- 后缀数组(不可重叠最长重复子串)
- 设备驱动中的i2c(kernel-4.7)
- TCP协议与UDP协议的异同
- mongodb中的populate方法
- linux c之shm共享内存的使用例子
- Spring AOP
- mysql 数据库的安装与配置 有关msi文件start service 停滞不前的问题及其解决办法
- Java 8之Stream适用场景
- 深度相机-PMD、Kinect、Camcube3.0的比较
- MFC基于对话框的用Picture Control显示OpenGL