1 /*
2  * dwmac-sunxi.c - Allwinner sunxi DWMAC specific glue layer
3  *
4  * Copyright (C) 2013 Chen-Yu Tsai
5  *
6  * Chen-Yu Tsai  <wens@csie.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  */
18 
19 #include <linux/stmmac.h>
20 #include <linux/clk.h>
21 #include <linux/module.h>
22 #include <linux/phy.h>
23 #include <linux/platform_device.h>
24 #include <linux/of_net.h>
25 #include <linux/regulator/consumer.h>
26 
27 #include "stmmac_platform.h"
28 
29 struct sunxi_priv_data {
30 	int interface;
31 	int clk_enabled;
32 	struct clk *tx_clk;
33 	struct regulator *regulator;
34 };
35 
36 static void *sun7i_gmac_setup(struct platform_device *pdev)
37 {
38 	struct sunxi_priv_data *gmac;
39 	struct device *dev = &pdev->dev;
40 
41 	gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL);
42 	if (!gmac)
43 		return ERR_PTR(-ENOMEM);
44 
45 	gmac->interface = of_get_phy_mode(dev->of_node);
46 
47 	gmac->tx_clk = devm_clk_get(dev, "allwinner_gmac_tx");
48 	if (IS_ERR(gmac->tx_clk)) {
49 		dev_err(dev, "could not get tx clock\n");
50 		return gmac->tx_clk;
51 	}
52 
53 	/* Optional regulator for PHY */
54 	gmac->regulator = devm_regulator_get_optional(dev, "phy");
55 	if (IS_ERR(gmac->regulator)) {
56 		if (PTR_ERR(gmac->regulator) == -EPROBE_DEFER)
57 			return ERR_PTR(-EPROBE_DEFER);
58 		dev_info(dev, "no regulator found\n");
59 		gmac->regulator = NULL;
60 	}
61 
62 	return gmac;
63 }
64 
65 #define SUN7I_GMAC_GMII_RGMII_RATE	125000000
66 #define SUN7I_GMAC_MII_RATE		25000000
67 
68 static int sun7i_gmac_init(struct platform_device *pdev, void *priv)
69 {
70 	struct sunxi_priv_data *gmac = priv;
71 	int ret;
72 
73 	if (gmac->regulator) {
74 		ret = regulator_enable(gmac->regulator);
75 		if (ret)
76 			return ret;
77 	}
78 
79 	/* Set GMAC interface port mode
80 	 *
81 	 * The GMAC TX clock lines are configured by setting the clock
82 	 * rate, which then uses the auto-reparenting feature of the
83 	 * clock driver, and enabling/disabling the clock.
84 	 */
85 	if (gmac->interface == PHY_INTERFACE_MODE_RGMII) {
86 		clk_set_rate(gmac->tx_clk, SUN7I_GMAC_GMII_RGMII_RATE);
87 		clk_prepare_enable(gmac->tx_clk);
88 		gmac->clk_enabled = 1;
89 	} else {
90 		clk_set_rate(gmac->tx_clk, SUN7I_GMAC_MII_RATE);
91 		clk_prepare(gmac->tx_clk);
92 	}
93 
94 	return 0;
95 }
96 
97 static void sun7i_gmac_exit(struct platform_device *pdev, void *priv)
98 {
99 	struct sunxi_priv_data *gmac = priv;
100 
101 	if (gmac->clk_enabled) {
102 		clk_disable(gmac->tx_clk);
103 		gmac->clk_enabled = 0;
104 	}
105 	clk_unprepare(gmac->tx_clk);
106 
107 	if (gmac->regulator)
108 		regulator_disable(gmac->regulator);
109 }
110 
111 static void sun7i_fix_speed(void *priv, unsigned int speed)
112 {
113 	struct sunxi_priv_data *gmac = priv;
114 
115 	/* only GMII mode requires us to reconfigure the clock lines */
116 	if (gmac->interface != PHY_INTERFACE_MODE_GMII)
117 		return;
118 
119 	if (gmac->clk_enabled) {
120 		clk_disable(gmac->tx_clk);
121 		gmac->clk_enabled = 0;
122 	}
123 	clk_unprepare(gmac->tx_clk);
124 
125 	if (speed == 1000) {
126 		clk_set_rate(gmac->tx_clk, SUN7I_GMAC_GMII_RGMII_RATE);
127 		clk_prepare_enable(gmac->tx_clk);
128 		gmac->clk_enabled = 1;
129 	} else {
130 		clk_set_rate(gmac->tx_clk, SUN7I_GMAC_MII_RATE);
131 		clk_prepare(gmac->tx_clk);
132 	}
133 }
134 
135 /* of_data specifying hardware features and callbacks.
136  * hardware features were copied from Allwinner drivers. */
137 static const struct stmmac_of_data sun7i_gmac_data = {
138 	.has_gmac = 1,
139 	.tx_coe = 1,
140 	.fix_mac_speed = sun7i_fix_speed,
141 	.setup = sun7i_gmac_setup,
142 	.init = sun7i_gmac_init,
143 	.exit = sun7i_gmac_exit,
144 };
145 
146 static const struct of_device_id sun7i_dwmac_match[] = {
147 	{ .compatible = "allwinner,sun7i-a20-gmac", .data = &sun7i_gmac_data},
148 	{ }
149 };
150 MODULE_DEVICE_TABLE(of, sun7i_dwmac_match);
151 
152 static struct platform_driver sun7i_dwmac_driver = {
153 	.probe  = stmmac_pltfr_probe,
154 	.remove = stmmac_pltfr_remove,
155 	.driver = {
156 		.name           = "sun7i-dwmac",
157 		.pm		= &stmmac_pltfr_pm_ops,
158 		.of_match_table = sun7i_dwmac_match,
159 	},
160 };
161 module_platform_driver(sun7i_dwmac_driver);
162 
163 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
164 MODULE_DESCRIPTION("Allwinner sunxi DWMAC specific glue layer");
165 MODULE_LICENSE("GPL");
166