埋め込み式Linux駆動ノート(14)------clockクロック(CCF)フレームワークとclk_を詳しく理解するget関数
24961 ワード
こんにちは!凧のブログですが、
私と一緒に交流することを歓迎します。
私が資料を探していたとき、ネット上のほとんどの文章がs 3 c 244 x_だったことに気づいた.init_clocks関数では:
void __init s3c244x_init_clocks(int xtal)
{
s3c24xx_register_baseclocks(xtal); //
s3c244x_setup_clocks();// ,
s3c2410_baseclk_add();// list ,
}
でも私はkernelにいます.8.17の中には、もうs 3 c 24 xxがありません.register_baseclocks関数、s 3 c 244 x_がありませんsetup_clocks関数、s 3 c 2410_がなくなりましたbaseclk_add関数、、、、そしてarcharmplat-samsungディレクトリの下でもclockは使いません.cファイル......
Linux 3からだそうです.10カーネルは最初からないでしょうが、正式にCCF(Common Clock Framewrok)フレームワークを使いました.
従来のClockセクションでは、名前に応じてClockを取得し、周波数を設定し、親クロックを取得し、親クロックを設定する関数も提供されていたが、これらのAPIはすべてSoCごとに単独で実現され、コードの違いが大きくなったため、この問題を解決するために新しい汎用クロックフレームワークが導入された.TIのエンジニアMike TurquetteがCommon Clock Framewrokを提供し、具体的なSoCにclk_を実現させるopsメンバー関数とclk_register、clk_register_clkdevはクロックソースとソースとデバイスの対応関係を登録し、特定のclockドライバはdrivers/clkディレクトリに統一的に移行します.そのため現在のクロックフレームはCCF方式を採用しており、CCFを使用するにはカーネルにCONFIGが配置されていることを前提としている.COMMON_CLK.
だからkernel 4.8.17を例に、clock登録部分を見直し、そのフレームワークを分析します:mach-smdk 2440.cファイルでは、呼び出し関係は次のとおりです.
smdk2440_init_time
->s3c2440_init_clocks(12000000)
->s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR)
s 3 c 2410を見てください.common_clk_Init関数、重点分析:
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,
int current_soc,
void __iomem *base)
{
struct samsung_clk_provider *ctx;
reg_base = base;
if (np) {
reg_base = of_iomap(np, 0);
if (!reg_base)
panic("%s: failed to map registers
", __func__);
}
ctx = samsung_clk_init(np, reg_base, NR_CLKS);
/* Register external clocks only in non-dt cases */
if (!np)
s3c2410_common_clk_register_fixed_ext(ctx, xti_f);
if (current_soc == S3C2410) {
if (_get_rate("xti") == 12 * MHZ) {
s3c2410_plls[mpll].rate_table = pll_s3c2410_12mhz_tbl;
s3c2410_plls[upll].rate_table = pll_s3c2410_12mhz_tbl;
}
/* Register PLLs. */
samsung_clk_register_pll(ctx, s3c2410_plls,
ARRAY_SIZE(s3c2410_plls), reg_base);//
} else { /* S3C2440, S3C2442 */
if (_get_rate("xti") == 12 * MHZ) {
/*
* plls follow different calculation schemes, with the
* upll following the same scheme as the s3c2410 plls
*/
s3c244x_common_plls[mpll].rate_table =
pll_s3c244x_12mhz_tbl;
s3c244x_common_plls[upll].rate_table =
pll_s3c2410_12mhz_tbl;
}
/* Register PLLs. */
samsung_clk_register_pll(ctx, s3c244x_common_plls,
ARRAY_SIZE(s3c244x_common_plls), reg_base);
}
/* Register common internal clocks. */
samsung_clk_register_mux(ctx, s3c2410_common_muxes,
ARRAY_SIZE(s3c2410_common_muxes));
samsung_clk_register_div(ctx, s3c2410_common_dividers,
ARRAY_SIZE(s3c2410_common_dividers));
samsung_clk_register_gate(ctx, s3c2410_common_gates,
ARRAY_SIZE(s3c2410_common_gates));
if (current_soc == S3C2440 || current_soc == S3C2442) {
samsung_clk_register_div(ctx, s3c244x_common_dividers,
ARRAY_SIZE(s3c244x_common_dividers));
samsung_clk_register_gate(ctx, s3c244x_common_gates,
ARRAY_SIZE(s3c244x_common_gates));
samsung_clk_register_mux(ctx, s3c244x_common_muxes,
ARRAY_SIZE(s3c244x_common_muxes));
samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,
ARRAY_SIZE(s3c244x_common_ffactor));
}
/* Register SoC-specific clocks. */
switch (current_soc) {
case S3C2410:
samsung_clk_register_div(ctx, s3c2410_dividers,
ARRAY_SIZE(s3c2410_dividers));
samsung_clk_register_fixed_factor(ctx, s3c2410_ffactor,
ARRAY_SIZE(s3c2410_ffactor));
samsung_clk_register_alias(ctx, s3c2410_aliases,
ARRAY_SIZE(s3c2410_aliases));
break;
case S3C2440:
samsung_clk_register_mux(ctx, s3c2440_muxes,
ARRAY_SIZE(s3c2440_muxes));
samsung_clk_register_gate(ctx, s3c2440_gates,
ARRAY_SIZE(s3c2440_gates));
break;
case S3C2442:
samsung_clk_register_mux(ctx, s3c2442_muxes,
ARRAY_SIZE(s3c2442_muxes));
samsung_clk_register_fixed_factor(ctx, s3c2442_ffactor,
ARRAY_SIZE(s3c2442_ffactor));
break;
}
/*
* Register common aliases at the end, as some of the aliased clocks
* are SoC specific.
*/
samsung_clk_register_alias(ctx, s3c2410_common_aliases,
ARRAY_SIZE(s3c2410_common_aliases));
if (current_soc == S3C2440 || current_soc == S3C2442) {
samsung_clk_register_alias(ctx, s3c244x_common_aliases,
ARRAY_SIZE(s3c244x_common_aliases));
}
s3c2410_clk_sleep_init();
samsung_clk_of_add_provider(np, ctx);
}
ここで我々が入力したパラメータnpはNULL,xti_である.fは12,000,current_socは1です.この関数に入り、分析:8行目:条件は成立しません.14行目:クロックを初期化し、主にctxに空間を割り当て、レジスタベースアドレスなどのデータを埋め込む.18行目:共通の外部固定クロックを登録し、呼び出し関係は:
s3c2410_common_clk_register_fixed_ext
->samsung_clk_register_fixed_rate
->clk_register_fixed_rate
->clk_register_fixed_rate_with_accuracy[ 1]
->clk_hw_register_fixed_rate_with_accuracy
->clk_hw_register
->clk_register[ 2]
->clk_register_clkdev
->__clk_register_clkdev
->vclkdev_create
->__clkdev_add[ 3]
->samsung_clk_register_alias[ 4]
->clk_register_clkdev
[注1]:clk_hw_register_fixed_rate_with_accuracyは主にname、flags、parent_などのパラメータを設定します.names、num_parentsとか.[注2]:clk_register関数ではcore->clksハッシュチェーンテーブルを初期化し、hw->clkをcore->clksにリンクし、hw->clkをclk_に戻します.register_fixed_rate関数では、hw->clkには2つの注意すべきメンバー変数があることに注意してください:dev_id(device ID)とcon_id(connection ID). [注3]:在_clkdev_add関数ではlist_add_tail(&cl->node, &clocks); cl->nodeをclocksにリンクします.clにもdev_があります.idとcon_id、同時にclocksというチェーンテーブルに注意![注4]:samsung_clk_register_alias関数は、クロックの別名(別名spiなど、PCLKに掛けられている)を登録し、clk_に呼び出す.register_clkdev,最終的に[注3]に入りlist->aliasをconとするid、clocksにリンクします.
続行:20行目:current_socは1,S 3 C 2410は0,ifは成立しない.43行目:ロックリングの登録に特化しています.48~52行目:samsung_clk_register_mux:クロック選択samsung_clk_register_div:クロック分周samsung_clk_register_gate:クロックゲート制御、登録されたクロックはスイッチのみ可能で、通過する.enable/.义齿
これらの関数はほとんど同じで、samsung_clk_register_gateから見るとsamsung_clk_register_gate(ctx, s3c2410_common_gates , ARRAY_SIZE(s3c2410_common_gates)); s 3 c 2410を見てください.common_gates配列:
struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),
GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),
GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),
GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),
GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),
GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),
GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),
GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),
GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),
GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),
GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),
GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),
GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),
GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),
GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};// :id、name、parent_name、offset、bit_idx、flags、gate_flags
これらが登録された時計のソースです.samsung_clk_register_gate関数の呼び出し関係は次のとおりです.
samsung_clk_register_gate
->clk_register_gate
->clk_hw_register_gate[ 5]
->clk_hw_register
->clk_register[ 2]
[注5]:clk_hw_register_gate関数は、name、flags、parent_などのパラメータを設定します.names、num_parentsなどと充填:init.ops = &clk_gate_ops.
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable,
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
ここでコールバック関数を入力しますclk_enable/clk_disableの場合に呼び出されます.[注2]:上記のとおりです.
続き:56~62行目:2440と2410は似ているので、2410に基づいて2440を補充します.94行目:クロック別名を登録することが重要です:samsung_clk_register_alias(ctx, s3c2410_common_aliases , ARRAY_SIZE(s3c2410_common_aliases)); s 3 c 2410を見てください.common_aliases配列:
struct samsung_clock_alias s3c2410_common_aliases[] __initdata = {
ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"),
ALIAS(PCLK_ADC, NULL, "adc"),
ALIAS(PCLK_RTC, NULL, "rtc"),
ALIAS(PCLK_PWM, NULL, "timers"),
ALIAS(HCLK_LCD, NULL, "lcd"),
ALIAS(HCLK_USBD, NULL, "usb-device"),
ALIAS(HCLK_USBH, NULL, "usb-host"),
ALIAS(UCLK, NULL, "usb-bus-host"),
ALIAS(UCLK, NULL, "usb-bus-gadget"),
ALIAS(ARMCLK, NULL, "armclk"),
ALIAS(UCLK, NULL, "uclk"),
ALIAS(HCLK, NULL, "hclk"),
ALIAS(MPLL, NULL, "mpll"),
ALIAS(FCLK, NULL, "fclk"),
ALIAS(PCLK, NULL, "watchdog"),
ALIAS(PCLK_SDI, NULL, "sdi"),
ALIAS(HCLK_NAND, NULL, "nand"),
ALIAS(PCLK_I2S, NULL, "iis"),
ALIAS(PCLK_I2C, NULL, "i2c"),
};// :id、dev_name、alias
samsung_clk_register_alias関数は以前[注4]に述べたようにcon_id(ここではalias)はclocksにリンクされている.
では、これで分析が終わりました.どうやって時計を手に入れますか?
もちろんとても大衆的なclkですget関数:
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);
}
通常、最初のパラメータはNULLに設定すればよい.of_clk_get_by_nameはdtsデバイスツリーに関するAPIである.clk_へget_Sys関数を見てみましょう.
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
struct clk *clk = NULL;
mutex_lock(&clocks_mutex);
cl = clk_find(dev_id, con_id);
if (!cl)
goto out;
clk = __clk_create_clk(cl->clk_hw, dev_id, con_id);
if (IS_ERR(clk))
goto out;
if (!__clk_get(clk)) {
__clk_free_clk(clk);
cl = NULL;
goto out;
}
out:
mutex_unlock(&clocks_mutex);
return cl ? clk : ERR_PTR(-ENOENT);
}
ここには2つの関数があります:clk_findと_clk_create_clk見てclk_find関数:
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{
struct clk_lookup *p, *cl = NULL;
int match, best_found = 0, best_possible = 0;
if (dev_id)
best_possible += 2;
if (con_id)
best_possible += 1;
list_for_each_entry(p, &clocks, node) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best_found) {
cl = p;
if (match != best_possible)
best_found = match;
else
break;
}
}
return cl;
}
中のリスト_for_each_entryはclocksというチェーンテーブルを巡って、Linuxの下のチェーンテーブルの使用と探究を見て、clocksというチェーンテーブルの名前とdevを比較することができます.idおよびcon_id,dev_idマッチング、match+=2、con_idマッチング、match+=1、match>best_foundはこのclockの構造体が見つかったことを示しているが,もちろん,match==best_possibleこそ最も完璧な状況だ.ここで,マッチングの優先度はdev+con>dev only>con onlyの後にこのclock構造が_clk_create_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 (IS_ERR_OR_NULL(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;
}
ハッシュ・テーブルに追加して管理します.
最後にclk構造体を得た後、どのようにクロックを使用できるかを見ます:clk_enable関数:呼び出し関係は次のとおりです.
clk_enable
->clk_core_enable_lock
->clk_core_enable
static int clk_core_enable(struct clk_core *core)
{
int ret = 0;
lockdep_assert_held(&enable_lock);
if (!core)
return 0;
if (WARN_ON(core->prepare_count == 0))
return -ESHUTDOWN;
if (core->enable_count == 0) {
ret = clk_core_enable(core->parent);
if (ret)
return ret;
trace_clk_enable_rcuidle(core);
if (core->ops->enable)
ret = core->ops->enable(core->hw);
trace_clk_enable_complete_rcuidle(core);
if (ret) {
clk_core_disable(core->parent);
return ret;
}
}
core->enable_count++;
return 0;
}
中では、最初にcoreの有効性を判断し、その後、自分を呼び出して再帰処理を続け、伝達されたパラメータはcore->parentであり、一つのデバイスのclockを可能にするには、必ず親デバイスのclockが可能であることを知っておく必要があります.その後core->ops->enableコールバック関数を呼び出して使用できます!
最後の最後に、いくつかのAPI関数が与えられます:struct clk*_clk_lookup(const char*name)クロック名でクロックstatic inline int clk_を見つけるenable(struct clk*clk)イネーブルクロック、睡眠static inline void clk_disable(struct clk*clk)クロック禁止、眠らないstatic inline int clk_prepare_enable(struct clk*clk)イネーブルクロックは、static inline void clk_disable_unprepare(struct clk*clk)クロック禁止、睡眠static inline unsigned long clk_get_rate(struct clk*clk)クロック周波数static inline int clk_を取得set_rate(struct clk*clk,unsigned long rate)クロック周波数static inline long clk_を設定round_rate(struct clk*clk,unsigned long rate)最も近いクロック周波数static inline int clk_を取得set_parent(struct clk*clk,struct clk*parent)クロックの親クロックstatic inline struct clk*clk_を設定するget_parent(struct clk*clk)クロックの親クロックを取得