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, ¤t_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,
+};