1 #include <linux/clk.h>
2 #include <linux/compiler.h>
3 #include <linux/io.h>
4 #include <linux/spinlock.h>
5 #include <asm/suspend.h>
6 #include <asm/hwblk.h>
7 #include <asm/clock.h>
8 
9 static DEFINE_SPINLOCK(hwblk_lock);
10 
hwblk_area_mod_cnt(struct hwblk_info * info,int area,int counter,int value,int goal)11 static void hwblk_area_mod_cnt(struct hwblk_info *info,
12 			       int area, int counter, int value, int goal)
13 {
14 	struct hwblk_area *hap = info->areas + area;
15 
16 	hap->cnt[counter] += value;
17 
18 	if (hap->cnt[counter] != goal)
19 		return;
20 
21 	if (hap->flags & HWBLK_AREA_FLAG_PARENT)
22 		hwblk_area_mod_cnt(info, hap->parent, counter, value, goal);
23 }
24 
25 
__hwblk_mod_cnt(struct hwblk_info * info,int hwblk,int counter,int value,int goal)26 static int __hwblk_mod_cnt(struct hwblk_info *info, int hwblk,
27 			  int counter, int value, int goal)
28 {
29 	struct hwblk *hp = info->hwblks + hwblk;
30 
31 	hp->cnt[counter] += value;
32 	if (hp->cnt[counter] == goal)
33 		hwblk_area_mod_cnt(info, hp->area, counter, value, goal);
34 
35 	return hp->cnt[counter];
36 }
37 
hwblk_mod_cnt(struct hwblk_info * info,int hwblk,int counter,int value,int goal)38 static void hwblk_mod_cnt(struct hwblk_info *info, int hwblk,
39 			  int counter, int value, int goal)
40 {
41 	unsigned long flags;
42 
43 	spin_lock_irqsave(&hwblk_lock, flags);
44 	__hwblk_mod_cnt(info, hwblk, counter, value, goal);
45 	spin_unlock_irqrestore(&hwblk_lock, flags);
46 }
47 
hwblk_cnt_inc(struct hwblk_info * info,int hwblk,int counter)48 void hwblk_cnt_inc(struct hwblk_info *info, int hwblk, int counter)
49 {
50 	hwblk_mod_cnt(info, hwblk, counter, 1, 1);
51 }
52 
hwblk_cnt_dec(struct hwblk_info * info,int hwblk,int counter)53 void hwblk_cnt_dec(struct hwblk_info *info, int hwblk, int counter)
54 {
55 	hwblk_mod_cnt(info, hwblk, counter, -1, 0);
56 }
57 
hwblk_enable(struct hwblk_info * info,int hwblk)58 void hwblk_enable(struct hwblk_info *info, int hwblk)
59 {
60 	struct hwblk *hp = info->hwblks + hwblk;
61 	unsigned long tmp;
62 	unsigned long flags;
63 	int ret;
64 
65 	spin_lock_irqsave(&hwblk_lock, flags);
66 
67 	ret = __hwblk_mod_cnt(info, hwblk, HWBLK_CNT_USAGE, 1, 1);
68 	if (ret == 1) {
69 		tmp = __raw_readl(hp->mstp);
70 		tmp &= ~(1 << hp->bit);
71 		__raw_writel(tmp, hp->mstp);
72 	}
73 
74 	spin_unlock_irqrestore(&hwblk_lock, flags);
75 }
76 
hwblk_disable(struct hwblk_info * info,int hwblk)77 void hwblk_disable(struct hwblk_info *info, int hwblk)
78 {
79 	struct hwblk *hp = info->hwblks + hwblk;
80 	unsigned long tmp;
81 	unsigned long flags;
82 	int ret;
83 
84 	spin_lock_irqsave(&hwblk_lock, flags);
85 
86 	ret = __hwblk_mod_cnt(info, hwblk, HWBLK_CNT_USAGE, -1, 0);
87 	if (ret == 0) {
88 		tmp = __raw_readl(hp->mstp);
89 		tmp |= 1 << hp->bit;
90 		__raw_writel(tmp, hp->mstp);
91 	}
92 
93 	spin_unlock_irqrestore(&hwblk_lock, flags);
94 }
95 
96 struct hwblk_info *hwblk_info;
97 
hwblk_register(struct hwblk_info * info)98 int __init hwblk_register(struct hwblk_info *info)
99 {
100 	hwblk_info = info;
101 	return 0;
102 }
103 
arch_hwblk_init(void)104 int __init __weak arch_hwblk_init(void)
105 {
106 	return 0;
107 }
108 
arch_hwblk_sleep_mode(void)109 int __weak arch_hwblk_sleep_mode(void)
110 {
111 	return SUSP_SH_SLEEP;
112 }
113 
hwblk_init(void)114 int __init hwblk_init(void)
115 {
116 	return arch_hwblk_init();
117 }
118 
119 /* allow clocks to enable and disable hardware blocks */
sh_hwblk_clk_enable(struct clk * clk)120 static int sh_hwblk_clk_enable(struct clk *clk)
121 {
122 	if (!hwblk_info)
123 		return -ENOENT;
124 
125 	hwblk_enable(hwblk_info, clk->arch_flags);
126 	return 0;
127 }
128 
sh_hwblk_clk_disable(struct clk * clk)129 static void sh_hwblk_clk_disable(struct clk *clk)
130 {
131 	if (hwblk_info)
132 		hwblk_disable(hwblk_info, clk->arch_flags);
133 }
134 
135 static struct clk_ops sh_hwblk_clk_ops = {
136 	.enable		= sh_hwblk_clk_enable,
137 	.disable	= sh_hwblk_clk_disable,
138 	.recalc		= followparent_recalc,
139 };
140 
sh_hwblk_clk_register(struct clk * clks,int nr)141 int __init sh_hwblk_clk_register(struct clk *clks, int nr)
142 {
143 	struct clk *clkp;
144 	int ret = 0;
145 	int k;
146 
147 	for (k = 0; !ret && (k < nr); k++) {
148 		clkp = clks + k;
149 
150 		/* skip over clocks using hwblk 0 (HWBLK_UNKNOWN) */
151 		if (!clkp->arch_flags)
152 			continue;
153 
154 		clkp->ops = &sh_hwblk_clk_ops;
155 		ret |= clk_register(clkp);
156 	}
157 
158 	return ret;
159 }
160