1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices |
4 | * |
5 | * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. |
6 | * |
7 | * Includes this patch from the mailing list: |
8 | * fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters |
9 | * Signed-off-by: Anatolij Gustschin <agust@denx.de> |
10 | */ |
11 | |
12 | /* |
13 | * This driver manages bridges on a Altera SOCFPGA between the ARM host |
14 | * processor system (HPS) and the embedded FPGA. |
15 | * |
16 | * This driver supports enabling and disabling of the configured ports, which |
17 | * allows for safe reprogramming of the FPGA, assuming that the new FPGA image |
18 | * uses the same port configuration. Bridges must be disabled before |
19 | * reprogramming the FPGA and re-enabled after the FPGA has been programmed. |
20 | */ |
21 | |
22 | #include <linux/clk.h> |
23 | #include <linux/fpga/fpga-bridge.h> |
24 | #include <linux/kernel.h> |
25 | #include <linux/mfd/syscon.h> |
26 | #include <linux/module.h> |
27 | #include <linux/of.h> |
28 | #include <linux/property.h> |
29 | #include <linux/regmap.h> |
30 | #include <linux/reset.h> |
31 | #include <linux/spinlock.h> |
32 | |
33 | #define ALT_L3_REMAP_OFST 0x0 |
34 | #define ALT_L3_REMAP_MPUZERO_MSK 0x00000001 |
35 | #define ALT_L3_REMAP_H2F_MSK 0x00000008 |
36 | #define ALT_L3_REMAP_LWH2F_MSK 0x00000010 |
37 | |
38 | #define HPS2FPGA_BRIDGE_NAME "hps2fpga" |
39 | #define LWHPS2FPGA_BRIDGE_NAME "lwhps2fpga" |
40 | #define FPGA2HPS_BRIDGE_NAME "fpga2hps" |
41 | |
42 | struct altera_hps2fpga_data { |
43 | const char *name; |
44 | struct reset_control *bridge_reset; |
45 | struct regmap *l3reg; |
46 | unsigned int remap_mask; |
47 | struct clk *clk; |
48 | }; |
49 | |
50 | static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge) |
51 | { |
52 | struct altera_hps2fpga_data *priv = bridge->priv; |
53 | |
54 | return reset_control_status(rstc: priv->bridge_reset); |
55 | } |
56 | |
57 | /* The L3 REMAP register is write only, so keep a cached value. */ |
58 | static unsigned int l3_remap_shadow; |
59 | static DEFINE_SPINLOCK(l3_remap_lock); |
60 | |
61 | static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, |
62 | bool enable) |
63 | { |
64 | unsigned long flags; |
65 | int ret; |
66 | |
67 | /* bring bridge out of reset */ |
68 | if (enable) |
69 | ret = reset_control_deassert(rstc: priv->bridge_reset); |
70 | else |
71 | ret = reset_control_assert(rstc: priv->bridge_reset); |
72 | if (ret) |
73 | return ret; |
74 | |
75 | /* Allow bridge to be visible to L3 masters or not */ |
76 | if (priv->remap_mask) { |
77 | spin_lock_irqsave(&l3_remap_lock, flags); |
78 | l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK; |
79 | |
80 | if (enable) |
81 | l3_remap_shadow |= priv->remap_mask; |
82 | else |
83 | l3_remap_shadow &= ~priv->remap_mask; |
84 | |
85 | ret = regmap_write(map: priv->l3reg, ALT_L3_REMAP_OFST, |
86 | val: l3_remap_shadow); |
87 | spin_unlock_irqrestore(lock: &l3_remap_lock, flags); |
88 | } |
89 | |
90 | return ret; |
91 | } |
92 | |
93 | static int alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable) |
94 | { |
95 | return _alt_hps2fpga_enable_set(priv: bridge->priv, enable); |
96 | } |
97 | |
98 | static const struct fpga_bridge_ops altera_hps2fpga_br_ops = { |
99 | .enable_set = alt_hps2fpga_enable_set, |
100 | .enable_show = alt_hps2fpga_enable_show, |
101 | }; |
102 | |
103 | static struct altera_hps2fpga_data hps2fpga_data = { |
104 | .name = HPS2FPGA_BRIDGE_NAME, |
105 | .remap_mask = ALT_L3_REMAP_H2F_MSK, |
106 | }; |
107 | |
108 | static struct altera_hps2fpga_data lwhps2fpga_data = { |
109 | .name = LWHPS2FPGA_BRIDGE_NAME, |
110 | .remap_mask = ALT_L3_REMAP_LWH2F_MSK, |
111 | }; |
112 | |
113 | static struct altera_hps2fpga_data fpga2hps_data = { |
114 | .name = FPGA2HPS_BRIDGE_NAME, |
115 | }; |
116 | |
117 | static const struct of_device_id altera_fpga_of_match[] = { |
118 | { .compatible = "altr,socfpga-hps2fpga-bridge" , |
119 | .data = &hps2fpga_data }, |
120 | { .compatible = "altr,socfpga-lwhps2fpga-bridge" , |
121 | .data = &lwhps2fpga_data }, |
122 | { .compatible = "altr,socfpga-fpga2hps-bridge" , |
123 | .data = &fpga2hps_data }, |
124 | {}, |
125 | }; |
126 | |
127 | static int alt_fpga_bridge_probe(struct platform_device *pdev) |
128 | { |
129 | struct device *dev = &pdev->dev; |
130 | struct altera_hps2fpga_data *priv; |
131 | struct fpga_bridge *br; |
132 | u32 enable; |
133 | int ret; |
134 | |
135 | priv = (struct altera_hps2fpga_data *)device_get_match_data(dev); |
136 | |
137 | priv->bridge_reset = of_reset_control_get_exclusive_by_index(node: dev->of_node, |
138 | index: 0); |
139 | if (IS_ERR(ptr: priv->bridge_reset)) { |
140 | dev_err(dev, "Could not get %s reset control\n" , priv->name); |
141 | return PTR_ERR(ptr: priv->bridge_reset); |
142 | } |
143 | |
144 | if (priv->remap_mask) { |
145 | priv->l3reg = syscon_regmap_lookup_by_compatible(s: "altr,l3regs" ); |
146 | if (IS_ERR(ptr: priv->l3reg)) { |
147 | dev_err(dev, "regmap for altr,l3regs lookup failed\n" ); |
148 | return PTR_ERR(ptr: priv->l3reg); |
149 | } |
150 | } |
151 | |
152 | priv->clk = devm_clk_get(dev, NULL); |
153 | if (IS_ERR(ptr: priv->clk)) { |
154 | dev_err(dev, "no clock specified\n" ); |
155 | return PTR_ERR(ptr: priv->clk); |
156 | } |
157 | |
158 | ret = clk_prepare_enable(clk: priv->clk); |
159 | if (ret) { |
160 | dev_err(dev, "could not enable clock\n" ); |
161 | return -EBUSY; |
162 | } |
163 | |
164 | if (!of_property_read_u32(np: dev->of_node, propname: "bridge-enable" , out_value: &enable)) { |
165 | if (enable > 1) { |
166 | dev_warn(dev, "invalid bridge-enable %u > 1\n" , enable); |
167 | } else { |
168 | dev_info(dev, "%s bridge\n" , |
169 | (enable ? "enabling" : "disabling" )); |
170 | |
171 | ret = _alt_hps2fpga_enable_set(priv, enable); |
172 | if (ret) |
173 | goto err; |
174 | } |
175 | } |
176 | |
177 | br = fpga_bridge_register(parent: dev, name: priv->name, |
178 | br_ops: &altera_hps2fpga_br_ops, priv); |
179 | if (IS_ERR(ptr: br)) { |
180 | ret = PTR_ERR(ptr: br); |
181 | goto err; |
182 | } |
183 | |
184 | platform_set_drvdata(pdev, data: br); |
185 | |
186 | return 0; |
187 | |
188 | err: |
189 | clk_disable_unprepare(clk: priv->clk); |
190 | |
191 | return ret; |
192 | } |
193 | |
194 | static int alt_fpga_bridge_remove(struct platform_device *pdev) |
195 | { |
196 | struct fpga_bridge *bridge = platform_get_drvdata(pdev); |
197 | struct altera_hps2fpga_data *priv = bridge->priv; |
198 | |
199 | fpga_bridge_unregister(br: bridge); |
200 | |
201 | clk_disable_unprepare(clk: priv->clk); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | MODULE_DEVICE_TABLE(of, altera_fpga_of_match); |
207 | |
208 | static struct platform_driver alt_fpga_bridge_driver = { |
209 | .probe = alt_fpga_bridge_probe, |
210 | .remove = alt_fpga_bridge_remove, |
211 | .driver = { |
212 | .name = "altera_hps2fpga_bridge" , |
213 | .of_match_table = of_match_ptr(altera_fpga_of_match), |
214 | }, |
215 | }; |
216 | |
217 | module_platform_driver(alt_fpga_bridge_driver); |
218 | |
219 | MODULE_DESCRIPTION("Altera SoCFPGA HPS to FPGA Bridge" ); |
220 | MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>" ); |
221 | MODULE_LICENSE("GPL v2" ); |
222 | |