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 struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) 23 { 24 struct usb_phy_roothub *roothub_entry; 25 26 roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); 27 if (!roothub_entry) 28 return ERR_PTR(-ENOMEM); 29 30 INIT_LIST_HEAD(&roothub_entry->list); 31 32 return roothub_entry; 33 } 34 35 static int usb_phy_roothub_add_phy(struct device *dev, int index, 36 struct list_head *list) 37 { 38 struct usb_phy_roothub *roothub_entry; 39 struct phy *phy = devm_of_phy_get_by_index(dev, dev->of_node, index); 40 41 if (IS_ERR_OR_NULL(phy)) { 42 if (!phy || PTR_ERR(phy) == -ENODEV) 43 return 0; 44 else 45 return PTR_ERR(phy); 46 } 47 48 roothub_entry = usb_phy_roothub_alloc(dev); 49 if (IS_ERR(roothub_entry)) 50 return PTR_ERR(roothub_entry); 51 52 roothub_entry->phy = phy; 53 54 list_add_tail(&roothub_entry->list, list); 55 56 return 0; 57 } 58 59 struct usb_phy_roothub *usb_phy_roothub_init(struct device *dev) 60 { 61 struct usb_phy_roothub *phy_roothub; 62 struct usb_phy_roothub *roothub_entry; 63 struct list_head *head; 64 int i, num_phys, err; 65 66 num_phys = of_count_phandle_with_args(dev->of_node, "phys", 67 "#phy-cells"); 68 if (num_phys <= 0) 69 return NULL; 70 71 phy_roothub = usb_phy_roothub_alloc(dev); 72 if (IS_ERR(phy_roothub)) 73 return phy_roothub; 74 75 for (i = 0; i < num_phys; i++) { 76 err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); 77 if (err) 78 goto err_out; 79 } 80 81 head = &phy_roothub->list; 82 83 list_for_each_entry(roothub_entry, head, list) { 84 err = phy_init(roothub_entry->phy); 85 if (err) 86 goto err_exit_phys; 87 } 88 89 return phy_roothub; 90 91 err_exit_phys: 92 list_for_each_entry_continue_reverse(roothub_entry, head, list) 93 phy_exit(roothub_entry->phy); 94 95 err_out: 96 return ERR_PTR(err); 97 } 98 EXPORT_SYMBOL_GPL(usb_phy_roothub_init); 99 100 int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub) 101 { 102 struct usb_phy_roothub *roothub_entry; 103 struct list_head *head; 104 int err, ret = 0; 105 106 if (!phy_roothub) 107 return 0; 108 109 head = &phy_roothub->list; 110 111 list_for_each_entry(roothub_entry, head, list) { 112 err = phy_exit(roothub_entry->phy); 113 if (err) 114 ret = ret; 115 } 116 117 return ret; 118 } 119 EXPORT_SYMBOL_GPL(usb_phy_roothub_exit); 120 121 int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) 122 { 123 struct usb_phy_roothub *roothub_entry; 124 struct list_head *head; 125 int err; 126 127 if (!phy_roothub) 128 return 0; 129 130 head = &phy_roothub->list; 131 132 list_for_each_entry(roothub_entry, head, list) { 133 err = phy_power_on(roothub_entry->phy); 134 if (err) 135 goto err_out; 136 } 137 138 return 0; 139 140 err_out: 141 list_for_each_entry_continue_reverse(roothub_entry, head, list) 142 phy_power_off(roothub_entry->phy); 143 144 return err; 145 } 146 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_on); 147 148 void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub) 149 { 150 struct usb_phy_roothub *roothub_entry; 151 152 if (!phy_roothub) 153 return; 154 155 list_for_each_entry_reverse(roothub_entry, &phy_roothub->list, list) 156 phy_power_off(roothub_entry->phy); 157 } 158 EXPORT_SYMBOL_GPL(usb_phy_roothub_power_off); 159