forked from mirrors/linux
		
	gpio: mlxbf3: Add gpio driver support
Add support for the BlueField-3 SoC GPIO driver. This driver configures and handles GPIO interrupts. It also enables a user to manipulate certain GPIO pins via libgpiod tools or other kernel drivers. The usables pins are defined via the "gpio-reserved-ranges" property. Signed-off-by: Asmaa Mnebhi <asmaa@nvidia.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
This commit is contained in:
		
							parent
							
								
									8efe124702
								
							
						
					
					
						commit
						cd33f216d2
					
				
					 3 changed files with 262 additions and 0 deletions
				
			
		|  | @ -1599,6 +1599,19 @@ config GPIO_MLXBF2 | |||
| 	help | ||||
| 	  Say Y here if you want GPIO support on Mellanox BlueField 2 SoC. | ||||
| 
 | ||||
| config GPIO_MLXBF3 | ||||
| 	tristate "Mellanox BlueField 3 SoC GPIO" | ||||
| 	depends on (MELLANOX_PLATFORM && ARM64) || COMPILE_TEST | ||||
| 	select GPIO_GENERIC | ||||
| 	select GPIOLIB_IRQCHIP | ||||
| 	help | ||||
| 	  Say Y if you want GPIO support on Mellanox BlueField 3 SoC. | ||||
| 	  This GPIO controller supports interrupt handling and enables the | ||||
| 	  manipulation of certain GPIO pins. | ||||
| 	  This controller should be used in parallel with pinctrl-mlxbf3 to | ||||
| 	  control the desired GPIOs. | ||||
| 	  This driver can also be built as a module called mlxbf3-gpio. | ||||
| 
 | ||||
| config GPIO_ML_IOH | ||||
| 	tristate "OKI SEMICONDUCTOR ML7213 IOH GPIO support" | ||||
| 	depends on X86 || COMPILE_TEST | ||||
|  |  | |||
|  | @ -104,6 +104,7 @@ obj-$(CONFIG_GPIO_MERRIFIELD)		+= gpio-merrifield.o | |||
| obj-$(CONFIG_GPIO_ML_IOH)		+= gpio-ml-ioh.o | ||||
| obj-$(CONFIG_GPIO_MLXBF)		+= gpio-mlxbf.o | ||||
| obj-$(CONFIG_GPIO_MLXBF2)		+= gpio-mlxbf2.o | ||||
| obj-$(CONFIG_GPIO_MLXBF3)		+= gpio-mlxbf3.o | ||||
| obj-$(CONFIG_GPIO_MM_LANTIQ)		+= gpio-mm-lantiq.o | ||||
| obj-$(CONFIG_GPIO_MOCKUP)		+= gpio-mockup.o | ||||
| obj-$(CONFIG_GPIO_MOXTET)		+= gpio-moxtet.o | ||||
|  |  | |||
							
								
								
									
										248
									
								
								drivers/gpio/gpio-mlxbf3.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								drivers/gpio/gpio-mlxbf3.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,248 @@ | |||
| // SPDX-License-Identifier: GPL-2.0-only or BSD-3-Clause
 | ||||
| /* Copyright (C) 2022 NVIDIA CORPORATION & AFFILIATES */ | ||||
| 
 | ||||
| #include <linux/bitfield.h> | ||||
| #include <linux/bitops.h> | ||||
| #include <linux/device.h> | ||||
| #include <linux/err.h> | ||||
| #include <linux/gpio/driver.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/io.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include <linux/types.h> | ||||
| 
 | ||||
| /*
 | ||||
|  * There are 2 YU GPIO blocks: | ||||
|  * gpio[0]: HOST_GPIO0->HOST_GPIO31 | ||||
|  * gpio[1]: HOST_GPIO32->HOST_GPIO55 | ||||
|  */ | ||||
| #define MLXBF3_GPIO_MAX_PINS_PER_BLOCK 32 | ||||
| 
 | ||||
| /*
 | ||||
|  * fw_gpio[x] block registers and their offset | ||||
|  */ | ||||
| #define MLXBF_GPIO_FW_OUTPUT_ENABLE_SET	  0x00 | ||||
| #define MLXBF_GPIO_FW_DATA_OUT_SET        0x04 | ||||
| 
 | ||||
| #define MLXBF_GPIO_FW_OUTPUT_ENABLE_CLEAR 0x00 | ||||
| #define MLXBF_GPIO_FW_DATA_OUT_CLEAR      0x04 | ||||
| 
 | ||||
| #define MLXBF_GPIO_CAUSE_RISE_EN          0x00 | ||||
| #define MLXBF_GPIO_CAUSE_FALL_EN          0x04 | ||||
| #define MLXBF_GPIO_READ_DATA_IN           0x08 | ||||
| 
 | ||||
| #define MLXBF_GPIO_CAUSE_OR_CAUSE_EVTEN0  0x00 | ||||
| #define MLXBF_GPIO_CAUSE_OR_EVTEN0        0x14 | ||||
| #define MLXBF_GPIO_CAUSE_OR_CLRCAUSE      0x18 | ||||
| 
 | ||||
| struct mlxbf3_gpio_context { | ||||
| 	struct gpio_chip gc; | ||||
| 
 | ||||
| 	/* YU GPIO block address */ | ||||
| 	void __iomem *gpio_set_io; | ||||
| 	void __iomem *gpio_clr_io; | ||||
| 	void __iomem *gpio_io; | ||||
| 
 | ||||
| 	/* YU GPIO cause block address */ | ||||
| 	void __iomem *gpio_cause_io; | ||||
| }; | ||||
| 
 | ||||
| static void mlxbf3_gpio_irq_enable(struct irq_data *irqd) | ||||
| { | ||||
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); | ||||
| 	struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc); | ||||
| 	irq_hw_number_t offset = irqd_to_hwirq(irqd); | ||||
| 	unsigned long flags; | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	gpiochip_enable_irq(gc, offset); | ||||
| 
 | ||||
| 	raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags); | ||||
| 	writel(BIT(offset), gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CLRCAUSE); | ||||
| 
 | ||||
| 	val = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0); | ||||
| 	val |= BIT(offset); | ||||
| 	writel(val, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0); | ||||
| 	raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags); | ||||
| } | ||||
| 
 | ||||
| static void mlxbf3_gpio_irq_disable(struct irq_data *irqd) | ||||
| { | ||||
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); | ||||
| 	struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc); | ||||
| 	irq_hw_number_t offset = irqd_to_hwirq(irqd); | ||||
| 	unsigned long flags; | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags); | ||||
| 	val = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0); | ||||
| 	val &= ~BIT(offset); | ||||
| 	writel(val, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_EVTEN0); | ||||
| 	raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags); | ||||
| 
 | ||||
| 	gpiochip_disable_irq(gc, offset); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t mlxbf3_gpio_irq_handler(int irq, void *ptr) | ||||
| { | ||||
| 	struct mlxbf3_gpio_context *gs = ptr; | ||||
| 	struct gpio_chip *gc = &gs->gc; | ||||
| 	unsigned long pending; | ||||
| 	u32 level; | ||||
| 
 | ||||
| 	pending = readl(gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CAUSE_EVTEN0); | ||||
| 	writel(pending, gs->gpio_cause_io + MLXBF_GPIO_CAUSE_OR_CLRCAUSE); | ||||
| 
 | ||||
| 	for_each_set_bit(level, &pending, gc->ngpio) | ||||
| 		generic_handle_domain_irq(gc->irq.domain, level); | ||||
| 
 | ||||
| 	return IRQ_RETVAL(pending); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| mlxbf3_gpio_irq_set_type(struct irq_data *irqd, unsigned int type) | ||||
| { | ||||
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd); | ||||
| 	struct mlxbf3_gpio_context *gs = gpiochip_get_data(gc); | ||||
| 	irq_hw_number_t offset = irqd_to_hwirq(irqd); | ||||
| 	unsigned long flags; | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	raw_spin_lock_irqsave(&gs->gc.bgpio_lock, flags); | ||||
| 
 | ||||
| 	switch (type & IRQ_TYPE_SENSE_MASK) { | ||||
| 	case IRQ_TYPE_EDGE_BOTH: | ||||
| 		val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN); | ||||
| 		val |= BIT(offset); | ||||
| 		writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN); | ||||
| 		val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN); | ||||
| 		val |= BIT(offset); | ||||
| 		writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_EDGE_RISING: | ||||
| 		val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN); | ||||
| 		val |= BIT(offset); | ||||
| 		writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_RISE_EN); | ||||
| 		break; | ||||
| 	case IRQ_TYPE_EDGE_FALLING: | ||||
| 		val = readl(gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN); | ||||
| 		val |= BIT(offset); | ||||
| 		writel(val, gs->gpio_io + MLXBF_GPIO_CAUSE_FALL_EN); | ||||
| 		break; | ||||
| 	default: | ||||
| 		raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	raw_spin_unlock_irqrestore(&gs->gc.bgpio_lock, flags); | ||||
| 
 | ||||
| 	irq_set_handler_locked(irqd, handle_edge_irq); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /* This function needs to be defined for handle_edge_irq() */ | ||||
| static void mlxbf3_gpio_irq_ack(struct irq_data *data) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| static const struct irq_chip gpio_mlxbf3_irqchip = { | ||||
| 	.name = "MLNXBF33", | ||||
| 	.irq_ack = mlxbf3_gpio_irq_ack, | ||||
| 	.irq_set_type = mlxbf3_gpio_irq_set_type, | ||||
| 	.irq_enable = mlxbf3_gpio_irq_enable, | ||||
| 	.irq_disable = mlxbf3_gpio_irq_disable, | ||||
| 	.flags = IRQCHIP_IMMUTABLE, | ||||
| 	GPIOCHIP_IRQ_RESOURCE_HELPERS, | ||||
| }; | ||||
| 
 | ||||
| static int mlxbf3_gpio_probe(struct platform_device *pdev) | ||||
| { | ||||
| 	struct device *dev = &pdev->dev; | ||||
| 	struct mlxbf3_gpio_context *gs; | ||||
| 	struct gpio_irq_chip *girq; | ||||
| 	struct gpio_chip *gc; | ||||
| 	int ret, irq; | ||||
| 
 | ||||
| 	gs = devm_kzalloc(dev, sizeof(*gs), GFP_KERNEL); | ||||
| 	if (!gs) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	gs->gpio_io = devm_platform_ioremap_resource(pdev, 0); | ||||
| 	if (IS_ERR(gs->gpio_io)) | ||||
| 		return PTR_ERR(gs->gpio_io); | ||||
| 
 | ||||
| 	gs->gpio_cause_io = devm_platform_ioremap_resource(pdev, 1); | ||||
| 	if (IS_ERR(gs->gpio_cause_io)) | ||||
| 		return PTR_ERR(gs->gpio_cause_io); | ||||
| 
 | ||||
| 	gs->gpio_set_io = devm_platform_ioremap_resource(pdev, 2); | ||||
| 	if (IS_ERR(gs->gpio_set_io)) | ||||
| 		return PTR_ERR(gs->gpio_set_io); | ||||
| 
 | ||||
| 	gs->gpio_clr_io = devm_platform_ioremap_resource(pdev, 3); | ||||
| 	if (IS_ERR(gs->gpio_clr_io)) | ||||
| 		return PTR_ERR(gs->gpio_clr_io); | ||||
| 	gc = &gs->gc; | ||||
| 
 | ||||
| 	ret = bgpio_init(gc, dev, 4, | ||||
| 			gs->gpio_io + MLXBF_GPIO_READ_DATA_IN, | ||||
| 			gs->gpio_set_io + MLXBF_GPIO_FW_DATA_OUT_SET, | ||||
| 			gs->gpio_clr_io + MLXBF_GPIO_FW_DATA_OUT_CLEAR, | ||||
| 			gs->gpio_set_io + MLXBF_GPIO_FW_OUTPUT_ENABLE_SET, | ||||
| 			gs->gpio_clr_io + MLXBF_GPIO_FW_OUTPUT_ENABLE_CLEAR, 0); | ||||
| 
 | ||||
| 	gc->request = gpiochip_generic_request; | ||||
| 	gc->free = gpiochip_generic_free; | ||||
| 	gc->owner = THIS_MODULE; | ||||
| 
 | ||||
| 	irq = platform_get_irq(pdev, 0); | ||||
| 	if (irq >= 0) { | ||||
| 		girq = &gs->gc.irq; | ||||
| 		gpio_irq_chip_set_chip(girq, &gpio_mlxbf3_irqchip); | ||||
| 		girq->default_type = IRQ_TYPE_NONE; | ||||
| 		/* This will let us handle the parent IRQ in the driver */ | ||||
| 		girq->num_parents = 0; | ||||
| 		girq->parents = NULL; | ||||
| 		girq->parent_handler = NULL; | ||||
| 		girq->handler = handle_bad_irq; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Directly request the irq here instead of passing | ||||
| 		 * a flow-handler because the irq is shared. | ||||
| 		 */ | ||||
| 		ret = devm_request_irq(dev, irq, mlxbf3_gpio_irq_handler, | ||||
| 				       IRQF_SHARED, dev_name(dev), gs); | ||||
| 		if (ret) | ||||
| 			return dev_err_probe(dev, ret, "failed to request IRQ"); | ||||
| 	} | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, gs); | ||||
| 
 | ||||
| 	ret = devm_gpiochip_add_data(dev, &gs->gc, gs); | ||||
| 	if (ret) | ||||
| 		dev_err_probe(dev, ret, "Failed adding memory mapped gpiochip\n"); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct acpi_device_id mlxbf3_gpio_acpi_match[] = { | ||||
| 	{ "MLNXBF33", 0 }, | ||||
| 	{} | ||||
| }; | ||||
| MODULE_DEVICE_TABLE(acpi, mlxbf3_gpio_acpi_match); | ||||
| 
 | ||||
| static struct platform_driver mlxbf3_gpio_driver = { | ||||
| 	.driver = { | ||||
| 		.name = "mlxbf3_gpio", | ||||
| 		.acpi_match_table = mlxbf3_gpio_acpi_match, | ||||
| 	}, | ||||
| 	.probe    = mlxbf3_gpio_probe, | ||||
| }; | ||||
| module_platform_driver(mlxbf3_gpio_driver); | ||||
| 
 | ||||
| MODULE_DESCRIPTION("NVIDIA BlueField-3 GPIO Driver"); | ||||
| MODULE_AUTHOR("Asmaa Mnebhi <asmaa@nvidia.com>"); | ||||
| MODULE_LICENSE("Dual BSD/GPL"); | ||||
		Loading…
	
		Reference in a new issue
	
	 Asmaa Mnebhi
						Asmaa Mnebhi