xref: /openbsd-src/sys/dev/pci/viapm.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: viapm.c,v 1.10 2009/03/29 21:53:53 sthen Exp $	*/
2 
3 /*
4  * Copyright (c) 2005 Mark Kettenis <kettenis@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * VIA VT8237 SMBus controller driver.
21  */
22 
23 #include <sys/param.h>
24 #include <sys/systm.h>
25 #include <sys/device.h>
26 #include <sys/kernel.h>
27 #include <sys/rwlock.h>
28 #include <sys/proc.h>
29 
30 #include <machine/bus.h>
31 
32 #include <dev/pci/pcidevs.h>
33 #include <dev/pci/pcireg.h>
34 #include <dev/pci/pcivar.h>
35 
36 #include <dev/i2c/i2cvar.h>
37 
38 /*
39  * VIA VT8237 ISA register definitions.
40  */
41 
42 /* PCI configuration registers */
43 #define VIAPM_SMB_BASE	0xd0		/* SMBus base address */
44 #define VIAPM_SMB_HOSTC	0xd2		/* host configuration */
45 #define VIAPM_SMB_HOSTC_HSTEN	(1 << 0)	/* enable host controller */
46 #define VIAPM_SMB_HOSTC_INTEN	(1 << 1)	/* enable SCI/SMI */
47 #define VIAPM_SMB_HOSTC_SCIEN	(1 << 3)	/* interrupt type (SCI/SMI) */
48 
49 /* SMBus I/O registers */
50 #define VIAPM_SMB_HS	0x00		/* host status */
51 #define VIAPM_SMB_HS_BUSY	(1 << 0)	/* running a command */
52 #define VIAPM_SMB_HS_INTR	(1 << 1)	/* command completed */
53 #define VIAPM_SMB_HS_DEVERR	(1 << 2)	/* command error */
54 #define VIAPM_SMB_HS_BUSERR	(1 << 3)	/* transaction collision */
55 #define VIAPM_SMB_HS_FAILED	(1 << 4)	/* failed bus transaction */
56 #define VIAPM_SMB_HS_INUSE	(1 << 6)	/* bus semaphore */
57 #define VIAPM_SMB_HS_BITS	\
58   "\020\001BUSY\002INTR\003DEVERR\004BUSERR\005FAILED\007INUSE"
59 #define VIAPM_SMB_HC	0x02		/* host control */
60 #define VIAPM_SMB_HC_INTREN	(1 << 0)	/* enable interrupts */
61 #define VIAPM_SMB_HC_KILL	(1 << 1)	/* kill current transaction */
62 #define VIAPM_SMB_HC_CMD_QUICK	(0 << 2)	/* QUICK command */
63 #define VIAPM_SMB_HC_CMD_BYTE	(1 << 2)	/* BYTE command */
64 #define VIAPM_SMB_HC_CMD_BDATA	(2 << 2)	/* BYTE DATA command */
65 #define VIAPM_SMB_HC_CMD_WDATA	(3 << 2)	/* WORD DATA command */
66 #define VIAPM_SMB_HC_CMD_PCALL	(4 << 2)	/* PROCESS CALL command */
67 #define VIAPM_SMB_HC_CMD_BLOCK	(5 << 2)	/* BLOCK command */
68 #define VIAPM_SMB_HC_START	(1 << 6)	/* start transaction */
69 #define VIAPM_SMB_HCMD	0x03		/* host command */
70 #define VIAPM_SMB_TXSLVA	0x04		/* transmit slave address */
71 #define VIAPM_SMB_TXSLVA_READ	(1 << 0)	/* read direction */
72 #define VIAPM_SMB_TXSLVA_ADDR(x) (((x) & 0x7f) << 1) /* 7-bit address */
73 #define VIAPM_SMB_HD0	0x05		/* host data 0 */
74 #define VIAPM_SMB_HD1	0x06		/* host data 1 */
75 #define VIAPM_SMB_HBDB	0x07		/* host block data byte */
76 
77 #define VIAPM_SMB_SIZE	16
78 
79 #ifdef VIAPM_DEBUG
80 #define DPRINTF(x) printf x
81 #else
82 #define DPRINTF(x)
83 #endif
84 
85 #define VIAPM_DELAY	100
86 #define VIAPM_TIMEOUT	1
87 
88 struct viapm_softc {
89 	struct device		sc_dev;
90 
91 	bus_space_tag_t		sc_iot;
92 	bus_space_handle_t	sc_ioh;
93 	void *			sc_ih;
94 	int			sc_poll;
95 
96 	struct i2c_controller	sc_i2c_tag;
97 	struct rwlock		sc_i2c_lock;
98 	struct {
99 		i2c_op_t     op;
100 		void *       buf;
101 		size_t       len;
102 		int          flags;
103 		volatile int error;
104 	}			sc_i2c_xfer;
105 };
106 
107 int	viapm_match(struct device *, void *, void *);
108 void	viapm_attach(struct device *, struct device *, void *);
109 
110 int	viapm_i2c_acquire_bus(void *, int);
111 void	viapm_i2c_release_bus(void *, int);
112 int	viapm_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
113 	    void *, size_t, int);
114 
115 int	viapm_intr(void *);
116 
117 struct cfattach viapm_ca = {
118 	sizeof(struct viapm_softc),
119 	viapm_match,
120 	viapm_attach
121 };
122 
123 struct cfdriver viapm_cd = {
124 	NULL, "viapm", DV_DULL
125 };
126 
127 const struct pci_matchid viapm_ids[] = {
128 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8233_ISA },
129 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8233A_ISA },
130 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8235_ISA },
131 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8237_ISA },
132 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8237A_ISA },
133 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8237S_ISA },
134 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_VT8251_ISA },
135 	{ PCI_VENDOR_VIATECH, PCI_PRODUCT_VIATECH_CX700_ISA }
136 };
137 
138 int
139 viapm_match(struct device *parent, void *match, void *aux)
140 {
141 	return (pci_matchbyid(aux, viapm_ids,
142 	    sizeof(viapm_ids) / sizeof(viapm_ids[0])));
143 }
144 
145 void
146 viapm_attach(struct device *parent, struct device *self, void *aux)
147 {
148 	struct viapm_softc *sc = (struct viapm_softc *)self;
149 	struct pci_attach_args *pa = aux;
150 	struct i2cbus_attach_args iba;
151 	pcireg_t conf, iobase;
152 #if 0
153 	pci_intr_handle_t ih;
154 	const char *intrstr = NULL;
155 #endif
156 
157 	/* Map I/O space */
158 	sc->sc_iot = pa->pa_iot;
159 	iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAPM_SMB_BASE);
160 	if (iobase == 0 ||
161 	    bus_space_map(sc->sc_iot, iobase & 0xfffe,
162 	    VIAPM_SMB_SIZE, 0, &sc->sc_ioh)) {
163 		printf(": can't map i/o space\n");
164 		return;
165 	}
166 
167 	/* Read configuration */
168 	conf = (iobase >> 16);
169 	DPRINTF((": conf 0x%x", conf));
170 
171 	if ((conf & VIAPM_SMB_HOSTC_HSTEN) == 0) {
172 		printf(": SMBus host disabled\n");
173 		goto fail;
174 	}
175 
176 	if (conf & VIAPM_SMB_HOSTC_INTEN) {
177 		if (conf & VIAPM_SMB_HOSTC_SCIEN)
178 			printf(": SCI");
179 		else
180 			printf(": SMI");
181 		sc->sc_poll = 1;
182 	} else {
183 #if 0
184 		/* Install interrupt handler */
185 		if (pci_intr_map(pa, &ih)) {
186 			printf(": can't map interrupt\n");
187 			goto fail;
188 		}
189 		intrstr = pci_intr_string(pa->pa_pc, ih);
190 		sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_BIO,
191 		    viapm_intr, sc, sc->sc_dev.dv_xname);
192 		if (sc->sc_ih == NULL) {
193 			printf(": can't establish interrupt");
194 			if (intrstr != NULL)
195 				printf(" at %s", intrstr);
196 			printf("\n");
197 			goto fail;
198 		}
199 		printf(": %s", intrstr);
200 #endif
201 		sc->sc_poll = 1;
202 	}
203 
204 	printf("\n");
205 
206 	/* Attach I2C bus */
207 	rw_init(&sc->sc_i2c_lock, "iiclk");
208 	sc->sc_i2c_tag.ic_cookie = sc;
209 	sc->sc_i2c_tag.ic_acquire_bus = viapm_i2c_acquire_bus;
210 	sc->sc_i2c_tag.ic_release_bus = viapm_i2c_release_bus;
211 	sc->sc_i2c_tag.ic_exec = viapm_i2c_exec;
212 
213 	bzero(&iba, sizeof iba);
214 	iba.iba_name = "iic";
215 	iba.iba_tag = &sc->sc_i2c_tag;
216 	config_found(self, &iba, iicbus_print);
217 
218 	return;
219 
220 fail:
221 	bus_space_unmap(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_SIZE);
222 }
223 
224 int
225 viapm_i2c_acquire_bus(void *cookie, int flags)
226 {
227 	struct viapm_softc *sc = cookie;
228 
229 	if (cold || sc->sc_poll || (flags & I2C_F_POLL))
230 		return (0);
231 
232 	return (rw_enter(&sc->sc_i2c_lock, RW_WRITE | RW_INTR));
233 }
234 
235 void
236 viapm_i2c_release_bus(void *cookie, int flags)
237 {
238 	struct viapm_softc *sc = cookie;
239 
240 	if (cold || sc->sc_poll || (flags & I2C_F_POLL))
241 		return;
242 
243 	rw_exit(&sc->sc_i2c_lock);
244 }
245 
246 int
247 viapm_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
248     const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
249 {
250 	struct viapm_softc *sc = cookie;
251 	u_int8_t *b;
252 	u_int8_t ctl, st;
253 	int retries;
254 
255 	DPRINTF(("%s: exec op %d, addr 0x%x, cmdlen %d, len %d, "
256 	    "flags 0x%x, status 0x%b\n", sc->sc_dev.dv_xname, op, addr,
257 	    cmdlen, len, flags, bus_space_read_1(sc->sc_iot, sc->sc_ioh,
258 	    VIAPM_SMB_HS), VIAPM_SMB_HS_BITS));
259 
260 	/* Check if there's a transfer already running */
261 	st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HS);
262 	DPRINTF(("%s: exec: st 0x%b\n", sc->sc_dev.dv_xname, st,
263 	    VIAPM_SMB_HS_BITS));
264 	if (st & VIAPM_SMB_HS_BUSY)
265 		return (1);
266 
267 	if (cold || sc->sc_poll)
268 		flags |= I2C_F_POLL;
269 
270 	if (!I2C_OP_STOP_P(op) || cmdlen > 1 || len > 2)
271 		return (1);
272 
273 	/* Setup transfer */
274 	sc->sc_i2c_xfer.op = op;
275 	sc->sc_i2c_xfer.buf = buf;
276 	sc->sc_i2c_xfer.len = len;
277 	sc->sc_i2c_xfer.flags = flags;
278 	sc->sc_i2c_xfer.error = 0;
279 
280 	/* Set slave address and transfer direction */
281 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_TXSLVA,
282 	    VIAPM_SMB_TXSLVA_ADDR(addr) |
283 	    (I2C_OP_READ_P(op) ? VIAPM_SMB_TXSLVA_READ : 0));
284 
285 	b = (void *)cmdbuf;
286 	if (cmdlen > 0)
287 		/* Set command byte */
288 		bus_space_write_1(sc->sc_iot, sc->sc_ioh,
289 		    VIAPM_SMB_HCMD, b[0]);
290 
291 	if (I2C_OP_WRITE_P(op)) {
292 		/* Write data */
293 		b = buf;
294 		if (len > 0)
295 			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
296 			    VIAPM_SMB_HD0, b[0]);
297 		if (len > 1)
298 			bus_space_write_1(sc->sc_iot, sc->sc_ioh,
299 			    VIAPM_SMB_HD1, b[1]);
300 	}
301 
302 	/* Set SMBus command */
303 	if (len == 0)
304 		ctl = VIAPM_SMB_HC_CMD_BYTE;
305 	else if (len == 1)
306 		ctl = VIAPM_SMB_HC_CMD_BDATA;
307 	else if (len == 2)
308 		ctl = VIAPM_SMB_HC_CMD_WDATA;
309 
310 	if ((flags & I2C_F_POLL) == 0)
311 		ctl |= VIAPM_SMB_HC_INTREN;
312 
313 	/* Start transaction */
314 	ctl |= VIAPM_SMB_HC_START;
315 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HC, ctl);
316 
317 	if (flags & I2C_F_POLL) {
318 		/* Poll for completion */
319 		DELAY(VIAPM_DELAY);
320 		for (retries = 1000; retries > 0; retries--) {
321 			st = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
322 			    VIAPM_SMB_HS);
323 			if ((st & VIAPM_SMB_HS_BUSY) == 0)
324 				break;
325 			DELAY(VIAPM_DELAY);
326 		}
327 		if (st & VIAPM_SMB_HS_BUSY)
328 			goto timeout;
329 		viapm_intr(sc);
330 	} else {
331 		/* Wait for interrupt */
332 		if (tsleep(sc, PRIBIO, "iicexec", VIAPM_TIMEOUT * hz))
333 			goto timeout;
334 	}
335 
336 	if (sc->sc_i2c_xfer.error)
337 		return (1);
338 
339 	return (0);
340 
341 timeout:
342 	/*
343 	 * Transfer timeout. Kill the transaction and clear status bits.
344 	 */
345 	printf("%s: timeout, status 0x%b\n", sc->sc_dev.dv_xname, st,
346 	    VIAPM_SMB_HS_BITS);
347 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HC,
348 	    VIAPM_SMB_HC_KILL);
349 	DELAY(VIAPM_DELAY);
350 	st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HS);
351 	if ((st & VIAPM_SMB_HS_FAILED) == 0)
352 		printf("%s: transaction abort failed, status 0x%b\n",
353 		    sc->sc_dev.dv_xname, st, VIAPM_SMB_HS_BITS);
354 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HS, st);
355 	return (1);
356 }
357 
358 int
359 viapm_intr(void *arg)
360 {
361 	struct viapm_softc *sc = arg;
362 	u_int8_t st;
363 	u_int8_t *b;
364 	size_t len;
365 
366 	/* Read status */
367 	st = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HS);
368 	if ((st & VIAPM_SMB_HS_BUSY) != 0 || (st & (VIAPM_SMB_HS_INTR |
369 	    VIAPM_SMB_HS_DEVERR | VIAPM_SMB_HS_BUSERR |
370 	    VIAPM_SMB_HS_FAILED)) == 0)
371 		/* Interrupt was not for us */
372 		return (0);
373 
374 	DPRINTF(("%s: intr st 0x%b\n", sc->sc_dev.dv_xname, st,
375 	    VIAPM_SMB_HS_BITS));
376 
377 	/* Clear status bits */
378 	bus_space_write_1(sc->sc_iot, sc->sc_ioh, VIAPM_SMB_HS, st);
379 
380 	/* Check for errors */
381 	if (st & (VIAPM_SMB_HS_DEVERR | VIAPM_SMB_HS_BUSERR |
382 	    VIAPM_SMB_HS_FAILED)) {
383 		sc->sc_i2c_xfer.error = 1;
384 		goto done;
385 	}
386 
387 	if (st & VIAPM_SMB_HS_INTR) {
388 		if (I2C_OP_WRITE_P(sc->sc_i2c_xfer.op))
389 			goto done;
390 
391 		/* Read data */
392 		b = sc->sc_i2c_xfer.buf;
393 		len = sc->sc_i2c_xfer.len;
394 		if (len > 0)
395 			b[0] = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
396 			    VIAPM_SMB_HD0);
397 		if (len > 1)
398 			b[1] = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
399 			    VIAPM_SMB_HD1);
400 	}
401 
402 done:
403 	if ((sc->sc_i2c_xfer.flags & I2C_F_POLL) == 0)
404 		wakeup(sc);
405 	return (1);
406 }
407