diff mbox series

[RFC,1/2] mfd: Add SEL PCI Virtual Multifunction (PVMF) support

Message ID 20241028223509.935-2-robert_joslyn@selinc.com (mailing list archive)
State RFC
Headers show
Series Add SEL Ethernet Support | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Guessed tree name to be net-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 5 this patch: 5
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 2 of 2 maintainers
netdev/build_clang fail Errors and warnings before: 4 this patch: 12
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api fail Found: 'module_param' was: 0 now: 1
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning CHECK: Prefer kernel type 'u32' over 'uint32_t' CHECK: Prefer kernel type 'u8' over 'uint8_t' WARNING: line length of 81 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc fail Errors and warnings before: 0 this patch: 5
netdev/source_inline success Was 0 now: 0

Commit Message

Robert Joslyn Oct. 28, 2024, 10:35 p.m. UTC
Add support for SEL FPGA based PCIe devices. These expose a single PCI
BAR with multiple IP cores for various functions, such as serial ports,
ethernet, and time (PTP/IRIG). This initial driver supports Ethernet on
the SEL-3350 mainboard, SEL-3390E4 ethernet card, and SEL-3390T ethernet
and time card.

Signed-off-by: Robert Joslyn <robert_joslyn@selinc.com>
---
 MAINTAINERS                |   8 +
 drivers/mfd/Kconfig        |  16 ++
 drivers/mfd/Makefile       |   3 +
 drivers/mfd/selpvmf-core.c | 482 +++++++++++++++++++++++++++++++++++++
 drivers/mfd/selpvmf-cvp.c  | 431 +++++++++++++++++++++++++++++++++
 drivers/mfd/selpvmf-cvp.h  |  18 ++
 6 files changed, 958 insertions(+)
 create mode 100644 drivers/mfd/selpvmf-core.c
 create mode 100644 drivers/mfd/selpvmf-cvp.c
 create mode 100644 drivers/mfd/selpvmf-cvp.h

Comments

Lee Jones Nov. 5, 2024, 5:30 p.m. UTC | #1
On Mon, 28 Oct 2024, Robert Joslyn wrote:

> Add support for SEL FPGA based PCIe devices. These expose a single PCI
> BAR with multiple IP cores for various functions, such as serial ports,
> ethernet, and time (PTP/IRIG). This initial driver supports Ethernet on
> the SEL-3350 mainboard, SEL-3390E4 ethernet card, and SEL-3390T ethernet
> and time card.
> 
> Signed-off-by: Robert Joslyn <robert_joslyn@selinc.com>
> ---
>  MAINTAINERS                |   8 +
>  drivers/mfd/Kconfig        |  16 ++
>  drivers/mfd/Makefile       |   3 +
>  drivers/mfd/selpvmf-core.c | 482 +++++++++++++++++++++++++++++++++++++
>  drivers/mfd/selpvmf-cvp.c  | 431 +++++++++++++++++++++++++++++++++
>  drivers/mfd/selpvmf-cvp.h  |  18 ++
>  6 files changed, 958 insertions(+)
>  create mode 100644 drivers/mfd/selpvmf-core.c
>  create mode 100644 drivers/mfd/selpvmf-cvp.c
>  create mode 100644 drivers/mfd/selpvmf-cvp.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a27407950242..4a24b3be8aa5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20730,6 +20730,14 @@ F:	tools/testing/selftests/lsm/
>  X:	security/selinux/
>  K:	\bsecurity_[a-z_0-9]\+\b
>  
> +SEL DRIVERS
> +M:	Robert Joslyn <robert_joslyn@selinc.com>
> +S:	Supported
> +B:	mailto:opensource@selinc.com
> +F:	drivers/mfd/selpvmf-*
> +F:	drivers/platform/x86/sel3350-platform.c
> +F:	include/linux/mfd/selpvmf.h
> +
>  SELINUX SECURITY MODULE
>  M:	Paul Moore <paul@paul-moore.com>
>  M:	Stephen Smalley <stephen.smalley.work@gmail.com>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index f9325bcce1b9..77e2ce3db505 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -2402,5 +2402,21 @@ config MFD_RSMU_SPI
>  	  Additional drivers must be enabled in order to use the functionality
>  	  of the device.
>  
> +config MFD_SELPVMF
> +	tristate "SEL PVMF Support"
> +	help
> +	  Support for SEL PCI virtual multifunction (PVMF) devcies utilizing
> +	  an FPGA based PCIe interface, including:
> +	    * SEL-3350
> +	    * SEL-3390E4
> +	    * SEL-3390T
> +
> +	  This driver provides common support for accessing the device.
> +	  Additional drivers must be enabled in order to use the functionality
> +	  of the device.
> +
> +	  This driver can be built as a module. If built as a module it will be
> +	  called "selpvmf".
> +
>  endmenu
>  endif
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 2a9f91e81af8..198e43ef7a51 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -289,3 +289,6 @@ obj-$(CONFIG_MFD_ATC260X_I2C)	+= atc260x-i2c.o
>  
>  obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
>  obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
> +
> +selpvmf-objs			:= selpvmf-core.o selpvmf-cvp.o
> +obj-$(CONFIG_MFD_SELPVMF)	+= selpvmf.o
> diff --git a/drivers/mfd/selpvmf-core.c b/drivers/mfd/selpvmf-core.c
> new file mode 100644
> index 000000000000..ec3e65fe8064
> --- /dev/null
> +++ b/drivers/mfd/selpvmf-core.c
> @@ -0,0 +1,482 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
> +/*
> + * Copyright 2017 Schweitzer Engineering Laboratories, Inc.

No changes since 2017?

Why upstream this now?

> + * 2350 NE Hopkins Court, Pullman, WA 99163 USA
> + * SEL Open Source <opensource@selinc.com>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/property.h>
> +#include <linux/mfd/core.h>
> +
> +#include "selpvmf-cvp.h"
> +
> +#define PCI_VENDOR_ID_SEL	0x1aa9
> +#define PCI_DEVICE_ID_B1190	0x0014 /* SEL-3390T */
> +#define PCI_DEVICE_ID_B2093	0x0015 /* SEL-3350 mainboard */
> +#define PCI_DEVICE_ID_B2077	0x0018 /* SEL-3390E4 */
> +#define PCI_DEVICE_ID_B2091	0x0019 /* SEL-2241-2 */
> +
> +#define B1190_NUM_ETHERNET 2
> +#define B2077_NUM_ETHERNET 4
> +#define B2091_NUM_ETHERNET 5
> +#define B2093_NUM_ETHERNET 5
> +#define SELETH_NUM_RESOURCES 4
> +
> +#define SELETH_LEN	0x1000
> +
> +static bool skipcvp;
> +module_param(skipcvp, bool, 0644);
> +MODULE_PARM_DESC(skipcvp, "Skip firmware load via CvP.");
> +
> +/**
> + * sel_create_cell() - Create an MFD cell and add it to the device.
> + * @pdev: The PCI device to operate on
> + * @name: MFD cell name
> + * @start: Start address of memory resource
> + * @length: Length of memory resource
> + * @msix_start: MSIX vector number to start from.
> + * @resources: Pointer to resources to add to the cell.
> + * @num_resources: Number of resources. The first resource is assumed to
> + *                 be memory, all other resources are IRQs.
> + */
> +static int sel_create_cell(struct pci_dev *pdev,

This is misleading.  It should be sel_register_device.

> +			   const char *name,

This never changes.  Why supply it as a variable?

> +			   unsigned int start,
> +			   unsigned int length,

This never changes.  Why supply it as a variable?

> +			   unsigned int msix_start,

This never changes.  Why supply it as a variable?

> +			   struct resource *resources,
> +			   unsigned int num_resources)

This never changes.  Why supply it as a variable?

> +{
> +	struct mfd_cell cell;
> +	unsigned int msix;
> +	int rc;
> +	int i;
> +
> +	if (!resources || num_resources < 1)

How would this be possible?

> +		return -EINVAL;
> +
> +	/* If MSI-X is enabled, assume we're using it and start at the

Malformed multi-line comment.

> +	 * provided vector number and iterate through the interrupts
> +	 * when adding IRQ resources. Otherwise leave the interrupt
> +	 * index at zero for legacy IRQs.
> +	 */
> +	if (pdev->msix_enabled)
> +		msix = msix_start;
> +	else
> +		msix = 0;

Preinitialise then drop the else.

> +	/* First resource is always a memory resource */
> +	resources[0].start = start;
> +	resources[0].end = start + length - 1;
> +	resources[0].flags = IORESOURCE_MEM;
> +
> +	/* Any additional resources are IRQs */
> +	for (i = 1; i < num_resources; i++) {
> +		resources[i].start = pci_irq_vector(pdev, msix);
> +		resources[i].end = pci_irq_vector(pdev, msix);

Does pci_irq_vector(pdev, msix) change between calls?

> +		resources[i].flags = IORESOURCE_IRQ;
> +		if (pdev->msix_enabled)
> +			msix++;
> +	}
> +
> +	cell.name = name;
> +	cell.num_resources = num_resources;
> +	cell.resources = resources;
> +
> +	rc = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, &cell,
> +			     1, NULL, 0, NULL);
> +
> +	return rc;
> +}
> +
> +/**
> + * create_eth_cell - Create and add an MFD cell for an selnet component
> + *
> + * @pdev: PCI device
> + * @offset: Offset from BAR 0 of the component
> + * @msix_num: Starting MSI-X vector number
> + */
> +static int create_eth_cell(struct pci_dev *pdev,
> +			   unsigned int offset,
> +			   unsigned int msix_num)
> +{
> +	struct resource resources[SELETH_NUM_RESOURCES] = {0};
> +
> +	return sel_create_cell(pdev,

This is only called from here, thus bring all of the sel_create_cell()
stuff into here instead.

> +			       "selpcimac",
> +			       pdev->resource[0].start + offset,
> +			       SELETH_LEN,
> +			       msix_num,
> +			       resources,
> +			       SELETH_NUM_RESOURCES);
> +}
> +
> +/**
> + * b1190_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
> + *
> + * @pdev: PCI device for the b1190
> + *
> + * +----------------------------------------+
> + * |          | Resources |     Address     |
> + * |  Device  | Mem | IRQ | Start  | Length |
> + * |----------+-----+-----+--------+--------|
> + * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
> + * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
> + * | seltime  |  1  |  1  | 0x3000 | 0x1000 |
> + * | upgrades |  1  |  0  | 0x4000 | 0x4000 |
> + * +----------------------------------------+
> + */
> +static int b1190_alloc_mfd_cells(struct pci_dev *pdev)
> +{
> +	unsigned int offset;
> +	int rc;

'ret' is more common.

> +	int i;

Use for (int i = 0 ... instead.

> +	/* Ethernet */
> +	offset = 0x0000;

Initialise this during declaration.

> +	for (i = 0; i < B1190_NUM_ETHERNET; i++) {
> +		rc = create_eth_cell(pdev, offset, i * 3);
> +		if (rc)
> +			goto out_fail;
> +		offset += SELETH_LEN;
> +	}
> +
> +out_fail:
> +	return rc;
> +}

These functions are always identical.  Create a single function and pass
the number of ports into it and remove the rest.

> +
> +/**
> + * b2077_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
> + *
> + * @pdev: PCI device for the b2077
> + *
> + * +----------------------------------------+
> + * |          | Resources |     Address     |
> + * |  Device  | Mem | IRQ | Start  | Length |
> + * |----------+-----+-----+--------+--------|
> + * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
> + * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
> + * | Ethernet |  1  |  3  | 0x2000 | 0x1000 |
> + * | Ethernet |  1  |  3  | 0x3000 | 0x1000 |
> + * | upgrades |  1  |  0  | 0x8000 | 0x4000 |
> + * | seltime  |  1  |  1  | 0xc000 | 0x1000 |
> + * +----------------------------------------+
> + */
> +static int b2077_alloc_mfd_cells(struct pci_dev *pdev)
> +{
> +	unsigned int offset;
> +	int rc;
> +	int i;
> +
> +	/* Ethernet */
> +	offset = 0x0000;
> +	for (i = 0; i < B2077_NUM_ETHERNET; i++) {
> +		rc = create_eth_cell(pdev, offset, i * 3);
> +		if (rc)
> +			goto out_fail;
> +		offset += SELETH_LEN;
> +	}
> +
> +out_fail:
> +	return rc;
> +}
> +
> +/*
> + * b2091_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
> + *
> + * @pdev: PCI device for the b2091
> + *
> + * +------------------------------------------+
> + * |            | Resources |     Address     |
> + * |   Device   | Mem | IRQ | Start  | Length |
> + * |------------+-----+-----+--------+--------|
> + * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
> + * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
> + * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
> + * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
> + * +------------------------------------------+
> + */
> +static int b2091_alloc_mfd_cells(struct pci_dev *pdev)
> +{
> +	unsigned int offset;
> +	int rc;
> +	int i;
> +
> +	/* Ethernet */
> +	offset = 0x0000;
> +	for (i = 0; i < B2091_NUM_ETHERNET; i++) {
> +		rc = create_eth_cell(pdev, offset, i * 3);
> +		if (rc)
> +			goto out_fail;
> +		offset += SELETH_LEN;
> +	}
> +
> +out_fail:
> +	return rc;
> +}
> +
> +/**
> + * b2093_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
> + *
> + * @pdev: PCI device for the b2093
> + *
> + * +------------------------------------------+
> + * |            | Resources |     Address     |
> + * |   Device   | Mem | IRQ | Start  | Length |
> + * |------------+-----+-----+--------+--------|
> + * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
> + * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
> + * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
> + * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
> + * | contact IO |  1  |  0  | 0xe000 | 0x1000 |
> + * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf400 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf500 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf600 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf700 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf800 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xf900 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xfa00 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xfb00 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xfc00 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xfd00 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xfe00 | 0x0100 |
> + * | multiuart  |  1  |  1  | 0xff00 | 0x0100 |
> + * +------------------------------------------+
> + */
> +static int b2093_alloc_mfd_cells(struct pci_dev *pdev)
> +{
> +	unsigned int offset;
> +	int rc;
> +	int i;
> +
> +	/* Ethernet */
> +	offset = 0x0000;
> +	for (i = 0; i < B2093_NUM_ETHERNET; i++) {
> +		rc = create_eth_cell(pdev, offset, i * 3);
> +		if (rc)
> +			goto out_fail;
> +		offset += SELETH_LEN;
> +	}
> +
> +out_fail:
> +	return rc;
> +}
> +
> +/*
> + * selpvmf_probe() - Probe function for the device.

None of these headers are really necessary.  This is not an external API.

> + *
> + * @pdev: PCI device being probed.
> + * @ent: Entry in the pci_tbl for the device.
> + *
> + * Return: 0 for success, otherwise the appropriate negative error code
> + */
> +static int selpvmf_probe(struct pci_dev *pdev, struct pci_device_id const *ent)
> +{
> +	typedef int (*alloc_fn_t)(struct pci_dev *pdev);
> +	alloc_fn_t alloc_fn = (alloc_fn_t)ent->driver_data;
> +	u16 cvp_capable;
> +	int num_msix;
> +	int rc = 0;
> +
> +	rc = pcim_enable_device(pdev);

Could it be that the device isn't ready?

No use for -EPROBE_DEFER?

> +	if (rc != 0)
> +		return rc;
> +
> +	/* Perform CvP program if necessary */
> +	cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
> +	if (!skipcvp && cvp_capable) {

The '_' inconsistency is bothering me.

As is the placement of "cvp".

> +		rc = cvp_start(pdev);
> +		if (rc != 0) {
> +			dev_err(&pdev->dev, "Error: CvP failed: 0x%x\n", rc);

This is a user facing error message and I still don't know what CvP is.

Please be more forthcoming.

> +			goto out_disable;
> +		}
> +		dev_info(&pdev->dev, "CvP Successful!\n");

This serves no purpose.  Please keep the log as clean as possible.

> +	}
> +
> +	pci_set_master(pdev);
> +
> +	/* Only MSI-X and legacy interrupts are supported. Try to get

Malformed multi-line comment.

> +	 * MSI-X, if we don't get them all, fall back to legacy.

Full stop after MSI-X.

> +	 */
> +	num_msix = pci_msix_vec_count(pdev);

'\n' here.

> +	rc = pci_alloc_irq_vectors(pdev, num_msix, num_msix, PCI_IRQ_MSIX);
> +	if (rc != num_msix) {
> +		dev_warn(&pdev->dev,
> +			 "Failed to get MSI-X, falling back to legacy\n");

Who is going to care about that?

'\n' here.

> +		rc = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
> +		if (rc != 1) {
> +			rc = -ENOSPC;

I think you should be returning rc as-is here.

> +			goto out_disable;
> +		}
> +	}
> +
> +	rc = alloc_fn(pdev);

These call-backs are horrible.  Please don't do this.

> +	if (rc < 0) {
> +		dev_err(&pdev->dev, "ERROR: Failed to add child cells\n");

"Failed to register child devices"

And it should come after the call to mfd_add_devices() not here.

> +		goto out_free_irq;
> +	}
> +
> +	dev_info(&pdev->dev, "Successfully added new devices\n");

Remove this please.

> +	return 0;
> +
> +out_free_irq:
> +	pci_free_irq_vectors(pdev);
> +out_disable:
> +	pci_clear_master(pdev);
> +	return rc;
> +}
> +
> +/*
> + * selpvmf_remove - Remove a selpvmf enabled PCI device from the system
> + *
> + * @pdev: PCI device to be removed
> + */
> +static void selpvmf_remove(struct pci_dev *pdev)
> +{
> +	mfd_remove_devices(&pdev->dev);

Use devm_* instead,

> +	pci_free_irq_vectors(pdev);
> +	pci_clear_master(pdev);
> +}
> +
> +/*
> + * selpvmf_shutdown() - Tears down a selpvmf card.
> + *
> + * @pdev: Pointer to the device structure describing the port.
> + */
> +static void selpvmf_shutdown(struct pci_dev *pdev)
> +{
> +	mfd_remove_devices(&pdev->dev);

As above.

> +}
> +
> +/*
> + * selpvmf_suspend - Suspend the device in preparation for entering a
> + * a low power state.
> + *
> + * @dev: Pointer to the device structure describing the port.
> + *
> + * Return: This function always indicates success.
> + *
> + * Most of the work should be handled by the child devices of this
> + * driver. There is no specific action necessary by the card, so when
> + * the child devices are done suspending, just do a generic PCI saving
> + * of state.
> + */
> +static int selpvmf_suspend(struct device *dev)
> +{
> +	struct pci_dev *pdev = to_pci_dev(dev);
> +
> +	pci_save_state(pdev);
> +
> +	return 0;
> +}
> +
> +/*
> + * selpvmf_resume() - Resume a selpvmf device that has previously been
> + * suspended
> + *
> + * @dev: Pointer to the device structure describing the port.
> + *
> + * Return: This function always indicates success.
> + */
> +static int selpvmf_resume(struct device *dev)
> +{
> +	struct pci_dev *pdev = to_pci_dev(dev);
> +	u16 cvp_capable;
> +	int rc;
> +
> +	/* Turn the card on by requesting a transition to D0 before
> +	 * restoring the PCI device state and enabling the UARTs
> +	 */
> +	pci_set_power_state(pdev, PCI_D0);
> +
> +	/* Restore so we can do IOMEM based CvP */
> +	pci_restore_state(pdev);
> +
> +	rc = pcim_enable_device(pdev);
> +	if (rc != 0) {
> +		/* We are not really supposed to return an error from this
> +		 * function, so if the enable fails, just print some info
> +		 * to aid in troubleshooting.
> +		 */

What?  It returns an int for a reason, please use it.

> +		dev_err(&pdev->dev,
> +			"Unable to enable the device. Continuing anyway\n");
> +	}
> +
> +	/* Perform CvP program if necessary */
> +	cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
> +	if (!skipcvp && cvp_capable) {
> +		pci_save_state(pdev);
> +
> +		rc = cvp_start(pdev);
> +		if (rc != 0) {
> +			dev_err(&pdev->dev,
> +				"Error: CvP failed (resume): 0x%x\n", rc);

You can use up to 100-chars to prevent this kind of wrapping.

> +		}
> +		dev_info(&pdev->dev, "CvP done (resume): 0x%x\n", rc);

Remove this please.

> +
> +		pci_restore_state(pdev);
> +	}
> +
> +	pci_set_master(pdev);
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops selpvmf_pm_ops = {
> +	SYSTEM_SLEEP_PM_OPS(selpvmf_suspend, selpvmf_resume)
> +};
> +
> +static struct pci_device_id selpvmf_pci_tbl[] = {
> +	{
> +		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B1190),
> +		.driver_data = (kernel_ulong_t)b1190_alloc_mfd_cells,

Pass an identifier here instead of call-back functions.

> +	},
> +	{
> +		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2077),
> +		.driver_data = (kernel_ulong_t)b2077_alloc_mfd_cells,
> +	},
> +	{
> +		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2091),
> +		.driver_data = (kernel_ulong_t)b2091_alloc_mfd_cells,
> +	},
> +	{
> +		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2093),
> +		.driver_data = (kernel_ulong_t)b2093_alloc_mfd_cells,
> +	},
> +	{0,}

What is this?  Where have you seen that before?

> +};
> +MODULE_DEVICE_TABLE(pci, selpvmf_pci_tbl);
> +
> +static struct pci_driver selpvmf_driver = {
> +	.name		= "selpvmf",
> +	.id_table	= selpvmf_pci_tbl,
> +	.probe		= selpvmf_probe,
> +	.remove		= selpvmf_remove,
> +	.shutdown	= selpvmf_shutdown,
> +	.driver.pm	= pm_ptr(&selpvmf_pm_ops),
> +};
> +module_pci_driver(selpvmf_driver);
> +
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_AUTHOR("Schweitzer Engineering Laboratories, Inc.");
> +MODULE_DESCRIPTION("Multi function driver for enumerating multiple device drivers for a single PCI resource");
> +MODULE_DEVICE_TABLE(pci, selpvmf_pci_tbl);
> +MODULE_FIRMWARE("sel/1AA9_0014_01.cvp");
> +MODULE_FIRMWARE("sel/1AA9_0015_01.cvp");
> +MODULE_FIRMWARE("sel/1AA9_0018_01.cvp");
> diff --git a/drivers/mfd/selpvmf-cvp.c b/drivers/mfd/selpvmf-cvp.c

I'm not sure what all of this is, but I suggest that it doesn't live
here.  drivers/firmware or drivers/fpga perhaps?

> new file mode 100644
> index 000000000000..e119cce83d7c
> --- /dev/null
> +++ b/drivers/mfd/selpvmf-cvp.c
> @@ -0,0 +1,431 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
> +/*
> + * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
> + * 2350 NE Hopkins Court, Pullman, WA 99163 USA
> + * SEL Open Source <opensource@selinc.com>

Do you really need the whole postal address here?

I suggest this is legacy of a time well past.

> + *
> + * Support for Configuration via Protocol (CvP) on SEL devices using
> + * Altera FPGAs.
> + */
> +
> +#include <linux/firmware.h>
> +#include <linux/pci.h>
> +#include <linux/sched.h>
> +
> +#include "selpvmf-cvp.h"
> +
> +#define MIN_WAIT 2 /* number of jiffies for unit wait period */

Comments should start with an upper case char.

> +#define US_PER_JIFFY  (1000000 / HZ) /* number of microseconds per jiffy */

Please tab all of these out.

> +#define CVP_WAIT_TIMEOUT 1000000 /* Wait timeout in microseconds */
> +#define BYTES_PER_DWORD	4
> +#define CORE_FIRMWARE_FILE_BASEDIR "sel/"
> +#define CORE_FIRMWARE_FILE_EXT ".cvp"
> +
> +#define CVP_STATUS_OFFSET	0x0000001C
> +#define CVP_CONFIG_READY	BIT(18)
> +#define CVP_CONFIG_ERROR	BIT(19)
> +#define CVP_EN			BIT(20)
> +#define USERMODE		BIT(21)
> +#define PLD_CLK_IN_USE		BIT(24)
> +
> +#define CVP_MODE_CONTROL_OFFSET		0x00000020
> +#define CVP_MODE			BIT(0)
> +#define HIP_CLK_SEL			BIT(1)
> +#define CVP_NUMCLKS_MASK		GENMASK(15, 8)
> +#define CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT	BIT(8)
> +#define CVP_NUMCLKS_COMPRESSED		BIT(11)
> +#define CVP_NUMCLKS_ENCRYPTED		BIT(10)
> +
> +#define CVP_DATA_OFFSET	0x00000028
> +
> +#define CVP_PROGRAMMING_CTRL_OFFSET	0x0000002C
> +#define CVP_CONFIG			BIT(0)
> +#define START_XFER			BIT(1)
> +
> +#define CVP_UNCORRECTABLE_IE_STATUS_OFFSET	0x00000034
> +#define CVP_CONFIG_ERROR_LATCHED		BIT(5)

I'm going to stop here.  Please address the issues before resubmitting.

> +struct cvp_state {
> +	int offset;
> +	struct firmware const *fw;
> +	struct pci_dev *pdev;
> +	uint32_t __iomem *bar;
> +};
> +
> +/**
> + * cvp_set_bit() - Sets the bit requested to 1
> + *
> + * @state: Initialized CvP State structure
> + * @offset: The pci capability offset to read the value from
> + * @bit: The bit to read at the pci config space offset
> + */
> +static void cvp_set_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
> +{
> +	uint32_t reg;
> +
> +	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
> +	reg |= bit;
> +	pci_write_config_dword(state->pdev, state->offset + offset, reg);
> +}
> +
> +/**
> + * cvp_clear_bit() - Sets the bit requested to 0
> + *
> + * @state: Initialized CvP State structure
> + * @offset: The pci capability offset to read the value from
> + * @bit: The bit to read at the pci config space offset
> + */
> +static void cvp_clear_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
> +{
> +	uint32_t reg;
> +
> +	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
> +	reg &= ~(bit);
> +	pci_write_config_dword(state->pdev, state->offset + offset, reg);
> +}
> +
> +/**
> + * cvp_read_bit() - Reads the requested bit to determine its state
> + *
> + * @state: Initialized CvP State structure
> + * @offset: The pci capability offset to read the value from
> + * @bit: The bit to read at the pci config space offset
> + *
> + * Return: 1 if the bit is set, 0 if not
> + */
> +static int cvp_read_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
> +{
> +	uint32_t reg;
> +
> +	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
> +	return ((reg & bit) == 0) ? 0 : 1;
> +}
> +
> +/**
> + * cvp_wait_for_bit() - Reads the requested bit and spins until it
> + * becomes the value requested
> + *
> + * This request will also timeout at the ns value requested by the caller
> + *
> + * @state: Initialized CvP State structure
> + * @offset: The pci capability offset to read the value from
> + * @bit: The bit to read at the pci config space offset
> + * @value: The value the bit needs to be set to in order to return
> + *
> + * Return: 1 if the bit_val became the value requested during the timeout
> + * period. 0 if a timeout occurred.
> + */
> +static int cvp_wait_for_bit(struct cvp_state *state,
> +			    uint32_t offset,
> +			    uint32_t bit,
> +			    uint32_t value)
> +{
> +	uint8_t bit_val;
> +	uint32_t n_wait_loops = 1;
> +
> +	if (CVP_WAIT_TIMEOUT > (US_PER_JIFFY * MIN_WAIT)) {
> +		n_wait_loops = (CVP_WAIT_TIMEOUT + (US_PER_JIFFY * MIN_WAIT) - 1) /
> +			       (US_PER_JIFFY * MIN_WAIT);
> +	}
> +
> +	DECLARE_WAIT_QUEUE_HEAD(cvp_wq);
> +
> +	bit_val = cvp_read_bit(state, offset, bit);
> +
> +	while ((bit_val != value) && (n_wait_loops != 0)) {
> +		wait_event_timeout(cvp_wq, 0, MIN_WAIT);
> +		bit_val = cvp_read_bit(state, offset, bit);
> +		--n_wait_loops;
> +	}
> +
> +	return bit_val == value;
> +}
> +
> +/**
> + * perform_244_dummy_writes() - Performs 244 dummy writes to the hard IP
> + * to clear the CvP state machine.
> + *
> + * @state: Initialized CvP State structure
> + */
> +static void perform_244_dummy_writes(struct cvp_state *state)
> +{
> +	for (int i = 0; i < 244; i++)
> +		iowrite32(0xFFFFFFFF, state->bar);
> +}
> +
> +/**
> + * set_numclks() - Updates the CVP_NUMCLKS value in the mode control register
> + *
> + * @state: Initialized CvP State structure
> + * @value: The value to set CVP_NUMCLKS to
> + */
> +static void set_numclks(struct cvp_state *state, uint32_t value)
> +{
> +	uint32_t reg;
> +
> +	pci_read_config_dword(state->pdev,
> +			      state->offset + CVP_MODE_CONTROL_OFFSET,
> +			      &reg);
> +	reg &= ~(CVP_NUMCLKS_MASK);
> +	reg |= (value & CVP_NUMCLKS_MASK);
> +	pci_write_config_dword(state->pdev,
> +			       state->offset + CVP_MODE_CONTROL_OFFSET,
> +			       reg);
> +}
> +
> +/**
> + * cvp_teardown() - Removes the PCI device from its ready to program state
> + *
> + * @state: Initialized CvP State structure
> + *
> + * Return: 0 for success, negative error code if not
> + */
> +static int cvp_teardown(struct cvp_state *state)
> +{
> +	int status = 0;
> +	uint32_t bit_val;
> +
> +	cvp_clear_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, START_XFER);
> +	cvp_clear_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, CVP_CONFIG);
> +	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
> +	perform_244_dummy_writes(state);
> +
> +	if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET, CVP_CONFIG_READY, 0)) {
> +		status = -1;
> +		goto exit;
> +	}
> +
> +	bit_val = cvp_read_bit(state,
> +			       CVP_UNCORRECTABLE_IE_STATUS_OFFSET,
> +			       CVP_CONFIG_ERROR_LATCHED);
> +
> +	if (bit_val == 1) {
> +		cvp_set_bit(state,
> +			    CVP_UNCORRECTABLE_IE_STATUS_OFFSET,
> +			    CVP_CONFIG_ERROR_LATCHED);
> +	}
> +
> +	cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
> +	cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
> +
> +	if (bit_val == 0) {
> +		if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET,
> +				      PLD_CLK_IN_USE, 1) ||
> +		    !cvp_wait_for_bit(state, CVP_STATUS_OFFSET, USERMODE, 1)) {
> +			status = -1;
> +		}
> +	}
> +
> +exit:
> +	return status;
> +}
> +
> +/**
> + * cvp_program() - Performs the FPGA programming via CvP
> + *
> + * @state: Initialized CvP State structure
> + *
> + * Return: 0 for success, negative error code if not
> + */
> +static int cvp_program(struct cvp_state *state)
> +{
> +	int status = 0;
> +	int i;
> +
> +	uint32_t *firmware_ptr;
> +	uint32_t dword_size;
> +
> +	set_numclks(state, CVP_NUMCLKS_COMPRESSED);
> +
> +	/*
> +	 * The file_read_data function returns a size in bytes,
> +	 * cvp_set_reg requires a write of a single dword. Therefore
> +	 * calculate a dword size.
> +	 */
> +	dword_size = (state->fw->size / BYTES_PER_DWORD);
> +	if ((state->fw->size % BYTES_PER_DWORD) != 0)
> +		dword_size++;
> +
> +	firmware_ptr = (uint32_t *)state->fw->data;
> +	for (i = 0; i < dword_size; i++)
> +		iowrite32(firmware_ptr[i], state->bar);
> +
> +	/* Check if any errors happened during the CvP process */
> +	if (cvp_read_bit(state,	CVP_STATUS_OFFSET, CVP_CONFIG_ERROR) == 1)
> +		status = -1;
> +
> +	return status;
> +}
> +
> +/**
> + * cvp_setup() - Performs the CvP setup logic readying the pci device for
> + * programming
> + *
> + * @state: Initialized CvP State structure
> + *
> + * Return: 0 for success, negative error code if not
> + */
> +static int cvp_setup(struct cvp_state *state)
> +{
> +	int status = 0;
> +
> +	/*
> +	 * Make sure the hardware isn't in a state where a previous CvP
> +	 * failed. If it did, run teardown to put it back in a pristine
> +	 * state.
> +	 */
> +	if (cvp_read_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE) != 0)
> +		cvp_teardown(state);
> +
> +	cvp_set_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
> +	cvp_set_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
> +	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
> +	perform_244_dummy_writes(state);
> +	cvp_set_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, CVP_CONFIG);
> +	if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET, CVP_CONFIG_READY, 1)) {
> +		status = -1;
> +		/*
> +		 * This may fail, but try to put it back.  Note that we
> +		 * aren't waiting for clocks to stabilize after doing
> +		 * this, so the device isn't going to be usable.  We are
> +		 * assuming the driver is going to bail.
> +		 */
> +		cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
> +		cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
> +		goto exit;
> +	}
> +
> +	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
> +	perform_244_dummy_writes(state);
> +	cvp_set_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, START_XFER);
> +
> +exit:
> +	return status;
> +}
> +
> +/**
> + * cvp_init() - Initialize the CvP state machine.
> + *
> + * @pdev: Pointer to the pci_device structure containing information
> + *        about the device being initialized
> + * @state: Caller allocated CvP state structure to be initialized.
> + *
> + * Return: 0 if the state was initialized successfully and CvP can be
> + *    executed.  A non-zero exit code will indicate an appropriate error.
> + *
> + *    If the function returns 0, the caller should call cvp_exit to
> + *    cleanup.
> + */
> +static int cvp_init(struct pci_dev *pdev, struct cvp_state *state)
> +{
> +	int status = 0;
> +	int value;
> +	char filepath[512];
> +
> +	state->pdev = pdev;
> +	state->fw = NULL;
> +	state->bar = NULL;
> +
> +	state->offset = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
> +	if (!state->offset) {
> +		dev_warn(&pdev->dev,
> +			 "Device requests CvP but indicating not capable\n");
> +		status = -ENODEV;
> +		goto exit;
> +	}
> +
> +	value = cvp_read_bit(state, CVP_STATUS_OFFSET, CVP_EN);
> +	if (value == 0) {
> +		dev_err(&pdev->dev, "CvP not enabled in device\n");
> +		status = -ENODEV;
> +		goto exit;
> +	}
> +
> +	/*
> +	 * BAR 0 will always exist and will always be mmio so we can do
> +	 * memory transactions to speed up CvP. Make sure to not map
> +	 * more than our first resource. CvP will write to only the
> +	 * first address that is mapped so a mapping size of 4 bytes
> +	 * should do.
> +	 */
> +	state->bar = pci_iomap(pdev, 0, 4);
> +	if (!state->bar) {
> +		dev_err(&pdev->dev, "Memory BAR not available\n");
> +		status = -ENODEV;
> +		goto exit;
> +	}
> +
> +	/* Create the filepath to request firmware from */
> +	snprintf(filepath,
> +		 512,
> +		 "%s%04X_%04X_%02x%s",
> +		 CORE_FIRMWARE_FILE_BASEDIR,
> +		 pdev->vendor,
> +		 pdev->device,
> +		 pdev->revision,
> +		 CORE_FIRMWARE_FILE_EXT);
> +
> +	if (request_firmware(&state->fw, filepath, &pdev->dev)) {
> +		dev_err(&pdev->dev,
> +			"Error: The driver failed to find a core image for this device at: %s\n",
> +			filepath);
> +		iounmap(state->bar);
> +		state->bar = NULL;
> +		status = -ENOENT;
> +	}
> +
> +exit:
> +	return status;
> +}
> +
> +/**
> + * cvp_exit() - Cleanup CvP resources
> + *
> + * @state: Initialized CvP state to cleanup.
> + */
> +static void cvp_exit(struct cvp_state *state)
> +{
> +	if (state->fw)
> +		release_firmware(state->fw);
> +	if (state->bar) {
> +		iounmap(state->bar);
> +		state->bar = NULL;
> +	}
> +}
> +
> +/**
> + * cvp_start() - Public facing function to kick off FPGA programming via CvP
> + *
> + * @pdev: Pointer to the pci_device structure containing information about the
> + *        device being initialized
> + *
> + * Return: 0 for success, otherwise the appropriate negative error code
> + */
> +int cvp_start(struct pci_dev *pdev)
> +{
> +	int status = 0;
> +	struct cvp_state state;
> +
> +	dev_info(&pdev->dev, "Loading device firmware. AER errors may occur during the loading process.\n");
> +
> +	status = cvp_init(pdev, &state);
> +	if (status != 0)
> +		goto exit;
> +
> +	status = cvp_setup(&state);
> +	if (status != 0)
> +		goto exit;
> +
> +	/*
> +	 * If setup succeeded we need to perform the cvp teardown
> +	 * regardless of whether or not the program was successful
> +	 */
> +	if (cvp_program(&state) != 0)
> +		status = -1;
> +	if (cvp_teardown(&state) != 0)
> +		status = -1;
> +
> +exit:
> +	cvp_exit(&state);
> +	return status;
> +}
> diff --git a/drivers/mfd/selpvmf-cvp.h b/drivers/mfd/selpvmf-cvp.h
> new file mode 100644
> index 000000000000..32de3b9e9884
> --- /dev/null
> +++ b/drivers/mfd/selpvmf-cvp.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
> +/*
> + * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
> + * 2350 NE Hopkins Court, Pullman, WA 99163 USA
> + * SEL Open Source <opensource@selinc.com>
> + */
> +
> +#ifndef _SELPVMF_CVP_H
> +#define _SELPVMF_CVP_H
> +
> +#include <linux/pci.h>
> +
> +/* Define for CvP in PCI config space */
> +#define CVP_CAPABILITY_ID	0x0000000B
> +
> +int cvp_start(struct pci_dev *pdev);
> +
> +#endif /* _SELPVMF_CVP_H */
> -- 
> 2.45.2
>
Robert Joslyn Nov. 7, 2024, 6:42 p.m. UTC | #2
> -----Original Message-----
> From: Lee Jones <lee@kernel.org>
> Sent: Tuesday, November 5, 2024 9:31 AM
> To: Robert Joslyn <robert_joslyn@selinc.com>
> Cc: linux-kernel@vger.kernel.org; netdev@vger.kernel.org
> Subject: Re: [RFC PATCH 1/2] mfd: Add SEL PCI Virtual Multifunction (PVMF)
> support
> 
> [Caution - External]
> 
> On Mon, 28 Oct 2024, Robert Joslyn wrote:
> 
> > Add support for SEL FPGA based PCIe devices. These expose a single PCI
> > BAR with multiple IP cores for various functions, such as serial
> > ports, ethernet, and time (PTP/IRIG). This initial driver supports
> > Ethernet on the SEL-3350 mainboard, SEL-3390E4 ethernet card, and
> > SEL-3390T ethernet and time card.
> >
> > Signed-off-by: Robert Joslyn <robert_joslyn@selinc.com>
> > ---
> >  MAINTAINERS                |   8 +
> >  drivers/mfd/Kconfig        |  16 ++
> >  drivers/mfd/Makefile       |   3 +
> >  drivers/mfd/selpvmf-core.c | 482
> > +++++++++++++++++++++++++++++++++++++
> >  drivers/mfd/selpvmf-cvp.c  | 431
> +++++++++++++++++++++++++++++++++
> > drivers/mfd/selpvmf-cvp.h  |  18 ++
> >  6 files changed, 958 insertions(+)
> >  create mode 100644 drivers/mfd/selpvmf-core.c  create mode 100644
> > drivers/mfd/selpvmf-cvp.c  create mode 100644
> > drivers/mfd/selpvmf-cvp.h
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS index
> > a27407950242..4a24b3be8aa5 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -20730,6 +20730,14 @@ F:   tools/testing/selftests/lsm/
> >  X:   security/selinux/
> >  K:   \bsecurity_[a-z_0-9]\+\b
> >
> > +SEL DRIVERS
> > +M:   Robert Joslyn <robert_joslyn@selinc.com>
> > +S:   Supported
> > +B:   mailto:opensource@selinc.com
> > +F:   drivers/mfd/selpvmf-*
> > +F:   drivers/platform/x86/sel3350-platform.c
> > +F:   include/linux/mfd/selpvmf.h
> > +
> >  SELINUX SECURITY MODULE
> >  M:   Paul Moore <paul@paul-moore.com>
> >  M:   Stephen Smalley <stephen.smalley.work@gmail.com>
> > diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index
> > f9325bcce1b9..77e2ce3db505 100644
> > --- a/drivers/mfd/Kconfig
> > +++ b/drivers/mfd/Kconfig
> > @@ -2402,5 +2402,21 @@ config MFD_RSMU_SPI
> >         Additional drivers must be enabled in order to use the functionality
> >         of the device.
> >
> > +config MFD_SELPVMF
> > +     tristate "SEL PVMF Support"
> > +     help
> > +       Support for SEL PCI virtual multifunction (PVMF) devcies utilizing
> > +       an FPGA based PCIe interface, including:
> > +         * SEL-3350
> > +         * SEL-3390E4
> > +         * SEL-3390T
> > +
> > +       This driver provides common support for accessing the device.
> > +       Additional drivers must be enabled in order to use the functionality
> > +       of the device.
> > +
> > +       This driver can be built as a module. If built as a module it will be
> > +       called "selpvmf".
> > +
> >  endmenu
> >  endif
> > diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index
> > 2a9f91e81af8..198e43ef7a51 100644
> > --- a/drivers/mfd/Makefile
> > +++ b/drivers/mfd/Makefile
> > @@ -289,3 +289,6 @@ obj-$(CONFIG_MFD_ATC260X_I2C)     += atc260x-
> i2c.o
> >
> >  obj-$(CONFIG_MFD_RSMU_I2C)   += rsmu_i2c.o rsmu_core.o
> >  obj-$(CONFIG_MFD_RSMU_SPI)   += rsmu_spi.o rsmu_core.o
> > +
> > +selpvmf-objs                 := selpvmf-core.o selpvmf-cvp.o
> > +obj-$(CONFIG_MFD_SELPVMF)    += selpvmf.o
> > diff --git a/drivers/mfd/selpvmf-core.c b/drivers/mfd/selpvmf-core.c
> > new file mode 100644 index 000000000000..ec3e65fe8064
> > --- /dev/null
> > +++ b/drivers/mfd/selpvmf-core.c
> > @@ -0,0 +1,482 @@
> > +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
> > +/*
> > + * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
> 
> No changes since 2017?
> 
> Why upstream this now?

The driver has been continually maintained out-of-tree since then. I can update the copyright notices to 2017-2024 to be more accurate. The hardware this enables is still actively maintained and sold.

> > + * 2350 NE Hopkins Court, Pullman, WA 99163 USA
> > + * SEL Open Source <opensource@selinc.com> */
> > +
> > +#include <linux/module.h>
> > +#include <linux/pci.h>
> > +#include <linux/property.h>
> > +#include <linux/mfd/core.h>
> > +
> > +#include "selpvmf-cvp.h"
> > +
> > +#define PCI_VENDOR_ID_SEL    0x1aa9
> > +#define PCI_DEVICE_ID_B1190  0x0014 /* SEL-3390T */ #define
> > +PCI_DEVICE_ID_B2093  0x0015 /* SEL-3350 mainboard */ #define
> > +PCI_DEVICE_ID_B2077  0x0018 /* SEL-3390E4 */ #define
> > +PCI_DEVICE_ID_B2091  0x0019 /* SEL-2241-2 */
> > +
> > +#define B1190_NUM_ETHERNET 2
> > +#define B2077_NUM_ETHERNET 4
> > +#define B2091_NUM_ETHERNET 5
> > +#define B2093_NUM_ETHERNET 5
> > +#define SELETH_NUM_RESOURCES 4
> > +
> > +#define SELETH_LEN   0x1000
> > +
> > +static bool skipcvp;
> > +module_param(skipcvp, bool, 0644);
> > +MODULE_PARM_DESC(skipcvp, "Skip firmware load via CvP.");
> > +
> > +/**
> > + * sel_create_cell() - Create an MFD cell and add it to the device.
> > + * @pdev: The PCI device to operate on
> > + * @name: MFD cell name
> > + * @start: Start address of memory resource
> > + * @length: Length of memory resource
> > + * @msix_start: MSIX vector number to start from.
> > + * @resources: Pointer to resources to add to the cell.
> > + * @num_resources: Number of resources. The first resource is assumed to
> > + *                 be memory, all other resources are IRQs.
> > + */
> > +static int sel_create_cell(struct pci_dev *pdev,
> 
> This is misleading.  It should be sel_register_device.

I'll rename the function.

> 
> > +                        const char *name,
> 
> This never changes.  Why supply it as a variable?
> 
> > +                        unsigned int start,
> > +                        unsigned int length,
> 
> This never changes.  Why supply it as a variable?
> 
> > +                        unsigned int msix_start,
> 
> This never changes.  Why supply it as a variable?
> 
> > +                        struct resource *resources,
> > +                        unsigned int num_resources)
> 
> This never changes.  Why supply it as a variable?

This patch only enables ethernet, but we have additional functionality that we will enable in subsequent patches, such as serial ports, a time component, and a firmware upgrade mechanism. Those components have different names, lengths, interrupt counts, etc. If you prefer, I can remove this abstraction until we have another cell type to add.

> 
> > +{
> > +     struct mfd_cell cell;
> > +     unsigned int msix;
> > +     int rc;
> > +     int i;
> > +
> > +     if (!resources || num_resources < 1)
> 
> How would this be possible?

It would be a bug in the code. I can remove this.

> 
> > +             return -EINVAL;
> > +
> > +     /* If MSI-X is enabled, assume we're using it and start at the
> 
> Malformed multi-line comment.

Is the style here to have an empty line with the leading /*?

/*
 * like this?
 */

I'll change the style.

> 
> > +      * provided vector number and iterate through the interrupts
> > +      * when adding IRQ resources. Otherwise leave the interrupt
> > +      * index at zero for legacy IRQs.
> > +      */
> > +     if (pdev->msix_enabled)
> > +             msix = msix_start;
> > +     else
> > +             msix = 0;
> 
> Preinitialise then drop the else.

I'll make this change.

> 
> > +     /* First resource is always a memory resource */
> > +     resources[0].start = start;
> > +     resources[0].end = start + length - 1;
> > +     resources[0].flags = IORESOURCE_MEM;
> > +
> > +     /* Any additional resources are IRQs */
> > +     for (i = 1; i < num_resources; i++) {
> > +             resources[i].start = pci_irq_vector(pdev, msix);
> > +             resources[i].end = pci_irq_vector(pdev, msix);
> 
> Does pci_irq_vector(pdev, msix) change between calls?

It does not, this can be simplified to one call to pci_irq_vector.

> 
> > +             resources[i].flags = IORESOURCE_IRQ;
> > +             if (pdev->msix_enabled)
> > +                     msix++;
> > +     }
> > +
> > +     cell.name = name;
> > +     cell.num_resources = num_resources;
> > +     cell.resources = resources;
> > +
> > +     rc = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, &cell,
> > +                          1, NULL, 0, NULL);
> > +
> > +     return rc;
> > +}
> > +
> > +/**
> > + * create_eth_cell - Create and add an MFD cell for an selnet
> > +component
> > + *
> > + * @pdev: PCI device
> > + * @offset: Offset from BAR 0 of the component
> > + * @msix_num: Starting MSI-X vector number  */ static int
> > +create_eth_cell(struct pci_dev *pdev,
> > +                        unsigned int offset,
> > +                        unsigned int msix_num) {
> > +     struct resource resources[SELETH_NUM_RESOURCES] = {0};
> > +
> > +     return sel_create_cell(pdev,
> 
> This is only called from here, thus bring all of the sel_create_cell() stuff into
> here instead.

As before, this is an abstraction to facilitate creation of multiple cell types. I can remove it until we have that if preferred.

> 
> > +                            "selpcimac",
> > +                            pdev->resource[0].start + offset,
> > +                            SELETH_LEN,
> > +                            msix_num,
> > +                            resources,
> > +                            SELETH_NUM_RESOURCES); }
> > +
> > +/**
> > + * b1190_alloc_mfd_cells - Allocate and initialize MFD cell
> > +descriptions
> > + *
> > + * @pdev: PCI device for the b1190
> > + *
> > + * +----------------------------------------+
> > + * |          | Resources |     Address     |
> > + * |  Device  | Mem | IRQ | Start  | Length |
> > + * |----------+-----+-----+--------+--------|
> > + * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
> > + * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
> > + * | seltime  |  1  |  1  | 0x3000 | 0x1000 |
> > + * | upgrades |  1  |  0  | 0x4000 | 0x4000 |
> > + * +----------------------------------------+
> > + */
> > +static int b1190_alloc_mfd_cells(struct pci_dev *pdev) {
> > +     unsigned int offset;
> > +     int rc;
> 
> 'ret' is more common.

I'll rename the variable.

> 
> > +     int i;
> 
> Use for (int i = 0 ... instead.
> 
> > +     /* Ethernet */
> > +     offset = 0x0000;
> 
> Initialise this during declaration.

I can move it. I did this because once there is a second cell type, such as seltime, there will be a pattern of assigning the offset, then creating the cells. So when we have the seltime driver ready, there will be a block of code right after this that will set the offset to 0x3000 and then create the time cell.

> 
> > +     for (i = 0; i < B1190_NUM_ETHERNET; i++) {
> > +             rc = create_eth_cell(pdev, offset, i * 3);
> > +             if (rc)
> > +                     goto out_fail;
> > +             offset += SELETH_LEN;
> > +     }
> > +
> > +out_fail:
> > +     return rc;
> > +}
> 
> These functions are always identical.  Create a single function and pass the
> number of ports into it and remove the rest.

As above, the abstraction is to accommodate future additions. I can remove the abstraction if that is preferred at this time.

> 
> > +
> > +/**
> > + * b2077_alloc_mfd_cells - Allocate and initialize MFD cell
> > +descriptions
> > + *
> > + * @pdev: PCI device for the b2077
> > + *
> > + * +----------------------------------------+
> > + * |          | Resources |     Address     |
> > + * |  Device  | Mem | IRQ | Start  | Length |
> > + * |----------+-----+-----+--------+--------|
> > + * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
> > + * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
> > + * | Ethernet |  1  |  3  | 0x2000 | 0x1000 |
> > + * | Ethernet |  1  |  3  | 0x3000 | 0x1000 |
> > + * | upgrades |  1  |  0  | 0x8000 | 0x4000 |
> > + * | seltime  |  1  |  1  | 0xc000 | 0x1000 |
> > + * +----------------------------------------+
> > + */
> > +static int b2077_alloc_mfd_cells(struct pci_dev *pdev) {
> > +     unsigned int offset;
> > +     int rc;
> > +     int i;
> > +
> > +     /* Ethernet */
> > +     offset = 0x0000;
> > +     for (i = 0; i < B2077_NUM_ETHERNET; i++) {
> > +             rc = create_eth_cell(pdev, offset, i * 3);
> > +             if (rc)
> > +                     goto out_fail;
> > +             offset += SELETH_LEN;
> > +     }
> > +
> > +out_fail:
> > +     return rc;
> > +}
> > +
> > +/*
> > + * b2091_alloc_mfd_cells - Allocate and initialize MFD cell
> > +descriptions
> > + *
> > + * @pdev: PCI device for the b2091
> > + *
> > + * +------------------------------------------+
> > + * |            | Resources |     Address     |
> > + * |   Device   | Mem | IRQ | Start  | Length |
> > + * |------------+-----+-----+--------+--------|
> > + * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
> > + * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
> > + * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
> > + * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
> > + * +------------------------------------------+
> > + */
> > +static int b2091_alloc_mfd_cells(struct pci_dev *pdev) {
> > +     unsigned int offset;
> > +     int rc;
> > +     int i;
> > +
> > +     /* Ethernet */
> > +     offset = 0x0000;
> > +     for (i = 0; i < B2091_NUM_ETHERNET; i++) {
> > +             rc = create_eth_cell(pdev, offset, i * 3);
> > +             if (rc)
> > +                     goto out_fail;
> > +             offset += SELETH_LEN;
> > +     }
> > +
> > +out_fail:
> > +     return rc;
> > +}
> > +
> > +/**
> > + * b2093_alloc_mfd_cells - Allocate and initialize MFD cell
> > +descriptions
> > + *
> > + * @pdev: PCI device for the b2093
> > + *
> > + * +------------------------------------------+
> > + * |            | Resources |     Address     |
> > + * |   Device   | Mem | IRQ | Start  | Length |
> > + * |------------+-----+-----+--------+--------|
> > + * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
> > + * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
> > + * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
> > + * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
> > + * | contact IO |  1  |  0  | 0xe000 | 0x1000 |
> > + * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf400 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf500 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf600 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf700 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf800 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xf900 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xfa00 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xfb00 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xfc00 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xfd00 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xfe00 | 0x0100 |
> > + * | multiuart  |  1  |  1  | 0xff00 | 0x0100 |
> > + * +------------------------------------------+
> > + */
> > +static int b2093_alloc_mfd_cells(struct pci_dev *pdev) {
> > +     unsigned int offset;
> > +     int rc;
> > +     int i;
> > +
> > +     /* Ethernet */
> > +     offset = 0x0000;
> > +     for (i = 0; i < B2093_NUM_ETHERNET; i++) {
> > +             rc = create_eth_cell(pdev, offset, i * 3);
> > +             if (rc)
> > +                     goto out_fail;
> > +             offset += SELETH_LEN;
> > +     }
> > +
> > +out_fail:
> > +     return rc;
> > +}
> > +
> > +/*
> > + * selpvmf_probe() - Probe function for the device.
> 
> None of these headers are really necessary.  This is not an external API.

I'll remove internal function headers.

> 
> > + *
> > + * @pdev: PCI device being probed.
> > + * @ent: Entry in the pci_tbl for the device.
> > + *
> > + * Return: 0 for success, otherwise the appropriate negative error
> > +code  */ static int selpvmf_probe(struct pci_dev *pdev, struct
> > +pci_device_id const *ent) {
> > +     typedef int (*alloc_fn_t)(struct pci_dev *pdev);
> > +     alloc_fn_t alloc_fn = (alloc_fn_t)ent->driver_data;
> > +     u16 cvp_capable;
> > +     int num_msix;
> > +     int rc = 0;
> > +
> > +     rc = pcim_enable_device(pdev);
> 
> Could it be that the device isn't ready?
> 
> No use for -EPROBE_DEFER?

I'll change this to return -EPROBE_DEFER.

> 
> > +     if (rc != 0)
> > +             return rc;
> > +
> > +     /* Perform CvP program if necessary */
> > +     cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
> > +     if (!skipcvp && cvp_capable) {
> 
> The '_' inconsistency is bothering me.
> 
> As is the placement of "cvp".

Would you prefer to see skipcvp called skip_cvp? Or cvp_skip? I'm happy to change it.

> 
> > +             rc = cvp_start(pdev);
> > +             if (rc != 0) {
> > +                     dev_err(&pdev->dev, "Error: CvP failed: 0x%x\n",
> > + rc);
> 
> This is a user facing error message and I still don't know what CvP is.
> 
> Please be more forthcoming.

CvP is Configuration via Protocol. It's a method for loading firmware into Altera FPGAs. If CvP fails, it means we're unable to load firmware into the FPGA and the card is basically dead. I can improve the error message. Maybe something like, "Loading FPGA firmware failed"?

> 
> > +                     goto out_disable;
> > +             }
> > +             dev_info(&pdev->dev, "CvP Successful!\n");
> 
> This serves no purpose.  Please keep the log as clean as possible.

I'll remove this.

> 
> > +     }
> > +
> > +     pci_set_master(pdev);
> > +
> > +     /* Only MSI-X and legacy interrupts are supported. Try to get
> 
> Malformed multi-line comment.
> 
> > +      * MSI-X, if we don't get them all, fall back to legacy.
> 
> Full stop after MSI-X.
> 
> > +      */
> > +     num_msix = pci_msix_vec_count(pdev);
> 
> '\n' here.

I'll fix these.

> 
> > +     rc = pci_alloc_irq_vectors(pdev, num_msix, num_msix, PCI_IRQ_MSIX);
> > +     if (rc != num_msix) {
> > +             dev_warn(&pdev->dev,
> > +                      "Failed to get MSI-X, falling back to
> > + legacy\n");
> 
> Who is going to care about that?
> 
> '\n' here.

Falling back to legacy interrupts has a performance impact, so this is mostly just a debugging aid. It can be removed.

> 
> > +             rc = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
> > +             if (rc != 1) {
> > +                     rc = -ENOSPC;
> 
> I think you should be returning rc as-is here.

Agreed, I'll change this.

> 
> > +                     goto out_disable;
> > +             }
> > +     }
> > +
> > +     rc = alloc_fn(pdev);
> 
> These call-backs are horrible.  Please don't do this.

Ok, we'll do something else. Would you prefer to pass in a constant and use a switch statement to select which function to call?

> 
> > +     if (rc < 0) {
> > +             dev_err(&pdev->dev, "ERROR: Failed to add child
> > + cells\n");
> 
> "Failed to register child devices"
> 
> And it should come after the call to mfd_add_devices() not here.

I'll update the message.

> 
> > +             goto out_free_irq;
> > +     }
> > +
> > +     dev_info(&pdev->dev, "Successfully added new devices\n");
> 
> Remove this please.

I'll remove this.

> 
> > +     return 0;
> > +
> > +out_free_irq:
> > +     pci_free_irq_vectors(pdev);
> > +out_disable:
> > +     pci_clear_master(pdev);
> > +     return rc;
> > +}
> > +
> > +/*
> > + * selpvmf_remove - Remove a selpvmf enabled PCI device from the
> > +system
> > + *
> > + * @pdev: PCI device to be removed
> > + */
> > +static void selpvmf_remove(struct pci_dev *pdev) {
> > +     mfd_remove_devices(&pdev->dev);
> 
> Use devm_* instead,

Didn't realize there was a devm version, I'll use it.

> 
> > +     pci_free_irq_vectors(pdev);
> > +     pci_clear_master(pdev);
> > +}
> > +
> > +/*
> > + * selpvmf_shutdown() - Tears down a selpvmf card.
> > + *
> > + * @pdev: Pointer to the device structure describing the port.
> > + */
> > +static void selpvmf_shutdown(struct pci_dev *pdev) {
> > +     mfd_remove_devices(&pdev->dev);
> 
> As above.
> 
> > +}
> > +
> > +/*
> > + * selpvmf_suspend - Suspend the device in preparation for entering a
> > + * a low power state.
> > + *
> > + * @dev: Pointer to the device structure describing the port.
> > + *
> > + * Return: This function always indicates success.
> > + *
> > + * Most of the work should be handled by the child devices of this
> > + * driver. There is no specific action necessary by the card, so when
> > + * the child devices are done suspending, just do a generic PCI
> > +saving
> > + * of state.
> > + */
> > +static int selpvmf_suspend(struct device *dev) {
> > +     struct pci_dev *pdev = to_pci_dev(dev);
> > +
> > +     pci_save_state(pdev);
> > +
> > +     return 0;
> > +}
> > +
> > +/*
> > + * selpvmf_resume() - Resume a selpvmf device that has previously
> > +been
> > + * suspended
> > + *
> > + * @dev: Pointer to the device structure describing the port.
> > + *
> > + * Return: This function always indicates success.
> > + */
> > +static int selpvmf_resume(struct device *dev) {
> > +     struct pci_dev *pdev = to_pci_dev(dev);
> > +     u16 cvp_capable;
> > +     int rc;
> > +
> > +     /* Turn the card on by requesting a transition to D0 before
> > +      * restoring the PCI device state and enabling the UARTs
> > +      */
> > +     pci_set_power_state(pdev, PCI_D0);
> > +
> > +     /* Restore so we can do IOMEM based CvP */
> > +     pci_restore_state(pdev);
> > +
> > +     rc = pcim_enable_device(pdev);
> > +     if (rc != 0) {
> > +             /* We are not really supposed to return an error from this
> > +              * function, so if the enable fails, just print some info
> > +              * to aid in troubleshooting.
> > +              */
> 
> What?  It returns an int for a reason, please use it.

I'll update this.

> 
> > +             dev_err(&pdev->dev,
> > +                     "Unable to enable the device. Continuing anyway\n");
> > +     }
> > +
> > +     /* Perform CvP program if necessary */
> > +     cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
> > +     if (!skipcvp && cvp_capable) {
> > +             pci_save_state(pdev);
> > +
> > +             rc = cvp_start(pdev);
> > +             if (rc != 0) {
> > +                     dev_err(&pdev->dev,
> > +                             "Error: CvP failed (resume): 0x%x\n",
> > + rc);
> 
> You can use up to 100-chars to prevent this kind of wrapping.

I'll update this.

> 
> > +             }
> > +             dev_info(&pdev->dev, "CvP done (resume): 0x%x\n", rc);
> 
> Remove this please.

I'll remove the message.

> 
> > +
> > +             pci_restore_state(pdev);
> > +     }
> > +
> > +     pci_set_master(pdev);
> > +
> > +     return 0;
> > +}
> > +
> > +static const struct dev_pm_ops selpvmf_pm_ops = {
> > +     SYSTEM_SLEEP_PM_OPS(selpvmf_suspend, selpvmf_resume) };
> > +
> > +static struct pci_device_id selpvmf_pci_tbl[] = {
> > +     {
> > +             PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B1190),
> > +             .driver_data = (kernel_ulong_t)b1190_alloc_mfd_cells,
> 
> Pass an identifier here instead of call-back functions.

Do you have a preference for the type of construct we use? Just a #define integer with a switch statement in probe? Something else?

> 
> > +     },
> > +     {
> > +             PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2077),
> > +             .driver_data = (kernel_ulong_t)b2077_alloc_mfd_cells,
> > +     },
> > +     {
> > +             PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2091),
> > +             .driver_data = (kernel_ulong_t)b2091_alloc_mfd_cells,
> > +     },
> > +     {
> > +             PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2093),
> > +             .driver_data = (kernel_ulong_t)b2093_alloc_mfd_cells,
> > +     },
> > +     {0,}
> 
> What is this?  Where have you seen that before?

The {0,} is how to signal the end of this array. I don't think the comma is required though. Random example: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mfd/lpc_sch.c?h=v6.12-rc6#n66

> 
> > +};
> > +MODULE_DEVICE_TABLE(pci, selpvmf_pci_tbl);
> > +
> > +static struct pci_driver selpvmf_driver = {
> > +     .name           = "selpvmf",
> > +     .id_table       = selpvmf_pci_tbl,
> > +     .probe          = selpvmf_probe,
> > +     .remove         = selpvmf_remove,
> > +     .shutdown       = selpvmf_shutdown,
> > +     .driver.pm      = pm_ptr(&selpvmf_pm_ops),
> > +};
> > +module_pci_driver(selpvmf_driver);
> > +
> > +MODULE_LICENSE("Dual BSD/GPL");
> > +MODULE_AUTHOR("Schweitzer Engineering Laboratories, Inc.");
> > +MODULE_DESCRIPTION("Multi function driver for enumerating multiple
> > +device drivers for a single PCI resource"); MODULE_DEVICE_TABLE(pci,
> > +selpvmf_pci_tbl); MODULE_FIRMWARE("sel/1AA9_0014_01.cvp");
> > +MODULE_FIRMWARE("sel/1AA9_0015_01.cvp");
> > +MODULE_FIRMWARE("sel/1AA9_0018_01.cvp");
> > diff --git a/drivers/mfd/selpvmf-cvp.c b/drivers/mfd/selpvmf-cvp.c
> 
> I'm not sure what all of this is, but I suggest that it doesn't live here.
> drivers/firmware or drivers/fpga perhaps?

I'll start a discussion with the FPGA manager maintainers to see what makes most sense. We can probably make this a library driver there.

> 
> > new file mode 100644
> > index 000000000000..e119cce83d7c
> > --- /dev/null
> > +++ b/drivers/mfd/selpvmf-cvp.c
> > @@ -0,0 +1,431 @@
> > +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
> > +/*
> > + * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
> > + * 2350 NE Hopkins Court, Pullman, WA 99163 USA
> > + * SEL Open Source <opensource@selinc.com>
> 
> Do you really need the whole postal address here?
> 
> I suggest this is legacy of a time well past.

We can probably remove this.

> 
> > + *
> > + * Support for Configuration via Protocol (CvP) on SEL devices using
> > + * Altera FPGAs.
> > + */
> > +
> > +#include <linux/firmware.h>
> > +#include <linux/pci.h>
> > +#include <linux/sched.h>
> > +
> > +#include "selpvmf-cvp.h"
> > +
> > +#define MIN_WAIT 2 /* number of jiffies for unit wait period */
> 
> Comments should start with an upper case char.
> 
> > +#define US_PER_JIFFY  (1000000 / HZ) /* number of microseconds per
> > +jiffy */
> 
> Please tab all of these out.

I'll update the formatting and grammar.

> 
> > +#define CVP_WAIT_TIMEOUT 1000000 /* Wait timeout in microseconds */
> > +#define BYTES_PER_DWORD      4
> > +#define CORE_FIRMWARE_FILE_BASEDIR "sel/"
> > +#define CORE_FIRMWARE_FILE_EXT ".cvp"
> > +
> > +#define CVP_STATUS_OFFSET    0x0000001C
> > +#define CVP_CONFIG_READY     BIT(18)
> > +#define CVP_CONFIG_ERROR     BIT(19)
> > +#define CVP_EN                       BIT(20)
> > +#define USERMODE             BIT(21)
> > +#define PLD_CLK_IN_USE               BIT(24)
> > +
> > +#define CVP_MODE_CONTROL_OFFSET              0x00000020
> > +#define CVP_MODE                     BIT(0)
> > +#define HIP_CLK_SEL                  BIT(1)
> > +#define CVP_NUMCLKS_MASK             GENMASK(15, 8)
> > +#define CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT      BIT(8)
> > +#define CVP_NUMCLKS_COMPRESSED               BIT(11)
> > +#define CVP_NUMCLKS_ENCRYPTED                BIT(10)
> > +
> > +#define CVP_DATA_OFFSET      0x00000028
> > +
> > +#define CVP_PROGRAMMING_CTRL_OFFSET  0x0000002C
> > +#define CVP_CONFIG                   BIT(0)
> > +#define START_XFER                   BIT(1)
> > +
> > +#define CVP_UNCORRECTABLE_IE_STATUS_OFFSET   0x00000034
> > +#define CVP_CONFIG_ERROR_LATCHED             BIT(5)
> 
> I'm going to stop here.  Please address the issues before resubmitting.
> 
> --
> Lee Jones [李琼斯]

Thank you for the review! I'll make these changes and start a discussion regarding the best path forward to integrate loading the FPGA firmware.

Thanks,
Robert
Lee Jones Nov. 12, 2024, 1:35 p.m. UTC | #3
On Thu, 07 Nov 2024, Robert Joslyn wrote:

> > -----Original Message-----
> > From: Lee Jones <lee@kernel.org>
> > Sent: Tuesday, November 5, 2024 9:31 AM
> > To: Robert Joslyn <robert_joslyn@selinc.com>
> > Cc: linux-kernel@vger.kernel.org; netdev@vger.kernel.org
> > Subject: Re: [RFC PATCH 1/2] mfd: Add SEL PCI Virtual Multifunction (PVMF)

These should not end up in the mail's body.

> > support
> > 
> > [Caution - External]
> > 
> > On Mon, 28 Oct 2024, Robert Joslyn wrote:
> > 
> > > Add support for SEL FPGA based PCIe devices. These expose a single PCI
> > > BAR with multiple IP cores for various functions, such as serial
> > > ports, ethernet, and time (PTP/IRIG). This initial driver supports
> > > Ethernet on the SEL-3350 mainboard, SEL-3390E4 ethernet card, and
> > > SEL-3390T ethernet and time card.
> > >
> > > Signed-off-by: Robert Joslyn <robert_joslyn@selinc.com>
> > > ---
> > >  MAINTAINERS                |   8 +
> > >  drivers/mfd/Kconfig        |  16 ++
> > >  drivers/mfd/Makefile       |   3 +
> > >  drivers/mfd/selpvmf-core.c | 482
> > > +++++++++++++++++++++++++++++++++++++
> > >  drivers/mfd/selpvmf-cvp.c  | 431
> > +++++++++++++++++++++++++++++++++
> > > drivers/mfd/selpvmf-cvp.h  |  18 ++
> > >  6 files changed, 958 insertions(+)
> > >  create mode 100644 drivers/mfd/selpvmf-core.c  create mode 100644
> > > drivers/mfd/selpvmf-cvp.c  create mode 100644
> > > drivers/mfd/selpvmf-cvp.h

[...]

> > > +/**
> > > + * sel_create_cell() - Create an MFD cell and add it to the device.
> > > + * @pdev: The PCI device to operate on
> > > + * @name: MFD cell name
> > > + * @start: Start address of memory resource
> > > + * @length: Length of memory resource
> > > + * @msix_start: MSIX vector number to start from.
> > > + * @resources: Pointer to resources to add to the cell.
> > > + * @num_resources: Number of resources. The first resource is assumed to
> > > + *                 be memory, all other resources are IRQs.
> > > + */
> > > +static int sel_create_cell(struct pci_dev *pdev,
> > 
> > This is misleading.  It should be sel_register_device.
> 
> I'll rename the function.

Please refrain from responding to comments that you agree with, then
trim the rest.  It will save multiple people a lot of time.

> 
> > 
> > > +                        const char *name,
> > 
> > This never changes.  Why supply it as a variable?
> > 
> > > +                        unsigned int start, +
> > > unsigned int length,
> > 
> > This never changes.  Why supply it as a variable?
> > 
> > > +                        unsigned int msix_start,
> > 
> > This never changes.  Why supply it as a variable?
> > 
> > > +                        struct resource *resources, +
> > > unsigned int num_resources)
> > 
> > This never changes.  Why supply it as a variable?
> 
> This patch only enables ethernet, but we have additional functionality
> that we will enable in subsequent patches, such as serial ports, a
> time component, and a firmware upgrade mechanism. Those components
> have different names, lengths, interrupt counts, etc. If you prefer, I
> can remove this abstraction until we have another cell type to add.

I can only review what I see before me.

Add the extra pieces when/if you upstream the additional functionality.

[...]

> > > +     if (rc != 0) +             return rc; + +     /* Perform CvP
> > > program if necessary */ +     cvp_capable =
> > > pci_find_ext_capability(pdev, CVP_CAPABILITY_ID); +     if
> > > (!skipcvp && cvp_capable) {
> > 
> > The '_' inconsistency is bothering me.
> > 
> > As is the placement of "cvp".
> 
> Would you prefer to see skipcvp called skip_cvp? Or cvp_skip? I'm
> happy to change it.

The latter seems more consistent.

[...]

> > > +             rc = cvp_start(pdev); +             if (rc != 0) { +
> > > dev_err(&pdev->dev, "Error: CvP failed: 0x%x\n", + rc);
> > 
> > This is a user facing error message and I still don't know what CvP
> > is.
> > 
> > Please be more forthcoming.
> 
> CvP is Configuration via Protocol. It's a method for loading firmware
> into Altera FPGAs. If CvP fails, it means we're unable to load
> firmware into the FPGA and the card is basically dead. I can improve
> the error message. Maybe something like, "Loading FPGA firmware
> failed"?

Sounds good.

[...] 

> > 
> > > +                     goto out_disable; +             } +     } +
> > > +     rc = alloc_fn(pdev);
> > 
> > These call-backs are horrible.  Please don't do this.
> 
> Ok, we'll do something else. Would you prefer to pass in a constant
> and use a switch statement to select which function to call?

That sort of thing, yes.

[...]

> > > + +             pci_restore_state(pdev); +     } + +
> > > pci_set_master(pdev); + +     return 0; +} + +static const struct
> > > dev_pm_ops selpvmf_pm_ops = { +
> > > SYSTEM_SLEEP_PM_OPS(selpvmf_suspend, selpvmf_resume) }; + +static
> > > struct pci_device_id selpvmf_pci_tbl[] = { +     { +
> > > PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B1190), +
> > > .driver_data = (kernel_ulong_t)b1190_alloc_mfd_cells,
> > 
> > Pass an identifier here instead of call-back functions.
> 
> Do you have a preference for the type of construct we use? Just a
> #define integer with a switch statement in probe? Something else?

Probably an enum.  Take a look at some other drivers.

> > > +     }, +     { +             PCI_DEVICE(PCI_VENDOR_ID_SEL,
> > > PCI_DEVICE_ID_B2077), +             .driver_data =
> > > (kernel_ulong_t)b2077_alloc_mfd_cells, +     }, +     { +
> > > PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2091), +
> > > .driver_data = (kernel_ulong_t)b2091_alloc_mfd_cells, +     }, +
> > > { +             PCI_DEVICE(PCI_VENDOR_ID_SEL,
> > > PCI_DEVICE_ID_B2093), +             .driver_data =
> > > (kernel_ulong_t)b2093_alloc_mfd_cells, +     }, +     {0,}
> > 
> > What is this?  Where have you seen that before?
> 
> The {0,} is how to signal the end of this array. I don't think the
> comma is required though. Random example:
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/mfd/lpc_sch.c?h=v6.12-rc6#n66

{} is fine.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index a27407950242..4a24b3be8aa5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20730,6 +20730,14 @@  F:	tools/testing/selftests/lsm/
 X:	security/selinux/
 K:	\bsecurity_[a-z_0-9]\+\b
 
+SEL DRIVERS
+M:	Robert Joslyn <robert_joslyn@selinc.com>
+S:	Supported
+B:	mailto:opensource@selinc.com
+F:	drivers/mfd/selpvmf-*
+F:	drivers/platform/x86/sel3350-platform.c
+F:	include/linux/mfd/selpvmf.h
+
 SELINUX SECURITY MODULE
 M:	Paul Moore <paul@paul-moore.com>
 M:	Stephen Smalley <stephen.smalley.work@gmail.com>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f9325bcce1b9..77e2ce3db505 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2402,5 +2402,21 @@  config MFD_RSMU_SPI
 	  Additional drivers must be enabled in order to use the functionality
 	  of the device.
 
+config MFD_SELPVMF
+	tristate "SEL PVMF Support"
+	help
+	  Support for SEL PCI virtual multifunction (PVMF) devcies utilizing
+	  an FPGA based PCIe interface, including:
+	    * SEL-3350
+	    * SEL-3390E4
+	    * SEL-3390T
+
+	  This driver provides common support for accessing the device.
+	  Additional drivers must be enabled in order to use the functionality
+	  of the device.
+
+	  This driver can be built as a module. If built as a module it will be
+	  called "selpvmf".
+
 endmenu
 endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 2a9f91e81af8..198e43ef7a51 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -289,3 +289,6 @@  obj-$(CONFIG_MFD_ATC260X_I2C)	+= atc260x-i2c.o
 
 obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
 obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
+
+selpvmf-objs			:= selpvmf-core.o selpvmf-cvp.o
+obj-$(CONFIG_MFD_SELPVMF)	+= selpvmf.o
diff --git a/drivers/mfd/selpvmf-core.c b/drivers/mfd/selpvmf-core.c
new file mode 100644
index 000000000000..ec3e65fe8064
--- /dev/null
+++ b/drivers/mfd/selpvmf-core.c
@@ -0,0 +1,482 @@ 
+// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
+/*
+ * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
+ * 2350 NE Hopkins Court, Pullman, WA 99163 USA
+ * SEL Open Source <opensource@selinc.com>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/property.h>
+#include <linux/mfd/core.h>
+
+#include "selpvmf-cvp.h"
+
+#define PCI_VENDOR_ID_SEL	0x1aa9
+#define PCI_DEVICE_ID_B1190	0x0014 /* SEL-3390T */
+#define PCI_DEVICE_ID_B2093	0x0015 /* SEL-3350 mainboard */
+#define PCI_DEVICE_ID_B2077	0x0018 /* SEL-3390E4 */
+#define PCI_DEVICE_ID_B2091	0x0019 /* SEL-2241-2 */
+
+#define B1190_NUM_ETHERNET 2
+#define B2077_NUM_ETHERNET 4
+#define B2091_NUM_ETHERNET 5
+#define B2093_NUM_ETHERNET 5
+#define SELETH_NUM_RESOURCES 4
+
+#define SELETH_LEN	0x1000
+
+static bool skipcvp;
+module_param(skipcvp, bool, 0644);
+MODULE_PARM_DESC(skipcvp, "Skip firmware load via CvP.");
+
+/**
+ * sel_create_cell() - Create an MFD cell and add it to the device.
+ * @pdev: The PCI device to operate on
+ * @name: MFD cell name
+ * @start: Start address of memory resource
+ * @length: Length of memory resource
+ * @msix_start: MSIX vector number to start from.
+ * @resources: Pointer to resources to add to the cell.
+ * @num_resources: Number of resources. The first resource is assumed to
+ *                 be memory, all other resources are IRQs.
+ */
+static int sel_create_cell(struct pci_dev *pdev,
+			   const char *name,
+			   unsigned int start,
+			   unsigned int length,
+			   unsigned int msix_start,
+			   struct resource *resources,
+			   unsigned int num_resources)
+{
+	struct mfd_cell cell;
+	unsigned int msix;
+	int rc;
+	int i;
+
+	if (!resources || num_resources < 1)
+		return -EINVAL;
+
+	/* If MSI-X is enabled, assume we're using it and start at the
+	 * provided vector number and iterate through the interrupts
+	 * when adding IRQ resources. Otherwise leave the interrupt
+	 * index at zero for legacy IRQs.
+	 */
+	if (pdev->msix_enabled)
+		msix = msix_start;
+	else
+		msix = 0;
+
+	/* First resource is always a memory resource */
+	resources[0].start = start;
+	resources[0].end = start + length - 1;
+	resources[0].flags = IORESOURCE_MEM;
+
+	/* Any additional resources are IRQs */
+	for (i = 1; i < num_resources; i++) {
+		resources[i].start = pci_irq_vector(pdev, msix);
+		resources[i].end = pci_irq_vector(pdev, msix);
+		resources[i].flags = IORESOURCE_IRQ;
+		if (pdev->msix_enabled)
+			msix++;
+	}
+
+	cell.name = name;
+	cell.num_resources = num_resources;
+	cell.resources = resources;
+
+	rc = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO, &cell,
+			     1, NULL, 0, NULL);
+
+	return rc;
+}
+
+/**
+ * create_eth_cell - Create and add an MFD cell for an selnet component
+ *
+ * @pdev: PCI device
+ * @offset: Offset from BAR 0 of the component
+ * @msix_num: Starting MSI-X vector number
+ */
+static int create_eth_cell(struct pci_dev *pdev,
+			   unsigned int offset,
+			   unsigned int msix_num)
+{
+	struct resource resources[SELETH_NUM_RESOURCES] = {0};
+
+	return sel_create_cell(pdev,
+			       "selpcimac",
+			       pdev->resource[0].start + offset,
+			       SELETH_LEN,
+			       msix_num,
+			       resources,
+			       SELETH_NUM_RESOURCES);
+}
+
+/**
+ * b1190_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
+ *
+ * @pdev: PCI device for the b1190
+ *
+ * +----------------------------------------+
+ * |          | Resources |     Address     |
+ * |  Device  | Mem | IRQ | Start  | Length |
+ * |----------+-----+-----+--------+--------|
+ * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
+ * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
+ * | seltime  |  1  |  1  | 0x3000 | 0x1000 |
+ * | upgrades |  1  |  0  | 0x4000 | 0x4000 |
+ * +----------------------------------------+
+ */
+static int b1190_alloc_mfd_cells(struct pci_dev *pdev)
+{
+	unsigned int offset;
+	int rc;
+	int i;
+
+	/* Ethernet */
+	offset = 0x0000;
+	for (i = 0; i < B1190_NUM_ETHERNET; i++) {
+		rc = create_eth_cell(pdev, offset, i * 3);
+		if (rc)
+			goto out_fail;
+		offset += SELETH_LEN;
+	}
+
+out_fail:
+	return rc;
+}
+
+/**
+ * b2077_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
+ *
+ * @pdev: PCI device for the b2077
+ *
+ * +----------------------------------------+
+ * |          | Resources |     Address     |
+ * |  Device  | Mem | IRQ | Start  | Length |
+ * |----------+-----+-----+--------+--------|
+ * | Ethernet |  1  |  3  | 0x0000 | 0x1000 |
+ * | Ethernet |  1  |  3  | 0x1000 | 0x1000 |
+ * | Ethernet |  1  |  3  | 0x2000 | 0x1000 |
+ * | Ethernet |  1  |  3  | 0x3000 | 0x1000 |
+ * | upgrades |  1  |  0  | 0x8000 | 0x4000 |
+ * | seltime  |  1  |  1  | 0xc000 | 0x1000 |
+ * +----------------------------------------+
+ */
+static int b2077_alloc_mfd_cells(struct pci_dev *pdev)
+{
+	unsigned int offset;
+	int rc;
+	int i;
+
+	/* Ethernet */
+	offset = 0x0000;
+	for (i = 0; i < B2077_NUM_ETHERNET; i++) {
+		rc = create_eth_cell(pdev, offset, i * 3);
+		if (rc)
+			goto out_fail;
+		offset += SELETH_LEN;
+	}
+
+out_fail:
+	return rc;
+}
+
+/*
+ * b2091_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
+ *
+ * @pdev: PCI device for the b2091
+ *
+ * +------------------------------------------+
+ * |            | Resources |     Address     |
+ * |   Device   | Mem | IRQ | Start  | Length |
+ * |------------+-----+-----+--------+--------|
+ * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
+ * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
+ * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
+ * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
+ * +------------------------------------------+
+ */
+static int b2091_alloc_mfd_cells(struct pci_dev *pdev)
+{
+	unsigned int offset;
+	int rc;
+	int i;
+
+	/* Ethernet */
+	offset = 0x0000;
+	for (i = 0; i < B2091_NUM_ETHERNET; i++) {
+		rc = create_eth_cell(pdev, offset, i * 3);
+		if (rc)
+			goto out_fail;
+		offset += SELETH_LEN;
+	}
+
+out_fail:
+	return rc;
+}
+
+/**
+ * b2093_alloc_mfd_cells - Allocate and initialize MFD cell descriptions
+ *
+ * @pdev: PCI device for the b2093
+ *
+ * +------------------------------------------+
+ * |            | Resources |     Address     |
+ * |   Device   | Mem | IRQ | Start  | Length |
+ * |------------+-----+-----+--------+--------|
+ * | Ethernet   |  1  |  3  | 0x0000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x1000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x2000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x3000 | 0x1000 |
+ * | Ethernet   |  1  |  3  | 0x4000 | 0x1000 |
+ * | upgrades   |  1  |  0  | 0x8000 | 0x4000 |
+ * | seltime    |  1  |  1  | 0xc000 | 0x1000 |
+ * | contact IO |  1  |  0  | 0xe000 | 0x1000 |
+ * | multiuart  |  1  |  1  | 0xf000 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf100 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf200 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf300 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf400 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf500 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf600 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf700 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf800 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xf900 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xfa00 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xfb00 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xfc00 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xfd00 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xfe00 | 0x0100 |
+ * | multiuart  |  1  |  1  | 0xff00 | 0x0100 |
+ * +------------------------------------------+
+ */
+static int b2093_alloc_mfd_cells(struct pci_dev *pdev)
+{
+	unsigned int offset;
+	int rc;
+	int i;
+
+	/* Ethernet */
+	offset = 0x0000;
+	for (i = 0; i < B2093_NUM_ETHERNET; i++) {
+		rc = create_eth_cell(pdev, offset, i * 3);
+		if (rc)
+			goto out_fail;
+		offset += SELETH_LEN;
+	}
+
+out_fail:
+	return rc;
+}
+
+/*
+ * selpvmf_probe() - Probe function for the device.
+ *
+ * @pdev: PCI device being probed.
+ * @ent: Entry in the pci_tbl for the device.
+ *
+ * Return: 0 for success, otherwise the appropriate negative error code
+ */
+static int selpvmf_probe(struct pci_dev *pdev, struct pci_device_id const *ent)
+{
+	typedef int (*alloc_fn_t)(struct pci_dev *pdev);
+	alloc_fn_t alloc_fn = (alloc_fn_t)ent->driver_data;
+	u16 cvp_capable;
+	int num_msix;
+	int rc = 0;
+
+	rc = pcim_enable_device(pdev);
+	if (rc != 0)
+		return rc;
+
+	/* Perform CvP program if necessary */
+	cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
+	if (!skipcvp && cvp_capable) {
+		rc = cvp_start(pdev);
+		if (rc != 0) {
+			dev_err(&pdev->dev, "Error: CvP failed: 0x%x\n", rc);
+			goto out_disable;
+		}
+		dev_info(&pdev->dev, "CvP Successful!\n");
+	}
+
+	pci_set_master(pdev);
+
+	/* Only MSI-X and legacy interrupts are supported. Try to get
+	 * MSI-X, if we don't get them all, fall back to legacy.
+	 */
+	num_msix = pci_msix_vec_count(pdev);
+	rc = pci_alloc_irq_vectors(pdev, num_msix, num_msix, PCI_IRQ_MSIX);
+	if (rc != num_msix) {
+		dev_warn(&pdev->dev,
+			 "Failed to get MSI-X, falling back to legacy\n");
+		rc = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
+		if (rc != 1) {
+			rc = -ENOSPC;
+			goto out_disable;
+		}
+	}
+
+	rc = alloc_fn(pdev);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "ERROR: Failed to add child cells\n");
+		goto out_free_irq;
+	}
+
+	dev_info(&pdev->dev, "Successfully added new devices\n");
+	return 0;
+
+out_free_irq:
+	pci_free_irq_vectors(pdev);
+out_disable:
+	pci_clear_master(pdev);
+	return rc;
+}
+
+/*
+ * selpvmf_remove - Remove a selpvmf enabled PCI device from the system
+ *
+ * @pdev: PCI device to be removed
+ */
+static void selpvmf_remove(struct pci_dev *pdev)
+{
+	mfd_remove_devices(&pdev->dev);
+	pci_free_irq_vectors(pdev);
+	pci_clear_master(pdev);
+}
+
+/*
+ * selpvmf_shutdown() - Tears down a selpvmf card.
+ *
+ * @pdev: Pointer to the device structure describing the port.
+ */
+static void selpvmf_shutdown(struct pci_dev *pdev)
+{
+	mfd_remove_devices(&pdev->dev);
+}
+
+/*
+ * selpvmf_suspend - Suspend the device in preparation for entering a
+ * a low power state.
+ *
+ * @dev: Pointer to the device structure describing the port.
+ *
+ * Return: This function always indicates success.
+ *
+ * Most of the work should be handled by the child devices of this
+ * driver. There is no specific action necessary by the card, so when
+ * the child devices are done suspending, just do a generic PCI saving
+ * of state.
+ */
+static int selpvmf_suspend(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	pci_save_state(pdev);
+
+	return 0;
+}
+
+/*
+ * selpvmf_resume() - Resume a selpvmf device that has previously been
+ * suspended
+ *
+ * @dev: Pointer to the device structure describing the port.
+ *
+ * Return: This function always indicates success.
+ */
+static int selpvmf_resume(struct device *dev)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+	u16 cvp_capable;
+	int rc;
+
+	/* Turn the card on by requesting a transition to D0 before
+	 * restoring the PCI device state and enabling the UARTs
+	 */
+	pci_set_power_state(pdev, PCI_D0);
+
+	/* Restore so we can do IOMEM based CvP */
+	pci_restore_state(pdev);
+
+	rc = pcim_enable_device(pdev);
+	if (rc != 0) {
+		/* We are not really supposed to return an error from this
+		 * function, so if the enable fails, just print some info
+		 * to aid in troubleshooting.
+		 */
+		dev_err(&pdev->dev,
+			"Unable to enable the device. Continuing anyway\n");
+	}
+
+	/* Perform CvP program if necessary */
+	cvp_capable = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
+	if (!skipcvp && cvp_capable) {
+		pci_save_state(pdev);
+
+		rc = cvp_start(pdev);
+		if (rc != 0) {
+			dev_err(&pdev->dev,
+				"Error: CvP failed (resume): 0x%x\n", rc);
+		}
+		dev_info(&pdev->dev, "CvP done (resume): 0x%x\n", rc);
+
+		pci_restore_state(pdev);
+	}
+
+	pci_set_master(pdev);
+
+	return 0;
+}
+
+static const struct dev_pm_ops selpvmf_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(selpvmf_suspend, selpvmf_resume)
+};
+
+static struct pci_device_id selpvmf_pci_tbl[] = {
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B1190),
+		.driver_data = (kernel_ulong_t)b1190_alloc_mfd_cells,
+	},
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2077),
+		.driver_data = (kernel_ulong_t)b2077_alloc_mfd_cells,
+	},
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2091),
+		.driver_data = (kernel_ulong_t)b2091_alloc_mfd_cells,
+	},
+	{
+		PCI_DEVICE(PCI_VENDOR_ID_SEL, PCI_DEVICE_ID_B2093),
+		.driver_data = (kernel_ulong_t)b2093_alloc_mfd_cells,
+	},
+	{0,}
+};
+MODULE_DEVICE_TABLE(pci, selpvmf_pci_tbl);
+
+static struct pci_driver selpvmf_driver = {
+	.name		= "selpvmf",
+	.id_table	= selpvmf_pci_tbl,
+	.probe		= selpvmf_probe,
+	.remove		= selpvmf_remove,
+	.shutdown	= selpvmf_shutdown,
+	.driver.pm	= pm_ptr(&selpvmf_pm_ops),
+};
+module_pci_driver(selpvmf_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Schweitzer Engineering Laboratories, Inc.");
+MODULE_DESCRIPTION("Multi function driver for enumerating multiple device drivers for a single PCI resource");
+MODULE_DEVICE_TABLE(pci, selpvmf_pci_tbl);
+MODULE_FIRMWARE("sel/1AA9_0014_01.cvp");
+MODULE_FIRMWARE("sel/1AA9_0015_01.cvp");
+MODULE_FIRMWARE("sel/1AA9_0018_01.cvp");
diff --git a/drivers/mfd/selpvmf-cvp.c b/drivers/mfd/selpvmf-cvp.c
new file mode 100644
index 000000000000..e119cce83d7c
--- /dev/null
+++ b/drivers/mfd/selpvmf-cvp.c
@@ -0,0 +1,431 @@ 
+// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
+/*
+ * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
+ * 2350 NE Hopkins Court, Pullman, WA 99163 USA
+ * SEL Open Source <opensource@selinc.com>
+ *
+ * Support for Configuration via Protocol (CvP) on SEL devices using
+ * Altera FPGAs.
+ */
+
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+
+#include "selpvmf-cvp.h"
+
+#define MIN_WAIT 2 /* number of jiffies for unit wait period */
+#define US_PER_JIFFY  (1000000 / HZ) /* number of microseconds per jiffy */
+#define CVP_WAIT_TIMEOUT 1000000 /* Wait timeout in microseconds */
+#define BYTES_PER_DWORD	4
+#define CORE_FIRMWARE_FILE_BASEDIR "sel/"
+#define CORE_FIRMWARE_FILE_EXT ".cvp"
+
+#define CVP_STATUS_OFFSET	0x0000001C
+#define CVP_CONFIG_READY	BIT(18)
+#define CVP_CONFIG_ERROR	BIT(19)
+#define CVP_EN			BIT(20)
+#define USERMODE		BIT(21)
+#define PLD_CLK_IN_USE		BIT(24)
+
+#define CVP_MODE_CONTROL_OFFSET		0x00000020
+#define CVP_MODE			BIT(0)
+#define HIP_CLK_SEL			BIT(1)
+#define CVP_NUMCLKS_MASK		GENMASK(15, 8)
+#define CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT	BIT(8)
+#define CVP_NUMCLKS_COMPRESSED		BIT(11)
+#define CVP_NUMCLKS_ENCRYPTED		BIT(10)
+
+#define CVP_DATA_OFFSET	0x00000028
+
+#define CVP_PROGRAMMING_CTRL_OFFSET	0x0000002C
+#define CVP_CONFIG			BIT(0)
+#define START_XFER			BIT(1)
+
+#define CVP_UNCORRECTABLE_IE_STATUS_OFFSET	0x00000034
+#define CVP_CONFIG_ERROR_LATCHED		BIT(5)
+
+struct cvp_state {
+	int offset;
+	struct firmware const *fw;
+	struct pci_dev *pdev;
+	uint32_t __iomem *bar;
+};
+
+/**
+ * cvp_set_bit() - Sets the bit requested to 1
+ *
+ * @state: Initialized CvP State structure
+ * @offset: The pci capability offset to read the value from
+ * @bit: The bit to read at the pci config space offset
+ */
+static void cvp_set_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
+{
+	uint32_t reg;
+
+	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
+	reg |= bit;
+	pci_write_config_dword(state->pdev, state->offset + offset, reg);
+}
+
+/**
+ * cvp_clear_bit() - Sets the bit requested to 0
+ *
+ * @state: Initialized CvP State structure
+ * @offset: The pci capability offset to read the value from
+ * @bit: The bit to read at the pci config space offset
+ */
+static void cvp_clear_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
+{
+	uint32_t reg;
+
+	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
+	reg &= ~(bit);
+	pci_write_config_dword(state->pdev, state->offset + offset, reg);
+}
+
+/**
+ * cvp_read_bit() - Reads the requested bit to determine its state
+ *
+ * @state: Initialized CvP State structure
+ * @offset: The pci capability offset to read the value from
+ * @bit: The bit to read at the pci config space offset
+ *
+ * Return: 1 if the bit is set, 0 if not
+ */
+static int cvp_read_bit(struct cvp_state *state, uint32_t offset, uint32_t bit)
+{
+	uint32_t reg;
+
+	pci_read_config_dword(state->pdev, state->offset + offset, &reg);
+	return ((reg & bit) == 0) ? 0 : 1;
+}
+
+/**
+ * cvp_wait_for_bit() - Reads the requested bit and spins until it
+ * becomes the value requested
+ *
+ * This request will also timeout at the ns value requested by the caller
+ *
+ * @state: Initialized CvP State structure
+ * @offset: The pci capability offset to read the value from
+ * @bit: The bit to read at the pci config space offset
+ * @value: The value the bit needs to be set to in order to return
+ *
+ * Return: 1 if the bit_val became the value requested during the timeout
+ * period. 0 if a timeout occurred.
+ */
+static int cvp_wait_for_bit(struct cvp_state *state,
+			    uint32_t offset,
+			    uint32_t bit,
+			    uint32_t value)
+{
+	uint8_t bit_val;
+	uint32_t n_wait_loops = 1;
+
+	if (CVP_WAIT_TIMEOUT > (US_PER_JIFFY * MIN_WAIT)) {
+		n_wait_loops = (CVP_WAIT_TIMEOUT + (US_PER_JIFFY * MIN_WAIT) - 1) /
+			       (US_PER_JIFFY * MIN_WAIT);
+	}
+
+	DECLARE_WAIT_QUEUE_HEAD(cvp_wq);
+
+	bit_val = cvp_read_bit(state, offset, bit);
+
+	while ((bit_val != value) && (n_wait_loops != 0)) {
+		wait_event_timeout(cvp_wq, 0, MIN_WAIT);
+		bit_val = cvp_read_bit(state, offset, bit);
+		--n_wait_loops;
+	}
+
+	return bit_val == value;
+}
+
+/**
+ * perform_244_dummy_writes() - Performs 244 dummy writes to the hard IP
+ * to clear the CvP state machine.
+ *
+ * @state: Initialized CvP State structure
+ */
+static void perform_244_dummy_writes(struct cvp_state *state)
+{
+	for (int i = 0; i < 244; i++)
+		iowrite32(0xFFFFFFFF, state->bar);
+}
+
+/**
+ * set_numclks() - Updates the CVP_NUMCLKS value in the mode control register
+ *
+ * @state: Initialized CvP State structure
+ * @value: The value to set CVP_NUMCLKS to
+ */
+static void set_numclks(struct cvp_state *state, uint32_t value)
+{
+	uint32_t reg;
+
+	pci_read_config_dword(state->pdev,
+			      state->offset + CVP_MODE_CONTROL_OFFSET,
+			      &reg);
+	reg &= ~(CVP_NUMCLKS_MASK);
+	reg |= (value & CVP_NUMCLKS_MASK);
+	pci_write_config_dword(state->pdev,
+			       state->offset + CVP_MODE_CONTROL_OFFSET,
+			       reg);
+}
+
+/**
+ * cvp_teardown() - Removes the PCI device from its ready to program state
+ *
+ * @state: Initialized CvP State structure
+ *
+ * Return: 0 for success, negative error code if not
+ */
+static int cvp_teardown(struct cvp_state *state)
+{
+	int status = 0;
+	uint32_t bit_val;
+
+	cvp_clear_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, START_XFER);
+	cvp_clear_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, CVP_CONFIG);
+	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
+	perform_244_dummy_writes(state);
+
+	if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET, CVP_CONFIG_READY, 0)) {
+		status = -1;
+		goto exit;
+	}
+
+	bit_val = cvp_read_bit(state,
+			       CVP_UNCORRECTABLE_IE_STATUS_OFFSET,
+			       CVP_CONFIG_ERROR_LATCHED);
+
+	if (bit_val == 1) {
+		cvp_set_bit(state,
+			    CVP_UNCORRECTABLE_IE_STATUS_OFFSET,
+			    CVP_CONFIG_ERROR_LATCHED);
+	}
+
+	cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
+	cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
+
+	if (bit_val == 0) {
+		if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET,
+				      PLD_CLK_IN_USE, 1) ||
+		    !cvp_wait_for_bit(state, CVP_STATUS_OFFSET, USERMODE, 1)) {
+			status = -1;
+		}
+	}
+
+exit:
+	return status;
+}
+
+/**
+ * cvp_program() - Performs the FPGA programming via CvP
+ *
+ * @state: Initialized CvP State structure
+ *
+ * Return: 0 for success, negative error code if not
+ */
+static int cvp_program(struct cvp_state *state)
+{
+	int status = 0;
+	int i;
+
+	uint32_t *firmware_ptr;
+	uint32_t dword_size;
+
+	set_numclks(state, CVP_NUMCLKS_COMPRESSED);
+
+	/*
+	 * The file_read_data function returns a size in bytes,
+	 * cvp_set_reg requires a write of a single dword. Therefore
+	 * calculate a dword size.
+	 */
+	dword_size = (state->fw->size / BYTES_PER_DWORD);
+	if ((state->fw->size % BYTES_PER_DWORD) != 0)
+		dword_size++;
+
+	firmware_ptr = (uint32_t *)state->fw->data;
+	for (i = 0; i < dword_size; i++)
+		iowrite32(firmware_ptr[i], state->bar);
+
+	/* Check if any errors happened during the CvP process */
+	if (cvp_read_bit(state,	CVP_STATUS_OFFSET, CVP_CONFIG_ERROR) == 1)
+		status = -1;
+
+	return status;
+}
+
+/**
+ * cvp_setup() - Performs the CvP setup logic readying the pci device for
+ * programming
+ *
+ * @state: Initialized CvP State structure
+ *
+ * Return: 0 for success, negative error code if not
+ */
+static int cvp_setup(struct cvp_state *state)
+{
+	int status = 0;
+
+	/*
+	 * Make sure the hardware isn't in a state where a previous CvP
+	 * failed. If it did, run teardown to put it back in a pristine
+	 * state.
+	 */
+	if (cvp_read_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE) != 0)
+		cvp_teardown(state);
+
+	cvp_set_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
+	cvp_set_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
+	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
+	perform_244_dummy_writes(state);
+	cvp_set_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, CVP_CONFIG);
+	if (!cvp_wait_for_bit(state, CVP_STATUS_OFFSET, CVP_CONFIG_READY, 1)) {
+		status = -1;
+		/*
+		 * This may fail, but try to put it back.  Note that we
+		 * aren't waiting for clocks to stabilize after doing
+		 * this, so the device isn't going to be usable.  We are
+		 * assuming the driver is going to bail.
+		 */
+		cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, CVP_MODE);
+		cvp_clear_bit(state, CVP_MODE_CONTROL_OFFSET, HIP_CLK_SEL);
+		goto exit;
+	}
+
+	set_numclks(state, CVP_NUMCLKS_UNCMPRSSD_UNENCRYPT);
+	perform_244_dummy_writes(state);
+	cvp_set_bit(state, CVP_PROGRAMMING_CTRL_OFFSET, START_XFER);
+
+exit:
+	return status;
+}
+
+/**
+ * cvp_init() - Initialize the CvP state machine.
+ *
+ * @pdev: Pointer to the pci_device structure containing information
+ *        about the device being initialized
+ * @state: Caller allocated CvP state structure to be initialized.
+ *
+ * Return: 0 if the state was initialized successfully and CvP can be
+ *    executed.  A non-zero exit code will indicate an appropriate error.
+ *
+ *    If the function returns 0, the caller should call cvp_exit to
+ *    cleanup.
+ */
+static int cvp_init(struct pci_dev *pdev, struct cvp_state *state)
+{
+	int status = 0;
+	int value;
+	char filepath[512];
+
+	state->pdev = pdev;
+	state->fw = NULL;
+	state->bar = NULL;
+
+	state->offset = pci_find_ext_capability(pdev, CVP_CAPABILITY_ID);
+	if (!state->offset) {
+		dev_warn(&pdev->dev,
+			 "Device requests CvP but indicating not capable\n");
+		status = -ENODEV;
+		goto exit;
+	}
+
+	value = cvp_read_bit(state, CVP_STATUS_OFFSET, CVP_EN);
+	if (value == 0) {
+		dev_err(&pdev->dev, "CvP not enabled in device\n");
+		status = -ENODEV;
+		goto exit;
+	}
+
+	/*
+	 * BAR 0 will always exist and will always be mmio so we can do
+	 * memory transactions to speed up CvP. Make sure to not map
+	 * more than our first resource. CvP will write to only the
+	 * first address that is mapped so a mapping size of 4 bytes
+	 * should do.
+	 */
+	state->bar = pci_iomap(pdev, 0, 4);
+	if (!state->bar) {
+		dev_err(&pdev->dev, "Memory BAR not available\n");
+		status = -ENODEV;
+		goto exit;
+	}
+
+	/* Create the filepath to request firmware from */
+	snprintf(filepath,
+		 512,
+		 "%s%04X_%04X_%02x%s",
+		 CORE_FIRMWARE_FILE_BASEDIR,
+		 pdev->vendor,
+		 pdev->device,
+		 pdev->revision,
+		 CORE_FIRMWARE_FILE_EXT);
+
+	if (request_firmware(&state->fw, filepath, &pdev->dev)) {
+		dev_err(&pdev->dev,
+			"Error: The driver failed to find a core image for this device at: %s\n",
+			filepath);
+		iounmap(state->bar);
+		state->bar = NULL;
+		status = -ENOENT;
+	}
+
+exit:
+	return status;
+}
+
+/**
+ * cvp_exit() - Cleanup CvP resources
+ *
+ * @state: Initialized CvP state to cleanup.
+ */
+static void cvp_exit(struct cvp_state *state)
+{
+	if (state->fw)
+		release_firmware(state->fw);
+	if (state->bar) {
+		iounmap(state->bar);
+		state->bar = NULL;
+	}
+}
+
+/**
+ * cvp_start() - Public facing function to kick off FPGA programming via CvP
+ *
+ * @pdev: Pointer to the pci_device structure containing information about the
+ *        device being initialized
+ *
+ * Return: 0 for success, otherwise the appropriate negative error code
+ */
+int cvp_start(struct pci_dev *pdev)
+{
+	int status = 0;
+	struct cvp_state state;
+
+	dev_info(&pdev->dev, "Loading device firmware. AER errors may occur during the loading process.\n");
+
+	status = cvp_init(pdev, &state);
+	if (status != 0)
+		goto exit;
+
+	status = cvp_setup(&state);
+	if (status != 0)
+		goto exit;
+
+	/*
+	 * If setup succeeded we need to perform the cvp teardown
+	 * regardless of whether or not the program was successful
+	 */
+	if (cvp_program(&state) != 0)
+		status = -1;
+	if (cvp_teardown(&state) != 0)
+		status = -1;
+
+exit:
+	cvp_exit(&state);
+	return status;
+}
diff --git a/drivers/mfd/selpvmf-cvp.h b/drivers/mfd/selpvmf-cvp.h
new file mode 100644
index 000000000000..32de3b9e9884
--- /dev/null
+++ b/drivers/mfd/selpvmf-cvp.h
@@ -0,0 +1,18 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */
+/*
+ * Copyright 2017 Schweitzer Engineering Laboratories, Inc.
+ * 2350 NE Hopkins Court, Pullman, WA 99163 USA
+ * SEL Open Source <opensource@selinc.com>
+ */
+
+#ifndef _SELPVMF_CVP_H
+#define _SELPVMF_CVP_H
+
+#include <linux/pci.h>
+
+/* Define for CvP in PCI config space */
+#define CVP_CAPABILITY_ID	0x0000000B
+
+int cvp_start(struct pci_dev *pdev);
+
+#endif /* _SELPVMF_CVP_H */