Re: [PATCH v0 2/2] typec: Add Type-C charger

From: Mat King
Date: Tue Apr 21 2020 - 11:56:21 EST


On Tue, Apr 21, 2020 at 6:41 AM Heikki Krogerus
<heikki.krogerus@xxxxxxxxxxxxxxx> wrote:
>
> On Tue, Apr 21, 2020 at 08:44:38AM +0000, Adam Thomson wrote:
> > On 20 April 2020 17:37, Mathew King wrote:
> >
> > > 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.
> >
> > Hmm, this functionally looks almost exactly like code already available in TCPM,
> > except a much smaller subset. This looks like it would duplicate that work so as
> > it stands doesn't feel sensible to me. It may be that the work in TCPM needs
> > refactoring, but I don't believe the two should coexist.
>
> I agree. We can't register a psy for every port and partner
> unconditionally like that.
>
> I do think that for the sake of uniformity it would make sense to have
> the Type-C subsystem supply API that the Type-C drivers can use for
> registering the psy instead of every Type-C driver doing that directly
> with the psy API. So this patch should first introduce that API
> without doing anything automatically.
>
> Once things settle down, we can consider taking care of the psy
> registration for the drivers as well.
>
> Ideally the psy(s) registered here would supply the same information
> and functionality as the psy registered in TCPM. Then a separate patch
> that follows (that is part of the series) could simply convert TCPM to
> use this new API for registering the psy.
>
> thanks,

Thank you for the feedback Adam and Heikki. I will work on improving
the port API so that the psy is not created unconditionally and I will
work on getting to parity with the TCPM psy so that it can be switched
to this new method.

>
> > >
> > > 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
>
> --
> heikki