1/*
2 * Samsung SoC USB 1.1/2.0 PHY driver
3 *
4 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
5 * Author: Kamil Debski <k.debski@samsung.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/clk.h>
13#include <linux/mfd/syscon.h>
14#include <linux/module.h>
15#include <linux/of.h>
16#include <linux/of_address.h>
17#include <linux/phy/phy.h>
18#include <linux/platform_device.h>
19#include <linux/spinlock.h>
20#include "phy-samsung-usb2.h"
21
22static int samsung_usb2_phy_power_on(struct phy *phy)
23{
24	struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
25	struct samsung_usb2_phy_driver *drv = inst->drv;
26	int ret;
27
28	dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n",
29		inst->cfg->label);
30	ret = clk_prepare_enable(drv->clk);
31	if (ret)
32		goto err_main_clk;
33	ret = clk_prepare_enable(drv->ref_clk);
34	if (ret)
35		goto err_instance_clk;
36	if (inst->cfg->power_on) {
37		spin_lock(&drv->lock);
38		ret = inst->cfg->power_on(inst);
39		spin_unlock(&drv->lock);
40		if (ret)
41			goto err_power_on;
42	}
43
44	return 0;
45
46err_power_on:
47	clk_disable_unprepare(drv->ref_clk);
48err_instance_clk:
49	clk_disable_unprepare(drv->clk);
50err_main_clk:
51	return ret;
52}
53
54static int samsung_usb2_phy_power_off(struct phy *phy)
55{
56	struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy);
57	struct samsung_usb2_phy_driver *drv = inst->drv;
58	int ret;
59
60	dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n",
61		inst->cfg->label);
62	if (inst->cfg->power_off) {
63		spin_lock(&drv->lock);
64		ret = inst->cfg->power_off(inst);
65		spin_unlock(&drv->lock);
66		if (ret)
67			return ret;
68	}
69	clk_disable_unprepare(drv->ref_clk);
70	clk_disable_unprepare(drv->clk);
71	return 0;
72}
73
74static struct phy_ops samsung_usb2_phy_ops = {
75	.power_on	= samsung_usb2_phy_power_on,
76	.power_off	= samsung_usb2_phy_power_off,
77	.owner		= THIS_MODULE,
78};
79
80static struct phy *samsung_usb2_phy_xlate(struct device *dev,
81					struct of_phandle_args *args)
82{
83	struct samsung_usb2_phy_driver *drv;
84
85	drv = dev_get_drvdata(dev);
86	if (!drv)
87		return ERR_PTR(-EINVAL);
88
89	if (WARN_ON(args->args[0] >= drv->cfg->num_phys))
90		return ERR_PTR(-ENODEV);
91
92	return drv->instances[args->args[0]].phy;
93}
94
95static const struct of_device_id samsung_usb2_phy_of_match[] = {
96#ifdef CONFIG_PHY_EXYNOS4X12_USB2
97	{
98		.compatible = "samsung,exynos3250-usb2-phy",
99		.data = &exynos3250_usb2_phy_config,
100	},
101#endif
102#ifdef CONFIG_PHY_EXYNOS4210_USB2
103	{
104		.compatible = "samsung,exynos4210-usb2-phy",
105		.data = &exynos4210_usb2_phy_config,
106	},
107#endif
108#ifdef CONFIG_PHY_EXYNOS4X12_USB2
109	{
110		.compatible = "samsung,exynos4x12-usb2-phy",
111		.data = &exynos4x12_usb2_phy_config,
112	},
113#endif
114#ifdef CONFIG_PHY_EXYNOS5250_USB2
115	{
116		.compatible = "samsung,exynos5250-usb2-phy",
117		.data = &exynos5250_usb2_phy_config,
118	},
119#endif
120#ifdef CONFIG_PHY_S5PV210_USB2
121	{
122		.compatible = "samsung,s5pv210-usb2-phy",
123		.data = &s5pv210_usb2_phy_config,
124	},
125#endif
126	{ },
127};
128MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match);
129
130static int samsung_usb2_phy_probe(struct platform_device *pdev)
131{
132	const struct of_device_id *match;
133	const struct samsung_usb2_phy_config *cfg;
134	struct device *dev = &pdev->dev;
135	struct phy_provider *phy_provider;
136	struct resource *mem;
137	struct samsung_usb2_phy_driver *drv;
138	int i, ret;
139
140	if (!pdev->dev.of_node) {
141		dev_err(dev, "This driver is required to be instantiated from device tree\n");
142		return -EINVAL;
143	}
144
145	match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node);
146	if (!match) {
147		dev_err(dev, "of_match_node() failed\n");
148		return -EINVAL;
149	}
150	cfg = match->data;
151
152	drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) +
153		cfg->num_phys * sizeof(struct samsung_usb2_phy_instance),
154								GFP_KERNEL);
155	if (!drv)
156		return -ENOMEM;
157
158	dev_set_drvdata(dev, drv);
159	spin_lock_init(&drv->lock);
160
161	drv->cfg = cfg;
162	drv->dev = dev;
163
164	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
165	drv->reg_phy = devm_ioremap_resource(dev, mem);
166	if (IS_ERR(drv->reg_phy)) {
167		dev_err(dev, "Failed to map register memory (phy)\n");
168		return PTR_ERR(drv->reg_phy);
169	}
170
171	drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
172		"samsung,pmureg-phandle");
173	if (IS_ERR(drv->reg_pmu)) {
174		dev_err(dev, "Failed to map PMU registers (via syscon)\n");
175		return PTR_ERR(drv->reg_pmu);
176	}
177
178	if (drv->cfg->has_mode_switch) {
179		drv->reg_sys = syscon_regmap_lookup_by_phandle(
180				pdev->dev.of_node, "samsung,sysreg-phandle");
181		if (IS_ERR(drv->reg_sys)) {
182			dev_err(dev, "Failed to map system registers (via syscon)\n");
183			return PTR_ERR(drv->reg_sys);
184		}
185	}
186
187	drv->clk = devm_clk_get(dev, "phy");
188	if (IS_ERR(drv->clk)) {
189		dev_err(dev, "Failed to get clock of phy controller\n");
190		return PTR_ERR(drv->clk);
191	}
192
193	drv->ref_clk = devm_clk_get(dev, "ref");
194	if (IS_ERR(drv->ref_clk)) {
195		dev_err(dev, "Failed to get reference clock for the phy controller\n");
196		return PTR_ERR(drv->ref_clk);
197	}
198
199	drv->ref_rate = clk_get_rate(drv->ref_clk);
200	if (drv->cfg->rate_to_clk) {
201		ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val);
202		if (ret)
203			return ret;
204	}
205
206	for (i = 0; i < drv->cfg->num_phys; i++) {
207		char *label = drv->cfg->phys[i].label;
208		struct samsung_usb2_phy_instance *p = &drv->instances[i];
209
210		dev_dbg(dev, "Creating phy \"%s\"\n", label);
211		p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops);
212		if (IS_ERR(p->phy)) {
213			dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n",
214				label);
215			return PTR_ERR(p->phy);
216		}
217
218		p->cfg = &drv->cfg->phys[i];
219		p->drv = drv;
220		phy_set_bus_width(p->phy, 8);
221		phy_set_drvdata(p->phy, p);
222	}
223
224	phy_provider = devm_of_phy_provider_register(dev,
225							samsung_usb2_phy_xlate);
226	if (IS_ERR(phy_provider)) {
227		dev_err(drv->dev, "Failed to register phy provider\n");
228		return PTR_ERR(phy_provider);
229	}
230
231	return 0;
232}
233
234static struct platform_driver samsung_usb2_phy_driver = {
235	.probe	= samsung_usb2_phy_probe,
236	.driver = {
237		.of_match_table	= samsung_usb2_phy_of_match,
238		.name		= "samsung-usb2-phy",
239	}
240};
241
242module_platform_driver(samsung_usb2_phy_driver);
243MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver");
244MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
245MODULE_LICENSE("GPL v2");
246MODULE_ALIAS("platform:samsung-usb2-phy");
247