light_aon_pd.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * Copyright (C) 2021 Alibaba Group Holding Limited.
  4. */
  5. #include <dt-bindings/firmware/thead/rsrc.h>
  6. #include <linux/ctype.h>
  7. #include <linux/debugfs.h>
  8. #include <linux/firmware/thead/ipc.h>
  9. #include <linux/io.h>
  10. #include <linux/module.h>
  11. #include <linux/of.h>
  12. #include <linux/of_address.h>
  13. #include <linux/of_platform.h>
  14. #include <linux/platform_device.h>
  15. #include <linux/pm.h>
  16. #include <linux/pm_domain.h>
  17. #include <linux/seq_file.h>
  18. #include <linux/slab.h>
  19. struct light_aon_msg_req_set_resource_power_mode {
  20. struct light_aon_rpc_msg_hdr hdr;
  21. u16 resource;
  22. u16 mode;
  23. u16 reserved[10];
  24. } __packed __aligned(4);
  25. #define LIGHT_AONU_PD_NAME_SIZE 20
  26. #define LIGHT_AONU_PD_STATE_NAME_SIZE 10
  27. struct light_aon_pm_domain {
  28. struct generic_pm_domain pd;
  29. char name[LIGHT_AONU_PD_NAME_SIZE];
  30. u16 rsrc;
  31. };
  32. struct light_aon_pd_range {
  33. char *name;
  34. u32 rsrc;
  35. u8 num;
  36. /* add domain index */
  37. bool postfix;
  38. u8 start_from;
  39. };
  40. struct light_aon_pd_soc {
  41. const struct light_aon_pd_range *pd_ranges;
  42. u8 num_ranges;
  43. };
  44. static const struct light_aon_pd_range light_aon_pd_ranges[] = {
  45. /* AUDIO SS */
  46. { "audio", LIGHT_AON_AUDIO_PD, 1, false, 0 },
  47. { "vdec", LIGHT_AON_VDEC_PD, 1, false, 0},
  48. { "npu", LIGHT_AON_NPU_PD, 1, false, 0},
  49. { "venc", LIGHT_AON_VENC_PD, 1, false, 0},
  50. { "gpu", LIGHT_AON_GPU_PD, 1, false, 0},
  51. { "dsp0", LIGHT_AON_DSP0_PD, 1, false, 0},
  52. { "dsp1", LIGHT_AON_DSP1_PD, 1, false, 0},
  53. {},
  54. };
  55. static const struct light_aon_pd_soc light_aon_pd = {
  56. .pd_ranges = light_aon_pd_ranges,
  57. .num_ranges = ARRAY_SIZE(light_aon_pd_ranges),
  58. };
  59. static struct light_aon_ipc *pm_ipc_handle;
  60. static struct dentry *pd_debugfs_root;
  61. struct dentry *pd_pde;
  62. struct genpd_onecell_data *genpd_data;
  63. static inline struct light_aon_pm_domain *to_light_aon_pd(struct generic_pm_domain *genpd)
  64. {
  65. return container_of(genpd, struct light_aon_pm_domain, pd);
  66. }
  67. static int light_aon_pd_power(struct generic_pm_domain *domain, bool power_on)
  68. {
  69. struct light_aon_msg_req_set_resource_power_mode msg;
  70. struct light_aon_rpc_msg_hdr *hdr = &msg.hdr;
  71. struct light_aon_pm_domain *pd;
  72. int ret;
  73. pd = to_light_aon_pd(domain);
  74. hdr->ver = LIGHT_AON_RPC_VERSION;
  75. hdr->svc = LIGHT_AON_RPC_SVC_PM;
  76. hdr->func = LIGHT_AON_PM_FUNC_SET_RESOURCE_POWER_MODE;
  77. hdr->size = LIGHT_AON_RPC_MSG_NUM;
  78. msg.resource = pd->rsrc;
  79. msg.mode = power_on ? LIGHT_AON_PM_PW_MODE_ON : LIGHT_AON_PM_PW_MODE_OFF;
  80. ret = light_aon_call_rpc(pm_ipc_handle, &msg, true);
  81. if (ret)
  82. dev_err(&domain->dev, "failed to power %s resource %d ret %d\n",
  83. power_on ? "up" : "off", pd->rsrc, ret);
  84. return ret;
  85. }
  86. static int light_aon_pd_power_on(struct generic_pm_domain *domain)
  87. {
  88. return light_aon_pd_power(domain, true);
  89. }
  90. static int light_aon_pd_power_off(struct generic_pm_domain *domain)
  91. {
  92. return light_aon_pd_power(domain, false);
  93. }
  94. static struct generic_pm_domain *light_aon_pd_xlate(struct of_phandle_args *spec,
  95. void *data)
  96. {
  97. struct generic_pm_domain *domain = ERR_PTR(-ENOENT);
  98. struct genpd_onecell_data *pd_data = data;
  99. unsigned int i;
  100. for (i = 0; i < pd_data->num_domains; i++) {
  101. struct light_aon_pm_domain *aon_pd;
  102. aon_pd = to_light_aon_pd(pd_data->domains[i]);
  103. if (aon_pd->rsrc == spec->args[0]) {
  104. domain = &aon_pd->pd;
  105. break;
  106. }
  107. }
  108. return domain;
  109. }
  110. static struct light_aon_pm_domain *
  111. light_aon_add_pm_domain(struct device *dev, int idx,
  112. const struct light_aon_pd_range *pd_ranges)
  113. {
  114. struct light_aon_pm_domain *aon_pd;
  115. int ret;
  116. aon_pd = devm_kzalloc(dev, sizeof(*aon_pd), GFP_KERNEL);
  117. if (!aon_pd)
  118. return ERR_PTR(-ENOMEM);
  119. aon_pd->rsrc = pd_ranges->rsrc + idx;
  120. aon_pd->pd.power_off = light_aon_pd_power_off;
  121. aon_pd->pd.power_on = light_aon_pd_power_on;
  122. if (pd_ranges->postfix)
  123. snprintf(aon_pd->name, sizeof(aon_pd->name),
  124. "%s%i", pd_ranges->name, pd_ranges->start_from + idx);
  125. else
  126. snprintf(aon_pd->name, sizeof(aon_pd->name),
  127. "%s", pd_ranges->name);
  128. aon_pd->pd.name = aon_pd->name;
  129. #if 0
  130. if (aon_pd->rsrc >= LIGHT_AON_R_LAST) {
  131. dev_warn(dev, "invalid pd %s rsrc id %d found",
  132. aon_pd->name, aon_pd->rsrc);
  133. devm_kfree(dev, aon_pd);
  134. return NULL;
  135. }
  136. #endif
  137. ret = pm_genpd_init(&aon_pd->pd, NULL, true);
  138. if (ret) {
  139. dev_warn(dev, "failed to init pd %s rsrc id %d",
  140. aon_pd->name, aon_pd->rsrc);
  141. devm_kfree(dev, aon_pd);
  142. return NULL;
  143. }
  144. return aon_pd;
  145. }
  146. static int light_aon_init_pm_domains(struct device *dev,
  147. const struct light_aon_pd_soc *pd_soc)
  148. {
  149. const struct light_aon_pd_range *pd_ranges = pd_soc->pd_ranges;
  150. struct generic_pm_domain **domains;
  151. struct genpd_onecell_data *pd_data;
  152. struct light_aon_pm_domain *aon_pd;
  153. u32 count = 0;
  154. int i, j;
  155. for (i = 0; i < pd_soc->num_ranges; i++)
  156. count += pd_ranges[i].num;
  157. domains = devm_kcalloc(dev, count, sizeof(*domains), GFP_KERNEL);
  158. if (!domains)
  159. return -ENOMEM;
  160. pd_data = devm_kzalloc(dev, sizeof(*pd_data), GFP_KERNEL);
  161. if (!pd_data)
  162. return -ENOMEM;
  163. count = 0;
  164. for (i = 0; i < pd_soc->num_ranges; i++) {
  165. for (j = 0; j < pd_ranges[i].num; j++) {
  166. aon_pd = light_aon_add_pm_domain(dev, j, &pd_ranges[i]);
  167. if (IS_ERR_OR_NULL(aon_pd))
  168. continue;
  169. domains[count++] = &aon_pd->pd;
  170. dev_dbg(dev, "added power domain %s\n", aon_pd->pd.name);
  171. }
  172. }
  173. pd_data->domains = domains;
  174. pd_data->num_domains = count;
  175. pd_data->xlate = light_aon_pd_xlate;
  176. genpd_data = pd_data;
  177. of_genpd_add_provider_onecell(dev->of_node, pd_data);
  178. return 0;
  179. }
  180. static char *pd_get_user_string(const char __user *userbuf, size_t userlen)
  181. {
  182. char *buffer;
  183. buffer = vmalloc(userlen + 1);
  184. if (!buffer)
  185. return ERR_PTR(-ENOMEM);
  186. if (copy_from_user(buffer, userbuf, userlen) != 0) {
  187. vfree(buffer);
  188. return ERR_PTR(-EFAULT);
  189. }
  190. /* got the string, now strip linefeed. */
  191. if (buffer[userlen - 1] == '\n')
  192. buffer[userlen -1] = '\0';
  193. else
  194. buffer[userlen] = '\0';
  195. pr_debug("buffer = %s\n", buffer);
  196. return buffer;
  197. }
  198. static ssize_t light_power_domain_write(struct file *file,
  199. const char __user *userbuf,
  200. size_t userlen, loff_t *ppos)
  201. {
  202. char *buffer, *start, *end;
  203. struct seq_file *m = (struct seq_file *)file->private_data;
  204. struct genpd_onecell_data *aon_pds_data = m->private;
  205. struct generic_pm_domain *hitted_pm_genpd;
  206. char pd_name[LIGHT_AONU_PD_NAME_SIZE];
  207. char pd_state[LIGHT_AONU_PD_STATE_NAME_SIZE];
  208. int idx, ret;
  209. size_t origin_len = userlen;
  210. buffer = pd_get_user_string(userbuf, userlen);
  211. if (IS_ERR(buffer))
  212. return PTR_ERR(buffer);
  213. start = skip_spaces(buffer);
  214. end = start;
  215. while(!isspace(*end) && *end != '\0')
  216. end++;
  217. *end = '\0';
  218. strcpy(pd_name, start);
  219. pr_debug("power domain name: %s\n", pd_name);
  220. /* find the target power domain */
  221. for (idx = 0; idx < aon_pds_data->num_domains; idx++) {
  222. struct generic_pm_domain *domain = aon_pds_data->domains[idx];
  223. pr_debug("generic pm domain name: %s, pd_name: %s, ret = %d\n",
  224. domain->name, pd_name, strcmp(pd_name, domain->name));
  225. if (strcmp(pd_name, domain->name))
  226. continue;
  227. else {
  228. hitted_pm_genpd = aon_pds_data->domains[idx];
  229. pr_debug("target pm power domain-%s found, index: %d\n",
  230. hitted_pm_genpd->name, idx);
  231. break;
  232. }
  233. }
  234. if (idx >= aon_pds_data->num_domains) {
  235. pr_err("no taget power domain-%s found, idx = %d, total pd numbers = %d\n",
  236. pd_name, idx, aon_pds_data->num_domains);
  237. userlen = -EINVAL;
  238. goto out;
  239. }
  240. if (!hitted_pm_genpd->power_on && !hitted_pm_genpd->power_off) {
  241. pr_err("no power operations registered for power domain-%s\n", pd_name);
  242. userlen = -EINVAL;
  243. goto out;
  244. }
  245. end = end + 1;
  246. start = skip_spaces(end);
  247. end = start;
  248. while(!isspace(*end) && *end != '\0')
  249. end++;
  250. *end = '\0';
  251. strcpy(pd_state, start);
  252. pr_debug("power domain target state: %s\n", pd_state);
  253. if (!strcmp(pd_state, "on")) {
  254. ret = hitted_pm_genpd->power_on(hitted_pm_genpd);
  255. if (ret) {
  256. userlen = ret;
  257. goto out;
  258. }
  259. } else if (!strcmp(pd_state, "off")) {
  260. ret = hitted_pm_genpd->power_off(hitted_pm_genpd);
  261. if (ret) {
  262. userlen = ret;
  263. goto out;
  264. }
  265. } else {
  266. pr_err("invalid power domain target state, not 'on' or 'off'\n");
  267. userlen = -EINVAL;
  268. goto out;
  269. }
  270. out:
  271. memset(buffer, 0, origin_len);
  272. vfree(buffer);
  273. return userlen;
  274. }
  275. static int light_power_domain_show(struct seq_file *m, void *v)
  276. {
  277. struct genpd_onecell_data *pd_data = m->private;
  278. u32 count = pd_data->num_domains;
  279. int idx;
  280. seq_puts(m, "[Power domain name list]: ");
  281. for(idx = 0; idx < count; idx++)
  282. seq_printf(m, "%s ", pd_data->domains[idx]->name);
  283. seq_printf(m, "\n");
  284. seq_puts(m, "[Power on domain usage]: echo power_name on > domain\n");
  285. seq_puts(m, "[Power off domain usage]: echo power_name off > domain\n");
  286. return 0;
  287. }
  288. static int light_power_domain_open(struct inode *inode, struct file *file)
  289. {
  290. struct genpd_onecell_data *pd_data = inode->i_private;
  291. return single_open(file, light_power_domain_show, pd_data);
  292. }
  293. static const struct file_operations light_power_domain_fops = {
  294. .owner = THIS_MODULE,
  295. .write = light_power_domain_write,
  296. .read = seq_read,
  297. .open = light_power_domain_open,
  298. .llseek = generic_file_llseek,
  299. };
  300. static void pd_debugfs_init(struct genpd_onecell_data *aon_pds_data)
  301. {
  302. umode_t mode = S_IRUSR | S_IWUSR | S_IFREG;
  303. pd_debugfs_root = debugfs_create_dir("power_domain", NULL);
  304. if (!pd_debugfs_root || IS_ERR(pd_debugfs_root))
  305. return;
  306. pd_pde = debugfs_create_file("domain", mode, pd_debugfs_root, (void *)aon_pds_data, &light_power_domain_fops);
  307. pr_info("succeed to create power domain debugfs direntry\n");
  308. }
  309. static int light_aon_pd_probe(struct platform_device *pdev)
  310. {
  311. const struct light_aon_pd_soc *pd_soc;
  312. int ret;
  313. ret = light_aon_get_handle(&pm_ipc_handle);
  314. if (ret)
  315. return ret;
  316. pd_soc = of_device_get_match_data(&pdev->dev);
  317. if (!pd_soc)
  318. return -ENODEV;
  319. ret = light_aon_init_pm_domains(&pdev->dev, pd_soc);
  320. if (ret)
  321. return ret;
  322. pd_debugfs_init(genpd_data);
  323. return 0;
  324. }
  325. static const struct of_device_id light_aon_pd_match[] = {
  326. { .compatible = "thead,light-aon-pd", &light_aon_pd},
  327. { /* sentinel */ }
  328. };
  329. static struct platform_driver light_aon_pd_driver = {
  330. .driver = {
  331. .name = "light-aon-pd",
  332. .of_match_table = light_aon_pd_match,
  333. },
  334. .probe = light_aon_pd_probe,
  335. };
  336. builtin_platform_driver(light_aon_pd_driver);
  337. MODULE_AUTHOR("fugang.duan <duanfugang.dfg@linux.alibaba.com>");
  338. MODULE_DESCRIPTION("Thead Light firmware protocol driver");
  339. MODULE_LICENSE("GPL v2");