diff options
author | Ezequiel Garcia <ezequiel.garcia@free-electrons.com> | 2013-04-23 15:21:26 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-05-21 13:11:56 -0400 |
commit | 3edad321b1bd2e6c8b5f38146c115c8982438f06 (patch) | |
tree | d58e2393d49e1f1af0797778d1ed87fb6780e8dc /drivers/memory | |
parent | ced9017a4fc7ecf35a9c0c0bd8e46d14876b9fd1 (diff) |
drivers: memory: Introduce Marvell EBU Device Bus driver
Marvell EBU SoCs such as Armada 370/XP, Orion5x (88f5xxx) and
Discovery (mv78xx0) supports a Device Bus controller to access several
kinds of memories and I/O devices (NOR, NAND, SRAM, FPGA).
This commit adds a driver to handle this controller. So far only
Armada 370, Armada XP and Discovery SoCs are supported.
The driver must be registered through a device tree node;
as explained in the binding document.
For each child node in the device tree, this driver will:
* set timing parameters
* register a child device
* setup an address decoding window, using the mbus driver
Keep in mind the address decoding window setup is only a temporary hack.
This code will be removed from this devbus driver as soon as a proper device
tree binding for the mbus driver is added.
Signed-off-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Jason Cooper <jason@lakedaemon.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/memory')
-rw-r--r-- | drivers/memory/Kconfig | 10 | ||||
-rw-r--r-- | drivers/memory/Makefile | 1 | ||||
-rw-r--r-- | drivers/memory/mvebu-devbus.c | 340 |
3 files changed, 351 insertions, 0 deletions
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 067f31174a0e..29a11db365bc 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig | |||
@@ -20,6 +20,16 @@ config TI_EMIF | |||
20 | parameters and other settings during frequency, voltage and | 20 | parameters and other settings during frequency, voltage and |
21 | temperature changes | 21 | temperature changes |
22 | 22 | ||
23 | config MVEBU_DEVBUS | ||
24 | bool "Marvell EBU Device Bus Controller" | ||
25 | default y | ||
26 | depends on PLAT_ORION && OF | ||
27 | help | ||
28 | This driver is for the Device Bus controller available in some | ||
29 | Marvell EBU SoCs such as Discovery (mv78xx0), Orion (88f5xxx) and | ||
30 | Armada 370 and Armada XP. This controller allows to handle flash | ||
31 | devices such as NOR, NAND, SRAM, and FPGA. | ||
32 | |||
23 | config TEGRA20_MC | 33 | config TEGRA20_MC |
24 | bool "Tegra20 Memory Controller(MC) driver" | 34 | bool "Tegra20 Memory Controller(MC) driver" |
25 | default y | 35 | default y |
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 9cce5d70ed52..969d923dad93 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile | |||
@@ -6,5 +6,6 @@ ifeq ($(CONFIG_DDR),y) | |||
6 | obj-$(CONFIG_OF) += of_memory.o | 6 | obj-$(CONFIG_OF) += of_memory.o |
7 | endif | 7 | endif |
8 | obj-$(CONFIG_TI_EMIF) += emif.o | 8 | obj-$(CONFIG_TI_EMIF) += emif.o |
9 | obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o | ||
9 | obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o | 10 | obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o |
10 | obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o | 11 | obj-$(CONFIG_TEGRA30_MC) += tegra30-mc.o |
diff --git a/drivers/memory/mvebu-devbus.c b/drivers/memory/mvebu-devbus.c new file mode 100644 index 000000000000..978e8e3abc5c --- /dev/null +++ b/drivers/memory/mvebu-devbus.c | |||
@@ -0,0 +1,340 @@ | |||
1 | /* | ||
2 | * Marvell EBU SoC Device Bus Controller | ||
3 | * (memory controller for NOR/NAND/SRAM/FPGA devices) | ||
4 | * | ||
5 | * Copyright (C) 2013 Marvell | ||
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 as published by | ||
9 | * the Free Software Foundation version 2 of the License. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/err.h> | ||
25 | #include <linux/io.h> | ||
26 | #include <linux/clk.h> | ||
27 | #include <linux/mbus.h> | ||
28 | #include <linux/of_platform.h> | ||
29 | #include <linux/of_address.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | |||
32 | /* Register definitions */ | ||
33 | #define DEV_WIDTH_BIT 30 | ||
34 | #define BADR_SKEW_BIT 28 | ||
35 | #define RD_HOLD_BIT 23 | ||
36 | #define ACC_NEXT_BIT 17 | ||
37 | #define RD_SETUP_BIT 12 | ||
38 | #define ACC_FIRST_BIT 6 | ||
39 | |||
40 | #define SYNC_ENABLE_BIT 24 | ||
41 | #define WR_HIGH_BIT 16 | ||
42 | #define WR_LOW_BIT 8 | ||
43 | |||
44 | #define READ_PARAM_OFFSET 0x0 | ||
45 | #define WRITE_PARAM_OFFSET 0x4 | ||
46 | |||
47 | static const char * const devbus_wins[] = { | ||
48 | "devbus-boot", | ||
49 | "devbus-cs0", | ||
50 | "devbus-cs1", | ||
51 | "devbus-cs2", | ||
52 | "devbus-cs3", | ||
53 | }; | ||
54 | |||
55 | struct devbus_read_params { | ||
56 | u32 bus_width; | ||
57 | u32 badr_skew; | ||
58 | u32 turn_off; | ||
59 | u32 acc_first; | ||
60 | u32 acc_next; | ||
61 | u32 rd_setup; | ||
62 | u32 rd_hold; | ||
63 | }; | ||
64 | |||
65 | struct devbus_write_params { | ||
66 | u32 sync_enable; | ||
67 | u32 wr_high; | ||
68 | u32 wr_low; | ||
69 | u32 ale_wr; | ||
70 | }; | ||
71 | |||
72 | struct devbus { | ||
73 | struct device *dev; | ||
74 | void __iomem *base; | ||
75 | unsigned long tick_ps; | ||
76 | }; | ||
77 | |||
78 | static int get_timing_param_ps(struct devbus *devbus, | ||
79 | struct device_node *node, | ||
80 | const char *name, | ||
81 | u32 *ticks) | ||
82 | { | ||
83 | u32 time_ps; | ||
84 | int err; | ||
85 | |||
86 | err = of_property_read_u32(node, name, &time_ps); | ||
87 | if (err < 0) { | ||
88 | dev_err(devbus->dev, "%s has no '%s' property\n", | ||
89 | name, node->full_name); | ||
90 | return err; | ||
91 | } | ||
92 | |||
93 | *ticks = (time_ps + devbus->tick_ps - 1) / devbus->tick_ps; | ||
94 | |||
95 | dev_dbg(devbus->dev, "%s: %u ps -> 0x%x\n", | ||
96 | name, time_ps, *ticks); | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | static int devbus_set_timing_params(struct devbus *devbus, | ||
101 | struct device_node *node) | ||
102 | { | ||
103 | struct devbus_read_params r; | ||
104 | struct devbus_write_params w; | ||
105 | u32 value; | ||
106 | int err; | ||
107 | |||
108 | dev_dbg(devbus->dev, "Setting timing parameter, tick is %lu ps\n", | ||
109 | devbus->tick_ps); | ||
110 | |||
111 | /* Get read timings */ | ||
112 | err = of_property_read_u32(node, "devbus,bus-width", &r.bus_width); | ||
113 | if (err < 0) { | ||
114 | dev_err(devbus->dev, | ||
115 | "%s has no 'devbus,bus-width' property\n", | ||
116 | node->full_name); | ||
117 | return err; | ||
118 | } | ||
119 | /* Convert bit width to byte width */ | ||
120 | r.bus_width /= 8; | ||
121 | |||
122 | err = get_timing_param_ps(devbus, node, "devbus,badr-skew-ps", | ||
123 | &r.badr_skew); | ||
124 | if (err < 0) | ||
125 | return err; | ||
126 | |||
127 | err = get_timing_param_ps(devbus, node, "devbus,turn-off-ps", | ||
128 | &r.turn_off); | ||
129 | if (err < 0) | ||
130 | return err; | ||
131 | |||
132 | err = get_timing_param_ps(devbus, node, "devbus,acc-first-ps", | ||
133 | &r.acc_first); | ||
134 | if (err < 0) | ||
135 | return err; | ||
136 | |||
137 | err = get_timing_param_ps(devbus, node, "devbus,acc-next-ps", | ||
138 | &r.acc_next); | ||
139 | if (err < 0) | ||
140 | return err; | ||
141 | |||
142 | err = get_timing_param_ps(devbus, node, "devbus,rd-setup-ps", | ||
143 | &r.rd_setup); | ||
144 | if (err < 0) | ||
145 | return err; | ||
146 | |||
147 | err = get_timing_param_ps(devbus, node, "devbus,rd-hold-ps", | ||
148 | &r.rd_hold); | ||
149 | if (err < 0) | ||
150 | return err; | ||
151 | |||
152 | /* Get write timings */ | ||
153 | err = of_property_read_u32(node, "devbus,sync-enable", | ||
154 | &w.sync_enable); | ||
155 | if (err < 0) { | ||
156 | dev_err(devbus->dev, | ||
157 | "%s has no 'devbus,sync-enable' property\n", | ||
158 | node->full_name); | ||
159 | return err; | ||
160 | } | ||
161 | |||
162 | err = get_timing_param_ps(devbus, node, "devbus,ale-wr-ps", | ||
163 | &w.ale_wr); | ||
164 | if (err < 0) | ||
165 | return err; | ||
166 | |||
167 | err = get_timing_param_ps(devbus, node, "devbus,wr-low-ps", | ||
168 | &w.wr_low); | ||
169 | if (err < 0) | ||
170 | return err; | ||
171 | |||
172 | err = get_timing_param_ps(devbus, node, "devbus,wr-high-ps", | ||
173 | &w.wr_high); | ||
174 | if (err < 0) | ||
175 | return err; | ||
176 | |||
177 | /* Set read timings */ | ||
178 | value = r.bus_width << DEV_WIDTH_BIT | | ||
179 | r.badr_skew << BADR_SKEW_BIT | | ||
180 | r.rd_hold << RD_HOLD_BIT | | ||
181 | r.acc_next << ACC_NEXT_BIT | | ||
182 | r.rd_setup << RD_SETUP_BIT | | ||
183 | r.acc_first << ACC_FIRST_BIT | | ||
184 | r.turn_off; | ||
185 | |||
186 | dev_dbg(devbus->dev, "read parameters register 0x%p = 0x%x\n", | ||
187 | devbus->base + READ_PARAM_OFFSET, | ||
188 | value); | ||
189 | |||
190 | writel(value, devbus->base + READ_PARAM_OFFSET); | ||
191 | |||
192 | /* Set write timings */ | ||
193 | value = w.sync_enable << SYNC_ENABLE_BIT | | ||
194 | w.wr_low << WR_LOW_BIT | | ||
195 | w.wr_high << WR_HIGH_BIT | | ||
196 | w.ale_wr; | ||
197 | |||
198 | dev_dbg(devbus->dev, "write parameters register: 0x%p = 0x%x\n", | ||
199 | devbus->base + WRITE_PARAM_OFFSET, | ||
200 | value); | ||
201 | |||
202 | writel(value, devbus->base + WRITE_PARAM_OFFSET); | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static int mvebu_devbus_probe(struct platform_device *pdev) | ||
208 | { | ||
209 | struct device *dev = &pdev->dev; | ||
210 | struct device_node *node = pdev->dev.of_node; | ||
211 | struct device_node *parent; | ||
212 | struct devbus *devbus; | ||
213 | struct resource *res; | ||
214 | struct clk *clk; | ||
215 | unsigned long rate; | ||
216 | const __be32 *ranges; | ||
217 | int err, cs; | ||
218 | int addr_cells, p_addr_cells, size_cells; | ||
219 | int ranges_len, tuple_len; | ||
220 | u32 base, size; | ||
221 | |||
222 | devbus = devm_kzalloc(&pdev->dev, sizeof(struct devbus), GFP_KERNEL); | ||
223 | if (!devbus) | ||
224 | return -ENOMEM; | ||
225 | |||
226 | devbus->dev = dev; | ||
227 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
228 | devbus->base = devm_ioremap_resource(&pdev->dev, res); | ||
229 | if (IS_ERR(devbus->base)) | ||
230 | return PTR_ERR(devbus->base); | ||
231 | |||
232 | clk = devm_clk_get(&pdev->dev, NULL); | ||
233 | if (IS_ERR(clk)) | ||
234 | return PTR_ERR(clk); | ||
235 | clk_prepare_enable(clk); | ||
236 | |||
237 | /* | ||
238 | * Obtain clock period in picoseconds, | ||
239 | * we need this in order to convert timing | ||
240 | * parameters from cycles to picoseconds. | ||
241 | */ | ||
242 | rate = clk_get_rate(clk) / 1000; | ||
243 | devbus->tick_ps = 1000000000 / rate; | ||
244 | |||
245 | /* Read the device tree node and set the new timing parameters */ | ||
246 | err = devbus_set_timing_params(devbus, node); | ||
247 | if (err < 0) | ||
248 | return err; | ||
249 | |||
250 | /* | ||
251 | * Allocate an address window for this device. | ||
252 | * If the device probing fails, then we won't be able to | ||
253 | * remove the allocated address decoding window. | ||
254 | * | ||
255 | * FIXME: This is only a temporary hack! We need to do this here | ||
256 | * because we still don't have device tree bindings for mbus. | ||
257 | * Once that support is added, we will declare these address windows | ||
258 | * statically in the device tree, and remove the window configuration | ||
259 | * from here. | ||
260 | */ | ||
261 | |||
262 | /* | ||
263 | * Get the CS to choose the window string. | ||
264 | * This is a bit hacky, but it will be removed once the | ||
265 | * address windows are declared in the device tree. | ||
266 | */ | ||
267 | cs = (((unsigned long)devbus->base) % 0x400) / 8; | ||
268 | |||
269 | /* | ||
270 | * Parse 'ranges' property to obtain a (base,size) window tuple. | ||
271 | * This will be removed once the address windows | ||
272 | * are declared in the device tree. | ||
273 | */ | ||
274 | parent = of_get_parent(node); | ||
275 | if (!parent) | ||
276 | return -EINVAL; | ||
277 | |||
278 | p_addr_cells = of_n_addr_cells(parent); | ||
279 | of_node_put(parent); | ||
280 | |||
281 | addr_cells = of_n_addr_cells(node); | ||
282 | size_cells = of_n_size_cells(node); | ||
283 | tuple_len = (p_addr_cells + addr_cells + size_cells) * sizeof(__be32); | ||
284 | |||
285 | ranges = of_get_property(node, "ranges", &ranges_len); | ||
286 | if (ranges == NULL || ranges_len != tuple_len) | ||
287 | return -EINVAL; | ||
288 | |||
289 | base = of_translate_address(node, ranges + addr_cells); | ||
290 | if (base == OF_BAD_ADDR) | ||
291 | return -EINVAL; | ||
292 | size = of_read_number(ranges + addr_cells + p_addr_cells, size_cells); | ||
293 | |||
294 | /* | ||
295 | * Create an mbus address windows. | ||
296 | * FIXME: Remove this, together with the above code, once the | ||
297 | * address windows are declared in the device tree. | ||
298 | */ | ||
299 | err = mvebu_mbus_add_window(devbus_wins[cs], base, size); | ||
300 | if (err < 0) | ||
301 | return err; | ||
302 | |||
303 | /* | ||
304 | * We need to create a child device explicitly from here to | ||
305 | * guarantee that the child will be probed after the timing | ||
306 | * parameters for the bus are written. | ||
307 | */ | ||
308 | err = of_platform_populate(node, NULL, NULL, dev); | ||
309 | if (err < 0) { | ||
310 | mvebu_mbus_del_window(base, size); | ||
311 | return err; | ||
312 | } | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | static const struct of_device_id mvebu_devbus_of_match[] = { | ||
318 | { .compatible = "marvell,mvebu-devbus" }, | ||
319 | {}, | ||
320 | }; | ||
321 | MODULE_DEVICE_TABLE(of, mvebu_devbus_of_match); | ||
322 | |||
323 | static struct platform_driver mvebu_devbus_driver = { | ||
324 | .probe = mvebu_devbus_probe, | ||
325 | .driver = { | ||
326 | .name = "mvebu-devbus", | ||
327 | .owner = THIS_MODULE, | ||
328 | .of_match_table = mvebu_devbus_of_match, | ||
329 | }, | ||
330 | }; | ||
331 | |||
332 | static int __init mvebu_devbus_init(void) | ||
333 | { | ||
334 | return platform_driver_register(&mvebu_devbus_driver); | ||
335 | } | ||
336 | module_init(mvebu_devbus_init); | ||
337 | |||
338 | MODULE_LICENSE("GPL v2"); | ||
339 | MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>"); | ||
340 | MODULE_DESCRIPTION("Marvell EBU SoC Device Bus controller"); | ||