ichss.c revision 142002
1141241Snjl/*-
2141241Snjl * Copyright (c) 2004-2005 Nate Lawson (SDG)
3141241Snjl * All rights reserved.
4141241Snjl *
5141241Snjl * Redistribution and use in source and binary forms, with or without
6141241Snjl * modification, are permitted provided that the following conditions
7141241Snjl * are met:
8141241Snjl * 1. Redistributions of source code must retain the above copyright
9141241Snjl *    notice, this list of conditions and the following disclaimer.
10141241Snjl * 2. Redistributions in binary form must reproduce the above copyright
11141241Snjl *    notice, this list of conditions and the following disclaimer in the
12141241Snjl *    documentation and/or other materials provided with the distribution.
13141241Snjl *
14141241Snjl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15141241Snjl * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16141241Snjl * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17141241Snjl * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18141241Snjl * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19141241Snjl * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20141241Snjl * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21141241Snjl * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22141241Snjl * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23141241Snjl * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24141241Snjl * SUCH DAMAGE.
25141241Snjl */
26141241Snjl
27141241Snjl#include <sys/cdefs.h>
28141241Snjl__FBSDID("$FreeBSD: head/sys/dev/cpufreq/ichss.c 142002 2005-02-17 01:01:40Z njl $");
29141241Snjl
30141241Snjl#include <sys/param.h>
31141241Snjl#include <sys/bus.h>
32141241Snjl#include <sys/cpu.h>
33141241Snjl#include <sys/kernel.h>
34141241Snjl#include <sys/malloc.h>
35141241Snjl#include <sys/module.h>
36141241Snjl#include <sys/pcpu.h>
37141241Snjl#include <sys/sysctl.h>
38141241Snjl#include <sys/systm.h>
39141241Snjl
40141241Snjl#include <dev/pci/pcivar.h>
41141293Simp#include <machine/bus.h>
42141241Snjl#include <machine/clock.h>
43141241Snjl#include <machine/resource.h>
44141241Snjl#include <sys/rman.h>
45141241Snjl
46141241Snjl#include "cpufreq_if.h"
47141241Snjl
48141241Snjl/*
49141241Snjl * The SpeedStep ICH feature is a chipset-initiated voltage and frequency
50141241Snjl * transition available on the ICH2M, 3M, and 4M.  It is different from
51141241Snjl * the newer Pentium-M SpeedStep feature.  It offers only two levels of
52141241Snjl * frequency/voltage.  Often, the BIOS will select one of the levels via
53141241Snjl * SMM code during the power-on process (i.e., choose a lower level if the
54141241Snjl * system is off AC power.)
55141241Snjl */
56141241Snjl
57141241Snjlstruct ichss_softc {
58141241Snjl	device_t	 dev;
59141241Snjl	int		 bm_rid;	/* Bus-mastering control (PM2REG). */
60141241Snjl	struct resource	*bm_reg;
61141241Snjl	int		 ctrl_rid;	/* Control/status register. */
62141241Snjl	struct resource	*ctrl_reg;
63141241Snjl	struct cf_setting sets[2];	/* Only two settings. */
64141241Snjl};
65141241Snjl
66141241Snjl/* Supported PCI IDs. */
67141241Snjl#define PCI_VENDOR_INTEL	0x8086
68141241Snjl#define PCI_DEV_82801BA		0x244c /* ICH2M */
69141241Snjl#define PCI_DEV_82801CA		0x248c /* ICH3M */
70141241Snjl#define PCI_DEV_82801DB		0x24cc /* ICH4M */
71141241Snjl#define PCI_DEV_82815BA		0x1130 /* Unsupported/buggy part */
72141241Snjl
73141241Snjl/* PCI config registers for finding PMBASE and enabling SpeedStep. */
74141241Snjl#define ICHSS_PMBASE_OFFSET	0x40
75141241Snjl#define ICHSS_PMCFG_OFFSET	0xa0
76141241Snjl
77141241Snjl/* Values and masks. */
78141241Snjl#define ICHSS_ENABLE		(1<<3)	/* Enable SpeedStep control. */
79141241Snjl#define ICHSS_IO_REG		0x1	/* Access register via I/O space. */
80141241Snjl#define ICHSS_PMBASE_MASK	0xff80	/* PMBASE address bits. */
81141241Snjl#define ICHSS_CTRL_BIT		0x1	/* 0 is high speed, 1 is low. */
82141241Snjl#define ICHSS_BM_DISABLE	0x1
83141241Snjl
84141241Snjl/* Offsets from PMBASE for various registers. */
85141241Snjl#define ICHSS_BM_OFFSET		0x20
86141241Snjl#define ICHSS_CTRL_OFFSET	0x50
87141241Snjl
88141241Snjl#define ICH_GET_REG(reg) 				\
89141241Snjl	(bus_space_read_1(rman_get_bustag((reg)), 	\
90141241Snjl	    rman_get_bushandle((reg)), 0))
91141241Snjl#define ICH_SET_REG(reg, val)				\
92141241Snjl	(bus_space_write_1(rman_get_bustag((reg)), 	\
93141241Snjl	    rman_get_bushandle((reg)), 0, (val)))
94141241Snjl
95141241Snjlstatic int	ichss_pci_probe(device_t dev);
96141241Snjlstatic int	ichss_probe(device_t dev);
97141241Snjlstatic int	ichss_attach(device_t dev);
98141241Snjlstatic int	ichss_detach(device_t dev);
99141241Snjlstatic int	ichss_settings(device_t dev, struct cf_setting *sets,
100141241Snjl		    int *count, int *type);
101141241Snjlstatic int	ichss_set(device_t dev, const struct cf_setting *set);
102141241Snjlstatic int	ichss_get(device_t dev, struct cf_setting *set);
103141241Snjl
104141241Snjlstatic device_method_t ichss_methods[] = {
105141241Snjl	/* Device interface */
106141241Snjl	DEVMETHOD(device_probe,		ichss_probe),
107141241Snjl	DEVMETHOD(device_attach,	ichss_attach),
108141241Snjl	DEVMETHOD(device_detach,	ichss_detach),
109141241Snjl
110141241Snjl	/* cpufreq interface */
111141241Snjl	DEVMETHOD(cpufreq_drv_set,	ichss_set),
112141241Snjl	DEVMETHOD(cpufreq_drv_get,	ichss_get),
113141241Snjl	DEVMETHOD(cpufreq_drv_settings,	ichss_settings),
114141241Snjl	{0, 0}
115141241Snjl};
116141241Snjlstatic driver_t ichss_driver = {
117141241Snjl	"ichss", ichss_methods, sizeof(struct ichss_softc)
118141241Snjl};
119141241Snjlstatic devclass_t ichss_devclass;
120141241SnjlDRIVER_MODULE(ichss, cpu, ichss_driver, ichss_devclass, 0, 0);
121141241Snjl
122141241Snjlstatic device_method_t ichss_pci_methods[] = {
123141241Snjl	DEVMETHOD(device_probe,		ichss_pci_probe),
124141241Snjl	{0, 0}
125141241Snjl};
126141241Snjlstatic driver_t ichss_pci_driver = {
127141241Snjl	"ichss_pci", ichss_pci_methods, 0
128141241Snjl};
129141241Snjlstatic devclass_t ichss_pci_devclass;
130141241SnjlDRIVER_MODULE(ichss_pci, pci, ichss_pci_driver, ichss_pci_devclass, 0, 0);
131141241Snjl
132141241Snjl#if 0
133141241Snjl#define DPRINT(x...)	printf(x)
134141241Snjl#else
135141241Snjl#define DPRINT(x...)
136141241Snjl#endif
137141241Snjl
138141241Snjl/*
139141241Snjl * We detect the chipset by looking for its LPC bus ID during the PCI
140141241Snjl * scan and reading its config registers during the probe.  However,
141141241Snjl * we add the ichss child under the cpu device since even though the
142141241Snjl * chipset provides the control, it really affects the cpu only.
143141241Snjl *
144141241Snjl * XXX This approach does not work if the module is loaded after boot.
145141241Snjl */
146141241Snjlstatic int
147141241Snjlichss_pci_probe(device_t dev)
148141241Snjl{
149141241Snjl	device_t child, parent;
150141241Snjl	uint32_t pmbase;
151141241Snjl	uint16_t ss_en;
152141241Snjl
153141241Snjl	/*
154141241Snjl	 * TODO: add a quirk to disable if we see the 82815_MC along
155141241Snjl	 * with the 82801BA and revision < 5.
156141241Snjl	 */
157141241Snjl	if (pci_get_vendor(dev) != PCI_VENDOR_INTEL ||
158141241Snjl	    (pci_get_device(dev) != PCI_DEV_82801BA &&
159141241Snjl	    pci_get_device(dev) != PCI_DEV_82801CA &&
160141241Snjl	    pci_get_device(dev) != PCI_DEV_82801DB))
161141241Snjl		return (ENXIO);
162141241Snjl
163141241Snjl	/* Only one CPU is supported for this hardware. */
164141241Snjl	if (devclass_get_device(ichss_devclass, 0))
165141241Snjl		return (ENXIO);
166141241Snjl
167141241Snjl	/* Add a child under the CPU parent. */
168141241Snjl	parent = devclass_get_device(devclass_find("cpu"), 0);
169141241Snjl	KASSERT(parent != NULL, ("cpu parent is NULL"));
170141241Snjl	child = BUS_ADD_CHILD(parent, 0, "ichss", 0);
171141241Snjl	if (child == NULL) {
172141241Snjl		device_printf(parent, "add SpeedStep child failed\n");
173141241Snjl		return (ENXIO);
174141241Snjl	}
175141241Snjl
176141241Snjl	/* Find the PMBASE register from our PCI config header. */
177141241Snjl	pmbase = pci_read_config(dev, ICHSS_PMBASE_OFFSET, sizeof(pmbase));
178141241Snjl	if ((pmbase & ICHSS_IO_REG) == 0) {
179141241Snjl		printf("ichss: invalid PMBASE memory type\n");
180141241Snjl		return (ENXIO);
181141241Snjl	}
182141241Snjl	pmbase &= ICHSS_PMBASE_MASK;
183141241Snjl	if (pmbase == 0) {
184141241Snjl		printf("ichss: invalid zero PMBASE address\n");
185141241Snjl		return (ENXIO);
186141241Snjl	}
187141241Snjl	DPRINT("ichss: PMBASE is %#x\n", pmbase);
188141241Snjl
189141241Snjl	/* Add the bus master arbitration and control registers. */
190141241Snjl	bus_set_resource(child, SYS_RES_IOPORT, 0, pmbase + ICHSS_BM_OFFSET,
191141241Snjl	    1);
192141241Snjl	bus_set_resource(child, SYS_RES_IOPORT, 1, pmbase + ICHSS_CTRL_OFFSET,
193141241Snjl	    1);
194141241Snjl
195141241Snjl	/* Activate SpeedStep control if not already enabled. */
196141241Snjl	ss_en = pci_read_config(dev, ICHSS_PMCFG_OFFSET, sizeof(ss_en));
197141241Snjl	if ((ss_en & ICHSS_ENABLE) == 0) {
198141241Snjl		printf("ichss: enabling SpeedStep support\n");
199141241Snjl		pci_write_config(dev, ICHSS_PMCFG_OFFSET,
200141241Snjl		    ss_en | ICHSS_ENABLE, sizeof(ss_en));
201141241Snjl	}
202141241Snjl
203141241Snjl	/* Attach the new CPU child now. */
204141241Snjl	device_probe_and_attach(child);
205141241Snjl
206141241Snjl	return (ENXIO);
207141241Snjl}
208141241Snjl
209141241Snjlstatic int
210141241Snjlichss_probe(device_t dev)
211141241Snjl{
212142002Snjl	struct cf_setting set;
213141241Snjl	device_t perf_dev;
214142002Snjl	int count, type;
215141241Snjl
216142002Snjl	/*
217142002Snjl	 * If the ACPI perf driver has attached and is not just offering
218142002Snjl	 * info, let it manage things.
219142002Snjl	 */
220142002Snjl	perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1);
221142002Snjl	if (perf_dev && device_is_attached(perf_dev)) {
222142002Snjl		type = 0;
223142002Snjl		count = 1;
224142002Snjl		CPUFREQ_DRV_SETTINGS(perf_dev, &set, &count, &type);
225142002Snjl		if ((type & CPUFREQ_FLAG_INFO_ONLY) == 0)
226142002Snjl			return (ENXIO);
227142002Snjl	}
228141241Snjl
229141241Snjl	device_set_desc(dev, "SpeedStep ICH");
230141241Snjl	return (-1000);
231141241Snjl}
232141241Snjl
233141241Snjlstatic int
234141241Snjlichss_attach(device_t dev)
235141241Snjl{
236141241Snjl	struct ichss_softc *sc;
237141241Snjl
238141241Snjl	sc = device_get_softc(dev);
239141241Snjl	sc->dev = dev;
240141241Snjl
241141241Snjl	sc->bm_rid = 0;
242141241Snjl	sc->bm_reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->bm_rid,
243141241Snjl	    RF_ACTIVE);
244141241Snjl	if (sc->bm_reg == NULL) {
245141241Snjl		device_printf(dev, "failed to alloc BM arb register\n");
246141241Snjl		return (ENXIO);
247141241Snjl	}
248141241Snjl	sc->ctrl_rid = 1;
249141241Snjl	sc->ctrl_reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
250141241Snjl	    &sc->ctrl_rid, RF_ACTIVE);
251141241Snjl	if (sc->ctrl_reg == NULL) {
252141241Snjl		device_printf(dev, "failed to alloc control register\n");
253141241Snjl		bus_release_resource(dev, SYS_RES_IOPORT, sc->bm_rid,
254141241Snjl		    sc->bm_reg);
255141241Snjl		return (ENXIO);
256141241Snjl	}
257141241Snjl
258141241Snjl	/* Setup some defaults for our exported settings. */
259141241Snjl	sc->sets[0].freq = CPUFREQ_VAL_UNKNOWN;
260141241Snjl	sc->sets[0].volts = CPUFREQ_VAL_UNKNOWN;
261141241Snjl	sc->sets[0].power = CPUFREQ_VAL_UNKNOWN;
262141241Snjl	sc->sets[0].lat = 1000;
263141241Snjl	sc->sets[0].dev = dev;
264141241Snjl	sc->sets[1] = sc->sets[0];
265141241Snjl	cpufreq_register(dev);
266141241Snjl
267141241Snjl	return (0);
268141241Snjl}
269141241Snjl
270141241Snjlstatic int
271141241Snjlichss_detach(device_t dev)
272141241Snjl{
273141241Snjl	/* TODO: teardown BM and CTRL registers. */
274141241Snjl	return (ENXIO);
275141241Snjl}
276141241Snjl
277141241Snjlstatic int
278141241Snjlichss_settings(device_t dev, struct cf_setting *sets, int *count, int *type)
279141241Snjl{
280141241Snjl	struct ichss_softc *sc;
281141241Snjl	struct cf_setting set;
282141241Snjl	int first, i;
283141241Snjl
284141241Snjl	if (sets == NULL || count == NULL)
285141241Snjl		return (EINVAL);
286141241Snjl	if (*count < 2) {
287141241Snjl		*count = 2;
288141241Snjl		return (E2BIG);
289141241Snjl	}
290141241Snjl	sc = device_get_softc(dev);
291141241Snjl
292141241Snjl	/*
293141241Snjl	 * Estimate frequencies for both levels, temporarily switching to
294141241Snjl	 * the other one if we haven't calibrated it yet.
295141241Snjl	 */
296141241Snjl	ichss_get(dev, &set);
297141241Snjl	for (i = 0; i < 2; i++) {
298141241Snjl		if (sc->sets[i].freq == CPUFREQ_VAL_UNKNOWN) {
299141241Snjl			first = (i == 0) ? 1 : 0;
300141241Snjl			ichss_set(dev, &sc->sets[i]);
301141241Snjl			ichss_set(dev, &sc->sets[first]);
302141241Snjl		}
303141241Snjl	}
304141241Snjl
305141241Snjl	bcopy(sc->sets, sets, sizeof(sc->sets));
306141241Snjl	*count = 2;
307141241Snjl	*type = CPUFREQ_TYPE_ABSOLUTE;
308141241Snjl
309141241Snjl	return (0);
310141241Snjl}
311141241Snjl
312141241Snjlstatic int
313141241Snjlichss_set(device_t dev, const struct cf_setting *set)
314141241Snjl{
315141241Snjl	struct ichss_softc *sc;
316141241Snjl	uint8_t bmval, new_val, old_val, req_val;
317141241Snjl	uint64_t rate;
318141363Snjl	register_t regs;
319141241Snjl
320141241Snjl	/* Look up appropriate bit value based on frequency. */
321141241Snjl	sc = device_get_softc(dev);
322141241Snjl	if (CPUFREQ_CMP(set->freq, sc->sets[0].freq))
323141241Snjl		req_val = 0;
324141241Snjl	else if (CPUFREQ_CMP(set->freq, sc->sets[1].freq))
325141241Snjl		req_val = ICHSS_CTRL_BIT;
326141241Snjl	else
327141241Snjl		return (EINVAL);
328141241Snjl	DPRINT("ichss: requested setting %d\n", req_val);
329141241Snjl
330141241Snjl	/* Disable interrupts and get the other register contents. */
331141363Snjl	regs = intr_disable();
332141241Snjl	old_val = ICH_GET_REG(sc->ctrl_reg) & ~ICHSS_CTRL_BIT;
333141241Snjl
334141241Snjl	/*
335141241Snjl	 * Disable bus master arbitration, write the new value to the control
336141241Snjl	 * register, and then re-enable bus master arbitration.
337141241Snjl	 */
338141241Snjl	bmval = ICH_GET_REG(sc->bm_reg) | ICHSS_BM_DISABLE;
339141241Snjl	ICH_SET_REG(sc->bm_reg, bmval);
340141241Snjl	ICH_SET_REG(sc->ctrl_reg, old_val | req_val);
341141241Snjl	ICH_SET_REG(sc->bm_reg, bmval & ~ICHSS_BM_DISABLE);
342141241Snjl
343141241Snjl	/* Get the new value and re-enable interrupts. */
344141241Snjl	new_val = ICH_GET_REG(sc->ctrl_reg);
345141363Snjl	intr_restore(regs);
346141241Snjl
347141241Snjl	/* Check if the desired state was indeed selected. */
348141241Snjl	if (req_val != (new_val & ICHSS_CTRL_BIT)) {
349141241Snjl	    device_printf(sc->dev, "transition to %d failed\n", req_val);
350141241Snjl	    return (ENXIO);
351141241Snjl	}
352141241Snjl
353141241Snjl	/* Re-initialize our cycle counter if we don't know this new state. */
354141241Snjl	if (sc->sets[req_val].freq == CPUFREQ_VAL_UNKNOWN) {
355141241Snjl		cpu_est_clockrate(0, &rate);
356141241Snjl		sc->sets[req_val].freq = rate / 1000000;
357141241Snjl		DPRINT("ichss: set calibrated new rate of %d\n",
358141241Snjl		    sc->sets[req_val].freq);
359141241Snjl	}
360141241Snjl
361141241Snjl	return (0);
362141241Snjl}
363141241Snjl
364141241Snjlstatic int
365141241Snjlichss_get(device_t dev, struct cf_setting *set)
366141241Snjl{
367141241Snjl	struct ichss_softc *sc;
368141241Snjl	uint64_t rate;
369141241Snjl	uint8_t state;
370141241Snjl
371141241Snjl	sc = device_get_softc(dev);
372141241Snjl	state = ICH_GET_REG(sc->ctrl_reg) & ICHSS_CTRL_BIT;
373141241Snjl
374141241Snjl	/* If we haven't changed settings yet, estimate the current value. */
375141241Snjl	if (sc->sets[state].freq == CPUFREQ_VAL_UNKNOWN) {
376141241Snjl		cpu_est_clockrate(0, &rate);
377141241Snjl		sc->sets[state].freq = rate / 1000000;
378141241Snjl		DPRINT("ichss: get calibrated new rate of %d\n",
379141241Snjl		    sc->sets[state].freq);
380141241Snjl	}
381141241Snjl	*set = sc->sets[state];
382141241Snjl
383141241Snjl	return (0);
384141241Snjl}
385