Initial commit of PSE and CDU

Change-Id: Ibcd57f7dac5ea7be54f7b8b37634baeba161b233
diff --git a/pse/pse-sysfs.c b/pse/pse-sysfs.c
new file mode 100644
index 0000000..d008128
--- /dev/null
+++ b/pse/pse-sysfs.c
@@ -0,0 +1,681 @@
+/* * ./kernel_modules/pse/pse-sysfs.c
+ *
+ * This file defines the sysfs interface for the PSE class
+ *
+ * Author: Cradlepoint Technology, Inc.  <source@cradlepoint.com>
+ *		Kyle Swenson <kswenson@cradlepoint.com>
+ *
+ * Copyright 2017 Cradlepoint Technology, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/idr.h>
+#include <linux/spinlock.h>
+#include <linux/list_sort.h>
+#include <linux/sort.h>
+
+#include "pse-port.h"
+#include "pse-class.h"
+#include "pse-sysfs.h"
+
+char *pse_detect_strings[] = {
+	"0:DETECT_NO_DEVICE",
+	"1:DETECT_NORMAL_DEVICE", /* Short */
+	"2:DETECT_LEGACY_DEVICE", /* High cap devices */
+	"3:DETECT_POWERED_DEVICE",/* Signature resistance good */ /* Maybe we want to even break this down into detect Type of device iff that's a detect-time thing */
+	"4:DETECT_ERROR",
+	NULL
+};
+EXPORT_SYMBOL(pse_detect_strings);
+char *pse_class_strings[] = {
+	"0:PD_CLASS_UNKNOWN",
+	"1:PD_CLASS_1",
+	"2:PD_CLASS_2",
+	"3:PD_CLASS_3",
+	"4:PD_CLASS_4",
+	"5:PD_CLASS_5",
+	"6:PD_CLASS_6",
+	"7:PD_CLASS_7",
+	"8:PD_CLASS_8",
+	"1D:PD_CLASS_1D",
+	"2D:PD_CLASS_2D",
+	"3D:PD_CLASS_3D",
+	"4D:PD_CLASS_4D",
+	"5D:PD_CLASS_5D",
+	"9:PD_CLASS_INVALID",
+	NULL
+};
+EXPORT_SYMBOL(pse_class_strings);
+
+uint16_t pse_port_current_table[] = {
+			375, /* Class 0 (unknown) */
+			112, /* Class 1 */
+			206, /* Class 2 */
+			375, /* Class 3 */
+			638, /* Class 4 (Type 2) */
+			938, /* Class 5 */
+			1250, /* Class 6 */
+			1563, /* Class 7 */
+			1875, /* Class 8 */
+			2*112, /* Class 1D */
+			2*206, /* Class 2D */
+			2*375, /* Class 3D */
+			2*638, /* Class 4D  */
+			2*938 /* Class 5D */
+};
+
+uint32_t pse_port_power_table[] = {
+			15400, /* Class 0 (unknown) */
+			4000, /* Class 1  */
+			7000, /* Class 2 */
+			15400, /* Class 3  */
+			30000, /* Class 4  */
+			45000, /* Class 5 */
+			60000, /* Class 6 */
+			75000, /* Class 7 */
+			90000, /* Class 8 */
+			2*4000, /* Class 1D  */
+			2*7000, /* Class 2D */
+			2*15400, /* Class 3D  */
+			2*30000, /* Class 4D  */
+			2*45000, /* Class 5D */
+};
+/* ============ PSE CLASS ATTRIBUTES =================
+* PSE Class level attributes; these appear under /sys/class/pse/
+*/
+static ssize_t
+pse_version_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%s\n", PSE_VERSION_STR);
+}
+
+static struct class_attribute pse_version = __ATTR_RO(pse_version);
+
+
+
+/* -1 for false
+ * 0 for no idea
+ * +1 for true
+ */
+static int is_trueish(const char *buf, size_t len)
+{
+	if (len == 0)
+		return 0;
+
+	switch (buf[0]) {
+	case '1':
+		/* ASCII char 0x31 => '1' means true*/
+	case 'y':
+	case 'Y':
+		/* [yY].*  */
+	case 't':
+	case 'T':
+		/* [tT].*  */
+	case 'e':
+	case 'E':
+		/* [eE] for enable */
+		return 1;
+	case '0':
+		/* ASCII char 0x30 => '0' means false */
+	case 'n':
+	case 'N':
+		/* [nN].* for No  */
+	case 'F':
+	case 'f':
+		/* [fF].* for false */
+	case 'd':
+	case 'D':
+		/* [dD] for disable*/
+		return -1;
+	case 'o':
+	case 'O':
+		/* Could be On, or Off need to look more */
+		break;
+	default:
+		/* Bail if we don't recognize the first char */
+		return 0;
+	}
+	/*If we're here we know the first char is [oO] */
+	/* But if we've only got one char in the buffer, we can't determine so bail*/
+	if (len == 1)
+		return 0;
+
+	/* Here, we know len > 1, so at least two  */
+	switch (buf[1]) {
+	case 'n':
+	case 'N':
+		/* for [oO][nN].* */
+		return 1;
+	case 'f':
+	case 'F':
+		/* for [oO][fF].* */
+		return -1;
+	default:
+		return 0;
+	}
+	return 0;
+}
+
+static ssize_t
+port_priority_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	struct class_dev_iter iter;
+	struct device *dev;
+	int bput = 0;
+
+	class_dev_iter_init(&iter, class, NULL, NULL);
+	dev = class_dev_iter_next(&iter);
+	while (dev) {
+		bput += snprintf((char *)(buf+bput), 32,  "port%d -> priority %d\n", to_pse_port_device(dev)->id, to_pse_port_device(dev)->priority);
+		dev = class_dev_iter_next(&iter);
+	}
+	class_dev_iter_exit(&iter);
+	return bput;
+}
+
+/* We are implying port and specifying priority:
+ * |Port 0 relative priority 2
+ * | |Port 1 is priority 3
+ * | | |Port 2 has priority 1
+ * | | | |Port 3 is priority 0 (highest)
+ * 2 3 1 0
+ *
+*/
+static ssize_t
+port_priority_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
+{
+	struct device *dev;
+	struct class_dev_iter iter;
+	char *substr;
+	char *lbuffs;
+
+	int vi = 0;
+	size_t bget = 0;
+	uint8_t port_priority = 0;
+	struct pse_data *pse_data = get_pse_data(class);
+	struct pse_port_device *port;
+	char *local_buff = kcalloc(count, sizeof(char), GFP_KERNEL);
+
+	lbuffs = memcpy((char *) local_buff, (const char *) buff, count);
+
+	mutex_lock(&pse_data->pse_lock);
+	while (bget < count && vi < PSE_MAX_PORTS) {
+		substr = strsep(&lbuffs, " ");
+		if (substr == NULL) {
+			break;
+		}
+		printk(KERN_INFO "Looking at substr = %s\n", substr);
+		if (kstrtou8(substr, 0, &port_priority) == 0) {
+			pse_data->port_priorities[vi++] = port_priority;
+			bget += strlen(substr);
+		} else {
+			printk(KERN_WARNING "Unable to parse the number out of %s\n", substr);
+		}
+	}
+	class_dev_iter_init(&iter, class, NULL, NULL);
+	dev = class_dev_iter_next(&iter);
+	while (dev) {
+		port = to_pse_port_device(dev);
+		port->priority = pse_data->port_priorities[port->id];
+		dev_dbg(dev, "Storing port_priority %hhu to port%u", port->priority,  port->id);
+		dev = class_dev_iter_next(&iter);
+	}
+	class_dev_iter_exit(&iter);
+	relink_priority_list(class); /* Rebuild the dubly linked list */
+	redistribute_power(class); /* Force every port off & on again, to get them to repower in correct, priority order */
+	mutex_unlock(&pse_data->pse_lock);
+
+	kfree(local_buff);
+	return count;
+}
+static struct class_attribute port_priority = __ATTR_RW(port_priority);
+
+static ssize_t
+power_budget_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	struct pse_data *pse_data = get_pse_data(class);
+
+	return sprintf(buf, "%d", pse_data->power_budget);
+}
+static ssize_t
+power_budget_store(struct class *class, struct class_attribute *attr, const char *buff, size_t count)
+{
+	int new;
+	int res;
+	struct pse_data *pse_data = get_pse_data(class);
+
+	res = kstrtoint(buff, 0, &new);
+	if (res != 0)
+		return res;
+	mutex_lock(&pse_data->pse_lock);
+	pse_data->power_budget = new;
+	redistribute_power(&pse_data->cls);
+	mutex_unlock(&pse_data->pse_lock);
+	return count;
+}
+static struct class_attribute power_budget = __ATTR_RW(power_budget);
+
+static ssize_t
+enable_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	struct pse_data *pse_data = get_pse_data(class);
+
+	return sprintf(buf, pse_data->enabled ? "True" : "False");
+}
+
+static ssize_t
+enable_store(struct class *class, struct class_attribute *attr, const char *buf, size_t count)
+{
+
+	struct pse_data *pse_data = get_pse_data(class);
+	int t = is_trueish(buf, count);
+
+	if (t == 0)
+		return -EINVAL;
+
+	mutex_lock(&pse_data->pse_lock);
+	if (t > 0)
+		pse_data->enabled = 1;
+	else
+		pse_data->enabled = 0;
+
+	redistribute_power(class);
+	mutex_unlock(&pse_data->pse_lock);
+	return count;
+}
+static struct class_attribute enable = __ATTR_RW(enable);
+
+static ssize_t
+power_used_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	struct pse_data *pse_data = get_pse_data(class);
+
+	return sprintf(buf, "%d", pse_data->power_used);
+}
+static struct class_attribute power_used = __ATTR_RO(power_used);
+
+int pse_class_sysfs_init(struct class *class)
+{
+	int err;
+
+	err = class_create_file(class, &pse_version);
+	if (err) {
+		pr_err("couldn't register pse class version file\n");
+		return err;
+	}
+	err = class_create_file(class, &port_priority);
+	if (err) {
+		pr_err("couldn't register pse class port priority file\n");
+		return err;
+	}
+	err = class_create_file(class, &power_budget);
+	if (err) {
+		pr_err("couldn't register pse class port priority file\n");
+		return err;
+	}
+	err = class_create_file(class, &enable);
+	if (err) {
+		pr_err("couldn't register pse class enable file\n");
+		return err;
+	}
+	err = class_create_file(class, &power_used);
+	if (err) {
+		pr_err("couldn't register pse class enable file\n");
+		return err;
+	}
+	return 0;
+}
+void pse_class_sysfs_close(struct class *class)
+{
+	class_remove_file(class, &pse_version);
+	class_remove_file(class, &port_priority);
+	class_remove_file(class, &power_budget);
+	class_remove_file(class, &enable);
+	class_remove_file(class, &power_used);
+}
+
+
+/* ============= PSE PORT DEVICE ATTRIBUTES ==================
+ * These are attributes that appear at /sys/class/pse/portX/<attribute>
+ * These Attributes DO NOT EXIST until a device driver has registered a port device with the pse class!
+ */
+
+/* current_show : Display the current in mA being sourced by the port
+*/
+static ssize_t
+pd_current_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int pd_current = 0;
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
+	if (!port || !port->ops->pd_current || !port->ops->pd_powered)
+		return -EINVAL;
+
+	if (port->enabled && port->ops->pd_powered(dev))
+		pd_current = to_pse_port_device(dev)->ops->pd_current(dev);
+
+	return sprintf(buf, "%d\n", pd_current);
+}
+
+/* current_store : Set the current limit for current being source by the port
+*/
+static ssize_t
+pd_current_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	/* We expect: "CLASS X" where X is a number 1 2 3 4  */
+	/* OR a single number, with the current specified in mA*/
+
+	int current_limit = -1;
+
+	if (!to_pse_port_device(dev)->ops->set_current_limit)
+		return -EINVAL;
+
+	dev_dbg(dev, "PD_CURRENT_STORE");
+	if (strncmp(buf, "CLASS ", 6) == 0) {
+		dev_dbg(dev, "%s interpreted as class\n", buf);
+		if (count >= 6) {
+			if (buf[6] >= '0' && buf[6] <= '8') {
+				dev_dbg(dev, "Setting class limit to Class %c", buf[6]);
+				current_limit = pse_port_current_table[buf[6]-'0'];
+			} else {
+				dev_dbg(dev, "Failed to set class %c", buf[6]);
+			}
+		} else {
+			dev_dbg(dev, "count invalid");
+		}
+	} else {
+		dev_alert(dev, "%s interpreted as current in mA\n", buf);
+		if (kstrtoint(buf, 0, &current_limit) == 0)
+			dev_dbg(dev, "Setting class limit to %d mA", current_limit);
+	}
+	if (current_limit > 0) {
+		if (to_pse_port_device(dev)->ops->set_current_limit(dev, current_limit) < 0)
+			return -EINVAL;
+		else
+			return count;
+	} else {
+		dev_warn(dev, "pd_current limit %d invalid", current_limit);
+		return -EINVAL;
+	}
+}
+
+static struct device_attribute port_current = __ATTR_RW(pd_current);
+
+/* voltage_show: Show the port's voltage
+ */
+static ssize_t
+port_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,
+		to_pse_port_device(dev)->enabled ? "True" : "False");
+}
+
+static ssize_t
+port_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	int t = is_trueish(buf, count);
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	if (t == 0)
+		return -EINVAL;
+
+	mutex_lock(&port->lock);
+
+	if (t < 0) {
+		port->enabled = 0;
+		queue_work(port->wq, &port->disconnect);
+	} else {
+		port->enabled = 1;
+	}
+	mutex_unlock(&port->lock);
+
+	return count;
+}
+static struct device_attribute port_enable = __ATTR_RW(port_enable);
+/* voltage_show: Show the port's voltage
+ */
+static ssize_t
+pd_voltage_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int pd_voltage = 0;
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	if (!port || !port->ops->pd_voltage || !port->ops->pd_powered)
+		return -EINVAL;
+
+	if (port->enabled && port->ops->pd_powered(dev)) {
+		pd_voltage = port->ops->pd_voltage(dev);
+	}
+	return sprintf(buf, "%d\n", pd_voltage);
+}
+
+static struct device_attribute port_voltage = __ATTR_RO(pd_voltage);
+
+/* class_show : Show the port's last classification result
+*/
+static ssize_t
+class_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	uint8_t lc;
+	struct pse_port_device *p = to_pse_port_device(dev);
+
+	if (!p->enabled) {
+		return sprintf(buf, "%s", pse_class_strings[CLASS_INVALID]);
+	}
+	if (p->ops->update_classification) {
+		p->ops->update_classification(dev);
+	} else {
+		mutex_lock(&p->lock);
+		p->last_class_result = p->ops->last_classification(dev);
+		mutex_unlock(&p->lock);
+	}
+
+	mutex_lock(&p->lock);
+	lc = p->last_class_result;
+	mutex_unlock(&p->lock);
+
+	if (lc > CLASS_INVALID) {
+		dev_warn(dev, "PSE Class saw invalid class code 0x%02X\n", lc);
+		lc = CLASS_INVALID;
+	}
+	return sprintf(buf, "%s", pse_class_strings[lc]);
+}
+
+static ssize_t
+class_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	int t;
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	if (!port || !port->ops->enable_classify)
+		return -EINVAL;
+
+	t = is_trueish(buf, count);
+	if (t == 0)
+		return -EINVAL;
+
+	if (t < 0) {
+		dev_dbg(dev, "disabling classification for port %d\n", to_pse_port_device(dev)->port_id);
+		port->ops->enable_classify(dev, 0);
+	} else {
+		dev_dbg(dev, "enabling classification for port %d\n", to_pse_port_device(dev)->port_id);
+		port->ops->enable_classify(dev, 1);
+	}
+	return count;
+}
+static struct device_attribute port_class = __ATTR_RW(class);
+
+/* detect_show : show the ports' last detection_result
+*/
+static ssize_t
+detect_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+	uint8_t detval;
+	struct pse_port_device *p = to_pse_port_device(dev);
+
+	if (!p->enabled) {
+		return sprintf(buf, "%s", pse_detect_strings[DETECT_NO_DEVICE]);
+	}
+	if (p->ops->update_detection) {
+		p->ops->update_detection(dev);
+	} else {
+		mutex_lock(&p->lock);
+		p->last_detect_result = p->ops->last_detection(dev);
+		mutex_unlock(&p->lock);
+	}
+	mutex_lock(&p->lock);
+	detval = p->last_detect_result;
+	mutex_unlock(&p->lock);
+
+	if (detval >=  PSE_NUM_DETECT_EVENTS) {
+		dev_warn(dev, "PSE class saw an invalid detection code 0x%02x\n", detval);
+		detval = DETECT_ERROR;
+	}
+	return sprintf(buf,  "%s", pse_detect_strings[detval]);
+}
+static ssize_t
+detect_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	int enable;
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	if (!port || !port->ops->enable_detect)
+		return -EINVAL;
+
+	enable = is_trueish(buf, count);
+	if (enable == 0)
+		return -EINVAL;
+
+	if (enable < 0) {
+		port->ops->enable_detect(dev, 0);
+		dev_dbg(dev, "disabling detection for port %d\n", to_pse_port_device(dev)->port_id);
+	} else {
+		port->ops->enable_detect(dev, 1);
+		dev_dbg(dev, "enabling detection for port %d\n", to_pse_port_device(dev)->port_id);
+	}
+	return count;
+}
+static struct device_attribute port_detect = __ATTR_RW(detect);
+
+/*port_power_allocation_show: Show the allocation for this port
+*/
+static ssize_t
+port_power_allocation_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pse_port_device *port = to_pse_port_device(dev);
+
+	if (!port)
+		return -EINVAL;
+
+	return sprintf(buf, "%d", port->power_allocation);
+}
+static struct device_attribute port_power_allocation = __ATTR_RO(port_power_allocation);
+
+/* port_power_store : power "on/off" a port */
+static ssize_t
+port_power_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	int t;
+
+	dev_dbg(dev, "%s:%d\n", __func__, __LINE__);
+	if (!to_pse_port_device(dev)->ops->apply_power)
+		return -EINVAL;
+
+	t = is_trueish(buf, count);
+	if (t == 0)
+		return -EINVAL;
+
+	if (t > 0)
+		to_pse_port_device(dev)->ops->apply_power(dev, 1);
+	else
+		to_pse_port_device(dev)->ops->apply_power(dev, 0);
+
+	return count;
+
+}
+static ssize_t
+port_power_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	if (!to_pse_port_device(dev)->ops->pd_powered)
+		return -EINVAL;
+
+	return sprintf(buf, to_pse_port_device(dev)->ops->pd_powered(dev) ? "True" : "False");
+}
+static struct device_attribute port_power = __ATTR_RW(port_power);
+
+
+static ssize_t
+config_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pse_port_config config;
+
+	if (!to_pse_port_device(dev)->ops->get_config)
+		return -EINVAL;
+
+	to_pse_port_device(dev)->ops->get_config(dev, &config);
+
+	return sprintf(buf, "enabled:%s\nmanaged:%s\ntype:%d\n"
+						"disconnect_detection:%s\npd_detection_enabled:%s\n"
+						"pd_classification_enabled:%s\n"
+						"legacy_device_enable:%s\ncurrent_limit:%d\n",
+						(config.enabled ? "True" : "False"),
+						(config.managed ? "True" : "False"),
+						(config.port_type+1),
+						(config.disconnect_event ? "True" : "False"),
+						(config.detect_enable ? "True" : "False"),
+						(config.classify_enable ? "True" : "False"),
+						(config.legacy_enable ? "True" : "False"),
+						config.current_limit);
+
+}
+static struct device_attribute port_config = __ATTR_RO(config);
+
+static ssize_t
+reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	if (!to_pse_port_device(dev)->ops->reset)
+		return -EINVAL;
+
+	to_pse_port_device(dev)->ops->reset(dev);
+	return count;
+}
+static struct device_attribute port_reset = __ATTR_WO(reset);
+
+struct attribute *dev_attrs[] = {
+	&port_current.attr,
+	&port_voltage.attr,
+	&port_class.attr,
+	&port_detect.attr,
+	&port_power.attr,
+	&port_power_allocation.attr,
+	&port_enable.attr,
+	&port_config.attr,
+	&port_reset.attr,
+	NULL,
+};
+
+struct attribute_group dev_attr_group = {
+	.attrs = dev_attrs,
+
+};
+
+const struct attribute_group *pse_port_dev_attr_groups[] = {
+	&dev_attr_group,
+	NULL,
+};