From: Mathew King <mathewk@chromium.org>
To: linux-kernel@vger.kernel.org
Cc: Mathew King <mathewk@chromium.org>,
Heikki Krogerus <heikki.krogerus@linux.intel.com>,
Benson Leung <bleung@chromium.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
linux-usb@vger.kernel.org, linux-pm@vger.kernel.org
Subject: [PATCH v0 2/2] typec: Add Type-C charger
Date: Mon, 20 Apr 2020 10:36:57 -0600 [thread overview]
Message-ID: <20200420163657.60650-3-mathewk@chromium.org> (raw)
In-Reply-To: <20200420163657.60650-1-mathewk@chromium.org>
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@chromium.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@google.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
next prev parent reply other threads:[~2020-04-20 16:37 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-04-20 16:36 [PATCH v0 0/2] Type-C charger support using power_supply Mathew King
2020-04-20 16:36 ` [PATCH v0 1/2] typec: Move typec class structs into a header file Mathew King
2020-04-20 16:36 ` Mathew King [this message]
2020-04-21 8:44 ` [PATCH v0 2/2] typec: Add Type-C charger Adam Thomson
2020-04-21 12:41 ` Heikki Krogerus
2020-04-21 15:56 ` Mat King
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200420163657.60650-3-mathewk@chromium.org \
--to=mathewk@chromium.org \
--cc=bleung@chromium.org \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.