1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * BCM63xx Power Domain Controller Driver
4 *
5 * Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
6 */
7
8#include <dt-bindings/soc/bcm6318-pm.h>
9#include <dt-bindings/soc/bcm6328-pm.h>
10#include <dt-bindings/soc/bcm6362-pm.h>
11#include <dt-bindings/soc/bcm63268-pm.h>
12#include <linux/io.h>
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/pm_domain.h>
16#include <linux/of.h>
17#include <linux/of_device.h>
18
19struct bcm63xx_power_dev {
20	struct generic_pm_domain genpd;
21	struct bcm63xx_power *power;
22	uint32_t mask;
23};
24
25struct bcm63xx_power {
26	void __iomem *base;
27	spinlock_t lock;
28	struct bcm63xx_power_dev *dev;
29	struct genpd_onecell_data genpd_data;
30	struct generic_pm_domain **genpd;
31};
32
33struct bcm63xx_power_data {
34	const char * const name;
35	uint8_t bit;
36	unsigned int flags;
37};
38
39static int bcm63xx_power_get_state(struct bcm63xx_power_dev *pmd, bool *is_on)
40{
41	struct bcm63xx_power *power = pmd->power;
42
43	if (!pmd->mask) {
44		*is_on = false;
45		return -EINVAL;
46	}
47
48	*is_on = !(__raw_readl(power->base) & pmd->mask);
49
50	return 0;
51}
52
53static int bcm63xx_power_set_state(struct bcm63xx_power_dev *pmd, bool on)
54{
55	struct bcm63xx_power *power = pmd->power;
56	unsigned long flags;
57	uint32_t val;
58
59	if (!pmd->mask)
60		return -EINVAL;
61
62	spin_lock_irqsave(&power->lock, flags);
63	val = __raw_readl(power->base);
64	if (on)
65		val &= ~pmd->mask;
66	else
67		val |= pmd->mask;
68	__raw_writel(val, power->base);
69	spin_unlock_irqrestore(&power->lock, flags);
70
71	return 0;
72}
73
74static int bcm63xx_power_on(struct generic_pm_domain *genpd)
75{
76	struct bcm63xx_power_dev *pmd = container_of(genpd,
77		struct bcm63xx_power_dev, genpd);
78
79	return bcm63xx_power_set_state(pmd, true);
80}
81
82static int bcm63xx_power_off(struct generic_pm_domain *genpd)
83{
84	struct bcm63xx_power_dev *pmd = container_of(genpd,
85		struct bcm63xx_power_dev, genpd);
86
87	return bcm63xx_power_set_state(pmd, false);
88}
89
90static int bcm63xx_power_probe(struct platform_device *pdev)
91{
92	struct device *dev = &pdev->dev;
93	struct device_node *np = dev->of_node;
94	struct resource *res;
95	const struct bcm63xx_power_data *entry, *table;
96	struct bcm63xx_power *power;
97	unsigned int ndom;
98	uint8_t max_bit = 0;
99	int ret;
100
101	power = devm_kzalloc(dev, sizeof(*power), GFP_KERNEL);
102	if (!power)
103		return -ENOMEM;
104
105	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
106	power->base = devm_ioremap_resource(&pdev->dev, res);
107	if (IS_ERR(power->base))
108		return PTR_ERR(power->base);
109
110	table = of_device_get_match_data(dev);
111	if (!table)
112		return -EINVAL;
113
114	power->genpd_data.num_domains = 0;
115	ndom = 0;
116	for (entry = table; entry->name; entry++) {
117		max_bit = max(max_bit, entry->bit);
118		ndom++;
119	}
120
121	if (!ndom)
122		return -ENODEV;
123
124	power->genpd_data.num_domains = max_bit + 1;
125
126	power->dev = devm_kcalloc(dev, power->genpd_data.num_domains,
127				  sizeof(struct bcm63xx_power_dev),
128				  GFP_KERNEL);
129	if (!power->dev)
130		return -ENOMEM;
131
132	power->genpd = devm_kcalloc(dev, power->genpd_data.num_domains,
133				    sizeof(struct generic_pm_domain *),
134				    GFP_KERNEL);
135	if (!power->genpd)
136		return -ENOMEM;
137
138	power->genpd_data.domains = power->genpd;
139
140	ndom = 0;
141	for (entry = table; entry->name; entry++) {
142		struct bcm63xx_power_dev *pmd = &power->dev[ndom];
143		bool is_on;
144
145		pmd->power = power;
146		pmd->mask = BIT(entry->bit);
147		pmd->genpd.name = entry->name;
148		pmd->genpd.flags = entry->flags;
149
150		ret = bcm63xx_power_get_state(pmd, &is_on);
151		if (ret)
152			dev_warn(dev, "unable to get current state for %s\n",
153				 pmd->genpd.name);
154
155		pmd->genpd.power_on = bcm63xx_power_on;
156		pmd->genpd.power_off = bcm63xx_power_off;
157
158		pm_genpd_init(&pmd->genpd, NULL, !is_on);
159		power->genpd[entry->bit] = &pmd->genpd;
160
161		ndom++;
162	}
163
164	spin_lock_init(&power->lock);
165
166	ret = of_genpd_add_provider_onecell(np, &power->genpd_data);
167	if (ret) {
168		dev_err(dev, "failed to register genpd driver: %d\n", ret);
169		return ret;
170	}
171
172	dev_info(dev, "registered %u power domains\n", ndom);
173
174	return 0;
175}
176
177static const struct bcm63xx_power_data bcm6318_power_domains[] = {
178	{
179		.name = "pcie",
180		.bit = BCM6318_POWER_DOMAIN_PCIE,
181	}, {
182		.name = "usb",
183		.bit = BCM6318_POWER_DOMAIN_USB,
184	}, {
185		.name = "ephy0",
186		.bit = BCM6318_POWER_DOMAIN_EPHY0,
187	}, {
188		.name = "ephy1",
189		.bit = BCM6318_POWER_DOMAIN_EPHY1,
190	}, {
191		.name = "ephy2",
192		.bit = BCM6318_POWER_DOMAIN_EPHY2,
193	}, {
194		.name = "ephy3",
195		.bit = BCM6318_POWER_DOMAIN_EPHY3,
196	}, {
197		.name = "ldo2p5",
198		.bit = BCM6318_POWER_DOMAIN_LDO2P5,
199		.flags = GENPD_FLAG_ALWAYS_ON,
200	}, {
201		.name = "ldo2p9",
202		.bit = BCM6318_POWER_DOMAIN_LDO2P9,
203		.flags = GENPD_FLAG_ALWAYS_ON,
204	}, {
205		.name = "sw1p0",
206		.bit = BCM6318_POWER_DOMAIN_SW1P0,
207		.flags = GENPD_FLAG_ALWAYS_ON,
208	}, {
209		.name = "pad",
210		.bit = BCM6318_POWER_DOMAIN_PAD,
211		.flags = GENPD_FLAG_ALWAYS_ON,
212	}, {
213		/* sentinel */
214	},
215};
216
217static const struct bcm63xx_power_data bcm6328_power_domains[] = {
218	{
219		.name = "adsl2-mips",
220		.bit = BCM6328_POWER_DOMAIN_ADSL2_MIPS,
221	}, {
222		.name = "adsl2-phy",
223		.bit = BCM6328_POWER_DOMAIN_ADSL2_PHY,
224	}, {
225		.name = "adsl2-afe",
226		.bit = BCM6328_POWER_DOMAIN_ADSL2_AFE,
227	}, {
228		.name = "sar",
229		.bit = BCM6328_POWER_DOMAIN_SAR,
230	}, {
231		.name = "pcm",
232		.bit = BCM6328_POWER_DOMAIN_PCM,
233	}, {
234		.name = "usbd",
235		.bit = BCM6328_POWER_DOMAIN_USBD,
236	}, {
237		.name = "usbh",
238		.bit = BCM6328_POWER_DOMAIN_USBH,
239	}, {
240		.name = "pcie",
241		.bit = BCM6328_POWER_DOMAIN_PCIE,
242	}, {
243		.name = "robosw",
244		.bit = BCM6328_POWER_DOMAIN_ROBOSW,
245	}, {
246		.name = "ephy",
247		.bit = BCM6328_POWER_DOMAIN_EPHY,
248	}, {
249		/* sentinel */
250	},
251};
252
253static const struct bcm63xx_power_data bcm6362_power_domains[] = {
254	{
255		.name = "sar",
256		.bit = BCM6362_POWER_DOMAIN_SAR,
257	}, {
258		.name = "ipsec",
259		.bit = BCM6362_POWER_DOMAIN_IPSEC,
260	}, {
261		.name = "mips",
262		.bit = BCM6362_POWER_DOMAIN_MIPS,
263		.flags = GENPD_FLAG_ALWAYS_ON,
264	}, {
265		.name = "dect",
266		.bit = BCM6362_POWER_DOMAIN_DECT,
267	}, {
268		.name = "usbh",
269		.bit = BCM6362_POWER_DOMAIN_USBH,
270	}, {
271		.name = "usbd",
272		.bit = BCM6362_POWER_DOMAIN_USBD,
273	}, {
274		.name = "robosw",
275		.bit = BCM6362_POWER_DOMAIN_ROBOSW,
276	}, {
277		.name = "pcm",
278		.bit = BCM6362_POWER_DOMAIN_PCM,
279	}, {
280		.name = "periph",
281		.bit = BCM6362_POWER_DOMAIN_PERIPH,
282		.flags = GENPD_FLAG_ALWAYS_ON,
283	}, {
284		.name = "adsl-phy",
285		.bit = BCM6362_POWER_DOMAIN_ADSL_PHY,
286	}, {
287		.name = "gmii-pads",
288		.bit = BCM6362_POWER_DOMAIN_GMII_PADS,
289	}, {
290		.name = "fap",
291		.bit = BCM6362_POWER_DOMAIN_FAP,
292	}, {
293		.name = "pcie",
294		.bit = BCM6362_POWER_DOMAIN_PCIE,
295	}, {
296		.name = "wlan-pads",
297		.bit = BCM6362_POWER_DOMAIN_WLAN_PADS,
298	}, {
299		/* sentinel */
300	},
301};
302
303static const struct bcm63xx_power_data bcm63268_power_domains[] = {
304	{
305		.name = "sar",
306		.bit = BCM63268_POWER_DOMAIN_SAR,
307	}, {
308		.name = "ipsec",
309		.bit = BCM63268_POWER_DOMAIN_IPSEC,
310	}, {
311		.name = "mips",
312		.bit = BCM63268_POWER_DOMAIN_MIPS,
313		.flags = GENPD_FLAG_ALWAYS_ON,
314	}, {
315		.name = "dect",
316		.bit = BCM63268_POWER_DOMAIN_DECT,
317	}, {
318		.name = "usbh",
319		.bit = BCM63268_POWER_DOMAIN_USBH,
320	}, {
321		.name = "usbd",
322		.bit = BCM63268_POWER_DOMAIN_USBD,
323	}, {
324		.name = "robosw",
325		.bit = BCM63268_POWER_DOMAIN_ROBOSW,
326	}, {
327		.name = "pcm",
328		.bit = BCM63268_POWER_DOMAIN_PCM,
329	}, {
330		.name = "periph",
331		.bit = BCM63268_POWER_DOMAIN_PERIPH,
332		.flags = GENPD_FLAG_ALWAYS_ON,
333	}, {
334		.name = "vdsl-phy",
335		.bit = BCM63268_POWER_DOMAIN_VDSL_PHY,
336	}, {
337		.name = "vdsl-mips",
338		.bit = BCM63268_POWER_DOMAIN_VDSL_MIPS,
339	}, {
340		.name = "fap",
341		.bit = BCM63268_POWER_DOMAIN_FAP,
342	}, {
343		.name = "pcie",
344		.bit = BCM63268_POWER_DOMAIN_PCIE,
345	}, {
346		.name = "wlan-pads",
347		.bit = BCM63268_POWER_DOMAIN_WLAN_PADS,
348	}, {
349		/* sentinel */
350	},
351};
352
353static const struct of_device_id bcm63xx_power_of_match[] = {
354	{
355		.compatible = "brcm,bcm6318-power-controller",
356		.data = &bcm6318_power_domains,
357	}, {
358		.compatible = "brcm,bcm6328-power-controller",
359		.data = &bcm6328_power_domains,
360	}, {
361		.compatible = "brcm,bcm6362-power-controller",
362		.data = &bcm6362_power_domains,
363	}, {
364		.compatible = "brcm,bcm63268-power-controller",
365		.data = &bcm63268_power_domains,
366	}, {
367		/* sentinel */
368	}
369};
370
371static struct platform_driver bcm63xx_power_driver = {
372	.driver = {
373		.name = "bcm63xx-power-controller",
374		.of_match_table = bcm63xx_power_of_match,
375	},
376	.probe  = bcm63xx_power_probe,
377};
378builtin_platform_driver(bcm63xx_power_driver);
379