bcm-pmb.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (c) 2013 Broadcom
  4. * Copyright (C) 2020 Rafał Miłecki <rafal@milecki.pl>
  5. */
  6. #include <dt-bindings/soc/bcm-pmb.h>
  7. #include <linux/io.h>
  8. #include <linux/module.h>
  9. #include <linux/of.h>
  10. #include <linux/platform_device.h>
  11. #include <linux/pm_domain.h>
  12. #include <linux/reset/bcm63xx_pmb.h>
  13. #define BPCM_ID_REG 0x00
  14. #define BPCM_CAPABILITIES 0x04
  15. #define BPCM_CAP_NUM_ZONES 0x000000ff
  16. #define BPCM_CAP_SR_REG_BITS 0x0000ff00
  17. #define BPCM_CAP_PLLTYPE 0x00030000
  18. #define BPCM_CAP_UBUS 0x00080000
  19. #define BPCM_CONTROL 0x08
  20. #define BPCM_STATUS 0x0c
  21. #define BPCM_ROSC_CONTROL 0x10
  22. #define BPCM_ROSC_THRESH_H 0x14
  23. #define BPCM_ROSC_THRESHOLD_BCM6838 0x14
  24. #define BPCM_ROSC_THRESH_S 0x18
  25. #define BPCM_ROSC_COUNT_BCM6838 0x18
  26. #define BPCM_ROSC_COUNT 0x1c
  27. #define BPCM_PWD_CONTROL_BCM6838 0x1c
  28. #define BPCM_PWD_CONTROL 0x20
  29. #define BPCM_SR_CONTROL_BCM6838 0x20
  30. #define BPCM_PWD_ACCUM_CONTROL 0x24
  31. #define BPCM_SR_CONTROL 0x28
  32. #define BPCM_GLOBAL_CONTROL 0x2c
  33. #define BPCM_MISC_CONTROL 0x30
  34. #define BPCM_MISC_CONTROL2 0x34
  35. #define BPCM_SGPHY_CNTL 0x38
  36. #define BPCM_SGPHY_STATUS 0x3c
  37. #define BPCM_ZONE0 0x40
  38. #define BPCM_ZONE_CONTROL 0x00
  39. #define BPCM_ZONE_CONTROL_MANUAL_CLK_EN 0x00000001
  40. #define BPCM_ZONE_CONTROL_MANUAL_RESET_CTL 0x00000002
  41. #define BPCM_ZONE_CONTROL_FREQ_SCALE_USED 0x00000004 /* R/O */
  42. #define BPCM_ZONE_CONTROL_DPG_CAPABLE 0x00000008 /* R/O */
  43. #define BPCM_ZONE_CONTROL_MANUAL_MEM_PWR 0x00000030
  44. #define BPCM_ZONE_CONTROL_MANUAL_ISO_CTL 0x00000040
  45. #define BPCM_ZONE_CONTROL_MANUAL_CTL 0x00000080
  46. #define BPCM_ZONE_CONTROL_DPG_CTL_EN 0x00000100
  47. #define BPCM_ZONE_CONTROL_PWR_DN_REQ 0x00000200
  48. #define BPCM_ZONE_CONTROL_PWR_UP_REQ 0x00000400
  49. #define BPCM_ZONE_CONTROL_MEM_PWR_CTL_EN 0x00000800
  50. #define BPCM_ZONE_CONTROL_BLK_RESET_ASSERT 0x00001000
  51. #define BPCM_ZONE_CONTROL_MEM_STBY 0x00002000
  52. #define BPCM_ZONE_CONTROL_RESERVED 0x0007c000
  53. #define BPCM_ZONE_CONTROL_PWR_CNTL_STATE 0x00f80000
  54. #define BPCM_ZONE_CONTROL_FREQ_SCALAR_DYN_SEL 0x01000000 /* R/O */
  55. #define BPCM_ZONE_CONTROL_PWR_OFF_STATE 0x02000000 /* R/O */
  56. #define BPCM_ZONE_CONTROL_PWR_ON_STATE 0x04000000 /* R/O */
  57. #define BPCM_ZONE_CONTROL_PWR_GOOD 0x08000000 /* R/O */
  58. #define BPCM_ZONE_CONTROL_DPG_PWR_STATE 0x10000000 /* R/O */
  59. #define BPCM_ZONE_CONTROL_MEM_PWR_STATE 0x20000000 /* R/O */
  60. #define BPCM_ZONE_CONTROL_ISO_STATE 0x40000000 /* R/O */
  61. #define BPCM_ZONE_CONTROL_RESET_STATE 0x80000000 /* R/O */
  62. #define BPCM_ZONE_CONFIG1 0x04
  63. #define BPCM_ZONE_CONFIG2 0x08
  64. #define BPCM_ZONE_FREQ_SCALAR_CONTROL 0x0c
  65. #define BPCM_ZONE_SIZE 0x10
  66. struct bcm_pmb {
  67. struct device *dev;
  68. void __iomem *base;
  69. spinlock_t lock;
  70. bool little_endian;
  71. struct genpd_onecell_data genpd_onecell_data;
  72. };
  73. struct bcm_pmb_pd_data {
  74. const char * const name;
  75. int id;
  76. u8 bus;
  77. u8 device;
  78. };
  79. struct bcm_pmb_pm_domain {
  80. struct bcm_pmb *pmb;
  81. const struct bcm_pmb_pd_data *data;
  82. struct generic_pm_domain genpd;
  83. };
  84. static int bcm_pmb_bpcm_read(struct bcm_pmb *pmb, int bus, u8 device,
  85. int offset, u32 *val)
  86. {
  87. void __iomem *base = pmb->base + bus * 0x20;
  88. unsigned long flags;
  89. int err;
  90. spin_lock_irqsave(&pmb->lock, flags);
  91. err = bpcm_rd(base, device, offset, val);
  92. spin_unlock_irqrestore(&pmb->lock, flags);
  93. if (!err)
  94. *val = pmb->little_endian ? le32_to_cpu(*val) : be32_to_cpu(*val);
  95. return err;
  96. }
  97. static int bcm_pmb_bpcm_write(struct bcm_pmb *pmb, int bus, u8 device,
  98. int offset, u32 val)
  99. {
  100. void __iomem *base = pmb->base + bus * 0x20;
  101. unsigned long flags;
  102. int err;
  103. val = pmb->little_endian ? cpu_to_le32(val) : cpu_to_be32(val);
  104. spin_lock_irqsave(&pmb->lock, flags);
  105. err = bpcm_wr(base, device, offset, val);
  106. spin_unlock_irqrestore(&pmb->lock, flags);
  107. return err;
  108. }
  109. static int bcm_pmb_power_off_zone(struct bcm_pmb *pmb, int bus, u8 device,
  110. int zone)
  111. {
  112. int offset;
  113. u32 val;
  114. int err;
  115. offset = BPCM_ZONE0 + zone * BPCM_ZONE_SIZE + BPCM_ZONE_CONTROL;
  116. err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);
  117. if (err)
  118. return err;
  119. val |= BPCM_ZONE_CONTROL_PWR_DN_REQ;
  120. val &= ~BPCM_ZONE_CONTROL_PWR_UP_REQ;
  121. err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);
  122. return err;
  123. }
  124. static int bcm_pmb_power_on_zone(struct bcm_pmb *pmb, int bus, u8 device,
  125. int zone)
  126. {
  127. int offset;
  128. u32 val;
  129. int err;
  130. offset = BPCM_ZONE0 + zone * BPCM_ZONE_SIZE + BPCM_ZONE_CONTROL;
  131. err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);
  132. if (err)
  133. return err;
  134. if (!(val & BPCM_ZONE_CONTROL_PWR_ON_STATE)) {
  135. val &= ~BPCM_ZONE_CONTROL_PWR_DN_REQ;
  136. val |= BPCM_ZONE_CONTROL_DPG_CTL_EN;
  137. val |= BPCM_ZONE_CONTROL_PWR_UP_REQ;
  138. val |= BPCM_ZONE_CONTROL_MEM_PWR_CTL_EN;
  139. val |= BPCM_ZONE_CONTROL_BLK_RESET_ASSERT;
  140. err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);
  141. }
  142. return err;
  143. }
  144. static int bcm_pmb_power_off_device(struct bcm_pmb *pmb, int bus, u8 device)
  145. {
  146. int offset;
  147. u32 val;
  148. int err;
  149. /* Entire device can be powered off by powering off the 0th zone */
  150. offset = BPCM_ZONE0 + BPCM_ZONE_CONTROL;
  151. err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);
  152. if (err)
  153. return err;
  154. if (!(val & BPCM_ZONE_CONTROL_PWR_OFF_STATE)) {
  155. val = BPCM_ZONE_CONTROL_PWR_DN_REQ;
  156. err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);
  157. }
  158. return err;
  159. }
  160. static int bcm_pmb_power_on_device(struct bcm_pmb *pmb, int bus, u8 device)
  161. {
  162. u32 val;
  163. int err;
  164. int i;
  165. err = bcm_pmb_bpcm_read(pmb, bus, device, BPCM_CAPABILITIES, &val);
  166. if (err)
  167. return err;
  168. for (i = 0; i < (val & BPCM_CAP_NUM_ZONES); i++) {
  169. err = bcm_pmb_power_on_zone(pmb, bus, device, i);
  170. if (err)
  171. return err;
  172. }
  173. return err;
  174. }
  175. static int bcm_pmb_power_on_sata(struct bcm_pmb *pmb, int bus, u8 device)
  176. {
  177. int err;
  178. err = bcm_pmb_power_on_zone(pmb, bus, device, 0);
  179. if (err)
  180. return err;
  181. /* Does not apply to the BCM963158 */
  182. err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_MISC_CONTROL, 0);
  183. if (err)
  184. return err;
  185. err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_SR_CONTROL, 0xffffffff);
  186. if (err)
  187. return err;
  188. err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_SR_CONTROL, 0);
  189. return err;
  190. }
  191. static int bcm_pmb_power_on(struct generic_pm_domain *genpd)
  192. {
  193. struct bcm_pmb_pm_domain *pd = container_of(genpd, struct bcm_pmb_pm_domain, genpd);
  194. const struct bcm_pmb_pd_data *data = pd->data;
  195. struct bcm_pmb *pmb = pd->pmb;
  196. switch (data->id) {
  197. case BCM_PMB_PCIE0:
  198. case BCM_PMB_PCIE1:
  199. case BCM_PMB_PCIE2:
  200. return bcm_pmb_power_on_zone(pmb, data->bus, data->device, 0);
  201. case BCM_PMB_HOST_USB:
  202. return bcm_pmb_power_on_device(pmb, data->bus, data->device);
  203. case BCM_PMB_SATA:
  204. return bcm_pmb_power_on_sata(pmb, data->bus, data->device);
  205. default:
  206. dev_err(pmb->dev, "unsupported device id: %d\n", data->id);
  207. return -EINVAL;
  208. }
  209. }
  210. static int bcm_pmb_power_off(struct generic_pm_domain *genpd)
  211. {
  212. struct bcm_pmb_pm_domain *pd = container_of(genpd, struct bcm_pmb_pm_domain, genpd);
  213. const struct bcm_pmb_pd_data *data = pd->data;
  214. struct bcm_pmb *pmb = pd->pmb;
  215. switch (data->id) {
  216. case BCM_PMB_PCIE0:
  217. case BCM_PMB_PCIE1:
  218. case BCM_PMB_PCIE2:
  219. return bcm_pmb_power_off_zone(pmb, data->bus, data->device, 0);
  220. case BCM_PMB_HOST_USB:
  221. return bcm_pmb_power_off_device(pmb, data->bus, data->device);
  222. default:
  223. dev_err(pmb->dev, "unsupported device id: %d\n", data->id);
  224. return -EINVAL;
  225. }
  226. }
  227. static int bcm_pmb_probe(struct platform_device *pdev)
  228. {
  229. struct device *dev = &pdev->dev;
  230. const struct bcm_pmb_pd_data *table;
  231. const struct bcm_pmb_pd_data *e;
  232. struct bcm_pmb *pmb;
  233. int max_id;
  234. int err;
  235. pmb = devm_kzalloc(dev, sizeof(*pmb), GFP_KERNEL);
  236. if (!pmb)
  237. return -ENOMEM;
  238. pmb->dev = dev;
  239. pmb->base = devm_platform_ioremap_resource(pdev, 0);
  240. if (IS_ERR(pmb->base))
  241. return PTR_ERR(pmb->base);
  242. spin_lock_init(&pmb->lock);
  243. pmb->little_endian = !of_device_is_big_endian(dev->of_node);
  244. table = of_device_get_match_data(dev);
  245. if (!table)
  246. return -EINVAL;
  247. max_id = 0;
  248. for (e = table; e->name; e++)
  249. max_id = max(max_id, e->id);
  250. pmb->genpd_onecell_data.num_domains = max_id + 1;
  251. pmb->genpd_onecell_data.domains =
  252. devm_kcalloc(dev, pmb->genpd_onecell_data.num_domains,
  253. sizeof(struct generic_pm_domain *), GFP_KERNEL);
  254. if (!pmb->genpd_onecell_data.domains)
  255. return -ENOMEM;
  256. for (e = table; e->name; e++) {
  257. struct bcm_pmb_pm_domain *pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
  258. if (!pd)
  259. return -ENOMEM;
  260. pd->pmb = pmb;
  261. pd->data = e;
  262. pd->genpd.name = e->name;
  263. pd->genpd.power_on = bcm_pmb_power_on;
  264. pd->genpd.power_off = bcm_pmb_power_off;
  265. pm_genpd_init(&pd->genpd, NULL, true);
  266. pmb->genpd_onecell_data.domains[e->id] = &pd->genpd;
  267. }
  268. err = of_genpd_add_provider_onecell(dev->of_node, &pmb->genpd_onecell_data);
  269. if (err) {
  270. dev_err(dev, "failed to add genpd provider: %d\n", err);
  271. return err;
  272. }
  273. return 0;
  274. }
  275. static const struct bcm_pmb_pd_data bcm_pmb_bcm4908_data[] = {
  276. { .name = "pcie2", .id = BCM_PMB_PCIE2, .bus = 0, .device = 2, },
  277. { .name = "pcie0", .id = BCM_PMB_PCIE0, .bus = 1, .device = 14, },
  278. { .name = "pcie1", .id = BCM_PMB_PCIE1, .bus = 1, .device = 15, },
  279. { .name = "usb", .id = BCM_PMB_HOST_USB, .bus = 1, .device = 17, },
  280. { },
  281. };
  282. static const struct bcm_pmb_pd_data bcm_pmb_bcm63138_data[] = {
  283. { .name = "sata", .id = BCM_PMB_SATA, .bus = 0, .device = 3, },
  284. { },
  285. };
  286. static const struct of_device_id bcm_pmb_of_match[] = {
  287. { .compatible = "brcm,bcm4908-pmb", .data = &bcm_pmb_bcm4908_data, },
  288. { .compatible = "brcm,bcm63138-pmb", .data = &bcm_pmb_bcm63138_data, },
  289. { },
  290. };
  291. static struct platform_driver bcm_pmb_driver = {
  292. .driver = {
  293. .name = "bcm-pmb",
  294. .of_match_table = bcm_pmb_of_match,
  295. },
  296. .probe = bcm_pmb_probe,
  297. };
  298. builtin_platform_driver(bcm_pmb_driver);