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;
  27
  28        phy = devm_of_phy_get_by_index(dev, dev->of_node, index);
  29        if (IS_ERR(phy)) {
  30                if (PTR_ERR(phy) == -ENODEV)
  31                        return 0;
  32                else
  33                        return PTR_ERR(phy);
  34        }
  35
  36        roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL);
  37        if (!roothub_entry)
  38                return -ENOMEM;
  39
  40        INIT_LIST_HEAD(&roothub_entry->list);
  41
  42        roothub_entry->phy = phy;
  43
  44        list_add_tail(&roothub_entry->list, list);
  45
  46        return 0;
  47}
  48
  49struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev)
  50{
  51        struct usb_phy_roothub *phy_roothub;
  52        int i, num_phys, err;
  53
  54        if (!IS_ENABLED(CONFIG_GENERIC_PHY))
  55                return NULL;
  56
  57        num_phys = of_count_phandle_with_args(dev->of_node, "phys",
  58                                              "#phy-cells");
  59        if (num_phys <= 0)
  60                return NULL;
  61
  62        phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
  63        if (!phy_roothub)
  64                return ERR_PTR(-ENOMEM);
  65
  66        INIT_LIST_HEAD(&phy_roothub->list);
  67
  68        for (i = 0; i < num_phys; i++) {
  69                err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list);
  70                if (err)
  71                        return ERR_PTR(err);
  72        }
  73
  74        return phy_roothub;
  75}
  76EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc);
  77
  78int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub)
  79{
  80        struct usb_phy_roothub *roothub_entry;
  81        struct list_head *head;
  82        int err;
  83
  84        if (!phy_roothub)
  85                return 0;
  86
  87        head = &phy_roothub->list;
  88
  89        list_for_each_entry(roothub_entry, head, list) {
  90                err = phy_init(roothub_entry->phy);
  91                if (err)
  92                        goto err_exit_phys;
  93        }
  94
  95        return 0;
  96
  97err_exit_phys:
  98        list_for_each_entry_continue_reverse(roothub_entry, head, list)
  99                phy_exit(roothub_entry->phy);
 100
 101        return err;
 102}
 103EXPORT_SYMBOL_GPL(usb_phy_roothub_init);
 104
 105int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub)
 106{
 107        struct usb_phy_roothub *roothub_entry;
 108        struct list_head *head;
 109        int err, ret = 0;
 110
 111        if (!phy_roothub)
 112                return 0;
 113
 114        head = &phy_roothub->list;
 115
 116        list_for_each_entry(roothub_entry, head, list) {
 117                err = phy_exit(roothub_entry->phy);
 118                if (err)
 119                        ret = err;
 120        }
 121
 122        return ret;
 123}
 124EXPORT_SYMBOL_GPL(usb_phy_roothub_exit);
 125
 126int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub,
 127                             enum phy_mode mode)
 128{
 129        struct usb_phy_roothub *roothub_entry;
 130        struct list_head *head;
 131        int err;
 132
 133        if (!phy_roothub)
 134                return 0;
 135
 136        head = &phy_roothub->list;
 137
 138        list_for_each_entry(roothub_entry, head, list) {
 139                err = phy_set_mode(roothub_entry->phy, mode);
 140                if (err)
 141                        goto err_out;
 142        }
 143
 144        return 0;
 145
 146err_out:
 147        list_for_each_entry_continue_reverse(roothub_entry, head, list)
 148                phy_power_off(roothub_entry->phy);
 149
 150        return err;
 151}
 152EXPORT_SYMBOL_GPL(usb_phy_roothub_set_mode);
 153
 154int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub)
 155{
 156        struct usb_phy_roothub *roothub_entry;
 157        struct list_head *head;
 158        int err;
 159
 160        if (!phy_roothub)
 161                return 0;
 162
 163        head = &phy_roothub->list;
 164
 165        list_for_each_entry(roothub_entry, head, list) {
 166                err = phy_power_on(roothub_entry->phy);
 167                if (err)
 168                        goto err_out;
 169        }
 170
 171        return 0;
 172
 173err_out:
 174        list_for_each_entry_continue_reverse(roothub_entry, head, list)
 175                phy_power_off(roothub_entry->phy);
 176
 177        return err;
 178}
 179EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on);
 180
 181void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub)
 182{
 183        struct usb_phy_roothub *roothub_entry;
 184
 185        if (!phy_roothub)
 186                return;
 187
 188        list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list)
 189                phy_power_off(roothub_entry->phy);
 190}
 191EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off);
 192
 193int usb_phy_roothub_suspend(struct device *controller_dev,
 194                            struct usb_phy_roothub *phy_roothub)
 195{
 196        usb_phy_roothub_power_off(phy_roothub);
 197
 198        /* keep the PHYs initialized so the device can wake up the system */
 199        if (device_may_wakeup(controller_dev))
 200                return 0;
 201
 202        return usb_phy_roothub_exit(phy_roothub);
 203}
 204EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend);
 205
 206int usb_phy_roothub_resume(struct device *controller_dev,
 207                           struct usb_phy_roothub *phy_roothub)
 208{
 209        int err;
 210
 211        /* if the device can't wake up the system _exit was called */
 212        if (!device_may_wakeup(controller_dev)) {
 213                err = usb_phy_roothub_init(phy_roothub);
 214                if (err)
 215                        return err;
 216        }
 217
 218        err = usb_phy_roothub_power_on(phy_roothub);
 219
 220        /* undo _init if _power_on failed */
 221        if (err && !device_may_wakeup(controller_dev))
 222                usb_phy_roothub_exit(phy_roothub);
 223
 224        return err;
 225}
 226EXPORT_SYMBOL_GPL(usb_phy_roothub_resume);
 227