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

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

 



Hi,

On Thursday 28 November 2013 09:29 PM, Heikki Krogerus wrote:
> 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, },

We had to introduce the entire phy_consumer stuff to support legacy pdata. Not
sure if it is a good idea to use it here.
> +};
> +
> +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);

Should this be exported?
> +
> +#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);

Instead of creating ulpi device and phy device here, phy device (phy_create)
should be created when the driver is probed (in this case when NXP ISP170X is
probed).
> +	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);

It's difficult to follow if you have allocation in one function and free it in
the error path of some other function. This can be improved.

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