1/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. 2 * 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License version 2 and 5 * only version 2 as published by the Free Software Foundation. 6 * 7 * This program is distributed in the hope that it will be useful, 8 * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 * GNU General Public License for more details. 11 */ 12 13#include <linux/clk.h> 14#include <linux/err.h> 15#include <linux/io.h> 16#include <linux/module.h> 17#include <linux/of.h> 18#include <linux/of_platform.h> 19#include <linux/platform_device.h> 20 21struct dwc3_qcom { 22 struct device *dev; 23 24 struct clk *core_clk; 25 struct clk *iface_clk; 26 struct clk *sleep_clk; 27}; 28 29static int dwc3_qcom_probe(struct platform_device *pdev) 30{ 31 struct device_node *node = pdev->dev.of_node; 32 struct dwc3_qcom *qdwc; 33 int ret; 34 35 qdwc = devm_kzalloc(&pdev->dev, sizeof(*qdwc), GFP_KERNEL); 36 if (!qdwc) 37 return -ENOMEM; 38 39 platform_set_drvdata(pdev, qdwc); 40 41 qdwc->dev = &pdev->dev; 42 43 qdwc->core_clk = devm_clk_get(qdwc->dev, "core"); 44 if (IS_ERR(qdwc->core_clk)) { 45 dev_err(qdwc->dev, "failed to get core clock\n"); 46 return PTR_ERR(qdwc->core_clk); 47 } 48 49 qdwc->iface_clk = devm_clk_get(qdwc->dev, "iface"); 50 if (IS_ERR(qdwc->iface_clk)) { 51 dev_info(qdwc->dev, "failed to get optional iface clock\n"); 52 qdwc->iface_clk = NULL; 53 } 54 55 qdwc->sleep_clk = devm_clk_get(qdwc->dev, "sleep"); 56 if (IS_ERR(qdwc->sleep_clk)) { 57 dev_info(qdwc->dev, "failed to get optional sleep clock\n"); 58 qdwc->sleep_clk = NULL; 59 } 60 61 ret = clk_prepare_enable(qdwc->core_clk); 62 if (ret) { 63 dev_err(qdwc->dev, "failed to enable core clock\n"); 64 goto err_core; 65 } 66 67 ret = clk_prepare_enable(qdwc->iface_clk); 68 if (ret) { 69 dev_err(qdwc->dev, "failed to enable optional iface clock\n"); 70 goto err_iface; 71 } 72 73 ret = clk_prepare_enable(qdwc->sleep_clk); 74 if (ret) { 75 dev_err(qdwc->dev, "failed to enable optional sleep clock\n"); 76 goto err_sleep; 77 } 78 79 ret = of_platform_populate(node, NULL, NULL, qdwc->dev); 80 if (ret) { 81 dev_err(qdwc->dev, "failed to register core - %d\n", ret); 82 goto err_clks; 83 } 84 85 return 0; 86 87err_clks: 88 clk_disable_unprepare(qdwc->sleep_clk); 89err_sleep: 90 clk_disable_unprepare(qdwc->iface_clk); 91err_iface: 92 clk_disable_unprepare(qdwc->core_clk); 93err_core: 94 return ret; 95} 96 97static int dwc3_qcom_remove(struct platform_device *pdev) 98{ 99 struct dwc3_qcom *qdwc = platform_get_drvdata(pdev); 100 101 of_platform_depopulate(&pdev->dev); 102 103 clk_disable_unprepare(qdwc->sleep_clk); 104 clk_disable_unprepare(qdwc->iface_clk); 105 clk_disable_unprepare(qdwc->core_clk); 106 107 return 0; 108} 109 110static const struct of_device_id of_dwc3_match[] = { 111 { .compatible = "qcom,dwc3" }, 112 { /* Sentinel */ } 113}; 114MODULE_DEVICE_TABLE(of, of_dwc3_match); 115 116static struct platform_driver dwc3_qcom_driver = { 117 .probe = dwc3_qcom_probe, 118 .remove = dwc3_qcom_remove, 119 .driver = { 120 .name = "qcom-dwc3", 121 .of_match_table = of_dwc3_match, 122 }, 123}; 124 125module_platform_driver(dwc3_qcom_driver); 126 127MODULE_ALIAS("platform:qcom-dwc3"); 128MODULE_LICENSE("GPL v2"); 129MODULE_DESCRIPTION("DesignWare USB3 QCOM Glue Layer"); 130MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); 131