regulator-fixedとregulator-gpioの活用

1、regulator-fixed

概要:固定電圧のレギュレータを作成します。通常はGPIOで制御される電源ラインであり、有効(enable)と無効(disabled)の2つの状態のみサポートします。

device-treeノードの例

io_vdd_en: regulator-JW5217DFND {
    compatible = "regulator-fixed";
    pinctrl-names = "default";
    pinctrl-0 = <&io_vdd_en_pins_default>;
    gpios = <&wkup_gpio0 69 GPIO_ACTIVE_HIGH>;
    regulator-name = "jw5217dfnd";
    regulator-min-microvolt = <3300000>;
    regulator-max-microvolt = <3300000>;
    regulator-always-on;
    regulator-boot-on;
    enable-active-high;
    vin-supply = <&vsys_3v3>;
};

プロパティ解説

compatible = "regulator-fixed";

固定型レギュレータ的特点:電圧制御が不可能であり、enable/disableのみ可能です。使用されていない間は自動的に電源が切れます。関連するカーネルコードの一部を以下に示します:

// drivers/regulator/fixed.c

static const struct regulator_ops fixed_voltage_ops = {
    /* 電圧制御不可 */
};

static const struct regulator_ops fixed_voltage_clkenabled_ops = {
    .enable = reg_clock_enable,
    .disable = reg_clock_disable,
    .is_enabled = reg_clock_is_enabled,
};

static const struct of_device_id fixed_of_match[] = {
    {
        .compatible = "regulator-fixed",
        .data = &fixed_voltage_data,
    },
    {
        .compatible = "regulator-fixed-clock",
        .data = &fixed_clkenable_data,
    },
    { },
};

static struct platform_driver regulator_fixed_voltage_driver = {
    .probe  = reg_fixed_voltage_probe,
    .driver = {
        .name   = "reg-fixed-voltage",
        .of_match_table = of_match_ptr(fixed_of_match),
    },
};

gpios = <&wkup_gpio0 69 GPIO_ACTIVE_HIGH>;

電源制御用のGPIOです。有効時にGPIOを有効レベルに設定し、無効時に無効レベルに設定します。GPIO取得のコード:

// drivers/regulator/fixed.c

static int reg_fixed_voltage_probe(struct platform_device *pdev)
{
    struct fixed_voltage_data *drvdata;

    drvdata = devm_kzalloc(&pdev->dev, sizeof(struct fixed_voltage_data),
                   GFP_KERNEL);
    ...
    cfg.ena_gpiod = gpiod_get_optional(&pdev->dev, NULL, gflags);
    if (IS_ERR(cfg.ena_gpiod))
        return PTR_ERR(cfg.ena_gpiod);
    ...
}
// drivers/regulator/core.c

reg_fixed_voltage_probe
  -> devm_regulator_register
    -> regulator_register
      -> regulator_ena_gpio_request

static int regulator_ena_gpio_request(struct regulator_dev *rdev,
                const struct regulator_config *config)
{
    struct regulator_enable_gpio *pin, *new_pin;
    struct gpio_desc *gpiod;

    gpiod = config->ena_gpiod;
    new_pin = kzalloc(sizeof(*new_pin), GFP_KERNEL);
    ...
    pin = new_pin;
    pin->gpiod = gpiod;
    rdev->ena_pin = pin;
    ...
}

regulator-min-microvolt = <3300000>;

regulator-max-microvolt = <3300000>;

regulatorの最小・最大電圧制限です。regulator-fixedでは特に意味を持たないプロパティです。

regulator-always-on;

電源を常に有効な状態に保ちます。他の要因による電源遮断を防ぎます。このオプションを指定しない場合、他のドライバでregulator_enable()/regulator_disable()を明示的に呼び出す必要があります。

このオプションを指定すると、仮想デバイスが常にこのregulatorを使用している状態になります。以下のコマンドで確認可能です:

cat /sys/class/regulator/regulator.*/num_users  # このregulatorを使用しているデバイス数
cat /sys/class/regulator/regulator.*/state      # regulatorの状態(enabled/disabled)

regulator-boot-on;

システム起動時に自動的に電源を投入します。一定時間使用されていない場合は自動的に電源が切れる可能性があるため、regulator-always-onと組み合わせて使用する必要があります。

制約条件の設定コード:

// drivers/regulator/of_regulator.c

reg_fixed_voltage_probe
  -> of_get_fixed_voltage_config
    -> of_get_regulator_init_data
      -> of_get_regulation_constraints

static int of_get_regulation_constraints(struct device *dev,
                    struct device_node *np,
                    struct regulator_init_data **init_data,
                    const struct regulator_desc *desc)
{
    struct regulation_constraints *constraints = &(*init_data)->constraints;
    ...
    constraints->boot_on = of_property_read_bool(np, "regulator-boot-on");
    constraints->always_on = of_property_read_bool(np, "regulator-always-on");
    ...
}
// drivers/regulator/core.c

static int set_machine_constraints(struct regulator_dev *rdev)
{
    ...
    if (rdev->constraints->always_on || rdev->constraints->boot_on) {
        if (rdev->supply_name && !rdev->supply)
            return -EPROBE_DEFER;

        if (rdev->supply) {
            ret = regulator_enable(rdev->supply);
            if (ret < 0) {
                _regulator_put(rdev->supply);
                rdev->supply = NULL;
                return ret;
            }
        }

        ret = _regulator_do_enable(rdev);
        if (ret < 0 && ret != -EINVAL) {
            rdev_err(rdev, "failed to enable: %pe\n", ERR_PTR(ret));
            return ret;
        }

        if (rdev->constraints->always_on)
            rdev->use_count++;
    }
    ...
}
// drivers/regulator/core.c

static int _regulator_do_enable(struct regulator_dev *rdev)
{
    ...
    if (rdev->ena_pin) {
        if (!rdev->ena_gpio_state) {
            ret = regulator_ena_gpio_ctrl(rdev, true);
            if (ret < 0)
                return ret;
            rdev->ena_gpio_state = 1;
        }
    } else if (rdev->desc->ops->enable) {
        ret = rdev->desc->ops->enable(rdev);
        if (ret < 0)
            return ret;
    } else {
        return -EINVAL;
    }
    ...
}
// drivers/regulator/core.c

static int regulator_ena_gpio_ctrl(struct regulator_dev *rdev, bool enable)
{
    struct regulator_enable_gpio *pin = rdev->ena_pin;

    if (!pin)
        return -EINVAL;

    if (enable) {
        if (pin->enable_count == 0)
            gpiod_set_value_cansleep(pin->gpiod, 1);
        pin->enable_count++;
    } else {
        if (pin->enable_count > 1) {
            pin->enable_count--;
            return 0;
        }
        if (pin->enable_count <= 1) {
            gpiod_set_value_cansleep(pin->gpiod, 0);
            pin->enable_count = 0;
        }
    }
    return 0;
}

自動電源遮断のコード:

// drivers/regulator/core.c

regulator_init_complete_work_function
  -> regulator_late_cleanup
    -> _regulator_do_disable
      -> regulator_ena_gpio_ctrl
        -> gpiod_set_value_cansleep

static void regulator_init_complete_work_function(struct work_struct *work)
{
    class_for_each_device(&regulator_class, NULL, NULL,
                  regulator_register_resolve_supply);

    class_for_each_device(&regulator_class, NULL, NULL,
                  regulator_late_cleanup);
}

static DECLARE_DELAYED_WORK(regulator_init_complete_work,
                regulator_init_complete_work_function);

enable-active-high;

enable GPIOの有効レベルを高位に指定します(デフォルトは低位)。regulatorにのみ適用されます。GPIOプロパティ内のGPIO_ACTIVE_xxxは無視されます(警告が表示されます)。コード:

// drivers/gpio/gpiolib-of.c

static void of_gpio_flags_quirks(struct device_node *np,
                 const char *propname,
                 enum of_gpio_flags *flags,
                 int index)
{
    if (IS_ENABLED(CONFIG_REGULATOR) &&
        (of_device_is_compatible(np, "regulator-fixed") ||
         of_device_is_compatible(np, "reg-fixed-voltage") ||
         (!(strcmp(propname, "enable-gpio") &&
            strcmp(propname, "enable-gpios")) &&
          of_device_is_compatible(np, "regulator-gpio")))) {
        bool active_low = !of_property_read_bool(np,
                             "enable-active-low");
        if ((*flags & OF_GPIO_ACTIVE_LOW) && !active_low) {
            pr_warn("%s GPIO handle specifies active low - ignored\n",
                of_node_full_name(np));
            *flags &= ~OF_GPIO_ACTIVE_LOW;
        }
        if (active_low)
            *flags |= OF_GPIO_ACTIVE_LOW;
    }
    ...
}

2、regulator-gpio

概要:GPIOで制御される可変電圧・電流のレギュレータを作成します。複数のGPIOを使用して異なる電圧または電流値を出力でき、enable/disableに加えて電圧/電流制御をサポートします。

device-treeノードの例

vdd_sd_dv: regulator-TLV71033 {
    compatible = "regulator-gpio";
    pinctrl-names = "default";
    pinctrl-0 = <&vdd_sd_dv_pins_default>;
    regulator-name = "tlv71033";
    regulator-min-microvolt = <1800000>;
    regulator-max-microvolt = <3300000>;
    regulator-type = voltage;
    regulator-boot-on;
    enable-active-high;
    vin-supply = <&vsys_5v0>;
    enable-gpios = <&wkup_gpio0 69 GPIO_ACTIVE_HIGH>;
    gpios = <&main_gpio0 8 GPIO_ACTIVE_HIGH>;
    gpios-states = <0>;
    states = <1800000 0x0>, <3300000 0x1>;
};

プロパティ解説

compatible = "regulator-gpio";

GPIO制御型 регулятор的特点:GPIOを使用して電圧または電流を制御します。関連するコード:

// drivers/regulator/gpio-regulator.c

static const struct regulator_ops gpio_regulator_voltage_ops = {
    .get_voltage = gpio_regulator_get_value,
    .set_voltage = gpio_regulator_set_value,
    .list_voltage = gpio_regulator_list_voltage,
};

static const struct regulator_ops gpio_regulator_current_ops = {
    .get_current_limit = gpio_regulator_get_current,
    .set_current_limit = gpio_regulator_set_current,
};

static const struct of_device_id regulator_gpio_of_match[] = {
    { .compatible = "regulator-gpio", },
    { },
};

static struct platform_driver gpio_regulator_driver = {
    .probe  = gpio_regulator_probe,
    .driver = {
        .name   = "gpio-regulator",
        .of_match_table = of_match_ptr(regulator_gpio_of_match),
    },
};

static int gpio_regulator_probe(struct platform_device *pdev)
{
    ...
    switch (config->type) {
    case REGULATOR_VOLTAGE:
        drvdata->desc.type = REGULATOR_VOLTAGE;
        drvdata->desc.ops = &gpio_regulator_voltage_ops;
        drvdata->desc.n_voltages = config->nr_states;
        break;
    case REGULATOR_CURRENT:
        drvdata->desc.type = REGULATOR_CURRENT;
        drvdata->desc.ops = &gpio_regulator_current_ops;
        break;
    default:
        dev_err(dev, "No regulator type set\n");
        return -EINVAL;
    }
    ...
}

regulator-min-microvolt = <1800000>;

regulator-max-microvolt = <3300000>;

regulatorの最小・最大電圧制限です。

regulator-type = voltage;

regulatorのタイプを指定します。オプション:voltage(デフォルト)、current。regulator-gpioでのみ有効です。

// drivers/regulator/gpio-regulator.c

gpio_regulator_probe
  -> of_get_gpio_regulator_config

static struct gpio_regulator_config *
of_get_gpio_regulator_config(struct device *dev, struct device_node *np,
                 const struct regulator_desc *desc)
{
    ...
    config->type = REGULATOR_VOLTAGE;
    ret = of_property_read_string(np, "regulator-type", &regtype);
    if (ret >= 0) {
        if (!strncmp("voltage", regtype, 7))
            config->type = REGULATOR_VOLTAGE;
        else if (!strncmp("current", regtype, 7))
            config->type = REGULATOR_CURRENT;
        else
            dev_warn(dev, "Unknown regulator-type '%s'\n", regtype);
    }
    return config;
}

enable-gpios = <&wkup_gpio0 69 GPIO_ACTIVE_HIGH>;

regulator-fixedとは異なり、regulator-gpioでは有効ピンをenable-gpiosプロパティで指定します(gpiosではありません)。

// drivers/regulator/gpio-regulator.c

static int gpio_regulator_probe(struct platform_device *pdev)
{
    ...
    if (config->enabled_at_boot)
        gflags = GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_NONEXCLUSIVE;
    else
        gflags = GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_NONEXCLUSIVE;

    cfg.ena_gpiod = gpiod_get_optional(dev, "enable", gflags);
    if (IS_ERR(cfg.ena_gpiod))
        return PTR_ERR(cfg.ena_gpiod);
    ...
}

gpios = <&main_gpio0 8 GPIO_ACTIVE_HIGH>;

複数の異なる電圧または電流値を出力するためのGPIOです。複数のGPIOをサポートしています。

gpios-states = <0>;

GPIOのデフォルト出力レベルを設定します。1は有効レベル、0は無効レベルで、regulatorのデフォルト出力値を設定します。gpios-statesの要素はgpiosの要素と一対一で対応します。

// drivers/regulator/gpio-regulator.c

gpio_regulator_probe
  -> of_get_gpio_regulator_config

static struct gpio_regulator_config *
of_get_gpio_regulator_config(struct device *dev, struct device_node *np,
                 const struct regulator_desc *desc)
{
    struct gpio_regulator_config *config;
    int ngpios;

    config = devm_kzalloc(dev, sizeof(struct gpio_regulator_config), GFP_KERNEL);
    ...
    ngpios = gpiod_count(dev, NULL);
    if (ngpios > 0) {
        config->gflags = devm_kzalloc(dev, sizeof(enum gpiod_flags) * ngpios,
                          GFP_KERNEL);
        if (!config->gflags)
            return ERR_PTR(-ENOMEM);

        for (i = 0; i < ngpios; i++) {
            u32 val;
            ret = of_property_read_u32_index(np, "gpios-states", i, &val);
            if (ret)
                config->gflags[i] = GPIOD_OUT_HIGH;
            else
                config->gflags[i] = val ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
        }
    }
    config->ngpios = ngpios;
    ...
}
// drivers/regulator/gpio-regulator.c

static int gpio_regulator_probe(struct platform_device *pdev)
{
    struct gpio_regulator_data *drvdata;

    drvdata = devm_kzalloc(dev, sizeof(struct gpio_regulator_data), GFP_KERNEL);
    if (drvdata == NULL)
        return -ENOMEM;

    if (np) {
        config = of_get_gpio_regulator_config(dev, np, &drvdata->desc);
        if (IS_ERR(config))
            return PTR_ERR(config);
    }
    ...
    drvdata->gpiods = devm_kzalloc(dev, sizeof(struct gpio_desc *), GFP_KERNEL);
    if (!drvdata->gpiods)
        return -ENOMEM;
    for (i = 0; i < config->ngpios; i++) {
        drvdata->gpiods[i] = devm_gpiod_get_index(dev, NULL, i, config->gflags[i]);
        if (IS_ERR(drvdata->gpiods[i]))
            return PTR_ERR(drvdata->gpiods[i]);
        gpiod_set_consumer_name(drvdata->gpiods[i], drvdata->desc.name);
    }
    drvdata->nr_gpios = config->ngpios;
    ...
    state = 0;
    for (ptr = 0; ptr < drvdata->nr_gpios; ptr++) {
        if (config->gflags[ptr] == GPIOD_OUT_HIGH)
            state |= (1 << ptr);
    }
    drvdata->state = state;
    ...
}

states = <1800000 0x0>, <3300000 0x1>;

regulator-gpioの核心となる設定です。偶数番目の要素が電圧または電流値,奇数番目の要素がGPIO出力レベルのバイナリ組み合わせエンコード値です。

// drivers/regulator/gpio-regulator.c

gpio_regulator_probe
  -> of_get_gpio_regulator_config

static struct gpio_regulator_config *
of_get_gpio_regulator_config(struct device *dev, struct device_node *np,
                 const struct regulator_desc *desc)
{
    ...
    proplen = of_property_count_u32_elems(np, "states");
    if (proplen < 0) {
        dev_err(dev, "No 'states' property found\n");
        return ERR_PTR(-EINVAL);
    }

    config->states = devm_kcalloc(dev, proplen / 2,
                sizeof(struct gpio_regulator_state), GFP_KERNEL);
    if (!config->states)
        return ERR_PTR(-ENOMEM);

    for (i = 0; i < proplen / 2; i++) {
        of_property_read_u32_index(np, "states", i * 2,
                       &config->states[i].value);
        of_property_read_u32_index(np, "states", i * 2 + 1,
                       &config->states[i].gpios);
    }
    config->nr_states = i;
    ...
}
// drivers/regulator/gpio-regulator.c

static int gpio_regulator_probe(struct platform_device *pdev)
{
    ...
    drvdata->states = devm_kmemdup(dev, config->states,
                   config->nr_states * sizeof(struct gpio_regulator_state),
                   GFP_KERNEL);
    if (drvdata->states == NULL) {
        dev_err(dev, "Failed to allocate state data\n");
        return -ENOMEM;
    }
    drvdata->nr_states = config->nr_states;
    ...
}

電圧・電流の取得・設定コード:

static int gpio_regulator_get_value(struct regulator_dev *dev)
{
    struct gpio_regulator_data *data = rdev_get_drvdata(dev);
    int ptr;

    for (ptr = 0; ptr < data->nr_states; ptr++)
        if (data->states[ptr].gpios == data->state)
            return data->states[ptr].value;

    return -EINVAL;
}

static int gpio_regulator_set_voltage(struct regulator_dev *dev,
                    int min_uV, int max_uV, unsigned *selector)
{
    struct gpio_regulator_data *data = rdev_get_drvdata(dev);
    int ptr, target = 0, state, best_val = INT_MAX;

    for (ptr = 0; ptr < data->nr_states; ptr++)
        if (data->states[ptr].value < best_val &&
            data->states[ptr].value >= min_uV &&
            data->states[ptr].value <= max_uV) {
            target = data->states[ptr].gpios;
            best_val = data->states[ptr].value;
            if (selector)
                *selector = ptr;
        }

    if (best_val == INT_MAX)
        return -EINVAL;

    for (ptr = 0; ptr < data->nr_gpios; ptr++) {
        state = (target & (1 << ptr)) >> ptr;
        gpiod_set_value_cansleep(data->gpiods[ptr], state);
    }
    data->state = target;

    return 0;
}

static int gpio_regulator_set_current_limit(struct regulator_dev *dev,
                    int min_uA, int max_uA)
{
    struct gpio_regulator_data *data = rdev_get_drvdata(dev);
    int ptr, target = 0, state, best_val = 0;

    for (ptr = 0; ptr < data->nr_states; ptr++)
        if (data->states[ptr].value > best_val &&
            data->states[ptr].value >= min_uA &&
            data->states[ptr].value <= max_uA) {
            target = data->states[ptr].gpios;
            best_val = data->states[ptr].value;
        }

    if (best_val == 0)
        return -EINVAL;

    for (ptr = 0; ptr < data->nr_gpios; ptr++) {
        state = (target & (1 << ptr)) >> ptr;
        gpiod_set_value_cansleep(data->gpiods[ptr], state);
    }
    data->state = target;

    return 0;
}

上記のコードから分かるように、電圧・電流の設定は具体的な値ではなく範囲を指定します。処理ロジックは以下の通りです:

  1. 全ての状態(device-treeのstatesプロパティ)を走査し、条件を満たす最適な値を見つけます( voltageの場合、条件を満たす最小値电流の場合、条件を満たす最大値)。最適な電圧/电流値とGPIO出力レベルのエンコード値を保存します。
  2. エンコード値に基づいて全てのGPIO出力レベルを設定します。GPIOの順序はバイナリ順に対応しています(例:1番目のGPIOは1ビット目に対応)。

タグ: linux-kernel device-tree regulator GPIO Embedded-Systems

6月6日 17:36 投稿