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