Home | History | Annotate | Line # | Download | only in ic
      1 /* $NetBSD: cdnsiic.c,v 1.1 2022/11/05 17:31:37 jmcneill Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2022 Jared McNeill <jmcneill (at) invisible.ca>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /*
     30  * Cadence I2C controller
     31  */
     32 
     33 #include <sys/cdefs.h>
     34 
     35 __KERNEL_RCSID(0, "$NetBSD: cdnsiic.c,v 1.1 2022/11/05 17:31:37 jmcneill Exp $");
     36 
     37 #include <sys/param.h>
     38 #include <sys/bus.h>
     39 #include <sys/device.h>
     40 #include <sys/intr.h>
     41 #include <sys/systm.h>
     42 #include <sys/time.h>
     43 #include <sys/kmem.h>
     44 
     45 #include <dev/clk/clk.h>
     46 #include <dev/i2c/i2cvar.h>
     47 
     48 #include <dev/ic/cdnsiicvar.h>
     49 
     50 /* From Zynq-7000 SoC Technical Reference Manual, "Supports 16-byte FIFO" */
     51 #define	FIFO_DEPTH	16
     52 
     53 /* Poll timeout, in microseconds. */
     54 #define	POLL_TIMEOUT	10000
     55 
     56 #define	CR_REG		0x00
     57 #define	 CR_DIV_A	__BITS(15,14)
     58 #define	 CR_DIV_B	__BITS(13,8)
     59 #define	 CR_CLR_FIFO	__BIT(6)
     60 #define	 CR_HOLD	__BIT(4)
     61 #define	 CR_ACKEN	__BIT(3)
     62 #define	 CR_NEA		__BIT(2)
     63 #define	 CR_MS		__BIT(1)
     64 #define	 CR_RD_WR	__BIT(0)
     65 #define	SR_REG		0x04
     66 #define	 SR_TXDV	__BIT(6)
     67 #define	 SR_RXDV	__BIT(5)
     68 #define	ADDR_REG	0x08
     69 #define	DATA_REG	0x0c
     70 #define	ISR_REG		0x10
     71 #define	 ISR_ARB_LOST	__BIT(9)
     72 #define	 ISR_RX_UNF	__BIT(7)
     73 #define	 ISR_TX_OVR	__BIT(6)
     74 #define	 ISR_RX_OVR	__BIT(5)
     75 #define	 ISR_SLV_RDY	__BIT(4)
     76 #define	 ISR_TO		__BIT(3)
     77 #define	 ISR_NACK	__BIT(2)
     78 #define	 ISR_DATA	__BIT(1)
     79 #define	 ISR_COMP	__BIT(0)
     80 #define	 ISR_ERROR_MASK	(ISR_ARB_LOST | ISR_TX_OVR | ISR_RX_OVR | ISR_NACK)
     81 #define	TRANS_SIZE_REG	0x14
     82 #define	SLV_PAUSE_REG	0x18
     83 #define	TIME_OUT_REG	0x1c
     84 #define	IMR_REG		0x20
     85 #define	IER_REG		0x24
     86 #define	IDR_REG		0x28
     87 
     88 #define	RD4(sc, reg)				\
     89 	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
     90 #define	WR4(sc, reg, val)			\
     91 	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
     92 
     93 static int
     94 cdnsiic_init(struct cdnsiic_softc *sc)
     95 {
     96 	int diva, divb;
     97 	int diff, calc_bus_freq;
     98 	int best_diva, best_divb, best_diff;
     99 	u_int pclk;
    100 
    101 	/*
    102 	 * SCL frequency is calculated by the following formula:
    103 	 *
    104 	 * SCL Divisor = 22 * (divisor_a + 1) * (divisor_b + 1)
    105 	 * SCL = PCLK / SCLK Divisor
    106 	 */
    107 
    108 	pclk = clk_get_rate(sc->sc_pclk);
    109 	best_diff = sc->sc_bus_freq;
    110 	best_diva = best_divb = 0;
    111 
    112 	for (diva = 0; diva <= 0x3; diva++) {
    113 		divb = howmany(pclk, 22 * sc->sc_bus_freq * (diva + 1)) - 1;
    114 		if (divb < 0 || divb > 0x3f) {
    115 			continue;
    116 		}
    117 		calc_bus_freq = pclk / (22 * (diva + 1) * (divb + 1));
    118 		diff = sc->sc_bus_freq - calc_bus_freq;
    119 		if (diff < best_diff) {
    120 			best_diff = diff;
    121 			best_diva = diva;
    122 			best_divb = divb;
    123 		}
    124 	}
    125 	if (best_diff == sc->sc_bus_freq) {
    126 		return ENXIO;
    127 	}
    128 
    129 	WR4(sc, CR_REG,
    130 	    __SHIFTIN(best_diva, CR_DIV_A) |
    131 	    __SHIFTIN(best_divb, CR_DIV_B) |
    132 	    CR_CLR_FIFO |
    133 	    CR_ACKEN |
    134 	    CR_NEA |
    135 	    CR_MS);
    136 	WR4(sc, TIME_OUT_REG, 0xff);
    137 
    138 	return 0;
    139 }
    140 
    141 static int
    142 cdnsiic_poll_fifo(struct cdnsiic_softc *sc, uint32_t sr_mask, uint32_t sr_maskval)
    143 {
    144 	uint32_t sr_val, isr_val;
    145 	int retry = POLL_TIMEOUT;
    146 
    147 	while (--retry > 0) {
    148 		sr_val = RD4(sc, SR_REG);
    149 		isr_val = RD4(sc, ISR_REG);
    150 		if ((isr_val & ISR_ERROR_MASK) != 0) {
    151 			return EIO;
    152 		}
    153 		if ((sr_val & sr_mask) == sr_maskval) {
    154 			return 0;
    155 		}
    156 		delay(1);
    157 	}
    158 
    159 	return ETIMEDOUT;
    160 }
    161 
    162 static int
    163 cdnsiic_poll_transfer_complete(struct cdnsiic_softc *sc)
    164 {
    165 	uint32_t val;
    166 	int retry = POLL_TIMEOUT;
    167 
    168 	while (--retry > 0) {
    169 		val = RD4(sc, ISR_REG);
    170 		if ((val & ISR_COMP) != 0) {
    171 			return 0;
    172 		}
    173 		delay(1);
    174 	}
    175 
    176 	return ETIMEDOUT;
    177 }
    178 
    179 static int
    180 cdnsiic_write(struct cdnsiic_softc *sc, i2c_addr_t addr,
    181     const uint8_t *data, size_t datalen, bool send_stop)
    182 {
    183 	uint32_t val;
    184 	u_int xferlen, fifo_space, n;
    185 	bool write_addr = true;
    186 	int error;
    187 
    188 	if (datalen == 0 || datalen > 256) {
    189 		return EINVAL;
    190 	}
    191 
    192 	val = RD4(sc, CR_REG);
    193 	val |= CR_CLR_FIFO;
    194 	val &= ~CR_RD_WR;
    195 	WR4(sc, CR_REG, val);
    196 	WR4(sc, ISR_REG, RD4(sc, ISR_REG));
    197 
    198 	while (datalen > 0) {
    199 		fifo_space = FIFO_DEPTH - RD4(sc, TRANS_SIZE_REG);
    200 		xferlen = uimin(datalen, fifo_space);
    201 		for (n = 0; n < xferlen; n++, data++) {
    202 			WR4(sc, DATA_REG, *data);
    203 		}
    204 		if (write_addr) {
    205 			WR4(sc, ADDR_REG, addr);
    206 			write_addr = false;
    207 		}
    208 		datalen -= xferlen;
    209 		error = cdnsiic_poll_fifo(sc, SR_TXDV, 0);
    210 		if (error != 0) {
    211 			return error;
    212 		}
    213 	}
    214 
    215 	return cdnsiic_poll_transfer_complete(sc);
    216 }
    217 
    218 static int
    219 cdnsiic_read(struct cdnsiic_softc *sc, i2c_addr_t addr,
    220     uint8_t *data, size_t datalen)
    221 {
    222 	uint32_t val;
    223 	int error;
    224 
    225 	if (datalen == 0 || datalen > 255) {
    226 		return EINVAL;
    227 	}
    228 
    229 	val = RD4(sc, CR_REG);
    230 	val |= CR_CLR_FIFO | CR_RD_WR;
    231 	WR4(sc, CR_REG, val);
    232 	WR4(sc, ISR_REG, RD4(sc, ISR_REG));
    233 	WR4(sc, TRANS_SIZE_REG, datalen);
    234 	WR4(sc, ADDR_REG, addr);
    235 
    236 	while (datalen > 0) {
    237 		error = cdnsiic_poll_fifo(sc, SR_RXDV, SR_RXDV);
    238 		if (error != 0) {
    239 			return error;
    240 		}
    241 		*data = RD4(sc, DATA_REG) & 0xff;
    242 		data++;
    243 		datalen--;
    244 	}
    245 
    246 	return cdnsiic_poll_transfer_complete(sc);
    247 }
    248 
    249 static int
    250 cdnsiic_exec(void *priv, i2c_op_t op, i2c_addr_t addr,
    251     const void *cmdbuf, size_t cmdlen, void *buf, size_t buflen, int flags)
    252 {
    253 	struct cdnsiic_softc * const sc = priv;
    254 	uint32_t val;
    255 	int error;
    256 
    257 	val = RD4(sc, CR_REG);
    258 	WR4(sc, CR_REG, val | CR_HOLD);
    259 
    260 	if (cmdlen > 0) {
    261 		error = cdnsiic_write(sc, addr, cmdbuf, cmdlen, false);
    262 		if (error != 0) {
    263 			goto done;
    264 		}
    265 	}
    266 	if (I2C_OP_READ_P(op)) {
    267 		error = cdnsiic_read(sc, addr, buf, buflen);
    268 	} else {
    269 		error = cdnsiic_write(sc, addr, buf, buflen, true);
    270 	}
    271 
    272 done:
    273 	val = RD4(sc, CR_REG);
    274 	WR4(sc, CR_REG, val & ~CR_HOLD);
    275 
    276 	return error;
    277 }
    278 
    279 int
    280 cdnsiic_attach(struct cdnsiic_softc *sc)
    281 {
    282 	int error;
    283 
    284 	aprint_naive("\n");
    285 	aprint_normal(": Cadence I2C (%u Hz)\n", sc->sc_bus_freq);
    286 
    287 	error = cdnsiic_init(sc);
    288 	if (error != 0) {
    289 		return error;
    290 	}
    291 
    292 	iic_tag_init(&sc->sc_ic);
    293 	sc->sc_ic.ic_cookie = sc;
    294 	sc->sc_ic.ic_exec = cdnsiic_exec;
    295 
    296 	return 0;
    297 }
    298