[RFC PATCH 1/3] USB: Add Generic USB PHY Layer code
Peter Chen
peter.chen at freescale.com
Tue Dec 6 05:43:12 EST 2011
This code is based on Heikki Krogerus's RFC that
Separate USB transceivers from the OTG utility
(http://www.spinics.net/lists/linux-usb/msg50149.html)
The main changes are:
- Change usb_get_transceiver's parameter from phy's name to related
controller device's name
- Fix some errors, and let it work OK at freescale i.MX51 bbg board.
Signed-off-by: Peter Chen <peter.chen at freescale.com>
---
drivers/usb/otg/Kconfig | 2 +
drivers/usb/otg/Makefile | 1 +
drivers/usb/otg/phy.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/usb/phy.h | 250 +++++++++++++++++++++++++++++++
4 files changed, 621 insertions(+), 0 deletions(-)
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
index c66481a..a313dbd 100644
--- a/drivers/usb/otg/Kconfig
+++ b/drivers/usb/otg/Kconfig
@@ -130,4 +130,6 @@ config FSL_USB2_OTG
help
Enable this to support Freescale USB OTG transceiver.
+config USB_PHY
+ bool
endif # USB || OTG
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
index 566655c..6545d6d 100644
--- a/drivers/usb/otg/Makefile
+++ b/drivers/usb/otg/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_TWL6030_USB) += twl6030-usb.o
obj-$(CONFIG_USB_LANGWELL_OTG) += langwell_otg.o
obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o
obj-$(CONFIG_USB_ULPI) += ulpi.o
+obj-$(CONFIG_USB_PHY) += phy.o
obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o
obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
diff --git a/drivers/usb/otg/phy.c b/drivers/usb/otg/phy.c
new file mode 100644
index 0000000..8a5f50c
--- /dev/null
+++ b/drivers/usb/otg/phy.c
@@ -0,0 +1,368 @@
+/*
+ * phy.c -- USB Pysical Layer utility code
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/list.h>
+
+#include <linux/usb/phy.h>
+
+static LIST_HEAD(xceiv_list);
+static DEFINE_MUTEX(xceiv_lock);
+
+/* For transceiver drivers */
+
+/*
+ * usb_register_transceiver - add a USB transceiver
+ * @xceiv: the transceiver to be added
+ *
+ * Add new transceiver to the list. USB transceiver drivers call this
+ * after filling struct usb_transceiver.
+ */
+void usb_register_transceiver(struct usb_transceiver *x)
+{
+ mutex_lock(&xceiv_lock);
+
+ list_add_tail(&x->list, &xceiv_list);
+
+ mutex_unlock(&xceiv_lock);
+}
+EXPORT_SYMBOL(usb_register_transceiver);
+
+/*
+ * usb_del_transceiver - remove a USB transceiver from the list
+ * @xceiv: the transceiver to be removed from the list
+ *
+ * Removes a transceiver from the list. USB transceiver drivers will
+ * call this when they are unloading. It is the responsibility of the
+ * driver the ensure there are no users left before calling this.
+ */
+void usb_unregister_transceiver(struct usb_transceiver *xceiv)
+{
+ mutex_lock(&xceiv_lock);
+
+ list_del(&xceiv->list);
+
+ mutex_unlock(&xceiv_lock);
+}
+EXPORT_SYMBOL(usb_unregister_transceiver);
+
+/* For everyone */
+
+/*
+ * usb_get_transceiver - find a USB transceiver
+ * @name: the device name which uses this transceiver
+ *
+ * Returns a transceiver driver matching the name, or NULL, and gets
+ * refcount to it. The caller is responsible for calling
+ * usb_put_transceiver() to release that count.
+ */
+struct usb_transceiver *usb_get_transceiver(const char *name)
+{
+ struct usb_transceiver *xceiv;
+
+ mutex_lock(&xceiv_lock);
+
+ list_for_each_entry(xceiv, &xceiv_list, list) {
+ if (strcmp(xceiv->recv_name, name) == 0) {
+ get_device(xceiv->dev);
+ mutex_unlock(&xceiv_lock);
+ return xceiv;
+ }
+ }
+
+ mutex_unlock(&xceiv_lock);
+
+ return NULL;
+}
+EXPORT_SYMBOL(usb_get_transceiver);
+
+/*
+ * usb_put_transceiver - release a USB transceiver
+ * @xceiv: the transceiver returned by usb_get_transceiver()
+ *
+ * Releases a refcount the caller received from usb_get_transceiver().
+ */
+void usb_put_transceiver(struct usb_transceiver *xceiv)
+{
+ mutex_lock(&xceiv_lock);
+
+ if (xceiv)
+ put_device(xceiv->dev);
+
+ mutex_unlock(&xceiv_lock);
+}
+EXPORT_SYMBOL(usb_put_transceiver);
+
+/* USB charging */
+
+static int usb_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct usb_charger *charger =
+ container_of(psy, struct usb_charger, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = charger->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = charger->online;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = charger->max_current;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, /* Charger detected */
+ POWER_SUPPLY_PROP_ONLINE, /* VBUS online */
+ POWER_SUPPLY_PROP_CURRENT_MAX, /* Maximum current in mA */
+};
+
+static void usb_charger_work(struct work_struct *data)
+{
+ int ret;
+ struct usb_charger *charger =
+ container_of(data, struct usb_charger, work);
+ struct usb_transceiver *xceiv =
+ container_of(charger, struct usb_transceiver, charger);
+
+ /**
+ * FIXME: THIS IS ONLY THE CONCEPT
+ */
+
+ if (!charger->online)
+ return;
+
+ mutex_lock(&charger->lock);
+
+ /*
+ * Let the charger know VBUS is online. This will usually
+ * starts data contact detection.
+ */
+ if (charger->connect && charger->connect(charger))
+ goto out;
+
+ /* Start the primary charger detection. */
+ if (charger->detect) {
+ ret = charger->detect(charger);
+ if (ret <= 0)
+ goto out;
+ else
+ charger->present = ret;
+ }
+
+ charger->psy.type = POWER_SUPPLY_TYPE_USB_DCP;
+
+ /* Set charger type (DCP, CDP, SDP...). */
+ if (charger->present && charger->get_type) {
+ ret = charger->get_type(charger);
+ if (ret >= 0)
+ charger->psy.type = ret;
+ }
+out:
+ switch (charger->psy.type) {
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ charger->max_current = 5000; /* FIXME */
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ charger->max_current = 500;
+ /* FALLTHROUGH */
+ case POWER_SUPPLY_TYPE_USB:
+ default:
+ if (xceiv->link_connect)
+ xceiv->link_connect(xceiv->link_data);
+ break;
+ }
+
+ power_supply_changed(&charger->psy);
+
+ mutex_unlock(&charger->lock);
+}
+
+/*
+ * usb_create_charger - create a USB charger
+ * @charger: the charger to be initialized
+ * @name: name for the power supply
+ * @supplied_to: the power supplies that use this charger (batteries)
+ * @num_supplicants: number of power supplies using this charger
+ *
+ * Registers a power supply for the charger. The charger driver will
+ * call this after filling struct usb_charger. All the users are
+ * expected to be in the supplied_to parameter.
+ *
+ * There is no expectation for charger detection capability. USB as
+ * B-peripheral will always supply power. If charger detection is
+ * supported, the driver will fill the appropriate callbacks in the
+ * struct usb_charger.
+ *
+ * A transceiver will always contain the charger, was it used or not.
+ * The charger detection may be done in the transceiver hw or it may
+ * be done in a completely separate hw block. In any case, a charger
+ * is always linked with a transceiver as a transceiver will always
+ * represent the USB PHY where the power is actually coming.
+ */
+int usb_create_charger(struct usb_charger *charger,
+ const char *name,
+ char **supplied_to,
+ size_t num_supplicants)
+{
+ int ret;
+ struct power_supply *psy = &charger->psy;
+ struct usb_transceiver *xceiv =
+ container_of(charger, struct usb_transceiver, charger);
+
+ if (!charger->dev)
+ return -EINVAL;
+
+ if (name)
+ psy->name = name;
+ else
+ psy->name = "usb";
+ psy->type = POWER_SUPPLY_TYPE_USB;
+ psy->properties = power_props;
+ psy->num_properties = ARRAY_SIZE(power_props);
+ psy->get_property = usb_charger_get_property;
+
+ psy->supplied_to = supplied_to;
+ psy->num_supplicants = num_supplicants;
+
+ ret = power_supply_register(charger->dev, psy);
+ if (ret)
+ goto fail;
+
+ mutex_init(&charger->lock);
+ INIT_WORK(&charger->work, usb_charger_work);
+
+ xceiv->has_charger = 1;
+fail:
+ return ret;
+}
+EXPORT_SYMBOL(usb_create_charger);
+
+/*
+ * usb_remove_charger - remove a USB charger
+ * @charger: the charger to be removed
+ *
+ * Unregister the chargers power supply.
+ */
+void usb_remove_charger(struct usb_charger *charger)
+{
+ struct usb_transceiver *xceiv =
+ container_of(charger, struct usb_transceiver, charger);
+
+ if (!xceiv->has_charger)
+ return;
+
+ power_supply_unregister(&charger->psy);
+ xceiv->has_charger = 0;
+}
+EXPORT_SYMBOL(usb_remove_charger);
+
+/*
+ * usb_set_power - Set the maximum power allowed to draw
+ * @xceiv: the transceiver containing the charger
+ * @mA: maximum current in milliamps
+ *
+ * Called from the controller after enumeration to inform the maximum
+ * power from the configuration, after bus suspend and resume.
+ */
+int usb_set_power(struct usb_transceiver *xceiv, unsigned mA)
+{
+ struct usb_charger *charger = &xceiv->charger;
+
+ /**
+ * FIXME: THIS IS ONLY THE CONCEPT
+ */
+
+ if (!xceiv->has_charger || !charger->online)
+ return 0;
+
+ if (charger->max_current == mA)
+ return 0;
+
+ /* If the charger is present, this is a CDP charger */
+ /* FIXME: Check the charger type. */
+ /* FIXME: When the bus is suspended, the current needs to be handled */
+ if (charger->present)
+ charger->max_current = 5000; /* FIXME */
+ else
+ charger->max_current = mA;
+
+ if (charger->set_power)
+ charger->set_power(charger, mA);
+
+ power_supply_changed(&charger->psy);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_set_power);
+
+/*
+ * usb_vbus_connect - inform about VBUS connection
+ * @xceiv: the transceiver containing the charger
+ *
+ * Inform the charger VBUS is connected. The USB device controller is
+ * expected to keep the dataline pullups disabled until link_connect()
+ * is called.
+ */
+int usb_vbus_connect(struct usb_transceiver *xceiv)
+{
+ struct usb_charger *charger = &xceiv->charger;
+
+ if (!xceiv->has_charger) {
+ if (xceiv->link_connect)
+ xceiv->link_connect(xceiv->link_data);
+ return 0;
+ }
+
+ charger->online = 1;
+ schedule_work(&charger->work);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_vbus_connect);
+
+/*
+ * usb_vbus_disconnect - inform about VBUS disconnection
+ * @xceiv: the transceiver containing the charger
+ *
+ * Inform the charger that VBUS is disconnected. The charging will be
+ * stopped and the charger properties cleared.
+ */
+int usb_vbus_disconnect(struct usb_transceiver *xceiv)
+{
+ struct usb_charger *charger = &xceiv->charger;
+
+ if (!xceiv->has_charger)
+ return 0;
+
+ charger->online = 0;
+ charger->present = 0;
+ charger->max_current = 0;
+ charger->psy.type = POWER_SUPPLY_TYPE_USB;
+
+ if (charger->disconnect)
+ charger->disconnect(charger);
+
+ power_supply_changed(&charger->psy);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_vbus_disconnect);
diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h
new file mode 100644
index 0000000..f0f4ecf
--- /dev/null
+++ b/include/linux/usb/phy.h
@@ -0,0 +1,250 @@
+
+#ifndef __LINUX_USB_PHY_H
+#define __LINUX_USB_PHY_H
+
+#include <linux/power_supply.h>
+
+struct usb_transceiver;
+
+struct usb_xceiv_io_ops {
+ int (*read)(struct usb_transceiver *xceiv, u32 reg);
+ int (*write)(struct usb_transceiver *xceiv, u32 val, u32 reg);
+};
+
+enum usb_xceiv_interface {
+ USB_XCEIV_UNDEF,
+ USB_XCEIV_UTMI,
+ USB_XCEIV_UTMIPLUS,
+ USB_XCEIV_ULPI,
+ USB_XCEIV_SIE,
+};
+
+enum battery_charging_spec {
+ BATTERY_CHARGING_SPEC_NONE = 0,
+ BATTERY_CHARGING_SPEC_UNKNOWN,
+ BATTERY_CHARGING_SPEC_1_0,
+ BATTERY_CHARGING_SPEC_1_1,
+ BATTERY_CHARGING_SPEC_1_2,
+};
+
+struct usb_charger {
+ struct device *dev;
+ struct power_supply psy;
+ struct work_struct work;
+ struct mutex lock;
+
+ /* Compliant with Battery Charging Specification version (if any) */
+ enum battery_charging_spec bc;
+
+ /* properties */
+ unsigned present:1;
+ unsigned online:1;
+ unsigned max_current;
+
+ int (*connect)(struct usb_charger *charger);
+ int (*disconnect)(struct usb_charger *charger);
+ int (*set_power)(struct usb_charger *charger, unsigned mA);
+
+ int (*detect)(struct usb_charger *charger);
+ int (*get_type)(struct usb_charger *charger);
+ /*void (*detect_aca)(struct usb_charger *charger);*/
+};
+
+struct usb_transceiver {
+ struct device *dev;
+ /* the device name of related usb controller */
+ char *recv_name;
+
+ struct list_head list;
+ struct usb_charger charger;
+ unsigned has_charger:1;
+
+ unsigned long link_data;
+ unsigned int flags;
+
+ /*
+ * For hw platforms where the transceiver needs to take care
+ * of some of the OTG protocols.
+ */
+ struct otg *otg;
+
+ enum usb_xceiv_interface interface;
+
+ struct usb_xceiv_io_ops *io_ops;
+ void __iomem *io_priv;
+
+ /* Initialize/shutdown the USB transceiver */
+ int (*init)(struct usb_transceiver *xceiv);
+ int (*shutdown)(struct usb_transceiver *xceiv);
+
+ /* set transceiver into suspend mode */
+ int (*set_suspend)(struct usb_transceiver *xceiv,
+ int suspend);
+ /* effective for A-peripheral, ignored for B devices */
+ int (*set_vbus)(struct usb_transceiver *xceiv,
+ bool enabled);
+
+ /* Called by the PHY utility code when it's safe to connect */
+ void (*link_connect)(unsigned long data);
+
+ /* UTMI+ OTG control functions */
+ int (*id_pullup)(struct usb_transceiver *xceiv, int on);
+ int (*dp_pulldown)(struct usb_transceiver *xceiv, int on);
+ int (*dm_pulldown)(struct usb_transceiver *xceiv, int on);
+ int (*dischrg_vbus)(struct usb_transceiver *xceiv, int on);
+ int (*chrg_vbus)(struct usb_transceiver *xceiv, int on);
+ int (*drv_vbus)(struct usb_transceiver *xceiv, int on);
+};
+
+/* helpers for direct access thru low-level io interface */
+static inline int xceiv_io_read(struct usb_transceiver *xceiv, u32 reg)
+{
+ if (xceiv->io_ops && xceiv->io_ops->read)
+ return xceiv->io_ops->read(xceiv, reg);
+
+ return -EINVAL;
+}
+
+static inline int xceiv_io_write(struct usb_transceiver *xceiv, u32 val, u32 reg)
+{
+ if (xceiv->io_ops && xceiv->io_ops->write)
+ return xceiv->io_ops->write(xceiv, val, reg);
+
+ return -EINVAL;
+}
+/* Prototypes */
+extern void usb_register_transceiver(struct usb_transceiver *x);
+extern void usb_unregister_transceiver(struct usb_transceiver *xceiv);
+extern struct usb_transceiver *usb_get_transceiver(const char *name);
+extern void usb_put_transceiver(struct usb_transceiver *xceiv);
+
+extern int usb_set_power(struct usb_transceiver *xceiv, unsigned mA);
+extern int usb_vbus_connect(struct usb_transceiver *xceiv);
+extern int usb_vbus_disconnect(struct usb_transceiver *xceiv);
+
+/* Helpers for USB transceiver initialization and powering */
+
+/*
+ * ULPI transceivers will need ULPI access from the controller. When the
+ * controller is initialized, the controller driver will fill the
+ * usb_xceiv_io_ops and call this.
+ */
+static inline int
+usb_transceiver_init(struct usb_transceiver *xceiv)
+{
+ if (xceiv->init)
+ return xceiv->init(xceiv);
+ return 0;
+}
+
+/*
+ * This is a wrapper that the controller driver can use to deliver all
+ * the required variables to the transceiver.
+ */
+static inline int
+usb_start_transceiver(struct usb_transceiver *xceiv,
+ struct usb_xceiv_io_ops *io_ops,
+ void __iomem *io_priv,
+ void (*link_connect)(unsigned long),
+ unsigned long link_data)
+{
+ xceiv->io_ops = io_ops;
+ xceiv->io_priv = io_priv;
+ xceiv->link_connect = link_connect;
+ xceiv->link_data = link_data;
+
+ if (xceiv->init)
+ return xceiv->init(xceiv);
+
+ return 0;
+}
+
+/*
+ * REVISIT With ULPI transceivers, the controller should still be able to allow
+ * ULPI reads and writes before calling this.
+ */
+static inline int
+usb_transceiver_shutdown(struct usb_transceiver *xceiv)
+{
+ if (xceiv->shutdown)
+ return xceiv->shutdown(xceiv);
+ return 0;
+}
+
+static inline int
+usb_transceiver_set_suspend(struct usb_transceiver *xceiv, int suspend)
+{
+ if (xceiv->set_suspend)
+ return xceiv->set_suspend(xceiv, suspend);
+ return 0;
+}
+
+/* Helpers for OTG capable transceivers */
+
+/*
+ * xceiv_to_otg - return struct otg member
+ * @xceiv: USB transceiver
+ *
+ * Simple helper that can be used with other drivers that need to
+ * access the otg structure. One case would be separate host
+ * controller drivers. If the device controller driver holds the
+ * struct otg, the struct usb_transceiver can be used to deliver it to
+ * the host controller drivers.
+ */
+static inline struct otg
+*xceiv_to_otg(struct usb_transceiver *xceiv)
+{
+ if (xceiv && xceiv->otg)
+ return xceiv->otg;
+ return NULL;
+}
+
+static inline int
+usb_phy_id_pullup(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->id_pullup)
+ return xceiv->id_pullup(xceiv, on);
+ return -ENOTSUPP;
+}
+
+static inline int
+usb_phy_dp_pulldown(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->dp_pulldown)
+ return xceiv->dp_pulldown(xceiv, on);
+ return -ENOTSUPP;
+}
+
+static inline int
+usb_phy_dm_pulldown(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->dm_pulldown)
+ return xceiv->dm_pulldown(xceiv, on);
+ return -ENOTSUPP;
+}
+
+static inline int
+usb_phy_dischrg_vbus(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->dischrg_vbus)
+ return xceiv->dischrg_vbus(xceiv, on);
+ return -ENOTSUPP;
+}
+
+static inline int
+usb_phy_chrg_vbus(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->chrg_vbus)
+ return xceiv->chrg_vbus(xceiv, on);
+ return -ENOTSUPP;
+}
+
+static inline int
+usb_phy_drv_vbus(struct usb_transceiver *xceiv, int on)
+{
+ if (xceiv->drv_vbus)
+ return xceiv->drv_vbus(xceiv, on);
+ return -ENOTSUPP;
+}
+
+#endif /* __LINUX_USB_PHY_H */
--
1.6.3.3
More information about the linux-arm-kernel
mailing list