diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2022-09-14 15:52:59 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2022-09-14 15:52:59 +0200 |
commit | 19df0b5883737c6ce50b893dfc8fe1722cd6560a (patch) | |
tree | 4a90f708d33a44f0b40eab69134543c38629c884 /drivers | |
parent | d01b0a64580833b4ccf10e6361badee1d81e1221 (diff) | |
parent | 7a9d2377b5e54cfec28a7645985a7371c1d8b8af (diff) | |
download | barebox-19df0b5883737c6ce50b893dfc8fe1722cd6560a.tar.gz barebox-19df0b5883737c6ce50b893dfc8fe1722cd6560a.tar.xz |
Merge branch 'for-next/misc'
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/base/Kconfig | 3 | ||||
-rw-r--r-- | drivers/base/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/driver.c | 25 | ||||
-rw-r--r-- | drivers/base/featctrl.c | 160 | ||||
-rw-r--r-- | drivers/base/power.c | 10 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.c | 59 | ||||
-rw-r--r-- | drivers/i2c/i2c.c | 33 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/axp20x-i2c.c | 71 | ||||
-rw-r--r-- | drivers/mfd/axp20x.c | 360 | ||||
-rw-r--r-- | drivers/net/phy/mdio_bus.c | 2 | ||||
-rw-r--r-- | drivers/nvmem/core.c | 33 | ||||
-rw-r--r-- | drivers/nvmem/ocotp.c | 62 | ||||
-rw-r--r-- | drivers/of/Kconfig | 12 | ||||
-rw-r--r-- | drivers/of/base.c | 67 | ||||
-rw-r--r-- | drivers/of/overlay.c | 20 | ||||
-rw-r--r-- | drivers/of/platform.c | 17 | ||||
-rw-r--r-- | drivers/power/reset/reboot-mode.c | 13 | ||||
-rw-r--r-- | drivers/reset/Kconfig | 1 | ||||
-rw-r--r-- | drivers/soc/imx/Kconfig | 6 | ||||
-rw-r--r-- | drivers/soc/imx/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/imx/imx8m-featctrl.c | 64 |
23 files changed, 982 insertions, 47 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 5bc70aa1e5..eebb60ce91 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -2,3 +2,6 @@ config PM_GENERIC_DOMAINS bool + +config FEATURE_CONTROLLER + bool "Feature controller support" if COMPILE_TEST diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 59645c6f53..e8e354cdaa 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,3 +6,4 @@ obj-y += resource.o obj-y += regmap/ obj-$(CONFIG_PM_GENERIC_DOMAINS) += power.o +obj-$(CONFIG_FEATURE_CONTROLLER) += featctrl.o diff --git a/drivers/base/driver.c b/drivers/base/driver.c index e7288f6a61..cbe1c974f4 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -27,6 +27,7 @@ #include <linux/err.h> #include <complete.h> #include <pinctrl.h> +#include <featctrl.h> #include <linux/clk/clk-conf.h> #ifdef CONFIG_DEBUG_PROBES @@ -45,6 +46,22 @@ LIST_HEAD(active_device_list); EXPORT_SYMBOL(active_device_list); static LIST_HEAD(deferred); +struct device_d *find_device(const char *str) +{ + struct device_d *dev; + struct device_node *np; + + dev = get_device_by_name(str); + if (dev) + return dev; + + np = of_find_node_by_path_or_alias(NULL, str); + if (np) + return of_find_device_by_node(np); + + return NULL; +} + struct device_d *get_device_by_name(const char *name) { struct device_d *dev; @@ -85,6 +102,14 @@ int device_probe(struct device_d *dev) static int depth = 0; int ret; + ret = of_feature_controller_check(dev->device_node); + if (ret < 0) + return ret; + if (ret == FEATCTRL_GATED) { + dev_dbg(dev, "feature gated, skipping probe\n"); + return -ENODEV; + } + depth++; pr_report_probe("%*sprobe-> %s\n", depth * 4, "", dev_name(dev)); diff --git a/drivers/base/featctrl.c b/drivers/base/featctrl.c new file mode 100644 index 0000000000..abe21698ed --- /dev/null +++ b/drivers/base/featctrl.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2022 Ahmad Fatoum, Pengutronix + +#define pr_fmt(fmt) "featctrl: " fmt + +#include <common.h> +#include <driver.h> +#include <errno.h> +#include <of.h> + +#include <featctrl.h> + +/* List of registered feature controllers */ +static LIST_HEAD(of_feature_controllers); + +/** + * feature_controller_register() - Register a feature controller + * @feat: Pointer to feature controller + */ +int feature_controller_register(struct feature_controller *feat) +{ + struct device_node *np = dev_of_node(feat->dev); + + if (!np) + return -EINVAL; + + list_add(&feat->list, &of_feature_controllers); + dev_dbg(feat->dev, "Registering feature controller\n"); + return 0; +} +EXPORT_SYMBOL_GPL(feature_controller_register); + +/** + * featctrl_get_from_provider() - Look-up feature gate + * @spec: OF phandle args to use for look-up + * @gateid: ID of feature controller gate populated on successful lookup + * + * Looks for a feature controller under the node specified by @spec. + * + * Returns a valid pointer to struct feature_controller on success or ERR_PTR() + * on failure. + */ +static struct feature_controller *featctrl_get_from_provider(struct of_phandle_args *spec, + unsigned *gateid) +{ + struct feature_controller *featctrl; + int ret; + + if (!spec) + return ERR_PTR(-EINVAL); + + ret = of_device_ensure_probed(spec->np); + if (ret < 0) + return ERR_PTR(ret); + + /* Check if we have such a controller in our array */ + list_for_each_entry(featctrl, &of_feature_controllers, list) { + if (dev_of_node(featctrl->dev) == spec->np) { + *gateid = spec->args[0]; + return featctrl; + } + } + + return ERR_PTR(-ENOENT); +} + +/** + * of_feature_controller_check - Check whether a feature controller gates the device + * @np: Device node to check + * + * Parse device's OF node to find a feature controller specifier. If such is + * found, checks it to determine whether device is gated. + * + * Returns FEATCTRL_GATED if a specified feature controller gates the device + * and FEATCTRL_OKAY if none do. On error a negative error code is returned. + */ +int of_feature_controller_check(struct device_node *np) +{ + struct of_phandle_args featctrl_args; + struct feature_controller *featctrl; + int ret, err = 0, i, ngates; + + ngates = of_count_phandle_with_args(np, "barebox,feature-gates", + "#feature-cells"); + if (ngates <= 0) + return FEATCTRL_OKAY; + + for (i = 0; i < ngates; i++) { + unsigned gateid = 0; + + ret = of_parse_phandle_with_args(np, "barebox,feature-gates", + "#feature-cells", i, &featctrl_args); + if (ret < 0) + return ret; + + featctrl = featctrl_get_from_provider(&featctrl_args, &gateid); + if (IS_ERR(featctrl)) { + ret = PTR_ERR(featctrl); + pr_debug("%s() failed to find feature controller: %pe\n", + __func__, ERR_PTR(ret)); + /* + * Assume that missing featctrls are unresolved + * dependency are report them as deferred + */ + return (ret == -ENOENT) ? -EPROBE_DEFER : ret; + } + + ret = featctrl->check(featctrl, gateid); + + dev_dbg(featctrl->dev, "checking %s: %d\n", np->full_name, ret); + + if (ret == FEATCTRL_OKAY) + return FEATCTRL_OKAY; + if (ret != FEATCTRL_GATED) + err = ret; + } + + return err ?: FEATCTRL_GATED; +} +EXPORT_SYMBOL_GPL(of_feature_controller_check); + +static int of_featctrl_fixup(struct device_node *root, void *context) +{ + struct device_node *srcnp, *dstnp; + int err = 0; + + for_each_node_with_property(srcnp, "barebox,feature-gates") { + int ret; + + ret = of_feature_controller_check(srcnp); + if (ret < 0) + err = ret; + if (ret != FEATCTRL_GATED) + continue; + + dstnp = of_get_node_by_reproducible_name(root, srcnp); + if (!dstnp) { + pr_debug("gated node %s not in fixup DT\n", + srcnp->full_name); + continue; + } + + pr_debug("fixing up gating of node %s\n", dstnp->full_name); + /* Convention is deleting non-existing CPUs, not disable them. */ + if (of_property_match_string(srcnp, "device_type", "cpu") >= 0) + of_delete_node(dstnp); + else + of_device_disable(dstnp); + } + + return err; +} + +static __maybe_unused int of_featctrl_fixup_register(void) +{ + return of_register_fixup(of_featctrl_fixup, NULL); +} +#ifdef CONFIG_FEATURE_CONTROLLER_FIXUP +device_initcall(of_featctrl_fixup_register); +#endif diff --git a/drivers/base/power.c b/drivers/base/power.c index 4a206051b1..3eabf3c897 100644 --- a/drivers/base/power.c +++ b/drivers/base/power.c @@ -266,3 +266,13 @@ int genpd_dev_pm_attach(struct device_d *dev) return __genpd_dev_pm_attach(dev, dev->device_node, 0, true); } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); + +void pm_genpd_print(void) +{ + struct generic_pm_domain *genpd; + + printf("%-20s %6s\n", "name", "active"); + list_for_each_entry(genpd, &gpd_list, gpd_list_node) + printf("%-20s %6s\n", genpd->name, + genpd->status == GPD_STATE_ACTIVE ? "on" : "off"); +} diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 97a99b84e3..7f20709035 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -636,6 +636,18 @@ static int of_gpio_simple_xlate(struct gpio_chip *chip, return chip->base + gpiospec->args[0]; } +struct gpio_chip *gpio_get_chip_by_dev(struct device_d *dev) +{ + struct gpio_chip *chip; + + list_for_each_entry(chip, &chip_list, list) { + if (chip->dev == dev) + return chip; + } + + return NULL; +} + int gpio_of_xlate(struct device_d *dev, struct of_phandle_args *gpiospec, int *flags) { struct gpio_chip *chip; @@ -643,16 +655,14 @@ int gpio_of_xlate(struct device_d *dev, struct of_phandle_args *gpiospec, int *f if (!dev) return -ENODEV; - list_for_each_entry(chip, &chip_list, list) { - if (chip->dev != dev) - continue; - if (chip->ops->of_xlate) - return chip->ops->of_xlate(chip, gpiospec, flags); - else - return of_gpio_simple_xlate(chip, gpiospec, flags); - } + chip = gpio_get_chip_by_dev(dev); + if (!chip) + return -EPROBE_DEFER; - return -EPROBE_DEFER; + if (chip->ops->of_xlate) + return chip->ops->of_xlate(chip, gpiospec, flags); + else + return of_gpio_simple_xlate(chip, gpiospec, flags); } struct gpio_chip *gpio_get_chip(int gpio) @@ -665,15 +675,35 @@ struct gpio_chip *gpio_get_chip(int gpio) #ifdef CONFIG_CMD_GPIO static int do_gpiolib(int argc, char *argv[]) { + struct gpio_chip *chip = NULL; int i; + if (argc > 2) + return COMMAND_ERROR_USAGE; + + if (argc == 1) { + struct device_d *dev; + + dev = find_device(argv[1]); + if (!dev) + return -ENODEV; + + chip = gpio_get_chip_by_dev(dev); + if (!chip) + return -EINVAL; + } + for (i = 0; i < ARCH_NR_GPIOS; i++) { struct gpio_info *gi = &gpio_desc[i]; int val = -1, dir = -1; + int idx; if (!gi->chip) continue; + if (chip && chip != gi->chip) + continue; + /* print chip information and header on first gpio */ if (gi->chip->base == i) { printf("\nGPIOs %u-%u, chip %s:\n", @@ -683,14 +713,14 @@ static int do_gpiolib(int argc, char *argv[]) printf(" %-3s %-3s %-9s %-20s %-20s\n", "dir", "val", "requested", "name", "label"); } + idx = i - gi->chip->base; + if (gi->chip->ops->get_direction) - dir = gi->chip->ops->get_direction(gi->chip, - i - gi->chip->base); + dir = gi->chip->ops->get_direction(gi->chip, idx); if (gi->chip->ops->get) - val = gi->chip->ops->get(gi->chip, - i - gi->chip->base); + val = gi->chip->ops->get(gi->chip, idx); - printf(" GPIO %4d: %-3s %-3s %-9s %-20s %-20s\n", i, + printf(" GPIO %4d: %-3s %-3s %-9s %-20s %-20s\n", chip ? idx : i, (dir < 0) ? "unk" : ((dir == GPIOF_DIR_IN) ? "in" : "out"), (val < 0) ? "unk" : ((val == 0) ? "lo" : "hi"), gi->requested ? (gi->active_low ? "active low" : "true") : "false", @@ -704,6 +734,7 @@ static int do_gpiolib(int argc, char *argv[]) BAREBOX_CMD_START(gpioinfo) .cmd = do_gpiolib, BAREBOX_CMD_DESC("list registered GPIOs") + BAREBOX_CMD_OPTS("[CONTROLLER]") BAREBOX_CMD_GROUP(CMD_GRP_INFO) BAREBOX_CMD_COMPLETE(empty_complete) BAREBOX_CMD_END diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c index b24fd88f5b..40590b7d11 100644 --- a/drivers/i2c/i2c.c +++ b/drivers/i2c/i2c.c @@ -427,6 +427,12 @@ static void of_i2c_register_devices(struct i2c_adapter *adap) const __be32 *addr; int len; + if (n->dev) { + dev_dbg(&adap->dev, "of_i2c: skipping already registered %s\n", + dev_name(n->dev)); + continue; + } + of_modalias_node(n, info.type, I2C_NAME_SIZE); info.of_node = n; @@ -452,6 +458,20 @@ static void of_i2c_register_devices(struct i2c_adapter *adap) } } +int of_i2c_register_devices_by_node(struct device_node *node) +{ + struct i2c_adapter *adap; + + adap = of_find_i2c_adapter_by_node(node); + if (!adap) + return -ENODEV; + if (IS_ERR(adap)) + return PTR_ERR(adap); + + of_i2c_register_devices(adap); + return 0; +} + /** * i2c_new_dummy - return a new i2c device bound to a dummy driver * @adapter: the adapter managing the device @@ -577,6 +597,19 @@ struct i2c_client *of_find_i2c_device_by_node(struct device_node *node) return to_i2c_client(dev); } +int of_i2c_device_enable_and_register_by_alias(const char *alias) +{ + struct device_node *np; + + np = of_find_node_by_alias(NULL, alias); + if (!np) + return -ENODEV; + + of_device_enable(np); + return of_i2c_register_devices_by_node(np->parent); +} + + static void i2c_parse_timing(struct device_d *dev, char *prop_name, u32 *cur_val_p, u32 def_val, bool use_def) { diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6c6b65dacf..5fd1a0aaa8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -117,4 +117,12 @@ config MFD_RK808 This driver provides common support for accessing the device through I2C interface. +config MFD_AXP20X_I2C + tristate "X-Powers AXP series PMICs with I2C" + depends on I2C && OFDEVICE + help + If you say Y here you get support for the X-Powers AXP series power + management ICs (PMICs) controlled with I2C. + This driver currently only provide a character device in /dev. + endmenu diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 7b5d0398d1..a3cb90e883 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_SMSC_SUPERIO) += smsc-superio.o obj-$(CONFIG_MFD_STM32_TIMERS) += stm32-timers.o obj-$(CONFIG_MFD_ATMEL_FLEXCOM) += atmel-flexcom.o obj-$(CONFIG_MFD_RK808) += rk808.o +obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o axp20x.o diff --git a/drivers/mfd/axp20x-i2c.c b/drivers/mfd/axp20x-i2c.c new file mode 100644 index 0000000000..d0f6a0f394 --- /dev/null +++ b/drivers/mfd/axp20x-i2c.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C driver for the X-Powers' Power Management ICs + * + * AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC + * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature + * as well as configurable GPIOs. + * + * This driver supports the I2C variants. + * + * Copyright (C) 2014 Carlo Caione + * + * Author: Carlo Caione <carlo@caione.org> + */ + +#include <common.h> +#include <of.h> +#include <linux/err.h> +#include <i2c/i2c.h> +#include <module.h> +#include <linux/mfd/axp20x.h> +#include <regmap.h> + +static int axp20x_i2c_probe(struct device_d *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct axp20x_dev *axp20x; + int ret; + + axp20x = xzalloc(sizeof(*axp20x)); + + axp20x->dev = dev; + + ret = axp20x_match_device(axp20x); + if (ret) + return ret; + + axp20x->regmap = regmap_init_i2c(client, axp20x->regmap_cfg); + if (IS_ERR(axp20x->regmap)) + return dev_err_probe(dev, PTR_ERR(axp20x->regmap), + "regmap init failed\n"); + + ret = axp20x_device_probe(axp20x); + if (ret) + return ret; + + return regmap_register_cdev(axp20x->regmap, NULL); +} + +static const struct of_device_id axp20x_i2c_of_match[] = { + { .compatible = "x-powers,axp152", .data = (void *)AXP152_ID }, + { .compatible = "x-powers,axp202", .data = (void *)AXP202_ID }, + { .compatible = "x-powers,axp209", .data = (void *)AXP209_ID }, + { .compatible = "x-powers,axp221", .data = (void *)AXP221_ID }, + { .compatible = "x-powers,axp223", .data = (void *)AXP223_ID }, + { .compatible = "x-powers,axp803", .data = (void *)AXP803_ID }, + { .compatible = "x-powers,axp806", .data = (void *)AXP806_ID }, + { }, +}; + +static struct driver_d axp20x_i2c_driver = { + .name = "axp20x-i2c", + .probe = axp20x_i2c_probe, + .of_compatible = DRV_OF_COMPAT(axp20x_i2c_of_match), +}; + +coredevice_i2c_driver(axp20x_i2c_driver); + +MODULE_DESCRIPTION("PMIC MFD I2C driver for AXP20X"); +MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c new file mode 100644 index 0000000000..da1e8ce35a --- /dev/null +++ b/drivers/mfd/axp20x.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD core driver for the X-Powers' Power Management ICs + * + * AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC + * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature + * as well as configurable GPIOs. + * + * This file contains the interface independent core functions. + * + * Copyright (C) 2014 Carlo Caione + * + * Author: Carlo Caione <carlo@caione.org> + */ + +#include <common.h> +#include <linux/bitops.h> +#include <clock.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/mfd/axp20x.h> +#include <linux/mfd/core.h> +#include <module.h> +#include <of.h> +#include <of_device.h> +#include <regmap.h> +#include <regulator.h> + +#define AXP20X_OFF BIT(7) + +#define AXP806_REG_ADDR_EXT_ADDR_MASTER_MODE 0 +#define AXP806_REG_ADDR_EXT_ADDR_SLAVE_MODE BIT(4) + +static const char * const axp20x_model_names[] = { + "AXP152", + "AXP202", + "AXP209", + "AXP221", + "AXP223", + "AXP288", + "AXP803", + "AXP806", + "AXP809", + "AXP813", +}; + +static const struct regmap_config axp152_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP152_PWM1_DUTY_CYCLE, +}; + +static const struct regmap_config axp20x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP20X_OCV(AXP20X_OCV_MAX), +}; + +static const struct regmap_config axp22x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP22X_BATLOW_THRES1, +}; + +static const struct regmap_config axp288_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP288_FG_TUNE5, +}; + +static const struct regmap_config axp806_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP806_REG_ADDR_EXT, +}; + +static const struct mfd_cell axp20x_cells[] = { + { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp209-gpio", */ + }, { + .name = "axp20x-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-adc", + /* .of_compatible = "x-powers,axp209-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp209-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp202-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp202-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp221_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp22x-adc", + /* .of_compatible = "x-powers,axp221-adc", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp221-ac-power-supply", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp221-battery-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp221-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp223_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp22x-adc", + /* .of_compatible = "x-powers,axp221-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp221-battery-power-supply", */ + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp221-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp223-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp152_cells[] = { + { + .name = "axp20x-pek", + }, +}; + +static const struct mfd_cell axp288_cells[] = { + { + .name = "axp288_adc", + }, { + .name = "axp288_extcon", + }, { + .name = "axp288_charger", + }, { + .name = "axp221-pek", + }, { + .name = "axp288_pmic_acpi", + }, +}; + +static const struct mfd_cell axp803_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp813-gpio", */ + }, { + .name = "axp813-adc", + /* .of_compatible = "x-powers,axp813-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp813-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp813-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp813-usb-power-supply", */ + }, + { .name = "axp20x-regulator" }, +}; + +static const struct mfd_cell axp806_cells[] = { + { + .name = "axp20x-regulator", + }, +}; + +static const struct mfd_cell axp809_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, +}; + +static const struct mfd_cell axp813_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp813-gpio", */ + }, { + .name = "axp813-adc", + /* .of_compatible = "x-powers,axp813-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp813-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp813-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp813-usb-power-supply", */ + }, +}; + +static void axp20x_power_off(struct poweroff_handler *handler) +{ + struct axp20x_dev *axp20x = container_of(handler, struct axp20x_dev, poweroff); + + regmap_write(axp20x->regmap, AXP20X_OFF_CTRL, AXP20X_OFF); + + shutdown_barebox(); + + /* Give capacitors etc. time to drain to avoid kernel panic msg. */ + mdelay(500); + hang(); +} + +int axp20x_match_device(struct axp20x_dev *axp20x) +{ + struct device_d *dev = axp20x->dev; + const struct of_device_id *of_id; + + of_id = of_match_device(dev->driver->of_compatible, dev); + if (!of_id) { + dev_err(dev, "Unable to match OF ID\n"); + return -ENODEV; + } + axp20x->variant = (long)of_id->data; + + switch (axp20x->variant) { + case AXP152_ID: + axp20x->nr_cells = ARRAY_SIZE(axp152_cells); + axp20x->cells = axp152_cells; + axp20x->regmap_cfg = &axp152_regmap_config; + break; + case AXP202_ID: + case AXP209_ID: + axp20x->nr_cells = ARRAY_SIZE(axp20x_cells); + axp20x->cells = axp20x_cells; + axp20x->regmap_cfg = &axp20x_regmap_config; + break; + case AXP221_ID: + axp20x->nr_cells = ARRAY_SIZE(axp221_cells); + axp20x->cells = axp221_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP223_ID: + axp20x->nr_cells = ARRAY_SIZE(axp223_cells); + axp20x->cells = axp223_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP288_ID: + axp20x->cells = axp288_cells; + axp20x->nr_cells = ARRAY_SIZE(axp288_cells); + axp20x->regmap_cfg = &axp288_regmap_config; + break; + case AXP803_ID: + axp20x->nr_cells = ARRAY_SIZE(axp803_cells); + axp20x->cells = axp803_cells; + axp20x->regmap_cfg = &axp288_regmap_config; + break; + case AXP806_ID: + /* + * Don't register the power key part if in slave mode or + * if there is no interrupt line. + */ + axp20x->nr_cells = ARRAY_SIZE(axp806_cells); + axp20x->cells = axp806_cells; + axp20x->regmap_cfg = &axp806_regmap_config; + break; + case AXP809_ID: + axp20x->nr_cells = ARRAY_SIZE(axp809_cells); + axp20x->cells = axp809_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP813_ID: + axp20x->nr_cells = ARRAY_SIZE(axp813_cells); + axp20x->cells = axp813_cells; + axp20x->regmap_cfg = &axp288_regmap_config; + break; + default: + dev_err(dev, "unsupported AXP20X ID %lu\n", axp20x->variant); + return -EINVAL; + } + dev_info(dev, "AXP20x variant %s found\n", + axp20x_model_names[axp20x->variant]); + + return 0; +} +EXPORT_SYMBOL(axp20x_match_device); + +int axp20x_device_probe(struct axp20x_dev *axp20x) +{ + int ret; + + /* + * The AXP806 supports either master/standalone or slave mode. + * Slave mode allows sharing the serial bus, even with multiple + * AXP806 which all have the same hardware address. + * + * This is done with extra "serial interface address extension", + * or AXP806_BUS_ADDR_EXT, and "register address extension", or + * AXP806_REG_ADDR_EXT, registers. The former is read-only, with + * 1 bit customizable at the factory, and 1 bit depending on the + * state of an external pin. The latter is writable. The device + * will only respond to operations to its other registers when + * the these device addressing bits (in the upper 4 bits of the + * registers) match. + * + * By default we support an AXP806 chained to an AXP809 in slave + * mode. Boards which use an AXP806 in master mode can set the + * property "x-powers,master-mode" to override the default. + */ + if (axp20x->variant == AXP806_ID) { + if (of_property_read_bool(axp20x->dev->device_node, + "x-powers,master-mode") || + of_property_read_bool(axp20x->dev->device_node, + "x-powers,self-working-mode")) + regmap_write(axp20x->regmap, AXP806_REG_ADDR_EXT, + AXP806_REG_ADDR_EXT_ADDR_MASTER_MODE); + else + regmap_write(axp20x->regmap, AXP806_REG_ADDR_EXT, + AXP806_REG_ADDR_EXT_ADDR_SLAVE_MODE); + } + + ret = mfd_add_devices(axp20x->dev, axp20x->cells, axp20x->nr_cells); + if (ret) + return dev_err_probe(axp20x->dev, ret, "failed to add MFD devices\n"); + + + axp20x->poweroff.name = "axp20x-poweroff"; + axp20x->poweroff.poweroff = axp20x_power_off; + axp20x->poweroff.priority = 200; + + if (axp20x->variant != AXP288_ID) + poweroff_handler_register(&axp20x->poweroff); + + dev_info(axp20x->dev, "AXP20X driver loaded\n"); + + return 0; +} +EXPORT_SYMBOL(axp20x_device_probe); + +MODULE_DESCRIPTION("PMIC MFD core driver for AXP20X"); +MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 99d23ffedf..e37ab79f3e 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -22,6 +22,7 @@ #include <linux/phy.h> #include <linux/err.h> #include <of_device.h> +#include <pinctrl.h> #define DEFAULT_GPIO_RESET_ASSERT 1000 /* us */ #define DEFAULT_GPIO_RESET_DEASSERT 1000 /* us */ @@ -202,6 +203,7 @@ static int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np) continue; } + of_pinctrl_select_state_default(child); of_mdiobus_reset_phy(mdio, child); of_mdiobus_register_phy(mdio, child, addr); } diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 093ea71b95..c89ad08f81 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -809,3 +809,36 @@ void *nvmem_cell_get_and_read(struct device_node *np, const char *cell_name, return value; } EXPORT_SYMBOL_GPL(nvmem_cell_get_and_read); + +/** + * nvmem_cell_read_variable_le_u32() - Read up to 32-bits of data as a little endian number. + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_variable_le_u32(struct device_d *dev, const char *cell_id, + u32 *val) +{ + size_t len; + const u8 *buf; + int i; + + len = sizeof(*val); + + buf = nvmem_cell_get_and_read(dev->device_node, cell_id, len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* Copy w/ implicit endian conversion */ + *val = 0; + for (i = 0; i < len; i++) + *val |= buf[i] << (8 * i); + + kfree(buf); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u32); diff --git a/drivers/nvmem/ocotp.c b/drivers/nvmem/ocotp.c index 1f99a8012f..9fcbd1a6a4 100644 --- a/drivers/nvmem/ocotp.c +++ b/drivers/nvmem/ocotp.c @@ -28,6 +28,7 @@ #include <mach/ocotp.h> #include <machine_id.h> #include <mach/ocotp-fusemap.h> +#include <soc/imx8m/featctrl.h> #include <linux/nvmem-provider.h> /* @@ -108,6 +109,7 @@ struct imx_ocotp_data { int (*fuse_blow)(struct ocotp_priv *priv, u32 addr, u32 value); u8 mac_offsets[MAX_MAC_OFFSETS]; u8 mac_offsets_num; + struct imx8m_featctrl_data *feat; }; struct ocotp_priv_ethaddr { @@ -638,19 +640,20 @@ static struct regmap_bus imx_ocotp_regmap_bus = { .reg_read = imx_ocotp_reg_read, }; -static void imx_ocotp_init_dt(struct ocotp_priv *priv) +static int imx_ocotp_init_dt(struct ocotp_priv *priv) { char mac[MAC_BYTES]; const __be32 *prop; struct device_node *node = priv->dev.parent->device_node; - int len; + u32 tester4; + int ret, len; if (!node) - return; + return 0; prop = of_get_property(node, "barebox,provide-mac-address", &len); if (!prop) - return; + return 0; for (; len >= MAC_ADDRESS_PROPLEN; len -= MAC_ADDRESS_PROPLEN) { struct device_node *rnode; @@ -668,6 +671,15 @@ static void imx_ocotp_init_dt(struct ocotp_priv *priv) of_eth_register_ethaddr(rnode, mac); } + + if (!of_property_read_bool(node, "barebox,feature-controller")) + return 0; + + ret = regmap_read(priv->map, OCOTP_OFFSET_TO_ADDR(0x450), &tester4); + if (ret != 0) + return ret; + + return imx8m_feat_ctrl_init(priv->dev.parent, tester4, priv->data->feat); } static int imx_ocotp_write(void *ctx, unsigned offset, const void *val, size_t bytes) @@ -785,10 +797,12 @@ static int imx_ocotp_probe(struct device_d *dev) if (IS_ENABLED(CONFIG_MACHINE_ID)) imx_ocotp_set_unique_machine_id(); - imx_ocotp_init_dt(priv); + ret = imx_ocotp_init_dt(priv); + if (ret) + dev_warn(dev, "feature controller registration failed: %pe\n", + ERR_PTR(ret)); dev_add_param_bool(&(priv->dev), "sense_enable", NULL, NULL, &priv->sense_enable, priv); - return 0; } @@ -895,6 +909,38 @@ static struct imx_ocotp_data imx8mq_ocotp_data = { .fuse_read = imx6_fuse_read_addr, }; +static struct imx8m_featctrl_data imx8mm_featctrl_data = { + .vpu_bitmask = 0x1c0000, +}; + +static struct imx_ocotp_data imx8mm_ocotp_data = { + .num_regs = 2048, + .addr_to_offset = imx6sl_addr_to_offset, + .mac_offsets_num = 1, + .mac_offsets = { 0x90 }, + .format_mac = imx_ocotp_format_mac, + .set_timing = imx6_ocotp_set_timing, + .fuse_blow = imx6_fuse_blow_addr, + .fuse_read = imx6_fuse_read_addr, + .feat = &imx8mm_featctrl_data, +}; + +static struct imx8m_featctrl_data imx8mn_featctrl_data = { + .gpu_bitmask = 0x1000000, +}; + +static struct imx_ocotp_data imx8mn_ocotp_data = { + .num_regs = 2048, + .addr_to_offset = imx6sl_addr_to_offset, + .mac_offsets_num = 1, + .mac_offsets = { 0x90 }, + .format_mac = imx_ocotp_format_mac, + .set_timing = imx6_ocotp_set_timing, + .fuse_blow = imx6_fuse_blow_addr, + .fuse_read = imx6_fuse_read_addr, + .feat = &imx8mn_featctrl_data, +}; + static struct imx_ocotp_data imx7d_ocotp_data = { .num_regs = 2048, .addr_to_offset = imx6sl_addr_to_offset, @@ -933,10 +979,10 @@ static __maybe_unused struct of_device_id imx_ocotp_dt_ids[] = { .data = &imx8mq_ocotp_data, }, { .compatible = "fsl,imx8mm-ocotp", - .data = &imx8mq_ocotp_data, + .data = &imx8mm_ocotp_data, }, { .compatible = "fsl,imx8mn-ocotp", - .data = &imx8mq_ocotp_data, + .data = &imx8mn_ocotp_data, }, { .compatible = "fsl,vf610-ocotp", .data = &vf610_ocotp_data, diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index f3d2cc00cc..7283331ba9 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -16,6 +16,18 @@ config OFDEVICE select DTC bool "Enable probing of devices from the devicetree" +config FEATURE_CONTROLLER_FIXUP + bool "Fix up DT nodes gated by feature controller" + depends on FEATURE_CONTROLLER + default y + help + When specified, barebox feature controller drivers are consulted + prior to probing nodes to detect whether the device may not + be available (e.g. because support is fused out). + This option additionally fixes up the kernel device tree, + so it doesn't attempt probing these devices either. + If unsure, say y. + config OF_ADDRESS_PCI bool diff --git a/drivers/of/base.c b/drivers/of/base.c index b91ee92e1b..ea2a88764b 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -2301,6 +2301,22 @@ void of_delete_property(struct property *pp) free(pp); } +struct property *of_rename_property(struct device_node *np, + const char *old_name, const char *new_name) +{ + struct property *pp; + + pp = of_find_property(np, old_name, NULL); + if (!pp) + return NULL; + + of_property_write_bool(np, new_name, false); + + free(pp->name); + pp->name = xstrdup(new_name); + return pp; +} + /** * of_set_property - create a property for a given node * @node - the node @@ -2362,6 +2378,36 @@ int of_append_property(struct device_node *np, const char *name, const void *val return 0; } +int of_prepend_property(struct device_node *np, const char *name, const void *val, int len) +{ + struct property *pp; + const void *oldval; + void *buf; + int oldlen; + + pp = of_find_property(np, name, &oldlen); + if (!pp) { + of_new_property(np, name, val, len); + return 0; + } + + oldval = of_property_get_value(pp); + + buf = malloc(len + oldlen); + if (!buf) + return -ENOMEM; + + memcpy(buf, val, len); + memcpy(buf + len, oldval, oldlen); + + free(pp->value); + pp->value = buf; + pp->length = len + oldlen; + pp->value_const = NULL; + + return 0; +} + int of_property_sprintf(struct device_node *np, const char *propname, const char *fmt, ...) { @@ -2504,6 +2550,12 @@ int of_probe(void) return -ENODEV; /* + * We do this first thing, so board drivers can patch the device + * tree prior to device creation if needed. + */ + of_platform_device_create_root(root_node); + + /* * Handle certain compatibles explicitly, since we don't want to create * platform_devices for every node in /reserved-memory with a * "compatible", @@ -2515,8 +2567,6 @@ int of_probe(void) if (node) of_platform_populate(node, NULL, NULL); - of_platform_device_create_root(root_node); - of_platform_populate(root_node, of_default_bus_match_table, NULL); return 0; @@ -2865,6 +2915,19 @@ struct device_node *of_find_node_by_reproducible_name(struct device_node *from, return NULL; } +struct device_node *of_get_node_by_reproducible_name(struct device_node *dstroot, + struct device_node *srcnp) +{ + struct device_node *dstnp; + char *name; + + name = of_get_reproducible_name(srcnp); + dstnp = of_find_node_by_reproducible_name(dstroot, name); + free(name); + + return dstnp; +} + /** * of_graph_parse_endpoint() - parse common endpoint node properties * @node: pointer to endpoint device_node diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index fff1d6ea02..0fc440fdcf 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -102,8 +102,10 @@ static char *of_overlay_fix_path(struct device_node *root, if (of_get_child_by_name(fragment, "__overlay__")) break; } - if (!fragment) + if (!fragment) { + pr_info("could not find __overlay__ node\n"); return NULL; + } target = find_target(root, fragment); if (!target) @@ -115,8 +117,8 @@ static char *of_overlay_fix_path(struct device_node *root, return basprintf("%s%s", target->full_name, path_tail); } -static void of_overlay_apply_symbols(struct device_node *root, - struct device_node *overlay) +static int of_overlay_apply_symbols(struct device_node *root, + struct device_node *overlay) { const char *old_path; char *new_path; @@ -129,12 +131,12 @@ static void of_overlay_apply_symbols(struct device_node *root, if (!overlay_symbols) { pr_debug("overlay doesn't have a __symbols__ node\n"); - return; + return -EINVAL; } if (!root_symbols) { pr_info("root doesn't have a __symbols__ node\n"); - return; + return -EINVAL; } list_for_each_entry(prop, &overlay_symbols->properties, list) { @@ -143,11 +145,15 @@ static void of_overlay_apply_symbols(struct device_node *root, old_path = of_property_get_value(prop); new_path = of_overlay_fix_path(root, overlay, old_path); + if (!new_path) + return -EINVAL; pr_debug("add symbol %s with new path %s\n", prop->name, new_path); of_property_write_string(root_symbols, prop->name, new_path); } + + return 0; } static int of_overlay_apply_fragment(struct device_node *root, @@ -190,7 +196,9 @@ int of_overlay_apply_tree(struct device_node *root, goto out_err; /* Copy symbols from resolved overlay to base device tree */ - of_overlay_apply_symbols(root, resolved); + err = of_overlay_apply_symbols(root, resolved); + if (err) + goto out_err; /* Copy nodes and properties from resolved overlay to root */ for_each_child_of_node(resolved, fragment) { diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 7f377b8b37..a9a5d4c2da 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -28,6 +28,9 @@ struct device_d *of_find_device_by_node(struct device_node *np) if (ret) return NULL; + if (deep_probe_is_supported()) + return np->dev; + for_each_device(dev) if (dev->device_node == np) return dev; @@ -522,12 +525,15 @@ int of_devices_ensure_probed_by_property(const char *property_name) return 0; for_each_node_with_property(node, property_name) { - ret = of_device_ensure_probed(node); + if (!of_device_is_available(node)) + continue; + + err = of_device_ensure_probed(node); if (err) ret = err; } - return 0; + return ret; } EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_property); @@ -540,12 +546,15 @@ int of_devices_ensure_probed_by_name(const char *name) return 0; for_each_node_by_name(node, name) { - ret = of_device_ensure_probed(node); + if (!of_device_is_available(node)) + continue; + + err = of_device_ensure_probed(node); if (err) ret = err; } - return 0; + return ret; } EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_name); diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index 1316af4213..5761128f8e 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -48,19 +48,6 @@ static int reboot_mode_add_param(struct device_d *dev, return PTR_ERR_OR_ZERO(param); } -static struct device_node *of_get_node_by_reproducible_name(struct device_node *dstroot, - struct device_node *srcnp) -{ - struct device_node *dstnp; - char *name; - - name = of_get_reproducible_name(srcnp); - dstnp = of_find_node_by_reproducible_name(dstroot, name); - free(name); - - return dstnp; -} - static int of_reboot_mode_fixup(struct device_node *root, void *ctx) { struct reboot_mode_driver *reboot = ctx; diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 913b309eac..16c05d50f0 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -41,6 +41,7 @@ config RESET_IMX7 config RESET_STARFIVE bool "StarFive Controller Driver" if COMPILE_TEST + depends on COMMON_CLK default SOC_STARFIVE help This enables the reset controller driver for the StarFive JH7100. diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig index 8f4f5150c7..06513c02da 100644 --- a/drivers/soc/imx/Kconfig +++ b/drivers/soc/imx/Kconfig @@ -7,4 +7,10 @@ config IMX_GPCV2_PM_DOMAINS select PM_GENERIC_DOMAINS default y if ARCH_IMX7 || ARCH_IMX8M +config IMX8M_FEATCTRL + bool "i.MX8M feature controller" + depends on ARCH_IMX8M + select FEATURE_CONTROLLER + default y + endmenu diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index 3a8a8d0b00..bd1717b038 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o +obj-$(CONFIG_IMX8M_FEATCTRL) += imx8m-featctrl.o diff --git a/drivers/soc/imx/imx8m-featctrl.c b/drivers/soc/imx/imx8m-featctrl.c new file mode 100644 index 0000000000..480c80e6c1 --- /dev/null +++ b/drivers/soc/imx/imx8m-featctrl.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2022 Ahmad Fatoum, Pengutronix + +#include <common.h> +#include <linux/bitmap.h> +#include <featctrl.h> +#include <soc/imx8m/featctrl.h> + +#include <dt-bindings/features/imx8m.h> + +struct imx_feat { + struct feature_controller feat; + unsigned long features[BITS_TO_LONGS(IMX8M_FEAT_END)]; +}; + +static struct imx_feat *to_imx_feat(struct feature_controller *feat) +{ + return container_of(feat, struct imx_feat, feat); +} + +static int imx8m_feat_check(struct feature_controller *feat, int idx) +{ + struct imx_feat *priv = to_imx_feat(feat); + + if (idx > IMX8M_FEAT_END) + return -EINVAL; + + return test_bit(idx, priv->features) ? FEATCTRL_OKAY : FEATCTRL_GATED; +} + +int imx8m_feat_ctrl_init(struct device_d *dev, u32 tester4, + const struct imx8m_featctrl_data *data) +{ + unsigned long *features; + struct imx_feat *priv; + + if (!dev || !data) + return -ENODEV; + + dev_dbg(dev, "tester4 = 0x%08x\n", tester4); + + priv = xzalloc(sizeof(*priv)); + features = priv->features; + + bitmap_fill(features, IMX8M_FEAT_END); + + if (tester4 & data->vpu_bitmask) + clear_bit(IMX8M_FEAT_VPU, features); + if (tester4 & data->gpu_bitmask) + clear_bit(IMX8M_FEAT_GPU, features); + + switch (tester4 & 3) { + case 0b11: + clear_bit(IMX8M_FEAT_CPU_DUAL, features); + fallthrough; + case 0b10: + clear_bit(IMX8M_FEAT_CPU_QUAD, features); + } + + priv->feat.dev = dev; + priv->feat.check = imx8m_feat_check; + + return feature_controller_register(&priv->feat); +} |