|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2016, NVIDIA Corporation | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of_device.h> | 
|  | #include <linux/reset.h> | 
|  |  | 
|  | #include <linux/usb/chipidea.h> | 
|  |  | 
|  | #include "ci.h" | 
|  |  | 
|  | struct tegra_udc { | 
|  | struct ci_hdrc_platform_data data; | 
|  | struct platform_device *dev; | 
|  |  | 
|  | struct usb_phy *phy; | 
|  | struct clk *clk; | 
|  | }; | 
|  |  | 
|  | struct tegra_udc_soc_info { | 
|  | unsigned long flags; | 
|  | }; | 
|  |  | 
|  | static const struct tegra_udc_soc_info tegra_udc_soc_info = { | 
|  | .flags = CI_HDRC_REQUIRES_ALIGNED_DMA, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id tegra_udc_of_match[] = { | 
|  | { | 
|  | .compatible = "nvidia,tegra20-udc", | 
|  | .data = &tegra_udc_soc_info, | 
|  | }, { | 
|  | .compatible = "nvidia,tegra30-udc", | 
|  | .data = &tegra_udc_soc_info, | 
|  | }, { | 
|  | .compatible = "nvidia,tegra114-udc", | 
|  | .data = &tegra_udc_soc_info, | 
|  | }, { | 
|  | .compatible = "nvidia,tegra124-udc", | 
|  | .data = &tegra_udc_soc_info, | 
|  | }, { | 
|  | /* sentinel */ | 
|  | } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, tegra_udc_of_match); | 
|  |  | 
|  | static int tegra_udc_probe(struct platform_device *pdev) | 
|  | { | 
|  | const struct tegra_udc_soc_info *soc; | 
|  | struct tegra_udc *udc; | 
|  | int err; | 
|  |  | 
|  | udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL); | 
|  | if (!udc) | 
|  | return -ENOMEM; | 
|  |  | 
|  | soc = of_device_get_match_data(&pdev->dev); | 
|  | if (!soc) { | 
|  | dev_err(&pdev->dev, "failed to match OF data\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | udc->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); | 
|  | if (IS_ERR(udc->phy)) { | 
|  | err = PTR_ERR(udc->phy); | 
|  | dev_err(&pdev->dev, "failed to get PHY: %d\n", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | udc->clk = devm_clk_get(&pdev->dev, NULL); | 
|  | if (IS_ERR(udc->clk)) { | 
|  | err = PTR_ERR(udc->clk); | 
|  | dev_err(&pdev->dev, "failed to get clock: %d\n", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = clk_prepare_enable(udc->clk); | 
|  | if (err < 0) { | 
|  | dev_err(&pdev->dev, "failed to enable clock: %d\n", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* setup and register ChipIdea HDRC device */ | 
|  | udc->data.name = "tegra-udc"; | 
|  | udc->data.flags = soc->flags; | 
|  | udc->data.usb_phy = udc->phy; | 
|  | udc->data.capoffset = DEF_CAPOFFSET; | 
|  |  | 
|  | udc->dev = ci_hdrc_add_device(&pdev->dev, pdev->resource, | 
|  | pdev->num_resources, &udc->data); | 
|  | if (IS_ERR(udc->dev)) { | 
|  | err = PTR_ERR(udc->dev); | 
|  | dev_err(&pdev->dev, "failed to add HDRC device: %d\n", err); | 
|  | goto fail_power_off; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, udc); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail_power_off: | 
|  | clk_disable_unprepare(udc->clk); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int tegra_udc_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct tegra_udc *udc = platform_get_drvdata(pdev); | 
|  |  | 
|  | ci_hdrc_remove_device(udc->dev); | 
|  | clk_disable_unprepare(udc->clk); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver tegra_udc_driver = { | 
|  | .driver = { | 
|  | .name = "tegra-udc", | 
|  | .of_match_table = tegra_udc_of_match, | 
|  | }, | 
|  | .probe = tegra_udc_probe, | 
|  | .remove = tegra_udc_remove, | 
|  | }; | 
|  | module_platform_driver(tegra_udc_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("NVIDIA Tegra USB device mode driver"); | 
|  | MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); | 
|  | MODULE_ALIAS("platform:tegra-udc"); | 
|  | MODULE_LICENSE("GPL v2"); |