]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
platform/mellanox: mlxreg-dpu: Add initial support for Nvidia DPU
authorVadim Pasternak <vadimp@nvidia.com>
Mon, 21 Apr 2025 09:20:46 +0000 (12:20 +0300)
committerIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Wed, 30 Apr 2025 11:05:49 +0000 (14:05 +0300)
Provide platform support for Nvidia (DPU) Data Processor Unit for the
Smart Switch SN4280.

The Smart Switch equipped with:
- Nvidia COME module based on AMD EPYC™ Embedded 3451 CPU.
- Nvidia Spectrum-3 ASIC.
- Four DPUs, each equipped with Nvidia BF3 ARM based processor and
  with Lattice LFD2NX-40 FPGA device.
- 28xQSFP-DD external ports.
- Two power supplies.
- Four cooling drawers.

Driver provides support for the platform management and monitoring
of DPU components. It includes support for: health events, resets and
boot progress indications logic, implemented by FPGA device.

Reviewed-by: Ciju Rajan K <crajank@nvidia.com>
Signed-off-by: Vadim Pasternak <vadimp@nvidia.com>
[ij: added depends on I2C]
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://lore.kernel.org/r/20250421092051.7687-2-vadimp@nvidia.com
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
drivers/platform/mellanox/Kconfig
drivers/platform/mellanox/Makefile
drivers/platform/mellanox/mlxreg-dpu.c [new file with mode: 0644]

index aa760f064a17c6e2f14338cc6e2ccbf4cb3ba5ff..e3afbe62c7f6fe68a5c1bfc7aa64323083c874fe 100644 (file)
@@ -27,6 +27,19 @@ config MLX_PLATFORM
 
          If you have a Mellanox system, say Y or M here.
 
+config MLXREG_DPU
+       tristate "Nvidia Data Processor Unit platform driver support"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         This driver provides support for the Nvidia BF3 Data Processor Units,
+         which are the part of SN4280 Ethernet smart switch systems
+         providing a high performance switching solution for Enterprise Data
+         Centers (EDC) for building Ethernet based clusters, High-Performance
+         Computing (HPC) and embedded environments.
+
+         If you have a Nvidia smart switch system, say Y or M here.
+
 config MLXREG_HOTPLUG
        tristate "Mellanox platform hotplug driver support"
        depends on HWMON
index ba56485cbe8ce44952f1aa3f00cc20ede2dad9bc..e86723b44c2edb95abd158c3928249a16a4f22d3 100644 (file)
@@ -7,6 +7,7 @@ obj-$(CONFIG_MLX_PLATFORM)      += mlx-platform.o
 obj-$(CONFIG_MLXBF_BOOTCTL)    += mlxbf-bootctl.o
 obj-$(CONFIG_MLXBF_PMC)                += mlxbf-pmc.o
 obj-$(CONFIG_MLXBF_TMFIFO)     += mlxbf-tmfifo.o
+obj-$(CONFIG_MLXREG_DPU)       += mlxreg-dpu.o
 obj-$(CONFIG_MLXREG_HOTPLUG)   += mlxreg-hotplug.o
 obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o
 obj-$(CONFIG_MLXREG_LC) += mlxreg-lc.o
diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c
new file mode 100644 (file)
index 0000000..277e4b8
--- /dev/null
@@ -0,0 +1,611 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Nvidia Data Processor Unit platform driver
+ *
+ * Copyright (C) 2025 Nvidia Technologies Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_data/mlxcpld.h>
+#include <linux/platform_data/mlxreg.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* I2C bus IO offsets */
+#define MLXREG_DPU_REG_FPGA1_VER_OFFSET                        0x2400
+#define MLXREG_DPU_REG_FPGA1_PN_OFFSET                 0x2404
+#define MLXREG_DPU_REG_FPGA1_PN1_OFFSET                        0x2405
+#define MLXREG_DPU_REG_PG_OFFSET                       0x2414
+#define MLXREG_DPU_REG_PG_EVENT_OFFSET                 0x2415
+#define MLXREG_DPU_REG_PG_MASK_OFFSET                  0x2416
+#define MLXREG_DPU_REG_RESET_GP1_OFFSET                        0x2417
+#define MLXREG_DPU_REG_RST_CAUSE1_OFFSET               0x241e
+#define MLXREG_DPU_REG_GP0_RO_OFFSET                   0x242b
+#define MLXREG_DPU_REG_GP0_OFFSET                      0x242e
+#define MLXREG_DPU_REG_GP1_OFFSET                      0x242c
+#define MLXREG_DPU_REG_GP4_OFFSET                      0x2438
+#define MLXREG_DPU_REG_AGGRCO_OFFSET                   0x2442
+#define MLXREG_DPU_REG_AGGRCO_MASK_OFFSET              0x2443
+#define MLXREG_DPU_REG_HEALTH_OFFSET                   0x244d
+#define MLXREG_DPU_REG_HEALTH_EVENT_OFFSET             0x244e
+#define MLXREG_DPU_REG_HEALTH_MASK_OFFSET              0x244f
+#define MLXREG_DPU_REG_FPGA1_MVER_OFFSET               0x24de
+#define MLXREG_DPU_REG_CONFIG3_OFFSET                  0x24fd
+#define MLXREG_DPU_REG_MAX                             0x3fff
+
+/* Power Good event masks. */
+#define MLXREG_DPU_PG_VDDIO_MASK                       BIT(0)
+#define MLXREG_DPU_PG_VDD_CPU_MASK                     BIT(1)
+#define MLXREG_DPU_PG_VDD_MASK                         BIT(2)
+#define MLXREG_DPU_PG_1V8_MASK                         BIT(3)
+#define MLXREG_DPU_PG_COMPARATOR_MASK                  BIT(4)
+#define MLXREG_DPU_PG_VDDQ_MASK                                BIT(5)
+#define MLXREG_DPU_PG_HVDD_MASK                                BIT(6)
+#define MLXREG_DPU_PG_DVDD_MASK                                BIT(7)
+#define MLXREG_DPU_PG_MASK                             (MLXREG_DPU_PG_DVDD_MASK | \
+                                                        MLXREG_DPU_PG_HVDD_MASK | \
+                                                        MLXREG_DPU_PG_VDDQ_MASK | \
+                                                        MLXREG_DPU_PG_COMPARATOR_MASK | \
+                                                        MLXREG_DPU_PG_1V8_MASK | \
+                                                        MLXREG_DPU_PG_VDD_CPU_MASK | \
+                                                        MLXREG_DPU_PG_VDD_MASK | \
+                                                        MLXREG_DPU_PG_VDDIO_MASK)
+
+/* Health event masks. */
+#define MLXREG_DPU_HLTH_THERMAL_TRIP_MASK              BIT(0)
+#define MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK          BIT(1)
+#define MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK            BIT(2)
+#define MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK         BIT(3)
+#define MLXREG_DPU_HLTH_VDDQ_ALERT_MASK                        BIT(4)
+#define MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK             BIT(5)
+#define MLXREG_DPU_HEALTH_MASK                         (MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK | \
+                                                        MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK | \
+                                                        MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK | \
+                                                        MLXREG_DPU_HLTH_VDDQ_ALERT_MASK | \
+                                                        MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK | \
+                                                        MLXREG_DPU_HLTH_THERMAL_TRIP_MASK)
+
+/* Hotplug aggregation masks. */
+#define MLXREG_DPU_HEALTH_AGGR_MASK                    BIT(0)
+#define MLXREG_DPU_PG_AGGR_MASK                                BIT(1)
+#define MLXREG_DPU_AGGR_MASK                           (MLXREG_DPU_HEALTH_AGGR_MASK | \
+                                                        MLXREG_DPU_PG_AGGR_MASK)
+
+/* Voltage regulator firmware update status mask. */
+#define MLXREG_DPU_VOLTREG_UPD_MASK                    GENMASK(5, 4)
+
+#define MLXREG_DPU_NR_NONE                             (-1)
+
+/*
+ * enum mlxreg_dpu_type - Data Processor Unit types
+ *
+ * @MLXREG_DPU_BF3: DPU equipped with BF3 SoC;
+ */
+enum mlxreg_dpu_type {
+       MLXREG_DPU_BF3 = 0x0050,
+};
+
+/* Default register access data. */
+static struct mlxreg_core_data mlxreg_dpu_io_data[] = {
+       {
+               .label = "fpga1_version",
+               .reg = MLXREG_DPU_REG_FPGA1_VER_OFFSET,
+               .bit = GENMASK(7, 0),
+               .mode = 0444,
+       },
+       {
+               .label = "fpga1_pn",
+               .reg = MLXREG_DPU_REG_FPGA1_PN_OFFSET,
+               .bit = GENMASK(15, 0),
+               .mode = 0444,
+               .regnum = 2,
+       },
+       {
+               .label = "fpga1_version_min",
+               .reg = MLXREG_DPU_REG_FPGA1_MVER_OFFSET,
+               .bit = GENMASK(7, 0),
+               .mode = 0444,
+       },
+       {
+               .label = "perst_rst",
+               .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(0),
+               .mode = 0644,
+       },
+       {
+               .label = "usbphy_rst",
+               .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(1),
+               .mode = 0644,
+       },
+       {
+               .label = "phy_rst",
+               .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(2),
+               .mode = 0644,
+       },
+       {
+               .label = "tpm_rst",
+               .reg = MLXREG_DPU_REG_RESET_GP1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(6),
+               .mode = 0644,
+       },
+       {
+               .label = "reset_from_main_board",
+               .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(1),
+               .mode = 0444,
+       },
+       {
+               .label = "reset_aux_pwr_or_reload",
+               .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(2),
+               .mode = 0444,
+       },
+       {
+               .label = "reset_comex_pwr_fail",
+               .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(3),
+               .mode = 0444,
+       },
+       {
+               .label = "reset_dpu_thermal",
+               .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(6),
+               .mode = 0444,
+       },
+       {
+               .label = "reset_pwr_off",
+               .reg = MLXREG_DPU_REG_RST_CAUSE1_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(7),
+               .mode = 0444,
+       },
+       {
+               .label = "dpu_id",
+               .reg = MLXREG_DPU_REG_GP0_RO_OFFSET,
+               .bit = GENMASK(3, 0),
+               .mode = 0444,
+       },
+       {
+               .label = "voltreg_update_status",
+               .reg = MLXREG_DPU_REG_GP0_RO_OFFSET,
+               .mask = MLXREG_DPU_VOLTREG_UPD_MASK,
+               .bit = 5,
+               .mode = 0444,
+       },
+       {
+               .label = "boot_progress",
+               .reg = MLXREG_DPU_REG_GP1_OFFSET,
+               .mask = GENMASK(3, 0),
+               .mode = 0444,
+       },
+       {
+               .label = "ufm_upgrade",
+               .reg = MLXREG_DPU_REG_GP4_OFFSET,
+               .mask = GENMASK(7, 0) & ~BIT(1),
+               .mode = 0644,
+       },
+};
+
+static struct mlxreg_core_platform_data mlxreg_dpu_default_regs_io_data = {
+               .data = mlxreg_dpu_io_data,
+               .counter = ARRAY_SIZE(mlxreg_dpu_io_data),
+};
+
+/* Default hotplug data. */
+static struct mlxreg_core_data mlxreg_dpu_power_events_items_data[] = {
+       {
+               .label = "pg_vddio",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_VDDIO_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_vdd_cpu",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_VDD_CPU_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_vdd",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_VDD_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_1v8",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_1V8_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_comparator",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_COMPARATOR_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_vddq",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_VDDQ_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_hvdd",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_HVDD_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "pg_dvdd",
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_DVDD_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+};
+
+static struct mlxreg_core_data mlxreg_dpu_health_events_items_data[] = {
+       {
+               .label = "thermal_trip",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_THERMAL_TRIP_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "ufm_upgrade_done",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_UFM_UPGRADE_DONE_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "vddq_hot_alert",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_VDDQ_HOT_ALERT_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "vdd_cpu_hot_alert",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_VDD_CPU_HOT_ALERT_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "vddq_alert",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_VDDQ_ALERT_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+       {
+               .label = "vdd_cpu_alert",
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HLTH_VDD_CPU_ALERT_MASK,
+               .hpdev.nr = MLXREG_DPU_NR_NONE,
+       },
+};
+
+static struct mlxreg_core_item mlxreg_dpu_hotplug_items[] = {
+       {
+               .data = mlxreg_dpu_power_events_items_data,
+               .aggr_mask = MLXREG_DPU_PG_AGGR_MASK,
+               .reg = MLXREG_DPU_REG_PG_OFFSET,
+               .mask = MLXREG_DPU_PG_MASK,
+               .count = ARRAY_SIZE(mlxreg_dpu_power_events_items_data),
+               .health = false,
+               .inversed = 0,
+       },
+       {
+               .data = mlxreg_dpu_health_events_items_data,
+               .aggr_mask = MLXREG_DPU_HEALTH_AGGR_MASK,
+               .reg = MLXREG_DPU_REG_HEALTH_OFFSET,
+               .mask = MLXREG_DPU_HEALTH_MASK,
+               .count = ARRAY_SIZE(mlxreg_dpu_health_events_items_data),
+               .health = false,
+               .inversed = 0,
+       },
+};
+
+static
+struct mlxreg_core_hotplug_platform_data mlxreg_dpu_default_hotplug_data = {
+       .items = mlxreg_dpu_hotplug_items,
+       .count = ARRAY_SIZE(mlxreg_dpu_hotplug_items),
+       .cell = MLXREG_DPU_REG_AGGRCO_OFFSET,
+       .mask = MLXREG_DPU_AGGR_MASK,
+};
+
+/**
+ * struct mlxreg_dpu - device private data
+ * @dev: platform device
+ * @data: platform core data
+ * @io_data: register access platform data
+ * @io_regs: register access device
+ * @hotplug_data: hotplug platform data
+ * @hotplug: hotplug device
+ */
+struct mlxreg_dpu {
+       struct device *dev;
+       struct mlxreg_core_data *data;
+       struct mlxreg_core_platform_data *io_data;
+       struct platform_device *io_regs;
+       struct mlxreg_core_hotplug_platform_data *hotplug_data;
+       struct platform_device *hotplug;
+};
+
+static bool mlxreg_dpu_writeable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MLXREG_DPU_REG_PG_EVENT_OFFSET:
+       case MLXREG_DPU_REG_PG_MASK_OFFSET:
+       case MLXREG_DPU_REG_RESET_GP1_OFFSET:
+       case MLXREG_DPU_REG_GP0_OFFSET:
+       case MLXREG_DPU_REG_GP1_OFFSET:
+       case MLXREG_DPU_REG_GP4_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
+               return true;
+       }
+       return false;
+}
+
+static bool mlxreg_dpu_readable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MLXREG_DPU_REG_FPGA1_VER_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_PN_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_PN1_OFFSET:
+       case MLXREG_DPU_REG_PG_OFFSET:
+       case MLXREG_DPU_REG_PG_EVENT_OFFSET:
+       case MLXREG_DPU_REG_PG_MASK_OFFSET:
+       case MLXREG_DPU_REG_RESET_GP1_OFFSET:
+       case MLXREG_DPU_REG_RST_CAUSE1_OFFSET:
+       case MLXREG_DPU_REG_GP0_RO_OFFSET:
+       case MLXREG_DPU_REG_GP0_OFFSET:
+       case MLXREG_DPU_REG_GP1_OFFSET:
+       case MLXREG_DPU_REG_GP4_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_MVER_OFFSET:
+       case MLXREG_DPU_REG_CONFIG3_OFFSET:
+               return true;
+       }
+       return false;
+}
+
+static bool mlxreg_dpu_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MLXREG_DPU_REG_FPGA1_VER_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_PN_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_PN1_OFFSET:
+       case MLXREG_DPU_REG_PG_OFFSET:
+       case MLXREG_DPU_REG_PG_EVENT_OFFSET:
+       case MLXREG_DPU_REG_PG_MASK_OFFSET:
+       case MLXREG_DPU_REG_RESET_GP1_OFFSET:
+       case MLXREG_DPU_REG_RST_CAUSE1_OFFSET:
+       case MLXREG_DPU_REG_GP0_RO_OFFSET:
+       case MLXREG_DPU_REG_GP0_OFFSET:
+       case MLXREG_DPU_REG_GP1_OFFSET:
+       case MLXREG_DPU_REG_GP4_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_OFFSET:
+       case MLXREG_DPU_REG_AGGRCO_MASK_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_EVENT_OFFSET:
+       case MLXREG_DPU_REG_HEALTH_MASK_OFFSET:
+       case MLXREG_DPU_REG_FPGA1_MVER_OFFSET:
+       case MLXREG_DPU_REG_CONFIG3_OFFSET:
+               return true;
+       }
+       return false;
+}
+
+/* Configuration for the register map of a device with 2 bytes address space. */
+static const struct regmap_config mlxreg_dpu_regmap_conf = {
+       .reg_bits = 16,
+       .val_bits = 8,
+       .max_register = MLXREG_DPU_REG_MAX,
+       .cache_type = REGCACHE_FLAT,
+       .writeable_reg = mlxreg_dpu_writeable_reg,
+       .readable_reg = mlxreg_dpu_readable_reg,
+       .volatile_reg = mlxreg_dpu_volatile_reg,
+};
+
+static int
+mlxreg_dpu_copy_hotplug_data(struct device *dev, struct mlxreg_dpu *mlxreg_dpu,
+                            const struct mlxreg_core_hotplug_platform_data *hotplug_data)
+{
+       struct mlxreg_core_item *item;
+       int i;
+
+       mlxreg_dpu->hotplug_data = devm_kmemdup(dev, hotplug_data,
+                                               sizeof(*mlxreg_dpu->hotplug_data), GFP_KERNEL);
+       if (!mlxreg_dpu->hotplug_data)
+               return -ENOMEM;
+
+       mlxreg_dpu->hotplug_data->items = devm_kmemdup(dev, hotplug_data->items,
+                                                      mlxreg_dpu->hotplug_data->count *
+                                                      sizeof(*mlxreg_dpu->hotplug_data->items),
+                                                      GFP_KERNEL);
+       if (!mlxreg_dpu->hotplug_data->items)
+               return -ENOMEM;
+
+       item = mlxreg_dpu->hotplug_data->items;
+       for (i = 0; i < hotplug_data->count; i++, item++) {
+               item->data = devm_kmemdup(dev, hotplug_data->items[i].data,
+                                         hotplug_data->items[i].count * sizeof(*item->data),
+                                         GFP_KERNEL);
+               if (!item->data)
+                       return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static int mlxreg_dpu_config_init(struct mlxreg_dpu *mlxreg_dpu, void *regmap,
+                                 struct mlxreg_core_data *data, int irq)
+{
+       struct device *dev = &data->hpdev.client->dev;
+       u32 regval;
+       int err;
+
+       /* Validate DPU type. */
+       err = regmap_read(regmap, MLXREG_DPU_REG_CONFIG3_OFFSET, &regval);
+       if (err)
+               return err;
+
+       switch (regval) {
+       case MLXREG_DPU_BF3:
+               /* Copy platform specific hotplug data. */
+               err = mlxreg_dpu_copy_hotplug_data(dev, mlxreg_dpu,
+                                                  &mlxreg_dpu_default_hotplug_data);
+               if (err)
+                       return err;
+
+               mlxreg_dpu->io_data = &mlxreg_dpu_default_regs_io_data;
+
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       /* Register IO access driver. */
+       if (mlxreg_dpu->io_data) {
+               mlxreg_dpu->io_data->regmap = regmap;
+               mlxreg_dpu->io_regs =
+                       platform_device_register_resndata(dev, "mlxreg-io",
+                                                         data->slot, NULL, 0,
+                                                         mlxreg_dpu->io_data,
+                                                         sizeof(*mlxreg_dpu->io_data));
+               if (IS_ERR(mlxreg_dpu->io_regs)) {
+                       dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n",
+                               data->hpdev.brdinfo->type, data->hpdev.nr,
+                               data->hpdev.brdinfo->addr);
+                       return PTR_ERR(mlxreg_dpu->io_regs);
+               }
+       }
+
+       /* Register hotplug driver. */
+       if (mlxreg_dpu->hotplug_data && irq) {
+               mlxreg_dpu->hotplug_data->regmap = regmap;
+               mlxreg_dpu->hotplug_data->irq = irq;
+               mlxreg_dpu->hotplug =
+                       platform_device_register_resndata(dev, "mlxreg-hotplug",
+                                                         data->slot, NULL, 0,
+                                                         mlxreg_dpu->hotplug_data,
+                                                         sizeof(*mlxreg_dpu->hotplug_data));
+               if (IS_ERR(mlxreg_dpu->hotplug)) {
+                       err = PTR_ERR(mlxreg_dpu->hotplug);
+                       goto fail_register_hotplug;
+               }
+       }
+
+       return 0;
+
+fail_register_hotplug:
+       platform_device_unregister(mlxreg_dpu->io_regs);
+
+       return err;
+}
+
+static void mlxreg_dpu_config_exit(struct mlxreg_dpu *mlxreg_dpu)
+{
+       platform_device_unregister(mlxreg_dpu->hotplug);
+       platform_device_unregister(mlxreg_dpu->io_regs);
+}
+
+static int mlxreg_dpu_probe(struct platform_device *pdev)
+{
+       struct mlxreg_core_data *data;
+       struct mlxreg_dpu *mlxreg_dpu;
+       void *regmap;
+       int err;
+
+       data = dev_get_platdata(&pdev->dev);
+       if (!data || !data->hpdev.brdinfo)
+               return -EINVAL;
+
+       data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr);
+       if (!data->hpdev.adapter)
+               return -EPROBE_DEFER;
+
+       mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL);
+       if (!mlxreg_dpu)
+               return -ENOMEM;
+
+       /* Create device at the top of DPU I2C tree. */
+       data->hpdev.client = i2c_new_client_device(data->hpdev.adapter,
+                                                  data->hpdev.brdinfo);
+       if (IS_ERR(data->hpdev.client)) {
+               dev_err(&pdev->dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
+                       data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
+               err = PTR_ERR(data->hpdev.client);
+               goto i2c_new_device_fail;
+       }
+
+       regmap = devm_regmap_init_i2c(data->hpdev.client, &mlxreg_dpu_regmap_conf);
+       if (IS_ERR(regmap)) {
+               dev_err(&pdev->dev, "Failed to create regmap for client %s at bus %d at addr 0x%02x\n",
+                       data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
+               err = PTR_ERR(regmap);
+               goto devm_regmap_init_i2c_fail;
+       }
+
+       /* Sync registers with hardware. */
+       regcache_mark_dirty(regmap);
+       err = regcache_sync(regmap);
+       if (err) {
+               dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n",
+                       data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
+               err = PTR_ERR(regmap);
+               goto regcache_sync_fail;
+       }
+
+       mlxreg_dpu->data = data;
+       mlxreg_dpu->dev = &pdev->dev;
+       platform_set_drvdata(pdev, mlxreg_dpu);
+
+       err = mlxreg_dpu_config_init(mlxreg_dpu, regmap, data, data->hpdev.brdinfo->irq);
+       if (err)
+               goto mlxreg_dpu_config_init_fail;
+
+       return err;
+
+mlxreg_dpu_config_init_fail:
+regcache_sync_fail:
+devm_regmap_init_i2c_fail:
+       i2c_unregister_device(data->hpdev.client);
+i2c_new_device_fail:
+       i2c_put_adapter(data->hpdev.adapter);
+       return err;
+}
+
+static void mlxreg_dpu_remove(struct platform_device *pdev)
+{
+       struct mlxreg_core_data *data = dev_get_platdata(&pdev->dev);
+       struct mlxreg_dpu *mlxreg_dpu = platform_get_drvdata(pdev);
+
+       mlxreg_dpu_config_exit(mlxreg_dpu);
+       i2c_unregister_device(data->hpdev.client);
+       i2c_put_adapter(data->hpdev.adapter);
+}
+
+static struct platform_driver mlxreg_dpu_driver = {
+       .probe = mlxreg_dpu_probe,
+       .remove = mlxreg_dpu_remove,
+       .driver = {
+               .name = "mlxreg-dpu",
+       },
+};
+
+module_platform_driver(mlxreg_dpu_driver);
+
+MODULE_AUTHOR("Vadim Pasternak <vadimp@nvidia.com>");
+MODULE_DESCRIPTION("Nvidia Data Processor Unit platform driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:mlxreg-dpu");