// SPDX-License-Identifier: GPL-2.0
/* Nerve DE01 Linux driver
* Copyright(c) 2021 TTTech Industrial Automation AG.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see <http://www.gnu.org/licenses/>.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*
* Contact Information:
* support@tttech-industrial.com
* TTTech Industrial Automation AG, Schoenbrunnerstrasse 7, 1040 Vienna, Austria
*/

#include <asm/types.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/watchdog.h>

/* set the approprate version at build time */
#ifndef DE01_VERSION
#define DE01_VERSION "unknown"
#endif

MODULE_LICENSE("GPL");
MODULE_AUTHOR("TTTech Industrial Automation AG");
MODULE_VERSION(DE01_VERSION);

#define PIO_DATA	0x0
#define PIO_DIR		0x4
#define PIO_IRQ_MASK	0x8
#define PIO_EDGE_CAP	0xc

#define PIO_FLAG_IN	1
#define PIO_FLAG_OUT	2
#define PIO_FLAG_IRQ	4

#define DE01_PIO_INT_IN	0x00
#define DE01_PIO_OUT	0x10
#define DE01_LEDS	0x20
#define DE01_WDOG	0x40
#define DE01_PIO_MIN_LEN	0x20
#define DE01_LED_MIN_LEN	0x40
#define DE01_WDOG_MIN_LEN	0x50

#define DE01_PIO_INT_IN_IRQ_TYPE	IRQ_TYPE_EDGE_BOTH


#define DE01_WDOG_CTRL	0
#define DE01_WDOG_TIMELEFT	4
#define DE01_WDOG_STATUS	8

static const char *de01_pio_int_in_io_names[] = {
	"ext-reset",
	"function-button",
	"sib-power-fail-5v",
	"sib-power-fail-12v",
	"sib-power-fail-24v",
	"cpum-cb-reset",
};

static const char *de01_pio_out_io_names[] = {
	"fpga-status-led-green",
	"fpga-status-led-red",
	"fpga-function-led-green",
	"fpga-function-led-red",
	"light-led",
};

static const struct {
	const char *name;
	const char *trigger;
} de01_led_table[] = {
	{ "de01:green:status", },
	{ "de01:red:status", },
	{ "de01:green:function", },
	{ "de01:red:function", },
	{ "de01:blue:light", },
};

struct altr_pio {
	struct gpio_chip gc;
	void __iomem *regs;
	int flags, irq_type;
	raw_spinlock_t lock;
};

static void printk_altr_pio(struct altr_pio *pio, const char *label)
{
	printk(KERN_DEBUG KBUILD_MODNAME " %s: altr_pio %s(data=%#x dir=%#x irq_mask=%#x edge_cap=%#x)\n",
		label,
		pio->gc.label,
		ioread32(pio->regs + PIO_DATA),
		ioread32(pio->regs + PIO_DIR),
		ioread32(pio->regs + PIO_IRQ_MASK),
		ioread32(pio->regs + PIO_EDGE_CAP));
}

static void de01_irq_mask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	long mask;
	raw_spin_lock(&pio->lock);
	mask = ioread32(pio->regs + PIO_IRQ_MASK);
	mask &= ~BIT(irqd_to_hwirq(d));
	iowrite32(mask, pio->regs + PIO_IRQ_MASK);
	raw_spin_unlock(&pio->lock);
	printk_altr_pio(pio, __func__);
}

static void de01_irq_unmask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	long mask;
	raw_spin_lock(&pio->lock);
	iowrite32(BIT(irqd_to_hwirq(d)), pio->regs + PIO_EDGE_CAP);
	mask = ioread32(pio->regs + PIO_IRQ_MASK);
	mask |= BIT(irqd_to_hwirq(d));
	iowrite32(mask, pio->regs + PIO_IRQ_MASK);
	raw_spin_unlock(&pio->lock);
	printk_altr_pio(pio, __func__);
}

static int de01_irq_set_type(struct irq_data *d, unsigned int type)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	printk_altr_pio(pio, __func__);
	if (type == IRQ_TYPE_NONE) {
		irq_set_handler_locked(d, handle_bad_irq);
		return 0;
	}
	// fixed in hardware, just check
	if (type == pio->irq_type) {
		if (type == IRQ_TYPE_LEVEL_HIGH)
			irq_set_handler_locked(d, handle_level_irq);
		else
			irq_set_handler_locked(d, handle_simple_irq);
		return 0;
	}
	irq_set_handler_locked(d, handle_bad_irq);
	return -EINVAL;
}

static unsigned int de01_irq_startup(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	printk_altr_pio(pio, __func__);
	de01_irq_unmask(d);
	return 0;
}

static struct irq_chip de01_irq_chip = {
	.name		= "de01-pio",
	.irq_mask	= de01_irq_mask,
	.irq_unmask	= de01_irq_unmask,
	.irq_set_type	= de01_irq_set_type,
	.irq_startup	= de01_irq_startup,
	.irq_shutdown	= de01_irq_mask,
};

static int de01_direction_input(struct gpio_chip *gc, unsigned offset)
{
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	long dir;
	if (!(pio->flags & PIO_FLAG_IN))
		return -EINVAL;
	raw_spin_lock(&pio->lock);
	dir = ioread32(pio->regs + PIO_DIR);
	dir &= ~BIT(offset);
	iowrite32(dir, pio->regs + PIO_DIR);
	raw_spin_unlock(&pio->lock);
	printk_altr_pio(pio, __func__);
	return 0;
}

static int de01_direction_output(struct gpio_chip *gc, unsigned offset, int value)
{
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	long data, dir;
	if (!(pio->flags & PIO_FLAG_OUT))
		return -EINVAL;
	raw_spin_lock(&pio->lock);
	data = ioread32(pio->regs + PIO_DATA);
	if (value)
		data |= BIT(offset);
	else
		data &= ~BIT(offset);
	iowrite32(data, pio->regs + PIO_DATA);
	dir = ioread32(pio->regs + PIO_DIR);
	dir |= BIT(offset);
	iowrite32(dir, pio->regs + PIO_DIR);
	raw_spin_unlock(&pio->lock);
	printk_altr_pio(pio, __func__);
	return 0;
}

static int de01_get(struct gpio_chip *gc, unsigned offset)
{
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	printk_altr_pio(pio, __func__);
	return !!(ioread32(pio->regs + PIO_DATA) & BIT(offset));
}

static void de01_set(struct gpio_chip *gc, unsigned offset, int value)
{
	struct altr_pio *pio = container_of(gc, struct altr_pio, gc);
	long data;
	raw_spin_lock(&pio->lock);
	data = ioread32(pio->regs + PIO_DATA);
	if (value)
		data |= BIT(offset);
	else
		data &= ~BIT(offset);
	iowrite32(data, pio->regs + PIO_DATA);
	raw_spin_unlock(&pio->lock);
	printk_altr_pio(pio, __func__);
}

static irqreturn_t de01_altr_pio_irq_handler(void *dev_id)
{
	struct altr_pio *pio = dev_id;
	struct gpio_irq_chip *ic = &pio->gc.irq;
	irqreturn_t rc = IRQ_NONE;
	long mask;
	long status;
	printk_altr_pio(pio, __func__);
	raw_spin_lock(&pio->lock);
	mask = ioread32(pio->regs + PIO_IRQ_MASK);
	while ((status = (ioread32(pio->regs + PIO_EDGE_CAP) & mask))) {
		long bit;
		iowrite32(status, pio->regs + PIO_EDGE_CAP); // clear pending irq
		for_each_set_bit(bit, &status, pio->gc.ngpio) {
			generic_handle_irq(irq_find_mapping(ic->domain, bit));
		}
		rc = IRQ_HANDLED;
	}
	raw_spin_unlock(&pio->lock);
	return rc;
}


static irqreturn_t de01_altr_irq_handler(int irq, void *dev_id)
{
	return de01_altr_pio_irq_handler(dev_id);
}

static irqreturn_t de01_altr_msi_irq_handler(int irq, void *dev_id)
{
	(void)de01_altr_pio_irq_handler(dev_id);
	return IRQ_HANDLED;
}

struct de01_led {
	struct led_classdev cdev;
	u32 __iomem *pwm_reg;
	// fallback to Altera PIO control if LED PWM not implemented
	struct altr_pio *pio;
	unsigned offset;
};

static void de01_led_set(struct led_classdev *cdev, enum led_brightness bright)
{
	struct de01_led *led = container_of(cdev, struct de01_led, cdev);
	if (!led->pwm_reg)
		de01_set(&led->pio->gc, led->offset, bright);
	else
		iowrite32(bright, led->pwm_reg);
}

static enum led_brightness de01_led_get(struct led_classdev *cdev)
{
	struct de01_led *led = container_of(cdev, struct de01_led, cdev);
	if (!led->pwm_reg)
		return de01_get(&led->pio->gc, led->offset);
	else
		return ioread32(led->pwm_reg);
}

struct de01_watchdog {
	struct watchdog_device wdev;
	void __iomem *regs;
};

static void printk_de01_watchdog(struct de01_watchdog *wdog, const char *label)
{
	printk(KERN_DEBUG KBUILD_MODNAME " %s: de01_watchdog(ctrl=%#x timeleft=%#x status=%#x)\n",
		label,
		ioread32(wdog->regs + DE01_WDOG_CTRL),
		ioread32(wdog->regs + DE01_WDOG_TIMELEFT),
		ioread32(wdog->regs + DE01_WDOG_STATUS));
}

static int de01_watchdog_start(struct watchdog_device *wdev)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	iowrite32(wdev->timeout, wdog->regs + DE01_WDOG_TIMELEFT);
	iowrite32(1, wdog->regs + DE01_WDOG_CTRL);
	printk_de01_watchdog(wdog, __func__);
	return 0;
}

static int de01_watchdog_stop(struct watchdog_device *wdev)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	iowrite32(0, wdog->regs + DE01_WDOG_CTRL);
	printk_de01_watchdog(wdog, __func__);
	return 0;
}

static int de01_watchdog_ping(struct watchdog_device *wdev)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	iowrite32(wdev->timeout, wdog->regs + DE01_WDOG_TIMELEFT);
	printk_de01_watchdog(wdog, __func__);
	return 0;
}

static unsigned int de01_watchdog_status(struct watchdog_device *wdev)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	unsigned int status = ioread32(wdog->regs + DE01_WDOG_STATUS);
	printk_de01_watchdog(wdog, __func__);
	return status;
}

static int de01_watchdog_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	wdev->timeout = timeout;
	iowrite32(timeout, wdog->regs + DE01_WDOG_TIMELEFT);
	printk_de01_watchdog(wdog, __func__);
	return 0;
}

static unsigned int de01_watchdog_get_timeleft(struct watchdog_device *wdev)
{
	struct de01_watchdog *wdog = container_of(wdev, struct de01_watchdog, wdev);
	unsigned int timeleft = ioread32(wdog->regs + DE01_WDOG_TIMELEFT);
	printk_de01_watchdog(wdog, __func__);
	return timeleft;
}

static const struct watchdog_ops de01_watchdog_ops = {
	.owner = THIS_MODULE,
	.start = de01_watchdog_start,
	.stop = de01_watchdog_stop,
	.ping = de01_watchdog_ping,
	.status = de01_watchdog_status,
	.set_timeout = de01_watchdog_set_timeout,
	.get_timeleft = de01_watchdog_get_timeleft,
};

struct de01_device {
	struct pci_dev *pdev;
	void __iomem *bar0;
	struct altr_pio pio_int_in, pio_out;
	struct de01_led *leds[ARRAY_SIZE(de01_led_table)];
	struct de01_watchdog *wdog;
};

static int de01_leds_probe(struct de01_device *de01dev)
{
	int rc, i;
	for (i = 0; i < ARRAY_SIZE(de01_led_table); i++) {
		struct de01_led *led = kzalloc(sizeof(*led), GFP_KERNEL);
		if (!led)
			return -ENOMEM;

		led->cdev.name = de01_led_table[i].name;
		led->cdev.brightness_set = de01_led_set;
		led->cdev.brightness_get = de01_led_get;
		led->cdev.default_trigger = de01_led_table[i].trigger;

		if (pci_resource_len(de01dev->pdev, 0) >= DE01_LED_MIN_LEN) {
			led->pwm_reg = (u32*)(de01dev->bar0 + DE01_LEDS) + i;
		} else {
			led->pio = &de01dev->pio_out;
			led->offset = i;
		}

		rc = led_classdev_register(&de01dev->pdev->dev, &led->cdev);
		if (rc) {
			kfree(led);
			return rc;
		}

		de01dev->leds[i] = led;
	}
	return 0;
}

static void de01_leds_remove(struct de01_device *de01dev)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(de01_led_table); i++) {
		struct de01_led *led = de01dev->leds[i];
		if (!led)
			continue;

		led_classdev_unregister(&led->cdev);
		kfree(led);
	}
}

static const struct watchdog_info de01_watchdog_info = {
	.identity = KBUILD_MODNAME " watchdog",
	.options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOS_DISABLECARD | WDIOS_ENABLECARD,
};

static int de01_watchdog_probe(struct de01_device *de01dev)
{
	int rc;
	struct de01_watchdog *wdog;

	if (pci_resource_len(de01dev->pdev, 0) < DE01_WDOG_MIN_LEN)
		return 0;

	wdog = kzalloc(sizeof(*wdog), GFP_KERNEL);
	if (!wdog)
		return -ENOMEM;

	wdog->wdev.parent = &de01dev->pdev->dev;
	wdog->wdev.info = &de01_watchdog_info;
	wdog->wdev.ops = &de01_watchdog_ops;
	wdog->wdev.timeout = 60;
	wdog->wdev.min_timeout = 0;
	wdog->wdev.max_timeout = 0xFFFF;
	wdog->regs = de01dev->bar0 + DE01_WDOG;

	de01_watchdog_stop(&wdog->wdev);
	de01_watchdog_set_timeout(&wdog->wdev, wdog->wdev.timeout);

	printk_de01_watchdog(wdog, __func__);

	rc = watchdog_register_device(&wdog->wdev);
	if (rc) {
		kfree(wdog);
		return rc;
	}

	de01dev->wdog = wdog;
	return 0;
}

static void de01_watchdog_remove(struct de01_device *de01dev)
{
	if (de01dev->wdog) {
		de01_watchdog_stop(&de01dev->wdog->wdev);
		watchdog_unregister_device(&de01dev->wdog->wdev);
		kfree(de01dev->wdog);
	}
}

static int de01_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	int rc = 0;
	struct de01_device *de01dev;

	if (!ent)
		goto remove;

	de01dev = kzalloc(sizeof(*de01dev), GFP_KERNEL);
	if (!de01dev) {
		return -ENOMEM;
	}

	de01dev->pdev = pdev;
	pci_set_drvdata(pdev, de01dev);

	rc = pci_enable_device(pdev);
	if (rc)
		goto erro_out_enable;

	pci_set_master(pdev);

	rc = pci_request_regions(pdev, KBUILD_MODNAME);
	if (rc)
		goto err_out_regions;

	de01dev->bar0 = pci_ioremap_bar(pdev, 0);
	if (!de01dev->bar0) {
		rc = -ENOMEM;
		dev_err(&pdev->dev, "BAR0 iomap failed\n");
		goto err_out_disable;
	}
	if (pci_resource_len(pdev, 0) < DE01_PIO_MIN_LEN) {
		rc = -ENODEV;
		dev_err(&pdev->dev, "BAR0 has wrong size\n");
		goto err_out_iounmap;
	}

	de01dev->pio_int_in.regs = de01dev->bar0 + DE01_PIO_INT_IN;
	de01dev->pio_int_in.flags = PIO_FLAG_IN|PIO_FLAG_IRQ;
	de01dev->pio_int_in.irq_type = DE01_PIO_INT_IN_IRQ_TYPE;

	de01dev->pio_out.regs = de01dev->bar0 + DE01_PIO_OUT;
	de01dev->pio_out.flags = PIO_FLAG_OUT;

	de01dev->pio_int_in.gc.label = "de01-pio-int-in";
	de01dev->pio_int_in.gc.parent = &pdev->dev;
	de01dev->pio_int_in.gc.direction_input = de01_direction_input;
	de01dev->pio_int_in.gc.direction_output = de01_direction_output;
	de01dev->pio_int_in.gc.get = de01_get;
	de01dev->pio_int_in.gc.set = de01_set;
	de01dev->pio_int_in.gc.base = -1;
	de01dev->pio_int_in.gc.ngpio = ARRAY_SIZE(de01_pio_int_in_io_names);
	de01dev->pio_int_in.gc.names = de01_pio_int_in_io_names;
	raw_spin_lock_init(&de01dev->pio_int_in.lock);

	printk_altr_pio(&de01dev->pio_int_in, __func__);

	rc = gpiochip_add(&de01dev->pio_int_in.gc);
	if (rc)
		goto err_out_irq;

	de01dev->pio_out.gc.label = "de01-pio-out";
	de01dev->pio_out.gc.direction_input = de01_direction_input;
	de01dev->pio_out.gc.direction_output = de01_direction_output;
	de01dev->pio_out.gc.get = de01_get;
	de01dev->pio_out.gc.set = de01_set;
	de01dev->pio_out.gc.base = -1;
	de01dev->pio_out.gc.ngpio = ARRAY_SIZE(de01_pio_out_io_names);
	de01dev->pio_out.gc.names = de01_pio_out_io_names;
	raw_spin_lock_init(&de01dev->pio_out.lock);

	printk_altr_pio(&de01dev->pio_out, __func__);

	rc = gpiochip_add(&de01dev->pio_out.gc);
	if (rc)
		goto err_out_gpiochip_int_in;

	iowrite32(0, de01dev->pio_int_in.regs + PIO_IRQ_MASK); // disable irq
	iowrite32(0xff, de01dev->pio_int_in.regs + PIO_EDGE_CAP); // clear pending irq

	/* Try using MSI interrupts */
	if (pci_enable_msi(pdev)) {
		dev_warn(&pdev->dev,
			 "Unable to use MSI interrupts, falling back to legacy\n");
		rc = devm_request_irq(&pdev->dev, pdev->irq,
				      de01_altr_irq_handler,
				      IRQF_SHARED, "de01-intx",
				      &de01dev->pio_int_in);
	} else {
		dev_info(&pdev->dev, "Using MSI interrupts\n");
		rc = devm_request_irq(&pdev->dev, pdev->irq,
				      de01_altr_msi_irq_handler,
				      0, "de01-msi", &de01dev->pio_int_in);
	}

	if (rc)
		goto err_out_gpiochip_out;

	rc = gpiochip_irqchip_add(&de01dev->pio_int_in.gc, &de01_irq_chip, 0, handle_edge_irq, IRQ_TYPE_NONE);
	if (rc) {
		dev_err(&pdev->dev, "can not register irqchip\n");
		goto err_out_gpiochip_out;
	}

	rc = de01_leds_probe(de01dev);
	if (rc)
		dev_warn(&pdev->dev, "LED probe failed (%d)\n", rc);

	rc = de01_watchdog_probe(de01dev);
	if (rc)
		dev_warn(&pdev->dev, "Watchdog probe failed (%d)\n", rc);

	return 0;

remove:
	de01dev = pci_get_drvdata(pdev);
	de01_watchdog_remove(de01dev);
	de01_leds_remove(de01dev);
err_out_gpiochip_out:
	gpiochip_remove(&de01dev->pio_out.gc);
err_out_gpiochip_int_in:
	gpiochip_remove(&de01dev->pio_int_in.gc);
err_out_irq:
	iowrite32(0, de01dev->pio_int_in.regs + PIO_IRQ_MASK); // disable irq
err_out_iounmap:
	pci_iounmap(pdev, de01dev->bar0);
err_out_disable:
	pci_disable_device(pdev);
err_out_regions:
	pci_release_regions(pdev);
erro_out_enable:
	kfree(de01dev);
	return rc;
}

static void de01_remove(struct pci_dev *pdev)
{
	de01_probe(pdev, 0);
};

static struct pci_device_id de01_id_tbl[] = {
	{ PCI_DEVICE(0x1c7e, 0xde01) },
	{ 0, },
};

MODULE_DEVICE_TABLE(pci, de01_id_tbl);

static struct pci_driver de01_driver = {
	.name = KBUILD_MODNAME,
	.id_table = de01_id_tbl,
	.probe = de01_probe,
	.remove = de01_remove,
};

module_pci_driver(de01_driver);
