// SPDX-License-Identifier: GPL-2.0-only
/*
 * Mitwell RSQ10 LED driver
 * Copyright (c) 2024, TTTech Industrial Automation AG.
 *
 * Author: Helmut Buchsbaum <helmut.buchsbaum@tttech-industrial.com>
 *
 */
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/spinlock.h>
#include "../platform/platform-rsq10.h"

struct rsq10_led {
	char *name;
	u32 value;
	u16 port;
	struct led_classdev cdev;
};

static struct rsq10_led rsq10_leds[] = {
	{ .name = "rsq10:blue:status-1", .value = BIT(0) },
	{ .name = "rsq10:blue:status-2", .value = BIT(1) },
	{ .name = "rsq10:blue:status-3", .value = BIT(2) },
	{ .name = "rsq10:blue:status-4", .value = BIT(3) },
	{ .name = "rsq10:blue:status-5", .value = BIT(4) },
	{ .name = "rsq10:blue:status-6", .value = BIT(5) },
	{ .name = "rsq10:blue:status-7", .value = BIT(6) },
	{ /* sentinel */ }
};

/* backwards compatible LED names from old MFN-100 */
static struct rsq10_led rsq10_tt_leds[] = {
	{ .name = "de01:green:status", .value = BIT(0) },   /* OVDM running */
	{ .name = "de01:green:function", .value = BIT(1) }, /* SoftPLC running */
	{ .name = "de01:blue:light", .value = BIT(2) },     /* connected to management system */
	{ /* sentinel */ }
};

static DEFINE_SPINLOCK(reg_lock);

static inline struct rsq10_led *cdev_to_led(struct led_classdev *led_cd)
{
	return container_of(led_cd, struct rsq10_led, cdev);
}

static void rsq10_led_set(struct led_classdev *led_cd,
			  enum led_brightness brightness)
{
	struct rsq10_led *led = cdev_to_led(led_cd);
	unsigned long flags;
	unsigned int val;

	spin_lock_irqsave(&reg_lock, flags);

	val = inw(led->port);
	if (brightness == LED_OFF)
		outw(val & ~led->value, led->port);
	else
		outw(val | led->value, led->port);

	spin_unlock_irqrestore(&reg_lock, flags);
}

static enum led_brightness rsq10_led_get(struct led_classdev *led_cd)
{
	struct rsq10_led *led = cdev_to_led(led_cd);

	return inw(led->port) & led->value ? led_cd->max_brightness : LED_OFF;
}

static int register_leds(struct platform_device *pdev, struct rsq10_led *led)
{
	struct device *dev = &pdev->dev;
	const struct rsq10_platform *plat = dev_get_platdata(dev);
	struct led_classdev *cdev;
	int err;

	while (led->value) {
		cdev = &led->cdev;
		cdev->brightness_set = rsq10_led_set;
		cdev->brightness_get = rsq10_led_get;
		cdev->max_brightness = LED_ON;
		cdev->name = led->name;
		led->port = plat->io_base + plat->led_gpio_offset;

		err = devm_led_classdev_register(dev, cdev);
		if (err < 0)
			return err;
		dev_dbg(dev, "Successfully registered LED %s\n", led->name);
		led++;
	}
	return 0;
}

static int rsq10_leds_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	const struct rsq10_platform *plat = dev_get_platdata(dev);
	struct resource res = DEFINE_RES_IO_NAMED(plat->io_base + plat->led_gpio_offset, SZ_1, KBUILD_MODNAME);
	int err;

	dev_info(dev, "Probing %s", KBUILD_MODNAME);

	if (!devm_request_region(dev, res.start, resource_size(&res), KBUILD_MODNAME)) {
		dev_err(dev, "Unable to register IO resource at %pR\n", &res);
		return -EBUSY;
	}

	/* always register LEDs of RSQ10 as status LEDs */
	err = register_leds(pdev, rsq10_leds);
	if (err < 0)
		return err;

	/* On TTTech MFN-200, i.e. RSQ10-TT, additionally add MFN-100 compatible LED names */
	if (plat->type == RSQ10_TT) {
		err = register_leds(pdev, rsq10_tt_leds);
		if (err < 0)
			return err;
	}

	return 0;
}

static struct platform_driver rsq10_led_driver = {
	.probe = rsq10_leds_probe,
	.driver = {
		.name = KBUILD_MODNAME,
	},
};

module_platform_driver(rsq10_led_driver);

MODULE_AUTHOR("Helmut Buchsbaum <helmut.buchsbaum@tttech-industrial.com>");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_DESCRIPTION("LED driver for Mitwell RSQ10 IPC");
MODULE_LICENSE("GPL");
