埋め込み式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)クロックの親クロックを取得