alpm.c revision 127135
1/*-
2 * Copyright (c) 1998, 1999, 2001 Nicolas Souchu
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/*
28 * Power Management support for the Acer M15x3 chipsets
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/sys/pci/alpm.c 127135 2004-03-17 17:50:55Z njl $");
33
34#include <sys/param.h>
35#include <sys/kernel.h>
36#include <sys/systm.h>
37#include <sys/module.h>
38#include <sys/bus.h>
39#include <sys/uio.h>
40
41#include <machine/bus_pio.h>
42#include <machine/bus_memio.h>
43#include <machine/bus.h>
44#include <machine/resource.h>
45#include <sys/rman.h>
46
47#include <dev/pci/pcivar.h>
48#include <dev/pci/pcireg.h>
49
50#include <dev/iicbus/iiconf.h>
51#include <dev/smbus/smbconf.h>
52#include "smbus_if.h"
53
54#define ALPM_DEBUG(x)	if (alpm_debug) (x)
55
56#ifdef DEBUG
57static int alpm_debug = 1;
58#else
59static int alpm_debug = 0;
60#endif
61
62#define ACER_M1543_PMU_ID	0x710110b9
63
64/* Uncomment this line to force another I/O base address for SMB */
65/* #define ALPM_SMBIO_BASE_ADDR	0x3a80 */
66
67/* I/O registers offsets - the base address is programmed via the
68 * SMBBA PCI configuration register
69 */
70#define SMBSTS		0x0	/* SMBus host/slave status register */
71#define SMBCMD		0x1	/* SMBus host/slave command register */
72#define SMBSTART	0x2	/* start to generate programmed cycle */
73#define SMBHADDR	0x3	/* host address register */
74#define SMBHDATA	0x4	/* data A register for host controller */
75#define SMBHDATB	0x5	/* data B register for host controller */
76#define SMBHBLOCK	0x6	/* block register for host controller */
77#define SMBHCMD		0x7	/* command register for host controller */
78
79/* SMBSTS masks */
80#define TERMINATE	0x80
81#define BUS_COLLI	0x40
82#define DEVICE_ERR	0x20
83#define SMI_I_STS	0x10
84#define HST_BSY		0x08
85#define IDL_STS		0x04
86#define HSTSLV_STS	0x02
87#define HSTSLV_BSY	0x01
88
89/* SMBCMD masks */
90#define SMB_BLK_CLR	0x80
91#define T_OUT_CMD	0x08
92#define ABORT_HOST	0x04
93
94/* SMBus commands */
95#define SMBQUICK	0x00
96#define SMBSRBYTE	0x10		/* send/receive byte */
97#define SMBWRBYTE	0x20		/* write/read byte */
98#define SMBWRWORD	0x30		/* write/read word */
99#define SMBWRBLOCK	0x40		/* write/read block */
100
101/* PCI configuration registers and masks
102 */
103#define COM		0x4
104#define COM_ENABLE_IO	0x1
105
106#define SMBBA		0x14
107
108#define ATPC		0x5b
109#define ATPC_SMBCTRL	0x04 		/* XX linux has this as 0x6 */
110
111#define SMBHSI		0xe0
112#define SMBHSI_SLAVE	0x2
113#define SMBHSI_HOST	0x1
114
115#define SMBHCBC		0xe2
116#define SMBHCBC_CLOCK	0x70
117
118#define SMBCLOCK_149K	0x0
119#define SMBCLOCK_74K	0x20
120#define SMBCLOCK_37K	0x40
121#define SMBCLOCK_223K	0x80
122#define SMBCLOCK_111K	0xa0
123#define SMBCLOCK_55K	0xc0
124
125struct alpm_softc {
126	int base;
127	struct resource *res;
128        bus_space_tag_t smbst;
129        bus_space_handle_t smbsh;
130	device_t smbus;
131};
132
133#define ALPM_SMBINB(alpm,register) \
134	(bus_space_read_1(alpm->smbst, alpm->smbsh, register))
135#define ALPM_SMBOUTB(alpm,register,value) \
136	(bus_space_write_1(alpm->smbst, alpm->smbsh, register, value))
137
138static int
139alpm_probe(device_t dev)
140{
141#ifdef ALPM_SMBIO_BASE_ADDR
142	u_int32_t l;
143#endif
144
145	if (pci_get_devid(dev) == ACER_M1543_PMU_ID) {
146		device_set_desc(dev, "AcerLabs M15x3 Power Management Unit");
147
148#ifdef ALPM_SMBIO_BASE_ADDR
149		if (bootverbose || alpm_debug)
150			device_printf(dev, "forcing base I/O at 0x%x\n",
151					ALPM_SMBIO_BASE_ADDR);
152
153		/* disable I/O */
154		l = pci_read_config(dev, COM, 2);
155		pci_write_config(dev, COM, l & ~COM_ENABLE_IO, 2);
156
157		/* set the I/O base address */
158		pci_write_config(dev, SMBBA, ALPM_SMBIO_BASE_ADDR | 0x1, 4);
159
160		/* enable I/O */
161		pci_write_config(dev, COM, l | COM_ENABLE_IO, 2);
162
163		if (bus_set_resource(dev, SYS_RES_IOPORT, SMBBA,
164					ALPM_SMBIO_BASE_ADDR, 256)) {
165			device_printf(dev, "could not set bus resource\n");
166			return (ENXIO);
167		}
168#endif
169		return (0);
170	}
171
172	return (ENXIO);
173}
174
175static int
176alpm_attach(device_t dev)
177{
178	int rid;
179	u_int32_t l;
180	struct alpm_softc *alpm;
181
182	alpm = device_get_softc(dev);
183
184	/* Unlock SMBIO base register access */
185	l = pci_read_config(dev, ATPC, 1);
186	pci_write_config(dev, ATPC, l & ~ATPC_SMBCTRL, 1);
187
188	/*
189	 * XX linux sets clock to 74k, should we?
190	l = pci_read_config(dev, SMBHCBC, 1);
191	l &= 0x1f;
192	l |= SMBCLOCK_74K;
193	pci_write_config(dev, SMBHCBC, l, 1);
194	 */
195
196	if (bootverbose || alpm_debug) {
197		l = pci_read_config(dev, SMBHSI, 1);
198		device_printf(dev, "%s/%s",
199			(l & SMBHSI_HOST) ? "host":"nohost",
200			(l & SMBHSI_SLAVE) ? "slave":"noslave");
201
202		l = pci_read_config(dev, SMBHCBC, 1);
203		switch (l & SMBHCBC_CLOCK) {
204		case SMBCLOCK_149K:
205			printf(" 149K");
206			break;
207		case SMBCLOCK_74K:
208			printf(" 74K");
209			break;
210		case SMBCLOCK_37K:
211			printf(" 37K");
212			break;
213		case SMBCLOCK_223K:
214			printf(" 223K");
215			break;
216		case SMBCLOCK_111K:
217			printf(" 111K");
218			break;
219		case SMBCLOCK_55K:
220			printf(" 55K");
221			break;
222		default:
223			printf("unkown");
224			break;
225		}
226		printf("\n");
227	}
228
229	rid = SMBBA;
230	alpm->res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid,
231	    RF_ACTIVE);
232
233	if (alpm->res == NULL) {
234		device_printf(dev,"Could not allocate Bus space\n");
235		return (ENXIO);
236	}
237	alpm->smbst = rman_get_bustag(alpm->res);
238	alpm->smbsh = rman_get_bushandle(alpm->res);
239
240	/* attach the smbus */
241	alpm->smbus = device_add_child(dev, "smbus", -1);
242	bus_generic_attach(dev);
243
244	return (0);
245}
246
247static int
248alpm_detach(device_t dev)
249{
250	struct alpm_softc *alpm = device_get_softc(dev);
251
252	if (alpm->smbus) {
253		device_delete_child(dev, alpm->smbus);
254		alpm->smbus = NULL;
255	}
256
257	if (alpm->res)
258		bus_release_resource(dev, SYS_RES_IOPORT, SMBBA, alpm->res);
259
260	return (0);
261}
262
263static int
264alpm_callback(device_t dev, int index, caddr_t *data)
265{
266	int error = 0;
267
268	switch (index) {
269	case SMB_REQUEST_BUS:
270	case SMB_RELEASE_BUS:
271		/* ok, bus allocation accepted */
272		break;
273	default:
274		error = EINVAL;
275	}
276
277	return (error);
278}
279
280static int
281alpm_clear(struct alpm_softc *sc)
282{
283	ALPM_SMBOUTB(sc, SMBSTS, 0xff);
284	DELAY(10);
285
286	return (0);
287}
288
289#if 0
290static int
291alpm_abort(struct alpm_softc *sc)
292{
293	ALPM_SMBOUTB(sc, SMBCMD, T_OUT_CMD | ABORT_HOST);
294
295	return (0);
296}
297#endif
298
299static int
300alpm_idle(struct alpm_softc *sc)
301{
302	u_char sts;
303
304	sts = ALPM_SMBINB(sc, SMBSTS);
305
306	ALPM_DEBUG(printf("alpm: idle? STS=0x%x\n", sts));
307
308	return (sts & IDL_STS);
309}
310
311/*
312 * Poll the SMBus controller
313 */
314static int
315alpm_wait(struct alpm_softc *sc)
316{
317	int count = 10000;
318	u_char sts = 0;
319	int error;
320
321	/* wait for command to complete and SMBus controller is idle */
322	while(count--) {
323		DELAY(10);
324		sts = ALPM_SMBINB(sc, SMBSTS);
325		if (sts & SMI_I_STS)
326			break;
327	}
328
329	ALPM_DEBUG(printf("alpm: STS=0x%x\n", sts));
330
331	error = SMB_ENOERR;
332
333	if (!count)
334		error |= SMB_ETIMEOUT;
335
336	if (sts & TERMINATE)
337		error |= SMB_EABORT;
338
339	if (sts & BUS_COLLI)
340		error |= SMB_ENOACK;
341
342	if (sts & DEVICE_ERR)
343		error |= SMB_EBUSERR;
344
345	if (error != SMB_ENOERR)
346		alpm_clear(sc);
347
348	return (error);
349}
350
351static int
352alpm_quick(device_t dev, u_char slave, int how)
353{
354	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
355	int error;
356
357	alpm_clear(sc);
358	if (!alpm_idle(sc))
359		return (EBUSY);
360
361	switch (how) {
362	case SMB_QWRITE:
363		ALPM_DEBUG(printf("alpm: QWRITE to 0x%x", slave));
364		ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
365		break;
366	case SMB_QREAD:
367		ALPM_DEBUG(printf("alpm: QREAD to 0x%x", slave));
368		ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
369		break;
370	default:
371		panic("%s: unknown QUICK command (%x)!", __func__,
372			how);
373	}
374	ALPM_SMBOUTB(sc, SMBCMD, SMBQUICK);
375	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
376
377	error = alpm_wait(sc);
378
379	ALPM_DEBUG(printf(", error=0x%x\n", error));
380
381	return (error);
382}
383
384static int
385alpm_sendb(device_t dev, u_char slave, char byte)
386{
387	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
388	int error;
389
390	alpm_clear(sc);
391	if (!alpm_idle(sc))
392		return (SMB_EBUSY);
393
394	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
395	ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE);
396	ALPM_SMBOUTB(sc, SMBHDATA, byte);
397	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
398
399	error = alpm_wait(sc);
400
401	ALPM_DEBUG(printf("alpm: SENDB to 0x%x, byte=0x%x, error=0x%x\n", slave, byte, error));
402
403	return (error);
404}
405
406static int
407alpm_recvb(device_t dev, u_char slave, char *byte)
408{
409	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
410	int error;
411
412	alpm_clear(sc);
413	if (!alpm_idle(sc))
414		return (SMB_EBUSY);
415
416	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
417	ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE);
418	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
419
420	if ((error = alpm_wait(sc)) == SMB_ENOERR)
421		*byte = ALPM_SMBINB(sc, SMBHDATA);
422
423	ALPM_DEBUG(printf("alpm: RECVB from 0x%x, byte=0x%x, error=0x%x\n", slave, *byte, error));
424
425	return (error);
426}
427
428static int
429alpm_writeb(device_t dev, u_char slave, char cmd, char byte)
430{
431	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
432	int error;
433
434	alpm_clear(sc);
435	if (!alpm_idle(sc))
436		return (SMB_EBUSY);
437
438	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
439	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
440	ALPM_SMBOUTB(sc, SMBHDATA, byte);
441	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
442	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
443
444	error = alpm_wait(sc);
445
446	ALPM_DEBUG(printf("alpm: WRITEB to 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, byte, error));
447
448	return (error);
449}
450
451static int
452alpm_readb(device_t dev, u_char slave, char cmd, char *byte)
453{
454	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
455	int error;
456
457	alpm_clear(sc);
458	if (!alpm_idle(sc))
459		return (SMB_EBUSY);
460
461	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
462	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
463	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
464	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
465
466	if ((error = alpm_wait(sc)) == SMB_ENOERR)
467		*byte = ALPM_SMBINB(sc, SMBHDATA);
468
469	ALPM_DEBUG(printf("alpm: READB from 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, *byte, error));
470
471	return (error);
472}
473
474static int
475alpm_writew(device_t dev, u_char slave, char cmd, short word)
476{
477	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
478	int error;
479
480	alpm_clear(sc);
481	if (!alpm_idle(sc))
482		return (SMB_EBUSY);
483
484	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
485	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
486	ALPM_SMBOUTB(sc, SMBHDATA, word & 0x00ff);
487	ALPM_SMBOUTB(sc, SMBHDATB, (word & 0xff00) >> 8);
488	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
489	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
490
491	error = alpm_wait(sc);
492
493	ALPM_DEBUG(printf("alpm: WRITEW to 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, word, error));
494
495	return (error);
496}
497
498static int
499alpm_readw(device_t dev, u_char slave, char cmd, short *word)
500{
501	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
502	int error;
503	u_char high, low;
504
505	alpm_clear(sc);
506	if (!alpm_idle(sc))
507		return (SMB_EBUSY);
508
509	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
510	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
511	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
512	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
513
514	if ((error = alpm_wait(sc)) == SMB_ENOERR) {
515		low = ALPM_SMBINB(sc, SMBHDATA);
516		high = ALPM_SMBINB(sc, SMBHDATB);
517
518		*word = ((high & 0xff) << 8) | (low & 0xff);
519	}
520
521	ALPM_DEBUG(printf("alpm: READW from 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, *word, error));
522
523	return (error);
524}
525
526static int
527alpm_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf)
528{
529	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
530	u_char remain, len, i;
531	int error = SMB_ENOERR;
532
533	alpm_clear(sc);
534	if(!alpm_idle(sc))
535		return (SMB_EBUSY);
536
537	remain = count;
538	while (remain) {
539		len = min(remain, 32);
540
541		ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
542
543		/* set the cmd and reset the
544		 * 32-byte long internal buffer */
545		ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
546
547		ALPM_SMBOUTB(sc, SMBHDATA, len);
548
549		/* fill the 32-byte internal buffer */
550		for (i=0; i<len; i++) {
551			ALPM_SMBOUTB(sc, SMBHBLOCK, buf[count-remain+i]);
552			DELAY(2);
553		}
554		ALPM_SMBOUTB(sc, SMBHCMD, cmd);
555		ALPM_SMBOUTB(sc, SMBSTART, 0xff);
556
557		if ((error = alpm_wait(sc)) != SMB_ENOERR)
558			goto error;
559
560		remain -= len;
561	}
562
563error:
564	ALPM_DEBUG(printf("alpm: WRITEBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error));
565
566	return (error);
567}
568
569static int
570alpm_bread(device_t dev, u_char slave, char cmd, u_char count, char *buf)
571{
572	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
573	u_char remain, len, i;
574	int error = SMB_ENOERR;
575
576	alpm_clear(sc);
577	if (!alpm_idle(sc))
578		return (SMB_EBUSY);
579
580	remain = count;
581	while (remain) {
582		ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
583
584		/* set the cmd and reset the
585		 * 32-byte long internal buffer */
586		ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
587
588		ALPM_SMBOUTB(sc, SMBHCMD, cmd);
589		ALPM_SMBOUTB(sc, SMBSTART, 0xff);
590
591		if ((error = alpm_wait(sc)) != SMB_ENOERR)
592			goto error;
593
594		len = ALPM_SMBINB(sc, SMBHDATA);
595
596		/* read the 32-byte internal buffer */
597		for (i=0; i<len; i++) {
598			buf[count-remain+i] = ALPM_SMBINB(sc, SMBHBLOCK);
599			DELAY(2);
600		}
601
602		remain -= len;
603	}
604error:
605	ALPM_DEBUG(printf("alpm: READBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error));
606
607	return (error);
608}
609
610static devclass_t alpm_devclass;
611
612static device_method_t alpm_methods[] = {
613	/* device interface */
614	DEVMETHOD(device_probe,		alpm_probe),
615	DEVMETHOD(device_attach,	alpm_attach),
616	DEVMETHOD(device_detach,	alpm_detach),
617
618	/* smbus interface */
619	DEVMETHOD(smbus_callback,	alpm_callback),
620	DEVMETHOD(smbus_quick,		alpm_quick),
621	DEVMETHOD(smbus_sendb,		alpm_sendb),
622	DEVMETHOD(smbus_recvb,		alpm_recvb),
623	DEVMETHOD(smbus_writeb,		alpm_writeb),
624	DEVMETHOD(smbus_readb,		alpm_readb),
625	DEVMETHOD(smbus_writew,		alpm_writew),
626	DEVMETHOD(smbus_readw,		alpm_readw),
627	DEVMETHOD(smbus_bwrite,		alpm_bwrite),
628	DEVMETHOD(smbus_bread,		alpm_bread),
629
630	{ 0, 0 }
631};
632
633static driver_t alpm_driver = {
634	"alpm",
635	alpm_methods,
636	sizeof(struct alpm_softc)
637};
638
639DRIVER_MODULE(alpm, pci, alpm_driver, alpm_devclass, 0, 0);
640MODULE_DEPEND(alpm, pci, 1, 1, 1);
641MODULE_DEPEND(alpm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
642MODULE_VERSION(alpm, 1);
643