On Thu, Aug 06, 2015 at 03:03:48PM +0800, Baolin Wang wrote: > This patch introduces the usb charger driver based on usb gadget that > makes an enhancement to a power driver. It works well in practice but > that requires a system with suitable hardware. > > The basic conception of the usb charger is that, when one usb charger > is added or removed by reporting from the usb gadget state change or > the extcon device state change, the usb charger will report to power > user to set the current limitation. > > The usb charger will register notifiees on the usb gadget or the extcon > device to get notified the usb charger state. > > Power user will register a notifiee on the usb charger to get notified > by status changes from the usb charger. It will report to power user > to set the current limitation when detecting the usb charger is added > or removed from extcon device state or usb gadget state. > > Signed-off-by: Baolin Wang <baolin.wang@xxxxxxxxxx> > --- > drivers/usb/gadget/charger.c | 547 +++++++++++++++++++++++++++++++++++++++ > include/linux/usb/usb_charger.h | 101 ++++++++ > 2 files changed, 648 insertions(+) > create mode 100644 drivers/usb/gadget/charger.c > create mode 100644 include/linux/usb/usb_charger.h > > diff --git a/drivers/usb/gadget/charger.c b/drivers/usb/gadget/charger.c > new file mode 100644 > index 0000000..3ca0180 > --- /dev/null > +++ b/drivers/usb/gadget/charger.c > @@ -0,0 +1,547 @@ > +/* > + * usb charger.c -- USB charger driver > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + */ > + > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/extcon.h> > +#include <linux/export.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > +#include <linux/of_address.h> > +#include <linux/platform_device.h> > +#include <linux/slab.h> > +#include <linux/usb.h> > +#include <linux/usb/ch9.h> > +#include <linux/usb/gadget.h> > +#include <linux/usb/usb_charger.h> > + > +#define DEFAULT_CUR_PROTECT (50) > +#define DEFAULT_SDP_CUR_LIMIT (500 - DEFAULT_CUR_PROTECT) > +#define DEFAULT_DCP_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT) > +#define DEFAULT_CDP_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT) > +#define DEFAULT_ACA_CUR_LIMIT (1500 - DEFAULT_CUR_PROTECT) > + > +static LIST_HEAD(usb_charger_list); > +static DEFINE_MUTEX(usb_charger_list_lock); > + > +/* > + * usb_charger_find_by_name - Get the usb charger device by name. > + * @name - usb charger device name. > + * > + * notes: when this function walks the list and returns a charger > + * it's dropped the lock which means that something else could come > + * along and delete the charger before we dereference the pointer. > + * It's very unlikely but it's a possibility so you should take care > + * of it. > + * Thus when you get the usb charger by name, you should call > + * put_usb_charger() to derease the reference count of the usb charger. > + * > + * return the instance of usb charger device. > + */ > +struct usb_charger *usb_charger_find_by_name(char *name) > +{ > + struct usb_charger *uchger; > + > + if (!name) > + return ERR_PTR(-EINVAL); > + > + mutex_lock(&usb_charger_list_lock); > + list_for_each_entry(uchger, &usb_charger_list, entry) { > + if (!strcmp(uchger->name, name)) { > + get_usb_charger(uchger); > + mutex_unlock(&usb_charger_list_lock); > + return uchger; > + } > + } > + mutex_unlock(&usb_charger_list_lock); > + > + return NULL; > +} > + > +/* > + * usb_charger_register_notify() - Register a notifiee to get notified by > + * any attach status changes from the usb charger type detection. > + * @uchger - the usb charger device which is monitored. > + * @nb - a notifier block to be registered. > + */ > +void usb_charger_register_notify(struct usb_charger *uchger, > + struct notifier_block *nb) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&uchger->lock, flags); > + raw_notifier_chain_register(&uchger->uchger_nh, nb); > + spin_unlock_irqrestore(&uchger->lock, flags); > +} > + > +/* > + * usb_charger_unregister_notify() - Unregister a notifiee from the usb charger. > + * @uchger - the usb charger device which is monitored. > + * @nb - a notifier block to be unregistered. > + */ > +void usb_charger_unregister_notify(struct usb_charger *uchger, > + struct notifier_block *nb) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&uchger->lock, flags); > + raw_notifier_chain_unregister(&uchger->uchger_nh, nb); > + spin_unlock_irqrestore(&uchger->lock, flags); > +} > + > +/* > + * usb_charger_register_extcon_notifier() - Register a notifiee of the usb > + * charger to get notified by any attach status changes from > + * the extcon device. > + * @uchger - the usb charger device. > + * @edev - the extcon device. > + * @extcon_id - extcon id. > + */ > +int usb_charger_register_extcon_notifier(struct usb_charger *uchger, > + struct extcon_dev *edev, > + unsigned int extcon_id) > +{ > + if (!uchger || !edev) > + return -EINVAL; > + > + return extcon_register_notifier(edev, extcon_id, &uchger->extcon_nb.nb); > +} > + > +/* > + * usb_charger_unregister_extcon_notifier() - Unregister a notifiee of the > + * usb charger from the extcon device. > + * @uchger - the usb charger device. > + * @edev - the extcon device. > + * @extcon_id - extcon id. > + */ > +int usb_charger_unregister_extcon_notifier(struct usb_charger *uchger, > + struct extcon_dev *edev, > + unsigned int extcon_id) > +{ > + if (!uchger || !edev) > + return -EINVAL; > + > + return extcon_unregister_notifier(edev, extcon_id, &uchger->extcon_nb.nb); > +} > + > +/* > + * usb_charger_register_gadget_notifier() - Register a notifiee of the usb > + * charger to get notified by any attach status changes from > + * the usb gadget device. > + * @uchger - the usb charger device. > + */ > +int usb_charger_register_gadget_notifier(struct usb_charger *uchger) > +{ > + struct usb_gadget *ugadget = uchger->gadget; > + > + return usb_gadget_register_notify(ugadget, &uchger->gadget_nb); > +} usb_gadget_register_notify is defined at your 2nd patch, it will cause 'git bisect' problem. > + > +/* > + * usb_charger_unregister_gadget_notifier() - Unregister a notifiee of the usb > + * charger from the usb gadget device. > + * @uchger - the usb charger device. > + */ > +int usb_charger_unregister_gadget_notifier(struct usb_charger *uchger) > +{ > + struct usb_gadget *ugadget = uchger->gadget; > + > + return usb_gadget_unregister_notify(ugadget, &uchger->gadget_nb); > +} > + > +/* > + * usb_charger_set_cur_limit() - Set the current limitation. > + * @uchger - the usb charger device. > + * @xxx_cur - the current limit by different charger type. > + * > + */ > +int usb_charger_set_cur_limit(struct usb_charger *uchger, > + struct usb_charger_cur_limit *cur_limit_set) > +{ > + if (!uchger || !cur_limit_set) > + return -EINVAL; > + > + uchger->cur_limit.sdp_cur_limit = cur_limit_set->sdp_cur_limit; > + uchger->cur_limit.dcp_cur_limit = cur_limit_set->dcp_cur_limit; > + uchger->cur_limit.cdp_cur_limit = cur_limit_set->cdp_cur_limit; > + uchger->cur_limit.aca_cur_limit = cur_limit_set->aca_cur_limit; > + return 0; > +} > + > +/* > + * usb_charger_get_cur_limit() - Get the current limitation by different usb > + * charger type. > + * @uchger - the usb charger device. > + * @type - the usb charger type. > + * > + * return the current limitation to set. > + */ > +static unsigned int > +usb_charger_get_cur_limit(struct usb_charger *uchger) > +{ > + enum usb_charger_type uchger_type = uchger->type; > + unsigned int cur_limit; > + > + switch (uchger_type) { > + case SDP_TYPE: > + cur_limit = uchger->cur_limit.sdp_cur_limit; > + break; > + case DCP_TYPE: > + cur_limit = uchger->cur_limit.dcp_cur_limit; > + break; > + case CDP_TYPE: > + cur_limit = uchger->cur_limit.cdp_cur_limit; > + break; > + case ACA_TYPE: > + cur_limit = uchger->cur_limit.aca_cur_limit; > + break; > + default: > + return 0; > + } > + > + return cur_limit; > +} > + > +/* > + * usb_charger_detect_type() - Get the usb charger type by the callback which is > + * implemented by user. > + * @uchger - the usb charger device. > + * > + * return the usb charger type. > + */ > +enum usb_charger_type > +usb_charger_detect_type(struct usb_charger *uchger) > +{ > + if (uchger->gadget && uchger->gadget->ops > + && uchger->gadget->ops->get_charger_type) > + uchger->type = > + uchger->gadget->ops->get_charger_type(uchger->gadget); > + else > + uchger->type = UNKNOWN_TYPE; > + > + return uchger->type; > +} > + > +/* > + * usb_charger_notifier_others() - It will notify other device registered on > + * usb charger. > + * @uchger - the usb charger device. > + * > + */ > +static void > +usb_charger_notify_others(struct usb_charger *uchger, > + enum usb_charger_state state) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&uchger->lock, flags); it is better to use mutex_lock instead of disabling interrupt since checking charger type may cause sleeping. > + uchger->state = state; > + > + switch (state) { > + case USB_CHARGER_PRESENT: > + usb_charger_detect_type(uchger); > + raw_notifier_call_chain(&uchger->uchger_nh, > + usb_charger_get_cur_limit(uchger), > + uchger); > + break; > + case USB_CHARGER_REMOVE: > + uchger->type = UNKNOWN_TYPE; > + raw_notifier_call_chain(&uchger->uchger_nh, 0, uchger); > + break; > + default: > + dev_warn(&uchger->dev, "Unknown USB charger state: %d\n", > + state); > + break; > + } > + spin_unlock_irqrestore(&uchger->lock, flags); > +} > + > +/* > + * usb_charger_plug_by_extcon() - The notifier call function which is registered > + * on the extcon device. > + * @nb - thr notifier block that notified by extcon device. > + * @state - the extcon device state changed. > + * @data - here specify a extcon device. > + * > + * return the notify flag. > + */ > +static int > +usb_charger_plug_by_extcon(struct notifier_block *nb, > + unsigned long state, void *data) > +{ > + struct usb_charger_nb *extcon_nb = > + container_of(nb, struct usb_charger_nb, nb); > + struct usb_charger *uchger = extcon_nb->uchger; > + enum usb_charger_state uchger_state; > + > + if (!uchger) > + return NOTIFY_BAD; > + > + /* Report event to power to setting the current limitation > + * for this usb charger when one usb charger is added or removed > + * with detecting by extcon device. > + */ > + if (state) > + uchger_state = USB_CHARGER_PRESENT; > + else > + uchger_state = USB_CHARGER_REMOVE; > + > + usb_charger_notify_others(uchger, uchger_state); > + > + return NOTIFY_OK; > +} > + > +/* > + * usb_charger_plug_by_gadget() - Set the usb charger current limitation > + * according to the usb gadget device state. > + * @data - here specify a usb charger device. > + * > + */ > +static int > +usb_charger_plug_by_gadget(struct notifier_block *nb, > + unsigned long state, void *data) > +{ > + struct usb_gadget *gadget = (struct usb_gadget *)data; > + struct usb_charger *uchger = gadget->uchger; > + enum usb_charger_state uchger_state; > + > + if (!uchger) > + return NOTIFY_BAD; > + > + /* Report event to power to setting the current limitation > + * for this usb charger when one usb charger state is changed > + * with detecting by usb gadget state. > + */ > + if (uchger->old_gadget_state != state) { > + uchger->old_gadget_state = state; > + > + if (state >= USB_STATE_ATTACHED) > + uchger_state = USB_CHARGER_PRESENT; > + else if (state == USB_STATE_NOTATTACHED) > + uchger_state = USB_CHARGER_REMOVE; > + else > + uchger_state = USB_CHARGER_DEFAULT; > + > + usb_charger_notify_others(uchger, uchger_state); > + } > + > + return NOTIFY_OK; > +} > + > +static int devm_uchger_dev_match(struct device *dev, void *res, void *data) > +{ > + struct usb_charger **r = res; > + > + if (WARN_ON(!r || !*r)) > + return 0; > + > + return *r == data; > +} > + > +static void usb_charger_release(struct device *dev) > +{ > + struct usb_charger *uchger = dev_get_drvdata(dev); > + > + if (!atomic_dec_and_test(&uchger->count)) { > + dev_err(dev, "The usb charger is still in use\n"); > + return; > + } > + > + kfree(uchger->name); > + kfree(uchger); > +} > + > +/* > + * usb_charger_unregister() - Unregister a usb charger device. > + * @uchger - the usb charger device. > + * > + */ > +int usb_charger_unregister(struct usb_charger *uchger) > +{ > + if (!uchger) > + return -EINVAL; > + > + mutex_lock(&usb_charger_list_lock); > + list_del(&uchger->entry); > + mutex_unlock(&usb_charger_list_lock); > + > + device_unregister(&uchger->dev); > + return 0; > +} > + > +static void devm_uchger_dev_unreg(struct device *dev, void *res) > +{ > + usb_charger_unregister(*(struct usb_charger **)res); > +} > + > +void devm_usb_charger_unregister(struct device *dev, > + struct usb_charger *uchger) > +{ > + devres_release(dev, devm_uchger_dev_unreg, > + devm_uchger_dev_match, uchger); > +} > + > +/* > + * usb_charger_register() - Register a new usb charger device. > + * @uchger - the new usb charger device. > + * > + */ > +int usb_charger_register(struct device *dev, struct usb_charger *uchger) > +{ > + static atomic_t uchger_no = ATOMIC_INIT(-1); > + struct usb_charger *tmp; > + int ret; > + > + if (!uchger) { > + dev_err(dev, "no device provided for charger\n"); > + return -EINVAL; > + } > + > + uchger->dev.parent = dev; > + uchger->dev.release = usb_charger_release; > + dev_set_name(&uchger->dev, "usb-chger%lu", > + (unsigned long)atomic_inc_return(&uchger_no)); For the name of usb-charger, which not using "usb-charger.x" directly? > + > + ret = device_register(&uchger->dev); > + if (ret) { > + put_device(&uchger->dev); > + return ret; > + } > + > + dev_set_drvdata(&uchger->dev, uchger); > + > + mutex_lock(&usb_charger_list_lock); > + list_for_each_entry(tmp, &usb_charger_list, entry) { > + if (!(strcmp(tmp->name, uchger->name))) { > + mutex_unlock(&usb_charger_list_lock); > + ret = -EEXIST; > + goto out; > + } > + } > + list_add_tail(&uchger->entry, &usb_charger_list); > + mutex_unlock(&usb_charger_list_lock); > + > + return 0; > + > +out: > + dev_err(dev, "Failed to register usb charger (%s)\n", > + uchger->name); > + device_unregister(&uchger->dev); > + put_device(&uchger->dev); > + return ret; > +} > + > +int devm_usb_charger_register(struct device *dev, > + struct usb_charger *uchger) > +{ > + struct usb_charger **ptr; > + int ret; > + > + ptr = devres_alloc(devm_uchger_dev_unreg, sizeof(*ptr), GFP_KERNEL); > + if (!ptr) > + return -ENOMEM; > + > + ret = usb_charger_register(dev, uchger); > + if (ret) { > + devres_free(ptr); > + return ret; > + } > + > + *ptr = uchger; > + devres_add(dev, ptr); > + > + return 0; > +} > + > +int usb_charger_init(struct usb_gadget *ugadget) > +{ > + struct usb_charger *uchger; > + struct extcon_dev *edev; > + char buf[100]; > + char *str; > + int ret; > + > + if (!ugadget) > + return -EINVAL; > + > + uchger = devm_kzalloc(&ugadget->dev, sizeof(struct usb_charger), > + GFP_KERNEL); > + if (!uchger) > + return -ENOMEM; > + > + sprintf(buf, "usb-charger.%s", ugadget->name); > + str = devm_kzalloc(&ugadget->dev, sizeof(char) * (strlen(buf) + 1), > + GFP_KERNEL); > + if (!str) > + return -ENOMEM; > + > + strcpy(str, buf); > + uchger->name = str; > + uchger->type = UNKNOWN_TYPE; > + uchger->state = USB_CHARGER_DEFAULT; > + uchger->cur_limit.sdp_cur_limit = DEFAULT_SDP_CUR_LIMIT; > + uchger->cur_limit.dcp_cur_limit = DEFAULT_DCP_CUR_LIMIT; > + uchger->cur_limit.cdp_cur_limit = DEFAULT_CDP_CUR_LIMIT; > + uchger->cur_limit.aca_cur_limit = DEFAULT_ACA_CUR_LIMIT; > + > + atomic_set(&uchger->count, 1); > + spin_lock_init(&uchger->lock); > + INIT_LIST_HEAD(&uchger->entry); > + RAW_INIT_NOTIFIER_HEAD(&uchger->uchger_nh); > + > + /* register a notifier on a extcon device if it is exsited */ > + edev = extcon_get_edev_by_phandle(ugadget->dev.parent, 0); > + if (!IS_ERR_OR_NULL(edev)) { > + uchger->extcon_dev = edev; > + uchger->extcon_nb.nb.notifier_call = usb_charger_plug_by_extcon; > + uchger->extcon_nb.uchger = uchger; > + usb_charger_register_extcon_notifier(uchger, edev, EXTCON_USB); > + } > + > + /* register a notifier on a usb gadget device */ > + uchger->gadget = ugadget; > + ugadget->uchger = uchger; > + uchger->old_gadget_state = ugadget->state; > + uchger->gadget_nb.notifier_call = usb_charger_plug_by_gadget; > + usb_charger_register_gadget_notifier(uchger); > + > + ret = usb_charger_register(&ugadget->dev, uchger); > + if (ret) > + goto reg_fail; > + > + return 0; > + > +reg_fail: > + if (uchger->extcon_dev) > + usb_charger_unregister_extcon_notifier(uchger, > + uchger->extcon_dev, EXTCON_USB); > + > + usb_charger_unregister_gadget_notifier(uchger); > + return ret; > +} > + > +int usb_charger_exit(struct usb_gadget *ugadget) > +{ > + struct usb_charger *uchger = ugadget->uchger; > + > + if (!uchger) > + return -EINVAL; > + > + if (uchger->extcon_dev) > + usb_charger_unregister_extcon_notifier(uchger, > + uchger->extcon_dev, EXTCON_USB); > + > + usb_charger_unregister_gadget_notifier(uchger); > + return usb_charger_unregister(uchger); > +} > + > +MODULE_AUTHOR("Baolin Wang <baolin.wang@xxxxxxxxxx>"); > +MODULE_DESCRIPTION("USB charger driver"); > diff --git a/include/linux/usb/usb_charger.h b/include/linux/usb/usb_charger.h > new file mode 100644 > index 0000000..da4d3c9 > --- /dev/null > +++ b/include/linux/usb/usb_charger.h > @@ -0,0 +1,101 @@ > +#ifndef __LINUX_USB_CHARGER_H__ > +#define __LINUX_USB_CHARGER_H__ > + > +#include <linux/device.h> > +#include <linux/notifier.h> > +#include <linux/sysfs.h> > +#include <linux/usb/ch9.h> > + > +/* USB charger type: > + * SDP (Standard Downstream Port) > + * DCP (Dedicated Charging Port) > + * CDP (Charging Downstream Port) > + * ACA (Accessory Charger Adapters) > + */ > +enum usb_charger_type { > + UNKNOWN_TYPE, > + SDP_TYPE, > + DCP_TYPE, > + CDP_TYPE, > + ACA_TYPE, > +}; > + > +/* USB charger state */ > +enum usb_charger_state { > + USB_CHARGER_DEFAULT, > + USB_CHARGER_PRESENT, > + USB_CHARGER_REMOVE, > +}; > + > +/* Current limitation by charger type */ > +struct usb_charger_cur_limit { > + unsigned int sdp_cur_limit; > + unsigned int dcp_cur_limit; > + unsigned int cdp_cur_limit; > + unsigned int aca_cur_limit; > +}; > + > +struct usb_charger_nb { > + struct notifier_block nb; > + struct usb_charger *uchger; > +}; > + > +struct usb_charger { > + /* Internal data. Please do not set. */ > + const char *name; > + struct device dev; > + struct raw_notifier_head uchger_nh; > + struct list_head entry; > + spinlock_t lock; > + enum usb_charger_type type; > + enum usb_charger_state state; > + atomic_t count; > + > + /* For supporting extcon usb gpio */ > + struct extcon_dev *extcon_dev; > + struct usb_charger_nb extcon_nb; > + > + /* For supporting usb gadget */ > + struct usb_gadget *gadget; > + enum usb_device_state old_gadget_state; > + struct notifier_block gadget_nb; > + > + /* Current limitation */ > + struct usb_charger_cur_limit cur_limit; > +}; > + > +extern struct usb_charger *usb_charger_find_by_name(char *name); > + > +extern void usb_charger_register_notify(struct usb_charger *uchger, > + struct notifier_block *nb); > +extern void usb_charger_unregister_notify(struct usb_charger *uchger, > + struct notifier_block *nb); > + > +extern int usb_charger_register_extcon_notifier(struct usb_charger *uchger, > + struct extcon_dev *edev, > + unsigned int extcon_id); > +extern int usb_charger_unregister_extcon_notifier(struct usb_charger *uchger, > + struct extcon_dev *edev, > + unsigned int extcon_id); > + > +extern int usb_charger_register_gadget_notifier(struct usb_charger *uchger); > +extern int usb_charger_unregister_gadget_notifier(struct usb_charger *uchger); > + > +extern int usb_charger_set_cur_limit(struct usb_charger *uchger, > + struct usb_charger_cur_limit *cur_limit_set); > +extern enum usb_charger_type usb_charger_detect_type(struct usb_charger *uchger); > + > +extern int usb_charger_init(struct usb_gadget *ugadget); > +extern int usb_charger_exit(struct usb_gadget *ugadget); > + > +static inline void get_usb_charger(struct usb_charger *uchger) > +{ > + atomic_inc(&uchger->count); > +} > + > +static inline void put_usb_charger(struct usb_charger *uchger) > +{ > + atomic_dec(&uchger->count); > +} > + > +#endif /* __LINUX_USB_CHARGER_H__ */ > -- > 1.7.9.5 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-usb" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Best Regards, Peter Chen -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html