18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * USB cluster support for Armada 375 platform.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2014 Marvell
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Gregory CLEMENT <gregory.clement@free-electrons.com>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Armada 375 comes with an USB2 host and device controller and an
108c2ecf20Sopenharmony_ci * USB3 controller. The USB cluster control register allows to manage
118c2ecf20Sopenharmony_ci * common features of both USB controllers.
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <dt-bindings/phy/phy.h>
158c2ecf20Sopenharmony_ci#include <linux/init.h>
168c2ecf20Sopenharmony_ci#include <linux/io.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/of_address.h>
198c2ecf20Sopenharmony_ci#include <linux/phy/phy.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define USB2_PHY_CONFIG_DISABLE BIT(0)
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct armada375_cluster_phy {
258c2ecf20Sopenharmony_ci	struct phy *phy;
268c2ecf20Sopenharmony_ci	void __iomem *reg;
278c2ecf20Sopenharmony_ci	bool use_usb3;
288c2ecf20Sopenharmony_ci	int phy_provided;
298c2ecf20Sopenharmony_ci};
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int armada375_usb_phy_init(struct phy *phy)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	struct armada375_cluster_phy *cluster_phy;
348c2ecf20Sopenharmony_ci	u32 reg;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	cluster_phy = phy_get_drvdata(phy);
378c2ecf20Sopenharmony_ci	if (!cluster_phy)
388c2ecf20Sopenharmony_ci		return -ENODEV;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	reg = readl(cluster_phy->reg);
418c2ecf20Sopenharmony_ci	if (cluster_phy->use_usb3)
428c2ecf20Sopenharmony_ci		reg |= USB2_PHY_CONFIG_DISABLE;
438c2ecf20Sopenharmony_ci	else
448c2ecf20Sopenharmony_ci		reg &= ~USB2_PHY_CONFIG_DISABLE;
458c2ecf20Sopenharmony_ci	writel(reg, cluster_phy->reg);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	return 0;
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic const struct phy_ops armada375_usb_phy_ops = {
518c2ecf20Sopenharmony_ci	.init = armada375_usb_phy_init,
528c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci/*
568c2ecf20Sopenharmony_ci * Only one controller can use this PHY. We shouldn't have the case
578c2ecf20Sopenharmony_ci * when two controllers want to use this PHY. But if this case occurs
588c2ecf20Sopenharmony_ci * then we provide a phy to the first one and return an error for the
598c2ecf20Sopenharmony_ci * next one. This error has also to be an error returned by
608c2ecf20Sopenharmony_ci * devm_phy_optional_get() so different from ENODEV for USB2. In the
618c2ecf20Sopenharmony_ci * USB3 case it still optional and we use ENODEV.
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_cistatic struct phy *armada375_usb_phy_xlate(struct device *dev,
648c2ecf20Sopenharmony_ci					struct of_phandle_args *args)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (!cluster_phy)
698c2ecf20Sopenharmony_ci		return  ERR_PTR(-ENODEV);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/*
728c2ecf20Sopenharmony_ci	 * Either the phy had never been requested and then the first
738c2ecf20Sopenharmony_ci	 * usb claiming it can get it, or it had already been
748c2ecf20Sopenharmony_ci	 * requested in this case, we only allow to use it with the
758c2ecf20Sopenharmony_ci	 * same configuration.
768c2ecf20Sopenharmony_ci	 */
778c2ecf20Sopenharmony_ci	if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) &&
788c2ecf20Sopenharmony_ci			(cluster_phy->phy_provided != args->args[0]))) {
798c2ecf20Sopenharmony_ci		dev_err(dev, "This PHY has already been provided!\n");
808c2ecf20Sopenharmony_ci		dev_err(dev, "Check your device tree, only one controller can use it\n.");
818c2ecf20Sopenharmony_ci		if (args->args[0] == PHY_TYPE_USB2)
828c2ecf20Sopenharmony_ci			return ERR_PTR(-EBUSY);
838c2ecf20Sopenharmony_ci		else
848c2ecf20Sopenharmony_ci			return ERR_PTR(-ENODEV);
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	if (args->args[0] == PHY_TYPE_USB2)
888c2ecf20Sopenharmony_ci		cluster_phy->use_usb3 = false;
898c2ecf20Sopenharmony_ci	else if (args->args[0] == PHY_TYPE_USB3)
908c2ecf20Sopenharmony_ci		cluster_phy->use_usb3 = true;
918c2ecf20Sopenharmony_ci	else {
928c2ecf20Sopenharmony_ci		dev_err(dev, "Invalid PHY mode\n");
938c2ecf20Sopenharmony_ci		return ERR_PTR(-ENODEV);
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	/* Store which phy mode is used for next test */
978c2ecf20Sopenharmony_ci	cluster_phy->phy_provided = args->args[0];
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	return cluster_phy->phy;
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int armada375_usb_phy_probe(struct platform_device *pdev)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1058c2ecf20Sopenharmony_ci	struct phy *phy;
1068c2ecf20Sopenharmony_ci	struct phy_provider *phy_provider;
1078c2ecf20Sopenharmony_ci	void __iomem *usb_cluster_base;
1088c2ecf20Sopenharmony_ci	struct resource *res;
1098c2ecf20Sopenharmony_ci	struct armada375_cluster_phy *cluster_phy;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL);
1128c2ecf20Sopenharmony_ci	if (!cluster_phy)
1138c2ecf20Sopenharmony_ci		return  -ENOMEM;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1168c2ecf20Sopenharmony_ci	usb_cluster_base = devm_ioremap_resource(&pdev->dev, res);
1178c2ecf20Sopenharmony_ci	if (IS_ERR(usb_cluster_base))
1188c2ecf20Sopenharmony_ci		return PTR_ERR(usb_cluster_base);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops);
1218c2ecf20Sopenharmony_ci	if (IS_ERR(phy)) {
1228c2ecf20Sopenharmony_ci		dev_err(dev, "failed to create PHY\n");
1238c2ecf20Sopenharmony_ci		return PTR_ERR(phy);
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	cluster_phy->phy = phy;
1278c2ecf20Sopenharmony_ci	cluster_phy->reg = usb_cluster_base;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, cluster_phy);
1308c2ecf20Sopenharmony_ci	phy_set_drvdata(phy, cluster_phy);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	phy_provider = devm_of_phy_provider_register(&pdev->dev,
1338c2ecf20Sopenharmony_ci						     armada375_usb_phy_xlate);
1348c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(phy_provider);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic const struct of_device_id of_usb_cluster_table[] = {
1388c2ecf20Sopenharmony_ci	{ .compatible = "marvell,armada-375-usb-cluster", },
1398c2ecf20Sopenharmony_ci	{ /* end of list */ },
1408c2ecf20Sopenharmony_ci};
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic struct platform_driver armada375_usb_phy_driver = {
1438c2ecf20Sopenharmony_ci	.probe	= armada375_usb_phy_probe,
1448c2ecf20Sopenharmony_ci	.driver = {
1458c2ecf20Sopenharmony_ci		.of_match_table	= of_usb_cluster_table,
1468c2ecf20Sopenharmony_ci		.name  = "armada-375-usb-cluster",
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci};
1498c2ecf20Sopenharmony_cibuiltin_platform_driver(armada375_usb_phy_driver);
150