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 17 struct usb_phy_roothub { 18 struct phy *phy; 19 struct list_head list; 20 }; 21 22 static 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 48 struct 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 } 75 EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); 76 77 int 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 96 err_exit_phys: 97 list_for_each_entry_continue_reverse(roothub_entry, head, list) 98 phy_exit(roothub_entry->phy); 99 100 return err; 101 } 102 EXPORT_SYMBOL_GPL(usb_phy_roothub_init); 103 104 int 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 } 123 EXPORT_SYMBOL_GPL(usb_phy_roothub_exit); 124 125 int 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 144 err_out: 145 list_for_each_entry_continue_reverse(roothub_entry, head, list) 146 phy_power_off(roothub_entry->phy); 147 148 return err; 149 } 150 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on); 151 152 void 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 } 162 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off); 163 164 int 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 } 175 EXPORT_SYMBOL_GPL(usb_phy_roothub_suspend); 176 177 int 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 } 197 EXPORT_SYMBOL_GPL(usb_phy_roothub_resume); 198