@@ -10,6 +10,7 @@ source "drivers/soc/bcm/Kconfig"
source "drivers/soc/canaan/Kconfig"
source "drivers/soc/fsl/Kconfig"
source "drivers/soc/fujitsu/Kconfig"
+source "drivers/soc/hpe/Kconfig"
source "drivers/soc/imx/Kconfig"
source "drivers/soc/ixp4xx/Kconfig"
source "drivers/soc/litex/Kconfig"
@@ -14,6 +14,7 @@ obj-$(CONFIG_MACH_DOVE) += dove/
obj-y += fsl/
obj-y += fujitsu/
obj-$(CONFIG_ARCH_GEMINI) += gemini/
+obj-$(CONFIG_ARCH_HPE) += hpe/
obj-y += imx/
obj-y += ixp4xx/
obj-$(CONFIG_SOC_XWAY) += lantiq/
new file mode 100644
@@ -0,0 +1,19 @@
+config SOC_VENDOR_HPE
+ bool "HPE SoC drivers"
+ default y
+ depends on ARCH_HPE
+
+if SOC_VENDOR_HPE
+
+config HPE_GXP_SOCLIB
+ bool
+ default n
+
+config HPE_GXP_PLREG
+ bool "GXP Programmable logic register support"
+ depends on ARCH_HPE_GXP
+ select HPE_GXP_SOCLIB
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to add support for the PLREG.
+endif
new file mode 100644
@@ -0,0 +1,7 @@
+obj-$(CONFIG_HPE_GXP_SOCLIB) += gxp-soclib.o
+obj-$(CONFIG_HPE_GXP_PLREG) += gxp-plreg.o
+obj-$(CONFIG_HPE_GXP_FN2) += gxp-fn2.o
+obj-$(CONFIG_HPE_GXP_CSM) += gxp-csm.o
+obj-$(CONFIG_HPE_GXP_SROM) += gxp-srom.o
+obj-$(CONFIG_HPE_GXP_CHIF) += gxp-chif.o
+obj-$(CONFIG_HPE_GXP_DBG) += gxp-dbg.o
new file mode 100644
@@ -0,0 +1,1207 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+
+#include <linux/soc/hpe/gxp.h>
+#include "gxp-soclib.h"
+
+#define IOP_LED1 0
+#define IOP_LED2 1
+#define IOP_LED3 2
+#define IOP_LED4 3
+#define IOP_LED5 4
+#define IOP_LED6 5
+#define IOP_LED7 6
+#define IOP_LED8 7
+#define FAN1_INST 8
+#define FAN2_INST 9
+#define FAN3_INST 10
+#define FAN4_INST 11
+#define FAN5_INST 12
+#define FAN6_INST 13
+#define FAN7_INST 14
+#define FAN8_INST 15
+#define FAN9_INST 16
+#define FAN10_INST 17
+#define FAN11_INST 18
+#define FAN12_INST 19
+#define FAN13_INST 20
+#define FAN14_INST 21
+#define FAN15_INST 22
+#define FAN16_INST 23
+#define FAN1_FAIL 24
+#define FAN2_FAIL 25
+#define FAN3_FAIL 26
+#define FAN4_FAIL 27
+#define FAN5_FAIL 28
+#define FAN6_FAIL 29
+#define FAN7_FAIL 30
+#define FAN8_FAIL 31
+#define FAN9_FAIL 32
+#define FAN10_FAIL 33
+#define FAN11_FAIL 34
+#define FAN12_FAIL 35
+#define FAN13_FAIL 36
+#define FAN14_FAIL 37
+#define FAN15_FAIL 38
+#define FAN16_FAIL 39
+#define FAN1_ID 40
+#define FAN2_ID 41
+#define FAN3_ID 42
+#define FAN4_ID 43
+#define FAN5_ID 44
+#define FAN6_ID 45
+#define FAN7_ID 46
+#define FAN8_ID 47
+#define FAN9_ID 48
+#define FAN10_ID 49
+#define FAN11_ID 50
+#define FAN12_ID 51
+#define FAN13_ID 52
+#define FAN14_ID 53
+#define FAN15_ID 54
+#define FAN16_ID 55
+#define LED_IDENTIFY 56
+#define LED_HEALTH_RED 57
+#define LED_HEALTH_AMBER 58
+#define PWR_BTN_INT 59
+#define UID_PRESS_INT 60
+#define SLP_INT 61
+#define PME_INT 62
+#define RESV0 63
+#define RESV1 64
+#define RESV2 65
+#define ACM_FORCE_OFF 70
+#define ACM_REMOVED 71
+#define ACM_REQ_N 72
+
+#define PLREG_INT_GRP5_PIN_BASE 59
+
+#define MAX_FAN 16
+#define IOP_LED_QUANTITY 8
+#define BYTE 0
+#define MASK 1
+#define VALUE 2
+
+struct fan_access {
+ u32 inst;
+ u32 fail;
+ u32 id;
+ u32 bit;
+};
+
+struct iop_led_access {
+ u32 iop_led[2];
+};
+
+struct health_led_access {
+ u32 red[2];
+ u32 amber[2];
+ u32 green[2];
+};
+
+struct identify_led_access {
+ u32 off[2];
+ u32 on[2];
+};
+
+struct acm_access {
+ int exists;
+ u32 force_off[2];
+ u32 removed[2];
+ u32 unlatch_req[2];
+};
+
+struct server_id_access {
+ u32 upper[2];
+ u32 lower[2];
+};
+
+struct sideband_access {
+ u32 disabled[2];
+ u32 embedded[2];
+ u32 adaptive[2];
+};
+
+struct grp5_intr_flag_access {
+ u32 ack[2];
+ u32 pwrbtn[2];
+ u32 uidpress[2];
+ u32 slpintr[2];
+};
+
+struct grp5_intr_mask_access {
+ u32 pwrbtn[2];
+ u32 slpintr[2];
+};
+
+struct grp_intr_masks_access {
+ u32 grp5[2];
+};
+
+struct grp_intr_flags_access {
+ u32 grp5[2];
+};
+
+struct pwrbtn_access {
+ u32 latch[3];
+};
+
+struct gxp_plreg_drvdata {
+ void __iomem *base;
+ struct regmap *plreg_map;
+ struct gpio_chip gpio_chip;
+ int irq;
+ struct fan_access fan[16];
+ struct health_led_access health_led;
+ struct iop_led_access iop_led[8];
+ struct identify_led_access identify_led;
+ struct acm_access acm;
+ struct server_id_access server_id;
+ struct sideband_access sideband;
+ struct grp5_intr_flag_access grp5_intr_flag;
+ struct grp5_intr_mask_access grp5_intr_mask;
+ struct grp_intr_masks_access grp_intr_masks;
+ struct grp_intr_flags_access grp_intr_flags;
+ struct pwrbtn_access pwrbtn;
+};
+
+struct gxp_plreg_drvdata client_data;
+
+static void address_translation(u32 desired_offset, u32 *offset, u32 *bit_shift)
+{
+ *offset = (desired_offset & 0xffc);
+ *bit_shift = (desired_offset - *offset) * 8;
+}
+
+int gxp_plreg_get_fan_inst(int fan)
+{
+ struct gxp_plreg_drvdata *drvdata = &client_data;
+ u32 trans_offset;
+ u32 trans_shift;
+ u32 val;
+
+ address_translation(drvdata->fan[fan].inst,
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[fan].bit;
+ if (val == drvdata->fan[fan].bit)
+ return 1;
+ else
+ return 0;
+}
+EXPORT_SYMBOL(gxp_plreg_get_fan_inst);
+
+int gxp_plreg_get_fan_fail(int fan)
+{
+ struct gxp_plreg_drvdata *drvdata = &client_data;
+ u32 trans_offset;
+ u32 trans_shift;
+ u32 val;
+
+ address_translation(drvdata->fan[fan].fail,
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[fan].bit;
+ if (val == drvdata->fan[fan].bit)
+ return 1;
+ else
+ return 0;
+}
+EXPORT_SYMBOL(gxp_plreg_get_fan_fail);
+
+int gxp_plreg_get_fan_id(int fan)
+{
+ struct gxp_plreg_drvdata *drvdata = &client_data;
+ u32 trans_offset;
+ u32 trans_shift;
+ u32 val;
+
+ address_translation(drvdata->fan[fan].id,
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[fan].bit;
+ if (val == drvdata->fan[fan].bit)
+ return 1;
+ else
+ return 0;
+}
+EXPORT_SYMBOL(gxp_plreg_get_fan_id);
+
+void gxp_plreg_do_virt_pwr_btn_latch(void)
+{
+ struct gxp_plreg_drvdata *drvdata = &client_data;
+ u32 trans_offset;
+ u32 trans_shift;
+
+ address_translation(drvdata->pwrbtn.latch[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->pwrbtn.latch[MASK] << trans_shift,
+ drvdata->pwrbtn.latch[VALUE] << trans_shift);
+}
+EXPORT_SYMBOL(gxp_plreg_do_virt_pwr_btn_latch);
+
+static ssize_t server_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(dev);
+ int value_upper;
+ int value_lower;
+ ssize_t ret;
+ u32 trans_offset;
+ u32 trans_shift;
+
+ /* read upper first */
+ address_translation(drvdata->server_id.upper[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &value_upper);
+ value_upper = value_upper >> trans_shift;
+ value_upper = value_upper & drvdata->server_id.upper[MASK];
+
+ /* read lower last */
+ address_translation(drvdata->server_id.lower[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &value_lower);
+ value_lower = value_lower >> trans_shift;
+ value_lower = value_lower & drvdata->server_id.lower[MASK];
+
+ ret = sprintf(buf, "0x%04x", value_upper | value_lower);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RO(server_id);
+
+static ssize_t sideband_sel_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(dev);
+ int value;
+ ssize_t ret;
+ u32 trans_offset;
+ u32 trans_shift;
+ u32 status = PLREG_SIDEBAND_UNKNOWN;
+
+ /* check first to see if its disabled */
+ address_translation(drvdata->sideband.disabled[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &value);
+ value = value >> trans_shift;
+
+ if ((value & drvdata->sideband.disabled[MASK]) ==
+ drvdata->sideband.disabled[MASK]) {
+ status = PLREG_SIDEBAND_DISABLED;
+ goto EXIT;
+ }
+
+ /* check next to see if its embedded */
+ address_translation(drvdata->sideband.embedded[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &value);
+ value = value >> trans_shift;
+
+ if ((value & drvdata->sideband.embedded[MASK]) ==
+ drvdata->sideband.embedded[MASK]) {
+ status = PLREG_SIDEBAND_EMBEDDED;
+ goto EXIT;
+ }
+
+ /* check finally to see if its adaptive */
+ address_translation(drvdata->sideband.adaptive[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_read(drvdata->plreg_map, trans_offset, &value);
+ value = value >> trans_shift;
+
+ if ((value & drvdata->sideband.adaptive[MASK]) ==
+ drvdata->sideband.adaptive[MASK])
+ status = PLREG_SIDEBAND_ADAPTIVE;
+EXIT:
+
+ ret = sprintf(buf, "0x%02x", status);
+
+ return ret;
+}
+
+static ssize_t sideband_sel_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(dev);
+ int input;
+ int rc;
+ u32 trans_offset;
+ u32 trans_shift;
+
+ rc = kstrtoint(buf, 0, &input);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (input > PLREG_SIDEBAND_ADAPTIVE || input < PLREG_SIDEBAND_DISABLED)
+ return -EINVAL;
+
+ if (input == PLREG_SIDEBAND_DISABLED) {
+ address_translation(drvdata->sideband.disabled[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->sideband.disabled[MASK] << trans_shift,
+ drvdata->sideband.disabled[MASK] << trans_shift);
+ } else if (input == PLREG_SIDEBAND_EMBEDDED) {
+ address_translation(drvdata->sideband.embedded[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->sideband.embedded[MASK] << trans_shift,
+ drvdata->sideband.embedded[MASK] << trans_shift);
+ } else {
+ address_translation(drvdata->sideband.adaptive[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->sideband.adaptive[MASK] << trans_shift,
+ drvdata->sideband.adaptive[MASK] << trans_shift);
+ }
+
+ return count;
+}
+static DEVICE_ATTR_RW(sideband_sel);
+
+static struct attribute *plreg_attrs[] = {
+ &dev_attr_server_id.attr,
+ &dev_attr_sideband_sel.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(plreg);
+
+static int sysfs_register(struct device *parent, struct gxp_plreg_drvdata *plreg)
+{
+ struct device *dev;
+
+ dev = device_create_with_groups(soc_class, parent, 0,
+ plreg, plreg_groups, "plreg");
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ return 0;
+}
+
+static int gxp_gpio_plreg_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+ int ret = 0;
+ u32 trans_offset;
+ u32 trans_shift;
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ address_translation(drvdata->iop_led[offset].iop_led[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->iop_led[offset].iop_led[MASK];
+ if (val == drvdata->iop_led[offset].iop_led[MASK])
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case FAN1_INST ...FAN16_INST:
+ address_translation(drvdata->fan[offset - FAN1_INST].inst,
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[offset - FAN1_INST].bit;
+ if (val == drvdata->fan[offset - FAN1_INST].bit)
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case FAN1_FAIL ... FAN16_FAIL:
+ address_translation(drvdata->fan[offset - FAN1_FAIL].fail,
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[offset - FAN1_FAIL].bit;
+ if (val == drvdata->fan[offset - FAN1_FAIL].bit)
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case FAN1_ID ... FAN16_ID:
+ address_translation(drvdata->fan[offset - FAN1_ID].id,
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->fan[offset - FAN1_ID].bit;
+ if (val == drvdata->fan[offset - FAN1_ID].bit)
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case PWR_BTN_INT:
+ address_translation(drvdata->grp5_intr_flag.pwrbtn[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->grp5_intr_flag.pwrbtn[MASK];
+ if (val == drvdata->grp5_intr_flag.pwrbtn[MASK])
+ ret = 0;
+ else
+ ret = 1;
+ break;
+ case UID_PRESS_INT:
+ address_translation(drvdata->grp5_intr_flag.uidpress[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->grp5_intr_flag.uidpress[MASK];
+ if (val == drvdata->grp5_intr_flag.uidpress[MASK])
+ ret = 0;
+ else
+ ret = 1;
+ break;
+ case SLP_INT:
+ address_translation(drvdata->grp5_intr_flag.slpintr[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->grp5_intr_flag.slpintr[MASK];
+ if (val == drvdata->grp5_intr_flag.slpintr[MASK])
+ ret = 0;
+ else
+ ret = 1;
+ break;
+ case ACM_FORCE_OFF:
+ if (!drvdata->acm.exists)
+ return -1;
+
+ address_translation(drvdata->acm.force_off[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->acm.force_off[MASK];
+ if (val == drvdata->acm.force_off[MASK])
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case ACM_REMOVED:
+ if (!drvdata->acm.exists)
+ return -1;
+ address_translation(drvdata->acm.removed[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->acm.removed[MASK];
+ if (val == drvdata->acm.removed[MASK])
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ case ACM_REQ_N:
+ if (!drvdata->acm.exists)
+ return -1;
+ address_translation(drvdata->acm.unlatch_req[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+ val = (val >> trans_shift) & drvdata->acm.unlatch_req[MASK];
+ if (val == drvdata->acm.unlatch_req[MASK])
+ ret = 1;
+ else
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_gpio_plreg_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ u32 trans_offset;
+ u32 trans_shift;
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ if (value > 1 || 0 < value)
+ return;
+
+ address_translation(drvdata->iop_led[offset].iop_led[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->iop_led[offset].iop_led[MASK] << trans_shift,
+ value << trans_shift);
+ break;
+ case LED_IDENTIFY:
+ //offset 0x05 bit 7:6
+ if (value == 1) {
+ address_translation(drvdata->identify_led.on[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->identify_led.on[MASK] << trans_shift,
+ drvdata->identify_led.on[MASK] << trans_shift);
+ } else if (value == 0) {
+ address_translation(drvdata->identify_led.off[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->identify_led.off[MASK] << trans_shift,
+ drvdata->identify_led.off[MASK] << trans_shift);
+ }
+ break;
+ case LED_HEALTH_RED:
+ if (value > 1 || 0 < value)
+ return;
+
+ address_translation(drvdata->health_led.red[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->health_led.red[MASK] << trans_shift,
+ value << trans_shift);
+ break;
+ case LED_HEALTH_AMBER:
+ if (value > 1 || 0 < value)
+ return;
+
+ address_translation(drvdata->health_led.amber[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->health_led.amber[MASK] << trans_shift,
+ value << trans_shift);
+ break;
+ case ACM_FORCE_OFF:
+ if (value > 1 || 0 < value || !drvdata->acm.exists)
+ return;
+
+ address_translation(drvdata->acm.force_off[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->acm.force_off[MASK] << trans_shift,
+ value << trans_shift);
+ break;
+ case ACM_REQ_N:
+ if (value > 1 || 0 < value || !drvdata->acm.exists)
+ return;
+
+ address_translation(drvdata->acm.force_off[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->acm.force_off[MASK] << trans_shift,
+ value << trans_shift);
+ break;
+ default:
+ break;
+ }
+}
+
+static int gxp_gpio_plreg_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret = GPIO_DIR_IN;
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ case LED_IDENTIFY ... LED_HEALTH_AMBER:
+ case ACM_FORCE_OFF:
+ case ACM_REQ_N:
+ ret = GPIO_DIR_OUT;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_plreg_direction_input(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case FAN1_INST ... FAN16_ID:
+ ret = 0;
+ break;
+ case PWR_BTN_INT ... RESV2:
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_plreg_direction_output(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ case LED_IDENTIFY ... LED_HEALTH_AMBER:
+ case ACM_FORCE_OFF:
+ case ACM_REQ_N:
+ gxp_gpio_plreg_set(chip, offset, value);
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_gpio_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+ u32 trans_offset;
+ u32 trans_shift;
+
+ // Read latched interrupt
+ address_translation(drvdata->grp5_intr_flag.ack[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_read(drvdata->plreg_map, trans_offset, &val);
+
+ //Clear latched interrupt
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->grp5_intr_flag.ack[MASK] << trans_shift,
+ drvdata->grp5_intr_flag.ack[MASK] << trans_shift);
+}
+
+static void gxp_gpio_irq_set_mask(struct irq_data *d, bool set)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_plreg_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ u32 trans_offset;
+ u32 trans_shift;
+
+ address_translation(drvdata->grp5_intr_flag.ack[BYTE],
+ &trans_offset,
+ &trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ (drvdata->grp5_intr_flag.pwrbtn[MASK] |
+ drvdata->grp5_intr_flag.slpintr[MASK]) << trans_shift,
+ set == 1 ? 0 : (drvdata->grp5_intr_flag.pwrbtn[MASK] |
+ drvdata->grp5_intr_flag.slpintr[MASK]) << trans_shift);
+
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->grp5_intr_flag.ack[MASK] << trans_shift,
+ drvdata->grp5_intr_flag.ack[MASK] << trans_shift);
+}
+
+static void gxp_gpio_irq_mask(struct irq_data *d)
+{
+ gxp_gpio_irq_set_mask(d, false);
+}
+
+static void gxp_gpio_irq_unmask(struct irq_data *d)
+{
+ gxp_gpio_irq_set_mask(d, true);
+}
+
+static int gxp_gpio_set_type(struct irq_data *d, unsigned int type)
+{
+ if (type & IRQ_TYPE_LEVEL_MASK)
+ irq_set_handler_locked(d, handle_level_irq);
+ else
+ irq_set_handler_locked(d, handle_edge_irq);
+
+ return 0;
+}
+
+static irqreturn_t gxp_plreg_irq_handle(int irq, void *_drvdata)
+{
+ struct gxp_plreg_drvdata *drvdata = (struct gxp_plreg_drvdata *)_drvdata;
+ unsigned int val, girq, i;
+
+ /* handle plreg interrupt group5 */
+ val = readb(drvdata->base + drvdata->grp5_intr_flag.ack[BYTE]);
+
+ for_each_set_bit(i, (unsigned long *)&val, 3) {
+ girq = irq_find_mapping(drvdata->gpio_chip.irq.domain,
+ i + PLREG_INT_GRP5_PIN_BASE);
+ generic_handle_irq(girq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct gpio_chip plreg_chip = {
+ .label = "gxp-plreg",
+ .owner = THIS_MODULE,
+ .get = gxp_gpio_plreg_get,
+ .set = gxp_gpio_plreg_set,
+ .get_direction = gxp_gpio_plreg_get_direction,
+ .direction_input = gxp_gpio_plreg_direction_input,
+ .direction_output = gxp_gpio_plreg_direction_output,
+ .base = -1,
+ //.can_sleep = true,
+};
+
+static struct irq_chip gxp_gpio_irqchip = {
+ .name = "gxp-plreg",
+ .irq_ack = gxp_gpio_irq_ack,
+ .irq_mask = gxp_gpio_irq_mask,
+ .irq_unmask = gxp_gpio_irq_unmask,
+ .irq_set_type = gxp_gpio_set_type,
+};
+
+static const struct of_device_id gxp_plreg_of_match[] = {
+ { .compatible = "hpe,gxp-plreg" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gxp_plreg_of_match);
+
+static int gxp_plreg_probe(struct platform_device *pdev)
+{
+ int i;
+ int j;
+ int ret;
+ struct gxp_plreg_drvdata *drvdata;
+ struct resource *res;
+ struct gpio_irq_chip *girq;
+ struct device_node *np;
+ char name_buf[10];
+ u32 trans_offset;
+ u32 trans_shift;
+
+ drvdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct gxp_plreg_drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ drvdata->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ drvdata->plreg_map = syscon_regmap_lookup_by_compatible("hpe,gxp-plreg");
+ if (IS_ERR(drvdata->plreg_map)) {
+ dev_err(&pdev->dev, "Unable to find plreg regmap\n");
+ return PTR_ERR(drvdata->plreg_map);
+ }
+
+ /* Supply driver with information to access specific offsets in plreg */
+ for (i = 0; i < MAX_FAN; i++) {
+ /* Find Fan Children */
+ snprintf(name_buf, sizeof(name_buf), "fan%d", i + 1);
+ np = of_get_child_by_name(pdev->dev.of_node, name_buf);
+
+ if (np) {
+ /* For each child there should be 3 fan properties */
+ if (of_property_read_u32(np, "inst",
+ &drvdata->fan[i].inst)) {
+ dev_err(&pdev->dev, "%s is missing its 'inst' property\n",
+ name_buf);
+ return -ENODEV;
+ }
+
+ if (of_property_read_u32(np, "fail",
+ &drvdata->fan[i].fail)) {
+ dev_err(&pdev->dev, "%s is missing its 'fail' property\n",
+ name_buf);
+ return -ENODEV;
+ }
+ if (of_property_read_u32(np, "id",
+ &drvdata->fan[i].id)) {
+ dev_err(&pdev->dev, "%s is missing its 'id' property\n",
+ name_buf);
+ return -ENODEV;
+ }
+ if (of_property_read_u32(np, "bit",
+ &drvdata->fan[i].bit)) {
+ dev_err(&pdev->dev, "%s is missing its 'bit' property\n",
+ name_buf);
+ return -ENODEV;
+ }
+ } else {
+ dev_warn(&pdev->dev, "%pOF is missing its '%s' node\n", np, name_buf);
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "healthled");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'healthled' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "red", i,
+ &drvdata->health_led.red[i])) {
+ dev_err(&pdev->dev, "healthled is missing its 'red' property index %d\n",
+ i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "amber", i,
+ &drvdata->health_led.amber[i])) {
+ dev_err(&pdev->dev, "healthled is missing its 'amber' property index %d\n",
+ i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "green", i,
+ &drvdata->health_led.green[i])) {
+ dev_err(&pdev->dev, "healthled is missing its 'green' property index %d\n",
+ i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i < IOP_LED_QUANTITY; i++) {
+ /* Find iopLed Children */
+ snprintf(name_buf, sizeof(name_buf), "iopled%d", i + 1);
+ np = of_get_child_by_name(pdev->dev.of_node, name_buf);
+
+ if (np) {
+ /* For each child there should be 1 property */
+ for (j = 0; j <= MASK; j++) {
+ if (of_property_read_u32_index(np, "on", j,
+ &drvdata->iop_led[i].iop_led[j]
+ )) {
+ dev_err(&pdev->dev, "%s is missing its 'on' property index %d\n",
+ name_buf, j);
+ return -ENODEV;
+ }
+ }
+ } else {
+ dev_err(&pdev->dev, "%pOF is missing its '%s' node\n", np, name_buf);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "identifyled");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'identifyled' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "on", i, &drvdata->identify_led.on[i])) {
+ dev_err(&pdev->dev, "identifyled is missing its 'on' property index %d\n",
+ i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "off", i, &drvdata->identify_led.off[i])) {
+ dev_err(&pdev->dev, "identifyled is missing its 'off' property index %d\n",
+ i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "acm");
+ if (!np) {
+ dev_warn(&pdev->dev, "%pOF is missing its 'acm' node\n", np);
+ } else {
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "forceoff", i,
+ &drvdata->acm.force_off[i])) {
+ dev_err(&pdev->dev,
+ "acm is missing its 'forceoff' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "removed", i,
+ &drvdata->acm.removed[i])) {
+ dev_err(&pdev->dev,
+ "acm is missing its 'removed' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "unlatchreq", i,
+ &drvdata->acm.unlatch_req[i])) {
+ dev_err(&pdev->dev,
+ "acm is missing its 'unlatchreq' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+ drvdata->acm.exists = 1;
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "serverid");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'serverid' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "upper", i,
+ &drvdata->server_id.upper[i])) {
+ dev_err(&pdev->dev,
+ "serverid is missing its 'upper' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "lower", i, &drvdata->server_id.lower[i])) {
+ dev_err(&pdev->dev,
+ "serverid is missing its 'lower' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "sideband");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'sideband' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "disabled", i,
+ &drvdata->sideband.disabled[i])) {
+ dev_err(&pdev->dev,
+ "sideband is missing its 'disabled' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "embedded", i,
+ &drvdata->sideband.embedded[i])) {
+ dev_err(&pdev->dev,
+ "sideband is missing its 'embedded' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "adaptive", i,
+ &drvdata->sideband.adaptive[i])) {
+ dev_err(&pdev->dev,
+ "sideband is missing its 'adaptive' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "grp5intflag");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'grp5intflag' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "ack", i, &drvdata->grp5_intr_flag.ack[i])) {
+ dev_err(&pdev->dev,
+ "grp5intflag is missing its 'ack' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "pwrbtn", i,
+ &drvdata->grp5_intr_flag.pwrbtn[i])) {
+ dev_err(&pdev->dev,
+ "grp5intflag is missing its 'pwrbtn' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "uidpress", i,
+ &drvdata->grp5_intr_flag.uidpress[i])) {
+ dev_err(&pdev->dev,
+ "grp5intflag is missing its 'uid' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "slpint", i,
+ &drvdata->grp5_intr_flag.slpintr[i])) {
+ dev_err(&pdev->dev,
+ "grp5intflag is missing its 'slpint' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "grp5intmask");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'grp5intmask' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "pwrbtn", i,
+ &drvdata->grp5_intr_mask.pwrbtn[i])) {
+ dev_err(&pdev->dev,
+ "grp5intmask is missing its 'pwrbtn' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "slpint", i,
+ &drvdata->grp5_intr_mask.slpintr[i])) {
+ dev_err(&pdev->dev,
+ "grp5intmask is missing its 'slpint' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "grpintsmasks");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'grpintsmasks' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "grp5", i,
+ &drvdata->grp_intr_masks.grp5[i])) {
+ dev_err(&pdev->dev,
+ "grpintsmasks is missing its 'grp5' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "grpintsflags");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'grpintsflags' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= MASK; i++) {
+ if (of_property_read_u32_index(np, "grp5", i,
+ &drvdata->grp_intr_flags.grp5[i])) {
+ dev_err(&pdev->dev,
+ "grp5intsflags is missing its 'grp5' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ np = of_get_child_by_name(pdev->dev.of_node, "pwrbtn");
+ if (!np) {
+ dev_err(&pdev->dev, "%pOF is missing its 'pwrbtn' node\n", np);
+ return -ENODEV;
+ }
+
+ for (i = 0; i <= VALUE; i++) {
+ if (of_property_read_u32_index(np, "latch", i, &drvdata->pwrbtn.latch[i])) {
+ dev_err(&pdev->dev, "pwrbtn is missing its 'latch' property index %d\n", i);
+ return -ENODEV;
+ }
+ }
+
+ drvdata->gpio_chip = plreg_chip;
+ drvdata->gpio_chip.ngpio = 100;
+ drvdata->gpio_chip.parent = &pdev->dev;
+
+ girq = &drvdata->gpio_chip.irq;
+ girq->chip = &gxp_gpio_irqchip;
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_edge_irq;
+ /* Set up interrupt from PLREG Group 5 Mask */
+ address_translation(drvdata->grp_intr_flags.grp5[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->grp_intr_flags.grp5[MASK] << trans_shift,
+ drvdata->grp_intr_flags.grp5[MASK] << trans_shift);
+ address_translation(drvdata->grp_intr_masks.grp5[BYTE],
+ &trans_offset,
+ &trans_shift);
+ regmap_update_bits(drvdata->plreg_map, trans_offset,
+ drvdata->grp_intr_masks.grp5[MASK] << trans_shift,
+ 0x00);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Get irq from platform fail - %d\n", ret);
+ return ret;
+ }
+ drvdata->irq = ret;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->irq, gxp_plreg_irq_handle, IRQF_SHARED,
+ "gxp-plreg", drvdata);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "IRQ handler failed - %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_register(&pdev->dev, drvdata);
+ if (ret < 0) {
+ dev_warn(&pdev->dev, "Unable to register sysfs\n");
+ return ret;
+ }
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->gpio_chip, NULL);
+ if (ret < 0)
+ dev_err(&pdev->dev, "Could not register gpiochip for plreg, %d\n", ret);
+
+ client_data = *drvdata;
+
+ dev_info(&pdev->dev, "HPE GXP PLREG driver loaded.\n");
+
+ return 0;
+}
+
+static struct platform_driver gxp_plreg_driver = {
+ .probe = gxp_plreg_probe,
+ .driver = {
+ .name = "gxp-plreg",
+ .of_match_table = of_match_ptr(gxp_plreg_of_match),
+ },
+};
+module_platform_driver(gxp_plreg_driver);
+
+MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
+MODULE_DESCRIPTION("HPE GXP Programmable Logic Registers Driver");
new file mode 100644
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/io.h>
+
+struct class *soc_class;
+
+static int __init gxp_soclib_init(void)
+{
+ soc_class = class_create(THIS_MODULE, "soc");
+ if (IS_ERR(soc_class))
+ return PTR_ERR(soc_class);
+ return 0;
+}
+
+module_init(gxp_soclib_init);
+
new file mode 100644
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright (C) 2019 Hewlett-Packard Development Company, L.P.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __GXP_SOCLIB_H__
+#define __GXP_SOCLIB_H__
+
+extern struct class *soc_class;
+
+#endif
new file mode 100644
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0=or-later */
+/* Copyright (C) 2022 Hewlett-Packard Development Company, L.P. */
+
+#ifndef _LINUX_SOC_HPE_GXP_
+#define _LINUX_SOC_HPE_GXP_H_
+
+#define PLREG_SIDEBAND_UNKNOWN 0x00
+#define PLREG_SIDEBAND_DISABLED 0x01
+#define PLREG_SIDEBAND_EMBEDDED 0x02
+#define PLREG_SIDEBAND_ADAPTIVE 0x03
+
+#define GPIO_DIR_OUT 0
+#define GPIO_DIR_IN 1
+
+#endif /* _LINUX_SOC_HPE_GXP_H_ */