pcf8584.c revision 1.16
1/*	$NetBSD: pcf8584.c,v 1.16 2019/12/22 23:23:32 thorpej 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/proc.h>
26#include <sys/bus.h>
27
28#include <dev/i2c/i2cvar.h>
29
30#include <dev/ic/pcf8584var.h>
31#include <dev/ic/pcf8584reg.h>
32
33/* Internal registers */
34#define PCF8584_S0		0x00
35#define PCF8584_S1		0x01
36#define PCF8584_S2		0x02
37#define PCF8584_S3		0x03
38
39void		pcfiic_init(struct pcfiic_softc *);
40int		pcfiic_i2c_acquire_bus(void *, int);
41void		pcfiic_i2c_release_bus(void *, int);
42int		pcfiic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
43		    size_t, void *, size_t, int);
44
45int		pcfiic_xmit(struct pcfiic_softc *, u_int8_t, const u_int8_t *,
46		    size_t, const u_int8_t *, size_t);
47int		pcfiic_recv(struct pcfiic_softc *, u_int8_t, u_int8_t *,
48		    size_t);
49
50u_int8_t	pcfiic_read(struct pcfiic_softc *, bus_size_t);
51void		pcfiic_write(struct pcfiic_softc *, bus_size_t, u_int8_t);
52void		pcfiic_choose_bus(struct pcfiic_softc *, u_int8_t);
53int		pcfiic_wait_BBN(struct pcfiic_softc *);
54int		pcfiic_wait_pin(struct pcfiic_softc *, volatile u_int8_t *);
55
56void
57pcfiic_init(struct pcfiic_softc *sc)
58{
59	/* init S1 */
60	pcfiic_write(sc, PCF8584_S1, PCF8584_CTRL_PIN);
61	/* own address */
62	pcfiic_write(sc, PCF8584_S0, sc->sc_addr);
63
64	/* select clock reg */
65	pcfiic_write(sc, PCF8584_S1, PCF8584_CTRL_PIN | PCF8584_CTRL_ES1);
66	pcfiic_write(sc, PCF8584_S0, sc->sc_clock);
67
68	pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_IDLE);
69
70	delay(200000);	/* Multi-Master mode, wait for longest i2c message */
71}
72
73void
74pcfiic_attach(struct pcfiic_softc *sc, i2c_addr_t addr, u_int8_t clock,
75    int swapregs)
76{
77	struct i2cbus_attach_args		iba;
78
79	if (swapregs) {
80		sc->sc_regmap[PCF8584_S1] = PCF8584_S0;
81		sc->sc_regmap[PCF8584_S0] = PCF8584_S1;
82	} else {
83		sc->sc_regmap[PCF8584_S0] = PCF8584_S0;
84		sc->sc_regmap[PCF8584_S1] = PCF8584_S1;
85	}
86	sc->sc_clock = clock;
87	sc->sc_addr = addr;
88
89	pcfiic_init(sc);
90
91	printf("\n");
92
93	if (sc->sc_master)
94		pcfiic_choose_bus(sc, 0);
95
96	iic_tag_init(&sc->sc_i2c);
97	sc->sc_i2c.ic_cookie = sc;
98	sc->sc_i2c.ic_exec = pcfiic_i2c_exec;
99
100	bzero(&iba, sizeof(iba));
101	iba.iba_tag = &sc->sc_i2c;
102	config_found(sc->sc_dev, &iba, iicbus_print);
103}
104
105int
106pcfiic_intr(void *arg)
107{
108	return (0);
109}
110
111int
112pcfiic_i2c_exec(void *arg, i2c_op_t op, i2c_addr_t addr,
113    const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
114{
115	struct pcfiic_softc	*sc = arg;
116	int			ret = 0;
117
118#if 0
119        printf("%s: exec op: %d addr: 0x%x cmdlen: %d len: %d flags 0x%x\n",
120            device_xname(sc->sc_dev), op, addr, (int)cmdlen, (int)len, flags);
121#endif
122
123	if (cold || sc->sc_poll)
124		flags |= I2C_F_POLL;
125
126	if (sc->sc_master)
127		pcfiic_choose_bus(sc, addr >> 7);
128
129	/*
130	 * If we are writing, write address, cmdbuf, buf.
131	 * If we are reading, write address, cmdbuf, then read address, buf.
132	 */
133	if (I2C_OP_WRITE_P(op)) {
134		ret = pcfiic_xmit(sc, addr & 0x7f, cmdbuf, cmdlen, buf, len);
135	} else {
136		if (pcfiic_xmit(sc, addr & 0x7f, cmdbuf, cmdlen, NULL, 0) != 0)
137			return (1);
138		ret = pcfiic_recv(sc, addr & 0x7f, buf, len);
139	}
140	return (ret);
141}
142
143int
144pcfiic_xmit(struct pcfiic_softc *sc, u_int8_t addr, const u_int8_t *cmdbuf,
145    size_t cmdlen, const u_int8_t *buf, size_t len)
146{
147	int			i, err = 0;
148	volatile u_int8_t	r;
149
150	if (pcfiic_wait_BBN(sc) != 0)
151		return (1);
152
153	pcfiic_write(sc, PCF8584_S0, addr << 1);
154	pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_START);
155
156	for (i = 0; i <= cmdlen + len; i++) {
157		if (pcfiic_wait_pin(sc, &r) != 0) {
158			pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_STOP);
159			return (1);
160		}
161
162		if (r & PCF8584_STATUS_LRB) {
163			err = 1;
164			break;
165		}
166
167		if (i < cmdlen)
168			pcfiic_write(sc, PCF8584_S0, cmdbuf[i]);
169		else if (i < cmdlen + len)
170			pcfiic_write(sc, PCF8584_S0, buf[i - cmdlen]);
171	}
172	pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_STOP);
173	return (err);
174}
175
176int
177pcfiic_recv(struct pcfiic_softc *sc, u_int8_t addr, u_int8_t *buf, size_t len)
178{
179	int			i = 0, err = 0;
180	volatile u_int8_t	r;
181
182	if (pcfiic_wait_BBN(sc) != 0)
183		return (1);
184
185	pcfiic_write(sc, PCF8584_S0, (addr << 1) | 0x01);
186	pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_START);
187
188	for (i = 0; i <= len; i++) {
189		if (pcfiic_wait_pin(sc, &r) != 0) {
190			pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_STOP);
191			return (1);
192		}
193
194		if ((i != len) && (r & PCF8584_STATUS_LRB)) {
195			pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_STOP);
196			return (1);
197		}
198
199		if (i == len - 1) {
200			pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_NAK);
201		} else if (i == len) {
202			pcfiic_write(sc, PCF8584_S1, PCF8584_CMD_STOP);
203		}
204
205		r = pcfiic_read(sc, PCF8584_S0);
206		if (i > 0)
207			buf[i - 1] = r;
208	}
209	return (err);
210}
211
212u_int8_t
213pcfiic_read(struct pcfiic_softc *sc, bus_size_t r)
214{
215	bus_space_barrier(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r], 1,
216	    BUS_SPACE_BARRIER_READ);
217	return (bus_space_read_1(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r]));
218}
219
220void
221pcfiic_write(struct pcfiic_softc *sc, bus_size_t r, u_int8_t v)
222{
223	bus_space_write_1(sc->sc_iot, sc->sc_ioh, sc->sc_regmap[r], v);
224	(void)bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCF8584_S1);
225}
226
227void
228pcfiic_choose_bus(struct pcfiic_softc *sc, u_int8_t bus)
229{
230	bus_space_write_1(sc->sc_iot, sc->sc_ioh2, 0, bus);
231	bus_space_barrier(sc->sc_iot, sc->sc_ioh2, 0, 1,
232	    BUS_SPACE_BARRIER_WRITE);
233}
234
235int
236pcfiic_wait_BBN(struct pcfiic_softc *sc)
237{
238	int		i;
239
240	for (i = 0; i < 1000; i++) {
241		if (pcfiic_read(sc, PCF8584_S1) & PCF8584_STATUS_BBN)
242			return (0);
243		delay(1000);
244	}
245	return (1);
246}
247
248int
249pcfiic_wait_pin(struct pcfiic_softc *sc, volatile u_int8_t *r)
250{
251	int		i;
252
253	for (i = 0; i < 1000; i++) {
254		*r = pcfiic_read(sc, PCF8584_S1);
255		if ((*r & PCF8584_STATUS_PIN) == 0)
256			return (0);
257		delay(1000);
258	}
259	return (1);
260}
261