xref: /netbsd-src/sys/arch/arm/at91/at91twi.c (revision c7fb772b85b2b5d4cfb282f868f454b4701534fd)
1*c7fb772bSthorpej /*	$Id: at91twi.c,v 1.10 2021/08/07 16:18:43 thorpej Exp $	*/
2*c7fb772bSthorpej /*	$NetBSD: at91twi.c,v 1.10 2021/08/07 16:18:43 thorpej Exp $	*/
3c62a0ac4Smatt 
4c62a0ac4Smatt /*-
5c62a0ac4Smatt  * Copyright (c) 2007 Embedtronics Oy. All rights reserved.
6c62a0ac4Smatt  *
7c62a0ac4Smatt  * Based on arch/macppc/dev/ki2c.c,
8c62a0ac4Smatt  * Copyright (c) 2001 Tsubai Masanari.  All rights reserved.
9c62a0ac4Smatt  *
10c62a0ac4Smatt  * Redistribution and use in source and binary forms, with or without
11c62a0ac4Smatt  * modification, are permitted provided that the following conditions
12c62a0ac4Smatt  * are met:
13c62a0ac4Smatt  * 1. Redistributions of source code must retain the above copyright
14c62a0ac4Smatt  *    notice, this list of conditions and the following disclaimer.
15c62a0ac4Smatt  * 2. Redistributions in binary form must reproduce the above copyright
16c62a0ac4Smatt  *    notice, this list of conditions and the following disclaimer in the
17c62a0ac4Smatt  *    documentation and/or other materials provided with the distribution.
18c62a0ac4Smatt  * 3. The name of the author may not be used to endorse or promote products
19c62a0ac4Smatt  *    derived from this software without specific prior written permission.
20c62a0ac4Smatt  *
21c62a0ac4Smatt  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22c62a0ac4Smatt  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23c62a0ac4Smatt  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24c62a0ac4Smatt  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25c62a0ac4Smatt  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26c62a0ac4Smatt  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27c62a0ac4Smatt  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28c62a0ac4Smatt  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29c62a0ac4Smatt  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30c62a0ac4Smatt  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31c62a0ac4Smatt  */
32c62a0ac4Smatt 
33c62a0ac4Smatt #include <sys/cdefs.h>
34*c7fb772bSthorpej __KERNEL_RCSID(0, "$NetBSD: at91twi.c,v 1.10 2021/08/07 16:18:43 thorpej Exp $");
35c62a0ac4Smatt 
36c62a0ac4Smatt #include <sys/param.h>
37c62a0ac4Smatt #include <sys/device.h>
38c62a0ac4Smatt #include <sys/systm.h>
39c62a0ac4Smatt 
40cf10107dSdyoung #include <sys/bus.h>
41c62a0ac4Smatt #include <sys/lock.h>
42c62a0ac4Smatt 
43c62a0ac4Smatt #include <arm/at91/at91var.h>
44c62a0ac4Smatt #include <arm/at91/at91reg.h>
45c62a0ac4Smatt 
46c62a0ac4Smatt #include <dev/i2c/i2cvar.h>
47c62a0ac4Smatt #include <arm/at91/at91twivar.h>
48c62a0ac4Smatt #include <arm/at91/at91twireg.h>
49c62a0ac4Smatt 
50c62a0ac4Smatt int at91twi_match(device_t, cfdata_t, void *);
51c62a0ac4Smatt void at91twi_attach(device_t, device_t, void *);
52c62a0ac4Smatt inline u_int at91twi_readreg(struct at91twi_softc *, int);
53c62a0ac4Smatt inline void at91twi_writereg(struct at91twi_softc *, int, u_int);
54c62a0ac4Smatt int at91twi_intr(void *);
55c62a0ac4Smatt int at91twi_poll(struct at91twi_softc *, int, int);
56c62a0ac4Smatt int at91twi_start(struct at91twi_softc *, int, void *, int, int);
57c62a0ac4Smatt int at91twi_read(struct at91twi_softc *, int, void *, int, int);
58c62a0ac4Smatt int at91twi_write(struct at91twi_softc *, int, void *, int, int);
59c62a0ac4Smatt 
60c62a0ac4Smatt /* I2C glue */
61c62a0ac4Smatt static int at91twi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
62c62a0ac4Smatt 		    void *, size_t, int);
63c62a0ac4Smatt 
64c62a0ac4Smatt 
65c62a0ac4Smatt CFATTACH_DECL_NEW(at91twi, sizeof(struct at91twi_softc),
66c62a0ac4Smatt 	at91twi_match, at91twi_attach, NULL, NULL);
67c62a0ac4Smatt 
68c62a0ac4Smatt int
at91twi_match(device_t parent,cfdata_t match,void * aux)69c62a0ac4Smatt at91twi_match(device_t parent, cfdata_t match, void *aux)
70c62a0ac4Smatt {
71c62a0ac4Smatt 	if (strcmp(match->cf_name, "at91twi") == 0)
72c62a0ac4Smatt 		return 2;
73c62a0ac4Smatt 	return 0;
74c62a0ac4Smatt }
75c62a0ac4Smatt 
76c62a0ac4Smatt void
at91twi_attach(device_t parent,device_t self,void * aux)77c62a0ac4Smatt at91twi_attach(device_t parent, device_t self, void *aux)
78c62a0ac4Smatt {
79c62a0ac4Smatt 	struct at91twi_softc *sc = device_private(self);
80c62a0ac4Smatt 	struct at91bus_attach_args *sa = aux;
81c62a0ac4Smatt 	struct i2cbus_attach_args iba;
82c62a0ac4Smatt 	unsigned ckdiv, cxdiv;
83c62a0ac4Smatt 
84c62a0ac4Smatt 	// gather attach data:
85c62a0ac4Smatt 	sc->sc_dev = self;
86c62a0ac4Smatt 	sc->sc_iot = sa->sa_iot;
87c62a0ac4Smatt 	sc->sc_pid = sa->sa_pid;
88c62a0ac4Smatt 
89c62a0ac4Smatt 	if (bus_space_map(sa->sa_iot, sa->sa_addr, sa->sa_size, 0, &sc->sc_ioh))
90cbab9cadSchs 		panic("%s: Cannot map registers", device_xname(self));
91c62a0ac4Smatt 
92c62a0ac4Smatt 	printf(": I2C controller\n");
93c62a0ac4Smatt 
94c62a0ac4Smatt 	/* initialize I2C controller */
95c62a0ac4Smatt 	at91_peripheral_clock(sc->sc_pid, 1);
96c62a0ac4Smatt 
97c62a0ac4Smatt 	at91twi_writereg(sc, TWI_CR, TWI_CR_SWRST);
98c62a0ac4Smatt 	delay(1000);
99c62a0ac4Smatt #if 1
100c62a0ac4Smatt 	// target to 100 kHz
101c62a0ac4Smatt 	for (ckdiv = 0; ckdiv < 8; ckdiv++) {
102c62a0ac4Smatt 		if ((cxdiv = (AT91_MSTCLK / (1U << ckdiv)) / (2 * 50000U)) < 256) {
103c62a0ac4Smatt 			goto found_ckdiv;
104c62a0ac4Smatt 		}
105c62a0ac4Smatt 	}
106c62a0ac4Smatt 	panic("%s: Cannot calculate clock divider!", __FUNCTION__);
107c62a0ac4Smatt 
108c62a0ac4Smatt found_ckdiv:
109c62a0ac4Smatt #else
110c62a0ac4Smatt 	ckdiv = 5; cxdiv = 0xFF;
111c62a0ac4Smatt #endif
112c62a0ac4Smatt 	at91twi_writereg(sc, TWI_CWGR, (ckdiv << 16) | (cxdiv << 8) | cxdiv);
113c62a0ac4Smatt 	at91twi_writereg(sc, TWI_CR, TWI_CR_MSEN);
114c62a0ac4Smatt 
115c62a0ac4Smatt //#ifdef AT91TWI_DEBUG
116cbab9cadSchs 	printf("%s: ckdiv=%d cxdiv=%d CWGR=0x%08X SR=0x%08X\n", device_xname(self), ckdiv, cxdiv, at91twi_readreg(sc, TWI_CWGR), at91twi_readreg(sc, TWI_SR));
117c62a0ac4Smatt //#endif
118c62a0ac4Smatt 
119c62a0ac4Smatt 	/* initialize rest */
120c62a0ac4Smatt 	sc->sc_ih = at91_intr_establish(sc->sc_pid, IPL_SERIAL, INTR_HIGH_LEVEL,
121c62a0ac4Smatt 					at91twi_intr, sc);
122c62a0ac4Smatt 
123c62a0ac4Smatt 	/* fill in the i2c tag */
124601e1783Sthorpej 	iic_tag_init(&sc->sc_i2c);
125c62a0ac4Smatt 	sc->sc_i2c.ic_cookie = sc;
126c62a0ac4Smatt 	sc->sc_i2c.ic_exec = at91twi_i2c_exec;
127c62a0ac4Smatt 
1282f02870fSchs 	memset(&iba, 0, sizeof(iba));
129c62a0ac4Smatt 	iba.iba_tag = &sc->sc_i2c;
130*c7fb772bSthorpej 	config_found(sc->sc_dev, &iba, iicbus_print, CFARGS_NONE);
131c62a0ac4Smatt }
132c62a0ac4Smatt 
133c62a0ac4Smatt u_int
at91twi_readreg(struct at91twi_softc * sc,int reg)134454af1c0Sdsl at91twi_readreg(struct at91twi_softc *sc, int reg)
135c62a0ac4Smatt {
136c62a0ac4Smatt 	return bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg);
137c62a0ac4Smatt }
138c62a0ac4Smatt 
139c62a0ac4Smatt void
at91twi_writereg(struct at91twi_softc * sc,int reg,u_int val)140454af1c0Sdsl at91twi_writereg(struct at91twi_softc *sc, int reg, u_int val)
141c62a0ac4Smatt {
142c62a0ac4Smatt 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg, val);
143c62a0ac4Smatt }
144c62a0ac4Smatt 
145c62a0ac4Smatt int
at91twi_intr(void * arg)146c62a0ac4Smatt at91twi_intr(void *arg)
147c62a0ac4Smatt {
148c62a0ac4Smatt 	struct at91twi_softc *sc = arg;
149c62a0ac4Smatt 	u_int sr, isr, imr;
150c62a0ac4Smatt 
151c62a0ac4Smatt 	sr = at91twi_readreg(sc, TWI_SR);
152c62a0ac4Smatt 	imr = at91twi_readreg(sc, TWI_IMR);
153c62a0ac4Smatt 	isr = sr & imr;
154c62a0ac4Smatt 
155c62a0ac4Smatt 	if (!isr) {
156c62a0ac4Smatt #ifdef AT91TWI_DEBUG
157c62a0ac4Smatt //		printf("%s(%s): interrupts are disabled (sr=%08X imr=%08X)\n", __FUNCTION__, device_xname(sc->sc_dev), sr, imr);
158c62a0ac4Smatt #endif
159c62a0ac4Smatt 		return 0;
160c62a0ac4Smatt 	}
161c62a0ac4Smatt 
162c62a0ac4Smatt 	if (isr & TWI_SR_TXCOMP) {
163c62a0ac4Smatt 		// transmission has completed!
164c62a0ac4Smatt 		if (sr & (TWI_SR_NACK | TWI_SR_UNRE | TWI_SR_OVRE)) {
165c62a0ac4Smatt 			// failed!
166c62a0ac4Smatt #ifdef AT91TWI_DEBUG
167c62a0ac4Smatt 			printf("%s(%s): FAILED (sr=%08X)\n", __FUNCTION__,
168c62a0ac4Smatt 			       device_xname(sc->sc_dev), sr);
169c62a0ac4Smatt #endif
170c62a0ac4Smatt 			sc->sc_flags |= I2C_ERROR;
171c62a0ac4Smatt 		} else {
172c62a0ac4Smatt #ifdef AT91TWI_DEBUG
173c62a0ac4Smatt 			printf("%s(%s): SUCCESS (sr=%08X)\n", __FUNCTION__,
174c62a0ac4Smatt 			       device_xname(sc->sc_dev), sr);
175c62a0ac4Smatt #endif
176c62a0ac4Smatt 		}
177c62a0ac4Smatt 		if (sc->sc_flags & I2C_READING && sr & TWI_SR_RXRDY) {
178c62a0ac4Smatt 			*sc->sc_data++ = at91twi_readreg(sc, TWI_RHR);
179c62a0ac4Smatt 			sc->sc_resid--;
180c62a0ac4Smatt 		}
181c62a0ac4Smatt 		sc->sc_flags &= ~I2C_BUSY;
182c62a0ac4Smatt 		at91twi_writereg(sc, TWI_IDR, -1);
183c62a0ac4Smatt 		goto out;
184c62a0ac4Smatt 	}
185c62a0ac4Smatt 
186c62a0ac4Smatt 	if (isr & TWI_SR_TXRDY) {
187c62a0ac4Smatt 		if (--sc->sc_resid > 0)
188c62a0ac4Smatt 			at91twi_writereg(sc, TWI_THR, *sc->sc_data++);
189c62a0ac4Smatt 	}
190c62a0ac4Smatt 
191c62a0ac4Smatt 	if (isr & TWI_SR_RXRDY) {
192c62a0ac4Smatt 		// data has been received
193c62a0ac4Smatt 		*sc->sc_data++ = at91twi_readreg(sc, TWI_RHR);
194c62a0ac4Smatt 		sc->sc_resid--;
195c62a0ac4Smatt 	}
196c62a0ac4Smatt 
197c62a0ac4Smatt 	if (isr & (TWI_SR_TXRDY | TWI_SR_RXRDY) && sc->sc_resid <= 0) {
198c62a0ac4Smatt 		// all bytes have been transmitted, send stop condition
199c62a0ac4Smatt 		at91twi_writereg(sc, TWI_IDR, TWI_SR_RXRDY | TWI_SR_TXRDY);
200c62a0ac4Smatt 		at91twi_writereg(sc, TWI_CR, TWI_CR_STOP);
201c62a0ac4Smatt 	}
202c62a0ac4Smatt out:
203c62a0ac4Smatt 	return 1;
204c62a0ac4Smatt }
205c62a0ac4Smatt 
206c62a0ac4Smatt int
at91twi_poll(struct at91twi_softc * sc,int timo,int flags)20782357f6dSdsl at91twi_poll(struct at91twi_softc *sc, int timo, int flags)
208c62a0ac4Smatt {
209c62a0ac4Smatt 
210c62a0ac4Smatt 	timo = 1000000U;
211c62a0ac4Smatt 
212c62a0ac4Smatt 	while (sc->sc_flags & I2C_BUSY) {
213c62a0ac4Smatt 		if (timo < 0) {
214c62a0ac4Smatt 			printf("i2c_poll: timeout\n");
215c62a0ac4Smatt 			return -1;
216c62a0ac4Smatt 		}
217c62a0ac4Smatt 		if (flags & I2C_F_POLL) {
218c62a0ac4Smatt 			at91_intr_poll(sc->sc_ih, 1);
219c62a0ac4Smatt 			delay(1);
220c62a0ac4Smatt 			timo--;
221c62a0ac4Smatt 		} else {
222c62a0ac4Smatt 			delay(100); // @@@ sleep!?
223c62a0ac4Smatt 			timo -= 100;
224c62a0ac4Smatt 		}
225c62a0ac4Smatt 	}
226c62a0ac4Smatt 	return 0;
227c62a0ac4Smatt }
228c62a0ac4Smatt 
229c62a0ac4Smatt int
at91twi_start(struct at91twi_softc * sc,int addr,void * data,int len,int flags)230c62a0ac4Smatt at91twi_start(struct at91twi_softc *sc, int addr, void *data, int len,
231c62a0ac4Smatt 	int flags)
232c62a0ac4Smatt {
233c62a0ac4Smatt 	int rd = (sc->sc_flags & I2C_READING);
234c62a0ac4Smatt 	int timo, s;
235c62a0ac4Smatt 
236c62a0ac4Smatt 	KASSERT((addr & 1) == 0);
237c62a0ac4Smatt 
238c62a0ac4Smatt 	sc->sc_data = data;
239c62a0ac4Smatt 	sc->sc_resid = len;
240c62a0ac4Smatt 	sc->sc_flags |= I2C_BUSY;
241c62a0ac4Smatt 
242c62a0ac4Smatt 	timo = 1000 + len * 200;
243c62a0ac4Smatt 
244c62a0ac4Smatt 	s = splserial();
245c62a0ac4Smatt 	// if writing, queue first byte immediately
246c62a0ac4Smatt 	if (!rd)
247c62a0ac4Smatt 		at91twi_writereg(sc, TWI_THR, *sc->sc_data++);
248c62a0ac4Smatt 	// if there's just one byte to transmit, we must set STOP-bit too
249c62a0ac4Smatt 	if (sc->sc_resid == 1) {
250c62a0ac4Smatt 		at91twi_writereg(sc, TWI_IER, TWI_SR_TXCOMP);
251c62a0ac4Smatt 		at91twi_writereg(sc, TWI_CR, TWI_CR_START | TWI_CR_STOP);
252c62a0ac4Smatt 	} else {
253c62a0ac4Smatt 		at91twi_writereg(sc, TWI_IER, TWI_SR_TXCOMP
254c62a0ac4Smatt 				  | (rd ? TWI_SR_RXRDY : TWI_SR_TXRDY));
255c62a0ac4Smatt 		at91twi_writereg(sc, TWI_CR, TWI_CR_START);
256c62a0ac4Smatt 	}
257c62a0ac4Smatt 	splx(s);
258c62a0ac4Smatt 
259c62a0ac4Smatt 	if (at91twi_poll(sc, timo, flags))
260c62a0ac4Smatt 		return -1;
261c62a0ac4Smatt 	if (sc->sc_flags & I2C_ERROR) {
262c62a0ac4Smatt 		printf("I2C_ERROR\n");
263c62a0ac4Smatt 		return -1;
264c62a0ac4Smatt 	}
265c62a0ac4Smatt 	return 0;
266c62a0ac4Smatt }
267c62a0ac4Smatt 
268c62a0ac4Smatt int
at91twi_read(struct at91twi_softc * sc,int addr,void * data,int len,int flags)26982357f6dSdsl at91twi_read(struct at91twi_softc *sc, int addr, void *data, int len, int flags)
270c62a0ac4Smatt {
271c62a0ac4Smatt 	sc->sc_flags = I2C_READING;
272c62a0ac4Smatt 	#ifdef AT91TWI_DEBUG
273c62a0ac4Smatt 		printf("at91twi_read: %02x %d\n", addr, len);
274c62a0ac4Smatt 	#endif
275c62a0ac4Smatt 	return at91twi_start(sc, addr, data, len, flags);
276c62a0ac4Smatt }
277c62a0ac4Smatt 
278c62a0ac4Smatt int
at91twi_write(struct at91twi_softc * sc,int addr,void * data,int len,int flags)27982357f6dSdsl at91twi_write(struct at91twi_softc *sc, int addr, void *data, int len, int flags)
280c62a0ac4Smatt {
281c62a0ac4Smatt 	sc->sc_flags = 0;
282c62a0ac4Smatt 	#ifdef AT91TWI_DEBUG
283c62a0ac4Smatt 		printf("at91twi_write: %02x %d\n", addr, len);
284c62a0ac4Smatt 	#endif
285c62a0ac4Smatt 	return at91twi_start(sc, addr, data, len, flags);
286c62a0ac4Smatt }
287c62a0ac4Smatt 
288c62a0ac4Smatt int
at91twi_i2c_exec(void * cookie,i2c_op_t op,i2c_addr_t addr,const void * vcmd,size_t cmdlen,void * vbuf,size_t buflen,int flags)289c62a0ac4Smatt at91twi_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *vcmd,
290c62a0ac4Smatt     size_t cmdlen, void *vbuf, size_t buflen, int flags)
291c62a0ac4Smatt {
292c62a0ac4Smatt 	struct at91twi_softc *sc = cookie;
293c62a0ac4Smatt 
294c62a0ac4Smatt 	if (I2C_OP_READ_P(op))
295c62a0ac4Smatt 	{
296c62a0ac4Smatt 		u_int iadr = 0;
297c62a0ac4Smatt 		if (vcmd) {
298c62a0ac4Smatt 			const uint8_t *cmd = (const uint8_t *)vcmd;
299c62a0ac4Smatt 			if (cmdlen > 3) {
300c62a0ac4Smatt 				// we're in trouble..
301c62a0ac4Smatt 				return -1;
302c62a0ac4Smatt 			}
303c62a0ac4Smatt 			iadr = cmd[0];
304c62a0ac4Smatt 			if (cmdlen > 1) {
305c62a0ac4Smatt 				iadr <<= 8;
306c62a0ac4Smatt 				iadr |= cmd[1];
307c62a0ac4Smatt 			}
308c62a0ac4Smatt 			if (cmdlen > 2) {
309c62a0ac4Smatt 				iadr <<= 8;
310c62a0ac4Smatt 				iadr |= cmd[2];
311c62a0ac4Smatt 			}
312c62a0ac4Smatt 		}
313c62a0ac4Smatt 		at91twi_writereg(sc, TWI_MMR, (addr << 16) | TWI_MMR_MREAD | (cmdlen << 8));
314c62a0ac4Smatt 		if (cmdlen > 0) {
315c62a0ac4Smatt 	#ifdef AT91TWI_DEBUG
316c62a0ac4Smatt 			printf("at91twi_read: %02x iadr=%08X mmr=%08X\n",
317c62a0ac4Smatt 			       addr, iadr, at91twi_readreg(sc, TWI_MMR));
318c62a0ac4Smatt 	#endif
319c62a0ac4Smatt 			at91twi_writereg(sc, TWI_IADR, iadr);
320c62a0ac4Smatt 		}
321c62a0ac4Smatt 		if (at91twi_read(sc, addr, vbuf, buflen, flags) != 0)
322c62a0ac4Smatt 			return -1;
323c62a0ac4Smatt 	} else if (vcmd) {
324c62a0ac4Smatt 		at91twi_writereg(sc, TWI_MMR, addr << 16);
325c62a0ac4Smatt 		if (at91twi_write(sc, addr, __UNCONST(vcmd), cmdlen, flags) !=0)
326c62a0ac4Smatt 			return -1;
327c62a0ac4Smatt 	}
328c62a0ac4Smatt 	return 0;
329c62a0ac4Smatt }
330