summaryrefslogtreecommitdiffstats
path: root/drivers/fpga/altera-hps2fpga.c
diff options
context:
space:
mode:
authorAlan Tull <atull@opensource.altera.com>2016-11-01 15:14:30 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-11-10 11:03:36 -0500
commite5f8efa5c8bf86c1fa698551d54db8f6aee221fd (patch)
tree8be983f965df4d7f28d05285b8d8ee57041a8dee /drivers/fpga/altera-hps2fpga.c
parent0fa20cdfcc1f68847cdfc47824476301eedc8297 (diff)
ARM: socfpga: fpga bridge driver support
Supports Altera SOCFPGA bridges: * fpga2sdram * fpga2hps * hps2fpga * lwhps2fpga Allows enabling/disabling the bridges through the FPGA Bridge Framework API functions. The fpga2sdram driver only supports enabling and disabling of the ports that been configured early on. This is due to a hardware limitation where the read, write, and command ports on the fpga2sdram bridge can only be reconfigured while there are no transactions to the sdram, i.e. when running out of OCRAM before the kernel boots. Device tree property 'init-val' configures the driver to enable or disable the bridge during probe. If the property does not exist, the driver will leave the bridge in its current state. Signed-off-by: Alan Tull <atull@opensource.altera.com> Signed-off-by: Matthew Gerlach <mgerlach@altera.com> Signed-off-by: Dinh Nguyen <dinguyen@opensource.altera.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/fpga/altera-hps2fpga.c')
-rw-r--r--drivers/fpga/altera-hps2fpga.c222
1 files changed, 222 insertions, 0 deletions
diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c
new file mode 100644
index 000000000000..4b354c79be31
--- /dev/null
+++ b/drivers/fpga/altera-hps2fpga.c
@@ -0,0 +1,222 @@
1/*
2 * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices
3 *
4 * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved.
5 *
6 * Includes this patch from the mailing list:
7 * fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters
8 * Signed-off-by: Anatolij Gustschin <agust@denx.de>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms and conditions of the GNU General Public License,
12 * version 2, as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * more details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * This driver manages bridges on a Altera SOCFPGA between the ARM host
25 * processor system (HPS) and the embedded FPGA.
26 *
27 * This driver supports enabling and disabling of the configured ports, which
28 * allows for safe reprogramming of the FPGA, assuming that the new FPGA image
29 * uses the same port configuration. Bridges must be disabled before
30 * reprogramming the FPGA and re-enabled after the FPGA has been programmed.
31 */
32
33#include <linux/clk.h>
34#include <linux/fpga/fpga-bridge.h>
35#include <linux/kernel.h>
36#include <linux/mfd/syscon.h>
37#include <linux/module.h>
38#include <linux/of_platform.h>
39#include <linux/regmap.h>
40#include <linux/reset.h>
41#include <linux/spinlock.h>
42
43#define ALT_L3_REMAP_OFST 0x0
44#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001
45#define ALT_L3_REMAP_H2F_MSK 0x00000008
46#define ALT_L3_REMAP_LWH2F_MSK 0x00000010
47
48#define HPS2FPGA_BRIDGE_NAME "hps2fpga"
49#define LWHPS2FPGA_BRIDGE_NAME "lwhps2fpga"
50#define FPGA2HPS_BRIDGE_NAME "fpga2hps"
51
52struct altera_hps2fpga_data {
53 const char *name;
54 struct reset_control *bridge_reset;
55 struct regmap *l3reg;
56 unsigned int remap_mask;
57 struct clk *clk;
58};
59
60static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge)
61{
62 struct altera_hps2fpga_data *priv = bridge->priv;
63
64 return reset_control_status(priv->bridge_reset);
65}
66
67/* The L3 REMAP register is write only, so keep a cached value. */
68static unsigned int l3_remap_shadow;
69static spinlock_t l3_remap_lock;
70
71static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv,
72 bool enable)
73{
74 unsigned long flags;
75 int ret;
76
77 /* bring bridge out of reset */
78 if (enable)
79 ret = reset_control_deassert(priv->bridge_reset);
80 else
81 ret = reset_control_assert(priv->bridge_reset);
82 if (ret)
83 return ret;
84
85 /* Allow bridge to be visible to L3 masters or not */
86 if (priv->remap_mask) {
87 spin_lock_irqsave(&l3_remap_lock, flags);
88 l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK;
89
90 if (enable)
91 l3_remap_shadow |= priv->remap_mask;
92 else
93 l3_remap_shadow &= ~priv->remap_mask;
94
95 ret = regmap_write(priv->l3reg, ALT_L3_REMAP_OFST,
96 l3_remap_shadow);
97 spin_unlock_irqrestore(&l3_remap_lock, flags);
98 }
99
100 return ret;
101}
102
103static int alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable)
104{
105 return _alt_hps2fpga_enable_set(bridge->priv, enable);
106}
107
108static const struct fpga_bridge_ops altera_hps2fpga_br_ops = {
109 .enable_set = alt_hps2fpga_enable_set,
110 .enable_show = alt_hps2fpga_enable_show,
111};
112
113static struct altera_hps2fpga_data hps2fpga_data = {
114 .name = HPS2FPGA_BRIDGE_NAME,
115 .remap_mask = ALT_L3_REMAP_H2F_MSK,
116};
117
118static struct altera_hps2fpga_data lwhps2fpga_data = {
119 .name = LWHPS2FPGA_BRIDGE_NAME,
120 .remap_mask = ALT_L3_REMAP_LWH2F_MSK,
121};
122
123static struct altera_hps2fpga_data fpga2hps_data = {
124 .name = FPGA2HPS_BRIDGE_NAME,
125};
126
127static const struct of_device_id altera_fpga_of_match[] = {
128 { .compatible = "altr,socfpga-hps2fpga-bridge",
129 .data = &hps2fpga_data },
130 { .compatible = "altr,socfpga-lwhps2fpga-bridge",
131 .data = &lwhps2fpga_data },
132 { .compatible = "altr,socfpga-fpga2hps-bridge",
133 .data = &fpga2hps_data },
134 {},
135};
136
137static int alt_fpga_bridge_probe(struct platform_device *pdev)
138{
139 struct device *dev = &pdev->dev;
140 struct altera_hps2fpga_data *priv;
141 const struct of_device_id *of_id;
142 u32 enable;
143 int ret;
144
145 of_id = of_match_device(altera_fpga_of_match, dev);
146 priv = (struct altera_hps2fpga_data *)of_id->data;
147
148 priv->bridge_reset = of_reset_control_get_by_index(dev->of_node, 0);
149 if (IS_ERR(priv->bridge_reset)) {
150 dev_err(dev, "Could not get %s reset control\n", priv->name);
151 return PTR_ERR(priv->bridge_reset);
152 }
153
154 if (priv->remap_mask) {
155 priv->l3reg = syscon_regmap_lookup_by_compatible("altr,l3regs");
156 if (IS_ERR(priv->l3reg)) {
157 dev_err(dev, "regmap for altr,l3regs lookup failed\n");
158 return PTR_ERR(priv->l3reg);
159 }
160 }
161
162 priv->clk = devm_clk_get(dev, NULL);
163 if (IS_ERR(priv->clk)) {
164 dev_err(dev, "no clock specified\n");
165 return PTR_ERR(priv->clk);
166 }
167
168 ret = clk_prepare_enable(priv->clk);
169 if (ret) {
170 dev_err(dev, "could not enable clock\n");
171 return -EBUSY;
172 }
173
174 spin_lock_init(&l3_remap_lock);
175
176 if (!of_property_read_u32(dev->of_node, "bridge-enable", &enable)) {
177 if (enable > 1) {
178 dev_warn(dev, "invalid bridge-enable %u > 1\n", enable);
179 } else {
180 dev_info(dev, "%s bridge\n",
181 (enable ? "enabling" : "disabling"));
182
183 ret = _alt_hps2fpga_enable_set(priv, enable);
184 if (ret) {
185 fpga_bridge_unregister(&pdev->dev);
186 return ret;
187 }
188 }
189 }
190
191 return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
192 priv);
193}
194
195static int alt_fpga_bridge_remove(struct platform_device *pdev)
196{
197 struct fpga_bridge *bridge = platform_get_drvdata(pdev);
198 struct altera_hps2fpga_data *priv = bridge->priv;
199
200 fpga_bridge_unregister(&pdev->dev);
201
202 clk_disable_unprepare(priv->clk);
203
204 return 0;
205}
206
207MODULE_DEVICE_TABLE(of, altera_fpga_of_match);
208
209static struct platform_driver alt_fpga_bridge_driver = {
210 .probe = alt_fpga_bridge_probe,
211 .remove = alt_fpga_bridge_remove,
212 .driver = {
213 .name = "altera_hps2fpga_bridge",
214 .of_match_table = of_match_ptr(altera_fpga_of_match),
215 },
216};
217
218module_platform_driver(alt_fpga_bridge_driver);
219
220MODULE_DESCRIPTION("Altera SoCFPGA HPS to FPGA Bridge");
221MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>");
222MODULE_LICENSE("GPL v2");