|  | /* | 
|  | * Samsung SoC USB 1.1/2.0 PHY driver | 
|  | * | 
|  | * Copyright (C) 2013 Samsung Electronics Co., Ltd. | 
|  | * Author: Kamil Debski <k.debski@samsung.com> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of the GNU General Public License version 2 as | 
|  | * published by the Free Software Foundation. | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/mfd/syscon.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/phy/phy.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include "phy-samsung-usb2.h" | 
|  |  | 
|  | static int samsung_usb2_phy_power_on(struct phy *phy) | 
|  | { | 
|  | struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); | 
|  | struct samsung_usb2_phy_driver *drv = inst->drv; | 
|  | int ret; | 
|  |  | 
|  | dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", | 
|  | inst->cfg->label); | 
|  | ret = clk_prepare_enable(drv->clk); | 
|  | if (ret) | 
|  | goto err_main_clk; | 
|  | ret = clk_prepare_enable(drv->ref_clk); | 
|  | if (ret) | 
|  | goto err_instance_clk; | 
|  | if (inst->cfg->power_on) { | 
|  | spin_lock(&drv->lock); | 
|  | ret = inst->cfg->power_on(inst); | 
|  | spin_unlock(&drv->lock); | 
|  | if (ret) | 
|  | goto err_power_on; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err_power_on: | 
|  | clk_disable_unprepare(drv->ref_clk); | 
|  | err_instance_clk: | 
|  | clk_disable_unprepare(drv->clk); | 
|  | err_main_clk: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int samsung_usb2_phy_power_off(struct phy *phy) | 
|  | { | 
|  | struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); | 
|  | struct samsung_usb2_phy_driver *drv = inst->drv; | 
|  | int ret; | 
|  |  | 
|  | dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", | 
|  | inst->cfg->label); | 
|  | if (inst->cfg->power_off) { | 
|  | spin_lock(&drv->lock); | 
|  | ret = inst->cfg->power_off(inst); | 
|  | spin_unlock(&drv->lock); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  | clk_disable_unprepare(drv->ref_clk); | 
|  | clk_disable_unprepare(drv->clk); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct phy_ops samsung_usb2_phy_ops = { | 
|  | .power_on	= samsung_usb2_phy_power_on, | 
|  | .power_off	= samsung_usb2_phy_power_off, | 
|  | .owner		= THIS_MODULE, | 
|  | }; | 
|  |  | 
|  | static struct phy *samsung_usb2_phy_xlate(struct device *dev, | 
|  | struct of_phandle_args *args) | 
|  | { | 
|  | struct samsung_usb2_phy_driver *drv; | 
|  |  | 
|  | drv = dev_get_drvdata(dev); | 
|  | if (!drv) | 
|  | return ERR_PTR(-EINVAL); | 
|  |  | 
|  | if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) | 
|  | return ERR_PTR(-ENODEV); | 
|  |  | 
|  | return drv->instances[args->args[0]].phy; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id samsung_usb2_phy_of_match[] = { | 
|  | #ifdef CONFIG_PHY_EXYNOS4X12_USB2 | 
|  | { | 
|  | .compatible = "samsung,exynos3250-usb2-phy", | 
|  | .data = &exynos3250_usb2_phy_config, | 
|  | }, | 
|  | #endif | 
|  | #ifdef CONFIG_PHY_EXYNOS4210_USB2 | 
|  | { | 
|  | .compatible = "samsung,exynos4210-usb2-phy", | 
|  | .data = &exynos4210_usb2_phy_config, | 
|  | }, | 
|  | #endif | 
|  | #ifdef CONFIG_PHY_EXYNOS4X12_USB2 | 
|  | { | 
|  | .compatible = "samsung,exynos4x12-usb2-phy", | 
|  | .data = &exynos4x12_usb2_phy_config, | 
|  | }, | 
|  | #endif | 
|  | #ifdef CONFIG_PHY_EXYNOS5250_USB2 | 
|  | { | 
|  | .compatible = "samsung,exynos5250-usb2-phy", | 
|  | .data = &exynos5250_usb2_phy_config, | 
|  | }, | 
|  | #endif | 
|  | #ifdef CONFIG_PHY_S5PV210_USB2 | 
|  | { | 
|  | .compatible = "samsung,s5pv210-usb2-phy", | 
|  | .data = &s5pv210_usb2_phy_config, | 
|  | }, | 
|  | #endif | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match); | 
|  |  | 
|  | static int samsung_usb2_phy_probe(struct platform_device *pdev) | 
|  | { | 
|  | const struct of_device_id *match; | 
|  | const struct samsung_usb2_phy_config *cfg; | 
|  | struct device *dev = &pdev->dev; | 
|  | struct phy_provider *phy_provider; | 
|  | struct resource *mem; | 
|  | struct samsung_usb2_phy_driver *drv; | 
|  | int i, ret; | 
|  |  | 
|  | if (!pdev->dev.of_node) { | 
|  | dev_err(dev, "This driver is required to be instantiated from device tree\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); | 
|  | if (!match) { | 
|  | dev_err(dev, "of_match_node() failed\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | cfg = match->data; | 
|  |  | 
|  | drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + | 
|  | cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), | 
|  | GFP_KERNEL); | 
|  | if (!drv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | dev_set_drvdata(dev, drv); | 
|  | spin_lock_init(&drv->lock); | 
|  |  | 
|  | drv->cfg = cfg; | 
|  | drv->dev = dev; | 
|  |  | 
|  | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | drv->reg_phy = devm_ioremap_resource(dev, mem); | 
|  | if (IS_ERR(drv->reg_phy)) { | 
|  | dev_err(dev, "Failed to map register memory (phy)\n"); | 
|  | return PTR_ERR(drv->reg_phy); | 
|  | } | 
|  |  | 
|  | drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | 
|  | "samsung,pmureg-phandle"); | 
|  | if (IS_ERR(drv->reg_pmu)) { | 
|  | dev_err(dev, "Failed to map PMU registers (via syscon)\n"); | 
|  | return PTR_ERR(drv->reg_pmu); | 
|  | } | 
|  |  | 
|  | if (drv->cfg->has_mode_switch) { | 
|  | drv->reg_sys = syscon_regmap_lookup_by_phandle( | 
|  | pdev->dev.of_node, "samsung,sysreg-phandle"); | 
|  | if (IS_ERR(drv->reg_sys)) { | 
|  | dev_err(dev, "Failed to map system registers (via syscon)\n"); | 
|  | return PTR_ERR(drv->reg_sys); | 
|  | } | 
|  | } | 
|  |  | 
|  | drv->clk = devm_clk_get(dev, "phy"); | 
|  | if (IS_ERR(drv->clk)) { | 
|  | dev_err(dev, "Failed to get clock of phy controller\n"); | 
|  | return PTR_ERR(drv->clk); | 
|  | } | 
|  |  | 
|  | drv->ref_clk = devm_clk_get(dev, "ref"); | 
|  | if (IS_ERR(drv->ref_clk)) { | 
|  | dev_err(dev, "Failed to get reference clock for the phy controller\n"); | 
|  | return PTR_ERR(drv->ref_clk); | 
|  | } | 
|  |  | 
|  | drv->ref_rate = clk_get_rate(drv->ref_clk); | 
|  | if (drv->cfg->rate_to_clk) { | 
|  | ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < drv->cfg->num_phys; i++) { | 
|  | char *label = drv->cfg->phys[i].label; | 
|  | struct samsung_usb2_phy_instance *p = &drv->instances[i]; | 
|  |  | 
|  | dev_dbg(dev, "Creating phy \"%s\"\n", label); | 
|  | p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops); | 
|  | if (IS_ERR(p->phy)) { | 
|  | dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", | 
|  | label); | 
|  | return PTR_ERR(p->phy); | 
|  | } | 
|  |  | 
|  | p->cfg = &drv->cfg->phys[i]; | 
|  | p->drv = drv; | 
|  | phy_set_bus_width(p->phy, 8); | 
|  | phy_set_drvdata(p->phy, p); | 
|  | } | 
|  |  | 
|  | phy_provider = devm_of_phy_provider_register(dev, | 
|  | samsung_usb2_phy_xlate); | 
|  | if (IS_ERR(phy_provider)) { | 
|  | dev_err(drv->dev, "Failed to register phy provider\n"); | 
|  | return PTR_ERR(phy_provider); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver samsung_usb2_phy_driver = { | 
|  | .probe	= samsung_usb2_phy_probe, | 
|  | .driver = { | 
|  | .of_match_table	= samsung_usb2_phy_of_match, | 
|  | .name		= "samsung-usb2-phy", | 
|  | } | 
|  | }; | 
|  |  | 
|  | module_platform_driver(samsung_usb2_phy_driver); | 
|  | MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); | 
|  | MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_ALIAS("platform:samsung-usb2-phy"); |