drivers/mfd/Kconfig | 1 + drivers/mfd/mfd-core.c | 123 ++++++++++++++++++++++++++++++++++++--- include/linux/mfd/aux.h | 30 ++++++++++ include/linux/mfd/core.h | 3 + 4 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 include/linux/mfd/aux.h
Extend MFD subsystem to support auxiliary child device. This is useful
for MFD usecases where parent device is on a discoverable bus and doesn't
fit into the platform device criteria. Purpose of this implementation is
to provide discoverable MFDs just enough infrastructure to register
independent child devices with their own memory and interrupt resources
without abusing the platform device.
Current support is limited to just PCI type MFDs, but this can be further
extended to support other types like USB in the future.
Signed-off-by: Raag Jadav <raag.jadav@intel.com>
---
v2: Introduce a shared struct mfd_aux_device
Introduce MFD_AUX_TYPE flag for auxiliary device opt-in
v3: Fix device_type ABI breakage (Andy)
Aesthetic adjustments (Andy)
PS: I'm leaning towards leaving the ioremap or regmap complexity out of
MFD core and think that we should enforce child devices to not overlap.
If there's a need to handle common register access by parent device, then
I think it warrants its own driver which adds auxiliary devices along with
a custom interface to communicate with them without involving MFD core.
drivers/mfd/Kconfig | 1 +
drivers/mfd/mfd-core.c | 123 ++++++++++++++++++++++++++++++++++++---
include/linux/mfd/aux.h | 30 ++++++++++
include/linux/mfd/core.h | 3 +
4 files changed, 149 insertions(+), 8 deletions(-)
create mode 100644 include/linux/mfd/aux.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 93773201a517..8c8e6797307c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -8,6 +8,7 @@ menu "Multifunction device drivers"
config MFD_CORE
tristate
+ select AUXILIARY_BUS
select IRQ_DOMAIN
default n
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index 76bd316a50af..fc6a34a045be 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -10,8 +10,11 @@
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/pci.h>
#include <linux/list.h>
#include <linux/property.h>
+#include <linux/mfd/aux.h>
#include <linux/mfd/core.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
@@ -136,10 +139,87 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev,
return 0;
}
-static int mfd_add_device(struct device *parent, int id,
- const struct mfd_cell *cell,
- struct resource *mem_base,
- int irq_base, struct irq_domain *domain)
+static void mfd_release_auxiliary_device(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct mfd_aux_device *mfd_aux = auxiliary_dev_to_mfd_aux_dev(auxdev);
+
+ kfree(mfd_aux);
+}
+
+static int mfd_add_auxiliary_device(struct device *parent, int id, const struct mfd_cell *cell,
+ struct resource *mem_base, int irq_base,
+ struct irq_domain *domain)
+{
+ struct mfd_aux_device *mfd_aux;
+ struct auxiliary_device *auxdev;
+ int r, ret;
+
+ mfd_aux = kzalloc(sizeof(*mfd_aux), GFP_KERNEL);
+ if (!mfd_aux)
+ return -ENOMEM;
+
+ for (r = 0; r < cell->num_resources; r++) {
+ /* Find out base to use */
+ if ((cell->resources[r].flags & IORESOURCE_MEM) && mem_base) {
+ mfd_aux->mem.name = cell->resources[r].name;
+ mfd_aux->mem.flags = cell->resources[r].flags;
+
+ mfd_aux->mem.parent = mem_base;
+ mfd_aux->mem.start = mem_base->start + cell->resources[r].start;
+ mfd_aux->mem.end = mem_base->start + cell->resources[r].end;
+ } else if (cell->resources[r].flags & IORESOURCE_IRQ) {
+ mfd_aux->irq.name = cell->resources[r].name;
+ mfd_aux->irq.flags = cell->resources[r].flags;
+
+ if (domain) {
+ /* Unable to create mappings for IRQ ranges */
+ WARN_ON(cell->resources[r].start != cell->resources[r].end);
+ mfd_aux->irq.start = mfd_aux->irq.end = irq_create_mapping(
+ domain, cell->resources[r].start);
+ } else {
+ mfd_aux->irq.start = irq_base + cell->resources[r].start;
+ mfd_aux->irq.end = irq_base + cell->resources[r].end;
+ }
+ } else {
+ mfd_aux->ext.name = cell->resources[r].name;
+ mfd_aux->ext.flags = cell->resources[r].flags;
+ mfd_aux->ext.parent = cell->resources[r].parent;
+ mfd_aux->ext.start = cell->resources[r].start;
+ mfd_aux->ext.end = cell->resources[r].end;
+ }
+ }
+
+ auxdev = &mfd_aux->auxdev;
+ auxdev->name = cell->name;
+ /* Use parent id for discoverable devices */
+ auxdev->id = dev_is_pci(parent) ? pci_dev_id(to_pci_dev(parent)) : cell->id;
+
+ auxdev->dev.parent = parent;
+ auxdev->dev.type = &mfd_dev_type;
+ auxdev->dev.release = mfd_release_auxiliary_device;
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret)
+ goto fail_aux_init;
+
+ ret = __auxiliary_device_add(auxdev, parent->driver->name);
+ if (ret)
+ goto fail_aux_add;
+
+ return 0;
+
+fail_aux_add:
+ /* auxdev will be freed with the put_device() and .release sequence */
+ auxiliary_device_uninit(auxdev);
+fail_aux_init:
+ kfree(mfd_aux);
+ return ret;
+}
+
+static int mfd_add_platform_device(struct device *parent, int id, const struct mfd_cell *cell,
+ struct resource *mem_base, int irq_base,
+ struct irq_domain *domain)
{
struct resource *res;
struct platform_device *pdev;
@@ -302,6 +382,16 @@ static int mfd_add_device(struct device *parent, int id,
return ret;
}
+static int mfd_add_device(struct device *parent, int id, const struct mfd_cell *cells,
+ struct resource *mem_base, int irq_base, struct irq_domain *domain)
+{
+ /* TODO: Convert the platform device abusers and remove this flag */
+ if (dev_is_pci(parent) && id == MFD_AUX_TYPE)
+ return mfd_add_auxiliary_device(parent, id, cells, mem_base, irq_base, domain);
+
+ return mfd_add_platform_device(parent, id, cells, mem_base, irq_base, domain);
+}
+
/**
* mfd_add_devices - register child devices
*
@@ -340,16 +430,22 @@ int mfd_add_devices(struct device *parent, int id,
}
EXPORT_SYMBOL(mfd_add_devices);
-static int mfd_remove_devices_fn(struct device *dev, void *data)
+static int mfd_remove_auxiliary_device(struct device *dev, void *data)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+ auxiliary_device_delete(auxdev);
+ auxiliary_device_uninit(auxdev);
+ return 0;
+}
+
+static int mfd_remove_platform_device(struct device *dev, void *data)
{
struct platform_device *pdev;
const struct mfd_cell *cell;
struct mfd_of_node_entry *of_entry, *tmp;
int *level = data;
- if (dev->type != &mfd_dev_type)
- return 0;
-
pdev = to_platform_device(dev);
cell = mfd_get_cell(pdev);
@@ -372,6 +468,17 @@ static int mfd_remove_devices_fn(struct device *dev, void *data)
return 0;
}
+static int mfd_remove_devices_fn(struct device *dev, void *data)
+{
+ if (dev->type != &mfd_dev_type)
+ return 0;
+
+ if (dev->bus == &platform_bus_type)
+ return mfd_remove_platform_device(dev, data);
+
+ return mfd_remove_auxiliary_device(dev, data);
+}
+
void mfd_remove_devices_late(struct device *parent)
{
int level = MFD_DEP_LEVEL_HIGH;
diff --git a/include/linux/mfd/aux.h b/include/linux/mfd/aux.h
new file mode 100644
index 000000000000..826a0a05e9e9
--- /dev/null
+++ b/include/linux/mfd/aux.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * MFD auxiliary device
+ *
+ * Copyright (c) 2025 Raag Jadav <raag.jadav@intel.com>
+ */
+
+#ifndef MFD_AUX_H
+#define MFD_AUX_H
+
+#include <linux/auxiliary_bus.h>
+#include <linux/container_of.h>
+#include <linux/ioport.h>
+
+/*
+ * Common structure between MFD parent and auxiliary child device.
+ * To be used by leaf drivers to access child device resources.
+ */
+struct mfd_aux_device {
+ struct auxiliary_device auxdev;
+ struct resource mem;
+ struct resource irq;
+ /* Place holder for other types */
+ struct resource ext;
+};
+
+#define auxiliary_dev_to_mfd_aux_dev(auxiliary_dev) \
+ container_of(auxiliary_dev, struct mfd_aux_device, auxdev)
+
+#endif
diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h
index faeea7abd688..96f6ff98a111 100644
--- a/include/linux/mfd/core.h
+++ b/include/linux/mfd/core.h
@@ -12,6 +12,9 @@
#include <linux/platform_device.h>
+/* TODO: Convert the platform device abusers and remove this flag */
+#define MFD_AUX_TYPE BIT(31)
+
#define MFD_RES_SIZE(arr) (sizeof(arr) / sizeof(struct resource))
#define MFD_CELL_ALL(_name, _res, _pdata, _pdsize, _id, _compat, _of_reg, _use_of_reg, _match) \
--
2.34.1
On Fri, Apr 18, 2025 at 06:04:33PM +0530, Raag Jadav wrote:
> Extend MFD subsystem to support auxiliary child device. This is useful
> for MFD usecases where parent device is on a discoverable bus and doesn't
> fit into the platform device criteria. Purpose of this implementation is
> to provide discoverable MFDs just enough infrastructure to register
> independent child devices with their own memory and interrupt resources
> without abusing the platform device.
>
> Current support is limited to just PCI type MFDs, but this can be further
> extended to support other types like USB in the future.
> create mode 100644 include/linux/mfd/aux.h
...
> config MFD_CORE
> tristate
> + select AUXILIARY_BUS
> select IRQ_DOMAIN
> default n
Can be dropped. It's the default 'default' and with user invisible
configuration, it makes even less sense.
...
> +/*
> + * Common structure between MFD parent and auxiliary child device.
> + * To be used by leaf drivers to access child device resources.
> + */
> +struct mfd_aux_device {
> + struct auxiliary_device auxdev;
> + struct resource mem;
> + struct resource irq;
> + /* Place holder for other types */
> + struct resource ext;
I don't like it. It's no-scalable solution, just make it VLA. Each resource
already has type in case one needs to extract an exact one, also this won't
work in case of the two resources for memory or IRQ needed.
> +};
...
> +/* TODO: Convert the platform device abusers and remove this flag */
> +#define MFD_AUX_TYPE BIT(31)
Yeah, LKP wants this to be signed... Or 'id' to be unsigned.
--
With Best Regards,
Andy Shevchenko
On Sat, Apr 19, 2025 at 06:28:28PM +0300, Andy Shevchenko wrote:
> On Fri, Apr 18, 2025 at 06:04:33PM +0530, Raag Jadav wrote:
> > Extend MFD subsystem to support auxiliary child device. This is useful
> > for MFD usecases where parent device is on a discoverable bus and doesn't
> > fit into the platform device criteria. Purpose of this implementation is
> > to provide discoverable MFDs just enough infrastructure to register
> > independent child devices with their own memory and interrupt resources
> > without abusing the platform device.
> >
> > Current support is limited to just PCI type MFDs, but this can be further
> > extended to support other types like USB in the future.
>
> ...
>
> > config MFD_CORE
> > tristate
> > + select AUXILIARY_BUS
> > select IRQ_DOMAIN
>
> > default n
>
> Can be dropped. It's the default 'default' and with user invisible
> configuration, it makes even less sense.
Sure.
> > +/*
> > + * Common structure between MFD parent and auxiliary child device.
> > + * To be used by leaf drivers to access child device resources.
> > + */
> > +struct mfd_aux_device {
> > + struct auxiliary_device auxdev;
> > + struct resource mem;
> > + struct resource irq;
> > + /* Place holder for other types */
> > + struct resource ext;
>
> I don't like it. It's no-scalable solution, just make it VLA. Each resource
> already has type in case one needs to extract an exact one, also this won't
> work in case of the two resources for memory or IRQ needed.
Agree, but is it worth introducing a whole new set of get_resource() and
friends? I'm not sure if the maintainers will be okay with it.
> > +/* TODO: Convert the platform device abusers and remove this flag */
> > +#define MFD_AUX_TYPE BIT(31)
>
> Yeah, LKP wants this to be signed... Or 'id' to be unsigned.
I'll revert it back to INT_MIN, a specific number would rather become more
questionable.
Raag
Hi Raag,
kernel test robot noticed the following build warnings:
[auto build test WARNING on lee-mfd/for-mfd-next]
[also build test WARNING on lee-mfd/for-mfd-fixes linus/master v6.15-rc2 next-20250417]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Raag-Jadav/mfd-core-Support-auxiliary-device/20250418-203658
base: https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link: https://lore.kernel.org/r/20250418123433.2586383-1-raag.jadav%40intel.com
patch subject: [PATCH v3] mfd: core: Support auxiliary device
config: riscv-randconfig-001-20250418 (https://download.01.org/0day-ci/archive/20250418/202504182338.SwsgSLNm-lkp@intel.com/config)
compiler: clang version 21.0.0git (https://github.com/llvm/llvm-project f819f46284f2a79790038e1f6649172789734ae8)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250418/202504182338.SwsgSLNm-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202504182338.SwsgSLNm-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/mfd/mfd-core.c:389:31: warning: result of comparison of constant 2147483648 with expression of type 'int' is always false [-Wtautological-constant-out-of-range-compare]
389 | if (dev_is_pci(parent) && id == MFD_AUX_TYPE)
| ~~ ^ ~~~~~~~~~~~~
1 warning generated.
vim +/int +389 drivers/mfd/mfd-core.c
384
385 static int mfd_add_device(struct device *parent, int id, const struct mfd_cell *cells,
386 struct resource *mem_base, int irq_base, struct irq_domain *domain)
387 {
388 /* TODO: Convert the platform device abusers and remove this flag */
> 389 if (dev_is_pci(parent) && id == MFD_AUX_TYPE)
390 return mfd_add_auxiliary_device(parent, id, cells, mem_base, irq_base, domain);
391
392 return mfd_add_platform_device(parent, id, cells, mem_base, irq_base, domain);
393 }
394
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.