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