root/drivers/pinctrl/pinctrl-amdisp.c
/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * AMD ISP Pinctrl Driver
 *
 * Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved.
 *
 */

#include <linux/gpio/driver.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#include "pinctrl-amdisp.h"

#define DRV_NAME                "amdisp-pinctrl"
#define GPIO_CONTROL_PIN        4
#define GPIO_OFFSET_0           0x0
#define GPIO_OFFSET_1           0x4
#define GPIO_OFFSET_2           0x50

static const u32 gpio_offset[] = {
        GPIO_OFFSET_0,
        GPIO_OFFSET_1,
        GPIO_OFFSET_2
};

struct amdisp_pinctrl_data {
        const struct pinctrl_pin_desc *pins;
        unsigned int npins;
        const struct amdisp_function *functions;
        unsigned int nfunctions;
        const struct amdisp_pingroup *groups;
        unsigned int ngroups;
};

static const struct amdisp_pinctrl_data amdisp_pinctrl_data = {
        .pins = amdisp_pins,
        .npins = ARRAY_SIZE(amdisp_pins),
        .functions = amdisp_functions,
        .nfunctions = ARRAY_SIZE(amdisp_functions),
        .groups = amdisp_groups,
        .ngroups = ARRAY_SIZE(amdisp_groups),
};

struct amdisp_pinctrl {
        struct device *dev;
        struct pinctrl_dev *pctrl;
        struct pinctrl_desc desc;
        struct pinctrl_gpio_range gpio_range;
        struct gpio_chip gc;
        const struct amdisp_pinctrl_data *data;
        void __iomem *gpiobase;
        raw_spinlock_t lock;
};

static int amdisp_get_groups_count(struct pinctrl_dev *pctldev)
{
        struct amdisp_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);

        return pctrl->data->ngroups;
}

static const char *amdisp_get_group_name(struct pinctrl_dev *pctldev,
                                         unsigned int group)
{
        struct amdisp_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);

        return pctrl->data->groups[group].name;
}

static int amdisp_get_group_pins(struct pinctrl_dev *pctldev,
                                 unsigned int group,
                                 const unsigned int **pins,
                                 unsigned int *num_pins)
{
        struct amdisp_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctldev);

        *pins = pctrl->data->groups[group].pins;
        *num_pins = pctrl->data->groups[group].npins;
        return 0;
}

static const struct pinctrl_ops amdisp_pinctrl_ops = {
        .get_groups_count       = amdisp_get_groups_count,
        .get_group_name         = amdisp_get_group_name,
        .get_group_pins         = amdisp_get_group_pins,
};

static int amdisp_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
{
        /* amdisp gpio only has output mode */
        return GPIO_LINE_DIRECTION_OUT;
}

static int amdisp_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio)
{
        return -EOPNOTSUPP;
}

static int amdisp_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio,
                                        int value)
{
        /* Nothing to do, amdisp gpio only has output mode */
        return 0;
}

static int amdisp_gpio_get(struct gpio_chip *gc, unsigned int gpio)
{
        unsigned long flags;
        u32 pin_reg;
        struct amdisp_pinctrl *pctrl = gpiochip_get_data(gc);

        raw_spin_lock_irqsave(&pctrl->lock, flags);
        pin_reg = readl(pctrl->gpiobase + gpio_offset[gpio]);
        raw_spin_unlock_irqrestore(&pctrl->lock, flags);

        return !!(pin_reg & BIT(GPIO_CONTROL_PIN));
}

static int amdisp_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value)
{
        unsigned long flags;
        u32 pin_reg;
        struct amdisp_pinctrl *pctrl = gpiochip_get_data(gc);

        raw_spin_lock_irqsave(&pctrl->lock, flags);
        pin_reg = readl(pctrl->gpiobase + gpio_offset[gpio]);
        if (value)
                pin_reg |= BIT(GPIO_CONTROL_PIN);
        else
                pin_reg &= ~BIT(GPIO_CONTROL_PIN);
        writel(pin_reg, pctrl->gpiobase + gpio_offset[gpio]);
        raw_spin_unlock_irqrestore(&pctrl->lock, flags);

        return 0;
}

static int amdisp_gpiochip_add(struct platform_device *pdev,
                               struct amdisp_pinctrl *pctrl)
{
        struct gpio_chip *gc = &pctrl->gc;
        struct pinctrl_gpio_range *grange = &pctrl->gpio_range;
        int ret;

        gc->label               = dev_name(pctrl->dev);
        gc->parent              = &pdev->dev;
        gc->names               = amdisp_range_pins_name;
        gc->request             = gpiochip_generic_request;
        gc->free                = gpiochip_generic_free;
        gc->get_direction       = amdisp_gpio_get_direction;
        gc->direction_input     = amdisp_gpio_direction_input;
        gc->direction_output    = amdisp_gpio_direction_output;
        gc->get                 = amdisp_gpio_get;
        gc->set                 = amdisp_gpio_set;
        gc->base                = -1;
        gc->ngpio               = ARRAY_SIZE(amdisp_range_pins);

        grange->id              = 0;
        grange->pin_base        = 0;
        grange->base            = 0;
        grange->pins            = amdisp_range_pins;
        grange->npins           = ARRAY_SIZE(amdisp_range_pins);
        grange->name            = gc->label;
        grange->gc              = gc;

        ret = devm_gpiochip_add_data(&pdev->dev, gc, pctrl);
        if (ret)
                return ret;

        pinctrl_add_gpio_range(pctrl->pctrl, grange);

        return 0;
}

static int amdisp_pinctrl_probe(struct platform_device *pdev)
{
        struct amdisp_pinctrl *pctrl;
        struct resource *res;
        int ret;

        pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
        if (!pctrl)
                return -ENOMEM;

        pdev->dev.init_name = DRV_NAME;

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res)
                return -EINVAL;

        pctrl->gpiobase = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(pctrl->gpiobase))
                return PTR_ERR(pctrl->gpiobase);

        platform_set_drvdata(pdev, pctrl);

        pctrl->dev = &pdev->dev;
        pctrl->data = &amdisp_pinctrl_data;
        pctrl->desc.owner = THIS_MODULE;
        pctrl->desc.pctlops = &amdisp_pinctrl_ops;
        pctrl->desc.pmxops = NULL;
        pctrl->desc.name = dev_name(&pdev->dev);
        pctrl->desc.pins = pctrl->data->pins;
        pctrl->desc.npins = pctrl->data->npins;
        ret = devm_pinctrl_register_and_init(&pdev->dev, &pctrl->desc,
                                             pctrl, &pctrl->pctrl);
        if (ret)
                return ret;

        ret = pinctrl_enable(pctrl->pctrl);
        if (ret)
                return ret;

        ret = amdisp_gpiochip_add(pdev, pctrl);
        if (ret)
                return ret;

        return 0;
}

static struct platform_driver amdisp_pinctrl_driver = {
        .driver = {
                .name = DRV_NAME,
        },
        .probe = amdisp_pinctrl_probe,
};
module_platform_driver(amdisp_pinctrl_driver);

MODULE_AUTHOR("Benjamin Chan <benjamin.chan@amd.com>");
MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
MODULE_DESCRIPTION("AMDISP pinctrl driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);