Clock

Overview

Function

The clock regulates the timing and speed of all functions in a device. For example, the CPU clock is an internal time generator of the CPU. It operates in frequency and used to synchronize and control the operations within the CPU.

Basic Concepts

The clock signal is used to synchronize and control the operations of the modules or components of an electronic device. It serves as a fundamental signal reference source to ensure proper functioning and accurate data transmission.

Working Principles

In the Hardware Driver Foundation (HDF), the Clock module uses the unified service mode for API adaptation. In this mode, a service is used as the clock 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 Clock module uses the unified service mode (see Figure 1).

The Clock module is divided into the following layers:

  • Interface layer: provides the APIs for 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

Usage Guidelines

When to Use

The Clock module provides chip-level clock management, including frequency division, frequency multiplication, clock source selection, and clock gating within the chip. Proper clock management can enhance chip efficiency while streamlining coordination and synergy among all functional components.

Available APIs

To enable applications to successfully operate the hardware by calling the Clock APIs, the system provides the following hook APIs in //drivers/hdf_core/framework/support/platform/include/clock/clock_core.h for the core layer. You need to implement these hook APIs at the adaptation layer and hook them to implement the interaction between the interface layer and the core layer.

Definitions of ClockMethod and ClockLockMethod:

struct ClockMethod {
    int32_t (*start)(struct ClockDevice *device);
    int32_t (*stop)(struct ClockDevice *device);
    int32_t (*setRate)(struct ClockDevice *device, uint32_t rate);
    int32_t (*getRate)(struct ClockDevice *device, uint32_t *rate);
    int32_t (*disable)(struct ClockDevice *device);
    int32_t (*enable)(struct ClockDevice *device);
    struct ClockDevice *(*getParent)(struct ClockDevice *device);
    int32_t (*setParent)(struct ClockDevice *device, struct ClockDevice *parent);
};

struct ClockLockMethod {
    int32_t (*lock)(struct ClockDevice *device);
    void (*unlock)(struct ClockDevice *device);
};

The ClockMethod method must be implemented at the adaptation layer, and ClockLockMethod can be implemented based on service requirements. The core layer provides the default ClockLockMethod method, in which a spinlock is used to protect the critical section.

static int32_t ClockDeviceLockDefault(struct ClockDevice *device)
{
    if (device == NULL) {
        HDF_LOGE("ClockDeviceLockDefault: device is null!");
        return HDF_ERR_INVALID_OBJECT;
    }
    return OsalSpinLock(&device->spin);
}

static void ClockDeviceUnlockDefault(struct ClockDevice *device)
{
    if (device == NULL) {
        HDF_LOGE("ClockDeviceUnlockDefault: device is null!");
        return;
    }
    (void)OsalSpinUnlock(&device->spin);
}

static const struct ClockLockMethod g_clockLockOpsDefault = {
    .lock = ClockDeviceLockDefault,
    .unlock = ClockDeviceUnlockDefault,
};

If spinlock cannot be used, you can use another type of lock to implement ClockLockMethod. The customized ClockLockMethod method will replace the default ClockLockMethod method.

Table 1 Hook APIs in ClockMethod

API Input Parameter Output Parameter Return Value Description
start device: structure pointer to the clock controller at the core layer. HDF_STATUS Starts a clock device.
stop device: structure pointer to the clock controller at the core layer. HDF_STATUS Stops a clock device.
setRate device: structure pointer to the clock controller at the core layer. HDF_STATUS Sets the clock rate.
getRate device: structure pointer to the clock controller at the core layer. Clock rate obtained. HDF_STATUS Obtains the clock rate.
disable device: structure pointer to the clock controller at the core layer. HDF_STATUS Enables clock.
enable device: structure pointer to the clock controller at the core layer. HDF_STATUS Disables clock.
getParent device: structure pointer to the clock controller at the core layer. Structure pointer to the clock controller obtained. HDF_STATUS Obtains the parent clock.
setParent device: structure pointer to the clock controller at the core layer. HDF_STATUS Sets the parent clock.

Table 2 APIs in ClockLockMethod

Function Input Parameter Output Parameter Return Value Description
lock device: structure pointer to the clock device object at the core layer. HDF_STATUS Acquires the critical section lock.
unlock device: structure pointer to the clock device object at the core layer. HDF_STATUS Releases the critical section lock.

How to Develop

The Clock module adaptation involves the following steps:

  1. Instantiate the driver entry.

    • Instantiate the HdfDriverEntry structure.
    • Call HDF_INIT to register the HdfDriverEntry instance with the HDF.
  2. Configure attribute files.

    • Add the deviceNode information to the device_info.hcs file.
    • (Optional) Add the clock_config.hcs file.
  3. Instantiate the core layer APIs.

    • Initialize ClockDevice.
    • Instantiate ClockMethod in the ClockDevice object.

      NOTE

      For details about the functions in ClockMethod, see Available APIs.

  4. (Optional) Debug the driver.

    Verify the basic driver functionalities.

Example

The following uses the RK3568 driver //drivers/hdf_core/adapter/khdf/linux/platform/clock/clock_adapter.c as an example to describe how to perform the clock driver adaptation.

  1. Instantiate the driver entry.

    The driver entry must be a global variable of the HdfDriverEntry type (defined in //drivers/hdf_core/interfaces/inner_api/host/shared/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.

    Clock driver entry example:

    Multiple devices may connect to the clock 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.

    You do not need to implement the driver of the clock manager, which is implemented by the core layer. However, the ClockDeviceAdd method of the core layer must be invoked in the Init method to implement the related features.

    struct HdfDriverEntry g_clockLinuxDriverEntry = {
        .moduleVersion = 1,
        .Bind = NULL,
        .Init = LinuxClockInit,
        .Release = LinuxClockRelease,
        .moduleName = "linux_clock_adapter",      // (Mandatory) The value must be the same as the module name in the device_info.hcs file.
    };
    HDF_INIT(g_clockLinuxDriverEntry);            // Call HDF_INIT to register the driver entry with the HDF.
    
    /* Driver entry of the clock_core.c manager service at the core layer */
    struct HdfDriverEntry g_clockManagerEntry = {
        .moduleVersion = 1,
        .Bind = ClockManagerBind,                     
        .Init = ClockManagerInit,                     
        .Release = ClockManagerRelease,               
        .moduleName = "HDF_PLATFORM_CLOCK_MANAGER",   // The value must be that of device0 in the device_info.hcs file.
    };
    HDF_INIT(g_clockManagerEntry);
    
  2. Add the deviceNode information to the //vendor/hihope/rk3568/hdf_config/khdf/device_info/device_info.hcs file and configure the device attributes in clock_config.hcs.

    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 ClockDevice members at the core layer.

    In the unified service mode, the first device node in the device_info.hcs file must be the clock manager. The parameters must be set as follows:

Parameter Value
policy Service publishing policy. For the clock controller, it is 2, which means to publish services for both kernel- and user-mode processes.
priority Loading priority of the cloud driver. The value range is 0 to 200. A larger value indicates a lower priority.
It is set to 59 for the clock controller.
permission Permission for the device node created. It is 0664 for the clock controller.
moduleName HDF_PLATFORM_CLOCK_MANAGER.
serviceName HDF_PLATFORM_CLOCK_MANAGER.
deviceMatchAttr Reserved.

Configure clock controller information from the second node. This node specifies a type of clock controllers rather than a clock controller. In this example, there is only one clock device. If there are multiple clock devices, add the deviceNode information to the device_info.hcs file and add the corresponding device attributes to the clock_config file for each device.

  • device_info.hcs example

    root {
        device_info {
            platform :: host {
                device_clock :: device {
                    device0 :: deviceNode {
                        policy = 2;
                        priority = 59;
                        permission = 0644;
                        moduleName = "HDF_PLATFORM_CLOCK_MANAGER";
                        serviceName = "HDF_PLATFORM_CLOCK_MANAGER";
                    }
                    device1 :: deviceNode {
                        policy = 0;                                 // The value 0 indicates that no service is published.
                        priority = 65;                              // Driver startup priority.
                        permission = 0644;                          // Permission for the device node created.
                        moduleName = "linux_clock_adapter";         // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
                        deviceMatchAttr = "linux_clock_adapter_0";  // (Mandatory) Private data of the controller. The value must be the same as that of the controller
                    }
                }
            }
        }
    }
    
- **clock_config.hcs** example

  The following uses RK3568 as an example. 

    ```
    root {
        platform {
            clock_config {
                match_attr = "linux_clock_adapter_0";
                template clock_device {
                }
                device_clock_0x0000 :: clock_device {
                    deviceName = "/cpus/cpu@0";
                    deviceIndex = 1;
                }
            }
        }
    }
    ```

  After the **clock_config.hcs** file is configured, include the file in the **hdf.hcs** file. Otherwise, the configuration file cannot take effect.
  
  For example, if the **clock_config.hcs** file is in **//vendor/hihope/rk3568/hdf_config/hdf_config/khdf/hdf.hcs**, add the following statement to **hdf.hcs** of the product:

    ```
    #include "platform/clock_config_linux.hcs" //  Relative path of the configuration file
    ```

  This example is based on RK3568 development board that runs the Standard system. The **hdf.hcs** file is in **//vendor/hihope/rk3568/hdf_config/ hdf_config/khdf/**. You can modify the file as required.
  1. Initialize the ClockDevice 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 ClocMethod in ClocDevice (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 clock_config.hcs to initialize the custom structure and passes some important parameters, such as the device number and bus number, to the ClockDevice object at the core layer.

      /* ClockDevice is the core layer controller structure. The **Init()** function assigns values to the members of ClockDevice. */
      struct ClockDevice {
          const struct ClockMethod *ops;
          OsalSpinlock spin;
          const char *deviceName;
          const char *clockName;
          uint32_t deviceIndex;
          const struct ClockLockMethod *lockOps;
          void *clk;
          void *priv;
          struct ClockDevice *parent;
      };
      
  • Instantiate the hook function structure ClockMethod of ClockDevice.

    The ClockLockMethod 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
      struct ClockMethod {
          int32_t (*start)(struct ClockDevice *device);
          int32_t (*stop)(struct ClockDevice *device);
          int32_t (*setRate)(struct ClockDevice *device, uint32_t rate);
          int32_t (*getRate)(struct ClockDevice *device, uint32_t *rate);
          int32_t (*disable)(struct ClockDevice *device);
          int32_t (*enable)(struct ClockDevice *device);
          struct ClockDevice *(*getParent)(struct ClockDevice *device);
          int32_t (*setParent)(struct ClockDevice *device, struct ClockDevice *parent);
      };
    
    
    - Implement the **Init** function.
    
     Input parameter:
     
     **HdfDeviceObject**, an interface parameter provided 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/hdf_core/interfaces/inner_api/utils/hdf_base.h** file.
    
       **Table 4** HDF_STATUS
    
    
Value 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 **ClockDevice**, and calls the **ClockDeviceAdd** function at the core layer.
  
  ```c
    static int32_t LinuxClockInit(struct HdfDeviceObject *device)
      {
          int32_t ret = HDF_SUCCESS;
          struct DeviceResourceNode *childNode = NULL;
  
          if (device == NULL || device->property == NULL) {
              HDF_LOGE("LinuxClockInit: device or property is null");
              return HDF_ERR_INVALID_OBJECT;
          }
  
          DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
              ret = ClockParseAndDeviceAdd(device, childNode);
              if (ret != HDF_SUCCESS) {
                  HDF_LOGE("LinuxClockInit: clock init fail!");
                  return ret;
              }
          }
          HDF_LOGE("LinuxClockInit: clock init success!");
  
          return HDF_SUCCESS;
      }
  
      static int32_t ClockParseAndDeviceAdd(struct HdfDeviceObject *device, struct DeviceResourceNode *node)
      {
          int32_t ret;
          struct ClockDevice *clockDevice = NULL;
  
          (void)device;
          clockDevice = (struct ClockDevice *)OsalMemCalloc(sizeof(*clockDevice));
          if (clockDevice == NULL) {
              HDF_LOGE("ClockParseAndDeviceAdd: alloc clockDevice fail!");
              return HDF_ERR_MALLOC_FAIL;
          }
          ret = ClockReadDrs(clockDevice, node);
          if (ret != HDF_SUCCESS) {
              HDF_LOGE("ClockParseAndDeviceAdd: read drs fail, ret: %d!", ret);
              OsalMemFree(clockDevice);
              return ret;
          }
  
          clockDevice->priv = (void *)node;
          clockDevice->ops = &g_method;
  
          ret = ClockDeviceAdd(clockDevice);
          if (ret != HDF_SUCCESS) {
              HDF_LOGE("ClockParseAndDeviceAdd: add clock device:%u fail!", clockDevice->deviceIndex);
              OsalMemFree(clockDevice);
              return ret;
          }
  
          return HDF_SUCCESS;
      }
  
      static int32_t ClockReadDrs(struct ClockDevice *clockDevice, const struct DeviceResourceNode *node)
      {
          int32_t ret;
          struct DeviceResourceIface *drsOps = NULL;
  
          drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
          if (drsOps == NULL || drsOps->GetUint32 == NULL || drsOps->GetString == NULL) {
              HDF_LOGE("ClockReadDrs: invalid drs ops!");
              return HDF_ERR_NOT_SUPPORT;
          }
          ret = drsOps->GetUint32(node, "deviceIndex", &clockDevice->deviceIndex, 0);
          if (ret != HDF_SUCCESS) {
              HDF_LOGE("ClockReadDrs: read deviceIndex fail, ret: %d!", ret);
              return ret;
          }
  
          drsOps->GetString(node, "clockName", &clockDevice->clockName, 0);
  
          ret = drsOps->GetString(node, "deviceName", &clockDevice->deviceName, 0);
          if (ret != HDF_SUCCESS) {
              HDF_LOGE("ClockReadDrs: read deviceName fail, ret: %d!", ret);
              return ret;
          }
          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.

    static void LinuxClockRelease(struct HdfDeviceObject *device)
    {
        const struct DeviceResourceNode *childNode = NULL;
        if (device == NULL || device->property == NULL) {
            HDF_LOGE("LinuxClockRelease: device or property is null!");
            return;
        }
        DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
            ClockRemoveByNode(childNode);
        }
    }
    
    static void ClockRemoveByNode(const struct DeviceResourceNode *node)
    {
        int32_t ret;
        int32_t deviceIndex;
        struct ClockDevice *device = NULL;
        struct DeviceResourceIface *drsOps = NULL;
    
        drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
        if (drsOps == NULL || drsOps->GetUint32 == NULL) {
            HDF_LOGE("ClockRemoveByNode: invalid drs ops!");
            return;
        }
    
        ret = drsOps->GetUint32(node, "deviceIndex", (uint32_t *)&deviceIndex, 0);
        if (ret != HDF_SUCCESS) {
            HDF_LOGE("ClockRemoveByNode: read deviceIndex fail, ret: %d!", ret);
            return;
        }
    
        device = ClockDeviceGet(deviceIndex);
        if (device != NULL && device->priv == node) {
            ret = ClockStop(device);
            if (ret != HDF_SUCCESS) {
                HDF_LOGE("ClockRemoveByNode: close fail, ret: %d!", ret);
            }
            if (device->parent  && device->parent->deviceName == NULL) {
                ClockDeviceRemove(device->parent);
                OsalMemFree(device->parent);
            }
            ClockDeviceRemove(device);
            OsalMemFree(device);
        }
    }
    
  1. (Optional) Debug the driver. Verify the basic driver functionalities.