summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Pham <jackp@codeaurora.org>2016-03-17 00:27:10 -0700
committerJeevan Shriram <jshriram@codeaurora.org>2016-05-10 13:20:25 -0700
commit82a0ca0e2d89e797fbbf2001ec5403764132998c (patch)
tree3806370e62427c606b6d4a858442ba364f8d9b22
parente4fd13f74c5a926c013ae80db1d65ed60b5bee9f (diff)
usb: pd: Add initial support for USB Power Delivery
Add PD policy engine driver. This driver will interact with the charger/Type-C module via power_supply framework, and in turn notify the USB controller on when to begin/end data operation using extcon. For this initial patch this driver is simply acting as a pass-through between Type-C connection states and relaying them as USB/USB_HOST notifications. Change-Id: Ieba8e68761beef83a572b75b6b5f3b7ab7802e9e Signed-off-by: Jack Pham <jackp@codeaurora.org>
-rw-r--r--drivers/usb/Kconfig2
-rw-r--r--drivers/usb/Makefile2
-rw-r--r--drivers/usb/pd/Kconfig16
-rw-r--r--drivers/usb/pd/Makefile5
-rw-r--r--drivers/usb/pd/policy_engine.c297
-rw-r--r--drivers/usb/pd/usbpd.h43
6 files changed, 365 insertions, 0 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 8ed451dd651e..1edaaf65c560 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -106,6 +106,8 @@ source "drivers/usb/chipidea/Kconfig"
source "drivers/usb/isp1760/Kconfig"
+source "drivers/usb/pd/Kconfig"
+
comment "USB port drivers"
if USB
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index d5c57f1e98fd..a0712e28c7d8 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -61,3 +61,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/
+
+obj-$(CONFIG_USB_PD) += pd/
diff --git a/drivers/usb/pd/Kconfig b/drivers/usb/pd/Kconfig
new file mode 100644
index 000000000000..fe61504a22e2
--- /dev/null
+++ b/drivers/usb/pd/Kconfig
@@ -0,0 +1,16 @@
+#
+# USB Power Delivery driver configuration
+#
+menu "USB Power Delivery"
+
+config USB_PD
+ def_bool n
+
+config USB_PD_POLICY
+ tristate "USB Power Delivery Protocol and Policy Engine"
+ depends on EXTCON
+ select USB_PD
+ help
+ Say Y here to enable USB PD protocol and policy engine.
+
+endmenu
diff --git a/drivers/usb/pd/Makefile b/drivers/usb/pd/Makefile
new file mode 100644
index 000000000000..4d9f20598751
--- /dev/null
+++ b/drivers/usb/pd/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for USB Power Delivery drivers
+#
+
+obj-$(CONFIG_USB_PD_POLICY) += policy_engine.o
diff --git a/drivers/usb/pd/policy_engine.c b/drivers/usb/pd/policy_engine.c
new file mode 100644
index 000000000000..af64c85027b9
--- /dev/null
+++ b/drivers/usb/pd/policy_engine.c
@@ -0,0 +1,297 @@
+/* Copyright (c) 2016, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/extcon.h>
+#include "usbpd.h"
+
+struct usbpd {
+ struct device dev;
+ struct workqueue_struct *wq;
+ struct work_struct sm_work;
+
+ struct extcon_dev *extcon;
+ struct power_supply *usb_psy;
+ struct notifier_block psy_nb;
+
+ enum power_supply_typec_mode typec_mode;
+ enum power_supply_type psy_type;
+
+ enum data_role current_dr;
+ enum power_role current_pr;
+
+ struct list_head instance;
+};
+
+static LIST_HEAD(_usbpd); /* useful for debugging */
+
+static const unsigned int usbpd_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static const u32 usbpd_extcon_exclusive[] = {0xffffffff, 0};
+
+static void usbpd_sm(struct work_struct *w)
+{
+ struct usbpd *pd = container_of(w, struct usbpd, sm_work);
+
+ if (pd->current_pr == PR_NONE) {
+ if (pd->current_dr == DR_UFP)
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB, 0);
+ else if (pd->current_dr == DR_DFP)
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 0);
+
+ pd->current_dr = DR_NONE;
+ } else if (pd->current_pr == PR_SINK && pd->current_dr == DR_NONE) {
+ pd->current_dr = DR_UFP;
+
+ if (pd->psy_type == POWER_SUPPLY_TYPE_USB ||
+ pd->psy_type == POWER_SUPPLY_TYPE_USB_CDP)
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB, 1);
+ } else if (pd->current_pr == PR_SRC && pd->current_dr == DR_NONE) {
+ pd->current_dr = DR_DFP;
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 1);
+ }
+}
+
+static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
+{
+ struct usbpd *pd = container_of(nb, struct usbpd, psy_nb);
+ union power_supply_propval val;
+ bool pd_allowed;
+ enum power_supply_typec_mode typec_mode;
+ enum power_supply_type psy_type;
+ int ret;
+
+ if (ptr != pd->usb_psy || evt != PSY_EVENT_PROP_CHANGED)
+ return 0;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ALLOWED, &val);
+ if (ret) {
+ dev_err(&pd->dev, "Unable to read USB PROP_PD_ALLOWED: %d\n",
+ ret);
+ return ret;
+ }
+
+ pd_allowed = val.intval;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPEC_MODE, &val);
+ if (ret) {
+ dev_err(&pd->dev, "Unable to read USB TYPEC_MODE: %d\n", ret);
+ return ret;
+ }
+
+ /* don't proceed if PD_ALLOWED is false */
+ if (!pd_allowed && val.intval != POWER_SUPPLY_TYPEC_NONE)
+ return 0;
+
+ typec_mode = val.intval;
+
+ ret = power_supply_get_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &val);
+ if (ret) {
+ dev_err(&pd->dev, "Unable to read USB TYPE: %d\n", ret);
+ return ret;
+ }
+
+ psy_type = val.intval;
+
+ dev_dbg(&pd->dev, "typec mode:%d present:%d type:%d\n", typec_mode,
+ pd->vbus_present, psy_type);
+
+ /* any change? */
+ if (pd->typec_mode == typec_mode && pd->psy_type == psy_type)
+ return 0;
+
+ pd->typec_mode = typec_mode;
+ pd->psy_type = psy_type;
+
+ switch (typec_mode) {
+ /* Disconnect */
+ case POWER_SUPPLY_TYPEC_NONE:
+ pd->current_pr = PR_NONE;
+ queue_work(pd->wq, &pd->sm_work);
+ break;
+
+ /* Sink states */
+ case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+ case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+ case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+ dev_info(&pd->dev, "Type-C Source connected\n");
+ pd->current_pr = PR_SINK;
+ queue_work(pd->wq, &pd->sm_work);
+ break;
+
+ /* Source states */
+ case POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE:
+ case POWER_SUPPLY_TYPEC_SINK:
+ dev_info(&pd->dev, "Type-C Sink connected\n");
+ pd->current_pr = PR_SRC;
+ queue_work(pd->wq, &pd->sm_work);
+ break;
+
+ case POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY:
+ dev_info(&pd->dev, "Type-C Debug Accessory connected\n");
+ break;
+ case POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER:
+ dev_info(&pd->dev, "Type-C Analog Audio Adapter connected\n");
+ break;
+ default:
+ dev_warn(&pd->dev, "Unsupported typec mode: %d\n", typec_mode);
+ break;
+ }
+
+ return 0;
+}
+
+static struct class usbpd_class = {
+ .name = "usbpd",
+ .owner = THIS_MODULE,
+};
+
+static int num_pd_instances;
+
+/**
+ * usbpd_create - Create a new instance of USB PD protocol/policy engine
+ * @parent - parent device to associate with
+ *
+ * This creates a new usbpd class device which manages the state of a
+ * USB PD-capable port. The parent device that is passed in should be
+ * associated with the physical device port, e.g. a PD PHY.
+ *
+ * Return: struct usbpd pointer, or an ERR_PTR value
+ */
+struct usbpd *usbpd_create(struct device *parent)
+{
+ int ret;
+ struct usbpd *pd;
+
+ pd = kzalloc(sizeof(*pd), GFP_KERNEL);
+ if (!pd)
+ return ERR_PTR(-ENOMEM);
+
+ device_initialize(&pd->dev);
+ pd->dev.class = &usbpd_class;
+ pd->dev.parent = parent;
+ dev_set_drvdata(&pd->dev, pd);
+
+ ret = dev_set_name(&pd->dev, "usbpd%d", num_pd_instances++);
+ if (ret)
+ goto free_pd;
+
+ ret = device_add(&pd->dev);
+ if (ret)
+ goto free_pd;
+
+ pd->wq = alloc_ordered_workqueue("usbpd_wq", WQ_FREEZABLE);
+ if (!pd->wq) {
+ ret = -ENOMEM;
+ goto del_pd;
+ }
+ INIT_WORK(&pd->sm_work, usbpd_sm);
+
+ pd->usb_psy = power_supply_get_by_name("usb");
+ if (!pd->usb_psy) {
+ dev_dbg(&pd->dev, "Could not get USB power_supply, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ goto destroy_wq;
+ }
+
+ pd->psy_nb.notifier_call = psy_changed;
+ ret = power_supply_reg_notifier(&pd->psy_nb);
+ if (ret)
+ goto put_psy;
+
+ /*
+ * associate extcon with the parent dev as it could have a DT
+ * node which will be useful for extcon_get_edev_by_phandle()
+ */
+ pd->extcon = devm_extcon_dev_allocate(parent, usbpd_extcon_cable);
+ if (IS_ERR(pd->extcon)) {
+ dev_err(&pd->dev, "failed to allocate extcon device\n");
+ ret = PTR_ERR(pd->extcon);
+ goto unreg_psy;
+ }
+
+ pd->extcon->mutually_exclusive = usbpd_extcon_exclusive;
+ ret = devm_extcon_dev_register(parent, pd->extcon);
+ if (ret) {
+ dev_err(&pd->dev, "failed to register extcon device\n");
+ goto unreg_psy;
+ }
+
+ pd->current_pr = PR_NONE;
+ pd->current_dr = DR_NONE;
+ list_add_tail(&pd->instance, &_usbpd);
+
+ /* force read initial power_supply values */
+ psy_changed(&pd->psy_nb, PSY_EVENT_PROP_CHANGED, pd->usb_psy);
+
+ return pd;
+
+unreg_psy:
+ power_supply_unreg_notifier(&pd->psy_nb);
+put_psy:
+ power_supply_put(pd->usb_psy);
+destroy_wq:
+ destroy_workqueue(pd->wq);
+del_pd:
+ device_del(&pd->dev);
+free_pd:
+ num_pd_instances--;
+ kfree(pd);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(usbpd_create);
+
+/**
+ * usbpd_destroy - Removes and frees a usbpd instance
+ * @pd: the instance to destroy
+ */
+void usbpd_destroy(struct usbpd *pd)
+{
+ if (!pd)
+ return;
+
+ list_del(&pd->instance);
+ power_supply_unreg_notifier(&pd->psy_nb);
+ power_supply_put(pd->usb_psy);
+ destroy_workqueue(pd->wq);
+ device_del(&pd->dev);
+ kfree(pd);
+}
+EXPORT_SYMBOL(usbpd_destroy);
+
+static int __init usbpd_init(void)
+{
+ return class_register(&usbpd_class);
+}
+module_init(usbpd_init);
+
+static void __exit usbpd_exit(void)
+{
+ class_unregister(&usbpd_class);
+}
+module_exit(usbpd_exit);
+
+MODULE_DESCRIPTION("USB Power Delivery Policy Engine");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/pd/usbpd.h b/drivers/usb/pd/usbpd.h
new file mode 100644
index 000000000000..7a24d33190e4
--- /dev/null
+++ b/drivers/usb/pd/usbpd.h
@@ -0,0 +1,43 @@
+/* Copyright (c) 2016, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _USBPD_H
+#define _USBPD_H
+
+#include <linux/device.h>
+
+struct usbpd;
+
+#if IS_ENABLED(CONFIG_USB_PD_POLICY)
+struct usbpd *usbpd_create(struct device *parent);
+void usbpd_destroy(struct usbpd *pd);
+#else
+static inline struct usbpd *usbpd_create(struct device *parent)
+{
+ return ERR_PTR(-ENODEV);
+}
+static inline void usbpd_destroy(struct usbpd *pd) { }
+#endif
+
+enum data_role {
+ DR_NONE = -1,
+ DR_UFP = 0,
+ DR_DFP = 1,
+};
+
+enum power_role {
+ PR_NONE = -1,
+ PR_SINK = 0,
+ PR_SRC = 1,
+};
+
+#endif /* _USBPD_H */