From: Nick Hawkins <nick.hawkins@xxxxxxx> The GXP SoC supports GPIO on multiple interfaces. The interfaces are CPLD and Host. The gpio-gxp-pl driver covers the CPLD which takes physical I/O from the board and shares it with GXP via a proprietary interface that maps the I/O onto a specific register area of the GXP. This driver supports interrupts from the CPLD. Signed-off-by: Nick Hawkins <nick.hawkins@xxxxxxx> --- v5: *No change v4: *Moved gpio-gxp-pl.c to a separate commit. *Moved regmap_config out of function and made it static const *Removed unnecessary variables *Removed redundant conditional *Modified regmap_read switch statements to calculate offset and mask then read at end. *Removed use of -EOPNOTSUPP *Removed redundant casting *Switched generic_handle_irq -> generic_handle_domain_irq *Used GENMASK where applicable *Used bitmap_xor and for_each_bit_set *Made GPIO chip const and marked as a template (in the name) *Made irq_chip const and immutable *Removed casting in one case *Corrected check on devm_gpiochip_add_data *Remove dev_err_probe on platform_get_irq *Changed return 0 to devm_request_irq v3: *Remove shared variables with gxp-fan-ctrl 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 | 9 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-gxp-pl.c | 582 +++++++++++++++++++++++++++++++++++++ 3 files changed, 592 insertions(+) create mode 100644 drivers/gpio/gpio-gxp-pl.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 5521f060d58e..2e0b003ccefb 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1263,6 +1263,15 @@ config HTC_EGPIO several HTC phones. It provides basic support for input pins, output pins, and IRQs. +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_ELKHARTLAKE tristate "Intel Elkhart Lake PSE GPIO support" depends on X86 || COMPILE_TEST diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 20036af3acb1..8903f446ef1b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.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_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..8506e2a96da4 --- /dev/null +++ b/drivers/gpio/gpio-gxp-pl.c @@ -0,0 +1,582 @@ +// 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_GRP5_STATE 0x32 +#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_SERVER_ID 0x01 /* 2 Bytes */ +#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 PLREG_SIDEBAND 0x40 +#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, + SIDEBAND_SEL_L = 63, + SIDEBAND_SEL_H = 64 +}; + +/* + * When an interrupt fires for a PSU config change + * there is a need to know the previous PSU configuration + * so that the appropriate gpio line is interrupted for + * the correct PSU. In order to keep this variable up to + * date it is global so that it can be set at init and + * each time the interrupt fires. + */ +u8 psu_presence; + +struct gxp_gpio_drvdata { + struct gpio_chip chip; + struct regmap *base; + struct regmap *interrupt; + int irq; +}; + +static const struct regmap_config gxp_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x80, + .name = "gxp-gpio-pl", +}; + +static const struct regmap_config gxp_int_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x7f, + .name = "gxp-gpio-pl-int", +}; + +static struct regmap *gxp_gpio_init_regmap(struct platform_device *pdev, + char *reg_name, bool is_interrupt) +{ + void __iomem *base; + + base = devm_platform_ioremap_resource_byname(pdev, reg_name); + if (IS_ERR(base)) + return ERR_CAST(base); + + if (is_interrupt) + return devm_regmap_init_mmio(&pdev->dev, base, &gxp_int_regmap_config); + else + return devm_regmap_init_mmio(&pdev->dev, base, &gxp_regmap_config); +} + +#ifdef CONFIG_DEBUG_FS + +#include <linux/debugfs.h> + +static int gxp_gpio_serverid_show(struct seq_file *s, void *unused) +{ + struct gxp_gpio_drvdata *drvdata = dev_get_drvdata(s->private); + unsigned int server_id_l; + unsigned int server_id_h; + + regmap_read(drvdata->base, PLREG_SERVER_ID, &server_id_l); + regmap_read(drvdata->base, PLREG_SERVER_ID + 0x01, &server_id_h); + + seq_printf(s, "%02x %02x", server_id_h, server_id_l); + + return 0; +} + +static void gxp_gpio_debuginit(struct platform_device *pdev) +{ + debugfs_create_devm_seqfile(&pdev->dev, "gxp_gpio_serverid", NULL, + gxp_gpio_serverid_show); +} + +#else + +static inline void gxp_gpio_debuginit(struct platform_device *pdev) +{ +} + +#endif + +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; + unsigned int reg_offset; + u8 reg_mask; + bool is_active_low = false; + + switch (offset) { + case IOP_LED1 ... IOP_LED8: + reg_offset = PLREG_IOP_LED; + reg_mask = BIT(offset); + break; + case FAN1_INST ...FAN8_INST: + regmap_read(drvdata->base, PLREG_FAN_INST, &val); + reg_mask = BIT(offset - FAN1_INST); + break; + case FAN1_FAIL ... FAN8_FAIL: + regmap_read(drvdata->base, PLREG_FAN_FAIL, &val); + reg_mask = BIT(offset - FAN1_FAIL); + break; + case PWR_BTN_INT ... SLP_INT: + /* Note this is active low */ + reg_offset = PLREG_INT_GRP5_STATE; + reg_mask = BIT(offset - PWR_BTN_INT); + is_active_low = true; + break; + case PSU1_INST ... PSU8_INST: + reg_offset = PLREG_PSU_INST; + reg_mask = BIT(offset - PSU1_INST); + break; + case PSU1_AC ... PSU8_AC: + reg_offset = PLREG_PSU_AC; + reg_mask = BIT(offset - PSU1_AC); + break; + case PSU1_DC ... PSU8_DC: + reg_offset = PLREG_PSU_DC; + reg_mask = BIT(offset - PSU1_DC); + break; + case LED_IDENTIFY: + reg_offset = PLREG_IDENT_LED; + reg_mask = BIT(1); + break; + case LED_HEALTH_RED: + reg_offset = PLREG_HEALTH_LED; + reg_mask = GENMASK(5, 4); /* Bit 5 set, bit 4 clear */ + break; + case LED_HEALTH_AMBER: + reg_offset = PLREG_HEALTH_LED; + reg_mask = GENMASK(5, 4); /* Bit 5, bit 4 set */ + break; + case SIDEBAND_SEL_L: + reg_offset = PLREG_SIDEBAND; + reg_mask = BIT(0); + break; + case SIDEBAND_SEL_H: + reg_offset = PLREG_SIDEBAND; + reg_mask = BIT(1); + break; + default: + return -ENOTSUPP; + } + + regmap_read(drvdata->base, reg_offset, &val); + + /* Special case: Check two bits for Health LED */ + if (offset == LED_HEALTH_RED) + /* Bit 5 set, bit 4 not set */ + return ((val & reg_mask) == BIT(5) ? 1 : 0); + else if (offset == LED_HEALTH_AMBER) + /* Bit 5 and 4 set */ + return ((val & reg_mask) == reg_mask ? 1 : 0); + + val = val & reg_mask; + + if (is_active_low) + return (val ? 0 : 1); + else + return (val ? 1 : 0); +} + +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, + GENMASK(7, 6), + value == 0 ? BIT(7) : GENMASK(7, 6)); + break; + case LED_HEALTH_RED: + regmap_update_bits(drvdata->base, + PLREG_HEALTH_LED, + GENMASK(7, 6), + value == 0 ? 0 : BIT(7)); + break; + case LED_HEALTH_AMBER: + regmap_update_bits(drvdata->base, + PLREG_HEALTH_LED, + GENMASK(7, 6), + value == 0 ? 0 : BIT(6)); + break; + case SIDEBAND_SEL_L ... SIDEBAND_SEL_H: + regmap_update_bits(drvdata->base, + PLREG_SIDEBAND, + BIT(offset - SIDEBAND_SEL_L), + value == 0 ? 0 : BIT(offset - SIDEBAND_SEL_L)); + break; + default: + break; + } +} + +static int gxp_gpio_pl_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + switch (offset) { + case IOP_LED1 ... IOP_LED8: + case LED_IDENTIFY ... LED_HEALTH_AMBER: + case ACM_FORCE_OFF: + case ACM_REQ_N: + case SIDEBAND_SEL_L ... SIDEBAND_SEL_H: + return GXP_GPIO_DIR_OUT; + default: + return -ENOTSUPP; + } +} + +static int gxp_gpio_pl_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + switch (offset) { + case FAN1_INST ... FAN8_FAIL: + return GXP_GPIO_DIR_OUT; + case PWR_BTN_INT ... SLP_INT: + return GXP_GPIO_DIR_OUT; + default: + return -ENOTSUPP; + } +} + +static int gxp_gpio_pl_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + switch (offset) { + case IOP_LED1 ... IOP_LED8: + case LED_IDENTIFY ... LED_HEALTH_AMBER: + case ACM_FORCE_OFF: + case ACM_REQ_N: + case SIDEBAND_SEL_L ... SIDEBAND_SEL_H: + gxp_gpio_pl_set(chip, offset, value); + return GXP_GPIO_DIR_OUT; + default: + return -ENOTSUPP; + } +} + +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 = _drvdata; + unsigned int val, i; + unsigned long temp; + + /* Check group 5 interrupts */ + + regmap_read(drvdata->base, PLREG_INT_GRP5_FLAG, &val); + + temp = (unsigned long)val; + for_each_set_bit(i, &temp, 3) { + generic_handle_domain_irq(drvdata->chip.irq.domain, + i + PWR_BTN_INT); + } + + /* Clear latched interrupt */ + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP5_FLAG, + GENMASK(7, 0), GENMASK(7, 0)); + 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 = val; + + if (old_psu != psu_presence) { + /* Identify all bits which differs */ + unsigned long current_val = psu_presence; + unsigned long old_val = old_psu; + unsigned long changed_bits; + + bitmap_xor(&changed_bits, ¤t_val, &old_val, 8); + + for_each_set_bit(i, &changed_bits, 8) { + /* PSU state has changed */ + generic_handle_domain_irq(drvdata->chip.irq.domain, + i + PSU1_INST); + } + } + } + + /* Clear latched interrupt */ + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_FLAG, + GENMASK(7, 0), GENMASK(7, 0)); + regmap_update_bits(drvdata->interrupt, PLREG_INT_GRP6_BASE, + BIT(2), 0); + + return IRQ_HANDLED; +} + +static const struct gpio_chip template_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 const 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, + .flags = IRQCHIP_IMMUTABLE, +}; + +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; + struct gpio_irq_chip *girq; + unsigned int val; + + /* Initialize global vars */ + psu_presence = 0; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + platform_set_drvdata(pdev, drvdata); + + drvdata->base = gxp_gpio_init_regmap(pdev, "base", false); + 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", true); + if (IS_ERR(drvdata->interrupt)) + return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->interrupt), + "failed to map interrupt base\n"); + + /* Necessary to read the server id */ + gxp_gpio_debuginit(pdev); + + /* Initialize psu_presence variable */ + regmap_read(drvdata->base, PLREG_PSU_INST, &val); + psu_presence = val; + + drvdata->chip = template_chip; + drvdata->chip.ngpio = 80; + drvdata->chip.parent = &pdev->dev; + + girq = &drvdata->chip.irq; + gpio_irq_chip_set_chip(girq, &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) + return dev_err_probe(&pdev->dev, ret, "Could not register gpiochip for plreg\n"); + + regmap_update_bits(drvdata->interrupt, + PLREG_INT_HI_PRI_EN, + PLREG_GRP4_GRP5_MASK, + PLREG_GRP4_GRP5_MASK); + regmap_update_bits(drvdata->interrupt, + PLREG_INT_GRP_STAT_MASK, + PLREG_GRP4_GRP5_MASK, + 0x00); + + regmap_read(drvdata->interrupt, PLREG_INT_HI_PRI_EN, &val); + regmap_read(drvdata->interrupt, PLREG_INT_GRP_STAT_MASK, &val); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + + drvdata->irq = ret; + + return devm_request_irq(&pdev->dev, drvdata->irq, gxp_gpio_pl_irq_handle, + IRQF_SHARED, "gxp-pl", drvdata); +} + +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 <nick.hawkins@xxxxxxx>"); +MODULE_DESCRIPTION("GPIO PL interface for GXP"); +MODULE_LICENSE("GPL"); -- 2.17.1