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