/*
 * Lenovo NE2580 CPLD2 Driver
 *
 * Copyright (c) 2019 Cumulus Networks, Inc.  All rights reserved.
 * Author: David Yen <dhyen@cumulusnetworks.com>
 *
 * 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.
 *
 * This program is distributed in the hope that 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.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/platform_data/at24.h>
#include <linux/i2c/sff-8436.h>
#include <linux/i2c/pca954x.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/cumulus-platform.h>

#include "platform_defs.h"
#include "platform_bitfield.h"
#include "lenovo_ne2580.h"

#define DRIVER_NAME	   NE2580_CPLD2_NAME
#define DRIVER_VERSION	   "1.0"

/* bitfield accessor functions */

#define cpld_read_reg  bf_i2c_read_reg
#define cpld_write_reg bf_i2c_write_reg

/* cpld register bitfields with enum-like values */

static const char * const system_direction_values[] = {
	"B2F",
	"F2B",
};

static const char * const green_led_values[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_GREEN_SLOW_BLINKING,
	PLATFORM_LED_GREEN_BLINKING,
};

static const char * const psu_values[] = {
	"550W",
	"800W",
};

static const char * const fan_direction_values[] = {
	"F2B",
	"B2F",
};

/* CPLD registers */

cpld_bt_ro(cpld2_system_airflow, LENOVO_NE2580_CPLD2_SYSTEM_BOARD_STATUS_REG,
	   LENOVO_NE2580_CPLD2_SYS_AIR_DIR, system_direction_values, 0)
cpld_bt_ro(cpld2_triton_model, LENOVO_NE2580_CPLD2_SYSTEM_BOARD_STATUS_REG,
	   LENOVO_NE2580_CPLD2_TRITON_MODEL_SEL, NULL, 0)

cpld_bt_rw(cpld2_system_ready, LENOVO_NE2580_CPLD2_SWITCH_READY_REG,
	   LENOVO_NE2580_CPLD2_SYS_RDY, NULL, 0)

cpld_bt_ro(psu_pwr2_type, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU2_TYPE, psu_values, 0)
cpld_bt_ro(psu_pwr2_present, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU2_PRESNT_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(psu_pwr2_all_ok, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU2_PWROK, NULL, 0)
cpld_bt_ro(psu_pwr1_type, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU1_TYPE, psu_values, 0)
cpld_bt_ro(psu_pwr1_present, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU1_PRESNT_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(psu_pwr1_all_ok, LENOVO_NE2580_CPLD2_PSU_STATUS_REG,
	   LENOVO_NE2580_CPLD2_PSU1_PWROK, NULL, 0)

cpld_bf_rw(pwm1, LENOVO_NE2580_CPLD2_FAN1_PWM_REG,
	   LENOVO_NE2580_CPLD2_PLD_PWM_FAN1, NULL, 0)
cpld_bf_rw(pwm2, LENOVO_NE2580_CPLD2_FAN2_PWM_REG,
	   LENOVO_NE2580_CPLD2_PLD_PWM_FAN2, NULL, 0)
cpld_bf_rw(pwm3, LENOVO_NE2580_CPLD2_FAN3_PWM_REG,
	   LENOVO_NE2580_CPLD2_PLD_PWM_FAN3, NULL, 0)
cpld_bf_rw(pwm4, LENOVO_NE2580_CPLD2_FAN4_PWM_REG,
	   LENOVO_NE2580_CPLD2_PLD_PWM_FAN4, NULL, 0)
cpld_bf_rw(pwm5, LENOVO_NE2580_CPLD2_FAN5_PWM_REG,
	   LENOVO_NE2580_CPLD2_PLD_PWM_FAN5, NULL, 0)

cpld_bt_ro(fan5_direction, LENOVO_NE2580_CPLD2_FAN_STATUS_1_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN5_DIR, fan_direction_values, 0)
cpld_bt_ro(fan4_direction, LENOVO_NE2580_CPLD2_FAN_STATUS_1_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN4_DIR, fan_direction_values, 0)
cpld_bt_ro(fan3_direction, LENOVO_NE2580_CPLD2_FAN_STATUS_1_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN3_DIR, fan_direction_values, 0)
cpld_bt_ro(fan2_direction, LENOVO_NE2580_CPLD2_FAN_STATUS_1_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN2_DIR, fan_direction_values, 0)
cpld_bt_ro(fan1_direction, LENOVO_NE2580_CPLD2_FAN_STATUS_1_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN1_DIR, fan_direction_values, 0)

cpld_bt_ro(fan5_present, LENOVO_NE2580_CPLD2_FAN_STATUS_2_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN5_PRES_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(fan4_present, LENOVO_NE2580_CPLD2_FAN_STATUS_2_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN4_PRES_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(fan3_present, LENOVO_NE2580_CPLD2_FAN_STATUS_2_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN3_PRES_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(fan2_present, LENOVO_NE2580_CPLD2_FAN_STATUS_2_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN2_PRES_N, NULL, BF_COMPLEMENT)
cpld_bt_ro(fan1_present, LENOVO_NE2580_CPLD2_FAN_STATUS_2_REG,
	   LENOVO_NE2580_CPLD2_PLD_FAN1_PRES_N, NULL, BF_COMPLEMENT)

cpld_bt_rw(set_max_fan_speed, LENOVO_NE2580_CPLD2_FAN_SPEED_CONTROL_REG,
	   LENOVO_NE2580_CPLD2_FAN_SPEED_MAX, NULL, 0)

/* special case for cpld version register */

static ssize_t cpld2_version_show(struct device *dev,
				  struct device_attribute *dattr,
				  char *buf)
{
	struct i2c_client *client = to_i2c_client(dev);
	s32 temp;
	int reg = LENOVO_NE2580_CPLD2_VERSION_REG;

	temp = i2c_smbus_read_byte_data(client, reg);
	if (temp < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, reg);
		return -EINVAL;
	}

	return sprintf(buf, "%d.%d\n",
		       (int)GET_FIELD(temp, LENOVO_NE2580_CPLD2_MAJOR_VERSION),
		       (int)GET_FIELD(temp, LENOVO_NE2580_CPLD2_MINOR_VERSION));
}

static SENSOR_DEVICE_ATTR_RO(cpld2_version, cpld2_version_show, 0);

/* special case for cpld date registers */

static ssize_t cpld2_date_show(struct device *dev,
			       struct device_attribute *dattr,
			       char *buf)
{
	struct i2c_client *client = to_i2c_client(dev);
	s32 temp1;
	int reg1 = LENOVO_NE2580_CPLD2_RELEASE_DATE_1_REG;
	s32 temp2;
	int reg2 = LENOVO_NE2580_CPLD2_RELEASE_DATE_2_REG;

	temp1 = i2c_smbus_read_byte_data(client, reg1);
	if (temp1 < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, reg1);
		return -EINVAL;
	}
	temp2 = i2c_smbus_read_byte_data(client, reg2);
	if (temp2 < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, reg2);
		return -EINVAL;
	}

	return sprintf(buf, "%d/%d/%d\n",
		       (int)GET_FIELD(temp1, LENOVO_NE2580_CPLD2_RELEASE_MONTH),
		       (int)GET_FIELD(temp2, LENOVO_NE2580_CPLD2_RELEASE_DATE),
		       (int)GET_FIELD(temp1, LENOVO_NE2580_CPLD2_RELEASE_YEAR) + 2014);
}

static SENSOR_DEVICE_ATTR_RO(cpld2_date, cpld2_date_show, 0);

/* special case for fan speed registers */

static ssize_t fan_show(struct device *dev, struct device_attribute *dattr,
			char *buf)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(dattr);
	int reg = attr->index;
	s32 temp1;
	s32 temp2;

	temp1 = i2c_smbus_read_byte_data(client, reg);
	if (temp1 < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, reg);
		return -EINVAL;
	}
	temp2 = i2c_smbus_read_byte_data(client, reg + 1);
	if (temp2 < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, reg + 1);
		return -EINVAL;
	}

	return sprintf(buf, "%d\n", (temp1 * 256) + temp2);
}

static SENSOR_DEVICE_ATTR_RO(fan1_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN1_INLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan2_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN2_INLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan3_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN3_INLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan4_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN4_INLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan5_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN5_INLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan6_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN1_OUTLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan7_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN2_OUTLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan8_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN3_OUTLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan9_input,  fan_show,
			     LENOVO_NE2580_CPLD2_FAN4_OUTLET_TACH_1_REG);
static SENSOR_DEVICE_ATTR_RO(fan10_input, fan_show,
			     LENOVO_NE2580_CPLD2_FAN5_OUTLET_TACH_1_REG);

/* special case for fan led registers */

static const char * const colors[] = {
	PLATFORM_LED_OFF,
	PLATFORM_LED_GREEN,
	PLATFORM_LED_RED,
	"green_and_red",
};
	

static ssize_t fan_led_show(struct device *dev, struct device_attribute *dattr,
			    char *buf)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(dattr);
	int bit = attr->index;
	int green_reg = LENOVO_NE2580_CPLD2_FAN_LED_CONTROL_1_REG;
	int red_reg = LENOVO_NE2580_CPLD2_FAN_LED_CONTROL_2_REG;
	s32 green;
	s32 red;
	int green_bit;
	int red_bit;
	int index;

	green = i2c_smbus_read_byte_data(client, green_reg);
	if (green < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, green_reg);
		return -EINVAL;
	}
	green_bit = (green >> bit) & 1;

	red = i2c_smbus_read_byte_data(client, red_reg);
	if (red < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, red_reg);
		return -EINVAL;
	}
	red_bit = (red >> bit) & 1;

	/* green is bit 0 and red is bit 1 */
	index = (red_bit << 1) | green_bit;
	
	return sprintf(buf, "%s\n", colors[index]);
}

static ssize_t fan_led_store(struct device *dev, struct device_attribute *dattr,
			     const char *buf, size_t count)
{
	struct i2c_client *client = to_i2c_client(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(dattr);
	int bit = attr->index;
	char raw[20];
	int i;
	int new_green;
	int new_red;
	int green_reg = LENOVO_NE2580_CPLD2_FAN_LED_CONTROL_1_REG;
	int red_reg = LENOVO_NE2580_CPLD2_FAN_LED_CONTROL_2_REG;
	s32 green;
	s32 red;
	int ret;

	if (sscanf(buf, "%19s", raw) <= 0)
		return -EINVAL;

	for (i = 0; i < ARRAY_SIZE(colors); i++) {
		if (strcmp(raw, colors[i]) == 0)
			break;
	}
	if (i == ARRAY_SIZE(colors))
	    return -EINVAL;

	/* green is bit 0 and red is bit 1 */
	new_green = (i & 0x1);
	new_red = ((i & 0x2) >> 1);
	    
	green = i2c_smbus_read_byte_data(client, green_reg);
	if (green < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, green_reg);
		return -EINVAL;
	}
	green &= ~(1 << bit);
	green |= (new_green << bit);
	ret = i2c_smbus_write_byte_data(client, green_reg, green);

	red = i2c_smbus_read_byte_data(client, red_reg);
	if (red < 0) {
		pr_err("CPLD read error - addr: 0x%02X, offset: 0x%02X\n",
		       client->addr, red_reg);
		return -EINVAL;
	}
	red &= ~(1 << bit);
	red |= (new_red << bit);
	ret = i2c_smbus_write_byte_data(client, red_reg, red);

	return count;
}

static SENSOR_DEVICE_ATTR_RW(led_fan1, fan_led_show, fan_led_store, 0);
static SENSOR_DEVICE_ATTR_RW(led_fan2, fan_led_show, fan_led_store, 1);
static SENSOR_DEVICE_ATTR_RW(led_fan3, fan_led_show, fan_led_store, 2);
static SENSOR_DEVICE_ATTR_RW(led_fan4, fan_led_show, fan_led_store, 3);
static SENSOR_DEVICE_ATTR_RW(led_fan5, fan_led_show, fan_led_store, 4);

/* sysfs registration */

static struct attribute *cpld_attrs[] = {
	&cpld_cpld2_system_airflow.attr,
	&cpld_cpld2_triton_model.attr,
	&cpld_cpld2_system_ready.attr,
	&cpld_psu_pwr2_type.attr,
	&cpld_psu_pwr2_present.attr,
	&cpld_psu_pwr2_all_ok.attr,
	&cpld_psu_pwr1_type.attr,
	&cpld_psu_pwr1_present.attr,
	&cpld_psu_pwr1_all_ok.attr,
	&cpld_pwm1.attr,
	&cpld_pwm2.attr,
	&cpld_pwm3.attr,
	&cpld_pwm4.attr,
	&cpld_pwm5.attr,
	&cpld_fan5_direction.attr,
	&cpld_fan4_direction.attr,
	&cpld_fan3_direction.attr,
	&cpld_fan2_direction.attr,
	&cpld_fan1_direction.attr,
	&cpld_fan5_present.attr,
	&cpld_fan4_present.attr,
	&cpld_fan3_present.attr,
	&cpld_fan2_present.attr,
	&cpld_fan1_present.attr,
	&cpld_set_max_fan_speed.attr,

	&sensor_dev_attr_cpld2_version.dev_attr.attr,
	&sensor_dev_attr_cpld2_date.dev_attr.attr,

	&sensor_dev_attr_fan1_input.dev_attr.attr,
	&sensor_dev_attr_fan2_input.dev_attr.attr,
	&sensor_dev_attr_fan3_input.dev_attr.attr,
	&sensor_dev_attr_fan4_input.dev_attr.attr,
	&sensor_dev_attr_fan5_input.dev_attr.attr,
	&sensor_dev_attr_fan6_input.dev_attr.attr,
	&sensor_dev_attr_fan7_input.dev_attr.attr,
	&sensor_dev_attr_fan8_input.dev_attr.attr,
	&sensor_dev_attr_fan9_input.dev_attr.attr,
	&sensor_dev_attr_fan10_input.dev_attr.attr,

	&sensor_dev_attr_led_fan1.dev_attr.attr,
	&sensor_dev_attr_led_fan2.dev_attr.attr,
	&sensor_dev_attr_led_fan3.dev_attr.attr,
	&sensor_dev_attr_led_fan4.dev_attr.attr,
	&sensor_dev_attr_led_fan5.dev_attr.attr,

	NULL,
};

static struct attribute_group cpld_attr_group = {
	.attrs = cpld_attrs,
};

static int cpld_probe(struct i2c_client *client,
		      const struct i2c_device_id *id)
{
	s32 temp;
	int ret = 0;
	struct kobject *kobj = &client->dev.kobj;

	temp = i2c_smbus_read_byte_data(client,
					LENOVO_NE2580_CPLD2_VERSION_REG);
	if (temp < 0) {
		pr_err(DRIVER_NAME ": read CPLD2 version register error: %d\n",
		       ret);
		return temp;
	}

	ret = sysfs_create_group(kobj, &cpld_attr_group);
	if (ret)
		pr_err(DRIVER_NAME ": failed to create sysfs group for cpld device\n");

	pr_info(DRIVER_NAME ": device probed, CPLD2 rev: %d.%d\n",
		(int)GET_FIELD(temp, LENOVO_NE2580_CPLD2_MAJOR_VERSION),
		(int)GET_FIELD(temp, LENOVO_NE2580_CPLD2_MINOR_VERSION));

	return ret;
}

static int cpld_remove(struct i2c_client *client)
{
	struct kobject *kobj = &client->dev.kobj;

	sysfs_remove_group(kobj, &cpld_attr_group);

	return 0;
}

static const struct i2c_device_id cpld_i2c_ids[] = {
	{
		.name = DRIVER_NAME,
	},
	{ /* end of list */ },
};

MODULE_DEVICE_TABLE(i2c, cpld_i2c_ids);

static struct i2c_driver cpld_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
	},
	.probe = cpld_probe,
	.remove = cpld_remove,
	.id_table = cpld_i2c_ids,
};

/* module init and exit */

static int __init cpld_init(void)
{
	int ret;

	ret = i2c_add_driver(&cpld_driver);
	if (ret) {
		pr_err(DRIVER_NAME ": driver failed to load\n");
		return ret;
	}

	pr_info(DRIVER_NAME ": version " DRIVER_VERSION " loaded\n");
	return 0;
}

static void __exit cpld_exit(void)
{
	i2c_del_driver(&cpld_driver);
	pr_info(DRIVER_NAME ": unloaded\n");
}

module_init(cpld_init);
module_exit(cpld_exit);

MODULE_AUTHOR("David Yen (dhyen@cumulusnetworks.com)");
MODULE_DESCRIPTION("Lenovo NE2580 CPLD2 Driver");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
