From: Nick Hawkins <[email protected]>
Note: The previous version of this patchset was titled "Add GPIO and PSU
support". Based on feedback and in an effort to reduce size PSU has been
removed. Link:
https://lore.kernel.org/all/[email protected]/
The GXP SoC supports GPIO on multiple interfaces. The interfaces are
CPLD and Host. The GPIOs is a combination of both physical and virtual
I/O across the interfaces. The gpio-gxp driver specifically covers the
CSM(physical), FN2(virtual), and VUHC(virtual) which are the host. The
gpio-gxp-pl driver covers the CPLD which takes physical I/O from the
board and shares it with GXP via a propriety interface that maps the I/O
onto a specific register area of the GXP. The drivers both support
interrupts but from different interrupt parents.
There is a need for both the host OpenBMC and the gxp-fan-ctrl driver to
access the same GPIO information from the CPLD. The OpenBMC stack is
reacting to changes in GPIOs and taking action. This requires it to hold
the GPIO which creates a problem where both the host and linux cannot
have the same GPIO. Thus an attempt to remedy this was to add a shared
variable between the GPIO driver and the fan control driver to provide
fan presence and failure information. This is why hwmon has been included
in this patchset.
---
Changes since v1:
*Removed ARM device tree changes and defconfig changes to reduce
patchset size
*Removed GXP PSU changes to reduce patchset size
*Corrected hpe,gxp-gpio YAML file based on feedback
*Created new gpio-gxp-pl file to reduce complexity
*Separated code into two files to keep size down: gpio-gxp.c and
gpio-gxp-pl.c
*Fixed Kconfig indentation as well as add new entry for gpio-gxp-pl
*Removed use of linux/of.h and linux/of_device.h
*Added mod_devicetable.h and property.h
*Fixed indentation of defines and uses consistent number of digits
*Corrected defines with improper GPIO_ namespace.
*For masks now use BIT()
*Added comment for PLREG offsets
*Move gpio_chip to be first in structure
*Calculate offset for high and low byte GPIO reads instead of having
H(High) and L(Low) letters added to the variables.
*Removed repeditive use of "? 1 : 0"
*Switched to handle_bad_irq()
*Removed improper bailout on gpiochip_add_data
*Used GENMASK to arm interrupts
*Removed use of of_match_device
*fixed sizeof in devm_kzalloc
*Added COMPILE_TEST to Kconfig
*Added dev_err_probe where applicable
*Removed unecessary parent and compatible checks
Nick Hawkins (5):
dt-bindings: gpio: Add HPE GXP GPIO
gpio: gxp: Add HPE GXP GPIO
dt-bindings: hwmon: hpe,gxp-fanctrl: remove fn2 and pl regs
hwmon: (gxp_fan_ctrl) Provide fan info via gpio
MAINTAINERS: hpe: Add GPIO
.../bindings/gpio/hpe,gxp-gpio.yaml | 190 ++++++
.../bindings/hwmon/hpe,gxp-fan-ctrl.yaml | 16 +-
MAINTAINERS | 2 +
drivers/gpio/Kconfig | 18 +
drivers/gpio/Makefile | 2 +
drivers/gpio/gpio-gxp-pl.c | 536 +++++++++++++++
drivers/gpio/gpio-gxp.c | 637 ++++++++++++++++++
drivers/hwmon/Kconfig | 2 +-
drivers/hwmon/gxp-fan-ctrl.c | 61 +-
drivers/hwmon/gxp-gpio.h | 13 +
10 files changed, 1409 insertions(+), 68 deletions(-)
create mode 100644 Documentation/devicetree/bindings/gpio/hpe,gxp-gpio.yaml
create mode 100644 drivers/gpio/gpio-gxp-pl.c
create mode 100644 drivers/gpio/gpio-gxp.c
create mode 100644 drivers/hwmon/gxp-gpio.h
--
2.17.1
From: Nick Hawkins <[email protected]>
The GXP SoC supports GPIO on multiple interfaces. The interfaces are
CPLD and Host. The GPIOs is a combination of both physical and virtual
I/O across the interfaces. The gpio-gxp driver specifically covers the
CSM(physical), FN2(virtual), and VUHC(virtual) which are the host. The
gpio-gxp-pl driver covers the CPLD which takes physical I/O from the
board and shares it with GXP via a propriety interface that maps the I/O
onto a specific register area of the GXP. The drivers both support
interrupts but from different interrupt parents.
Signed-off-by: Nick Hawkins <[email protected]>
---
v2:
*Separated code into two files to keep size down: gpio-gxp.c and
gpio-gxp-pl.c
*Fixed Kconfig indentation as well as add new entry for gpio-gxp-pl
*Removed use of linux/of.h and linux/of_device.h
*Added mod_devicetable.h and property.h
*Fixed indentation of defines and uses consistent number of digits
*Corrected defines with improper GPIO_ namespace.
*For masks now use BIT()
*Added comment for PLREG offsets
*Move gpio_chip to be first in structure
*Calculate offset for high and low byte GPIO reads instead of having
H(High) and L(Low) letters added to the variables.
*Removed repeditive use of "? 1 : 0"
*Switched to handle_bad_irq()
*Removed improper bailout on gpiochip_add_data
*Used GENMASK to arm interrupts
*Removed use of of_match_device
*fixed sizeof in devm_kzalloc
*Added COMPILE_TEST to Kconfig
*Added dev_err_probe
*Removed unecessary parent and compatible checks
---
drivers/gpio/Kconfig | 18 ++
drivers/gpio/Makefile | 2 +
drivers/gpio/gpio-gxp-pl.c | 536 +++++++++++++++++++++++++++++++
drivers/gpio/gpio-gxp.c | 637 +++++++++++++++++++++++++++++++++++++
4 files changed, 1193 insertions(+)
create mode 100644 drivers/gpio/gpio-gxp-pl.c
create mode 100644 drivers/gpio/gpio-gxp.c
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 13be729710f2..b0a24ef18392 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1235,6 +1235,24 @@ config HTC_EGPIO
several HTC phones. It provides basic support for input
pins, output pins, and IRQs.
+config GPIO_GXP
+ tristate "GXP GPIO support"
+ depends on ARCH_HPE_GXP || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to support GXP GPIO controllers. It provides
+ support for the multiple GPIO interfaces available to be
+ available to the Host.
+
+config GPIO_GXP_PL
+ tristate "GXP GPIO PL support"
+ depends on ARCH_HPE_GXP || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ help
+ Say Y here to support GXP GPIO PL controller. It provides
+ support for the GPIO PL interface available to be
+ available to the Host.
+
config GPIO_JANZ_TTL
tristate "Janz VMOD-TTL Digital IO Module"
depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index c048ba003367..a401dd472c93 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -63,6 +63,8 @@ obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
+obj-$(CONFIG_GPIO_GXP) += gpio-gxp.o
+obj-$(CONFIG_GPIO_GXP_PL) += gpio-gxp-pl.o
obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o
obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o
obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o
diff --git a/drivers/gpio/gpio-gxp-pl.c b/drivers/gpio/gpio-gxp-pl.c
new file mode 100644
index 000000000000..3b27848d6bfc
--- /dev/null
+++ b/drivers/gpio/gpio-gxp-pl.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Specific offsets in CPLD registers for interrupts */
+#define PLREG_INT_GRP_STAT_MASK 0x08
+#define PLREG_INT_HI_PRI_EN 0x0C
+#define PLREG_INT_GRP5_BASE 0x31
+#define PLREG_INT_GRP6_BASE 0x35
+#define PLREG_INT_GRP5_FLAG 0x30
+#define PLREG_INT_GRP6_FLAG 0x34
+
+/* Specific bits to enable Group 4 and Group 5 interrupts */
+#define PLREG_GRP4_GRP5_MASK GENMASK(5, 4)
+
+/* Specific offsets in CPLD registers */
+#define PLREG_IOP_LED 0x04
+#define PLREG_IDENT_LED 0x05
+#define PLREG_HEALTH_LED 0x0D
+#define PLREG_PSU_INST 0x19
+#define PLREG_PSU_AC 0x1B
+#define PLREG_PSU_DC 0x1C
+#define PLREG_FAN_INST 0x27
+#define PLREG_FAN_FAIL 0x29
+
+#define GXP_GPIO_DIR_OUT 0x00
+#define GXP_GPIO_DIR_IN 0x01
+
+enum pl_gpio_pn {
+ IOP_LED1 = 0,
+ IOP_LED2 = 1,
+ IOP_LED3 = 2,
+ IOP_LED4 = 3,
+ IOP_LED5 = 4,
+ IOP_LED6 = 5,
+ IOP_LED7 = 6,
+ IOP_LED8 = 7,
+ FAN1_INST = 8,
+ FAN2_INST = 9,
+ FAN3_INST = 10,
+ FAN4_INST = 11,
+ FAN5_INST = 12,
+ FAN6_INST = 13,
+ FAN7_INST = 14,
+ FAN8_INST = 15,
+ FAN1_FAIL = 16,
+ FAN2_FAIL = 17,
+ FAN3_FAIL = 18,
+ FAN4_FAIL = 19,
+ FAN5_FAIL = 20,
+ FAN6_FAIL = 21,
+ FAN7_FAIL = 22,
+ FAN8_FAIL = 23,
+ LED_IDENTIFY = 24,
+ LED_HEALTH_RED = 25,
+ LED_HEALTH_AMBER = 26,
+ PWR_BTN_INT = 27,
+ UID_PRESS_INT = 28,
+ SLP_INT = 29,
+ ACM_FORCE_OFF = 30,
+ ACM_REMOVED = 31,
+ ACM_REQ_N = 32,
+ PSU1_INST = 33,
+ PSU2_INST = 34,
+ PSU3_INST = 35,
+ PSU4_INST = 36,
+ PSU5_INST = 37,
+ PSU6_INST = 38,
+ PSU7_INST = 39,
+ PSU8_INST = 40,
+ PSU1_AC = 41,
+ PSU2_AC = 42,
+ PSU3_AC = 43,
+ PSU4_AC = 44,
+ PSU5_AC = 45,
+ PSU6_AC = 46,
+ PSU7_AC = 47,
+ PSU8_AC = 48,
+ PSU1_DC = 49,
+ PSU2_DC = 50,
+ PSU3_DC = 51,
+ PSU4_DC = 52,
+ PSU5_DC = 53,
+ PSU6_DC = 54,
+ PSU7_DC = 55,
+ PSU8_DC = 56,
+ RESET = 57,
+ NMI_OUT = 58,
+ VPBTN = 59,
+ PGOOD = 60,
+ PERST = 61,
+ POST_COMPLETE = 62,
+};
+
+/* Provide info for fan driver */
+u8 fan_presence;
+EXPORT_SYMBOL(fan_presence);
+
+u8 fan_fail;
+EXPORT_SYMBOL(fan_fail);
+
+/* Remember last PSU config */
+u8 psu_presence;
+
+struct gxp_gpio_drvdata {
+ struct regmap *base;
+ struct regmap *interrupt;
+ struct gpio_chip chip;
+ int irq;
+};
+
+static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev,
+ char *reg_name, u8 bits)
+{
+ struct regmap_config regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ };
+ void __iomem *base;
+
+ if (bits == 8) {
+ regmap_config.reg_bits = 8;
+ regmap_config.reg_stride = 1;
+ regmap_config.val_bits = 8;
+ regmap_config.max_register = 0xff;
+ }
+
+ base = devm_platform_ioremap_resource_byname(pdev, reg_name);
+ if (IS_ERR(base))
+ return ERR_CAST(base);
+
+ regmap_config.name = reg_name;
+
+ return devm_regmap_init_mmio(&pdev->dev, base, ®map_config);
+}
+
+static int gxp_gpio_pl_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+ int ret = 0;
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ regmap_read(drvdata->base, PLREG_IOP_LED, &val);
+ ret = (val & BIT(offset)) ? 1 : 0;
+ break;
+ case FAN1_INST ...FAN8_INST:
+ regmap_read(drvdata->base, PLREG_FAN_INST, &val);
+ fan_presence = (u8)val;
+ ret = (fan_presence & BIT((offset - FAN1_INST))) ? 1 : 0;
+ break;
+ case FAN1_FAIL ... FAN8_FAIL:
+ regmap_read(drvdata->base, PLREG_FAN_FAIL, &val);
+ fan_fail = (u8)val;
+ ret = (fan_fail & BIT((offset - FAN1_FAIL))) ? 1 : 0;
+ break;
+ case PWR_BTN_INT ... SLP_INT:
+ regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val);
+ /* Active low */
+ ret = (val & BIT((offset - PWR_BTN_INT) + 16)) ? 0 : 1;
+ break;
+ case PSU1_INST ... PSU8_INST:
+ regmap_read(drvdata->base, PLREG_PSU_INST, &val);
+ psu_presence = (u8)val;
+ ret = (psu_presence & BIT((offset - PSU1_INST))) ? 1 : 0;
+ break;
+ case PSU1_AC ... PSU8_AC:
+ regmap_read(drvdata->base, PLREG_PSU_AC, &val);
+ ret = (val & BIT((offset - PSU1_AC))) ? 1 : 0;
+ break;
+ case PSU1_DC ... PSU8_DC:
+ regmap_read(drvdata->base, PLREG_PSU_DC, &val);
+ ret = (val & BIT((offset - PSU1_DC))) ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_gpio_pl_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+ switch (offset) {
+ case IOP_LED1 ... IOP_LED8:
+ regmap_update_bits(drvdata->base,
+ PLREG_IOP_LED,
+ BIT(offset),
+ value == 0 ? 0 : BIT(offset));
+ break;
+ case LED_IDENTIFY:
+ regmap_update_bits(drvdata->base,
+ PLREG_IDENT_LED,
+ BIT(7) | BIT(6),
+ value == 0 ? BIT(7) : BIT(7) | BIT(6));
+ break;
+ case LED_HEALTH_RED:
+ regmap_update_bits(drvdata->base,
+ PLREG_HEALTH_LED,
+ BIT(7),
+ value == 0 ? 0 : BIT(7));
+ break;
+ case LED_HEALTH_AMBER:
+ regmap_update_bits(drvdata->base,
+ PLREG_HEALTH_LED,
+ BIT(6),
+ value == 0 ? 0 : BIT(6));
+ break;
+ default:
+ break;
+ }
+}
+
+static int gxp_gpio_pl_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret = GXP_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 = GXP_GPIO_DIR_OUT;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_pl_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case 8 ... 55:
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ case 59 ... 65:
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_pl_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_pl_set(chip, offset, value);
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_gpio_pl_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+
+ /* Read latched interrupt for group 5 */
+ regmap_read(drvdata->interrupt, PLREG_INT_GRP5_FLAG, &val);
+ /* Clear latched interrupt */
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
+ 0xFF, 0xFF);
+
+ /* Read latched interrupt for group 6 */
+ regmap_read(drvdata->interrupt, PLREG_INT_GRP6_FLAG, &val);
+ /* Clear latched interrupt */
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
+ 0xFF, 0xFF);
+}
+
+static void gxp_gpio_pl_irq_set_mask(struct irq_data *d, bool set)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+ BIT(0) | BIT(2), set ? 0 : BIT(0) | BIT(2));
+
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+ BIT(2), set ? 0 : BIT(2));
+}
+
+static void gxp_gpio_pl_irq_mask(struct irq_data *d)
+{
+ gxp_gpio_pl_irq_set_mask(d, false);
+}
+
+static void gxp_gpio_pl_irq_unmask(struct irq_data *d)
+{
+ gxp_gpio_pl_irq_set_mask(d, true);
+}
+
+static int gxp_gpio_irq_init_hw(struct gpio_chip *chip)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+ BIT(0) | BIT(2), 0);
+
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+ BIT(2), 0);
+
+ return 0;
+}
+
+static int gxp_gpio_pl_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_gpio_pl_irq_handle(int irq, void *_drvdata)
+{
+ struct gxp_gpio_drvdata *drvdata = (struct gxp_gpio_drvdata *)_drvdata;
+ unsigned int val, girq, i;
+
+ /* Check group 5 interrupts */
+
+ regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val);
+
+ if (val) {
+ for_each_set_bit(i, (unsigned long *)&val, 3) {
+ girq = irq_find_mapping(drvdata->chip.irq.domain,
+ i + PWR_BTN_INT);
+ generic_handle_irq(girq);
+ }
+
+ /* Clear latched interrupt */
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
+ 0xFF, 0xFF);
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
+ BIT(0) | BIT(2), 0);
+ }
+
+ /* Check group 6 interrupts */
+
+ regmap_read(drvdata->base, PLREG_INT_GRP6_FLAG, &val);
+
+ if (val & BIT(2)) {
+ u8 old_psu = psu_presence;
+
+ regmap_read(drvdata->base, PLREG_PSU_INST, &val);
+ psu_presence = (u8)val;
+
+ if (old_psu != psu_presence) {
+ /* Identify all bits which differs */
+ u8 current_val = psu_presence;
+ u8 old_val = old_psu;
+
+ for (i = 0 ; i < 8 ; i++) {
+ if ((current_val & 0x1) != (old_val & 0x1)) {
+ /* PSU state has changed */
+ girq = irq_find_mapping(drvdata->chip.irq.domain,
+ i + PSU1_INST);
+ if (girq)
+ generic_handle_irq(girq);
+ }
+ current_val = current_val >> 1;
+ old_val = old_val >> 1;
+ }
+ }
+ }
+
+ /* Clear latched interrupt */
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
+ 0xFF, 0xFF);
+ regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
+ BIT(2), 0);
+
+ return IRQ_HANDLED;
+}
+
+static struct gpio_chip plreg_chip = {
+ .label = "gxp_gpio_plreg",
+ .owner = THIS_MODULE,
+ .get = gxp_gpio_pl_get,
+ .set = gxp_gpio_pl_set,
+ .get_direction = gxp_gpio_pl_get_direction,
+ .direction_input = gxp_gpio_pl_direction_input,
+ .direction_output = gxp_gpio_pl_direction_output,
+ .base = -1,
+};
+
+static struct irq_chip gxp_plreg_irqchip = {
+ .name = "gxp_plreg",
+ .irq_ack = gxp_gpio_pl_irq_ack,
+ .irq_mask = gxp_gpio_pl_irq_mask,
+ .irq_unmask = gxp_gpio_pl_irq_unmask,
+ .irq_set_type = gxp_gpio_pl_set_type,
+};
+
+static int gxp_gpio_pl_init(struct platform_device *pdev)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+ struct gpio_irq_chip *girq;
+ int ret;
+ unsigned int val;
+
+ drvdata->base = gxp_gpio_init_regmap(pdev, "base", 8);
+ if (IS_ERR(drvdata->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->base),
+ "failed to map base\n");
+
+ drvdata->interrupt = gxp_gpio_init_regmap(pdev, "interrupt", 8);
+ if (IS_ERR(drvdata->interrupt))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->interrupt),
+ "failed to map interrupt base\n");
+
+ regmap_read(drvdata->base, PLREG_FAN_INST, &val);
+ fan_presence = (u8)val;
+
+ regmap_read(drvdata->base, PLREG_FAN_FAIL, &val);
+ fan_fail = (u8)val;
+
+ regmap_read(drvdata->base, PLREG_PSU_INST, &val);
+ psu_presence = (u8)val;
+
+ drvdata->chip = plreg_chip;
+ drvdata->chip.ngpio = 57;
+ drvdata->chip.parent = &pdev->dev;
+
+ girq = &drvdata->chip.irq;
+ girq->chip = &gxp_plreg_irqchip;
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ girq->init_hw = gxp_gpio_irq_init_hw;
+
+ ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->chip, drvdata);
+ if (ret < 0)
+ dev_err_probe(&pdev->dev, ret, "Could not register gpiochip for plreg\n");
+
+ regmap_update_bits(drvdata->base,
+ PLREG_INT_HI_PRI_EN,
+ PLREG_GRP4_GRP5_MASK,
+ PLREG_GRP4_GRP5_MASK);
+ regmap_update_bits(drvdata->base,
+ PLREG_INT_GRP_STAT_MASK,
+ PLREG_GRP4_GRP5_MASK,
+ 0x00);
+ regmap_read(drvdata->base, PLREG_INT_HI_PRI_EN, &val);
+ regmap_read(drvdata->base, PLREG_INT_GRP_STAT_MASK, &val);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Get irq from platform fail\n");
+
+ drvdata->irq = ret;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_pl_irq_handle,
+ IRQF_SHARED, "gxp-pl", drvdata);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id gxp_gpio_of_match[] = {
+ { .compatible = "hpe,gxp-gpio-pl" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gxp_gpio_of_match);
+
+static int gxp_gpio_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct gxp_gpio_drvdata *drvdata;
+
+ /* Initialize global vars */
+ fan_presence = 0;
+ fan_fail = 0;
+ psu_presence = 0;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ ret = gxp_gpio_pl_init(pdev);
+
+ return ret;
+}
+
+static struct platform_driver gxp_gpio_driver = {
+ .driver = {
+ .name = "gxp-gpio-pl",
+ .of_match_table = gxp_gpio_of_match,
+ },
+ .probe = gxp_gpio_probe,
+};
+module_platform_driver(gxp_gpio_driver);
+
+MODULE_AUTHOR("Nick Hawkins <[email protected]>");
+MODULE_DESCRIPTION("GPIO PL interface for GXP");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpio/gpio-gxp.c b/drivers/gpio/gpio-gxp.c
new file mode 100644
index 000000000000..ed6d8577e6b7
--- /dev/null
+++ b/drivers/gpio/gpio-gxp.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define GPIDAT 0x040
+#define GPODAT 0x0b0
+#define GPODAT2 0x0f8
+#define GPOOWN 0x110
+#define GPOOWN2 0x118
+#define ASR_OFS 0x05c
+
+#define GXP_GPIO_DIR_OUT 0
+#define GXP_GPIO_DIR_IN 1
+
+#define PGOOD_MASK BIT(0)
+
+struct gxp_gpio_drvdata {
+ struct gpio_chip chip;
+ struct regmap *csm_map;
+ void __iomem *fn2_vbtn;
+ struct regmap *fn2_stat;
+ struct regmap *vuhc0_map;
+ int irq;
+};
+
+/*
+ * Note: Instead of definining all PINs here are the select few that
+ * are specifically defined in DTS and offsets are used here.
+ */
+enum gxp_gpio_pn {
+ RESET = 192,
+ VPBTN = 210, /* aka POWER_OK */
+ PGOOD = 211, /* aka PS_PWROK */
+ PERST = 212, /* aka PCIERST */
+ POST_COMPLETE = 213,
+};
+
+static int gxp_gpio_csm_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ int ret = 0;
+
+ switch (offset) {
+ case 0 ... 31:
+ regmap_read(drvdata->csm_map, GPIDAT, &ret);
+ ret = (ret & BIT(offset));
+ break;
+ case 32 ... 63:
+ regmap_read(drvdata->csm_map, GPIDAT + 0x20, &ret);
+ ret = (ret & BIT(offset - 32));
+ break;
+ case 64 ... 95:
+ regmap_read(drvdata->csm_map, GPODAT, &ret);
+ ret = (ret & BIT(offset - 64));
+ break;
+ case 96 ... 127:
+ regmap_read(drvdata->csm_map, GPODAT + 0x04, &ret);
+ ret = (ret & BIT(offset - 96));
+ break;
+ case 128 ... 159:
+ regmap_read(drvdata->csm_map, GPODAT2, &ret);
+ ret = (ret & BIT(offset - 128));
+ break;
+ case 160 ... 191:
+ regmap_read(drvdata->csm_map, GPODAT2 + 0x04, &ret);
+ ret = (ret & BIT(offset - 160));
+ break;
+ case RESET:
+ /* SW_RESET */
+ regmap_read(drvdata->csm_map, ASR_OFS, &ret);
+ ret = (ret & BIT(15));
+ break;
+ default:
+ break;
+ }
+
+ /* Return either 1 or 0 */
+ return (ret ? 1 : 0);
+}
+
+static void gxp_gpio_csm_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ u32 tmp;
+
+ switch (offset) {
+ case 64 ... 95:
+ /* Keep ownership setting */
+ regmap_read(drvdata->csm_map, GPOOWN, &tmp);
+ tmp = (tmp & BIT(offset - 64)) ? 1 : 0;
+
+ regmap_update_bits(drvdata->csm_map, GPOOWN,
+ BIT(offset - 64), BIT(offset - 64));
+ regmap_update_bits(drvdata->csm_map, GPODAT,
+ BIT(offset - 64), value ? BIT(offset - 64) : 0);
+
+ /* Restore ownership setting */
+ regmap_update_bits(drvdata->csm_map, GPOOWN,
+ BIT(offset - 64), tmp ? BIT(offset - 64) : 0);
+ break;
+ case 96 ... 127:
+ /* Keep ownership setting */
+ regmap_read(drvdata->csm_map, GPOOWN + 0x04, &tmp);
+ tmp = (tmp & BIT(offset - 96)) ? 1 : 0;
+
+ regmap_update_bits(drvdata->csm_map, GPOOWN + 0x04,
+ BIT(offset - 96), BIT(offset - 96));
+ regmap_update_bits(drvdata->csm_map, GPODAT + 0x04,
+ BIT(offset - 96), value ? BIT(offset - 96) : 0);
+
+ /* Restore ownership setting */
+ regmap_update_bits(drvdata->csm_map, GPOOWN + 0x04,
+ BIT(offset - 96), tmp ? BIT(offset - 96) : 0);
+ break;
+ case 128 ... 159:
+ /* Keep ownership setting */
+ regmap_read(drvdata->csm_map, GPOOWN2, &tmp);
+ tmp = (tmp & BIT(offset - 128)) ? 1 : 0;
+
+ regmap_update_bits(drvdata->csm_map, GPOOWN2,
+ BIT(offset - 128), BIT(offset - 128));
+ regmap_update_bits(drvdata->csm_map, GPODAT2,
+ BIT(offset - 128), value ? BIT(offset - 128) : 0);
+
+ /* Restore ownership setting */
+ regmap_update_bits(drvdata->csm_map, GPOOWN2,
+ BIT(offset - 128), tmp ? BIT(offset - 128) : 0);
+ break;
+ case 160 ... 191:
+ /* Keep ownership setting */
+ regmap_read(drvdata->csm_map, GPOOWN2 + 0x04, &tmp);
+ tmp = (tmp & BIT(offset - 160)) ? 1 : 0;
+
+ regmap_update_bits(drvdata->csm_map, GPOOWN2 + 0x04,
+ BIT(offset - 160), BIT(offset - 160));
+ regmap_update_bits(drvdata->csm_map, GPODAT2 + 0x04,
+ BIT(offset - 160), value ? BIT(offset - 160) : 0);
+
+ /* Restore ownership setting */
+ regmap_update_bits(drvdata->csm_map, GPOOWN2 + 0x04,
+ BIT(offset - 160), tmp ? BIT(offset - 160) : 0);
+ break;
+ case 192:
+ if (value) {
+ regmap_update_bits(drvdata->csm_map, ASR_OFS,
+ BIT(0), BIT(0));
+ regmap_update_bits(drvdata->csm_map, ASR_OFS,
+ BIT(15), BIT(15));
+ } else {
+ regmap_update_bits(drvdata->csm_map, ASR_OFS,
+ BIT(15), 0);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static int gxp_gpio_csm_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = 0;
+
+ switch (offset) {
+ case 0 ... 63:
+ ret = GXP_GPIO_DIR_IN;
+ break;
+ case 64 ... 191:
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ case 192 ... 193:
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ case 194:
+ ret = GXP_GPIO_DIR_IN;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_csm_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case 0 ... 63:
+ ret = 0;
+ break;
+ case 194:
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_csm_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case 64 ... 191:
+ case 192 ... 193:
+ gxp_gpio_csm_set(chip, offset, value);
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_vuhc_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+ int ret = 0;
+
+ if (offset < 8) {
+ regmap_read(drvdata->vuhc0_map, 0x64 + 4 * offset, &val);
+ ret = (val & BIT(13)) ? 1 : 0;
+ }
+
+ return ret;
+}
+
+static void gxp_gpio_vuhc_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ switch (offset) {
+ default:
+ break;
+ }
+}
+
+static int gxp_gpio_vuhc_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = 0;
+
+ switch (offset) {
+ case 0:
+ case 1:
+ case 2:
+ ret = GXP_GPIO_DIR_IN;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_vuhc_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case 0:
+ case 1:
+ case 2:
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_vuhc_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_fn2_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+ int ret = 0;
+
+ switch (offset) {
+ case PGOOD:
+ regmap_read(drvdata->fn2_stat, 0, &val);
+ ret = (val & BIT(24));
+
+ break;
+ case PERST:
+ regmap_read(drvdata->fn2_stat, 0, &val);
+ ret = (val & BIT(25));
+
+ break;
+ default:
+ break;
+ }
+
+ /* Return either 1 or 0 */
+ return (ret ? 1 : 0);
+}
+
+static void gxp_gpio_fn2_set(struct gpio_chip *chip, unsigned int offset,
+ int value)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+ switch (offset) {
+ case VPBTN:
+ writeb(1, drvdata->fn2_vbtn);
+ break;
+ default:
+ break;
+ }
+}
+
+static int gxp_gpio_fn2_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = GXP_GPIO_DIR_IN;
+
+ switch (offset) {
+ case VPBTN:
+ ret = GXP_GPIO_DIR_OUT;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_fn2_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = -EOPNOTSUPP;
+
+ switch (offset) {
+ case PGOOD:
+ case PERST:
+ case POST_COMPLETE:
+ ret = 0;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int gxp_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ int ret = 0;
+
+ if (offset < 200)
+ ret = gxp_gpio_csm_get(chip, offset);
+ else if (offset >= 250 && offset < 300)
+ ret = gxp_gpio_vuhc_get(chip, offset - 250);
+ else if (offset >= 300)
+ ret = gxp_gpio_fn2_get(chip, offset);
+
+ return ret;
+}
+
+static void gxp_gpio_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ if (offset < 200)
+ gxp_gpio_csm_set(chip, offset, value);
+ else if (offset >= 250 && offset < 300)
+ gxp_gpio_vuhc_set(chip, offset - 250, value);
+ else if (offset >= 300)
+ gxp_gpio_fn2_set(chip, offset, value);
+}
+
+static int gxp_gpio_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = 0;
+
+ if (offset < 200)
+ ret = gxp_gpio_csm_get_direction(chip, offset);
+ else if (offset >= 250 && offset < 300)
+ ret = gxp_gpio_vuhc_get_direction(chip, offset - 250);
+ else if (offset >= 300)
+ ret = gxp_gpio_fn2_get_direction(chip, offset);
+
+ return ret;
+}
+
+static int gxp_gpio_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret = 0;
+
+ if (offset < 200)
+ ret = gxp_gpio_csm_direction_input(chip, offset);
+ else if (offset >= 250 && offset < 300)
+ ret = gxp_gpio_vuhc_direction_input(chip, offset - 250);
+ else if (offset >= 300)
+ ret = gxp_gpio_fn2_direction_input(chip, offset);
+
+ return ret;
+}
+
+static int gxp_gpio_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ int ret = 0;
+
+ if (offset < 200)
+ ret = gxp_gpio_csm_direction_output(chip, offset, value);
+ else if (offset >= 250 && offset < 300)
+ ret = gxp_gpio_vuhc_direction_output(chip, offset - 250, value);
+ return ret;
+}
+
+static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev,
+ char *reg_name, u8 bits)
+{
+ struct regmap_config regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ };
+ void __iomem *base;
+
+ if (bits == 8) {
+ regmap_config.reg_bits = 8;
+ regmap_config.reg_stride = 1;
+ regmap_config.val_bits = 8;
+ regmap_config.max_register = 0xff;
+ }
+
+ base = devm_platform_ioremap_resource_byname(pdev, reg_name);
+ if (IS_ERR(base))
+ return ERR_CAST(base);
+
+ regmap_config.name = reg_name;
+
+ return devm_regmap_init_mmio(&pdev->dev, base, ®map_config);
+}
+
+static void gxp_gpio_fn2_irq_ack(struct irq_data *d)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+ unsigned int val;
+
+ /* Read latched interrupt */
+ regmap_read(drvdata->fn2_stat, 0, &val);
+ /* Clear latched interrupt */
+ regmap_update_bits(drvdata->fn2_stat, 0,
+ 0xFFFF, 0xFFFF);
+}
+
+#define FN2_SEVMASK BIT(2)
+static void gxp_gpio_fn2_irq_set_mask(struct irq_data *d, bool set)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
+
+ regmap_update_bits(drvdata->fn2_stat, FN2_SEVMASK,
+ BIT(0), set ? BIT(0) : 0);
+}
+
+static void gxp_gpio_fn2_irq_mask(struct irq_data *d)
+{
+ gxp_gpio_fn2_irq_set_mask(d, false);
+}
+
+static void gxp_gpio_fn2_irq_unmask(struct irq_data *d)
+{
+ gxp_gpio_fn2_irq_set_mask(d, true);
+}
+
+static int gxp_gpio_fn2_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_gpio_fn2_irq_handle(int irq, void *_drvdata)
+{
+ struct gxp_gpio_drvdata *drvdata = (struct gxp_gpio_drvdata *)_drvdata;
+ unsigned int val, girq;
+
+ regmap_read(drvdata->fn2_stat, 0, &val);
+
+ if (val & PGOOD_MASK) {
+ girq = irq_find_mapping(drvdata->chip.irq.domain, PGOOD);
+ generic_handle_irq(girq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct irq_chip gxp_gpio_irqchip = {
+ .name = "gxp_fn2",
+ .irq_ack = gxp_gpio_fn2_irq_ack,
+ .irq_mask = gxp_gpio_fn2_irq_mask,
+ .irq_unmask = gxp_gpio_fn2_irq_unmask,
+ .irq_set_type = gxp_gpio_fn2_set_type,
+};
+
+static struct gpio_chip common_chip = {
+ .label = "gxp_gpio",
+ .owner = THIS_MODULE,
+ .get = gxp_gpio_get,
+ .set = gxp_gpio_set,
+ .get_direction = gxp_gpio_get_direction,
+ .direction_input = gxp_gpio_direction_input,
+ .direction_output = gxp_gpio_direction_output,
+ .base = 0,
+};
+
+static int gxp_gpio_init(struct platform_device *pdev)
+{
+ struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
+ struct gpio_irq_chip *girq;
+ int ret;
+
+ drvdata->csm_map = gxp_gpio_init_regmap(pdev, "csm", 32);
+ if (IS_ERR(drvdata->csm_map))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->csm_map),
+ "failed to map csm_handle\n");
+
+ drvdata->fn2_vbtn = devm_platform_ioremap_resource_byname(pdev, "fn2-vbtn");
+ if (IS_ERR(drvdata->fn2_vbtn))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->fn2_vbtn),
+ "failed to map fn2_vbtn\n");
+
+ drvdata->fn2_stat = gxp_gpio_init_regmap(pdev, "fn2-stat", 32);
+ if (IS_ERR(drvdata->fn2_stat))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->fn2_stat),
+ "failed to map fn2_stat\n");
+
+ drvdata->vuhc0_map = gxp_gpio_init_regmap(pdev, "vuhc", 32);
+ if (IS_ERR(drvdata->vuhc0_map))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->vuhc0_map),
+ "failed to map vuhc0_map\n");
+
+ girq = &drvdata->chip.irq;
+ girq->chip = &gxp_gpio_irqchip;
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_bad_irq;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "Get irq from platform fail\n");
+
+ drvdata->irq = ret;
+
+ ret = devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_fn2_irq_handle,
+ IRQF_SHARED, "gxp-fn2", drvdata);
+ if (ret < 0)
+ return ret;
+
+ drvdata->chip = common_chip;
+ drvdata->chip.ngpio = 220;
+
+ drvdata->chip.parent = &pdev->dev;
+ ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->chip, NULL);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "Could not register gpiochip for fn2\n");
+
+ return 0;
+}
+
+static const struct of_device_id gxp_gpio_of_match[] = {
+ { .compatible = "hpe,gxp-gpio" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, gxp_gpio_of_match);
+
+static int gxp_gpio_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct gxp_gpio_drvdata *drvdata;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ ret = gxp_gpio_init(pdev);
+
+ return ret;
+}
+
+static struct platform_driver gxp_gpio_driver = {
+ .driver = {
+ .name = "gxp-gpio",
+ .of_match_table = gxp_gpio_of_match,
+ },
+ .probe = gxp_gpio_probe,
+};
+module_platform_driver(gxp_gpio_driver);
+
+MODULE_AUTHOR("Nick Hawkins <[email protected]>");
+MODULE_DESCRIPTION("GPIO interface for GXP");
+MODULE_LICENSE("GPL");
--
2.17.1
From: Nick Hawkins <[email protected]>
The fan driver now receives fan data from GPIO via a shared variable.
This is a necessity as the Host and the driver need access to the same
information. Part of the changes includes removing a system power check
as the GPIO driver needs it to report power state to host.
Signed-off-by: Nick Hawkins <[email protected]>
---
v2:
*Removed use of shared functions to GPIO in favor of a shared variable
*Added build dependency on GXP GPIO driver.
---
drivers/hwmon/Kconfig | 2 +-
drivers/hwmon/gxp-fan-ctrl.c | 61 +++++-------------------------------
drivers/hwmon/gxp-gpio.h | 13 ++++++++
3 files changed, 21 insertions(+), 55 deletions(-)
create mode 100644 drivers/hwmon/gxp-gpio.h
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5b3b76477b0e..5c606bea31f9 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -716,7 +716,7 @@ config SENSORS_GPIO_FAN
config SENSORS_GXP_FAN_CTRL
tristate "HPE GXP fan controller"
- depends on ARCH_HPE_GXP || COMPILE_TEST
+ depends on (ARCH_HPE_GXP && GPIO_GXP_PL) || COMPILE_TEST
help
If you say yes here you get support for GXP fan control functionality.
diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c
index 0014b8b0fd41..8555231328d7 100644
--- a/drivers/hwmon/gxp-fan-ctrl.c
+++ b/drivers/hwmon/gxp-fan-ctrl.c
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
-/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
#include <linux/bits.h>
#include <linux/err.h>
@@ -8,51 +8,28 @@
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include "gxp-gpio.h"
#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */
#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */
-#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */
-#define POWER_BIT 24
struct gxp_fan_ctrl_drvdata {
- void __iomem *base;
- void __iomem *plreg;
- void __iomem *fn2;
+ void __iomem *base;
};
static bool fan_installed(struct device *dev, int fan)
{
- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
- u8 val;
-
- val = readb(drvdata->plreg + OFS_FAN_INST);
-
- return !!(val & BIT(fan));
+ return !!(fan_presence & BIT(fan));
}
static long fan_failed(struct device *dev, int fan)
{
- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
- u8 val;
-
- val = readb(drvdata->plreg + OFS_FAN_FAIL);
-
- return !!(val & BIT(fan));
+ return !!(fan_fail & BIT(fan));
}
static long fan_enabled(struct device *dev, int fan)
{
- struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
- u32 val;
-
- /*
- * Check the power status as if the platform is off the value
- * reported for the PWM will be incorrect. Report fan as
- * disabled.
- */
- val = readl(drvdata->fn2 + OFS_SEVSTAT);
-
- return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan));
+ return !!(fan_installed(dev, fan));
}
static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val)
@@ -98,20 +75,8 @@ static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val)
static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val)
{
struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
- u32 reg;
- /*
- * Check the power status of the platform. If the platform is off
- * the value reported for the PWM will be incorrect. In this case
- * report a PWM of zero.
- */
-
- reg = readl(drvdata->fn2 + OFS_SEVSTAT);
-
- if (reg & BIT(POWER_BIT))
- *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0;
- else
- *val = 0;
+ *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0;
return 0;
}
@@ -212,18 +177,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev)
return dev_err_probe(dev, PTR_ERR(drvdata->base),
"failed to map base\n");
- drvdata->plreg = devm_platform_ioremap_resource_byname(pdev,
- "pl");
- if (IS_ERR(drvdata->plreg))
- return dev_err_probe(dev, PTR_ERR(drvdata->plreg),
- "failed to map plreg\n");
-
- drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev,
- "fn2");
- if (IS_ERR(drvdata->fn2))
- return dev_err_probe(dev, PTR_ERR(drvdata->fn2),
- "failed to map fn2\n");
-
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
"hpe_gxp_fan_ctrl",
drvdata,
diff --git a/drivers/hwmon/gxp-gpio.h b/drivers/hwmon/gxp-gpio.h
new file mode 100644
index 000000000000..88abe60bbe83
--- /dev/null
+++ b/drivers/hwmon/gxp-gpio.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#ifndef __GPIO_GXP_H__
+#define __GPIO_GXP_H__
+
+#include <linux/err.h>
+
+/* Data provided by GPIO */
+extern u8 fan_presence;
+extern u8 fan_fail;
+
+#endif /* __GPIO_GXP_H__ */
--
2.17.1
On 5/31/23 08:19, [email protected] wrote:
> From: Nick Hawkins <[email protected]>
>
> The fan driver now receives fan data from GPIO via a shared variable.
No, it is not necessary. The driver can and should get the GPIO data using
the gpio API.
> This is a necessity as the Host and the driver need access to the same
> information. Part of the changes includes removing a system power check
> as the GPIO driver needs it to report power state to host.
>
> Signed-off-by: Nick Hawkins <[email protected]>
>
> ---
>
> v2:
> *Removed use of shared functions to GPIO in favor of a shared variable
> *Added build dependency on GXP GPIO driver.
> ---
> drivers/hwmon/Kconfig | 2 +-
> drivers/hwmon/gxp-fan-ctrl.c | 61 +++++-------------------------------
> drivers/hwmon/gxp-gpio.h | 13 ++++++++
> 3 files changed, 21 insertions(+), 55 deletions(-)
> create mode 100644 drivers/hwmon/gxp-gpio.h
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 5b3b76477b0e..5c606bea31f9 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -716,7 +716,7 @@ config SENSORS_GPIO_FAN
>
> config SENSORS_GXP_FAN_CTRL
> tristate "HPE GXP fan controller"
> - depends on ARCH_HPE_GXP || COMPILE_TEST
> + depends on (ARCH_HPE_GXP && GPIO_GXP_PL) || COMPILE_TEST
Compile test will fail badly unless those external variables
are declared.
> help
> If you say yes here you get support for GXP fan control functionality.
>
> diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c
> index 0014b8b0fd41..8555231328d7 100644
> --- a/drivers/hwmon/gxp-fan-ctrl.c
> +++ b/drivers/hwmon/gxp-fan-ctrl.c
> @@ -1,5 +1,5 @@
> // SPDX-License-Identifier: GPL-2.0-only
> -/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
> +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
>
> #include <linux/bits.h>
> #include <linux/err.h>
> @@ -8,51 +8,28 @@
> #include <linux/module.h>
> #include <linux/of_device.h>
> #include <linux/platform_device.h>
> +#include "gxp-gpio.h"
>
> #define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */
> #define OFS_FAN_FAIL 2 /* Is 2 bytes after base */
> -#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */
> -#define POWER_BIT 24
>
> struct gxp_fan_ctrl_drvdata {
> - void __iomem *base;
> - void __iomem *plreg;
> - void __iomem *fn2;
> + void __iomem *base;
> };
>
> static bool fan_installed(struct device *dev, int fan)
> {
> - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
> - u8 val;
> -
> - val = readb(drvdata->plreg + OFS_FAN_INST);
> -
> - return !!(val & BIT(fan));
> + return !!(fan_presence & BIT(fan));
> }
>
> static long fan_failed(struct device *dev, int fan)
> {
> - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
> - u8 val;
> -
> - val = readb(drvdata->plreg + OFS_FAN_FAIL);
> -
> - return !!(val & BIT(fan));
> + return !!(fan_fail & BIT(fan));
> }
>
> static long fan_enabled(struct device *dev, int fan)
> {
> - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
> - u32 val;
> -
> - /*
> - * Check the power status as if the platform is off the value
> - * reported for the PWM will be incorrect. Report fan as
> - * disabled.
> - */
> - val = readl(drvdata->fn2 + OFS_SEVSTAT);
> -
> - return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan));
> + return !!(fan_installed(dev, fan));
Unnecessary () around function call.
> }
>
> static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val)
> @@ -98,20 +75,8 @@ static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val)
> static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val)
> {
> struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev);
> - u32 reg;
>
> - /*
> - * Check the power status of the platform. If the platform is off
> - * the value reported for the PWM will be incorrect. In this case
> - * report a PWM of zero.
> - */
> -
> - reg = readl(drvdata->fn2 + OFS_SEVSTAT);
> -
> - if (reg & BIT(POWER_BIT))
> - *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0;
> - else
> - *val = 0;
> + *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0;
>
> return 0;
> }
> @@ -212,18 +177,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev)
> return dev_err_probe(dev, PTR_ERR(drvdata->base),
> "failed to map base\n");
>
> - drvdata->plreg = devm_platform_ioremap_resource_byname(pdev,
> - "pl");
> - if (IS_ERR(drvdata->plreg))
> - return dev_err_probe(dev, PTR_ERR(drvdata->plreg),
> - "failed to map plreg\n");
> -
> - drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev,
> - "fn2");
> - if (IS_ERR(drvdata->fn2))
> - return dev_err_probe(dev, PTR_ERR(drvdata->fn2),
> - "failed to map fn2\n");
> -
> hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
> "hpe_gxp_fan_ctrl",
> drvdata,
> diff --git a/drivers/hwmon/gxp-gpio.h b/drivers/hwmon/gxp-gpio.h
> new file mode 100644
> index 000000000000..88abe60bbe83
> --- /dev/null
> +++ b/drivers/hwmon/gxp-gpio.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
> +
> +#ifndef __GPIO_GXP_H__
> +#define __GPIO_GXP_H__
> +
> +#include <linux/err.h>
> +
> +/* Data provided by GPIO */
> +extern u8 fan_presence;
> +extern u8 fan_fail;
> +
This is not acceptable. It is way too generic for a global variable, and it
does not use the gpio API. Besides, the variables would have to be declared
in an include file associated with the code introducing them.
If you want to use gpio pins in the hwmon driver, use the gpio API
([devm_]gpiod_get() and associated functions). There are lots of examples
in the kernel showing how to do that.
Guenter
> +#endif /* __GPIO_GXP_H__ */
> This is not acceptable. It is way too generic for a global variable, and it
> does not use the gpio API. Besides, the variables would have to be declared
> in an include file associated with the code introducing them.
> If you want to use gpio pins in the hwmon driver, use the gpio API
> ([devm_]gpiod_get() and associated functions). There are lots of examples
> in the kernel showing how to do that.
Hi Guenter,
Thank you for the feedback. I did try and create a driver for both the fan
and the psu but I had an issue where the host and linux driver both
need to monitor and access it.
I made a brief query about it here to the mailing list.
(Apologies if this is the incorrect way to share a link)
https://lore.kernel.org/all/DM4PR84MB19274817C2D8A751E3218F4888759@DM4PR84MB1927.NAMPRD84.PROD.OUTLOOK.COM/
I am open for trying a different approach, I am just not sure what is
the correct way to proceed.
Is there a way for the driver to temporarily take the GPIO away from the
Host and return it? The host is wanting to hold the GPIO all the time to
monitor for change.
Another thought I had was perhaps having some duplicate I/Os where
there is one for the host consumption and the other for linux kernel
driver consumption.
Thank you for the assistance,
-Nick Hawkins
On 5/31/23 11:17, Hawkins, Nick wrote:
>> This is not acceptable. It is way too generic for a global variable, and it
>> does not use the gpio API. Besides, the variables would have to be declared
>> in an include file associated with the code introducing them.
>
>> If you want to use gpio pins in the hwmon driver, use the gpio API
>> ([devm_]gpiod_get() and associated functions). There are lots of examples
>> in the kernel showing how to do that.
>
> Hi Guenter,
>
> Thank you for the feedback. I did try and create a driver for both the fan
> and the psu but I had an issue where the host and linux driver both
> need to monitor and access it.
>
> I made a brief query about it here to the mailing list.
> (Apologies if this is the incorrect way to share a link)
> https://lore.kernel.org/all/DM4PR84MB19274817C2D8A751E3218F4888759@DM4PR84MB1927.NAMPRD84.PROD.OUTLOOK.COM/
>
> I am open for trying a different approach, I am just not sure what is
> the correct way to proceed.
>
> Is there a way for the driver to temporarily take the GPIO away from the
> Host and return it? The host is wanting to hold the GPIO all the time to
I don't think so.
> monitor for change.
>
If the host wants to own the fan status from gpio pins, it has to live up to
it and own it entirely. The kernel hwmon driver does not have access in that
case.
In a more "normal" world, the hwmon driver would "own" the gpio pin(s)
and user space would listen to associated hwmon attribute events (presumably
fan_enable and fan_fault), either by listening for sysfs attribute events
or via udev or both. Again, if you don't want to do that, and want user space
to have access to the raw gpio pins, you'll have to live with the consequences.
I don't see the need to bypass existing mechanisms just because user space
programmers want direct access to gpio pins.
> Another thought I had was perhaps having some duplicate I/Os where
> there is one for the host consumption and the other for linux kernel
> driver consumption.
>
I neither think this is a good idea nor that it is really necessary.
Again, the desire to have direct user space access to gpio pins is
something you _want_, not something that is really needed.
Guenter
On Wed, May 31, 2023 at 5:23 PM <[email protected]> wrote:
>
> From: Nick Hawkins <[email protected]>
>
> The GXP SoC supports GPIO on multiple interfaces. The interfaces are
> CPLD and Host. The GPIOs is a combination of both physical and virtual
> I/O across the interfaces. The gpio-gxp driver specifically covers the
> CSM(physical), FN2(virtual), and VUHC(virtual) which are the host. The
> gpio-gxp-pl driver covers the CPLD which takes physical I/O from the
> board and shares it with GXP via a propriety interface that maps the I/O
> onto a specific register area of the GXP. The drivers both support
> interrupts but from different interrupt parents.
>
> Signed-off-by: Nick Hawkins <[email protected]>
>
> ---
>
> v2:
> *Separated code into two files to keep size down: gpio-gxp.c and
> gpio-gxp-pl.c
> *Fixed Kconfig indentation as well as add new entry for gpio-gxp-pl
> *Removed use of linux/of.h and linux/of_device.h
> *Added mod_devicetable.h and property.h
> *Fixed indentation of defines and uses consistent number of digits
> *Corrected defines with improper GPIO_ namespace.
> *For masks now use BIT()
> *Added comment for PLREG offsets
> *Move gpio_chip to be first in structure
> *Calculate offset for high and low byte GPIO reads instead of having
> H(High) and L(Low) letters added to the variables.
> *Removed repeditive use of "? 1 : 0"
> *Switched to handle_bad_irq()
> *Removed improper bailout on gpiochip_add_data
> *Used GENMASK to arm interrupts
> *Removed use of of_match_device
> *fixed sizeof in devm_kzalloc
> *Added COMPILE_TEST to Kconfig
> *Added dev_err_probe
> *Removed unecessary parent and compatible checks
> ---
> drivers/gpio/Kconfig | 18 ++
> drivers/gpio/Makefile | 2 +
> drivers/gpio/gpio-gxp-pl.c | 536 +++++++++++++++++++++++++++++++
> drivers/gpio/gpio-gxp.c | 637 +++++++++++++++++++++++++++++++++++++
> 4 files changed, 1193 insertions(+)
> create mode 100644 drivers/gpio/gpio-gxp-pl.c
> create mode 100644 drivers/gpio/gpio-gxp.c
>
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 13be729710f2..b0a24ef18392 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1235,6 +1235,24 @@ config HTC_EGPIO
> several HTC phones. It provides basic support for input
> pins, output pins, and IRQs.
>
> +config GPIO_GXP
> + tristate "GXP GPIO support"
> + depends on ARCH_HPE_GXP || COMPILE_TEST
> + select GPIOLIB_IRQCHIP
> + help
> + Say Y here to support GXP GPIO controllers. It provides
> + support for the multiple GPIO interfaces available to be
> + available to the Host.
> +
> +config GPIO_GXP_PL
> + tristate "GXP GPIO PL support"
> + depends on ARCH_HPE_GXP || COMPILE_TEST
> + select GPIOLIB_IRQCHIP
> + help
> + Say Y here to support GXP GPIO PL controller. It provides
> + support for the GPIO PL interface available to be
> + available to the Host.
> +
> config GPIO_JANZ_TTL
> tristate "Janz VMOD-TTL Digital IO Module"
> depends on MFD_JANZ_CMODIO
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index c048ba003367..a401dd472c93 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -63,6 +63,8 @@ obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
> obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
> obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
> obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
> +obj-$(CONFIG_GPIO_GXP) += gpio-gxp.o
> +obj-$(CONFIG_GPIO_GXP_PL) += gpio-gxp-pl.o
> obj-$(CONFIG_GPIO_GW_PLD) += gpio-gw-pld.o
> obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o
> obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o
> diff --git a/drivers/gpio/gpio-gxp-pl.c b/drivers/gpio/gpio-gxp-pl.c
> new file mode 100644
> index 000000000000..3b27848d6bfc
> --- /dev/null
> +++ b/drivers/gpio/gpio-gxp-pl.c
> @@ -0,0 +1,536 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
> +
> +#include <linux/gpio/driver.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +/* Specific offsets in CPLD registers for interrupts */
> +#define PLREG_INT_GRP_STAT_MASK 0x08
> +#define PLREG_INT_HI_PRI_EN 0x0C
> +#define PLREG_INT_GRP5_BASE 0x31
> +#define PLREG_INT_GRP6_BASE 0x35
> +#define PLREG_INT_GRP5_FLAG 0x30
> +#define PLREG_INT_GRP6_FLAG 0x34
> +
> +/* Specific bits to enable Group 4 and Group 5 interrupts */
> +#define PLREG_GRP4_GRP5_MASK GENMASK(5, 4)
> +
> +/* Specific offsets in CPLD registers */
> +#define PLREG_IOP_LED 0x04
> +#define PLREG_IDENT_LED 0x05
> +#define PLREG_HEALTH_LED 0x0D
> +#define PLREG_PSU_INST 0x19
> +#define PLREG_PSU_AC 0x1B
> +#define PLREG_PSU_DC 0x1C
> +#define PLREG_FAN_INST 0x27
> +#define PLREG_FAN_FAIL 0x29
> +
> +#define GXP_GPIO_DIR_OUT 0x00
> +#define GXP_GPIO_DIR_IN 0x01
> +
> +enum pl_gpio_pn {
> + IOP_LED1 = 0,
> + IOP_LED2 = 1,
> + IOP_LED3 = 2,
> + IOP_LED4 = 3,
> + IOP_LED5 = 4,
> + IOP_LED6 = 5,
> + IOP_LED7 = 6,
> + IOP_LED8 = 7,
> + FAN1_INST = 8,
> + FAN2_INST = 9,
> + FAN3_INST = 10,
> + FAN4_INST = 11,
> + FAN5_INST = 12,
> + FAN6_INST = 13,
> + FAN7_INST = 14,
> + FAN8_INST = 15,
> + FAN1_FAIL = 16,
> + FAN2_FAIL = 17,
> + FAN3_FAIL = 18,
> + FAN4_FAIL = 19,
> + FAN5_FAIL = 20,
> + FAN6_FAIL = 21,
> + FAN7_FAIL = 22,
> + FAN8_FAIL = 23,
> + LED_IDENTIFY = 24,
> + LED_HEALTH_RED = 25,
> + LED_HEALTH_AMBER = 26,
> + PWR_BTN_INT = 27,
> + UID_PRESS_INT = 28,
> + SLP_INT = 29,
> + ACM_FORCE_OFF = 30,
> + ACM_REMOVED = 31,
> + ACM_REQ_N = 32,
> + PSU1_INST = 33,
> + PSU2_INST = 34,
> + PSU3_INST = 35,
> + PSU4_INST = 36,
> + PSU5_INST = 37,
> + PSU6_INST = 38,
> + PSU7_INST = 39,
> + PSU8_INST = 40,
> + PSU1_AC = 41,
> + PSU2_AC = 42,
> + PSU3_AC = 43,
> + PSU4_AC = 44,
> + PSU5_AC = 45,
> + PSU6_AC = 46,
> + PSU7_AC = 47,
> + PSU8_AC = 48,
> + PSU1_DC = 49,
> + PSU2_DC = 50,
> + PSU3_DC = 51,
> + PSU4_DC = 52,
> + PSU5_DC = 53,
> + PSU6_DC = 54,
> + PSU7_DC = 55,
> + PSU8_DC = 56,
> + RESET = 57,
> + NMI_OUT = 58,
> + VPBTN = 59,
> + PGOOD = 60,
> + PERST = 61,
> + POST_COMPLETE = 62,
> +};
> +
> +/* Provide info for fan driver */
> +u8 fan_presence;
> +EXPORT_SYMBOL(fan_presence);
> +
This is awful. Not only do you export a random global variable with no
namespace prefix - that's also not declared in any header - but you
then set this singular global variable from per-device probe().
There's no other mention of this anywhere, so please explain who needs
that and I'm sure we can find a better solution.
Bart
> +u8 fan_fail;
> +EXPORT_SYMBOL(fan_fail);
> +
> +/* Remember last PSU config */
> +u8 psu_presence;
> +
> +struct gxp_gpio_drvdata {
> + struct regmap *base;
> + struct regmap *interrupt;
> + struct gpio_chip chip;
> + int irq;
> +};
> +
> +static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev,
> + char *reg_name, u8 bits)
> +{
> + struct regmap_config regmap_config = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + };
> + void __iomem *base;
> +
> + if (bits == 8) {
> + regmap_config.reg_bits = 8;
> + regmap_config.reg_stride = 1;
> + regmap_config.val_bits = 8;
> + regmap_config.max_register = 0xff;
> + }
> +
> + base = devm_platform_ioremap_resource_byname(pdev, reg_name);
> + if (IS_ERR(base))
> + return ERR_CAST(base);
> +
> + regmap_config.name = reg_name;
> +
> + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config);
> +}
> +
> +static int gxp_gpio_pl_get(struct gpio_chip *chip, unsigned int offset)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + unsigned int val;
> + int ret = 0;
> +
> + switch (offset) {
> + case IOP_LED1 ... IOP_LED8:
> + regmap_read(drvdata->base, PLREG_IOP_LED, &val);
> + ret = (val & BIT(offset)) ? 1 : 0;
> + break;
> + case FAN1_INST ...FAN8_INST:
> + regmap_read(drvdata->base, PLREG_FAN_INST, &val);
> + fan_presence = (u8)val;
> + ret = (fan_presence & BIT((offset - FAN1_INST))) ? 1 : 0;
> + break;
> + case FAN1_FAIL ... FAN8_FAIL:
> + regmap_read(drvdata->base, PLREG_FAN_FAIL, &val);
> + fan_fail = (u8)val;
> + ret = (fan_fail & BIT((offset - FAN1_FAIL))) ? 1 : 0;
> + break;
> + case PWR_BTN_INT ... SLP_INT:
> + regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val);
> + /* Active low */
> + ret = (val & BIT((offset - PWR_BTN_INT) + 16)) ? 0 : 1;
> + break;
> + case PSU1_INST ... PSU8_INST:
> + regmap_read(drvdata->base, PLREG_PSU_INST, &val);
> + psu_presence = (u8)val;
> + ret = (psu_presence & BIT((offset - PSU1_INST))) ? 1 : 0;
> + break;
> + case PSU1_AC ... PSU8_AC:
> + regmap_read(drvdata->base, PLREG_PSU_AC, &val);
> + ret = (val & BIT((offset - PSU1_AC))) ? 1 : 0;
> + break;
> + case PSU1_DC ... PSU8_DC:
> + regmap_read(drvdata->base, PLREG_PSU_DC, &val);
> + ret = (val & BIT((offset - PSU1_DC))) ? 1 : 0;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static void gxp_gpio_pl_set(struct gpio_chip *chip,
> + unsigned int offset, int value)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> +
> + switch (offset) {
> + case IOP_LED1 ... IOP_LED8:
> + regmap_update_bits(drvdata->base,
> + PLREG_IOP_LED,
> + BIT(offset),
> + value == 0 ? 0 : BIT(offset));
> + break;
> + case LED_IDENTIFY:
> + regmap_update_bits(drvdata->base,
> + PLREG_IDENT_LED,
> + BIT(7) | BIT(6),
> + value == 0 ? BIT(7) : BIT(7) | BIT(6));
> + break;
> + case LED_HEALTH_RED:
> + regmap_update_bits(drvdata->base,
> + PLREG_HEALTH_LED,
> + BIT(7),
> + value == 0 ? 0 : BIT(7));
> + break;
> + case LED_HEALTH_AMBER:
> + regmap_update_bits(drvdata->base,
> + PLREG_HEALTH_LED,
> + BIT(6),
> + value == 0 ? 0 : BIT(6));
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static int gxp_gpio_pl_get_direction(struct gpio_chip *chip, unsigned int offset)
> +{
> + int ret = GXP_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 = GXP_GPIO_DIR_OUT;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_pl_direction_input(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + case 8 ... 55:
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + case 59 ... 65:
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_pl_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_pl_set(chip, offset, value);
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static void gxp_gpio_pl_irq_ack(struct irq_data *d)
> +{
> + struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + unsigned int val;
> +
> + /* Read latched interrupt for group 5 */
> + regmap_read(drvdata->interrupt, PLREG_INT_GRP5_FLAG, &val);
> + /* Clear latched interrupt */
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
> + 0xFF, 0xFF);
> +
> + /* Read latched interrupt for group 6 */
> + regmap_read(drvdata->interrupt, PLREG_INT_GRP6_FLAG, &val);
> + /* Clear latched interrupt */
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
> + 0xFF, 0xFF);
> +}
> +
> +static void gxp_gpio_pl_irq_set_mask(struct irq_data *d, bool set)
> +{
> + struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> +
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
> + BIT(0) | BIT(2), set ? 0 : BIT(0) | BIT(2));
> +
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
> + BIT(2), set ? 0 : BIT(2));
> +}
> +
> +static void gxp_gpio_pl_irq_mask(struct irq_data *d)
> +{
> + gxp_gpio_pl_irq_set_mask(d, false);
> +}
> +
> +static void gxp_gpio_pl_irq_unmask(struct irq_data *d)
> +{
> + gxp_gpio_pl_irq_set_mask(d, true);
> +}
> +
> +static int gxp_gpio_irq_init_hw(struct gpio_chip *chip)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> +
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
> + BIT(0) | BIT(2), 0);
> +
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
> + BIT(2), 0);
> +
> + return 0;
> +}
> +
> +static int gxp_gpio_pl_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_gpio_pl_irq_handle(int irq, void *_drvdata)
> +{
> + struct gxp_gpio_drvdata *drvdata = (struct gxp_gpio_drvdata *)_drvdata;
> + unsigned int val, girq, i;
> +
> + /* Check group 5 interrupts */
> +
> + regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val);
> +
> + if (val) {
> + for_each_set_bit(i, (unsigned long *)&val, 3) {
> + girq = irq_find_mapping(drvdata->chip.irq.domain,
> + i + PWR_BTN_INT);
> + generic_handle_irq(girq);
> + }
> +
> + /* Clear latched interrupt */
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG,
> + 0xFF, 0xFF);
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_BASE,
> + BIT(0) | BIT(2), 0);
> + }
> +
> + /* Check group 6 interrupts */
> +
> + regmap_read(drvdata->base, PLREG_INT_GRP6_FLAG, &val);
> +
> + if (val & BIT(2)) {
> + u8 old_psu = psu_presence;
> +
> + regmap_read(drvdata->base, PLREG_PSU_INST, &val);
> + psu_presence = (u8)val;
> +
> + if (old_psu != psu_presence) {
> + /* Identify all bits which differs */
> + u8 current_val = psu_presence;
> + u8 old_val = old_psu;
> +
> + for (i = 0 ; i < 8 ; i++) {
> + if ((current_val & 0x1) != (old_val & 0x1)) {
> + /* PSU state has changed */
> + girq = irq_find_mapping(drvdata->chip.irq.domain,
> + i + PSU1_INST);
> + if (girq)
> + generic_handle_irq(girq);
> + }
> + current_val = current_val >> 1;
> + old_val = old_val >> 1;
> + }
> + }
> + }
> +
> + /* Clear latched interrupt */
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG,
> + 0xFF, 0xFF);
> + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE,
> + BIT(2), 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static struct gpio_chip plreg_chip = {
> + .label = "gxp_gpio_plreg",
> + .owner = THIS_MODULE,
> + .get = gxp_gpio_pl_get,
> + .set = gxp_gpio_pl_set,
> + .get_direction = gxp_gpio_pl_get_direction,
> + .direction_input = gxp_gpio_pl_direction_input,
> + .direction_output = gxp_gpio_pl_direction_output,
> + .base = -1,
> +};
> +
> +static struct irq_chip gxp_plreg_irqchip = {
> + .name = "gxp_plreg",
> + .irq_ack = gxp_gpio_pl_irq_ack,
> + .irq_mask = gxp_gpio_pl_irq_mask,
> + .irq_unmask = gxp_gpio_pl_irq_unmask,
> + .irq_set_type = gxp_gpio_pl_set_type,
> +};
> +
> +static int gxp_gpio_pl_init(struct platform_device *pdev)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
> + struct gpio_irq_chip *girq;
> + int ret;
> + unsigned int val;
> +
> + drvdata->base = gxp_gpio_init_regmap(pdev, "base", 8);
> + if (IS_ERR(drvdata->base))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->base),
> + "failed to map base\n");
> +
> + drvdata->interrupt = gxp_gpio_init_regmap(pdev, "interrupt", 8);
> + if (IS_ERR(drvdata->interrupt))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->interrupt),
> + "failed to map interrupt base\n");
> +
> + regmap_read(drvdata->base, PLREG_FAN_INST, &val);
> + fan_presence = (u8)val;
> +
> + regmap_read(drvdata->base, PLREG_FAN_FAIL, &val);
> + fan_fail = (u8)val;
> +
> + regmap_read(drvdata->base, PLREG_PSU_INST, &val);
> + psu_presence = (u8)val;
> +
> + drvdata->chip = plreg_chip;
> + drvdata->chip.ngpio = 57;
> + drvdata->chip.parent = &pdev->dev;
> +
> + girq = &drvdata->chip.irq;
> + girq->chip = &gxp_plreg_irqchip;
> + girq->parent_handler = NULL;
> + girq->num_parents = 0;
> + girq->parents = NULL;
> + girq->default_type = IRQ_TYPE_NONE;
> + girq->handler = handle_bad_irq;
> +
> + girq->init_hw = gxp_gpio_irq_init_hw;
> +
> + ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->chip, drvdata);
> + if (ret < 0)
> + dev_err_probe(&pdev->dev, ret, "Could not register gpiochip for plreg\n");
> +
> + regmap_update_bits(drvdata->base,
> + PLREG_INT_HI_PRI_EN,
> + PLREG_GRP4_GRP5_MASK,
> + PLREG_GRP4_GRP5_MASK);
> + regmap_update_bits(drvdata->base,
> + PLREG_INT_GRP_STAT_MASK,
> + PLREG_GRP4_GRP5_MASK,
> + 0x00);
> + regmap_read(drvdata->base, PLREG_INT_HI_PRI_EN, &val);
> + regmap_read(drvdata->base, PLREG_INT_GRP_STAT_MASK, &val);
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret < 0)
> + return dev_err_probe(&pdev->dev, ret, "Get irq from platform fail\n");
> +
> + drvdata->irq = ret;
> +
> + ret = devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_pl_irq_handle,
> + IRQF_SHARED, "gxp-pl", drvdata);
> + if (ret < 0)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct of_device_id gxp_gpio_of_match[] = {
> + { .compatible = "hpe,gxp-gpio-pl" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, gxp_gpio_of_match);
> +
> +static int gxp_gpio_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct gxp_gpio_drvdata *drvdata;
> +
> + /* Initialize global vars */
> + fan_presence = 0;
> + fan_fail = 0;
> + psu_presence = 0;
> +
> + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
> + if (!drvdata)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, drvdata);
> +
> + ret = gxp_gpio_pl_init(pdev);
> +
> + return ret;
> +}
> +
> +static struct platform_driver gxp_gpio_driver = {
> + .driver = {
> + .name = "gxp-gpio-pl",
> + .of_match_table = gxp_gpio_of_match,
> + },
> + .probe = gxp_gpio_probe,
> +};
> +module_platform_driver(gxp_gpio_driver);
> +
> +MODULE_AUTHOR("Nick Hawkins <[email protected]>");
> +MODULE_DESCRIPTION("GPIO PL interface for GXP");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpio/gpio-gxp.c b/drivers/gpio/gpio-gxp.c
> new file mode 100644
> index 000000000000..ed6d8577e6b7
> --- /dev/null
> +++ b/drivers/gpio/gpio-gxp.c
> @@ -0,0 +1,637 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
> +
> +#include <linux/gpio/driver.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#define GPIDAT 0x040
> +#define GPODAT 0x0b0
> +#define GPODAT2 0x0f8
> +#define GPOOWN 0x110
> +#define GPOOWN2 0x118
> +#define ASR_OFS 0x05c
> +
> +#define GXP_GPIO_DIR_OUT 0
> +#define GXP_GPIO_DIR_IN 1
> +
> +#define PGOOD_MASK BIT(0)
> +
> +struct gxp_gpio_drvdata {
> + struct gpio_chip chip;
> + struct regmap *csm_map;
> + void __iomem *fn2_vbtn;
> + struct regmap *fn2_stat;
> + struct regmap *vuhc0_map;
> + int irq;
> +};
> +
> +/*
> + * Note: Instead of definining all PINs here are the select few that
> + * are specifically defined in DTS and offsets are used here.
> + */
> +enum gxp_gpio_pn {
> + RESET = 192,
> + VPBTN = 210, /* aka POWER_OK */
> + PGOOD = 211, /* aka PS_PWROK */
> + PERST = 212, /* aka PCIERST */
> + POST_COMPLETE = 213,
> +};
> +
> +static int gxp_gpio_csm_get(struct gpio_chip *chip, unsigned int offset)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + int ret = 0;
> +
> + switch (offset) {
> + case 0 ... 31:
> + regmap_read(drvdata->csm_map, GPIDAT, &ret);
> + ret = (ret & BIT(offset));
> + break;
> + case 32 ... 63:
> + regmap_read(drvdata->csm_map, GPIDAT + 0x20, &ret);
> + ret = (ret & BIT(offset - 32));
> + break;
> + case 64 ... 95:
> + regmap_read(drvdata->csm_map, GPODAT, &ret);
> + ret = (ret & BIT(offset - 64));
> + break;
> + case 96 ... 127:
> + regmap_read(drvdata->csm_map, GPODAT + 0x04, &ret);
> + ret = (ret & BIT(offset - 96));
> + break;
> + case 128 ... 159:
> + regmap_read(drvdata->csm_map, GPODAT2, &ret);
> + ret = (ret & BIT(offset - 128));
> + break;
> + case 160 ... 191:
> + regmap_read(drvdata->csm_map, GPODAT2 + 0x04, &ret);
> + ret = (ret & BIT(offset - 160));
> + break;
> + case RESET:
> + /* SW_RESET */
> + regmap_read(drvdata->csm_map, ASR_OFS, &ret);
> + ret = (ret & BIT(15));
> + break;
> + default:
> + break;
> + }
> +
> + /* Return either 1 or 0 */
> + return (ret ? 1 : 0);
> +}
> +
> +static void gxp_gpio_csm_set(struct gpio_chip *chip, unsigned int offset,
> + int value)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + u32 tmp;
> +
> + switch (offset) {
> + case 64 ... 95:
> + /* Keep ownership setting */
> + regmap_read(drvdata->csm_map, GPOOWN, &tmp);
> + tmp = (tmp & BIT(offset - 64)) ? 1 : 0;
> +
> + regmap_update_bits(drvdata->csm_map, GPOOWN,
> + BIT(offset - 64), BIT(offset - 64));
> + regmap_update_bits(drvdata->csm_map, GPODAT,
> + BIT(offset - 64), value ? BIT(offset - 64) : 0);
> +
> + /* Restore ownership setting */
> + regmap_update_bits(drvdata->csm_map, GPOOWN,
> + BIT(offset - 64), tmp ? BIT(offset - 64) : 0);
> + break;
> + case 96 ... 127:
> + /* Keep ownership setting */
> + regmap_read(drvdata->csm_map, GPOOWN + 0x04, &tmp);
> + tmp = (tmp & BIT(offset - 96)) ? 1 : 0;
> +
> + regmap_update_bits(drvdata->csm_map, GPOOWN + 0x04,
> + BIT(offset - 96), BIT(offset - 96));
> + regmap_update_bits(drvdata->csm_map, GPODAT + 0x04,
> + BIT(offset - 96), value ? BIT(offset - 96) : 0);
> +
> + /* Restore ownership setting */
> + regmap_update_bits(drvdata->csm_map, GPOOWN + 0x04,
> + BIT(offset - 96), tmp ? BIT(offset - 96) : 0);
> + break;
> + case 128 ... 159:
> + /* Keep ownership setting */
> + regmap_read(drvdata->csm_map, GPOOWN2, &tmp);
> + tmp = (tmp & BIT(offset - 128)) ? 1 : 0;
> +
> + regmap_update_bits(drvdata->csm_map, GPOOWN2,
> + BIT(offset - 128), BIT(offset - 128));
> + regmap_update_bits(drvdata->csm_map, GPODAT2,
> + BIT(offset - 128), value ? BIT(offset - 128) : 0);
> +
> + /* Restore ownership setting */
> + regmap_update_bits(drvdata->csm_map, GPOOWN2,
> + BIT(offset - 128), tmp ? BIT(offset - 128) : 0);
> + break;
> + case 160 ... 191:
> + /* Keep ownership setting */
> + regmap_read(drvdata->csm_map, GPOOWN2 + 0x04, &tmp);
> + tmp = (tmp & BIT(offset - 160)) ? 1 : 0;
> +
> + regmap_update_bits(drvdata->csm_map, GPOOWN2 + 0x04,
> + BIT(offset - 160), BIT(offset - 160));
> + regmap_update_bits(drvdata->csm_map, GPODAT2 + 0x04,
> + BIT(offset - 160), value ? BIT(offset - 160) : 0);
> +
> + /* Restore ownership setting */
> + regmap_update_bits(drvdata->csm_map, GPOOWN2 + 0x04,
> + BIT(offset - 160), tmp ? BIT(offset - 160) : 0);
> + break;
> + case 192:
> + if (value) {
> + regmap_update_bits(drvdata->csm_map, ASR_OFS,
> + BIT(0), BIT(0));
> + regmap_update_bits(drvdata->csm_map, ASR_OFS,
> + BIT(15), BIT(15));
> + } else {
> + regmap_update_bits(drvdata->csm_map, ASR_OFS,
> + BIT(15), 0);
> + }
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static int gxp_gpio_csm_get_direction(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = 0;
> +
> + switch (offset) {
> + case 0 ... 63:
> + ret = GXP_GPIO_DIR_IN;
> + break;
> + case 64 ... 191:
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + case 192 ... 193:
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + case 194:
> + ret = GXP_GPIO_DIR_IN;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_csm_direction_input(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + case 0 ... 63:
> + ret = 0;
> + break;
> + case 194:
> + ret = 0;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_csm_direction_output(struct gpio_chip *chip,
> + unsigned int offset, int value)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + case 64 ... 191:
> + case 192 ... 193:
> + gxp_gpio_csm_set(chip, offset, value);
> + ret = 0;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_vuhc_get(struct gpio_chip *chip, unsigned int offset)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + unsigned int val;
> + int ret = 0;
> +
> + if (offset < 8) {
> + regmap_read(drvdata->vuhc0_map, 0x64 + 4 * offset, &val);
> + ret = (val & BIT(13)) ? 1 : 0;
> + }
> +
> + return ret;
> +}
> +
> +static void gxp_gpio_vuhc_set(struct gpio_chip *chip, unsigned int offset,
> + int value)
> +{
> + switch (offset) {
> + default:
> + break;
> + }
> +}
> +
> +static int gxp_gpio_vuhc_get_direction(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = 0;
> +
> + switch (offset) {
> + case 0:
> + case 1:
> + case 2:
> + ret = GXP_GPIO_DIR_IN;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_vuhc_direction_input(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + case 0:
> + case 1:
> + case 2:
> + ret = 0;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_vuhc_direction_output(struct gpio_chip *chip,
> + unsigned int offset, int value)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_fn2_get(struct gpio_chip *chip, unsigned int offset)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + unsigned int val;
> + int ret = 0;
> +
> + switch (offset) {
> + case PGOOD:
> + regmap_read(drvdata->fn2_stat, 0, &val);
> + ret = (val & BIT(24));
> +
> + break;
> + case PERST:
> + regmap_read(drvdata->fn2_stat, 0, &val);
> + ret = (val & BIT(25));
> +
> + break;
> + default:
> + break;
> + }
> +
> + /* Return either 1 or 0 */
> + return (ret ? 1 : 0);
> +}
> +
> +static void gxp_gpio_fn2_set(struct gpio_chip *chip, unsigned int offset,
> + int value)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> +
> + switch (offset) {
> + case VPBTN:
> + writeb(1, drvdata->fn2_vbtn);
> + break;
> + default:
> + break;
> + }
> +}
> +
> +static int gxp_gpio_fn2_get_direction(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = GXP_GPIO_DIR_IN;
> +
> + switch (offset) {
> + case VPBTN:
> + ret = GXP_GPIO_DIR_OUT;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_fn2_direction_input(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = -EOPNOTSUPP;
> +
> + switch (offset) {
> + case PGOOD:
> + case PERST:
> + case POST_COMPLETE:
> + ret = 0;
> + break;
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_get(struct gpio_chip *chip, unsigned int offset)
> +{
> + int ret = 0;
> +
> + if (offset < 200)
> + ret = gxp_gpio_csm_get(chip, offset);
> + else if (offset >= 250 && offset < 300)
> + ret = gxp_gpio_vuhc_get(chip, offset - 250);
> + else if (offset >= 300)
> + ret = gxp_gpio_fn2_get(chip, offset);
> +
> + return ret;
> +}
> +
> +static void gxp_gpio_set(struct gpio_chip *chip,
> + unsigned int offset, int value)
> +{
> + if (offset < 200)
> + gxp_gpio_csm_set(chip, offset, value);
> + else if (offset >= 250 && offset < 300)
> + gxp_gpio_vuhc_set(chip, offset - 250, value);
> + else if (offset >= 300)
> + gxp_gpio_fn2_set(chip, offset, value);
> +}
> +
> +static int gxp_gpio_get_direction(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = 0;
> +
> + if (offset < 200)
> + ret = gxp_gpio_csm_get_direction(chip, offset);
> + else if (offset >= 250 && offset < 300)
> + ret = gxp_gpio_vuhc_get_direction(chip, offset - 250);
> + else if (offset >= 300)
> + ret = gxp_gpio_fn2_get_direction(chip, offset);
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_direction_input(struct gpio_chip *chip,
> + unsigned int offset)
> +{
> + int ret = 0;
> +
> + if (offset < 200)
> + ret = gxp_gpio_csm_direction_input(chip, offset);
> + else if (offset >= 250 && offset < 300)
> + ret = gxp_gpio_vuhc_direction_input(chip, offset - 250);
> + else if (offset >= 300)
> + ret = gxp_gpio_fn2_direction_input(chip, offset);
> +
> + return ret;
> +}
> +
> +static int gxp_gpio_direction_output(struct gpio_chip *chip,
> + unsigned int offset, int value)
> +{
> + int ret = 0;
> +
> + if (offset < 200)
> + ret = gxp_gpio_csm_direction_output(chip, offset, value);
> + else if (offset >= 250 && offset < 300)
> + ret = gxp_gpio_vuhc_direction_output(chip, offset - 250, value);
> + return ret;
> +}
> +
> +static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev,
> + char *reg_name, u8 bits)
> +{
> + struct regmap_config regmap_config = {
> + .reg_bits = 32,
> + .reg_stride = 4,
> + .val_bits = 32,
> + };
> + void __iomem *base;
> +
> + if (bits == 8) {
> + regmap_config.reg_bits = 8;
> + regmap_config.reg_stride = 1;
> + regmap_config.val_bits = 8;
> + regmap_config.max_register = 0xff;
> + }
> +
> + base = devm_platform_ioremap_resource_byname(pdev, reg_name);
> + if (IS_ERR(base))
> + return ERR_CAST(base);
> +
> + regmap_config.name = reg_name;
> +
> + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config);
> +}
> +
> +static void gxp_gpio_fn2_irq_ack(struct irq_data *d)
> +{
> + struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> + unsigned int val;
> +
> + /* Read latched interrupt */
> + regmap_read(drvdata->fn2_stat, 0, &val);
> + /* Clear latched interrupt */
> + regmap_update_bits(drvdata->fn2_stat, 0,
> + 0xFFFF, 0xFFFF);
> +}
> +
> +#define FN2_SEVMASK BIT(2)
> +static void gxp_gpio_fn2_irq_set_mask(struct irq_data *d, bool set)
> +{
> + struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(chip->parent);
> +
> + regmap_update_bits(drvdata->fn2_stat, FN2_SEVMASK,
> + BIT(0), set ? BIT(0) : 0);
> +}
> +
> +static void gxp_gpio_fn2_irq_mask(struct irq_data *d)
> +{
> + gxp_gpio_fn2_irq_set_mask(d, false);
> +}
> +
> +static void gxp_gpio_fn2_irq_unmask(struct irq_data *d)
> +{
> + gxp_gpio_fn2_irq_set_mask(d, true);
> +}
> +
> +static int gxp_gpio_fn2_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_gpio_fn2_irq_handle(int irq, void *_drvdata)
> +{
> + struct gxp_gpio_drvdata *drvdata = (struct gxp_gpio_drvdata *)_drvdata;
> + unsigned int val, girq;
> +
> + regmap_read(drvdata->fn2_stat, 0, &val);
> +
> + if (val & PGOOD_MASK) {
> + girq = irq_find_mapping(drvdata->chip.irq.domain, PGOOD);
> + generic_handle_irq(girq);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static struct irq_chip gxp_gpio_irqchip = {
> + .name = "gxp_fn2",
> + .irq_ack = gxp_gpio_fn2_irq_ack,
> + .irq_mask = gxp_gpio_fn2_irq_mask,
> + .irq_unmask = gxp_gpio_fn2_irq_unmask,
> + .irq_set_type = gxp_gpio_fn2_set_type,
> +};
> +
> +static struct gpio_chip common_chip = {
> + .label = "gxp_gpio",
> + .owner = THIS_MODULE,
> + .get = gxp_gpio_get,
> + .set = gxp_gpio_set,
> + .get_direction = gxp_gpio_get_direction,
> + .direction_input = gxp_gpio_direction_input,
> + .direction_output = gxp_gpio_direction_output,
> + .base = 0,
> +};
> +
> +static int gxp_gpio_init(struct platform_device *pdev)
> +{
> + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
> + struct gpio_irq_chip *girq;
> + int ret;
> +
> + drvdata->csm_map = gxp_gpio_init_regmap(pdev, "csm", 32);
> + if (IS_ERR(drvdata->csm_map))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->csm_map),
> + "failed to map csm_handle\n");
> +
> + drvdata->fn2_vbtn = devm_platform_ioremap_resource_byname(pdev, "fn2-vbtn");
> + if (IS_ERR(drvdata->fn2_vbtn))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->fn2_vbtn),
> + "failed to map fn2_vbtn\n");
> +
> + drvdata->fn2_stat = gxp_gpio_init_regmap(pdev, "fn2-stat", 32);
> + if (IS_ERR(drvdata->fn2_stat))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->fn2_stat),
> + "failed to map fn2_stat\n");
> +
> + drvdata->vuhc0_map = gxp_gpio_init_regmap(pdev, "vuhc", 32);
> + if (IS_ERR(drvdata->vuhc0_map))
> + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->vuhc0_map),
> + "failed to map vuhc0_map\n");
> +
> + girq = &drvdata->chip.irq;
> + girq->chip = &gxp_gpio_irqchip;
> + girq->parent_handler = NULL;
> + girq->num_parents = 0;
> + girq->parents = NULL;
> + girq->default_type = IRQ_TYPE_NONE;
> + girq->handler = handle_bad_irq;
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret < 0)
> + return dev_err_probe(&pdev->dev, ret,
> + "Get irq from platform fail\n");
> +
> + drvdata->irq = ret;
> +
> + ret = devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_fn2_irq_handle,
> + IRQF_SHARED, "gxp-fn2", drvdata);
> + if (ret < 0)
> + return ret;
> +
> + drvdata->chip = common_chip;
> + drvdata->chip.ngpio = 220;
> +
> + drvdata->chip.parent = &pdev->dev;
> + ret = devm_gpiochip_add_data(&pdev->dev, &drvdata->chip, NULL);
> + if (ret < 0)
> + return dev_err_probe(&pdev->dev, ret,
> + "Could not register gpiochip for fn2\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id gxp_gpio_of_match[] = {
> + { .compatible = "hpe,gxp-gpio" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, gxp_gpio_of_match);
> +
> +static int gxp_gpio_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct gxp_gpio_drvdata *drvdata;
> +
> + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
> + if (!drvdata)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, drvdata);
> +
> + ret = gxp_gpio_init(pdev);
> +
> + return ret;
> +}
> +
> +static struct platform_driver gxp_gpio_driver = {
> + .driver = {
> + .name = "gxp-gpio",
> + .of_match_table = gxp_gpio_of_match,
> + },
> + .probe = gxp_gpio_probe,
> +};
> +module_platform_driver(gxp_gpio_driver);
> +
> +MODULE_AUTHOR("Nick Hawkins <[email protected]>");
> +MODULE_DESCRIPTION("GPIO interface for GXP");
> +MODULE_LICENSE("GPL");
> --
> 2.17.1
>
Hi Nick,
thanks for your patch!
On Wed, May 31, 2023 at 5:23 PM <[email protected]> wrote:
> +/* Provide info for fan driver */
> +u8 fan_presence;
> +EXPORT_SYMBOL(fan_presence);
> +
> +u8 fan_fail;
> +EXPORT_SYMBOL(fan_fail);
> +
> +/* Remember last PSU config */
> +u8 psu_presence;
As Bartosz said this doesn't work.
to me it looks like you should define a GPIO-controlled fan in the
device tree, similar to this:
fan0: gpio-fan {
compatible = "gpio-fan";
gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
gpio-fan,speed-map = <0 0>, <10000 1>;
#cooling-cells = <2>;
};
Then that cooling cell should be used by a thermal driver for the chassis
to keep the temperature reasonable. I bet you have a temperature sensor
as well, meaning they should form a thermal zone controlled by the fan.
Examples of chassis thermal zones in device tree:
arch/arm/boot/dts/gemini-dlink-dir-685.dts
arch/arm/boot/dts/gemini-dlink-dns-313.dts
Yours,
Linus Walleij
> If the host wants to own the fan status from gpio pins, it has to live up to
> it and own it entirely. The kernel hwmon driver does not have access in that
> case.
> In a more "normal" world, the hwmon driver would "own" the gpio pin(s)
> and user space would listen to associated hwmon attribute events (presumably
> fan_enable and fan_fault), either by listening for sysfs attribute events
> or via udev or both. Again, if you don't want to do that, and want user space
> to have access to the raw gpio pins, you'll have to live with the consequences.
> I don't see the need to bypass existing mechanisms just because user space
> programmers want direct access to gpio pins.
Greetings Guenter,
Thank you for your valuable feedback with the solutions you have provided.
Before I proceed though I have a quick query about the fan driver.
If I were to let the user space "own" gpio pins, would it be permissible for
the userspace to feed a kernel driver data via sysfs?
Ex:
GPIO Driver -> (OpenBMC) -> Fandriver (sysfs).
Here the GPIO driver would provide fan presence information to OpenBMC
and then OpenBMC would provide fan presence info to the fan driver.
If it were permissible to provide data to the driver via this method I could
apply it to the PSU driver as well. the PSU driver which requires presence
info to verify a PSU is inserted / removed.
Thanks,
-Nick Hawkins
On 6/1/23 10:11, Linus Walleij wrote:
> On Thu, Jun 1, 2023 at 5:48 PM Hawkins, Nick <[email protected]> wrote:
>
>> Thank you for your valuable feedback with the solutions you have provided.
>> Before I proceed though I have a quick query about the fan driver.
>> If I were to let the user space "own" gpio pins, would it be permissible for
>> the userspace to feed a kernel driver data via sysfs?
>>
>> Ex:
>> GPIO Driver -> (OpenBMC) -> Fandriver (sysfs).
>>
>> Here the GPIO driver would provide fan presence information to OpenBMC
>> and then OpenBMC would provide fan presence info to the fan driver.
>
> But why? Don't be so obsessed about userspace doing stuff using
> sysfs, usually it is a better idea to let the kernel handle hardware.
>
> I think this is a simple thermal zone you can define in the device
> tree as indicated in my previous comment.
>
>> If it were permissible to provide data to the driver via this method I could
>> apply it to the PSU driver as well. the PSU driver which requires presence
>> info to verify a PSU is inserted / removed.
>
> It feels like you are looking for a way for two drivers to communicate
> with each other.
>
> This can be done several ways, the most straight-forward is notifiers.
> include/linux/notifier.h
>
This is all unnecessary. The hwmon driver could register a gpio pin,
including interrupt, and then report state changes to userspace with
sysfs or udev events on the registered hwmon sysfs attributes.
If they really want to use userspace for everything, they should
just use userspace for everything and not bother with a kernel driver.
Guenter
On Thu, Jun 1, 2023 at 5:48 PM Hawkins, Nick <[email protected]> wrote:
> Thank you for your valuable feedback with the solutions you have provided.
> Before I proceed though I have a quick query about the fan driver.
> If I were to let the user space "own" gpio pins, would it be permissible for
> the userspace to feed a kernel driver data via sysfs?
>
> Ex:
> GPIO Driver -> (OpenBMC) -> Fandriver (sysfs).
>
> Here the GPIO driver would provide fan presence information to OpenBMC
> and then OpenBMC would provide fan presence info to the fan driver.
But why? Don't be so obsessed about userspace doing stuff using
sysfs, usually it is a better idea to let the kernel handle hardware.
I think this is a simple thermal zone you can define in the device
tree as indicated in my previous comment.
> If it were permissible to provide data to the driver via this method I could
> apply it to the PSU driver as well. the PSU driver which requires presence
> info to verify a PSU is inserted / removed.
It feels like you are looking for a way for two drivers to communicate
with each other.
This can be done several ways, the most straight-forward is notifiers.
include/linux/notifier.h
Yours,
Linus Walleij
> >
> > This can be done several ways, the most straight-forward is notifiers.
> > include/linux/notifier.h
> >
> This is all unnecessary. The hwmon driver could register a gpio pin,
> including interrupt, and then report state changes to userspace with
> sysfs or udev events on the registered hwmon sysfs attributes.
> If they really want to use userspace for everything, they should
> just use userspace for everything and not bother with a kernel driver.
Greetings Guenter and Linus,
Thank you for your feedback and assistance. I discussed this with my
team and the direction they are leaning is that they want to own the
GPIOs in user space. The fan driver it would still need to be used
to set and read PWMs as they are kernel protected registers. It will
also need to be there to coordinate the proper offset in the GXP
registers to control a particular fans PWM.
For hot pluggable devices such as fans and psu they will need to
bind/unbind the hwmon driver of the device as it is inserted/removed.
Is this an acceptable path forward?
If it is I will revise this patchset once more to make the fan independent
of the GPIO driver.
Thanks again for all the guidance,
-Nick Hawkins