[RFC PATH 1/3] phy: add USB ULPI abstraction layer

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



ULPI PHY is an USB2 PHY that is accessed from the USB
controller. ULPI PHYs allow discovery based on vendor and
product ids which allows binding the PHY to a driver.

For USB controllers that are enumerated from buses such as
PCI, that do not provide any information about the PHY, ULPI
abstraction layer allows runtime detection. This makes it
possible to take advantage of vendor specific functions of
the PHYs with product specific drivers without the need for
platform or device specific quirks.

Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
---
 drivers/phy/Kconfig       |   2 +
 drivers/phy/Makefile      |   1 +
 drivers/phy/ulpi/Kconfig  |  11 ++
 drivers/phy/ulpi/Makefile |   1 +
 drivers/phy/ulpi/ulpi.c   | 273 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phy/ulpi.h  | 105 ++++++++++++++++++
 6 files changed, 393 insertions(+)
 create mode 100644 drivers/phy/ulpi/Kconfig
 create mode 100644 drivers/phy/ulpi/Makefile
 create mode 100644 drivers/phy/ulpi/ulpi.c
 create mode 100644 include/linux/phy/ulpi.h

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index a344f3d..6c03824 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -51,4 +51,6 @@ config PHY_EXYNOS_DP_VIDEO
 	help
 	  Support for Display Port PHY found on Samsung EXYNOS SoCs.
 
+source "drivers/phy/ulpi/Kconfig"
+
 endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index d0caae9..d6af605 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO)	+= phy-exynos-dp-video.o
 obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO)	+= phy-exynos-mipi-video.o
 obj-$(CONFIG_OMAP_USB2)			+= phy-omap-usb2.o
 obj-$(CONFIG_TWL4030_USB)		+= phy-twl4030-usb.o
+obj-$(CONFIG_ULPI_PHY)			+= ulpi/
diff --git a/drivers/phy/ulpi/Kconfig b/drivers/phy/ulpi/Kconfig
new file mode 100644
index 0000000..3211aaa
--- /dev/null
+++ b/drivers/phy/ulpi/Kconfig
@@ -0,0 +1,11 @@
+
+config ULPI_PHY
+	tristate "USB ULPI PHY interface support"
+	depends on USB || USB_GADGET
+	select GENERIC_PHY
+	help
+	  Say yes if you have ULPI PHY attached to your USB controller. If no
+	  vendor modules are selected, the driver will act as NOP PHY driver.
+
+	  If unsure, say Y.
+
diff --git a/drivers/phy/ulpi/Makefile b/drivers/phy/ulpi/Makefile
new file mode 100644
index 0000000..7ed0895
--- /dev/null
+++ b/drivers/phy/ulpi/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_ULPI_PHY)		+= ulpi.o
diff --git a/drivers/phy/ulpi/ulpi.c b/drivers/phy/ulpi/ulpi.c
new file mode 100644
index 0000000..7aa2f5d
--- /dev/null
+++ b/drivers/phy/ulpi/ulpi.c
@@ -0,0 +1,273 @@
+/**
+ * ulpi.c - USB ULPI PHY abstraction module
+ *
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+ *
+ * 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/phy/ulpi.h>
+#include <linux/phy/phy.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+/* -------------------------------------------------------------------------- */
+
+static struct phy_consumer phy_consumer[] = {
+	{ .port = ULPI_PORT_NAME, },
+};
+
+static struct phy_init_data phy_data = {
+	.num_consumers = 1,
+	.consumers = phy_consumer,
+};
+
+static int ulpi_power_on(struct phy *phy)
+{
+	struct ulpi_dev *ulpi = phy_get_drvdata(phy);
+	struct ulpi_driver *drv = to_ulpi_driver(ulpi->dev.driver);
+
+	if (drv && drv->phy_ops && drv->phy_ops->power_on)
+		return drv->phy_ops->power_on(phy);
+
+	return 0;
+}
+
+static int ulpi_power_off(struct phy *phy)
+{
+	struct ulpi_dev *ulpi = phy_get_drvdata(phy);
+	struct ulpi_driver *drv = to_ulpi_driver(ulpi->dev.driver);
+
+	if (drv && drv->phy_ops && drv->phy_ops->power_off)
+		return drv->phy_ops->power_off(phy);
+
+	return 0;
+}
+
+static struct phy_ops phy_ops = {
+	.owner = THIS_MODULE,
+	.power_on = ulpi_power_on,
+	.power_off = ulpi_power_off,
+};
+
+/* -------------------------------------------------------------------------- */
+
+static int ulpi_match(struct device *dev, struct device_driver *driver)
+{
+	struct ulpi_driver *drv = to_ulpi_driver(driver);
+	struct ulpi_dev *ulpi = to_ulpi_dev(dev);
+	const struct ulpi_device_id *id;
+
+	for (id = drv->id_table; id->vendor; id++)
+		if (id->vendor == ulpi->id.vendor &&
+		    id->product == ulpi->id.product)
+			return 1;
+
+	return 0;
+}
+
+static int ulpi_probe(struct device *dev)
+{
+	struct ulpi_driver *drv = to_ulpi_driver(dev->driver);
+
+	return drv->probe(to_ulpi_dev(dev));
+}
+
+static int ulpi_remove(struct device *dev)
+{
+	struct ulpi_driver *drv = to_ulpi_driver(dev->driver);
+
+	if (drv->remove)
+		drv->remove(to_ulpi_dev(dev));
+
+	return 0;
+}
+
+static struct bus_type ulpi_bus = {
+	.name = "ulpi",
+	.match = ulpi_match,
+	.probe = ulpi_probe,
+	.remove = ulpi_remove,
+};
+
+/**
+ * ulpi_register_driver - register driver with the bus
+ * @drv: the driver
+ */
+int ulpi_register_driver(struct ulpi_driver *drv)
+{
+	if (!drv->probe)
+		return -EINVAL;
+
+	drv->driver.bus = &ulpi_bus;
+
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(ulpi_register_driver);
+
+void ulpi_unregister_driver(struct ulpi_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(ulpi_unregister_driver);
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * ulpi_alloc_interface - allocate new ULPI interface
+ *
+ * Allows USB controller to create struct ulpi_interface without
+ * registering the interface immediately.
+ */
+struct ulpi_interface *ulpi_alloc_interface(void)
+{
+	struct ulpi_dev *ulpi;
+
+	ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
+	if (!ulpi)
+		return NULL;
+
+	return &ulpi->interface;
+}
+EXPORT_SYMBOL_GPL(ulpi_alloc_interface);
+
+#define interface_to_ulpi(c) container_of(c, struct ulpi_dev, interface)
+
+/**
+ * ulpi_register - register new ULPI device
+ * @dev: USB controller's device interface
+ * @interface: Controller which the device is connected
+ *
+ * Companion function to ulpi_alloc_interface. Creates device for the
+ * ULPI PHY attached to the interface, binds it to a driver and
+ * creates phy instance for it.
+ */
+int ulpi_register(struct device *dev, struct ulpi_interface *interface)
+{
+	struct ulpi_dev *ulpi = interface_to_ulpi(interface);
+	int ret;
+
+	/* Test the interface */
+	ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa);
+	if (ret < 0)
+		goto err;
+
+	ret = ulpi_read(ulpi, ULPI_SCRATCH);
+	if (ret < 0)
+		goto err;
+
+	if (ret != 0xaa) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ulpi->id.vendor = ulpi_read(ulpi, ULPI_VENDOR_ID_LOW);
+	ulpi->id.vendor |= ulpi_read(ulpi, ULPI_VENDOR_ID_HIGH) << 8;
+
+	ulpi->id.product = ulpi_read(ulpi, ULPI_PRODUCT_ID_LOW);
+	ulpi->id.product |= ulpi_read(ulpi, ULPI_PRODUCT_ID_HIGH) << 8;
+
+	ulpi->dev.parent = dev;
+	ulpi->dev.bus = &ulpi_bus;
+	dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev));
+
+	ret = device_register(&ulpi->dev);
+	if (ret)
+		goto err;
+
+	/**
+	 * Create phy for the device.
+	 * The controller is always the consumer
+	 */
+	phy_consumer[0].dev_name = dev_name(dev);
+
+	ulpi->phy = phy_create(&ulpi->dev, &phy_ops, &phy_data);
+	if (IS_ERR(ulpi->phy)) {
+		ret = PTR_ERR(ulpi->phy);
+		device_unregister(&ulpi->dev);
+		goto err;
+	}
+
+	phy_set_drvdata(ulpi->phy, ulpi);
+
+	dev_dbg(&ulpi->dev, "registered vendor %04x, product %04x\n",
+		ulpi->id.vendor, ulpi->id.product);
+
+	return 0;
+err:
+	kfree(ulpi);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(ulpi_register);
+
+void ulpi_unregister(struct ulpi_interface *interface)
+{
+	struct ulpi_dev *ulpi = interface_to_ulpi(interface);
+
+	device_unregister(&ulpi->dev);
+	phy_destroy(ulpi->phy);
+	kfree(ulpi);
+}
+EXPORT_SYMBOL_GPL(ulpi_unregister);
+
+/**
+ * ulpi_new_interface - instantiate new ULPI device
+ * @dev: USB controller's device interface
+ * @read: read operation for ULPI register access
+ * @write: write operation for ULPI register access
+ * @data: USB controller's private data
+ *
+ * Allocates and registers a ULPI device. Called from USB controller
+ * that provides the ULPI interface. Returns struct ulpi_interface
+ * instance of the new ULPI device.
+ */
+struct ulpi_interface
+*ulpi_new_interface(struct device *dev,
+		    int (*read)(struct ulpi_interface *, u8),
+		    int (*write)(struct ulpi_interface *, u8, u8),
+		    void *data)
+{
+	struct ulpi_interface *interface;
+	int ret;
+
+	interface = ulpi_alloc_interface();
+	if (!interface)
+		return ERR_PTR(-ENOMEM);
+
+	interface->read = read;
+	interface->write = write;
+	interface->priv = data;
+
+	ret = ulpi_register(dev, interface);
+	if (ret) {
+		kfree(interface_to_ulpi(interface));
+		return ERR_PTR(ret);
+	}
+
+	return interface;
+}
+EXPORT_SYMBOL_GPL(ulpi_new_interface);
+
+/* -------------------------------------------------------------------------- */
+
+static int __init ulpi_init(void)
+{
+	return bus_register(&ulpi_bus);
+}
+module_init(ulpi_init);
+
+static void __exit ulpi_exit(void)
+{
+	bus_unregister(&ulpi_bus);
+}
+module_exit(ulpi_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("USB ULPI PHY abstraction module");
diff --git a/include/linux/phy/ulpi.h b/include/linux/phy/ulpi.h
new file mode 100644
index 0000000..9ff15ee
--- /dev/null
+++ b/include/linux/phy/ulpi.h
@@ -0,0 +1,105 @@
+/**
+ * ulpi.h - USB ULPI PHY Header
+ *
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
+ *
+ * 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.
+ */
+
+#ifndef __DRIVERS_PHY_ULPI_H
+#define __DRIVERS_PHY_ULPI_H
+
+#include <linux/usb/ulpi.h>
+#include <linux/types.h>
+
+#define ULPI_PORT_NAME "usb2-phy"
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * struct ulpi_interface - describes ULPI interface to ULPI PHY device
+ * @read: read opearation for ULPI register access
+ * @write: write opearation for ULPI register access
+ * @priv: controller's private data
+ */
+struct ulpi_interface {
+	int (*read)(struct ulpi_interface *interface, u8 addr);
+	int (*write)(struct ulpi_interface *interface, u8 addr, u8 val);
+	void *priv;
+};
+
+struct ulpi_interface *ulpi_alloc_interface(void);
+int ulpi_register(struct device *, struct ulpi_interface *);
+void ulpi_unregister(struct ulpi_interface *);
+struct ulpi_interface *ulpi_new_interface(struct device *dev,
+		    int (*read)(struct ulpi_interface *, u8),
+		    int (*write)(struct ulpi_interface *, u8, u8),
+		    void *data);
+
+/* -------------------------------------------------------------------------- */
+
+struct ulpi_device_id {
+	__u16 vendor;
+	__u16 product;
+};
+
+/**
+ * struct ulpi_dev - describes ULPI PHY device
+ * @interface: I/O access
+ * @id: vendor and product ids for ULPI device
+ * @dev: device interface to this driver
+ * @phy: phy instance of this device
+ * @priv: for drivers private use
+ */
+struct ulpi_dev {
+	struct ulpi_interface interface;
+	struct ulpi_device_id id;
+	struct device dev;
+	struct phy *phy;
+	void *priv;
+};
+
+#define to_ulpi_dev(d) container_of(d, struct ulpi_dev, dev)
+#define phy_to_ulpi_drvdata(p) dev_get_drvdata(phy_get_drvdata(p))
+
+/**
+ * struct ulpi_driver - describes a ULPI PHY driver
+ * @id_table: array of devices supported by this driver
+ * @phy_ops: the driver can provide phy operations
+ * @probe: binds this driver to ULPI device
+ * @remove: unbinds this driver from ULPI device
+ * @driver: the name and owner members must be initialized by the drivers
+ */
+struct ulpi_driver {
+	const struct ulpi_device_id *id_table;
+	const struct phy_ops *phy_ops;
+	int (*probe)(struct ulpi_dev *ulpi);
+	void (*remove)(struct ulpi_dev *ulpi);
+	struct device_driver driver;
+};
+
+#define to_ulpi_driver(d) container_of(d, struct ulpi_driver, driver)
+
+int ulpi_register_driver(struct ulpi_driver *drv);
+void ulpi_unregister_driver(struct ulpi_driver *drv);
+
+#define module_ulpi_driver(__ulpi_driver) \
+	module_driver(__ulpi_driver, ulpi_register_driver, \
+		      ulpi_unregister_driver)
+
+static inline int ulpi_read(struct ulpi_dev *ulpi, u8 addr)
+{
+	return ulpi->interface.read(&ulpi->interface, addr);
+}
+
+static inline int ulpi_write(struct ulpi_dev *ulpi, u8 addr, u8 val)
+{
+	return ulpi->interface.write(&ulpi->interface, addr, val);
+}
+
+#endif /* __DRIVERS_PHY_ULPI_H */
-- 
1.8.4.4

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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux