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@xxxxxxxxxxxx> --- 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@xxxxxxxxxx> + */ + +#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