Clock子系统


1.   架构介绍

Clock统是Linux内核中专门管理时钟的子系统.

时钟在嵌入式系统中很重要, 它就像人的脉搏一样, 驱动器件工作.

任何一个CPU, 都需要给它提供一个外部晶, 这个晶振就是用来提供时钟的; 任何一个CPU内部的片上外设, 也需要工作时钟: 例如GPIO控制器, 首先得给它提供工作时钟, 然后才能访问它的寄存器.

 

如果你去看一个ARM CPU的芯片手册, 你一定能找到一个章节, 专门描述系统时钟, 一般称之为时钟树(clock tree).

芯片手册从硬件的角度上描述了某个CPU的时钟系统是如何设计的, Clock子系统从软件的层面来抽象这个设计.

 

在本章中, 我们首先从硬件的角度来看看一个时钟树的例子, 然后自己思考一下软件层面该如何设计, 最后看看clock子系统是怎么做的.

2.1             时钟树

如今, 可运行Linux的主流CPU, 都有非常复杂的clock tree, 我们随便拿一个处理器的spec, 查看clock相关的章节, 一定会有一个非常庞大和复杂的树状图. 这个图由clock相关的器件,以及这些器件输出的clock组成.

 

下图是一个简单的示例:

clock相关的器件包括

         用于产生clockOscillator(有源振荡器, 也称作振荡器)或者Crystal(无源振荡器, 也称晶振)

         用于倍频的PLL(锁相环, Phase Locked Loop

         用于分频的divider

         用于多路选择的Mux

         用于clock enable控制的与门

         使用clock的硬件模块(可称作consumer, 例如HW1, 它可能是GPIO控制器

 

器件用于产生具体的clock, 例如osc_clk. 这些器件的特性如下:

         从输入(parent)和输出(children)的角度来看

         某些器件没有parent, 有一个或多个输出. 例如osc_clk

         某些器件有一个parent, 有一个或多个输出. 例如PLL1, 它的parentosc_clk, 输出只有一个pll1_clk

         某些器件有多个parent, 有一个或多个输出. 例如MUX, 它有多个parent, 输出只有一个hw3_clk

         从频率的角度上来看

         某些clock的频率是可以调整的, 我们可以通过设置倍频和分频因子来调整输出频率.

这一类clock最常见.

         某些clock的频率是固定的, 而且不能开关, 比如osc_clk. 最常见的是24M25M.

这一类clock称作fixed_rate_clock

         某些clock的频率也是固定的, 不过它可以开关.

这一类clock称作gate_clock

         某些clock有固定的倍频和分频因子, 它的输出频率跟随parent的变化而变化.

这一类clock称作fixed_factor_clock

 

在上图的时钟树中, 有些是clock的提供者, 我们可以称之为provider, 例如oscillator, PLLs; 有些是clock的使用者, 我们可以称之为consumer, 例如HW1, HW2, HW3.

ARM CPU的内部, 时钟树系统用来provide各种各样的时钟; 各片上外设consume这些时钟. 例如时钟树系统负责提供时钟给GPIO控制器, GPIO控制器则消费提供给它的工作时钟.

 

在设备驱动开发的过程中, 我们经常会遇到的一个问题是: 想要开启某个模块的时钟.

例如开发GPIO的驱动, 在驱动的probe函数中, 我们需要使能GPIO模块的工作时钟.

从软件层面, 我们就是要提供一种机制, consumer可以方便的获取/使能/配置/关闭一个时钟.

 

接下来, 我们看看软件层面上该如何抽象.

2.2             软件抽象

上一节我们介绍了时钟树, 并介绍了时钟的providerconsumer. 一个CPU芯片内部, 会有很多个provider, 也会有很多的consumer. 软件层面需要做的事情就是管理所有这些provider, 并向consumer提供尽量简单的接口使得consumer可以获取/使能/配置/关闭一个时钟.

 

因此, 我们可以设计这样一个池子, 所有的provider都可以向池子注册, 把自己添加到池子里面. 池子里面可以用一个链表把所有的provider都串起来, 不同的provider以不同的name区分. consumer需要获取某个clock的时候, 通过name向池子查询即可.

 

在这个池子里面, 每一个provider都可以抽象成一个独立的元素, 因此我们最好设计一个数据结构, 来表示每一个元素.

 

大致逻辑就是这样了, Linux内核的clock子系统基本上就是在干这些事情.

2.3             clock子系统

Linux内核的clock子系统, 按照其职能, 可以大致分为3部分:

         向下提供注册接口, 以便各个clocks能注册进clock子系统

         在核心层维护一个池子, 管理所有注册进来的clocks. 这一部分实现的是通用逻辑, 与具体硬件无关.

         向上, 也就是像各个消费clocks的模块的device driver, 提供获取/使能/配置/关闭clock的通用API

 

clock子系统的结构框图如下, 一些细节你现在可能还不能理解. 不过没关系, 读完后面的章节你就会明白了.

后面的章节, 我们也会分为上述3部分来描述, 在阅读的过程中, 你可以边看边对着下面这张图理解, 这样会更加清晰.

2.   clock provider -- 如何注册Clocks

3.1             简介

前文我们介绍了时钟树, 本章要阐述的主要问题就是如何把时钟树产生的这些clocks注册进Linux内核的clock子系统. 换句话说, 就是如何编写clock driver.

 

ARM CPU内部, 管理时钟树的也是一个单独的模块, 一般叫PCM(Programmable Clock Management), 编写clock driver其实就是编写PCMdriver.

PCM也是CPU的一个片上外设, 因此它也会借用platform这套机制. 因此我们就需要有platform_device来描述设备, 同时要有与之对应的platform_driver来控制设备. 所谓控制, 就写读写PCM的寄存器来使能/关闭时钟, 设置时钟频率等等. platform_driverprobe函数中, 还有一项重要功能, 就是调用clock子系统提供的API, clock子系统注册.

 

下面, 我看看编写clock driver 的大致步骤是怎样的.

3.2             编写clock driver的大致步骤

由前文可知, 首先你得准备一个platform_device, 引入device tree的机制后, platform_devicedts替代了, 因此我们就需要在dts里面描述时钟树.

编写platform_device(DTS node)

那么这个DTS该怎么写呢? 通常有两种方式:

方式: 将时钟树中所有的clock,抽象为一个虚拟的设备,用一个DTS node表示.

   1: /* arch/arm/boot/dts/exynos4210.dtsi */

   2: theclock: clock-controller@0x10030000 {

   3:         compatible = "samsung,exynos4210-clock";

   4:         reg = <0x10030000 0x20000>;

   5:         #clock-cells = <1>;

   6: };

这种方式跟编写一个普通的片上外设的DTS很类似, 比如GPIO控制器的DTS, 对比一下, 是不是很类似.

从这个例子里面, 我们可以看出一个clockDTS node的基本语法:

         compatible , 决定了与这个node匹配的driver

         reg就是用来描述PCM的寄存器

         #clock-cells, 这个是clock provider node独有的, 表明在引用此clock, 需要用几个32位来描述. 什么意思呢?

 

假设这个clock只有一个输出时钟, 那么#clock-cells = <0>, 我们在引用此clock的时候, 只用指明此clock即可.

引用此clock是什么意思? 引用指的是clockconsumer. 例如GPIO模块需要工作时钟, 那么我们在编写GPIODTS node, 需要指明它的工作时钟是多少, 这个过程就是引用, 写个简单的例子:

gpio : gpio-controller@xxxx {

compatible = yyyy;

reg = <.>;

……

clocks = <&theclock>;  /* 指明/引用某一个clock */

}

 

假设这个clock有多个输出时钟, 那么#clock-cells = <0>肯定不行, 因为我们在引用此clock的时候, 需要指明到底用哪一个输出时钟.

这个时候#clock-cells 应该为  <1>, 在引用此clock, 就得这样写:

gpio : gpio-controller@xxxx {

compatible = yyyy;

reg = <.>;

……

clocks = <&theclock  num>;  /* 指明/引用某一个clock, num是一个32位的整数, 表明到底用哪一个输出clock */

}

         theclock, DTS nodelable, clock consumer可以根据该名称引用clock

 

方式二: 将时钟树中的每一个clock抽象为一个DTS node, 并以树形的结构组织. 这种方式相较与方式, 能更清晰的抽象出时钟的树形结构, 从逻辑上也更合理, 因此推荐大家使用这种方式.

举个方式二的例子:

   1: /* arch/arm/boot/dts/sun4i-a10.dtsi */

   2: clocks {

   3:     #address-cells = <1>;

   4:     #size-cells = <1>;

   5:     ranges;

 

  19:     osc24M: osc24M@01c20050 {

  20:         #clock-cells = <0>;

  21:         compatible = "allwinner,sun4i-osc-clk";

  22:         reg = <0x01c20050 0x4>;

  23:         clock-frequency = <24000000>;

  24:     };

  25: 

  26:     osc32k: osc32k {

  27:         #clock-cells = <0>;

  28:         compatible = "fixed-clock";

  29:         clock-frequency = <32768>;

  30:     };

  31: 

  32:     pll1: pll1@01c20000 {

  33:         #clock-cells = <0>;

  34:         compatible = "allwinner,sun4i-pll1-clk";

  35:         reg = <0x01c20000 0x4>;

  36:         clocks = <&osc24M>;

  37:     };

  38: 

  39:     /* dummy is 200M */

  40:     cpu: cpu@01c20054 {

  41:         #clock-cells = <0>;

  42:         compatible = "allwinner,sun4i-cpu-clk";

  43:         reg = <0x01c20054 0x4>;

  44:         clocks = <&osc32k>, <&osc24M>, <&pll1>, <&dummy>;

  45:     };

  46: 

  47:     axi: axi@01c20054 {

  48:         #clock-cells = <0>;

  49:         compatible = "allwinner,sun4i-axi-clk";

  50:         reg = <0x01c20054 0x4>;

  51:         clocks = <&cpu>;

  52:     };

  53: 

  54:     axi_gates: axi_gates@01c2005c {

  55:         #clock-cells = <1>;

  56:         compatible = "allwinner,sun4i-axi-gates-clk";

  57:         reg = <0x01c2005c 0x4>;

  58:         clocks = <&axi>;

  59:         clock-output-names = "axi_dram";

  60:     };

  61: 

  62:     ahb: ahb@01c20054 {

  63:         #clock-cells = <0>;

  64:         compatible = "allwinner,sun4i-ahb-clk";

  65:         reg = <0x01c20054 0x4>;

  66:         clocks = <&axi>;

  67:     };

  68: 

  69:     ahb_gates: ahb_gates@01c20060 {

  70:         #clock-cells = <1>;

  71:         compatible = "allwinner,sun4i-ahb-gates-clk";

  72:         reg = <0x01c20060 0x8>;

  73:         clocks = <&ahb>;

  74:         clock-output-names = "ahb_usb0", "ahb_ehci0",

  75:             "ahb_ohci0", "ahb_ehci1", "ahb_ohci1", "ahb_ss",

  76:             "ahb_dma", "ahb_bist", "ahb_mmc0", "ahb_mmc1",

  77:             "ahb_mmc2", "ahb_mmc3", "ahb_ms", "ahb_nand",

  78:             "ahb_sdram", "ahb_ace",    "ahb_emac", "ahb_ts",

  79:             "ahb_spi0", "ahb_spi1", "ahb_spi2", "ahb_spi3",

  80:             "ahb_pata", "ahb_sata", "ahb_gps", "ahb_ve",

  81:             "ahb_tvd", "ahb_tve0", "ahb_tve1", "ahb_lcd0",

  82:             "ahb_lcd1", "ahb_csi0", "ahb_csi1", "ahb_hdmi",

  83:             "ahb_de_be0", "ahb_de_be1", "ahb_de_fe0",

  84:             "ahb_de_fe1", "ahb_mp", "ahb_mali400";

  85:     };

  86: 

  87:     apb0: apb0@01c20054 {

  88:         #clock-cells = <0>;

  89:         compatible = "allwinner,sun4i-apb0-clk";

  90:         reg = <0x01c20054 0x4>;

  91:         clocks = <&ahb>;

  92:     };

  93: 

  94:     apb0_gates: apb0_gates@01c20068 {

  95:         #clock-cells = <1>;

  96:         compatible = "allwinner,sun4i-apb0-gates-clk";

  97:         reg = <0x01c20068 0x4>;

  98:         clocks = <&apb0>;

  99:         clock-output-names = "apb0_codec", "apb0_spdif",

 100:             "apb0_ac97", "apb0_iis", "apb0_pio", "apb0_ir0",

 101:             "apb0_ir1", "apb0_keypad";

 102:     };

 103: 

 104:     /* dummy is pll62 */

 105:     apb1_mux: apb1_mux@01c20058 {

 106:         #clock-cells = <0>;

 107:         compatible = "allwinner,sun4i-apb1-mux-clk";

 108:         reg = <0x01c20058 0x4>;

 109:         clocks = <&osc24M>, <&dummy>, <&osc32k>;

 110:     };

 111: 

 112:     apb1: apb1@01c20058 {

 113:         #clock-cells = <0>;

 114:         compatible = "allwinner,sun4i-apb1-clk";

 115:         reg = <0x01c20058 0x4>;

 116:         clocks = <&apb1_mux>;

 117:     };

 118: 

 119:     apb1_gates: apb1_gates@01c2006c {

 120:         #clock-cells = <1>;

 121:         compatible = "allwinner,sun4i-apb1-gates-clk";

 122:         reg = <0x01c2006c 0x4>;

 123:         clocks = <&apb1>;

 124:         clock-output-names = "apb1_i2c0", "apb1_i2c1",

 125:             "apb1_i2c2", "apb1_can", "apb1_scr",

 126:             "apb1_ps20", "apb1_ps21", "apb1_uart0",

 127:             "apb1_uart1", "apb1_uart2", "apb1_uart3",

 128:             "apb1_uart4", "apb1_uart5", "apb1_uart6",

 129:             "apb1_uart7";

 130:     };

 131: };

         osc24M 代表24M的晶振

         osc32k 代表32k的慢速时钟

         pll1 , 它的父时钟是osc24M, 只有一个输出时钟

         cpu , 它的父时钟有多个<&osc32k>, <&osc24M>, <&pll1>, <&dummy>, 只有一个输出时钟

         ahb_gates , 它的父时钟只有一个, ahb, 但是它的输出时钟有很多个

         ...... 后面的都类似, 不一一说明了

看见了吧, 这种方式确实能清晰的描述多个clocks的树形关系.

编写platform_driver

有了platform_device之后, 接下来就得编写platform_driver, driver里面最重要的事情就是向clock子系统注册.

 

如何注册呢?

clock子系统定义了clock driver需要实现的数据结构, 同时提供了注册的函数. 我们只需要准备好相关的数据结构, 然后调用注册函数进行注册即可.

这些数据结构和接口函数的定义是在: include/linux/clk-provider.h

 

需要实现的数据结构是 struct clk_hw, 需要调用的注册函数是struct clk *clk_register(struct device *dev, struct clk_hw *hw). 数据结构和注册函数的细节我们在后文说明.

 

注册函数会返回给你一个struct clk类型的指针, Linuxclock子系统中, 用一个struct clk代表一个clock. 你的CPU的时钟树里有多少个clock, 就会有多少个对应的struct clk.

 

当你拿到返回结果之后, 你需要调用另外一个API : of_clk_add_provider, 把刚刚拿到的返回结果通过此API丢到池子里面, 好让consumer从这个池子里获取某一个clock.

 

池子的概念我们在前文讲述过, 除了上述方法, 你还可以用另外一种方法把struct clk添加到池子里面.

如果你要用这种方法, 你会用到clock子系统提供给你的另外几个API, 这些API的定义在: include/linux/clkdev.h

其中最主要的一个APIint clk_register_clkdev(struct clk *, const char *, const char *, ...), 你可以在你的clock driver里面调用这个API, 刚刚拿到的返回结果通过此API丢到池子里面.

 

为什么会存在这两种方式呢? 得从consumer的角度来解答这个问题.

我们用GPIO来举个例子, GPIO控制器需要工作时钟, 这个时钟假设叫gpio_clk, 它是一个provider. 你需要把这个provider注册进clock子系统, 并把用于描述这个gpio_clkstruct clk添加到池子里面.

GPIO控制器的driver代码里, 我们需要获取到gpio_clk这个时钟并使能它, 获取的过程就是向池子查询.

怎么查询? 你可以直接给定一个name, 然后通过这个name向池子查询; 你也可以在GPIODTS  node里面用clocks = <&theclock>;方式指明使用哪一个clock, 然后通过这种方式向池子查询.

如果consumer是通过name查询, 则对应的添加到池子的API就是clk_register_clkdev

如果consumer是通过DTS查询, 则对应的添加到池子的API就是of_clk_add_provider

 

那么我在我的clock driver里面到底应该用哪个API向池子添加clk?

两者你都应该同时使用, 这样consumer端不管用哪种查询方式都能工作.

 

读到这里, 建议你回头看看clock子系统的系统框图, 结合框图在琢磨琢磨.

 

接下来, 我们就会详细介绍这些数据结构和相关的API.

3.3             主要数据结构

通过前文, 我们知道了clock driver需要实现的一个主要的数据结构struct clk_hw.

与之相关的还有另外几个重要数据结构: struct clk_init_datastruct clk_ops.

下面我们看看这几个数据结构.

struct clk_hw

头文件: include/linux/clk-provider.h

struct  clk_hw

Comment

struct clk_core *core

clk_coreclock子系统核心层的一个数据结构, 由核心层代码创建和维护, 一个clk_core对应一个具体的clock.

struct clk *clk

clk也是clock子系统核心层的一个数据结构, 同样由核心层代码创建和维护, 它也对应一个具体的clock.

clk_coreclk的细节, 放在《clock core》一章中描述

const struct clk_init_data *init

clk_init_dataprovider需要实现并填充的一个数据结构, 编写clock driver, 最主要的任务就是实现它

struct clk_init_data

头文件: include/linux/clk-provider.h

struct  clk_init_data

Comment

const char*name

clockname, 系统中会存在很多个clock, 每一个clock都会有一个名称, 可以用名称来区分不同的clock, 因此不能重名

const struct clk_ops*ops

clock控制相关的ops, 例如enable/disable; set_rate等等.

consumer端想要操作某个clock, 最终就会调用到该clockclk_ops

const char**parent_names

clock的所有parents. 通过name, 就能找到parent是谁了

u8num_parents

一个clock可能有多个parents, num_parents指明到底有几个

unsigned longflags

标志位, 表明该clock的一些特性, 核心层代码会根据不同的flags采取不同的动作. 可选的值如下:

#define CLK_SET_RATE_GATEBIT(0) /* must be gated across rate change */

#define CLK_SET_PARENT_GATEBIT(1) /* must be gated across re-parent */

#define CLK_SET_RATE_PARENTBIT(2) /* propagate rate change up one level */

#define CLK_IGNORE_UNUSEDBIT(3) /* do not gate even if unused */

#define CLK_IS_ROOTBIT(4) /* root clk, has no parent */

#define CLK_IS_BASICBIT(5) /* Basic clk, can't do a to_clk_foo() */

#define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */

#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */

#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */

struct clk_ops

头文件: include/linux/clk-provider.h

下述这些ops.h文件里面都有详细的注释, 可以阅读源代码获取更多信息.

struct  clk_ops

Comment

int(*prepare)(struct clk_hw *hw)

为什么要有prepare接口, 直接enable不就行了吗?

从硬件的角度来说, 某些clock, 如果想使能它, 需要等待一段时间.

例如倍频器PLL, 当你使能倍频器之后, 你需要等待几毫秒让倍频器工作平稳.

因为要等待, 软件上就有可能sleep, 也就是休眠. 但是Linux内核中有很多情况下不能休眠, 比如说中断服务器程序.

如果你把所有的操作都放在enable这一个函数里面, 那么enable函数就可能休眠, 因而中断服务程序里面就不能调用enable函数.

但实际情况是, 很多时候, 我们都要求在中断服务程序里面开/关某个clock

怎么办呢?

拆分成2个函数, prepareenable.

prepare负责使能clock之前的准备工作, prepare里面可以休眠, 一旦prepare返回, 就意味着clock已经完全准备好了, 可以直接开/

enable负责打开clock, 它不能休眠, 这样在中断服务程序中也可以调用enable

void(*unprepare)(struct clk_hw *hw)

prepare的反函数, un-do prepare里面做的所有事情

int(*is_prepared)(struct clk_hw *hw)

is_prepared, 判断clock是否已经prepared, 可以不提供.

clock framework core会维护一个prepare的计数(该计数在clk_prepare调用时加一, clk_unprepare时减一, 并依据该计数判断是否prepared

void(*unprepare_unused)(struct clk_hw *hw)

自动unprepare unused clocks

clock framework core提供一个clk_disable_unused接口, 在系统初始化的late_call中调用, 用于关闭unused clocks, 这个接口会调用相应clock.unprepare_unused.disable_unused函数

int(*enable)(struct clk_hw *hw)

使能clock, 此函数不能休眠

当此函数返回时, 代表consumer端可以收到一个稳定, 可用的clock波形了.

void(*disable)(struct clk_hw *hw)

禁止clock, 此函数不能休眠

int(*is_enabled)(struct clk_hw *hw)

is_prepared类似

void(*disable_unused)(struct clk_hw *hw)

unprepare_unused类似

int(*save_context)(struct clk_hw *hw)

Save the context of the clock in prepration for poweroff

void(*restore_context)(struct clk_hw *hw)

Restore the context of the clock after a restoration of power

unsigned long(*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate)

parent clock rate为参数, 从新计算并返回clock rate

long(*round_rate)(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)

Given a target rate as input, returns the closest rate actually supported by the clock

该接口有点特别, 在返回rounded rate的同时, 会通过一个指针, 返回roundparentrate. 这和CLK_SET_RATE_PARENT flag有关

 

clock consumer调用clk_round_rate获取一个近似的rate, 如果该clock没有提供.round_rate函数, 有两种方法:

         在没有设置CLK_SET_RATE_PARENT标志时, 直接返回该clockcache rate

         如果设置了CLK_SET_RATE_PARENT标志, 则会询问parent, 即调用clk_round_rate获取parent clock能提供的、最接近该rate的值.

这是什么意思呢? 也就是说, 如果parent clock可以得到一个近似的rate, 那么通过改变parent clock, 就能得到所需的clock

long(*determine_rate)(struct clk_hw *hw,

unsigned long rate,

unsigned long min_rate,

unsigned long max_rate,

unsigned long *best_parent_rate,

struct clk_hw **best_parent_hw)

round_rate类似, 暂时不清楚它俩有什么区别.

int(*set_parent)(struct clk_hw *hw, u8 index)

Change the input source of this clock

有的clocks可能有多个parents, 那到底用哪一个呢? 由参数index决定

u8(*get_parent)(struct clk_hw *hw)

Queries the hardware to determine the parent of a clock

返回值是一个u8类型的变量, 它是一个index, 通过它可以查找到对应的parent. 所有的parents都存储在.parent_names or .parents arrays里面

 

实际上, 此函数会读取硬件寄存器, 然后把寄存器的值转变为对应的index

int(*set_rate)(struct clk_hw *hw, unsigned long rate,

unsigned long parent_rate)

Change the rate of this clock

The requested rate is specified by the second argument, which should typically be the return of .round_rate call

 

The third argument gives the parent rate which is likely helpful for most .set_rate implementation

int (*set_rate_and_parent)(struct clk_hw *hw,

unsigned long rate,

unsigned long parent_rate,

u8 index)

Change the rate and the parent of this clock

This callback is optional (and unnecessary) for clocks with 0 or 1 parents as well as for clocks that can tolerate switching the rate and the parent separately via calls to .set_parent and .set_rate

unsigned long (*recalc_accuracy)(struct clk_hw *hw,

   unsigned long parent_accuracy)

Recalculate the accuracy of this clock

The clock accuracy is expressed in ppb (parts per billion)

int(*get_phase)(struct clk_hw *hw)

Queries the hardware to get the current phase of a clock

Returned values are 0-359 degrees on success, negative error codes on failure

int(*set_phase)(struct clk_hw *hw, int degrees)

Shift the phase this clock signal in degrees specified by the second argument

Valid values for degrees are 0-359

Return 0 on success, otherwise -EERROR

void(*init)(struct clk_hw *hw)

Perform platform-specific initialization magic

不过从代码注释来看, 内核推荐你不要实现这个接口函数, 后面可能会遗弃

int(*debug_init)(struct clk_hw *hw, struct dentry *dentry)

debugfs相关, 这里不细述

细节可以看代码注释

3.4             主要API说明

Linux clock子系统向下提供几个重要的API, 下面我挨个看下这些API的细节.

clk_register / devm_clk_register

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk.c

/**

* clk_register - allocate a new clock, register it and return an opaque cookie

* @dev: device that is registering this clock

* @hw: link to hardware-specific clock data

*

* clk_register is the primary interface for populating the clock tree with new

* clock nodes.  It returns a pointer to the newly allocated struct clk which

* cannot be dereferenced by driver code but may be used in conjuction with the

* rest of the clock API.  In the event of an error clk_register will return an

* error code; drivers must test for an error code after calling clk_register.

*/

struct clk *clk_register(struct device *dev, struct clk_hw *hw);

struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);

 

void clk_unregister(struct clk *clk);

void devm_clk_unregister(struct device *dev, struct clk *clk);

clk_registerclock子系统提供的注册clock基础的API函数, 后文描述的其它APIs都是对它的封装.

devm_clk_registerclk_registerdevm版本, devm机制在《设备模型》一文中有详述.

要向系统注册一个clock也很简单, 准备好clk_hw结构体, 然后调用clk_register接口即可.

 

不过, clock framework所做的远比这周到, 它基于clk_register, 又封装了其它接口, 在向clock子系统注册时, struct clk_hw都不需要关心, 而是直接使用类似人类语言的方式.

也就是说, 实际在编写clock driver的时候, 我们不会直接使用clk_register接口, 只需要调用下面的某个API即可.

下文我们一一介绍这些API.

clk_register_fixed_rate

clock有不同的类型, 有的clock频率是固定的, 不可调整的, 例如外部晶, 频率就是固定的(24M / 25M). 这种类型的clock就是fixed_rate clock.

fixed_rate clock具有固定的频率, 不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops调函数, 是最简单的一类clock.

 

如果要注册这种类型的clock, 直接调用本API, 传递相应的参数给本API即可.  API的细节如下:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fixed-rate.c

/*

* DOC: Basic clock implementations common to many platforms

*

* Each basic clock hardware type is comprised of a structure describing the

* clock hardware, implementations of the relevant callbacks in struct clk_ops,

* unique flags for that hardware type, a registration function and an

* alternative macro for static initialization

*/

 

/**

* struct clk_fixed_rate - fixed-rate clock

* @hw:     handle between common and hardware-specific interfaces

* @fixed_rate: constant frequency of clock

*/

struct clk_fixed_rate {

    struct      clk_hw hw;

    unsigned long   fixed_rate;

    unsigned long   fixed_accuracy;

    u8      flags;

};

 

extern const struct clk_ops clk_fixed_rate_ops;

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,

        const char *parent_name, unsigned long flags,

        unsigned long fixed_rate);

struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev,

        const char *name, const char *parent_name, unsigned long flags,

        unsigned long fixed_rate, unsigned long fixed_accuracy);

 

void of_fixed_clk_setup(struct device_node *np);

 

想注册一个fixed rate clock, 除了你可以在自己的clock driver里面手动调用clk_register_fixed_rate之外, 还有一种更简单的方式, 那就是用DTS.

 

你可以在DTS里面用如下方式描述一个fixed rate clock:

  26:     osc32k: osc32k {

  27:         #clock-cells = <0>;

  28:         compatible = "fixed-clock";

  29:         clock-frequency = <32768>;

               clock-accuracy = xxxx;

               clock-output-names = xxxx;

  30:     };

         关键地方在于compatible一定要是fixed-clock

         drivers/clk/clk-fixed-rate.c中的of_fixed_clk_setup负责匹配这个compatible, 这个C文件是clock子系统实现的.

         of_fixed_clk_setup会解析3个参数: clock-frequency, clock-accuracy, clock-output-names

clock-frequency是必须的, 另外2个参数可选.

参数解析完毕之后, 会调用clk_register_fixed_rate_with_accuracy向系统注册一个clock.

clk_register_gate

这一类clock只可开关(会提供.enable/.disable回调), 可使用下面接口注册:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-gate.c

/**

* struct clk_gate - gating clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register controlling gate

* @bit_idx:    single bit controlling gate

* @flags:  hardware-specific flags

* @lock:   register lock

*

* Clock which can gate its output.  Implements .enable & .disable

*

* Flags:

* CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to

 *  enable the clock.  Setting this flag does the opposite: setting the bit

 *  disable the clock and clearing it enables the clock

* CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit

 *  of this register, and mask of gate bits are in higher 16-bit of this

 *  register.  While setting the gate bits, higher 16-bit should also be

 *  updated to indicate changing gate bits.

*/

struct clk_gate {

    struct clk_hw hw;

    void __iomem    *reg;

    u8      bit_idx;

    u8      flags;

    spinlock_*lock;

};

 

#define CLK_GATE_SET_TO_DISABLE     BIT(0)

#define CLK_GATE_HIWORD_MASK        BIT(1)

 

extern const struct clk_ops clk_gate_ops;

struct clk *clk_register_gate(struct device *dev, const char *name,

        const char *parent_name, unsigned long flags,

        void __iomem *reg, u8 bit_idx,

        u8 clk_gate_flags, spinlock_t *lock);

void clk_unregister_gate(struct clk *clk);

clk_register_gate, 它的参数列表如下:

         nameclock的名称

         parent_nameparent clock的名称, 没有的话可留空

         flags : 参考3.3 struct clk_hw结构体中对于flags的描述

         reg控制该clock开关的寄存器地址(虚拟地址)

         bit_idx:  reg, 第几个bit是控制clock/关的

         clk_gate_flags:  gate clock特有的参数. 其中一个可选值是CLK_GATE_SET_TO_DISABLE, 它的意思是1表示开还是0表示开, 类似于翻转位.

         lock:  如果clock开关时需要互斥, 可提供一个spinlock.

 

clock子系统并没有定义类似fixed rate clockDTS处理方式.

如果你想借用DTS, 也很简单. 如下:

    ehrpwm1_tbclk: ehrpwm1_tbclk@44e10664 {

        #clock-cells = <0>;

        compatible = "ti,gate-clock";

        clocks = <&l4ls_gclk>;

        ti,bit-shift = <1>;

        reg = <0x0664>;

    };

注意它的compatible, 自己实现一个driver, 匹配这个compatible. 然后在driver里面解析DTS相关参数并调用clk_register_gate  API 即可.

clk_register_divider / clk_register_divider_table

这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调), 可通过下面两个接口注册

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-divider.c

/**

* struct clk_divider - adjustable divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register containing the divider

* @shift:  shift to the divider bit field

* @width:  width of the divider bit field

* @table:  array of value/divider pairs, last entry should have div = 0

* @lock:   register lock

*

* Clock with an adjustable divider affecting its output frequency.  Implements

* .recalc_rate, .set_rate and .round_rate

*

* Flags:

* CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the

 *  register plus one.  If CLK_DIVIDER_ONE_BASED is set then the divider is

 *  the raw value read from the register, with the value of zero considered

 *  invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.

* CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from

 *  the hardware register

* CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors.  For dividers which have

 *  CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.

 *  Some hardware implementations gracefully handle this case and allow a

 *  zero divisor by not modifying their input clock

 *  (divide by one / bypass).

* CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit

 *  of this register, and mask of divider bits are in higher 16-bit of this

 *  register.  While setting the divider bits, higher 16-bit should also be

 *  updated to indicate changing divider bits.

* CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded

 *  to the closest integer instead of the up one.

* CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should

 *  not be changed by the clock framework.

*/

struct clk_divider {

    struct clk_hw   hw;

    void __iomem    *reg;

    u8      shift;

    u8      width;

    u8      flags;

    const struct clk_div_table  *table;

    spinlock_*lock;

    u32     context;

};

 

#define CLK_DIVIDER_ONE_BASED       BIT(0)

#define CLK_DIVIDER_POWER_OF_TWO    BIT(1)

#define CLK_DIVIDER_ALLOW_ZERO      BIT(2)

#define CLK_DIVIDER_HIWORD_MASK     BIT(3)

#define CLK_DIVIDER_ROUND_CLOSEST   BIT(4)

#define CLK_DIVIDER_READ_ONLY       BIT(5)

 

extern const struct clk_ops clk_divider_ops;

 

unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,

        unsigned int val, const struct clk_div_table *table,

        unsigned long flags);

long divider_round_rate(struct clk_hw *hw, unsigned long rate,

        unsigned long *prate, const struct clk_div_table *table,

        u8 width, unsigned long flags);

int divider_get_val(unsigned long rate, unsigned long parent_rate,

        const struct clk_div_table *table, u8 width,

        unsigned long flags);

 

struct clk *clk_register_divider(struct device *dev, const char *name,

        const char *parent_name, unsigned long flags,

        void __iomem *reg, u8 shift, u8 width,

        u8 clk_divider_flags, spinlock_t *lock);

struct clk *clk_register_divider_table(struct device *dev, const char *name,

        const char *parent_name, unsigned long flags,

        void __iomem *reg, u8 shift, u8 width,

        u8 clk_divider_flags, const struct clk_div_table *table,

        spinlock_t *lock);

void clk_unregister_divider(struct clk *clk);

clk_register_divider : 该接口用于注册分频比规则的clock

         reg控制clock分频比的寄存器

         shift控制分频比的bit在寄存器中的偏移

         width: 控制分频比的bit位数, 默认情况下, 实际的divider值是寄存器值加1.

如果有其它例外, 可使用下面的flag指示。

         clk_divider_flagsdivider clock特有的flag, 包括: CLK_DIVIDER_ONE_BASED:

实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag

CLK_DIVIDER_POWER_OF_TWO:

实际的divider值是寄存器值得2次方

CLK_DIVIDER_ALLOW_ZERO:

divider值可以为0(不改变,视硬件支持而定)

 

clk_register_divider_table : 该接口用于注册分频比不规则的clock,和上面接口比较,差别在于divider值和寄存器值得对应关系由一个table决定. table的原型如下:

struct clk_div_table {

    unsigned int    val;

    unsigned int    div;

};

val代表寄存器值,  div代表对应的分频值.

 

同样, clock子系统并没有实现DTS相关接口, 不过你可以自己编写driver去解析DTS并调用API注册.

clk_register_mux

这一类clock可以选择多个parent, 因为会实现.get_parent/.set_parent/.recalc_rate回调, 可通过下面两个接口注册:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-mux.c

/**

* struct clk_mux - multiplexer clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register controlling multiplexer

* @shift:  shift to multiplexer bit field

* @width:  width of mutliplexer bit field

* @flags:  hardware-specific flags

* @lock:   register lock

*

* Clock with multiple selectable parents.  Implements .get_parent, .set_parent

* and .recalc_rate

*

* Flags:

* CLK_MUX_INDEX_ONE - register index starts at 1, not 0

* CLK_MUX_INDEX_BIT - register index is a single bit (power of two)

* CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this

 *  register, and mask of mux bits are in higher 16-bit of this register.

 *  While setting the mux bits, higher 16-bit should also be updated to

 *  indicate changing mux bits.

* CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired

 *  frequency.

*/

struct clk_mux {

    struct clk_hw   hw;

    void __iomem    *reg;

    u32     *table;

    u32     mask;

    u8      shift;

    u8      flags;

    spinlock_*lock;

    u8      saved_parent;

};

 

#define CLK_MUX_INDEX_ONE       BIT(0)

#define CLK_MUX_INDEX_BIT       BIT(1)

#define CLK_MUX_HIWORD_MASK     BIT(2)

#define CLK_MUX_READ_ONLY       BIT(3) /* mux can't be changed */

#define CLK_MUX_ROUND_CLOSEST       BIT(4)

 

extern const struct clk_ops clk_mux_ops;

extern const struct clk_ops clk_mux_ro_ops;

 

struct clk *clk_register_mux(struct device *dev, const char *name,

        const char **parent_names, u8 num_parents, unsigned long flags,

        void __iomem *reg, u8 shift, u8 width,

        u8 clk_mux_flags, spinlock_t *lock);

 

struct clk *clk_register_mux_table(struct device *dev, const char *name,

        const char **parent_names, u8 num_parents, unsigned long flags,

        void __iomem *reg, u8 shift, u32 mask,

        u8 clk_mux_flags, u32 *table, spinlock_t *lock);

 

void clk_unregister_mux(struct clk *clk);

clk_register_mux : 该接口可注册mux控制比较规则的clock(类似divider clock

         parent_names一个字符串数组,用于描述所有可能的parent clock

         num_parentsparent clock的个数

         regshiftwidth选择parent的寄存器、偏移、宽度

         clk_mux_flagsmux clock特有的flag

CLK_MUX_INDEX_ONE : 寄存器值不是从0开始,而是从1开始

CLK_MUX_INDEX_BIT : 寄存器值为2

 

clk_register_mux_table : 该接口通过一个table, 注册mux控制不规则的clock, 原理和divider clock类似, 不再详细介绍

 

同样, clock子系统并没有实现DTS相关接口, 不过你可以自己编写driver去解析DTS并调用API注册.

clk_register_fixed_factor

这一类clock具有固定的factor(即multiplierdividerclock的频率是由parent clock的频率, 乘以mul, 除以div, 多用于一些具有固定分频系数的clock.

由于parent clock的频率可以改变, 因而fix factor clock也可改变频率, 因此也会提供.recalc_rate/.set_rate/.round_rate等回调.

 

可通过下面接口注册:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fixed-factor.c

void of_fixed_factor_clk_setup(struct device_node *node);

 

/**

* struct clk_fixed_factor - fixed multiplier and divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @mult:   multiplier

* @div:    divider

*

* Clock with a fixed multiplier and divider. The output frequency is the

* parent clock rate divided by div and multiplied by mult.

* Implements .recalc_rate, .set_rate and .round_rate

*/

 

struct clk_fixed_factor {

    struct clk_hw   hw;

    unsigned int    mult;

    unsigned int    div;

};

 

extern struct clk_ops clk_fixed_factor_ops;

struct clk *clk_register_fixed_factor(struct device *dev, const char *name,

        const char *parent_name, unsigned long flags,

        unsigned int mult, unsigned int div);

API 参数比较简单, 不多说了.

 

另外, clock子系统还提供了此种类型clockDTS接口.

clk-fixed-factor.c中的of_fixed_factor_clk_setup函数会负责解析DTS. 关于DTS的匹配规则和相关参数, 自己看看源码吧.

clk_register_fractional_divider

这一类和divider clock很像, 唯一的不同在于它可支持到更细的粒度 (可用小数表示分频因子) . 例如 clk = parent / 1.5

 

API说明如下:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fractional-divider.c

/**

* struct clk_fractional_divider - adjustable fractional divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register containing the divider

* @mshift: shift to the numerator bit field

* @mwidth: width of the numerator bit field

* @nshift: shift to the denominator bit field

* @nwidth: width of the denominator bit field

* @lock:   register lock

*

* Clock with adjustable fractional divider affecting its output frequency.

*/

 

struct clk_fractional_divider {

    struct clk_hw   hw;

    void __iomem    *reg;

    u8      mshift;

    u32     mmask;

    u8      nshift;

    u32     nmask;

    u8      flags;

    spinlock_*lock;

};

 

extern const struct clk_ops clk_fractional_divider_ops;

struct clk *clk_register_fractional_divider(struct device *dev,

        const char *name, const char *parent_name, unsigned long flags,

        void __iomem *reg, u8 mshift, u8 mwidth, u8 nshift, u8 nwidth,

        u8 clk_divider_flags, spinlock_t *lock);

 

clock子系统没有提供相关的DTS解析函数.

clk_register_composite

顾名思义, 就是muxdividergateclock的组合, 可通过下面接口注册:

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-composite.c

/***

* struct clk_composite - aggregate clock of mux, divider and gate clocks

*

* @hw:     handle between common and hardware-specific interfaces

* @mux_hw: handle between composite and hardware-specific mux clock

* @rate_hw:    handle between composite and hardware-specific rate clock

* @gate_hw:    handle between composite and hardware-specific gate clock

* @mux_ops:    clock ops for mux

* @rate_ops:   clock ops for rate

* @gate_ops:   clock ops for gate

*/

struct clk_composite {

    struct clk_hw   hw;

    struct clk_ops  ops;

 

    struct clk_hw   *mux_hw;

    struct clk_hw   *rate_hw;

    struct clk_hw   *gate_hw;

 

    const struct clk_ops    *mux_ops;

    const struct clk_ops    *rate_ops;

    const struct clk_ops    *gate_ops;

};

 

struct clk *clk_register_composite(struct device *dev, const char *name,

        const char **parent_names, int num_parents,

        struct clk_hw *mux_hw, const struct clk_ops *mux_ops,

        struct clk_hw *rate_hw, const struct clk_ops *rate_ops,

        struct clk_hw *gate_hw, const struct clk_ops *gate_ops,

        unsigned long flags);

看着有点复杂, 但理解了上面1~5clock, 这里就只剩下苦力了, 耐心一点,就可以了.

 

另外, clock子系统没有提供相关的DTS解析函数.

clk_register_gpio_gate

把某个gpio当做一个gate clock. 也就是说可以通过clock子系统来控制这个gpio /, 也就是控制这个GPIO输出高/低电平.

 

某些情况下, 有的模块需要通过某个GPIO来控制其使能/禁止, 例如蓝牙模块. 而且你也不需要向此模块提供时钟, 模板本身就有晶振存在, 可以自己给自己供时钟.

这个时候我们就可以把该GPIO抽象成gpio  gate  clock, 借助clock子系统, 来控制模块的enable/disable.

 

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-gpio-gate.c

/***

* struct clk_gpio_gate - gpio gated clock

*

* @hw:     handle between common and hardware-specific interfaces

* @gpiod:  gpio descriptor

*

* Clock with a gpio control for enabling and disabling the parent clock.

* Implements .enable, .disable and .is_enabled

*/

 

struct clk_gpio {

    struct clk_hw   hw;

    struct gpio_desc *gpiod;

};

 

extern const struct clk_ops clk_gpio_gate_ops;

struct clk *clk_register_gpio_gate(struct device *dev, const char *name,

        const char *parent_name, unsigned gpio, bool active_low,

        unsigned long flags);

 

void of_gpio_clk_gate_setup(struct device_node *node);

API 参数比较简单, 不多说了.

 

另外, clock子系统还提供了此种类型clockDTS接口.

xxx_unregister

上述xxx_register函数都有对应的xxx_unregister.

unregister的函数原型在上述代码中都有提及, 这里不多说了, 有兴趣可以自行阅读源代码.

clk_register_clkdev

头文件: include/linux/clkdev.h

实现文件: drivers/clk/clkdev.c

原型: int clk_register_clkdev(struct clk *, const char *, const char *, ...);

 

上述的clk_register_xxx接口会向clock子系统注册一个clock, 并返回一个代表该clockstruct clk结构体.

clk_register_clkdev的作用就是把返回的这个struct clk添加到某个池子里面.

 

这个池子其实就是个链表啦, 链表定义在clkdev.c里面.

池子里面主要是用name做为关键字, 区分不同的clk.

 

consumer端想要查询某个clk的时候, 就会尝试从这个链表里面通过clock name去检索.

of_clk_add_provider

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk.c

原型:

int of_clk_add_provider(struct device_node *np,

            struct clk *(*clk_src_get)(struct of_phandle_args *args,

                           void *data),

            void *data);

 

API的主要目的也是把得到的struct clk结构体添加到某个池子里面.

 

这个池子也是个链表, 定义在clk.c里面.

与上面那个池子的不同之处在于, 这里是用device_node做为关键字来区分不同的clk.

 

consumer端想要查询某个clk的时候, 会在DTS node里面通过clocks = <&xxxx>来引用某个clock.

通过引用的这个clockphandle, 就能找到对应的device_node. 然后通过device_node就能从池子里面检索出需要的clk.

 

其实, 这两种池子对应了我们的consumer端的两种方式: 一种是通过name获取clk, 不需要DTS;  另外一种就是通过DTS.

随着Linux内核大力推行DTS, 方式二会逐渐成为主流.

3.   clock core -- 如何管理Clocks

4.1             简介

3章我们描述了clock子系统的功能之一 : 向底层的clock driver提供注册接口.

本章我们描述clock子系统的功能之二 : 如何管理这些clocks.

 

clock driver向子系统注册某一个clock的时候, 子系统内部会创建一个数据结构来表示这个clock. 这个数据结构在前文提过, 就是struct clk.

 

一个struct clk就对应一个clock. 到目前为止, 我们还没见过这个数据结构的庐山真面目呢! 本章会围绕这个数据结构展开, 充分理解它, 你就里面本章了.

4.2             主要数据结构

struct clk

结构体定义在一个C文件里面 : drivers/clk/clk.c

struct  clk

Comment

struct clk_core*core

clk_coreclock核心层的一个私有数据结构, 下面细述

const char *dev_id

使用该clkdevicename

const char *con_id

connection ID string on device

unsigned long min_rate

最小rate

unsigned long max_rate

最大rate

struct hlist_node clks_node

这个结构体有点颠覆本文的一个观点, 那就是: 一个struct clk结构体对应一个具体的clock.

 

仔细阅读源码后发现, 原来每当某一个driver尝试获取某个clock的时候, clock子系统就会创建一个struct clk.

例如假设有个clock, 名称是pll_clk.  GPIO, SPI, I2C3个模块都是用此pll_clk做为工作时钟的.

那么clock子系统核心层会有一个clk_core用于描述pll_clk, 每当GPIO/SPI/I2Cdriver首次尝试获取pll_clk, clock子系统就会创建一个struct clk结构体, 并将创建的这个struct clk返回给对应的driver. clk结构体的dev_idcon_id都是对应的driver指定的.

 

不过为了简便起见, 我们在本文中还是暂定一个struct clk对应一个clock, 理解起来不会有什么异常.

struct clk_core

前文我们说过, 一个struct clk就代表一个clock. 一个clk_coreclock也是一一对应的关系. 那它俩有什么区别呢?

 

struct clk更像是对外的接口, 例如当providerclock子系统注册时, 它会得到一个struct clk*的返回结果; consumer想要使用某个clock, 也会首先获取struct clk*这个结构体.

 

struct clk_core则是clock子系统核心层的一个私有数据结构, 核心层代描述某个具体的clock. providerconsumer不会触碰这个数据结构.

 

结构体定义在一个C文件里面 : drivers/clk/clk.c

struct  clk_core

Comment

const char*name

clockname,  provider在注册的时候会提供clockname

const struct clk_ops*ops

操作此clockops,  provider在注册的时候会提供clockops

struct clk_hw*hw

provider端提供clk_hw结构体

struct module*owner

 

struct clk_core*parent

一个clock可能有多个parent, 但是同一时刻只能有一个parent有效.

这里指向有效的那个父时钟所对应的clk_core

const char**parent_names

所有可选的父时钟的names

struct clk_core**parents

所有可选的父时钟的clk_core

u8num_parents

有多少个parents

u8new_parent_index

你可以把所有的parents理解成一个数组, index就对应数组的某个元素

unsigned longrate

clockrate

unsigned longreq_rate

consumer端要求rate

unsigned longnew_rate

新的rate

struct clk_core*new_parent

新的parent

struct clk_core*new_child

新的child

unsigned longflags

clockflags,  可选的flag3.3节《struct clk_init_data》中有介绍

unsigned intenable_count

被使能的次数

unsigned intprepare_count

prepare的次数

unsigned longaccuracy

clock当前的accuracy, The clock accuracy is expressed in ppb (parts per billion)

intphase

clock当前的phase, 取值范围 0 - 359

struct hlist_headchildren

链表头, 用于挂接本clock所有的children

struct hlist_nodechild_node

链表节点, 用于把本clock挂接到对应的parentchildren链表下

struct hlist_nodedebug_node

链表节点, debugfs相关

struct hlist_headclks

一个struct clk_core可能对应多个struct clk, 这个链表头挂接所有的clks.

关于struct clk_corestruct clk的关系, 参见4.2节《struct clk》的说明

unsigned intnotifier_count

内核通知链机制相关.

当内核其它模块想知道某个clock的变化时 (例如频率等的改变), 可以向此clock的通知链注册. 这样当clock发生变化时, 就会向通知链上的所有模块发送通知

notifier_count代表有多少个模块向本clock注册了

struct dentry*dentry

debugfs相关

struct krefref

引用计数

4.3  关键代码分析

clk_register

我们在3.4节介绍了clock子系统提供给provider端的总clk_register_xxx函数. 这些函数都是对clk_register的封装, 基础都是clk_register.

 

因此我们本章只重点讲解这一个API, 其它API有兴趣可以自己阅读源码.

 

在开始分析之前, 结合之前讲解的内容, 你能猜到clk_register会做哪些事情吗?

首先, 得创建struct clkstruct clk_core这两个数据结构.

然后, 它得维护clk_core的树形关系, 就像时钟数的硬件形态那样:

有一个或多个ROOT_CLOCK, ROOT_CLOCK没有parent, 下面挂载的是children, children下面在挂载children.

为什么要维护这样的树形结构呢? 因为我们在操作某个clk, 往往会跟它的parent有关: 例如要使能某个clk, 必须保证它的parent也使能; 或者parentrate改变了, 那它的childrenrate都有可能改变. 因此clock子系统必须维护好树形结构, 才能方便的处理这些相关性.

 

下面我们来看看代码:

实现文件: drivers/clk/clk.c

/**

* clk_register - allocate a new clock, register it and return an opaque cookie

* @dev: device that is registering this clock

* @hw: link to hardware-specific clock data

*

* clk_register is the primary interface for populating the clock tree with new

* clock nodes.  It returns a pointer to the newly allocated struct clk which

* cannot be dereferenced by driver code but may be used in conjuction with the

* rest of the clock API.  In the event of an error clk_register will return an

* error code; drivers must test for an error code after calling clk_register.

*/

struct clk *clk_register(struct device *dev, struct clk_hw *hw)

{

    int i, ret;

    struct clk_core *clk;

 

    clk = kzalloc(sizeof(*clk), GFP_KERNEL);

    if (!clk) {

        pr_err("%s: could not allocate clk\n", __func__);

        ret = -ENOMEM;

        goto fail_out;

    }

 

    clk->name = kstrdup_const(hw->init->name, GFP_KERNEL);

    if (!clk->name) {

        pr_err("%s: could not allocate clk->name\n", __func__);

        ret = -ENOMEM;

        goto fail_name;

    }

    clk->ops = hw->init->ops;

    if (dev && dev->driver)

        clk->owner = dev->driver->owner;

    clk->hw = hw;

    clk->flags = hw->init->flags;

    clk->num_parents = hw->init->num_parents;

    hw->core = clk;

 

    /* allocate local copy in case parent_names is __initdata */

    clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),

                    GFP_KERNEL);

 

    if (!clk->parent_names) {

        pr_err("%s: could not allocate clk->parent_names\n", __func__);

        ret = -ENOMEM;

        goto fail_parent_names;

    }

 

 

    /* copy each string name in case parent_names is __initdata */

    for (i = 0; i < clk->num_parents; i++) {

        clk->parent_names[i] = kstrdup_const(hw->init->parent_names[i],

                        GFP_KERNEL);

        if (!clk->parent_names[i]) {

            pr_err("%s: could not copy parent_names\n", __func__);

            ret = -ENOMEM;

            goto fail_parent_names_copy;

        }

    }

 

    INIT_HLIST_HEAD(&clk->clks);

 

    hw->clk = __clk_create_clk(hw, NULL, NULL);

    if (IS_ERR(hw->clk)) {

        pr_err("%s: could not allocate per-user clk\n", __func__);

        ret = PTR_ERR(hw->clk);

        goto fail_parent_names_copy;

    }

 

    ret = __clk_init(dev, hw->clk);

    if (!ret)

        return hw->clk;

 

    __clk_free_clk(hw->clk);

    hw->clk = NULL;

 

fail_parent_names_copy:

    while (--i >= 0)

        kfree_const(clk->parent_names[i]);

    kfree(clk->parent_names);

fail_parent_names:

    kfree_const(clk->name);

fail_name:

    kfree(clk);

fail_out:

    return ERR_PTR(ret);

}

EXPORT_SYMBOL_GPL(clk_register);

上面这段代码的逻辑比较简单:

         首先, 创建了clk_core结构体, 然后用provider提供的clk_hw, 填充clk_core中的各个字.

         然后, 调用 __clk_create_clk, __clk_create_clk里面会创建struct clk这个结构体并初始化其相关字段. clk_register会返回给provider一个struct clk*的结构体指针, 这个clk*就是这里创建的.

下文我们会单独介绍__clk_create_clk这个函数.

         最后, 会调用__clk_init, __clk_init里面会处理clk_core之间的树形结构关系.

下文我们会单独介绍__clk_init这个函数.

__clk_create_clk

这个API会创建一个struct clk结构体.

要理解此API的细节, 首先得理清楚struct clkstruct clk_core的关系.

 

我们在4.2节《struct clk_core》中已经初步介绍了clkclk_core的关系, 可以回头看看.

这里我们在做进一步的说明.

还记得我们在《字符设备驱动》一文中介绍过的struct filestruct inode这两个结构体吗? inode在物理上代表一个文件, 一个文件对应唯一一个inode; file则代表一个打开的文件, 文件被打开几次, 就会有几个file.

clkclk_core的关系与上面类型, clk_core代表一个硬件上的clock, 一个clock对应唯一一个clk_core; clk则代表被使用的clock, 例如GPIO driver想要获取某个clock, 它会得到一个struct clk, SPI driver想要获取同一个clock, 它也会得到一个struct clk, 另外, 当此clockproviderclock子系统注册时, provider也会得到一个struct clk.

 

接下来我们看看代码细节吧:

struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,

                 const char *con_id)

{

    struct clk *clk;

 

    /* This is to allow this function to be chained to others */

    if (!hw || IS_ERR(hw))

        return (struct clk *) hw;

 

    clk = kzalloc(sizeof(*clk), GFP_KERNEL);

    if (!clk)

        return ERR_PTR(-ENOMEM);

 

    clk->core = hw->core;

    clk->dev_id = dev_id;

    clk->con_id = con_id;

    clk->max_rate = ULONG_MAX;

 

    clk_prepare_lock();

    hlist_add_head(&clk->clks_node, &hw->core->clks);

    clk_prepare_unlock();

 

    return clk;

}

逻辑比较简单:

         首先创建struct clk结构体, 然后初始化结构体的相关参数, 最后把这个struct clk挂载到clk_coreclks链表头下面.

__clk_init

这个API主要是处理clock之间的树形关系.

当任何一个clock调用clk_register接口进行注册时, __clk_init都会负责把这个clock放在树形结构的恰当位置.

 

代码细节如下:

   1: /**

   2:  * __clk_init - initialize the data structures in a struct clk

   3:  * @dev:    device initializing this clk, placeholder for now

   4:  * @clk:    clk being initialized

   5:  *

   6:  * Initializes the lists in struct clk, queries the hardware for the

   7:  * parent and rate and sets them both.

   8:  */

   9: int __clk_init(struct device *dev, struct clk *clk)

  10: {

  11:     int i, ret = 0;

  12:     struct clk *orphan;

  13:     struct hlist_node *tmp2;

  14: 

  15:     if (!clk)

  16:         return -EINVAL;

  17: 

  18:     clk_prepare_lock();

  19: 

  20:     /* check to see if a clock with this name is already registered */

  21:     if (__clk_lookup(clk->name)) {

  22:         pr_debug("%s: clk %s already initialized\n",

  23:                 __func__, clk->name);

  24:         ret = -EEXIST;

  25:         goto out;

  26:     }

  27: 

  28:     /* check that clk_ops are sane.  See Documentation/clk.txt */

  29:     if (clk->ops->set_rate &&

  30:             !(clk->ops->round_rate && clk->ops->recalc_rate)) {

  31:         pr_warning("%s: %s must implement .round_rate & .recalc_rate\n",

  32:                 __func__, clk->name);

  33:         ret = -EINVAL;

  34:         goto out;

  35:     }

  36: 

  37:     if (clk->ops->set_parent && !clk->ops->get_parent) {

  38:         pr_warning("%s: %s must implement .get_parent & .set_parent\n",

  39:                 __func__, clk->name);

  40:         ret = -EINVAL;

  41:         goto out;

  42:     }

  43: 

  44:     /* throw a WARN if any entries in parent_names are NULL */

  45:     for (i = 0; i < clk->num_parents; i++)

  46:         WARN(!clk->parent_names[i],

  47:                 "%s: invalid NULL in %s's .parent_names\n",

  48:                 __func__, clk->name);

  49: 

  50:     /*

  51:      * Allocate an array of struct clk *'s to avoid unnecessary string

  52:      * look-ups of clk's possible parents.  This can fail for clocks passed

  53:      * in to clk_init during early boot; thus any access to clk->parents[]

  54:      * must always check for a NULL pointer and try to populate it if

  55:      * necessary.

  56:      *

  57:      * If clk->parents is not NULL we skip this entire block.  This allows

  58:      * for clock drivers to statically initialize clk->parents.

  59:      */

  60:     if (clk->num_parents > 1 && !clk->parents) {

  61:         clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents),

  62:                 GFP_KERNEL);

  63:         /*

  64:          * __clk_lookup returns NULL for parents that have not been

  65:          * clk_init'd; thus any access to clk->parents[] must check

  66:          * for a NULL pointer.  We can always perform lazy lookups for

  67:          * missing parents later on.

  68:          */

  69:         if (clk->parents)

  70:             for (i = 0; i < clk->num_parents; i++)

  71:                 clk->parents[i] =

  72:                     __clk_lookup(clk->parent_names[i]);

  73:     }

  74: 

  75:     clk->parent = __clk_init_parent(clk);

  76: 

  77:     /*

  78:      * Populate clk->parent if parent has already been __clk_init'd.  If

  79:      * parent has not yet been __clk_init'd then place clk in the orphan

  80:      * list.  If clk has set the CLK_IS_ROOT flag then place it in the root

  81:      * clk list.

  82:      *

  83:      * Every time a new clk is clk_init'd then we walk the list of orphan

  84:      * clocks and re-parent any that are children of the clock currently

  85:      * being clk_init'd.

  86:      */

  87:     if (clk->parent)

  88:         hlist_add_head(&clk->child_node,

  89:                 &clk->parent->children);

  90:     else if (clk->flags & CLK_IS_ROOT)

  91:         hlist_add_head(&clk->child_node, &clk_root_list);

  92:     else

  93:         hlist_add_head(&clk->child_node, &clk_orphan_list);

  94: 

  95:     /*

  96:      * Set clk's rate.  The preferred method is to use .recalc_rate.  For

  97:      * simple clocks and lazy developers the default fallback is to use the

  98:      * parent's rate.  If a clock doesn't have a parent (or is orphaned)

  99:      * then rate is set to zero.

100:      */

 101:     if (clk->ops->recalc_rate)

 102:         clk->rate = clk->ops->recalc_rate(clk->hw,

 103:                 __clk_get_rate(clk->parent));

 104:     else if (clk->parent)

 105:         clk->rate = clk->parent->rate;

 106:     else

 107:         clk->rate = 0;

 108: 

 109:     /*

110:      * walk the list of orphan clocks and reparent any that are children of

111:      * this clock

112:      */

 113:     hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {

 114:         if (orphan->ops->get_parent) {

 115:             i = orphan->ops->get_parent(orphan->hw);

 116:             if (!strcmp(clk->name, orphan->parent_names[i]))

 117:                 __clk_reparent(orphan, clk);

 118:             continue;

 119:         }

 120: 

 121:         for (i = 0; i < orphan->num_parents; i++)

 122:             if (!strcmp(clk->name, orphan->parent_names[i])) {

 123:                 __clk_reparent(orphan, clk);

 124:                 break;

 125:             }

 126:      }

 127: 

 128:     /*

129:      * optional platform-specific magic

130:      *

131:      * The .init callback is not used by any of the basic clock types, but

132:      * exists for weird hardware that must perform initialization magic.

133:      * Please consider other ways of solving initialization problems before

134:      * using this callback, as it's use is discouraged.

135:      */

 136:     if (clk->ops->init)

 137:         clk->ops->init(clk->hw);

 138: 

 139:     clk_debug_register(clk);

 140: 

 141: out:

 142:     clk_prepare_unlock();

 143: 

 144:     return ret;

 145: }

这一段代码的逻辑很复杂, 主要做的事情如下:

         20~26行,以clock name为参数,调用__clk_lookup接口,查找是否已有相同nameclock注册,如果有,则返回错误。由此可以看出,clock frameworkname唯一识别一个clock,因此不能有同名的clock存在

         28~42行,检查clk ops的完整性,例如:如果提供了set_rate接口,就必须提供round_raterecalc_rate接口;如果提供了set_parent,就必须提供get_parent

         50~73行,分配一个struct clk *类型的数组,缓存该clockparents clock。具体方法是根据parents_name,查找相应的struct clk指针

         75行,获取当前的parent clock,并将其保存在parent指针中。具体可参考下面“说明2

         77~93行,根据该clock的特性,将它添加到clk_root_listclk_orphan_list或者parent->children三个链表中的一个,具体请参考下面“说明1

         95~107行,计算clock的初始rate,具体请参考下面“说明3

         109~126行,尝试reparent当前所有的孤儿(orphanclock,具体请参考下面“说明4

         128~137行,如果clock ops提供了init接口,执行之(由注释可知,kernel不建议提供init接口)

 

说明1: clock的管理和查询

clock framework2条全局的链表:clk_root_listclk_orphan_list。所有设置了CLK_IS_ROOT属性的clock都会挂在clk_root_list中。其它clock,如果有validparent ,则会挂到parent的“children”链表中,如果没有validparent,则会挂到clk_orphan_list中。

查询时(__clk_lookup接口做的事情),依次搜索:

clk_root_list-->root_clk-->children-->child's children

clk_orphan_list-->orphan_clk-->children-->child's children

即可

 

说明2:当前parent clock的选择(__clk_init_parent

对于没有parent,或者只有1parent clock来说,比较简单,设置为NULL,或者根据parent name获得parentstruct clk指针接。

对于有多个parentclock,就必须提供.get_parent ops,该ops要根据当前硬件的配置情况,例如寄存器值,返回当前所有使用的parentindex(即第几个parent)。然后根据index,取出对应parent clockstruct clk指针,作为当前的parent

 

说明3: clock的初始rate计算

对于提供.recalc_rate opsclock来说,优先使用该ops获取初始的rate。如果没有提供,退而求其次,直接使用parent clockrate。最后,如果该clock没有parent,则初始的rate只能选择为0

.recalc_rate ops的功能,是以parent clockrate为输入参数,根据当前硬件的配置情况,如寄存器值,计算获得自身的rate

 

说明4orphan clocksreparent

有些情况下,child clock会先于parent clock注册,此时该child就会成为orphan clock,被收养在clk_orphan_list中。

而每当新的clock注册时,kernel都会检查这个clock是否是某个orphanparent,如果是,就把这个orphanclk_orphan_list中移除,放到新注册的clock的怀抱。这就是reparent的功能,它的处理逻辑是:

         遍历orphan list,如果orphan提供了.get_parent ops,则通过该ops得到当前parentindex,并从parent_names中取出该parentname,然后和新注册的clock name比较,如果相同,呵呵,找到parent了,执行__clk_reparent,进行后续的操作

         如果没有提供.get_parent ops,只能遍历自己的parent_names,检查是否有和新注册clock匹配的,如果有,执行__clk_reparent,进行后续的操作

         __clk_reparent会把这个orphanclk_orphan_list中移除,并挂到新注册的clock上。然后调用__clk_recalc_rates,重新计算自己以及自己所有childrenrate。计算过程和上面的clock rate设置类似

4.   clock consumer -- 如何使用Clocks

5.1        简介

clock 子系统管理clocks的最终目的, 是让device driver可以方便的获取并使用这些clocks.

 

我们知道clock子系统用一个struct clk结构体来抽象某一个clock.

device driver要操作某个clock, 它需要做两件事情:

         首先, 获取clock. 也叫clk_get.

         然后, 操作这个clock. clk_prepare/ clk_enable/ clk_disable/ clk_set_rate/

 

举个例子, GPIO控制器为例. 从硬件的角度来说, GPIO控制器需要工作时钟. 因此, GPIO控制器的platform_driverprobe函数里面, 就需要操作这个时钟.

 

操作的第一步是获取clock, 获取clock有两种方式:

 

第一种方式是直接通过name来获取, 例如假设已知GPIO控制器的工作时钟的namegpio_clk, 那么我们就可以在probe函数里面通过clk_get(&device, “gpio_clk”) 这种方式获取.

 

另外一种方式是说, 对于GPIO控制器来讲, 我们会有一个platform_device, 在里面描述GPIO控制器的资源, 也就是寄存器地址, 中断号什么的. GPIO控制器的工作时钟也算一种资源了, 我们能否在platform_device里面一并描述这个资源呢?

答案是可以. platform_device现在都是在DTS中描述的, 因此这个GPIO控制器的DTS可以这样写:

gpio : gpio-controller@xxxx {

compatible = yyyy;

reg = <.>;

……

clocks = <&theclock>;  /* 指明/引用某一个clock */

}

我们用clocks这个property来描述时钟资源.

在对应的platform_driverprobe函数里面, 我们就可以通过clk_get(&device, NULL)这种方式来获取所需的clock.

这种方式下, 我们只需要知道platform_device, 然后就能通过它找到对应的DTS node, 然后就能通过DTS  nodeclocks property获取到对应的时钟资源.

 

获取到clk之后, 就可以通过clock子系统提供的API来操作clk.

 

clock子系统提供给consumer端的APIs都定义在头文件 include/linux/clk.h里面.

 

接下来的章节, 我们会分两部分来介绍这些APIs.

5.2节介绍获取clk相关的APIs.

5.3节介绍操作clk相关的APIs.

5.2        APIs 获取clk

头文件: include/linux/clk.h

实现文件: drivers/clk/clkdev.c

 

最主要的两个API:

struct clk *clk_get(struct device *dev, const char *id);

 

struct clk *devm_clk_get(struct device *dev, const char *id);

devm_clk_getdevm版本的clk_get. 用于自动释放资源, 我们在《设备模型》一文中有过介绍, 这里不多说了.

 

除了这两个用的最多的API之外, 还有一些其他的API, 如下:

struct clk *clk_get_sys(const char *dev_id, const char *con_id)

 

struct clk *of_clk_get(struct device_node *np, int index);

struct clk *of_clk_get_by_name(struct device_node *np, const char *name);

struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);

 

clk_get相当于一个总逻辑, 它会根据不同的情况调用上述这些API, 在实际代码中, 基本上我们只会用到clk_get或者devm_clk_get.

 

接下来, 我们会仔细看看clk_get的内部逻辑.

clk_get / devm_clk_get

头文件: include/linux/clk.h

实现文件: drivers/clk/clkdev.c

原型: struct clk *clk_get(struct device *dev, const char *id);

API的主要作用就是根据参数, clock子系统中获取一个clk给到consumer, 然后consumer就可以操作该clk. 因此该API的返回值就是用于描述某个clock的数据结构: struct clk.

 

代码细节如下:

struct clk *clk_get(struct device *dev, const char *con_id)

{

    const char *dev_id = dev ? dev_name(dev) : NULL;

    struct clk *clk;

 

    if (dev) {

        clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);

        if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)

            return clk;

    }

 

    return clk_get_sys(dev_id, con_id);

}

EXPORT_SYMBOL(clk_get);

如果你已经充分理解了2.3节的那个系统框图, 那么此处的逻辑就很简单了:

         如果dev不为空, 那么就以dev->of_node为参数, 调用of_XXX那一套, LIST_HEAD(of_clk_providers)这个池子里面查询某个clk.

如果查询到了, 则返回该clk. 这种情况其实对应5.1节中描述的DTS node方式.

         如果上面没有获取到clk, 则调用clk_get_sys, LIST_HEAD(clocks)这个池子里面查询clk. 查询的关键字是con_id, 其实就是clockname.

5.3        APIs 操作clk

头文件: include/linux/clk.h

实现文件: drivers/clk/clk.c

   1: int clk_prepare(struct clk *clk)

   2: void clk_unprepare(struct clk *clk)

   3: 

   4: static inline int clk_enable(struct clk *clk)

   5: static inline void clk_disable(struct clk *clk)

   6: 

   7: static inline unsigned long clk_get_rate(struct clk *clk)

   8: static inline int clk_set_rate(struct clk *clk, unsigned long rate)

   9: static inline long clk_round_rate(struct clk *clk, unsigned long rate)

  10: 

  11: static inline int clk_set_parent(struct clk *clk, struct clk *parent)

  12: static inline struct clk *clk_get_parent(struct clk *clk)

  13: 

  14: static inline int clk_prepare_enable(struct clk *clk)

  15: static inline void clk_disable_unprepare(struct clk *clk)

         clk_enable/clk_disable: 启动/停止clock. 不会睡眠

         clk_prepare/clk_unprepare: 启动clock前的准备工作/停止clock后的善后工作. 可能会睡眠

         clk_get_rate/clk_set_rate/clk_round_rate: clock频率的获取和设置, 其中clk_set_rate可能会不成功(例如没有对应的分频比), 此时会返回错误. 如果要确保设置成功, 则需要先调用clk_round_rate接口, 得到和需要设置的rate比较接近的那个值

         clk_set_parent / clk_get_parent: 获取/选择clockparent clock

         clk_prepare_enableclk_prepareclk_enable组合起来,一起调用

         clk_disable_unprepare: clk_disableclk_unprepare组合起来,一起调用

 

prepare/unprepareenable/disable的说明:

这两套API的本质,是把clock的启动/停止分为atomicnon-atomic两个阶段,以方便实现和调用。

因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:

一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable

二是提醒上层使用clockdriver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用哦,而调用enable/disable接口则可放心。

 

另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU

 

最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enabledisable/unprepared,为了简单,framework就帮忙封装了这两个接口。

5.4        其它APIs

头文件: include/linux/clk.h

实现文件: drivers/clk/clk.c

   1: int clk_notifier_register(struct clk *clk, struct notifier_block *nb);

   2: int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

这两个API与内核提供的通知链机制有关.

这两个notify接口, 用于注册/注销 clock rate改变的通知.

例如某个driver关心某个clock, 期望这个clockrate改变时, 通知到自己, 就可以注册一个notify.


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM