summaryrefslogtreecommitdiff
path: root/arch/arm/mach-shmobile/pm-rcar.c
blob: 1f465a12d1b1932422935e35a1c8f3f7bb2a4b1b (plain)
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
/*
 * R-Car SYSC Power management support
 *
 * Copyright (C) 2014  Magnus Damm
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */

#include <linux/delay.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <mach/pm-rcar.h>

/* SYSC */
#define SYSCSR 0x00
#define SYSCISR 0x04
#define SYSCISCR 0x08

#define PWRSR_OFFS 0x00
#define PWROFFCR_OFFS 0x04
#define PWRONCR_OFFS 0x0c
#define PWRER_OFFS 0x14

#define SYSCSR_RETRIES 100
#define SYSCSR_DELAY_US 1

#define SYSCISR_RETRIES 1000
#define SYSCISR_DELAY_US 1

#if defined(CONFIG_PM) || defined(CONFIG_SMP)

static void __iomem *rcar_sysc_base;
static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */

static int rcar_sysc_pwr_on_off(struct rcar_sysc_ch *sysc_ch,
				int sr_bit, int reg_offs)
{
	int k;

	for (k = 0; k < SYSCSR_RETRIES; k++) {
		if (ioread32(rcar_sysc_base + SYSCSR) & (1 << sr_bit))
			break;
		udelay(SYSCSR_DELAY_US);
	}

	if (k == SYSCSR_RETRIES)
		return -EAGAIN;

	iowrite32(1 << sysc_ch->chan_bit,
		  rcar_sysc_base + sysc_ch->chan_offs + reg_offs);

	return 0;
}

static int rcar_sysc_pwr_off(struct rcar_sysc_ch *sysc_ch)
{
	return rcar_sysc_pwr_on_off(sysc_ch, 0, PWROFFCR_OFFS);
}

static int rcar_sysc_pwr_on(struct rcar_sysc_ch *sysc_ch)
{
	return rcar_sysc_pwr_on_off(sysc_ch, 1, PWRONCR_OFFS);
}

static int rcar_sysc_update(struct rcar_sysc_ch *sysc_ch,
			    int (*on_off_fn)(struct rcar_sysc_ch *))
{
	unsigned int isr_mask = 1 << sysc_ch->isr_bit;
	unsigned int chan_mask = 1 << sysc_ch->chan_bit;
	unsigned int status;
	unsigned long flags;
	int ret = 0;
	int k;

	spin_lock_irqsave(&rcar_sysc_lock, flags);

	iowrite32(isr_mask, rcar_sysc_base + SYSCISCR);

	do {
		ret = on_off_fn(sysc_ch);
		if (ret)
			goto out;

		status = ioread32(rcar_sysc_base +
				  sysc_ch->chan_offs + PWRER_OFFS);
	} while (status & chan_mask);

	for (k = 0; k < SYSCISR_RETRIES; k++) {
		if (ioread32(rcar_sysc_base + SYSCISR) & isr_mask)
			break;
		udelay(SYSCISR_DELAY_US);
	}

	if (k == SYSCISR_RETRIES)
		ret = -EIO;

	iowrite32(isr_mask, rcar_sysc_base + SYSCISCR);

 out:
	spin_unlock_irqrestore(&rcar_sysc_lock, flags);

	pr_debug("sysc power domain %d: %08x -> %d\n",
		 sysc_ch->isr_bit, ioread32(rcar_sysc_base + SYSCISR), ret);
	return ret;
}

int rcar_sysc_power_down(struct rcar_sysc_ch *sysc_ch)
{
	return rcar_sysc_update(sysc_ch, rcar_sysc_pwr_off);
}

int rcar_sysc_power_up(struct rcar_sysc_ch *sysc_ch)
{
	return rcar_sysc_update(sysc_ch, rcar_sysc_pwr_on);
}

bool rcar_sysc_power_is_off(struct rcar_sysc_ch *sysc_ch)
{
	unsigned int st;

	st = ioread32(rcar_sysc_base + sysc_ch->chan_offs + PWRSR_OFFS);
	if (st & (1 << sysc_ch->chan_bit))
		return true;

	return false;
}

void __iomem *rcar_sysc_init(phys_addr_t base)
{
	rcar_sysc_base = ioremap_nocache(base, PAGE_SIZE);
	if (!rcar_sysc_base)
		panic("unable to ioremap R-Car SYSC hardware block\n");

	return rcar_sysc_base;
}

#endif /* CONFIG_PM || CONFIG_SMP */