alpm.c revision 302408
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: stable/11/sys/dev/alpm/alpm.c 298955 2016-05-03 03:41:25Z pfg $");
33
34#include <sys/param.h>
35#include <sys/bus.h>
36#include <sys/kernel.h>
37#include <sys/lock.h>
38#include <sys/module.h>
39#include <sys/mutex.h>
40#include <sys/systm.h>
41
42#include <machine/bus.h>
43#include <machine/resource.h>
44#include <sys/rman.h>
45
46#include <dev/pci/pcivar.h>
47#include <dev/pci/pcireg.h>
48
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/*
63 * I/O registers offsets - the base address is programmed via the
64 * SMBBA PCI configuration register
65 */
66#define SMBSTS		0x0	/* SMBus host/slave status register */
67#define SMBCMD		0x1	/* SMBus host/slave command register */
68#define SMBSTART	0x2	/* start to generate programmed cycle */
69#define SMBHADDR	0x3	/* host address register */
70#define SMBHDATA	0x4	/* data A register for host controller */
71#define SMBHDATB	0x5	/* data B register for host controller */
72#define SMBHBLOCK	0x6	/* block register for host controller */
73#define SMBHCMD		0x7	/* command register for host controller */
74
75/* SMBHADDR mask. */
76#define	LSB		0x1	/* XXX: Better name: Read/Write? */
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		PCIR_BAR(1)
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	struct mtx lock;
131};
132
133#define	ALPM_LOCK(alpm)		mtx_lock(&(alpm)->lock)
134#define	ALPM_UNLOCK(alpm)	mtx_unlock(&(alpm)->lock)
135#define	ALPM_LOCK_ASSERT(alpm)	mtx_assert(&(alpm)->lock, MA_OWNED)
136
137#define ALPM_SMBINB(alpm,register) \
138	(bus_space_read_1(alpm->smbst, alpm->smbsh, register))
139#define ALPM_SMBOUTB(alpm,register,value) \
140	(bus_space_write_1(alpm->smbst, alpm->smbsh, register, value))
141
142static int	alpm_detach(device_t dev);
143
144static int
145alpm_probe(device_t dev)
146{
147
148	if (pci_get_devid(dev) == ACER_M1543_PMU_ID) {
149		device_set_desc(dev, "AcerLabs M15x3 Power Management Unit");
150
151		return (BUS_PROBE_DEFAULT);
152	}
153
154	return (ENXIO);
155}
156
157static int
158alpm_attach(device_t dev)
159{
160	int rid;
161	u_int32_t l;
162	struct alpm_softc *alpm;
163
164	alpm = device_get_softc(dev);
165
166	/* Unlock SMBIO base register access */
167	l = pci_read_config(dev, ATPC, 1);
168	pci_write_config(dev, ATPC, l & ~ATPC_SMBCTRL, 1);
169
170	/*
171	 * XX linux sets clock to 74k, should we?
172	l = pci_read_config(dev, SMBHCBC, 1);
173	l &= 0x1f;
174	l |= SMBCLOCK_74K;
175	pci_write_config(dev, SMBHCBC, l, 1);
176	 */
177
178	if (bootverbose || alpm_debug) {
179		l = pci_read_config(dev, SMBHSI, 1);
180		device_printf(dev, "%s/%s",
181			(l & SMBHSI_HOST) ? "host":"nohost",
182			(l & SMBHSI_SLAVE) ? "slave":"noslave");
183
184		l = pci_read_config(dev, SMBHCBC, 1);
185		switch (l & SMBHCBC_CLOCK) {
186		case SMBCLOCK_149K:
187			printf(" 149K");
188			break;
189		case SMBCLOCK_74K:
190			printf(" 74K");
191			break;
192		case SMBCLOCK_37K:
193			printf(" 37K");
194			break;
195		case SMBCLOCK_223K:
196			printf(" 223K");
197			break;
198		case SMBCLOCK_111K:
199			printf(" 111K");
200			break;
201		case SMBCLOCK_55K:
202			printf(" 55K");
203			break;
204		default:
205			printf("unknown");
206			break;
207		}
208		printf("\n");
209	}
210
211	rid = SMBBA;
212	alpm->res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid,
213	    RF_ACTIVE);
214
215	if (alpm->res == NULL) {
216		device_printf(dev,"Could not allocate Bus space\n");
217		return (ENXIO);
218	}
219	alpm->smbst = rman_get_bustag(alpm->res);
220	alpm->smbsh = rman_get_bushandle(alpm->res);
221	mtx_init(&alpm->lock, device_get_nameunit(dev), "alpm", MTX_DEF);
222
223	/* attach the smbus */
224	alpm->smbus = device_add_child(dev, "smbus", -1);
225	if (alpm->smbus == NULL) {
226		alpm_detach(dev);
227		return (EINVAL);
228	}
229	bus_generic_attach(dev);
230
231	return (0);
232}
233
234static int
235alpm_detach(device_t dev)
236{
237	struct alpm_softc *alpm = device_get_softc(dev);
238
239	if (alpm->smbus) {
240		device_delete_child(dev, alpm->smbus);
241		alpm->smbus = NULL;
242	}
243	mtx_destroy(&alpm->lock);
244
245	if (alpm->res)
246		bus_release_resource(dev, SYS_RES_IOPORT, SMBBA, alpm->res);
247
248	return (0);
249}
250
251static int
252alpm_callback(device_t dev, int index, void *data)
253{
254	int error = 0;
255
256	switch (index) {
257	case SMB_REQUEST_BUS:
258	case SMB_RELEASE_BUS:
259		/* ok, bus allocation accepted */
260		break;
261	default:
262		error = EINVAL;
263	}
264
265	return (error);
266}
267
268static int
269alpm_clear(struct alpm_softc *sc)
270{
271	ALPM_SMBOUTB(sc, SMBSTS, 0xff);
272	DELAY(10);
273
274	return (0);
275}
276
277#if 0
278static int
279alpm_abort(struct alpm_softc *sc)
280{
281	ALPM_SMBOUTB(sc, SMBCMD, T_OUT_CMD | ABORT_HOST);
282
283	return (0);
284}
285#endif
286
287static int
288alpm_idle(struct alpm_softc *sc)
289{
290	u_char sts;
291
292	sts = ALPM_SMBINB(sc, SMBSTS);
293
294	ALPM_DEBUG(printf("alpm: idle? STS=0x%x\n", sts));
295
296	return (sts & IDL_STS);
297}
298
299/*
300 * Poll the SMBus controller
301 */
302static int
303alpm_wait(struct alpm_softc *sc)
304{
305	int count = 10000;
306	u_char sts = 0;
307	int error;
308
309	/* wait for command to complete and SMBus controller is idle */
310	while (count--) {
311		DELAY(10);
312		sts = ALPM_SMBINB(sc, SMBSTS);
313		if (sts & SMI_I_STS)
314			break;
315	}
316
317	ALPM_DEBUG(printf("alpm: STS=0x%x\n", sts));
318
319	error = SMB_ENOERR;
320
321	if (!count)
322		error |= SMB_ETIMEOUT;
323
324	if (sts & TERMINATE)
325		error |= SMB_EABORT;
326
327	if (sts & BUS_COLLI)
328		error |= SMB_ENOACK;
329
330	if (sts & DEVICE_ERR)
331		error |= SMB_EBUSERR;
332
333	if (error != SMB_ENOERR)
334		alpm_clear(sc);
335
336	return (error);
337}
338
339static int
340alpm_quick(device_t dev, u_char slave, int how)
341{
342	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
343	int error;
344
345	ALPM_LOCK(sc);
346	alpm_clear(sc);
347	if (!alpm_idle(sc)) {
348		ALPM_UNLOCK(sc);
349		return (EBUSY);
350	}
351
352	switch (how) {
353	case SMB_QWRITE:
354		ALPM_DEBUG(printf("alpm: QWRITE to 0x%x", slave));
355		ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
356		break;
357	case SMB_QREAD:
358		ALPM_DEBUG(printf("alpm: QREAD to 0x%x", slave));
359		ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
360		break;
361	default:
362		panic("%s: unknown QUICK command (%x)!", __func__,
363			how);
364	}
365	ALPM_SMBOUTB(sc, SMBCMD, SMBQUICK);
366	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
367
368	error = alpm_wait(sc);
369
370	ALPM_DEBUG(printf(", error=0x%x\n", error));
371	ALPM_UNLOCK(sc);
372
373	return (error);
374}
375
376static int
377alpm_sendb(device_t dev, u_char slave, char byte)
378{
379	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
380	int error;
381
382	ALPM_LOCK(sc);
383	alpm_clear(sc);
384	if (!alpm_idle(sc)) {
385		ALPM_UNLOCK(sc);
386		return (SMB_EBUSY);
387	}
388
389	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
390	ALPM_SMBOUTB(sc, SMBCMD, SMBSRBYTE);
391	ALPM_SMBOUTB(sc, SMBHDATA, byte);
392	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
393
394	error = alpm_wait(sc);
395
396	ALPM_DEBUG(printf("alpm: SENDB to 0x%x, byte=0x%x, error=0x%x\n", slave, byte, error));
397	ALPM_UNLOCK(sc);
398
399	return (error);
400}
401
402static int
403alpm_recvb(device_t dev, u_char slave, char *byte)
404{
405	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
406	int error;
407
408	ALPM_LOCK(sc);
409	alpm_clear(sc);
410	if (!alpm_idle(sc)) {
411		ALPM_UNLOCK(sc);
412		return (SMB_EBUSY);
413	}
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	ALPM_UNLOCK(sc);
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_LOCK(sc);
435	alpm_clear(sc);
436	if (!alpm_idle(sc)) {
437		ALPM_UNLOCK(sc);
438		return (SMB_EBUSY);
439	}
440
441	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
442	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
443	ALPM_SMBOUTB(sc, SMBHDATA, byte);
444	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
445	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
446
447	error = alpm_wait(sc);
448
449	ALPM_DEBUG(printf("alpm: WRITEB to 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, byte, error));
450	ALPM_UNLOCK(sc);
451
452	return (error);
453}
454
455static int
456alpm_readb(device_t dev, u_char slave, char cmd, char *byte)
457{
458	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
459	int error;
460
461	ALPM_LOCK(sc);
462	alpm_clear(sc);
463	if (!alpm_idle(sc)) {
464		ALPM_UNLOCK(sc);
465		return (SMB_EBUSY);
466	}
467
468	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
469	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBYTE);
470	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
471	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
472
473	if ((error = alpm_wait(sc)) == SMB_ENOERR)
474		*byte = ALPM_SMBINB(sc, SMBHDATA);
475
476	ALPM_DEBUG(printf("alpm: READB from 0x%x, cmd=0x%x, byte=0x%x, error=0x%x\n", slave, cmd, *byte, error));
477	ALPM_UNLOCK(sc);
478
479	return (error);
480}
481
482static int
483alpm_writew(device_t dev, u_char slave, char cmd, short word)
484{
485	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
486	int error;
487
488	ALPM_LOCK(sc);
489	alpm_clear(sc);
490	if (!alpm_idle(sc)) {
491		ALPM_UNLOCK(sc);
492		return (SMB_EBUSY);
493	}
494
495	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
496	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
497	ALPM_SMBOUTB(sc, SMBHDATA, word & 0x00ff);
498	ALPM_SMBOUTB(sc, SMBHDATB, (word & 0xff00) >> 8);
499	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
500	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
501
502	error = alpm_wait(sc);
503
504	ALPM_DEBUG(printf("alpm: WRITEW to 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, word, error));
505	ALPM_UNLOCK(sc);
506
507	return (error);
508}
509
510static int
511alpm_readw(device_t dev, u_char slave, char cmd, short *word)
512{
513	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
514	int error;
515	u_char high, low;
516
517	ALPM_LOCK(sc);
518	alpm_clear(sc);
519	if (!alpm_idle(sc)) {
520		ALPM_UNLOCK(sc);
521		return (SMB_EBUSY);
522	}
523
524	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
525	ALPM_SMBOUTB(sc, SMBCMD, SMBWRWORD);
526	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
527	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
528
529	if ((error = alpm_wait(sc)) == SMB_ENOERR) {
530		low = ALPM_SMBINB(sc, SMBHDATA);
531		high = ALPM_SMBINB(sc, SMBHDATB);
532
533		*word = ((high & 0xff) << 8) | (low & 0xff);
534	}
535
536	ALPM_DEBUG(printf("alpm: READW from 0x%x, cmd=0x%x, word=0x%x, error=0x%x\n", slave, cmd, *word, error));
537	ALPM_UNLOCK(sc);
538
539	return (error);
540}
541
542static int
543alpm_bwrite(device_t dev, u_char slave, char cmd, u_char count, char *buf)
544{
545	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
546	u_char i;
547	int error;
548
549	if (count < 1 || count > 32)
550		return (SMB_EINVAL);
551
552	ALPM_LOCK(sc);
553	alpm_clear(sc);
554	if(!alpm_idle(sc)) {
555		ALPM_UNLOCK(sc);
556		return (SMB_EBUSY);
557	}
558
559	ALPM_SMBOUTB(sc, SMBHADDR, slave & ~LSB);
560
561	/* set the cmd and reset the
562	 * 32-byte long internal buffer */
563	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
564
565	ALPM_SMBOUTB(sc, SMBHDATA, count);
566
567	/* fill the 32-byte internal buffer */
568	for (i = 0; i < count; i++) {
569		ALPM_SMBOUTB(sc, SMBHBLOCK, buf[i]);
570		DELAY(2);
571	}
572	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
573	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
574
575	error = alpm_wait(sc);
576
577	ALPM_DEBUG(printf("alpm: WRITEBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, count, cmd, error));
578	ALPM_UNLOCK(sc);
579
580	return (error);
581}
582
583static int
584alpm_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf)
585{
586	struct alpm_softc *sc = (struct alpm_softc *)device_get_softc(dev);
587	u_char data, len, i;
588	int error;
589
590	if (*count < 1 || *count > 32)
591		return (SMB_EINVAL);
592
593	ALPM_LOCK(sc);
594	alpm_clear(sc);
595	if (!alpm_idle(sc)) {
596		ALPM_UNLOCK(sc);
597		return (SMB_EBUSY);
598	}
599
600	ALPM_SMBOUTB(sc, SMBHADDR, slave | LSB);
601
602	/* set the cmd and reset the
603	 * 32-byte long internal buffer */
604	ALPM_SMBOUTB(sc, SMBCMD, SMBWRBLOCK | SMB_BLK_CLR);
605
606	ALPM_SMBOUTB(sc, SMBHCMD, cmd);
607	ALPM_SMBOUTB(sc, SMBSTART, 0xff);
608
609	if ((error = alpm_wait(sc)) != SMB_ENOERR)
610			goto error;
611
612	len = ALPM_SMBINB(sc, SMBHDATA);
613
614	/* read the 32-byte internal buffer */
615	for (i = 0; i < len; i++) {
616		data = ALPM_SMBINB(sc, SMBHBLOCK);
617		if (i < *count)
618			buf[i] = data;
619		DELAY(2);
620	}
621	*count = len;
622
623error:
624	ALPM_DEBUG(printf("alpm: READBLK to 0x%x, count=0x%x, cmd=0x%x, error=0x%x", slave, *count, cmd, error));
625	ALPM_UNLOCK(sc);
626
627	return (error);
628}
629
630static devclass_t alpm_devclass;
631
632static device_method_t alpm_methods[] = {
633	/* device interface */
634	DEVMETHOD(device_probe,		alpm_probe),
635	DEVMETHOD(device_attach,	alpm_attach),
636	DEVMETHOD(device_detach,	alpm_detach),
637
638	/* smbus interface */
639	DEVMETHOD(smbus_callback,	alpm_callback),
640	DEVMETHOD(smbus_quick,		alpm_quick),
641	DEVMETHOD(smbus_sendb,		alpm_sendb),
642	DEVMETHOD(smbus_recvb,		alpm_recvb),
643	DEVMETHOD(smbus_writeb,		alpm_writeb),
644	DEVMETHOD(smbus_readb,		alpm_readb),
645	DEVMETHOD(smbus_writew,		alpm_writew),
646	DEVMETHOD(smbus_readw,		alpm_readw),
647	DEVMETHOD(smbus_bwrite,		alpm_bwrite),
648	DEVMETHOD(smbus_bread,		alpm_bread),
649
650	{ 0, 0 }
651};
652
653static driver_t alpm_driver = {
654	"alpm",
655	alpm_methods,
656	sizeof(struct alpm_softc)
657};
658
659DRIVER_MODULE(alpm, pci, alpm_driver, alpm_devclass, 0, 0);
660DRIVER_MODULE(smbus, alpm, smbus_driver, smbus_devclass, 0, 0);
661MODULE_DEPEND(alpm, pci, 1, 1, 1);
662MODULE_DEPEND(alpm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
663MODULE_VERSION(alpm, 1);
664