// SPDX-License-Identifier: GPL-2.0+

/*
 * Cisco Meraki MX100 (Tinkerbell) board platform driver
 *
 * Based off of arch/x86/platform/meraki/tink.c from the
 * Meraki GPL release meraki-firmware-sources-r23-20150601
 *
 * Format inspired by platform/x86/pcengines-apuv2.c
 *
 * Copyright (C) 2021 Chris Blake <chrisrblake93@gmail.com>
 */

#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt

#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/gpio_keys.h>
#include <linux/gpio/machine.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define TINK_GPIO_DRIVER_NAME "gpio_ich"

/* LEDs */
static const struct gpio_led tink_leds[] = {
	{
		.name = "mx100:green:internet",
		.default_trigger = "default-on",
	},
	{
		.name = "mx100:green:lan2",
	},
	{
		.name = "mx100:green:lan3",
	},
	{
		.name = "mx100:green:lan4",
	},
	{
		.name = "mx100:green:lan5",
	},
	{
		.name = "mx100:green:lan6",
	},
	{
		.name = "mx100:green:lan7",
	},
	{
		.name = "mx100:green:lan8",
	},
	{
		.name = "mx100:green:lan9",
	},
	{
		.name = "mx100:green:lan10",
	},
	{
		.name = "mx100:green:lan11",
	},
	{
		.name = "mx100:green:ha",
	},
	{
		.name = "mx100:orange:ha",
	},
	{
		.name = "mx100:green:usb",
	},
	{
		.name = "mx100:orange:usb",
	},
};

static const struct gpio_led_platform_data tink_leds_pdata = {
	.num_leds	= ARRAY_SIZE(tink_leds),
	.leds		= tink_leds,
};

static struct gpiod_lookup_table tink_leds_table = {
	.dev_id = "leds-gpio",
	.table = {
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 11,
				NULL, 0, GPIO_ACTIVE_LOW),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 18,
				NULL, 1, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 20,
				NULL, 2, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 22,
				NULL, 3, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 23,
				NULL, 4, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 32,
				NULL, 5, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 34,
				NULL, 6, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 35,
				NULL, 7, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 36,
				NULL, 8, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 37,
				NULL, 9, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 48,
				NULL, 10, GPIO_ACTIVE_HIGH),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 16,
				NULL, 11, GPIO_ACTIVE_LOW),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 7,
				NULL, 12, GPIO_ACTIVE_LOW),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 21,
				NULL, 13, GPIO_ACTIVE_LOW),
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 19,
				NULL, 14, GPIO_ACTIVE_LOW),
		{} /* Terminating entry */
	}
};

/* Reset Button */
static struct gpio_keys_button tink_buttons[] = {
	{
		.desc			= "Reset",
		.type			= EV_KEY,
		.code			= KEY_RESTART,
		.active_low             = 1,
		.debounce_interval      = 100,
	},
};

static const struct gpio_keys_platform_data tink_buttons_pdata = {
	.buttons	= tink_buttons,
	.nbuttons	= ARRAY_SIZE(tink_buttons),
	.poll_interval  = 20,
	.rep		= 0,
	.name		= "mx100-keys",
};

static struct gpiod_lookup_table tink_keys_table = {
	.dev_id = "gpio-keys-polled",
	.table = {
		GPIO_LOOKUP_IDX(TINK_GPIO_DRIVER_NAME, 60,
				NULL, 0, GPIO_ACTIVE_LOW),
		{} /* Terminating entry */
	}
};

/* Board setup */
static const struct dmi_system_id tink_systems[] __initconst = {
	{
		.matches = {
			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Cisco"),
			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MX100-HW"),
		},
	},
	{} /* Terminating entry */
};
MODULE_DEVICE_TABLE(dmi, tink_systems);

static struct platform_device *tink_leds_pdev;
static struct platform_device *tink_keys_pdev;

static struct platform_device * __init tink_create_dev(
	const char *name, const void *pdata, size_t sz)
{
	struct platform_device *pdev;

	pdev = platform_device_register_data(NULL,
		name, PLATFORM_DEVID_NONE, pdata, sz);
	if (IS_ERR(pdev))
		pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));

	return pdev;
}

static int __init tink_board_init(void)
{
	int ret;

	if (!dmi_first_match(tink_systems))
		return -ENODEV;

	/*
	 * We need to make sure that GPIO60 isn't set to native mode as is default since it's our
	 * Reset Button. To do this, write to GPIO_USE_SEL2 to have GPIO60 set to GPIO mode.
	 * This is documented on page 1609 of the PCH datasheet, order number 327879-005US
	 */
	outl(inl(0x530) | BIT(28), 0x530);

	gpiod_add_lookup_table(&tink_leds_table);
	gpiod_add_lookup_table(&tink_keys_table);

	tink_leds_pdev = tink_create_dev("leds-gpio",
		&tink_leds_pdata, sizeof(tink_leds_pdata));
	if (IS_ERR(tink_leds_pdev)) {
		ret = PTR_ERR(tink_leds_pdev);
		goto err;
	}

	tink_keys_pdev = tink_create_dev("gpio-keys-polled",
		&tink_buttons_pdata, sizeof(tink_buttons_pdata));
	if (IS_ERR(tink_keys_pdev)) {
		ret = PTR_ERR(tink_keys_pdev);
		platform_device_unregister(tink_leds_pdev);
		goto err;
	}

	return 0;

err:
	gpiod_remove_lookup_table(&tink_keys_table);
	gpiod_remove_lookup_table(&tink_leds_table);
	return ret;
}
module_init(tink_board_init);

static void __exit tink_board_exit(void)
{
	platform_device_unregister(tink_keys_pdev);
	platform_device_unregister(tink_leds_pdev);
	gpiod_remove_lookup_table(&tink_keys_table);
	gpiod_remove_lookup_table(&tink_leds_table);
}
module_exit(tink_board_exit);

MODULE_AUTHOR("Chris Blake <chrisrblake93@gmail.com>");
MODULE_DESCRIPTION("Cisco Meraki MX100 Platform Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:meraki-mx100"