forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mvebu-mbus.c
1378 lines (1153 loc) · 36.4 KB
/
mvebu-mbus.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Address map functions for Marvell EBU SoCs (Kirkwood, Armada
* 370/XP, Dove, Orion5x and MV78xx0)
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*
* The Marvell EBU SoCs have a configurable physical address space:
* the physical address at which certain devices (PCIe, NOR, NAND,
* etc.) sit can be configured. The configuration takes place through
* two sets of registers:
*
* - One to configure the access of the CPU to the devices. Depending
* on the families, there are between 8 and 20 configurable windows,
* each can be use to create a physical memory window that maps to a
* specific device. Devices are identified by a tuple (target,
* attribute).
*
* - One to configure the access to the CPU to the SDRAM. There are
* either 2 (for Dove) or 4 (for other families) windows to map the
* SDRAM into the physical address space.
*
* This driver:
*
* - Reads out the SDRAM address decoding windows at initialization
* time, and fills the mvebu_mbus_dram_info structure with these
* informations. The exported function mv_mbus_dram_info() allow
* device drivers to get those informations related to the SDRAM
* address decoding windows. This is because devices also have their
* own windows (configured through registers that are part of each
* device register space), and therefore the drivers for Marvell
* devices have to configure those device -> SDRAM windows to ensure
* that DMA works properly.
*
* - Provides an API for platform code or device drivers to
* dynamically add or remove address decoding windows for the CPU ->
* device accesses. This API is mvebu_mbus_add_window_by_id(),
* mvebu_mbus_add_window_remap_by_id() and
* mvebu_mbus_del_window().
*
* - Provides a debugfs interface in /sys/kernel/debug/mvebu-mbus/ to
* see the list of CPU -> SDRAM windows and their configuration
* (file 'sdram') and the list of CPU -> devices windows and their
* configuration (file 'devices').
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mbus.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/debugfs.h>
#include <linux/log2.h>
#include <linux/memblock.h>
#include <linux/syscore_ops.h>
/*
* DDR target is the same on all platforms.
*/
#define TARGET_DDR 0
/*
* CPU Address Decode Windows registers
*/
#define WIN_CTRL_OFF 0x0000
#define WIN_CTRL_ENABLE BIT(0)
/* Only on HW I/O coherency capable platforms */
#define WIN_CTRL_SYNCBARRIER BIT(1)
#define WIN_CTRL_TGT_MASK 0xf0
#define WIN_CTRL_TGT_SHIFT 4
#define WIN_CTRL_ATTR_MASK 0xff00
#define WIN_CTRL_ATTR_SHIFT 8
#define WIN_CTRL_SIZE_MASK 0xffff0000
#define WIN_CTRL_SIZE_SHIFT 16
#define WIN_BASE_OFF 0x0004
#define WIN_BASE_LOW 0xffff0000
#define WIN_BASE_HIGH 0xf
#define WIN_REMAP_LO_OFF 0x0008
#define WIN_REMAP_LOW 0xffff0000
#define WIN_REMAP_HI_OFF 0x000c
#define UNIT_SYNC_BARRIER_OFF 0x84
#define UNIT_SYNC_BARRIER_ALL 0xFFFF
#define ATTR_HW_COHERENCY (0x1 << 4)
#define DDR_BASE_CS_OFF(n) (0x0000 + ((n) << 3))
#define DDR_BASE_CS_HIGH_MASK 0xf
#define DDR_BASE_CS_LOW_MASK 0xff000000
#define DDR_SIZE_CS_OFF(n) (0x0004 + ((n) << 3))
#define DDR_SIZE_ENABLED BIT(0)
#define DDR_SIZE_CS_MASK 0x1c
#define DDR_SIZE_CS_SHIFT 2
#define DDR_SIZE_MASK 0xff000000
#define DOVE_DDR_BASE_CS_OFF(n) ((n) << 4)
/* Relative to mbusbridge_base */
#define MBUS_BRIDGE_CTRL_OFF 0x0
#define MBUS_BRIDGE_BASE_OFF 0x4
/* Maximum number of windows, for all known platforms */
#define MBUS_WINS_MAX 20
struct mvebu_mbus_state;
struct mvebu_mbus_soc_data {
unsigned int num_wins;
bool has_mbus_bridge;
unsigned int (*win_cfg_offset)(const int win);
unsigned int (*win_remap_offset)(const int win);
void (*setup_cpu_target)(struct mvebu_mbus_state *s);
int (*save_cpu_target)(struct mvebu_mbus_state *s,
u32 __iomem *store_addr);
int (*show_cpu_target)(struct mvebu_mbus_state *s,
struct seq_file *seq, void *v);
};
/*
* Used to store the state of one MBus window accross suspend/resume.
*/
struct mvebu_mbus_win_data {
u32 ctrl;
u32 base;
u32 remap_lo;
u32 remap_hi;
};
struct mvebu_mbus_state {
void __iomem *mbuswins_base;
void __iomem *sdramwins_base;
void __iomem *mbusbridge_base;
phys_addr_t sdramwins_phys_base;
struct dentry *debugfs_root;
struct dentry *debugfs_sdram;
struct dentry *debugfs_devs;
struct resource pcie_mem_aperture;
struct resource pcie_io_aperture;
const struct mvebu_mbus_soc_data *soc;
int hw_io_coherency;
/* Used during suspend/resume */
u32 mbus_bridge_ctrl;
u32 mbus_bridge_base;
struct mvebu_mbus_win_data wins[MBUS_WINS_MAX];
};
static struct mvebu_mbus_state mbus_state;
/*
* We provide two variants of the mv_mbus_dram_info() function:
*
* - The normal one, where the described DRAM ranges may overlap with
* the I/O windows, but for which the DRAM ranges are guaranteed to
* have a power of two size. Such ranges are suitable for the DMA
* masters that only DMA between the RAM and the device, which is
* actually all devices except the crypto engines.
*
* - The 'nooverlap' one, where the described DRAM ranges are
* guaranteed to not overlap with the I/O windows, but for which the
* DRAM ranges will not have power of two sizes. They will only be
* aligned on a 64 KB boundary, and have a size multiple of 64
* KB. Such ranges are suitable for the DMA masters that DMA between
* the crypto SRAM (which is mapped through an I/O window) and a
* device. This is the case for the crypto engines.
*/
static struct mbus_dram_target_info mvebu_mbus_dram_info;
static struct mbus_dram_target_info mvebu_mbus_dram_info_nooverlap;
const struct mbus_dram_target_info *mv_mbus_dram_info(void)
{
return &mvebu_mbus_dram_info;
}
EXPORT_SYMBOL_GPL(mv_mbus_dram_info);
const struct mbus_dram_target_info *mv_mbus_dram_info_nooverlap(void)
{
return &mvebu_mbus_dram_info_nooverlap;
}
EXPORT_SYMBOL_GPL(mv_mbus_dram_info_nooverlap);
/* Checks whether the given window has remap capability */
static bool mvebu_mbus_window_is_remappable(struct mvebu_mbus_state *mbus,
const int win)
{
return mbus->soc->win_remap_offset(win) != MVEBU_MBUS_NO_REMAP;
}
/*
* Functions to manipulate the address decoding windows
*/
static void mvebu_mbus_read_window(struct mvebu_mbus_state *mbus,
int win, int *enabled, u64 *base,
u32 *size, u8 *target, u8 *attr,
u64 *remap)
{
void __iomem *addr = mbus->mbuswins_base +
mbus->soc->win_cfg_offset(win);
u32 basereg = readl(addr + WIN_BASE_OFF);
u32 ctrlreg = readl(addr + WIN_CTRL_OFF);
if (!(ctrlreg & WIN_CTRL_ENABLE)) {
*enabled = 0;
return;
}
*enabled = 1;
*base = ((u64)basereg & WIN_BASE_HIGH) << 32;
*base |= (basereg & WIN_BASE_LOW);
*size = (ctrlreg | ~WIN_CTRL_SIZE_MASK) + 1;
if (target)
*target = (ctrlreg & WIN_CTRL_TGT_MASK) >> WIN_CTRL_TGT_SHIFT;
if (attr)
*attr = (ctrlreg & WIN_CTRL_ATTR_MASK) >> WIN_CTRL_ATTR_SHIFT;
if (remap) {
if (mvebu_mbus_window_is_remappable(mbus, win)) {
u32 remap_low, remap_hi;
void __iomem *addr_rmp = mbus->mbuswins_base +
mbus->soc->win_remap_offset(win);
remap_low = readl(addr_rmp + WIN_REMAP_LO_OFF);
remap_hi = readl(addr_rmp + WIN_REMAP_HI_OFF);
*remap = ((u64)remap_hi << 32) | remap_low;
} else
*remap = 0;
}
}
static void mvebu_mbus_disable_window(struct mvebu_mbus_state *mbus,
int win)
{
void __iomem *addr;
addr = mbus->mbuswins_base + mbus->soc->win_cfg_offset(win);
writel(0, addr + WIN_BASE_OFF);
writel(0, addr + WIN_CTRL_OFF);
if (mvebu_mbus_window_is_remappable(mbus, win)) {
addr = mbus->mbuswins_base + mbus->soc->win_remap_offset(win);
writel(0, addr + WIN_REMAP_LO_OFF);
writel(0, addr + WIN_REMAP_HI_OFF);
}
}
/* Checks whether the given window number is available */
static int mvebu_mbus_window_is_free(struct mvebu_mbus_state *mbus,
const int win)
{
void __iomem *addr = mbus->mbuswins_base +
mbus->soc->win_cfg_offset(win);
u32 ctrl = readl(addr + WIN_CTRL_OFF);
return !(ctrl & WIN_CTRL_ENABLE);
}
/*
* Checks whether the given (base, base+size) area doesn't overlap an
* existing region
*/
static int mvebu_mbus_window_conflicts(struct mvebu_mbus_state *mbus,
phys_addr_t base, size_t size,
u8 target, u8 attr)
{
u64 end = (u64)base + size;
int win;
for (win = 0; win < mbus->soc->num_wins; win++) {
u64 wbase, wend;
u32 wsize;
u8 wtarget, wattr;
int enabled;
mvebu_mbus_read_window(mbus, win,
&enabled, &wbase, &wsize,
&wtarget, &wattr, NULL);
if (!enabled)
continue;
wend = wbase + wsize;
/*
* Check if the current window overlaps with the
* proposed physical range
*/
if ((u64)base < wend && end > wbase)
return 0;
}
return 1;
}
static int mvebu_mbus_find_window(struct mvebu_mbus_state *mbus,
phys_addr_t base, size_t size)
{
int win;
for (win = 0; win < mbus->soc->num_wins; win++) {
u64 wbase;
u32 wsize;
int enabled;
mvebu_mbus_read_window(mbus, win,
&enabled, &wbase, &wsize,
NULL, NULL, NULL);
if (!enabled)
continue;
if (base == wbase && size == wsize)
return win;
}
return -ENODEV;
}
static int mvebu_mbus_setup_window(struct mvebu_mbus_state *mbus,
int win, phys_addr_t base, size_t size,
phys_addr_t remap, u8 target,
u8 attr)
{
void __iomem *addr = mbus->mbuswins_base +
mbus->soc->win_cfg_offset(win);
u32 ctrl, remap_addr;
if (!is_power_of_2(size)) {
WARN(true, "Invalid MBus window size: 0x%zx\n", size);
return -EINVAL;
}
if ((base & (phys_addr_t)(size - 1)) != 0) {
WARN(true, "Invalid MBus base/size: %pa len 0x%zx\n", &base,
size);
return -EINVAL;
}
ctrl = ((size - 1) & WIN_CTRL_SIZE_MASK) |
(attr << WIN_CTRL_ATTR_SHIFT) |
(target << WIN_CTRL_TGT_SHIFT) |
WIN_CTRL_ENABLE;
if (mbus->hw_io_coherency)
ctrl |= WIN_CTRL_SYNCBARRIER;
writel(base & WIN_BASE_LOW, addr + WIN_BASE_OFF);
writel(ctrl, addr + WIN_CTRL_OFF);
if (mvebu_mbus_window_is_remappable(mbus, win)) {
void __iomem *addr_rmp = mbus->mbuswins_base +
mbus->soc->win_remap_offset(win);
if (remap == MVEBU_MBUS_NO_REMAP)
remap_addr = base;
else
remap_addr = remap;
writel(remap_addr & WIN_REMAP_LOW, addr_rmp + WIN_REMAP_LO_OFF);
writel(0, addr_rmp + WIN_REMAP_HI_OFF);
}
return 0;
}
static int mvebu_mbus_alloc_window(struct mvebu_mbus_state *mbus,
phys_addr_t base, size_t size,
phys_addr_t remap, u8 target,
u8 attr)
{
int win;
if (remap == MVEBU_MBUS_NO_REMAP) {
for (win = 0; win < mbus->soc->num_wins; win++) {
if (mvebu_mbus_window_is_remappable(mbus, win))
continue;
if (mvebu_mbus_window_is_free(mbus, win))
return mvebu_mbus_setup_window(mbus, win, base,
size, remap,
target, attr);
}
}
for (win = 0; win < mbus->soc->num_wins; win++) {
/* Skip window if need remap but is not supported */
if ((remap != MVEBU_MBUS_NO_REMAP) &&
!mvebu_mbus_window_is_remappable(mbus, win))
continue;
if (mvebu_mbus_window_is_free(mbus, win))
return mvebu_mbus_setup_window(mbus, win, base, size,
remap, target, attr);
}
return -ENOMEM;
}
/*
* Debugfs debugging
*/
/* Common function used for Dove, Kirkwood, Armada 370/XP and Orion 5x */
static int mvebu_sdram_debug_show_orion(struct mvebu_mbus_state *mbus,
struct seq_file *seq, void *v)
{
int i;
for (i = 0; i < 4; i++) {
u32 basereg = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
u32 sizereg = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
u64 base;
u32 size;
if (!(sizereg & DDR_SIZE_ENABLED)) {
seq_printf(seq, "[%d] disabled\n", i);
continue;
}
base = ((u64)basereg & DDR_BASE_CS_HIGH_MASK) << 32;
base |= basereg & DDR_BASE_CS_LOW_MASK;
size = (sizereg | ~DDR_SIZE_MASK);
seq_printf(seq, "[%d] %016llx - %016llx : cs%d\n",
i, (unsigned long long)base,
(unsigned long long)base + size + 1,
(sizereg & DDR_SIZE_CS_MASK) >> DDR_SIZE_CS_SHIFT);
}
return 0;
}
/* Special function for Dove */
static int mvebu_sdram_debug_show_dove(struct mvebu_mbus_state *mbus,
struct seq_file *seq, void *v)
{
int i;
for (i = 0; i < 2; i++) {
u32 map = readl(mbus->sdramwins_base + DOVE_DDR_BASE_CS_OFF(i));
u64 base;
u32 size;
if (!(map & 1)) {
seq_printf(seq, "[%d] disabled\n", i);
continue;
}
base = map & 0xff800000;
size = 0x100000 << (((map & 0x000f0000) >> 16) - 4);
seq_printf(seq, "[%d] %016llx - %016llx : cs%d\n",
i, (unsigned long long)base,
(unsigned long long)base + size, i);
}
return 0;
}
static int mvebu_sdram_debug_show(struct seq_file *seq, void *v)
{
struct mvebu_mbus_state *mbus = &mbus_state;
return mbus->soc->show_cpu_target(mbus, seq, v);
}
static int mvebu_sdram_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, mvebu_sdram_debug_show, inode->i_private);
}
static const struct file_operations mvebu_sdram_debug_fops = {
.open = mvebu_sdram_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int mvebu_devs_debug_show(struct seq_file *seq, void *v)
{
struct mvebu_mbus_state *mbus = &mbus_state;
int win;
for (win = 0; win < mbus->soc->num_wins; win++) {
u64 wbase, wremap;
u32 wsize;
u8 wtarget, wattr;
int enabled;
mvebu_mbus_read_window(mbus, win,
&enabled, &wbase, &wsize,
&wtarget, &wattr, &wremap);
if (!enabled) {
seq_printf(seq, "[%02d] disabled\n", win);
continue;
}
seq_printf(seq, "[%02d] %016llx - %016llx : %04x:%04x",
win, (unsigned long long)wbase,
(unsigned long long)(wbase + wsize), wtarget, wattr);
if (!is_power_of_2(wsize) ||
((wbase & (u64)(wsize - 1)) != 0))
seq_puts(seq, " (Invalid base/size!!)");
if (mvebu_mbus_window_is_remappable(mbus, win)) {
seq_printf(seq, " (remap %016llx)\n",
(unsigned long long)wremap);
} else
seq_printf(seq, "\n");
}
return 0;
}
static int mvebu_devs_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, mvebu_devs_debug_show, inode->i_private);
}
static const struct file_operations mvebu_devs_debug_fops = {
.open = mvebu_devs_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
/*
* SoC-specific functions and definitions
*/
static unsigned int generic_mbus_win_cfg_offset(int win)
{
return win << 4;
}
static unsigned int armada_370_xp_mbus_win_cfg_offset(int win)
{
/* The register layout is a bit annoying and the below code
* tries to cope with it.
* - At offset 0x0, there are the registers for the first 8
* windows, with 4 registers of 32 bits per window (ctrl,
* base, remap low, remap high)
* - Then at offset 0x80, there is a hole of 0x10 bytes for
* the internal registers base address and internal units
* sync barrier register.
* - Then at offset 0x90, there the registers for 12
* windows, with only 2 registers of 32 bits per window
* (ctrl, base).
*/
if (win < 8)
return win << 4;
else
return 0x90 + ((win - 8) << 3);
}
static unsigned int mv78xx0_mbus_win_cfg_offset(int win)
{
if (win < 8)
return win << 4;
else
return 0x900 + ((win - 8) << 4);
}
static unsigned int generic_mbus_win_remap_2_offset(int win)
{
if (win < 2)
return generic_mbus_win_cfg_offset(win);
else
return MVEBU_MBUS_NO_REMAP;
}
static unsigned int generic_mbus_win_remap_4_offset(int win)
{
if (win < 4)
return generic_mbus_win_cfg_offset(win);
else
return MVEBU_MBUS_NO_REMAP;
}
static unsigned int generic_mbus_win_remap_8_offset(int win)
{
if (win < 8)
return generic_mbus_win_cfg_offset(win);
else
return MVEBU_MBUS_NO_REMAP;
}
static unsigned int armada_xp_mbus_win_remap_offset(int win)
{
if (win < 8)
return generic_mbus_win_cfg_offset(win);
else if (win == 13)
return 0xF0 - WIN_REMAP_LO_OFF;
else
return MVEBU_MBUS_NO_REMAP;
}
/*
* Use the memblock information to find the MBus bridge hole in the
* physical address space.
*/
static void __init
mvebu_mbus_find_bridge_hole(uint64_t *start, uint64_t *end)
{
struct memblock_region *r;
uint64_t s = 0;
for_each_memblock(memory, r) {
/*
* This part of the memory is above 4 GB, so we don't
* care for the MBus bridge hole.
*/
if (r->base >= 0x100000000ULL)
continue;
/*
* The MBus bridge hole is at the end of the RAM under
* the 4 GB limit.
*/
if (r->base + r->size > s)
s = r->base + r->size;
}
*start = s;
*end = 0x100000000ULL;
}
/*
* This function fills in the mvebu_mbus_dram_info_nooverlap data
* structure, by looking at the mvebu_mbus_dram_info data, and
* removing the parts of it that overlap with I/O windows.
*/
static void __init
mvebu_mbus_setup_cpu_target_nooverlap(struct mvebu_mbus_state *mbus)
{
uint64_t mbus_bridge_base, mbus_bridge_end;
int cs_nooverlap = 0;
int i;
mvebu_mbus_find_bridge_hole(&mbus_bridge_base, &mbus_bridge_end);
for (i = 0; i < mvebu_mbus_dram_info.num_cs; i++) {
struct mbus_dram_window *w;
u64 base, size, end;
w = &mvebu_mbus_dram_info.cs[i];
base = w->base;
size = w->size;
end = base + size;
/*
* The CS is fully enclosed inside the MBus bridge
* area, so ignore it.
*/
if (base >= mbus_bridge_base && end <= mbus_bridge_end)
continue;
/*
* Beginning of CS overlaps with end of MBus, raise CS
* base address, and shrink its size.
*/
if (base >= mbus_bridge_base && end > mbus_bridge_end) {
size -= mbus_bridge_end - base;
base = mbus_bridge_end;
}
/*
* End of CS overlaps with beginning of MBus, shrink
* CS size.
*/
if (base < mbus_bridge_base && end > mbus_bridge_base)
size -= end - mbus_bridge_base;
w = &mvebu_mbus_dram_info_nooverlap.cs[cs_nooverlap++];
w->cs_index = i;
w->mbus_attr = 0xf & ~(1 << i);
if (mbus->hw_io_coherency)
w->mbus_attr |= ATTR_HW_COHERENCY;
w->base = base;
w->size = size;
}
mvebu_mbus_dram_info_nooverlap.mbus_dram_target_id = TARGET_DDR;
mvebu_mbus_dram_info_nooverlap.num_cs = cs_nooverlap;
}
static void __init
mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state *mbus)
{
int i;
int cs;
mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
for (i = 0, cs = 0; i < 4; i++) {
u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
/*
* We only take care of entries for which the chip
* select is enabled, and that don't have high base
* address bits set (devices can only access the first
* 32 bits of the memory).
*/
if ((size & DDR_SIZE_ENABLED) &&
!(base & DDR_BASE_CS_HIGH_MASK)) {
struct mbus_dram_window *w;
w = &mvebu_mbus_dram_info.cs[cs++];
w->cs_index = i;
w->mbus_attr = 0xf & ~(1 << i);
if (mbus->hw_io_coherency)
w->mbus_attr |= ATTR_HW_COHERENCY;
w->base = base & DDR_BASE_CS_LOW_MASK;
w->size = (u64)(size | ~DDR_SIZE_MASK) + 1;
}
}
mvebu_mbus_dram_info.num_cs = cs;
}
static int
mvebu_mbus_default_save_cpu_target(struct mvebu_mbus_state *mbus,
u32 __iomem *store_addr)
{
int i;
for (i = 0; i < 4; i++) {
u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
writel(mbus->sdramwins_phys_base + DDR_BASE_CS_OFF(i),
store_addr++);
writel(base, store_addr++);
writel(mbus->sdramwins_phys_base + DDR_SIZE_CS_OFF(i),
store_addr++);
writel(size, store_addr++);
}
/* We've written 16 words to the store address */
return 16;
}
static void __init
mvebu_mbus_dove_setup_cpu_target(struct mvebu_mbus_state *mbus)
{
int i;
int cs;
mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
for (i = 0, cs = 0; i < 2; i++) {
u32 map = readl(mbus->sdramwins_base + DOVE_DDR_BASE_CS_OFF(i));
/*
* Chip select enabled?
*/
if (map & 1) {
struct mbus_dram_window *w;
w = &mvebu_mbus_dram_info.cs[cs++];
w->cs_index = i;
w->mbus_attr = 0; /* CS address decoding done inside */
/* the DDR controller, no need to */
/* provide attributes */
w->base = map & 0xff800000;
w->size = 0x100000 << (((map & 0x000f0000) >> 16) - 4);
}
}
mvebu_mbus_dram_info.num_cs = cs;
}
static int
mvebu_mbus_dove_save_cpu_target(struct mvebu_mbus_state *mbus,
u32 __iomem *store_addr)
{
int i;
for (i = 0; i < 2; i++) {
u32 map = readl(mbus->sdramwins_base + DOVE_DDR_BASE_CS_OFF(i));
writel(mbus->sdramwins_phys_base + DOVE_DDR_BASE_CS_OFF(i),
store_addr++);
writel(map, store_addr++);
}
/* We've written 4 words to the store address */
return 4;
}
int mvebu_mbus_save_cpu_target(u32 __iomem *store_addr)
{
return mbus_state.soc->save_cpu_target(&mbus_state, store_addr);
}
static const struct mvebu_mbus_soc_data armada_370_mbus_data = {
.num_wins = 20,
.has_mbus_bridge = true,
.win_cfg_offset = armada_370_xp_mbus_win_cfg_offset,
.win_remap_offset = generic_mbus_win_remap_8_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
};
static const struct mvebu_mbus_soc_data armada_xp_mbus_data = {
.num_wins = 20,
.has_mbus_bridge = true,
.win_cfg_offset = armada_370_xp_mbus_win_cfg_offset,
.win_remap_offset = armada_xp_mbus_win_remap_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
};
static const struct mvebu_mbus_soc_data kirkwood_mbus_data = {
.num_wins = 8,
.win_cfg_offset = generic_mbus_win_cfg_offset,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
.win_remap_offset = generic_mbus_win_remap_4_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
};
static const struct mvebu_mbus_soc_data dove_mbus_data = {
.num_wins = 8,
.win_cfg_offset = generic_mbus_win_cfg_offset,
.save_cpu_target = mvebu_mbus_dove_save_cpu_target,
.win_remap_offset = generic_mbus_win_remap_4_offset,
.setup_cpu_target = mvebu_mbus_dove_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_dove,
};
/*
* Some variants of Orion5x have 4 remappable windows, some other have
* only two of them.
*/
static const struct mvebu_mbus_soc_data orion5x_4win_mbus_data = {
.num_wins = 8,
.win_cfg_offset = generic_mbus_win_cfg_offset,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
.win_remap_offset = generic_mbus_win_remap_4_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
};
static const struct mvebu_mbus_soc_data orion5x_2win_mbus_data = {
.num_wins = 8,
.win_cfg_offset = generic_mbus_win_cfg_offset,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
.win_remap_offset = generic_mbus_win_remap_2_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
};
static const struct mvebu_mbus_soc_data mv78xx0_mbus_data = {
.num_wins = 14,
.win_cfg_offset = mv78xx0_mbus_win_cfg_offset,
.save_cpu_target = mvebu_mbus_default_save_cpu_target,
.win_remap_offset = generic_mbus_win_remap_8_offset,
.setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
.show_cpu_target = mvebu_sdram_debug_show_orion,
};
static const struct of_device_id of_mvebu_mbus_ids[] = {
{ .compatible = "marvell,armada370-mbus",
.data = &armada_370_mbus_data, },
{ .compatible = "marvell,armada375-mbus",
.data = &armada_xp_mbus_data, },
{ .compatible = "marvell,armada380-mbus",
.data = &armada_xp_mbus_data, },
{ .compatible = "marvell,armadaxp-mbus",
.data = &armada_xp_mbus_data, },
{ .compatible = "marvell,kirkwood-mbus",
.data = &kirkwood_mbus_data, },
{ .compatible = "marvell,dove-mbus",
.data = &dove_mbus_data, },
{ .compatible = "marvell,orion5x-88f5281-mbus",
.data = &orion5x_4win_mbus_data, },
{ .compatible = "marvell,orion5x-88f5182-mbus",
.data = &orion5x_2win_mbus_data, },
{ .compatible = "marvell,orion5x-88f5181-mbus",
.data = &orion5x_2win_mbus_data, },
{ .compatible = "marvell,orion5x-88f6183-mbus",
.data = &orion5x_4win_mbus_data, },
{ .compatible = "marvell,mv78xx0-mbus",
.data = &mv78xx0_mbus_data, },
{ },
};
/*
* Public API of the driver
*/
int mvebu_mbus_add_window_remap_by_id(unsigned int target,
unsigned int attribute,
phys_addr_t base, size_t size,
phys_addr_t remap)
{
struct mvebu_mbus_state *s = &mbus_state;
if (!mvebu_mbus_window_conflicts(s, base, size, target, attribute)) {
pr_err("cannot add window '%x:%x', conflicts with another window\n",
target, attribute);
return -EINVAL;
}
return mvebu_mbus_alloc_window(s, base, size, remap, target, attribute);
}
int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute,
phys_addr_t base, size_t size)
{
return mvebu_mbus_add_window_remap_by_id(target, attribute, base,
size, MVEBU_MBUS_NO_REMAP);
}
int mvebu_mbus_del_window(phys_addr_t base, size_t size)
{
int win;
win = mvebu_mbus_find_window(&mbus_state, base, size);
if (win < 0)
return win;
mvebu_mbus_disable_window(&mbus_state, win);
return 0;
}
void mvebu_mbus_get_pcie_mem_aperture(struct resource *res)
{
if (!res)
return;
*res = mbus_state.pcie_mem_aperture;
}
void mvebu_mbus_get_pcie_io_aperture(struct resource *res)
{
if (!res)
return;
*res = mbus_state.pcie_io_aperture;
}
int mvebu_mbus_get_dram_win_info(phys_addr_t phyaddr, u8 *target, u8 *attr)
{
const struct mbus_dram_target_info *dram;
int i;
/* Get dram info */
dram = mv_mbus_dram_info();
if (!dram) {
pr_err("missing DRAM information\n");
return -ENODEV;
}
/* Try to find matching DRAM window for phyaddr */
for (i = 0; i < dram->num_cs; i++) {
const struct mbus_dram_window *cs = dram->cs + i;
if (cs->base <= phyaddr &&
phyaddr <= (cs->base + cs->size - 1)) {
*target = dram->mbus_dram_target_id;
*attr = cs->mbus_attr;
return 0;
}
}
pr_err("invalid dram address %pa\n", &phyaddr);
return -EINVAL;
}
EXPORT_SYMBOL_GPL(mvebu_mbus_get_dram_win_info);
int mvebu_mbus_get_io_win_info(phys_addr_t phyaddr, u32 *size, u8 *target,
u8 *attr)
{
int win;
for (win = 0; win < mbus_state.soc->num_wins; win++) {
u64 wbase;
int enabled;
mvebu_mbus_read_window(&mbus_state, win, &enabled, &wbase,
size, target, attr, NULL);
if (!enabled)
continue;
if (wbase <= phyaddr && phyaddr <= wbase + *size)
return win;
}
return -EINVAL;
}