• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# I2C
2
3## Overview
4
5### Function
6
7The Inter-Integrated Circuit (I2C) is a simple, bidirectional, and synchronous serial bus that uses merely two wires. It is widely used in short-distance communication due to simple connection and low cost.
8
9### Working Principles
10
11In the Hardware Driver Foundation (HDF), the I2C module uses the unified service mode for API adaptation. In this mode, a device service is used as the I2C manager to handle access requests from the devices of the same type in a unified manner. The unified service mode applies to the scenario where there are many device objects of the same type. If the independent service mode is used in this case, more device nodes need to be configured and more memory resources will be consumed. The following figure illustrates the unified service mode.
12
13In the unified service mode, the core layer manages all controllers in a unified manner and publishes a service for the interface layer. That is, the driver does not need to publish a service for each controller.
14
15The I2C module is divided into the following layers:
16
17- Interface layer: provides the capabilities of opening and closing a device and transferring data.
18- Core layer: binds services, initializes and releases the PlatformManager, and provides the capabilities of adding, deleting, and obtaining controllers.
19- Adaptation layer: implements hardware-related functions, such as controller initialization.
20
21**Figure 1** Unified service mode
22
23
24![image](figures/unified-service-mode.png "I2C Unified Service Mode")
25
26## Usage Guidelines
27
28### When to Use
29
30The I2C is used in communication with the sensors, executors, and input/output devices that support the I2C protocol. Before using I2C devices with OpenHarmony, you need to adapt the I2C driver to OpenHarmony. The following describes how to do it.
31
32### Available APIs
33
34To enable the upper layer to successfully operate the hardware by calling the I2C APIs, hook functions are defined in **//drivers/hdf_core/framework/support/platform/include/i2c/i2c_core.h** for the core layer. You need to implement these hook functions at the adaptation layer and hook them to implement the interaction between the interface layer and the core layer.
35
36**I2cMethod** and **I2cLockMethod**:
37
38```c
39struct I2cMethod {
40    int32_t (*transfer)(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count);
41};
42
43struct I2cLockMethod {// Structure for the lock operation.
44    int32_t (*lock)(struct I2cCntlr *cntlr);
45    void (*unlock)(struct I2cCntlr *cntlr);
46};
47```
48
49At the adaptation layer, **I2cMethod** must be implemented, and **I2cLockMethod** can be implemented based on service requirements. The core layer provides the default **I2cLockMethod**, in which a mutex is used to protect the critical section.
50
51```c
52static int32_t I2cCntlrLockDefault(struct I2cCntlr *cntlr)
53{
54    if (cntlr == NULL) {
55        return HDF_ERR_INVALID_OBJECT;
56    }
57    return OsalMutexLock(&cntlr->lock);
58}
59
60static void I2cCntlrUnlockDefault(struct I2cCntlr *cntlr)
61{
62    if (cntlr == NULL) {
63        return;
64    }
65    (void)OsalMutexUnlock(&cntlr->lock);
66}
67
68static const struct I2cLockMethod g_i2cLockOpsDefault = {
69    .lock = I2cCntlrLockDefault,
70    .unlock = I2cCntlrUnlockDefault,
71};
72```
73
74If a mutex cannot be used (for example, an I2C API is called in the interrupt context, which does not allow sleep, but a mutex may cause sleep), you can use another type of lock to implement **I2cLockMethod**. The implemented **I2cLockMethod** will replace the default **I2cLockMethod**.
75
76  **Table 2** Function in **I2cMethod**
77
78| Function| Input Parameter| Output Parameter| Return Value| Description|
79| -------- | -------- | -------- | -------- | -------- |
80| transfer | **cntlr**: structure pointer to the I2C controller at the core layer.<br>**msgs**: structure pointer to the messages to transfer.<br>**count**: number of messages. The value is of the uint16_t type.| –| HDF_STATUS| Transfers user messages.|
81
82  **Table 2** Functions in **I2cLockMethod**
83
84| Function| Input Parameter| Output Parameter| Return Value| Description|
85| -------- | -------- | -------- | -------- | -------- |
86| lock | **cntlr**: structure pointer to the I2C controller at the core layer.| –| HDF_STATUS| Acquires the critical section lock.|
87| unlock | **cntlr**: structure pointer to the I2C controller at the core layer.| –| HDF_STATUS| Releases the critical section lock.|
88
89### How to Develop
90
91The I2C module adaptation involves the following steps:
92
931. Instantiate the driver entry.
94
95   - Instantiate the **HdfDriverEntry** structure.
96   - Call **HDF_INIT** to register the **HdfDriverEntry** instance with the HDF.
97
982. Configure attribute files.
99
100   - Add the **deviceNode** information to the **device_info.hcs** file.
101   - (Optional) Add the **i2c_config.hcs** file.
102
1033. Instantiate the I2C controller object.
104
105   - Initialize **I2cCntlr**.
106   - Instantiate **I2cMethod** and **I2cLockMethod** in **I2cCntlr**.
107      > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br>
108      > For details, see [Available APIs](#available-apis).
109
1104. Debug the driver.
111
112   (Optional) For new drivers, verify basic functions, for example, check whether data is successfully transferred and the information returned after the virtual file system (VFS) is mounted.
113
114### Example
115
116The following uses the Hi3516D V300 driver **//device/soc/hisilicon/common/platform/i2c/i2c_hi35xx.c** as an example to describe how to perform the I2C driver adaptation.
117
1181. Instantiate the driver entry.
119
120   The driver entry must be a global variable of the **HdfDriverEntry** type (defined in **hdf_device_desc.h**), and the value of **moduleName** must be the same as that in **device_info.hcs**. In the HDF, the start address of each **HdfDriverEntry** object of all loaded drivers is collected to form a segment address space similar to an array for the upper layer to invoke.
121
122   Generally, the HDF calls **Bind()** and then **Init()** to load a driver. If **Init()** fails to be called, the HDF calls **Release()** to release driver resources and exit.
123
124   I2C driver entry example:
125
126   Multiple devices may connect to the I2C controller. In the HDF, a manager object needs to be created for this type of devices, and a manager service is published to handle external access requests uniformly. When a device needs to be started, the manager service locates the target device based on the specified parameters.
127
128   You do not need to implement the driver of the I2C manager, which is implemented by the core layer. However, the **I2cCntlrAdd** function of the core layer must be invoked in the **Init** function to implement the related features.
129
130    ```c
131    struct HdfDriverEntry g_i2cDriverEntry = {
132       .moduleVersion = 1,
133       .Init = Hi35xxI2cInit,
134       .Release = Hi35xxI2cRelease,
135       .moduleName = "hi35xx_i2c_driver",        // (Mandatory) The value must be the same as that in the config.hcs file.
136    };
137    HDF_INIT(g_i2cDriverEntry);                  // Call HDF_INIT to register the driver entry with the HDF.
138
139    /* Driver entry of the manager service i2c_core.c at the core layer. */
140    struct HdfDriverEntry g_i2cManagerEntry = {
141       .moduleVersion = 1,
142       .Bind = I2cManagerBind,
143       .Init = I2cManagerInit,
144       .Release = I2cManagerRelease,
145       .moduleName = "HDF_PLATFORM_I2C_MANAGER", // The value must be the same as that of device0 in the device_info.hcs file.
146    };
147    HDF_INIT(g_i2cManagerEntry);
148    ```
149
1502. Add the **deviceNode** information to the **//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs** file and configure the device attributes in **i2c_config.hcs**.
151
152   The **deviceNode** information is related to the driver entry registration. The device attribute values are closely related to the driver implementation and the default values or value ranges of the **I2cCntlr** members at the core layer.
153
154   In the unified service mode, the first device node in the **device_info.hcs** file must be the I2C manager. The table below lists the settings of its parameters.
155
156   **Table 3** Settings of the I2C manager
157
158   | Parameter| Value|
159   | -------- | -------- |
160   | moduleName | **HDF_PLATFORM_I2C_MANAGER**|
161   | serviceName | **HDF_PLATFORM_I2C_MANAGER**|
162   | policy | **1** or **2**, depending on whether the service is published to the user mode.|
163   | deviceMatchAttr | This parameter is reserved.|
164
165    Configure I2C controller information from the second node. This node specifies a type of I2C controllers rather than a specific I2C controller. The controllers are distinguished by **busID** and **reg_pbase**, which can be seen in the **i2c_config.hcs** file.
166
167   - **device_info.hcs** example
168
169      ```c
170      root {
171          device_info {
172              match_attr = "hdf_manager";
173              device_i2c :: device {
174                  device0 :: deviceNode {
175                      policy = 2;
176                      priority = 50;
177                      permission = 0644;
178                      moduleName = "HDF_PLATFORM_I2C_MANAGER";
179                      serviceName = "HDF_PLATFORM_I2C_MANAGER";
180                      deviceMatchAttr = "hdf_platform_i2c_manager";
181                  }
182                  device1 :: deviceNode {
183                      policy = 0;                                // The value 0 indicates that no service is published.
184                      priority = 55;                             // Driver startup priority.
185                      permission = 0644;                         // Permission for the device node created.
186                      moduleName = "hi35xx_i2c_driver";          // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
187                      serviceName = "HI35XX_I2C_DRIVER";         // (Mandatory) Unique name of the service published by the driver.
188                       deviceMatchAttr = "hisilicon_hi35xx_i2c"; // (Mandatory) Private data of the controller. The value must be the same as the controller information in i2c_config.hcs.
189                                                                 //The specific controller information is in i2c_config.hcs.
190                  }
191              }
192          }
193      }
194      ```
195
196   - **i2c_config.hcs** example
197
198      ```c
199      root {
200          platform {
201              i2c_config {
202                  match_attr = "hisilicon_hi35xx_i2c";  // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs.
203                  template i2c_controller {             // Template configuration. In the template, you can configure the common parameters shared by service nodes.
204                      bus = 0;                          // (Mandatory) I2C identifier.
205                      reg_pbase = 0x120b0000;           // (Mandatory) Physical base address.
206                      reg_size = 0xd1;                  // (Mandatory) Register bit width.
207                      irq = 0;                          // (Optional) Interrupt request (IRQ) number. The interrupt feature of the controller determines whether an IRQ number is required.
208                      freq = 400000;                    // (Optional) Frequency used in hardware controller initialization.
209                      clk = 50000000;                   // (Optional) Controller clock. The controller clock initialization process determines whether a controller clock is required.
210                  }
211                  controller_0x120b0000 :: i2c_controller {
212                      bus = 0;
213                  }
214                  controller_0x120b1000 :: i2c_controller {
215                      bus = 1;
216                      reg_pbase = 0x120b1000;
217                  }
218                  ...
219              }
220          }
221      }
222      ```
223
224      After the **i2c_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
225
226      For example, if the path of **i2c_config.hcs** is **device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i2c/i2c_config.hcs**, add the following statement to **hdf.hcs** of the product:
227
228      ```c
229      #include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i2c/i2c_config.hcs" // Relative path of the file.
230      ```
231
2323. Initialize the **I2cCntlr** object at the core layer, including defining a custom structure (to pass parameters and data) and implementing the **HdfDriverEntry** member functions (**Bind**, **Init** and **Release**) to instantiate **I2cMethod** in **I2cCntlr** (so that the underlying driver functions can be called).
233
234   - Define a custom structure.
235
236      To the driver, the custom structure holds parameters and data. The **DeviceResourceIface** method provided by the HDF reads the values in the **i2c_config.hcs** file to initialize the members in the custom structure and passes important parameters, such as the device number and bus number, to the **I2cCntlr** object at the core layer.
237
238      ```c
239      /* Custom structure. */
240      struct Hi35xxI2cCntlr {
241          struct I2cCntlr cntlr;            // (Mandatory) Control object at the core layer. For details, see the following description.
242          OsalSpinlock spin;                // (Mandatory) Lock or unlock an I2C operation function.
243          volatile unsigned char *regBase;// (Mandatory) Register base address.
244          uint16_t regSize;                 // (Mandatory) Register bit width.
245          int16_t bus;                      // (Mandatory) The value can be read from the i2c_config.hcs file.
246          uint32_t clk;                     // (Optional) Set it as required.
247          uint32_t freq;                    // (Optional) Set it as required.
248          uint32_t irq;                     // (Optional) Set it as required.
249          uint32_t regBasePhy            // (Mandatory) Physical base address of the register.
250      };
251
252      /* I2cCntlr is the core layer controller structure. The **Init()** function assigns values to the members of I2cCntlr. */
253      struct I2cCntlr {
254          struct OsalMutex lock;
255          void *owner;
256          int16_t busId;
257          void *priv;
258          const struct I2cMethod *ops;
259          const struct I2cLockMethod *lockOps;
260      };
261      ```
262
263   - Instantiate **I2cMethod** and **I2cLockMethod**. Other members are initialized by **Init**.
264
265      ```c
266      /* Example in i2c_hi35xx.c */
267      static const struct I2cMethod g_method = {
268          .transfer = Hi35xxI2cTransfer,
269      };
270
271      static const struct I2cLockMethod g_lockOps = {
272          .lock = Hi35xxI2cLock,     // Acquires the lock.
273          .unlock = Hi35xxI2cUnlock, // Release the lock.
274      };
275      ```
276
277   - Implement the **Init** function.
278
279      Input parameter:
280
281      **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
282
283      Return value:
284
285      **HDF_STATUS**<br/>The table below describes some status. For more information, see **HDF_STATUS** in the **//drivers/hdf_core/framework/include/utils/hdf_base.h** file.
286
287      **Table 4** HDF_STATUS
288
289      | Status| Description|
290      | -------- | -------- |
291      | HDF_ERR_INVALID_OBJECT | Invalid controller object.|
292      | HDF_ERR_INVALID_PARAM | Invalid parameter.|
293      | HDF_ERR_MALLOC_FAIL | Failed to allocate memory.|
294      | HDF_ERR_IO | I/O error.|
295      | HDF_SUCCESS | Transmission successful.|
296      | HDF_FAILURE | Transmission failed.|
297
298      Function description:
299
300      Initialize the custom structure object and **I2cCntlr**, call **I2cCntlrAdd()** at the core layer, and connect to the VFS (optional).
301
302      ```c
303      static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device)
304      {
305          ...
306          /* Traverse and parse all nodes in i2c_config.hcs and call Hi35xxI2cParseAndInit to initialize the devices separately. */
307          DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
308              ret = Hi35xxI2cParseAndInit(device, childNode);// The function is defined as follows.
309          ...
310          }
311          ...
312      }
313
314      static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
315      {
316          struct Hi35xxI2cCntlr *hi35xx = NULL;
317          ... // Check whether the input parameter is null.
318          hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx));   // Allocate memory.
319          ... // Verify the return value.
320          hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // Address mapping.
321          ... // Verify the return value.
322          Hi35xxI2cCntlrInit(hi35xx);         // (Mandatory) Initialize the I2C device.
323
324          hi35xx->cntlr.priv = (void *)node;  // (Mandatory) Device attributes.
325          hi35xx->cntlr.busId = hi35xx->bus; // (Mandatory) Initialize busId in I2cCntlr.
326           hi35xx->cntlr.ops = &g_method;      // (Mandatory) Hook the I2cMethod instance.
327           hi35xx->cntlr.lockOps = &g_lockOps; // (Mandatory) Hook the I2cLockMethod instance.
328          (void)OsalSpinInit(&hi35xx->spin); // (Mandatory) Initialize the lock.
329          ret = I2cCntlrAdd(&hi35xx->cntlr); // (Mandatory) Call this function to add the controller object to the core layer of the platform. The driver can access the core layer of the platform only after a success signal is returned.
330          ...
331      #ifdef USER_VFS_SUPPORT
332          (void)I2cAddVfsById(hi35xx->cntlr.busId);// (Optional) Mount the user-level VFS if required.
333      #endif
334          return HDF_SUCCESS;
335      __ERR__:                                      // If the operation fails, roll back the operations that have been performed in the function (such as unmapping I/O and releasing memory) and return an error code.
336          if (hi35xx != NULL) {
337              if (hi35xx->regBase != NULL) {
338                  OsalIoUnmap((void *)hi35xx->regBase);
339                  hi35xx->regBase = NULL;
340              }
341              OsalMemFree(hi35xx);
342              hi35xx = NULL;
343          }
344          return ret;
345      }
346      ```
347
348   - Implement the **Release** function.
349
350      Input parameter:
351
352      **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
353
354      Return value:
355
356      No value is returned.
357
358      Function description:
359
360      Releases the memory and deletes the controller. This function assigns values to the **Release** function in the driver entry structure. If the HDF fails to call the **Init** function to initialize the driver, the **Release** function can be called to release driver resources.
361
362      ```c
363      static void Hi35xxI2cRelease(struct HdfDeviceObject *device)
364      {
365          ...
366          /* Release each node separately, like Hi35xxI2cInit. */
367          DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
368              Hi35xxI2cRemoveByNode(childNode);// The function is defined as follows:
369          }
370      }
371
372      static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node)
373      {
374          ...
375          /* (Mandatory) Call I2cCntlrGet() to obtain the pointer to the I2cCntlr object based on the bus number of the device, and call I2cCntlrRemove() to remove the I2cCntlr object from the core layer of the platform. */
376          cntlr = I2cCntlrGet(bus);
377          if (cntlr != NULL && cntlr->priv == node) {
378              ...
379              I2cCntlrRemove(cntlr);
380              /* (Mandatory) Unmap the register address and release the lock and memory. */
381              hi35xx = (struct Hi35xxI2cCntlr *)cntlr;
382              OsalIoUnmap((void *)hi35xx->regBase);
383              (void)OsalSpinDestroy(&hi35xx->spin);
384              OsalMemFree(hi35xx);
385          }
386          return;
387      }
388      ```
389