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
|
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/debugfs.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <mach/ocmem_priv.h>
#include <mach/rpm-smd.h>
static unsigned num_regions;
static unsigned num_macros;
static unsigned num_ports;
static unsigned num_banks;
static unsigned long macro_size;
static unsigned long region_size;
static bool rpm_power_control;
struct ocmem_hw_macro {
atomic_t m_on[OCMEM_CLIENT_MAX];
atomic_t m_retain[OCMEM_CLIENT_MAX];
unsigned m_state;
};
struct ocmem_hw_region {
unsigned psgsc_ctrl;
bool interleaved;
unsigned int mode;
atomic_t mode_counter;
unsigned int num_macros;
struct ocmem_hw_macro *macro;
struct msm_rpm_request *rpm_req;
unsigned r_state;
};
static struct ocmem_hw_region *region_ctrl;
static struct mutex region_ctrl_lock;
static void *ocmem_base;
#define OCMEM_V1_REGIONS 3
#define OCMEM_V1_MACROS 8
#define OCMEM_V1_MACRO_SZ (SZ_64K)
#define OC_HW_VERS (0x0)
#define OC_HW_PROFILE (0x4)
#define OC_GEN_STATUS (0xC)
#define OC_PSGSC_STATUS (0x38)
#define OC_PSGSC_CTL (0x3C)
#define OC_REGION_MODE_CTL (0x1000)
#define OC_GFX_MPU_START (0x1004)
#define OC_GFX_MPU_END (0x1008)
#define NUM_PORTS_MASK (0xF << 0)
#define NUM_PORTS_SHIFT (0)
#define GFX_MPU_SHIFT (12)
#define NUM_MACROS_MASK (0xF << 8)
#define NUM_MACROS_SHIFT (8)
#define INTERLEAVING_MASK (0x1 << 17)
#define INTERLEAVING_SHIFT (17)
/* Power states of each memory macro */
#define PASSTHROUGH (0x0)
#define CORE_ON (0x2)
#define PERI_ON (0x1)
#define CLK_OFF (0x4)
#define MACRO_ON (0x0)
#define MACRO_SLEEP_RETENTION (CLK_OFF|CORE_ON)
#define MACRO_SLEEP_RETENTION_PERI_ON (CLK_OFF|MACRO_ON)
#define MACRO_OFF (CLK_OFF)
#define M_PSCGC_CTL_n(x) (0x7 << (x * 4))
#define PSCGC_CTL_IDX(x) ((x) * 0x4)
#define PSCGC_CTL_n(x) (OC_PSGSC_CTL + (PSCGC_CTL_IDX(x)))
/* Power states of each ocmem region */
#define REGION_NORMAL_PASSTHROUGH 0x00000000
#define REGION_FORCE_PERI_ON 0x00001111
#define REGION_FORCE_CORE_ON 0x00002222
#define REGION_FORCE_ALL_ON 0x00003333
#define REGION_SLEEP_NO_RETENTION 0x00004444
#define REGION_SLEEP_PERI_OFF 0x00006666
#define REGION_SLEEP_PERI_ON 0x00007777
#define REGION_DEFAULT_OFF REGION_SLEEP_NO_RETENTION
#define REGION_DEFAULT_ON REGION_NORMAL_PASSTHROUGH
#define REGION_DEFAULT_RETENTION REGION_SLEEP_PERI_OFF
enum rpm_macro_state {
rpm_macro_off = 0x0,
rpm_macro_retain,
rpm_macro_on,
};
static int rpm_write(unsigned long val, unsigned id);
static inline unsigned hw_macro_state(unsigned region_state)
{
unsigned macro_state;
switch (region_state) {
case REGION_DEFAULT_ON:
macro_state = MACRO_ON;
break;
case REGION_DEFAULT_OFF:
macro_state = MACRO_OFF;
break;
case REGION_DEFAULT_RETENTION:
macro_state = MACRO_SLEEP_RETENTION;
break;
default:
macro_state = MACRO_OFF;
break;
}
return macro_state;
}
static inline unsigned rpm_macro_state(unsigned hw_macro_state)
{
unsigned macro_state;
switch (hw_macro_state) {
case MACRO_ON:
macro_state = rpm_macro_on;
break;
case MACRO_OFF:
macro_state = rpm_macro_off;
break;
case MACRO_SLEEP_RETENTION:
macro_state = rpm_macro_retain;
break;
default:
macro_state = rpm_macro_off;
break;
}
return macro_state;
}
/* Generic wrapper that sets the region state either
by a direct write or through appropriate RPM call
*/
/* Must be called with region mutex held */
static int commit_region_state(unsigned region_num)
{
int rc = -1;
unsigned new_state;
if (region_num >= num_regions)
return -EINVAL;
new_state = region_ctrl[region_num].r_state;
pr_debug("ocmem: commit region (%d) new state %x\n", region_num,
new_state);
if (rpm_power_control)
rc = rpm_write(new_state, region_num);
else
rc = ocmem_write(new_state,
ocmem_base + PSCGC_CTL_n(region_num));
/* Barrier to commit the region state */
mb();
return 0;
}
/* Returns the current state of a OCMEM region */
/* Must be called with region mutex held */
static int read_region_state(unsigned region_num)
{
int state;
pr_debug("rpm_get_region_state: #: %d\n", region_num);
if (region_num >= num_regions)
return -EINVAL;
if (rpm_power_control)
state = region_ctrl[region_num].r_state;
else
state = ocmem_read(ocmem_base + PSCGC_CTL_n(region_num));
pr_debug("ocmem: region (%d) state %x\n", region_num, state);
return state;
}
/* Returns the current state of a OCMEM macro that belongs to a region */
static int read_macro_state(unsigned region_num, unsigned macro_num)
{
int state;
if (macro_num >= num_banks)
return -EINVAL;
state = read_region_state(region_num);
if (state < 0)
return -EINVAL;
state &= M_PSCGC_CTL_n(macro_num);
state = state >> (macro_num * 4);
pr_debug("rpm_get_macro_state: macro (%d) region (%d) state %x\n",
macro_num, region_num, state);
return state;
}
static int apply_macro_vote(int id, unsigned region_num,
unsigned macro_num, int new_state)
{
struct ocmem_hw_macro *m = NULL;
struct ocmem_hw_region *region = NULL;
if (region_num >= num_regions)
return -EINVAL;
if (macro_num >= num_banks)
return -EINVAL;
region = ®ion_ctrl[region_num];
m = ®ion->macro[macro_num];
pr_debug("m (%d): curr state %x votes (on: %d retain %d) new state %x\n",
macro_num, m->m_state,
atomic_read(&m->m_on[id]),
atomic_read(&m->m_retain[id]),
new_state);
switch (m->m_state) {
case MACRO_OFF:
if (new_state == MACRO_ON)
atomic_inc(&m->m_on[id]);
break;
case MACRO_ON:
if (new_state == MACRO_OFF) {
atomic_dec(&m->m_on[id]);
} else if (new_state == MACRO_SLEEP_RETENTION) {
atomic_inc(&m->m_retain[id]);
atomic_dec(&m->m_on[id]);
}
break;
case MACRO_SLEEP_RETENTION:
if (new_state == MACRO_OFF) {
atomic_dec(&m->m_retain[id]);
} else if (new_state == MACRO_ON) {
atomic_inc(&m->m_on[id]);
atomic_dec(&m->m_retain[id]);
}
break;
}
pr_debug("macro (%d) region (%d) votes for %d (on: %d retain %d)\n",
region_num, macro_num, id,
atomic_read(&m->m_on[id]),
atomic_read(&m->m_retain[id]));
return 0;
}
static int aggregate_macro_state(unsigned region_num, unsigned macro_num)
{
struct ocmem_hw_macro *m = NULL;
struct ocmem_hw_region *region = NULL;
int i = 0;
/* The default is for the macro to be OFF */
unsigned m_state = MACRO_OFF;
if (region_num >= num_regions)
return -EINVAL;
if (macro_num >= num_banks)
return -EINVAL;
region = ®ion_ctrl[region_num];
m = ®ion->macro[macro_num];
for (i = 0; i < OCMEM_CLIENT_MAX; i++) {
if (atomic_read(&m->m_on[i]) > 0) {
/* atleast one client voted for ON state */
m_state = MACRO_ON;
goto done_aggregation;
} else if (atomic_read(&m->m_retain[i]) > 0) {
m_state = MACRO_SLEEP_RETENTION;
/* continue and examine votes of other clients */
}
}
done_aggregation:
m->m_state = m_state;
pr_debug("macro (%d) region (%d) aggregated state %x", macro_num,
region_num, m->m_state);
return 0;
}
static int aggregate_region_state(unsigned region_num)
{
struct ocmem_hw_region *region = NULL;
unsigned r_state;
unsigned i = 0;
if (region_num >= num_regions)
return -EINVAL;
region = ®ion_ctrl[region_num];
r_state = REGION_DEFAULT_OFF;
/* In wide mode all macros must have the same state */
if (region->mode == WIDE_MODE) {
for (i = 0; i < region->num_macros; i++) {
if (region->macro[i].m_state == MACRO_ON) {
r_state = REGION_DEFAULT_ON;
break;
} else if (region->macro[i].m_state ==
MACRO_SLEEP_RETENTION) {
r_state = REGION_DEFAULT_RETENTION;
}
}
} else {
/* In narrow mode each macro is allowed to be in a different state */
/* The region mode is simply the collection of all macro states */
for (i = 0; i < region->num_macros; i++) {
pr_debug("aggregated region state %x\n", r_state);
pr_debug("macro %d\n state %x\n", i,
region->macro[i].m_state);
r_state &= ~M_PSCGC_CTL_n(i);
r_state |= region->macro[i].m_state << (i * 4);
}
}
pr_debug("region (%d) curr state (%x) aggregated state (%x)\n",
region_num, region->r_state, r_state);
region->r_state = r_state;
return 0;
}
static int rpm_write(unsigned long val, unsigned id)
{
int i = 0;
int ret = 0;
struct ocmem_hw_region *region;
region = ®ion_ctrl[id];
for (i = 0; i < region->num_macros; i++) {
unsigned macro_state;
unsigned rpm_state;
macro_state = read_macro_state(id, i);
rpm_state = rpm_macro_state(macro_state);
if (val == REGION_DEFAULT_ON) {
pr_debug("macro (%d) region (%d) -> active\n",
i, id);
rpm_state = rpm_macro_on;
}
if (val == REGION_DEFAULT_OFF) {
pr_debug("macro (%d) region (%d) -> off\n",
i, id);
rpm_state = rpm_macro_off;
}
ret = msm_rpm_add_kvp_data(region->rpm_req, i,
(u8 *) &rpm_state, 4);
if (ret < 0) {
pr_err("ocmem: Error adding key %d val %d on rsc %d\n",
i, rpm_state, id);
return -EINVAL;
}
}
ret = msm_rpm_send_request(region->rpm_req);
if (ret < 0) {
pr_err("ocmem: Error sending RPM request\n");
return -EINVAL;
}
pr_debug("Transmit request to rpm for region %d\n", id);
return 0;
}
static int switch_region_mode(unsigned long offset, unsigned long len,
enum region_mode new_mode)
{
unsigned region_start = num_regions;
unsigned region_end = num_regions;
int i = 0;
if (offset < 0)
return -EINVAL;
if (len < region_size)
return -EINVAL;
pr_debug("ocmem: mode_transistion to %x\n", new_mode);
region_start = offset / region_size;
region_end = (offset + len - 1) / region_size;
pr_debug("ocmem: region start %u end %u\n", region_start, region_end);
if (region_start >= num_regions ||
(region_end >= num_regions))
return -EINVAL;
for (i = region_start; i <= region_end; i++) {
struct ocmem_hw_region *region = ®ion_ctrl[i];
if (region->mode == MODE_DEFAULT) {
/* No prior mode programming on this region */
/* Set the region to its new mode */
region->mode = new_mode;
atomic_inc(®ion->mode_counter);
pr_debug("Region (%d) switching to mode %d\n",
i, new_mode);
continue;
} else if (region->mode != new_mode) {
/* The region is currently set to a different mode */
if (new_mode == MODE_DEFAULT) {
if (atomic_dec_and_test
(®ion->mode_counter)) {
region->mode = MODE_DEFAULT;
pr_debug("Region (%d) restoring to default mode\n",
i);
} else {
/* More than 1 client in region */
/* Cannot move to default mode */
pr_debug("Region (%d) using current mode %d\n",
i, region->mode);
continue;
}
} else {
/* Do not switch modes */
pr_err("Region (%d) requested mode %x conflicts with current\n",
i, new_mode);
goto mode_switch_fail;
}
}
}
return 0;
mode_switch_fail:
return -EINVAL;
}
#ifdef CONFIG_MSM_OCMEM_NONSECURE
static int commit_region_modes(void)
{
uint32_t region_mode_ctrl = 0x0;
unsigned pos = 0;
unsigned i = 0;
for (i = 0; i < num_regions; i++) {
struct ocmem_hw_region *region = ®ion_ctrl[i];
pos = i << 2;
if (region->mode == THIN_MODE)
region_mode_ctrl |= BIT(pos);
}
pr_debug("ocmem_region_mode_control %x\n", region_mode_ctrl);
ocmem_write(region_mode_ctrl, ocmem_base + OC_REGION_MODE_CTL);
/* Barrier to commit the region mode */
mb();
return 0;
}
static int ocmem_gfx_mpu_set(unsigned long offset, unsigned long len)
{
int mpu_start = 0x0;
int mpu_end = 0x0;
if (offset)
mpu_start = (offset >> GFX_MPU_SHIFT) - 1;
if (mpu_start < 0)
/* Avoid underflow */
mpu_start = 0;
mpu_end = ((offset+len) >> GFX_MPU_SHIFT) - 1;
BUG_ON(mpu_end < 0);
pr_debug("ocmem: mpu: start %x end %x\n", mpu_start, mpu_end);
ocmem_write(mpu_start << GFX_MPU_SHIFT, ocmem_base + OC_GFX_MPU_START);
ocmem_write(mpu_end << GFX_MPU_SHIFT, ocmem_base + OC_GFX_MPU_END);
return 0;
}
static void ocmem_gfx_mpu_remove(void)
{
ocmem_write(0x0, ocmem_base + OC_GFX_MPU_START);
ocmem_write(0x0, ocmem_base + OC_GFX_MPU_END);
}
static int do_lock(enum ocmem_client id, unsigned long offset,
unsigned long len, enum region_mode mode)
{
return 0;
}
static int do_unlock(enum ocmem_client id, unsigned long offset,
unsigned long len)
{
ocmem_clear(offset, len);
return 0;
}
#else
static int ocmem_gfx_mpu_set(unsigned long offset, unsigned long len)
{
return 0;
}
static void ocmem_gfx_mpu_remove(void)
{
}
static int commit_region_modes(void)
{
return 0;
}
static int do_lock(enum ocmem_client id, unsigned long offset,
unsigned long len, enum region_mode mode)
{
return 0;
}
static int do_unlock(enum ocmem_client id, unsigned long offset,
unsigned long len)
{
return 0;
}
#endif /* CONFIG_MSM_OCMEM_NONSECURE */
int ocmem_lock(enum ocmem_client id, unsigned long offset, unsigned long len,
enum region_mode mode)
{
if (len < OCMEM_MIN_ALLOC) {
pr_err("ocmem: Invalid len %lx for lock\n", len);
return -EINVAL;
}
if (id == OCMEM_GRAPHICS)
ocmem_gfx_mpu_set(offset, len);
mutex_lock(®ion_ctrl_lock);
if (switch_region_mode(offset, len, mode) < 0)
goto switch_region_fail;
commit_region_modes();
do_lock(id, offset, len, mode);
mutex_unlock(®ion_ctrl_lock);
return 0;
switch_region_fail:
mutex_unlock(®ion_ctrl_lock);
return -EINVAL;
}
int ocmem_unlock(enum ocmem_client id, unsigned long offset, unsigned long len)
{
if (id == OCMEM_GRAPHICS)
ocmem_gfx_mpu_remove();
mutex_lock(®ion_ctrl_lock);
do_unlock(id, offset, len);
switch_region_mode(offset, len , MODE_DEFAULT);
commit_region_modes();
mutex_unlock(®ion_ctrl_lock);
return 0;
}
#if defined(CONFIG_MSM_OCMEM_POWER_DISABLE)
static int ocmem_core_set_default_state(void)
{
int rc = 0;
/* The OCMEM core clock and branch clocks are always turned ON */
rc = ocmem_enable_core_clock();
if (rc < 0)
return rc;
rc = ocmem_enable_iface_clock();
if (rc < 0)
return rc;
return 0;
}
/* Initializes a region to be turned ON in wide mode */
static int ocmem_region_set_default_state(unsigned int r_num)
{
unsigned m_num = 0;
mutex_lock(®ion_ctrl_lock);
for (m_num = 0; m_num < num_banks; m_num++) {
apply_macro_vote(0, r_num, m_num, MACRO_ON);
aggregate_macro_state(r_num, m_num);
}
aggregate_region_state(r_num);
commit_region_state(r_num);
mutex_unlock(®ion_ctrl_lock);
return 0;
}
#else
static int ocmem_region_set_default_state(unsigned int region_num)
{
return 0;
}
static int ocmem_core_set_default_state(void)
{
return 0;
}
#endif
#if defined(CONFIG_MSM_OCMEM_POWER_DEBUG)
static int read_hw_region_state(unsigned region_num)
{
int state;
pr_debug("rpm_get_region_state: #: %d\n", region_num);
if (region_num >= num_regions)
return -EINVAL;
state = ocmem_read(ocmem_base + PSCGC_CTL_n(region_num));
pr_debug("ocmem: region (%d) state %x\n", region_num, state);
return state;
}
int ocmem_region_toggle(unsigned int r_num)
{
unsigned reboot_state = ~0x0;
unsigned m_num = 0;
mutex_lock(®ion_ctrl_lock);
/* Turn on each macro at boot for quick hw sanity check */
reboot_state = read_hw_region_state(r_num);
if (reboot_state != REGION_DEFAULT_OFF) {
pr_err("Region %d not in power off state (%x)\n",
r_num, reboot_state);
goto toggle_fail;
}
for (m_num = 0; m_num < num_banks; m_num++) {
apply_macro_vote(0, r_num, m_num, MACRO_ON);
aggregate_macro_state(r_num, m_num);
}
aggregate_region_state(r_num);
commit_region_state(r_num);
reboot_state = read_hw_region_state(r_num);
if (reboot_state != REGION_DEFAULT_ON) {
pr_err("Failed to power on Region %d(state:%x)\n",
r_num, reboot_state);
goto toggle_fail;
}
/* Turn off all memory macros again */
for (m_num = 0; m_num < num_banks; m_num++) {
apply_macro_vote(0, r_num, m_num, MACRO_OFF);
aggregate_macro_state(r_num, m_num);
}
aggregate_region_state(r_num);
commit_region_state(r_num);
reboot_state = read_hw_region_state(r_num);
if (reboot_state != REGION_DEFAULT_OFF) {
pr_err("Failed to power off Region %d(state:%x)\n",
r_num, reboot_state);
goto toggle_fail;
}
mutex_unlock(®ion_ctrl_lock);
return 0;
toggle_fail:
mutex_unlock(®ion_ctrl_lock);
return -EINVAL;
}
int memory_is_off(unsigned int num)
{
if (read_hw_region_state(num) == REGION_DEFAULT_OFF)
return 1;
else
return 0;
}
#else
int ocmem_region_toggle(unsigned int region_num)
{
return 0;
}
int memory_is_off(unsigned int num)
{
return 0;
}
#endif /* CONFIG_MSM_OCMEM_POWER_DEBUG */
/* Memory Macro Power Transition Sequences
* Normal to Sleep With Retention:
REGION_DEFAULT_ON -> REGION_DEFAULT_RETENTION
* Sleep With Retention to Normal:
REGION_DEFAULT_RETENTION -> REGION_FORCE_CORE_ON -> REGION_DEFAULT_ON
* Normal to OFF:
REGION_DEFAULT_ON -> REGION_DEFAULT_OFF
* OFF to Normal:
REGION_DEFAULT_OFF -> REGION_DEFAULT_ON
**/
#if defined(CONFIG_MSM_OCMEM_POWER_DISABLE)
/* If power management is disabled leave the macro states as is */
static int switch_power_state(int id, unsigned long offset, unsigned long len,
unsigned new_state)
{
return 0;
}
#else
static int switch_power_state(int id, unsigned long offset, unsigned long len,
unsigned new_state)
{
unsigned region_start = num_regions;
unsigned region_end = num_regions;
unsigned curr_state = 0x0;
int i = 0;
int j = 0;
unsigned start_m = num_banks;
unsigned end_m = num_banks;
unsigned long region_offset = 0;
int rc = 0;
if (offset < 0)
return -EINVAL;
if (len < macro_size)
return -EINVAL;
pr_debug("ocmem: power_transition to %x for client %d\n", new_state,
id);
region_start = offset / region_size;
region_end = (offset + len - 1) / region_size;
pr_debug("ocmem: region start %u end %u\n", region_start, region_end);
if (region_start >= num_regions ||
(region_end >= num_regions))
return -EINVAL;
rc = ocmem_enable_core_clock();
if (rc < 0) {
pr_err("ocmem: Power transistion request for client %s (id: %d) failed\n",
get_name(id), id);
return rc;
}
mutex_lock(®ion_ctrl_lock);
for (i = region_start; i <= region_end; i++) {
curr_state = read_region_state(i);
switch (curr_state) {
case REGION_DEFAULT_OFF:
if (new_state != REGION_DEFAULT_ON)
goto invalid_transition;
break;
case REGION_DEFAULT_RETENTION:
if (new_state != REGION_DEFAULT_ON)
goto invalid_transition;
break;
default:
break;
}
if (len >= region_size) {
pr_debug("switch: entire region (%d)\n", i);
start_m = 0;
end_m = num_banks;
} else {
region_offset = offset - (i * region_size);
start_m = region_offset / macro_size;
end_m = (region_offset + len - 1) / macro_size;
pr_debug("switch: macro (%u to %u)\n", start_m, end_m);
}
for (j = start_m; j <= end_m; j++) {
pr_debug("vote: macro (%d) region (%d)\n", j, i);
apply_macro_vote(id, i, j,
hw_macro_state(new_state));
aggregate_macro_state(i, j);
}
aggregate_region_state(i);
commit_region_state(i);
len -= region_size;
/* If we voted ON/retain the banks must never be OFF */
if (new_state != REGION_DEFAULT_OFF) {
if (memory_is_off(i)) {
pr_err("ocmem: Accessing memory during sleep\n");
WARN_ON(1);
}
}
}
mutex_unlock(®ion_ctrl_lock);
ocmem_disable_core_clock();
return 0;
invalid_transition:
mutex_unlock(®ion_ctrl_lock);
ocmem_disable_core_clock();
pr_err("ocmem_core: Invalid state transition detected for %d\n", id);
pr_err("ocmem_core: Offset %lx Len %lx curr_state %x new_state %x\n",
offset, len, curr_state, new_state);
WARN_ON(1);
return -EINVAL;
}
#endif
/* Interfaces invoked from the scheduler */
int ocmem_memory_off(int id, unsigned long offset, unsigned long len)
{
return switch_power_state(id, offset, len, REGION_DEFAULT_OFF);
}
int ocmem_memory_on(int id, unsigned long offset, unsigned long len)
{
return switch_power_state(id, offset, len, REGION_DEFAULT_ON);
}
int ocmem_memory_retain(int id, unsigned long offset, unsigned long len)
{
return switch_power_state(id, offset, len, REGION_DEFAULT_RETENTION);
}
int ocmem_core_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ocmem_plat_data *pdata = NULL;
unsigned hw_ver;
bool interleaved;
unsigned i, j, k;
unsigned rsc_type = 0;
int rc = 0;
pdata = platform_get_drvdata(pdev);
ocmem_base = pdata->reg_base;
rc = ocmem_enable_core_clock();
if (rc < 0)
return rc;
hw_ver = ocmem_read(ocmem_base + OC_HW_PROFILE);
if (pdata->nr_regions != OCMEM_V1_REGIONS) {
pr_err("Invalid number of regions (%d)\n", pdata->nr_regions);
goto hw_not_supported;
}
num_macros = (hw_ver & NUM_MACROS_MASK) >> NUM_MACROS_SHIFT;
num_ports = (hw_ver & NUM_PORTS_MASK) >> NUM_PORTS_SHIFT;
if (num_macros != OCMEM_V1_MACROS) {
pr_err("Invalid number of macros (%d)\n", pdata->nr_macros);
goto hw_not_supported;
}
interleaved = (hw_ver & INTERLEAVING_MASK) >> INTERLEAVING_SHIFT;
if (interleaved == false) {
pr_err("Interleaving is disabled\n");
goto hw_not_supported;
}
num_regions = pdata->nr_regions;
pdata->interleaved = true;
pdata->nr_macros = num_macros;
pdata->nr_ports = num_ports;
macro_size = OCMEM_V1_MACRO_SZ * 2;
num_banks = num_ports / 2;
region_size = macro_size * num_banks;
rsc_type = pdata->rpm_rsc_type;
pr_debug("ocmem_core: ports %d regions %d macros %d interleaved %d\n",
num_ports, num_regions, num_macros,
interleaved);
region_ctrl = devm_kzalloc(dev, sizeof(struct ocmem_hw_region)
* num_regions, GFP_KERNEL);
if (!region_ctrl) {
goto err_no_mem;
}
mutex_init(®ion_ctrl_lock);
for (i = 0 ; i < num_regions; i++) {
struct ocmem_hw_region *region = ®ion_ctrl[i];
struct msm_rpm_request *req = NULL;
region->interleaved = interleaved;
region->mode = MODE_DEFAULT;
atomic_set(®ion->mode_counter, 0);
region->r_state = REGION_DEFAULT_OFF;
region->num_macros = num_banks;
region->macro = devm_kzalloc(dev,
sizeof(struct ocmem_hw_macro) *
num_banks, GFP_KERNEL);
if (!region->macro) {
goto err_no_mem;
}
for (j = 0; j < num_banks; j++) {
struct ocmem_hw_macro *m = ®ion->macro[j];
m->m_state = MACRO_OFF;
for (k = 0; k < OCMEM_CLIENT_MAX; k++) {
atomic_set(&m->m_on[k], 0);
atomic_set(&m->m_retain[k], 0);
}
}
if (pdata->rpm_pwr_ctrl) {
rpm_power_control = true;
req = msm_rpm_create_request(MSM_RPM_CTX_ACTIVE_SET,
rsc_type, i, num_banks);
if (!req) {
pr_err("Unable to create RPM request\n");
goto region_init_error;
}
pr_debug("rpm request type %x (rsc: %d) with %d elements\n",
rsc_type, i, num_banks);
region->rpm_req = req;
}
if (ocmem_region_toggle(i)) {
pr_err("Failed to verify region %d\n", i);
goto region_init_error;
}
if (ocmem_region_set_default_state(i)) {
pr_err("Failed to initialize region %d\n", i);
goto region_init_error;
}
}
rc = ocmem_core_set_default_state();
if (rc < 0)
return rc;
ocmem_disable_core_clock();
return 0;
err_no_mem:
pr_err("ocmem: Unable to allocate memory\n");
region_init_error:
hw_not_supported:
pr_err("Unsupported OCMEM h/w configuration %x\n", hw_ver);
ocmem_disable_core_clock();
return -EINVAL;
}
|