forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
usb: Add USB repeater generic framework
With more SoCs moving towards eUSB2, such platforms will have to use a USB 2.0 compliance repeater. This repeater HW-wise usually deals with level shifting, but depending on the implementation it can do much more. So add a ganeric USB framework that would allow either a generic PHY or some USB host controller to control and get a repeater from a devicetree phandle. This framework will further be used by platform specific drivers to register the repeater and implement platform specific ops. Signed-off-by: Abel Vesa <abel.vesa@linaro.org>
- Loading branch information
1 parent
dd65a24
commit 19c3fed
Showing
6 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# SPDX-License-Identifier: GPL-2.0 | ||
|
||
config USB_REPEATER | ||
tristate "USB Repeater Framework" | ||
help | ||
Enable this to allow the USB PHY drivers and the connected repeater | ||
driver to bind together and to provide the USB driver ways to perform | ||
set of operations with repeater for USB2 functionality using PHY and | ||
controller driver. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# SPDX-License-Identifier: GPL-2.0 | ||
# | ||
# Makefile for USB repeater drivers | ||
# | ||
|
||
obj-$(CONFIG_USB_REPEATER) += repeater.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. | ||
* Copyright (c) 2022, Linaro Limited | ||
*/ | ||
|
||
#include <linux/device.h> | ||
#include <linux/list.h> | ||
#include <linux/module.h> | ||
#include <linux/of.h> | ||
#include <linux/overflow.h> | ||
|
||
#include <linux/usb/repeater.h> | ||
|
||
static LIST_HEAD(usb_repeater_list); | ||
static DEFINE_SPINLOCK(usb_repeater_lock); | ||
|
||
/** | ||
* usb_put_repeater - release the USB repeater | ||
* @rptr: the repeater returned by usb_get_repeater() | ||
* | ||
* Releases a refcount the caller received from usb_get_repeater(). | ||
* | ||
* For use by USB PHY drivers. | ||
*/ | ||
void usb_put_repeater(struct usb_repeater *rptr) | ||
{ | ||
if (rptr) { | ||
put_device(rptr->dev); | ||
if (rptr->dev->driver && rptr->dev->driver->owner) { | ||
struct module *owner = rptr->dev->driver->owner; | ||
|
||
module_put(owner); | ||
} | ||
} | ||
} | ||
EXPORT_SYMBOL_GPL(usb_put_repeater); | ||
|
||
static struct usb_repeater *of_usb_find_repeater(struct device_node *node) | ||
{ | ||
struct usb_repeater *rptr; | ||
|
||
if (!of_device_is_available(node)) | ||
return ERR_PTR(-ENODEV); | ||
|
||
list_for_each_entry(rptr, &usb_repeater_list, head) { | ||
if (node != rptr->dev->of_node) | ||
continue; | ||
|
||
return rptr; | ||
} | ||
|
||
return ERR_PTR(-EPROBE_DEFER); | ||
} | ||
|
||
static void devm_usb_repeater_release(struct device *dev, void *res) | ||
{ | ||
struct usb_repeater *rptr = *(struct usb_repeater **)res; | ||
|
||
usb_put_repeater(rptr); | ||
} | ||
|
||
/** | ||
* devm_usb_get_repeater_by_node - find the USB repeater by device_node | ||
* @dev: device that requests this USB repeater | ||
* @node: the device_node for the USB repeater device. | ||
* @nb: a notifier_block to register with the USB repeater. | ||
* | ||
* Returns the USB repeater driver associated with the given device_node, | ||
* after getting a refcount to it, -ENODEV if there is no such repeater or | ||
* -EPROBE_DEFER if the device is not yet loaded. While at that, it | ||
* also associates the device with | ||
* the repeater using devres. On driver detach, release function is invoked | ||
* on the devres data, then, devres data is freed. | ||
* | ||
* For use by peripheral drivers for devices related to a repeater, | ||
* such as a charger. | ||
*/ | ||
struct usb_repeater *devm_usb_get_repeater_by_node(struct device *dev, | ||
struct device_node *node, | ||
struct notifier_block *nb) | ||
{ | ||
struct usb_repeater *rptr = ERR_PTR(-ENOMEM); | ||
struct usb_repeater *rptr_devm; | ||
unsigned long flags; | ||
|
||
rptr_devm = devres_alloc(devm_usb_repeater_release, | ||
sizeof(*rptr_devm), GFP_KERNEL); | ||
if (!rptr_devm) { | ||
dev_dbg(dev, "failed to allocate memory for devres\n"); | ||
goto err0; | ||
} | ||
|
||
spin_lock_irqsave(&usb_repeater_lock, flags); | ||
|
||
rptr = of_usb_find_repeater(node); | ||
if (IS_ERR(rptr)) { | ||
devres_free(rptr_devm); | ||
goto err0; | ||
} | ||
|
||
if (!try_module_get(rptr->dev->driver->owner)) { | ||
rptr = ERR_PTR(-ENODEV); | ||
devres_free(rptr_devm); | ||
goto err0; | ||
} | ||
devres_add(dev, rptr_devm); | ||
|
||
get_device(rptr->dev); | ||
err0: | ||
spin_unlock_irqrestore(&usb_repeater_lock, flags); | ||
|
||
return rptr; | ||
} | ||
EXPORT_SYMBOL_GPL(devm_usb_get_repeater_by_node); | ||
|
||
/** | ||
* devm_usb_get_repeater_by_phandle - find the USB repeater by phandle | ||
* @dev: device that requests this repeater | ||
* @phandle: name of the property holding the repeater phandle value | ||
* @index: the index of the repeater | ||
* | ||
* Returns the USB repeater driver associated with the given phandle value, | ||
* after getting a refcount to it, -ENODEV if there is no such repeater or | ||
* -EPROBE_DEFER if there is a phandle to the repeater, but the device is | ||
* not yet loaded. While at that, it also associates the device with | ||
* the repeater using devres. On driver detach, release function is invoked | ||
* on the devres data, then, devres data is freed. | ||
* | ||
* For use by USB PHY drivers. | ||
*/ | ||
struct usb_repeater *devm_usb_get_repeater_by_phandle(struct device *dev, | ||
const char *phandle, u8 index) | ||
{ | ||
struct device_node *node; | ||
struct usb_repeater *rptr; | ||
|
||
if (!dev->of_node) { | ||
dev_dbg(dev, "device does not have a device node entry\n"); | ||
return ERR_PTR(-EINVAL); | ||
} | ||
|
||
node = of_parse_phandle(dev->of_node, phandle, index); | ||
if (!node) { | ||
dev_dbg(dev, "failed to get %s phandle in %pOF node\n", phandle, | ||
dev->of_node); | ||
return ERR_PTR(-ENODEV); | ||
} | ||
rptr = devm_usb_get_repeater_by_node(dev, node, NULL); | ||
of_node_put(node); | ||
return rptr; | ||
} | ||
EXPORT_SYMBOL_GPL(devm_usb_get_repeater_by_phandle); | ||
|
||
/** | ||
* usb_add_repeater_dev - declare the USB repeater | ||
* @rptr: the USB repeater to be used; or NULL | ||
* | ||
* This call is exclusively for use by repeater drivers to | ||
* register the device to allow the USB phy driver to control it | ||
* via repeater specific ops. | ||
*/ | ||
int usb_add_repeater_dev(struct usb_repeater *rptr) | ||
{ | ||
unsigned long flags; | ||
|
||
if (!rptr->dev) { | ||
dev_err(rptr->dev, "no device provided for repeater\n"); | ||
return -EINVAL; | ||
} | ||
|
||
spin_lock_irqsave(&usb_repeater_lock, flags); | ||
list_add_tail(&rptr->head, &usb_repeater_list); | ||
spin_unlock_irqrestore(&usb_repeater_lock, flags); | ||
|
||
return 0; | ||
} | ||
EXPORT_SYMBOL_GPL(usb_add_repeater_dev); | ||
|
||
/** | ||
* usb_remove_repeater - remove the USB repeater | ||
* @rptr: the USB Repeater to be removed; | ||
* | ||
* This reverts the effects of usb_add_repeater_dev | ||
*/ | ||
void usb_remove_repeater_dev(struct usb_repeater *rptr) | ||
{ | ||
unsigned long flags; | ||
|
||
spin_lock_irqsave(&usb_repeater_lock, flags); | ||
if (rptr) | ||
list_del(&rptr->head); | ||
spin_unlock_irqrestore(&usb_repeater_lock, flags); | ||
} | ||
EXPORT_SYMBOL_GPL(usb_remove_repeater_dev); | ||
|
||
MODULE_DESCRIPTION("USB Repeater Framework"); | ||
MODULE_LICENSE("GPL"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* SPDX-License-Identifier: GPL-2.0 */ | ||
/* | ||
* USB Repeater defines | ||
*/ | ||
|
||
#ifndef __LINUX_USB_REPEATER_H | ||
#define __LINUX_USB_REPEATER_H | ||
|
||
struct usb_repeater { | ||
struct device *dev; | ||
const char *label; | ||
unsigned int flags; | ||
|
||
struct list_head head; | ||
int (*reset)(struct usb_repeater *rptr, bool assert); | ||
int (*init)(struct usb_repeater *rptr); | ||
int (*power_on)(struct usb_repeater *rptr); | ||
int (*power_off)(struct usb_repeater *rptr); | ||
}; | ||
|
||
#if IS_ENABLED(CONFIG_USB_REPEATER) | ||
extern struct usb_repeater *devm_usb_get_repeater_by_phandle(struct device *dev, | ||
const char *phandle, u8 index); | ||
extern struct usb_repeater *devm_usb_get_repeater_by_node(struct device *dev, | ||
struct device_node *node, struct notifier_block *nb); | ||
extern int usb_add_repeater_dev(struct usb_repeater *rptr); | ||
extern void usb_remove_repeater_dev(struct usb_repeater *rptr); | ||
#else | ||
static inline struct usb_repeater *devm_usb_get_repeater_by_phandle(struct device *dev, | ||
const char *phandle, u8 index) | ||
{ | ||
return ERR_PTR(-ENXIO); | ||
} | ||
|
||
static inline struct usb_repeater *devm_usb_get_repeater_by_node(struct device *dev, | ||
struct device_node *node, struct notifier_block *nb) | ||
{ | ||
return ERR_PTR(-ENXIO); | ||
} | ||
|
||
static inline int usb_add_repeater_dev(struct usb_repeater *rptr) { return 0; } | ||
static inline void usb_remove_repeater_dev(struct usb_repeater *rptr) { } | ||
|
||
#endif | ||
|
||
static inline int usb_repeater_reset(struct usb_repeater *rptr, bool assert) | ||
{ | ||
if (rptr && rptr->reset != NULL) | ||
return rptr->reset(rptr, assert); | ||
else | ||
return 0; | ||
} | ||
|
||
static inline int usb_repeater_init(struct usb_repeater *rptr) | ||
{ | ||
if (rptr && rptr->init != NULL) | ||
return rptr->init(rptr); | ||
else | ||
return 0; | ||
} | ||
|
||
static inline int usb_repeater_power_on(struct usb_repeater *rptr) | ||
{ | ||
if (rptr && rptr->power_on != NULL) | ||
return rptr->power_on(rptr); | ||
else | ||
return 0; | ||
} | ||
|
||
static inline int usb_repeater_power_off(struct usb_repeater *rptr) | ||
{ | ||
if (rptr && rptr->power_off != NULL) | ||
return rptr->power_off(rptr); | ||
else | ||
return 0; | ||
} | ||
|
||
#endif /* __LINUX_USB_REPEATER_H */ |