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