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