Message ID | 20230531151918.105223-3-nick.hawkins@hpe.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | ARM: Add GPIO support | expand |
On Wed, May 31, 2023 at 5:23 PM <nick.hawkins@hpe.com> wrote: > > From: Nick Hawkins <nick.hawkins@hpe.com> > > 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 <nick.hawkins@hpe.com> > > --- > > 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 <nick.hawkins@hpe.com>"); > +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 <nick.hawkins@hpe.com>"); > +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 <nick.hawkins@hpe.com> 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
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 <nick.hawkins@hpe.com>"); +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 <nick.hawkins@hpe.com>"); +MODULE_DESCRIPTION("GPIO interface for GXP"); +MODULE_LICENSE("GPL");