/*
 * Juniper Networks RE-FPGA qfx platform specific driver
 *
 * Copyright (C) 2020 Juniper Networks
 * Author: Ciju Rajan K <crajank@juniper.net>
 *
 * This driver implements various features such as
 *  - ALARM led driver
 *  - Fan full speed reset control
 *  - FAN precense detection
 *  - FAN type detection
 *  - Any new QFX specific features which uses RE-FPGA
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>

#define NUM_LEDS			7	/* Max number of Alarm + FAN LEDs */

#define ALARM_MINOR_LED			0
#define ALARM_MAJOR_LED			1

#define REFPGA_PCIE_RESET_CTRL		0x13
#define REFPGA_PCIE_ALARM		0x33
#define REFPGA_FAN0_CTRL_STAT		0x28

#define REFPGA_RESET_FAN_SPEED		BIT(3)
#define REFPGA_OPER_TYPE		BIT(0)
#define REFPGA_OPER_START		BIT(1)
#define REFPGA_OPER_DONE		BIT(2)

#define TMC_REFPGA_ADDR_REG		0x0     /* TMC offset: 0x228 */
#define TMC_REFPGA_DATA_REG		0x4     /* TMC offset: 0x22C */
#define TMC_REFPGA_CTRL_REG		0x8     /* TMC offset: 0x230 */

#define TMC_REFPGA_READ_CMD		0x3
#define TMC_REFPGA_WRITE_CMD		0x2

#define REFPGA_INTR_NR_GROUPS		1
#define REFPGA_INTR_MAX_IRQS_PG		32

#define MAX_FANS			5

#define REFPGA_IRQ_MAX_BITS_PER_REG	32

#define POLL_INTERVAL			5000

#define AFI_MASK			(0x01)
#define AFO_MASK			(0x02)
#define AFI_AFO_MASK			(0x03)
/*
 * LED specific data structures
 */
struct refpga_led {
	struct led_classdev lc;
	struct work_struct work;
	int blink;
	int on;
	int bit;
	void __iomem *addr;
};

struct refpga_led_data {
	int num_leds;
	struct refpga_led *leds;
};

static DEFINE_MUTEX(alarm_led_lock);

/*
 * Common routines
 */
struct refpga_chip {
    struct refpga_led_data *led;
};

static struct refpga_chip *refpga;

static DEFINE_MUTEX(refpga_lock);

static void __iomem *tmc_membase;

static void wait_for_refpga_oper(void __iomem *base_addr)
{
	volatile u32 done = ~(-1);
	unsigned long int timeout;
	void __iomem *addr;

	addr = base_addr + (TMC_REFPGA_CTRL_REG);
	/*
	 * Wait till the transaction is complete
	 */
	timeout = jiffies + msecs_to_jiffies(100);

	do {
		usleep_range(50, 100);
		done = ioread32(addr);
		if (done & (REFPGA_OPER_DONE))
			break;
	} while(time_before(jiffies, timeout));
}
static u32 refpga_read(void __iomem *base_addr, u32 refpga_offset)
{
	u32 value;

	mutex_lock(&refpga_lock);
	iowrite32(refpga_offset, base_addr + (TMC_REFPGA_ADDR_REG));
	iowrite32(TMC_REFPGA_READ_CMD, base_addr + (TMC_REFPGA_CTRL_REG));
	wait_for_refpga_oper(base_addr);
	value = ioread32(base_addr + (TMC_REFPGA_DATA_REG));
	mutex_unlock(&refpga_lock);

	return value;
}

static void refpga_write(void __iomem *base_addr, u32 refpga_offset, u32 val)
{
	mutex_lock(&refpga_lock);
	iowrite32(refpga_offset, base_addr + (TMC_REFPGA_ADDR_REG));
	iowrite32(val, base_addr + (TMC_REFPGA_DATA_REG));
	iowrite32(TMC_REFPGA_WRITE_CMD, base_addr + (TMC_REFPGA_CTRL_REG));
	wait_for_refpga_oper(base_addr);
	mutex_unlock(&refpga_lock);
}

static bool get_fan_presense(u8 idx)
{
	u8 value = 0x00;
	u8 offset = REFPGA_FAN0_CTRL_STAT;
	bool ret = 0;
	
	value = refpga_read(tmc_membase, (offset + (idx * 2)));
        /*
         * Get the last two bits of REFPGA_FANx_CTRL_STAT.
	 * REFPGA_FANx_CTRL_STAT register of REFPGA gives the fan airflow
	 * status. There are 5 fans in QFX5200. Last two bits give the AFI
 	 * & AFO status. If any of these bits are set, fan is present.
         */
        value = (value & BIT(0)) | (value & BIT(1));
        if (value)
            ret = 1;

	return ret;
}

static int get_fan_type(u8 idx)
{
	u8 value = 0x00;
	u8 offset = REFPGA_FAN0_CTRL_STAT;
	int ret = -1;
	
	value = refpga_read(tmc_membase, (offset + (idx * 2)));
        /*
         * Get the last two bits of REFPGA_FANx_CTRL_STAT.
	 * REFPGA_FANx_CTRL_STAT register of REFPGA gives the fan airflow
	 * status. There are 5 fans in QFX5200. Last two bits give the AFI
 	 * & AFO status. If bit1 is set, it's AFO and if bit 0 is set,
	 * it's AFI.
	 *
	 * This function will return '1' for AFO, '0' for AFI, and '-1'
	 * if there is no fan or if both AFI & AFO bits are set.
         */
	value &= AFI_AFO_MASK;

	switch(value) {
		case AFI_MASK:
			ret = 0;
			break;
		case AFO_MASK:
			ret = 1;
			break;
		default:
			ret = -1;
			break;
	};

	return ret;
}

enum sysfs_fan_attributes {
	FAN0_PRESENT,
	FAN1_PRESENT,
	FAN2_PRESENT,
	FAN3_PRESENT,
	FAN4_PRESENT,
};

enum sysfs_fan_type_attributes {
	FAN0_TYPE,
	FAN1_TYPE,
	FAN2_TYPE,
	FAN3_TYPE,
	FAN4_TYPE,
};

/* 
 * The sysfs files will be present in this path
 * /sys/devices/pci0000:00/0000:00:1c.0/0000:0f:00.0/refpga-tmc.15/fan*_present
 * /sys/devices/pci0000:00/0000:00:1c.0/0000:0f:00.0/refpga-tmc.15/fan*_type
 */

#define DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(index) \
	    static SENSOR_DEVICE_ATTR(fan##index##_present, S_IRUGO, refpga_fan_presense_show, NULL, FAN##index##_PRESENT)
#define DECLARE_FAN_PRESENT_ATTR(index)      &sensor_dev_attr_fan##index##_present.dev_attr.attr

#define DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(index) \
	    static SENSOR_DEVICE_ATTR(fan##index##_type, S_IRUGO, refpga_fan_type_show, NULL, FAN##index##_TYPE)
#define DECLARE_FAN_TYPE_ATTR(index)      &sensor_dev_attr_fan##index##_type.dev_attr.attr

static ssize_t refpga_fan_presense_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr);
	
	return sprintf(buf, "%d\n", get_fan_presense(s_attr->index));

}

static ssize_t refpga_fan_type_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr);
	
	return sprintf(buf, "%d\n", get_fan_type(s_attr->index));

}

DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(0);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(1);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(2);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(3);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(4);

DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(0);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(1);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(2);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(3);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(4);

static struct attribute *refpga_fan_attrs[] = {
	DECLARE_FAN_PRESENT_ATTR(0),
	DECLARE_FAN_PRESENT_ATTR(1),
	DECLARE_FAN_PRESENT_ATTR(2),
	DECLARE_FAN_PRESENT_ATTR(3),
	DECLARE_FAN_PRESENT_ATTR(4),
	DECLARE_FAN_TYPE_ATTR(0),
	DECLARE_FAN_TYPE_ATTR(1),
	DECLARE_FAN_TYPE_ATTR(2),
	DECLARE_FAN_TYPE_ATTR(3),
	DECLARE_FAN_TYPE_ATTR(4),
	NULL
};

static struct attribute_group refpga_fan_attr_group = {
	.attrs = refpga_fan_attrs,
};

/*
 * There is only a single ALARM led in QFX5200 and that
 * is used for both Major & Minor alarm indicator.
 * These are represented by two different bits in RE-FPGA
 * PCIE_ALARM register. Only one of the bit (either Red or
 * Yellow) should be set a time. If both the bits are set,
 * it's an undefined behaviour.
 *
 * The following table describes how the conditions are
 * handled in the driver as there can be both Major & Minor
 * alarms can be triggered from userspace.
 *
 *  Major   Minor   Colour
 *
 *   0       0       Nil
 *   0       1      Yellow
 *   1       1       Red
 *   1       0       Red
 *
 */
static void manage_alarm_led(void __iomem *addr, int led_type, int value)
{
	static int alarm_major = 0, alarm_minor = 0;
	u32 reg = 0x0;

	mutex_lock(&alarm_led_lock);
	reg = refpga_read(addr, REFPGA_PCIE_ALARM);

	(led_type == ALARM_MAJOR_LED) ?
			((value == 1) ? (alarm_major = 1) : (alarm_major = 0)) :
			((value == 1) ? (alarm_minor = 1) : (alarm_minor = 0));
	if (alarm_major) {
		reg &= ~BIT(ALARM_MINOR_LED);
		reg |= BIT(ALARM_MAJOR_LED);
	} else {
		if (alarm_minor) {
			reg &= ~BIT(ALARM_MAJOR_LED);
			reg |= BIT(ALARM_MINOR_LED);
		} else {
			reg &= ~BIT(ALARM_MINOR_LED);
			reg &= ~BIT(ALARM_MAJOR_LED);
		}
	}
	refpga_write(addr, REFPGA_PCIE_ALARM, reg);
	mutex_unlock(&alarm_led_lock);
}

static void manage_fan_led(void __iomem *addr, int fan_slot, int value)
{
	u8 offset = REFPGA_FAN0_CTRL_STAT + (fan_slot * 2);
	u32 reg = 0x0;

	reg = refpga_read(addr, offset);
	if(value) {
		/* Turn on s/w control */
		reg = reg | BIT(4);
		/* Turn off green led */
		reg &= ~BIT(5);
		/* Turn on yellow led & make it blink */
		reg |= (BIT(6) | BIT(7));
	} else {
		/* Clear yellow led & stop blink */
		reg &= ~(BIT(6) | BIT(7));
		/* Stop s/w control */
		reg &= ~BIT(4);
	}
	refpga_write(addr, offset, reg);
}

static void refpga_led_work(struct work_struct *work)
{
	struct refpga_led *led = container_of(work, struct refpga_led, work);
	void __iomem *addr;

	addr = led->addr;
	
	if(strstr(led->lc.name, "fan"))
		manage_fan_led(addr, led->bit, led->on);
	else
		manage_alarm_led(addr, led->bit, led->on);
}

static void refpga_led_brightness_set(struct led_classdev *lc,
				 enum led_brightness brightness)
{
	struct refpga_led *led = container_of(lc, struct refpga_led, lc);

	led->on = (brightness != LED_OFF);
	led->blink = 0; /* always turn off hw blink on brightness_set() */
	schedule_work(&led->work);
}

struct led_table
{
	const char *name;
	int reg;
};

static struct led_table qfx5200_led_data[] = {
	{
		.name = "alarm-minor",
		.reg = 0,
	},
	{
		.name = "alarm-major",
		.reg = 1,
	},
	{
		.name = "fan0-fault",
		.reg = 0,
	},
	{
		.name = "fan1-fault",
		.reg = 1,
	},
	{
		.name = "fan2-fault",
		.reg = 2,
	},
	{
		.name = "fan3-fault",
		.reg = 3,
	},
	{
		.name = "fan4-fault",
		.reg = 4,
	}
};

static int refpga_led_init_one(struct device *dev,
				struct refpga_led_data *ild,
				int num)
{
	struct refpga_led *led;
	int ret = 0;

	led = &ild->leds[num];
	led->addr = tmc_membase;

	led->lc.name = qfx5200_led_data[num].name;
	led->bit = qfx5200_led_data[num].reg;
	led->lc.brightness = LED_OFF;
	led->lc.brightness_set = refpga_led_brightness_set;

	ret = devm_led_classdev_register(dev, &led->lc);
	if (ret) {
		dev_err(dev, "devm_led_classdev_register failed\n");
		return ret;
	}

	INIT_WORK(&led->work, refpga_led_work);

	return 0;
}

static int refpga_led_qfx5200_init(struct device *dev, struct refpga_led_data *ild)
{
	int ret = 0, idx = 0;

	
	if (!dev->parent) {
		dev_err(dev, "dev->parent is null\n");
		return -ENODEV;
	}

	ild->num_leds = NUM_LEDS;
	ild->leds = devm_kzalloc(dev, sizeof(struct refpga_led) * NUM_LEDS,
					GFP_KERNEL);
	if (!ild->leds) {
		dev_err(dev, "LED allocation failed\n");
		return -ENOMEM;
	}

	for(idx=0; idx<NUM_LEDS; idx++){
		ret = refpga_led_init_one(dev, ild, idx);
		if (ret)
			return ret;
	}

	return 0;
}

static int jnx_refpga_led_probe(struct platform_device *pdev)
{	
	struct device *dev = &pdev->dev;
	struct refpga_led_data *ild;
	int ret;

	ild = devm_kzalloc(dev, sizeof(*ild), GFP_KERNEL);
	if (!ild) {
		dev_err(dev, "ild allocation failed\n");
		return -ENOMEM;
	}

	ret = refpga_led_qfx5200_init(dev, ild);
	if (ret < 0)
		return ret;
	
	refpga->led = ild;

	return 0;
}

static int jnx_refpga_led_remove(struct platform_device *pdev)
{
	struct refpga_chip *drv_data = platform_get_drvdata(pdev);	
	struct refpga_led_data *ild = drv_data->led;
	int i;

	for (i = 0; i < ild->num_leds; i++) {
		devm_led_classdev_unregister(&pdev->dev, &ild->leds[i].lc);
		cancel_work_sync(&ild->leds[i].work);
	}
    if (ild) {
		if (ild->leds)
			devm_kfree(&pdev->dev, ild->leds);
		devm_kfree(&pdev->dev, ild);
	}
	return 0;
}

static void reset_fan_full_speed(struct device *dev)
{
	u32 val = ~(-1), tmp = ~(-1);

	/*
	 * Reading the REFPGA_PCIE_RESET_CTRL register
	 */
	val = refpga_read(tmc_membase, REFPGA_PCIE_RESET_CTRL);
	/*
	 * Clearing the fan full_speed bit
	 */
	val &= ~(REFPGA_RESET_FAN_SPEED);
	/*
	 * Writing the REFPGA_PCIE_RESET_CTRL register
	 */
	refpga_write(tmc_membase, REFPGA_PCIE_RESET_CTRL, val);
	/*
	 * Reading the REFPGA_PCIE_RESET_CTRL register
	 */
	tmp = refpga_read(tmc_membase, REFPGA_PCIE_RESET_CTRL);
	dev_info(dev, "After resetting fan full speed control: %X\n", tmp);
}

static int jnx_refpga_tmc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct resource *res;
	int ret = 0;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(dev, "resource allocation failed\n");
		return -ENODEV;
	}

	tmc_membase = devm_ioremap(dev, res->start, resource_size(res));
	if (!tmc_membase) {
		dev_err(dev, "ioremap failed\n");
		return -ENOMEM;
	}

	refpga = devm_kzalloc(dev, sizeof(*refpga), GFP_KERNEL);
	if (!refpga) {
		dev_err(dev, "refpga memory allocation failed\n");
		return -ENOMEM;
	}

	reset_fan_full_speed(dev);

	ret = jnx_refpga_led_probe(pdev);
	if (ret != 0) {
		dev_err(dev, "Refpga LED probe failed\n");
		return ret;
	}

	dev_info(dev, "Refpga LED probe successful: TMC memoy base: %p\n",
					tmc_membase);

	ret = sysfs_create_group(&dev->kobj, &refpga_fan_attr_group);
	if (ret != 0) {
		dev_err(dev, "sysfs_create_group failed: %d\n", ret);
		return ret;
	} 

	platform_set_drvdata(pdev, refpga);

	return 0;
}

static int jnx_refpga_tmc_remove(struct platform_device *pdev)
{
	jnx_refpga_led_remove(pdev);
	sysfs_remove_group(&pdev->dev.kobj, &refpga_fan_attr_group);

	return 0;
}

static struct platform_driver jnx_refpga_tmc_driver = {
	.driver = {
		.name  = "refpga-tmc",
		.owner = THIS_MODULE,
	},
	.probe = jnx_refpga_tmc_probe,
	.remove = jnx_refpga_tmc_remove,
};

static int __init jnx_refpga_tmc_driver_init(void)
{
	int ret = -1;

	ret = platform_driver_register(&jnx_refpga_tmc_driver);
	
	return ret;

}

static void __exit jnx_refpga_tmc_driver_exit(void)
{
	platform_driver_unregister(&jnx_refpga_tmc_driver);
}

module_init(jnx_refpga_tmc_driver_init);
module_exit(jnx_refpga_tmc_driver_exit);

MODULE_DESCRIPTION("Juniper Networks REFPGA / TMC driver");
MODULE_AUTHOR("Ciju Rajan K <crajank@juniper.net>");
MODULE_LICENSE("GPL");