未验证 提交 0f390741 编写于 作者: O openharmony_ci 提交者: Gitee

!13065 【翻译完成】#I5ZK8S

Merge pull request !13065 from Annie_wang/PR10751
...@@ -4,78 +4,69 @@ ...@@ -4,78 +4,69 @@
### Function ### Function
An analog-to-digital converter (ADC) is a device that converts analog signals into digital signals. An analog-to-digital converter (ADC) converts analog signals into digital signals for storage and computing. In addition to the power cable and ground cable, the ADC requires only one cable to connect to the target device. The following figure shows the physical connection of the ADC.
**Figure 1** ADC physical connection
![](figures/ADC_physical_connection.png "ADC_physical_connection")
The ADC module provides a set of APIs to complete AD conversion, including:
The ADC APIs provide a set of common functions for ADC data transfer, including:
- Opening or closing an ADC device - Opening or closing an ADC device
- Obtaining the analog-to-digital (AD) conversion result - Obtaining the analog-to-digital (AD) conversion result
### Basic Concepts ### Basic Concepts
The ADC converts analog parameters into digital parameters for easy storage and computing. The technical specifications of the ADC include the following:
- Resolution - Resolution
The number of binary bits that can be converted by an ADC. A greater number of bits indicates a higher resolution. The number of binary bits that can be converted by an ADC. A greater number of bits indicates a higher resolution.
- Conversion error - Conversion error
Difference between the actual and theoretical digital values output by an ADC. It is expressed by a multiple of the least significant bit. Generally, the maximum output error is used. Difference between the actual and theoretical digital values output by an ADC. It is expressed by a multiple of the least significant bit. Generally, the maximum output error is used.
- Transition time - Transition time
Time required by an ADC to perform a complete conversion. Time required by an ADC to perform a complete conversion.
### Working Principles ### Working Principles
In the Hardware Driver Foundation (HDF), the ADC module uses the unified service mode for API adaptation. In this mode, a service is used as the ADC manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple 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. In the Hardware Driver Foundation (HDF), the ADC module uses the unified service mode for API adaptation. In this mode, a service is used as the ADC manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple 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 ADC module is divided into the following layers:
- Interface layer: provides APIs for opening or closing a device and writing data.
- Core layer: provides the capabilities of binding, initializing, and releasing devices.
- Adaptation layer: implements driver-specific functions.
In addition to the power and ground cables, the ADC requires only one cable to connect to the target device. The figure below shows the physical connection.
**Figure 1** ADC physical connection
![](figures/ADC_physical_connection.png "ADC_physical_connection")
### Constraints ### Constraints
Currently, the ADC module supports only the kernels (LiteOS) of mini and small systems. The ADC module can read data only in polling mode.
## Usage Guidelines ## Usage Guidelines
### When to Use ### When to Use
An ADC is usually used to convert an analog voltage into a digital parameter, for example, it is used with a microphone to collect sound, used with an NTC resistor to measure temperature, or converts the output of analog sensors into digital parameters. ADC devices are used to convert analog voltage or current into digital parameters. For example, an ADC can be used with an NTC resistor to measure temperature, or can be used to convert the output of an analog sensor into a digital parameter.
### Available APIs ### Available APIs
The table below describes the APIs of the ADC module. For more details, see API Reference. The following table describes the APIs of the ADC module. For more information, see **//drivers/hdf_core/framework/include/platform/adc_if.h**.
**Table 1** APIs of the ADC driver **Table 1** APIs of the ADC driver
| API | Description |
| API | Description |
| -------- | ---------------- | | -------- | ---------------- |
| AdcOpen | Opens an ADC device. | | DevHandle AdcOpen(uint32_t number) | Opens an ADC device. |
| AdcClose | Closes an ADC device. | | void AdcClose(DevHandle handle) | Closes an ADC device. |
| AdcRead | Obtains the AD conversion result.| | int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t \*val) | Obtains the AD conversion result.|
### How to Develop ### How to Develop
The figure below shows the general development process. The following figure illustrates how to use ADC APIs.
**Figure 2** Process of using ADC APIs **Figure 2** Process of using ADC APIs
![](figures/using-ADC-process.png) ![](figures/using-ADC-process.png)
#### Opening an ADC Device #### Opening an ADC Device
Call **AdcOpen** to open an ADC device. Call **AdcOpen()** to open an ADC device.
```c ```c
DevHandle AdcOpen(int16_t number); DevHandle AdcOpen(int16_t number);
...@@ -83,12 +74,11 @@ DevHandle AdcOpen(int16_t number); ...@@ -83,12 +74,11 @@ DevHandle AdcOpen(int16_t number);
**Table 2** Description of AdcOpen **Table 2** Description of AdcOpen
| Parameter | Description | | Parameter | Description |
| ---------- | ----------------- | | ---------- | ----------------- |
| number | ADC device number. | | number | ADC device number. |
| **Return Value**| **Description** | | **Return Value**| **Description** |
| NULL | The operation failed. | | NULL | The operation fails. |
| Device handle | The operation is successful. The handle of the ADC device opened is returned.| | Device handle | The operation is successful. The handle of the ADC device opened is returned.|
Example: Open device 1 of the two ADCs (numbered 0 and 1) in the system. Example: Open device 1 of the two ADCs (numbered 0 and 1) in the system.
...@@ -99,7 +89,7 @@ DevHandle adcHandle = NULL; /* ADC device handle / ...@@ -99,7 +89,7 @@ DevHandle adcHandle = NULL; /* ADC device handle /
/* Open ADC device 1. */ /* Open ADC device 1. */
adcHandle = AdcOpen(1); adcHandle = AdcOpen(1);
if (adcHandle == NULL) { if (adcHandle == NULL) {
HDF_LOGE("AdcOpen: failed\n"); HDF_LOGE("AdcOpen: fail\n");
return; return;
} }
``` ```
...@@ -112,7 +102,6 @@ int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t *val); ...@@ -112,7 +102,6 @@ int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t *val);
**Table 3** Description of AdcRead **Table 3** Description of AdcRead
| Parameter | Description | | Parameter | Description |
| ---------- | -------------- | | ---------- | -------------- |
| handle | ADC device handle. | | handle | ADC device handle. |
...@@ -120,7 +109,7 @@ int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t *val); ...@@ -120,7 +109,7 @@ int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t *val);
| val | Pointer to the AD conversion result. | | val | Pointer to the AD conversion result. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
Example: Obtain the AD conversion result of channel 1. Example: Obtain the AD conversion result of channel 1.
...@@ -137,13 +126,12 @@ if (ret != 0) { ...@@ -137,13 +126,12 @@ if (ret != 0) {
#### Closing an ADC Device #### Closing an ADC Device
Call **AdcClose** to close the ADC device after the ADC communication is complete. Call **AdcClose()** to close the ADC device after the ADC communication is complete.
```c ```c
void AdcClose(DevHandle handle); void AdcClose(DevHandle handle);
``` ```
**Table 4** Description of AdcClose **Table 4** Description of AdcClose
| Parameter | Description | | Parameter | Description |
| ------ | ----------- | | ------ | ----------- |
| handle | ADC device handle.| | handle | ADC device handle.|
...@@ -158,13 +146,13 @@ AdcClose(adcHandle); /* Close the ADC device. */ ...@@ -158,13 +146,13 @@ AdcClose(adcHandle); /* Close the ADC device. */
### Example ### Example
This following example shows how to use ADC APIs to manage an ADC device on a Hi3516D V300 board. The following example shows how to use ADC APIs to manage an ADC device on a Hi3516D V300 board.
The basic hardware information is as follows: The hardware information is as follows:
- SoC: hi3516dv300 - SoC: hi3516dv300
- The potentiometer is connected to channel 1 of ADC device 0. - The potentiometer is connected to channel 1 of ADC 0.
Perform continuous read operations on the ADC device to check whether the ADC is functioning. Perform continuous read operations on the ADC device to check whether the ADC is functioning.
...@@ -175,16 +163,17 @@ Example: ...@@ -175,16 +163,17 @@ Example:
#include "hdf_log.h" /* Header file for log APIs */ #include "hdf_log.h" /* Header file for log APIs */
/* Define device 0, channel 1. */ /* Define device 0, channel 1. */
#define ADC_DEVICE_NUM 0 #define ADC_DEVICE_NUM 0
#define ADC_CHANNEL_NUM 1 #define ADC_CHANNEL_NUM 1
#define ADC_TEST_NUM 30
/* Main entry of ADC routines. */ /* Main entry of ADC routines. */
static int32_t TestCaseAdc(void) static int32_t TestCaseAdc(void)
{ {
int32_t i; int32_t i;
int32_t ret; int32_t ret;
DevHandle adcHandle; DevHandle adcHandle = NULL;
uint32_t readBuf[30] = {0}; uint32_t readBuf[ADC_TEST_NUM] = {0};
/* Open the ADC device. */ /* Open the ADC device. */
adcHandle = AdcOpen(ADC_DEVICE_NUM); adcHandle = AdcOpen(ADC_DEVICE_NUM);
...@@ -194,10 +183,10 @@ static int32_t TestCaseAdc(void) ...@@ -194,10 +183,10 @@ static int32_t TestCaseAdc(void)
} }
/* Perform 30 times of AD conversions continuously and read the conversion results. */ /* Perform 30 times of AD conversions continuously and read the conversion results. */
for (i = 0; i < 30; i++) { for (i = 0; i < ADC_TEST_NUM; i++) {
ret = AdcRead(adcHandle, ADC_CHANNEL_NUM, &readBuf[i]); ret = AdcRead(adcHandle, ADC_CHANNEL_NUM, &readBuf[i]);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: Failed to read ADC!:%d", __func__, ret); HDF_LOGE("%s: ADC read fail!:%d", __func__, ret);
AdcClose(adcHandle); AdcClose(adcHandle);
return -1; return -1;
} }
......
# ADC # ADC
## Overview ## Overview
An analog-to-digital converter (ADC) is a device that converts analog signals into digital signals. In the Hardware Driver Foundation (HDF), the ADC module uses the unified service mode for API adaptation. In this mode, a device service is used as the ADC manager to handle external access requests in a unified manner, which is reflected in the configuration file. The unified service mode applies to the scenario where there are many device objects of the same type, for example, when the ADC has more than 10 controllers. If the independent service mode is used, more device nodes need to be configured and memory resources will be consumed by services. ### Function
**Figure 1** Unified service mode An analog-to-digital converter (ADC) is a device that converts analog signals into digital signals.
![](figures/unified-service-mode.png "ADC Unified Service Mode") ### Basic Concepts
- Resolution
## Available APIs The number of binary bits that can be converted by an ADC. A greater number of bits indicates a higher resolution.
**AdcMethod**: - Conversion error
Difference between the actual and theoretical digital values output by an ADC. It is expressed by a multiple of the least significant bit. Generally, the maximum output error is used.
``` - Transition time
Time required by an ADC to perform a complete conversion.
### Working Principles
In the Hardware Driver Foundation (HDF), the ADC module uses the unified service mode for API adaptation. In this mode, a device service is used as the ADC 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 of the ADC module.
The ADC module is divided into the following layers:
- Interface layer: provides the capabilities of opening a device, writing data, and closing a device.
- Core layer: binds services, initializes and releases the PlatformManager, and provides the capabilities of adding, deleting, and obtaining controllers.
- Adaptation layer: implements hardware-related functions, such as controller initialization.
In 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.
**Figure 1** Unified service mode
![](figures/unified-service-mode.png "ADC Unified Service Mode")
## Usage Guidelines
### When to Use
ADC devices are used to convert analog voltage into digital parameters. For example, an ADC can be used with an NTC resistor to measure temperature, or can be used to convert the output of an analog sensor into a digital parameter. Before using ADC devices with OpenHarmony, you need to adapt the ADC driver to OpenHarmony. The following describes how to do it.
### Available APIs
To enable the upper layer to successfully operate the hardware by calling the ADC APIs, hook functions are defined in **//drivers/hdf_core/framework/support/platform/include/adc/adc_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.
Definitions of **AdcMethod** and **AdcLockMethod**:
```c
struct AdcMethod { struct AdcMethod {
int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val); int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *val);
int32_t (*start)(struct AdcDevice *device); int32_t (*start)(struct AdcDevice *device);
int32_t (*stop)(struct AdcDevice *device); int32_t (*stop)(struct AdcDevice *device);
}; };
struct AdcLockMethod {
int32_t (*lock)(struct AdcDevice *device);
void (*unlock)(struct AdcDevice *device);
};
``` ```
**Table 1** Description of the callback functions in AdcMethod At the adaptation layer, **AdcMethod** must be implemented, and **AdcLockMethod** can be implemented based on service requirements. The core layer provides the default **AdcLockMethod**, in which a spinlock is used to protect the critical section.
```c
static int32_t AdcDeviceLockDefault(struct AdcDevice *device)
{
if (device == NULL) {
return HDF_ERR_INVALID_OBJECT;
}
return OsalSpinLock(&device->spin);
}
static void AdcDeviceUnlockDefault(struct AdcDevice *device)
{
if (device == NULL) {
return;
}
(void)OsalSpinUnlock(&device->spin);
}
static const struct AdcLockMethod g_adcLockOpsDefault = {
.lock = AdcDeviceLockDefault,
.unlock = AdcDeviceUnlockDefault,
};
```
If spinlock cannot be used, you can use another type of lock to implement **AdcLockMethod**. The custom **AdcLockMethod** will replace the default **AdcLockMethod**.
**Table 1** Hook functions in **AdcMethod**
| Function| Input Parameter| Output Parameter| Return Value| Description| | Function| Input Parameter| Output Parameter| Return Value| Description|
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
| read | **device**: structure pointer to the ADC controller at the core layer.<br>**channel**: channel ID, which is of the uint32_t type.| **val**: pointer to the signal data to be transmitted. It is of the uint32_t type.| HDF_STATUS| Reads the signal data sampled by the ADC.| | read | **device**: structure pointer to the ADC controller at the core layer.<br>**channel**: channel number, which is of the uint32_t type.| **val**: pointer to the signal data to be transmitted. It is of the uint32_t type.| HDF_STATUS| Reads the signal data sampled by the ADC.|
| stop | **device**: structure pointer to the ADC controller at the core layer.| –| HDF_STATUS| Stops an ADC device.| | stop | **device**: structure pointer to the ADC controller at the core layer.| –| HDF_STATUS| Stops an ADC device.|
| start | **device**: structure pointer to the ADC controller at the core layer.| –| HDF_STATUS| Starts an ADC device.| | start | **device**: structure pointer to the ADC controller at the core layer.| –| HDF_STATUS| Starts an ADC device.|
**Table 2** Functions in **AdcLockMethod**
| Function| Input Parameter| Output Parameter| Return Value| Description|
| -------- | -------- | -------- | -------- | -------- |
| lock | **device**: structure pointer to the ADC device object at the core layer.| –| HDF_STATUS| Acquires the critical section lock.|
| unlock | **device**: structure pointer to the ADC device object at the core layer.| –| HDF_STATUS| Releases the critical section lock.|
## How to Develop ### How to Develop
The ADC module adaptation involves the following steps: The ADC module adaptation involves the following steps:
...@@ -44,137 +118,144 @@ The ADC module adaptation involves the following steps: ...@@ -44,137 +118,144 @@ The ADC module adaptation involves the following steps:
- Add the **deviceNode** information to the **device_info.hcs** file. - Add the **deviceNode** information to the **device_info.hcs** file.
- (Optional) Add the **adc_config.hcs** file. - (Optional) Add the **adc_config.hcs** file.
3. Instantiate the ADC controller object. 3. Instantiate the core layer APIs.
- Initialize **AdcDevice**. - Initialize **AdcDevice**.
- Instantiate **AdcMethod** in the **AdcDevice** object. - Instantiate **AdcMethod** in the **AdcDevice** object.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br>
> For details, see [Available APIs](#available-apis). > For details about the functions in **AdcMethod**, see [Available APIs](#available-apis).
4. Debug the driver.
(Optional) For new drivers, verify basic functions, for example, check the information returned after the driver is attached and check whether signals are successfully collected.
### Example
## Development Example The following uses the Hi3516D V300 driver **//device/soc/hisilicon/common/platform/adc/adc_hi35xx.c** as an example to describe how to perform the ADC driver adaptation.
The following uses **adc_hi35xx.c** as an example to present the information required for implementing device functions.
1. Instantiate the driver entry. 1. Instantiate the driver entry.
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. 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.
Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit. Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit.
ADC driver entry example: ADC driver entry example:
Many devices may connect to the ADC. In the HDF, a manager object needs to be created for the ADC. When a device needs to be started, the manager object locates the target device based on the specified parameters. Multiple devices may connect to the ADC controller. In the HDF, a manager object needs to be created for this type of devices. When a device needs to be started, the manager object locates the target device based on the specified parameters.
The driver of the ADC manager is implemented by the core layer. The **Init** function of the driver layer calls **AdcDeviceAdd** of the core layer to add devices to the ADC manager. Therefore, you do not need to implement this part. You do not need to implement the driver of the ADC manager, which is implemented by the core layer. However, the **AdcDeviceAdd** function of the core layer must be invoked in the **Init** function to implement the related features.
``` ```c
static struct HdfDriverEntry g_hi35xxAdcDriverEntry = { static struct HdfDriverEntry g_hi35xxAdcDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxAdcInit, .Init = Hi35xxAdcInit,
.Release = Hi35xxAdcRelease, .Release = Hi35xxAdcRelease,
.moduleName = "hi35xx_adc_driver", // (Mandatory) This parameter must be the same as that in the .hcs file. .moduleName = "hi35xx_adc_driver", // (Mandatory) The value must be the same as the module name in the device_info.hcs file.
}; };
HDF_INIT(g_hi35xxAdcDriverEntry); // Call HDF_INIT to register the driver entry with the HDF. HDF_INIT(g_hi35xxAdcDriverEntry); // Call HDF_INIT to register the driver entry with the HDF.
// Driver entry of the adc_core.c manager service at the core layer /* Driver entry of the adc_core.c manager service at the core layer */
struct HdfDriverEntry g_adcManagerEntry = { struct HdfDriverEntry g_adcManagerEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = AdcManagerInit, .Init = AdcManagerInit,
.Release = AdcManagerRelease, .Release = AdcManagerRelease,
.moduleName = "HDF_PLATFORM_ADC_MANAGER", // The value is device0 in the device_info file. .moduleName = "HDF_PLATFORM_ADC_MANAGER", // The value must be that of device0 in the device_info.hcs file.
}; };
HDF_INIT(g_adcManagerEntry); HDF_INIT(g_adcManagerEntry);
``` ```
2. Add **deviceNode** to the **device_info.hcs** file, and configure the device attributes in the **adc_config.hcs** file. 2. Add the **deviceNode** information to the **//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs** file and configure the device attributes in **adc_config.hcs**.
The **deviceNode** information is related to registration of the driver entry. The device attribute values are closely related to the driver implementation and the default values or restriction ranges of the **AdcDevice** members at the core layer. 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 **AdcDevice** members at the core layer.
In the unified service mode, the first device node in the **device_info** file must be the ADC manager. The parameters must be set as follows: In the unified service mode, the first device node in the **device_info.hcs** file must be the ADC manager. The parameters must be set as follows:
| Parameter| Value| | Parameter| Value|
| -------- | -------- | | -------- | -------- |
| moduleName | **HDF_PLATFORM_ADC_MANAGER**| | moduleName | **HDF_PLATFORM_ADC_MANAGER**|
| serviceName | –| | serviceName | –|
| policy | **0**, which indicates that no service is published.| | policy | **0**, which indicates that no service is published.|
| deviceMatchAttr | Reserved.| | deviceMatchAttr | Reserved.|
Configure ADC controller information from the second node. This node specifies a type of ADC controllers rather than an ADC controller. In this example, there is only one ADC device. If there are multiple ADC devices, add the **deviceNode** information to the **device_info** file and add the corresponding device attributes to the **adc_config** file for each device. Configure ADC controller information from the second node. This node specifies a type of ADC controllers rather than an ADC controller. In this example, there is only one ADC device. If there are multiple ADC devices, add the **deviceNode** information to the **device_info.hcs** file and add the corresponding device attributes to the **adc_config** file for each device.
- **device_info.hcs** configuration example - **device_info.hcs** example
```c
```
root { root {
device_info { device_info {
platform :: host { platform :: host {
device_adc :: device { device_adc :: device {
device0 :: deviceNode { device0 :: deviceNode {
policy = 0; policy = 0;
priority = 50; priority = 50;
permission = 0644; permission = 0644;
moduleName = "HDF_PLATFORM_ADC_MANAGER"; moduleName = "HDF_PLATFORM_ADC_MANAGER";
serviceName = "HDF_PLATFORM_ADC_MANAGER"; serviceName = "HDF_PLATFORM_ADC_MANAGER";
} }
device1 :: deviceNode { device1 :: deviceNode {
policy = 0; // The value 0 indicates that no service is published. policy = 0; // The value 0 indicates that no service is published.
priority = 55; // Driver startup priority. priority = 55; // Driver startup priority.
permission = 0644; // Permission to create device nodes for the driver. permission = 0644; // Permission for the device node created.
moduleName = "hi35xx_adc_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry. moduleName = "hi35xx_adc_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
serviceName = "HI35XX_ADC_DRIVER"; // (Mandatory) Unique name of the service published by the driver. serviceName = "HI35XX_ADC_DRIVER"; // (Mandatory) Unique name of the service published by the driver.
deviceMatchAttr = "hisilicon_hi35xx_adc";// (Mandatory) Private data of the controller. The value must be the same as that of the corresponding controller in adc_config.hcs. deviceMatchAttr = "hisilicon_hi35xx_adc"; // (Mandatory) Private data of the controller. The value must be the same as that of the controller in adc_config.hcs.
// The specific controller information is configured in adc_config.hcs. // The specific controller information is in adc_config.hcs.
} }
} }
}
} }
}
} }
``` ```
- **adc_config.hcs** configuration example
- **adc_config.hcs** example
```
The following uses Hi3516D V300 as an example. Some fields are unique to Hi3516D V300. You can delete or add fields as required.
```c
root { root {
platform { platform {
adc_config_hi35xx { adc_config_hi35xx {
match_attr = "hisilicon_hi35xx_adc"; match_attr = "hisilicon_hi35xx_adc";
template adc_device { template adc_device {
regBasePhy = 0x120e0000;// Physical base address of the register. regBasePhy = 0x120e0000; // Physical base address of the register.
regSize = 0x34; // Bit width of the register. regSize = 0x34; // Bit width of the register.
deviceNum = 0; // Device number. deviceNum = 0; // Device number.
validChannel = 0x1; // Valid channel. validChannel = 0x1; // Valid channel.
dataWidth = 10; // Bit width of the received signal. dataWidth = 10; // Data width after AD conversion, that is, the resolution.
scanMode = 1; // Scan mode. scanMode = 1; // Scan mode.
delta = 0; // Delta parameter. delta = 0; // Error range of the conversion result.
deglitch = 0; deglitch = 0; // Setting of the deglitch.
glitchSample = 5000; glitchSample = 5000; // Deglitch time window.
rate = 20000; rate = 20000; // Conversion rate.
}
device_0 :: adc_device {
deviceNum = 0;
validChannel = 0x2;
}
}
} }
device_0 :: adc_device {
deviceNum = 0;
validChannel = 0x2;
}
}
}
} }
``` ```
3. Initialize the **AdcDevice** 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 **AdcMethod** in **AdcDevice** (so that the underlying driver functions can be called). After the **adc_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
- Defining a custom structure
To the driver, the custom structure holds parameters and data. The **DeviceResourceIface()** function provided by the HDF reads **adc_config.hcs** to initialize the custom structure and passes some important parameters, such as the device number and bus number, to the **AdcDevice** object at the core layer. For example, if the **adc_config.hcs** file is in **//device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/adc/**, add the following statement to **hdf.hcs** of the product:
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/adc/adc_config.hcs" // Relative path of the configuration file
``` ```
This example is based on the Hi3516D V300 development board that runs the LiteOS. The corresponding **hdf.hcs** file is in **vendor/hisilicon/hispark_taurus/hdf_config/hdf.hcs** and **//device/hisilicon/hispark_taurus/sdk_liteos/hdf_config/hdf.hcs**. You can modify the file as required.
3. Initialize the **AdcDevice** 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 **AdcMethod** in **AdcDevice** (so that the underlying driver functions can be called).
- Define a custom structure.
To the driver, the custom structure holds parameters and data. The DeviceResourceIface() function provided by the HDF reads **adc_config.hcs** to initialize the custom structure and passes some important parameters, such as the device number and bus number, to the **AdcDevice** object at the core layer.
```c
struct Hi35xxAdcDevice { struct Hi35xxAdcDevice {
struct AdcDevice device; // (Mandatory) Control object at the core layer. For details, see the description of AdcDevice. struct AdcDevice device; // (Mandatory) Control object at the core layer. It must be the first member of the custom structure. For details, see the following description.
volatile unsigned char *regBase; // (Mandatory) Register base address. volatile unsigned char *regBase; // (Mandatory) Register base address.
volatile unsigned char *pinCtrlBase; volatile unsigned char *pinCtrlBase;
uint32_t regBasePhy; // (Mandatory) Physical base address of the register. uint32_t regBasePhy; // (Mandatory) Physical base address of the register.
uint32_t regSize; // (Mandatory) Bit width of the register. uint32_t regSize; // (Mandatory) Register bit width.
uint32_t deviceNum; // (Mandatory) Device number. uint32_t deviceNum; // (Mandatory) Device number.
uint32_t dataWidth; // (Mandatory) Data bit width of received signals. uint32_t dataWidth; // (Mandatory) Data bit width of received signals.
uint32_t validChannel; // (Mandatory) Valid channel. uint32_t validChannel; // (Mandatory) Valid channel.
...@@ -185,7 +266,7 @@ The ADC module adaptation involves the following steps: ...@@ -185,7 +266,7 @@ The ADC module adaptation involves the following steps:
uint32_t rate; // (Mandatory) Sampling rate. uint32_t rate; // (Mandatory) Sampling rate.
}; };
// AdcDevice is the core layer controller structure. The **Init()** function assigns values to the members of AdcDevice. /* AdcDevice is the core layer controller structure. The **Init()** function assigns values to the members of AdcDevice. */
struct AdcDevice { struct AdcDevice {
const struct AdcMethod *ops; const struct AdcMethod *ops;
OsalSpinlock spin; OsalSpinlock spin;
...@@ -196,25 +277,27 @@ The ADC module adaptation involves the following steps: ...@@ -196,25 +277,27 @@ The ADC module adaptation involves the following steps:
}; };
``` ```
- Instantiating **AdcMethod** in **AdcDevice**<br>This example does not provide the implementation of the **AdcLockMethod** callback. For details, see I2C driver development. Other members are initialized by the **Init** function. - Instantiate the hook function structure **AdcMethod** of **AdcDevice**.
The **AdcLockMethod** is not implemented in this example. To instantiate the structure, refer to the I2C driver development. Other members are initialized in the **Init** function.
```
```c
static const struct AdcMethod g_method = { static const struct AdcMethod g_method = {
.read = Hi35xxAdcRead, .read = Hi35xxAdcRead,
.stop = Hi35xxAdcStop, .stop = Hi35xxAdcStop,
.start = Hi35xxAdcStart, .start = Hi35xxAdcStart,
}; };
``` ```
- **Init** function
**Input parameter**: - Implement the **Init** function.
Input parameter:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information. **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
**Return value**: Return value:
**HDF_STATUS**<br/>The table below describes some status. For more information, see **HDF_STATUS** in the **/drivers/framework/include/utils/hdf_base.h** file. **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.
| Status| Description| | Status| Description|
| -------- | -------- | | -------- | -------- |
...@@ -225,19 +308,19 @@ The ADC module adaptation involves the following steps: ...@@ -225,19 +308,19 @@ The ADC module adaptation involves the following steps:
| HDF_SUCCESS | Transmission successful.| | HDF_SUCCESS | Transmission successful.|
| HDF_FAILURE | Transmission failed.| | HDF_FAILURE | Transmission failed.|
**Function description**: Function description:
Initializes the custom structure object and **AdcDevice**, and calls the **AdcDeviceAdd** function at the core layer. Initializes the custom structure object and **AdcDevice**, and calls the **AdcDeviceAdd** function at the core layer.
``` ```c
static int32_t Hi35xxAdcInit(struct HdfDeviceObject *device) static int32_t Hi35xxAdcInit(struct HdfDeviceObject *device)
{ {
int32_t ret; int32_t ret;
struct DeviceResourceNode *childNode = NULL; struct DeviceResourceNode *childNode = NULL;
... ...
// Traverse and parse all nodes in adc_config.hcs and call the **Hi35xxAdcParseInit** function to initialize the devices separately. /* Traverse and parse all nodes in adc_config.hcs and call the **Hi35xxAdcParseInit** function to initialize the devices separately. */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = Hi35xxAdcParseInit(device, childNode);// The function definition is as follows: ret = Hi35xxAdcParseInit(device, childNode); // The function definition is as follows:
... ...
} }
return ret; return ret;
...@@ -245,87 +328,111 @@ The ADC module adaptation involves the following steps: ...@@ -245,87 +328,111 @@ The ADC module adaptation involves the following steps:
static int32_t Hi35xxAdcParseInit(struct HdfDeviceObject *device, struct DeviceResourceNode *node) static int32_t Hi35xxAdcParseInit(struct HdfDeviceObject *device, struct DeviceResourceNode *node)
{ {
int32_t ret; int32_t ret;
struct Hi35xxAdcDevice *hi35xx = NULL; // (Mandatory) Custom structure object. struct Hi35xxAdcDevice *hi35xx = NULL; // (Mandatory) Custom structure object.
(void)device; (void)device;
hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); // (Mandatory) Allocate memory. hi35xx = (struct Hi35xxAdcDevice *)OsalMemCalloc(sizeof(*hi35xx)); // (Mandatory) Allocate memory.
... ...
ret = Hi35xxAdcReadDrs(hi35xx, node); // (Mandatory) Fill the default values in the adc_config file to the structure. ret = Hi35xxAdcReadDrs(hi35xx, node); // (Mandatory) Use the default values in the adc_config file to fill in the structure. The function definition is as follows.
... ...
hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize);// (Mandatory) Address mapping. hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // (Mandatory) Address mapping.
... ...
hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE); hi35xx->pinCtrlBase = OsalIoRemap(HI35XX_ADC_IO_CONFIG_BASE, HI35XX_ADC_IO_CONFIG_SIZE);
... ...
Hi35xxAdcDeviceInit(hi35xx); // (Mandatory) Initialize the ADC. Hi35xxAdcDeviceInit(hi35xx); // (Mandatory) Initialize the ADC.
hi35xx->device.priv = (void *)node; // (Mandatory) Store device attributes. hi35xx->device.priv = (void *)node; // (Mandatory) Save device attributes.
hi35xx->device.devNum = hi35xx->deviceNum;// (Mandatory) Initialize AdcDevice members. hi35xx->device.devNum = hi35xx->deviceNum;// (Mandatory) Initialize AdcDevice.
hi35xx->device.ops = &g_method; // (Mandatory) Attach the AdcMethod instance object. hi35xx->device.ops = &g_method; // (Mandatory) Attach the AdcMethod instance object.
ret = AdcDeviceAdd(&hi35xx->device); // (Mandatory) Call this function to set the structure of the core layer. The driver accesses the platform core layer only after a success signal is returned. ret = AdcDeviceAdd(&hi35xx->device)); // (Mandatory) Call this function to set the structure at the core layer. The driver can access the platform core layer only after a success signal is returned.
... ...
return HDF_SUCCESS; return HDF_SUCCESS;
__ERR__: __ERR__:
if (hi35xx != NULL) { // If the operation fails, execute the initialization function reversely. if (hi35xx != NULL) { // If the operation fails, deinitialize related functions.
if (hi35xx->regBase != NULL) { if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
hi35xx->regBase = NULL; hi35xx->regBase = NULL;
}
AdcDeviceRemove(&hi35xx->device);
OsalMemFree(hi35xx);
} }
AdcDeviceRemove(&hi35xx->device); return ret;
OsalMemFree(hi35xx);
} }
return ret;
static int32_t Hi35xxAdcReadDrs(struct Hi35xxAdcDevice *hi35xx, const struct DeviceResourceNode *node)
{
int32_t ret;
struct DeviceResourceIface *drsOps = NULL;
/* Obtain the drsOps method. */
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL) {
HDF_LOGE("%s: invalid drs ops", __func__);
return HDF_ERR_NOT_SUPPORT;
}
/* Read the configuration parameters in sequence and fill them in the structure. */
ret = drsOps->GetUint32(node, "regBasePhy", &hi35xx->regBasePhy, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regBasePhy failed", __func__);
return ret;
}
ret = drsOps->GetUint32(node, "regSize", &hi35xx->regSize, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read regSize failed", __func__);
return ret;
}
···
return HDF_SUCCESS;
} }
``` ```
- **Release** function
**Input parameter**: - Implement the **Release** function.
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information. Input parameter:
**Return value**: **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
No value is returned. Return value:
**Function description**: No value is returned.
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. Function description:
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br> 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.
> All forced conversion operations for obtaining the corresponding object can be successful only when the **Init** function has the value assignment operations.
```c
```
static void Hi35xxAdcRelease(struct HdfDeviceObject *device) static void Hi35xxAdcRelease(struct HdfDeviceObject *device)
{ {
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
... ...
// Traverse and parse all nodes in adc_config.hcs and perform the release operation on each node. /* Traverse and parse all nodes in adc_config.hcs and perform the release operation on each node. */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
Hi35xxAdcRemoveByNode(childNode);// The function definition is as follows: Hi35xxAdcRemoveByNode(childNode);// The function definition is as follows:
} }
} }
static void Hi35xxAdcRemoveByNode(const struct DeviceResourceNode *node) static void Hi35xxAdcRemoveByNode(const struct DeviceResourceNode *node)
{ {
int32_t ret; int32_t ret;
int32_t deviceNum; int32_t deviceNum;
struct AdcDevice *device = NULL; struct AdcDevice *device = NULL;
struct Hi35xxAdcDevice *hi35xx = NULL; struct Hi35xxAdcDevice *hi35xx = NULL;
struct DeviceResourceIface *drsOps = NULL; struct DeviceResourceIface *drsOps = NULL;
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
... ...
ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0); ret = drsOps->GetUint32(node, "deviceNum", (uint32_t *)&deviceNum, 0);
... ...
// You can call the AdcDeviceGet function to obtain the AdcDevice object based on deviceNum and call the AdcDeviceRemove function to release the AdcDevice object. /* You can use AdcDeviceGet() to obtain the AdcDevice object based on deviceNum and use AdcDeviceRemove() to release the AdcDevice object. */
device = AdcDeviceGet(deviceNum); device = AdcDeviceGet(deviceNum);
if (device != NULL && device->priv == node) { if (device != NULL && device->priv == node) {
AdcDevicePut(device); AdcDevicePut(device);
AdcDeviceRemove(device); // (Mandatory) Remove the AdcDevice object from the driver manager. AdcDeviceRemove(device); // (Mandatory) Remove the AdcDevice object from the driver manager.
hi35xx = (struct Hi35xxAdcDevice *)device;// (Mandatory) Obtain the custom object through forced conversion and release the operation. hi35xx = (struct Hi35xxAdcDevice *)device; // (Mandatory) Obtain the custom object through forcible conversion and perform the Release operation. To perform this operation, the device must be the first member of the custom structure.
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
OsalMemFree(hi35xx); OsalMemFree(hi35xx);
}
return;
} }
return
``` ```
...@@ -2,18 +2,19 @@ ...@@ -2,18 +2,19 @@
## Overview ## Overview
### DAC ### Function
A digit-to-analog converter (DAC) is a device that converts a digital signal into an analog signal in electronics. A digit-to-analog converter (DAC) is a device that converts a digital signal into an analog signal in electronics. DAC devices are used to:
The DAC APIs provide a set of methods for DAC data transfer, including: - Provide the output channel for the process control computer system and connect to the executor to implement automatic control of the production process.
- Serve as an important module in the analog-to-digital converter using feedback technologies.
The DAC module provides a set of methods for DAC data transfer, including:
- Opening or closing a DAC device - Opening or closing a DAC device
- Setting the target digital-to-analog (DA) value - Setting the target digital-to-analog (DA) value
### Basic Concepts ### Basic Concepts
The DAC module provides the output channel for the process control computer system. It connects to the executor to implement automatic control of the production process. It is also an important module in the analog-to-digital converter using feedback technologies.
- Resolution - Resolution
The number of binary bits that can be converted by a DAC. A greater number of bits indicates a higher resolution. The number of binary bits that can be converted by a DAC. A greater number of bits indicates a higher resolution.
...@@ -32,7 +33,7 @@ The DAC module provides the output channel for the process control computer syst ...@@ -32,7 +33,7 @@ The DAC module provides the output channel for the process control computer syst
### Working Principles ### Working Principles
In the Hardware Driver Foundation (HDF), the DAC module uses the unified service mode for API adaptation. In this mode, a service is used as the DAC manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple 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 figure below shows the unified service mode. In the Hardware Driver Foundation (HDF), the DAC module uses the unified service mode for API adaptation. In this mode, a service is used as the DAC manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple device objects of the same type. If the independent service mode is used, more device nodes need to be configured and memory resources will be consumed by services. The following figure illustrates the unified service mode of the DAC module.
The DAC module is divided into the following layers: The DAC module is divided into the following layers:
...@@ -40,9 +41,7 @@ The DAC module is divided into the following layers: ...@@ -40,9 +41,7 @@ The DAC module is divided into the following layers:
- Core layer: provides the capabilities of binding, initializing, and releasing devices. - Core layer: provides the capabilities of binding, initializing, and releasing devices.
- Adaptation layer: implements driver-specific functions. - Adaptation layer: implements driver-specific functions.
>![](../public_sys-resources/icon-note.gif) **NOTE** ![](../public_sys-resources/icon-note.gif)**NOTE**<br/>The core layer can call the functions of the interface layer and uses the hook to call functions of the adaptation layer. In this way, the adaptation layer can indirectly call the functions of the interface layer, but the interface layer cannot call the functions of the adaptation layer.
>
> The core layer can call the functions of the interface layer and uses a hook to call functions of the adaptation layer. In this way, the adaptation layer can indirectly call the functions of the interface layer, but the interface layer cannot call the functions of the adaptation layer.
**Figure 1** Unified service mode **Figure 1** Unified service mode
...@@ -50,7 +49,7 @@ The DAC module is divided into the following layers: ...@@ -50,7 +49,7 @@ The DAC module is divided into the following layers:
### Constraints ### Constraints
Currently, the DAC module supports only the kernels (LiteOS) of mini and small systems. The DAC module supports only the kernel (LiteOS-A) for mini and small systems.
## Usage Guidelines ## Usage Guidelines
...@@ -60,11 +59,11 @@ The DAC module converts digital signals into analog signals in the form of curre ...@@ -60,11 +59,11 @@ The DAC module converts digital signals into analog signals in the form of curre
### Available APIs ### Available APIs
The table below describes the APIs of the DAC module. For more details, see API Reference. The following table describes the APIs of the DAC module. For more information about the APIs, see **//drivers/hdf_core/framework/include/platform/dac_if.h**.
**Table 1** DAC driver APIs **Table 1** DAC driver APIs
| API | Description | | API | Description |
| ------------------------------------------------------------------ | ------------ | | ------------------------------------------------------------------ | ------------ |
| DevHandle DacOpen(uint32_t number) | Opens a DAC device. | | DevHandle DacOpen(uint32_t number) | Opens a DAC device. |
| void DacClose(DevHandle handle) | Closes a DAC device. | | void DacClose(DevHandle handle) | Closes a DAC device. |
...@@ -72,12 +71,11 @@ The table below describes the APIs of the DAC module. For more details, see API ...@@ -72,12 +71,11 @@ The table below describes the APIs of the DAC module. For more details, see API
### How to Develop ### How to Develop
The figure below shows the general development process. The following figure illustrates how to use DAC APIs.
**Figure 2** Process of using DAC APIs **Figure 2** Process of using DAC APIs
![Process of using a DAC](figures/using-DAC-process.png "Process of using a DAC")
![](figures/using-DAC-process.png)
#### Opening a DAC Device #### Opening a DAC Device
...@@ -93,13 +91,13 @@ DevHandle DacOpen(uint32_t number); ...@@ -93,13 +91,13 @@ DevHandle DacOpen(uint32_t number);
| --------- | ---------------- | | --------- | ---------------- |
| number | DAC device number. | | number | DAC device number. |
| **Return Value**| **Description** | | **Return Value**| **Description** |
| NULL | The operation failed. | | NULL | The operation fails. |
| Device handle | The operation is successful. The handle of the DAC device opened is returned.| | Device handle | The operation is successful. The handle of the DAC device opened is returned.|
Example: Open device 1 of the two DAC devices (numbered 0 and 1) in the system. Open device 1 of the two DAC devices (numbered 0 and 1) in the system.
```c++ ```c++
DevHandle dacHandle = NULL; /* DAC device handle / DevHandle dacHandle = NULL; // DAC device handle.
/* Open DAC device 1. */ /* Open DAC device 1. */
dacHandle = DacOpen(1); dacHandle = DacOpen(1);
...@@ -109,7 +107,7 @@ if (dacHandle == NULL) { ...@@ -109,7 +107,7 @@ if (dacHandle == NULL) {
} }
``` ```
#### Setting a Target DA Value #### Setting a DA Value
```c++ ```c++
int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val); int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val);
...@@ -124,16 +122,16 @@ int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val); ...@@ -124,16 +122,16 @@ int32_t DacWrite(DevHandle handle, uint32_t channel, uint32_t val);
| val | DA value to set. | | val | DA value to set. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
```c++ ```c++
/* Write the target DA value through the DAC_CHANNEL_NUM channel. */ /* Write the target DA value through the DAC_CHANNEL_NUM channel. */
ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val); ret = DacWrite(dacHandle, DAC_CHANNEL_NUM, val);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret); HDF_LOGE("%s: tp DAC write reg fail!:%d", __func__, ret);
DacClose(dacHandle); DacClose(dacHandle);
return -1; return -1;
} }
``` ```
#### Closing a DAC Device #### Closing a DAC Device
...@@ -168,8 +166,8 @@ The procedure is as follows: ...@@ -168,8 +166,8 @@ The procedure is as follows:
You can obtain the operation result by printing the log information based on the **val**. You can obtain the operation result by printing the log information based on the **val**.
```c++ ```c++
#include "dac_if.h" /* Header file for DAC APIs */ #include "dac_if.h" /* Header file for DAC APIs. */
#include "hdf_log.h" /* Header file for log APIs */ #include "hdf_log.h" /* Header file for log APIs. */
/* Define device 0, channel 1. */ /* Define device 0, channel 1. */
#define DAC_DEVICE_NUM 0 #define DAC_DEVICE_NUM 0
......
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
A digit-to-analog converter (DAC) is a device that converts a digital signal into an analog signal in electronics. A digit-to-analog converter (DAC) is a device that converts a digital signal into an analog signal in electronics.
The DAC module supports development of digital-to-analog conversion. The DAC module provides the output channel for the process control computer system. It connects to the executor to implement automatic control of the production process. It is also an important module in the analog-to-digital converter using feedback technologies. The DAC module supports development of digital-to-analog conversion. The DAC devices can be used to:
- Provide the output channel for the process control computer system and connect to the executor to implement automatic control of the production process.
- Serve as an important module in the analog-to-digital converter using feedback technologies.
### Basic Concepts ### Basic Concepts
...@@ -28,54 +31,96 @@ The DAC module supports development of digital-to-analog conversion. The DAC mod ...@@ -28,54 +31,96 @@ The DAC module supports development of digital-to-analog conversion. The DAC mod
### Working Principles ### Working Principles
In the Hardware Driver Foundation (HDF), the DAC module uses the unified service mode for API adaptation. In this mode, a device service is used as the DAC 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 figure below shows the unified service mode. In the Hardware Driver Foundation (HDF), the DAC module uses the unified service mode for API adaptation. In this mode, a device service is used as the DAC 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 DAC module uses the unified service mode, as shown in Figure 1.
The DAC module is divided into the following layers: The DAC module is divided into the following layers:
- Interface layer: provides the capabilities of opening and closing a device and writing data.
- Interface layer: provides APIs for opening or closing a device and writing data.
- Core layer: provides the capabilities of binding, initializing, and releasing devices. - Core layer: provides the capabilities of binding, initializing, and releasing devices.
- Adaptation layer: implements other functions. - Adaptation layer: implements hardware-related functions, such as controller initialization.
![](../public_sys-resources/icon-note.gif) **NOTE**<br/>The core layer can call the APIs of the interface layer and uses hooks to call APIs of the adaptation layer. In this way, the adaptation layer can indirectly call the APIs of the interface layer, but the interface layer cannot call the APIs of the adaptation layer. In 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.
**Figure 1** Unified service mode >![](../public_sys-resources/icon-note.gif) **NOTE**<br/>The core layer can call the APIs of the interface layer and uses hooks to call APIs of the adaptation layer. In this way, the adaptation layer can indirectly call the APIs of the interface layer, but the interface layer cannot call the APIs of the adaptation layer.
**Figure 1** Unified service mode
![](figures/unified-service-mode.png "DAC unified service mode") ![](figures/unified-service-mode.png "DAC unified service mode")
### Constraints ### Constraints
Currently, the DAC module supports only the kernels (LiteOS) of mini and small systems. The DAC module supports only the kernel (LiteOS-A) for mini and small systems.
## Development Guidelines ## Development Guidelines
### When to Use ### When to Use
The DAC module is used for digital-to-analog conversion, audio output, and motor control. The DAC driver is used when the digital signals input by the DAC module are converted into analog signals to output. The DAC module is used for digital-to-analog conversion, audio output, and motor control. The DAC driver is used when the digital signals input by the DAC module are converted into analog signals to output. Before using DAC devices with OpenHarmony, you need to adapt the DAC driver to OpenHarmony. The following describes how to do it.
### Available APIs ### Available APIs
The **DacMethod** structure is used to call the DAC driver APIs. To enable the upper layer to successfully operate the hardware by calling the DAC APIs, hook functions are defined in **//drivers/hdf_core/framework/support/platform/include/dac/dac_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.
**DacMethod**: Definitions of **DacMethod** and **DacLockMethod**:
```c++ ```c++
struct DacMethod { struct DacMethod {
// Hook used to write data. /* Hook used to write data. */
int32_t (*write)(struct DacDevice *device, uint32_t channel, uint32_t val); int32_t (*write)(struct DacDevice *device, uint32_t channel, uint32_t val);
// Hook used to start a DAC device. /* Hook used to start a DAC device. */
int32_t (*start)(struct DacDevice *device); int32_t (*start)(struct DacDevice *device);
// Hook used to stop a DAC device /* Hook used to stop a DAC device. */
int32_t (*stop)(struct DacDevice *device); int32_t (*stop)(struct DacDevice *device);
}; };
struct DacLockMethod {
int32_t (*lock)(struct DacDevice *device);
void (*unlock)(struct DacDevice *device);
};
```
At the adaptation layer, **DacMethod** must be implemented, and **DacLockMethod** can be implemented based on service requirements. The core layer provides the default **DacLockMethod**, in which a spinlock is used to protect the critical section.
```c
static int32_t DacDeviceLockDefault(struct DacDevice *device)
{
if (device == NULL) {
HDF_LOGE("%s: device is null", __func__);
return HDF_ERR_INVALID_OBJECT;
}
return OsalSpinLock(&device->spin);
}
static void DacDeviceUnlockDefault(struct DacDevice *device)
{
if (device == NULL) {
HDF_LOGE("%s: device is null", __func__);
return;
}
(void)OsalSpinUnlock(&device->spin);
}
static const struct DacLockMethod g_dacLockOpsDefault = {
.lock = DacDeviceLockDefault,
.unlock = DacDeviceUnlockDefault,
};
``` ```
**Table 1** Description of the DacMethod structure If spinlock cannot be used, you can use another type of lock to implement **DacLockMethod**. The implemented **DacLockMethod** will replace the default **DacLockMethod**.
**Table 1** Hook functions in **DacMethod**
| Function| Input Parameter | Output Parameter| Return Value | Description | | Function| Input Parameter | Output Parameter| Return Value | Description |
| -------- | ------------------------------------------------------------ | ---- | ------------------ | -------------- | | -------- | ------------------------------------------------------------ | ---- | ------------------ | -------------- |
| write | **device**: structure pointer to the DAC controller at the core layer.<br>**channel**: channel ID, which is of the uint32_t type.<br>**val**: data to write, which is of the uint32_t type.| - | HDF_STATUS| Writes the target digit-to-analog (DA) value.| | write | **device**: structure pointer to the DAC controller at the core layer.<br>**channel**: channel ID, which is of the uint32_t type.<br>**val**: data to write, which is of the uint32_t type.| - | HDF_STATUS| Writes the target digit-to-analog (DA) value.|
| start | **device**: structure pointer to the DAC controller at the core layer. | - | HDF_STATUS| Starts a DAC device. | | start | **device**: structure pointer to the DAC controller at the core layer. | - | HDF_STATUS| Starts a DAC device. |
| stop | **device**: structure pointer to the DAC controller at the core layer. | - | HDF_STATUS| Stops a DAC device. | | stop | **device**: structure pointer to the DAC controller at the core layer. | - | HDF_STATUS| Stops a DAC device. |
**Table 2** Functions in **DacLockMethod**
| Function| Input Parameter| Output Parameter| Return Value| Description|
| -------- | -------- | -------- | -------- | -------- |
| lock | **device**: structure pointer to the DAC device object at the core layer.| -| HDF_STATUS| Acquires the critical section lock.|
| unlock | **device**: structure pointer to the DAC device object at the core layer.| -| HDF_STATUS| Releases the critical section lock.|
### How to Develop ### How to Develop
...@@ -86,31 +131,31 @@ The DAC module adaptation procedure is as follows: ...@@ -86,31 +131,31 @@ The DAC module adaptation procedure is as follows:
3. Instantiate the core layer APIs. 3. Instantiate the core layer APIs.
4. Debug the driver. 4. Debug the driver.
### Development Example ### Example
The following presents the information required for implementing device functions. The following uses the Hi3516D V300 driver **//device/soc/hisilicon/common/platform/dac/dac_hi35xx.c** as an example to describe how to perform the DAC driver adaptation.
1. Instantiate the driver entry. 1. Instantiate the driver entry.
The driver entry must be a global variable of the **HdfDriverEntry** type (defined in **hdf_device_desc.h**), and the module name must be the same as that in **//vendor/hisilicon/hispark_taurus/hdf_config/device_info/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.
Generally, the HDF calls **Init()** to load the driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit.
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.
Generally, the HDF calls the **Init()** function to load the driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit.
```c++ ```c++
static struct HdfDriverEntry g_dacDriverEntry = { static struct HdfDriverEntry g_dacDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = VirtualDacInit, .Init = VirtualDacInit,
.Release = VirtualDacRelease, .Release = VirtualDacRelease,
.moduleName = "virtual_dac_driver",// (Mandatory) The value must be the same as that in the .hcs file. .moduleName = "virtual_dac_driver",// (Mandatory) The value must be the same as that in the .hcs file.
}; };
HDF_INIT(g_dacDriverEntry); // Call HDF_INIT to register the driver entry with the HDF. HDF_INIT(g_dacDriverEntry); // Call HDF_INIT to register the driver entry with the HDF.
``` ```
2. Configure attribute files. 2. Configure attribute files.
- Add the device node description to the **vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs** file. - Add the **//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs** file.
The device attribute values are closely related to the driver implementation and the default values or restriction ranges of the **DacDevice** members at the core layer, for example, the number of device channels and the maximum transmission speed. The device attribute values are closely related to the driver implementation and the default values or value ranges of the **DacDevice** members at the core layer, for example, the number of device channels and the maximum transmission speed.
In the unified service mode, the first device node in the **device_info.hcs** file must be the DAC manager. The parameters must be set as follows: In the unified service mode, the first device node in the **device_info.hcs** file must be the DAC manager. The parameters must be set as follows:
...@@ -123,14 +168,14 @@ The following presents the information required for implementing device function ...@@ -123,14 +168,14 @@ The following presents the information required for implementing device function
| serviceName | **HDF_PLATFORM_DAC_MANAGER** | | serviceName | **HDF_PLATFORM_DAC_MANAGER** |
| deviceMatchAttr | Reserved. | | deviceMatchAttr | Reserved. |
Configure DAC controller information from the second node. This node specifies a type of DAC controllers rather than a specific DAC controller. In this example, there is only one DAC device. If there are multiple DAC devices, you need to add the **deviceNode** information to the **device_info** file and add the corresponding device attributes to the **dac_config** file. Configure DAC controller information from the second node. This node specifies a type of DAC controllers rather than a specific DAC controller. In this example, there is only one DAC device. If there are multiple DAC devices, add the **deviceNode** information to the **device_info.hcs** file and add the corresponding device attributes to the **dac_config.hcs** file for each device.
**device_info.hcs** configuration example: **device_info.hcs** example:
```hcs ```hcs
root { root {
device_dac :: device { device_dac :: device {
// device0 is a DAC manager. /* device0 is the DAC manager. */
device0 :: deviceNode { device0 :: deviceNode {
policy = 0; policy = 0;
priority = 52; priority = 52;
...@@ -139,21 +184,23 @@ The following presents the information required for implementing device function ...@@ -139,21 +184,23 @@ The following presents the information required for implementing device function
moduleName = "HDF_PLATFORM_DAC_MANAGER"; moduleName = "HDF_PLATFORM_DAC_MANAGER";
} }
} }
// dac_virtual is a DAC controller. /* dac_virtual is a DAC controller. */
dac_virtual :: deviceNode { dac_virtual :: deviceNode {
policy = 0; policy = 0;
priority = 56; priority = 56;
permission = 0644; permission = 0644;
moduleName = "virtual_dac_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry. moduleName = "virtual_dac_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
serviceName = "VIRTUAL_DAC_DRIVER"; // (Mandatory) Unique name of the service published by the driver. serviceName = "VIRTUAL_DAC_DRIVER"; // (Mandatory) Unique name of the service published by the driver.
deviceMatchAttr = "virtual_dac"; // (Mandatory) Controller private data, which must be same as that of the corresponding controller in dac_config.hcs. deviceMatchAttr = "virtual_dac"; // (Mandatory) Controller private data, which must be same as that of the controller in dac_config.hcs.
} }
} }
``` ```
- Configure the **dac_test_config.hcs** file. - Configure the **dac_test_config.hcs** file.
Add a file, for example, **vendor/vendor_hisilicon/hispark_taurus/hdf_config/hdf_test/dac_test_config.hcs** and configure driver parameters. Add a file to the directory of a product to configure driver parameters. For example, add the **vendor/hisilicon/hispark_taurus/hdf_config/hdf_test/dac_test_config.hcs** file for the hispark_taurus development board.
The configuration parameters are as follows:
```hcs ```hcs
root { root {
...@@ -162,304 +209,352 @@ The following presents the information required for implementing device function ...@@ -162,304 +209,352 @@ The following presents the information required for implementing device function
match_attr = "virtual_dac"; // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs. match_attr = "virtual_dac"; // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs.
template dac_device { template dac_device {
deviceNum = 0; // Device number. deviceNum = 0; // Device number.
validChannel = 0x1; // Valid channel 1. validChannel = 0x1; // Valid channel 1.
rate = 20000; // Transmission speed. rate = 20000; // Transmission speed.
} }
device_0 :: dac_device { device_0 :: dac_device {
deviceNum = 0; // Device number. deviceNum = 0; // Device number.
validChannel = 0x2; // Valid channel 2. validChannel = 0x2; // Valid channel 2.
} }
} }
} }
} }
``` ```
3. Instantiate the core layer APIs. After the **dac_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
- Initializing the **DacDevice** object For example, if the path of **dac_config.hcs** is **device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/dac/dac_config.hcs**, add the following statement to **hdf.hcs** of the product:
Initialize the **DacDevice** member in the **VirtualDacParseAndInit** function. ```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/dac/dac_config.hcs" // Relative path of the file.
```
3. Instantiate the core layer APIs.
- Initialize the **DacDevice** object.
Initialize **DacDevice** in the **VirtualDacParseAndInit** function.
```c++ ```c++
// Custom structure of the virtual driver /* Custom structure of the virtual driver. */
struct VirtualDacDevice { struct VirtualDacDevice {
// DAC device structure /* DAC device structure. */
struct DacDevice device; struct DacDevice device;
// DAC device number /* DAC device number. */
uint32_t deviceNum; uint32_t deviceNum;
// Valid channel /* Valid channel. */
uint32_t validChannel; uint32_t validChannel;
// DAC rate /* DAC rate. */
uint32_t rate; uint32_t rate;
}; };
// Parse and initialize the **DacDevice** object of the core layer. /* Parse and initialize the **DacDevice** object of the core layer. */
static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
// Define the return values. /* Define the return values. */
int32_t ret; int32_t ret;
// Pointer to the virtual DAC device /* Pointer to the virtual DAC device. */
struct VirtualDacDevice *virtual = NULL; struct VirtualDacDevice *virtual = NULL;
(void)device; (void)device;
// Allocate space for this pointer. /* Allocate space for this pointer. */
virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual)); virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual));
if (virtual == NULL) { if (virtual == NULL) {
// If the value is null, return an error code. /* If the value is null, return an error code. */
HDF_LOGE("%s: Malloc virtual fail!", __func__); HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL; return HDF_ERR_MALLOC_FAIL;
} }
// Read the content of the attribute file. /* Read the attribute file. */
ret = VirtualDacReadDrs(virtual, node); ret = VirtualDacReadDrs(virtual, node);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// Failed to read the file. /* Failed to read the file. */
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret); HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
// Release the space for the virtual DAC device. /* Release the space for the virtual DAC device. */
OsalMemFree(virtual); OsalMemFree(virtual);
// Set the pointer to 0. /* Set the pointer to 0. */
virtual = NULL; virtual = NULL;
return ret; return ret;
} }
// Initialize the pointer to the virtual DAC device. /* Initialize the pointer to the virtual DAC device. */
VirtualDacDeviceInit(virtual); VirtualDacDeviceInit(virtual);
// Initialize the priv object in DacDevice. /* Initialize the priv object in DacDevice. */
virtual->device.priv = (void *)node; virtual->device.priv = (void *)node;
// Initialize the devNum object in DacDevice. /* Initialize the devNum object in DacDevice. */
virtual->device.devNum = virtual->deviceNum; virtual->device.devNum = virtual->deviceNum;
// Initialize the ops object in DacDevice. /* Initialize the ops object in DacDevice. */
virtual->device.ops = &g_method; virtual->device.ops = &g_method;
// Add a DAC device. /* Add a DAC device. */
ret = DacDeviceAdd(&virtual->device); ret = DacDeviceAdd(&virtual->device);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
// Failed to add the device. /* Failed to add the device. */
HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret); HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret);
// Release the space for the virtual DAC device. /* Release the space for the virtual DAC device. */
OsalMemFree(virtual); OsalMemFree(virtual);
// Set this pointer to null. /* Set this pointer to null. */
virtual = NULL; virtual = NULL;
return ret; return ret;
} }
return HDF_SUCCESS; return HDF_SUCCESS;
} }
``` ```
- Defining a custom structure
The custom structure holds parameters and data for the driver. Define the custom structure based on the function parameters of the device. The **DacTestReadConfig()** provided by the HDF reads the values in the **dac_config.hcs** file, and **DeviceResourceIface()** initializes the custom structure and passes some important parameters, such as the device number and bus number, to the **DacDevice** object at the core layer. - Define a custom structure.
```c++ The custom structure holds parameters and data for the driver. Define the custom structure based on the function parameters of the device. The **DacTestReadConfig()** provided by the HDF reads the values in the **dac_config.hcs** file, and **DeviceResourceIface()** initializes the custom structure and passes some important parameters, such as the device number and bus number, to the **DacDevice** object at the core layer.
struct VirtualDacDevice {
struct DacDevice device;// (Mandatory) Control object at the core layer. For details, see the description below. ```c++
uint32_t deviceNum; // (Mandatory) Device number. struct VirtualDacDevice {
uint32_t validChannel; // (Mandatory) Valid channel. struct DacDevice device;// (Mandatory) Control object at the core layer. For details, see the description below.
uint32_t rate; // (Mandatory) Sampling rate. uint32_t deviceNum; // (Mandatory) Device number.
}; uint32_t validChannel; // (Mandatory) Valid channel.
uint32_t rate; // (Mandatory) Sampling rate.
// DacDevice is the core layer controller structure. The Init() function assigns values to the members of DacDevice. };
struct DacDevice {
const struct DacMethod *ops; /* DacDevice is the core layer controller structure. The Init() function assigns values to the members of DacDevice. */
OsalSpinlock spin; // Spinlock. struct DacDevice {
uint32_t devNum; // Device number. const struct DacMethod *ops;
uint32_t chanNum; // Device channel number. OsalSpinlock spin; // Spinlock.
const struct DacLockMethod *lockOps; uint32_t devNum; // Device number.
void *priv; uint32_t chanNum; // Device channel number.
}; const struct DacLockMethod *lockOps;
``` void *priv;
};
- Instantiating **DacDevice** in **DacMethod**. ```
The **VirtualDacWrite**, **VirtualDacStop**, and **VirtualDacStart** functions are instantiated in **dac_virtual.c**.
- Instantiate **DacDevice** in **DacMethod**.
The **VirtualDacWrite**, **VirtualDacStop**, and **VirtualDacStart** functions are instantiated in **dac_virtual.c**.
```c++ ```c++
static const struct DacMethod g_method = { static const struct DacMethod g_method = {
.write = VirtualDacWrite, // Write data to a DAC device. .write = VirtualDacWrite, // Write data to a DAC device.
.stop = VirtualDacStop, // Stop a DAC device. .stop = VirtualDacStop, // Stop a DAC device.
.start = VirtualDacStart, // Start a DAC device. .start = VirtualDacStart, // Start a DAC device.
}; };
``` ```
![](../public_sys-resources/icon-note.gif) **NOTE**<br> >![](../public_sys-resources/icon-note.gif) **NOTE**<br>For details about **DacMethod**, see [Available APIs](#available-apis).
For details about **DacMethod**, see [Available APIs](#available-apis).
- **Init** function
**Input parameter**:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information.
**Return value**:
**HDF_STATUS**<br/>The table below describes some status. For more information, see **HDF_STATUS** in the **/drivers/framework/include/utils/hdf_base.h** file.
| Status | Description |
| ---------------------- | ------------- |
| HDF_ERR_INVALID_OBJECT | Invalid controller object.|
| HDF_ERR_INVALID_PARAM | Invalid parameter. |
| HDF_ERR_MALLOC_FAIL | Failed to allocate memory. |
| HDF_ERR_IO | I/O error. |
| HDF_SUCCESS | Transmission successful. |
| HDF_FAILURE | Transmission failed. |
**Function description**:
Initializes the custom structure object and **DacDevice**, and calls the **AdcDeviceAdd** function at the core layer.
```c++
static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{
// Define return value parameters.
int32_t ret;
// Pointer to the DAC device.
struct VirtualDacDevice *virtual = NULL;
(void)device;
// Allocate memory of the specified size.
virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual));
if (virtual == NULL) {
// Failed to allocate memory.
HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL;
}
// Read the node parameters in the .hcs file.
ret = VirtualDacReadDrs(virtual, node);
if (ret != HDF_SUCCESS) {
// Failed to read the node data.
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
goto __ERR__;
}
// Initialize the DAC device pointer.
VirtualDacDeviceInit(virtual);
// Pass private node data.
virtual->device.priv = (void *)node;
// Pass the device number.
virtual->device.devNum = virtual->deviceNum;
// Pass the method.
virtual->device.ops = &g_method;
// Add a DAC device.
ret = DacDeviceAdd(&virtual->device);
if (ret != HDF_SUCCESS) {
// Failed to add the DAC device.
HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret);
goto __ERR__;
}
// The DAC device is added.
return HDF_SUCCESS;
__ERR__:
// If the pointer is null
if (virtual != NULL) {
// Release the memory.
OsalMemFree(virtual);
// Set this pointer to null.
virtual = NULL;
}
return ret;
}
static int32_t VirtualDacInit(struct HdfDeviceObject *device)
{
// Define return value parameters.
int32_t ret;
// Child node of the device structure
const struct DeviceResourceNode *childNode = NULL;
// Check the input parameter pointer.
if (device == NULL || device->property == NULL) {
// The input parameter pointer is null.
HDF_LOGE("%s: device or property is NULL", __func__);
return HDF_ERR_INVALID_OBJECT;
}
// The input parameter pointer is not null.
ret = HDF_SUCCESS;
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
// Parse the child node.
ret = VirtualDacParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) {
// Failed to parse the child node.
break;
}
}
// The child node is parsed.
return ret;
}
```
- **Release** function
- Implement the **Init** function.
**Input parameter**:
Input parameter:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information.
**HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
**Return value**:
Return value:
No value is returned.
**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.
**Function description**:
| Status | Description |
| ---------------------- | -------------------------- |
| HDF_ERR_INVALID_OBJECT | Invalid controller object. |
| HDF_ERR_INVALID_PARAM | Invalid parameter. |
| HDF_ERR_MALLOC_FAIL | Failed to allocate memory. |
| HDF_ERR_IO | I/O error. |
| HDF_SUCCESS | Transmission successful. |
| HDF_FAILURE | Transmission failed. |
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.
>![](../public_sys-resources/icon-note.gif) **NOTE** Function description:
>
>All forced conversion operations for obtaining the corresponding object can be successful only when **Init()** has the corresponding value assignment operations. Initializes the custom structure object and **DacDevice**, and calls the **DacDeviceAdd** function at the core layer.
```c++ ```c++
static void VirtualDacRemoveByNode(const struct DeviceResourceNode *node) static int32_t VirtualDacParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
// Define return value parameters. /* Define the return values. */
int32_t ret; int32_t ret;
// Define the DAC device number. /* Pointer to the VirtualDacDevice structure. */
int16_t devNum; struct VirtualDacDevice *virtual = NULL;
// Pointer to the DacDevice structure. (void)device;
struct DacDevice *device = NULL; /* Allocate memory of the specified size. */
// Pointer to the VirtualDacDevice structure. virtual = (struct VirtualDacDevice *)OsalMemCalloc(sizeof(*virtual));
struct VirtualDacDevice *virtual = NULL; if (virtual == NULL) {
// Pointer to the DeviceResourceIface structure. /* Failed to allocate memory. */
struct DeviceResourceIface *drsOps = NULL; HDF_LOGE("%s: Malloc virtual fail!", __func__);
// Obtain device resources through the instance entry. return HDF_ERR_MALLOC_FAIL;
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); }
// Check the input parameter pointer. /* Read the node parameters in the HCS. The function definition is as follows. */
if (drsOps == NULL || drsOps->GetUint32 == NULL) { ret = VirtualDacReadDrs(virtual, node);
// The pointer is null. if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: invalid drs ops fail!", __func__); /* Failed to read the node data. */
return; HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
} goto __ERR__;
// Obtain data of the devNum node. }
ret = drsOps->GetUint16(node, "devNum", (uint16_t *)&devNum, 0); /* Initialize the DAC device pointer. */
if (ret != HDF_SUCCESS) { VirtualDacDeviceInit(virtual);
// Failed to obtain node data. /* Pass in the private data of the node. */
HDF_LOGE("%s: read devNum fail!", __func__); virtual->device.priv = (void *)node;
return; /* Pass in the device number. */
} virtual->device.devNum = virtual->deviceNum;
// Obtain the DAC device number. /* Pass in the method. */
device = DacDeviceGet(devNum); virtual->device.ops = &g_method;
// Check whether the DAC device number and data are null. /* Add a DAC device. */
if (device != NULL && device->priv == node) { ret = DacDeviceAdd(&virtual->device);
// Release the DAC device number if the device data is null. if (ret != HDF_SUCCESS) {
DacDevicePut(device); /* Failed to add the DAC device. */
// Remove the DAC device number. HDF_LOGE("%s: add Dac controller failed! ret = %d", __func__, ret);
DacDeviceRemove(device); goto __ERR__;
virtual = (struct VirtualDacDevice *)device; }
// Release the virtual pointer. /* The DAC device is added successfully. */
OsalMemFree(virtual); return HDF_SUCCESS;
__ERR__:
/* If the pointer is null */
if (virtual != NULL) {
/ Release the memory. */
OsalMemFree(virtual);
/* Set this pointer to null. */
virtual = NULL;
}
return ret;
} }
return;
} static int32_t VirtualDacInit(struct HdfDeviceObject *device)
{
static void VirtualDacRelease(struct HdfDeviceObject *device) /* Define return values. */
{ int32_t ret;
// Define the child node structure pointer to the DeviceResourceNode. /* Child node of the device structure. */
const struct DeviceResourceNode *childNode = NULL; const struct DeviceResourceNode *childNode = NULL;
// Check whether the input parameter pointer is null. /* Check the input parameter pointer. */
if (device == NULL || device->property == NULL) { if (device == NULL || device->property == NULL) {
// The input parameter pointer is null. /* The input parameter pointer is null. */
HDF_LOGE("%s: device or property is NULL", __func__); HDF_LOGE("%s: device or property is NULL", __func__);
return; return HDF_ERR_INVALID_OBJECT;
}
/* The input parameter pointer is not null. */
ret = HDF_SUCCESS;
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
/* Parse the child node. */
ret = VirtualDacParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) {
/* Failed to parse the child node. */
break;
}
}
/* The child node is parsed. */
return ret;
} }
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { static int32_t VirtualDacReadDrs(struct VirtualDacDevice *virtual, const struct DeviceResourceNode *node)
// Remove the DAC through the node. {
VirtualDacRemoveByNode(childNode); struct DeviceResourceIface *drsOps = NULL;
/* Obtain the drsOps method. */
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetUint16 == NULL) {
HDF_LOGE("%s: Invalid drs ops fail!", __func__);
return HDF_FAILURE;
}
/* Read the configuration parameters in sequence and fill them in the structure. */
if (drsOps->GetUint32(node, "deviceNum", &virtual->deviceNum, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read deviceNum fail!", __func__);
return HDF_ERR_IO;
}
if (drsOps->GetUint32(node, "validChannel", &virtual->validChannel, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read validChannel fail!", __func__);
return HDF_ERR_IO;
}
if (drsOps->GetUint32(node, "rate", &virtual->rate, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read rate fail!", __func__);
return HDF_ERR_IO;
}
return HDF_SUCCESS;
} }
}
``` ```
- Implement the **Release** function.
Input parameter:
**HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
Return value:
No value is returned.
Function description:
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.
>![](../public_sys-resources/icon-note.gif) **NOTE** <br>
>
>All forced conversion operations for obtaining the corresponding object can be successful only when **Init()** has the corresponding value assignment operations.
```c++
static void VirtualDacRemoveByNode(const struct DeviceResourceNode *node)
{
/* Define return values. */
int32_t ret;
/*Define the DAC device number. */
int16_t devNum;
/* Pointer to the DacDevice structure. */
struct DacDevice *device = NULL;
// Pointer to the VirtualDacDevice structure. */
struct VirtualDacDevice *virtual = NULL;
/* Pointer to the DeviceResourceIface structure. */
struct DeviceResourceIface *drsOps = NULL;
/* Obtain device resources through the instance entry. */
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
/* Check the input parameter pointer. */
if (drsOps == NULL || drsOps->GetUint32 == NULL) {
/* The pointer is null. */
HDF_LOGE("%s: invalid drs ops fail!", __func__);
return;
}
/* Obtain data of the devNum node. */
ret = drsOps->GetUint16(node, "devNum", (uint16_t *)&devNum, 0);
if (ret != HDF_SUCCESS) {
/* The information fails to be obtained. */
HDF_LOGE("%s: read devNum fail!", __func__);
return;
}
/* Obtain the DAC device number. */
device = DacDeviceGet(devNum);
/* Check whether the DAC device number and data are null. */
if (device != NULL && device->priv == node) {
/* Release the DAC device number if the device data is null. */
DacDevicePut(device);
/* Remove the DAC device number. */
DacDeviceRemove(device);
virtual = (struct VirtualDacDevice *)device;
/* Release the virtual pointer. */
OsalMemFree(virtual);
}
return;
}
static void VirtualDacRelease(struct HdfDeviceObject *device)
{
/* Define the child node structure pointer to the DeviceResourceNode. */
const struct DeviceResourceNode *childNode = NULL;
/* Check whether the input parameter pointer is null. */
if (device == NULL || device->property == NULL) {
/* The input parameter pointer is null. */
HDF_LOGE("%s: device or property is NULL", __func__);
return;
}
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
/* Remove the DAC by node. */
VirtualDacRemoveByNode(childNode);
}
}
```
4. Debug the driver. 4. Debug the driver.
(Optional) Verify the basic functions of the new driver, for example, check whether the test cases are successful after the driver is loaded. (Optional) Verify the basic functions of the new driver, for example, check whether the test cases are successful after the driver is loaded.
# I2C<a name="EN-US_TOPIC_0000001206171515"></a> # I2C
## Overview<a name="section5361140416"></a>
The Inter-Integrated Circuit \(I2C\) is a simple, bidirectional, and synchronous serial bus that uses merely two wires. ## Overview
In an I2C communication, one controller communicates with one or more devices through the serial data line \(SDA\) and serial clock line \(SCL\), as shown in the figure below. ### Function
I2C data transfer must begin with a **START** condition and end with a **STOP** condition. Data is transmitted byte-by-byte from the most significant bit to the least significant bit. The 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.
### Working Principles
In I2C communication, one controller communicates with one or more devices through the serial data line (SDA) and serial clock line (SCL), as shown in the figure below.
I2C data transfer must begin with a **START** condition and end with a **STOP** condition. Data is transmitted byte-by-byte from the most significant bit to the least significant bit.
Each I2C node is recognized by a unique address and can serve as either a controller or a device. When the controller needs to communicate with a device, it writes the device address to the bus through broadcast. A device matching this address sends a response to set up a data transfer channel. Each I2C node is recognized by a unique address and can serve as either a controller or a device. When the controller needs to communicate with a device, it writes the device address to the bus through broadcast. A device matching this address sends a response to set up a data transfer channel.
The I2C APIs define a set of common functions for I2C data transfer, including: The I2C module provides a set of APIs for I2C data transfer, including:
- Opening or closing an I2C controller
- Performing custom transfer via a message array
**Figure 1** I2C physical connection
- I2C controller management: opening or closing an I2C controller ![](figures/physical-connection-diagram-for-i2c.png "physical-connection-diagram-for-i2c")
- I2C message transfer: custom transfer by using a message array
**Figure 1** Physical connection diagram for I2C<br/>![](figures/physical-connection-diagram-for-i2c.png "physical-connection-diagram-for-i2c") ## Usage Guidelines
### When to Use
## Available APIs<a name="section545869122317"></a> The I2C is used in communication with the sensors, executors, and input/output devices that support the I2C protocol.
**Table 1** APIs available for the I2C driver ### Available APIs
<a name="table1731550155318"></a> The following table describes the APIs provided by the I2C module. For more information about the APIs, see **//drivers/hdf_core/framework/include/platform/i2c_if.h**.
<table><thead align="left"><tr id="row4419501537"><th class="cellrowborder" valign="top" width="18.63%" id="mcps1.2.4.1.1"><p id="p641050105320"><a name="p641050105320"></a><a name="p641050105320"></a>Capability</p>
</th>
<th class="cellrowborder" valign="top" width="28.03%" id="mcps1.2.4.1.2"><p id="p54150165315"><a name="p54150165315"></a><a name="p54150165315"></a>Function</p>
</th>
<th class="cellrowborder" valign="top" width="53.339999999999996%" id="mcps1.2.4.1.3"><p id="p941150145313"><a name="p941150145313"></a><a name="p941150145313"></a>Description</p>
</th>
</tr>
</thead>
<tbody><tr id="row34145016535"><td class="cellrowborder" rowspan="2" valign="top" width="18.63%" headers="mcps1.2.4.1.1 "><p id="p229610227124"><a name="p229610227124"></a><a name="p229610227124"></a>I2C controller management</p>
</td>
<td class="cellrowborder" valign="top" width="28.03%" headers="mcps1.2.4.1.2 "><p id="p19389143041518"><a name="p19389143041518"></a><a name="p19389143041518"></a>I2cOpen</p>
</td>
<td class="cellrowborder" valign="top" width="53.339999999999996%" headers="mcps1.2.4.1.3 "><p id="p8738101941716"><a name="p8738101941716"></a><a name="p8738101941716"></a>Opens an I2C controller.</p>
</td>
</tr>
<tr id="row5632152611414"><td class="cellrowborder" valign="top" headers="mcps1.2.4.1.1 "><p id="p143890309153"><a name="p143890309153"></a><a name="p143890309153"></a>I2cClose</p>
</td>
<td class="cellrowborder" valign="top" headers="mcps1.2.4.1.2 "><p id="p573815197171"><a name="p573815197171"></a><a name="p573815197171"></a>Closes an I2C controller.</p>
</td>
</tr>
<tr id="row15108165391412"><td class="cellrowborder" valign="top" width="18.63%" headers="mcps1.2.4.1.1 "><p id="p91084533141"><a name="p91084533141"></a><a name="p91084533141"></a>I2C message transfer</p>
</td>
<td class="cellrowborder" valign="top" width="28.03%" headers="mcps1.2.4.1.2 "><p id="p13901730101511"><a name="p13901730101511"></a><a name="p13901730101511"></a>I2cTransfer</p>
</td>
<td class="cellrowborder" valign="top" width="53.339999999999996%" headers="mcps1.2.4.1.3 "><p id="p12738111912171"><a name="p12738111912171"></a><a name="p12738111912171"></a>Performs a custom transfer.</p>
</td>
</tr>
</tbody>
</table>
>![](../public_sys-resources/icon-note.gif) **NOTE**<br/> **Table 1** I2C driver APIs
>All functions provided in this document can be called only in kernel mode.
## Usage Guidelines<a name="section1695201514281"></a> | API | Description|
| -------- | -------- |
| DevHandle I2cOpen(int16_t number) | Opens an I2C controller.|
| void I2cClose(DevHandle handle) | Closes an I2C controller.|
| int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count) | Transfers data.|
### How to Use<a name="section1338373417288"></a> ### How to Use
The figure below illustrates how to use the APIs. The following figure illustrates how to use I2C APIs.
**Figure 2** Using I2C driver APIs **Figure 2** Process of using I2C APIs
![](figures/using-I2C-process.png "process-of-using-an-i2c-device") ![](figures/using-I2C-process.png "process-of-using-an-i2c-device")
### Opening an I2C Controller<a name="section13751110132914"></a>
Call the **I2cOpen()** function to open an I2C controller.
DevHandle I2cOpen\(int16\_t number\);
**Table 2** Description of I2cOpen
<a name="table7603619123820"></a>
<table><thead align="left"><tr id="row1060351914386"><th class="cellrowborder" valign="top" width="20.66%" id="mcps1.2.3.1.1"><p id="p14603181917382"><a name="p14603181917382"></a><a name="p14603181917382"></a><strong id="b72871132125714"><a name="b72871132125714"></a><a name="b72871132125714"></a>Parameter</strong></p>
</th>
<th class="cellrowborder" valign="top" width="79.34%" id="mcps1.2.3.1.2"><p id="p36031519183819"><a name="p36031519183819"></a><a name="p36031519183819"></a><strong id="b1721203713577"><a name="b1721203713577"></a><a name="b1721203713577"></a>Description</strong></p>
</th>
</tr>
</thead>
<tbody><tr id="row1960431983813"><td class="cellrowborder" valign="top" width="20.66%" headers="mcps1.2.3.1.1 "><p id="p3604719123817"><a name="p3604719123817"></a><a name="p3604719123817"></a>number</p>
</td>
<td class="cellrowborder" valign="top" width="79.34%" headers="mcps1.2.3.1.2 "><p id="p221392414442"><a name="p221392414442"></a><a name="p221392414442"></a>I2C controller ID.</p>
</td>
</tr>
<tr id="row11410612183019"><td class="cellrowborder" valign="top" width="20.66%" headers="mcps1.2.3.1.1 "><p id="p460381915385"><a name="p460381915385"></a><a name="p460381915385"></a><strong id="b14182113945714"><a name="b14182113945714"></a><a name="b14182113945714"></a>Return Value</strong></p>
</td>
<td class="cellrowborder" valign="top" width="79.34%" headers="mcps1.2.3.1.2 "><p id="p96031619153812"><a name="p96031619153812"></a><a name="p96031619153812"></a><strong id="b92161640145714"><a name="b92161640145714"></a><a name="b92161640145714"></a>Description</strong></p>
</td>
</tr>
<tr id="row15410111273017"><td class="cellrowborder" valign="top" width="20.66%" headers="mcps1.2.3.1.1 "><p id="p1060418195389"><a name="p1060418195389"></a><a name="p1060418195389"></a>NULL</p>
</td>
<td class="cellrowborder" valign="top" width="79.34%" headers="mcps1.2.3.1.2 "><p id="p760471912388"><a name="p760471912388"></a><a name="p760471912388"></a>Failed to open the I2C controller.</p>
</td>
</tr>
<tr id="row1241081213303"><td class="cellrowborder" valign="top" width="20.66%" headers="mcps1.2.3.1.1 "><p id="p5604719133811"><a name="p5604719133811"></a><a name="p5604719133811"></a>Device handle</p>
</td>
<td class="cellrowborder" valign="top" width="79.34%" headers="mcps1.2.3.1.2 "><p id="p3604181933818"><a name="p3604181933818"></a><a name="p3604181933818"></a>Handle of the I2C controller.</p>
</td>
</tr>
</tbody>
</table>
This example assumes that the system has eight I2C controllers \(numbered from 0 to 7\) and I2C controller 3 is to open.
#### Opening an I2C Controller
Call **I2cOpen()** to open an I2C controller.
```c
DevHandle I2cOpen(int16_t number);
``` ```
DevHandle i2cHandle = NULL; /* I2C controller handle */
/* Open an I2C controller. */ **Table 2** Description of I2cOpen
| **Parameter**| **Description**|
| -------- | -------- |
| number | I2C controller number.|
| Return Value| **Description**|
| NULL | The operation fails.|
| Device handle| The operation is successful. The handle of the I2C controller opened is returned.|
Example: Open controller 3 of the eight I2C controllers (numbered 0 and 7) in the system.
```c
DevHandle i2cHandle = NULL; /* I2C controller handle. */
/* Open I2C controller 3. */
i2cHandle = I2cOpen(3); i2cHandle = I2cOpen(3);
if (i2cHandle == NULL) { if (i2cHandle == NULL) {
HDF_LOGE("I2cOpen: failed\n"); HDF_LOGE("I2cOpen: failed\n");
...@@ -117,70 +80,45 @@ if (i2cHandle == NULL) { ...@@ -117,70 +80,45 @@ if (i2cHandle == NULL) {
} }
``` ```
### Performing I2C Communication<a name="section9202183372916"></a>
Call the **I2cTransfer()** function to transfer messages.
int32\_t I2cTransfer\(DevHandle handle, struct I2cMsg \*msgs, int16\_t count\);
**Table 3** Description of I2cTransfer
<a name="table1934414174212"></a>
<table><thead align="left"><tr id="row1134415176216"><th class="cellrowborder" valign="top" width="50%" id="mcps1.2.3.1.1"><p id="p13295152320217"><a name="p13295152320217"></a><a name="p13295152320217"></a><strong id="b16680203655712"><a name="b16680203655712"></a><a name="b16680203655712"></a>Parameter</strong></p>
</th>
<th class="cellrowborder" valign="top" width="50%" id="mcps1.2.3.1.2"><p id="p1295112352115"><a name="p1295112352115"></a><a name="p1295112352115"></a><strong id="b183651837145710"><a name="b183651837145710"></a><a name="b183651837145710"></a>Description</strong></p>
</th>
</tr>
</thead>
<tbody><tr id="row5344101702113"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p19295132382111"><a name="p19295132382111"></a><a name="p19295132382111"></a>handle</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p1051172572919"><a name="p1051172572919"></a><a name="p1051172572919"></a>Handle of an I2C controller.</p>
</td>
</tr>
<tr id="row17344171722117"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p9295122332113"><a name="p9295122332113"></a><a name="p9295122332113"></a>msgs</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p202951238218"><a name="p202951238218"></a><a name="p202951238218"></a>Message array of the data to transfer.</p>
</td>
</tr>
<tr id="row45812466213"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p1659246112117"><a name="p1659246112117"></a><a name="p1659246112117"></a>count</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p259124622119"><a name="p259124622119"></a><a name="p259124622119"></a>Length of the message array.</p>
</td>
</tr>
<tr id="row04701426105110"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p17295142322113"><a name="p17295142322113"></a><a name="p17295142322113"></a><strong id="b9681123965720"><a name="b9681123965720"></a><a name="b9681123965720"></a>Return Value</strong></p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p142959232211"><a name="p142959232211"></a><a name="p142959232211"></a><strong id="b1159414403579"><a name="b1159414403579"></a><a name="b1159414403579"></a>Description</strong></p>
</td>
</tr>
<tr id="row74701226125110"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p929532313211"><a name="p929532313211"></a><a name="p929532313211"></a>Positive integer</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p829512237217"><a name="p829512237217"></a><a name="p829512237217"></a>Number of message structures that are successfully transmitted.</p>
</td>
</tr>
<tr id="row204701126195115"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p12958234217"><a name="p12958234217"></a><a name="p12958234217"></a>Negative value</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p1295192312112"><a name="p1295192312112"></a><a name="p1295192312112"></a>Failed to perform the message transfer.</p>
</td>
</tr>
</tbody>
</table>
The type of an I2C message transfer is defined by **I2cMsg**. Each message structure indicates a read or write operation. Multiple read or write operations can be performed by using a message array.
#### Performing I2C Communication
Call **I2cTransfer()** to transfer data.
```c
int32_t I2cTransfer(DevHandle handle, struct I2cMsg \*msgs, int16_t count);
``` ```
**Table 3** Description of I2cTransfer
| **Parameter**| **Description**|
| -------- | -------- |
| handle | Handle of the I2C controller.|
| msgs | Pointer to the message array to transfer.|
| count | Number of messages in the message array to transfer.|
| Return Value| **Description**|
| Positive integer| The operation is successful. The number of messages that are successfully transferred is returned.|
| Negative value| The operation fails.|
The I2C message type is defined by **I2cMsg**. Each message structure indicates a read or write operation. A message array specifies multiple read and write operations to perform.
Example of read and write operations:
```c
int32_t ret; int32_t ret;
uint8_t wbuff[2] = { 0x12, 0x13 }; uint8_t wbuff[2] = { 0x12, 0x13 };
uint8_t rbuff[2] = { 0 }; uint8_t rbuff[2] = { 0 };
struct I2cMsg msgs[2]; /* Custom message array for transfer */ struct I2cMsg msgs[2]; /* Custom message array to transfer. */
msgs[0].buf = wbuff; /* Data to write */ msgs[0].buf = wbuff; /* Data to write. */
msgs[0].len = 2; /* The length of the data to write is 2. */ msgs[0].len = 2; /* The length of the data to write is 2. */
msgs[0].addr = 0x5A; /* The address of the device to write the data is 0x5A. */ msgs[0].addr = 0x5A; /* The address of the device to write the data is 0x5A. */
msgs[0].flags = 0; /* The flag is 0, indicating the write operation. */ msgs[0].flags = 0; /* The flag 0 indicates a write operation. */
msgs[1].buf = rbuff; /* Data to read */ msgs[1].buf = rbuff; /* Data to read. */
msgs[1].len = 2; /* The length of the data to read is 2. */ msgs[1].len = 2; /* The length of the data to read is 2. */
msgs[1].addr = 0x5A; /* The address of the device to read is 0x5A. */ msgs[1].addr = 0x5A; /* The address of the device to read is 0x5A. */
msgs[1].flags = I2C_FLAG_READ /* I2C_FLAG_READ is configured, indicating the read operation. */ msgs[1].flags = I2C_FLAG_READ /* I2C_FLAG_READ is set. */
/* Perform a custom transfer to transfer two messages. */ /* Transfer two messages. */
ret = I2cTransfer(i2cHandle, msgs, 2); ret = I2cTransfer(i2cHandle, msgs, 2);
if (ret != 2) { if (ret != 2) {
HDF_LOGE("I2cTransfer: failed, ret %d\n", ret); HDF_LOGE("I2cTransfer: failed, ret %d\n", ret);
...@@ -188,78 +126,74 @@ if (ret != 2) { ...@@ -188,78 +126,74 @@ if (ret != 2) {
} }
``` ```
>![](../public_sys-resources/icon-caution.gif) **CAUTION**<br/> > ![icon-caution.gif](../public_sys-resources/icon-caution.gif) **CAUTION**<br/>
>- The device address in the **I2cMsg** structure does not contain the read/write flag bit. The read/write information is transferred by the read/write control bit in the member variable **flags**. > - The device address in the **I2cMsg** structure does not contain the read/write flag bit. The read/write information is passed by the read/write control bit in **flags**.
>- The **I2cTransfer** function does not limit the number of message structures and the data length of each message structure, which are determined by the I2C controller. >
>- The **I2cTransfer** function may cause the system to sleep and therefore cannot be called in the interrupt context. > - The I2C controller determines the maximum number of messages to be transferred at a time and the maximum length of each message to transfer.
>
> - The **I2cTransfer** function may cause the system to sleep and therefore cannot be called in the interrupt context.
### Closing an I2C Controller<a name="section19481164133018"></a>
Call the **I2cClose()** function to close the I2C controller after the communication is complete. #### Closing an I2C Controller
void I2cClose\(DevHandle \*handle\); Call **I2cClose()** to close the I2C controller after the communication is complete.
**Table 4** Description of I2cClose ```c
void I2cClose(DevHandle handle);
```
<a name="table72517953115"></a> **Table 4** Description of I2cClose
<table><thead align="left"><tr id="row1525793312"><th class="cellrowborder" valign="top" width="50%" id="mcps1.2.3.1.1"><p id="p115402031153111"><a name="p115402031153111"></a><a name="p115402031153111"></a>Parameter</p>
</th>
<th class="cellrowborder" valign="top" width="50%" id="mcps1.2.3.1.2"><p id="p65406313319"><a name="p65406313319"></a><a name="p65406313319"></a>Description</p>
</th>
</tr>
</thead>
<tbody><tr id="row1926109193116"><td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.1 "><p id="p105419317318"><a name="p105419317318"></a><a name="p105419317318"></a>handle</p>
</td>
<td class="cellrowborder" valign="top" width="50%" headers="mcps1.2.3.1.2 "><p id="p1213245577"><a name="p1213245577"></a><a name="p1213245577"></a>Handle of the I2C controller to close.</p>
</td>
</tr>
</tbody>
</table>
| Parameter| Description|
| -------- | -------- |
| handle | Handle of the I2C controller to close.|
``` Example:
I2cClose(i2cHandle); /* Close the I2C controller. */
```c
I2cClose(i2cHandle); /* Close an I2C controller. */
``` ```
## Usage Example<a name="section5302202015300"></a>
This example describes how to use I2C APIs with an I2C device on a development board. ### Example
This example shows a simple register read/write operation on TouchPad on a Hi3516D V300 development board. The basic hardware information is as follows: The following example describes how to use I2C APIs to implement simple read/write operations on TouchPad from a Hi3516D V300 development board.
- SoC: hi3516dv300 The basic hardware information is as follows:
- Touch IC: The I2C address is 0x38, and the bit width of Touch IC's internal register is 1 byte. - SoC: Hi3516D V300
- Schematic diagram: TouchPad is mounted to I2C controller 3. The reset pin of Touch IC is GPIO3. - Touch IC: The I2C address is 0x38, and the bit width of touch IC internal register is 1 byte.
In this example, first we reset Touch IC. \(The development board supplies power to Touch IC by default after being powered on, and this use case does not consider the power supply\). Then, we perform a read/write operation on an internal register to test whether the I2C channel is normal. - Hardware connection: The TouchPad is connected to I2C controller 3. The reset pin of the touch IC is GPIO 3.
>![](../public_sys-resources/icon-note.gif) **NOTE** <br/> In this example, reset the touch IC (the development board supplies power to the touch IC by default after being powered on) and perform read/write operations on the internal register to test whether the I2C channel is functioning.
>The example focuses on I2C device access and verifies the I2C channel. The read and write values of the device register are not concerned. The behavior caused by the read and write operations on the register is determined by the device itself.
Example: > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br>
> This example focuses on access to the I2C device and verifies the I2C channel, rather than the specific data read from or written to the device register and the result caused by the read and write operations on the register.
``` The sample code is as follows:
#include "i2c_if.h" /* Header file of I2C APIs */
#include "gpio_if.h" /* Header file of GPIO APIs */ ```c
#include "hdf_log.h" /* Header file for log APIs */ #include "i2c_if.h" /* Header file of I2C APIs. */
#include "osal_io.h" /* Header file of I/O read and write APIs */ #include "gpio_if.h" /* Header file of GPIO APIs. */
#include "osal_time.h" /* Header file of delay and sleep APIs */ #include "hdf_log.h" /* Header file of log APIs. */
#include "osal_io.h" /* Header file of I/O read and write APIs. */
#include "osal_time.h" /* Header file of delay and sleep APIs. */
/* Define a TP device structure to store I2C and GPIO hardware information. */ /* Define a TP device structure to store I2C and GPIO hardware information. */
struct TpI2cDevice { struct TpI2cDevice {
uint16_t rstGpio; /* Reset pin */ uint16_t rstGpio; /* Reset pin. */
uint16_t busId; /* I2C bus ID */ uint16_t busId; /* I2C bus number. */
uint16_t addr; /* I2C device address */ uint16_t addr; /* I2C device address. */
uint16_t regLen; /* Register bit width */ uint16_t regLen; /* Register bit width. */
DevHandle i2cHandle; /* I2C controller handle */ DevHandle i2cHandle; /* I2C controller handle. */
}; };
/* I2C pin I/O configuration. For details, see the SoC register manual. */ /* I2C pin I/O configuration. For details, see the SoC register manual. */
#define I2C3_DATA_REG_ADDR 0x112f008c /* Address of the SDA pin configuration register of I2C controller 3 #define I2C3_DATA_REG_ADDR 0x112f008c /* Address of the SDA pin configuration register of I2C controller 3. */
#define I2C3_CLK_REG_ADDR 0x112f0090 /* Address of the SCL pin configuration register of I2C controller 3 #define I2C3_CLK_REG_ADDR 0x112f0090 /* Address of the SCL pin configuration register of I2C controller 3. */
#define I2C_REG_CFG 0x5f1 /* Configuration values of SDA and SCL pins of I2C controller 3 #define I2C_REG_CFG 0x5f1 /* Configuration values of SDA and SCL pins of I2C controller 3. */
static void TpSocIoCfg(void) static void TpSocIoCfg(void)
{ {
...@@ -268,7 +202,7 @@ static void TpSocIoCfg(void) ...@@ -268,7 +202,7 @@ static void TpSocIoCfg(void)
OSAL_WRITEL(I2C_REG_CFG, IO_DEVICE_ADDR(I2C3_CLK_REG_ADDR)); OSAL_WRITEL(I2C_REG_CFG, IO_DEVICE_ADDR(I2C3_CLK_REG_ADDR));
} }
/* Initialize the reset pin of the TP. Pull up the pin for 20 ms, pull down the pin for 50 ms, and then pull up the pin for 20 ms to complete the resetting. */ /* Initialize the reset pin of the TP. Pull up the pin for 20 ms, pull down the pin for 50 ms, and then pull up the pin for 20 ms to complete the reset. */
static int32_t TestCaseGpioInit(struct TpI2cDevice *tpDevice) static int32_t TestCaseGpioInit(struct TpI2cDevice *tpDevice)
{ {
int32_t ret; int32_t ret;
...@@ -322,7 +256,7 @@ static int TpI2cReadWrite(struct TpI2cDevice *tpDevice, unsigned int regAddr, ...@@ -322,7 +256,7 @@ static int TpI2cReadWrite(struct TpI2cDevice *tpDevice, unsigned int regAddr,
/* Fill in the I2cMsg message structure. */ /* Fill in the I2cMsg message structure. */
msgs[0].addr = tpDevice->addr; msgs[0].addr = tpDevice->addr;
msgs[0].flags = 0; /* The flag is 0, indicating the write operation. */ msgs[0].flags = 0; /* The flag 0 indicates a write operation. */
msgs[0].len = tpDevice->regLen; msgs[0].len = tpDevice->regLen;
msgs[0].buf = regBuf; msgs[0].buf = regBuf;
...@@ -338,14 +272,14 @@ static int TpI2cReadWrite(struct TpI2cDevice *tpDevice, unsigned int regAddr, ...@@ -338,14 +272,14 @@ static int TpI2cReadWrite(struct TpI2cDevice *tpDevice, unsigned int regAddr,
return HDF_SUCCESS; return HDF_SUCCESS;
} }
/* TP register read function */ /* TP register read function. */
static inline int TpI2cReadReg(struct TpI2cDevice *tpDevice, unsigned int regAddr, static inline int TpI2cReadReg(struct TpI2cDevice *tpDevice, unsigned int regAddr,
unsigned char *regData, unsigned int dataLen) unsigned char *regData, unsigned int dataLen)
{ {
return TpI2cReadWrite(tpDevice, regAddr, regData, dataLen, 1); return TpI2cReadWrite(tpDevice, regAddr, regData, dataLen, 1);
} }
/* TP register write function */ /* TP register write function. */
static inline int TpI2cWriteReg(struct TpI2cDevice *tpDevice, unsigned int regAddr, static inline int TpI2cWriteReg(struct TpI2cDevice *tpDevice, unsigned int regAddr,
unsigned char *regData, unsigned int dataLen) unsigned char *regData, unsigned int dataLen)
{ {
...@@ -361,7 +295,7 @@ static int32_t TestCaseI2c(void) ...@@ -361,7 +295,7 @@ static int32_t TestCaseI2c(void)
unsigned char bufRead[7] = {0}; unsigned char bufRead[7] = {0};
static struct TpI2cDevice tpDevice; static struct TpI2cDevice tpDevice;
/* I/O pin function configuration */ /* Configuration of I/O pin functions. */
TpSocIoCfg(); TpSocIoCfg();
/* Initialize TP device information. */ /* Initialize TP device information. */
...@@ -371,7 +305,7 @@ static int32_t TestCaseI2c(void) ...@@ -371,7 +305,7 @@ static int32_t TestCaseI2c(void)
tpDevice.regLen = 1; tpDevice.regLen = 1;
tpDevice.i2cHandle = NULL; tpDevice.i2cHandle = NULL;
/* Initialize the GPIO pin. */ /* Initialize the GPIO pin. */
ret = TestCaseGpioInit(&tpDevice); ret = TestCaseGpioInit(&tpDevice);
if (ret != HDF_SUCCESS) { if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: gpio init fail!:%d", __func__, ret); HDF_LOGE("%s: gpio init fail!:%d", __func__, ret);
...@@ -412,4 +346,3 @@ static int32_t TestCaseI2c(void) ...@@ -412,4 +346,3 @@ static int32_t TestCaseI2c(void)
return ret; return ret;
} }
``` ```
# I2C # I2C
## Overview ## Overview
The Inter-Integrated Circuit (I2C) bus is a simple and bidirectional two-wire synchronous serial bus developed by Philips. In 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 external access requests in a unified manner, which is reflected in the configuration file. The unified service mode applies to the scenario where there are many device objects of the same type, for example, when the I2C module has more than 10 controllers. If the independent service mode is used, more device nodes need to be configured and memory resources will be consumed by services. ### Function
The 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.
### Working Principles
In 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.
In 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.
The I2C module is divided into the following layers:
- Interface layer: provides the capabilities of opening and closing a device and transferring data.
- Core layer: binds services, initializes and releases the PlatformManager, and provides the capabilities of adding, deleting, and obtaining controllers.
- Adaptation layer: implements hardware-related functions, such as controller initialization.
**Figure 1** Unified service mode **Figure 1** Unified service mode
![image](figures/unified-service-mode.png "I2C Unified Service Mode")
![image](figures/unified-service-mode.png "I2C Unified Service Mode")
## Available APIs ## Usage Guidelines
**I2cMethod** and **I2cLockMethod** ### When to Use
The 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.
``` ### Available APIs
To 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.
**I2cMethod** and **I2cLockMethod**:
```c
struct I2cMethod { struct I2cMethod {
int32_t (*transfer)(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count); int32_t (*transfer)(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count);
};
struct I2cLockMethod {// Structure for the lock operation.
int32_t (*lock)(struct I2cCntlr *cntlr);
void (*unlock)(struct I2cCntlr *cntlr);
}; };
struct I2cLockMethod {// Lock mechanism operation structure ```
int32_t (*lock)(struct I2cCntlr *cntlr); // Add a lock.
void (*unlock)(struct I2cCntlr *cntlr); // Release the lock. At 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.
```c
static int32_t I2cCntlrLockDefault(struct I2cCntlr *cntlr)
{
if (cntlr == NULL) {
return HDF_ERR_INVALID_OBJECT;
}
return OsalMutexLock(&cntlr->lock);
}
static void I2cCntlrUnlockDefault(struct I2cCntlr *cntlr)
{
if (cntlr == NULL) {
return;
}
(void)OsalMutexUnlock(&cntlr->lock);
}
static const struct I2cLockMethod g_i2cLockOpsDefault = {
.lock = I2cCntlrLockDefault,
.unlock = I2cCntlrUnlockDefault,
}; };
``` ```
**Table 1** Description of the callback functions in I2cMethod If 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**.
**Table 2** Function in **I2cMethod**
| Function| Input Parameter| Output Parameter| Return Value| Description| | Function| Input Parameter| Output Parameter| Return Value| Description|
| -------- | -------- | -------- | -------- | -------- | | -------- | -------- | -------- | -------- | -------- |
| transfer | **cntlr**: structure pointer to the I2C controller at the core layer.<br><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.| | 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.|
**Table 2** Functions in **I2cLockMethod**
| Function| Input Parameter| Output Parameter| Return Value| Description|
| -------- | -------- | -------- | -------- | -------- |
| lock | **cntlr**: structure pointer to the I2C controller at the core layer.| –| HDF_STATUS| Acquires the critical section lock.|
| unlock | **cntlr**: structure pointer to the I2C controller at the core layer.| –| HDF_STATUS| Releases the critical section lock.|
## How to Develop ### How to Develop
The I2C module adaptation involves the following steps: The I2C module adaptation involves the following steps:
1. Instantiate the driver entry. 1. Instantiate the driver entry.
- Instantiate the **HdfDriverEntry** structure. - Instantiate the **HdfDriverEntry** structure.
- Call **HDF_INIT** to register the **HdfDriverEntry** instance with the HDF. - Call **HDF_INIT** to register the **HdfDriverEntry** instance with the HDF.
2. Configure attribute files. 2. Configure attribute files.
- Add the **deviceNode** information to the **device_info.hcs** file. - Add the **deviceNode** information to the **device_info.hcs** file.
- (Optional) Add the **i2c_config.hcs** file. - (Optional) Add the **i2c_config.hcs** file.
3. Instantiate the I2C controller object. 3. Instantiate the I2C controller object.
- Initialize **I2cCntlr**. - Initialize **I2cCntlr**.
- Instantiate **I2cMethod** and **I2cLockMethod** in **I2cCntlr**. - Instantiate **I2cMethod** and **I2cLockMethod** in **I2cCntlr**.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE** > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br>
>
> For details, see [Available APIs](#available-apis). > For details, see [Available APIs](#available-apis).
4. Debug the driver.<br>
(Optional) For new drivers, verify basic functions, for example, check whether data is successfully transmitted and the information returned after the virtual file system (VFS) is mounted.
4. Debug the driver.
(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.
## Development Example ### Example
The following uses **i2c_hi35xx.c** as an example to present the information required for implementing device functions. The 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.
1. Instantiate the driver entry. 1. Instantiate the driver entry.
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. 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.
Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init()** fails to be called, the HDF calls **Release()** to release driver resources and exit. 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.
I2C driver entry example: I2C driver entry example:
An I2C controller may be connected with multiple devices. You need to create a manager object in the HDF and publish a manager service to handle external access requests in a unified manner. Before a device is used, the manager service must be obtained first. Then, the manager service locates the target device based on the specified parameters. 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.
The driver of the I2C manager is implemented by the core layer. Vendors do not need to care about the implementation of this part. However, the **Init** function must call the **I2cCntlrAdd** function of the core layer to implement corresponding features. 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.
``` ```c
struct HdfDriverEntry g_i2cDriverEntry = { struct HdfDriverEntry g_i2cDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = Hi35xxI2cInit, .Init = Hi35xxI2cInit,
.Release = Hi35xxI2cRelease, .Release = Hi35xxI2cRelease,
.moduleName = "hi35xx_i2c_driver", // (Mandatory) The value must be the same as that in the config.hcs file. .moduleName = "hi35xx_i2c_driver", // (Mandatory) The value must be the same as that in the config.hcs file.
}; };
HDF_INIT(g_i2cDriverEntry); // Call HDF_INIT to register the driver entry with the HDF. HDF_INIT(g_i2cDriverEntry); // Call HDF_INIT to register the driver entry with the HDF.
// Driver entry of the i2c_core.c manager service at the core layer /* Driver entry of the manager service i2c_core.c at the core layer. */
struct HdfDriverEntry g_i2cManagerEntry = { struct HdfDriverEntry g_i2cManagerEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Bind = I2cManagerBind, .Bind = I2cManagerBind,
.Init = I2cManagerInit, .Init = I2cManagerInit,
.Release = I2cManagerRelease, .Release = I2cManagerRelease,
.moduleName = "HDF_PLATFORM_I2C_MANAGER", // This parameter corresponds to device0 in the device_info file. .moduleName = "HDF_PLATFORM_I2C_MANAGER", // The value must be the same as that of device0 in the device_info.hcs file.
}; };
HDF_INIT(g_i2cManagerEntry); HDF_INIT(g_i2cManagerEntry);
``` ```
2. Add the **deviceNode** information to the **device_info.hcs** file and configure the device attributes in the **i2c_config.hcs** file. 2. 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**.
The **deviceNode** information is related to registration of the driver entry. 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. 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.
In the unified service mode, the first device node in the **device_info** file must be the I2C manager. The table below lists the settings of its parameters. 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.
**Table 2** Settings of the I2C manager
**Table 3** Settings of the I2C manager
| Parameter| Value| | Parameter| Value|
| -------- | -------- | | -------- | -------- |
| moduleName | **HDF_PLATFORM_I2C_MANAGER**| | moduleName | **HDF_PLATFORM_I2C_MANAGER**|
| serviceName | **HDF_PLATFORM_I2C_MANAGER**| | serviceName | **HDF_PLATFORM_I2C_MANAGER**|
| policy | **1** or **2**, depending on whether the service is published to the user mode.| | policy | **1** or **2**, depending on whether the service is published to the user mode.|
| deviceMatchAttr | Reserved| | deviceMatchAttr | This parameter is reserved.|
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 distinguishes by **busID** and **reg_pbase**, which can be seen in the **i2c_config** file. 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.
- **device_info.hcs** configuration example - **device_info.hcs** example
```c
``` root {
root { device_info {
device_info { match_attr = "hdf_manager";
match_attr = "hdf_manager"; device_i2c :: device {
device_i2c :: device { device0 :: deviceNode {
device0 :: deviceNode { policy = 2;
policy = 2; priority = 50;
priority = 50; permission = 0644;
permission = 0644; moduleName = "HDF_PLATFORM_I2C_MANAGER";
moduleName = "HDF_PLATFORM_I2C_MANAGER"; serviceName = "HDF_PLATFORM_I2C_MANAGER";
serviceName = "HDF_PLATFORM_I2C_MANAGER"; deviceMatchAttr = "hdf_platform_i2c_manager";
deviceMatchAttr = "hdf_platform_i2c_manager"; }
} device1 :: deviceNode {
device1 :: deviceNode { policy = 0; // The value 0 indicates that no service is published.
policy = 0; // The value 0 indicates that no service needs to be published. priority = 55; // Driver startup priority.
priority = 55; // Driver startup priority. permission = 0644; // Permission for the device node created.
permission = 0644; // Permission for the driver to create a device node. moduleName = "hi35xx_i2c_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
moduleName = "hi35xx_i2c_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry. serviceName = "HI35XX_I2C_DRIVER"; // (Mandatory) Unique name of the service published by the driver.
serviceName = "HI35XX_I2C_DRIVER"; // (Mandatory) Unique name of the service published by the driver. deviceMatchAttr = "hisilicon_hi35xx_i2c"; // (Mandatory) Private data of the controller. The value must be the same as the controller information in i2c_config.hcs.
deviceMatchAttr = "hisilicon_hi35xx_i2c"; // (Mandatory) Used to configure the private data of the controller. The value must be the same as the controller information in i2c_config.hcs. //The specific controller information is in i2c_config.hcs.
//The specific controller information is stored in i2c_config.hcs. }
} }
} }
} }
} ```
```
- **i2c_config.hcs** example
- **i2c_config.hcs** configuration example
```c
root {
``` platform {
root { i2c_config {
platform { match_attr = "hisilicon_hi35xx_i2c"; // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs.
i2c_config { template i2c_controller { // Template configuration. In the template, you can configure the common parameters shared by service nodes.
match_attr = "hisilicon_hi35xx_i2c"; // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs. bus = 0; // (Mandatory) I2C identifier.
template i2c_controller { // Template configuration. In the template, you can configure the common parameters shared by service nodes. reg_pbase = 0x120b0000; // (Mandatory) Physical base address.
bus = 0; // (Mandatory) I2C identifier. reg_size = 0xd1; // (Mandatory) Register bit width.
reg_pbase = 0x120b0000; // (Mandatory) Physical base address. irq = 0; // (Optional) Interrupt request (IRQ) number. The interrupt feature of the controller determines whether an IRQ number is required.
reg_size = 0xd1; // (Mandatory) Register bit width. freq = 400000; // (Optional) Frequency used in hardware controller initialization.
irq = 0; // (Optional) Configured based on your requirements. clk = 50000000; // (Optional) Controller clock. The controller clock initialization process determines whether a controller clock is required.
freq = 400000; // (Optional) Configured based on your requirements. }
clk = 50000000; // (Optional) Configured based on your requirements. controller_0x120b0000 :: i2c_controller {
} bus = 0;
controller_0x120b0000 :: i2c_controller { }
bus = 0; controller_0x120b1000 :: i2c_controller {
} bus = 1;
controller_0x120b1000 :: i2c_controller { reg_pbase = 0x120b1000;
bus = 1; }
reg_pbase = 0x120b1000; ...
} }
... }
} }
} ```
}
``` After the **i2c_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
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:
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i2c/i2c_config.hcs" // Relative path of the file.
```
3. 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). 3. 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).
- Defining a custom structure
- Define a custom structure.
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. 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.
```c
``` /* Custom structure. */
// Custom structure
struct Hi35xxI2cCntlr { struct Hi35xxI2cCntlr {
struct I2cCntlr cntlr; // (Mandatory) Control object at the core layer. For details, see the following description. struct I2cCntlr cntlr; // (Mandatory) Control object at the core layer. For details, see the following description.
OsalSpinlock spin; // (Mandatory) You need to implement lock() and unlock() based on this variable. OsalSpinlock spin; // (Mandatory) Lock or unlock an I2C operation function.
volatile unsigned char *regBase; // (Mandatory) Register base address. volatile unsigned char *regBase;// (Mandatory) Register base address.
uint16_t regSize; // (Mandatory) Register bit width. uint16_t regSize; // (Mandatory) Register bit width.
int16_t bus; // (Mandatory) The value can be read from the i2c_config.hcs file. int16_t bus; // (Mandatory) The value can be read from the i2c_config.hcs file.
uint32_t clk; // (Optional) Customized. uint32_t clk; // (Optional) Set it as required.
uint32_t freq; // (Optional) Customized. uint32_t freq; // (Optional) Set it as required.
uint32_t irq; // (Optional) Customized. uint32_t irq; // (Optional) Set it as required.
uint32_t regBasePhy; // (Mandatory) Physical base address of the register. uint32_t regBasePhy // (Mandatory) Physical base address of the register.
}; };
// I2cCntlr is a controller structure at the core layer. The Init function assigns values to the members of I2cCntlr. /* I2cCntlr is the core layer controller structure. The **Init()** function assigns values to the members of I2cCntlr. */
struct I2cCntlr { struct I2cCntlr {
struct OsalMutex lock; struct OsalMutex lock;
void *owner; void *owner;
...@@ -199,31 +259,32 @@ The following uses **i2c_hi35xx.c** as an example to present the information req ...@@ -199,31 +259,32 @@ The following uses **i2c_hi35xx.c** as an example to present the information req
const struct I2cLockMethod *lockOps; const struct I2cLockMethod *lockOps;
}; };
``` ```
- Instantiating **I2cMethod** and **I2cLockMethod** (other members are initialized by **Init**)
- Instantiate **I2cMethod** and **I2cLockMethod**. Other members are initialized by **Init**.
```
// Example in i2c_hi35xx.c ```c
/* Example in i2c_hi35xx.c */
static const struct I2cMethod g_method = { static const struct I2cMethod g_method = {
.transfer = Hi35xxI2cTransfer, .transfer = Hi35xxI2cTransfer,
}; };
static const struct I2cLockMethod g_lockOps = { static const struct I2cLockMethod g_lockOps = {
.lock = Hi35xxI2cLock, // Lock function .lock = Hi35xxI2cLock, // Acquires the lock.
.unlock = Hi35xxI2cUnlock, // Unlock function .unlock = Hi35xxI2cUnlock, // Release the lock.
}; };
``` ```
- **Init** function
**Input parameter**: - Implement the **Init** function.
Input parameter:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information. **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
**Return value**: Return value:
**HDF_STATUS**<br/>The table below describes some status. For more information, see **HDF_STATUS** in the **/drivers/framework/include/utils/hdf_base.h** file. **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.
**Table 3** HDF_STATUS **Table 4** HDF_STATUS
| Status| Description| | Status| Description|
| -------- | -------- | | -------- | -------- |
...@@ -234,16 +295,15 @@ The following uses **i2c_hi35xx.c** as an example to present the information req ...@@ -234,16 +295,15 @@ The following uses **i2c_hi35xx.c** as an example to present the information req
| HDF_SUCCESS | Transmission successful.| | HDF_SUCCESS | Transmission successful.|
| HDF_FAILURE | Transmission failed.| | HDF_FAILURE | Transmission failed.|
**Function description**: Function description:
Initializes the custom structure object and **I2cCntlr**, calls the **I2cCntlrAdd** function at the core layer, and mounts the VFS (optional). Initialize the custom structure object and **I2cCntlr**, call **I2cCntlrAdd()** at the core layer, and connect to the VFS (optional).
```c
```
static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device) static int32_t Hi35xxI2cInit(struct HdfDeviceObject *device)
{ {
... ...
// Traverse and parse all nodes in i2c_config.hcs and call Hi35xxI2cParseAndInit to initialize the devices separately. /* Traverse and parse all nodes in i2c_config.hcs and call Hi35xxI2cParseAndInit to initialize the devices separately. */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = Hi35xxI2cParseAndInit(device, childNode);// The function is defined as follows. ret = Hi35xxI2cParseAndInit(device, childNode);// The function is defined as follows.
... ...
...@@ -254,25 +314,25 @@ The following uses **i2c_hi35xx.c** as an example to present the information req ...@@ -254,25 +314,25 @@ The following uses **i2c_hi35xx.c** as an example to present the information req
static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node) static int32_t Hi35xxI2cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{ {
struct Hi35xxI2cCntlr *hi35xx = NULL; struct Hi35xxI2cCntlr *hi35xx = NULL;
... ... // Check whether the input parameter is null.
hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx)); // Allocate memory. hi35xx = (struct Hi35xxI2cCntlr *)OsalMemCalloc(sizeof(*hi35xx)); // Allocate memory.
... ... // Verify the return value.
hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // Address mapping. hi35xx->regBase = OsalIoRemap(hi35xx->regBasePhy, hi35xx->regSize); // Address mapping.
... ... // Verify the return value.
Hi35xxI2cCntlrInit(hi35xx); // (Mandatory) Initialize the I2C device. Hi35xxI2cCntlrInit(hi35xx); // (Mandatory) Initialize the I2C device.
hi35xx->cntlr.priv = (void *)node; // (Mandatory) Device attributes. hi35xx->cntlr.priv = (void *)node; // (Mandatory) Device attributes.
hi35xx->cntlr.busId = hi35xx->bus; // (Mandatory) Initialize busId in I2cCntlr. hi35xx->cntlr.busId = hi35xx->bus; // (Mandatory) Initialize busId in I2cCntlr.
hi35xx->cntlr.ops = &g_method; // (Mandatory) Hook the I2cMethod instance. hi35xx->cntlr.ops = &g_method; // (Mandatory) Hook the I2cMethod instance.
hi35xx->cntlr.lockOps = &g_lockOps; // (Mandatory) Hook the I2cLockMethod instance. hi35xx->cntlr.lockOps = &g_lockOps; // (Mandatory) Hook the I2cLockMethod instance.
(void)OsalSpinInit(&hi35xx->spin); // (Mandatory) Initialize the lock. (void)OsalSpinInit(&hi35xx->spin); // (Mandatory) Initialize the lock.
ret = I2cCntlrAdd(&hi35xx->cntlr); // (Mandatory) Call this function to set the structure of the core layer. The driver accesses the platform core layer only after a success signal is returned. 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.
... ...
#ifdef USER_VFS_SUPPORT #ifdef USER_VFS_SUPPORT
(void)I2cAddVfsById(hi35xx->cntlr.busId);// (Optional) Mount the user-level VFS if required. (void)I2cAddVfsById(hi35xx->cntlr.busId);// (Optional) Mount the user-level VFS if required.
#endif #endif
return HDF_SUCCESS; return HDF_SUCCESS;
__ERR__: // If the operation fails, execute the initialization functions reversely. __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.
if (hi35xx != NULL) { if (hi35xx != NULL) {
if (hi35xx->regBase != NULL) { if (hi35xx->regBase != NULL) {
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
...@@ -284,26 +344,26 @@ The following uses **i2c_hi35xx.c** as an example to present the information req ...@@ -284,26 +344,26 @@ The following uses **i2c_hi35xx.c** as an example to present the information req
return ret; return ret;
} }
``` ```
- **Release** function
**Input parameter**: - Implement the **Release** function.
Input parameter:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information. **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
**Return value**: Return value:
No value is returned. No value is returned.
**Function description**: Function description:
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. 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.
```c
```
static void Hi35xxI2cRelease(struct HdfDeviceObject *device) static void Hi35xxI2cRelease(struct HdfDeviceObject *device)
{ {
... ...
// Release each node separately, like Hi35xxI2cInit. /* Release each node separately, like Hi35xxI2cInit. */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
Hi35xxI2cRemoveByNode(childNode);// The function is defined as follows: Hi35xxI2cRemoveByNode(childNode);// The function is defined as follows:
} }
...@@ -312,12 +372,12 @@ The following uses **i2c_hi35xx.c** as an example to present the information req ...@@ -312,12 +372,12 @@ The following uses **i2c_hi35xx.c** as an example to present the information req
static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node) static void Hi35xxI2cRemoveByNode(const struct DeviceResourceNode *node)
{ {
... ...
// (Mandatory) Call the I2cCntlrGet function to obtain the I2cCntlr object based on bus ID of the device, and call the I2cCntlrRemove function to release the I2cCntlr object. /* (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. */
cntlr = I2cCntlrGet(bus); cntlr = I2cCntlrGet(bus);
if (cntlr != NULL && cntlr->priv == node) { if (cntlr != NULL && cntlr->priv == node) {
... ...
I2cCntlrRemove(cntlr); I2cCntlrRemove(cntlr);
// (Mandatory) Unmap the addresses and release the lock and memory. /* (Mandatory) Unmap the register address and release the lock and memory. */
hi35xx = (struct Hi35xxI2cCntlr *)cntlr; hi35xx = (struct Hi35xxI2cCntlr *)cntlr;
OsalIoUnmap((void *)hi35xx->regBase); OsalIoUnmap((void *)hi35xx->regBase);
(void)OsalSpinDestroy(&hi35xx->spin); (void)OsalSpinDestroy(&hi35xx->spin);
......
...@@ -8,7 +8,7 @@ Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient two-wire ...@@ -8,7 +8,7 @@ Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient two-wire
I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target devices and controlled by only one I3C controller at a time. It is backward compatible with Inter-Integrated circuit (I2C) target devices, but features higher speed and lower power consumption. Moreover, I3C supports in-band interrupts (IBIs), hot-joins of target devices, and controller switchover. The IBIs over the serial bus eliminates the need for an extra interrupt line to complete interrupts in I2C. I2C devices, I3C target devices, and the I3C secondary controller can co-exist on the same I3C bus. I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target devices and controlled by only one I3C controller at a time. It is backward compatible with Inter-Integrated circuit (I2C) target devices, but features higher speed and lower power consumption. Moreover, I3C supports in-band interrupts (IBIs), hot-joins of target devices, and controller switchover. The IBIs over the serial bus eliminates the need for an extra interrupt line to complete interrupts in I2C. I2C devices, I3C target devices, and the I3C secondary controller can co-exist on the same I3C bus.
The I3C driver APIs provide a set of common functions for I3C transfer, including: The I3C module provides a set of common APIs for I3C transfer, including:
- Opening and closing an I3C controller - Opening and closing an I3C controller
- Obtaining and setting I3C controller parameters - Obtaining and setting I3C controller parameters
- Performing custom I3C message transfer by using a message array - Performing custom I3C message transfer by using a message array
...@@ -17,17 +17,14 @@ The I3C driver APIs provide a set of common functions for I3C transfer, includin ...@@ -17,17 +17,14 @@ The I3C driver APIs provide a set of common functions for I3C transfer, includin
### Basic Concepts ### Basic Concepts
- IBI - IBI
When there is no start signal on the serial clock (SCL) line, the I3C target device can pull down the serial data (SDA) line to make the controller send an SCL start signal, which initiates an IBI request. If multiple target devices send interrupt requests at the same time, the I3C controller arbitrates the requests based on the target device addresses. The request with a lower address is responded first. When there is no start signal on the serial clock (SCL) line, the I3C target device can pull down the serial data (SDA) line to make the controller send an SCL start signal, which initiates an IBI request. If multiple target devices send interrupt requests at the same time, the I3C controller arbitrates the requests based on the target device addresses. The request with a lower address is responded first.
- Dynamic Address Assignment (DAA) - Dynamic Address Assignment (DAA)
The I3C controller can dynamically allocate addresses to target devices to avoid address conflicts. Before addresses are allocated, each I3C device connected to a I3C bus must be uniquely identified in either of the following ways: The I3C controller can dynamically allocate addresses to target devices to avoid address conflicts. Before addresses are allocated, each I3C device connected to a I3C bus must be uniquely identified in either of the following ways:
1) The device has an I2C compliant static address that can be used by the host.
- The device has an I2C compliant static address that can be used by the host. 2) The device has a 48-bit temporary ID. The host must use a 48-bit temporary ID unless the device has a static IP address.
- The device has a 48-bit temporary ID.
The host must use a 48-bit temporary ID unless the device has a static IP address.
- Common Command Code (CCC) - Common Command Code (CCC)
...@@ -43,49 +40,54 @@ The I3C driver APIs provide a set of common functions for I3C transfer, includin ...@@ -43,49 +40,54 @@ The I3C driver APIs provide a set of common functions for I3C transfer, includin
### Working Principles ### Working Principles
In the Hardware Driver Foundation (HDF), the I3C module uses the unified service mode for API adaptation. In this mode, a service is used as the I3C manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple device objects of the same type, for example, when there are more than 10 I3C controllers. If the independent service mode is used in this case, more device nodes need to be configured and more memory resources will be consumed. In the Hardware Driver Foundation (HDF), the I3C module uses the unified service mode for API adaptation. In this mode, a service is used as the I3C manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple device objects of the same type, for example, when there are more than 10 I3C controllers. If the independent service mode is used in this case, more device nodes need to be configured and more memory resources will be consumed.
Multiple devices, such as I2C target device, I3C target device, and I3C secondary controller, can be connected to an I3C bus. However, the I3C bus must have only one controller. Compared with I2C, I3C features higher speed and lower power consumption, supports IBIs, hot-joins of target devices, and controller switchover. I3C is also backward compatible with I2C target devices. Multiple devices, such as I2C target device, I3C target device, and I3C secondary controller, can be connected to an I3C bus. However, the I3C bus must have only one controller.
**Figure 1** I3C physical connection
**Figure 1** I3C physical connection
![](figures/I3C_physical_connection.png "I3C_physical_connection") ![](figures/I3C_physical_connection.png "I3C_physical_connection")
### Constraints ### Constraints
Currently, the I3C module supports only the kernels (LiteOS) of mini and small systems. The I3C module supports only the kernel (LiteOS-A) for mini and small systems and cannot be used in user mode.
## Usage Guidelines ## Usage Guidelines
### When to Use ### When to Use
I3C can connect to one or more I3C or I2C target devices. It is used to: I3C can connect to one or more I3C or I2C target devices. It is used to:
- Communicate with sensors, such as gyroscopes, barometers, and image sensors that support the I3C protocol. - Communicate with sensors, such as gyroscopes, barometers, and image sensors that support the I3C protocol.
- Communicate with devices with other ports (such as UART serial ports) through software or hardware protocols. - Communicate with devices with other ports (such as UART serial ports) through software or hardware protocols.
### Available APIs ### Available APIs
**Table 1** I3C driver APIs The following table describes the APIs provided by the I3C module. For more information about the APIs, see **//drivers/hdf_core/framework/include/platform/i3c_if.h**.
**Table 1** I3C driver APIs
| API | Description | | API | Description |
| ------------- | ----------------- | | ------------- | ----------------- |
| I3cOpen | Opens an I3C controller. | | DevHandle I3cOpen(int16_t number) | Opens an I3C controller. |
| I3cClose | Closes an I3C controller. | | void I3cClose(DevHandle handle) | Closes an I3C controller. |
| I3cTransfer | Performs custom transfer. | | int32_t I3cTransfer(DevHandle handle, struct I3cMsg \*msg, int16_t count, enum TransMode mode) | Performs custom transfer. |
| I3cSetConfig | Sets the I3C controller. | | int32_t I3cSetConfig(DevHandle handle, struct I3cConfig \*config) | Sets the I3C controller. |
| I3cGetConfig | Obtains the I3C controller configuration. | | int32_t I3cGetConfig(DevHandle handle, struct I3cConfig \*config) | Obtains I3C controller configuration.|
| I3cRequestIbi | Requests an IBI. | | int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload) | Requests an IBI. |
| I3cFreeIbi | Releases an IBI. | | int32_t I3cFreeIbi(DevHandle handle, uint16_t addr) | Releases an IBI. |
>![](../public_sys-resources/icon-note.gif) **NOTE**<br> >![](../public_sys-resources/icon-note.gif) **NOTE**
>
>All APIs described in this document can be called only in kernel mode. >All APIs described in this document can be called only in kernel mode.
### How to Develop ### How to Develop
The figure below illustrates the use of I3C driver APIs. The following figure illustrates how to use the I3C APIs.
**Figure 2** Process of using I3C driver APIs
**Figure 2** Process of using I3C driver APIs ![](figures/using-I3C-process.png)
![](figures/I3C_usage_flowchart.png "I3C_usage_flowchart")
#### Opening an I3C Controller #### Opening an I3C Controller
...@@ -98,10 +100,10 @@ DevHandle I3cOpen(int16_t number); ...@@ -98,10 +100,10 @@ DevHandle I3cOpen(int16_t number);
| Name | Description | | Name | Description |
| ---------- | ------------------- | | ---------- | ------------------- |
| number | I3C controller number. | | number | I3C controller number. |
| **Return Value**| **Description** | | **Return Value**| **Description** |
| NULL | The operation failed. | | NULL | The operation fails. |
| Controller handle| The operation is successful. The handle of the I3C controller opened is returned. | | Controller handle| The operation is successful. The handle of the I3C controller opened is returned.|
Example: Open I3C controller 1 of the eight I3C controllers numbered from 0 to 7 in the system. Example: Open I3C controller 1 of the eight I3C controllers numbered from 0 to 7 in the system.
...@@ -116,64 +118,13 @@ if (i3cHandle == NULL) { ...@@ -116,64 +118,13 @@ if (i3cHandle == NULL) {
} }
``` ```
#### Performing I3C Communication
Call **I3cTransfer()** to transfer messages.
```c
int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode);
```
**Table 3** Description of I3cTransfer
| Name | Description |
| ---------- | -------------------------------------------- |
| handle | I3C controller handle. |
| msgs | Pointer to the message array of the data to transfer. |
| count | Length of the message array. |
| mode | Transmission mode. The value **0** indicates I2C mode, **1** indicates I3C mode, and **2** indicates CCC transmission. |
| **Return Value**| **Description** |
| Positive integer | The operation is successful. The number of message structures that are successfully transmitted is returned. |
| Negative value | The operation failed. |
The I3C messages are of the I3cMsg type. Each message structure indicates a read or write operation. A message array can be used to perform multiple read or write operations.
```c
int32_t ret;
uint8_t wbuff[2] = { 0x12, 0x13 };
uint8_t rbuff[2] = { 0 };
struct I3cMsg msgs[2]; /* Custom message array for transfer. */
msgs[0].buf = wbuff; /* Data to write. */
msgs[0].len = 2; /* Length of the data to write. */
msgs[0].addr = 0x3F; /* Address of the device to which the data is written. */
msgs[0].flags = 0; /* Transfer flag. A write operation is performed by default. */
msgs[1].buf = rbuff; /* Data to read. */
msgs[1].len = 2; /* Length of the data to read. */
msgs[1].addr = 0x3F; /* Address of the device from which the data is read. */
msgs[1].flags = I3C_FLAG_READ /* I3C_FLAG_READ is set. */
/* Transfer two messages in I2C mode. */
ret = I3cTransfer(i3cHandle, msgs, 2, I2C_MODE);
if (ret != 2) {
HDF_LOGE("I3cTransfer: failed, ret %d\n", ret);
return;
}
```
>![](./public_sys-resources/icon-caution.gif) **Caution**<br>
>- The device address in the **I3cMsg** structure does not contain the read/write flag bit. The read/write information is passed by the read/write control bit in the member variable **flags**.
>- The **I3cTransfer()** function does not limit the number of message structures or the length of data in each message structure. The I3C controller determines these two limits.
>- Using **I3cTransfer()** may cause the system to sleep. Do not call it in the interrupt context.
#### Obtaining the I3C Controller Configuration #### Obtaining the I3C Controller Configuration
Call **I3cGetConfig()** to obtain the configuration of an I3C controller.
```c ```c
int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config); int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config);
``` ```
**Table 4** Description of I3cGetConfig **Table 3** Description of I3cGetConfig
| Name | Description | | Name | Description |
| ---------- | -------------- | | ---------- | -------------- |
...@@ -181,7 +132,7 @@ int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config); ...@@ -181,7 +132,7 @@ int32_t I3cGetConfig(DevHandle handle, struct I3cConfig *config);
| config | Pointer to the I3C controller configuration. | | config | Pointer to the I3C controller configuration. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
The following is an example of obtaining the I3C controller configuration: The following is an example of obtaining the I3C controller configuration:
...@@ -197,14 +148,11 @@ if (ret != HDF_SUCCESS) { ...@@ -197,14 +148,11 @@ if (ret != HDF_SUCCESS) {
#### Setting an I3C Controller #### Setting an I3C Controller
Call **I3cSetConfig()** to set an I3C controller.
```c ```c
int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config); int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config);
``` ```
**Table 5** Description of I3cSetConfig **Table 4** Description of I3cSetConfig
| Name | Description | | Name | Description |
| ---------- | -------------- | | ---------- | -------------- |
...@@ -212,7 +160,7 @@ int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config); ...@@ -212,7 +160,7 @@ int32_t I3cSetConfig(DevHandle handle, struct I3cConfig *config);
| config | Pointer to the I3C controller configuration. | | config | Pointer to the I3C controller configuration. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
The following is an example of setting an I3C controller: The following is an example of setting an I3C controller:
...@@ -228,9 +176,54 @@ if (ret != HDF_SUCCESS) { ...@@ -228,9 +176,54 @@ if (ret != HDF_SUCCESS) {
} }
``` ```
#### Requesting an IBI #### Performing I3C Communication
Call **I3cTransfer()** to transfer messages.
```c
int32_t I3cTransfer(DevHandle handle, struct I3cMsg *msgs, int16_t count, enum TransMode mode);
```
**Table 5** Description of I3cTransfer
| Name | Description |
| ---------- | -------------------------------------------- |
| handle | I3C controller handle. |
| msgs | Pointer to the message array of the data to transfer. |
| count | Length of the message array. |
| mode | Transmission mode. The value **0** indicates I2C mode, **1** indicates I3C mode, and **2** indicates CCC transmission.|
| **Return Value**| **Description** |
| Positive integer | The operation is successful. The number of message structures that are successfully transmitted is returned. |
| Negative value | The operation fails. |
The I3C messages are of the I3cMsg type. Each message structure indicates a read or write operation. A message array can be used to perform multiple read or write operations.
```c
int32_t ret;
uint8_t wbuff[2] = { 0x12, 0x13 };
uint8_t rbuff[2] = { 0 };
struct I3cMsg msgs[2]; /* Custom message array for transfer. */
msgs[0].buf = wbuff; /* Data to write. */
msgs[0].len = 2; /* Length of the data to write. */
msgs[0].addr = 0x3F; /* Address of the device to which the data is written. */
msgs[0].flags = 0; /* Transfer flag. A write operation is performed by default. */
msgs[1].buf = rbuff; /* Data to read. */
msgs[1].len = 2; /* Length of the data to read. */
msgs[1].addr = 0x3F; /* Address of the device from which the data is read. */
msgs[1].flags = I3C_FLAG_READ /* I3C_FLAG_READ is set. */
/* Transfer two messages in I2C mode. */
ret = I3cTransfer(i3cHandle, msgs, 2, I2C_MODE);
if (ret != 2) {
HDF_LOGE("I3cTransfer: failed, ret %d\n", ret);
return;
}
```
>![](./public_sys-resources/icon-caution.gif) **Caution**<br>
>- The device address in the **I3cMsg** structure does not contain the read/write flag bit. The read/write information is passed by the read/write control bit in **flags**.
>- The I3C controller determines the maximum number of messages to transfer at a time and the maximum length of each message.
>- Using **I3cTransfer()** may cause the system to sleep. Do not call it in the interrupt context.
Call **I3cRequestIbi()** to request an IBI. #### Requesting an IBI
```c ```c
int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload); int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t payload);
...@@ -238,7 +231,6 @@ int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t ...@@ -238,7 +231,6 @@ int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t
**Table 6** Description of I3cRequestIbi **Table 6** Description of I3cRequestIbi
| Name | Description | | Name | Description |
| ---------- | -------------- | | ---------- | -------------- |
| handle | I3C controller handle. | | handle | I3C controller handle. |
...@@ -247,7 +239,7 @@ int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t ...@@ -247,7 +239,7 @@ int32_t I3cRequestIbi(DevHandle handle, uint16_t addr, I3cIbiFunc func, uint32_t
| payload | IBI payload. | | payload | IBI payload. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
The following is an example: The following is an example:
...@@ -287,22 +279,19 @@ int32_t I3cTestRequestIbi(void) ...@@ -287,22 +279,19 @@ int32_t I3cTestRequestIbi(void)
#### Releasing an IBI #### Releasing an IBI
Call **I3cFreeIbi()** to release an IBI.
```c ```c
int32_t I3cFreeIbi(DevHandle handle, uint16_t addr); int32_t I3cFreeIbi(DevHandle handle, uint16_t addr);
``` ```
**Table 7** Description of I3cFreeIbi **Table 7** Description of I3cFreeIbi
| Name | Description | | Name | Description |
| ---------- | -------------- | | ---------- | -------------- |
| handle | I3C controller handle. | | handle | I3C controller handle. |
| addr | I3C device address. | | addr | I3C device address. |
| **Return Value**| **Description**| | **Return Value**| **Description**|
| 0 | The operation is successful. | | 0 | The operation is successful. |
| Negative value | The operation failed. | | Negative value | The operation fails. |
The following is an example: The following is an example:
...@@ -319,7 +308,6 @@ void I3cClose(DevHandle handle); ...@@ -319,7 +308,6 @@ void I3cClose(DevHandle handle);
**Table 8** Description of I3cClose **Table 8** Description of I3cClose
| Name | Description | | Name | Description |
| ---------- | -------------- | | ---------- | -------------- |
| handle | I3C controller handle. | | handle | I3C controller handle. |
...@@ -330,17 +318,15 @@ The following is an example: ...@@ -330,17 +318,15 @@ The following is an example:
I3cClose(i3cHandle); /* Close the I3C controller. */ I3cClose(i3cHandle); /* Close the I3C controller. */
``` ```
## Development Example ## Example
This following example shows how to use I3C APIs to manage an I3C device on a Hi3516D V300 development board.
Because the Hi3516D V300 SoC has no I3C controller, this example describes how to perform simple transfer operations on a virtual driver on a Hi3516D V300. The basic information is as follows: The following example presents how to use I3C APIs to manage an I3C device on a Hi3516D V300 development board. <br>The basic hardware information is as follows:
- SoC: Hi3516D V300 - SoC: Hi3516D V300
- Virtual: The I3C address is 0x3f, and the register bit width is 1 byte. - Virtual I3C device: The I3C address is 0x3f, and the register bit width is 1 byte.
- The virtual I3C devices are connected to virtual I3C controllers 18 and 19. - The virtual I3C device is connected to I3C controllers 18 and 19.
Perform simple I3C transfer to test whether the I3C channels are normal. Perform simple I3C transfer to test whether the I3C channels are normal.
...@@ -349,7 +335,7 @@ The sample code is as follows: ...@@ -349,7 +335,7 @@ The sample code is as follows:
```c ```c
#include "i3c_if.h" /* Header file for I3C standard APIs */ #include "i3c_if.h" /* Header file for I3C standard APIs */
#include "hdf_log.h" /* Header file for log APIs */ #include "hdf_log.h" /* Header file for log APIs */
##include "osal_io.h" /* Header file for I/O read and write APIs */ ##include "osal_io.h" /* Header file for I/O read and write APIs */
#include "osal_time.h" /* Header file for delay and sleep APIs */ #include "osal_time.h" /* Header file for delay and sleep APIs */
/* Define a device structure to hold information. */ /* Define a device structure to hold information. */
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient two-wire bidirectional synchronous serial bus protocol developed by the Mobile Industry Processor Interface (MIPI) Alliance. Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient two-wire bidirectional synchronous serial bus protocol developed by the Mobile Industry Processor Interface (MIPI) Alliance.
I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target devices and controlled by only one I3C controller at a time. It is backward compatible with Inter-Integrated circuit (I2C) target devices, but features higher speed and lower power consumption and supports in-band interrupts (IBIs), hot-joins of target devices, and controller switchover. I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target devices and controlled by only one I3C controller at a time. It is backward compatible with Inter-Integrated circuit (I2C) target devices, but features higher speed and lower power consumption and supports in-band interrupts (IBIs), hot-joins of target devices, and controller switchover.
The IBIs over the serial bus eliminates the need for an extra interrupt line to complete interrupts in I2C. I2C devices, I3C target devices, and the I3C secondary controller can co-exist on the same I3C bus.
### Basic Concepts ### Basic Concepts
...@@ -16,16 +18,16 @@ I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target ...@@ -16,16 +18,16 @@ I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target
- Dynamic Address Assignment (DAA) - Dynamic Address Assignment (DAA)
The I3C controller can dynamically allocate addresses to target devices to avoid address conflicts. Before addresses are allocated, each I3C device connected to a I3C bus must be uniquely identified in either of the following ways: The I3C controller can dynamically allocate addresses to target devices to avoid address conflicts. Before addresses are allocated, each I3C device connected to an I3C bus must be uniquely identified in either of the following ways:
- The device has an I2C compliant static address that can be used by the host. - The device has an I2C compliant static address that can be used by the host.
- The device has a 48-bit temporary ID. - The device has a 48-bit temporary ID.
The host must use a 48-bit temporary ID unless the device has a static IP address. The host must use a 48-bit temporary ID unless the device has a static IP address.
- Common Command Code (CCC) - Common Command Code (CCC)
All I3C devices support CCC. The CCC can be sent to a specific I3C target device or all I3C target devices. All I3C devices support CCC. The CCC can be sent to an I3C target device or all I3C target devices.
- Bus Characteristic Register (BCR) - Bus Characteristic Register (BCR)
...@@ -38,21 +40,23 @@ I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target ...@@ -38,21 +40,23 @@ I3C is a two-wire bidirectional serial bus, optimized for multiple sensor target
### Working Principles ### Working Principles
In the Hardware Driver Foundation (HDF), the I3C module uses the unified service mode for API adaptation. In this mode, a service is used as the I3C manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple device objects of the same type, for example, when there are more than ten I3C controllers. 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 figure below illustrates the unified service mode. In the Hardware Driver Foundation (HDF), the I3C module uses the unified service mode for API adaptation. In this mode, a service is used as the I3C manager to handle external access requests in a unified manner. The unified service mode applies when the system has multiple device objects of the same type, for example, when there are more than 10 I3C controllers. 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 illustrtes the unified service mode.
The I3C module is divided into the following layers: The I3C module is divided into the following layers:
- Interface layer: provides APIs for opening or closing a controller, transmitting messages, and obtaining and setting controller parameters. - Interface layer: provides the capabilities of opening a device, writing data, and closing a device.
- Core layer: provides the capabilities of binding, initializing, and releasing devices. - Core layer: binds services, initializes and releases the PlatformManager, and provides the capabilities of adding, deleting, and obtaining controllers. The core layer also provides capabilities of adding, deleting, and obtaining the devices connected to the I3C bus and interrupt callbacks.
- Adaptation layer: implements other functions. - Adaptation layer: implements hardware-related functions, such as controller initialization.
In 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.
**Figure 1** Unified service mode **Figure 1** Unified service mode
![image1](figures/unified-service-mode.png) ![image1](figures/unified-service-mode.png)
### Constraints ### Constraints
Currently, the I3C module supports only the kernels (LiteOS) of mini and small systems. The I3C module supports only the kernel (LiteOS-A) for mini and small systems.
## Development Guidelines ## Development Guidelines
...@@ -63,12 +67,13 @@ I3C can connect to one or more I3C or I2C target devices. It is used to: ...@@ -63,12 +67,13 @@ I3C can connect to one or more I3C or I2C target devices. It is used to:
- Communicate with sensors, such as gyroscopes, barometers, and image sensors that support the I3C protocol. - Communicate with sensors, such as gyroscopes, barometers, and image sensors that support the I3C protocol.
- Communicate with devices with other ports (such as UART serial ports) through software or hardware protocols. - Communicate with devices with other ports (such as UART serial ports) through software or hardware protocols.
### Available APIs Before using I3C devices with OpenHarmony, you need to adapt the I3C driver to OpenHarmony. The following describes how to do it.
**I3cMethod**:
### Available APIs
To enable the upper layer to successfully operate the hardware by calling the I3C APIs, hook functions are defined in **//drivers/hdf_core/framework/support/platform/include/i3c/i3c_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.
**I3cMethod**:
```c ```c
struct I3cMethod { struct I3cMethod {
int32_t (*sendCccCmd)(struct I3cCntlr *cntlr, struct I3cCccCmd *ccc); int32_t (*sendCccCmd)(struct I3cCntlr *cntlr, struct I3cCccCmd *ccc);
...@@ -81,16 +86,17 @@ struct I3cMethod { ...@@ -81,16 +86,17 @@ struct I3cMethod {
}; };
``` ```
**Table 1** Description of the callback functions in I3cMethod **Table 1** Hook functions in **I3cMethod**
|Function|Input Parameter|Output Parameter|Return Value|Description| |Function|Input Parameter|Output Parameter|Return Value|Description|
|-|-|-|-|-| |-|-|-|-|-|
|sendCccCmd| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**ccc**: pointer to the CCC to send.| **ccc**: pointer to the CCC sent.| HDF_STATUS|Sends a CCC.| |sendCccCmd| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**ccc**: pointer to the CCC to send.| **ccc**: pointer to the CCC sent.| HDF_STATUS|Sends a CCC.|
|Transfer| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**msgs**: structure pointer to the messages to transfer.<br>**count**: length of the message array, which is of the int16_t type.| **msgs**: structure pointer to the messages transferred.| HDF_STATUS|Transfers user messages in I3C mode.| |Transfer | **cntlr**: structure pointer to an I3C controller at the core layer.<br>**msgs**: structure pointer to the messages to transfer.<br>**count**: number of messages to transfer, which is of the int16_t type.| **msgs**: structure pointer to the messages transferred.| HDF_STATUS| Transfers user messages in I3C mode.|
|i2cTransfer| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**msgs**: structure pointer to the messages to transfer.<br>**count**: length of the message array, which is of the int16_t type.| **msgs**: structure pointer to the messages transferred.| HDF_STATUS|Transfers user messages in I2C mode.| |i2cTransfer | **cntlr**: structure pointer to an I3C controller at the core layer.<br>**msgs**: structure pointer to the messages to transfer.<br>**count**: number of messages to transfer, which is of the int16_t type.| **msgs**: structure pointer to the messages transferred.| HDF_STATUS| Transfers user messages in I2C mode.|
|setConfig| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**config**: pointer to the controller configuration.| –| HDF_STATUS|Sets an I3C controller.| |setConfig| **cntlr**: structure pointer to an I3C controller at the core layer.<br>**config**: pointer to the controller configuration.| –| HDF_STATUS| Sets an I3C controller.|
|getConfig| **cntlr**: structure pointer to an I3C controller at the core layer.| **config**: pointer to the controller configuration.| HDF_STATUS|Obtains the I3C controller configuration.| |getConfig| **cntlr**: structure pointer to an I3C controller at the core layer.| **config**: pointer to the controller configuration.| HDF_STATUS| Obtains the I3C controller configuration.|
|requestIbi| **device**: structure pointer to an I3C device at the core layer.| –| HDF_STATUS|Requests an IBI for an I3C device.| |requestIbi| **device**: structure pointer to an I3C device at the core layer.| –| HDF_STATUS| Requests an IBI for an I3C device.|
|freeIbi| **device**: structure pointer to an I3C device at the core layer.| –| HDF_STATUS|Releases the IBI for an I3C device.| |freeIbi| **device**: structure pointer to an I3C device at the core layer.| –| HDF_STATUS| Releases the IBI for an I3C device.|
### How to Develop ### How to Develop
The I3C module adaptation involves the following steps: The I3C module adaptation involves the following steps:
...@@ -108,65 +114,65 @@ The I3C module adaptation involves the following steps: ...@@ -108,65 +114,65 @@ The I3C module adaptation involves the following steps:
3. Instantiate the I3C controller object. 3. Instantiate the I3C controller object.
- Initialize **I3cCntlr**. - Initialize **I3cCntlr**.
- Instantiate **I3cMethod** in **I3cCntlr**. For details, see [Available APIs](#available-apis). - Instantiate **I3cMethod** in **I3cCntlr**. For details, see the description of **I3cMethod** below.
4. Register an interrupt handler. 4. Register an interrupt handler.
Registers an interrupt handler for the controller to implement the device hot-join and IBI features. Registers an interrupt handler for the controller to implement the device hot-join and IBI features.
### Example
## Development Example
1. Instantiate the driver entry. 1. Instantiate the driver entry.
The driver entry must be a global variable of the **HdfDriverEntry** type (which is 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. The driver entry must be a global variable of the **HdfDriverEntry** type (defined in **//drivers/hdf_core/framework/include/core/hdf_device_desc.h**), and the module name 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.
Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit. Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit.
I3C driver entry example: I3C driver entry example:
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**
> The system may have multiple I3C controllers. Therefore, you need to create a manager object in the HDF and publish a manager service to uniformly handle external access requests. Then, the manager service locates the controller to open based on the specified parameters. >
> > Multiple devices may connect to the I3C 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.
> The core layer implements the driver of the I3C manager service. You do not need to care about the implementation. However, the **I3cCntlrAdd()** function at the core layer must be called in the implementation of **Init()** to implement related features. >
> You do not need to implement the driver of the I3C manager, which is implemented by the core layer. However, the **I3cCntlrAdd** function of the core layer must be invoked in the **Init** function to implement the related features.
```c ```c
static struct HdfDriverEntry g_virtualI3cDriverEntry = { static struct HdfDriverEntry g_virtualI3cDriverEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = VirtualI3cInit, .Init = VirtualI3cInit,
.Release = VirtualI3cRelease, .Release = VirtualI3cRelease,
.moduleName = "virtual_i3c_driver",// (Mandatory) The value must be the same as that in the .hcs file. .moduleName = "virtual_i3c_driver", // (Mandatory) The value must be the same as that in the .hcs file.
}; };
HDF_INIT(g_virtualI3cDriverEntry); // Call HDF_INIT to register the driver entry with the HDF. HDF_INIT(g_virtualI3cDriverEntry); // Call HDF_INIT to register the driver entry with the HDF.
/* Driver entry of the i3c_core.c manager service at the core layer */ /* Driver entry of the i3c_core.c manager service at the core layer. */
struct HdfDriverEntry g_i3cManagerEntry = { struct HdfDriverEntry g_i3cManagerEntry = {
.moduleVersion = 1, .moduleVersion = 1,
.Init = I3cManagerInit, .Init = I3cManagerInit,
.Release = I3cManagerRelease, .Release = I3cManagerRelease,
.moduleName = "HDF_PLATFORM_I3C_MANAGER",// Correspond to device0 in the device_info file. .moduleName = "HDF_PLATFORM_I3C_MANAGER", // The value must be the same as that of device0 in the device_info.hcs file.
}; };
HDF_INIT(g_i3cManagerEntry); HDF_INIT(g_i3cManagerEntry);
``` ```
2. Configure attribute files. 2. Configure attribute files.
Add **deviceNode** to the **device_info.hcs** file, and configure the device attributes in the **i3c_config.hcs** file. The **deviceNode** information is related to registration of the driver entry. The device attribute values are closely related to the driver implementation and the default values or restriction ranges of the **I3cCntlr** members at the core layer. Add the **deviceNode** information to the **//vendor/hisilicon/hispark_taurus/hdf_config/device_info/device_info.hcs** file and configure the device attributes in **i3c_config.hcs**.
In the unified service mode, the first device node in the **device_info** file must be the I3C manager. The I3C manager parameters must be set as follows. 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 **I3cCntlr** members at the core layer.
In the unified service mode, the first device node in the **device_info.hcs** file must be the I3C manager. The I3C manager parameters must be set as follows:
|Parameter|Value| |Parameter|Value|
|-|-| |-|-|
|moduleName |HDF_PLATFORM_I3C_MANAGER| |moduleName |HDF_PLATFORM_I3C_MANAGER|
|serviceName|Reserved| |serviceName|Reserved.|
|policy|0| |policy|0|
|cntlrMatchAttr| Reserved | |cntlrMatchAttr| Reserved.|
Configure I3C controller information from the second node. This node specifies a type of I3C controllers rather than a specific I3C controller. In this example, there is only one I3C controller. If there are multiple I3C controllers, add the **deviceNode** information to the **device_info** file and add the corresponding device attributes to the **i3c_config** file for each controller. Configure I3C controller information from the second node. This node specifies a type of I3C controllers rather than a specific I3C controller. In this example, there is only one I3C controller. If there are multiple I3C controllers, add the **deviceNode** information to the **device_info.hcs** file and add the corresponding device attributes to the **i3c_config** file for each controller.
- **device_info.hcs** configuration example - **device_info.hcs** example
```c ```c
root { root {
...@@ -180,17 +186,17 @@ The I3C module adaptation involves the following steps: ...@@ -180,17 +186,17 @@ The I3C module adaptation involves the following steps:
} }
} }
i3c_virtual :: deviceNode { i3c_virtual :: deviceNode {
policy = 0; // The value 0 indicates that no service is published. policy = 0; // The value 0 indicates that no service is published.
priority = 56; // Driver startup priority. priority = 56; // Driver startup priority.
permission = 0644; // Permission to create device nodes for the driver. permission = 0644; // Permission for the device node created.
moduleName = "virtual_i3c_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry. moduleName = "virtual_i3c_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
serviceName = "VIRTUAL_I3C_DRIVER"; // (Mandatory) Unique name of the service published by the driver. serviceName = "VIRTUAL_I3C_DRIVER"; // (Mandatory) Unique name of the service published by the driver.
deviceMatchAttr = "virtual_i3c"; // (Mandatory) Controller private data, which must be same as that of the corresponding controller in i3c_config.hcs. deviceMatchAttr = "virtual_i3c"; // (Mandatory) Controller private data, which must be same as that of the controller in i3c_config.hcs.
} // The specific controller information is in i3c_config.hcs. } // The specific controller information is in i3c_config.hcs.
} }
``` ```
- i3c_config.hcs configuration example - i3c_config.hcs example
```c ```c
root { root {
...@@ -216,208 +222,245 @@ The I3C module adaptation involves the following steps: ...@@ -216,208 +222,245 @@ The I3C module adaptation involves the following steps:
} }
} }
``` ```
After the **i3c_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
For example, if the path of **i3c_config.hcs** is **device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i3c/i3c_config.hcs**, add the following statement to **hdf.hcs** of the product:
```c
#include "../../../../device/soc/hisilicon/hi3516dv300/sdk_liteos/hdf_config/i3c/i3c_config.hcs" // Relative path of the file.
```
3. Instantiate the I3C controller object. 3. Instantiate the I3C controller object.
Initialize the **I3cCntlr** 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 **I3cMethod** in **I3cCntlr** (so that the underlying driver functions can be called). Initialize the **I3cCntlr** 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 I3cMethod in I3cCntlr (so that the underlying driver functions can be called).
Instantiate **I3cMethod** in **I3cCntlr**. The **I3cLockMethod** callback structure is not implemented in this example. To instantiate the structure, refer to the I2C driver development. Other members are initialized in **Init()**.
- Defining a custom structure
To the driver, the custom structure holds parameters and data. The **DeviceResourceIface** method provided by the HDF reads the values in the **i3c_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 **I3cCntlr** object at the core layer.
```c
struct VirtualI3cCntlr {
struct I3cCntlr cntlr; // (Mandatory) Control object at the core layer. For details, see the following description.
volatile unsigned char *regBase; //(Mandatory) Register base address.
uint32_t regBasePhy; // (Mandatory) Physical base address of the register.
uint32_t regSize; // (Mandatory) Bit width of the register.
uint16_t busId; // (Mandatory) Bus number.
uint16_t busMode;
uint16_t IrqNum;
uint32_t i3cMaxRate;
uint32_t i3cRate;
uint32_t i2cFmRate;
uint32_t i2cFmPlusRate;
};
/* I3cCntlr is the controller structure at the core layer. The Init function assigns values to the members of I3cCntlr.
struct I3cCntlr {
OsalSpinlock lock;
void *owner;
int16_t busId;
struct I3cConfig config;
uint16_t addrSlot[(I3C_ADDR_MAX + 1) / ADDRS_PER_UINT16];
struct I3cIbiInfo *ibiSlot[I3C_IBI_MAX];
const struct I3cMethod *ops;
const struct I3cLockMethod *lockOps;
void *priv;
};
```
- **Init** function
**Input parameter**:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs configuration file information.
**Return value**:
**HDF_STATUS**<br>The table below lists some status. For more information, see **HDF_STATUS** in the **/drivers/framework/include/utils/hdf_base.h** file.
| Status| Description|
| -------- | -------- |
| HDF_ERR_INVALID_OBJECT | Invalid controller object.|
| HDF_ERR_MALLOC_FAIL | Failed to allocate memory.|
| HDF_ERR_INVALID_PARAM | Invalid parameter.|
| HDF_ERR_IO | I/O error.|
| HDF_SUCCESS | Transmission successful.|
| HDF_FAILURE | Transmission failed.|
**Function description**:
Initializes the custom structure object and **I3cCntlr**, and calls the **I3cCntlrAdd** function to add the I3C controller to the core layer.
```c
static int32_t VirtualI3cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{
int32_t ret;
struct VirtualI3cCntlr *virtual = NULL; // (Mandatory) Custom structure object.
(void)device;
virtual = (struct VirtualI3cCntlr *)OsalMemCalloc(sizeof(*virtual)); // (Mandatory) Allocate memory.
if (virtual == NULL) {
HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL;
}
ret = VirtualI3cReadDrs(virtual, node); // (Mandatory) Fill the default values defined in the i3c_config file to the structure.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
goto __ERR__;
}
...
virtual->regBase = OsalIoRemap(virtual->regBasePhy, virtual->regSize);// (Mandatory) Address mapping.
ret = OsalRegisterIrq(hi35xx->softIrqNum, OSAL_IRQF_TRIGGER_NONE, I3cIbiHandle, "I3C", virtual); // (Mandatory) Register an interrupt handler.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: register irq failed!", __func__);
return ret;
}
...
VirtualI3cCntlrInit(virtual); // (Mandatory) Initialize the I3C device.
virtual->cntlr.priv = (void *)node; // (Mandatory) Set the storage device attributes.
virtual->cntlr.busId = virtual->busId; // (Mandatory) Initialize I3cCntlr.
virtual->cntlr.ops = &g_method; // (Mandatory) Attach the I3cMethod instance.
(void)OsalSpinInit(&virtual->spin);
ret = I3cCntlrAdd(&virtual->cntlr); // (Mandatory) Call this function to add the controller to the core layer. If a success signal is returned, the driver is completely connected to the core layer.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: add i3c controller failed! ret = %d", __func__, ret);
(void)OsalSpinDestroy(&virtual->spin);
goto __ERR__;
}
return HDF_SUCCESS;
__ERR__: // If the controller fails to be added, deinitialize related functions.
if (virtual != NULL) {
OsalMemFree(virtual);
virtual = NULL;
}
return ret;
}
static int32_t VirtualI3cInit(struct HdfDeviceObject *device)
{
int32_t ret;
const struct DeviceResourceNode *childNode = NULL;
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is NULL", __func__);
return HDF_ERR_INVALID_OBJECT;
}
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = VirtualI3cParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) {
break;
}
}
return ret;
}
```
- **Release** function
**Input parameter**:
**HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs information.
**Return value**:
No value is returned. Instantiate **I3cMethod** in **I3cCntlr**.<br>The **I3cLockMethod** hook function structure is not implemented in this example. To instantiate the structure, refer to the I2C driver development. Other members are initialized in **Init()**.
**Function description**:
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. - Define a custom structure.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br> > ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**
> All forced conversion operations for obtaining the corresponding object can be successful only when the **Init** function has the value assignment operations. >
> To the driver, the custom structure holds parameters and data. The **DeviceResourceIface** method provided by the HDF reads the values in the **i3c_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 **I3cCntlr** object at the core layer.
```c ```c
static void VirtualI3cRemoveByNode(const struct DeviceResourceNode *node) struct VirtualI3cCntlr {
{ struct I3cCntlr cntlr; // (Mandatory) Control object at the core layer. For details, see the following description.
int32_t ret; volatile unsigned char *regBase; // (Mandatory) Register base address.
int16_t busId; uint32_t regBasePhy // (Mandatory) Physical base address of the register.
struct I3cCntlr *cntlr = NULL; uint32_t regSize; // (Mandatory) Register bit width.
struct VirtualI3cCntlr *virtual = NULL; uint16_t busId; // (Mandatory) Bus number.
struct DeviceResourceIface *drsOps = NULL; uint16_t busMode;
uint16_t IrqNum;
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); uint32_t i3cMaxRate;
if (drsOps == NULL || drsOps->GetUint32 == NULL) { uint32_t i3cRate;
HDF_LOGE("%s: invalid drs ops fail!", __func__); uint32_t i2cFmRate;
return; uint32_t i2cFmPlusRate;
} };
ret = drsOps->GetUint16(node, "busId", (uint16_t *)&busId, 0); /* I3cCntlr is the controller structure at the core layer. The Init function assigns values to the members of I3cCntlr. */
if (ret != HDF_SUCCESS) { struct I3cCntlr {
HDF_LOGE("%s: read busId fail!", __func__); OsalSpinlock lock;
return; void *owner;
} int16_t busId;
... struct I3cConfig config;
/* Call I3cCntlrGet() to obtain the I3cCntlr object based on the cntlrNum of the device, and then call I3cCntlrRemove() to release the I3cCntlr object. */ uint16_t addrSlot[(I3C_ADDR_MAX + 1) / ADDRS_PER_UINT16];
cntlr = I3cCntlrGet(busId); struct I3cIbiInfo *ibiSlot[I3C_IBI_MAX];
if (cntlr != NULL && cntlr->priv == node) { const struct I3cMethod *ops;
I3cCntlrPut(cntlr); const struct I3cLockMethod *lockOps;
I3cCntlrRemove(cntlr); // (Mandatory) Remove the I3cCntlr object from the manager driver. void *priv;
virtual = (struct VirtualI3cCntlr *)cntlr; // (Mandatory) Obtain the custom object through a forced conversion and perform the release operation. };
(void)OsalSpinDestroy(&virtual->spin); ```
OsalMemFree(virtual);
} - Implement the **Init** function.
return;
} **Input parameter**:
static void VirtualI3cRelease(struct HdfDeviceObject *device) **HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
{
const struct DeviceResourceNode *childNode = NULL; **Return value**:
HDF_LOGI("%s: enter", __func__); **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.
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is NULL", __func__); |Status|Description|
|:-|:-:|
|HDF_ERR_INVALID_OBJECT|Invalid controller object.|
|HDF_ERR_INVALID_PARAM |Invalid parameter.|
|HDF_ERR_MALLOC_FAIL |Failed to allocate the memory.|
|HDF_ERR_IO |I/O error.|
|HDF_SUCCESS |Transmission successful.|
|HDF_FAILURE |Transmission failed.|
**Function description**:
Initializes the custom structure object and **I3cCntlr**, and calls the **I3cCntlrAdd** function to add the I3C controller to the core layer.
```c
static int32_t VirtualI3cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
{
int32_t ret;
struct VirtualI3cCntlr *virtual = NULL; // (Mandatory) Custom structure object.
(void)device;
virtual = (struct VirtualI3cCntlr *)OsalMemCalloc(sizeof(*virtual)); // (Mandatory) Allocate memory.
if (virtual == NULL) {
HDF_LOGE("%s: Malloc virtual fail!", __func__);
return HDF_ERR_MALLOC_FAIL;
}
ret = VirtualI3cReadDrs(virtual, node); // (Mandatory) Use the default values in the i3c_config file to fill in the structure. For details about the function definition, see the following.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
goto __ERR__;
}
...
virtual->regBase = OsalIoRemap(virtual->regBasePhy, virtual->regSize);// (Mandatory) Address mapping.
ret = OsalRegisterIrq(hi35xx->softIrqNum, OSAL_IRQF_TRIGGER_NONE, I3cIbiHandle, "I3C", virtual); // (Mandatory) Register an interrupt handler.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: register irq failed!", __func__);
return ret;
}
...
VirtualI3cCntlrInit(virtual); // (Mandatory) Initialize the I3C device.
virtual->cntlr.priv = (void *)node; // (Mandatory) Set the storage device attributes.
virtual->cntlr.busId = virtual->busId; // (Mandatory) Initialize I3cCntlr.
virtual->cntlr.ops = &g_method; // (Mandatory) Attach the I3cMethod instance.
(void)OsalSpinInit(&virtual->spin);
ret = I3cCntlrAdd(&virtual->cntlr); // (Mandatory) Call this function to add the controller to the core layer. The driver can access the platform core layer only when a success signal is returned.
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: add i3c controller failed! ret = %d", __func__, ret);
(void)OsalSpinDestroy(&virtual->spin);
goto __ERR__;
}
return HDF_SUCCESS;
__ERR__: // If the controller fails to be added, deinitialize related functions.
if (virtual != NULL) {
OsalMemFree(virtual);
virtual = NULL;
}
return ret;
}
static int32_t VirtualI3cInit(struct HdfDeviceObject *device)
{
int32_t ret;
const struct DeviceResourceNode *childNode = NULL;
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is NULL", __func__);
return HDF_ERR_INVALID_OBJECT;
}
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
ret = VirtualI3cParseAndInit(device, childNode);
if (ret != HDF_SUCCESS) {
break;
}
}
return ret;
}
static int32_t VirtualI3cReadDrs(struct VirtualI3cCntlr *virtual, const struct DeviceResourceNode *node)
{
struct DeviceResourceIface *drsOps = NULL;
/* Obtain the drsOps method. */
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetUint16 == NULL) {
HDF_LOGE("%s: Invalid drs ops fail!", __func__);
return HDF_FAILURE;
}
/* Read the configuration parameters in sequence and fill them in the structure. */
if (drsOps->GetUint16(node, "busId", &virtual->busId, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read busId fail!", __func__);
return HDF_ERR_IO;
}
if (drsOps->GetUint16(node, "busMode", &virtual->busMode, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read busMode fail!", __func__);
return HDF_ERR_IO;
}
if (drsOps->GetUint16(node, "IrqNum", &virtual->IrqNum, 0) != HDF_SUCCESS) {
HDF_LOGE("%s: Read IrqNum fail!", __func__);
return HDF_ERR_IO;
}
···
return HDF_SUCCESS;
}
```
- Implement the **Release** function.
**Input parameter**:
**HdfDeviceObject**, an interface parameter provided by the driver, contains the .hcs information.
**Return value**:
No value is returned.
**Function description**:
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.
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**
>
> All forced conversion operations for obtaining the corresponding object can be successful only when the **Init** function has the value assignment operations.
```c
static void VirtualI3cRemoveByNode(const struct DeviceResourceNode *node)
{
int32_t ret;
int16_t busId;
struct I3cCntlr *cntlr = NULL;
struct VirtualI3cCntlr *virtual = NULL;
struct DeviceResourceIface *drsOps = NULL;
drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
if (drsOps == NULL || drsOps->GetUint32 == NULL) {
HDF_LOGE("%s: invalid drs ops fail!", __func__);
return;
}
ret = drsOps->GetUint16(node, "busId", (uint16_t *)&busId, 0);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: read busId fail!", __func__);
return;
}
...
/* Call I3cCntlrGet() to obtain the I3cCntlr object based on the cntlrNum of the device, and then call I3cCntlrRemove() to release the I3cCntlr object. */
cntlr = I3cCntlrGet(busId);
if (cntlr != NULL && cntlr->priv == node) {
I3cCntlrPut(cntlr);
I3cCntlrRemove(cntlr); // (Mandatory) Remove the I3cCntlr object from the manager driver.
virtual = (struct VirtualI3cCntlr *)cntlr; // (Mandatory) Obtain the custom object through a forced conversion and perform the release operation.
(void)OsalSpinDestroy(&virtual->spin);
OsalMemFree(virtual);
}
return; return;
} }
...
// Traverse and parse all nodes in i3c_config.hcs and perform the release operation on each node. static void VirtualI3cRelease(struct HdfDeviceObject *device)
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) { {
VirtualI3cRemoveByNode(childNode); // See the description of VirtualI3cRemoveByNode for more details. const struct DeviceResourceNode *childNode = NULL;
HDF_LOGI("%s: enter", __func__);
if (device == NULL || device->property == NULL) {
HDF_LOGE("%s: device or property is NULL", __func__);
return;
}
...
/* Traverse and parse all nodes in i3c_config.hcs and perform the release operation on each node. */
DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
VirtualI3cRemoveByNode(childNode); // See the description of VirtualI3cRemoveByNode for more details.
}
} }
} ```
```
4. Register an interrupt handler. 4. Register an interrupt handler.
...@@ -445,12 +488,10 @@ The I3C module adaptation involves the following steps: ...@@ -445,12 +488,10 @@ The I3C module adaptation involves the following steps:
HDF_LOGD("%s: Reserved address which is not supported!", __func__); HDF_LOGD("%s: Reserved address which is not supported!", __func__);
break; break;
} }
return HDF_SUCCESS; return HDF_SUCCESS;
} }
```
```c
static int32_t I3cIbiHandle(uint32_t irq, void *data) static int32_t I3cIbiHandle(uint32_t irq, void *data)
{ {
struct VirtualI3cCntlr *virtual = NULL; struct VirtualI3cCntlr *virtual = NULL;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册