前面三节分析的3.16.57内核中,三星的驱动中,恰好S5PV210的CPU没有使用Linux common clock framework来处理clk。所以,本文采用4.9.92的内核来分析S5PV210对前面第三节中时钟注册部分的使用。对于老的时钟框架,后面章节可能会用1到2部分说明一下。
首先是根据设备树传过来来的时钟资源的寄存器地址做地址映射,后面则就是使用设备树节点做大量的时钟注册了。
static void __init s5pv210_clk_dt_init(struct device_node *np)
{
reg_base = of_iomap(np, 0);
if (!reg_base)
panic("%s: failed to map registers\n", __func__);
__s5pv210_clk_init(np, 0, 0, false);
}
CLK_OF_DECLARE(s5pv210_clk, "samsung,s5pv210-clock", s5pv210_clk_dt_init);
在内核的下面路径中定义了s5pv210的所有的时钟。
include/dt-bindings/clock/s5pv210.h
下面列出这些时钟的编号,后面所有时钟都会使用自己的编号识别。
#ifndef _DT_BINDINGS_CLOCK_S5PV210_H
#define _DT_BINDINGS_CLOCK_S5PV210_H
/* Core clocks. */
#define FIN_PLL 1
#define FOUT_APLL 2
#define FOUT_MPLL 3
#define FOUT_EPLL 4
#define FOUT_VPLL 5
/* Muxes. */
#define MOUT_FLASH 6
#define MOUT_PSYS 7
#define MOUT_DSYS 8
#define MOUT_MSYS 9
#define MOUT_VPLL 10
#define MOUT_EPLL 11
#define MOUT_MPLL 12
#define MOUT_APLL 13
#define MOUT_VPLLSRC 14
#define MOUT_CSIS 15
#define MOUT_FIMD 16
#define MOUT_CAM1 17
#define MOUT_CAM0 18
#define MOUT_DAC 19
#define MOUT_MIXER 20
#define MOUT_HDMI 21
#define MOUT_G2D 22
#define MOUT_MFC 23
#define MOUT_G3D 24
#define MOUT_FIMC2 25
#define MOUT_FIMC1 26
#define MOUT_FIMC0 27
#define MOUT_UART3 28
#define MOUT_UART2 29
#define MOUT_UART1 30
#define MOUT_UART0 31
#define MOUT_MMC3 32
#define MOUT_MMC2 33
#define MOUT_MMC1 34
#define MOUT_MMC0 35
#define MOUT_PWM 36
#define MOUT_SPI0 37
#define MOUT_SPI1 38
#define MOUT_DMC0 39
#define MOUT_PWI 40
#define MOUT_HPM 41
#define MOUT_SPDIF 42
#define MOUT_AUDIO2 43
#define MOUT_AUDIO1 44
#define MOUT_AUDIO0 45
/* Dividers. */
#define DOUT_PCLKP 46
#define DOUT_HCLKP 47
#define DOUT_PCLKD 48
#define DOUT_HCLKD 49
#define DOUT_PCLKM 50
#define DOUT_HCLKM 51
#define DOUT_A2M 52
#define DOUT_APLL 53
#define DOUT_CSIS 54
#define DOUT_FIMD 55
#define DOUT_CAM1 56
#define DOUT_CAM0 57
#define DOUT_TBLK 58
#define DOUT_G2D 59
#define DOUT_MFC 60
#define DOUT_G3D 61
#define DOUT_FIMC2 62
#define DOUT_FIMC1 63
#define DOUT_FIMC0 64
#define DOUT_UART3 65
#define DOUT_UART2 66
#define DOUT_UART1 67
#define DOUT_UART0 68
#define DOUT_MMC3 69
#define DOUT_MMC2 70
#define DOUT_MMC1 71
#define DOUT_MMC0 72
#define DOUT_PWM 73
#define DOUT_SPI1 74
#define DOUT_SPI0 75
#define DOUT_DMC0 76
#define DOUT_PWI 77
#define DOUT_HPM 78
#define DOUT_COPY 79
#define DOUT_FLASH 80
#define DOUT_AUDIO2 81
#define DOUT_AUDIO1 82
#define DOUT_AUDIO0 83
#define DOUT_DPM 84
#define DOUT_DVSEM 85
/* Gates */
#define SCLK_FIMC 86
#define CLK_CSIS 87
#define CLK_ROTATOR 88
#define CLK_FIMC2 89
#define CLK_FIMC1 90
#define CLK_FIMC0 91
#define CLK_MFC 92
#define CLK_G2D 93
#define CLK_G3D 94
#define CLK_IMEM 95
#define CLK_PDMA1 96
#define CLK_PDMA0 97
#define CLK_MDMA 98
#define CLK_DMC1 99
#define CLK_DMC0 100
#define CLK_NFCON 101
#define CLK_SROMC 102
#define CLK_CFCON 103
#define CLK_NANDXL 104
#define CLK_USB_HOST 105
#define CLK_USB_OTG 106
#define CLK_HDMI 107
#define CLK_TVENC 108
#define CLK_MIXER 109
#define CLK_VP 110
#define CLK_DSIM 111
#define CLK_FIMD 112
#define CLK_TZIC3 113
#define CLK_TZIC2 114
#define CLK_TZIC1 115
#define CLK_TZIC0 116
#define CLK_VIC3 117
#define CLK_VIC2 118
#define CLK_VIC1 119
#define CLK_VIC0 120
#define CLK_TSI 121
#define CLK_HSMMC3 122
#define CLK_HSMMC2 123
#define CLK_HSMMC1 124
#define CLK_HSMMC0 125
#define CLK_JTAG 126
#define CLK_MODEMIF 127
#define CLK_CORESIGHT 128
#define CLK_SDM 129
#define CLK_SECSS 130
#define CLK_PCM2 131
#define CLK_PCM1 132
#define CLK_PCM0 133
#define CLK_SYSCON 134
#define CLK_GPIO 135
#define CLK_TSADC 136
#define CLK_PWM 137
#define CLK_WDT 138
#define CLK_KEYIF 139
#define CLK_UART3 140
#define CLK_UART2 141
#define CLK_UART1 142
#define CLK_UART0 143
#define CLK_SYSTIMER 144
#define CLK_RTC 145
#define CLK_SPI1 146
#define CLK_SPI0 147
#define CLK_I2C_HDMI_PHY 148
#define CLK_I2C1 149
#define CLK_I2C2 150
#define CLK_I2C0 151
#define CLK_I2S1 152
#define CLK_I2S2 153
#define CLK_I2S0 154
#define CLK_AC97 155
#define CLK_SPDIF 156
#define CLK_TZPC3 157
#define CLK_TZPC2 158
#define CLK_TZPC1 159
#define CLK_TZPC0 160
#define CLK_SECKEY 161
#define CLK_IEM_APC 162
#define CLK_IEM_IEC 163
#define CLK_CHIPID 164
#define CLK_JPEG 163
/* Special clocks*/
#define SCLK_PWI 164
#define SCLK_SPDIF 165
#define SCLK_AUDIO2 166
#define SCLK_AUDIO1 167
#define SCLK_AUDIO0 168
#define SCLK_PWM 169
#define SCLK_SPI1 170
#define SCLK_SPI0 171
#define SCLK_UART3 172
#define SCLK_UART2 173
#define SCLK_UART1 174
#define SCLK_UART0 175
#define SCLK_MMC3 176
#define SCLK_MMC2 177
#define SCLK_MMC1 178
#define SCLK_MMC0 179
#define SCLK_FINVPLL 180
#define SCLK_CSIS 181
#define SCLK_FIMD 182
#define SCLK_CAM1 183
#define SCLK_CAM0 184
#define SCLK_DAC 185
#define SCLK_MIXER 186
#define SCLK_HDMI 187
#define SCLK_FIMC2 188
#define SCLK_FIMC1 189
#define SCLK_FIMC0 190
#define SCLK_HDMI27M 191
#define SCLK_HDMIPHY 192
#define SCLK_USBPHY0 193
#define SCLK_USBPHY1 194
/* S5P6442-specific clocks */
#define MOUT_D0SYNC 195
#define MOUT_D1SYNC 196
#define DOUT_MIXER 197
#define CLK_ETB 198
#define CLK_ETM 199
/* CLKOUT */
#define FOUT_APLL_CLKOUT 200
#define FOUT_MPLL_CLKOUT 201
#define DOUT_APLL_CLKOUT 202
#define MOUT_CLKSEL 203
#define DOUT_CLKOUT 204
#define MOUT_CLKOUT 205
/* Total number of clocks. */
#define NR_CLKS 206
#endif /* _DT_BINDINGS_CLOCK_S5PV210_H */
下面这个函数就是真个S5PV210芯片的注册流程。
static void __init __s5pv210_clk_init(struct device_node *np,
unsigned long xxti_f,
unsigned long xusbxti_f,
bool is_s5p6442) /* 这个参数传的是false */
{
struct samsung_clk_provider *ctx;
ctx = samsung_clk_init(np, reg_base, NR_CLKS); /* 申请空间等等 */
samsung_clk_register_mux(ctx, early_mux_clks, /* 注册早期的多选一开关 */
ARRAY_SIZE(early_mux_clks));
if (is_s5p6442) {
samsung_clk_register_fixed_rate(ctx, s5p6442_frate_clks,
ARRAY_SIZE(s5p6442_frate_clks));
samsung_clk_register_pll(ctx, s5p6442_pll_clks,
ARRAY_SIZE(s5p6442_pll_clks), reg_base);
samsung_clk_register_mux(ctx, s5p6442_mux_clks,
ARRAY_SIZE(s5p6442_mux_clks));
samsung_clk_register_div(ctx, s5p6442_div_clks,
ARRAY_SIZE(s5p6442_div_clks));
samsung_clk_register_gate(ctx, s5p6442_gate_clks,
ARRAY_SIZE(s5p6442_gate_clks));
} else {
samsung_clk_register_fixed_rate(ctx, s5pv210_frate_clks, /* 注册s5pv210只有的固定频率的时钟 */
ARRAY_SIZE(s5pv210_frate_clks));
samsung_clk_register_pll(ctx, s5pv210_pll_clks, /* 注册s5pv210只有倍频的时钟 */
ARRAY_SIZE(s5pv210_pll_clks), reg_base);
samsung_clk_register_mux(ctx, s5pv210_mux_clks, /* 注册s5pv210只有多选一时钟 */
ARRAY_SIZE(s5pv210_mux_clks));
samsung_clk_register_div(ctx, s5pv210_div_clks, /* 注册s5pv210只有分频时钟 */
ARRAY_SIZE(s5pv210_div_clks));
samsung_clk_register_gate(ctx, s5pv210_gate_clks, /* 注册s5pv210只有开关类型时钟 */
ARRAY_SIZE(s5pv210_gate_clks));
}
samsung_clk_register_mux(ctx, mux_clks, ARRAY_SIZE(mux_clks)); /* 注册通用的多选一时钟 */
samsung_clk_register_div(ctx, div_clks, ARRAY_SIZE(div_clks)); /* 注册通用的分频时钟 */
samsung_clk_register_gate(ctx, gate_clks, ARRAY_SIZE(gate_clks)); /* 注册通用开关时钟 */
samsung_clk_register_fixed_factor(ctx, ffactor_clks, /* 注册通用固定频率时钟 */
ARRAY_SIZE(ffactor_clks));
samsung_clk_register_alias(ctx, s5pv210_aliases, /* 注册别名时钟 */
ARRAY_SIZE(s5pv210_aliases));
s5pv210_clk_sleep_init(); /* 如果使用电源管理,则注册电源管理类时钟 */
samsung_clk_of_add_provider(np, ctx); /*为设备时钟节点注册时钟提供程序*/
pr_info("%s clocks: mout_apll = %ld, mout_mpll = %ld\n"
"\tmout_epll = %ld, mout_vpll = %ld\n",
is_s5p6442 ? "S5P6442" : "S5PV210",
_get_rate("mout_apll"), _get_rate("mout_mpll"),
_get_rate("mout_epll"), _get_rate("mout_vpll"));
}
接下来我从上到下按顺序进行分析,类似的倍频,多选一,分频时钟,这里只分析一两类其它类似,不再分析。
1.这里先fesa分析第一个函数samung_clk_init
/**
* struct samsung_clk_provider: information about clock provider
* @reg_base: virtual address for the register base.
* @clk_data: holds clock related data like clk* and number of clocks.
* @lock: maintains exclusion between callbacks for a given clock-provider.
*/
struct samsung_clk_provider {
void __iomem *reg_base; /* 保存ioremap映射过寄存器地址 */
struct clk_onecell_data clk_data; /* 保存时钟相关的数据 */
spinlock_t lock;
};
struct clk_onecell_data {
struct clk **clks; /* 保存所有clk组成的数组 */
unsigned int clk_num; /* 记录clk的数量 */
};
/* setup the essentials required to support clock lookup using ccf */
struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np,
void __iomem *base, unsigned long nr_clks)
{
struct samsung_clk_provider *ctx;
struct clk **clk_table;
int i;
ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); /* 申请一个生产商信息空间 */
if (!ctx)
panic("could not allocate clock provider context.\n");
/* 申请nr_clks个存放clk结构体的指针,前面已经看到s5pv210是206个 */
clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
if (!clk_table)
panic("could not allocate clock lookup table\n");
for (i = 0; i < nr_clks; ++i)
clk_table[i] = ERR_PTR(-ENOENT); /* 时钟的内容初始化成无效值 */
ctx->reg_base = base; /* 保存clk相关寄存器映射后的虚拟地址的基地址 */
ctx->clk_data.clks = clk_table; /* 保存clk存放表 */
ctx->clk_data.clk_num = nr_clks; /* 记录时钟个数 */
spin_lock_init(&ctx->lock);
return ctx;
}
上面已经申请好了空间,下面则就开始填充
2.注册早期的多选一时钟samsung_clk_register_mux
/* PLL input mux (fin_pll), which needs to be registered before PLLs. */
static const struct samsung_mux_clock early_mux_clks[] __initconst = {
MUX_F(FIN_PLL, "fin_pll", fin_pll_p, OM_STAT, 0, 1,
CLK_MUX_READ_ONLY, 0),
};
//通过OM_PIN选择,而且这个时钟选择后,只能读,不能更改,它的父时钟有两个,的名字如下
/* Mux parent lists. */
static const char *const fin_pll_p[] __initconst = {
"xxti",
"xusbxti"
};
/**
* struct samsung_mux_clock: information about mux clock
* @id: platform specific id of the clock.
* @dev_name: name of the device to which this clock belongs.
* @name: name of this mux clock.
* @parent_names: array of pointer to parent clock names.
* @num_parents: number of parents listed in @parent_names.
* @flags: optional flags for basic clock.
* @offset: offset of the register for configuring the mux.
* @shift: starting bit location of the mux control bit-field in @reg.
* @width: width of the mux control bit-field in @reg.
* @mux_flags: flags for mux-type clock.
* @alias: optional clock alias name to be assigned to this clock.
*/
struct samsung_mux_clock {
unsigned int id;
const char *dev_name;
const char *name;
const char *const *parent_names;
u8 num_parents;
unsigned long flags;
unsigned long offset;
u8 shift;
u8 width;
u8 mux_flags;
const char *alias;
};
#define __MUX(_id, dname, cname, pnames, o, s, w, f, mf, a) \
{ \
.id = _id, \
.dev_name = dname, \
.name = cname, \
.parent_names = pnames, \
.num_parents = ARRAY_SIZE(pnames), \ /* 注意这里以及计算了父clk数量 */
.flags = (f) | CLK_SET_RATE_NO_REPARENT, \
.offset = o, \
.shift = s, \
.width = w, \
.mux_flags = mf, \
.alias = a, \
}
#define MUX_F(_id, cname, pnames, o, s, w, f, mf) \
__MUX(_id, NULL, cname, pnames, o, s, w, f, mf, NULL)
上面注释也说的很明白,PLL输入多路复用器(fin_pll),需要在PLL之前注册。(看下图)
这个也是最早期的就选择的,绝大多数情况下我们都会选择XXTI作为FIN_PLL的时钟。
接下来就看一下三星对多路选择时钟的注册。
/* register a list of mux clocks */
void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx,
const struct samsung_mux_clock *list,
unsigned int nr_clk)
{
struct clk *clk;
unsigned int idx, ret;
for (idx = 0; idx < nr_clk; idx++, list++) {
/* 注册多路选择时钟,即申请clk结构并初始化,绑定ops,查找父clk并绑定,参数检查等 */
clk = clk_register_mux(NULL, list->name, list->parent_names,
list->num_parents, list->flags,
ctx->reg_base + list->offset,
list->shift, list->width, list->mux_flags, &ctx->lock);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n", __func__,
list->name);
continue;
}
/* 把clk绑定到samsung_clk_init函数申请的206个clk指针数组中的字节id对应的位置 */
samsung_clk_add_lookup(ctx, clk, list->id);
/* register a clock lookup only if a clock alias is specified */
if (list->alias) {
ret = clk_register_clkdev(clk, list->alias, /* 注册这个时钟到clock链表,方便查找使用 */
list->dev_name);
if (ret)
pr_err("%s: failed to register lookup %s\n",
__func__, list->alias);
}
}
}
/* add a clock instance to the clock lookup table used for dt based lookup */
void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk,
unsigned int id)
{
if (ctx->clk_data.clks && id)
ctx->clk_data.clks[id] = clk; /* 时钟放到数组中 */
}
3.接下来就是几个S5PV210的特有的固定时钟了
/* S5PV210-specific fixed rate clocks generated inside the SoC. */
static const struct samsung_fixed_rate_clock s5pv210_frate_clks[] __initconst = {
FRATE(SCLK_HDMI27M, "sclk_hdmi27m", NULL, 0, 27000000),
FRATE(SCLK_HDMIPHY, "sclk_hdmiphy", NULL, 0, 27000000),
FRATE(SCLK_USBPHY0, "sclk_usbphy0", NULL, 0, 48000000),
FRATE(SCLK_USBPHY1, "sclk_usbphy1", NULL, 0, 48000000),
};
在datasheet中的位置如下
这种函数的注册如下:和上面基本是相同的讨论
/* register a list of fixed clocks */
void __init samsung_clk_register_fixed_rate(struct samsung_clk_provider *ctx,
const struct samsung_fixed_rate_clock *list,
unsigned int nr_clk)
{
struct clk *clk;
unsigned int idx, ret;
for (idx = 0; idx < nr_clk; idx++, list++) {
/* 注册固定频率时钟 */
clk = clk_register_fixed_rate(NULL, list->name,
list->parent_name, list->flags, list->fixed_rate);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n", __func__,
list->name);
continue;
}
/* clk绑定到三星自己封装的数组 */
samsung_clk_add_lookup(ctx, clk, list->id);
/*
* Unconditionally add a clock lookup for the fixed rate clocks.
* There are not many of these on any of Samsung platforms.
*/
/* clk时钟注册到clokc链表 */
ret = clk_register_clkdev(clk, list->name, NULL);
if (ret)
pr_err("%s: failed to register clock lookup for %s",
__func__, list->name);
}
}
其它的基本都是一样,这里就不把快200个时钟一个一个重复来,说一下pll的注册就行,
4.PLL倍频器的注册
/* S5PV210-specific PLLs. */
static const struct samsung_pll_clock s5pv210_pll_clks[] __initconst = {
[apll] = PLL(pll_4508, FOUT_APLL, "fout_apll", "fin_pll",
APLL_LOCK, APLL_CON0, NULL),
[mpll] = PLL(pll_4502, FOUT_MPLL, "fout_mpll", "fin_pll",
MPLL_LOCK, MPLL_CON, NULL),
[epll] = PLL(pll_4600, FOUT_EPLL, "fout_epll", "fin_pll",
EPLL_LOCK, EPLL_CON0, NULL),
[vpll] = PLL(pll_4502, FOUT_VPLL, "fout_vpll", "mout_vpllsrc",
VPLL_LOCK, VPLL_CON, NULL),
};
//因为三星的pll的类型有多种,所以上面第一个参数都是用的下面枚举,下面注册时也会根据这些判断使用不同的ops函数
enum samsung_pll_type {
pll_2126,
pll_3000,
pll_35xx,
pll_36xx,
pll_2550,
pll_2650,
pll_4500,
pll_4502,
pll_4508,
pll_4600,
pll_4650,
pll_4650c,
pll_6552,
pll_6552_s3c2416,
pll_6553,
pll_s3c2410_mpll,
pll_s3c2410_upll,
pll_s3c2440_mpll,
pll_2550x,
pll_2550xx,
pll_2650x,
pll_2650xx,
pll_1450x,
pll_1451x,
pll_1452x,
pll_1460x,
};
因为上面用到来PLL的相关寄存器,所以三星以相对基址时钟偏移量的形式给出了
/**
* struct samsung_pll_clock: information about pll clock
* @id: platform specific id of the clock.
* @dev_name: name of the device to which this clock belongs.
* @name: name of this pll clock.
* @parent_name: name of the parent clock.
* @flags: optional flags for basic clock.
* @con_offset: offset of the register for configuring the PLL.
* @lock_offset: offset of the register for locking the PLL.
* @type: Type of PLL to be registered.
* @alias: optional clock alias name to be assigned to this clock.
*/
struct samsung_pll_clock {
unsigned int id;
const char *dev_name;
const char *name;
const char *parent_name;
unsigned long flags;
int con_offset; //寄存器相对基址寄存器的偏移量
int lock_offset; //PLL的时钟锁存寄存器的偏移量
enum samsung_pll_type type;
const struct samsung_pll_rate_table *rate_table;
const char *alias;
};
下面就是给出的寄存器偏移量
/* S5PC110/S5PV210 clock controller register offsets */
#define APLL_LOCK 0x0000
#define MPLL_LOCK 0x0008
#define EPLL_LOCK 0x0010
#define VPLL_LOCK 0x0020
#define APLL_CON0 0x0100
#define APLL_CON1 0x0104
#define MPLL_CON 0x0108
#define EPLL_CON0 0x0110
#define EPLL_CON1 0x0114
#define VPLL_CON 0x0120
#define CLK_SRC0 0x0200
#define CLK_SRC1 0x0204
#define CLK_SRC2 0x0208
#define CLK_SRC3 0x020c
#define CLK_SRC4 0x0210
#define CLK_SRC5 0x0214
#define CLK_SRC6 0x0218
#define CLK_SRC_MASK0 0x0280
#define CLK_SRC_MASK1 0x0284
#define CLK_DIV0 0x0300
#define CLK_DIV1 0x0304
#define CLK_DIV2 0x0308
#define CLK_DIV3 0x030c
#define CLK_DIV4 0x0310
#define CLK_DIV5 0x0314
#define CLK_DIV6 0x0318
#define CLK_DIV7 0x031c
#define CLK_GATE_MAIN0 0x0400
#define CLK_GATE_MAIN1 0x0404
#define CLK_GATE_MAIN2 0x0408
#define CLK_GATE_PERI0 0x0420
#define CLK_GATE_PERI1 0x0424
#define CLK_GATE_SCLK0 0x0440
#define CLK_GATE_SCLK1 0x0444
#define CLK_GATE_IP0 0x0460
#define CLK_GATE_IP1 0x0464
#define CLK_GATE_IP2 0x0468
#define CLK_GATE_IP3 0x046c
#define CLK_GATE_IP4 0x0470
#define CLK_GATE_BLOCK 0x0480
#define CLK_GATE_IP5 0x0484
#define CLK_OUT 0x0500
#define MISC 0xe000
#define OM_STAT 0xe100
这四个PLL,从芯片手册上也能看到。
因为PLL倍频器的注册,common clock framework没有提供,所以是三星自己实现的。
void __init samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_list,
unsigned int nr_pll, void __iomem *base)
{
int cnt;
for (cnt = 0; cnt < nr_pll; cnt++)
_samsung_clk_register_pll(ctx, &pll_list[cnt], base); /* 每次注册一个 */
}
static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_clk,
void __iomem *base)
{
struct samsung_clk_pll *pll;
struct clk *clk;
struct clk_init_data init;
int ret, len;
pll = kzalloc(sizeof(*pll), GFP_KERNEL); /* 申请pll */
if (!pll) {
pr_err("%s: could not allocate pll clk %s\n",
__func__, pll_clk->name);
return;
}
/* 初始化厂商的信息 */
init.name = pll_clk->name;
init.flags = pll_clk->flags;
init.parent_names = &pll_clk->parent_name;
init.num_parents = 1;
if (pll_clk->rate_table) { /* s5pv210的这几个倍频的参数范围表都是给的NULL */
/* find count of rates in rate_table */
for (len = 0; pll_clk->rate_table[len].rate != 0; )
len++;
pll->rate_count = len;
pll->rate_table = kmemdup(pll_clk->rate_table,
pll->rate_count *
sizeof(struct samsung_pll_rate_table),
GFP_KERNEL);
WARN(!pll->rate_table,
"%s: could not allocate rate table for %s\n",
__func__, pll_clk->name);
}
/* 根据不同的pll,绑定不同的ops操作集合 */
switch (pll_clk->type) {
case pll_2126:
init.ops = &samsung_pll2126_clk_ops;
break;
case pll_3000:
init.ops = &samsung_pll3000_clk_ops;
break;
/* clk_ops for 35xx and 2550 are similar */
case pll_35xx:
case pll_2550:
case pll_1450x:
case pll_1451x:
case pll_1452x:
if (!pll->rate_table)
init.ops = &samsung_pll35xx_clk_min_ops;
else
init.ops = &samsung_pll35xx_clk_ops;
break;
case pll_4500:
init.ops = &samsung_pll45xx_clk_min_ops;
break;
case pll_4502:
case pll_4508:
if (!pll->rate_table)
init.ops = &samsung_pll45xx_clk_min_ops;
else
init.ops = &samsung_pll45xx_clk_ops;
break;
/* clk_ops for 36xx and 2650 are similar */
case pll_36xx:
case pll_2650:
if (!pll->rate_table)
init.ops = &samsung_pll36xx_clk_min_ops;
else
init.ops = &samsung_pll36xx_clk_ops;
break;
case pll_6552:
case pll_6552_s3c2416:
init.ops = &samsung_pll6552_clk_ops;
break;
case pll_6553:
init.ops = &samsung_pll6553_clk_ops;
break;
case pll_4600:
case pll_4650:
case pll_4650c:
case pll_1460x:
if (!pll->rate_table)
init.ops = &samsung_pll46xx_clk_min_ops;
else
init.ops = &samsung_pll46xx_clk_ops;
break;
case pll_s3c2410_mpll:
if (!pll->rate_table)
init.ops = &samsung_s3c2410_mpll_clk_min_ops;
else
init.ops = &samsung_s3c2410_mpll_clk_ops;
break;
case pll_s3c2410_upll:
if (!pll->rate_table)
init.ops = &samsung_s3c2410_upll_clk_min_ops;
else
init.ops = &samsung_s3c2410_upll_clk_ops;
break;
case pll_s3c2440_mpll:
if (!pll->rate_table)
init.ops = &samsung_s3c2440_mpll_clk_min_ops;
else
init.ops = &samsung_s3c2440_mpll_clk_ops;
break;
case pll_2550x:
init.ops = &samsung_pll2550x_clk_ops;
break;
case pll_2550xx:
if (!pll->rate_table)
init.ops = &samsung_pll2550xx_clk_min_ops;
else
init.ops = &samsung_pll2550xx_clk_ops;
break;
case pll_2650x:
if (!pll->rate_table)
init.ops = &samsung_pll2650x_clk_min_ops;
else
init.ops = &samsung_pll2650x_clk_ops;
break;
case pll_2650xx:
if (!pll->rate_table)
init.ops = &samsung_pll2650xx_clk_min_ops;
else
init.ops = &samsung_pll2650xx_clk_ops;
break;
default:
pr_warn("%s: Unknown pll type for pll clk %s\n",
__func__, pll_clk->name);
}
/* 绑定到pll上 */
pll->hw.init = &init;
pll->type = pll_clk->type; /* 绑定pll类型 */
pll->lock_reg = base + pll_clk->lock_offset; /* 绑定锁定寄存器地址 */
pll->con_reg = base + pll_clk->con_offset; /* 绑定控制寄存器地址 */
clk = clk_register(NULL, &pll->hw); /* 申请clk空间,并初始化,查找父节点,最后绑定到clk_root_list链表树下等等*/
if (IS_ERR(clk)) {
pr_err("%s: failed to register pll clock %s : %ld\n",
__func__, pll_clk->name, PTR_ERR(clk));
kfree(pll);
return;
}
/* 绑定clk到固定数组下标位置 */
samsung_clk_add_lookup(ctx, clk, pll_clk->id);
if (!pll_clk->alias)
return;
/* 注册到clock链表下,方便查找 */
ret = clk_register_clkdev(clk, pll_clk->alias, pll_clk->dev_name);
if (ret)
pr_err("%s: failed to register lookup for %s : %d",
__func__, pll_clk->name, ret);
}
下面其他通用的之类就不说了,都是一样的函数逻辑,变化的只是寄存的偏移量,父时钟名称,寄存器占的位宽,寄存器占的起始位,之类这里就不一一分析了,把下面这几个别名的分析一下。
,
5.时钟起个别名
/*
* Clock aliases for legacy clkdev look-up.
* NOTE: Needed only to support legacy board files.
*/
static const struct samsung_clock_alias s5pv210_aliases[] __initconst = {
ALIAS(DOUT_APLL, NULL, "armclk"),
ALIAS(DOUT_HCLKM, NULL, "hclk_msys"),
ALIAS(MOUT_DMC0, NULL, "sclk_dmc0"),
};
这几个时钟在手册中位置如下
既然是起别名,那肯定别的地方已经注册过了,搜索一下。
可以发现确实是在多选一和分频时钟里已经注册过了。
下面看一下是如何该名的
/* register a list of aliases */
void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx,
const struct samsung_clock_alias *list,
unsigned int nr_clk)
{
struct clk *clk;
unsigned int idx, ret;
if (!ctx->clk_data.clks) {
pr_err("%s: clock table missing\n", __func__);
return;
}
for (idx = 0; idx < nr_clk; idx++, list++) {
if (!list->id) { /* 确定id号存在 */
pr_err("%s: clock id missing for index %d\n", __func__,
idx);
continue;
}
clk = ctx->clk_data.clks[list->id]; /* 取出之前的已经注册的时钟 */
if (!clk) {
pr_err("%s: failed to find clock %d\n", __func__,
list->id);
continue;
}
/* 使用新的名字注册到clock链表,到时使用的时候就可以用新的名字 */
/* 这里dev_name是NULL,新名字就是alias里的名字注册 */
ret = clk_register_clkdev(clk, list->alias, list->dev_name);
if (ret)
pr_err("%s: failed to register lookup %s\n",
__func__, list->alias);
}
}
6.和电源管理休眠有关的部分
首先是对时钟寄存器的偏移值保在了一个数组
/* List of registers that need to be preserved across suspend/resume. */
static unsigned long s5pv210_clk_regs[] __initdata = {
CLK_SRC0,
CLK_SRC1,
CLK_SRC2,
CLK_SRC3,
CLK_SRC4,
CLK_SRC5,
CLK_SRC6,
CLK_SRC_MASK0,
CLK_SRC_MASK1,
CLK_DIV0,
CLK_DIV1,
CLK_DIV2,
CLK_DIV3,
CLK_DIV4,
CLK_DIV5,
CLK_DIV6,
CLK_DIV7,
CLK_GATE_MAIN0,
CLK_GATE_MAIN1,
CLK_GATE_MAIN2,
CLK_GATE_PERI0,
CLK_GATE_PERI1,
CLK_GATE_SCLK0,
CLK_GATE_SCLK1,
CLK_GATE_IP0,
CLK_GATE_IP1,
CLK_GATE_IP2,
CLK_GATE_IP3,
CLK_GATE_IP4,
CLK_GATE_IP5,
CLK_GATE_BLOCK,
APLL_LOCK,
MPLL_LOCK,
EPLL_LOCK,
VPLL_LOCK,
APLL_CON0,
APLL_CON1,
MPLL_CON,
EPLL_CON0,
EPLL_CON1,
VPLL_CON,
CLK_OUT,
};
static struct samsung_clk_reg_dump *s5pv210_clk_dump;
static void s5pv210_clk_sleep_init(void)
{
/* 保存寄存器偏移 */
s5pv210_clk_dump =
samsung_clk_alloc_reg_dump(s5pv210_clk_regs,
ARRAY_SIZE(s5pv210_clk_regs));
if (!s5pv210_clk_dump) {
pr_warn("%s: Failed to allocate sleep save data\n", __func__);
return;
}
/* 把时钟睡眠相关的函数即挂接到系统链表上 */
register_syscore_ops(&s5pv210_clk_syscore_ops);
}
/**
* struct samsung_clk_reg_dump: register dump of clock controller registers.
* @offset: clock register offset from the controller base address.
* @value: the value to be register at offset.
*/
/* 这个结构体就是保存时钟的地址偏移,和对应地址存放的值 */
struct samsung_clk_reg_dump {
u32 offset;
u32 value;
};
struct samsung_clk_reg_dump *samsung_clk_alloc_reg_dump(
const unsigned long *rdump,
unsigned long nr_rdump)
{
struct samsung_clk_reg_dump *rd;
unsigned int i;
/* 申请nr个samsung_clk_reg_dump,保存每个寄存器的偏移和地址的值 */
rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL);
if (!rd)
return NULL;
for (i = 0; i < nr_rdump; ++i)
rd[i].offset = rdump[i]; /* 默认的地址偏移保存到新申请的结构数组中 */
return rd;
}
/* 系统电源注册核心结构 */
struct syscore_ops {
struct list_head node;
int (*suspend)(void);
void (*resume)(void);
void (*shutdown)(void);
};
/**
* register_syscore_ops - Register a set of system core operations.
* @ops: System core operations to register.
*/
void register_syscore_ops(struct syscore_ops *ops)
{
/* 注册一组系统核心操作 */
mutex_lock(&syscore_ops_lock);
list_add_tail(&ops->node, &syscore_ops_list);
mutex_unlock(&syscore_ops_lock);
}
/* 挂起函数 */
static int s5pv210_clk_suspend(void)
{
samsung_clk_save(reg_base, s5pv210_clk_dump,
ARRAY_SIZE(s5pv210_clk_regs));
return 0;
}
/* 释放函数 */
static void s5pv210_clk_resume(void)
{
samsung_clk_restore(reg_base, s5pv210_clk_dump,
ARRAY_SIZE(s5pv210_clk_regs));
}
/* 操作集合 */
static struct syscore_ops s5pv210_clk_syscore_ops = {
.suspend = s5pv210_clk_suspend,
.resume = s5pv210_clk_resume,
};
/* 挂起函数就是把错有的寄存器的值保存起来 */
void samsung_clk_save(void __iomem *base,
struct samsung_clk_reg_dump *rd,
unsigned int num_regs)
{
for (; num_regs > 0; --num_regs, ++rd)
rd->value = readl(base + rd->offset);
}
/* 恢复就是把挂起时保存的值,还原的原来的寄存器中 */
void samsung_clk_restore(void __iomem *base,
const struct samsung_clk_reg_dump *rd,
unsigned int num_regs)
{
for (; num_regs > 0; --num_regs, ++rd)
writel(rd->value, base + rd->offset);
}
6.为节点注册时钟提供程序
这个就比较简单来,主要是设备树中拿到的节点和时钟做绑定。
void __init samsung_clk_of_add_provider(struct device_node *np,
struct samsung_clk_provider *ctx)
{
if (np) {
if (of_clk_add_provider(np, of_clk_src_onecell_get,
&ctx->clk_data))
panic("could not register clk provider\n");
}
}
/* 提供回掉函数,做具体时钟的获取 */
struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
{
struct clk_onecell_data *clk_data = data;
unsigned int idx = clkspec->args[0];
if (idx >= clk_data->clk_num) {
pr_err("%s: invalid clock index %u\n", __func__, idx);
return ERR_PTR(-EINVAL);
}
return clk_data->clks[idx];
}