Skip to content

Commit

Permalink
usb: add bus type for USB ULPI
Browse files Browse the repository at this point in the history
UTMI+ Low Pin Interface (ULPI) is a commonly used PHY
interface for USB 2.0. The ULPI specification describes a
standard set of registers which the vendors can extend for
their specific needs. ULPI PHYs provide often functions
such as charger detection and ADP sensing and probing.

There are two major issues that the bus type is meant to
tackle:

Firstly, ULPI registers are accessed from the controller.
The bus provides convenient method for the controller
drivers to share that access with the actual PHY drivers.

Secondly, there are already platforms that assume ULPI PHYs
are runtime detected, such as many Intel Baytrail based
platforms. They do not provide any kind of hardware
description for the ULPI PHYs like separate ACPI device
object that could be used to enumerate a device from.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Acked-by: David Cohen <david.a.cohen@linux.intel.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
  • Loading branch information
Heikki Krogerus authored and Felipe Balbi committed May 13, 2015
1 parent 3521a39 commit 289fcff
Show file tree
Hide file tree
Showing 11 changed files with 521 additions and 132 deletions.
7 changes: 7 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -10453,6 +10453,13 @@ S: Maintained
F: Documentation/video4linux/zr364xx.txt
F: drivers/media/usb/zr364xx/

ULPI BUS
M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
L: linux-usb@vger.kernel.org
S: Maintained
F: drivers/usb/common/ulpi.c
F: include/linux/ulpi/

USER-MODE LINUX (UML)
M: Jeff Dike <jdike@addtoit.com>
M: Richard Weinberger <richard@nod.at>
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/common/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ usb-common-y += common.o
usb-common-$(CONFIG_USB_LED_TRIG) += led.o

obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o
255 changes: 255 additions & 0 deletions drivers/usb/common/ulpi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/**
* ulpi.c - USB ULPI PHY bus
*
* Copyright (C) 2015 Intel Corporation
*
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

#include <linux/ulpi/interface.h>
#include <linux/ulpi/driver.h>
#include <linux/ulpi/regs.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/acpi.h>

/* -------------------------------------------------------------------------- */

int ulpi_read(struct ulpi *ulpi, u8 addr)
{
return ulpi->ops->read(ulpi->ops, addr);
}
EXPORT_SYMBOL_GPL(ulpi_read);

int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val)
{
return ulpi->ops->write(ulpi->ops, addr, val);
}
EXPORT_SYMBOL_GPL(ulpi_write);

/* -------------------------------------------------------------------------- */

static int ulpi_match(struct device *dev, struct device_driver *driver)
{
struct ulpi_driver *drv = to_ulpi_driver(driver);
struct ulpi *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_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct ulpi *ulpi = to_ulpi_dev(dev);

if (add_uevent_var(env, "MODALIAS=ulpi:v%04xp%04x",
ulpi->id.vendor, ulpi->id.product))
return -ENOMEM;
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,
.uevent = ulpi_uevent,
.probe = ulpi_probe,
.remove = ulpi_remove,
};

/* -------------------------------------------------------------------------- */

static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ulpi *ulpi = to_ulpi_dev(dev);

return sprintf(buf, "ulpi:v%04xp%04x\n",
ulpi->id.vendor, ulpi->id.product);
}
static DEVICE_ATTR_RO(modalias);

static struct attribute *ulpi_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL
};

static struct attribute_group ulpi_dev_attr_group = {
.attrs = ulpi_dev_attrs,
};

static const struct attribute_group *ulpi_dev_attr_groups[] = {
&ulpi_dev_attr_group,
NULL
};

static void ulpi_dev_release(struct device *dev)
{
kfree(to_ulpi_dev(dev));
}

static struct device_type ulpi_dev_type = {
.name = "ulpi_device",
.groups = ulpi_dev_attr_groups,
.release = ulpi_dev_release,
};

/* -------------------------------------------------------------------------- */

/**
* ulpi_register_driver - register a driver with the ULPI bus
* @drv: driver being registered
*
* Registers a driver with the ULPI bus.
*/
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);

/**
* ulpi_unregister_driver - unregister a driver with the ULPI bus
* @drv: driver to unregister
*
* Unregisters a driver with the ULPI bus.
*/
void ulpi_unregister_driver(struct ulpi_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(ulpi_unregister_driver);

/* -------------------------------------------------------------------------- */

static int ulpi_register(struct device *dev, struct ulpi *ulpi)
{
int ret;

/* Test the interface */
ret = ulpi_write(ulpi, ULPI_SCRATCH, 0xaa);
if (ret < 0)
return ret;

ret = ulpi_read(ulpi, ULPI_SCRATCH);
if (ret < 0)
return ret;

if (ret != 0xaa)
return -ENODEV;

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;
ulpi->dev.type = &ulpi_dev_type;
dev_set_name(&ulpi->dev, "%s.ulpi", dev_name(dev));

ACPI_COMPANION_SET(&ulpi->dev, ACPI_COMPANION(dev));

request_module("ulpi:v%04xp%04x", ulpi->id.vendor, ulpi->id.product);

ret = device_register(&ulpi->dev);
if (ret)
return ret;

dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
ulpi->id.vendor, ulpi->id.product);

return 0;
}

/**
* ulpi_register_interface - instantiate new ULPI device
* @dev: USB controller's device interface
* @ops: ULPI register access
*
* Allocates and registers a ULPI device and an interface for it. Called from
* the USB controller that provides the ULPI interface.
*/
struct ulpi *ulpi_register_interface(struct device *dev, struct ulpi_ops *ops)
{
struct ulpi *ulpi;
int ret;

ulpi = kzalloc(sizeof(*ulpi), GFP_KERNEL);
if (!ulpi)
return ERR_PTR(-ENOMEM);

ulpi->ops = ops;
ops->dev = dev;

ret = ulpi_register(dev, ulpi);
if (ret) {
kfree(ulpi);
return ERR_PTR(ret);
}

return ulpi;
}
EXPORT_SYMBOL_GPL(ulpi_register_interface);

/**
* ulpi_unregister_interface - unregister ULPI interface
* @intrf: struct ulpi_interface
*
* Unregisters a ULPI device and it's interface that was created with
* ulpi_create_interface().
*/
void ulpi_unregister_interface(struct ulpi *ulpi)
{
device_unregister(&ulpi->dev);
}
EXPORT_SYMBOL_GPL(ulpi_unregister_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 v2");
MODULE_DESCRIPTION("USB ULPI PHY bus");
20 changes: 20 additions & 0 deletions drivers/usb/core/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ config USB_OTG_FSM
Implements OTG Finite State Machine as specified in On-The-Go
and Embedded Host Supplement to the USB Revision 2.0 Specification.

config USB_ULPI_BUS
tristate "USB ULPI PHY interface support"
depends on USB_SUPPORT
help
UTMI+ Low Pin Interface (ULPI) is specification for a commonly used
USB 2.0 PHY interface. The ULPI specification defines a standard set
of registers that can be used to detect the vendor and product which
allows ULPI to be handled as a bus. This module is the driver for that
bus.

The ULPI interfaces (the buses) are registered by the drivers for USB
controllers which support ULPI register access and have ULPI PHY
attached to them. The ULPI PHY drivers themselves are normal PHY
drivers.

ULPI PHYs provide often functions such as ADP sensing/probing (OTG
protocol) and USB charger detection.

To compile this driver as a module, choose M here: the module will
be called ulpi.
6 changes: 6 additions & 0 deletions include/linux/mod_devicetable.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,4 +629,10 @@ struct mcb_device_id {
kernel_ulong_t driver_data;
};

struct ulpi_device_id {
__u16 vendor;
__u16 product;
kernel_ulong_t driver_data;
};

#endif /* LINUX_MOD_DEVICETABLE_H */
60 changes: 60 additions & 0 deletions include/linux/ulpi/driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef __LINUX_ULPI_DRIVER_H
#define __LINUX_ULPI_DRIVER_H

#include <linux/mod_devicetable.h>

#include <linux/device.h>

struct ulpi_ops;

/**
* struct ulpi - describes ULPI PHY device
* @id: vendor and product ids for ULPI device
* @ops: I/O access
* @dev: device interface
*/
struct ulpi {
struct ulpi_device_id id;
struct ulpi_ops *ops;
struct device dev;
};

#define to_ulpi_dev(d) container_of(d, struct ulpi, dev)

static inline void ulpi_set_drvdata(struct ulpi *ulpi, void *data)
{
dev_set_drvdata(&ulpi->dev, data);
}

static inline void *ulpi_get_drvdata(struct ulpi *ulpi)
{
return dev_get_drvdata(&ulpi->dev);
}

/**
* struct ulpi_driver - describes a ULPI PHY driver
* @id_table: array of device identifiers supported by this driver
* @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;
int (*probe)(struct ulpi *ulpi);
void (*remove)(struct ulpi *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)

int ulpi_read(struct ulpi *ulpi, u8 addr);
int ulpi_write(struct ulpi *ulpi, u8 addr, u8 val);

#endif /* __LINUX_ULPI_DRIVER_H */
23 changes: 23 additions & 0 deletions include/linux/ulpi/interface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef __LINUX_ULPI_INTERFACE_H
#define __LINUX_ULPI_INTERFACE_H

#include <linux/types.h>

struct ulpi;

/**
* struct ulpi_ops - ULPI register access
* @dev: the interface provider
* @read: read operation for ULPI register access
* @write: write operation for ULPI register access
*/
struct ulpi_ops {
struct device *dev;
int (*read)(struct ulpi_ops *ops, u8 addr);
int (*write)(struct ulpi_ops *ops, u8 addr, u8 val);
};

struct ulpi *ulpi_register_interface(struct device *, struct ulpi_ops *);
void ulpi_unregister_interface(struct ulpi *);

#endif /* __LINUX_ULPI_INTERFACE_H */
Loading

0 comments on commit 289fcff

Please sign in to comment.