ppc4xx_cpm.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. * PowerPC 4xx Clock and Power Management
  3. *
  4. * Copyright (C) 2010, Applied Micro Circuits Corporation
  5. * Victor Gallardo (vgallardo@apm.com)
  6. *
  7. * Based on arch/powerpc/platforms/44x/idle.c:
  8. * Jerone Young <jyoung5@us.ibm.com>
  9. * Copyright 2008 IBM Corp.
  10. *
  11. * Based on arch/powerpc/sysdev/fsl_pmc.c:
  12. * Anton Vorontsov <avorontsov@ru.mvista.com>
  13. * Copyright 2009 MontaVista Software, Inc.
  14. *
  15. * See file CREDITS for list of people who contributed to this
  16. * project.
  17. *
  18. * This program is free software; you can redistribute it and/or
  19. * modify it under the terms of the GNU General Public License as
  20. * published by the Free Software Foundation; either version 2 of
  21. * the License, or (at your option) any later version.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU General Public License
  29. * along with this program; if not, write to the Free Software
  30. * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  31. * MA 02111-1307 USA
  32. */
  33. #include <linux/kernel.h>
  34. #include <linux/of_platform.h>
  35. #include <linux/sysfs.h>
  36. #include <linux/cpu.h>
  37. #include <linux/suspend.h>
  38. #include <asm/dcr.h>
  39. #include <asm/dcr-native.h>
  40. #include <asm/machdep.h>
  41. #define CPM_ER 0
  42. #define CPM_FR 1
  43. #define CPM_SR 2
  44. #define CPM_IDLE_WAIT 0
  45. #define CPM_IDLE_DOZE 1
  46. struct cpm {
  47. dcr_host_t dcr_host;
  48. unsigned int dcr_offset[3];
  49. unsigned int powersave_off;
  50. unsigned int unused;
  51. unsigned int idle_doze;
  52. unsigned int standby;
  53. unsigned int suspend;
  54. };
  55. static struct cpm cpm;
  56. struct cpm_idle_mode {
  57. unsigned int enabled;
  58. const char *name;
  59. };
  60. static struct cpm_idle_mode idle_mode[] = {
  61. [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */
  62. [CPM_IDLE_DOZE] = { 0, "doze" },
  63. };
  64. static unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask)
  65. {
  66. unsigned int value;
  67. /* CPM controller supports 3 different types of sleep interface
  68. * known as class 1, 2 and 3. For class 1 units, they are
  69. * unconditionally put to sleep when the corresponding CPM bit is
  70. * set. For class 2 and 3 units this is not case; if they can be
  71. * put to to sleep, they will. Here we do not verify, we just
  72. * set them and expect them to eventually go off when they can.
  73. */
  74. value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]);
  75. dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask);
  76. /* return old state, to restore later if needed */
  77. return value;
  78. }
  79. static void cpm_idle_wait(void)
  80. {
  81. unsigned long msr_save;
  82. /* save off initial state */
  83. msr_save = mfmsr();
  84. /* sync required when CPM0_ER[CPU] is set */
  85. mb();
  86. /* set wait state MSR */
  87. mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE);
  88. isync();
  89. /* return to initial state */
  90. mtmsr(msr_save);
  91. isync();
  92. }
  93. static void cpm_idle_sleep(unsigned int mask)
  94. {
  95. unsigned int er_save;
  96. /* update CPM_ER state */
  97. er_save = cpm_set(CPM_ER, mask);
  98. /* go to wait state so that CPM0_ER[CPU] can take effect */
  99. cpm_idle_wait();
  100. /* restore CPM_ER state */
  101. dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save);
  102. }
  103. static void cpm_idle_doze(void)
  104. {
  105. cpm_idle_sleep(cpm.idle_doze);
  106. }
  107. static void cpm_idle_config(int mode)
  108. {
  109. int i;
  110. if (idle_mode[mode].enabled)
  111. return;
  112. for (i = 0; i < ARRAY_SIZE(idle_mode); i++)
  113. idle_mode[i].enabled = 0;
  114. idle_mode[mode].enabled = 1;
  115. }
  116. static ssize_t cpm_idle_show(struct kobject *kobj,
  117. struct kobj_attribute *attr, char *buf)
  118. {
  119. char *s = buf;
  120. int i;
  121. for (i = 0; i < ARRAY_SIZE(idle_mode); i++) {
  122. if (idle_mode[i].enabled)
  123. s += sprintf(s, "[%s] ", idle_mode[i].name);
  124. else
  125. s += sprintf(s, "%s ", idle_mode[i].name);
  126. }
  127. *(s-1) = '\n'; /* convert the last space to a newline */
  128. return s - buf;
  129. }
  130. static ssize_t cpm_idle_store(struct kobject *kobj,
  131. struct kobj_attribute *attr,
  132. const char *buf, size_t n)
  133. {
  134. int i;
  135. char *p;
  136. int len;
  137. p = memchr(buf, '\n', n);
  138. len = p ? p - buf : n;
  139. for (i = 0; i < ARRAY_SIZE(idle_mode); i++) {
  140. if (strncmp(buf, idle_mode[i].name, len) == 0) {
  141. cpm_idle_config(i);
  142. return n;
  143. }
  144. }
  145. return -EINVAL;
  146. }
  147. static struct kobj_attribute cpm_idle_attr =
  148. __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store);
  149. static void cpm_idle_config_sysfs(void)
  150. {
  151. struct device *dev;
  152. unsigned long ret;
  153. dev = get_cpu_device(0);
  154. ret = sysfs_create_file(&dev->kobj,
  155. &cpm_idle_attr.attr);
  156. if (ret)
  157. printk(KERN_WARNING
  158. "cpm: failed to create idle sysfs entry\n");
  159. }
  160. static void cpm_idle(void)
  161. {
  162. if (idle_mode[CPM_IDLE_DOZE].enabled)
  163. cpm_idle_doze();
  164. else
  165. cpm_idle_wait();
  166. }
  167. static int cpm_suspend_valid(suspend_state_t state)
  168. {
  169. switch (state) {
  170. case PM_SUSPEND_STANDBY:
  171. return !!cpm.standby;
  172. case PM_SUSPEND_MEM:
  173. return !!cpm.suspend;
  174. default:
  175. return 0;
  176. }
  177. }
  178. static void cpm_suspend_standby(unsigned int mask)
  179. {
  180. unsigned long tcr_save;
  181. /* disable decrement interrupt */
  182. tcr_save = mfspr(SPRN_TCR);
  183. mtspr(SPRN_TCR, tcr_save & ~TCR_DIE);
  184. /* go to sleep state */
  185. cpm_idle_sleep(mask);
  186. /* restore decrement interrupt */
  187. mtspr(SPRN_TCR, tcr_save);
  188. }
  189. static int cpm_suspend_enter(suspend_state_t state)
  190. {
  191. switch (state) {
  192. case PM_SUSPEND_STANDBY:
  193. cpm_suspend_standby(cpm.standby);
  194. break;
  195. case PM_SUSPEND_MEM:
  196. cpm_suspend_standby(cpm.suspend);
  197. break;
  198. }
  199. return 0;
  200. }
  201. static struct platform_suspend_ops cpm_suspend_ops = {
  202. .valid = cpm_suspend_valid,
  203. .enter = cpm_suspend_enter,
  204. };
  205. static int cpm_get_uint_property(struct device_node *np,
  206. const char *name)
  207. {
  208. int len;
  209. const unsigned int *prop = of_get_property(np, name, &len);
  210. if (prop == NULL || len < sizeof(u32))
  211. return 0;
  212. return *prop;
  213. }
  214. static int __init cpm_init(void)
  215. {
  216. struct device_node *np;
  217. int dcr_base, dcr_len;
  218. int ret = 0;
  219. if (!cpm.powersave_off) {
  220. cpm_idle_config(CPM_IDLE_WAIT);
  221. ppc_md.power_save = &cpm_idle;
  222. }
  223. np = of_find_compatible_node(NULL, NULL, "ibm,cpm");
  224. if (!np) {
  225. ret = -EINVAL;
  226. goto out;
  227. }
  228. dcr_base = dcr_resource_start(np, 0);
  229. dcr_len = dcr_resource_len(np, 0);
  230. if (dcr_base == 0 || dcr_len == 0) {
  231. printk(KERN_ERR "cpm: could not parse dcr property for %s\n",
  232. np->full_name);
  233. ret = -EINVAL;
  234. goto node_put;
  235. }
  236. cpm.dcr_host = dcr_map(np, dcr_base, dcr_len);
  237. if (!DCR_MAP_OK(cpm.dcr_host)) {
  238. printk(KERN_ERR "cpm: failed to map dcr property for %s\n",
  239. np->full_name);
  240. ret = -EINVAL;
  241. goto node_put;
  242. }
  243. /* All 4xx SoCs with a CPM controller have one of two
  244. * different order for the CPM registers. Some have the
  245. * CPM registers in the following order (ER,FR,SR). The
  246. * others have them in the following order (SR,ER,FR).
  247. */
  248. if (cpm_get_uint_property(np, "er-offset") == 0) {
  249. cpm.dcr_offset[CPM_ER] = 0;
  250. cpm.dcr_offset[CPM_FR] = 1;
  251. cpm.dcr_offset[CPM_SR] = 2;
  252. } else {
  253. cpm.dcr_offset[CPM_ER] = 1;
  254. cpm.dcr_offset[CPM_FR] = 2;
  255. cpm.dcr_offset[CPM_SR] = 0;
  256. }
  257. /* Now let's see what IPs to turn off for the following modes */
  258. cpm.unused = cpm_get_uint_property(np, "unused-units");
  259. cpm.idle_doze = cpm_get_uint_property(np, "idle-doze");
  260. cpm.standby = cpm_get_uint_property(np, "standby");
  261. cpm.suspend = cpm_get_uint_property(np, "suspend");
  262. /* If some IPs are unused let's turn them off now */
  263. if (cpm.unused) {
  264. cpm_set(CPM_ER, cpm.unused);
  265. cpm_set(CPM_FR, cpm.unused);
  266. }
  267. /* Now let's export interfaces */
  268. if (!cpm.powersave_off && cpm.idle_doze)
  269. cpm_idle_config_sysfs();
  270. if (cpm.standby || cpm.suspend)
  271. suspend_set_ops(&cpm_suspend_ops);
  272. node_put:
  273. of_node_put(np);
  274. out:
  275. return ret;
  276. }
  277. late_initcall(cpm_init);
  278. static int __init cpm_powersave_off(char *arg)
  279. {
  280. cpm.powersave_off = 1;
  281. return 0;
  282. }
  283. __setup("powersave=off", cpm_powersave_off);