lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 20 Apr 2020 10:36:57 -0600
From:   Mathew King <mathewk@...omium.org>
To:     linux-kernel@...r.kernel.org
Cc:     Mathew King <mathewk@...omium.org>,
        Heikki Krogerus <heikki.krogerus@...ux.intel.com>,
        Benson Leung <bleung@...omium.org>,
        Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
        linux-usb@...r.kernel.org, linux-pm@...r.kernel.org
Subject: [PATCH v0 2/2] typec: Add Type-C charger

Add an option to expose USB Type-C ports that can charge system
batteries as a power_supply class. This implementation only exposes
three properties of the power supply.

POWER_SUPPLY_PROP_ONLINE - Set to true if the Type-C port is configured
                           as a sink and is connected to a partner
POWER_SUPPLY_PROP_STATUS - Set to CHARGING if a partner is connected and
                           the port is a sink and set to NOT_CHARGING
                           otherwise
POWER_SUPPLY_PROP_USB_TYPE - When a partner is conneced set to TYPE_C,
                             TYPE_PD, or TYPE_PD_DRP depending on the
                             partner capibilities and set to
                             TYPE_UNKNOWN otherwise

This implementation can be expanded as the typec class is expaneded. In
particular the STATUS property should show more than CHARGING and
NOT_CHARGING. Also properties like VOLTAGE and CURRENT can be added when
the typec class supports getting PDOs.

Signed-off-by: Mathew King <mathewk@...omium.org>
---
 drivers/usb/typec/Kconfig   |  11 ++
 drivers/usb/typec/Makefile  |   1 +
 drivers/usb/typec/charger.c | 204 ++++++++++++++++++++++++++++++++++++
 drivers/usb/typec/charger.h |  33 ++++++
 drivers/usb/typec/class.c   |  48 +++++++--
 drivers/usb/typec/class.h   |   2 +
 6 files changed, 290 insertions(+), 9 deletions(-)
 create mode 100644 drivers/usb/typec/charger.c
 create mode 100644 drivers/usb/typec/charger.h

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index b4f2aac7ae8a..1040c990cb7e 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -46,6 +46,17 @@ menuconfig TYPEC
 
 if TYPEC
 
+config TYPEC_CHARGER
+	bool "Type-C Power Supply support"
+	depends on POWER_SUPPLY
+	help
+	  Say Y here to enable Type-C charging ports to be exposed as a power
+	  supply class.
+
+	  If you choose this option Type-C charger support will be built into
+	  the typec driver. This will expose all Type-C ports as a power_supply
+	  class.
+
 source "drivers/usb/typec/tcpm/Kconfig"
 
 source "drivers/usb/typec/ucsi/Kconfig"
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 7753a5c3cd46..6fc5424761a1 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)		+= typec.o
 typec-y				:= class.o mux.o bus.o
+typec-$(CONFIG_TYPEC_CHARGER)	+= charger.o
 obj-$(CONFIG_TYPEC)		+= altmodes/
 obj-$(CONFIG_TYPEC_TCPM)	+= tcpm/
 obj-$(CONFIG_TYPEC_UCSI)	+= ucsi/
diff --git a/drivers/usb/typec/charger.c b/drivers/usb/typec/charger.c
new file mode 100644
index 000000000000..07c3cd065be8
--- /dev/null
+++ b/drivers/usb/typec/charger.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Type-C Charger Class
+ *
+ * Copyright (C) 2020, Google LLC
+ * Author: Mathew King <mathewk@...gle.com>
+ */
+
+#include <linux/slab.h>
+
+#include "charger.h"
+#include "class.h"
+
+static enum power_supply_property typec_charger_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_USB_TYPE
+};
+
+static enum power_supply_usb_type typec_charger_usb_types[] = {
+	POWER_SUPPLY_USB_TYPE_UNKNOWN,
+	POWER_SUPPLY_USB_TYPE_C,
+	POWER_SUPPLY_USB_TYPE_PD,
+	POWER_SUPPLY_USB_TYPE_PD_DRP,
+};
+
+static int typec_charger_get_prop(struct power_supply *psy,
+				  enum power_supply_property psp,
+				  union power_supply_propval *val)
+{
+	struct typec_charger *charger = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = charger->psy_online;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = charger->psy_status;
+		break;
+	case POWER_SUPPLY_PROP_USB_TYPE:
+		val->intval = charger->psy_usb_type;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int typec_charger_set_prop(struct power_supply *psy,
+				  enum power_supply_property psp,
+				  const union power_supply_propval *val)
+{
+	return -EINVAL;
+}
+
+static int typec_charger_is_writeable(struct power_supply *psy,
+				      enum power_supply_property psp)
+{
+	return 0;
+}
+
+/**
+ * typec_charger_changed - Notify of a Type-C charger change
+ * @charger: Type-C charger that changed
+ *
+ * Notifies the Type-C charger that one or more of its attributes may have
+ * changed.
+ */
+void typec_charger_changed(struct typec_charger *charger)
+{
+	int last_psy_status, last_psy_usb_type, last_psy_online;
+
+	last_psy_online = charger->psy_online;
+	last_psy_status = charger->psy_status;
+	last_psy_usb_type = charger->psy_usb_type;
+
+	if (!charger->partner) {
+		charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+		charger->psy_online = 0;
+		charger->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		goto out_notify;
+	}
+
+	if (charger->port->pwr_role == TYPEC_SOURCE) {
+		charger->psy_online = 0;
+		charger->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		if (charger->partner->usb_pd)
+			charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD_DRP;
+		else
+			charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+
+		goto out_notify;
+	}
+
+	charger->psy_online = 1;
+	charger->psy_status = POWER_SUPPLY_STATUS_CHARGING;
+
+	if (charger->partner->usb_pd)
+		charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD;
+	else
+		charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+out_notify:
+	if (last_psy_usb_type != charger->psy_usb_type ||
+	    last_psy_status != charger->psy_status ||
+	    last_psy_online != charger->psy_online)
+		power_supply_changed(charger->psy);
+}
+EXPORT_SYMBOL_GPL(typec_charger_changed);
+
+/**
+ * typec_register_charger - Register a USB Type-C Charger
+ * @port: Type-C port to register as a charger
+ *
+ * Registers a Type-C port as a charger.
+ *
+ * Returns handle to the charger on success or ERR_PTR on failure.
+ */
+struct typec_charger *typec_register_charger(struct typec_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+	struct typec_charger *charger;
+	struct power_supply *psy;
+
+	charger = kzalloc(sizeof(struct typec_charger), GFP_KERNEL);
+	if (!port)
+		return ERR_PTR(-ENOMEM);
+
+	charger->port = port;
+	sprintf(charger->name, TYPEC_CHARGER_DIR_NAME, port->id);
+	charger->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+	charger->psy_online = 0;
+	charger->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+	charger->psy_desc.name = charger->name;
+	charger->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+	charger->psy_desc.get_property = typec_charger_get_prop;
+	charger->psy_desc.set_property = typec_charger_set_prop;
+	charger->psy_desc.property_is_writeable =
+		typec_charger_is_writeable;
+	charger->psy_desc.properties = typec_charger_props;
+	charger->psy_desc.num_properties =
+				ARRAY_SIZE(typec_charger_props);
+	charger->psy_desc.usb_types = typec_charger_usb_types;
+	charger->psy_desc.num_usb_types =
+			ARRAY_SIZE(typec_charger_usb_types);
+	psy_cfg.drv_data = charger;
+
+	psy = devm_power_supply_register_no_ws(&port->dev, &charger->psy_desc,
+					       &psy_cfg);
+	if (IS_ERR(psy)) {
+		dev_err(&port->dev, "Failed to register Type-C power supply\n");
+		return ERR_CAST(psy);
+	}
+	charger->psy = psy;
+
+	return charger;
+}
+EXPORT_SYMBOL_GPL(typec_register_charger);
+
+/**
+ * typec_unregister_charger - Unregister a USB Type-C Charger
+ * @charger: The charger to unregister
+ *
+ * Unregisters a charger created with typec_register_charger().
+ */
+void typec_unregister_charger(struct typec_charger *charger)
+{
+	if (!IS_ERR_OR_NULL(charger))
+		kfree(charger);
+}
+EXPORT_SYMBOL_GPL(typec_unregister_charger);
+
+/**
+ * typec_charger_register_partner - Register a partner with a USB Type-C Charger
+ * @charger: The charger to add the partner too
+ * @partner: The partner to add
+ *
+ * Add a partner to a Type-C charger to indicate that the partner is connected
+ * and may be charging.
+ */
+void typec_charger_register_partner(struct typec_charger *charger,
+				    struct typec_partner *partner)
+{
+	charger->partner = partner;
+	typec_charger_changed(charger);
+}
+EXPORT_SYMBOL_GPL(typec_charger_register_partner);
+
+/**
+ * typec_charger_unregister_partner - Unregister a USB Type-C Charger partner
+ * @charger: The charger to remove the partner from
+ *
+ * Remove partner added with typec_charger_register_partner().
+ */
+void typec_charger_unregister_partner(struct typec_charger *charger)
+{
+	if (!IS_ERR_OR_NULL(charger))
+		charger->partner = NULL;
+
+	typec_charger_changed(charger);
+}
+EXPORT_SYMBOL_GPL(typec_charger_unregister_partner);
diff --git a/drivers/usb/typec/charger.h b/drivers/usb/typec/charger.h
new file mode 100644
index 000000000000..32cdaa7c1a83
--- /dev/null
+++ b/drivers/usb/typec/charger.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_CHARGER_H__
+#define __USB_TYPEC_CHARGER_H__
+
+#include <linux/power_supply.h>
+#include <linux/usb/typec.h>
+
+#include "class.h"
+
+#define TYPEC_CHARGER_DIR_NAME			"TYPEC_CHARGER%d"
+#define TYPEC_CHARGER_DIR_NAME_LENGTH		sizeof(TYPEC_CHARGER_DIR_NAME)
+
+struct typec_charger {
+	struct typec_port *port;
+	struct typec_partner *partner;
+	char name[TYPEC_CHARGER_DIR_NAME_LENGTH];
+	struct power_supply *psy;
+	struct power_supply_desc psy_desc;
+	int psy_usb_type;
+	int psy_online;
+	int psy_status;
+};
+
+struct typec_charger *typec_register_charger(struct typec_port *port);
+void typec_unregister_charger(struct typec_charger *charger);
+
+void typec_charger_register_partner(struct typec_charger *charger,
+				    struct typec_partner *partner);
+void typec_charger_unregister_partner(struct typec_charger *charger);
+void typec_charger_changed(struct typec_charger *charger);
+
+#endif /* __USB_TYPEC_CHARGER_H__ */
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 9a1fdce137b9..1542d3af342c 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -13,6 +13,7 @@
 #include <linux/slab.h>
 
 #include "bus.h"
+#include "charger.h"
 #include "class.h"
 
 static DEFINE_IDA(typec_index_ida);
@@ -489,6 +490,12 @@ static void typec_partner_release(struct device *dev)
 {
 	struct typec_partner *partner = to_typec_partner(dev);
 
+	if (IS_ENABLED(CONFIG_TYPEC_CHARGER)) {
+		struct typec_port *port = to_typec_port(dev->parent);
+
+		typec_charger_unregister_partner(port->charger);
+	}
+
 	ida_destroy(&partner->mode_ids);
 	kfree(partner);
 }
@@ -580,6 +587,10 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
 		return ERR_PTR(ret);
 	}
 
+	if (IS_ENABLED(CONFIG_TYPEC_CHARGER) && port->charger) {
+		typec_charger_register_partner(port->charger, partner);
+	}
+
 	return partner;
 }
 EXPORT_SYMBOL_GPL(typec_register_partner);
@@ -1283,6 +1294,9 @@ static void typec_release(struct device *dev)
 {
 	struct typec_port *port = to_typec_port(dev);
 
+	if (IS_ENABLED(CONFIG_TYPEC_CHARGER))
+		typec_unregister_charger(port->charger);
+
 	ida_simple_remove(&typec_index_ida, port->id);
 	ida_destroy(&port->mode_ids);
 	typec_switch_put(port->sw);
@@ -1564,7 +1578,8 @@ struct typec_port *typec_register_port(struct device *parent,
 	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
 	if (id < 0) {
 		kfree(port);
-		return ERR_PTR(id);
+		ret = id;
+		goto err_return;
 	}
 
 	switch (cap->type) {
@@ -1617,32 +1632,47 @@ struct typec_port *typec_register_port(struct device *parent,
 
 	port->cap = kmemdup(cap, sizeof(*cap), GFP_KERNEL);
 	if (!port->cap) {
-		put_device(&port->dev);
-		return ERR_PTR(-ENOMEM);
+		ret = -ENOMEM;
+		goto err_put_device;
 	}
 
 	port->sw = typec_switch_get(&port->dev);
 	if (IS_ERR(port->sw)) {
 		ret = PTR_ERR(port->sw);
-		put_device(&port->dev);
-		return ERR_PTR(ret);
+		goto err_put_device;
 	}
 
 	port->mux = typec_mux_get(&port->dev, NULL);
 	if (IS_ERR(port->mux)) {
 		ret = PTR_ERR(port->mux);
-		put_device(&port->dev);
-		return ERR_PTR(ret);
+		goto err_put_device;
 	}
 
 	ret = device_add(&port->dev);
 	if (ret) {
 		dev_err(parent, "failed to register port (%d)\n", ret);
-		put_device(&port->dev);
-		return ERR_PTR(ret);
+		goto err_put_device;
+	}
+
+	if (IS_ENABLED(CONFIG_TYPEC_CHARGER)) {
+		port->charger = typec_register_charger(port);
+
+		if (IS_ERR(port->charger)) {
+			ret = PTR_ERR(port->charger);
+			goto err_device_del;
+		}
 	}
 
 	return port;
+
+err_device_del:
+	device_del(&port->dev);
+
+err_put_device:
+	put_device(&port->dev);
+
+err_return:
+	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(typec_register_port);
 
diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h
index ec933dfe1323..0ff0a590d316 100644
--- a/drivers/usb/typec/class.h
+++ b/drivers/usb/typec/class.h
@@ -41,6 +41,8 @@ struct typec_port {
 	struct typec_switch		*sw;
 	struct typec_mux		*mux;
 
+	struct typec_charger		*charger;
+
 	const struct typec_capability	*cap;
 	const struct typec_operations   *ops;
 };
-- 
2.26.1.301.g55bc3eb7cb9-goog

Powered by blists - more mailing lists