1 /*-
2  * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
3  * Copyright (c) 2010, Broadcom Corporation.
4  * All rights reserved.
5  *
6  * This file is derived from the siutils.c source distributed with the
7  * Asus RT-N16 firmware source code release.
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
16  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
18  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
19  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20  *
21  * $Id: siutils.c,v 1.821.2.48 2011-02-11 20:59:28 Exp $
22  */
23 
24 #include <sys/cdefs.h>
25 __FBSDID("$FreeBSD$");
26 
27 #include <sys/param.h>
28 #include <sys/kernel.h>
29 #include <sys/bus.h>
30 #include <sys/limits.h>
31 #include <sys/malloc.h>
32 #include <sys/module.h>
33 #include <sys/systm.h>
34 
35 #include <dev/bhnd/bhnd.h>
36 
37 #include <dev/bhnd/cores/chipc/chipcreg.h>
38 #include <dev/bhnd/cores/chipc/chipcvar.h>
39 
40 #include <dev/bhnd/cores/pmu/bhnd_pmuvar.h>
41 #include <dev/bhnd/cores/pmu/bhnd_pmureg.h>
42 
43 #include "bhnd_chipc_if.h"
44 
45 #include "bhnd_pwrctl_private.h"
46 
47 /*
48  * ChipCommon Power Control.
49  *
50  * Provides a bhnd_pmu_if-compatible interface to device clocking and
51  * power management on non-PMU chipsets.
52  */
53 
54 typedef enum {
55 	BHND_PWRCTL_WAR_UP,	/**< apply attach/resume workarounds */
56 	BHND_PWRCTL_WAR_RUN,	/**< apply running workarounds */
57 	BHND_PWRCTL_WAR_DOWN,	/**< apply detach/suspend workarounds */
58 } bhnd_pwrctl_wars;
59 
60 static int	bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc,
61 		    bhnd_pwrctl_wars wars);
62 
63 static struct bhnd_device_quirk pwrctl_quirks[];
64 
65 
66 /* Supported parent core device identifiers */
67 static const struct bhnd_device pwrctl_devices[] = {
68 	BHND_DEVICE(BCM, CC, "ChipCommon Power Control", pwrctl_quirks),
69 	BHND_DEVICE_END
70 };
71 
72 /* Device quirks table */
73 static struct bhnd_device_quirk pwrctl_quirks[] = {
74 	BHND_CORE_QUIRK	(HWREV_LTE(5),		PWRCTL_QUIRK_PCICLK_CTL),
75 	BHND_CORE_QUIRK	(HWREV_RANGE(6, 9),	PWRCTL_QUIRK_SLOWCLK_CTL),
76 	BHND_CORE_QUIRK	(HWREV_RANGE(10, 19),	PWRCTL_QUIRK_INSTACLK_CTL),
77 
78 	BHND_DEVICE_QUIRK_END
79 };
80 
81 static int
82 bhnd_pwrctl_probe(device_t dev)
83 {
84 	const struct bhnd_device	*id;
85 	struct chipc_caps		*ccaps;
86 	device_t			 chipc;
87 
88 	/* Look for compatible chipc parent */
89 	chipc = device_get_parent(dev);
90 	if (device_get_devclass(chipc) != devclass_find("bhnd_chipc"))
91 		return (ENXIO);
92 
93 	if (device_get_driver(chipc) != &bhnd_chipc_driver)
94 		return (ENXIO);
95 
96 	/* Verify chipc capability flags */
97 	ccaps = BHND_CHIPC_GET_CAPS(chipc);
98 	if (ccaps->pmu || !ccaps->pwr_ctrl)
99 		return (ENXIO);
100 
101 	/* Check for chipc device match */
102 	id = bhnd_device_lookup(chipc, pwrctl_devices,
103 	    sizeof(pwrctl_devices[0]));
104 	if (id == NULL)
105 		return (ENXIO);
106 
107 	device_set_desc(dev, id->desc);
108 	return (BUS_PROBE_NOWILDCARD);
109 }
110 
111 static int
112 bhnd_pwrctl_attach(device_t dev)
113 {
114 	struct bhnd_pwrctl_softc	*sc;
115 	const struct bhnd_chipid	*cid;
116 	struct chipc_softc		*chipc_sc;
117 	bhnd_devclass_t			 hostb_class;
118 	device_t			 hostb_dev;
119 	int				 error;
120 
121 	sc = device_get_softc(dev);
122 
123 	/* TODO: Need further testing on actual PWRCTL hardware */
124 	device_printf(dev, "WARNING: Using untested PWRCTL support\n");
125 
126 	sc->dev = dev;
127 	sc->chipc_dev = device_get_parent(dev);
128 	sc->quirks = bhnd_device_quirks(sc->chipc_dev, pwrctl_devices,
129 	    sizeof(pwrctl_devices[0]));
130 
131 	/* On devices that lack a slow clock source, HT must always be
132 	 * enabled. */
133 	hostb_class = BHND_DEVCLASS_INVALID;
134 	hostb_dev = bhnd_find_hostb_device(device_get_parent(sc->chipc_dev));
135 	if (hostb_dev != NULL)
136 		hostb_class = bhnd_get_class(hostb_dev);
137 
138 	cid = bhnd_get_chipid(sc->chipc_dev);
139 	switch (cid->chip_id) {
140 	case BHND_CHIPID_BCM4311:
141 		if (cid->chip_rev <= 1 && hostb_class == BHND_DEVCLASS_PCI)
142 			sc->quirks |= PWRCTL_QUIRK_FORCE_HT;
143 		break;
144 
145 	case BHND_CHIPID_BCM4321:
146 		if (hostb_class == BHND_DEVCLASS_PCIE ||
147 		    hostb_class == BHND_DEVCLASS_PCI)
148 			sc->quirks |= PWRCTL_QUIRK_FORCE_HT;
149 		break;
150 
151 	case BHND_CHIPID_BCM4716:
152 		if (hostb_class == BHND_DEVCLASS_PCIE)
153 			sc->quirks |= PWRCTL_QUIRK_FORCE_HT;
154 		break;
155 	}
156 
157 	/* Fetch core register block from ChipCommon parent */
158 	chipc_sc = device_get_softc(sc->chipc_dev);
159 	sc->res = chipc_sc->core;
160 
161 	PWRCTL_LOCK_INIT(sc);
162 	STAILQ_INIT(&sc->clkres_list);
163 
164 	/* Initialize power control */
165 	PWRCTL_LOCK(sc);
166 
167 	if ((error = bhnd_pwrctl_init(sc))) {
168 		PWRCTL_UNLOCK(sc);
169 		goto cleanup;
170 	}
171 
172 	/* Apply default clock transitions */
173 	if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) {
174 		PWRCTL_UNLOCK(sc);
175 		goto cleanup;
176 	}
177 
178 	PWRCTL_UNLOCK(sc);
179 
180 	return (0);
181 
182 cleanup:
183 	PWRCTL_LOCK_DESTROY(sc);
184 	return (error);
185 }
186 
187 static int
188 bhnd_pwrctl_detach(device_t dev)
189 {
190 	struct bhnd_pwrctl_softc	*sc;
191 	struct bhnd_pwrctl_clkres	*clkres, *crnext;
192 	int				 error;
193 
194 	sc = device_get_softc(dev);
195 
196 	if ((error = bhnd_pwrctl_setclk(sc, BHND_CLOCK_DYN)))
197 		return (error);
198 
199 	STAILQ_FOREACH_SAFE(clkres, &sc->clkres_list, cr_link, crnext)
200 		free(clkres, M_DEVBUF);
201 
202 	PWRCTL_LOCK_DESTROY(sc);
203 	return (0);
204 }
205 
206 static int
207 bhnd_pwrctl_suspend(device_t dev)
208 {
209 	struct bhnd_pwrctl_softc	*sc;
210 	int				 error;
211 
212 	sc = device_get_softc(dev);
213 
214 	/* Update clock state */
215 	PWRCTL_LOCK(sc);
216 	error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_DOWN);
217 	PWRCTL_UNLOCK(sc);
218 
219 	return (error);
220 }
221 
222 static int
223 bhnd_pwrctl_resume(device_t dev)
224 {
225 	struct bhnd_pwrctl_softc	*sc;
226 	int				 error;
227 
228 	sc = device_get_softc(dev);
229 
230 	PWRCTL_LOCK(sc);
231 
232 	/* Re-initialize power control registers */
233 	if ((error = bhnd_pwrctl_init(sc))) {
234 		device_printf(sc->dev, "PWRCTL init failed: %d\n", error);
235 		goto cleanup;
236 	}
237 
238 	/* Restore clock state */
239 	if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) {
240 		device_printf(sc->dev, "clock state restore failed: %d\n",
241 		    error);
242 		goto cleanup;
243 	}
244 
245 cleanup:
246 	PWRCTL_UNLOCK(sc);
247 	return (error);
248 }
249 
250 /**
251  * Find the clock reservation associated with @p pinfo, if any.
252  *
253  * @param sc Driver instance state.
254  * @param pinfo PMU info for device.
255  */
256 static struct bhnd_pwrctl_clkres *
257 bhnd_pwrctl_find_res(struct bhnd_pwrctl_softc *sc,
258     struct bhnd_core_pmu_info *pinfo)
259 {
260 	struct bhnd_pwrctl_clkres *clkres;
261 
262 	PWRCTL_LOCK_ASSERT(sc, MA_OWNED);
263 
264 	STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link) {
265 		if (clkres->owner == pinfo->pm_dev)
266 			return (clkres);
267 	}
268 
269 	/* not found */
270 	return (NULL);
271 }
272 
273 /**
274  * Enumerate all active clock requests, compute the minimum required clock,
275  * and issue any required clock transition.
276  *
277  * @param sc Driver instance state.
278  * @param wars Work-around state.
279  */
280 static int
281 bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc, bhnd_pwrctl_wars wars)
282 {
283 	struct bhnd_pwrctl_clkres	*clkres;
284 	bhnd_clock			 clock;
285 
286 	PWRCTL_LOCK_ASSERT(sc, MA_OWNED);
287 
288 	/* Default clock target */
289 	clock = BHND_CLOCK_DYN;
290 
291 	/* Apply quirk-specific overrides to the clock target */
292 	switch (wars) {
293 	case BHND_PWRCTL_WAR_UP:
294 		/* Force HT clock */
295 		if (PWRCTL_QUIRK(sc, FORCE_HT))
296 			clock = BHND_CLOCK_HT;
297 		break;
298 
299 	case BHND_PWRCTL_WAR_RUN:
300 		/* Cannot transition clock if FORCE_HT */
301 		if (PWRCTL_QUIRK(sc, FORCE_HT))
302 			return (0);
303 		break;
304 
305 	case BHND_PWRCTL_WAR_DOWN:
306 		/* Leave default clock unmodified to permit
307 		 * transition back to BHND_CLOCK_DYN on FORCE_HT devices. */
308 		break;
309 	}
310 
311 	/* Determine required clock */
312 	STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link)
313 		clock = bhnd_clock_max(clock, clkres->clock);
314 
315 	/* Map to supported clock setting */
316 	switch (clock) {
317 	case BHND_CLOCK_DYN:
318 	case BHND_CLOCK_ILP:
319 		clock = BHND_CLOCK_DYN;
320 		break;
321 	case BHND_CLOCK_ALP:
322 		/* In theory FORCE_ALP is supported by the hardware, but
323 		 * there are currently no known use-cases for it; mapping
324 		 * to HT is still valid, and allows us to punt on determing
325 		 * where FORCE_ALP is supported and functional */
326 		clock = BHND_CLOCK_HT;
327 		break;
328 	case BHND_CLOCK_HT:
329 		break;
330 	default:
331 		device_printf(sc->dev, "unknown clock: %#x\n", clock);
332 		return (ENODEV);
333 	}
334 
335 	/* Issue transition */
336 	return (bhnd_pwrctl_setclk(sc, clock));
337 }
338 
339 static int
340 bhnd_pwrctl_core_req_clock(device_t dev, struct bhnd_core_pmu_info *pinfo,
341     bhnd_clock clock)
342 {
343 	struct bhnd_pwrctl_softc	*sc;
344 	struct bhnd_pwrctl_clkres	*clkres;
345 	int				 error;
346 
347 	sc = device_get_softc(dev);
348 	error = 0;
349 
350 	PWRCTL_LOCK(sc);
351 
352 	clkres = bhnd_pwrctl_find_res(sc, pinfo);
353 
354 	/* BHND_CLOCK_DYN discards the clock reservation entirely */
355 	if (clock == BHND_CLOCK_DYN) {
356 		/* nothing to clean up? */
357 		if (clkres == NULL) {
358 			PWRCTL_UNLOCK(sc);
359 			return (0);
360 		}
361 
362 		/* drop reservation and apply clock transition */
363 		STAILQ_REMOVE(&sc->clkres_list, clkres,
364 		    bhnd_pwrctl_clkres, cr_link);
365 
366 		if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN))) {
367 			device_printf(dev, "clock transition failed: %d\n",
368 			    error);
369 
370 			/* restore reservation */
371 			STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link);
372 
373 			PWRCTL_UNLOCK(sc);
374 			return (error);
375 		}
376 
377 		/* deallocate orphaned reservation */
378 		free(clkres, M_DEVBUF);
379 
380 		PWRCTL_UNLOCK(sc);
381 		return (0);
382 	}
383 
384 	/* create (or update) reservation */
385 	if (clkres == NULL) {
386 		clkres = malloc(sizeof(struct bhnd_pwrctl_clkres), M_DEVBUF,
387 		    M_NOWAIT);
388 		if (clkres == NULL)
389 			return (ENOMEM);
390 
391 		clkres->owner = pinfo->pm_dev;
392 		clkres->clock = clock;
393 
394 		STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link);
395 	} else {
396 		KASSERT(clkres->owner == pinfo->pm_dev, ("invalid owner"));
397 		clkres->clock = clock;
398 	}
399 
400 	/* apply clock transition */
401 	error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN);
402 	if (error) {
403 		STAILQ_REMOVE(&sc->clkres_list, clkres, bhnd_pwrctl_clkres,
404 		    cr_link);
405 		free(clkres, M_DEVBUF);
406 	}
407 
408 	PWRCTL_UNLOCK(sc);
409 	return (error);
410 }
411 
412 static int
413 bhnd_pwrctl_core_req_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo,
414     u_int rsrc)
415 {
416 	/* HW does not support per-core external resources */
417 	return (ENODEV);
418 }
419 
420 static int
421 bhnd_pwrctl_core_release_ext_rsrc(device_t dev,
422     struct bhnd_core_pmu_info *pinfo, u_int rsrc)
423 {
424 	/* HW does not support per-core external resources */
425 	return (ENODEV);
426 }
427 
428 static int
429 bhnd_pwrctl_core_en_clocks(device_t dev, struct bhnd_core_pmu_info *pinfo,
430     uint32_t clocks)
431 {
432 	/* All supported clocks are already enabled by default (?) */
433 	clocks &= ~(BHND_CLOCK_DYN |
434 		    BHND_CLOCK_ILP |
435 		    BHND_CLOCK_ALP |
436 		    BHND_CLOCK_HT);
437 
438 	if (clocks != 0) {
439 		device_printf(dev, "%s requested unknown clocks: %#x\n",
440 		    device_get_nameunit(pinfo->pm_dev), clocks);
441 		return (ENODEV);
442 	}
443 
444 	return (0);
445 }
446 
447 static int
448 bhnd_pwrctl_core_release(device_t dev, struct bhnd_core_pmu_info *pinfo)
449 {
450 	/* Requesting BHND_CLOCK_DYN releases any outstanding clock
451 	 * reservations */
452 	return (bhnd_pwrctl_core_req_clock(dev, pinfo, BHND_CLOCK_DYN));
453 }
454 
455 static device_method_t bhnd_pwrctl_methods[] = {
456 	/* Device interface */
457 	DEVMETHOD(device_probe,			bhnd_pwrctl_probe),
458 	DEVMETHOD(device_attach,		bhnd_pwrctl_attach),
459 	DEVMETHOD(device_detach,		bhnd_pwrctl_detach),
460 	DEVMETHOD(device_suspend,		bhnd_pwrctl_suspend),
461 	DEVMETHOD(device_resume,		bhnd_pwrctl_resume),
462 
463 	/* BHND PMU interface */
464 	DEVMETHOD(bhnd_pmu_core_req_clock,	bhnd_pwrctl_core_req_clock),
465 	DEVMETHOD(bhnd_pmu_core_en_clocks,	bhnd_pwrctl_core_en_clocks),
466 	DEVMETHOD(bhnd_pmu_core_req_ext_rsrc,	bhnd_pwrctl_core_req_ext_rsrc),
467 	DEVMETHOD(bhnd_pmu_core_release_ext_rsrc, bhnd_pwrctl_core_release_ext_rsrc),
468 	DEVMETHOD(bhnd_pmu_core_release,	bhnd_pwrctl_core_release),
469 
470 	DEVMETHOD_END
471 };
472 
473 DEFINE_CLASS_0(bhnd_pmu, bhnd_pwrctl_driver, bhnd_pwrctl_methods,
474     sizeof(struct bhnd_pwrctl_softc));
475 EARLY_DRIVER_MODULE(bhnd_pwrctl, bhnd_chipc, bhnd_pwrctl_driver,
476     bhnd_pmu_devclass, NULL, NULL, BUS_PASS_TIMER + BUS_PASS_ORDER_MIDDLE);
477 
478 MODULE_DEPEND(bhnd_pwrctl, bhnd, 1, 1, 1);
479 MODULE_VERSION(bhnd_pwrctl, 1);
480