1/*	$OpenBSD: ipmi_i2c.c,v 1.4 2022/04/06 18:59:28 naddy Exp $	*/
2/*
3 * Copyright (c) 2019 Mark Kettenis <kettenis@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21
22#include <machine/bus.h>
23
24#include <dev/i2c/i2cvar.h>
25#include <dev/ipmivar.h>
26
27#define BMC_SA			0x20	/* BMC/ESM3 */
28#define BMC_LUN			0
29
30struct ipmi_i2c_softc {
31	struct ipmi_softc sc;
32	i2c_tag_t	sc_tag;
33	i2c_addr_t	sc_addr;
34	uint8_t		sc_rev;
35};
36
37void	cmn_buildmsg(struct ipmi_cmd *);
38int	ssif_sendmsg(struct ipmi_cmd *);
39int	ssif_recvmsg(struct ipmi_cmd *);
40int	ssif_reset(struct ipmi_softc *);
41int	ssif_probe(struct ipmi_softc *);
42
43struct ipmi_if ssif_if = {
44	"SSIF",
45	0,
46	cmn_buildmsg,
47	ssif_sendmsg,
48	ssif_recvmsg,
49	ssif_reset,
50	ssif_probe,
51	IPMI_MSG_DATASND,
52	IPMI_MSG_DATARCV
53};
54
55extern void ipmi_attach(struct device *, struct device *, void *);
56
57int	ipmi_i2c_match(struct device *, void *, void *);
58void	ipmi_i2c_attach(struct device *, struct device *, void *);
59
60const struct cfattach ipmi_i2c_ca = {
61	sizeof(struct ipmi_i2c_softc), ipmi_i2c_match, ipmi_i2c_attach
62};
63
64int	ipmi_i2c_get_interface_caps(struct ipmi_i2c_softc *);
65int	ipmi_i2c_get_device_id(struct ipmi_i2c_softc *);
66
67int
68ipmi_i2c_match(struct device *parent, void *match, void *aux)
69{
70	struct i2c_attach_args *ia = aux;
71
72	return (strcmp(ia->ia_name, "IPI0001") == 0 ||
73	    strcmp(ia->ia_name, "APMC0D8A") == 0);
74}
75
76void
77ipmi_i2c_attach(struct device *parent, struct device *self, void *aux)
78{
79	struct ipmi_i2c_softc *sc = (struct ipmi_i2c_softc *)self;
80	struct i2c_attach_args *ia = aux;
81	struct ipmi_attach_args iaa;
82
83	sc->sc_tag = ia->ia_tag;
84	sc->sc_addr = ia->ia_addr;
85	sc->sc.sc_if = &ssif_if;
86
87	if (ipmi_i2c_get_interface_caps(sc)) {
88		printf(": can't get system interface capabilities\n");
89		return;
90	}
91
92	if (ipmi_i2c_get_device_id(sc)) {
93		printf(": can't get system interface capabilities\n");
94		return;
95	}
96
97	memset(&iaa, 0, sizeof(iaa));
98	iaa.iaa_if_type = IPMI_IF_SSIF;
99	iaa.iaa_if_rev = (sc->sc_rev >> 4 | sc->sc_rev << 4);
100	iaa.iaa_if_irq = -1;
101	ipmi_attach_common(&sc->sc, &iaa);
102}
103
104int
105ipmi_i2c_get_interface_caps(struct ipmi_i2c_softc *sc)
106{
107	struct ipmi_cmd c;
108	uint8_t data[5];
109
110	data[0] = 0;		/* SSIF */
111
112	c.c_sc = &sc->sc;
113	c.c_rssa = BMC_SA;
114	c.c_rslun = BMC_LUN;
115	c.c_netfn = APP_NETFN;
116	c.c_cmd = APP_GET_SYSTEM_INTERFACE_CAPS;
117	c.c_txlen = 1;
118	c.c_rxlen = 0;
119	c.c_maxrxlen = sizeof(data);
120	c.c_data = data;
121	if (ipmi_sendcmd(&c) || ipmi_recvcmd(&c))
122		return EIO;
123
124	/* Check SSIF version number. */
125	if ((data[1] & 0x7) != 0)
126		return EINVAL;
127	/* Check input and output message sizes. */
128	if (data[2] < 32 || data[3] < 32)
129		return EINVAL;
130
131	return 0;
132}
133
134int
135ipmi_i2c_get_device_id(struct ipmi_i2c_softc *sc)
136{
137	struct ipmi_cmd c;
138	uint8_t data[16];
139
140	c.c_sc = &sc->sc;
141	c.c_rssa = BMC_SA;
142	c.c_rslun = BMC_LUN;
143	c.c_netfn = APP_NETFN;
144	c.c_cmd = APP_GET_DEVICE_ID;
145	c.c_txlen = 0;
146	c.c_rxlen = 0;
147	c.c_maxrxlen = sizeof(data);
148	c.c_data = data;
149	if (ipmi_sendcmd(&c) || ipmi_recvcmd(&c))
150		return EIO;
151
152	sc->sc_rev = data[4];
153	return 0;
154}
155
156int
157ssif_sendmsg(struct ipmi_cmd *c)
158{
159	struct ipmi_i2c_softc *sc = (struct ipmi_i2c_softc *)c->c_sc;
160	uint8_t *buf = sc->sc.sc_buf;
161	uint8_t cmd[2];
162	int error, retry;
163
164	/* We only support single-part writes. */
165	if (c->c_txlen > 32)
166		return -1;
167
168	iic_acquire_bus(sc->sc_tag, 0);
169
170	cmd[0] = 2;
171	cmd[1] = c->c_txlen;
172	for (retry = 0; retry < 5; retry++) {
173		error = iic_exec(sc->sc_tag, I2C_OP_WRITE_BLOCK,
174		    sc->sc_addr, cmd, sizeof(cmd), buf, c->c_txlen, 0);
175		if (!error)
176			break;
177	}
178
179	iic_release_bus(sc->sc_tag, 0);
180
181	return (error ? -1 : 0);
182}
183
184int
185ssif_recvmsg(struct ipmi_cmd *c)
186{
187	struct ipmi_i2c_softc *sc = (struct ipmi_i2c_softc *)c->c_sc;
188	uint8_t buf[33];
189	uint8_t cmd[1];
190	uint8_t len;
191	int error, retry;
192	int blkno = 0;
193
194	iic_acquire_bus(sc->sc_tag, 0);
195
196	cmd[0] = 3;
197	for (retry = 0; retry < 250; retry++) {
198		memset(buf, 0, sizeof(buf));
199		error = iic_exec(sc->sc_tag, I2C_OP_READ_BLOCK,
200		    sc->sc_addr, cmd, sizeof(cmd), buf, sizeof(buf), 0);
201		if (error)
202			continue;
203
204		if (buf[0] < 1 || buf[0] > 32) {
205			error = EIO;
206			goto release;
207		}
208
209		if (buf[0] == 32 && buf[1] == 0x00 && buf[2] == 0x01) {
210			/* Multi-part read start. */
211			c->c_rxlen = MIN(30, c->c_maxrxlen);
212			memcpy(sc->sc.sc_buf, &buf[3], c->c_rxlen);
213			break;
214		} else {
215			/* Single-part read. */
216			c->c_rxlen = MIN(buf[0], c->c_maxrxlen);
217			memcpy(sc->sc.sc_buf, &buf[1], c->c_rxlen);
218			goto release;
219		}
220	}
221	if (retry == 250)
222		goto release;
223
224	cmd[0] = 9;
225	while (buf[1] != 0xff && c->c_rxlen < c->c_maxrxlen) {
226		memset(buf, 0, sizeof(buf));
227		error = iic_exec(sc->sc_tag, I2C_OP_READ_BLOCK,
228		    sc->sc_addr, cmd, sizeof(cmd), buf, sizeof(buf), 0);
229		if (error)
230			goto release;
231
232		if (buf[0] < 1 || buf[0] > 32) {
233			error = EIO;
234			goto release;
235		}
236
237		if (buf[0] == 32 && buf[1] == blkno) {
238			/* Multi-part read middle. */
239			len = MIN(31, c->c_maxrxlen - c->c_rxlen);
240			memcpy(&sc->sc.sc_buf[c->c_rxlen], &buf[2], len);
241		} else if (buf[1] == 0xff) {
242			/* Multi-part read end. */
243			len = MIN(buf[0] - 1, c->c_maxrxlen - c->c_rxlen);
244			memcpy(&sc->sc.sc_buf[c->c_rxlen], &buf[2], len);
245		} else {
246			error = EIO;
247			goto release;
248		}
249		c->c_rxlen += len;
250		blkno++;
251	}
252
253release:
254	iic_release_bus(sc->sc_tag, 0);
255
256	return (error ? -1 : 0);
257}
258
259int
260ssif_reset(struct ipmi_softc *sc)
261{
262	return -1;
263}
264
265int
266ssif_probe(struct ipmi_softc *sc)
267{
268	return 0;
269}
270