从零开始之驱动发开、linux驱动(四十、Linux common clock framework(5)_使用举例)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/84347918

前面三节分析的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];
}

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/84347918