Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | // SPDX-License-Identifier: GPL-2.0+ /* * USB cluster support for Armada 375 platform. * * Copyright (C) 2014 Marvell * * Gregory CLEMENT <gregory.clement@free-electrons.com> * * Armada 375 comes with an USB2 host and device controller and an * USB3 controller. The USB cluster control register allows to manage * common features of both USB controllers. */ #include <dt-bindings/phy/phy.h> #include <linux/init.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #define USB2_PHY_CONFIG_DISABLE BIT(0) struct armada375_cluster_phy { struct phy *phy; void __iomem *reg; bool use_usb3; int phy_provided; }; static int armada375_usb_phy_init(struct phy *phy) { struct armada375_cluster_phy *cluster_phy; u32 reg; cluster_phy = phy_get_drvdata(phy); if (!cluster_phy) return -ENODEV; reg = readl(cluster_phy->reg); if (cluster_phy->use_usb3) reg |= USB2_PHY_CONFIG_DISABLE; else reg &= ~USB2_PHY_CONFIG_DISABLE; writel(reg, cluster_phy->reg); return 0; } static const struct phy_ops armada375_usb_phy_ops = { .init = armada375_usb_phy_init, .owner = THIS_MODULE, }; /* * Only one controller can use this PHY. We shouldn't have the case * when two controllers want to use this PHY. But if this case occurs * then we provide a phy to the first one and return an error for the * next one. This error has also to be an error returned by * devm_phy_optional_get() so different from ENODEV for USB2. In the * USB3 case it still optional and we use ENODEV. */ static struct phy *armada375_usb_phy_xlate(struct device *dev, struct of_phandle_args *args) { struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev); if (!cluster_phy) return ERR_PTR(-ENODEV); /* * Either the phy had never been requested and then the first * usb claiming it can get it, or it had already been * requested in this case, we only allow to use it with the * same configuration. */ if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) && (cluster_phy->phy_provided != args->args[0]))) { dev_err(dev, "This PHY has already been provided!\n"); dev_err(dev, "Check your device tree, only one controller can use it\n."); if (args->args[0] == PHY_TYPE_USB2) return ERR_PTR(-EBUSY); else return ERR_PTR(-ENODEV); } if (args->args[0] == PHY_TYPE_USB2) cluster_phy->use_usb3 = false; else if (args->args[0] == PHY_TYPE_USB3) cluster_phy->use_usb3 = true; else { dev_err(dev, "Invalid PHY mode\n"); return ERR_PTR(-ENODEV); } /* Store which phy mode is used for next test */ cluster_phy->phy_provided = args->args[0]; return cluster_phy->phy; } static int armada375_usb_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct phy *phy; struct phy_provider *phy_provider; void __iomem *usb_cluster_base; struct armada375_cluster_phy *cluster_phy; cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL); if (!cluster_phy) return -ENOMEM; usb_cluster_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(usb_cluster_base)) return PTR_ERR(usb_cluster_base); phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); if (IS_ERR(phy)) { dev_err(dev, "failed to create PHY\n"); return PTR_ERR(phy); } cluster_phy->phy = phy; cluster_phy->reg = usb_cluster_base; dev_set_drvdata(dev, cluster_phy); phy_set_drvdata(phy, cluster_phy); phy_provider = devm_of_phy_provider_register(&pdev->dev, armada375_usb_phy_xlate); return PTR_ERR_OR_ZERO(phy_provider); } static const struct of_device_id of_usb_cluster_table[] = { { .compatible = "marvell,armada-375-usb-cluster", }, { /* end of list */ }, }; static struct platform_driver armada375_usb_phy_driver = { .probe = armada375_usb_phy_probe, .driver = { .of_match_table = of_usb_cluster_table, .name = "armada-375-usb-cluster", } }; builtin_platform_driver(armada375_usb_phy_driver); |