summaryrefslogtreecommitdiff
path: root/sys/dev/pci/alipm.c
blob: da1c657dedff856f5bd2c013492dde17a3ffda16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/*	$OpenBSD: alipm.c,v 1.16 2012/10/05 10:51:28 haesbaert Exp $	*/

/*
 * Copyright (c) 2005 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/rwlock.h>
#include <sys/systm.h>

#include <dev/i2c/i2cvar.h>

#include <dev/pci/pcidevs.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>

#ifdef __sparc64__
#include <arch/sparc64/dev/ofwi2cvar.h>
#endif

/*
 * Acer Labs M7101 Power register definitions.
 */

/* PCI configuration registers. */
#define ALIPM_CONF	0xd0		/* general configuration */
#define ALIPM_CONF_SMBEN	0x0400		/* enable SMBus */
#define ALIPM_BASE	0xe0		/* ACPI and SMBus base address */
#define ALIPM_SMB_HOSTC	0xf0		/* host configuration */
#define ALIPM_SMB_HOSTC_HSTEN	0x00000001	/* enable host controller */
#define ALIPM_SMB_HOSTC_CLOCK	0x00e00000	/* clock speed */
#define ALIPM_SMB_HOSTC_149K	0x00000000	/* 149 KHz clock */
#define ALIPM_SMB_HOSTC_74K	0x00200000	/*  74 KHz clock */
#define ALIPM_SMB_HOSTC_37K	0x00400000	/*  37 KHz clock */
#define ALIPM_SMB_HOSTC_223K	0x00800000	/* 223 KHz clock */
#define ALIPM_SMB_HOSTC_111K	0x00a00000	/* 111 KHz clock */
#define ALIPM_SMB_HOSTC_55K	0x00c00000	/*  55 KHz clock */

#define ALIPM_SMB_SIZE		32	/* SMBus I/O space size */

/* SMBus I/O registers */
#define ALIPM_SMB_HS	0x00		/* host status */
#define ALIPM_SMB_HS_IDLE	0x04
#define ALIPM_SMB_HS_BUSY	0x08	/* running a command */
#define ALIPM_SMB_HS_DONE	0x10	/* command completed */
#define ALIPM_SMB_HS_DEVERR	0x20	/* command error */
#define ALIPM_SMB_HS_BUSERR	0x40	/* transaction collision */
#define ALIPM_SMB_HS_FAILED	0x80	/* failed bus transaction */
#define ALIPM_SMB_HS_BITS \
  "\020\003IDLE\004BUSY\005DONE\006DEVERR\007BUSERR\010FAILED"
#define ALIPM_SMB_HC	0x01		/* host control */
#define ALIPM_SMB_HC_KILL	0x04		/* kill command */
#define ALIPM_SMB_HC_RESET	0x08		/* reset bus */
#define ALIPM_SMB_HC_CMD_QUICK	0x00		/* QUICK command */
#define ALIPM_SMB_HC_CMD_BYTE	0x10		/* BYTE command */
#define ALIPM_SMB_HC_CMD_BDATA	0x20		/* BYTE DATA command */
#define ALIPM_SMB_HC_CMD_WDATA	0x30		/* WORD DATA command */
#define ALIPM_SMB_HC_CMD_BLOCK 0x40		/* BLOCK command */
#define ALIPM_SMB_START		0x02	/* start command */
#define ALIPM_SMB_TXSLVA	0x03	/* transmit slave address */
#define ALIPM_SMB_TXSLVA_READ	(1 << 0)	/* read direction */
#define ALIPM_SMB_TXSLVA_ADDR(x) (((x) & 0x7f) << 1) /* 7-bit address */
#define ALIPM_SMB_HD0		0x04	/* host data 0 */
#define ALIPM_SMB_HD1		0x05	/* host data 1 */
#define ALIPM_SMB_HBDB		0x06	/* host block data byte */
#define ALIPM_SMB_HCMD		0x07	/* host command */

/*
 * Newer chips have a more standard, but different PCI configuration
 * register layout.
 */

#define ALIPM_SMB_BASE	0x14		/* SMBus base address */
#define ALIPM_SMB_HOSTX	0xe0		/* host configuration */

#ifdef ALIPM_DEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif

#define ALIPM_DELAY	100
#define ALIPM_TIMEOUT	1

struct alipm_softc {
	struct device sc_dev;

	bus_space_tag_t sc_iot;
	bus_space_handle_t sc_ioh;

	struct i2c_controller sc_smb_tag;
	struct rwlock sc_smb_lock;
};

int	alipm_match(struct device *, void *, void *);
void	alipm_attach(struct device *, struct device *, void *);

int	alipm_smb_acquire_bus(void *, int);
void	alipm_smb_release_bus(void *, int);
int	alipm_smb_exec(void *, i2c_op_t, i2c_addr_t, const void *,
	    size_t, void *, size_t, int);

struct cfattach alipm_ca = {
	sizeof(struct alipm_softc),
	alipm_match,
	alipm_attach
};

struct cfdriver alipm_cd = {
	NULL, "alipm", DV_DULL
};

int
alipm_match(struct device *parent, void *match, void *aux)
{
	struct pci_attach_args *pa = aux;

	if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_ALI &&
	    (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_ALI_M7101))
		return (1);
	return (0);
}

void
alipm_attach(struct device *parent, struct device *self, void *aux)
{
	struct alipm_softc *sc = (struct alipm_softc *) self;
	struct pci_attach_args *pa = aux;
	struct i2cbus_attach_args iba;
	pcireg_t iobase, reg;
	bus_size_t iosize = ALIPM_SMB_SIZE;

	/* Old chips don't have the PCI 2.2 Capabilities List. */
	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
	if ((reg & PCI_STATUS_CAPLIST_SUPPORT) == 0) {
		/* Map I/O space */
		iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_BASE);
		sc->sc_iot = pa->pa_iot;
		if (iobase == 0 ||
		    bus_space_map(sc->sc_iot, iobase >> 16,
		    iosize, 0, &sc->sc_ioh)) {
			printf(": can't map i/o space\n");
			return;
		}

		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_CONF);
		if ((reg & ALIPM_CONF_SMBEN) == 0) {
			printf(": SMBus disabled\n");
			goto fail;
		}

		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_SMB_HOSTC);
		if ((reg & ALIPM_SMB_HOSTC_HSTEN) == 0) {
			printf(": SMBus host disabled\n");
			goto fail;
		}
	} else {
		/* Map I/O space */
		if (pci_mapreg_map(pa, ALIPM_SMB_BASE, PCI_MAPREG_TYPE_IO, 0,
		    &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, ALIPM_SMB_SIZE)) {
			printf(": can't map i/o space\n");
			return;
		}

		reg = pci_conf_read(pa->pa_pc, pa->pa_tag, ALIPM_SMB_HOSTX);
		if ((reg & ALIPM_SMB_HOSTC_HSTEN) == 0) {
			printf(": SMBus host disabled\n");
			goto fail;
		}
	}

	switch (reg & ALIPM_SMB_HOSTC_CLOCK) {
	case ALIPM_SMB_HOSTC_149K:
		printf(": 149KHz clock");
		break;
	case ALIPM_SMB_HOSTC_74K:
		printf(": 74KHz clock");
		break;
	case ALIPM_SMB_HOSTC_37K:
		printf(": 37KHz clock");
		break;
	case ALIPM_SMB_HOSTC_223K:
		printf(": 223KHz clock");
		break;
	case ALIPM_SMB_HOSTC_111K:
		printf(": 111KHz clock");
		break;
	case ALIPM_SMB_HOSTC_55K:
		printf(": 55KHz clock");
		break;
	default:
		printf(" unknown clock speed");
		break;
	}

	printf("\n");

	/* Attach I2C bus */
	rw_init(&sc->sc_smb_lock, "alipm");
	sc->sc_smb_tag.ic_cookie = sc;
	sc->sc_smb_tag.ic_acquire_bus = alipm_smb_acquire_bus;
	sc->sc_smb_tag.ic_release_bus = alipm_smb_release_bus;
	sc->sc_smb_tag.ic_exec = alipm_smb_exec;

	bzero(&iba, sizeof iba);
	iba.iba_name = "iic";
	iba.iba_tag = &sc->sc_smb_tag;
#ifdef __sparc64__
	iba.iba_bus_scan = ofwiic_pci_scan;
	iba.iba_bus_scan_arg = pa;
#endif
	config_found(&sc->sc_dev, &iba, iicbus_print);

	return;

fail:
	bus_space_unmap(sc->sc_iot, sc->sc_ioh, iosize);
}

int
alipm_smb_acquire_bus(void *cookie, int flags)
{
	struct alipm_softc *sc = cookie;

	if (flags & I2C_F_POLL)
		return (0);

	return (rw_enter(&sc->sc_smb_lock, RW_WRITE | RW_INTR));
}

void
alipm_smb_release_bus(void *cookie, int flags)
{
	struct alipm_softc *sc = cookie;

	if (flags & I2C_F_POLL)
		return;

	rw_exit(&sc->sc_smb_lock);
}

int
alipm_smb_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
{
	struct alipm_softc *sc = cookie;
	u_int8_t *b;
	u_int8_t ctl, st;
	int retries, error = 0;

	DPRINTF(("%s: exec op %d, addr 0x%x, cmdlen %d, len %d, "
	    "flags 0x%x\n", sc->sc_dev.dv_xname, op, addr, cmdlen,
	    len, flags));

	if (!I2C_OP_STOP_P(op) || cmdlen > 1 || len > 2)
		return (EOPNOTSUPP);

	/* Clear status bits */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS,
	    ALIPM_SMB_HS_DONE | ALIPM_SMB_HS_FAILED |
	    ALIPM_SMB_HS_BUSERR | ALIPM_SMB_HS_DEVERR);
	bus_space_barrier(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, 1,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);

	/* Wait until bus is idle */
	for (retries = 1000; retries > 0; retries--) {
		st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, 1,
		    BUS_SPACE_BARRIER_READ);
		if (st & (ALIPM_SMB_HS_IDLE | ALIPM_SMB_HS_FAILED |
		    ALIPM_SMB_HS_BUSERR | ALIPM_SMB_HS_DEVERR))
			break;
		DELAY(ALIPM_DELAY);
	}
	if (retries == 0) {
		printf("%s: timeout st 0x%b\n", sc->sc_dev.dv_xname,
		    st, ALIPM_SMB_HS_BITS);
		return (ETIMEDOUT);
	}
	if (st & (ALIPM_SMB_HS_FAILED |
	    ALIPM_SMB_HS_BUSERR | ALIPM_SMB_HS_DEVERR)) {
		printf("%s: error st 0x%b\n", sc->sc_dev.dv_xname,
		    st, ALIPM_SMB_HS_BITS);
		return (EIO);
	}

	/* Set slave address and transfer direction. */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_TXSLVA,
	    ALIPM_SMB_TXSLVA_ADDR(addr) |
	    (I2C_OP_READ_P(op) ? ALIPM_SMB_TXSLVA_READ : 0));

	b = (void *)cmdbuf;
	if (cmdlen > 0)
		/* Set command byte */
		bus_space_write_1(sc->sc_iot, sc->sc_ioh,
		     ALIPM_SMB_HCMD, b[0]);

	if (I2C_OP_WRITE_P(op)) {
		/* Write data. */
		b = buf;
		if (len > 0)
			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD0, b[0]);
		if (len > 1)
			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD1, b[1]);
	}

	/* Set SMBus command */
	if (len == 0)
		ctl = ALIPM_SMB_HC_CMD_BYTE;
	else if (len == 1)
		ctl = ALIPM_SMB_HC_CMD_BDATA;
	else if (len == 2)
		ctl = ALIPM_SMB_HC_CMD_WDATA;
	else
		panic("%s: unexpected len %zd", __func__, len);
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HC, ctl);

	/* Start transaction */
	bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, ALIPM_SMB_SIZE,
	    BUS_SPACE_BARRIER_WRITE);
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_START, 0xff);
	bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, ALIPM_SMB_SIZE,
	    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);

	/* Poll for completion */
	DELAY(ALIPM_DELAY);
	for (retries = 1000; retries > 0; retries--) {
		st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, 1,
		    BUS_SPACE_BARRIER_READ);
		if (st & (ALIPM_SMB_HS_IDLE | ALIPM_SMB_HS_FAILED |
		    ALIPM_SMB_HS_BUSERR | ALIPM_SMB_HS_DEVERR))
			break;
		DELAY(ALIPM_DELAY);
	}
	if (retries == 0) {
		printf("%s: timeout st 0x%b, resetting\n",
		    sc->sc_dev.dv_xname, st, ALIPM_SMB_HS_BITS);
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HC,
		    ALIPM_SMB_HC_RESET);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, ALIPM_SMB_SIZE,
		     BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
		st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, 1,
		    BUS_SPACE_BARRIER_READ);
		error = ETIMEDOUT;
		goto done;
	}

	if ((st & ALIPM_SMB_HS_DONE) == 0) {
		bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HC,
		     ALIPM_SMB_HC_KILL);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, ALIPM_SMB_SIZE,
		     BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
		st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS);
		bus_space_barrier(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, 1,
		    BUS_SPACE_BARRIER_READ);
		if ((st & ALIPM_SMB_HS_FAILED) == 0)
			printf("%s: error st 0x%b\n", sc->sc_dev.dv_xname,
			    st, ALIPM_SMB_HS_BITS);
	}

	/* Check for errors */
	if (st & (ALIPM_SMB_HS_FAILED |
	    ALIPM_SMB_HS_BUSERR | ALIPM_SMB_HS_DEVERR)) {
		error = EIO;
		goto done;
	}

	if (I2C_OP_READ_P(op)) {
		/* Read data */
		b = buf;
		if (len > 0) {
			b[0] = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD0);
			bus_space_barrier(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD0, 1, BUS_SPACE_BARRIER_READ);
		}
		if (len > 1) {
			b[1] = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD1);
			bus_space_barrier(sc->sc_iot, sc->sc_ioh,
			    ALIPM_SMB_HD1, 1, BUS_SPACE_BARRIER_READ);
		}
	}

done:
	/* Clear status bits */
	bus_space_write_1(sc->sc_iot, sc->sc_ioh, ALIPM_SMB_HS, st);

	return (error);
}