linux/drivers/usb/core/phy.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * A wrapper for multiple PHYs which passes all phy_* function calls to
   4 * multiple (actual) PHY devices. This is comes handy when initializing
   5 * all PHYs on a HCD and to keep them all in the same state.
   6 *
   7 * Copyright (C) 2018 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
   8 */
   9
  10#include <linux/device.h>
  11#include <linux/list.h>
  12#include <linux/phy/phy.h>
  13#include <linux/of.h>
  14
  15#include "phy.h"
  16
  17struct usb_phy_roothub {
  18        struct phy              *phy;
  19        struct list_head        list;
  20};
  21
  22static int usb_phy_roothub_add_phy(struct device *dev, int index,
  23                                   struct list_head *list)
  24{
  25        struct usb_phy_roothub *roothub_entry;
  26        struct phy *phy = devm_of_phy_get_by_index(dev, dev->of_node, index);
  27
  28        if (IS_ERR_OR_NULL(phy)) {
  29                if (!phy || PTR_ERR(phy) == -ENODEV)
  30                        return 0;
  31                else
  32                        return PTR_ERR(phy);
  33        }
  34
  35        roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
  36        if (!roothub_entry)
  37                return -ENOMEM;
  38
  39        INIT_LIST_HEAD(&roothub_entry->list);
  40
  41        roothub_entry->phy = phy;
  42
  43        list_add_tail(&roothub_entry->list, list);
  44
  45        return 0;
  46}
  47
  48struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev)
  49{
  50        struct usb_phy_roothub *phy_roothub;
  51        int i, num_phys, err;
  52
  53        if (!IS_ENABLED(CONFIG_GENERIC_PHY))
  54                return NULL;
  55
  56        num_phys = of_count_phandle_with_args(dev->of_node, "phys",
  57                                              "#phy-cells");
  58        if (num_phys <= 0)
  59                return NULL;
  60
  61        phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
  62        if (!phy_roothub)
  63                return ERR_PTR(-ENOMEM);
  64
  65        INIT_LIST_HEAD(&phy_roothub->list);
  66
  67        for (i = 0; i < num_phys; i++) {
  68                err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list);
  69                if (err)
  70                        return ERR_PTR(err);
  71        }
  72
  73        return phy_roothub;
  74}
  75EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc);
  76
  77int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub)
  78{
  79        struct usb_phy_roothub *roothub_entry;
  80        struct list_head *head;
  81        int err;
  82
  83        if (!phy_roothub)
  84                return 0;
  85
  86        head = &phy_roothub->list;
  87
  88        list_for_each_entry(roothub_entry, head, list) {
  89                err = phy_init(roothub_entry->phy);
  90                if (err)
  91                        goto err_exit_phys;
  92        }
  93
  94        return 0;
  95
  96err_exit_phys:
  97        list_for_each_entry_continue_reverse(roothub_entry, head, list)
  98                phy_exit(roothub_entry->phy);
  99
 100        return err;
 101}
 102EXPORT_SYMBOL_GPL(usb_phy_roothub_init);
 103
 104int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub)
 105{
 106        struct usb_phy_roothub *roothub_entry;
 107        struct list_head *head;
 108        int err, ret = 0;
 109
 110        if (!phy_roothub)
 111                return 0;
 112
 113        head = &phy_roothub->list;
 114
 115        list_for_each_entry(roothub_entry, head, list) {
 116                err = phy_exit(roothub_entry->phy);
 117                if (err)
 118                        ret = err;
 119        }
 120
 121        return ret;
 122}
 123EXPORT_SYMBOL_GPL(usb_phy_roothub_exit);
 124
 125int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub)
 126{
 127        struct usb_phy_roothub *roothub_entry;
 128        struct list_head *head;
 129        int err;
 130
 131        if (!phy_roothub)
 132                return 0;
 133
 134        head = &phy_roothub->list;
 135
 136        list_for_each_entry(roothub_entry, head, list) {
 137                err = phy_power_on(roothub_entry->phy);
 138                if (err)
 139                        goto err_out;
 140        }
 141
 142        return 0;
 143
 144err_out:
 145        list_for_each_entry_continue_reverse(roothub_entry, head, list)
 146                phy_power_off(roothub_entry->phy);
 147
 148        return err;
 149}
 150EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on);
 151
 152void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub)
 153{
 154        struct usb_phy_roothub *roothub_entry;
 155
 156        if (!phy_roothub)
 157                return;
 158
 159        list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list)
 160                phy_power_off(roothub_entry->phy);
 161}
 162EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off);
 163
 164int usb_phy_roothub_suspend(struct device *controller_dev,
 165                            struct usb_phy_roothub *phy_roothub)
 166{
 167        usb_phy_roothub_power_off(phy_roothub);
 168
 169        /* keep the PHYs initialized so the device can wake up the system */
 170        if (device_may_wakeup(controller_dev))
 171                return 0;
 172
 173        return usb_phy_roothub_exit(phy_roothub);
 174}
 175EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend);
 176
 177int usb_phy_roothub_resume(struct device *controller_dev,
 178                           struct usb_phy_roothub *phy_roothub)
 179{
 180        int err;
 181
 182        /* if the device can't wake up the system _exit was called */
 183        if (!device_may_wakeup(controller_dev)) {
 184                err = usb_phy_roothub_init(phy_roothub);
 185                if (err)
 186                        return err;
 187        }
 188
 189        err = usb_phy_roothub_power_on(phy_roothub);
 190
 191        /* undo _init if _power_on failed */
 192        if (err && !device_may_wakeup(controller_dev))
 193                usb_phy_roothub_exit(phy_roothub);
 194
 195        return err;
 196}
 197EXPORT_SYMBOL_GPL(usb_phy_roothub_resume);
 198