aboutsummaryrefslogblamecommitdiffstats
path: root/arch/arm/mach-omap2/powerdomain.c
blob: 983f1cb676be26ad24f88c7d834035a2c5fea15c (plain) (tree)
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
1001
1002
1003
1004
1005


































                                                                       
                             











































































































































































































                                                                            






































































































































                                                                             





















































































































































































































































































































































































































































































































































































































































                                                                                



































































                                                                      



























                                                                             
                                           










                                                                               
/*
 * OMAP powerdomain control
 *
 * Copyright (C) 2007-2008 Texas Instruments, Inc.
 * Copyright (C) 2007-2008 Nokia Corporation
 *
 * Written by Paul Walmsley
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#ifdef CONFIG_OMAP_DEBUG_POWERDOMAIN
# define DEBUG
#endif

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/io.h>

#include <asm/atomic.h>

#include "cm.h"
#include "cm-regbits-34xx.h"
#include "prm.h"
#include "prm-regbits-34xx.h"

#include <mach/cpu.h>
#include <mach/powerdomain.h>
#include <mach/clockdomain.h>

/* pwrdm_list contains all registered struct powerdomains */
static LIST_HEAD(pwrdm_list);

/*
 * pwrdm_rwlock protects pwrdm_list add and del ops - also reused to
 * protect pwrdm_clkdms[] during clkdm add/del ops
 */
static DEFINE_RWLOCK(pwrdm_rwlock);


/* Private functions */

static u32 prm_read_mod_bits_shift(s16 domain, s16 idx, u32 mask)
{
	u32 v;

	v = prm_read_mod_reg(domain, idx);
	v &= mask;
	v >>= __ffs(mask);

	return v;
}

static struct powerdomain *_pwrdm_lookup(const char *name)
{
	struct powerdomain *pwrdm, *temp_pwrdm;

	pwrdm = NULL;

	list_for_each_entry(temp_pwrdm, &pwrdm_list, node) {
		if (!strcmp(name, temp_pwrdm->name)) {
			pwrdm = temp_pwrdm;
			break;
		}
	}

	return pwrdm;
}

/* _pwrdm_deps_lookup - look up the specified powerdomain in a pwrdm list */
static struct powerdomain *_pwrdm_deps_lookup(struct powerdomain *pwrdm,
					      struct pwrdm_dep *deps)
{
	struct pwrdm_dep *pd;

	if (!pwrdm || !deps || !omap_chip_is(pwrdm->omap_chip))
		return ERR_PTR(-EINVAL);

	for (pd = deps; pd; pd++) {

		if (!omap_chip_is(pd->omap_chip))
			continue;

		if (!pd->pwrdm && pd->pwrdm_name)
			pd->pwrdm = pwrdm_lookup(pd->pwrdm_name);

		if (pd->pwrdm == pwrdm)
			break;

	}

	if (!pd)
		return ERR_PTR(-ENOENT);

	return pd->pwrdm;
}


/* Public functions */

/**
 * pwrdm_init - set up the powerdomain layer
 *
 * Loop through the list of powerdomains, registering all that are
 * available on the current CPU. If pwrdm_list is supplied and not
 * null, all of the referenced powerdomains will be registered.  No
 * return value.
 */
void pwrdm_init(struct powerdomain **pwrdm_list)
{
	struct powerdomain **p = NULL;

	if (pwrdm_list)
		for (p = pwrdm_list; *p; p++)
			pwrdm_register(*p);
}

/**
 * pwrdm_register - register a powerdomain
 * @pwrdm: struct powerdomain * to register
 *
 * Adds a powerdomain to the internal powerdomain list.  Returns
 * -EINVAL if given a null pointer, -EEXIST if a powerdomain is
 * already registered by the provided name, or 0 upon success.
 */
int pwrdm_register(struct powerdomain *pwrdm)
{
	unsigned long flags;
	int ret = -EINVAL;

	if (!pwrdm)
		return -EINVAL;

	if (!omap_chip_is(pwrdm->omap_chip))
		return -EINVAL;

	write_lock_irqsave(&pwrdm_rwlock, flags);
	if (_pwrdm_lookup(pwrdm->name)) {
		ret = -EEXIST;
		goto pr_unlock;
	}

	list_add(&pwrdm->node, &pwrdm_list);

	pr_debug("powerdomain: registered %s\n", pwrdm->name);
	ret = 0;

pr_unlock:
	write_unlock_irqrestore(&pwrdm_rwlock, flags);

	return ret;
}

/**
 * pwrdm_unregister - unregister a powerdomain
 * @pwrdm: struct powerdomain * to unregister
 *
 * Removes a powerdomain from the internal powerdomain list.  Returns
 * -EINVAL if pwrdm argument is NULL.
 */
int pwrdm_unregister(struct powerdomain *pwrdm)
{
	unsigned long flags;

	if (!pwrdm)
		return -EINVAL;

	write_lock_irqsave(&pwrdm_rwlock, flags);
	list_del(&pwrdm->node);
	write_unlock_irqrestore(&pwrdm_rwlock, flags);

	pr_debug("powerdomain: unregistered %s\n", pwrdm->name);

	return 0;
}

/**
 * pwrdm_lookup - look up a powerdomain by name, return a pointer
 * @name: name of powerdomain
 *
 * Find a registered powerdomain by its name.  Returns a pointer to the
 * struct powerdomain if found, or NULL otherwise.
 */
struct powerdomain *pwrdm_lookup(const char *name)
{
	struct powerdomain *pwrdm;
	unsigned long flags;

	if (!name)
		return NULL;

	read_lock_irqsave(&pwrdm_rwlock, flags);
	pwrdm = _pwrdm_lookup(name);
	read_unlock_irqrestore(&pwrdm_rwlock, flags);

	return pwrdm;
}

/**
 * pwrdm_for_each - call function on each registered clockdomain
 * @fn: callback function *
 *
 * Call the supplied function for each registered powerdomain.  The
 * callback function can return anything but 0 to bail out early from
 * the iterator.  The callback function is called with the pwrdm_rwlock
 * held for reading, so no powerdomain structure manipulation
 * functions should be called from the callback, although hardware
 * powerdomain control functions are fine.  Returns the last return
 * value of the callback function, which should be 0 for success or
 * anything else to indicate failure; or -EINVAL if the function
 * pointer is null.
 */
int pwrdm_for_each(int (*fn)(struct powerdomain *pwrdm))
{
	struct powerdomain *temp_pwrdm;
	unsigned long flags;
	int ret = 0;

	if (!fn)
		return -EINVAL;

	read_lock_irqsave(&pwrdm_rwlock, flags);
	list_for_each_entry(temp_pwrdm, &pwrdm_list, node) {
		ret = (*fn)(temp_pwrdm);
		if (ret)
			break;
	}
	read_unlock_irqrestore(&pwrdm_rwlock, flags);

	return ret;
}

/**
 * pwrdm_add_clkdm - add a clockdomain to a powerdomain
 * @pwrdm: struct powerdomain * to add the clockdomain to
 * @clkdm: struct clockdomain * to associate with a powerdomain
 *
 * Associate the clockdomain 'clkdm' with a powerdomain 'pwrdm'.  This
 * enables the use of pwrdm_for_each_clkdm().  Returns -EINVAL if
 * presented with invalid pointers; -ENOMEM if memory could not be allocated;
 * or 0 upon success.
 */
int pwrdm_add_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm)
{
	unsigned long flags;
	int i;
	int ret = -EINVAL;

	if (!pwrdm || !clkdm)
		return -EINVAL;

	pr_debug("powerdomain: associating clockdomain %s with powerdomain "
		 "%s\n", clkdm->name, pwrdm->name);

	write_lock_irqsave(&pwrdm_rwlock, flags);

	for (i = 0; i < PWRDM_MAX_CLKDMS; i++) {
		if (!pwrdm->pwrdm_clkdms[i])
			break;
#ifdef DEBUG
		if (pwrdm->pwrdm_clkdms[i] == clkdm) {
			ret = -EINVAL;
			goto pac_exit;
		}
#endif
	}

	if (i == PWRDM_MAX_CLKDMS) {
		pr_debug("powerdomain: increase PWRDM_MAX_CLKDMS for "
			 "pwrdm %s clkdm %s\n", pwrdm->name, clkdm->name);
		WARN_ON(1);
		ret = -ENOMEM;
		goto pac_exit;
	}

	pwrdm->pwrdm_clkdms[i] = clkdm;

	ret = 0;

pac_exit:
	write_unlock_irqrestore(&pwrdm_rwlock, flags);

	return ret;
}

/**
 * pwrdm_del_clkdm - remove a clockdomain from a powerdomain
 * @pwrdm: struct powerdomain * to add the clockdomain to
 * @clkdm: struct clockdomain * to associate with a powerdomain
 *
 * Dissociate the clockdomain 'clkdm' from the powerdomain
 * 'pwrdm'. Returns -EINVAL if presented with invalid pointers;
 * -ENOENT if the clkdm was not associated with the powerdomain, or 0
 * upon success.
 */
int pwrdm_del_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm)
{
	unsigned long flags;
	int ret = -EINVAL;
	int i;

	if (!pwrdm || !clkdm)
		return -EINVAL;

	pr_debug("powerdomain: dissociating clockdomain %s from powerdomain "
		 "%s\n", clkdm->name, pwrdm->name);

	write_lock_irqsave(&pwrdm_rwlock, flags);

	for (i = 0; i < PWRDM_MAX_CLKDMS; i++)
		if (pwrdm->pwrdm_clkdms[i] == clkdm)
			break;

	if (i == PWRDM_MAX_CLKDMS) {
		pr_debug("powerdomain: clkdm %s not associated with pwrdm "
			 "%s ?!\n", clkdm->name, pwrdm->name);
		ret = -ENOENT;
		goto pdc_exit;
	}

	pwrdm->pwrdm_clkdms[i] = NULL;

	ret = 0;

pdc_exit:
	write_unlock_irqrestore(&pwrdm_rwlock, flags);

	return ret;
}

/**
 * pwrdm_for_each_clkdm - call function on each clkdm in a pwrdm
 * @pwrdm: struct powerdomain * to iterate over
 * @fn: callback function *
 *
 * Call the supplied function for each clockdomain in the powerdomain
 * 'pwrdm'.  The callback function can return anything but 0 to bail
 * out early from the iterator.  The callback function is called with
 * the pwrdm_rwlock held for reading, so no powerdomain structure
 * manipulation functions should be called from the callback, although
 * hardware powerdomain control functions are fine.  Returns -EINVAL
 * if presented with invalid pointers; or passes along the last return
 * value of the callback function, which should be 0 for success or
 * anything else to indicate failure.
 */
int pwrdm_for_each_clkdm(struct powerdomain *pwrdm,
			 int (*fn)(struct powerdomain *pwrdm,
				   struct clockdomain *clkdm))
{
	unsigned long flags;
	int ret = 0;
	int i;

	if (!fn)
		return -EINVAL;

	read_lock_irqsave(&pwrdm_rwlock, flags);

	for (i = 0; i < PWRDM_MAX_CLKDMS && !ret; i++)
		ret = (*fn)(pwrdm, pwrdm->pwrdm_clkdms[i]);

	read_unlock_irqrestore(&pwrdm_rwlock, flags);

	return ret;
}


/**
 * pwrdm_add_wkdep - add a wakeup dependency from pwrdm2 to pwrdm1
 * @pwrdm1: wake this struct powerdomain * up (dependent)
 * @pwrdm2: when this struct powerdomain * wakes up (source)
 *
 * When the powerdomain represented by pwrdm2 wakes up (due to an
 * interrupt), wake up pwrdm1.	Implemented in hardware on the OMAP,
 * this feature is designed to reduce wakeup latency of the dependent
 * powerdomain.  Returns -EINVAL if presented with invalid powerdomain
 * pointers, -ENOENT if pwrdm2 cannot wake up pwrdm1 in hardware, or
 * 0 upon success.
 */
int pwrdm_add_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear wake up of "
			 "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name);
		return IS_ERR(p);
	}

	pr_debug("powerdomain: hardware will wake up %s when %s wakes up\n",
		 pwrdm1->name, pwrdm2->name);

	prm_set_mod_reg_bits((1 << pwrdm2->dep_bit),
			     pwrdm1->prcm_offs, PM_WKDEP);

	return 0;
}

/**
 * pwrdm_del_wkdep - remove a wakeup dependency from pwrdm2 to pwrdm1
 * @pwrdm1: wake this struct powerdomain * up (dependent)
 * @pwrdm2: when this struct powerdomain * wakes up (source)
 *
 * Remove a wakeup dependency that causes pwrdm1 to wake up when pwrdm2
 * wakes up.  Returns -EINVAL if presented with invalid powerdomain
 * pointers, -ENOENT if pwrdm2 cannot wake up pwrdm1 in hardware, or
 * 0 upon success.
 */
int pwrdm_del_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear wake up of "
			 "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name);
		return IS_ERR(p);
	}

	pr_debug("powerdomain: hardware will no longer wake up %s after %s "
		 "wakes up\n", pwrdm1->name, pwrdm2->name);

	prm_clear_mod_reg_bits((1 << pwrdm2->dep_bit),
			       pwrdm1->prcm_offs, PM_WKDEP);

	return 0;
}

/**
 * pwrdm_read_wkdep - read wakeup dependency state from pwrdm2 to pwrdm1
 * @pwrdm1: wake this struct powerdomain * up (dependent)
 * @pwrdm2: when this struct powerdomain * wakes up (source)
 *
 * Return 1 if a hardware wakeup dependency exists wherein pwrdm1 will be
 * awoken when pwrdm2 wakes up; 0 if dependency is not set; -EINVAL
 * if either powerdomain pointer is invalid; or -ENOENT if the hardware
 * is incapable.
 *
 * REVISIT: Currently this function only represents software-controllable
 * wakeup dependencies.  Wakeup dependencies fixed in hardware are not
 * yet handled here.
 */
int pwrdm_read_wkdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->wkdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear wake up of "
			 "%s when %s wakes up\n", pwrdm1->name, pwrdm2->name);
		return IS_ERR(p);
	}

	return prm_read_mod_bits_shift(pwrdm1->prcm_offs, PM_WKDEP,
					(1 << pwrdm2->dep_bit));
}

/**
 * pwrdm_add_sleepdep - add a sleep dependency from pwrdm2 to pwrdm1
 * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent)
 * @pwrdm2: when this struct powerdomain * is active (source)
 *
 * Prevent pwrdm1 from automatically going inactive (and then to
 * retention or off) if pwrdm2 is still active.	 Returns -EINVAL if
 * presented with invalid powerdomain pointers or called on a machine
 * that does not support software-configurable hardware sleep dependencies,
 * -ENOENT if the specified dependency cannot be set in hardware, or
 * 0 upon success.
 */
int pwrdm_add_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	if (!cpu_is_omap34xx())
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear sleep "
			 "dependency affecting %s from %s\n", pwrdm1->name,
			 pwrdm2->name);
		return IS_ERR(p);
	}

	pr_debug("powerdomain: will prevent %s from sleeping if %s is active\n",
		 pwrdm1->name, pwrdm2->name);

	cm_set_mod_reg_bits((1 << pwrdm2->dep_bit),
			    pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP);

	return 0;
}

/**
 * pwrdm_del_sleepdep - remove a sleep dependency from pwrdm2 to pwrdm1
 * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent)
 * @pwrdm2: when this struct powerdomain * is active (source)
 *
 * Allow pwrdm1 to automatically go inactive (and then to retention or
 * off), independent of the activity state of pwrdm2.  Returns -EINVAL
 * if presented with invalid powerdomain pointers or called on a machine
 * that does not support software-configurable hardware sleep dependencies,
 * -ENOENT if the specified dependency cannot be cleared in hardware, or
 * 0 upon success.
 */
int pwrdm_del_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	if (!cpu_is_omap34xx())
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear sleep "
			 "dependency affecting %s from %s\n", pwrdm1->name,
			 pwrdm2->name);
		return IS_ERR(p);
	}

	pr_debug("powerdomain: will no longer prevent %s from sleeping if "
		 "%s is active\n", pwrdm1->name, pwrdm2->name);

	cm_clear_mod_reg_bits((1 << pwrdm2->dep_bit),
			      pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP);

	return 0;
}

/**
 * pwrdm_read_sleepdep - read sleep dependency state from pwrdm2 to pwrdm1
 * @pwrdm1: prevent this struct powerdomain * from sleeping (dependent)
 * @pwrdm2: when this struct powerdomain * is active (source)
 *
 * Return 1 if a hardware sleep dependency exists wherein pwrdm1 will
 * not be allowed to automatically go inactive if pwrdm2 is active;
 * 0 if pwrdm1's automatic power state inactivity transition is independent
 * of pwrdm2's; -EINVAL if either powerdomain pointer is invalid or called
 * on a machine that does not support software-configurable hardware sleep
 * dependencies; or -ENOENT if the hardware is incapable.
 *
 * REVISIT: Currently this function only represents software-controllable
 * sleep dependencies.	Sleep dependencies fixed in hardware are not
 * yet handled here.
 */
int pwrdm_read_sleepdep(struct powerdomain *pwrdm1, struct powerdomain *pwrdm2)
{
	struct powerdomain *p;

	if (!pwrdm1)
		return -EINVAL;

	if (!cpu_is_omap34xx())
		return -EINVAL;

	p = _pwrdm_deps_lookup(pwrdm2, pwrdm1->sleepdep_srcs);
	if (IS_ERR(p)) {
		pr_debug("powerdomain: hardware cannot set/clear sleep "
			 "dependency affecting %s from %s\n", pwrdm1->name,
			 pwrdm2->name);
		return IS_ERR(p);
	}

	return prm_read_mod_bits_shift(pwrdm1->prcm_offs, OMAP3430_CM_SLEEPDEP,
					(1 << pwrdm2->dep_bit));
}

/**
 * pwrdm_get_mem_bank_count - get number of memory banks in this powerdomain
 * @pwrdm: struct powerdomain *
 *
 * Return the number of controllable memory banks in powerdomain pwrdm,
 * starting with 1.  Returns -EINVAL if the powerdomain pointer is null.
 */
int pwrdm_get_mem_bank_count(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	return pwrdm->banks;
}

/**
 * pwrdm_set_next_pwrst - set next powerdomain power state
 * @pwrdm: struct powerdomain * to set
 * @pwrst: one of the PWRDM_POWER_* macros
 *
 * Set the powerdomain pwrdm's next power state to pwrst.  The powerdomain
 * may not enter this state immediately if the preconditions for this state
 * have not been satisfied.  Returns -EINVAL if the powerdomain pointer is
 * null or if the power state is invalid for the powerdomin, or returns 0
 * upon success.
 */
int pwrdm_set_next_pwrst(struct powerdomain *pwrdm, u8 pwrst)
{
	if (!pwrdm)
		return -EINVAL;

	if (!(pwrdm->pwrsts & (1 << pwrst)))
		return -EINVAL;

	pr_debug("powerdomain: setting next powerstate for %s to %0x\n",
		 pwrdm->name, pwrst);

	prm_rmw_mod_reg_bits(OMAP_POWERSTATE_MASK,
			     (pwrst << OMAP_POWERSTATE_SHIFT),
			     pwrdm->prcm_offs, PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_read_next_pwrst - get next powerdomain power state
 * @pwrdm: struct powerdomain * to get power state
 *
 * Return the powerdomain pwrdm's next power state.  Returns -EINVAL
 * if the powerdomain pointer is null or returns the next power state
 * upon success.
 */
int pwrdm_read_next_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTCTRL,
					OMAP_POWERSTATE_MASK);
}

/**
 * pwrdm_read_pwrst - get current powerdomain power state
 * @pwrdm: struct powerdomain * to get power state
 *
 * Return the powerdomain pwrdm's current power state.	Returns -EINVAL
 * if the powerdomain pointer is null or returns the current power state
 * upon success.
 */
int pwrdm_read_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST,
					OMAP_POWERSTATEST_MASK);
}

/**
 * pwrdm_read_prev_pwrst - get previous powerdomain power state
 * @pwrdm: struct powerdomain * to get previous power state
 *
 * Return the powerdomain pwrdm's previous power state.  Returns -EINVAL
 * if the powerdomain pointer is null or returns the previous power state
 * upon success.
 */
int pwrdm_read_prev_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	return prm_read_mod_bits_shift(pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST,
					OMAP3430_LASTPOWERSTATEENTERED_MASK);
}

/**
 * pwrdm_set_logic_retst - set powerdomain logic power state upon retention
 * @pwrdm: struct powerdomain * to set
 * @pwrst: one of the PWRDM_POWER_* macros
 *
 * Set the next power state that the logic portion of the powerdomain
 * pwrdm will enter when the powerdomain enters retention.  This will
 * be either RETENTION or OFF, if supported.  Returns -EINVAL if the
 * powerdomain pointer is null or the target power state is not not
 * supported, or returns 0 upon success.
 */
int pwrdm_set_logic_retst(struct powerdomain *pwrdm, u8 pwrst)
{
	if (!pwrdm)
		return -EINVAL;

	if (!(pwrdm->pwrsts_logic_ret & (1 << pwrst)))
		return -EINVAL;

	pr_debug("powerdomain: setting next logic powerstate for %s to %0x\n",
		 pwrdm->name, pwrst);

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	prm_rmw_mod_reg_bits(OMAP3430_LOGICL1CACHERETSTATE,
			     (pwrst << __ffs(OMAP3430_LOGICL1CACHERETSTATE)),
			     pwrdm->prcm_offs, PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_set_mem_onst - set memory power state while powerdomain ON
 * @pwrdm: struct powerdomain * to set
 * @bank: memory bank number to set (0-3)
 * @pwrst: one of the PWRDM_POWER_* macros
 *
 * Set the next power state that memory bank x of the powerdomain
 * pwrdm will enter when the powerdomain enters the ON state.  Bank
 * will be a number from 0 to 3, and represents different types of
 * memory, depending on the powerdomain.  Returns -EINVAL if the
 * powerdomain pointer is null or the target power state is not not
 * supported for this memory bank, -EEXIST if the target memory bank
 * does not exist or is not controllable, or returns 0 upon success.
 */
int pwrdm_set_mem_onst(struct powerdomain *pwrdm, u8 bank, u8 pwrst)
{
	u32 m;

	if (!pwrdm)
		return -EINVAL;

	if (pwrdm->banks < (bank + 1))
		return -EEXIST;

	if (!(pwrdm->pwrsts_mem_on[bank] & (1 << pwrst)))
		return -EINVAL;

	pr_debug("powerdomain: setting next memory powerstate for domain %s "
		 "bank %0x while pwrdm-ON to %0x\n", pwrdm->name, bank, pwrst);

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	switch (bank) {
	case 0:
		m = OMAP3430_SHAREDL1CACHEFLATONSTATE_MASK;
		break;
	case 1:
		m = OMAP3430_L1FLATMEMONSTATE_MASK;
		break;
	case 2:
		m = OMAP3430_SHAREDL2CACHEFLATONSTATE_MASK;
		break;
	case 3:
		m = OMAP3430_L2FLATMEMONSTATE_MASK;
		break;
	default:
		WARN_ON(1); /* should never happen */
		return -EEXIST;
	}

	prm_rmw_mod_reg_bits(m, (pwrst << __ffs(m)),
			     pwrdm->prcm_offs, PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_set_mem_retst - set memory power state while powerdomain in RET
 * @pwrdm: struct powerdomain * to set
 * @bank: memory bank number to set (0-3)
 * @pwrst: one of the PWRDM_POWER_* macros
 *
 * Set the next power state that memory bank x of the powerdomain
 * pwrdm will enter when the powerdomain enters the RETENTION state.
 * Bank will be a number from 0 to 3, and represents different types
 * of memory, depending on the powerdomain.  pwrst will be either
 * RETENTION or OFF, if supported. Returns -EINVAL if the powerdomain
 * pointer is null or the target power state is not not supported for
 * this memory bank, -EEXIST if the target memory bank does not exist
 * or is not controllable, or returns 0 upon success.
 */
int pwrdm_set_mem_retst(struct powerdomain *pwrdm, u8 bank, u8 pwrst)
{
	u32 m;

	if (!pwrdm)
		return -EINVAL;

	if (pwrdm->banks < (bank + 1))
		return -EEXIST;

	if (!(pwrdm->pwrsts_mem_ret[bank] & (1 << pwrst)))
		return -EINVAL;

	pr_debug("powerdomain: setting next memory powerstate for domain %s "
		 "bank %0x while pwrdm-RET to %0x\n", pwrdm->name, bank, pwrst);

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	switch (bank) {
	case 0:
		m = OMAP3430_SHAREDL1CACHEFLATRETSTATE;
		break;
	case 1:
		m = OMAP3430_L1FLATMEMRETSTATE;
		break;
	case 2:
		m = OMAP3430_SHAREDL2CACHEFLATRETSTATE;
		break;
	case 3:
		m = OMAP3430_L2FLATMEMRETSTATE;
		break;
	default:
		WARN_ON(1); /* should never happen */
		return -EEXIST;
	}

	prm_rmw_mod_reg_bits(m, (pwrst << __ffs(m)), pwrdm->prcm_offs,
			     PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_read_logic_pwrst - get current powerdomain logic retention power state
 * @pwrdm: struct powerdomain * to get current logic retention power state
 *
 * Return the current power state that the logic portion of
 * powerdomain pwrdm will enter
 * Returns -EINVAL if the powerdomain pointer is null or returns the
 * current logic retention power state upon success.
 */
int pwrdm_read_logic_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST,
					OMAP3430_LOGICSTATEST);
}

/**
 * pwrdm_read_prev_logic_pwrst - get previous powerdomain logic power state
 * @pwrdm: struct powerdomain * to get previous logic power state
 *
 * Return the powerdomain pwrdm's logic power state.  Returns -EINVAL
 * if the powerdomain pointer is null or returns the previous logic
 * power state upon success.
 */
int pwrdm_read_prev_logic_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	return prm_read_mod_bits_shift(pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST,
					OMAP3430_LASTLOGICSTATEENTERED);
}

/**
 * pwrdm_read_mem_pwrst - get current memory bank power state
 * @pwrdm: struct powerdomain * to get current memory bank power state
 * @bank: memory bank number (0-3)
 *
 * Return the powerdomain pwrdm's current memory power state for bank
 * x.  Returns -EINVAL if the powerdomain pointer is null, -EEXIST if
 * the target memory bank does not exist or is not controllable, or
 * returns the current memory power state upon success.
 */
int pwrdm_read_mem_pwrst(struct powerdomain *pwrdm, u8 bank)
{
	u32 m;

	if (!pwrdm)
		return -EINVAL;

	if (pwrdm->banks < (bank + 1))
		return -EEXIST;

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	switch (bank) {
	case 0:
		m = OMAP3430_SHAREDL1CACHEFLATSTATEST_MASK;
		break;
	case 1:
		m = OMAP3430_L1FLATMEMSTATEST_MASK;
		break;
	case 2:
		m = OMAP3430_SHAREDL2CACHEFLATSTATEST_MASK;
		break;
	case 3:
		m = OMAP3430_L2FLATMEMSTATEST_MASK;
		break;
	default:
		WARN_ON(1); /* should never happen */
		return -EEXIST;
	}

	return prm_read_mod_bits_shift(pwrdm->prcm_offs, PM_PWSTST, m);
}

/**
 * pwrdm_read_prev_mem_pwrst - get previous memory bank power state
 * @pwrdm: struct powerdomain * to get previous memory bank power state
 * @bank: memory bank number (0-3)
 *
 * Return the powerdomain pwrdm's previous memory power state for bank
 * x.  Returns -EINVAL if the powerdomain pointer is null, -EEXIST if
 * the target memory bank does not exist or is not controllable, or
 * returns the previous memory power state upon success.
 */
int pwrdm_read_prev_mem_pwrst(struct powerdomain *pwrdm, u8 bank)
{
	u32 m;

	if (!pwrdm)
		return -EINVAL;

	if (pwrdm->banks < (bank + 1))
		return -EEXIST;

	/*
	 * The register bit names below may not correspond to the
	 * actual names of the bits in each powerdomain's register,
	 * but the type of value returned is the same for each
	 * powerdomain.
	 */
	switch (bank) {
	case 0:
		m = OMAP3430_LASTMEM1STATEENTERED_MASK;
		break;
	case 1:
		m = OMAP3430_LASTMEM2STATEENTERED_MASK;
		break;
	case 2:
		m = OMAP3430_LASTSHAREDL2CACHEFLATSTATEENTERED_MASK;
		break;
	case 3:
		m = OMAP3430_LASTL2FLATMEMSTATEENTERED_MASK;
		break;
	default:
		WARN_ON(1); /* should never happen */
		return -EEXIST;
	}

	return prm_read_mod_bits_shift(pwrdm->prcm_offs,
					OMAP3430_PM_PREPWSTST, m);
}

/**
 * pwrdm_clear_all_prev_pwrst - clear previous powerstate register for a pwrdm
 * @pwrdm: struct powerdomain * to clear
 *
 * Clear the powerdomain's previous power state register.  Clears the
 * entire register, including logic and memory bank previous power states.
 * Returns -EINVAL if the powerdomain pointer is null, or returns 0 upon
 * success.
 */
int pwrdm_clear_all_prev_pwrst(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	/*
	 * XXX should get the powerdomain's current state here;
	 * warn & fail if it is not ON.
	 */

	pr_debug("powerdomain: clearing previous power state reg for %s\n",
		 pwrdm->name);

	prm_write_mod_reg(0, pwrdm->prcm_offs, OMAP3430_PM_PREPWSTST);

	return 0;
}

/**
 * pwrdm_enable_hdwr_sar - enable automatic hardware SAR for a pwrdm
 * @pwrdm: struct powerdomain *
 *
 * Enable automatic context save-and-restore upon power state change
 * for some devices in a powerdomain.  Warning: this only affects a
 * subset of devices in a powerdomain; check the TRM closely.  Returns
 * -EINVAL if the powerdomain pointer is null or if the powerdomain
 * does not support automatic save-and-restore, or returns 0 upon
 * success.
 */
int pwrdm_enable_hdwr_sar(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	if (!(pwrdm->flags & PWRDM_HAS_HDWR_SAR))
		return -EINVAL;

	pr_debug("powerdomain: %s: setting SAVEANDRESTORE bit\n",
		 pwrdm->name);

	prm_rmw_mod_reg_bits(0, 1 << OMAP3430ES2_SAVEANDRESTORE_SHIFT,
			     pwrdm->prcm_offs, PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_disable_hdwr_sar - disable automatic hardware SAR for a pwrdm
 * @pwrdm: struct powerdomain *
 *
 * Disable automatic context save-and-restore upon power state change
 * for some devices in a powerdomain.  Warning: this only affects a
 * subset of devices in a powerdomain; check the TRM closely.  Returns
 * -EINVAL if the powerdomain pointer is null or if the powerdomain
 * does not support automatic save-and-restore, or returns 0 upon
 * success.
 */
int pwrdm_disable_hdwr_sar(struct powerdomain *pwrdm)
{
	if (!pwrdm)
		return -EINVAL;

	if (!(pwrdm->flags & PWRDM_HAS_HDWR_SAR))
		return -EINVAL;

	pr_debug("powerdomain: %s: clearing SAVEANDRESTORE bit\n",
		 pwrdm->name);

	prm_rmw_mod_reg_bits(1 << OMAP3430ES2_SAVEANDRESTORE_SHIFT, 0,
			     pwrdm->prcm_offs, PM_PWSTCTRL);

	return 0;
}

/**
 * pwrdm_has_hdwr_sar - test whether powerdomain supports hardware SAR
 * @pwrdm: struct powerdomain *
 *
 * Returns 1 if powerdomain 'pwrdm' supports hardware save-and-restore
 * for some devices, or 0 if it does not.
 */
bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm)
{
	return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0;
}

/**
 * pwrdm_wait_transition - wait for powerdomain power transition to finish
 * @pwrdm: struct powerdomain * to wait for
 *
 * If the powerdomain pwrdm is in the process of a state transition,
 * spin until it completes the power transition, or until an iteration
 * bailout value is reached. Returns -EINVAL if the powerdomain
 * pointer is null, -EAGAIN if the bailout value was reached, or
 * returns 0 upon success.
 */
int pwrdm_wait_transition(struct powerdomain *pwrdm)
{
	u32 c = 0;

	if (!pwrdm)
		return -EINVAL;

	/*
	 * REVISIT: pwrdm_wait_transition() may be better implemented
	 * via a callback and a periodic timer check -- how long do we expect
	 * powerdomain transitions to take?
	 */

	/* XXX Is this udelay() value meaningful? */
	while ((prm_read_mod_reg(pwrdm->prcm_offs, PM_PWSTST) &
		OMAP_INTRANSITION) &&
	       (c++ < PWRDM_TRANSITION_BAILOUT))
		udelay(1);

	if (c > PWRDM_TRANSITION_BAILOUT) {
		printk(KERN_ERR "powerdomain: waited too long for "
		       "powerdomain %s to complete transition\n", pwrdm->name);
		return -EAGAIN;
	}

	pr_debug("powerdomain: completed transition in %d loops\n", c);

	return 0;
}