1. 程式人生 > >從零開始之驅動發開、linux驅動(三十九、Linux common clock framework(4)_總結)

從零開始之驅動發開、linux驅動(三十九、Linux common clock framework(4)_總結)

前面三節參考蝸窩大神的文章分析了Linux common clock framework的主要實現細節,本篇則是對前三篇從全域性的一個整合說明。

 

common clock framework主要維護著四條連結串列

static HLIST_HEAD(clk_root_list);
static HLIST_HEAD(clk_orphan_list);
static LIST_HEAD(clk_notifier_list);

 

static LIST_HEAD(clocks);

其中clock連結串列我們在上一節的末尾已經說明,這個主要就是為了方便查詢。

clk_root_list是存放根時鐘的,一個連結串列,通常根時鐘都是固定頻率的時鐘。(fix)

clk_orphan_list是存放孤兒節點的時鐘,即一般而言沒有父時鐘,也不是根時鐘,這種時鐘通常就是孤兒時鐘。

clk_notifier_list是通知連結串列,即某個父時鐘跟新了頻率,其也要通知子時鐘更新它的頻率計算。

註冊時的位置如下

clk_register
    __clk_init

 

正常情況下,絕大多數時鐘都是有父時鐘的。一般都是root連結串列上某個根時鐘的子時鐘。(比如下圖的藍色框標註的)

當然,上面的各個root的子時鐘,下面有可以繼續級聯子時鐘。

 

但是對於某些特殊情況下就會出現孤兒時鐘:

即註冊時,其父節點還沒註冊的非根時鐘都是孤兒時鐘。orphan時鐘和root時鐘的管理是一致的,孤兒時鐘下面,也是可以掛接子時鐘的。

 

一般在註冊時,如果子時鐘先於父時鐘註冊,這時就會掛接到clk_orphan_list時鐘連結串列上,這種情況下,待父時鐘註冊好後,子時鐘又要從orphan時鐘連結串列上移到它的父時鐘下面(位於root連結串列下)。

函式同樣位於註冊時鐘的函式中。

clk_register
    __clk_init

 

最後,再次列出註冊用的哪幾種函式原型。

 

開關型別的時鐘

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);

固定頻率的時鐘

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		unsigned long fixed_rate);

多選一型別的時鐘

void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx,
				struct samsung_mux_clock *list,
				unsigned int nr_clk);

分頻型別的時鐘

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);

綜合類型的時鐘(上面幾個的結合,但一般用的不多)

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)

 

通過上面可以發現,倍頻的時鐘註冊是沒有的。

一般都是用的鎖相環PLL(phase locked loop)來實現的。

因為上面幾個標準的時鐘註冊函式是沒有這個的(因為這個各廠商的差異太大),所以這個的ops等都是廠商單獨來實現的。

 

已三星為例,三星是根據鎖相環電路(晶片)的不通過來實現自己平臺下的各種處理器的pll註冊的。

 


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);
	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) {
		/* 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);
	}

	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->hw.init = &init;
	pll->type = pll_clk->type;
	pll->lock_reg = base + pll_clk->lock_offset;
	pll->con_reg = base + pll_clk->con_offset;

	clk = clk_register(NULL, &pll->hw);
	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;
	}

	samsung_clk_add_lookup(ctx, clk, pll_clk->id);

	if (!pll_clk->alias)
		return;

	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);
}

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);
}

下面列出三星目前有的鎖相環型別

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,
};

因為每種鎖相環支援的操作不一樣,所以三星對鎖相環的有多種ops介面。

 

 

最後就是每種時鐘註冊時為了方便查詢,都統一繫結在clock連結串列的註冊介面:

int clk_register_clkdev(struct clk *clk, const char *con_id,
	const char *dev_id);

 

即最終,一般情況下下