root/drivers/gpio/gpio-mpfs.c
// SPDX-License-Identifier: (GPL-2.0)
/*
 * Microchip PolarFire SoC (MPFS) GPIO controller driver
 *
 * Copyright (c) 2018-2024 Microchip Technology Inc. and its subsidiaries
 */

#include <linux/clk.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/gpio/driver.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>

#define MPFS_GPIO_CTRL(i)               (0x4 * (i))
#define MPFS_MAX_NUM_GPIO               32
#define MPFS_GPIO_EN_INT                BIT(3)
#define MPFS_GPIO_EN_OUT_BUF            BIT(2)
#define MPFS_GPIO_EN_IN                 BIT(1)
#define MPFS_GPIO_EN_OUT                BIT(0)
#define MPFS_GPIO_DIR_MASK              GENMASK(2, 0)

#define MPFS_GPIO_TYPE_INT_EDGE_BOTH    0x80
#define MPFS_GPIO_TYPE_INT_EDGE_NEG     0x60
#define MPFS_GPIO_TYPE_INT_EDGE_POS     0x40
#define MPFS_GPIO_TYPE_INT_LEVEL_LOW    0x20
#define MPFS_GPIO_TYPE_INT_LEVEL_HIGH   0x00
#define MPFS_GPIO_TYPE_INT_MASK         GENMASK(7, 5)
#define MPFS_IRQ_REG                    0x80

#define MPFS_INP_REG                    0x84
#define COREGPIO_INP_REG                0x90
#define MPFS_OUTP_REG                   0x88
#define COREGPIO_OUTP_REG               0xA0

struct mpfs_gpio_reg_offsets {
        u8 inp;
        u8 outp;
};

struct mpfs_gpio_chip {
        struct regmap *regs;
        const struct mpfs_gpio_reg_offsets *offsets;
        struct gpio_chip gc;
};

static const struct regmap_config mpfs_gpio_regmap_config = {
        .reg_bits = 32,
        .reg_stride = 4,
        .val_bits = 32,
        .use_raw_spinlock = true,
};

static int mpfs_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio_index)
{
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);

        regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
                           MPFS_GPIO_DIR_MASK, MPFS_GPIO_EN_IN);

        return 0;
}

static int mpfs_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio_index, int value)
{
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);

        regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
                           MPFS_GPIO_DIR_MASK, MPFS_GPIO_EN_OUT | MPFS_GPIO_EN_OUT_BUF);
        regmap_update_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp, BIT(gpio_index),
                           value << gpio_index);

        return 0;
}

static int mpfs_gpio_get_direction(struct gpio_chip *gc,
                                   unsigned int gpio_index)
{
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
        unsigned int gpio_cfg;

        regmap_read(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index), &gpio_cfg);
        if (gpio_cfg & MPFS_GPIO_EN_IN)
                return GPIO_LINE_DIRECTION_IN;

        return GPIO_LINE_DIRECTION_OUT;
}

static int mpfs_gpio_get(struct gpio_chip *gc, unsigned int gpio_index)
{
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);

        if (mpfs_gpio_get_direction(gc, gpio_index) == GPIO_LINE_DIRECTION_OUT)
                return regmap_test_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp, BIT(gpio_index));
        else
                return regmap_test_bits(mpfs_gpio->regs, mpfs_gpio->offsets->inp, BIT(gpio_index));
}

static int mpfs_gpio_set(struct gpio_chip *gc, unsigned int gpio_index, int value)
{
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
        int ret;

        mpfs_gpio_get(gc, gpio_index);

        ret = regmap_update_bits(mpfs_gpio->regs, mpfs_gpio->offsets->outp,
                                 BIT(gpio_index), value << gpio_index);

        mpfs_gpio_get(gc, gpio_index);

        return ret;
}

static int mpfs_gpio_irq_set_type(struct irq_data *data, unsigned int type)
{
        struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
        int gpio_index = irqd_to_hwirq(data) % 32;
        u32 interrupt_type;

        switch (type) {
        case IRQ_TYPE_EDGE_BOTH:
                interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_BOTH;
                break;
        case IRQ_TYPE_EDGE_FALLING:
                interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_NEG;
                break;
        case IRQ_TYPE_EDGE_RISING:
                interrupt_type = MPFS_GPIO_TYPE_INT_EDGE_POS;
                break;
        case IRQ_TYPE_LEVEL_HIGH:
                interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_HIGH;
                break;
        case IRQ_TYPE_LEVEL_LOW:
                interrupt_type = MPFS_GPIO_TYPE_INT_LEVEL_LOW;
                break;
        }

        regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
                           MPFS_GPIO_TYPE_INT_MASK, interrupt_type);

        return 0;
}

static void mpfs_gpio_irq_unmask(struct irq_data *data)
{
        struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
        int gpio_index = irqd_to_hwirq(data) % 32;

        gpiochip_enable_irq(gc, gpio_index);
        mpfs_gpio_direction_input(gc, gpio_index);
        regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
                             MPFS_GPIO_EN_INT, MPFS_GPIO_EN_INT);
}

static void mpfs_gpio_irq_mask(struct irq_data *data)
{
        struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
        struct mpfs_gpio_chip *mpfs_gpio = gpiochip_get_data(gc);
        int gpio_index = irqd_to_hwirq(data) % 32;

        regmap_update_bits(mpfs_gpio->regs, MPFS_GPIO_CTRL(gpio_index),
                           MPFS_GPIO_EN_INT, 0);
        gpiochip_disable_irq(gc, gpio_index);
}

static const struct irq_chip mpfs_gpio_irqchip = {
        .name = "MPFS GPIO",
        .irq_set_type = mpfs_gpio_irq_set_type,
        .irq_mask = mpfs_gpio_irq_mask,
        .irq_unmask = mpfs_gpio_irq_unmask,
        .flags = IRQCHIP_IMMUTABLE | IRQCHIP_MASK_ON_SUSPEND,
        GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

static void mpfs_gpio_irq_handler(struct irq_desc *desc)
{
        struct irq_chip *irqchip = irq_desc_get_chip(desc);
        struct mpfs_gpio_chip *mpfs_gpio = irq_desc_get_handler_data(desc);
        unsigned long status;
        u32 val;
        int i;

        chained_irq_enter(irqchip, desc);

        regmap_read(mpfs_gpio->regs, MPFS_IRQ_REG, &val);
        status = val;
        for_each_set_bit(i, &status, MPFS_MAX_NUM_GPIO) {
                regmap_write(mpfs_gpio->regs, MPFS_IRQ_REG, BIT(i));
                generic_handle_domain_irq(mpfs_gpio->gc.irq.domain, i);
        }

        chained_irq_exit(irqchip, desc);
}

static int mpfs_gpio_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct device_node *node = dev->of_node;
        struct mpfs_gpio_chip *mpfs_gpio;
        struct gpio_irq_chip *girq;
        struct clk *clk;
        void __iomem *base;
        int ngpios, nirqs, ret;

        mpfs_gpio = devm_kzalloc(dev, sizeof(*mpfs_gpio), GFP_KERNEL);
        if (!mpfs_gpio)
                return -ENOMEM;

        mpfs_gpio->offsets = device_get_match_data(&pdev->dev);

        base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(base))
                return dev_err_probe(dev, PTR_ERR(base), "failed to ioremap memory resource\n");

        mpfs_gpio->regs = devm_regmap_init_mmio(dev, base, &mpfs_gpio_regmap_config);
        if (IS_ERR(mpfs_gpio->regs))
                return dev_err_probe(dev, PTR_ERR(mpfs_gpio->regs),
                                     "failed to initialise regmap\n");

        clk = devm_clk_get_enabled(dev, NULL);
        if (IS_ERR(clk))
                return dev_err_probe(dev, PTR_ERR(clk), "failed to get and enable clock\n");

        ngpios = MPFS_MAX_NUM_GPIO;
        device_property_read_u32(dev, "ngpios", &ngpios);
        if (ngpios > MPFS_MAX_NUM_GPIO)
                ngpios = MPFS_MAX_NUM_GPIO;

        mpfs_gpio->gc.direction_input = mpfs_gpio_direction_input;
        mpfs_gpio->gc.direction_output = mpfs_gpio_direction_output;
        mpfs_gpio->gc.get_direction = mpfs_gpio_get_direction;
        mpfs_gpio->gc.get = mpfs_gpio_get;
        mpfs_gpio->gc.set = mpfs_gpio_set;
        mpfs_gpio->gc.base = -1;
        mpfs_gpio->gc.ngpio = ngpios;
        mpfs_gpio->gc.label = dev_name(dev);
        mpfs_gpio->gc.parent = dev;
        mpfs_gpio->gc.owner = THIS_MODULE;

        nirqs = of_irq_count(node);
        if (nirqs > MPFS_MAX_NUM_GPIO)
                return -ENXIO;

        if (nirqs) {
                girq = &mpfs_gpio->gc.irq;

                gpio_irq_chip_set_chip(girq, &mpfs_gpio_irqchip);

                girq->num_parents = nirqs;
                girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents,
                                             sizeof(*girq->parents), GFP_KERNEL);
                if (!girq->parents)
                        return -ENOMEM;

                for (int i = 0; i < nirqs; i++) {
                        ret = platform_get_irq(pdev, i);
                        if (ret < 0)
                                return ret;

                        girq->parents[i] = ret;
                        girq->parent_handler_data = mpfs_gpio;
                        girq->parent_handler = mpfs_gpio_irq_handler;
                }

                girq->handler = handle_level_irq;
                girq->default_type = IRQ_TYPE_NONE;
        }

        return devm_gpiochip_add_data(dev, &mpfs_gpio->gc, mpfs_gpio);
}

static const struct mpfs_gpio_reg_offsets mpfs_reg_offsets = {
        .inp = MPFS_INP_REG,
        .outp = MPFS_OUTP_REG,
};

static const struct mpfs_gpio_reg_offsets coregpio_reg_offsets = {
        .inp = COREGPIO_INP_REG,
        .outp = COREGPIO_OUTP_REG,
};

static const struct of_device_id mpfs_gpio_of_ids[] = {
        {
                .compatible = "microchip,mpfs-gpio",
                .data = &mpfs_reg_offsets,
        }, {
                .compatible = "microchip,coregpio-rtl-v3",
                .data = &coregpio_reg_offsets,
        },
        { /* end of list */ }
};

static struct platform_driver mpfs_gpio_driver = {
        .probe = mpfs_gpio_probe,
        .driver = {
                .name = "microchip,mpfs-gpio",
                .of_match_table = mpfs_gpio_of_ids,
        },
};
builtin_platform_driver(mpfs_gpio_driver);