Ultra96の電源制御

LTC2954のエラー内容は下記のとおりです。

root@ultra96:~# dmesg | grep -i ltc
[    1.393172] ltc2952-poweroff ltc2954: pm_power_off already registered
[    1.393186] ltc2952-poweroff: probe of ltc2954 failed with error -16

単純に言えば、すでに登録されてしまっているんだなってことだよね。

ということは誰に負けたのかを見つければいいだけで・・・

Devicetreeから逆に追いかけるとltc2954-poweroff.cにたどり着く。

ソースコードをみるとprobeの最初でエラーしていることがわかる。

static int ltc2952_poweroff_probe(struct platform_device *pdev)
{
    int ret;
    struct ltc2952_poweroff *data;

    if (pm_power_off) {
        dev_err(&pdev->dev, "pm_power_off already registered");
        return -EBUSY;
    }

ということはpm_power_offを先にセットした奴を見つければよいのだ。

これは単なる犯人探しゲームのようなものだな。

さて、ざっくりとソースコードからpm_power_off変数にセットする奴を探してみましょう。

grep -R -i pm_power_off|grep pm_power_off
platform/x86/pmc_atom.c:    if (acpi_base_addr != 0 && pm_power_off == NULL)
platform/x86/pmc_atom.c:        pm_power_off = pmc_power_off;
parisc/power.c:         if (pm_power_off)
parisc/power.c:             pm_power_off();
char/ipmi/ipmi_poweroff.c:  old_poweroff_func = pm_power_off;
char/ipmi/ipmi_poweroff.c:  pm_power_off = ipmi_poweroff_function;
char/ipmi/ipmi_poweroff.c:  pm_power_off = old_poweroff_func;
char/ipmi/ipmi_poweroff.c:      pm_power_off = old_poweroff_func;
mfd/tps80031.c: if (pdata->use_power_off && !pm_power_off) {
mfd/tps80031.c:     pm_power_off = tps80031_power_off;
mfd/tps80031.c:     pm_power_off = NULL;
mfd/rn5t618.c:static struct rn5t618 *rn5t618_pm_power_off;
mfd/rn5t618.c:  regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_REPCNT,
mfd/rn5t618.c:  regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_SLPCNT,
mfd/rn5t618.c:  rn5t618_pm_power_off = priv;
mfd/rn5t618.c:      if (!pm_power_off)
mfd/rn5t618.c:          pm_power_off = rn5t618_power_off;
mfd/rn5t618.c:  if (priv == rn5t618_pm_power_off) {
mfd/rn5t618.c:      rn5t618_pm_power_off = NULL;
mfd/rn5t618.c:      pm_power_off = NULL;
mfd/tps65910.c: if (pmic_plat_data->pm_off && !pm_power_off) {
mfd/tps65910.c:     pm_power_off = tps65910_power_off;
mfd/max8907.c:  if (pm_off && !pm_power_off) {
mfd/max8907.c:      pm_power_off = max8907_power_off;
mfd/palmas.c:       } else if (pdata->pm_off && !pm_power_off) {
mfd/palmas.c:           pm_power_off = palmas_power_off;
mfd/palmas.c:       pm_power_off = NULL;
mfd/retu-mfd.c:static struct retu_dev *retu_pm_power_off;
mfd/retu-mfd.c: struct retu_dev *rdev = retu_pm_power_off;
mfd/retu-mfd.c: mutex_lock(&retu_pm_power_off->mutex);
mfd/retu-mfd.c: mutex_unlock(&retu_pm_power_off->mutex);
mfd/retu-mfd.c: if (i2c->addr == 1 && !pm_power_off) {
mfd/retu-mfd.c:     retu_pm_power_off = rdev;
mfd/retu-mfd.c:     pm_power_off      = retu_power_off;
mfd/retu-mfd.c: if (retu_pm_power_off == rdev) {
mfd/retu-mfd.c:     pm_power_off      = NULL;
mfd/retu-mfd.c:     retu_pm_power_off = NULL;
mfd/tps6586x.c: if (pdata->pm_off && !pm_power_off) {
mfd/tps6586x.c:     pm_power_off = tps6586x_power_off;
mfd/rk808.c:    if (pm_off && !pm_power_off) {
mfd/rk808.c:        pm_power_off = pm_pwroff_fn;
mfd/rk808.c:    pm_power_off = NULL;
mfd/axp20x.c:static struct axp20x_dev *axp20x_pm_power_off;
mfd/axp20x.c:   if (axp20x_pm_power_off->variant == AXP288_ID)
mfd/axp20x.c:   regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL,
mfd/axp20x.c:   if (!pm_power_off) {
mfd/axp20x.c:       axp20x_pm_power_off = axp20x;
mfd/axp20x.c:       pm_power_off = axp20x_power_off;
mfd/axp20x.c:   if (axp20x == axp20x_pm_power_off) {
mfd/axp20x.c:       axp20x_pm_power_off = NULL;
mfd/axp20x.c:       pm_power_off = NULL;
mfd/dm355evm_msp.c: pm_power_off = NULL;
mfd/dm355evm_msp.c: pm_power_off = dm355evm_power_off;
mfd/ab8500-sysctrl.c:   if (!pm_power_off)
mfd/ab8500-sysctrl.c:       pm_power_off = ab8500_power_off;
mfd/ab8500-sysctrl.c:   if (pm_power_off == ab8500_power_off)
mfd/ab8500-sysctrl.c:       pm_power_off = NULL;
mfd/twl4030-power.c:    if (twl4030_power_use_poweroff(pdata, node) && !pm_power_off) {
mfd/twl4030-power.c:        pm_power_off = twl4030_power_off;
rtc/rtc-jz4740.c:       if (!pm_power_off) {
rtc/rtc-jz4740.c:           pm_power_off = jz4740_rtc_power_off;
rtc/rtc-omap.c:     if (!pm_power_off) {
rtc/rtc-omap.c:         pm_power_off = omap_rtc_power_off;
rtc/rtc-omap.c: if (pm_power_off == omap_rtc_power_off &&
rtc/rtc-omap.c:     pm_power_off = NULL;
pinctrl/qcom/pinctrl-msm.c:         pm_power_off = msm_ps_hold_poweroff;
memory/emif.c:      if (pm_power_off) {
memory/emif.c:          WARN(1, "FIXME: NO pm_power_off!!! trying restart\n");
staging/nvec/nvec.c:    pm_power_off = nvec_power_off;
staging/nvec/nvec.c:    pm_power_off = NULL;
power/reset/vexpress-poweroff.c:        pm_power_off = vexpress_power_off;
power/reset/restart-poweroff.c: /* If a pm_power_off function has already been added, leave it alone */
power/reset/restart-poweroff.c: if (pm_power_off != NULL) {
power/reset/restart-poweroff.c:         "pm_power_off function already registered");
power/reset/restart-poweroff.c: pm_power_off = &restart_poweroff_do_poweroff;
power/reset/restart-poweroff.c: if (pm_power_off == &restart_poweroff_do_poweroff)
power/reset/restart-poweroff.c:     pm_power_off = NULL;
power/reset/at91-poweroff.c:    pm_power_off = at91_poweroff;
power/reset/at91-poweroff.c:        pm_power_off = at91_lpddr_poweroff;
power/reset/at91-poweroff.c:    if (pm_power_off == at91_poweroff ||
power/reset/at91-poweroff.c:        pm_power_off == at91_lpddr_poweroff)
power/reset/at91-poweroff.c:        pm_power_off = NULL;
power/reset/Kconfig:      say N here or disable in dts to make sure pm_power_off never be
power/reset/qnap-poweroff.c:    if (pm_power_off) {
power/reset/qnap-poweroff.c:        lookup_symbol_name((ulong)pm_power_off, symname);
power/reset/qnap-poweroff.c:            "pm_power_off already claimed %p %s",
power/reset/qnap-poweroff.c:            pm_power_off, symname);
power/reset/qnap-poweroff.c:    pm_power_off = qnap_power_off;
power/reset/qnap-poweroff.c:    pm_power_off = NULL;
power/reset/gpio-poweroff.c: * since pm_power_off itself is global.
power/reset/gpio-poweroff.c:    /* If a pm_power_off function has already been added, leave it alone */
power/reset/gpio-poweroff.c:    if (pm_power_off != NULL) {
power/reset/gpio-poweroff.c:            "%s: pm_power_off function already registered",
power/reset/gpio-poweroff.c:    pm_power_off = &gpio_poweroff_do_poweroff;
power/reset/gpio-poweroff.c:    if (pm_power_off == &gpio_poweroff_do_poweroff)
power/reset/gpio-poweroff.c:        pm_power_off = NULL;
power/reset/gemini-poweroff.c:  pm_power_off = gemini_poweroff;
power/reset/syscon-poweroff.c:  if (pm_power_off) {
power/reset/syscon-poweroff.c:      lookup_symbol_name((ulong)pm_power_off, symname);
power/reset/syscon-poweroff.c:      "pm_power_off already claimed %p %s",
power/reset/syscon-poweroff.c:      pm_power_off, symname);
power/reset/syscon-poweroff.c:  pm_power_off = syscon_poweroff;
power/reset/syscon-poweroff.c:  if (pm_power_off == syscon_poweroff)
power/reset/syscon-poweroff.c:      pm_power_off = NULL;
power/reset/imx-snvs-poweroff.c:    pm_power_off = do_imx_poweroff;
power/reset/piix4-poweroff.c:   pm_power_off = piix4_poweroff;
power/reset/piix4-poweroff.c:   if (pm_power_off == piix4_poweroff)
power/reset/piix4-poweroff.c:       pm_power_off = NULL;
power/reset/at91-sama5d2_shdwc.c: * since pm_power_off itself is global.
power/reset/at91-sama5d2_shdwc.c:   pm_power_off = at91_poweroff;
power/reset/at91-sama5d2_shdwc.c:       pm_power_off = at91_lpddr_poweroff;
power/reset/at91-sama5d2_shdwc.c:   if (pm_power_off == at91_poweroff ||
power/reset/at91-sama5d2_shdwc.c:       pm_power_off == at91_lpddr_poweroff)
power/reset/at91-sama5d2_shdwc.c:       pm_power_off = NULL;
power/reset/ltc2952-poweroff.c: * This global variable is only needed for pm_power_off. We should
power/reset/ltc2952-poweroff.c: if (pm_power_off) {
power/reset/ltc2952-poweroff.c:     dev_err(&pdev->dev, "pm_power_off already registered");
power/reset/ltc2952-poweroff.c: pm_power_off = ltc2952_poweroff_kill;
power/reset/ltc2952-poweroff.c: pm_power_off = NULL;
power/reset/msm-poweroff.c: pm_power_off = do_msm_poweroff;
power/reset/as3722-poweroff.c:static void as3722_pm_power_off(void)
power/reset/as3722-poweroff.c:  if (!pm_power_off)
power/reset/as3722-poweroff.c:      pm_power_off = as3722_pm_power_off;
power/reset/as3722-poweroff.c:  if (pm_power_off == as3722_pm_power_off)
power/reset/as3722-poweroff.c:      pm_power_off = NULL;
acpi/sleep.c:       pm_power_off_prepare = acpi_power_off_prepare;
acpi/sleep.c:       pm_power_off = acpi_power_off;
regulator/act8865-regulator.c:      if (!pm_power_off && (off_reg > 0)) {
regulator/act8865-regulator.c:          pm_power_off = act8865_power_off;
firmware/psci.c:    pm_power_off = psci_sys_poweroff;
firmware/efi/reboot.c:static void (*orig_pm_power_off)(void);
firmware/efi/reboot.c:  if (orig_pm_power_off)
firmware/efi/reboot.c:      orig_pm_power_off();
firmware/efi/reboot.c:      orig_pm_power_off = pm_power_off;
firmware/efi/reboot.c:      pm_power_off = efi_power_off;
watchdog/bcm2835_wdt.c: if (pm_power_off == NULL)
watchdog/bcm2835_wdt.c:     pm_power_off = bcm2835_power_off;
watchdog/bcm2835_wdt.c: if (pm_power_off == bcm2835_power_off)
watchdog/bcm2835_wdt.c:     pm_power_off = NULL;

これでおおよそ、"pm_power_off = "で、もちろんスペース込みで検索すればさらに犯人を絞れます。

$ grep -R -i pm_power_off|grep "pm_power_off = "
platform/x86/pmc_atom.c:        pm_power_off = pmc_power_off;
char/ipmi/ipmi_poweroff.c:  pm_power_off = ipmi_poweroff_function;
char/ipmi/ipmi_poweroff.c:  pm_power_off = old_poweroff_func;
char/ipmi/ipmi_poweroff.c:      pm_power_off = old_poweroff_func;
mfd/tps80031.c:     pm_power_off = tps80031_power_off;
mfd/tps80031.c:     pm_power_off = NULL;
mfd/rn5t618.c:  rn5t618_pm_power_off = priv;
mfd/rn5t618.c:          pm_power_off = rn5t618_power_off;
mfd/rn5t618.c:      rn5t618_pm_power_off = NULL;
mfd/rn5t618.c:      pm_power_off = NULL;
mfd/tps65910.c:     pm_power_off = tps65910_power_off;
mfd/max8907.c:      pm_power_off = max8907_power_off;
mfd/palmas.c:           pm_power_off = palmas_power_off;
mfd/palmas.c:       pm_power_off = NULL;
mfd/retu-mfd.c:     retu_pm_power_off = rdev;
mfd/retu-mfd.c:     retu_pm_power_off = NULL;
mfd/tps6586x.c:     pm_power_off = tps6586x_power_off;
mfd/rk808.c:        pm_power_off = pm_pwroff_fn;
mfd/rk808.c:    pm_power_off = NULL;
mfd/axp20x.c:       axp20x_pm_power_off = axp20x;
mfd/axp20x.c:       pm_power_off = axp20x_power_off;
mfd/axp20x.c:       axp20x_pm_power_off = NULL;
mfd/axp20x.c:       pm_power_off = NULL;
mfd/dm355evm_msp.c: pm_power_off = NULL;
mfd/dm355evm_msp.c: pm_power_off = dm355evm_power_off;
mfd/ab8500-sysctrl.c:       pm_power_off = ab8500_power_off;
mfd/ab8500-sysctrl.c:       pm_power_off = NULL;
mfd/twl4030-power.c:        pm_power_off = twl4030_power_off;
rtc/rtc-jz4740.c:           pm_power_off = jz4740_rtc_power_off;
rtc/rtc-omap.c:         pm_power_off = omap_rtc_power_off;
rtc/rtc-omap.c:     pm_power_off = NULL;
pinctrl/qcom/pinctrl-msm.c:         pm_power_off = msm_ps_hold_poweroff;
staging/nvec/nvec.c:    pm_power_off = nvec_power_off;
staging/nvec/nvec.c:    pm_power_off = NULL;
power/reset/vexpress-poweroff.c:        pm_power_off = vexpress_power_off;
power/reset/restart-poweroff.c: pm_power_off = &restart_poweroff_do_poweroff;
power/reset/restart-poweroff.c:     pm_power_off = NULL;
power/reset/at91-poweroff.c:    pm_power_off = at91_poweroff;
power/reset/at91-poweroff.c:        pm_power_off = at91_lpddr_poweroff;
power/reset/at91-poweroff.c:        pm_power_off = NULL;
power/reset/qnap-poweroff.c:    pm_power_off = qnap_power_off;
power/reset/qnap-poweroff.c:    pm_power_off = NULL;
power/reset/gpio-poweroff.c:    pm_power_off = &gpio_poweroff_do_poweroff;
power/reset/gpio-poweroff.c:        pm_power_off = NULL;
power/reset/gemini-poweroff.c:  pm_power_off = gemini_poweroff;
power/reset/syscon-poweroff.c:  pm_power_off = syscon_poweroff;
power/reset/syscon-poweroff.c:      pm_power_off = NULL;
power/reset/imx-snvs-poweroff.c:    pm_power_off = do_imx_poweroff;
power/reset/piix4-poweroff.c:   pm_power_off = piix4_poweroff;
power/reset/piix4-poweroff.c:       pm_power_off = NULL;
power/reset/at91-sama5d2_shdwc.c:   pm_power_off = at91_poweroff;
power/reset/at91-sama5d2_shdwc.c:       pm_power_off = at91_lpddr_poweroff;
power/reset/at91-sama5d2_shdwc.c:       pm_power_off = NULL;
power/reset/ltc2952-poweroff.c: pm_power_off = ltc2952_poweroff_kill;
power/reset/ltc2952-poweroff.c: pm_power_off = NULL;
power/reset/msm-poweroff.c: pm_power_off = do_msm_poweroff;
power/reset/as3722-poweroff.c:      pm_power_off = as3722_pm_power_off;
power/reset/as3722-poweroff.c:      pm_power_off = NULL;
acpi/sleep.c:       pm_power_off = acpi_power_off;
regulator/act8865-regulator.c:          pm_power_off = act8865_power_off;
firmware/psci.c:    pm_power_off = psci_sys_poweroff;
firmware/efi/reboot.c:      orig_pm_power_off = pm_power_off;
firmware/efi/reboot.c:      pm_power_off = efi_power_off;
watchdog/bcm2835_wdt.c:     pm_power_off = bcm2835_power_off;
watchdog/bcm2835_wdt.c:     pm_power_off = NULL;

オブジェクトが作成されているか確認する

相当な数を絞って、最後にこの中からオブジェクトになっているソースコードのみに絞ります。

そしたら、下記のように3だけがpm_power_offを書き込むのね。

$ grep -R -i pm_power_off|grep "pm_power_off = "
power/reset/ltc2952-poweroff.c: pm_power_off = ltc2952_poweroff_kill;
firmware/psci.c:    pm_power_off = psci_sys_poweroff;
firmware/efi/reboot.c:      pm_power_off = efi_power_off;

printkを入れて犯人探し

3つぐらいの関数ならprintkを入れて実行した一発で答え出ますよ。

root@ultra96:~# dmesg|grep KNL
[    0.000000] [KNL] psci_0_2_set_functions: ffffff800875d4f8
[    1.395875] [KNL] ltc2952_poweroff_probe: ffffff800875d4f8

ほらね。

犯人はfirmware/psci.cでした。

ここからどうするかです

じゃぁ、firmware/psci.cをロードさせているトリガーは何か探す必要が有ります。

このドライバがDeviceTreeでロードされているのか、それとも固定的に組み込まれているドライバなのか判断する必要が有ります。

もし、固定的なドライバならコンフィグで外せるか、もしくは、固定的に組み込まれて勝てないので諦めるかしなければいけません。

これがもし、DeviceTreeでロードされているなら設定の方法によって対照することができる可能性が残っています。

と、compatibleを見てみると・・・

static const struct of_device_id psci_of_match[] __initconst = {
    { .compatible = "arm,psci", .data = psci_0_1_init},
    { .compatible = "arm,psci-0.2", .data = psci_0_2_init},
    { .compatible = "arm,psci-1.0", .data = psci_0_2_init},
    {},
};

DeviceTreeでロードしてるので確認すると・・・

        idle-states {
            entry-method = "arm,psci";

            CPU_SLEEP_0: cpu-sleep-0 {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x40000000>;
                local-timer-stop;
                entry-latency-us = <300>;
                exit-latency-us = <600>;
                min-residency-us = <10000>;
            };
        };
    psci {
        compatible = "arm,psci-0.2";
        method = "smc";
    };

Ultra96のsysfdを確認するとどうも後者側の方で呼び出されているようです。

そこでDeviceTreeからpsciを無効にしました。

そうすると次のようにLTC2954のドライバが読み込まれました。

[    1.462987] ltc2952-poweroff ltc2954: unable to claim gpio "watchdog"
[    4.328691] ltc2952-poweroff ltc2954: probe successful

ここまでできたのですが、poweroffとかshutdownで電源が落ちません。

しかも、シリアルポートが見えないのでどこまで進んだかわからずじまい・・・

まぁ、落ちなくてもいいかぁ・・・

write: 2018/11/10/ 20:52:53