fdc_acpi.c revision 132214
1/*-
2 * Copyright (c) 2004 Nate Lawson (SDG)
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *	notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *	notice, this list of conditions and the following disclaimer in the
12 *	documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/dev/fdc/fdc_acpi.c 132214 2004-07-15 16:38:07Z njl $");
29
30#include <sys/param.h>
31#include <sys/kernel.h>
32#include <sys/bio.h>
33#include <sys/bus.h>
34#include <sys/malloc.h>
35#include <sys/module.h>
36#include <sys/proc.h>
37
38#include "acpi.h"
39#include <dev/acpica/acpivar.h>
40#include <dev/fdc/fdcreg.h>
41#include <dev/fdc/fdcvar.h>
42
43static int		fdc_acpi_probe(device_t dev);
44static int		fdc_acpi_attach(device_t dev);
45static ACPI_STATUS	fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev,
46			    int level, void *arg);
47static void		fdctl_wr_acpi(fdc_p fdc, u_int8_t v);
48
49/* Parameters for the tape drive (5th device). */
50#define ACPI_TAPE_UNKNOWN	0
51#define ACPI_TAPE_PRESENT	1
52#define ACPI_TAPE_NEVER_PRESENT	2
53
54/* Temporary buf length for evaluating _FDE and _FDI. */
55#define ACPI_FDC_BUFLEN		1024
56
57/* Context for walking FDC child devices. */
58struct fdc_walk_ctx {
59	uint32_t	fd_present[5];
60	int		index;
61	device_t	acpi_dev;
62	device_t	dev;
63};
64
65static void
66fdctl_wr_acpi(fdc_p fdc, u_int8_t v)
67{
68	bus_space_write_1(fdc->ctlt, fdc->ctlh, 0, v);
69}
70
71static int
72fdc_acpi_probe(device_t dev)
73{
74	device_t bus;
75	static char *fdc_ids[] = { "PNP0700", "PNP0701", NULL };
76
77	bus = device_get_parent(dev);
78	if (ACPI_ID_PROBE(bus, dev, fdc_ids) == NULL)
79		return (ENXIO);
80
81	if (ACPI_SUCCESS(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, NULL)))
82		device_set_desc(dev, "floppy drive controller (FDE)");
83	else
84		device_set_desc(dev, "floppy drive controller");
85	return (0);
86}
87
88static int
89fdc_acpi_attach(device_t dev)
90{
91	struct fdc_walk_ctx *ctx;
92	struct fdc_data *sc;
93	ACPI_BUFFER buf;
94	device_t bus;
95	int error, ic_type;
96	ACPI_HANDLE h;
97
98	/* Get our softc and use the same accessor as ISA. */
99	sc = device_get_softc(dev);
100	sc->fdc_dev = dev;
101	sc->fdctl_wr = fdctl_wr_acpi;
102	sc->flags |= FDC_ISPNP;
103
104	/* Initialize variables and get a temporary buffer for _FDE. */
105	error = ENXIO;
106	h = acpi_get_handle(dev);
107	buf.Length = ACPI_FDC_BUFLEN;
108	buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO);
109	if (buf.Pointer == NULL)
110		goto out;
111
112	/* Allocate resources the same as the ISA attachment. */
113	error = fdc_isa_alloc_resources(dev, sc);
114	if (error != 0)
115		goto out;
116
117	/* Call common attach code in fdc(4) first. */
118	error = fdc_attach(dev);
119	if (error != 0)
120		goto out;
121
122	/* Check that the controller is working and get its type. */
123	error = fdc_initial_reset(sc);
124	if (error)
125		goto out;
126	if (fd_cmd(sc, 1, NE7CMD_VERSION, 1, &ic_type) == 0) {
127		ic_type = (u_char)ic_type;
128		switch (ic_type) {
129		case 0x80:
130			sc->fdct = FDC_NE765;
131			break;
132		case 0x81:	/* not mentioned in any hardware doc */
133		case 0x90:
134			sc->fdct = FDC_ENHANCED;
135			break;
136		default:
137			sc->fdct = FDC_UNKNOWN;
138			break;
139		}
140	}
141
142	/*
143	 * Enumerate _FDE, which lists floppy drives that are present.  If
144	 * this fails, fall back to the ISA hints-based probe method.
145	 */
146	bus = device_get_parent(dev);
147	if (ACPI_SUCCESS(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, &buf))) {
148		/* Setup the context and walk all child devices. */
149		ctx = malloc(sizeof(struct fdc_walk_ctx), M_TEMP, M_NOWAIT);
150		if (ctx == NULL) {
151			device_printf(dev, "no memory for walking children\n");
152			goto out;
153		}
154		bcopy(buf.Pointer, ctx->fd_present, sizeof(ctx->fd_present));
155		ctx->index = 0;
156		ctx->dev = dev;
157		ctx->acpi_dev = bus;
158		ACPI_SCAN_CHILDREN(ctx->acpi_dev, dev, 1, fdc_acpi_probe_child,
159		    ctx);
160		free(ctx, M_TEMP);
161
162		/* Attach any children found during the probe. */
163		error = bus_generic_attach(dev);
164		if (error != 0)
165			goto out;
166	} else {
167		error = fdc_hints_probe(dev);
168		if (error != 0)
169			goto out;
170	}
171
172out:
173	if (buf.Pointer)
174		free(buf.Pointer, M_TEMP);
175	if (error != 0)
176		fdc_release_resources(sc);
177
178	return (error);
179}
180
181static ACPI_STATUS
182fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg)
183{
184	struct fdc_walk_ctx *ctx;
185	device_t child;
186	ACPI_BUFFER buf;
187	ACPI_OBJECT *pkg, *obj;
188	ACPI_STATUS status;
189
190	/*
191	 * The first four ints are booleans that indicate whether fd0-3 are
192	 * present or not.  The last is for a tape device, which we don't
193	 * bother supporting for now.
194	 */
195	ctx = (struct fdc_walk_ctx *)arg;
196	if (ctx->index > 3)
197		return (AE_OK);
198
199	/* Get temporary buffer for _FDI probe. */
200	buf.Length = ACPI_FDC_BUFLEN;
201	buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO);
202	if (buf.Pointer == NULL)
203		goto out;
204
205	/* This device is not present, move on to the next. */
206	if (ctx->fd_present[ctx->index] == 0)
207		goto out;
208
209	/* Evaluate _FDI to get drive type to pass to the child. */
210	status = ACPI_EVALUATE_OBJECT(ctx->acpi_dev, *dev, "_FDI", NULL, &buf);
211	if (ACPI_FAILURE(status)) {
212		device_printf(ctx->dev, "_FDI not available - %#x\n", status);
213		goto out;
214	}
215	pkg = (ACPI_OBJECT *)buf.Pointer;
216	if (!ACPI_PKG_VALID(pkg, 16)) {
217		device_printf(ctx->dev, "invalid _FDI package\n");
218		goto out;
219	}
220	obj = &pkg->Package.Elements[1];
221	if (obj == NULL || obj->Type != ACPI_TYPE_INTEGER) {
222		device_printf(ctx->dev, "invalid type object in _FDI\n");
223		goto out;
224	}
225
226	/* Create a device for the child with the given index and set ivars. */
227	child = fdc_add_child(ctx->dev, "fd", ctx->index);
228	if (child == NULL)
229		goto out;
230	*dev = child;
231	fdc_set_fdtype(child, obj->Integer.Value);
232
233out:
234	ctx->index++;
235	if (buf.Pointer)
236		free(buf.Pointer, M_TEMP);
237	return (AE_OK);
238}
239
240static device_method_t fdc_acpi_methods[] = {
241	/* Device interface */
242	DEVMETHOD(device_probe,		fdc_acpi_probe),
243	DEVMETHOD(device_attach,	fdc_acpi_attach),
244	DEVMETHOD(device_detach,	fdc_detach),
245
246	/* Bus interface */
247	DEVMETHOD(bus_print_child,	fdc_print_child),
248	DEVMETHOD(bus_read_ivar,	fdc_read_ivar),
249	DEVMETHOD(bus_write_ivar,	fdc_write_ivar),
250
251	{0, 0}
252};
253
254static driver_t fdc_acpi_driver = {
255	"fdc",
256	fdc_acpi_methods,
257	sizeof(struct fdc_data)
258};
259
260DRIVER_MODULE(fdc, acpi, fdc_acpi_driver, fdc_devclass, 0, 0);
261