mfd: Add Ampere's Altra SMpro MFD driver
authorQuan Nguyen <quan@os.amperecomputing.com>
Thu, 29 Sep 2022 09:43:20 +0000 (16:43 +0700)
committerLee Jones <lee@kernel.org>
Wed, 7 Dec 2022 13:28:07 +0000 (13:28 +0000)
Adds Multi-function devices driver for SMpro co-processor found on the
Mt.Jade hardware reference platform with Ampere's Altra processor family.

Signed-off-by: Quan Nguyen <quan@os.amperecomputing.com>
Signed-off-by: Lee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/20220929094321.770125-9-quan@os.amperecomputing.com
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/smpro-core.c [new file with mode: 0644]

index 8b93856de432aeede26b670ec190ea45260569b0..9e77f4762999451f391f5f04ba74d71cfbff5793 100644 (file)
@@ -77,6 +77,18 @@ config MFD_AS3711
        help
          Support for the AS3711 PMIC from AMS
 
+config MFD_SMPRO
+       tristate "Ampere Computing SMpro core driver"
+       depends on I2C
+       select MFD_CORE
+       select REGMAP_I2C
+       help
+         Say yes here to enable SMpro driver support for Ampere's Altra
+         processor family.
+
+         Ampere's Altra SMpro exposes an I2C regmap interface that can
+         be accessed by child devices.
+
 config MFD_AS3722
        tristate "ams AS3722 Power Management IC"
        select MFD_CORE
index 7ed3ef4a698cf2299404b58cee9c09e44c154b21..9387c3ddab4e55d86b44ba13835bb795cfabfb18 100644 (file)
@@ -271,6 +271,7 @@ obj-$(CONFIG_MFD_QCOM_PM8008)       += qcom-pm8008.o
 
 obj-$(CONFIG_SGI_MFD_IOC3)     += ioc3.o
 obj-$(CONFIG_MFD_SIMPLE_MFD_I2C)       += simple-mfd-i2c.o
+obj-$(CONFIG_MFD_SMPRO)                += smpro-core.o
 obj-$(CONFIG_MFD_INTEL_M10_BMC)   += intel-m10-bmc.o
 
 obj-$(CONFIG_MFD_ATC260X)      += atc260x-core.o
diff --git a/drivers/mfd/smpro-core.c b/drivers/mfd/smpro-core.c
new file mode 100644 (file)
index 0000000..d7729cf
--- /dev/null
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ampere Altra Family SMPro core driver
+ * Copyright (c) 2022, Ampere Computing LLC
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+
+/* Identification Registers */
+#define MANUFACTURER_ID_REG     0x02
+#define AMPERE_MANUFACTURER_ID  0xCD3A
+
+#define CORE_CE_ERR_DATA        0x82
+#define CORE_UE_ERR_DATA        0x85
+#define MEM_CE_ERR_DATA         0x92
+#define MEM_UE_ERR_DATA         0x95
+#define PCIE_CE_ERR_DATA        0xC2
+#define PCIE_UE_ERR_DATA        0xC5
+#define OTHER_CE_ERR_DATA       0xD2
+#define OTHER_UE_ERR_DATA       0xDA
+
+static int smpro_core_write(void *context, const void *data, size_t count)
+{
+       struct device *dev = context;
+       struct i2c_client *i2c = to_i2c_client(dev);
+       int ret;
+
+       ret = i2c_master_send(i2c, data, count);
+       if (unlikely(ret != count))
+               return (ret < 0) ? ret : -EIO;
+
+       return 0;
+}
+
+static int smpro_core_read(void *context, const void *reg, size_t reg_size,
+                          void *val, size_t val_size)
+{
+       struct device *dev = context;
+       struct i2c_client *i2c = to_i2c_client(dev);
+       struct i2c_msg xfer[2];
+       unsigned char buf[2];
+       int ret;
+
+       xfer[0].addr = i2c->addr;
+       xfer[0].flags = 0;
+
+       buf[0] = *(u8 *)reg;
+       buf[1] = val_size;
+       xfer[0].len = 2;
+       xfer[0].buf = buf;
+
+       xfer[1].addr = i2c->addr;
+       xfer[1].flags = I2C_M_RD;
+       xfer[1].len = val_size;
+       xfer[1].buf = val;
+
+       ret = i2c_transfer(i2c->adapter, xfer, 2);
+       if (unlikely(ret != 2))
+               return (ret < 0) ? ret : -EIO;
+
+       return 0;
+}
+
+static const struct regmap_bus smpro_regmap_bus = {
+       .read = smpro_core_read,
+       .write = smpro_core_write,
+       .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static bool smpro_core_readable_noinc_reg(struct device *dev, unsigned int reg)
+{
+       return  (reg == CORE_CE_ERR_DATA || reg == CORE_UE_ERR_DATA ||
+                reg == MEM_CE_ERR_DATA || reg == MEM_UE_ERR_DATA ||
+                reg == PCIE_CE_ERR_DATA || reg == PCIE_UE_ERR_DATA ||
+                reg == OTHER_CE_ERR_DATA || reg == OTHER_UE_ERR_DATA);
+}
+
+static const struct regmap_config smpro_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 16,
+       .readable_noinc_reg = smpro_core_readable_noinc_reg,
+};
+
+static const struct mfd_cell smpro_devs[] = {
+       MFD_CELL_NAME("smpro-hwmon"),
+       MFD_CELL_NAME("smpro-errmon"),
+       MFD_CELL_NAME("smpro-misc"),
+};
+
+static int smpro_core_probe(struct i2c_client *i2c)
+{
+       const struct regmap_config *config;
+       struct regmap *regmap;
+       unsigned int val;
+       int ret;
+
+       config = device_get_match_data(&i2c->dev);
+       if (!config)
+               return -EINVAL;
+
+       regmap = devm_regmap_init(&i2c->dev, &smpro_regmap_bus, &i2c->dev, config);
+       if (IS_ERR(regmap))
+               return PTR_ERR(regmap);
+
+       ret = regmap_read(regmap, MANUFACTURER_ID_REG, &val);
+       if (ret)
+               return ret;
+
+       if (val != AMPERE_MANUFACTURER_ID)
+               return -ENODEV;
+
+       return devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO,
+                                   smpro_devs, ARRAY_SIZE(smpro_devs), NULL, 0, NULL);
+}
+
+static const struct of_device_id smpro_core_of_match[] = {
+       { .compatible = "ampere,smpro", .data = &smpro_regmap_config },
+       {}
+};
+MODULE_DEVICE_TABLE(of, smpro_core_of_match);
+
+static struct i2c_driver smpro_core_driver = {
+       .probe_new = smpro_core_probe,
+       .driver = {
+               .name = "smpro-core",
+               .of_match_table = smpro_core_of_match,
+       },
+};
+module_i2c_driver(smpro_core_driver);
+
+MODULE_AUTHOR("Quan Nguyen <quan@os.amperecomputing.com>");
+MODULE_DESCRIPTION("SMPRO CORE - I2C driver");
+MODULE_LICENSE("GPL");