Commit d60bc140 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'pwrseq-updates-for-v7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux

Pull power sequencing updates from Bartosz Golaszewski:
 "For this release we have an extension of the pwrseq-pcie-m2 driver
  with support for PCIe M.2 Key E connectors.

  The rest of the commits fulfill a supporting role: document the
  hardware in DT bindings, provide required serdev helpers (this has
  been provided in an immutable branch to Rob Herring so you may see it
  in his PR as well) and is followed up by some Kconfig fixes from Arnd.

  Summary:

   - add support for the PCIe M.2 Key E connectors in pwrseq-pcie-m2

   - describe PCIe M.2 Mechanical Key E connectors in DT bindings

   - add serdev helpers for looking up devices by OF nodes

   - minor serdev core rework to enable support for PCIe M.2 Key E
     connectors"

* tag 'pwrseq-updates-for-v7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux:
  power: sequencing: pcie-m2: add SERIAL_DEV_BUS dependency
  power: sequencing: pcie-m2: enforce PCI and OF dependencies
  power: sequencing: pcie-m2: Create serdev device for WCN7850 bluetooth
  power: sequencing: pcie-m2: Add support for PCIe M.2 Key E connectors
  dt-bindings: connector: Add PCIe M.2 Mechanical Key E connector
  dt-bindings: serial: Document the graph port
  serdev: Do not return -ENODEV from of_serdev_register_devices() if external connector is used
  serdev: Add an API to find the serdev controller associated with the devicetree node
  serdev: Convert to_serdev_*() helpers to macros and use container_of_const()
parents 1334d2a3 b4464d8f
Loading
Loading
Loading
Loading
+184 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/connector/pcie-m2-e-connector.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#

title: PCIe M.2 Mechanical Key E Connector

maintainers:
  - Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>

description:
  A PCIe M.2 E connector node represents a physical PCIe M.2 Mechanical Key E
  connector. Mechanical Key E connectors are used to connect Wireless
  Connectivity devices including combinations of Wi-Fi, BT, NFC to the host
  machine over interfaces like PCIe/SDIO, USB/UART+PCM, and I2C.

properties:
  compatible:
    const: pcie-m2-e-connector

  vpcie3v3-supply:
    description: A phandle to the regulator for 3.3v supply.

  vpcie1v8-supply:
    description: A phandle to the regulator for VIO 1.8v supply.

  i2c-parent:
    $ref: /schemas/types.yaml#/definitions/phandle
    description: I2C interface

  clocks:
    description: 32.768 KHz Suspend Clock (SUSCLK) input from the host system to
      the M.2 card. Refer, PCI Express M.2 Specification r4.0, sec 3.1.12.1 for
      more details.
    maxItems: 1

  w-disable1-gpios:
    description: GPIO output to W_DISABLE1# signal. This signal is used by the
      host system to disable WiFi radio in the M.2 card. Refer, PCI Express M.2
      Specification r4.0, sec 3.1.12.3 for more details.
    maxItems: 1

  w-disable2-gpios:
    description: GPIO output to W_DISABLE2# signal. This signal is used by the
      host system to disable BT radio in the M.2 card. Refer, PCI Express M.2
      Specification r4.0, sec 3.1.12.3 for more details.
    maxItems: 1

  viocfg-gpios:
    description: GPIO input to IO voltage configuration (VIO_CFG) signal. The
      card drives this signal to indicate to the host system whether the card
      supports an independent IO voltage domain for sideband signals. Refer,
      PCI Express M.2 Specification r4.0, sec 3.1.15.1 for more details.
    maxItems: 1

  uart-wake-gpios:
    description: GPIO input to UART_WAKE# signal. The card asserts this signal
      to wake the host system and initiate UART interface communication. Refer,
      PCI Express M.2 Specification r4.0, sec 3.1.8.1 for more details.
    maxItems: 1

  sdio-wake-gpios:
    description: GPIO input to SDIO_WAKE# signal. The card asserts this signal
      to wake the host system and initiate SDIO interface communication. Refer,
      PCI Express M.2 Specification r4.0, sec 3.1.7 for more details.
    maxItems: 1

  sdio-reset-gpios:
    description: GPIO output to SDIO_RESET# signal. This signal is used by the
      host system to reset SDIO interface of the M.2 card. Refer, PCI Express
      M.2 Specification r4.0, sec 3.1.7 for more details.
    maxItems: 1

  vendor-porta-gpios:
    description: GPIO for the first vendor specific signal (VENDOR_PORTA). This
      signal's functionality is defined by the card manufacturer and may be
      used for proprietary features. Refer the card vendor's documentation for
      details.
    maxItems: 1

  vendor-portb-gpios:
    description: GPIO for the second vendor specific signal (VENDOR_PORTB). This
      signal's functionality is defined by the card manufacturer and may be
      used for proprietary features. Refer the card vendor's documentation for
      details.
    maxItems: 1

  vendor-portc-gpios:
    description: GPIO for the third vendor specific signal (VENDOR_PORTC). This
      signal's functionality is defined by the card manufacturer and may be
      used for proprietary features. Refer the card vendor's documentation for
      details.
    maxItems: 1

  ports:
    $ref: /schemas/graph.yaml#/properties/ports
    description: OF graph bindings modeling the interfaces exposed on the
      connector. Since a single connector can have multiple interfaces, every
      interface has an assigned OF graph port number as described below.

    properties:
      port@0:
        $ref: /schemas/graph.yaml#/properties/port
        description: PCIe interface for Wi-Fi

      port@1:
        $ref: /schemas/graph.yaml#/properties/port
        description: SDIO interface for Wi-Fi

      port@2:
        $ref: /schemas/graph.yaml#/properties/port
        description: USB 2.0 interface for BT

      port@3:
        $ref: /schemas/graph.yaml#/properties/port
        description: UART interface for BT

      port@4:
        $ref: /schemas/graph.yaml#/properties/port
        description: PCM/I2S interface

    anyOf:
      - anyOf:
          - required:
              - port@0
          - required:
              - port@1
      - anyOf:
          - required:
              - port@2
          - required:
              - port@3

required:
  - compatible
  - vpcie3v3-supply

additionalProperties: false

examples:
  # PCI M.2 Key E connector for Wi-Fi/BT with PCIe/UART interfaces
  - |
    #include <dt-bindings/gpio/gpio.h>

    connector {
        compatible = "pcie-m2-e-connector";
        vpcie3v3-supply = <&vreg_wcn_3p3>;
        vpcie1v8-supply = <&vreg_l15b_1p8>;
        i2c-parent = <&i2c0>;
        w-disable1-gpios = <&tlmm 115 GPIO_ACTIVE_LOW>;
        w-disable2-gpios = <&tlmm 116 GPIO_ACTIVE_LOW>;
        viocfg-gpios = <&tlmm 117 GPIO_ACTIVE_HIGH>;
        uart-wake-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>;
        sdio-wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>;
        sdio-reset-gpios = <&tlmm 120 GPIO_ACTIVE_LOW>;

        ports {
            #address-cells = <1>;
            #size-cells = <0>;

            port@0 {
                reg = <0>;
                #address-cells = <1>;
                #size-cells = <0>;

                endpoint@0 {
                    reg = <0>;
                    remote-endpoint = <&pcie4_port0_ep>;
                };
            };

            port@3 {
                reg = <3>;
                #address-cells = <1>;
                #size-cells = <0>;

                endpoint@0 {
                    reg = <0>;
                    remote-endpoint = <&uart14_ep>;
                };
            };
        };
    };
+3 −0
Original line number Diff line number Diff line
@@ -87,6 +87,9 @@ properties:
    description:
      TX FIFO threshold configuration (in bytes).

  port:
    $ref: /schemas/graph.yaml#/properties/port

patternProperties:
  "^(bluetooth|bluetooth-gnss|embedded-controller|gnss|gps|mcu|onewire)$":
    if:
+1 −0
Original line number Diff line number Diff line
@@ -21064,6 +21064,7 @@ PCIE M.2 POWER SEQUENCING
M:	Manivannan Sadhasivam <mani@kernel.org>
L:	linux-pci@vger.kernel.org
S:	Maintained
F:	Documentation/devicetree/bindings/connector/pcie-m2-e-connector.yaml
F:	Documentation/devicetree/bindings/connector/pcie-m2-m-connector.yaml
F:	drivers/power/sequencing/pwrseq-pcie-m2.c
+4 −1
Original line number Diff line number Diff line
@@ -37,7 +37,10 @@ config POWER_SEQUENCING_TH1520_GPU

config POWER_SEQUENCING_PCIE_M2
	tristate "PCIe M.2 connector power sequencing driver"
	depends on OF || COMPILE_TEST
	depends on OF
	depends on PCI
	depends on SERIAL_DEV_BUS
	select OF_DYNAMIC
	help
	  Say Y here to enable the power sequencing driver for PCIe M.2
	  connectors. This driver handles the power sequencing for the M.2
+332 −14
Original line number Diff line number Diff line
@@ -5,13 +5,18 @@
 */

#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pwrseq/provider.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include <linux/slab.h>

struct pwrseq_pcie_m2_pdata {
@@ -25,16 +30,21 @@ struct pwrseq_pcie_m2_ctx {
	struct regulator_bulk_data *regs;
	size_t num_vregs;
	struct notifier_block nb;
	struct gpio_desc *w_disable1_gpio;
	struct gpio_desc *w_disable2_gpio;
	struct serdev_device *serdev;
	struct of_changeset *ocs;
	struct device *dev;
};

static int pwrseq_pcie_m2_m_vregs_enable(struct pwrseq_device *pwrseq)
static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

	return regulator_bulk_enable(ctx->num_vregs, ctx->regs);
}

static int pwrseq_pcie_m2_m_vregs_disable(struct pwrseq_device *pwrseq)
static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

@@ -43,18 +53,84 @@ static int pwrseq_pcie_m2_m_vregs_disable(struct pwrseq_device *pwrseq)

static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = {
	.name = "regulators-enable",
	.enable = pwrseq_pcie_m2_m_vregs_enable,
	.disable = pwrseq_pcie_m2_m_vregs_disable,
	.enable = pwrseq_pcie_m2_vregs_enable,
	.disable = pwrseq_pcie_m2_vregs_disable,
};

static const struct pwrseq_unit_data *pwrseq_pcie_m2_m_unit_deps[] = {
static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = {
	&pwrseq_pcie_m2_vregs_unit_data,
	NULL
};

static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

	return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0);
}

static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

	return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1);
}

static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = {
	.name = "uart-enable",
	.deps = pwrseq_pcie_m2_unit_deps,
	.enable = pwrseq_pci_m2_e_uart_enable,
	.disable = pwrseq_pci_m2_e_uart_disable,
};

static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

	return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0);
}

static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq)
{
	struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);

	return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1);
}

static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = {
	.name = "pcie-enable",
	.deps = pwrseq_pcie_m2_unit_deps,
	.enable = pwrseq_pci_m2_e_pcie_enable,
	.disable = pwrseq_pci_m2_e_pcie_disable,
};

static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = {
	.name = "pcie-enable",
	.deps = pwrseq_pcie_m2_m_unit_deps,
	.deps = pwrseq_pcie_m2_unit_deps,
};

static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq)
{
	/*
	 * FIXME: This delay is only required for some Qcom WLAN/BT cards like
	 * WCN7850 and not for all devices. But currently, there is no way to
	 * identify the device model before enumeration.
	 */
	msleep(50);

	return 0;
}

static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = {
	.name = "uart",
	.unit = &pwrseq_pcie_m2_e_uart_unit_data,
	.post_enable = pwrseq_pcie_m2_e_pwup_delay,
};

static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = {
	.name = "pcie",
	.unit = &pwrseq_pcie_m2_e_pcie_unit_data,
	.post_enable = pwrseq_pcie_m2_e_pwup_delay,
};

static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = {
@@ -62,11 +138,21 @@ static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = {
	.unit = &pwrseq_pcie_m2_m_pcie_unit_data,
};

static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = {
	&pwrseq_pcie_m2_e_pcie_target_data,
	&pwrseq_pcie_m2_e_uart_target_data,
	NULL
};

static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = {
	&pwrseq_pcie_m2_m_pcie_target_data,
	NULL
};

static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = {
	.targets = pwrseq_pcie_m2_e_targets,
};

static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = {
	.targets = pwrseq_pcie_m2_m_targets,
};
@@ -91,11 +177,202 @@ static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq,
	return PWRSEQ_NO_MATCH;
}

static void pwrseq_pcie_m2_free_regulators(void *data)
static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx,
					struct device_node *parent)
{
	struct pwrseq_pcie_m2_ctx *ctx = data;
	struct device *dev = ctx->dev;
	struct device_node *np;
	int ret;

	regulator_bulk_free(ctx->num_vregs, ctx->regs);
	ctx->ocs = kzalloc_obj(*ctx->ocs);
	if (!ctx->ocs)
		return -ENOMEM;

	of_changeset_init(ctx->ocs);

	np = of_changeset_create_node(ctx->ocs, parent, "bluetooth");
	if (!np) {
		dev_err(dev, "Failed to create bluetooth node\n");
		ret = -ENODEV;
		goto err_destroy_changeset;
	}

	ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt");
	if (ret) {
		dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret);
		goto err_destroy_changeset;
	}

	ret = of_changeset_apply(ctx->ocs);
	if (ret) {
		dev_err(dev, "Failed to apply changeset: %d\n", ret);
		goto err_destroy_changeset;
	}

	ret = device_add_of_node(&ctx->serdev->dev, np);
	if (ret) {
		dev_err(dev, "Failed to add OF node: %d\n", ret);
		goto err_revert_changeset;
	}

	return 0;

err_revert_changeset:
	of_changeset_revert(ctx->ocs);
err_destroy_changeset:
	of_changeset_destroy(ctx->ocs);
	kfree(ctx->ocs);
	ctx->ocs = NULL;

	return ret;
}

static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
{
	struct serdev_controller *serdev_ctrl;
	struct device *dev = ctx->dev;
	int ret;

	struct device_node *serdev_parent __free(device_node) =
		of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0);
	if (!serdev_parent)
		return 0;

	serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent);
	if (!serdev_ctrl)
		return 0;

	/* Bail out if the device was already attached to this controller */
	if (serdev_ctrl->serdev) {
		serdev_controller_put(serdev_ctrl);
		return 0;
	}

	ctx->serdev = serdev_device_alloc(serdev_ctrl);
	if (!ctx->serdev) {
		ret = -ENOMEM;
		goto err_put_ctrl;
	}

	ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent);
	if (ret)
		goto err_free_serdev;

	ret = serdev_device_add(ctx->serdev);
	if (ret) {
		dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret);
		goto err_free_dt_node;
	}

	serdev_controller_put(serdev_ctrl);

	return 0;

err_free_dt_node:
	device_remove_of_node(&ctx->serdev->dev);
	of_changeset_revert(ctx->ocs);
	of_changeset_destroy(ctx->ocs);
	kfree(ctx->ocs);
	ctx->ocs = NULL;
err_free_serdev:
	serdev_device_put(ctx->serdev);
	ctx->serdev = NULL;
err_put_ctrl:
	serdev_controller_put(serdev_ctrl);

	return ret;
}

static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx)
{
	if (ctx->serdev) {
		device_remove_of_node(&ctx->serdev->dev);
		serdev_device_remove(ctx->serdev);
		ctx->serdev = NULL;
	}

	if (ctx->ocs) {
		of_changeset_revert(ctx->ocs);
		of_changeset_destroy(ctx->ocs);
		kfree(ctx->ocs);
		ctx->ocs = NULL;
	}
}

static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action,
			      void *data)
{
	struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb);
	struct pci_dev *pdev = to_pci_dev(data);
	int ret;

	/*
	 * Check whether the PCI device is associated with this M.2 connector or
	 * not, by comparing the OF node of the PCI device parent and the Port 0
	 * (PCIe) remote node parent OF node.
	 */
	struct device_node *pci_parent __free(device_node) =
			of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
	if (!pci_parent || (pci_parent != pdev->dev.parent->of_node))
		return NOTIFY_DONE;

	switch (action) {
	case BUS_NOTIFY_ADD_DEVICE:
		/* Create serdev device for WCN7850 */
		if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) {
			ret = pwrseq_pcie_m2_create_serdev(ctx);
			if (ret)
				return notifier_from_errno(ret);
		}
		break;
	case BUS_NOTIFY_REMOVED_DEVICE:
		/* Destroy serdev device for WCN7850 */
		if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107)
			pwrseq_pcie_m2_remove_serdev(ctx);

		break;
	}

	return NOTIFY_OK;
}

static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint,
					     const char *node)
{
	struct device_node *remote __free(device_node) =
			of_graph_get_remote_node(dev_of_node(dev), port, endpoint);

	if (remote && of_node_name_eq(remote, node))
		return true;

	return false;
}

/*
 * If the connector exposes a non-discoverable bus like UART, the respective
 * protocol device needs to be created manually with the help of the notifier
 * of the discoverable bus like PCIe.
 */
static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev)
{
	int ret;

	/*
	 * Register a PCI notifier for Key E connector that has PCIe as Port
	 * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface.
	 */
	if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) {
		if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) {
			ctx->dev = dev;
			ctx->nb.notifier_call = pwrseq_m2_pcie_notify;
			ret = bus_register_notifier(&pci_bus_type, &ctx->nb);
			if (ret)
				return dev_err_probe(dev, ret,
						     "Failed to register notifier for serdev\n");
		}
	}

	return 0;
}

static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
@@ -109,6 +386,7 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
	if (!ctx)
		return -ENOMEM;

	platform_set_drvdata(pdev, ctx);
	ctx->of_node = dev_of_node(dev);
	ctx->pdata = device_get_match_data(dev);
	if (!ctx->pdata)
@@ -127,9 +405,19 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)

	ctx->num_vregs = ret;

	ret = devm_add_action_or_reset(dev, pwrseq_pcie_m2_free_regulators, ctx);
	if (ret)
		return ret;
	ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->w_disable1_gpio)) {
		ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio),
				     "Failed to get the W_DISABLE_1# GPIO\n");
		goto err_free_regulators;
	}

	ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH);
	if (IS_ERR(ctx->w_disable2_gpio)) {
		ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio),
				     "Failed to get the W_DISABLE_2# GPIO\n");
		goto err_free_regulators;
	}

	config.parent = dev;
	config.owner = THIS_MODULE;
@@ -138,11 +426,36 @@ static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
	config.targets = ctx->pdata->targets;

	ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
	if (IS_ERR(ctx->pwrseq))
		return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
	if (IS_ERR(ctx->pwrseq)) {
		ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
				     "Failed to register the power sequencer\n");
		goto err_free_regulators;
	}

	/*
	 * Register a notifier for creating protocol devices for
	 * non-discoverable busses like UART.
	 */
	ret = pwrseq_pcie_m2_register_notifier(ctx, dev);
	if (ret)
		goto err_free_regulators;

	return 0;

err_free_regulators:
	regulator_bulk_free(ctx->num_vregs, ctx->regs);

	return ret;
}

static void pwrseq_pcie_m2_remove(struct platform_device *pdev)
{
	struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev);

	bus_unregister_notifier(&pci_bus_type, &ctx->nb);
	pwrseq_pcie_m2_remove_serdev(ctx);

	regulator_bulk_free(ctx->num_vregs, ctx->regs);
}

static const struct of_device_id pwrseq_pcie_m2_of_match[] = {
@@ -150,6 +463,10 @@ static const struct of_device_id pwrseq_pcie_m2_of_match[] = {
		.compatible = "pcie-m2-m-connector",
		.data = &pwrseq_pcie_m2_m_of_data,
	},
	{
		.compatible = "pcie-m2-e-connector",
		.data = &pwrseq_pcie_m2_e_of_data,
	},
	{ }
};
MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match);
@@ -160,6 +477,7 @@ static struct platform_driver pwrseq_pcie_m2_driver = {
		.of_match_table = pwrseq_pcie_m2_of_match,
	},
	.probe = pwrseq_pcie_m2_probe,
	.remove = pwrseq_pcie_m2_remove,
};
module_platform_driver(pwrseq_pcie_m2_driver);

Loading