1/* $OpenBSD: aplspmi.c,v 1.2 2022/04/06 18:59:26 naddy Exp $ */ 2/* 3 * Copyright (c) 2021 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#include <machine/fdt.h> 24 25#include <dev/ofw/openfirm.h> 26#include <dev/ofw/fdt.h> 27 28#include <dev/fdt/spmivar.h> 29 30/* 31 * This driver is based on preliminary device tree bindings and will 32 * almost certainly need changes once the official bindings land in 33 * mainline Linux. Support for these preliminary bindings will be 34 * dropped as soon as official bindings are available. 35 */ 36 37#define SPMI_STAT 0x00 38#define SPMI_STAT_RXEMPTY (1 << 24) 39#define SPMI_STAT_TXEMPTY (1 << 8) 40#define SPMI_CMD 0x04 41#define SPMI_CMD_ADDR(x) ((x) << 16) 42#define SPMI_CMD_LAST (1 << 15) 43#define SPMI_CMD_SID(x) ((x) << 8) 44#define SPMI_RESP 0x08 45#define SPMI_INTEN(i) (0x20 + (i) * 4) 46#define SPMI_INTSTAT(i) (0x60 + (i) * 4) 47 48#define HREAD4(sc, reg) \ 49 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) 50#define HWRITE4(sc, reg, val) \ 51 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 52 53struct aplspmi_softc { 54 struct device sc_dev; 55 bus_space_tag_t sc_iot; 56 bus_space_handle_t sc_ioh; 57 58 struct spmi_controller sc_tag; 59}; 60 61int aplspmi_match(struct device *, void *, void *); 62void aplspmi_attach(struct device *, struct device *, void *); 63 64const struct cfattach aplspmi_ca = { 65 sizeof (struct aplspmi_softc), aplspmi_match, aplspmi_attach 66}; 67 68struct cfdriver aplspmi_cd = { 69 NULL, "aplspmi", DV_DULL 70}; 71 72int aplspmi_print(void *, const char *); 73int aplspmi_cmd_read(void *, uint8_t, uint8_t, uint16_t, void *, size_t); 74int aplspmi_cmd_write(void *, uint8_t, uint8_t, uint16_t, 75 const void *, size_t); 76 77int 78aplspmi_match(struct device *parent, void *match, void *aux) 79{ 80 struct fdt_attach_args *faa = aux; 81 82 return OF_is_compatible(faa->fa_node, "apple,spmi"); 83} 84 85void 86aplspmi_attach(struct device *parent, struct device *self, void *aux) 87{ 88 struct aplspmi_softc *sc = (struct aplspmi_softc *)self; 89 struct fdt_attach_args *faa = aux; 90 struct spmi_attach_args sa; 91 char name[32]; 92 uint32_t reg[2]; 93 int node; 94 95 if (faa->fa_nreg < 1) { 96 printf(": no registers\n"); 97 return; 98 } 99 100 sc->sc_iot = faa->fa_iot; 101 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 102 faa->fa_reg[0].size, 0, &sc->sc_ioh)) { 103 printf(": can't map registers\n"); 104 return; 105 } 106 107 printf("\n"); 108 109 sc->sc_tag.sc_cookie = sc; 110 sc->sc_tag.sc_cmd_read = aplspmi_cmd_read; 111 sc->sc_tag.sc_cmd_write = aplspmi_cmd_write; 112 113 for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) { 114 if (OF_getpropintarray(node, "reg", reg, 115 sizeof(reg)) != sizeof(reg)) 116 continue; 117 118 memset(name, 0, sizeof(name)); 119 if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) 120 continue; 121 if (name[0] == '\0') 122 continue; 123 124 memset(&sa, 0, sizeof(sa)); 125 sa.sa_tag = &sc->sc_tag; 126 sa.sa_sid = reg[0]; 127 sa.sa_name = name; 128 sa.sa_node = node; 129 config_found(self, &sa, aplspmi_print); 130 } 131} 132 133int 134aplspmi_print(void *aux, const char *pnp) 135{ 136 struct spmi_attach_args *sa = aux; 137 138 if (pnp != NULL) 139 printf("\"%s\" at %s", sa->sa_name, pnp); 140 printf(" sid 0x%x", sa->sa_sid); 141 142 return UNCONF; 143} 144 145int 146aplspmi_read_resp(struct aplspmi_softc *sc, uint32_t *resp) 147{ 148 int retry; 149 150 for (retry = 1000; retry > 0; retry--) { 151 if ((HREAD4(sc, SPMI_STAT) & SPMI_STAT_RXEMPTY) == 0) 152 break; 153 delay(1); 154 } 155 if (retry == 0) 156 return ETIMEDOUT; 157 158 *resp = HREAD4(sc, SPMI_RESP); 159 return 0; 160} 161 162int 163aplspmi_cmd_read(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr, 164 void *buf, size_t len) 165{ 166 struct aplspmi_softc *sc = cookie; 167 uint8_t *cbuf = buf; 168 uint32_t resp; 169 int error; 170 171 if (len == 0 || len > 8) 172 return EINVAL; 173 174 HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) | 175 (len - 1) | SPMI_CMD_LAST); 176 177 error = aplspmi_read_resp(sc, &resp); 178 if (error) 179 return error; 180 181 while (len > 0) { 182 error = aplspmi_read_resp(sc, &resp); 183 if (error) 184 return error; 185 memcpy(cbuf, &resp, MIN(len, 4)); 186 cbuf += MIN(len, 4); 187 len -= MIN(len, 4); 188 } 189 190 return 0; 191} 192 193int 194aplspmi_cmd_write(void *cookie, uint8_t sid, uint8_t cmd, uint16_t addr, 195 const void *buf, size_t len) 196{ 197 struct aplspmi_softc *sc = cookie; 198 const uint8_t *cbuf = buf; 199 uint32_t data, resp; 200 201 if (len == 0 || len > 8) 202 return EINVAL; 203 204 HWRITE4(sc, SPMI_CMD, SPMI_CMD_SID(sid) | cmd | SPMI_CMD_ADDR(addr) | 205 (len - 1) | SPMI_CMD_LAST); 206 207 while (len > 0) { 208 memcpy(&data, cbuf, MIN(len, 4)); 209 HWRITE4(sc, SPMI_CMD, data); 210 cbuf += MIN(len, 4); 211 len -= MIN(len, 4); 212 } 213 214 return aplspmi_read_resp(sc, &resp); 215} 216