summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2022-09-14 15:52:59 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2022-09-14 15:52:59 +0200
commit19df0b5883737c6ce50b893dfc8fe1722cd6560a (patch)
tree4a90f708d33a44f0b40eab69134543c38629c884 /drivers
parentd01b0a64580833b4ccf10e6361badee1d81e1221 (diff)
parent7a9d2377b5e54cfec28a7645985a7371c1d8b8af (diff)
downloadbarebox-19df0b5883737c6ce50b893dfc8fe1722cd6560a.tar.gz
barebox-19df0b5883737c6ce50b893dfc8fe1722cd6560a.tar.xz
Merge branch 'for-next/misc'
Diffstat (limited to 'drivers')
-rw-r--r--drivers/base/Kconfig3
-rw-r--r--drivers/base/Makefile1
-rw-r--r--drivers/base/driver.c25
-rw-r--r--drivers/base/featctrl.c160
-rw-r--r--drivers/base/power.c10
-rw-r--r--drivers/gpio/gpiolib.c59
-rw-r--r--drivers/i2c/i2c.c33
-rw-r--r--drivers/mfd/Kconfig8
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/axp20x-i2c.c71
-rw-r--r--drivers/mfd/axp20x.c360
-rw-r--r--drivers/net/phy/mdio_bus.c2
-rw-r--r--drivers/nvmem/core.c33
-rw-r--r--drivers/nvmem/ocotp.c62
-rw-r--r--drivers/of/Kconfig12
-rw-r--r--drivers/of/base.c67
-rw-r--r--drivers/of/overlay.c20
-rw-r--r--drivers/of/platform.c17
-rw-r--r--drivers/power/reset/reboot-mode.c13
-rw-r--r--drivers/reset/Kconfig1
-rw-r--r--drivers/soc/imx/Kconfig6
-rw-r--r--drivers/soc/imx/Makefile1
-rw-r--r--drivers/soc/imx/imx8m-featctrl.c64
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);
+}