pcf8584.c revision 1.6
1/*	$NetBSD: pcf8584.c,v 1.6 2010/02/28 11:47:28 martin Exp $	*/
2/*	$OpenBSD: pcf8584.c,v 1.9 2007/10/20 18:46:21 kettenis Exp $ */
3
4/*
5 * Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <sys/param.h>
21#include <sys/systm.h>
22#include <sys/device.h>
23#include <sys/malloc.h>
24#include <sys/kernel.h>
25#include <sys/rwlock.h>
26#include <sys/proc.h>
27
28#include <machine/bus.h>
29
30#include <dev/i2c/i2cvar.h>
31
32#include <dev/ic/pcf8584var.h>
33
34#define PCF_S0			0x00
35#define PCF_S1			0x01
36#define PCF_S2			0x02
37#define PCF_S3			0x03
38
39#define PCF_CTRL_ACK		(1<<0)
40#define PCF_CTRL_STO		(1<<1)
41#define PCF_CTRL_STA		(1<<2)
42#define PCF_CTRL_ENI		(1<<3)
43#define PCF_CTRL_ES2		(1<<4)
44#define PCF_CTRL_ES1		(1<<5)
45#define PCF_CTRL_ESO		(1<<6)
46#define PCF_CTRL_PIN		(1<<7)
47
48#define PCF_CTRL_START		(PCF_CTRL_PIN | PCF_CTRL_ESO | \
49    PCF_CTRL_STA | PCF_CTRL_ACK)
50#define PCF_CTRL_STOP		(PCF_CTRL_PIN | PCF_CTRL_ESO | \
51    PCF_CTRL_STO | PCF_CTRL_ACK)
52#define PCF_CTRL_REPSTART	(PCF_CTRL_ESO | PCF_CTRL_STA | PCF_CTRL_ACK)
53#define PCF_CTRL_IDLE		(PCF_CTRL_PIN | PCF_CTRL_ESO | PCF_CTRL_ACK)
54
55#define PCF_STAT_nBB		(1<<0)
56#define PCF_STAT_LAB		(1<<1)
57#define PCF_STAT_AAS		(1<<2)
58#define PCF_STAT_AD0		(1<<3)
59#define PCF_STAT_LRB		(1<<3)
60#define PCF_STAT_BER		(1<<4)
61#define PCF_STAT_STS		(1<<5)
62#define PCF_STAT_PIN		(1<<7)
63
64void		pcfiic_init(struct pcfiic_softc *);
65int		pcfiic_i2c_acquire_bus(void *, int);
66void		pcfiic_i2c_release_bus(void *, int);
67int		pcfiic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
68		    size_t, void *, size_t, int);
69
70int		pcfiic_xmit(struct pcfiic_softc *, u_int8_t, const u_int8_t *,
71		    size_t);
72int		pcfiic_recv(struct pcfiic_softc *, u_int8_t, u_int8_t *,
73		    size_t);
74
75u_int8_t	pcfiic_read(struct pcfiic_softc *, bus_size_t);
76void		pcfiic_write(struct pcfiic_softc *, bus_size_t, u_int8_t);
77void		pcfiic_choose_bus(struct pcfiic_softc *, u_int8_t);
78int		pcfiic_wait_nBB(struct pcfiic_softc *);
79int		pcfiic_wait_pin(struct pcfiic_softc *, volatile u_int8_t *);
80
81void
82pcfiic_init(struct pcfiic_softc *sc)
83{
84	/* init S1 */
85	pcfiic_write(sc, PCF_S1, PCF_CTRL_PIN);
86	/* own address */
87	pcfiic_write(sc, PCF_S0, sc->sc_addr);
88
89	/* select clock reg */
90	pcfiic_write(sc, PCF_S1, PCF_CTRL_PIN|PCF_CTRL_ES1);
91	pcfiic_write(sc, PCF_S0, sc->sc_clock);
92
93	pcfiic_write(sc, PCF_S1, PCF_CTRL_IDLE);
94
95	delay(200000);	/* Multi-Master mode, wait for longest i2c message */
96}
97
98void
99pcfiic_attach(struct pcfiic_softc *sc, i2c_addr_t addr, u_int8_t clock,
100    int swapregs)
101{
102	struct i2cbus_attach_args		iba;
103
104	if (swapregs) {
105		sc->sc_regmap[PCF_S1] = PCF_S0;
106		sc->sc_regmap[PCF_S0] = PCF_S1;
107	} else {
108		sc->sc_regmap[PCF_S0] = PCF_S0;
109		sc->sc_regmap[PCF_S1] = PCF_S1;
110	}
111	sc->sc_clock = clock;
112	sc->sc_addr = addr;
113
114	pcfiic_init(sc);
115
116	printf("\n");
117
118	if (sc->sc_master)
119		pcfiic_choose_bus(sc, 0);
120
121	rw_init(&sc->sc_lock);
122	sc->sc_i2c.ic_cookie = sc;
123	sc->sc_i2c.ic_acquire_bus = pcfiic_i2c_acquire_bus;
124	sc->sc_i2c.ic_release_bus = pcfiic_i2c_release_bus;
125	sc->sc_i2c.ic_exec = pcfiic_i2c_exec;
126
127	bzero(&iba, sizeof(iba));
128	iba.iba_tag = &sc->sc_i2c;
129	config_found(sc->sc_dev, &iba, iicbus_print);
130}
131
132int
133pcfiic_intr(void *arg)
134{
135	return (0);
136}
137
138int
139pcfiic_i2c_acquire_bus(void *arg, int flags)
140{
141	struct pcfiic_softc	*sc = arg;
142
143	if (cold || sc->sc_poll || (flags & I2C_F_POLL))
144		return (0);
145
146	rw_enter(&sc->sc_lock, RW_WRITER);
147	return 0;
148}
149
150void
151pcfiic_i2c_release_bus(void *arg, int flags)
152{
153	struct pcfiic_softc	*sc = arg;
154
155	if (cold || sc->sc_poll || (flags & I2C_F_POLL))
156		return;
157
158	rw_exit(&sc->sc_lock);
159}
160
161int
162pcfiic_i2c_exec(void *arg, i2c_op_t op, i2c_addr_t addr,
163    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
164{
165	struct pcfiic_softc	*sc = arg;
166	int			ret = 0;
167
168#if 0
169        printf("%s: exec op: %d addr: 0x%x cmdlen: %d len: %d flags 0x%x\n",
170            sc->sc_dev.dv_xname, op, addr, cmdlen, len, flags);
171#endif
172
173	if (cold || sc->sc_poll)
174		flags |= I2C_F_POLL;
175
176	if (sc->sc_master)
177		pcfiic_choose_bus(sc, addr >> 7);
178
179	if (cmdlen > 0)
180		if (pcfiic_xmit(sc, addr & 0x7f, cmdbuf, cmdlen) != 0)
181			return (1);
182
183	if (len > 0) {
184		if (I2C_OP_WRITE_P(op))
185			ret = pcfiic_xmit(sc, addr & 0x7f, buf, len);
186		else
187			ret = pcfiic_recv(sc, addr & 0x7f, buf, len);
188	}
189	return (ret);
190}
191
192int
193pcfiic_xmit(struct pcfiic_softc *sc, u_int8_t addr, const u_int8_t *buf,
194    size_t len)
195{
196	int			i, err = 0;
197	volatile u_int8_t	r;
198
199	if (pcfiic_wait_nBB(sc) != 0)
200		return (1);
201
202	pcfiic_write(sc, PCF_S0, addr << 1);
203	pcfiic_write(sc, PCF_S1, PCF_CTRL_START);
204
205	for (i = 0; i <= len; i++) {
206		if (pcfiic_wait_pin(sc, &r) != 0) {
207			pcfiic_write(sc, PCF_S1, PCF_CTRL_STOP);
208			return (1);
209		}
210
211		if (r & PCF_STAT_LRB) {
212			err = 1;
213			break;
214		}
215
216		if (i < len)
217			pcfiic_write(sc, PCF_S0, buf[i]);
218	}
219	pcfiic_write(sc, PCF_S1, PCF_CTRL_STOP);
220	return (err);
221}
222
223int
224pcfiic_recv(struct pcfiic_softc *sc, u_int8_t addr, u_int8_t *buf, size_t len)
225{
226	int			i = 0, err = 0;
227	volatile u_int8_t	r;
228
229	if (pcfiic_wait_nBB(sc) != 0)
230		return (1);
231
232	pcfiic_write(sc, PCF_S0, (addr << 1) | 0x01);
233	pcfiic_write(sc, PCF_S1, PCF_CTRL_START);
234
235	for (i = 0; i <= len; i++) {
236		if (pcfiic_wait_pin(sc, &r) != 0) {
237			pcfiic_write(sc, PCF_S1, PCF_CTRL_STOP);
238			return (1);
239		}
240
241		if ((i != len) && (r & PCF_STAT_LRB)) {
242			pcfiic_write(sc, PCF_S1, PCF_CTRL_STOP);
243			return (1);
244		}
245
246		if (i == len - 1) {
247			pcfiic_write(sc, PCF_S1, PCF_CTRL_ESO);
248		} else if (i == len) {
249			pcfiic_write(sc, PCF_S1, PCF_CTRL_STOP);
250		}
251
252		r = pcfiic_read(sc, PCF_S0);
253		if (i > 0)
254			buf[i - 1] = r;
255	}
256	return (err);
257}
258
259u_int8_t
260pcfiic_read(struct pcfiic_softc *sc, bus_size_t r)
261{
262	bus_space_barrier(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r], 1,
263	    BUS_SPACE_BARRIER_READ);
264	return (bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r]));
265}
266
267void
268pcfiic_write(struct pcfiic_softc *sc, bus_size_t r, u_int8_t v)
269{
270	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r], v);
271	bus_space_barrier(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r], 1,
272	    BUS_SPACE_BARRIER_WRITE);
273}
274
275void
276pcfiic_choose_bus(struct pcfiic_softc *sc, u_int8_t bus)
277{
278	bus_space_write_1(sc->sc_iot, sc->sc_ioh2, 0, bus);
279	bus_space_barrier(sc->sc_iot, sc->sc_ioh2, 0, 1,
280	    BUS_SPACE_BARRIER_WRITE);
281}
282
283int
284pcfiic_wait_nBB(struct pcfiic_softc *sc)
285{
286	int		i;
287
288	for (i = 0; i < 1000; i++) {
289		if (pcfiic_read(sc, PCF_S1) & PCF_STAT_nBB)
290			return (0);
291		delay(1000);
292	}
293	return (1);
294}
295
296int
297pcfiic_wait_pin(struct pcfiic_softc *sc, volatile u_int8_t *r)
298{
299	int		i;
300
301	for (i = 0; i < 1000; i++) {
302		*r = pcfiic_read(sc, PCF_S1);
303		if ((*r & PCF_STAT_PIN) == 0)
304			return (0);
305		delay(1000);
306	}
307	return (1);
308}
309