|  | // SPDX-License-Identifier: GPL-2.0 | 
|  |  | 
|  | /* | 
|  | * Copyright 2017-2018 Cadence | 
|  | * | 
|  | * Authors: | 
|  | *  Jan Kotas <jank@cadence.com> | 
|  | *  Boris Brezillon <boris.brezillon@free-electrons.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/gpio/driver.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/spinlock.h> | 
|  |  | 
|  | #define CDNS_GPIO_BYPASS_MODE		0x00 | 
|  | #define CDNS_GPIO_DIRECTION_MODE	0x04 | 
|  | #define CDNS_GPIO_OUTPUT_EN		0x08 | 
|  | #define CDNS_GPIO_OUTPUT_VALUE		0x0c | 
|  | #define CDNS_GPIO_INPUT_VALUE		0x10 | 
|  | #define CDNS_GPIO_IRQ_MASK		0x14 | 
|  | #define CDNS_GPIO_IRQ_EN		0x18 | 
|  | #define CDNS_GPIO_IRQ_DIS		0x1c | 
|  | #define CDNS_GPIO_IRQ_STATUS		0x20 | 
|  | #define CDNS_GPIO_IRQ_TYPE		0x24 | 
|  | #define CDNS_GPIO_IRQ_VALUE		0x28 | 
|  | #define CDNS_GPIO_IRQ_ANY_EDGE		0x2c | 
|  |  | 
|  | struct cdns_gpio_chip { | 
|  | struct gpio_chip gc; | 
|  | struct clk *pclk; | 
|  | void __iomem *regs; | 
|  | u32 bypass_orig; | 
|  | }; | 
|  |  | 
|  | static int cdns_gpio_request(struct gpio_chip *chip, unsigned int offset) | 
|  | { | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&chip->bgpio_lock, flags); | 
|  |  | 
|  | iowrite32(ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE) & ~BIT(offset), | 
|  | cgpio->regs + CDNS_GPIO_BYPASS_MODE); | 
|  |  | 
|  | spin_unlock_irqrestore(&chip->bgpio_lock, flags); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void cdns_gpio_free(struct gpio_chip *chip, unsigned int offset) | 
|  | { | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&chip->bgpio_lock, flags); | 
|  |  | 
|  | iowrite32(ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE) | | 
|  | (BIT(offset) & cgpio->bypass_orig), | 
|  | cgpio->regs + CDNS_GPIO_BYPASS_MODE); | 
|  |  | 
|  | spin_unlock_irqrestore(&chip->bgpio_lock, flags); | 
|  | } | 
|  |  | 
|  | static void cdns_gpio_irq_mask(struct irq_data *d) | 
|  | { | 
|  | struct gpio_chip *chip = irq_data_get_irq_chip_data(d); | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  |  | 
|  | iowrite32(BIT(d->hwirq), cgpio->regs + CDNS_GPIO_IRQ_DIS); | 
|  | } | 
|  |  | 
|  | static void cdns_gpio_irq_unmask(struct irq_data *d) | 
|  | { | 
|  | struct gpio_chip *chip = irq_data_get_irq_chip_data(d); | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  |  | 
|  | iowrite32(BIT(d->hwirq), cgpio->regs + CDNS_GPIO_IRQ_EN); | 
|  | } | 
|  |  | 
|  | static int cdns_gpio_irq_set_type(struct irq_data *d, unsigned int type) | 
|  | { | 
|  | struct gpio_chip *chip = irq_data_get_irq_chip_data(d); | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  | unsigned long flags; | 
|  | u32 int_value; | 
|  | u32 int_type; | 
|  | u32 mask = BIT(d->hwirq); | 
|  | int ret = 0; | 
|  |  | 
|  | spin_lock_irqsave(&chip->bgpio_lock, flags); | 
|  |  | 
|  | int_value = ioread32(cgpio->regs + CDNS_GPIO_IRQ_VALUE) & ~mask; | 
|  | int_type = ioread32(cgpio->regs + CDNS_GPIO_IRQ_TYPE) & ~mask; | 
|  |  | 
|  | /* | 
|  | * The GPIO controller doesn't have an ACK register. | 
|  | * All interrupt statuses are cleared on a status register read. | 
|  | * Don't support edge interrupts for now. | 
|  | */ | 
|  |  | 
|  | if (type == IRQ_TYPE_LEVEL_HIGH) { | 
|  | int_type |= mask; | 
|  | int_value |= mask; | 
|  | } else if (type == IRQ_TYPE_LEVEL_LOW) { | 
|  | int_type |= mask; | 
|  | } else { | 
|  | ret = -EINVAL; | 
|  | goto err_irq_type; | 
|  | } | 
|  |  | 
|  | iowrite32(int_value, cgpio->regs + CDNS_GPIO_IRQ_VALUE); | 
|  | iowrite32(int_type, cgpio->regs + CDNS_GPIO_IRQ_TYPE); | 
|  |  | 
|  | err_irq_type: | 
|  | spin_unlock_irqrestore(&chip->bgpio_lock, flags); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void cdns_gpio_irq_handler(struct irq_desc *desc) | 
|  | { | 
|  | struct gpio_chip *chip = irq_desc_get_handler_data(desc); | 
|  | struct cdns_gpio_chip *cgpio = gpiochip_get_data(chip); | 
|  | struct irq_chip *irqchip = irq_desc_get_chip(desc); | 
|  | unsigned long status; | 
|  | int hwirq; | 
|  |  | 
|  | chained_irq_enter(irqchip, desc); | 
|  |  | 
|  | status = ioread32(cgpio->regs + CDNS_GPIO_IRQ_STATUS) & | 
|  | ~ioread32(cgpio->regs + CDNS_GPIO_IRQ_MASK); | 
|  |  | 
|  | for_each_set_bit(hwirq, &status, chip->ngpio) | 
|  | generic_handle_irq(irq_find_mapping(chip->irq.domain, hwirq)); | 
|  |  | 
|  | chained_irq_exit(irqchip, desc); | 
|  | } | 
|  |  | 
|  | static struct irq_chip cdns_gpio_irqchip = { | 
|  | .name		= "cdns-gpio", | 
|  | .irq_mask	= cdns_gpio_irq_mask, | 
|  | .irq_unmask	= cdns_gpio_irq_unmask, | 
|  | .irq_set_type	= cdns_gpio_irq_set_type | 
|  | }; | 
|  |  | 
|  | static int cdns_gpio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct cdns_gpio_chip *cgpio; | 
|  | int ret, irq; | 
|  | u32 dir_prev; | 
|  | u32 num_gpios = 32; | 
|  |  | 
|  | cgpio = devm_kzalloc(&pdev->dev, sizeof(*cgpio), GFP_KERNEL); | 
|  | if (!cgpio) | 
|  | return -ENOMEM; | 
|  |  | 
|  | cgpio->regs = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(cgpio->regs)) | 
|  | return PTR_ERR(cgpio->regs); | 
|  |  | 
|  | of_property_read_u32(pdev->dev.of_node, "ngpios", &num_gpios); | 
|  |  | 
|  | if (num_gpios > 32) { | 
|  | dev_err(&pdev->dev, "ngpios must be less or equal 32\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Set all pins as inputs by default, otherwise: | 
|  | * gpiochip_lock_as_irq: | 
|  | * tried to flag a GPIO set as output for IRQ | 
|  | * Generic GPIO driver stores the direction value internally, | 
|  | * so it needs to be changed before bgpio_init() is called. | 
|  | */ | 
|  | dir_prev = ioread32(cgpio->regs + CDNS_GPIO_DIRECTION_MODE); | 
|  | iowrite32(GENMASK(num_gpios - 1, 0), | 
|  | cgpio->regs + CDNS_GPIO_DIRECTION_MODE); | 
|  |  | 
|  | ret = bgpio_init(&cgpio->gc, &pdev->dev, 4, | 
|  | cgpio->regs + CDNS_GPIO_INPUT_VALUE, | 
|  | cgpio->regs + CDNS_GPIO_OUTPUT_VALUE, | 
|  | NULL, | 
|  | NULL, | 
|  | cgpio->regs + CDNS_GPIO_DIRECTION_MODE, | 
|  | BGPIOF_READ_OUTPUT_REG_SET); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "Failed to register generic gpio, %d\n", | 
|  | ret); | 
|  | goto err_revert_dir; | 
|  | } | 
|  |  | 
|  | cgpio->gc.label = dev_name(&pdev->dev); | 
|  | cgpio->gc.ngpio = num_gpios; | 
|  | cgpio->gc.parent = &pdev->dev; | 
|  | cgpio->gc.base = -1; | 
|  | cgpio->gc.owner = THIS_MODULE; | 
|  | cgpio->gc.request = cdns_gpio_request; | 
|  | cgpio->gc.free = cdns_gpio_free; | 
|  |  | 
|  | cgpio->pclk = devm_clk_get(&pdev->dev, NULL); | 
|  | if (IS_ERR(cgpio->pclk)) { | 
|  | ret = PTR_ERR(cgpio->pclk); | 
|  | dev_err(&pdev->dev, | 
|  | "Failed to retrieve peripheral clock, %d\n", ret); | 
|  | goto err_revert_dir; | 
|  | } | 
|  |  | 
|  | ret = clk_prepare_enable(cgpio->pclk); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, | 
|  | "Failed to enable the peripheral clock, %d\n", ret); | 
|  | goto err_revert_dir; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Optional irq_chip support | 
|  | */ | 
|  | irq = platform_get_irq(pdev, 0); | 
|  | if (irq >= 0) { | 
|  | struct gpio_irq_chip *girq; | 
|  |  | 
|  | girq = &cgpio->gc.irq; | 
|  | girq->chip = &cdns_gpio_irqchip; | 
|  | girq->parent_handler = cdns_gpio_irq_handler; | 
|  | girq->num_parents = 1; | 
|  | girq->parents = devm_kcalloc(&pdev->dev, 1, | 
|  | sizeof(*girq->parents), | 
|  | GFP_KERNEL); | 
|  | if (!girq->parents) { | 
|  | ret = -ENOMEM; | 
|  | goto err_disable_clk; | 
|  | } | 
|  | girq->parents[0] = irq; | 
|  | girq->default_type = IRQ_TYPE_NONE; | 
|  | girq->handler = handle_level_irq; | 
|  | } | 
|  |  | 
|  | ret = devm_gpiochip_add_data(&pdev->dev, &cgpio->gc, cgpio); | 
|  | if (ret < 0) { | 
|  | dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret); | 
|  | goto err_disable_clk; | 
|  | } | 
|  |  | 
|  | cgpio->bypass_orig = ioread32(cgpio->regs + CDNS_GPIO_BYPASS_MODE); | 
|  |  | 
|  | /* | 
|  | * Enable gpio outputs, ignored for input direction | 
|  | */ | 
|  | iowrite32(GENMASK(num_gpios - 1, 0), | 
|  | cgpio->regs + CDNS_GPIO_OUTPUT_EN); | 
|  | iowrite32(0, cgpio->regs + CDNS_GPIO_BYPASS_MODE); | 
|  |  | 
|  | platform_set_drvdata(pdev, cgpio); | 
|  | return 0; | 
|  |  | 
|  | err_disable_clk: | 
|  | clk_disable_unprepare(cgpio->pclk); | 
|  |  | 
|  | err_revert_dir: | 
|  | iowrite32(dir_prev, cgpio->regs + CDNS_GPIO_DIRECTION_MODE); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int cdns_gpio_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct cdns_gpio_chip *cgpio = platform_get_drvdata(pdev); | 
|  |  | 
|  | iowrite32(cgpio->bypass_orig, cgpio->regs + CDNS_GPIO_BYPASS_MODE); | 
|  | clk_disable_unprepare(cgpio->pclk); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id cdns_of_ids[] = { | 
|  | { .compatible = "cdns,gpio-r1p02" }, | 
|  | { /* sentinel */ }, | 
|  | }; | 
|  |  | 
|  | static struct platform_driver cdns_gpio_driver = { | 
|  | .driver = { | 
|  | .name = "cdns-gpio", | 
|  | .of_match_table = cdns_of_ids, | 
|  | }, | 
|  | .probe = cdns_gpio_probe, | 
|  | .remove = cdns_gpio_remove, | 
|  | }; | 
|  | module_platform_driver(cdns_gpio_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Jan Kotas <jank@cadence.com>"); | 
|  | MODULE_DESCRIPTION("Cadence GPIO driver"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_ALIAS("platform:cdns-gpio"); |